Compare commits
9 commits
86c6890ca1
...
df9e285954
Author | SHA1 | Date | |
---|---|---|---|
df9e285954 | |||
f6d778e3ed | |||
722c4acfb4 | |||
e9757ab306 | |||
cab5ec0514 | |||
c4ad7d5620 | |||
be3c7bd6b2 | |||
1617086692 | |||
9e43c63c4e |
17 changed files with 389 additions and 107 deletions
|
@ -6,6 +6,7 @@
|
|||
Camera camera(glm::vec3(0.0, 0.0, 3.0));
|
||||
//std::vector<Part> parts;
|
||||
std::shared_ptr<DataModel> gDataModel = DataModel::New();
|
||||
std::shared_ptr<DataModel> editModeDataModel = gDataModel;
|
||||
std::optional<HierarchyPreUpdateHandler> hierarchyPreUpdateHandler;
|
||||
std::optional<HierarchyPostUpdateHandler> hierarchyPostUpdateHandler;
|
||||
std::shared_ptr<Handles> editorToolHandles = Handles::New();
|
||||
|
|
|
@ -17,6 +17,7 @@ typedef std::function<void(InstanceRef instance, std::string property, Data::Var
|
|||
|
||||
extern Camera camera;
|
||||
extern std::shared_ptr<DataModel> gDataModel;
|
||||
extern std::shared_ptr<DataModel> editModeDataModel;
|
||||
inline std::shared_ptr<Workspace> gWorkspace() { return std::dynamic_pointer_cast<Workspace>(gDataModel->services["Workspace"]); }
|
||||
extern std::optional<HierarchyPreUpdateHandler> hierarchyPreUpdateHandler;
|
||||
extern std::optional<HierarchyPostUpdateHandler> hierarchyPostUpdateHandler;
|
||||
|
|
|
@ -25,4 +25,9 @@ class MemberNotFound : public Error {
|
|||
class AssignToReadOnlyMember : public Error {
|
||||
public:
|
||||
inline AssignToReadOnlyMember(std::string className, std::string memberName) : Error("AssignToReadOnlyMember", "Attempt to assign value to read-only member '" + memberName + "' in class " + className) {}
|
||||
};
|
||||
|
||||
class InstanceCastError : public Error {
|
||||
public:
|
||||
inline InstanceCastError(std::string sourceClass, std::string targetClass) : Error("InstanceCastError", "Attempt to cast object of type " + sourceClass + " to incompatible type " + targetClass) {}
|
||||
};
|
|
@ -2,8 +2,10 @@
|
|||
#include "common.h"
|
||||
#include "datatypes/meta.h"
|
||||
#include "datatypes/base.h"
|
||||
#include "datatypes/ref.h"
|
||||
#include "error/instance.h"
|
||||
#include "objects/base/member.h"
|
||||
#include "objects/base/refstate.h"
|
||||
#include "objects/meta.h"
|
||||
#include "logger.h"
|
||||
#include "panic.h"
|
||||
|
@ -12,6 +14,9 @@
|
|||
#include <cstdio>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
// Static so that this variable name is "local" to this source file
|
||||
const InstanceType Instance::TYPE = {
|
||||
|
@ -348,4 +353,73 @@ DescendantsIterator::self_type DescendantsIterator::operator++(int _) {
|
|||
current = current->GetParent().value()->GetChildren()[siblingIndex.back()];
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::optional<std::shared_ptr<Instance>> Instance::Clone(RefState<_RefStatePropertyCell> state) {
|
||||
std::shared_ptr<Instance> newInstance = GetClass()->constructor();
|
||||
|
||||
// Copy properties
|
||||
for (std::string property : GetProperties()) {
|
||||
PropertyMeta meta = GetPropertyMeta(property).expect();
|
||||
|
||||
if (meta.flags & (PROP_READONLY | PROP_NOSAVE)) continue;
|
||||
|
||||
// Update InstanceRef properties using map above
|
||||
if (meta.type == &Data::InstanceRef::TYPE) {
|
||||
std::weak_ptr<Instance> refWeak = GetPropertyValue(property).expect().get<Data::InstanceRef>();
|
||||
if (refWeak.expired()) continue;
|
||||
|
||||
auto ref = refWeak.lock();
|
||||
auto remappedRef = state->remappedInstances[ref]; // TODO: I think this is okay? Maybe?? Add null check?
|
||||
|
||||
if (remappedRef) {
|
||||
// If the instance has already been remapped, set the new value
|
||||
newInstance->SetPropertyValue(property, Data::InstanceRef(remappedRef)).expect();
|
||||
} else {
|
||||
// Otheriise, queue this property to be updated later, and keep its current value
|
||||
auto& refs = state->refsAwaitingRemap[ref];
|
||||
refs.push_back(std::make_pair(newInstance, property));
|
||||
state->refsAwaitingRemap[ref] = refs;
|
||||
|
||||
newInstance->SetPropertyValue(property, Data::InstanceRef(ref)).expect();
|
||||
}
|
||||
} else {
|
||||
Data::Variant value = GetPropertyValue(property).expect();
|
||||
newInstance->SetPropertyValue(property, value).expect();
|
||||
}
|
||||
}
|
||||
|
||||
// Remap self
|
||||
state->remappedInstances[shared_from_this()] = newInstance;
|
||||
|
||||
// Remap queued properties
|
||||
for (std::pair<std::shared_ptr<Instance>, std::string> ref : state->refsAwaitingRemap[shared_from_this()]) {
|
||||
ref.first->SetPropertyValue(ref.second, Data::InstanceRef(newInstance)).expect();
|
||||
}
|
||||
|
||||
// Clone children
|
||||
for (std::shared_ptr<Instance> child : GetChildren()) {
|
||||
std::optional<std::shared_ptr<Instance>> clonedChild = child->Clone(state);
|
||||
if (clonedChild)
|
||||
newInstance->AddChild(clonedChild.value());
|
||||
}
|
||||
|
||||
return newInstance;
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::string, std::shared_ptr<Instance>>> Instance::GetReferenceProperties() {
|
||||
std::vector<std::pair<std::string, std::shared_ptr<Instance>>> referenceProperties;
|
||||
|
||||
auto propertyNames = GetProperties();
|
||||
|
||||
for (std::string property : propertyNames) {
|
||||
PropertyMeta meta = GetPropertyMeta(property).expect();
|
||||
if (meta.type != &Data::InstanceRef::TYPE) continue;
|
||||
|
||||
std::weak_ptr<Instance> ref = GetPropertyValue(property).expect().get<Data::InstanceRef>();
|
||||
if (ref.expired()) continue;
|
||||
referenceProperties.push_back(std::make_pair(property, ref.lock()));
|
||||
}
|
||||
|
||||
return referenceProperties;
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
@ -14,6 +15,7 @@
|
|||
#include "error/instance.h"
|
||||
#include "error/result.h"
|
||||
#include "member.h"
|
||||
#include "objects/base/refstate.h"
|
||||
|
||||
class Instance;
|
||||
typedef std::shared_ptr<Instance>(*InstanceConstructor)();
|
||||
|
@ -36,8 +38,10 @@ struct InstanceType {
|
|||
InstanceFlags flags;
|
||||
};
|
||||
|
||||
typedef std::pair<std::shared_ptr<Instance>, std::string> _RefStatePropertyCell;
|
||||
|
||||
class DescendantsIterator;
|
||||
class Snap;
|
||||
|
||||
// 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
|
||||
|
@ -55,6 +59,8 @@ private:
|
|||
|
||||
bool ancestryContinuityCheck(std::optional<std::shared_ptr<Instance>> newParent);
|
||||
void updateAncestry(std::optional<std::shared_ptr<Instance>> child, std::optional<std::shared_ptr<Instance>> newParent);
|
||||
|
||||
friend Snap; // This isn't ideal, but oh well
|
||||
protected:
|
||||
bool parentLocked = false;
|
||||
std::unique_ptr<MemberMap> memberMap;
|
||||
|
@ -99,10 +105,21 @@ public:
|
|||
void UpdateProperty(std::string name);
|
||||
// Returning a list of property names feels kinda janky. Is this really the way to go?
|
||||
std::vector<std::string> GetProperties();
|
||||
std::vector<std::pair<std::string, std::shared_ptr<Instance>>> GetReferenceProperties();
|
||||
|
||||
template <typename T>
|
||||
result<std::shared_ptr<T>, InstanceCastError> CastTo() {
|
||||
// TODO: Too lazy to implement a manual check
|
||||
std::shared_ptr<T> result = std::dynamic_pointer_cast<T>(shared_from_this());
|
||||
if (result != nullptr)
|
||||
return InstanceCastError(GetClass()->className, T::TYPE.className);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Serialization
|
||||
void Serialize(pugi::xml_node parent);
|
||||
static result<std::shared_ptr<Instance>, NoSuchInstance> Deserialize(pugi::xml_node node);
|
||||
std::optional<std::shared_ptr<Instance>> Clone(RefState<_RefStatePropertyCell> state = std::make_shared<__RefState<_RefStatePropertyCell>>());
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<Instance> InstanceRef;
|
||||
|
|
17
core/src/objects/base/refstate.h
Normal file
17
core/src/objects/base/refstate.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
// Helper struct used for remapping reference when cloning/serializing
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
class Instance;
|
||||
|
||||
template <typename T>
|
||||
struct __RefState {
|
||||
std::map<std::shared_ptr<Instance>, std::shared_ptr<Instance>> remappedInstances;
|
||||
std::map<std::shared_ptr<Instance>, std::vector<T>> refsAwaitingRemap;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using RefState = std::shared_ptr<__RefState<T>>;
|
|
@ -1,6 +1,7 @@
|
|||
#include "datamodel.h"
|
||||
#include "base/service.h"
|
||||
#include "objects/base/instance.h"
|
||||
#include "objects/base/refstate.h"
|
||||
#include "objects/base/service.h"
|
||||
#include "objects/meta.h"
|
||||
#include "workspace.h"
|
||||
|
@ -9,6 +10,7 @@
|
|||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
const InstanceType DataModel::TYPE = {
|
||||
.super = &Instance::TYPE,
|
||||
|
@ -119,5 +121,89 @@ std::shared_ptr<DataModel> DataModel::LoadFromFile(std::string path) {
|
|||
|
||||
newModel->Init();
|
||||
|
||||
return newModel;
|
||||
}
|
||||
|
||||
result<std::shared_ptr<Service>, NoSuchService> DataModel::GetService(std::string className) {
|
||||
if (services.count(className) != 0)
|
||||
return std::dynamic_pointer_cast<Service>(services[className]);
|
||||
|
||||
if (!INSTANCE_MAP[className] || (INSTANCE_MAP[className]->flags ^ (INSTANCE_NOTCREATABLE | INSTANCE_SERVICE)) != 0) {
|
||||
return NoSuchService(className);
|
||||
}
|
||||
|
||||
services[className] = std::dynamic_pointer_cast<Service>(INSTANCE_MAP[className]->constructor());
|
||||
AddChild(std::dynamic_pointer_cast<Instance>(services[className]));
|
||||
|
||||
return std::dynamic_pointer_cast<Service>(services[className]);
|
||||
}
|
||||
|
||||
result<std::optional<std::shared_ptr<Service>>, NoSuchService> DataModel::FindService(std::string className) {
|
||||
if (!INSTANCE_MAP[className] || (INSTANCE_MAP[className]->flags ^ (INSTANCE_NOTCREATABLE | INSTANCE_SERVICE)) != 0) {
|
||||
return NoSuchService(className);
|
||||
}
|
||||
|
||||
if (services.count(className) != 0)
|
||||
return std::make_optional(std::dynamic_pointer_cast<Service>(services[className]));
|
||||
return (std::optional<std::shared_ptr<Service>>)std::nullopt;
|
||||
}
|
||||
|
||||
std::shared_ptr<DataModel> DataModel::CloneModel() {
|
||||
RefState<_RefStatePropertyCell> state = std::make_shared<__RefState<_RefStatePropertyCell>>();
|
||||
std::shared_ptr<DataModel> newModel = DataModel::New();
|
||||
|
||||
// Copy properties
|
||||
for (std::string property : GetProperties()) {
|
||||
PropertyMeta meta = GetPropertyMeta(property).expect();
|
||||
|
||||
if (meta.flags & (PROP_READONLY | PROP_NOSAVE)) continue;
|
||||
|
||||
// Update InstanceRef properties using map above
|
||||
if (meta.type == &Data::InstanceRef::TYPE) {
|
||||
std::weak_ptr<Instance> refWeak = GetPropertyValue(property).expect().get<Data::InstanceRef>();
|
||||
if (refWeak.expired()) continue;
|
||||
|
||||
auto ref = refWeak.lock();
|
||||
auto remappedRef = state->remappedInstances[ref]; // TODO: I think this is okay? Maybe?? Add null check?
|
||||
|
||||
if (remappedRef) {
|
||||
// If the instance has already been remapped, set the new value
|
||||
newModel->SetPropertyValue(property, Data::InstanceRef(remappedRef)).expect();
|
||||
} else {
|
||||
// Otheriise, queue this property to be updated later, and keep its current value
|
||||
auto& refs = state->refsAwaitingRemap[ref];
|
||||
refs.push_back(std::make_pair(newModel, property));
|
||||
state->refsAwaitingRemap[ref] = refs;
|
||||
|
||||
newModel->SetPropertyValue(property, Data::InstanceRef(ref)).expect();
|
||||
}
|
||||
} else {
|
||||
Data::Variant value = GetPropertyValue(property).expect();
|
||||
newModel->SetPropertyValue(property, value).expect();
|
||||
}
|
||||
}
|
||||
|
||||
// Remap self
|
||||
state->remappedInstances[shared_from_this()] = newModel;
|
||||
|
||||
// Remap queued properties
|
||||
for (std::pair<std::shared_ptr<Instance>, std::string> ref : state->refsAwaitingRemap[shared_from_this()]) {
|
||||
ref.first->SetPropertyValue(ref.second, Data::InstanceRef(newModel)).expect();
|
||||
}
|
||||
|
||||
// Clone services
|
||||
for (std::shared_ptr<Instance> child : GetChildren()) {
|
||||
auto result = child->Clone(state);
|
||||
if (!result)
|
||||
continue;
|
||||
|
||||
newModel->AddChild(result.value());
|
||||
|
||||
// Special case: Ignore instances parented to DataModel which are not services
|
||||
if (child->GetClass()->flags & INSTANCE_SERVICE) {
|
||||
newModel->services[child->GetClass()->className] = std::dynamic_pointer_cast<Service>(result.value());
|
||||
}
|
||||
}
|
||||
|
||||
return newModel;
|
||||
}
|
|
@ -1,12 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include "error/instance.h"
|
||||
#include "error/result.h"
|
||||
#include "logger.h"
|
||||
#include "objects/base/instance.h"
|
||||
#include "objects/meta.h"
|
||||
#include "panic.h"
|
||||
#include "objects/base/refstate.h"
|
||||
#include <memory>
|
||||
#include <variant>
|
||||
|
||||
class Workspace;
|
||||
|
||||
|
@ -17,6 +15,7 @@ class Service;
|
|||
class DataModel : public Instance {
|
||||
private:
|
||||
void DeserializeService(pugi::xml_node node);
|
||||
static void cloneService(std::shared_ptr<DataModel> target, std::shared_ptr<Service>, RefState<_RefStatePropertyCell>);
|
||||
public:
|
||||
const static InstanceType TYPE;
|
||||
|
||||
|
@ -30,47 +29,25 @@ public:
|
|||
static inline std::shared_ptr<DataModel> New() { return std::make_shared<DataModel>(); };
|
||||
virtual const InstanceType* GetClass() override;
|
||||
|
||||
// Inserts a service if it doesn't already exist
|
||||
fallible<ServiceAlreadyExists, NoSuchService> InsertService(std::string name) {
|
||||
if (services.count(name) != 0)
|
||||
return fallible<ServiceAlreadyExists, NoSuchService>(ServiceAlreadyExists(name));
|
||||
|
||||
if (!INSTANCE_MAP[name] || (INSTANCE_MAP[name]->flags ^ (INSTANCE_NOTCREATABLE | INSTANCE_SERVICE)) != 0) {
|
||||
Logger::fatalErrorf("Attempt to create instance of unknown type %s", name);
|
||||
panic();
|
||||
}
|
||||
|
||||
services[name] = std::dynamic_pointer_cast<Service>(INSTANCE_MAP[name]->constructor());
|
||||
AddChild(std::dynamic_pointer_cast<Instance>(services[name]));
|
||||
|
||||
return {};
|
||||
}
|
||||
result<std::shared_ptr<Service>, NoSuchService> GetService(std::string className);
|
||||
result<std::optional<std::shared_ptr<Service>>, NoSuchService> FindService(std::string className);
|
||||
|
||||
template <typename T>
|
||||
result<std::shared_ptr<T>, NoSuchService> GetService(std::string name) {
|
||||
if (services.count(name) != 0)
|
||||
return std::dynamic_pointer_cast<T>(services[name]);
|
||||
|
||||
// TODO: Replace this with a result return type
|
||||
if (!INSTANCE_MAP[name] || (INSTANCE_MAP[name]->flags ^ (INSTANCE_NOTCREATABLE | INSTANCE_SERVICE)) != 0) {
|
||||
return NoSuchService(name);
|
||||
}
|
||||
|
||||
services[name] = std::dynamic_pointer_cast<Service>(INSTANCE_MAP[name]->constructor());
|
||||
AddChild(std::dynamic_pointer_cast<Instance>(services[name]));
|
||||
|
||||
return std::dynamic_pointer_cast<T>(services[name]);
|
||||
std::shared_ptr<T> GetService() {
|
||||
auto result = GetService(T::TYPE.className);
|
||||
return std::dynamic_pointer_cast<T>(result.expect("GetService<T>() was called with a non-service instance type"));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::optional<std::shared_ptr<T>> FindService() {
|
||||
if (services.count(name) != 0)
|
||||
return std::dynamic_pointer_cast<T>(services[name]);
|
||||
return std::nullopt;
|
||||
auto result = FindService(T::TYPE.className).expect("FindService<T>() was called with a non-service instance type");
|
||||
if (!result) return std::nullopt;
|
||||
return std::dynamic_pointer_cast<T>(result.value());
|
||||
}
|
||||
|
||||
// Saving/loading
|
||||
inline bool HasFile() { return this->currentFile.has_value(); }
|
||||
void SaveToFile(std::optional<std::string> path = std::nullopt);
|
||||
static std::shared_ptr<DataModel> LoadFromFile(std::string path);
|
||||
std::shared_ptr<DataModel> CloneModel();
|
||||
};
|
|
@ -1,4 +1,5 @@
|
|||
#include "jointsservice.h"
|
||||
#include "workspace.h"
|
||||
|
||||
const InstanceType JointsService::TYPE = {
|
||||
.super = &Instance::TYPE,
|
||||
|
@ -21,3 +22,9 @@ void JointsService::InitService() {
|
|||
if (initialized) return;
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
std::optional<std::shared_ptr<Workspace>> JointsService::jointWorkspace() {
|
||||
if (!dataModel()) return std::nullopt;
|
||||
|
||||
return dataModel().value()->FindService<Workspace>();
|
||||
}
|
|
@ -2,7 +2,12 @@
|
|||
|
||||
#include "objects/base/service.h"
|
||||
|
||||
class Snap;
|
||||
class JointsService : public Service {
|
||||
private:
|
||||
std::optional<std::shared_ptr<Workspace>> jointWorkspace();
|
||||
|
||||
friend Snap;
|
||||
protected:
|
||||
void InitService() override;
|
||||
bool initialized = false;
|
||||
|
|
|
@ -2,13 +2,12 @@
|
|||
|
||||
#include "datatypes/cframe.h"
|
||||
#include "datatypes/ref.h"
|
||||
#include "datatypes/vector.h"
|
||||
#include "objects/datamodel.h"
|
||||
#include "objects/jointsservice.h"
|
||||
#include "workspace.h"
|
||||
#include "part.h"
|
||||
#include <memory>
|
||||
#include <reactphysics3d/constraint/FixedJoint.h>
|
||||
#include <reactphysics3d/engine/PhysicsWorld.h>
|
||||
|
||||
const InstanceType Snap::TYPE = {
|
||||
.super = &Instance::TYPE,
|
||||
|
@ -40,7 +39,7 @@ Snap::Snap(): Instance(&TYPE) {
|
|||
.codec = fieldCodecOf<Data::CFrame>(),
|
||||
.updateCallback = memberFunctionOf(&Snap::onUpdated, this),
|
||||
}}, { "C1", {
|
||||
.backingField = &c0,
|
||||
.backingField = &c1,
|
||||
.type = &Data::CFrame::TYPE,
|
||||
.codec = fieldCodecOf<Data::CFrame>(),
|
||||
.updateCallback = memberFunctionOf(&Snap::onUpdated, this),
|
||||
|
@ -53,66 +52,47 @@ Snap::~Snap() {
|
|||
}
|
||||
|
||||
void Snap::OnAncestryChanged(std::optional<std::shared_ptr<Instance>>, std::optional<std::shared_ptr<Instance>>) {
|
||||
// If the old workspace existed, and the new one differs, delete the current joint
|
||||
if (this->joint && !this->oldWorkspace.expired() && (!workspace() || workspace().value() != this->oldWorkspace.lock())) {
|
||||
// printf("Broke joint - Removed from workspace\n");
|
||||
oldJointWorkspace.lock()->physicsWorld->destroyJoint(this->joint);
|
||||
this->joint = nullptr;
|
||||
}
|
||||
// Destroy and rebuild the joint, it's the simplest solution that actually works
|
||||
|
||||
// If the previous parent was JointsService, and now it isn't, delete the joint
|
||||
if (this->joint && !oldParent.expired() && oldParent.lock()->GetClass() == &JointsService::TYPE && (!GetParent() || GetParent() != oldParent.lock())) {
|
||||
// printf("Broke joint - Removed from JointsService\n");
|
||||
oldJointWorkspace.lock()->physicsWorld->destroyJoint(this->joint);
|
||||
this->joint = nullptr;
|
||||
}
|
||||
|
||||
// If the new workspace exists, and the old one differs, create the joint
|
||||
if (!this->joint && workspace() && (oldWorkspace.expired() || oldWorkspace.lock() != workspace().value())) {
|
||||
// printf("Made joint - Added to workspace\n");
|
||||
buildJoint();
|
||||
}
|
||||
|
||||
// If the new parent is JointsService and the previous wasn't, then create the joint
|
||||
if (!this->joint && GetParent() && GetParent().value()->GetClass() == &JointsService::TYPE && (oldParent.expired() || GetParent() != oldParent.lock())) {
|
||||
// printf("Made joint - Added to JointsService\n");
|
||||
buildJoint();
|
||||
}
|
||||
|
||||
this->oldParent = !GetParent() ? std::weak_ptr<Instance>() : GetParent().value();
|
||||
this->oldWorkspace = !workspace() ? std::weak_ptr<Workspace>() : workspace().value();
|
||||
this->oldJointWorkspace = !jointWorkspace() ? std::weak_ptr<Workspace>() : jointWorkspace().value();
|
||||
breakJoint();
|
||||
buildJoint();
|
||||
}
|
||||
|
||||
void Snap::onUpdated(std::string property) {
|
||||
// We are not in the workspace, so we don't really care what values are currently set
|
||||
if (!jointWorkspace()) return;
|
||||
|
||||
// Workspace cannot have changed, so if the joint currently exists, it is in the present one
|
||||
if (this->joint)
|
||||
jointWorkspace().value()->physicsWorld->destroyJoint(this->joint);
|
||||
// Destroy and rebuild the joint, if applicable
|
||||
|
||||
breakJoint();
|
||||
buildJoint();
|
||||
}
|
||||
|
||||
void Snap::buildJoint() {
|
||||
if (part0.expired() || part1.expired() || !jointWorkspace()) return;
|
||||
// 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> 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;
|
||||
jointWorkspace().value()->SyncPartPhysics(part1.lock());
|
||||
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*>(jointWorkspace().value()->physicsWorld->createJoint(jointInfo));
|
||||
this->joint = dynamic_cast<rp::FixedJoint*>(workspace->physicsWorld->createJoint(jointInfo));
|
||||
jointWorkspace = workspace;
|
||||
}
|
||||
|
||||
std::optional<std::shared_ptr<Workspace>> Snap::jointWorkspace() {
|
||||
if (workspace()) return 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;
|
||||
|
||||
if (GetParent() && GetParent().value()->GetClass() == &JointsService::TYPE)
|
||||
return std::dynamic_pointer_cast<DataModel>(GetParent().value()->GetParent().value())->GetService<Workspace>("Workspace");
|
||||
|
||||
return {};
|
||||
jointWorkspace.lock()->physicsWorld->destroyJoint(this->joint);
|
||||
this->joint = nullptr;
|
||||
}
|
|
@ -10,15 +10,11 @@ class Workspace;
|
|||
class Snap : public Instance {
|
||||
rp::FixedJoint* joint = nullptr;
|
||||
|
||||
std::weak_ptr<Instance> oldParent;
|
||||
// The actual workspace the joint is a part of
|
||||
std::weak_ptr<Workspace> oldWorkspace;
|
||||
// The pseudo-workspace the joint is a part of (including if parented to JointsService)
|
||||
std::weak_ptr<Workspace> oldJointWorkspace;
|
||||
// The workspace the joint was created in, if it exists
|
||||
std::weak_ptr<Workspace> jointWorkspace;
|
||||
protected:
|
||||
void OnAncestryChanged(std::optional<std::shared_ptr<Instance>>, std::optional<std::shared_ptr<Instance>>) override;
|
||||
|
||||
std::optional<std::shared_ptr<Workspace>> jointWorkspace();
|
||||
void onUpdated(std::string property);
|
||||
void buildJoint();
|
||||
void breakJoint();
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#include "workspace.h"
|
||||
#include "objects/base/instance.h"
|
||||
#include "objects/jointsservice.h"
|
||||
#include "objects/snap.h"
|
||||
#include "physics/util.h"
|
||||
#include <reactphysics3d/engine/PhysicsCommon.h>
|
||||
|
||||
|
@ -35,6 +37,8 @@ void Workspace::InitService() {
|
|||
// world->setContactsPositionCorrectionTechnique(rp3d::ContactsPositionCorrectionTechnique::BAUMGARTE_CONTACTS);
|
||||
physicsWorld->setNbIterationsPositionSolver(2000);
|
||||
physicsWorld->setNbIterationsVelocitySolver(2000);
|
||||
// physicsWorld->setSleepLinearVelocity(10);
|
||||
// physicsWorld->setSleepAngularVelocity(5);
|
||||
|
||||
// physicsWorld->setEventListener(&eventListener);
|
||||
|
||||
|
@ -45,6 +49,20 @@ void Workspace::InitService() {
|
|||
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(obj);
|
||||
this->SyncPartPhysics(part);
|
||||
}
|
||||
|
||||
// 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<Snap> joint = std::dynamic_pointer_cast<Snap>(obj);
|
||||
joint->UpdateProperty("Part0");
|
||||
}
|
||||
|
||||
for (auto obj : dataModel().value()->GetService<JointsService>()->GetChildren()) {
|
||||
if (obj->GetClass()->className != "Snap") continue; // TODO: Replace this with a .IsA call instead of comparing the class name directly
|
||||
std::shared_ptr<Snap> joint = std::dynamic_pointer_cast<Snap>(obj);
|
||||
joint->UpdateProperty("Part0");
|
||||
}
|
||||
}
|
||||
|
||||
void Workspace::SyncPartPhysics(std::shared_ptr<Part> part) {
|
||||
|
@ -61,15 +79,23 @@ void Workspace::SyncPartPhysics(std::shared_ptr<Part> part) {
|
|||
|
||||
rp::BoxShape* shape = physicsCommon->createBoxShape(glmToRp(part->size * glm::vec3(0.5f)));
|
||||
|
||||
if (part->rigidBody->getNbColliders() > 0) {
|
||||
if (part->rigidBody->getNbColliders() > 0)
|
||||
part->rigidBody->removeCollider(part->rigidBody->getCollider(0));
|
||||
}
|
||||
|
||||
if (part->rigidBody->getNbColliders() == 0)
|
||||
part->rigidBody->addCollider(shape, rp::Transform());
|
||||
part->rigidBody->addCollider(shape, rp::Transform());
|
||||
part->rigidBody->setType(part->anchored ? rp::BodyType::STATIC : rp::BodyType::DYNAMIC);
|
||||
part->rigidBody->getCollider(0)->setCollisionCategoryBits(0b11);
|
||||
|
||||
rp::Material& material = part->rigidBody->getCollider(0)->getMaterial();
|
||||
material.setFrictionCoefficient(0.35);
|
||||
material.setMassDensity(1.f);
|
||||
|
||||
//https://github.com/DanielChappuis/reactphysics3d/issues/170#issuecomment-691514860
|
||||
part->rigidBody->updateMassFromColliders();
|
||||
part->rigidBody->updateLocalInertiaTensorFromColliders();
|
||||
|
||||
// part->rigidBody->setMass(density * part->size.x * part->size.y * part->size.z);
|
||||
|
||||
part->rigidBody->setUserData(&*part);
|
||||
}
|
||||
|
||||
|
|
|
@ -462,6 +462,9 @@ void MainGLWidget::keyPressEvent(QKeyEvent* evt) {
|
|||
Logger::warning("warning message");
|
||||
if (evt->key() == Qt::Key_O)
|
||||
Logger::error("error message");
|
||||
|
||||
if (evt->key() == Qt::Key_C && getSelection().size() > 0 && !getSelection()[0].expired())
|
||||
getSelection()[0].lock()->Clone().value()->SetParent(gWorkspace());
|
||||
}
|
||||
|
||||
void MainGLWidget::keyReleaseEvent(QKeyEvent* evt) {
|
||||
|
|
|
@ -17,7 +17,13 @@
|
|||
#define NDEBUG
|
||||
#endif
|
||||
|
||||
bool simulationPlaying = false;
|
||||
enum RunState {
|
||||
RUN_STOPPED,
|
||||
RUN_RUNNING,
|
||||
RUN_PAUSED
|
||||
};
|
||||
|
||||
RunState runState = RUN_STOPPED;
|
||||
|
||||
bool worldSpaceTransforms = false;
|
||||
|
||||
|
@ -155,7 +161,7 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
auto part0 = ui->mainWidget->lastPart;
|
||||
|
||||
gWorkspace()->AddChild(ui->mainWidget->lastPart = Part::New({
|
||||
.position = glm::vec3(1.6691498, 0.82489049, -0.73040605),
|
||||
.position = glm::vec3(1.7610925, 0.48568499, -0.82623518),
|
||||
// .rotation = glm::vec3(0.5, 2, 1),
|
||||
.rotation = glm::vec3(-2.6415927, 1.1415926, -2.141639),
|
||||
.size = glm::vec3(4, 1.2, 2),
|
||||
|
@ -171,7 +177,7 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
snap->c1 = part0->cframe;
|
||||
|
||||
// gWorkspace()->AddChild(snap);
|
||||
gDataModel->GetService<JointsService>("JointsService").expect()->AddChild(snap);
|
||||
gDataModel->GetService<JointsService>()->AddChild(snap);
|
||||
}
|
||||
|
||||
void MainWindow::closeEvent(QCloseEvent* evt) {
|
||||
|
@ -217,7 +223,7 @@ void MainWindow::timerEvent(QTimerEvent* evt) {
|
|||
float deltaTime = std::chrono::duration_cast<std::chrono::duration<float>>(std::chrono::steady_clock::now() - lastTime).count();
|
||||
lastTime = std::chrono::steady_clock::now();
|
||||
|
||||
if (simulationPlaying)
|
||||
if (runState == RUN_RUNNING)
|
||||
gWorkspace()->PhysicsStep(deltaTime);
|
||||
ui->mainWidget->update();
|
||||
ui->mainWidget->updateCycle();
|
||||
|
@ -254,19 +260,53 @@ void MainWindow::connectActionHandlers() {
|
|||
});
|
||||
ui->actionToggleEditSounds->setChecked(true);
|
||||
|
||||
connect(ui->actionToggleSimulation, &QAction::triggered, this, [&]() {
|
||||
simulationPlaying = !simulationPlaying;
|
||||
if (simulationPlaying) {
|
||||
ui->actionToggleSimulation->setText("Pause simulation");
|
||||
ui->actionToggleSimulation->setToolTip("Pause the simulation");
|
||||
ui->actionToggleSimulation->setIcon(QIcon::fromTheme("media-playback-pause"));
|
||||
} else {
|
||||
ui->actionToggleSimulation->setText("Resume simulation");
|
||||
ui->actionToggleSimulation->setToolTip("Resume the simulation");
|
||||
ui->actionToggleSimulation->setIcon(QIcon::fromTheme("media-playback-start"));
|
||||
connect(ui->actionRunSimulation, &QAction::triggered, this, [&]() {
|
||||
if (runState == RUN_RUNNING) return;
|
||||
|
||||
if (runState == RUN_PAUSED) {
|
||||
runState = RUN_RUNNING;
|
||||
ui->actionRunSimulation->setEnabled(false);
|
||||
ui->actionPauseSimulation->setEnabled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
runState = RUN_RUNNING;
|
||||
ui->actionRunSimulation->setEnabled(false);
|
||||
ui->actionPauseSimulation->setEnabled(true);
|
||||
ui->actionStopSimulation->setEnabled(true);
|
||||
|
||||
std::shared_ptr<DataModel> newModel = editModeDataModel->CloneModel();
|
||||
gDataModel = newModel;
|
||||
gDataModel->Init();
|
||||
ui->explorerView->updateRoot(gDataModel);
|
||||
});
|
||||
|
||||
connect(ui->actionPauseSimulation, &QAction::triggered, this, [&]() {
|
||||
if (runState != RUN_RUNNING) return;
|
||||
|
||||
runState = RUN_PAUSED;
|
||||
ui->actionRunSimulation->setEnabled(true);
|
||||
ui->actionPauseSimulation->setEnabled(false);
|
||||
});
|
||||
|
||||
connect(ui->actionStopSimulation, &QAction::triggered, this, [&]() {
|
||||
if (runState == RUN_STOPPED) return;
|
||||
|
||||
runState = RUN_STOPPED;
|
||||
ui->actionRunSimulation->setEnabled(true);
|
||||
ui->actionPauseSimulation->setEnabled(false);
|
||||
ui->actionStopSimulation->setEnabled(false);
|
||||
|
||||
// GC: Check to make sure gDataModel gets properly garbage collected prior to this
|
||||
gDataModel = editModeDataModel;
|
||||
gDataModel->Init();
|
||||
ui->explorerView->updateRoot(gDataModel);
|
||||
});
|
||||
|
||||
ui->actionRunSimulation->setEnabled(true);
|
||||
ui->actionPauseSimulation->setEnabled(false);
|
||||
ui->actionStopSimulation->setEnabled(false);
|
||||
|
||||
connect(ui->actionToggleSpace, &QAction::triggered, this, [&]() {
|
||||
worldSpaceTransforms = !worldSpaceTransforms;
|
||||
updateToolbars();
|
||||
|
|
|
@ -188,7 +188,9 @@
|
|||
<attribute name="toolBarBreak">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<addaction name="actionToggleSimulation"/>
|
||||
<addaction name="actionRunSimulation"/>
|
||||
<addaction name="actionPauseSimulation"/>
|
||||
<addaction name="actionStopSimulation"/>
|
||||
</widget>
|
||||
<widget class="QToolBar" name="surfaceTools">
|
||||
<property name="windowTitle">
|
||||
|
@ -675,6 +677,48 @@
|
|||
<enum>QAction::MenuRole::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRunSimulation">
|
||||
<property name="icon">
|
||||
<iconset theme="media-playback-start"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Run</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Run simulation</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::MenuRole::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionPauseSimulation">
|
||||
<property name="icon">
|
||||
<iconset theme="media-playback-pause"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Pause</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Pause simulation</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::MenuRole::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionStopSimulation">
|
||||
<property name="icon">
|
||||
<iconset theme="media-playback-stop"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Stop</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Stop simulation</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::MenuRole::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
|
|
|
@ -296,7 +296,7 @@ void PropertiesView::setSelected(std::optional<InstanceRef> instance) {
|
|||
PropertyMeta meta = inst->GetPropertyMeta(property).expect();
|
||||
Data::Variant currentValue = inst->GetPropertyValue(property).expect();
|
||||
|
||||
if (meta.type == &Data::CFrame::TYPE) continue;
|
||||
// if (meta.type == &Data::CFrame::TYPE) continue;
|
||||
|
||||
QTreeWidgetItem* item = new QTreeWidgetItem;
|
||||
item->setFlags(item->flags() | Qt::ItemIsEditable | Qt::ItemIsSelectable);
|
||||
|
@ -311,6 +311,9 @@ void PropertiesView::setSelected(std::optional<InstanceRef> instance) {
|
|||
} else if (meta.type == &Data::Vector3::TYPE) {
|
||||
Data::Vector3 vector = currentValue.get<Data::Vector3>();
|
||||
item->setData(1, Qt::DisplayRole, QString::fromStdString(currentValue.ToString()));
|
||||
} else if (meta.type == &Data::CFrame::TYPE) {
|
||||
Data::Vector3 vector = currentValue.get<Data::CFrame>().Position();
|
||||
item->setData(1, Qt::DisplayRole, QString::fromStdString(currentValue.ToString()));
|
||||
} else {
|
||||
item->setData(1, Qt::DisplayRole, QString::fromStdString(currentValue.ToString()));
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue