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/base/instance.h"
#include "objects/joint/jointinstance.h" #include "objects/joint/jointinstance.h"
#include <memory> #include <memory>
#include <optional>
class Part;
class Workspace;
class Snap : public JointInstance { class Snap : public JointInstance {
rp::FixedJoint* joint = nullptr; 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/color3.h"
#include "datatypes/vector.h" #include "datatypes/vector.h"
#include "objects/base/member.h" #include "objects/base/member.h"
#include "objects/joint/weld.h"
#include "objects/jointsservice.h" #include "objects/jointsservice.h"
#include "objects/joint/jointinstance.h" #include "objects/joint/jointinstance.h"
#include "objects/joint/snap.h" #include "objects/joint/snap.h"
@ -260,26 +261,41 @@ SurfaceType Part::surfaceFromFace(NormalId face) {
return SurfaceSmooth; // Unreachable 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 // 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) { for (auto joint : primaryJoints) {
if (joint.expired() || joint.lock()->part1.expired()) continue; 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 false;
} }
return true;
}
bool Part::checkJointContinuityUp(std::shared_ptr<Part> otherPart) {
if (shared<Part>() == otherPart) return false;
for (auto joint : secondaryJoints) { for (auto joint : secondaryJoints) {
if (joint.expired() || joint.lock()->part0.expired()) continue; 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 false;
} }
return true; 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() { void Part::MakeJoints() {
// Algorithm: Find nearby parts // Algorithm: Find nearby parts
// Make sure parts are not dependant on each other (via primary/secondaryJoints) // Make sure parts are not dependant on each other (via primary/secondaryJoints)
@ -311,26 +327,22 @@ void Part::MakeJoints() {
float dot = myWorldNormal.Dot(otherWorldNormal); float dot = myWorldNormal.Dot(otherWorldNormal);
if (dot > -0.99) continue; // Surface is pointing opposite to ours 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 (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 mySurface = surfaceFromFace(faceFromNormal(myFace));
SurfaceType otherSurface = surfaceFromFace(faceFromNormal(otherFace)); SurfaceType otherSurface = surfaceFromFace(faceFromNormal(otherFace));
if (mySurface == SurfaceSmooth) continue; // We're not responsible for any joints auto joint_ = makeJointFromSurfaces(mySurface, otherSurface);
else if (mySurface == SurfaceWeld || mySurface == SurfaceGlue if (!joint_) continue;
|| (mySurface == SurfaceStuds && (otherSurface == SurfaceInlets || otherSurface == SurfaceUniversal)) std::shared_ptr<JointInstance> joint = joint_.value();
|| (mySurface == SurfaceInlets && (otherSurface == SurfaceStuds || otherSurface == SurfaceUniversal)) joint->part0 = shared<Part>();
|| (mySurface == SurfaceUniversal && (otherSurface == SurfaceStuds || otherSurface == SurfaceInlets || otherSurface == SurfaceUniversal))) { // Always make a weld no matter what the other surface is joint->part1 = otherPart->shared<Part>();
std::shared_ptr<Snap> joint = Snap::New(); joint->c1 = cframe;
joint->part0 = shared<Part>(); joint->c0 = otherPart->cframe;
joint->part1 = otherPart->shared<Part>(); dataModel().value()->GetService<JointsService>()->AddChild(joint);
joint->c1 = cframe; joint->UpdateProperty("Part0");
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 "datatypes/vector.h"
#include "objects/base/instance.h" #include "objects/base/instance.h"
#include "rendering/surface.h" #include "rendering/surface.h"
#include <optional>
#include <reactphysics3d/reactphysics3d.h> #include <reactphysics3d/reactphysics3d.h>
#include <vector> #include <vector>
@ -37,7 +38,9 @@ protected:
void untrackJoint(std::shared_ptr<JointInstance>); void untrackJoint(std::shared_ptr<JointInstance>);
SurfaceType surfaceFromFace(NormalId); 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; friend JointInstance;

View file

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

View file

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

View file

@ -395,6 +395,12 @@ void MainWindow::connectActionHandlers() {
gDataModel = newModel; gDataModel = newModel;
newModel->Init(); newModel->Init();
ui->explorerView->updateRoot(newModel); 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, [&]() { connect(ui->actionDelete, &QAction::triggered, this, [&]() {