From 6a461143a47923558423b95f0df1c3c8aa9c8b18 Mon Sep 17 00:00:00 2001 From: maelstrom Date: Wed, 23 Apr 2025 17:39:30 +0200 Subject: [PATCH] refactor(joint): abstracted jointinstance into its own class away from snap --- core/src/objects/base/instance.cpp | 6 + core/src/objects/base/instance.h | 7 +- core/src/objects/joint/jointinstance.cpp | 94 +++++++++++++ .../objects/{snap.h => joint/jointinstance.h} | 19 ++- core/src/objects/joint/snap.cpp | 59 +++++++++ core/src/objects/joint/snap.h | 25 ++++ core/src/objects/jointsservice.h | 3 - core/src/objects/meta.cpp | 4 +- core/src/objects/part.cpp | 11 +- core/src/objects/part.h | 10 +- core/src/objects/snap.cpp | 123 ------------------ core/src/objects/workspace.cpp | 14 +- editor/mainwindow.cpp | 2 +- 13 files changed, 219 insertions(+), 158 deletions(-) create mode 100644 core/src/objects/joint/jointinstance.cpp rename core/src/objects/{snap.h => joint/jointinstance.h} (68%) create mode 100644 core/src/objects/joint/snap.cpp create mode 100644 core/src/objects/joint/snap.h delete mode 100644 core/src/objects/snap.cpp diff --git a/core/src/objects/base/instance.cpp b/core/src/objects/base/instance.cpp index 6e8754e..dbe5460 100644 --- a/core/src/objects/base/instance.cpp +++ b/core/src/objects/base/instance.cpp @@ -148,6 +148,12 @@ void Instance::Destroy() { parentLocked = true; } +bool Instance::IsA(std::string className) { + const InstanceType* cur = GetClass(); + while (cur && cur->className != className) { cur = cur->super; } + return cur != nullptr; +} + static std::shared_ptr DUMMY_INSTANCE; DescendantsIterator Instance::GetDescendantsStart() { return DescendantsIterator(GetChildren().size() > 0 ? GetChildren()[0] : DUMMY_INSTANCE); diff --git a/core/src/objects/base/instance.h b/core/src/objects/base/instance.h index 038c149..5177126 100644 --- a/core/src/objects/base/instance.h +++ b/core/src/objects/base/instance.h @@ -41,7 +41,7 @@ struct InstanceType { typedef std::pair, std::string> _RefStatePropertyCell; class DescendantsIterator; -class Snap; +class JointInstance; // Base class for all instances in the data model // Note: enable_shared_from_this HAS to be public or else its field will not be populated @@ -60,7 +60,7 @@ private: bool ancestryContinuityCheck(std::optional> newParent); void updateAncestry(std::optional> child, std::optional> newParent); - friend Snap; // This isn't ideal, but oh well + friend JointInstance; // This isn't ideal, but oh well protected: bool parentLocked = false; std::unique_ptr memberMap; @@ -92,6 +92,9 @@ public: bool IsParentLocked(); inline const std::vector> GetChildren() { return children; } void Destroy(); + // Determines whether this object is an instance of, or an instance of a subclass of the sepcified type's class name + bool IsA(std::string className); + template bool IsA() { return IsA(T::TYPE.className); } DescendantsIterator GetDescendantsStart(); DescendantsIterator GetDescendantsEnd(); diff --git a/core/src/objects/joint/jointinstance.cpp b/core/src/objects/joint/jointinstance.cpp new file mode 100644 index 0000000..c3c3904 --- /dev/null +++ b/core/src/objects/joint/jointinstance.cpp @@ -0,0 +1,94 @@ +#include "jointinstance.h" + +#include "datatypes/cframe.h" +#include "datatypes/ref.h" +#include "objects/datamodel.h" +#include "objects/jointsservice.h" +#include "objects/part.h" +#include "objects/workspace.h" +#include +#include +#include +#include "ptr_helpers.h" + +const InstanceType JointInstance::TYPE = { + .super = &Instance::TYPE, + .className = "JointInstance", +}; + +const InstanceType* JointInstance::GetClass() { + return &TYPE; +} + +JointInstance::JointInstance(const InstanceType* type): Instance(type) { + this->memberMap = std::make_unique(MemberMap { + .super = std::move(this->memberMap), + .members = { + { "Part0", { + .backingField = &part0, + .type = &Data::InstanceRef::TYPE, + .codec = fieldCodecOf>(), + .updateCallback = memberFunctionOf(&JointInstance::onUpdated, this), + }}, { "Part1", { + .backingField = &part1, + .type = &Data::InstanceRef::TYPE, + .codec = fieldCodecOf>(), + .updateCallback = memberFunctionOf(&JointInstance::onUpdated, this), + }}, { "C0", { + .backingField = &c0, + .type = &Data::CFrame::TYPE, + .codec = fieldCodecOf(), + .updateCallback = memberFunctionOf(&JointInstance::onUpdated, this), + }}, { "C1", { + .backingField = &c1, + .type = &Data::CFrame::TYPE, + .codec = fieldCodecOf(), + .updateCallback = memberFunctionOf(&JointInstance::onUpdated, this), + }}, + } + }); +} + +JointInstance::~JointInstance() { +} + +void JointInstance::OnAncestryChanged(std::optional>, std::optional>) { + // Destroy and rebuild the joint, it's the simplest solution that actually works + + breakJoint(); + buildJoint(); +} + +void JointInstance::onUpdated(std::string property) { + // Add ourselves to the attached parts, or remove, if applicable + + // Parts differ, delete + if (part0 != oldPart0 && !oldPart0.expired()) { + oldPart0.lock()->untrackJoint(shared()); + } + + if (part1 != oldPart1 && !oldPart1.expired()) { + oldPart1.lock()->untrackJoint(shared()); + } + + // Parts differ, add + if (part0 != oldPart0 && !part0.expired()) { + part0.lock()->trackJoint(shared()); + } + + if (part1 != oldPart1 && !part1.expired()) { + part1.lock()->trackJoint(shared()); + } + + // Destroy and rebuild the joint, if applicable + + breakJoint(); + buildJoint(); + + oldPart0 = part0; + oldPart1 = part1; +} + +std::optional> JointInstance::workspaceOfPart(std::shared_ptr part) { + return part->workspace(); +} \ No newline at end of file diff --git a/core/src/objects/snap.h b/core/src/objects/joint/jointinstance.h similarity index 68% rename from core/src/objects/snap.h rename to core/src/objects/joint/jointinstance.h index f61b10e..79efffa 100644 --- a/core/src/objects/snap.h +++ b/core/src/objects/joint/jointinstance.h @@ -7,20 +7,19 @@ class Part; class Workspace; -class Snap : public Instance { - rp::FixedJoint* joint = nullptr; - +class JointInstance : public Instance { std::weak_ptr oldPart0; std::weak_ptr oldPart1; - +protected: // The workspace the joint was created in, if it exists std::weak_ptr jointWorkspace; -protected: + void OnAncestryChanged(std::optional>, std::optional>) override; + std::optional> workspaceOfPart(std::shared_ptr); void onUpdated(std::string property); - void buildJoint(); - void breakJoint(); + virtual void buildJoint() = 0; + virtual void breakJoint() = 0; public: const static InstanceType TYPE; @@ -29,10 +28,8 @@ public: Data::CFrame c0; Data::CFrame c1; - Snap(); - ~Snap(); + JointInstance(const InstanceType*); + ~JointInstance(); - static inline std::shared_ptr New() { return std::make_shared(); }; - static inline std::shared_ptr Create() { return std::make_shared(); }; virtual const InstanceType* GetClass() override; }; \ No newline at end of file diff --git a/core/src/objects/joint/snap.cpp b/core/src/objects/joint/snap.cpp new file mode 100644 index 0000000..4b80593 --- /dev/null +++ b/core/src/objects/joint/snap.cpp @@ -0,0 +1,59 @@ +#include "snap.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 +#include +#include + +const InstanceType Snap::TYPE = { + .super = &JointInstance::TYPE, + .className = "Snap", + .constructor = &Snap::Create, +}; + +const InstanceType* Snap::GetClass() { + return &TYPE; +} + +Snap::Snap(): JointInstance(&TYPE) { +} + +Snap::~Snap() { +} + +void Snap::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 = 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(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 Snap::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; +} \ No newline at end of file diff --git a/core/src/objects/joint/snap.h b/core/src/objects/joint/snap.h new file mode 100644 index 0000000..174cc9e --- /dev/null +++ b/core/src/objects/joint/snap.h @@ -0,0 +1,25 @@ +#pragma once + +#include "objects/base/instance.h" +#include "objects/joint/jointinstance.h" +#include +#include + +class Part; +class Workspace; + +class Snap : public JointInstance { + rp::FixedJoint* joint = nullptr; + + virtual void buildJoint() override; + virtual void breakJoint() override; +public: + const static InstanceType TYPE; + + Snap(); + ~Snap(); + + static inline std::shared_ptr New() { return std::make_shared(); }; + static inline std::shared_ptr Create() { return std::make_shared(); }; + virtual const InstanceType* GetClass() override; +}; \ No newline at end of file diff --git a/core/src/objects/jointsservice.h b/core/src/objects/jointsservice.h index 577f245..b57e121 100644 --- a/core/src/objects/jointsservice.h +++ b/core/src/objects/jointsservice.h @@ -2,12 +2,9 @@ #include "objects/base/service.h" -class Snap; class JointsService : public Service { private: std::optional> jointWorkspace(); - - friend Snap; protected: void InitService() override; bool initialized = false; diff --git a/core/src/objects/meta.cpp b/core/src/objects/meta.cpp index 908a78c..2a170b5 100644 --- a/core/src/objects/meta.cpp +++ b/core/src/objects/meta.cpp @@ -1,7 +1,8 @@ #include "meta.h" +#include "objects/joint/jointinstance.h" #include "objects/jointsservice.h" #include "objects/part.h" -#include "objects/snap.h" +#include "objects/joint/snap.h" #include "objects/workspace.h" std::map INSTANCE_MAP = { @@ -10,5 +11,6 @@ std::map INSTANCE_MAP = { { "Workspace", &Workspace::TYPE }, { "DataModel", &DataModel::TYPE }, { "Snap", &Snap::TYPE }, + { "JointInstance", &JointInstance::TYPE }, { "JointsService", &JointsService::TYPE }, }; \ No newline at end of file diff --git a/core/src/objects/part.cpp b/core/src/objects/part.cpp index d8921ce..79f1205 100644 --- a/core/src/objects/part.cpp +++ b/core/src/objects/part.cpp @@ -7,7 +7,8 @@ #include "datatypes/vector.h" #include "objects/base/member.h" #include "objects/jointsservice.h" -#include "objects/snap.h" +#include "objects/joint/jointinstance.h" +#include "objects/joint/snap.h" #include "rendering/surface.h" #include #include @@ -227,12 +228,12 @@ Vector3 Part::GetAABB() { } void Part::BreakJoints() { - for (std::weak_ptr joint : primaryJoints) { + for (std::weak_ptr joint : primaryJoints) { if (joint.expired()) continue; joint.lock()->Destroy(); } - for (std::weak_ptr joint : secondaryJoints) { + for (std::weak_ptr joint : secondaryJoints) { if (joint.expired()) continue; joint.lock()->Destroy(); } @@ -335,7 +336,7 @@ void Part::MakeJoints() { } } -void Part::trackJoint(std::shared_ptr joint) { +void Part::trackJoint(std::shared_ptr joint) { if (!joint->part0.expired() && joint->part0.lock() == shared_from_this()) { for (auto it = primaryJoints.begin(); it != primaryJoints.end();) { // Clean expired refs @@ -369,7 +370,7 @@ void Part::trackJoint(std::shared_ptr joint) { } } -void Part::untrackJoint(std::shared_ptr joint) { +void Part::untrackJoint(std::shared_ptr joint) { for (auto it = primaryJoints.begin(); it != primaryJoints.end();) { // Clean expired refs if (it->expired() || it->lock() == joint) { diff --git a/core/src/objects/part.h b/core/src/objects/part.h index 8f5ecd0..c780f58 100644 --- a/core/src/objects/part.h +++ b/core/src/objects/part.h @@ -29,17 +29,17 @@ class Snap; class Part : public Instance { protected: // Joints where this part is Part0 - std::vector> primaryJoints; + std::vector> primaryJoints; // Joints where this part is Part1 - std::vector> secondaryJoints; + std::vector> secondaryJoints; - void trackJoint(std::shared_ptr); - void untrackJoint(std::shared_ptr); + void trackJoint(std::shared_ptr); + void untrackJoint(std::shared_ptr); SurfaceType surfaceFromFace(NormalId); bool checkJointContinuinty(std::shared_ptr); - friend Snap; + friend JointInstance; void OnAncestryChanged(std::optional> child, std::optional> newParent) override; void onUpdated(std::string); diff --git a/core/src/objects/snap.cpp b/core/src/objects/snap.cpp deleted file mode 100644 index ab75b2b..0000000 --- a/core/src/objects/snap.cpp +++ /dev/null @@ -1,123 +0,0 @@ -#include "snap.h" - -#include "datatypes/cframe.h" -#include "datatypes/ref.h" -#include "objects/datamodel.h" -#include "objects/jointsservice.h" -#include "objects/part.h" -#include "workspace.h" -#include -#include -#include -#include "ptr_helpers.h" - -const InstanceType Snap::TYPE = { - .super = &Instance::TYPE, - .className = "Snap", - .constructor = &Snap::Create, -}; - -const InstanceType* Snap::GetClass() { - return &TYPE; -} - -Snap::Snap(): Instance(&TYPE) { - this->memberMap = std::make_unique(MemberMap { - .super = std::move(this->memberMap), - .members = { - { "Part0", { - .backingField = &part0, - .type = &Data::InstanceRef::TYPE, - .codec = fieldCodecOf>(), - .updateCallback = memberFunctionOf(&Snap::onUpdated, this), - }}, { "Part1", { - .backingField = &part1, - .type = &Data::InstanceRef::TYPE, - .codec = fieldCodecOf>(), - .updateCallback = memberFunctionOf(&Snap::onUpdated, this), - }}, { "C0", { - .backingField = &c0, - .type = &Data::CFrame::TYPE, - .codec = fieldCodecOf(), - .updateCallback = memberFunctionOf(&Snap::onUpdated, this), - }}, { "C1", { - .backingField = &c1, - .type = &Data::CFrame::TYPE, - .codec = fieldCodecOf(), - .updateCallback = memberFunctionOf(&Snap::onUpdated, this), - }}, - } - }); -} - -Snap::~Snap() { -} - -void Snap::OnAncestryChanged(std::optional>, std::optional>) { - // Destroy and rebuild the joint, it's the simplest solution that actually works - - breakJoint(); - buildJoint(); -} - -void Snap::onUpdated(std::string property) { - // Add ourselves to the attached parts, or remove, if applicable - - // Parts differ, delete - if (part0 != oldPart0 && !oldPart0.expired()) { - oldPart0.lock()->untrackJoint(shared()); - } - - if (part1 != oldPart1 && !oldPart1.expired()) { - oldPart1.lock()->untrackJoint(shared()); - } - - // Parts differ, add - if (part0 != oldPart0 && !part0.expired()) { - part0.lock()->trackJoint(shared()); - } - - if (part1 != oldPart1 && !part1.expired()) { - part1.lock()->trackJoint(shared()); - } - - // Destroy and rebuild the joint, if applicable - - breakJoint(); - buildJoint(); - - oldPart0 = part0; - oldPart1 = part1; -} - -void Snap::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() || !part0.lock()->workspace() || part0.lock()->workspace() != part1.lock()->workspace()) 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 = part0.lock()->workspace().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(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 Snap::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; -} \ No newline at end of file diff --git a/core/src/objects/workspace.cpp b/core/src/objects/workspace.cpp index 544304d..e6c5ef6 100644 --- a/core/src/objects/workspace.cpp +++ b/core/src/objects/workspace.cpp @@ -1,7 +1,7 @@ #include "workspace.h" #include "objects/base/instance.h" #include "objects/jointsservice.h" -#include "objects/snap.h" +#include "objects/joint/jointinstance.h" #include "physics/util.h" #include @@ -45,8 +45,8 @@ void Workspace::InitService() { // Sync all parts for (auto it = this->GetDescendantsStart(); it != this->GetDescendantsEnd(); it++) { InstanceRef obj = *it; - if (obj->GetClass()->className != "Part") continue; // TODO: Replace this with a .IsA call instead of comparing the class name directly - std::shared_ptr part = std::dynamic_pointer_cast(obj); + if (!obj->IsA()) continue; + std::shared_ptr part = obj->CastTo().expect(); this->SyncPartPhysics(part); part->MakeJoints(); } @@ -54,14 +54,14 @@ void Workspace::InitService() { // Activate all joints for (auto it = this->GetDescendantsStart(); it != this->GetDescendantsEnd(); it++) { InstanceRef obj = *it; - if (obj->GetClass()->className != "Snap") continue; // TODO: Replace this with a .IsA call instead of comparing the class name directly - std::shared_ptr joint = std::dynamic_pointer_cast(obj); + if (!obj->IsA()) continue; + std::shared_ptr joint = obj->CastTo().expect(); joint->UpdateProperty("Part0"); } for (auto obj : dataModel().value()->GetService()->GetChildren()) { - if (obj->GetClass()->className != "Snap") continue; // TODO: Replace this with a .IsA call instead of comparing the class name directly - std::shared_ptr joint = std::dynamic_pointer_cast(obj); + if (!obj->IsA()) continue; + std::shared_ptr joint = obj->CastTo().expect(); joint->UpdateProperty("Part0"); } } diff --git a/editor/mainwindow.cpp b/editor/mainwindow.cpp index 76d48a2..e59fab6 100644 --- a/editor/mainwindow.cpp +++ b/editor/mainwindow.cpp @@ -3,7 +3,7 @@ #include "common.h" #include "logger.h" #include "objects/jointsservice.h" -#include "objects/snap.h" +#include "objects/joint/snap.h" #include #include #include