refactor(physics): integrated physics world into workspace (likely for bugs to ensue. I'll deal with that later)
This commit is contained in:
parent
35f49b8a45
commit
6a017b2238
11 changed files with 213 additions and 220 deletions
|
@ -1,7 +1,6 @@
|
||||||
#include <GL/glew.h>
|
#include <GL/glew.h>
|
||||||
#include <GLFW/glfw3.h>
|
#include <GLFW/glfw3.h>
|
||||||
#include "objects/part.h"
|
#include "objects/part.h"
|
||||||
#include "physics/simulation.h"
|
|
||||||
#include "rendering/renderer.h"
|
#include "rendering/renderer.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
|
@ -34,7 +33,6 @@ int main() {
|
||||||
glewInit();
|
glewInit();
|
||||||
|
|
||||||
gDataModel->Init();
|
gDataModel->Init();
|
||||||
simulationInit();
|
|
||||||
renderInit(window, 1200, 900);
|
renderInit(window, 1200, 900);
|
||||||
|
|
||||||
// Baseplate
|
// Baseplate
|
||||||
|
@ -56,7 +54,7 @@ int main() {
|
||||||
for (InstanceRef inst : gWorkspace()->GetChildren()) {
|
for (InstanceRef inst : gWorkspace()->GetChildren()) {
|
||||||
if (inst->GetClass()->className != "Part") continue;
|
if (inst->GetClass()->className != "Part") continue;
|
||||||
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
|
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
|
||||||
syncPartPhysics(part);
|
gWorkspace()->SyncPartPhysics(part);
|
||||||
}
|
}
|
||||||
|
|
||||||
float lastTime = glfwGetTime();
|
float lastTime = glfwGetTime();
|
||||||
|
@ -65,7 +63,7 @@ int main() {
|
||||||
lastTime = glfwGetTime();
|
lastTime = glfwGetTime();
|
||||||
|
|
||||||
processInput(window);
|
processInput(window);
|
||||||
physicsStep(deltaTime);
|
gWorkspace()->PhysicsStep(deltaTime);
|
||||||
render(window);
|
render(window);
|
||||||
|
|
||||||
glfwSwapBuffers(window);
|
glfwSwapBuffers(window);
|
||||||
|
@ -103,15 +101,15 @@ void processInput(GLFWwindow* window) {
|
||||||
shiftFactor *= deltaTime;
|
shiftFactor *= deltaTime;
|
||||||
if (glfwGetKey(window, GLFW_KEY_X) == GLFW_PRESS) {
|
if (glfwGetKey(window, GLFW_KEY_X) == GLFW_PRESS) {
|
||||||
// lastPart->rotation *= glm::angleAxis(shiftFactor, glm::vec3(1, 0, 0));
|
// lastPart->rotation *= glm::angleAxis(shiftFactor, glm::vec3(1, 0, 0));
|
||||||
syncPartPhysics(lastPart);
|
gWorkspace()->SyncPartPhysics(lastPart);
|
||||||
}
|
}
|
||||||
if (glfwGetKey(window, GLFW_KEY_Y) == GLFW_PRESS) {
|
if (glfwGetKey(window, GLFW_KEY_Y) == GLFW_PRESS) {
|
||||||
// lastPart->rotation *= glm::angleAxis(shiftFactor, glm::vec3(0, 1, 0));
|
// lastPart->rotation *= glm::angleAxis(shiftFactor, glm::vec3(0, 1, 0));
|
||||||
syncPartPhysics(lastPart);
|
gWorkspace()->SyncPartPhysics(lastPart);
|
||||||
}
|
}
|
||||||
if (glfwGetKey(window, GLFW_KEY_Z) == GLFW_PRESS) {
|
if (glfwGetKey(window, GLFW_KEY_Z) == GLFW_PRESS) {
|
||||||
// lastPart->rotation *= glm::angleAxis(shiftFactor, glm::vec3(0, 0, 1));
|
// lastPart->rotation *= glm::angleAxis(shiftFactor, glm::vec3(0, 0, 1));
|
||||||
syncPartPhysics(lastPart);
|
gWorkspace()->SyncPartPhysics(lastPart);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -152,35 +150,35 @@ void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods
|
||||||
.size = glm::vec3(1, 1, 1),
|
.size = glm::vec3(1, 1, 1),
|
||||||
.color = glm::vec3(1.0f, 0.5f, 0.31f),
|
.color = glm::vec3(1.0f, 0.5f, 0.31f),
|
||||||
}));
|
}));
|
||||||
syncPartPhysics(lastPart);
|
gWorkspace()->SyncPartPhysics(lastPart);
|
||||||
}
|
}
|
||||||
|
|
||||||
float shiftFactor = (mods & GLFW_MOD_SHIFT) ? -0.2 : 0.2;
|
float shiftFactor = (mods & GLFW_MOD_SHIFT) ? -0.2 : 0.2;
|
||||||
if (mode == 0) {
|
if (mode == 0) {
|
||||||
if (key == GLFW_KEY_X && action == GLFW_PRESS) {
|
if (key == GLFW_KEY_X && action == GLFW_PRESS) {
|
||||||
// lastPart->position.x += shiftFactor;
|
// lastPart->position.x += shiftFactor;
|
||||||
syncPartPhysics(lastPart);
|
gWorkspace()->SyncPartPhysics(lastPart);
|
||||||
}
|
}
|
||||||
if (key == GLFW_KEY_Y && action == GLFW_PRESS) {
|
if (key == GLFW_KEY_Y && action == GLFW_PRESS) {
|
||||||
// lastPart->position.y += shiftFactor;
|
// lastPart->position.y += shiftFactor;
|
||||||
syncPartPhysics(lastPart);
|
gWorkspace()->SyncPartPhysics(lastPart);
|
||||||
}
|
}
|
||||||
if (key == GLFW_KEY_Z && action == GLFW_PRESS) {
|
if (key == GLFW_KEY_Z && action == GLFW_PRESS) {
|
||||||
// lastPart->position.z += shiftFactor;
|
// lastPart->position.z += shiftFactor;
|
||||||
syncPartPhysics(lastPart);
|
gWorkspace()->SyncPartPhysics(lastPart);
|
||||||
}
|
}
|
||||||
} else if (mode == 1) {
|
} else if (mode == 1) {
|
||||||
if (key == GLFW_KEY_X && action == GLFW_PRESS) {
|
if (key == GLFW_KEY_X && action == GLFW_PRESS) {
|
||||||
lastPart->size.x += shiftFactor;
|
lastPart->size.x += shiftFactor;
|
||||||
syncPartPhysics(lastPart);
|
gWorkspace()->SyncPartPhysics(lastPart);
|
||||||
}
|
}
|
||||||
if (key == GLFW_KEY_Y && action == GLFW_PRESS) {
|
if (key == GLFW_KEY_Y && action == GLFW_PRESS) {
|
||||||
lastPart->size.y += shiftFactor;
|
lastPart->size.y += shiftFactor;
|
||||||
syncPartPhysics(lastPart);
|
gWorkspace()->SyncPartPhysics(lastPart);
|
||||||
}
|
}
|
||||||
if (key == GLFW_KEY_Z && action == GLFW_PRESS) {
|
if (key == GLFW_KEY_Z && action == GLFW_PRESS) {
|
||||||
lastPart->size.z += shiftFactor;
|
lastPart->size.z += shiftFactor;
|
||||||
syncPartPhysics(lastPart);
|
gWorkspace()->SyncPartPhysics(lastPart);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -88,14 +88,12 @@ bool Instance::SetParent(std::optional<std::shared_ptr<Instance>> newParent) {
|
||||||
this->_workspace = std::nullopt;
|
this->_workspace = std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Algorithm for updating descendant's dataModel and workspace fields
|
updateAncestry(this->shared<Instance>(), newParent);
|
||||||
if (oldDataModel != _dataModel || oldWorkspace != _workspace)
|
|
||||||
updateAncestry();
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Instance::updateAncestry() {
|
void Instance::updateAncestry(std::optional<std::shared_ptr<Instance>> updatedChild, std::optional<std::shared_ptr<Instance>> newParent) {
|
||||||
if (GetParent()) {
|
if (GetParent()) {
|
||||||
this->_dataModel = GetParent().value()->GetClass() == &DataModel::TYPE ? std::make_optional(std::dynamic_pointer_cast<DataModel>(GetParent().value())) : GetParent().value()->dataModel();
|
this->_dataModel = GetParent().value()->GetClass() == &DataModel::TYPE ? std::make_optional(std::dynamic_pointer_cast<DataModel>(GetParent().value())) : GetParent().value()->dataModel();
|
||||||
this->_workspace = GetParent().value()->GetClass() == &Workspace::TYPE ? std::make_optional(std::dynamic_pointer_cast<Workspace>(GetParent().value())) : GetParent().value()->workspace();
|
this->_workspace = GetParent().value()->GetClass() == &Workspace::TYPE ? std::make_optional(std::dynamic_pointer_cast<Workspace>(GetParent().value())) : GetParent().value()->workspace();
|
||||||
|
@ -104,9 +102,11 @@ void Instance::updateAncestry() {
|
||||||
this->_workspace = std::nullopt;
|
this->_workspace = std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OnAncestryChanged(updatedChild, newParent);
|
||||||
|
|
||||||
// Update ancestry in descendants
|
// Update ancestry in descendants
|
||||||
for (InstanceRef child : children) {
|
for (InstanceRef child : children) {
|
||||||
child->updateAncestry();
|
child->updateAncestry(updatedChild, newParent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,6 +141,10 @@ void Instance::OnParentUpdated(std::optional<std::shared_ptr<Instance>> oldParen
|
||||||
// Empty stub
|
// Empty stub
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Instance::OnAncestryChanged(std::optional<std::shared_ptr<Instance>> child, std::optional<std::shared_ptr<Instance>> newParent) {
|
||||||
|
// Empty stub
|
||||||
|
}
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
|
|
||||||
tl::expected<Data::Variant, MemberNotFound> Instance::GetPropertyValue(std::string name) {
|
tl::expected<Data::Variant, MemberNotFound> Instance::GetPropertyValue(std::string name) {
|
||||||
|
|
|
@ -61,7 +61,7 @@ private:
|
||||||
std::optional<std::weak_ptr<Workspace>> _workspace;
|
std::optional<std::weak_ptr<Workspace>> _workspace;
|
||||||
|
|
||||||
bool ancestryContinuityCheck(std::optional<std::shared_ptr<Instance>> newParent);
|
bool ancestryContinuityCheck(std::optional<std::shared_ptr<Instance>> newParent);
|
||||||
void updateAncestry();
|
void updateAncestry(std::optional<std::shared_ptr<Instance>> child, std::optional<std::shared_ptr<Instance>> newParent);
|
||||||
protected:
|
protected:
|
||||||
bool parentLocked = false;
|
bool parentLocked = false;
|
||||||
std::unique_ptr<MemberMap> memberMap;
|
std::unique_ptr<MemberMap> memberMap;
|
||||||
|
@ -70,6 +70,7 @@ protected:
|
||||||
virtual ~Instance();
|
virtual ~Instance();
|
||||||
|
|
||||||
virtual void OnParentUpdated(std::optional<std::shared_ptr<Instance>> oldParent, std::optional<std::shared_ptr<Instance>> newParent);
|
virtual void OnParentUpdated(std::optional<std::shared_ptr<Instance>> oldParent, std::optional<std::shared_ptr<Instance>> newParent);
|
||||||
|
virtual void OnAncestryChanged(std::optional<std::shared_ptr<Instance>> child, std::optional<std::shared_ptr<Instance>> newParent);
|
||||||
|
|
||||||
// The root data model this object is a descendant of
|
// The root data model this object is a descendant of
|
||||||
std::optional<std::shared_ptr<DataModel>> dataModel();
|
std::optional<std::shared_ptr<DataModel>> dataModel();
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#include "part.h"
|
#include "part.h"
|
||||||
#include "base/instance.h"
|
#include "base/instance.h"
|
||||||
|
#include "common.h"
|
||||||
#include "datatypes/base.h"
|
#include "datatypes/base.h"
|
||||||
#include "datatypes/cframe.h"
|
#include "datatypes/cframe.h"
|
||||||
#include "datatypes/color3.h"
|
#include "datatypes/color3.h"
|
||||||
|
@ -7,7 +8,6 @@
|
||||||
#include "objects/base/member.h"
|
#include "objects/base/member.h"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include "physics/simulation.h"
|
|
||||||
|
|
||||||
using Data::Vector3;
|
using Data::Vector3;
|
||||||
|
|
||||||
|
@ -106,24 +106,26 @@ Part::Part(PartConstructParams params): Instance(&TYPE), cframe(Data::CFrame(par
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// This feels wrong. Get access to PhysicsWorld somehow else? Part will need access to this often though, most likely...
|
|
||||||
extern rp::PhysicsWorld* world;
|
|
||||||
Part::~Part() {
|
Part::~Part() {
|
||||||
// This relies on physicsCommon still existing. Be very careful.
|
// This relies on physicsCommon still existing. Be very careful.
|
||||||
if (this->rigidBody)
|
if (this->rigidBody && workspace())
|
||||||
world->destroyRigidBody(rigidBody);
|
workspace().value()->DestroyRigidBody(rigidBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Part::OnParentUpdated(std::optional<std::shared_ptr<Instance>> oldParent, std::optional<std::shared_ptr<Instance>> newParent) {
|
void Part::OnAncestryChanged(std::optional<std::shared_ptr<Instance>> child, std::optional<std::shared_ptr<Instance>> newParent) {
|
||||||
if (this->rigidBody)
|
if (this->rigidBody)
|
||||||
this->rigidBody->setIsActive(newParent.has_value());
|
this->rigidBody->setIsActive(workspace().has_value());
|
||||||
|
|
||||||
|
if (workspace())
|
||||||
|
workspace().value()->SyncPartPhysics(std::dynamic_pointer_cast<Part>(this->shared_from_this()));
|
||||||
|
|
||||||
// TODO: Sleeping bodies that touch this one also need to be updated
|
// TODO: Sleeping bodies that touch this one also need to be updated
|
||||||
}
|
}
|
||||||
|
|
||||||
void Part::onUpdated(std::string property) {
|
void Part::onUpdated(std::string property) {
|
||||||
syncPartPhysics(std::dynamic_pointer_cast<Part>(this->shared_from_this()));
|
if (workspace())
|
||||||
|
workspace().value()->SyncPartPhysics(std::dynamic_pointer_cast<Part>(this->shared_from_this()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expands provided extents to fit point
|
// Expands provided extents to fit point
|
||||||
|
|
|
@ -24,7 +24,7 @@ struct PartConstructParams {
|
||||||
|
|
||||||
class Part : public Instance {
|
class Part : public Instance {
|
||||||
protected:
|
protected:
|
||||||
void OnParentUpdated(std::optional<std::shared_ptr<Instance>> oldParent, std::optional<std::shared_ptr<Instance>> newParent) override;
|
void OnAncestryChanged(std::optional<std::shared_ptr<Instance>> child, std::optional<std::shared_ptr<Instance>> newParent) override;
|
||||||
void onUpdated(std::string);
|
void onUpdated(std::string);
|
||||||
public:
|
public:
|
||||||
const static InstanceType TYPE;
|
const static InstanceType TYPE;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#include "workspace.h"
|
#include "workspace.h"
|
||||||
#include "objects/base/instance.h"
|
#include "objects/base/instance.h"
|
||||||
|
#include "physics/util.h"
|
||||||
|
|
||||||
const InstanceType Workspace::TYPE = {
|
const InstanceType Workspace::TYPE = {
|
||||||
.super = &Instance::TYPE,
|
.super = &Instance::TYPE,
|
||||||
|
@ -13,5 +14,134 @@ const InstanceType* Workspace::GetClass() {
|
||||||
return &TYPE;
|
return &TYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static rp::PhysicsCommon physicsCommon;
|
||||||
|
|
||||||
Workspace::Workspace(): Service(&TYPE) {
|
Workspace::Workspace(): Service(&TYPE) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Workspace::~Workspace() {
|
||||||
|
if (physicsWorld)
|
||||||
|
physicsCommon.destroyPhysicsWorld(physicsWorld);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Workspace::InitService() {
|
||||||
|
if (initialized) return;
|
||||||
|
initialized = true;
|
||||||
|
|
||||||
|
physicsWorld = physicsCommon.createPhysicsWorld();
|
||||||
|
|
||||||
|
physicsWorld->setGravity(rp::Vector3(0, -196.2, 0));
|
||||||
|
// world->setContactsPositionCorrectionTechnique(rp3d::ContactsPositionCorrectionTechnique::BAUMGARTE_CONTACTS);
|
||||||
|
physicsWorld->setNbIterationsPositionSolver(2000);
|
||||||
|
physicsWorld->setNbIterationsVelocitySolver(2000);
|
||||||
|
|
||||||
|
// physicsWorld->setEventListener(&eventListener);
|
||||||
|
|
||||||
|
// 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> part = std::dynamic_pointer_cast<Part>(obj);
|
||||||
|
this->SyncPartPhysics(part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Workspace::SyncPartPhysics(std::shared_ptr<Part> part) {
|
||||||
|
if (!physicsWorld) return;
|
||||||
|
|
||||||
|
glm::mat4 rotMat = glm::mat4(1.0f);
|
||||||
|
|
||||||
|
rp::Transform transform = part->cframe;
|
||||||
|
if (!part->rigidBody) {
|
||||||
|
part->rigidBody = physicsWorld->createRigidBody(transform);
|
||||||
|
} else {
|
||||||
|
part->rigidBody->setTransform(transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
rp::BoxShape* shape = physicsCommon.createBoxShape(glmToRp(part->size * glm::vec3(0.5f)));
|
||||||
|
|
||||||
|
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->setType(part->anchored ? rp::BodyType::STATIC : rp::BodyType::DYNAMIC);
|
||||||
|
part->rigidBody->getCollider(0)->setCollisionCategoryBits(0b11);
|
||||||
|
|
||||||
|
part->rigidBody->setUserData(&*part);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Workspace::PhysicsStep(float deltaTime) {
|
||||||
|
// Step the simulation a few steps
|
||||||
|
physicsWorld->update(std::min(deltaTime / 2, (1/60.f)));
|
||||||
|
|
||||||
|
// Naive implementation. Parts are only considered so if they are just under Workspace
|
||||||
|
// TODO: Add list of tracked parts in workspace based on their ancestry using inWorkspace property of Instance
|
||||||
|
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> part = std::dynamic_pointer_cast<Part>(obj);
|
||||||
|
const rp::Transform& transform = part->rigidBody->getTransform();
|
||||||
|
part->cframe = Data::CFrame(transform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
RaycastResult::RaycastResult(const rp::RaycastInfo& raycastInfo)
|
||||||
|
: worldPoint(raycastInfo.worldPoint)
|
||||||
|
, worldNormal(raycastInfo.worldNormal)
|
||||||
|
, hitFraction(raycastInfo.hitFraction)
|
||||||
|
, triangleIndex(raycastInfo.triangleIndex)
|
||||||
|
, body(raycastInfo.body)
|
||||||
|
, collider(raycastInfo.collider) {}
|
||||||
|
|
||||||
|
class NearestRayHit : public rp::RaycastCallback {
|
||||||
|
rp::Vector3 startPos;
|
||||||
|
std::optional<RaycastFilter> filter;
|
||||||
|
|
||||||
|
std::optional<RaycastResult> nearestHit;
|
||||||
|
float nearestHitDistance = -1;
|
||||||
|
|
||||||
|
// Order is not guaranteed, so we have to figure out the nearest object using a more sophisticated algorith,
|
||||||
|
rp::decimal notifyRaycastHit(const rp::RaycastInfo& raycastInfo) override {
|
||||||
|
// If the detected object is further away than the nearest object, continue.
|
||||||
|
int distance = (raycastInfo.worldPoint - startPos).length();
|
||||||
|
if (nearestHitDistance != -1 && distance >= nearestHitDistance)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
if (!filter) {
|
||||||
|
nearestHit = raycastInfo;
|
||||||
|
nearestHitDistance = distance;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Part> part = partFromBody(raycastInfo.body);
|
||||||
|
FilterResult result = filter.value()(part);
|
||||||
|
if (result == FilterResult::BLOCK) {
|
||||||
|
nearestHit = std::nullopt;
|
||||||
|
nearestHitDistance = distance;
|
||||||
|
return 1;
|
||||||
|
} else if (result == FilterResult::TARGET) {
|
||||||
|
nearestHit = raycastInfo;
|
||||||
|
nearestHitDistance = distance;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
NearestRayHit(rp::Vector3 startPos, std::optional<RaycastFilter> filter = std::nullopt) : startPos(startPos), filter(filter) {}
|
||||||
|
std::optional<const RaycastResult> getNearestHit() { return nearestHit; };
|
||||||
|
};
|
||||||
|
|
||||||
|
std::optional<const RaycastResult> Workspace::CastRayNearest(glm::vec3 point, glm::vec3 rotation, float maxLength, std::optional<RaycastFilter> filter, unsigned short categoryMaskBits) {
|
||||||
|
rp::Ray ray(glmToRp(point), glmToRp(glm::normalize(rotation)) * maxLength);
|
||||||
|
NearestRayHit rayHit(glmToRp(point), filter);
|
||||||
|
physicsWorld->raycast(ray, &rayHit, categoryMaskBits);
|
||||||
|
return rayHit.getNearestHit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Workspace::DestroyRigidBody(rp::RigidBody* rigidBody) {
|
||||||
|
physicsWorld->destroyRigidBody(rigidBody);
|
||||||
|
}
|
|
@ -1,17 +1,52 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "base.h"
|
|
||||||
#include "objects/base/service.h"
|
#include "objects/base/service.h"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <reactphysics3d/body/RigidBody.h>
|
||||||
|
#include <reactphysics3d/engine/PhysicsCommon.h>
|
||||||
|
#include <reactphysics3d/engine/PhysicsWorld.h>
|
||||||
|
|
||||||
|
struct RaycastResult {
|
||||||
|
rp::Vector3 worldPoint;
|
||||||
|
rp::Vector3 worldNormal;
|
||||||
|
rp::decimal hitFraction;
|
||||||
|
int triangleIndex;
|
||||||
|
rp::Body* body;
|
||||||
|
rp::Collider* collider;
|
||||||
|
|
||||||
|
RaycastResult(const rp::RaycastInfo& raycastInfo);
|
||||||
|
};
|
||||||
|
|
||||||
|
enum FilterResult {
|
||||||
|
TARGET, // The object is captured
|
||||||
|
BLOCK, // The object blocks any objects behind it, but is not captured
|
||||||
|
PASS, // The object is transparent, ignore it
|
||||||
|
};
|
||||||
|
|
||||||
|
class Part;
|
||||||
|
typedef std::function<FilterResult(std::shared_ptr<Part>)> RaycastFilter;
|
||||||
|
|
||||||
class Workspace : public Service {
|
class Workspace : public Service {
|
||||||
//private:
|
rp::PhysicsWorld *physicsWorld = nullptr;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void InitService() override;
|
||||||
|
bool initialized = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
const static InstanceType TYPE;
|
const static InstanceType TYPE;
|
||||||
|
|
||||||
Workspace();
|
Workspace();
|
||||||
|
~Workspace();
|
||||||
|
|
||||||
// static inline std::shared_ptr<Workspace> New() { return std::make_shared<Workspace>(); };
|
// static inline std::shared_ptr<Workspace> New() { return std::make_shared<Workspace>(); };
|
||||||
static inline std::shared_ptr<Instance> Create() { return std::make_shared<Workspace>(); };
|
static inline std::shared_ptr<Instance> Create() { return std::make_shared<Workspace>(); };
|
||||||
virtual const InstanceType* GetClass() override;
|
virtual const InstanceType* GetClass() override;
|
||||||
|
|
||||||
|
void SyncPartPhysics(std::shared_ptr<Part> part);
|
||||||
|
void DestroyRigidBody(rp::RigidBody* rigidBody);
|
||||||
|
|
||||||
|
void PhysicsStep(float deltaTime);
|
||||||
|
std::optional<const RaycastResult> CastRayNearest(glm::vec3 point, glm::vec3 rotation, float maxLength, std::optional<RaycastFilter> filter = std::nullopt, unsigned short categoryMaskBits = 0xFFFF);
|
||||||
|
|
||||||
};
|
};
|
|
@ -1,141 +0,0 @@
|
||||||
#include <cstdio>
|
|
||||||
#include <glm/ext/matrix_float3x3.hpp>
|
|
||||||
#include <glm/gtc/quaternion.hpp>
|
|
||||||
#include <memory>
|
|
||||||
#include <reactphysics3d/collision/RaycastInfo.h>
|
|
||||||
#include <reactphysics3d/collision/shapes/BoxShape.h>
|
|
||||||
#include <reactphysics3d/collision/shapes/CollisionShape.h>
|
|
||||||
#include <reactphysics3d/components/RigidBodyComponents.h>
|
|
||||||
#include <reactphysics3d/configuration.h>
|
|
||||||
#include <reactphysics3d/engine/EventListener.h>
|
|
||||||
#include <reactphysics3d/engine/PhysicsCommon.h>
|
|
||||||
#include <reactphysics3d/mathematics/Quaternion.h>
|
|
||||||
#include <reactphysics3d/mathematics/Ray.h>
|
|
||||||
#include <reactphysics3d/mathematics/Transform.h>
|
|
||||||
#include <reactphysics3d/mathematics/Vector3.h>
|
|
||||||
#include <reactphysics3d/memory/DefaultAllocator.h>
|
|
||||||
#include <reactphysics3d/memory/MemoryAllocator.h>
|
|
||||||
#include <reactphysics3d/reactphysics3d.h>
|
|
||||||
#include "../common.h"
|
|
||||||
#include "../objects/part.h"
|
|
||||||
#include "datatypes/cframe.h"
|
|
||||||
#include "util.h"
|
|
||||||
|
|
||||||
#include "simulation.h"
|
|
||||||
|
|
||||||
namespace rp = reactphysics3d;
|
|
||||||
|
|
||||||
class PhysicsListener : public rp::EventListener {
|
|
||||||
void onContact(const CollisionCallback::CallbackData& /*callbackData*/) override {
|
|
||||||
// printf("Collision occurred!\n");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
rp::PhysicsCommon* physicsCommon;
|
|
||||||
rp::PhysicsWorld* world;
|
|
||||||
PhysicsListener eventListener;
|
|
||||||
|
|
||||||
void simulationInit() {
|
|
||||||
physicsCommon = new rp::PhysicsCommon; // I allocate this on the heap to ensure it exists while Parts are getting destructed. This is probably not great
|
|
||||||
world = physicsCommon->createPhysicsWorld();
|
|
||||||
|
|
||||||
world->setGravity(rp::Vector3(0, -196.2, 0));
|
|
||||||
// world->setContactsPositionCorrectionTechnique(rp3d::ContactsPositionCorrectionTechnique::BAUMGARTE_CONTACTS);
|
|
||||||
world->setNbIterationsPositionSolver(2000);
|
|
||||||
world->setNbIterationsVelocitySolver(2000);
|
|
||||||
|
|
||||||
world->setEventListener(&eventListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
void syncPartPhysics(std::shared_ptr<Part> part) {
|
|
||||||
glm::mat4 rotMat = glm::mat4(1.0f);
|
|
||||||
|
|
||||||
rp::Transform transform = part->cframe;
|
|
||||||
if (!part->rigidBody) {
|
|
||||||
part->rigidBody = world->createRigidBody(transform);
|
|
||||||
} else {
|
|
||||||
part->rigidBody->setTransform(transform);
|
|
||||||
}
|
|
||||||
|
|
||||||
rp::BoxShape* shape = physicsCommon->createBoxShape(glmToRp(part->size * glm::vec3(0.5f)));
|
|
||||||
|
|
||||||
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->setType(part->anchored ? rp::BodyType::STATIC : rp::BodyType::DYNAMIC);
|
|
||||||
part->rigidBody->getCollider(0)->setCollisionCategoryBits(0b11);
|
|
||||||
|
|
||||||
part->rigidBody->setUserData(&*part);
|
|
||||||
}
|
|
||||||
|
|
||||||
void physicsStep(float deltaTime) {
|
|
||||||
// Step the simulation a few steps
|
|
||||||
world->update(std::min(deltaTime / 2, (1/60.f)));
|
|
||||||
|
|
||||||
// Naive implementation. Parts are only considered so if they are just under Workspace
|
|
||||||
// TODO: Add list of tracked parts in workspace based on their ancestry using inWorkspace property of Instance
|
|
||||||
for (auto it = gWorkspace()->GetDescendantsStart(); it != gWorkspace()->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> part = std::dynamic_pointer_cast<Part>(obj);
|
|
||||||
const rp::Transform& transform = part->rigidBody->getTransform();
|
|
||||||
part->cframe = Data::CFrame(transform);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RaycastResult::RaycastResult(const rp::RaycastInfo& raycastInfo)
|
|
||||||
: worldPoint(raycastInfo.worldPoint)
|
|
||||||
, worldNormal(raycastInfo.worldNormal)
|
|
||||||
, hitFraction(raycastInfo.hitFraction)
|
|
||||||
, triangleIndex(raycastInfo.triangleIndex)
|
|
||||||
, body(raycastInfo.body)
|
|
||||||
, collider(raycastInfo.collider) {}
|
|
||||||
|
|
||||||
class NearestRayHit : public rp::RaycastCallback {
|
|
||||||
rp::Vector3 startPos;
|
|
||||||
std::optional<RaycastFilter> filter;
|
|
||||||
|
|
||||||
std::optional<RaycastResult> nearestHit;
|
|
||||||
float nearestHitDistance = -1;
|
|
||||||
|
|
||||||
// Order is not guaranteed, so we have to figure out the nearest object using a more sophisticated algorith,
|
|
||||||
rp::decimal notifyRaycastHit(const rp::RaycastInfo& raycastInfo) override {
|
|
||||||
// If the detected object is further away than the nearest object, continue.
|
|
||||||
int distance = (raycastInfo.worldPoint - startPos).length();
|
|
||||||
if (nearestHitDistance != -1 && distance >= nearestHitDistance)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
if (!filter) {
|
|
||||||
nearestHit = raycastInfo;
|
|
||||||
nearestHitDistance = distance;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<Part> part = partFromBody(raycastInfo.body);
|
|
||||||
FilterResult result = filter.value()(part);
|
|
||||||
if (result == FilterResult::BLOCK) {
|
|
||||||
nearestHit = std::nullopt;
|
|
||||||
nearestHitDistance = distance;
|
|
||||||
return 1;
|
|
||||||
} else if (result == FilterResult::TARGET) {
|
|
||||||
nearestHit = raycastInfo;
|
|
||||||
nearestHitDistance = distance;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
|
||||||
NearestRayHit(rp::Vector3 startPos, std::optional<RaycastFilter> filter = std::nullopt) : startPos(startPos), filter(filter) {}
|
|
||||||
std::optional<const RaycastResult> getNearestHit() { return nearestHit; };
|
|
||||||
};
|
|
||||||
|
|
||||||
std::optional<const RaycastResult> castRayNearest(glm::vec3 point, glm::vec3 rotation, float maxLength, std::optional<RaycastFilter> filter, unsigned short categoryMaskBits) {
|
|
||||||
rp::Ray ray(glmToRp(point), glmToRp(glm::normalize(rotation)) * maxLength);
|
|
||||||
NearestRayHit rayHit(glmToRp(point), filter);
|
|
||||||
world->raycast(ray, &rayHit, categoryMaskBits);
|
|
||||||
return rayHit.getNearestHit();
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../objects/part.h"
|
|
||||||
#include <glm/ext/vector_float3.hpp>
|
|
||||||
#include <memory>
|
|
||||||
#include <reactphysics3d/collision/RaycastInfo.h>
|
|
||||||
|
|
||||||
struct RaycastResult {
|
|
||||||
rp::Vector3 worldPoint;
|
|
||||||
rp::Vector3 worldNormal;
|
|
||||||
rp::decimal hitFraction;
|
|
||||||
int triangleIndex;
|
|
||||||
rp::Body* body;
|
|
||||||
rp::Collider* collider;
|
|
||||||
|
|
||||||
RaycastResult(const rp::RaycastInfo& raycastInfo);
|
|
||||||
};
|
|
||||||
|
|
||||||
enum FilterResult {
|
|
||||||
TARGET, // The object is captured
|
|
||||||
BLOCK, // The object blocks any objects behind it, but is not captured
|
|
||||||
PASS, // The object is transparent, ignore it
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef std::function<FilterResult(std::shared_ptr<Part>)> RaycastFilter;
|
|
||||||
|
|
||||||
void simulationInit();
|
|
||||||
void syncPartPhysics(std::shared_ptr<Part> part);
|
|
||||||
void physicsStep(float deltaTime);
|
|
||||||
std::optional<const RaycastResult> castRayNearest(glm::vec3 point, glm::vec3 rotation, float maxLength, std::optional<RaycastFilter> filter = std::nullopt, unsigned short categoryMaskBits = 0xFFFF);
|
|
|
@ -3,7 +3,6 @@
|
||||||
#include "mainglwidget.h"
|
#include "mainglwidget.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "math_helper.h"
|
#include "math_helper.h"
|
||||||
#include "physics/simulation.h"
|
|
||||||
#include "physics/util.h"
|
#include "physics/util.h"
|
||||||
#include "rendering/renderer.h"
|
#include "rendering/renderer.h"
|
||||||
#include "rendering/shader.h"
|
#include "rendering/shader.h"
|
||||||
|
@ -112,7 +111,7 @@ void MainGLWidget::handleObjectDrag(QMouseEvent* evt) {
|
||||||
QPoint position = evt->pos();
|
QPoint position = evt->pos();
|
||||||
|
|
||||||
glm::vec3 pointDir = camera.getScreenDirection(glm::vec2(position.x(), position.y()), glm::vec2(width(), height()));
|
glm::vec3 pointDir = camera.getScreenDirection(glm::vec2(position.x(), position.y()), glm::vec2(width(), height()));
|
||||||
std::optional<const RaycastResult> rayHit = castRayNearest(camera.cameraPos, pointDir, 50000, [](std::shared_ptr<Part> part) {
|
std::optional<const RaycastResult> rayHit = gWorkspace()->CastRayNearest(camera.cameraPos, pointDir, 50000, [](std::shared_ptr<Part> part) {
|
||||||
return (part == draggingObject->lock()) ? FilterResult::PASS : FilterResult::TARGET;
|
return (part == draggingObject->lock()) ? FilterResult::PASS : FilterResult::TARGET;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -145,7 +144,7 @@ void MainGLWidget::handleObjectDrag(QMouseEvent* evt) {
|
||||||
|
|
||||||
draggingObject->lock()->cframe = newFrame + unsinkOffset;
|
draggingObject->lock()->cframe = newFrame + unsinkOffset;
|
||||||
|
|
||||||
syncPartPhysics(draggingObject->lock());
|
gWorkspace()->SyncPartPhysics(draggingObject->lock());
|
||||||
}
|
}
|
||||||
|
|
||||||
inline glm::vec3 vec3fy(glm::vec4 vec) {
|
inline glm::vec3 vec3fy(glm::vec4 vec) {
|
||||||
|
@ -222,7 +221,7 @@ void MainGLWidget::handleLinearTransform(QMouseEvent* evt) {
|
||||||
if (mainWindow()->editSoundEffects && (oldSize != part->size) && QFile::exists("./assets/excluded/switch.wav"))
|
if (mainWindow()->editSoundEffects && (oldSize != part->size) && QFile::exists("./assets/excluded/switch.wav"))
|
||||||
playSound("./assets/excluded/switch.wav");
|
playSound("./assets/excluded/switch.wav");
|
||||||
|
|
||||||
syncPartPhysics(std::dynamic_pointer_cast<Part>(editorToolHandles->adornee->lock()));
|
gWorkspace()->SyncPartPhysics(std::dynamic_pointer_cast<Part>(editorToolHandles->adornee->lock()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also implemented based on Godot: [c7ea8614](godot/editor/plugins/canvas_item_editor_plugin.cpp#L1490)
|
// Also implemented based on Godot: [c7ea8614](godot/editor/plugins/canvas_item_editor_plugin.cpp#L1490)
|
||||||
|
@ -264,7 +263,7 @@ void MainGLWidget::handleRotationalTransform(QMouseEvent* evt) {
|
||||||
|
|
||||||
part->cframe = initialFrame * Data::CFrame::FromEulerAnglesXYZ(-angles);
|
part->cframe = initialFrame * Data::CFrame::FromEulerAnglesXYZ(-angles);
|
||||||
|
|
||||||
syncPartPhysics(std::dynamic_pointer_cast<Part>(editorToolHandles->adornee->lock()));
|
gWorkspace()->SyncPartPhysics(std::dynamic_pointer_cast<Part>(editorToolHandles->adornee->lock()));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<HandleFace> MainGLWidget::raycastHandle(glm::vec3 pointDir) {
|
std::optional<HandleFace> MainGLWidget::raycastHandle(glm::vec3 pointDir) {
|
||||||
|
@ -282,7 +281,7 @@ void MainGLWidget::handleCursorChange(QMouseEvent* evt) {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::optional<const RaycastResult> rayHit = castRayNearest(camera.cameraPos, pointDir, 50000);
|
std::optional<const RaycastResult> rayHit = gWorkspace()->CastRayNearest(camera.cameraPos, pointDir, 50000);
|
||||||
if (rayHit && partFromBody(rayHit->body)->name != "Baseplate") {
|
if (rayHit && partFromBody(rayHit->body)->name != "Baseplate") {
|
||||||
setCursor(Qt::OpenHandCursor);
|
setCursor(Qt::OpenHandCursor);
|
||||||
return;
|
return;
|
||||||
|
@ -339,7 +338,7 @@ void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// raycast part
|
// raycast part
|
||||||
std::optional<const RaycastResult> rayHit = castRayNearest(camera.cameraPos, pointDir, 50000);
|
std::optional<const RaycastResult> rayHit = gWorkspace()->CastRayNearest(camera.cameraPos, pointDir, 50000);
|
||||||
if (!rayHit || !partFromBody(rayHit->body)) return;
|
if (!rayHit || !partFromBody(rayHit->body)) return;
|
||||||
std::shared_ptr<Part> part = partFromBody(rayHit->body);
|
std::shared_ptr<Part> part = partFromBody(rayHit->body);
|
||||||
if (part->name == "Baseplate") return;
|
if (part->name == "Baseplate") return;
|
||||||
|
@ -416,7 +415,7 @@ void MainGLWidget::keyPressEvent(QKeyEvent* evt) {
|
||||||
.size = glm::vec3(1, 1, 1),
|
.size = glm::vec3(1, 1, 1),
|
||||||
.color = glm::vec3(1.0f, 0.5f, 0.31f),
|
.color = glm::vec3(1.0f, 0.5f, 0.31f),
|
||||||
}));
|
}));
|
||||||
syncPartPhysics(lastPart);
|
gWorkspace()->SyncPartPhysics(lastPart);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (evt->key() == Qt::Key_U)
|
if (evt->key() == Qt::Key_U)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "./ui_mainwindow.h"
|
#include "./ui_mainwindow.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "physics/simulation.h"
|
|
||||||
#include <qclipboard.h>
|
#include <qclipboard.h>
|
||||||
#include <qmessagebox.h>
|
#include <qmessagebox.h>
|
||||||
#include <qmimedata.h>
|
#include <qmimedata.h>
|
||||||
|
@ -160,9 +159,6 @@ MainWindow::MainWindow(QWidget *parent)
|
||||||
gDataModel->Init();
|
gDataModel->Init();
|
||||||
ui->explorerView->updateRoot(gDataModel);
|
ui->explorerView->updateRoot(gDataModel);
|
||||||
|
|
||||||
// TODO: Remove this and use a proper fix. This *WILL* cause a leak and memory issues in the future
|
|
||||||
simulationInit();
|
|
||||||
|
|
||||||
// Baseplate
|
// Baseplate
|
||||||
gWorkspace()->AddChild(ui->mainWidget->lastPart = Part::New({
|
gWorkspace()->AddChild(ui->mainWidget->lastPart = Part::New({
|
||||||
.position = glm::vec3(0, -5, 0),
|
.position = glm::vec3(0, -5, 0),
|
||||||
|
@ -172,7 +168,7 @@ MainWindow::MainWindow(QWidget *parent)
|
||||||
.anchored = true,
|
.anchored = true,
|
||||||
}));
|
}));
|
||||||
ui->mainWidget->lastPart->name = "Baseplate";
|
ui->mainWidget->lastPart->name = "Baseplate";
|
||||||
syncPartPhysics(ui->mainWidget->lastPart);
|
gWorkspace()->SyncPartPhysics(ui->mainWidget->lastPart);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(ui->actionSave, &QAction::triggered, this, [&]() {
|
connect(ui->actionSave, &QAction::triggered, this, [&]() {
|
||||||
|
@ -201,6 +197,7 @@ MainWindow::MainWindow(QWidget *parent)
|
||||||
// simulationInit();
|
// simulationInit();
|
||||||
std::shared_ptr<DataModel> newModel = DataModel::LoadFromFile(path.value());
|
std::shared_ptr<DataModel> newModel = DataModel::LoadFromFile(path.value());
|
||||||
gDataModel = newModel;
|
gDataModel = newModel;
|
||||||
|
newModel->Init();
|
||||||
ui->explorerView->updateRoot(newModel);
|
ui->explorerView->updateRoot(newModel);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -333,8 +330,6 @@ MainWindow::MainWindow(QWidget *parent)
|
||||||
|
|
||||||
// ui->explorerView->Init(ui);
|
// ui->explorerView->Init(ui);
|
||||||
|
|
||||||
simulationInit();
|
|
||||||
|
|
||||||
// Baseplate
|
// Baseplate
|
||||||
gWorkspace()->AddChild(ui->mainWidget->lastPart = Part::New({
|
gWorkspace()->AddChild(ui->mainWidget->lastPart = Part::New({
|
||||||
.position = glm::vec3(0, -5, 0),
|
.position = glm::vec3(0, -5, 0),
|
||||||
|
@ -344,7 +339,7 @@ MainWindow::MainWindow(QWidget *parent)
|
||||||
.anchored = true,
|
.anchored = true,
|
||||||
}));
|
}));
|
||||||
ui->mainWidget->lastPart->name = "Baseplate";
|
ui->mainWidget->lastPart->name = "Baseplate";
|
||||||
syncPartPhysics(ui->mainWidget->lastPart);
|
gWorkspace()->SyncPartPhysics(ui->mainWidget->lastPart);
|
||||||
|
|
||||||
gWorkspace()->AddChild(ui->mainWidget->lastPart = Part::New({
|
gWorkspace()->AddChild(ui->mainWidget->lastPart = Part::New({
|
||||||
.position = glm::vec3(0),
|
.position = glm::vec3(0),
|
||||||
|
@ -352,7 +347,7 @@ MainWindow::MainWindow(QWidget *parent)
|
||||||
.size = glm::vec3(4, 1.2, 2),
|
.size = glm::vec3(4, 1.2, 2),
|
||||||
.color = glm::vec3(0.639216f, 0.635294f, 0.647059f),
|
.color = glm::vec3(0.639216f, 0.635294f, 0.647059f),
|
||||||
}));
|
}));
|
||||||
syncPartPhysics(ui->mainWidget->lastPart);
|
gWorkspace()->SyncPartPhysics(ui->mainWidget->lastPart);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::closeEvent(QCloseEvent* evt) {
|
void MainWindow::closeEvent(QCloseEvent* evt) {
|
||||||
|
@ -399,7 +394,7 @@ void MainWindow::timerEvent(QTimerEvent* evt) {
|
||||||
lastTime = std::chrono::steady_clock::now();
|
lastTime = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
if (simulationPlaying)
|
if (simulationPlaying)
|
||||||
physicsStep(deltaTime);
|
gWorkspace()->PhysicsStep(deltaTime);
|
||||||
ui->mainWidget->update();
|
ui->mainWidget->update();
|
||||||
ui->mainWidget->updateCycle();
|
ui->mainWidget->updateCycle();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue