Compare commits

...

9 commits

17 changed files with 389 additions and 107 deletions

View file

@ -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();

View file

@ -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;

View file

@ -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) {}
};

View file

@ -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;
}

View file

@ -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;

View 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>>;

View file

@ -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;
}

View file

@ -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();
};

View file

@ -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>();
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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();

View file

@ -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);
}

View file

@ -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) {

View file

@ -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();

View file

@ -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>

View file

@ -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()));
}