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 <GLFW/glfw3.h>
|
||||
#include "objects/part.h"
|
||||
#include "physics/simulation.h"
|
||||
#include "rendering/renderer.h"
|
||||
#include "common.h"
|
||||
|
||||
|
@ -34,7 +33,6 @@ int main() {
|
|||
glewInit();
|
||||
|
||||
gDataModel->Init();
|
||||
simulationInit();
|
||||
renderInit(window, 1200, 900);
|
||||
|
||||
// Baseplate
|
||||
|
@ -56,7 +54,7 @@ int main() {
|
|||
for (InstanceRef inst : gWorkspace()->GetChildren()) {
|
||||
if (inst->GetClass()->className != "Part") continue;
|
||||
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
|
||||
syncPartPhysics(part);
|
||||
gWorkspace()->SyncPartPhysics(part);
|
||||
}
|
||||
|
||||
float lastTime = glfwGetTime();
|
||||
|
@ -65,7 +63,7 @@ int main() {
|
|||
lastTime = glfwGetTime();
|
||||
|
||||
processInput(window);
|
||||
physicsStep(deltaTime);
|
||||
gWorkspace()->PhysicsStep(deltaTime);
|
||||
render(window);
|
||||
|
||||
glfwSwapBuffers(window);
|
||||
|
@ -103,15 +101,15 @@ void processInput(GLFWwindow* window) {
|
|||
shiftFactor *= deltaTime;
|
||||
if (glfwGetKey(window, GLFW_KEY_X) == GLFW_PRESS) {
|
||||
// lastPart->rotation *= glm::angleAxis(shiftFactor, glm::vec3(1, 0, 0));
|
||||
syncPartPhysics(lastPart);
|
||||
gWorkspace()->SyncPartPhysics(lastPart);
|
||||
}
|
||||
if (glfwGetKey(window, GLFW_KEY_Y) == GLFW_PRESS) {
|
||||
// lastPart->rotation *= glm::angleAxis(shiftFactor, glm::vec3(0, 1, 0));
|
||||
syncPartPhysics(lastPart);
|
||||
gWorkspace()->SyncPartPhysics(lastPart);
|
||||
}
|
||||
if (glfwGetKey(window, GLFW_KEY_Z) == GLFW_PRESS) {
|
||||
// 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),
|
||||
.color = glm::vec3(1.0f, 0.5f, 0.31f),
|
||||
}));
|
||||
syncPartPhysics(lastPart);
|
||||
gWorkspace()->SyncPartPhysics(lastPart);
|
||||
}
|
||||
|
||||
float shiftFactor = (mods & GLFW_MOD_SHIFT) ? -0.2 : 0.2;
|
||||
if (mode == 0) {
|
||||
if (key == GLFW_KEY_X && action == GLFW_PRESS) {
|
||||
// lastPart->position.x += shiftFactor;
|
||||
syncPartPhysics(lastPart);
|
||||
gWorkspace()->SyncPartPhysics(lastPart);
|
||||
}
|
||||
if (key == GLFW_KEY_Y && action == GLFW_PRESS) {
|
||||
// lastPart->position.y += shiftFactor;
|
||||
syncPartPhysics(lastPart);
|
||||
gWorkspace()->SyncPartPhysics(lastPart);
|
||||
}
|
||||
if (key == GLFW_KEY_Z && action == GLFW_PRESS) {
|
||||
// lastPart->position.z += shiftFactor;
|
||||
syncPartPhysics(lastPart);
|
||||
gWorkspace()->SyncPartPhysics(lastPart);
|
||||
}
|
||||
} else if (mode == 1) {
|
||||
if (key == GLFW_KEY_X && action == GLFW_PRESS) {
|
||||
lastPart->size.x += shiftFactor;
|
||||
syncPartPhysics(lastPart);
|
||||
gWorkspace()->SyncPartPhysics(lastPart);
|
||||
}
|
||||
if (key == GLFW_KEY_Y && action == GLFW_PRESS) {
|
||||
lastPart->size.y += shiftFactor;
|
||||
syncPartPhysics(lastPart);
|
||||
gWorkspace()->SyncPartPhysics(lastPart);
|
||||
}
|
||||
if (key == GLFW_KEY_Z && action == GLFW_PRESS) {
|
||||
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;
|
||||
}
|
||||
|
||||
// Algorithm for updating descendant's dataModel and workspace fields
|
||||
if (oldDataModel != _dataModel || oldWorkspace != _workspace)
|
||||
updateAncestry();
|
||||
updateAncestry(this->shared<Instance>(), newParent);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Instance::updateAncestry() {
|
||||
void Instance::updateAncestry(std::optional<std::shared_ptr<Instance>> updatedChild, std::optional<std::shared_ptr<Instance>> newParent) {
|
||||
if (GetParent()) {
|
||||
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();
|
||||
|
@ -104,9 +102,11 @@ void Instance::updateAncestry() {
|
|||
this->_workspace = std::nullopt;
|
||||
}
|
||||
|
||||
OnAncestryChanged(updatedChild, newParent);
|
||||
|
||||
// Update ancestry in descendants
|
||||
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
|
||||
}
|
||||
|
||||
void Instance::OnAncestryChanged(std::optional<std::shared_ptr<Instance>> child, std::optional<std::shared_ptr<Instance>> newParent) {
|
||||
// Empty stub
|
||||
}
|
||||
|
||||
// Properties
|
||||
|
||||
tl::expected<Data::Variant, MemberNotFound> Instance::GetPropertyValue(std::string name) {
|
||||
|
|
|
@ -61,7 +61,7 @@ private:
|
|||
std::optional<std::weak_ptr<Workspace>> _workspace;
|
||||
|
||||
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:
|
||||
bool parentLocked = false;
|
||||
std::unique_ptr<MemberMap> memberMap;
|
||||
|
@ -70,6 +70,7 @@ protected:
|
|||
virtual ~Instance();
|
||||
|
||||
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
|
||||
std::optional<std::shared_ptr<DataModel>> dataModel();
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "part.h"
|
||||
#include "base/instance.h"
|
||||
#include "common.h"
|
||||
#include "datatypes/base.h"
|
||||
#include "datatypes/cframe.h"
|
||||
#include "datatypes/color3.h"
|
||||
|
@ -7,7 +8,6 @@
|
|||
#include "objects/base/member.h"
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include "physics/simulation.h"
|
||||
|
||||
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() {
|
||||
// This relies on physicsCommon still existing. Be very careful.
|
||||
if (this->rigidBody)
|
||||
world->destroyRigidBody(rigidBody);
|
||||
if (this->rigidBody && workspace())
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -24,7 +24,7 @@ struct PartConstructParams {
|
|||
|
||||
class Part : public Instance {
|
||||
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);
|
||||
public:
|
||||
const static InstanceType TYPE;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "workspace.h"
|
||||
#include "objects/base/instance.h"
|
||||
#include "physics/util.h"
|
||||
|
||||
const InstanceType Workspace::TYPE = {
|
||||
.super = &Instance::TYPE,
|
||||
|
@ -13,5 +14,134 @@ const InstanceType* Workspace::GetClass() {
|
|||
return &TYPE;
|
||||
}
|
||||
|
||||
static rp::PhysicsCommon physicsCommon;
|
||||
|
||||
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
|
||||
|
||||
#include "base.h"
|
||||
#include "objects/base/service.h"
|
||||
#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 {
|
||||
//private:
|
||||
rp::PhysicsWorld *physicsWorld = nullptr;
|
||||
|
||||
protected:
|
||||
void InitService() override;
|
||||
bool initialized = false;
|
||||
|
||||
public:
|
||||
const static InstanceType TYPE;
|
||||
|
||||
Workspace();
|
||||
~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>(); };
|
||||
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 "common.h"
|
||||
#include "math_helper.h"
|
||||
#include "physics/simulation.h"
|
||||
#include "physics/util.h"
|
||||
#include "rendering/renderer.h"
|
||||
#include "rendering/shader.h"
|
||||
|
@ -112,7 +111,7 @@ void MainGLWidget::handleObjectDrag(QMouseEvent* evt) {
|
|||
QPoint position = evt->pos();
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
|
@ -145,7 +144,7 @@ void MainGLWidget::handleObjectDrag(QMouseEvent* evt) {
|
|||
|
||||
draggingObject->lock()->cframe = newFrame + unsinkOffset;
|
||||
|
||||
syncPartPhysics(draggingObject->lock());
|
||||
gWorkspace()->SyncPartPhysics(draggingObject->lock());
|
||||
}
|
||||
|
||||
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"))
|
||||
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)
|
||||
|
@ -264,7 +263,7 @@ void MainGLWidget::handleRotationalTransform(QMouseEvent* evt) {
|
|||
|
||||
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) {
|
||||
|
@ -282,7 +281,7 @@ void MainGLWidget::handleCursorChange(QMouseEvent* evt) {
|
|||
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") {
|
||||
setCursor(Qt::OpenHandCursor);
|
||||
return;
|
||||
|
@ -339,7 +338,7 @@ void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
|
|||
}
|
||||
|
||||
// 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;
|
||||
std::shared_ptr<Part> part = partFromBody(rayHit->body);
|
||||
if (part->name == "Baseplate") return;
|
||||
|
@ -416,7 +415,7 @@ void MainGLWidget::keyPressEvent(QKeyEvent* evt) {
|
|||
.size = glm::vec3(1, 1, 1),
|
||||
.color = glm::vec3(1.0f, 0.5f, 0.31f),
|
||||
}));
|
||||
syncPartPhysics(lastPart);
|
||||
gWorkspace()->SyncPartPhysics(lastPart);
|
||||
}
|
||||
|
||||
if (evt->key() == Qt::Key_U)
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#include "mainwindow.h"
|
||||
#include "./ui_mainwindow.h"
|
||||
#include "common.h"
|
||||
#include "physics/simulation.h"
|
||||
#include <qclipboard.h>
|
||||
#include <qmessagebox.h>
|
||||
#include <qmimedata.h>
|
||||
|
@ -160,9 +159,6 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
gDataModel->Init();
|
||||
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
|
||||
gWorkspace()->AddChild(ui->mainWidget->lastPart = Part::New({
|
||||
.position = glm::vec3(0, -5, 0),
|
||||
|
@ -172,7 +168,7 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
.anchored = true,
|
||||
}));
|
||||
ui->mainWidget->lastPart->name = "Baseplate";
|
||||
syncPartPhysics(ui->mainWidget->lastPart);
|
||||
gWorkspace()->SyncPartPhysics(ui->mainWidget->lastPart);
|
||||
});
|
||||
|
||||
connect(ui->actionSave, &QAction::triggered, this, [&]() {
|
||||
|
@ -201,6 +197,7 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
// simulationInit();
|
||||
std::shared_ptr<DataModel> newModel = DataModel::LoadFromFile(path.value());
|
||||
gDataModel = newModel;
|
||||
newModel->Init();
|
||||
ui->explorerView->updateRoot(newModel);
|
||||
});
|
||||
|
||||
|
@ -333,8 +330,6 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
|
||||
// ui->explorerView->Init(ui);
|
||||
|
||||
simulationInit();
|
||||
|
||||
// Baseplate
|
||||
gWorkspace()->AddChild(ui->mainWidget->lastPart = Part::New({
|
||||
.position = glm::vec3(0, -5, 0),
|
||||
|
@ -344,7 +339,7 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
.anchored = true,
|
||||
}));
|
||||
ui->mainWidget->lastPart->name = "Baseplate";
|
||||
syncPartPhysics(ui->mainWidget->lastPart);
|
||||
gWorkspace()->SyncPartPhysics(ui->mainWidget->lastPart);
|
||||
|
||||
gWorkspace()->AddChild(ui->mainWidget->lastPart = Part::New({
|
||||
.position = glm::vec3(0),
|
||||
|
@ -352,7 +347,7 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
.size = glm::vec3(4, 1.2, 2),
|
||||
.color = glm::vec3(0.639216f, 0.635294f, 0.647059f),
|
||||
}));
|
||||
syncPartPhysics(ui->mainWidget->lastPart);
|
||||
gWorkspace()->SyncPartPhysics(ui->mainWidget->lastPart);
|
||||
}
|
||||
|
||||
void MainWindow::closeEvent(QCloseEvent* evt) {
|
||||
|
@ -399,7 +394,7 @@ void MainWindow::timerEvent(QTimerEvent* evt) {
|
|||
lastTime = std::chrono::steady_clock::now();
|
||||
|
||||
if (simulationPlaying)
|
||||
physicsStep(deltaTime);
|
||||
gWorkspace()->PhysicsStep(deltaTime);
|
||||
ui->mainWidget->update();
|
||||
ui->mainWidget->updateCycle();
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue