fix(joint): error in continuity check leading to infinity loop then segfault

This commit is contained in:
maelstrom 2025-04-23 18:08:05 +02:00
parent 6a461143a4
commit d4f7582780
8 changed files with 127 additions and 25 deletions

View file

@ -3,10 +3,6 @@
#include "objects/base/instance.h"
#include "objects/joint/jointinstance.h"
#include <memory>
#include <optional>
class Part;
class Workspace;
class Snap : public JointInstance {
rp::FixedJoint* joint = nullptr;

View file

@ -0,0 +1,59 @@
#include "weld.h"
#include "datatypes/cframe.h"
#include "objects/datamodel.h"
#include "objects/joint/jointinstance.h"
#include "objects/jointsservice.h"
#include "objects/part.h"
#include "objects/workspace.h"
#include <memory>
#include <reactphysics3d/constraint/FixedJoint.h>
#include <reactphysics3d/engine/PhysicsWorld.h>
const InstanceType Weld::TYPE = {
.super = &JointInstance::TYPE,
.className = "Weld",
.constructor = &Weld::Create,
};
const InstanceType* Weld::GetClass() {
return &TYPE;
}
Weld::Weld(): JointInstance(&TYPE) {
}
Weld::~Weld() {
}
void Weld::buildJoint() {
// Only if both parts are set, are not the same part, are part of a workspace, and are part of the same workspace, we build the joint
if (part0.expired() || part1.expired() || part0.lock() == part1.lock() || !workspaceOfPart(part0.lock()) || workspaceOfPart(part0.lock()) != workspaceOfPart(part1.lock())) return;
// Don't build the joint if we're not part of either a workspace or JointsService
if ((!GetParent() || GetParent().value()->GetClass() != &JointsService::TYPE) && !workspace()) return;
std::shared_ptr<Workspace> workspace = workspaceOfPart(part0.lock()).value();
if (!workspace->physicsWorld) return;
// Update Part1's rotation and cframe prior to creating the joint as reactphysics3d locks rotation based on how it
// used to be rather than specifying an anchor rotation, so whatever.
Data::CFrame newFrame = part0.lock()->cframe * (c1.Inverse() * c0);
part1.lock()->cframe = newFrame;
workspace->SyncPartPhysics(part1.lock());
// printf("c1.Rotation: ");
// printVec(c1.ToEulerAnglesXYZ());
rp::FixedJointInfo jointInfo(part0.lock()->rigidBody, part1.lock()->rigidBody, (c0.Inverse() * c1).Position());
this->joint = dynamic_cast<rp::FixedJoint*>(workspace->physicsWorld->createJoint(jointInfo));
jointWorkspace = workspace;
}
// !!! REMINDER: This has to be called manually when parts are destroyed/removed from the workspace, or joints will linger
void Weld::breakJoint() {
// If the joint doesn't exist, or its workspace expired (not our problem anymore), then no need to do anything
if (!this->joint || jointWorkspace.expired() || !jointWorkspace.lock()->physicsWorld) return;
jointWorkspace.lock()->physicsWorld->destroyJoint(this->joint);
this->joint = nullptr;
}

View file

@ -0,0 +1,21 @@
#pragma once
#include "objects/base/instance.h"
#include "objects/joint/jointinstance.h"
#include <memory>
class Weld : public JointInstance {
rp::FixedJoint* joint = nullptr;
virtual void buildJoint() override;
virtual void breakJoint() override;
public:
const static InstanceType TYPE;
Weld();
~Weld();
static inline std::shared_ptr<Weld> New() { return std::make_shared<Weld>(); };
static inline std::shared_ptr<Instance> Create() { return std::make_shared<Weld>(); };
virtual const InstanceType* GetClass() override;
};

View file

@ -6,6 +6,7 @@
#include "datatypes/color3.h"
#include "datatypes/vector.h"
#include "objects/base/member.h"
#include "objects/joint/weld.h"
#include "objects/jointsservice.h"
#include "objects/joint/jointinstance.h"
#include "objects/joint/snap.h"
@ -260,26 +261,41 @@ SurfaceType Part::surfaceFromFace(NormalId face) {
return SurfaceSmooth; // Unreachable
}
bool Part::checkJointContinuinty(std::shared_ptr<Part> otherPart) {
bool Part::checkJointContinuity(std::shared_ptr<Part> otherPart) {
// Make sure that the two parts don't depend on one another
if (shared<Part>() == otherPart) return false;
return checkJointContinuityUp(otherPart) && checkJointContinuityDown(otherPart);
}
bool Part::checkJointContinuityDown(std::shared_ptr<Part> otherPart) {
if (shared<Part>() == otherPart) return false;
for (auto joint : primaryJoints) {
if (joint.expired() || joint.lock()->part1.expired()) continue;
if (!joint.lock()->part1.lock()->checkJointContinuinty(otherPart))
if (!joint.lock()->part1.lock()->checkJointContinuityDown(otherPart))
return false;
}
return true;
}
bool Part::checkJointContinuityUp(std::shared_ptr<Part> otherPart) {
if (shared<Part>() == otherPart) return false;
for (auto joint : secondaryJoints) {
if (joint.expired() || joint.lock()->part0.expired()) continue;
if (!joint.lock()->part0.lock()->checkJointContinuinty(otherPart))
if (!joint.lock()->part0.lock()->checkJointContinuityUp(otherPart))
return false;
}
return true;
}
std::optional<std::shared_ptr<JointInstance>> makeJointFromSurfaces(SurfaceType a, SurfaceType b) {
if (a == SurfaceWeld || b == SurfaceWeld || a == SurfaceGlue || b == SurfaceGlue) return Weld::New();
if ((a == SurfaceStuds && (b == SurfaceInlets || b == SurfaceUniversal))
|| (a == SurfaceInlets && (b == SurfaceStuds || b == SurfaceUniversal))
|| (a == SurfaceUniversal && (b == SurfaceStuds || b == SurfaceInlets || b == SurfaceUniversal)))
return Snap::New();
return std::nullopt;
}
void Part::MakeJoints() {
// Algorithm: Find nearby parts
// Make sure parts are not dependant on each other (via primary/secondaryJoints)
@ -311,26 +327,22 @@ void Part::MakeJoints() {
float dot = myWorldNormal.Dot(otherWorldNormal);
if (dot > -0.99) continue; // Surface is pointing opposite to ours
if (abs(surfacePointLocalToMyFrame.Z()) > 0.05) continue; // Surfaces are within 0.05 studs of one another
if (!checkJointContinuinty(otherPart)) continue;
if (!checkJointContinuity(otherPart)) continue;
SurfaceType mySurface = surfaceFromFace(faceFromNormal(myFace));
SurfaceType otherSurface = surfaceFromFace(faceFromNormal(otherFace));
if (mySurface == SurfaceSmooth) continue; // We're not responsible for any joints
else if (mySurface == SurfaceWeld || mySurface == SurfaceGlue
|| (mySurface == SurfaceStuds && (otherSurface == SurfaceInlets || otherSurface == SurfaceUniversal))
|| (mySurface == SurfaceInlets && (otherSurface == SurfaceStuds || otherSurface == SurfaceUniversal))
|| (mySurface == SurfaceUniversal && (otherSurface == SurfaceStuds || otherSurface == SurfaceInlets || otherSurface == SurfaceUniversal))) { // Always make a weld no matter what the other surface is
std::shared_ptr<Snap> joint = Snap::New();
joint->part0 = shared<Part>();
joint->part1 = otherPart->shared<Part>();
joint->c1 = cframe;
joint->c0 = otherPart->cframe;
dataModel().value()->GetService<JointsService>()->AddChild(joint);
joint->UpdateProperty("Part0");
auto joint_ = makeJointFromSurfaces(mySurface, otherSurface);
if (!joint_) continue;
std::shared_ptr<JointInstance> joint = joint_.value();
joint->part0 = shared<Part>();
joint->part1 = otherPart->shared<Part>();
joint->c1 = cframe;
joint->c0 = otherPart->cframe;
dataModel().value()->GetService<JointsService>()->AddChild(joint);
joint->UpdateProperty("Part0");
Logger::debugf("Made joint between %s and %s!\n", name.c_str(), otherPart->name.c_str());
}
Logger::debugf("Made joint between %s and %s!\n", name.c_str(), otherPart->name.c_str());
}
}
}

View file

@ -8,6 +8,7 @@
#include "datatypes/vector.h"
#include "objects/base/instance.h"
#include "rendering/surface.h"
#include <optional>
#include <reactphysics3d/reactphysics3d.h>
#include <vector>
@ -37,7 +38,9 @@ protected:
void untrackJoint(std::shared_ptr<JointInstance>);
SurfaceType surfaceFromFace(NormalId);
bool checkJointContinuinty(std::shared_ptr<Part>);
bool checkJointContinuity(std::shared_ptr<Part>);
bool checkJointContinuityUp(std::shared_ptr<Part>);
bool checkJointContinuityDown(std::shared_ptr<Part>);
friend JointInstance;

View file

@ -25,6 +25,7 @@ enum FilterResult {
class Part;
class Snap;
class Weld;
typedef std::function<FilterResult(std::shared_ptr<Part>)> RaycastFilter;
@ -34,6 +35,7 @@ class Workspace : public Service {
friend Part;
friend Snap;
friend Weld;
protected:
void InitService() override;
bool initialized = false;

View file

@ -3,6 +3,7 @@
#include <glm/vector_relational.hpp>
#include <qnamespace.h>
#include <qsoundeffect.h>
#include <string>
#include "mainglwidget.h"
#include "common.h"
#include "math_helper.h"
@ -445,6 +446,7 @@ void MainGLWidget::updateCycle() {
}
int partId = 1;
void MainGLWidget::keyPressEvent(QKeyEvent* evt) {
if (evt->key() == Qt::Key_W) moveZ = 1;
else if (evt->key() == Qt::Key_S) moveZ = -1;
@ -460,6 +462,7 @@ void MainGLWidget::keyPressEvent(QKeyEvent* evt) {
.color = glm::vec3(1.0f, 0.5f, 0.31f),
}));
gWorkspace()->SyncPartPhysics(lastPart);
lastPart->name = "Part" + std::to_string(partId++);
}
if (evt->key() == Qt::Key_U)

View file

@ -395,6 +395,12 @@ void MainWindow::connectActionHandlers() {
gDataModel = newModel;
newModel->Init();
ui->explorerView->updateRoot(newModel);
// Reset running state
runState = RUN_STOPPED;
ui->actionRunSimulation->setEnabled(true);
ui->actionPauseSimulation->setEnabled(false);
ui->actionStopSimulation->setEnabled(false);
});
connect(ui->actionDelete, &QAction::triggered, this, [&]() {