refactor(physics): moved implementation-specific workspace functions into physics/world
This commit is contained in:
parent
d922dd3727
commit
bb81aff4fd
8 changed files with 274 additions and 276 deletions
|
@ -57,6 +57,9 @@ set(SOURCES
|
||||||
src/rendering/font.h
|
src/rendering/font.h
|
||||||
src/rendering/defaultmeshes.h
|
src/rendering/defaultmeshes.h
|
||||||
src/rendering/texture3d.cpp
|
src/rendering/texture3d.cpp
|
||||||
|
src/physics/util.h
|
||||||
|
src/physics/world.h
|
||||||
|
src/physics/world.cpp
|
||||||
src/logger.cpp
|
src/logger.cpp
|
||||||
src/handles.h
|
src/handles.h
|
||||||
src/timeutil.h
|
src/timeutil.h
|
||||||
|
@ -126,7 +129,6 @@ set(SOURCES
|
||||||
src/panic.h
|
src/panic.h
|
||||||
src/lua/instancelib.cpp
|
src/lua/instancelib.cpp
|
||||||
src/timeutil.cpp
|
src/timeutil.cpp
|
||||||
src/physics/util.h
|
|
||||||
src/luaapis.h
|
src/luaapis.h
|
||||||
src/math_helper.h
|
src/math_helper.h
|
||||||
)
|
)
|
||||||
|
|
|
@ -29,8 +29,7 @@ BasePart::BasePart(const InstanceType* type, PartConstructParams params): PVInst
|
||||||
BasePart::~BasePart() {
|
BasePart::~BasePart() {
|
||||||
// This relies on physicsCommon still existing. Be very careful.
|
// This relies on physicsCommon still existing. Be very careful.
|
||||||
if (this->rigidBody && workspace()) {
|
if (this->rigidBody && workspace()) {
|
||||||
workspace().value()->DestroyRigidBody(rigidBody);
|
workspace().value()->RemoveBody(shared<BasePart>());
|
||||||
this->rigidBody = nullptr;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,6 +52,7 @@ void BasePart::OnWorkspaceAdded(std::optional<std::shared_ptr<Workspace>> oldWor
|
||||||
}
|
}
|
||||||
|
|
||||||
void BasePart::OnWorkspaceRemoved(std::shared_ptr<Workspace> oldWorkspace) {
|
void BasePart::OnWorkspaceRemoved(std::shared_ptr<Workspace> oldWorkspace) {
|
||||||
|
oldWorkspace->RemoveBody(shared<BasePart>());
|
||||||
}
|
}
|
||||||
|
|
||||||
void BasePart::onUpdated(std::string property) {
|
void BasePart::onUpdated(std::string property) {
|
||||||
|
|
|
@ -32,7 +32,7 @@ struct PartConstructParams {
|
||||||
bool locked = false;
|
bool locked = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Workspace;
|
class PhysWorld;
|
||||||
|
|
||||||
class DEF_INST_ABSTRACT_(explorer_icon="part") BasePart : public PVInstance {
|
class DEF_INST_ABSTRACT_(explorer_icon="part") BasePart : public PVInstance {
|
||||||
AUTOGEN_PREAMBLE
|
AUTOGEN_PREAMBLE
|
||||||
|
@ -52,7 +52,7 @@ protected:
|
||||||
bool checkSurfacesTouching(CFrame surfaceFrame, Vector3 size, Vector3 myFace, Vector3 otherFace, std::shared_ptr<BasePart> otherPart);
|
bool checkSurfacesTouching(CFrame surfaceFrame, Vector3 size, Vector3 myFace, Vector3 otherFace, std::shared_ptr<BasePart> otherPart);
|
||||||
|
|
||||||
friend JointInstance;
|
friend JointInstance;
|
||||||
friend Workspace;
|
friend PhysWorld;
|
||||||
|
|
||||||
virtual void OnWorkspaceAdded(std::optional<std::shared_ptr<Workspace>> oldWorkspace, std::shared_ptr<Workspace> newWorkspace) override;
|
virtual void OnWorkspaceAdded(std::optional<std::shared_ptr<Workspace>> oldWorkspace, std::shared_ptr<Workspace> newWorkspace) override;
|
||||||
virtual void OnWorkspaceRemoved(std::shared_ptr<Workspace> oldWorkspace) override;
|
virtual void OnWorkspaceRemoved(std::shared_ptr<Workspace> oldWorkspace) override;
|
||||||
|
|
|
@ -12,79 +12,18 @@
|
||||||
#include "physics/util.h"
|
#include "physics/util.h"
|
||||||
#include "timeutil.h"
|
#include "timeutil.h"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <reactphysics3d/collision/CollisionCallback.h>
|
|
||||||
#include <reactphysics3d/collision/OverlapCallback.h>
|
|
||||||
#include <reactphysics3d/engine/PhysicsCommon.h>
|
|
||||||
|
|
||||||
rp::PhysicsCommon* Workspace::physicsCommon = new rp::PhysicsCommon;
|
Workspace::Workspace(): Service(&TYPE), physicsWorld(std::make_shared<PhysWorld>()) {
|
||||||
|
|
||||||
Workspace::Workspace(): Service(&TYPE), physicsEventListener(this) {
|
|
||||||
physicsWorld = physicsCommon->createPhysicsWorld();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Workspace::~Workspace() {
|
Workspace::~Workspace() = default;
|
||||||
if (physicsCommon)
|
|
||||||
physicsCommon->destroyPhysicsWorld(physicsWorld);
|
|
||||||
}
|
|
||||||
|
|
||||||
PhysicsEventListener::PhysicsEventListener(Workspace* parent) : workspace(parent) {}
|
|
||||||
|
|
||||||
void PhysicsEventListener::onContact(const rp::CollisionCallback::CallbackData& data) {
|
|
||||||
workspace->contactQueueLock.lock();
|
|
||||||
for (size_t i = 0; i < data.getNbContactPairs(); i++) {
|
|
||||||
auto pair = data.getContactPair(i);
|
|
||||||
auto type = pair.getEventType();
|
|
||||||
if (type == rp::CollisionCallback::ContactPair::EventType::ContactStay) continue;
|
|
||||||
|
|
||||||
if (type == reactphysics3d::CollisionCallback::ContactPair::EventType::ContactStay)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
ContactItem contact;
|
|
||||||
contact.part0 = reinterpret_cast<BasePart*>(pair.getBody1()->getUserData())->shared<BasePart>();
|
|
||||||
contact.part1 = reinterpret_cast<BasePart*>(pair.getBody2()->getUserData())->shared<BasePart>();
|
|
||||||
contact.action = type == reactphysics3d::CollisionCallback::ContactPair::EventType::ContactStart ? ContactItem::CONTACTITEM_TOUCHED : ContactItem::CONTACTITEM_TOUCHENDED;
|
|
||||||
|
|
||||||
workspace->contactQueue.push(contact);
|
|
||||||
}
|
|
||||||
workspace->contactQueueLock.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
void PhysicsEventListener::onTrigger(const rp::OverlapCallback::CallbackData& data) {
|
|
||||||
workspace->contactQueueLock.lock();
|
|
||||||
for (size_t i = 0; i < data.getNbOverlappingPairs(); i++) {
|
|
||||||
auto pair = data.getOverlappingPair(i);
|
|
||||||
auto type = pair.getEventType();
|
|
||||||
if (type == rp::OverlapCallback::OverlapPair::EventType::OverlapStay) continue;
|
|
||||||
|
|
||||||
auto part0 = reinterpret_cast<BasePart*>(pair.getBody1()->getUserData())->shared<BasePart>();
|
|
||||||
auto part1 = reinterpret_cast<BasePart*>(pair.getBody2()->getUserData())->shared<BasePart>();
|
|
||||||
|
|
||||||
if (type == reactphysics3d::OverlapCallback::OverlapPair::EventType::OverlapStart) {
|
|
||||||
part0->Touched->Fire({ (Variant)InstanceRef(part1) });
|
|
||||||
part1->Touched->Fire({ (Variant)InstanceRef(part0) });
|
|
||||||
} else if (type == reactphysics3d::OverlapCallback::OverlapPair::EventType::OverlapExit) {
|
|
||||||
part0->TouchEnded->Fire({ (Variant)InstanceRef(part1) });
|
|
||||||
part1->TouchEnded->Fire({ (Variant)InstanceRef(part0) });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
workspace->contactQueueLock.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Workspace::InitService() {
|
void Workspace::InitService() {
|
||||||
if (initialized) return;
|
if (initialized) return;
|
||||||
initialized = true;
|
initialized = true;
|
||||||
|
|
||||||
physicsWorld->setGravity(rp::Vector3(0, -196.2, 0));
|
|
||||||
// world->setContactsPositionCorrectionTechnique(rp3d::ContactsPositionCorrectionTechnique::BAUMGARTE_CONTACTS);
|
|
||||||
// physicsWorld->setNbIterationsPositionSolver(2000);
|
|
||||||
// physicsWorld->setNbIterationsVelocitySolver(2000);
|
|
||||||
// physicsWorld->setSleepLinearVelocity(10);
|
|
||||||
// physicsWorld->setSleepAngularVelocity(5);
|
|
||||||
|
|
||||||
physicsWorld->setEventListener(&physicsEventListener);
|
|
||||||
|
|
||||||
// Create meshes
|
// Create meshes
|
||||||
WedgePart::createWedgeShape(physicsCommon);
|
// WedgePart::createWedgeShape(physicsCommon);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Workspace::OnRun() {
|
void Workspace::OnRun() {
|
||||||
|
@ -110,83 +49,14 @@ void Workspace::OnRun() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Workspace::updatePartPhysics(std::shared_ptr<BasePart> part) {
|
|
||||||
rp::Transform transform = part->cframe;
|
|
||||||
if (!part->rigidBody) {
|
|
||||||
part->rigidBody = physicsWorld->createRigidBody(transform);
|
|
||||||
} else {
|
|
||||||
part->rigidBody->setTransform(transform);
|
|
||||||
}
|
|
||||||
|
|
||||||
part->updateCollider(physicsCommon);
|
|
||||||
|
|
||||||
part->rigidBody->setType(part->anchored ? rp::BodyType::STATIC : rp::BodyType::DYNAMIC);
|
|
||||||
part->rigidBody->getCollider(0)->setCollisionCategoryBits(0b11);
|
|
||||||
|
|
||||||
part->rigidBody->getCollider(0)->setIsSimulationCollider(part->canCollide);
|
|
||||||
part->rigidBody->getCollider(0)->setIsTrigger(!part->canCollide);
|
|
||||||
|
|
||||||
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->setLinearVelocity(part->velocity);
|
|
||||||
// part->rigidBody->setMass(density * part->size.x * part->size.y * part->size.z);
|
|
||||||
|
|
||||||
part->rigidBody->setUserData(&*part);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Workspace::ProcessContactEvents() {
|
|
||||||
contactQueueLock.lock();
|
|
||||||
while (!contactQueue.empty()) {
|
|
||||||
ContactItem& contact = contactQueue.front();
|
|
||||||
contactQueue.pop();
|
|
||||||
if (contact.action == ContactItem::CONTACTITEM_TOUCHED) {
|
|
||||||
contact.part0->Touched->Fire({ (Variant)InstanceRef(contact.part1) });
|
|
||||||
contact.part1->Touched->Fire({ (Variant)InstanceRef(contact.part0) });
|
|
||||||
} else if (contact.action == ContactItem::CONTACTITEM_TOUCHENDED) {
|
|
||||||
contact.part0->TouchEnded->Fire({ (Variant)InstanceRef(contact.part1) });
|
|
||||||
contact.part1->TouchEnded->Fire({ (Variant)InstanceRef(contact.part0) });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
contactQueueLock.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Workspace::SyncPartPhysics(std::shared_ptr<BasePart> part) {
|
void Workspace::SyncPartPhysics(std::shared_ptr<BasePart> part) {
|
||||||
updatePartPhysics(part);
|
physicsWorld->syncBodyProperties(part);
|
||||||
}
|
}
|
||||||
|
|
||||||
tu_time_t physTime;
|
|
||||||
void Workspace::PhysicsStep(float deltaTime) {
|
void Workspace::PhysicsStep(float deltaTime) {
|
||||||
tu_time_t startTime = tu_clock_micros();
|
physicsWorld->step(deltaTime);
|
||||||
|
|
||||||
std::scoped_lock lock(globalPhysicsLock);
|
|
||||||
physicsWorld->update(std::min(deltaTime / 2, (1/60.f)));
|
|
||||||
|
|
||||||
// TODO: Add list of tracked parts in workspace based on their ancestry using inWorkspace property of Instance
|
|
||||||
for (std::shared_ptr<BasePart> part : simulatedBodies) {
|
|
||||||
if (!part->rigidBody) continue;
|
|
||||||
|
|
||||||
// Sync properties
|
|
||||||
const rp::Transform& transform = part->rigidBody->getTransform();
|
|
||||||
part->cframe = CFrame(transform);
|
|
||||||
part->velocity = part->rigidBody->getLinearVelocity();
|
|
||||||
|
|
||||||
// part->rigidBody->enableGravity(true);
|
|
||||||
// RotateV/Motor joint
|
|
||||||
for (auto& joint : part->secondaryJoints) {
|
|
||||||
if (joint.expired() || !joint.lock()->IsA("RotateV")) continue;
|
|
||||||
|
|
||||||
std::shared_ptr<JointInstance> motor = joint.lock()->CastTo<JointInstance>().expect();
|
|
||||||
float rate = motor->part0.lock()->GetSurfaceParamB(-motor->c0.LookVector().Unit()) * 30;
|
|
||||||
// part->rigidBody->enableGravity(false);
|
|
||||||
part->rigidBody->setAngularVelocity(-(motor->part0.lock()->cframe * motor->c0).LookVector() * rate);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
for (std::shared_ptr<BasePart> part : physicsWorld->getSimulatedBodies()) {
|
||||||
// Destroy fallen parts
|
// Destroy fallen parts
|
||||||
if (part->cframe.Position().Y() < this->fallenPartsDestroyHeight) {
|
if (part->cframe.Position().Y() < this->fallenPartsDestroyHeight) {
|
||||||
auto parent = part->GetParent();
|
auto parent = part->GetParent();
|
||||||
|
@ -197,88 +67,15 @@ void Workspace::PhysicsStep(float deltaTime) {
|
||||||
parent.value()->Destroy();
|
parent.value()->Destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
physTime = tu_clock_micros() - startTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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<BasePart> 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) {
|
|
||||||
// std::scoped_lock lock(globalPhysicsLock);
|
|
||||||
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) {
|
|
||||||
std::scoped_lock lock(globalPhysicsLock);
|
|
||||||
physicsWorld->destroyRigidBody(rigidBody);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Workspace::DestroyJoint(rp::Joint* joint) {
|
void Workspace::DestroyJoint(rp::Joint* joint) {
|
||||||
std::scoped_lock lock(globalPhysicsLock);
|
// physicsWorld->destroyJoint(joint);
|
||||||
physicsWorld->destroyJoint(joint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rp::Joint* Workspace::CreateJoint(const rp::JointInfo& jointInfo) {
|
rp::Joint* Workspace::CreateJoint(const rp::JointInfo& jointInfo) {
|
||||||
std::scoped_lock lock(globalPhysicsLock);
|
// rp::Joint* joint = physicsWorld->createJoint(jointInfo);
|
||||||
rp::Joint* joint = physicsWorld->createJoint(jointInfo);
|
|
||||||
|
|
||||||
return joint;
|
// return joint;
|
||||||
}
|
return nullptr;
|
||||||
|
|
||||||
void Workspace::AddBody(std::shared_ptr<BasePart> part) {
|
|
||||||
simulatedBodies.push_back(part);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Workspace::RemoveBody(std::shared_ptr<BasePart> part) {
|
|
||||||
auto it = std::find(simulatedBodies.begin(), simulatedBodies.end(), part);
|
|
||||||
simulatedBodies.erase(it);
|
|
||||||
}
|
}
|
|
@ -2,35 +2,13 @@
|
||||||
|
|
||||||
#include "objects/annotation.h"
|
#include "objects/annotation.h"
|
||||||
#include "objects/base/service.h"
|
#include "objects/base/service.h"
|
||||||
|
#include "physics/world.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include <glm/ext/vector_float3.hpp>
|
#include <glm/ext/vector_float3.hpp>
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <reactphysics3d/body/RigidBody.h>
|
|
||||||
#include <reactphysics3d/engine/EventListener.h>
|
|
||||||
#include <reactphysics3d/engine/PhysicsCommon.h>
|
|
||||||
#include <reactphysics3d/engine/PhysicsWorld.h>
|
|
||||||
|
|
||||||
namespace rp = reactphysics3d;
|
|
||||||
|
|
||||||
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 BasePart;
|
class BasePart;
|
||||||
class Snap;
|
class Snap;
|
||||||
|
@ -38,16 +16,6 @@ class Weld;
|
||||||
class Rotate;
|
class Rotate;
|
||||||
class RotateV;
|
class RotateV;
|
||||||
|
|
||||||
typedef std::function<FilterResult(std::shared_ptr<BasePart>)> RaycastFilter;
|
|
||||||
|
|
||||||
struct QueueItem {
|
|
||||||
std::shared_ptr<BasePart> part;
|
|
||||||
enum {
|
|
||||||
QUEUEITEM_ADD,
|
|
||||||
QUEUEITEM_REMOVE,
|
|
||||||
} action;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ContactItem {
|
struct ContactItem {
|
||||||
std::shared_ptr<BasePart> part0;
|
std::shared_ptr<BasePart> part0;
|
||||||
std::shared_ptr<BasePart> part1;
|
std::shared_ptr<BasePart> part1;
|
||||||
|
@ -57,30 +25,13 @@ struct ContactItem {
|
||||||
} action;
|
} action;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Workspace;
|
|
||||||
class PhysicsEventListener : public rp::EventListener {
|
|
||||||
friend Workspace;
|
|
||||||
Workspace* workspace;
|
|
||||||
|
|
||||||
PhysicsEventListener(Workspace*);
|
|
||||||
|
|
||||||
void onContact(const rp::CollisionCallback::CallbackData&) override;
|
|
||||||
void onTrigger(const rp::OverlapCallback::CallbackData&) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class DEF_INST_SERVICE_(explorer_icon="workspace") Workspace : public Service {
|
class DEF_INST_SERVICE_(explorer_icon="workspace") Workspace : public Service {
|
||||||
AUTOGEN_PREAMBLE
|
AUTOGEN_PREAMBLE
|
||||||
|
|
||||||
friend PhysicsEventListener;
|
|
||||||
|
|
||||||
std::list<std::shared_ptr<BasePart>> simulatedBodies;
|
|
||||||
std::queue<ContactItem> contactQueue;
|
std::queue<ContactItem> contactQueue;
|
||||||
std::mutex contactQueueLock;
|
std::mutex contactQueueLock;
|
||||||
rp::PhysicsWorld* physicsWorld;
|
|
||||||
static rp::PhysicsCommon* physicsCommon;
|
|
||||||
PhysicsEventListener physicsEventListener;
|
|
||||||
|
|
||||||
void updatePartPhysics(std::shared_ptr<BasePart> part);
|
std::shared_ptr<PhysWorld> physicsWorld;
|
||||||
protected:
|
protected:
|
||||||
void InitService() override;
|
void InitService() override;
|
||||||
void OnRun() override;
|
void OnRun() override;
|
||||||
|
@ -90,7 +41,6 @@ public:
|
||||||
Workspace();
|
Workspace();
|
||||||
~Workspace();
|
~Workspace();
|
||||||
|
|
||||||
std::mutex globalPhysicsLock;
|
|
||||||
std::recursive_mutex queueLock;
|
std::recursive_mutex queueLock;
|
||||||
|
|
||||||
DEF_PROP float fallenPartsDestroyHeight = -500;
|
DEF_PROP float fallenPartsDestroyHeight = -500;
|
||||||
|
@ -98,15 +48,13 @@ public:
|
||||||
// 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>(); };
|
||||||
|
|
||||||
void AddBody(std::shared_ptr<BasePart> part);
|
inline void AddBody(std::shared_ptr<BasePart> part) { physicsWorld->addBody(part); }
|
||||||
void RemoveBody(std::shared_ptr<BasePart> part);
|
inline void RemoveBody(std::shared_ptr<BasePart> part) { physicsWorld->removeBody(part); }
|
||||||
void DestroyRigidBody(rp::RigidBody* rigidBody);
|
|
||||||
void SyncPartPhysics(std::shared_ptr<BasePart> part);
|
void SyncPartPhysics(std::shared_ptr<BasePart> part);
|
||||||
|
|
||||||
rp::Joint* CreateJoint(const rp::JointInfo& jointInfo);
|
rp::Joint* CreateJoint(const rp::JointInfo& jointInfo);
|
||||||
void DestroyJoint(rp::Joint* joint);
|
void DestroyJoint(rp::Joint* joint);
|
||||||
|
|
||||||
void ProcessContactEvents();
|
|
||||||
void PhysicsStep(float deltaTime);
|
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);
|
inline std::optional<const RaycastResult> CastRayNearest(glm::vec3 point, glm::vec3 rotation, float maxLength, std::optional<RaycastFilter> filter = std::nullopt, unsigned short categoryMaskBits = 0xFFFF) { return physicsWorld->castRay(point, rotation, maxLength, filter, categoryMaskBits); }
|
||||||
};
|
};
|
194
core/src/physics/world.cpp
Normal file
194
core/src/physics/world.cpp
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
#include "world.h"
|
||||||
|
#include "datatypes/variant.h"
|
||||||
|
#include "datatypes/vector.h"
|
||||||
|
#include "objects/joint/jointinstance.h"
|
||||||
|
#include "objects/part/basepart.h"
|
||||||
|
#include "physics/util.h"
|
||||||
|
#include "timeutil.h"
|
||||||
|
|
||||||
|
rp3d::PhysicsCommon physicsCommon;
|
||||||
|
|
||||||
|
PhysWorld::PhysWorld() : physicsEventListener(this) {
|
||||||
|
worldImpl = physicsCommon.createPhysicsWorld();
|
||||||
|
|
||||||
|
worldImpl->setGravity(rp::Vector3(0, -196.2, 0));
|
||||||
|
// world->setContactsPositionCorrectionTechnique(rp3d::ContactsPositionCorrectionTechnique::BAUMGARTE_CONTACTS);
|
||||||
|
// physicsWorld->setNbIterationsPositionSolver(2000);
|
||||||
|
// physicsWorld->setNbIterationsVelocitySolver(2000);
|
||||||
|
// physicsWorld->setSleepLinearVelocity(10);
|
||||||
|
// physicsWorld->setSleepAngularVelocity(5);
|
||||||
|
|
||||||
|
worldImpl->setEventListener(&physicsEventListener);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
PhysWorld::~PhysWorld() {
|
||||||
|
physicsCommon.destroyPhysicsWorld(worldImpl);
|
||||||
|
}
|
||||||
|
|
||||||
|
PhysicsEventListener::PhysicsEventListener(PhysWorld* world) : world(world) {}
|
||||||
|
|
||||||
|
void PhysicsEventListener::onContact(const rp::CollisionCallback::CallbackData& data) {
|
||||||
|
for (size_t i = 0; i < data.getNbContactPairs(); i++) {
|
||||||
|
auto pair = data.getContactPair(i);
|
||||||
|
auto type = pair.getEventType();
|
||||||
|
if (type == rp::CollisionCallback::ContactPair::EventType::ContactStay) continue;
|
||||||
|
|
||||||
|
auto part0 = reinterpret_cast<BasePart*>(pair.getBody1()->getUserData())->shared<BasePart>();
|
||||||
|
auto part1 = reinterpret_cast<BasePart*>(pair.getBody2()->getUserData())->shared<BasePart>();
|
||||||
|
|
||||||
|
if (type == reactphysics3d::CollisionCallback::ContactPair::EventType::ContactStart) {
|
||||||
|
part0->Touched->Fire({ (Variant)InstanceRef(part1) });
|
||||||
|
part1->Touched->Fire({ (Variant)InstanceRef(part0) });
|
||||||
|
} else if (type == reactphysics3d::CollisionCallback::ContactPair::EventType::ContactExit) {
|
||||||
|
part0->TouchEnded->Fire({ (Variant)InstanceRef(part1) });
|
||||||
|
part1->TouchEnded->Fire({ (Variant)InstanceRef(part0) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhysicsEventListener::onTrigger(const rp::OverlapCallback::CallbackData& data) {
|
||||||
|
for (size_t i = 0; i < data.getNbOverlappingPairs(); i++) {
|
||||||
|
auto pair = data.getOverlappingPair(i);
|
||||||
|
auto type = pair.getEventType();
|
||||||
|
if (type == rp::OverlapCallback::OverlapPair::EventType::OverlapStay) continue;
|
||||||
|
|
||||||
|
auto part0 = reinterpret_cast<BasePart*>(pair.getBody1()->getUserData())->shared<BasePart>();
|
||||||
|
auto part1 = reinterpret_cast<BasePart*>(pair.getBody2()->getUserData())->shared<BasePart>();
|
||||||
|
|
||||||
|
if (type == reactphysics3d::OverlapCallback::OverlapPair::EventType::OverlapStart) {
|
||||||
|
part0->Touched->Fire({ (Variant)InstanceRef(part1) });
|
||||||
|
part1->Touched->Fire({ (Variant)InstanceRef(part0) });
|
||||||
|
} else if (type == reactphysics3d::OverlapCallback::OverlapPair::EventType::OverlapExit) {
|
||||||
|
part0->TouchEnded->Fire({ (Variant)InstanceRef(part1) });
|
||||||
|
part1->TouchEnded->Fire({ (Variant)InstanceRef(part0) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tu_time_t physTime;
|
||||||
|
void PhysWorld::step(float deltaTime) {
|
||||||
|
tu_time_t startTime = tu_clock_micros();
|
||||||
|
|
||||||
|
worldImpl->update(std::min(deltaTime / 2, (1/60.f)));
|
||||||
|
|
||||||
|
// TODO: Add list of tracked parts in workspace based on their ancestry using inWorkspace property of Instance
|
||||||
|
for (std::shared_ptr<BasePart> part : simulatedBodies) {
|
||||||
|
if (!part->rigidBody) continue;
|
||||||
|
|
||||||
|
// Sync properties
|
||||||
|
const rp::Transform& transform = part->rigidBody->getTransform();
|
||||||
|
part->cframe = CFrame(transform);
|
||||||
|
part->velocity = part->rigidBody->getLinearVelocity();
|
||||||
|
|
||||||
|
// part->rigidBody->enableGravity(true);
|
||||||
|
// RotateV/Motor joint
|
||||||
|
for (auto& joint : part->secondaryJoints) {
|
||||||
|
if (joint.expired() || !joint.lock()->IsA("RotateV")) continue;
|
||||||
|
|
||||||
|
std::shared_ptr<JointInstance> motor = joint.lock()->CastTo<JointInstance>().expect();
|
||||||
|
float rate = motor->part0.lock()->GetSurfaceParamB(-motor->c0.LookVector().Unit()) * 30;
|
||||||
|
// part->rigidBody->enableGravity(false);
|
||||||
|
part->rigidBody->setAngularVelocity(-(motor->part0.lock()->cframe * motor->c0).LookVector() * rate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
physTime = tu_clock_micros() - startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhysWorld::addBody(std::shared_ptr<BasePart> part) {
|
||||||
|
simulatedBodies.push_back(part);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhysWorld::removeBody(std::shared_ptr<BasePart> part) {
|
||||||
|
auto it = std::find(simulatedBodies.begin(), simulatedBodies.end(), part);
|
||||||
|
simulatedBodies.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhysWorld::syncBodyProperties(std::shared_ptr<BasePart> part) {
|
||||||
|
rp::Transform transform = part->cframe;
|
||||||
|
if (!part->rigidBody) {
|
||||||
|
part->rigidBody = worldImpl->createRigidBody(transform);
|
||||||
|
} else {
|
||||||
|
part->rigidBody->setTransform(transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
part->updateCollider(&physicsCommon);
|
||||||
|
|
||||||
|
part->rigidBody->setType(part->anchored ? rp::BodyType::STATIC : rp::BodyType::DYNAMIC);
|
||||||
|
part->rigidBody->getCollider(0)->setCollisionCategoryBits(0b11);
|
||||||
|
|
||||||
|
part->rigidBody->getCollider(0)->setIsSimulationCollider(part->canCollide);
|
||||||
|
part->rigidBody->getCollider(0)->setIsTrigger(!part->canCollide);
|
||||||
|
|
||||||
|
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->setLinearVelocity(part->velocity);
|
||||||
|
// part->rigidBody->setMass(density * part->size.x * part->size.y * part->size.z);
|
||||||
|
|
||||||
|
part->rigidBody->setUserData(&*part);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ray-casting
|
||||||
|
|
||||||
|
|
||||||
|
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<BasePart> 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> PhysWorld::castRay(Vector3 point, Vector3 rotation, float maxLength, std::optional<RaycastFilter> filter, unsigned short categoryMaskBits) {
|
||||||
|
// std::scoped_lock lock(globalPhysicsLock);
|
||||||
|
rp::Ray ray(glmToRp(point), glmToRp(glm::normalize((glm::vec3)rotation)) * maxLength);
|
||||||
|
NearestRayHit rayHit(glmToRp(point), filter);
|
||||||
|
worldImpl->raycast(ray, &rayHit, categoryMaskBits);
|
||||||
|
return rayHit.getNearestHit();
|
||||||
|
}
|
58
core/src/physics/world.h
Normal file
58
core/src/physics/world.h
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "datatypes/vector.h"
|
||||||
|
#include <functional>
|
||||||
|
#include <list>
|
||||||
|
#include <memory>
|
||||||
|
#include <reactphysics3d/reactphysics3d.h>
|
||||||
|
|
||||||
|
namespace rp = reactphysics3d;
|
||||||
|
|
||||||
|
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 BasePart;
|
||||||
|
typedef std::function<FilterResult(std::shared_ptr<BasePart>)> RaycastFilter;
|
||||||
|
|
||||||
|
class PhysWorld;
|
||||||
|
class PhysicsEventListener : public rp::EventListener {
|
||||||
|
friend PhysWorld;
|
||||||
|
PhysWorld* world;
|
||||||
|
|
||||||
|
PhysicsEventListener(PhysWorld*);
|
||||||
|
|
||||||
|
void onContact(const rp::CollisionCallback::CallbackData&) override;
|
||||||
|
void onTrigger(const rp::OverlapCallback::CallbackData&) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PhysWorld : std::enable_shared_from_this<PhysWorld> {
|
||||||
|
rp::PhysicsWorld* worldImpl;
|
||||||
|
PhysicsEventListener physicsEventListener;
|
||||||
|
std::list<std::shared_ptr<BasePart>> simulatedBodies;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PhysWorld();
|
||||||
|
~PhysWorld();
|
||||||
|
|
||||||
|
void step(float deltaTime);
|
||||||
|
|
||||||
|
void addBody(std::shared_ptr<BasePart>);
|
||||||
|
void removeBody(std::shared_ptr<BasePart>);
|
||||||
|
inline const std::list<std::shared_ptr<BasePart>>& getSimulatedBodies() { return simulatedBodies; }
|
||||||
|
void syncBodyProperties(std::shared_ptr<BasePart>);
|
||||||
|
std::optional<const RaycastResult> castRay(Vector3 point, Vector3 rotation, float maxLength, std::optional<RaycastFilter> filter, unsigned short categoryMaskBits);
|
||||||
|
};
|
|
@ -94,7 +94,6 @@ void PlaceDocument::timerEvent(QTimerEvent* evt) {
|
||||||
placeWidget->updateCycle();
|
placeWidget->updateCycle();
|
||||||
gDataModel->GetService<ScriptContext>()->RunSleepingThreads();
|
gDataModel->GetService<ScriptContext>()->RunSleepingThreads();
|
||||||
if (_runState == RUN_RUNNING) gDataModel->GetService<Workspace>()->PhysicsStep(0.033);
|
if (_runState == RUN_RUNNING) gDataModel->GetService<Workspace>()->PhysicsStep(0.033);
|
||||||
gDataModel->GetService<Workspace>()->ProcessContactEvents();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue