mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-07-05 11:44:29 +00:00
Merge 839debff57 into 0b73e701ac
This commit is contained in:
commit
9028b355e0
1138 changed files with 216458 additions and 155 deletions
|
|
@ -79,7 +79,7 @@ torqueAddSourceDirectories("platform/nativeDialogs")
|
|||
torqueAddSourceDirectories( "T3D" "T3D/AI" "T3D/assets" "T3D/decal" "T3D/examples" "T3D/fps" "T3D/fx"
|
||||
"T3D/gameBase" "T3D/gameBase/std"
|
||||
"T3D/lighting"
|
||||
"T3D/physics"
|
||||
"T3D/physics" "T3D/physics/jolt"
|
||||
# "T3D/components" "T3D/sceneComponent" "T3D/systems" "T3D/gameOBjects"
|
||||
"T3D/sfx" "T3D/turret" "T3D/vehicles")
|
||||
|
||||
|
|
|
|||
384
Engine/source/T3D/physics/jolt/joltBody.cpp
Normal file
384
Engine/source/T3D/physics/jolt/joltBody.cpp
Normal file
|
|
@ -0,0 +1,384 @@
|
|||
#include "platform/platform.h"
|
||||
#include "T3D/physics/jolt/joltBody.h"
|
||||
|
||||
#include "math/mBox.h"
|
||||
#include "console/console.h"
|
||||
#include "scene/sceneObject.h"
|
||||
|
||||
JoltBody::JoltBody()
|
||||
:mBody(NULL),
|
||||
mWorld(NULL),
|
||||
mMass(0.0f),
|
||||
mCenterOfMass(NULL),
|
||||
mInvCenterOfMass(NULL),
|
||||
mIsDynamic(false),
|
||||
mIsEnabled(false)
|
||||
|
||||
{
|
||||
}
|
||||
|
||||
JoltBody::~JoltBody()
|
||||
{
|
||||
SAFE_DELETE(mCenterOfMass);
|
||||
SAFE_DELETE(mInvCenterOfMass);
|
||||
|
||||
mColShape = nullptr;
|
||||
|
||||
if (!isValid())
|
||||
return;
|
||||
|
||||
// User data must be cleared BEFORE the body is removed or destroyed.
|
||||
// DestroyBody returns the body to Jolt's internal pool; touching it
|
||||
// afterwards is use-after-free undefined behaviour.
|
||||
mBody->SetUserData(0);
|
||||
|
||||
setSimulationEnabled(false); // removes body from the simulation
|
||||
|
||||
JPH::BodyInterface& bi = mWorld->getPhysicsSystem()->GetBodyInterface();
|
||||
bi.DestroyBody(mBody->GetID());
|
||||
mBody = nullptr;
|
||||
}
|
||||
|
||||
PhysicsWorld* JoltBody::getWorld()
|
||||
{
|
||||
return mWorld;
|
||||
}
|
||||
|
||||
PhysicsCollision* JoltBody::getColShape()
|
||||
{
|
||||
return mColShape;
|
||||
}
|
||||
|
||||
bool JoltBody::init(PhysicsCollision* shape, F32 mass, U32 bodyFlags, SceneObject* obj, PhysicsWorld* world)
|
||||
{
|
||||
if (!shape || !world)
|
||||
return false;
|
||||
|
||||
mWorld = dynamic_cast<JoltWorld*>(world);
|
||||
if (!mWorld)
|
||||
return false;
|
||||
|
||||
mColShape = dynamic_cast<JoltCollision*>(shape);
|
||||
if (!mColShape)
|
||||
return false;
|
||||
|
||||
mIsDynamic = false;
|
||||
|
||||
const JPH::Shape* joltShape = mColShape->getJoltShape();
|
||||
|
||||
// Determine motion type
|
||||
JPH::EMotionType motionType = JPH::EMotionType::Static;
|
||||
JPH::ObjectLayer layer = Layers::NON_MOVING;
|
||||
|
||||
if (mass > 0.0f)
|
||||
{
|
||||
motionType = JPH::EMotionType::Dynamic;
|
||||
layer = Layers::MOVING;
|
||||
}
|
||||
else if (bodyFlags & BF_KINEMATIC)
|
||||
{
|
||||
motionType = JPH::EMotionType::Kinematic;
|
||||
layer = Layers::MOVING;
|
||||
}
|
||||
|
||||
mMass = mass;
|
||||
|
||||
MatrixF objXfm = obj->getTransform();
|
||||
|
||||
QuatF angPos(objXfm);
|
||||
Point3F pos;
|
||||
objXfm.getColumn(3, &pos);
|
||||
|
||||
JPH::BodyCreationSettings settings(
|
||||
joltShape,
|
||||
joltCast(pos), // initial position
|
||||
joltCast(angPos), // rotation
|
||||
motionType,
|
||||
layer
|
||||
);
|
||||
|
||||
// Mass override if dynamic
|
||||
if (motionType == JPH::EMotionType::Dynamic)
|
||||
{
|
||||
settings.mOverrideMassProperties = JPH::EOverrideMassProperties::CalculateInertia;
|
||||
settings.mMassPropertiesOverride.mMass = mass;
|
||||
settings.mMaxLinearVelocity = 1000.0f; // stops an assert, this is torques upper limit, jolts is 500.0f by default.
|
||||
mIsDynamic = true;
|
||||
}
|
||||
|
||||
JPH::BodyInterface& bi = mWorld->getPhysicsSystem()->GetBodyInterface();
|
||||
JPH::Body* body = bi.CreateBody(settings);
|
||||
bi.AddBody(body->GetID(), JPH::EActivation::Activate);
|
||||
|
||||
if (bodyFlags & BF_TRIGGER)
|
||||
{
|
||||
body->SetIsSensor(true);
|
||||
}
|
||||
|
||||
mBody = body;
|
||||
|
||||
getUserData().setObject(obj);
|
||||
getUserData().setBody(this);
|
||||
mBody->SetUserData(reinterpret_cast<U64>(&mUserData));
|
||||
|
||||
const JPH::Mat44 trans = mBody->GetCenterOfMassTransform();
|
||||
MatrixF comMat;
|
||||
QuatF qang = joltCast(trans.GetQuaternion());
|
||||
qang.setMatrix(&comMat);
|
||||
comMat.setPosition(joltCast(trans.GetTranslation()));
|
||||
|
||||
mCenterOfMass = new MatrixF(comMat);
|
||||
mInvCenterOfMass = new MatrixF(*mCenterOfMass);
|
||||
mInvCenterOfMass->inverse();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void JoltBody::setTransform(const MatrixF& xfm)
|
||||
{
|
||||
if (!isValid())
|
||||
return;
|
||||
|
||||
Point3F pos = xfm.getPosition();
|
||||
QuatF q(xfm);
|
||||
JPH::BodyInterface& bi = mWorld->getPhysicsSystem()->GetBodyInterface();
|
||||
|
||||
bi.SetPositionAndRotation(
|
||||
mBody->GetID(),
|
||||
joltCast(pos),
|
||||
joltCast(q),
|
||||
mIsEnabled ? JPH::EActivation::Activate : JPH::EActivation::DontActivate
|
||||
);
|
||||
|
||||
if (isDynamic())
|
||||
{
|
||||
// ResetMotion (clears accumulated forces/torques) is a Body method only,
|
||||
// so acquire a write lock. Velocity is cleared through the thread-safe interface.
|
||||
{
|
||||
JPH::BodyLockWrite lock(mWorld->getPhysicsSystem()->GetBodyLockInterface(), mBody->GetID());
|
||||
if (lock.Succeeded())
|
||||
lock.GetBody().ResetMotion();
|
||||
}
|
||||
bi.SetLinearVelocity(mBody->GetID(), JPH::Vec3::sZero());
|
||||
bi.SetAngularVelocity(mBody->GetID(), JPH::Vec3::sZero());
|
||||
}
|
||||
}
|
||||
|
||||
void JoltBody::applyCorrection(const MatrixF& xfm)
|
||||
{
|
||||
setTransform(xfm);
|
||||
}
|
||||
|
||||
MatrixF& JoltBody::getTransform(MatrixF* outMatrix)
|
||||
{
|
||||
const JPH::Mat44 trans = mBody->GetWorldTransform();
|
||||
*outMatrix = joltCast(trans);
|
||||
|
||||
return *outMatrix;
|
||||
}
|
||||
|
||||
void JoltBody::applyImpulse(const Point3F& origin, const Point3F& force)
|
||||
{
|
||||
if (!isValid())
|
||||
return;
|
||||
JPH::BodyInterface& bi = mWorld->getPhysicsSystem()->GetBodyInterface();
|
||||
bi.AddImpulse(mBody->GetID(), joltCast(force), joltCast(origin));
|
||||
}
|
||||
|
||||
void JoltBody::applyTorque(const Point3F& torque)
|
||||
{
|
||||
if (!isValid())
|
||||
return;
|
||||
JPH::BodyInterface& bi = mWorld->getPhysicsSystem()->GetBodyInterface();
|
||||
bi.AddTorque(mBody->GetID(), joltCast(torque));
|
||||
}
|
||||
|
||||
void JoltBody::applyForce(const Point3F& force)
|
||||
{
|
||||
if (!isValid())
|
||||
return;
|
||||
JPH::BodyInterface& bi = mWorld->getPhysicsSystem()->GetBodyInterface();
|
||||
bi.AddForce(mBody->GetID(), joltCast(force));
|
||||
}
|
||||
|
||||
void JoltBody::moveKinematicTo(const MatrixF& xfm)
|
||||
{
|
||||
Point3F pos = xfm.getPosition();
|
||||
QuatF angPos(xfm);
|
||||
mWorld->getPhysicsSystem()->GetBodyInterface().MoveKinematic(
|
||||
mBody->GetID(),
|
||||
joltCast(pos),
|
||||
joltCast(angPos),
|
||||
TickSec
|
||||
);
|
||||
}
|
||||
|
||||
bool JoltBody::isValid()
|
||||
{
|
||||
return mBody != nullptr && mWorld != NULL;
|
||||
}
|
||||
|
||||
Box3F JoltBody::getWorldBounds()
|
||||
{
|
||||
JPH::AABox box = mBody->GetWorldSpaceBounds();
|
||||
|
||||
return Box3F(
|
||||
Point3F(joltCast(box.mMin)),
|
||||
Point3F(joltCast(box.mMax))
|
||||
);
|
||||
}
|
||||
|
||||
void JoltBody::setSimulationEnabled(bool enabled)
|
||||
{
|
||||
if (!isValid())
|
||||
return;
|
||||
|
||||
JPH::BodyInterface& bi = mWorld->getPhysicsSystem()->GetBodyInterface();
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
if (bi.IsAdded(mBody->GetID()))
|
||||
{
|
||||
bi.ActivateBody(mBody->GetID());
|
||||
}
|
||||
else
|
||||
{
|
||||
bi.AddBody(mBody->GetID(), JPH::EActivation::Activate);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bi.DeactivateBody(mBody->GetID());
|
||||
bi.RemoveBody(mBody->GetID());
|
||||
}
|
||||
|
||||
mIsEnabled = enabled;
|
||||
}
|
||||
|
||||
void JoltBody::setSleepThreshold(F32 linear, F32 angular)
|
||||
{
|
||||
if (!mBody->IsDynamic())
|
||||
return;
|
||||
|
||||
JPH::MotionProperties* mp = mBody->GetMotionProperties();
|
||||
|
||||
/* mp->SetLinearSleepThreshold(linear);
|
||||
mp->SetAngularSleepThreshold(angular);*/
|
||||
}
|
||||
|
||||
void JoltBody::setDamping(F32 linear, F32 angular)
|
||||
{
|
||||
if (!mBody->IsDynamic())
|
||||
return;
|
||||
|
||||
JPH::MotionProperties* mp = mBody->GetMotionProperties();
|
||||
|
||||
mp->SetLinearDamping(linear);
|
||||
mp->SetAngularDamping(angular);
|
||||
}
|
||||
|
||||
void JoltBody::setSleeping(bool sleeping)
|
||||
{
|
||||
if (!isValid())
|
||||
return;
|
||||
|
||||
if (!mIsEnabled)
|
||||
return;
|
||||
|
||||
JPH::BodyInterface& bi = mWorld->getPhysicsSystem()->GetBodyInterface();
|
||||
|
||||
if (sleeping)
|
||||
bi.DeactivateBody(mBody->GetID());
|
||||
else
|
||||
bi.ActivateBody(mBody->GetID());
|
||||
}
|
||||
|
||||
void JoltBody::setMaterial(F32 restitution, F32 friction, F32 staticFriction)
|
||||
{
|
||||
mBody->SetRestitution(restitution);
|
||||
mBody->SetFriction(friction);
|
||||
}
|
||||
|
||||
void JoltBody::getState(PhysicsState* outState)
|
||||
{
|
||||
if (!outState)
|
||||
return;
|
||||
|
||||
const JPH::RMat44 worldXfm = mBody->GetWorldTransform();
|
||||
|
||||
outState->position = joltCast(worldXfm.GetTranslation());
|
||||
outState->orientation = joltCast(mBody->GetRotation());
|
||||
outState->linVelocity = getLinVelocity();
|
||||
outState->angVelocity = getAngVelocity();
|
||||
|
||||
if (mBody->IsDynamic())
|
||||
{
|
||||
const JPH::MotionProperties* mp = mBody->GetMotionProperties();
|
||||
|
||||
// Linear momentum = m * v
|
||||
F32 mass = mp->GetInverseMass() > 0.0f ? 1.0f / mp->GetInverseMass() : 0.0f;
|
||||
outState->momentum = outState->linVelocity * mass;
|
||||
outState->angularMomentum = outState->angVelocity;
|
||||
}
|
||||
else
|
||||
{
|
||||
outState->momentum.set(0, 0, 0);
|
||||
outState->angularMomentum.set(0, 0, 0);
|
||||
}
|
||||
|
||||
// Sleeping?
|
||||
outState->sleeping = !mBody->IsActive();
|
||||
}
|
||||
|
||||
Point3F JoltBody::getCMassPosition() const
|
||||
{
|
||||
JPH::RVec3 p = mBody->GetCenterOfMassPosition();
|
||||
return Point3F(joltCast(p));
|
||||
}
|
||||
|
||||
Point3F JoltBody::getLinVelocity() const
|
||||
{
|
||||
return joltCast(mBody->GetLinearVelocity());
|
||||
}
|
||||
|
||||
Point3F JoltBody::getAngVelocity() const
|
||||
{
|
||||
return joltCast(mBody->GetAngularVelocity());
|
||||
}
|
||||
|
||||
void JoltBody::setLinVelocity(const Point3F& vel)
|
||||
{
|
||||
if (!isValid())
|
||||
return;
|
||||
|
||||
JPH::BodyInterface& bi = mWorld->getPhysicsSystem()->GetBodyInterface();
|
||||
|
||||
if (!bi.IsAdded(mBody->GetID()))
|
||||
return;
|
||||
|
||||
bi.SetLinearVelocity(mBody->GetID(), joltCast(vel));
|
||||
}
|
||||
|
||||
void JoltBody::setAngVelocity(const Point3F& vel)
|
||||
{
|
||||
if (!isValid())
|
||||
return;
|
||||
|
||||
JPH::BodyInterface& bi = mWorld->getPhysicsSystem()->GetBodyInterface();
|
||||
|
||||
if (!bi.IsAdded(mBody->GetID()))
|
||||
return;
|
||||
|
||||
bi.SetAngularVelocity(mBody->GetID(), joltCast(vel));
|
||||
}
|
||||
|
||||
void JoltBody::findContact(SceneObject** contactObject,
|
||||
VectorF* contactNormal,
|
||||
Vector<SceneObject*>* outOverlapObjects) const
|
||||
{
|
||||
if (contactObject)
|
||||
*contactObject = nullptr;
|
||||
|
||||
if (contactNormal)
|
||||
*contactNormal = VectorF::Zero;
|
||||
}
|
||||
94
Engine/source/T3D/physics/jolt/joltBody.h
Normal file
94
Engine/source/T3D/physics/jolt/joltBody.h
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
#ifndef _JOLTBODY_H_
|
||||
#define _JOLTBODY_H_
|
||||
|
||||
#ifndef _T3D_PHYSICS_PHYSICSBODY_H_
|
||||
#include "T3D/physics/physicsBody.h"
|
||||
#endif
|
||||
#ifndef _REFBASE_H_
|
||||
#include "core/util/refBase.h"
|
||||
#endif
|
||||
#ifndef _MMATRIX_H_
|
||||
#include "math/mMatrix.h"
|
||||
#endif
|
||||
|
||||
#ifndef _T3D_JOLT_PLUGIN_H_
|
||||
#include "T3D/physics/jolt/joltPlugin.h"
|
||||
#endif
|
||||
|
||||
#ifndef _JOLTWORLD_H_
|
||||
#include "T3D/physics/jolt/joltWorld.h"
|
||||
#endif
|
||||
|
||||
#ifndef _JOLTCOLLISION_H_
|
||||
#include "T3D/physics/jolt/joltCollision.h"
|
||||
#endif
|
||||
|
||||
|
||||
class JoltBody : public PhysicsBody
|
||||
{
|
||||
protected:
|
||||
JPH::Body* mBody;
|
||||
|
||||
JoltWorld* mWorld;
|
||||
JoltCollisionRef mColShape;
|
||||
|
||||
///
|
||||
F32 mMass;
|
||||
|
||||
///
|
||||
bool mIsDynamic;
|
||||
|
||||
/// Is the body participating in the physics simulation.
|
||||
bool mIsEnabled;
|
||||
|
||||
/// The center of mass offset used if the graphical
|
||||
/// transform is not at the mass center.
|
||||
MatrixF* mCenterOfMass;
|
||||
|
||||
/// The inverse center of mass offset.
|
||||
MatrixF* mInvCenterOfMass;
|
||||
|
||||
public:
|
||||
JoltBody();
|
||||
virtual ~JoltBody();
|
||||
|
||||
// PhysicsObject
|
||||
virtual PhysicsWorld* getWorld();
|
||||
virtual void setTransform(const MatrixF& xfm);
|
||||
virtual MatrixF& getTransform(MatrixF* outMatrix);
|
||||
virtual Box3F getWorldBounds();
|
||||
virtual void setSimulationEnabled(bool enabled);
|
||||
virtual bool isSimulationEnabled() { return mIsEnabled; }
|
||||
|
||||
// PhysicsBody
|
||||
virtual bool init(PhysicsCollision* shape,
|
||||
F32 mass,
|
||||
U32 bodyFlags,
|
||||
SceneObject* obj,
|
||||
PhysicsWorld* world);
|
||||
bool isDynamic() const override { return mIsDynamic; }
|
||||
PhysicsCollision* getColShape() override;
|
||||
void setSleepThreshold(F32 linear, F32 angular) override;
|
||||
void setDamping(F32 linear, F32 angular) override;
|
||||
void getState(PhysicsState* outState) override;
|
||||
F32 getMass() const override { return mMass; }
|
||||
Point3F getCMassPosition() const override;
|
||||
void setLinVelocity(const Point3F& vel) override;
|
||||
void setAngVelocity(const Point3F& vel) override;
|
||||
Point3F getLinVelocity() const override;
|
||||
Point3F getAngVelocity() const override;
|
||||
void setSleeping(bool sleeping) override;
|
||||
void setMaterial(F32 restitution,
|
||||
F32 friction,
|
||||
F32 staticFriction) override;
|
||||
void applyCorrection(const MatrixF& xfm) override;
|
||||
void applyImpulse(const Point3F& origin, const Point3F& force) override;
|
||||
void applyTorque(const Point3F& torque) override;
|
||||
void applyForce(const Point3F& force) override;
|
||||
void findContact(SceneObject** contactObject, VectorF* contactNormal, Vector<SceneObject*>* outOverlapObjects) const override;
|
||||
void moveKinematicTo(const MatrixF& xfm) override;
|
||||
|
||||
bool isValid() override;
|
||||
};
|
||||
|
||||
#endif // !_JOLTBODY_H_
|
||||
370
Engine/source/T3D/physics/jolt/joltCollision.cpp
Normal file
370
Engine/source/T3D/physics/jolt/joltCollision.cpp
Normal file
|
|
@ -0,0 +1,370 @@
|
|||
#include "T3D/physics/jolt/joltCollision.h"
|
||||
#include "terrain/terrFile.h"
|
||||
|
||||
// Save and undefine the macro if it exists
|
||||
#ifdef Offset
|
||||
#pragma push_macro("Offset")
|
||||
#undef Offset
|
||||
#endif
|
||||
|
||||
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/CapsuleShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/ConvexHullShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/TriangleShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/HeightFieldShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/PlaneShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/CompoundShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/MeshShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/StaticCompoundShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/RotatedTranslatedShape.h>
|
||||
|
||||
#ifdef Offset
|
||||
// Restore the original macro after includes
|
||||
#pragma pop_macro("Offset")
|
||||
#endif
|
||||
|
||||
JoltCollision::JoltCollision()
|
||||
{
|
||||
VECTOR_SET_ASSOCIATION(mChildren);
|
||||
}
|
||||
|
||||
JoltCollision::~JoltCollision()
|
||||
{
|
||||
}
|
||||
|
||||
void JoltCollision::addPlane(const PlaneF& plane)
|
||||
{
|
||||
JPH::Plane joltPlane({ plane.x, plane.y, plane.z }, plane.d);
|
||||
JPH::PlaneShapeSettings planeSettings(joltPlane);
|
||||
auto result = planeSettings.Create();
|
||||
if (result.HasError())
|
||||
{
|
||||
Con::errorf("Jolt Error: %s", result.GetError().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
ChildShapeEntry entry;
|
||||
entry.shape = result.Get();
|
||||
entry.localXfm = JPH::Mat44::sIdentity(); // Plane at origin by default
|
||||
entry.localPos = JPH::Vec3::sZero();
|
||||
entry.localRot = JPH::Quat::sIdentity();
|
||||
mChildren.push_back(entry);
|
||||
|
||||
rebuildCompound();
|
||||
}
|
||||
|
||||
void JoltCollision::addBox(const Point3F& halfWidth, const MatrixF& localXfm)
|
||||
{
|
||||
JPH::BoxShapeSettings boxSettings(JPH::Vec3(halfWidth.x, halfWidth.y, halfWidth.z));
|
||||
boxSettings.mConvexRadius = 0.01f;
|
||||
|
||||
auto result = boxSettings.Create();
|
||||
if (result.HasError())
|
||||
{
|
||||
Con::errorf("Jolt Error: %s", result.GetError().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
auto baseShape = result.Get();
|
||||
|
||||
// Convert engine matrix to Jolt position + rotation
|
||||
JPH::Vec3 localPos;
|
||||
JPH::Quat localRot;
|
||||
toJolt(localXfm, localPos, localRot);
|
||||
|
||||
// Wrap the base shape with RotatedTranslatedShape
|
||||
JPH::Ref<JPH::RotatedTranslatedShapeSettings> rtsSettings = new JPH::RotatedTranslatedShapeSettings(localPos, localRot, baseShape);
|
||||
auto rtsShape = rtsSettings->Create().Get();
|
||||
|
||||
// Store in child entry
|
||||
ChildShapeEntry entry;
|
||||
entry.shape = rtsShape;
|
||||
entry.localPos = localPos;
|
||||
entry.localRot = localRot;
|
||||
entry.localXfm = joltCast(localXfm);
|
||||
mChildren.push_back(entry);
|
||||
|
||||
rebuildCompound();
|
||||
}
|
||||
|
||||
void JoltCollision::addSphere(F32 radius, const MatrixF& localXfm)
|
||||
{
|
||||
JPH::SphereShapeSettings settings(radius);
|
||||
auto result = settings.Create();
|
||||
if (result.HasError())
|
||||
{
|
||||
Con::errorf("Jolt Error: %s", result.GetError().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
auto baseShape = result.Get();
|
||||
|
||||
JPH::Vec3 localPos;
|
||||
JPH::Quat localRot;
|
||||
toJolt(localXfm, localPos, localRot);
|
||||
|
||||
JPH::Ref<JPH::RotatedTranslatedShapeSettings> rtsSettings = new JPH::RotatedTranslatedShapeSettings(localPos, localRot, baseShape);
|
||||
auto rtsShape = rtsSettings->Create().Get();
|
||||
|
||||
ChildShapeEntry entry;
|
||||
entry.shape = rtsShape;
|
||||
entry.localPos = localPos;
|
||||
entry.localRot = localRot;
|
||||
entry.localXfm = joltCast(localXfm);
|
||||
mChildren.push_back(entry);
|
||||
|
||||
rebuildCompound();
|
||||
}
|
||||
|
||||
void JoltCollision::addCapsule(F32 radius, F32 height, const MatrixF& localXfm)
|
||||
{
|
||||
JPH::CapsuleShapeSettings settings(radius, height);
|
||||
auto result = settings.Create();
|
||||
if (result.HasError())
|
||||
{
|
||||
Con::errorf("Jolt Error: %s", result.GetError().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
auto baseShape = result.Get();
|
||||
|
||||
JPH::Vec3 localPos;
|
||||
JPH::Quat localRot;
|
||||
toJolt(localXfm, localPos, localRot);
|
||||
|
||||
JPH::Ref<JPH::RotatedTranslatedShapeSettings> rtsSettings = new JPH::RotatedTranslatedShapeSettings(localPos, localRot, baseShape);
|
||||
auto rtsShape = rtsSettings->Create().Get();
|
||||
|
||||
ChildShapeEntry entry;
|
||||
entry.shape = rtsShape;
|
||||
entry.localPos = localPos;
|
||||
entry.localRot = localRot;
|
||||
entry.localXfm = joltCast(localXfm);
|
||||
mChildren.push_back(entry);
|
||||
|
||||
rebuildCompound();
|
||||
}
|
||||
|
||||
bool JoltCollision::addConvex(const Point3F* points, U32 count, const MatrixF& localXfm)
|
||||
{
|
||||
if (count == 0)
|
||||
return false;
|
||||
|
||||
// Pre-transform points into shape-local space for the same reason as
|
||||
// addTriangleMesh: avoids the RTS wrapper and the double-transform in
|
||||
// rebuildCompound when multiple shapes share a JoltCollision.
|
||||
const bool isIdentity = localXfm.isIdentity();
|
||||
|
||||
std::vector<JPH::Vec3> verts;
|
||||
verts.reserve(count);
|
||||
for (U32 i = 0; i < count; ++i)
|
||||
{
|
||||
Point3F p = points[i];
|
||||
if (!isIdentity)
|
||||
localXfm.mulP(points[i], &p);
|
||||
verts.emplace_back(p.x, p.y, p.z);
|
||||
}
|
||||
|
||||
JPH::ConvexHullShapeSettings settings(verts.data(), (int)verts.size());
|
||||
auto result = settings.Create();
|
||||
if (result.HasError())
|
||||
{
|
||||
Con::errorf("Jolt addConvex Error: %s", result.GetError().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
ChildShapeEntry entry;
|
||||
entry.shape = result.Get();
|
||||
entry.localPos = JPH::Vec3::sZero();
|
||||
entry.localRot = JPH::Quat::sIdentity();
|
||||
entry.localXfm = JPH::Mat44::sIdentity();
|
||||
mChildren.push_back(entry);
|
||||
|
||||
rebuildCompound();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool JoltCollision::addTriangleMesh(const Point3F* vert, U32 vertCount, const U32* index, U32 triCount, const MatrixF& localXfm)
|
||||
{
|
||||
if (!vert || !index || vertCount == 0 || triCount == 0)
|
||||
return false;
|
||||
|
||||
// Bake localXfm directly into the vertex positions so the MeshShape sits in
|
||||
// shape-local space with an identity transform. This avoids the need for an
|
||||
// RTS wrapper and eliminates the double-transform bug that occurs when the
|
||||
// wrapper's position is later re-applied by rebuildCompound's AddShape call.
|
||||
const bool isIdentity = localXfm.isIdentity();
|
||||
|
||||
JPH::TriangleList triangles;
|
||||
triangles.reserve(triCount);
|
||||
|
||||
for (U32 i = 0; i < triCount; ++i)
|
||||
{
|
||||
Point3F p0 = vert[index[i * 3 + 0]];
|
||||
Point3F p1 = vert[index[i * 3 + 1]];
|
||||
Point3F p2 = vert[index[i * 3 + 2]];
|
||||
|
||||
if (!isIdentity)
|
||||
{
|
||||
localXfm.mulP(vert[index[i * 3 + 0]], &p0);
|
||||
localXfm.mulP(vert[index[i * 3 + 1]], &p1);
|
||||
localXfm.mulP(vert[index[i * 3 + 2]], &p2);
|
||||
}
|
||||
|
||||
triangles.push_back(
|
||||
JPH::Triangle(
|
||||
JPH::Float3(p0.x, p0.y, p0.z),
|
||||
JPH::Float3(p2.x, p2.y, p2.z), // winding order maintained
|
||||
JPH::Float3(p1.x, p1.y, p1.z),
|
||||
0, // material index
|
||||
i // user data = original triangle index
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
JPH::MeshShapeSettings settings(triangles);
|
||||
auto result = settings.Create();
|
||||
if (result.HasError())
|
||||
{
|
||||
Con::errorf("Jolt addTriangleMesh Error: %s", result.GetError().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Store at identity — vertices are already in shape-local space.
|
||||
ChildShapeEntry entry;
|
||||
entry.shape = result.Get();
|
||||
entry.localPos = JPH::Vec3::sZero();
|
||||
entry.localRot = JPH::Quat::sIdentity();
|
||||
entry.localXfm = JPH::Mat44::sIdentity();
|
||||
mChildren.push_back(entry);
|
||||
|
||||
rebuildCompound();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool JoltCollision::addHeightfield(
|
||||
const U16* heightData,
|
||||
const bool* holes,
|
||||
U32 blockSize,
|
||||
F32 metersPerSample,
|
||||
const MatrixF& localXfm) // We won’t use localXfm directly
|
||||
{
|
||||
if (!heightData || blockSize == 0)
|
||||
return false;
|
||||
|
||||
// Jolt's internal BVH page size: power-of-2 between 2 and 8, independent of
|
||||
// inSampleCount. Use 4 for larger terrains to produce a shallower BVH tree.
|
||||
const U32 joltBlockSize = (blockSize >= 512) ? 4 : 2;
|
||||
|
||||
// inSampleCount must be a multiple of joltBlockSize. Round up so any blockSize
|
||||
// works — padding columns/rows are edge-clamped to stay physically correct.
|
||||
const U32 paddedSize = ((blockSize + joltBlockSize - 1) / joltBlockSize) * joltBlockSize;
|
||||
const U32 totalSamples = paddedSize * paddedSize;
|
||||
|
||||
// Pass 1: build a flat (un-flipped) padded grid with edge-clamping.
|
||||
std::vector<float> unflipped(totalSamples);
|
||||
for (U32 y = 0; y < paddedSize; ++y)
|
||||
{
|
||||
U32 srcY = (y < blockSize) ? y : blockSize - 1;
|
||||
for (U32 x = 0; x < paddedSize; ++x)
|
||||
{
|
||||
U32 srcX = (x < blockSize) ? x : blockSize - 1;
|
||||
U32 srcIdx = srcY * blockSize + srcX;
|
||||
|
||||
float h = fixedToFloat(heightData[srcIdx]);
|
||||
if (holes && holes[srcIdx])
|
||||
h = JPH::HeightFieldShapeConstants::cNoCollisionValue;
|
||||
|
||||
unflipped[y * paddedSize + x] = h;
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 2: flip Y axis into the final sample array for Jolt's coordinate system.
|
||||
std::vector<float> samples(totalSamples);
|
||||
for (U32 y = 0; y < paddedSize; ++y)
|
||||
for (U32 x = 0; x < paddedSize; ++x)
|
||||
samples[(paddedSize - 1 - y) * paddedSize + x] = unflipped[y * paddedSize + x];
|
||||
|
||||
// Offset uses actual terrain extent (blockSize) so the padded fringe never
|
||||
// shifts the visible terrain. The vertical adjustment centres quantisation error.
|
||||
const float heightScale = 0.03125f;
|
||||
const float verticalAdjust = -heightScale * 0.5f;
|
||||
const float terrainSize = blockSize * metersPerSample;
|
||||
|
||||
JPH::Vec3 joltOffset(0.0f, verticalAdjust, -terrainSize);
|
||||
JPH::Vec3 joltScale(metersPerSample, 1.0f, metersPerSample);
|
||||
|
||||
JPH::HeightFieldShapeSettings settings(samples.data(), joltOffset, joltScale, paddedSize);
|
||||
settings.mBlockSize = joltBlockSize;
|
||||
|
||||
auto result = settings.Create();
|
||||
if (result.HasError())
|
||||
{
|
||||
Con::errorf("Jolt addHeightfield Error (blockSize=%u paddedSize=%u): %s",
|
||||
blockSize, paddedSize, result.GetError().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
auto baseShape = result.Get();
|
||||
|
||||
JPH::Vec3 localPos;
|
||||
JPH::Quat localRot;
|
||||
toJolt(localXfm, localPos, localRot);
|
||||
JPH::Quat rotFix = JPH::Quat::sRotation(JPH::Vec3::sAxisX(), JPH::DegreesToRadians(90.0f));
|
||||
localRot = rotFix * localRot;
|
||||
|
||||
JPH::Ref<JPH::RotatedTranslatedShapeSettings> rtsSettings = new JPH::RotatedTranslatedShapeSettings(localPos, localRot, baseShape);
|
||||
auto rtsShape = rtsSettings->Create().Get();
|
||||
|
||||
ChildShapeEntry entry;
|
||||
entry.shape = rtsShape;
|
||||
entry.localPos = localPos;
|
||||
entry.localRot = localRot;
|
||||
mChildren.push_back(entry);
|
||||
|
||||
rebuildCompound();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void JoltCollision::rebuildCompound()
|
||||
{
|
||||
// clear
|
||||
mCompundShape = nullptr;
|
||||
|
||||
if (mChildren.empty())
|
||||
return;
|
||||
|
||||
if (mChildren.size() == 1)
|
||||
{
|
||||
// Single shape: use it directly. For primitive shapes this is the
|
||||
// RotatedTranslatedShape with the local transform baked in. For
|
||||
// triangle meshes and convex hulls it is the base shape at identity.
|
||||
mCompundShape = mChildren[0].shape;
|
||||
return;
|
||||
}
|
||||
|
||||
// Multiple shapes: build a static compound. Each child's ChildShapeEntry
|
||||
// already has its local transform baked into entry.shape (either as an RTS
|
||||
// wrapper for primitives, or pre-transformed vertices for meshes/convex).
|
||||
// Using localPos/localRot here would apply the offset a SECOND time.
|
||||
JPH::StaticCompoundShapeSettings compoundSettings;
|
||||
for (const auto& child : mChildren)
|
||||
{
|
||||
compoundSettings.AddShape(
|
||||
JPH::Vec3::sZero(),
|
||||
JPH::Quat::sIdentity(),
|
||||
child.shape
|
||||
);
|
||||
}
|
||||
|
||||
auto result = compoundSettings.Create();
|
||||
if (result.HasError())
|
||||
{
|
||||
Con::errorf("Jolt rebuildCompound Error: %s", result.GetError().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
mCompundShape = result.Get();
|
||||
}
|
||||
62
Engine/source/T3D/physics/jolt/joltCollision.h
Normal file
62
Engine/source/T3D/physics/jolt/joltCollision.h
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
#ifndef _JOLTCOLLISION_H_
|
||||
#define _JOLTCOLLISION_H_
|
||||
|
||||
#ifndef _T3D_PHYSICS_PHYSICSCOLLISION_H_
|
||||
#include "T3D/physics/physicsCollision.h"
|
||||
#endif
|
||||
|
||||
#ifndef _T3D_JOLT_PLUGIN_H_
|
||||
#include "T3D/physics/jolt/joltPlugin.h"
|
||||
#endif
|
||||
|
||||
#ifndef _MMATRIX_H_
|
||||
#include "math/mMatrix.h"
|
||||
#endif
|
||||
|
||||
#ifndef _TVECTOR_H_
|
||||
#include "core/util/tVector.h"
|
||||
#endif
|
||||
|
||||
struct ChildShapeEntry
|
||||
{
|
||||
JPH::Ref<JPH::Shape> shape;
|
||||
JPH::Mat44 localXfm;
|
||||
JPH::Vec3 localPos;
|
||||
JPH::Quat localRot;
|
||||
};
|
||||
|
||||
class JoltCollision : public PhysicsCollision
|
||||
{
|
||||
private:
|
||||
JPH::Ref<JPH::Shape> mCompundShape;
|
||||
Vector<ChildShapeEntry> mChildren;
|
||||
|
||||
public:
|
||||
JoltCollision();
|
||||
virtual ~JoltCollision();
|
||||
|
||||
// Add basic primitives
|
||||
void addPlane(const PlaneF& plane) override;
|
||||
|
||||
void addBox(const Point3F& halfWidth, const MatrixF& localXfm) override;
|
||||
void addSphere(F32 radius, const MatrixF& localXfm) override;
|
||||
void addCapsule(F32 radius, F32 height, const MatrixF& localXfm) override;
|
||||
bool addConvex(const Point3F* points, U32 count, const MatrixF& localXfm) override;
|
||||
bool addTriangleMesh(const Point3F* vert, U32 vertCount,
|
||||
const U32* index, U32 triCount,
|
||||
const MatrixF& localXfm) override;
|
||||
bool addHeightfield(const U16* heights, const bool* holes, U32 blockSize, F32 metersPerSample, const MatrixF& localXfm) override;
|
||||
|
||||
void rebuildCompound();
|
||||
|
||||
JPH::Ref<JPH::Shape> getJoltShape() const { return mCompundShape; }
|
||||
};
|
||||
|
||||
/// A strong pointer to a reference counted JoltCollision.
|
||||
typedef StrongRefPtr<JoltCollision> JoltCollisionRef;
|
||||
|
||||
|
||||
/// A weak pointer to a reference counted JoltCollision.
|
||||
typedef WeakRefPtr<JoltCollision> JoltCollisionWeakRef;
|
||||
|
||||
#endif // !_JOLTCOLLISION_H_
|
||||
371
Engine/source/T3D/physics/jolt/joltPlayer.cpp
Normal file
371
Engine/source/T3D/physics/jolt/joltPlayer.cpp
Normal file
|
|
@ -0,0 +1,371 @@
|
|||
#include "platform/platform.h"
|
||||
#include "T3D/physics/jolt/joltPlayer.h"
|
||||
#include "collision/collision.h"
|
||||
#include "scene/sceneObject.h"
|
||||
#include "T3D/player.h"
|
||||
|
||||
#ifdef Offset
|
||||
#pragma push_macro("Offset")
|
||||
#undef Offset
|
||||
#endif
|
||||
|
||||
#include <Jolt/Physics/Collision/Shape/CapsuleShape.h>
|
||||
#include <Jolt/Physics/Collision/CollideShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/RotatedTranslatedShape.h>
|
||||
#include <Jolt/Physics/Collision/BroadPhase/BroadPhaseLayer.h>
|
||||
|
||||
#ifdef Offset
|
||||
#pragma pop_macro("Offset")
|
||||
#endif
|
||||
|
||||
JoltPlayer::JoltPlayer()
|
||||
: mCharacter(NULL),
|
||||
mObject(NULL),
|
||||
mWorld(NULL),
|
||||
mMaxSlopeCos(0.0f),
|
||||
mStepHeight(0.0f),
|
||||
mOriginOffset(0.0f),
|
||||
mAllowSliding(false),
|
||||
mCollisionEnabled(true),
|
||||
mIsEnabled(false)
|
||||
{
|
||||
mDesiredVelocity = JPH::Vec3::sZero();
|
||||
}
|
||||
|
||||
JoltPlayer::~JoltPlayer()
|
||||
{
|
||||
if (mCharacter)
|
||||
{
|
||||
mWorld->getPhysicsSystem()->GetBodyInterface().SetUserData(
|
||||
mCharacter->GetBodyID(), 0);
|
||||
|
||||
if (mCollisionEnabled)
|
||||
disableCollision();
|
||||
|
||||
mCharacter = NULL;
|
||||
}
|
||||
mIsEnabled = false;
|
||||
}
|
||||
|
||||
void JoltPlayer::setTransform(const MatrixF& xfm)
|
||||
{
|
||||
if (!mCharacter)
|
||||
return;
|
||||
|
||||
const Point3F pos = xfm.getPosition();
|
||||
const QuatF rot(xfm);
|
||||
|
||||
// Character::SetPosition / SetRotation go through BodyInterface internally.
|
||||
mCharacter->SetPosition(joltCast(pos), JPH::EActivation::Activate);
|
||||
mCharacter->SetRotation(joltCast(rot), JPH::EActivation::Activate);
|
||||
}
|
||||
|
||||
MatrixF& JoltPlayer::getTransform(MatrixF* outMatrix)
|
||||
{
|
||||
*outMatrix = joltCast(mCharacter->GetWorldTransform());
|
||||
return *outMatrix;
|
||||
}
|
||||
|
||||
Box3F JoltPlayer::getWorldBounds()
|
||||
{
|
||||
const JPH::AABox box = mCharacter->GetShape()->GetWorldSpaceBounds(
|
||||
mCharacter->GetWorldTransform(), JPH::Vec3::sOne());
|
||||
return Box3F(Point3F(joltCast(box.mMin)), Point3F(joltCast(box.mMax)));
|
||||
}
|
||||
|
||||
void JoltPlayer::setSimulationEnabled(bool enabled)
|
||||
{
|
||||
if (!mCharacter || !mWorld)
|
||||
return;
|
||||
mIsEnabled = enabled;
|
||||
}
|
||||
|
||||
void JoltPlayer::init(const char* type, const Point3F& size, F32 runSurfaceCos,
|
||||
F32 stepHeight, SceneObject* obj, PhysicsWorld* world)
|
||||
{
|
||||
if (!obj || !world)
|
||||
return;
|
||||
|
||||
mObject = obj;
|
||||
mWorld = dynamic_cast<JoltWorld*>(world);
|
||||
if (!mWorld)
|
||||
return;
|
||||
|
||||
mStepHeight = stepHeight;
|
||||
mMaxSlopeCos = runSurfaceCos;
|
||||
|
||||
const F32 radius = mMax(size.x, size.y) * 0.5f;
|
||||
F32 height = mMax(0.01f, size.z);
|
||||
|
||||
mOriginOffset = (0.5f * height);
|
||||
|
||||
// Capsule centred at mOriginOffset along Z so the bottom of the capsule
|
||||
// sits at world Z = 0 (feet). rotFix converts Y-axis capsule → Z-up.
|
||||
const JPH::Quat rotFix = JPH::Quat::sRotation(
|
||||
JPH::Vec3::sAxisX(), JPH::DegreesToRadians(90.0f));
|
||||
|
||||
JPH::Ref<JPH::Shape> shape =
|
||||
JPH::RotatedTranslatedShapeSettings(
|
||||
JPH::Vec3(0.0f, 0.0f, mOriginOffset), rotFix,
|
||||
new JPH::CapsuleShape((0.5f * height) - radius, radius))
|
||||
.Create().Get();
|
||||
|
||||
JPH::Ref<JPH::CharacterSettings> settings = new JPH::CharacterSettings();
|
||||
|
||||
settings->mUp = JPH::Vec3::sAxisZ(); // Z-up world
|
||||
settings->mMaxSlopeAngle = mAcos(runSurfaceCos);
|
||||
settings->mShape = shape;
|
||||
settings->mMass = obj->getMass();
|
||||
settings->mFriction = 0.5f;
|
||||
settings->mLayer = Layers::CHARACTER;
|
||||
|
||||
// Accept contacts that touch the hemisphere at the bottom of the capsule
|
||||
settings->mSupportingVolume = JPH::Plane(JPH::Vec3::sAxisZ(), -radius);
|
||||
|
||||
// Torque's Player already applies gravity via the displacement passed to
|
||||
// move(). Suppress Jolt's built-in gravity here so it is not applied twice.
|
||||
settings->mGravityFactor = 0.0f;
|
||||
|
||||
Con::printf("JoltPlayer::init - maxSlopeAngle: %f degrees",
|
||||
mRadToDeg(mAcos(runSurfaceCos)));
|
||||
|
||||
// --- Create Character ---
|
||||
MatrixF objXfm = obj->getTransform();
|
||||
QuatF angPos(objXfm);
|
||||
Point3F pos;
|
||||
objXfm.getColumn(3, &pos);
|
||||
|
||||
mCharacter = new JPH::Character(
|
||||
settings,
|
||||
joltCast(pos),
|
||||
joltCast(angPos),
|
||||
reinterpret_cast<U64>(&mUserData),
|
||||
mWorld->getPhysicsSystem()
|
||||
);
|
||||
|
||||
// Ensure Z is treated as up (may also be set via CharacterBaseSettings but
|
||||
// calling SetUp explicitly guarantees it regardless of library version).
|
||||
mCharacter->SetUp(JPH::Vec3::sAxisZ());
|
||||
|
||||
getUserData().setObject(obj);
|
||||
|
||||
mCharacter->AddToPhysicsSystem(JPH::EActivation::Activate);
|
||||
setSimulationEnabled(true);
|
||||
}
|
||||
|
||||
void JoltPlayer::preUpdate(F32 dt)
|
||||
{
|
||||
if (!mCharacter || !mIsEnabled)
|
||||
return;
|
||||
|
||||
static const float cCollisionTolerance = 0.05f;
|
||||
mCharacter->PostSimulation(cCollisionTolerance);
|
||||
}
|
||||
|
||||
Point3F JoltPlayer::move(const VectorF& displacement, CollisionList& outCol)
|
||||
{
|
||||
if (!mCharacter || !mWorld)
|
||||
return Point3F::Zero;
|
||||
|
||||
if (!mIsEnabled)
|
||||
return joltCast(mCharacter->GetPosition());
|
||||
|
||||
// If preUpdate() was not called by the engine, call PostSimulation here.
|
||||
// (In architectures where preUpdate IS called this is a no-op because
|
||||
// PostSimulation is idempotent between physics steps.)
|
||||
static const float cCollisionTolerance = 0.05f;
|
||||
mCharacter->PostSimulation(cCollisionTolerance);
|
||||
|
||||
const F32 dt = TickSec;
|
||||
JPH::Vec3 velocity = joltCast(displacement) / dt;
|
||||
const JPH::Vec3 up = mCharacter->GetUp();
|
||||
|
||||
const JPH::Character::EGroundState groundState = mCharacter->GetGroundState();
|
||||
const bool isOnGround =
|
||||
groundState == JPH::Character::EGroundState::OnGround;
|
||||
|
||||
if (isOnGround && !mCharacter->IsSlopeTooSteep(mCharacter->GetGroundNormal()))
|
||||
{
|
||||
const JPH::Vec3 horizontalVel(velocity.GetX(), velocity.GetY(), 0.0f);
|
||||
mAllowSliding = horizontalVel.Length() > 0.01f;
|
||||
|
||||
// Clamp small upward residuals on flat ground to prevent floating
|
||||
if (velocity.GetZ() < 0.05f)
|
||||
velocity.SetZ(0.0f);
|
||||
|
||||
// Carry platform / conveyor velocity
|
||||
const JPH::Vec3 groundVel = mCharacter->GetGroundVelocity();
|
||||
if (groundVel.LengthSq() > 0.01f)
|
||||
{
|
||||
velocity.SetX(velocity.GetX() + groundVel.GetX());
|
||||
velocity.SetY(velocity.GetY() + groundVel.GetY());
|
||||
// Only add upward elevator velocity, not static-surface residuals
|
||||
if (groundVel.GetZ() > 0.05f)
|
||||
velocity.SetZ(velocity.GetZ() + groundVel.GetZ());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mAllowSliding = true;
|
||||
}
|
||||
|
||||
// Hand velocity to Jolt — the physics step (tickPhysics) will move the body.
|
||||
// Unlike CharacterVirtual there is no ExtendedUpdate here; collision response
|
||||
// and stair-stepping are resolved by Jolt's constraint solver.
|
||||
mCharacter->SetLinearVelocity(velocity);
|
||||
const bool activelyJumping = (velocity.GetZ() > 0.01f);
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Fill collision list — ground contact only
|
||||
// (Non-ground contacts come through the physics ContactListener;
|
||||
// sensor / trigger overlaps are found in findContact via broadphase)
|
||||
// -----------------------------------------------------------------
|
||||
if (isOnGround && !activelyJumping && outCol.getCount() < CollisionList::MaxCollisions)
|
||||
{
|
||||
const JPH::BodyID groundBodyID = mCharacter->GetGroundBodyID();
|
||||
JPH::BodyLockRead lock(
|
||||
mWorld->getPhysicsSystem()->GetBodyLockInterface(), groundBodyID);
|
||||
|
||||
if (lock.Succeeded())
|
||||
{
|
||||
const JPH::Body& body = lock.GetBody();
|
||||
PhysicsUserData* ud = reinterpret_cast<PhysicsUserData*>(body.GetUserData());
|
||||
|
||||
Collision& col = outCol.increment();
|
||||
col.point = joltCast(mCharacter->GetGroundPosition());
|
||||
col.normal = joltCast(mCharacter->GetGroundNormal());
|
||||
col.object = ud ? ud->getObject() : NULL;
|
||||
col.material = NULL;
|
||||
col.distance = 0.0f;
|
||||
col.faceDot = mDot(col.normal, VectorF(0.f, 0.f, 1.f));
|
||||
}
|
||||
}
|
||||
|
||||
// Return the position from the last physics step. tickPhysics() will move
|
||||
// the body further based on the velocity we just set; that result feeds into
|
||||
// the next tick via GetPosition() here.
|
||||
return joltCast(mCharacter->GetPosition());
|
||||
}
|
||||
|
||||
void JoltPlayer::findContact(SceneObject** contactObject, VectorF* contactNormal,
|
||||
Vector<SceneObject*>* outOverlapObjects) const
|
||||
{
|
||||
if (!mCharacter || !mIsEnabled)
|
||||
return;
|
||||
|
||||
const auto& system = mWorld->getPhysicsSystem();
|
||||
|
||||
// --- Primary ground contact -----------------------------------------
|
||||
// PostSimulation() already ran in preUpdate() and produced reliable ground
|
||||
// data. Pull it directly rather than re-deriving the normal from
|
||||
// CheckCollision's mPenetrationAxis.
|
||||
if (mCharacter->GetGroundState() == JPH::Character::EGroundState::OnGround
|
||||
&& !mCharacter->GetGroundBodyID().IsInvalid())
|
||||
{
|
||||
JPH::BodyLockRead lock(system->GetBodyLockInterface(),
|
||||
mCharacter->GetGroundBodyID());
|
||||
if (lock.Succeeded())
|
||||
{
|
||||
auto* ud = reinterpret_cast<PhysicsUserData*>(lock.GetBody().GetUserData());
|
||||
if (ud)
|
||||
{
|
||||
*contactObject = ud->getObject();
|
||||
*contactNormal = joltCast(mCharacter->GetGroundNormal());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Overlap harvest for triggers / items ---------------------------
|
||||
// CheckCollision tests the character's actual capsule (not an expanded
|
||||
// AABB) and returns every body within the skin distance. We only need
|
||||
// body references here — no normal math — so the collector is trivial.
|
||||
// Sensor bodies (triggers) and physical bodies (items) are both included
|
||||
// so that Player::findContact() can call potentialEnterObject() on them.
|
||||
struct OverlapCollector : public JPH::CollideShapeCollector
|
||||
{
|
||||
OverlapCollector(const JPH::PhysicsSystem* inSystem,
|
||||
Vector<SceneObject*>& inOverlaps)
|
||||
: mSystem(inSystem), mOverlaps(inOverlaps) {
|
||||
}
|
||||
|
||||
void AddHit(const JPH::CollideShapeResult& inResult) override
|
||||
{
|
||||
JPH::BodyLockRead lock(mSystem->GetBodyLockInterface(), inResult.mBodyID2);
|
||||
if (!lock.Succeeded())
|
||||
return;
|
||||
|
||||
auto* ud = reinterpret_cast<PhysicsUserData*>(
|
||||
lock.GetBody().GetUserData());
|
||||
if (!ud)
|
||||
return;
|
||||
|
||||
if (auto* obj = ud->getObject(); obj && !mOverlaps.contains(obj))
|
||||
mOverlaps.push_back(obj);
|
||||
}
|
||||
|
||||
const JPH::PhysicsSystem* mSystem;
|
||||
Vector<SceneObject*>& mOverlaps;
|
||||
};
|
||||
|
||||
const JPH::RVec3 pos = mCharacter->GetPosition();
|
||||
|
||||
OverlapCollector collector(system, *outOverlapObjects);
|
||||
mCharacter->CheckCollision(
|
||||
pos,
|
||||
mCharacter->GetRotation(),
|
||||
JPH::Vec3::sZero(), // no movement hint needed — pure overlap harvest
|
||||
0.1f, // 10 cm skin
|
||||
mCharacter->GetShape(),
|
||||
pos, // base offset for float precision near origin
|
||||
collector
|
||||
);
|
||||
}
|
||||
|
||||
bool JoltPlayer::testSpacials(const Point3F& nPos, const Point3F& nSize) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void JoltPlayer::setSpacials(const Point3F& nPos, const Point3F& nSize)
|
||||
{
|
||||
if (!mCharacter || !mWorld)
|
||||
return;
|
||||
|
||||
const F32 radius = mMax(nSize.x, nSize.y) * 0.5f;
|
||||
const F32 height = mMax(0.01f, nSize.z);
|
||||
|
||||
mOriginOffset = 0.5f * height;
|
||||
|
||||
const JPH::Quat rotFix = JPH::Quat::sRotation(
|
||||
JPH::Vec3::sAxisX(), JPH::DegreesToRadians(90.0f));
|
||||
|
||||
JPH::Ref<JPH::Shape> shape =
|
||||
JPH::RotatedTranslatedShapeSettings(
|
||||
JPH::Vec3(0.0f, 0.0f, mOriginOffset), rotFix,
|
||||
new JPH::CapsuleShape((0.5f * height) - radius, radius))
|
||||
.Create().Get();
|
||||
|
||||
// Character::SetShape is simpler than CharacterVirtual::SetShape —
|
||||
// no filter objects or TempAllocator required.
|
||||
const float maxPenetration = 0.05f;
|
||||
mCharacter->SetShape(shape, maxPenetration);
|
||||
}
|
||||
|
||||
void JoltPlayer::enableCollision()
|
||||
{
|
||||
if (!mCharacter || !mWorld || mCollisionEnabled)
|
||||
return;
|
||||
|
||||
mCollisionEnabled = true;
|
||||
// Character IS a body — adding it back to the physics system re-enables it
|
||||
mCharacter->AddToPhysicsSystem(JPH::EActivation::Activate);
|
||||
}
|
||||
|
||||
void JoltPlayer::disableCollision()
|
||||
{
|
||||
if (!mCharacter || !mWorld || !mCollisionEnabled)
|
||||
return;
|
||||
|
||||
mCollisionEnabled = false;
|
||||
mCharacter->RemoveFromPhysicsSystem();
|
||||
}
|
||||
69
Engine/source/T3D/physics/jolt/joltPlayer.h
Normal file
69
Engine/source/T3D/physics/jolt/joltPlayer.h
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
#ifndef _JOLTPLAYER_H_
|
||||
#define _JOLTPLAYER_H_
|
||||
|
||||
#ifndef _T3D_PHYSICS_PHYSICSPLAYER_H_
|
||||
#include "T3D/physics/physicsPlayer.h"
|
||||
#endif
|
||||
#ifndef _T3D_JOLT_PLUGIN_H_
|
||||
#include "T3D/physics/jolt/joltPlugin.h"
|
||||
#endif
|
||||
#ifndef _JOLTWORLD_H_
|
||||
#include "T3D/physics/jolt/joltWorld.h"
|
||||
#endif
|
||||
|
||||
#ifdef Offset
|
||||
#pragma push_macro("Offset")
|
||||
#undef Offset
|
||||
#endif
|
||||
|
||||
#include <Jolt/Physics/Character/Character.h>
|
||||
|
||||
#ifdef Offset
|
||||
#pragma pop_macro("Offset")
|
||||
#endif
|
||||
|
||||
class JoltPlayer : public PhysicsPlayer
|
||||
{
|
||||
protected:
|
||||
JoltWorld* mWorld;
|
||||
SceneObject* mObject;
|
||||
JPH::Ref<JPH::Character> mCharacter;
|
||||
JPH::Vec3 mDesiredVelocity;
|
||||
F32 mMaxSlopeCos;
|
||||
F32 mStepHeight;
|
||||
F32 mOriginOffset;
|
||||
bool mIsEnabled;
|
||||
bool mAllowSliding;
|
||||
bool mCollisionEnabled;
|
||||
|
||||
public:
|
||||
JoltPlayer();
|
||||
virtual ~JoltPlayer();
|
||||
|
||||
// PhysicsObject
|
||||
PhysicsWorld* getWorld() override { return mWorld; }
|
||||
void setTransform(const MatrixF& xfm) override;
|
||||
MatrixF& getTransform(MatrixF* outMatrix) override;
|
||||
Box3F getWorldBounds() override;
|
||||
void setSimulationEnabled(bool enabled) override;
|
||||
bool isSimulationEnabled() override { return mIsEnabled; }
|
||||
|
||||
// PhysicsPlayer
|
||||
void init(const char* type, const Point3F& size, F32 runSurfaceCos,
|
||||
F32 stepHeight, SceneObject* obj, PhysicsWorld* world) override;
|
||||
|
||||
void preUpdate(F32 dt);
|
||||
|
||||
Point3F move(const VectorF& displacement, CollisionList& outCol) override;
|
||||
|
||||
void findContact(SceneObject** contactObject, VectorF* contactNormal,
|
||||
Vector<SceneObject*>* outOverlapObjects) const override;
|
||||
bool testSpacials(const Point3F& nPos, const Point3F& nSize) const override;
|
||||
void setSpacials(const Point3F& nPos, const Point3F& nSize) override;
|
||||
void enableCollision() override;
|
||||
void disableCollision() override;
|
||||
|
||||
JPH::Ref<JPH::Character> getCharacter() { return mCharacter; }
|
||||
};
|
||||
|
||||
#endif // _JOLTPLAYER_H_
|
||||
303
Engine/source/T3D/physics/jolt/joltPlugin.cpp
Normal file
303
Engine/source/T3D/physics/jolt/joltPlugin.cpp
Normal file
|
|
@ -0,0 +1,303 @@
|
|||
|
||||
#include "platform/platform.h"
|
||||
#include "T3D/physics/jolt/joltPlugin.h"
|
||||
|
||||
#include "T3D/physics/jolt/joltWorld.h"
|
||||
#include "T3D/physics/jolt/joltCollision.h"
|
||||
#include "T3D/physics/jolt/joltBody.h"
|
||||
#include "T3D/physics/jolt/joltPlayer.h"
|
||||
#include "T3D/gameBase/gameProcess.h"
|
||||
#include "core/util/tNamedFactory.h"
|
||||
#include "gfx/sim/debugDraw.h"
|
||||
|
||||
AFTER_MODULE_INIT(Sim)
|
||||
{
|
||||
NamedFactory<PhysicsPlugin>::add("JoltPhysics", &JoltPlugin::create);
|
||||
}
|
||||
|
||||
JoltPlugin::JoltPlugin()
|
||||
{
|
||||
JPH::RegisterDefaultAllocator();
|
||||
|
||||
JPH::Factory::sInstance = new JPH::Factory();
|
||||
JPH::RegisterTypes();
|
||||
|
||||
#ifdef JPH_DEBUG_RENDERER
|
||||
JPH::DebugRenderer::sInstance = new JoltDebugRenderer();
|
||||
#endif // TORQUE_TOOLS
|
||||
}
|
||||
|
||||
JoltPlugin::~JoltPlugin()
|
||||
{
|
||||
delete JPH::Factory::sInstance;
|
||||
#ifdef JPH_DEBUG_RENDERER
|
||||
delete JPH::DebugRenderer::sInstance;
|
||||
JPH::DebugRenderer::sInstance = nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
void JoltPlugin::reset()
|
||||
{
|
||||
// First delete all the cleanup objects.
|
||||
if (getPhysicsCleanup())
|
||||
getPhysicsCleanup()->deleteAllObjects();
|
||||
|
||||
getPhysicsResetSignal().trigger(PhysicsResetEvent_Restore);
|
||||
|
||||
// Now let each world reset itself.
|
||||
Map<StringNoCase, PhysicsWorld*>::Iterator iter = mPhysicsWorldLookup.begin();
|
||||
for (; iter != mPhysicsWorldLookup.end(); iter++)
|
||||
iter->value->reset();
|
||||
}
|
||||
|
||||
PhysicsPlugin* JoltPlugin::create()
|
||||
{
|
||||
return new JoltPlugin();
|
||||
}
|
||||
|
||||
|
||||
void JoltPlugin::destroyPlugin()
|
||||
{
|
||||
// Cleanup any worlds that are still kicking.
|
||||
Map<StringNoCase, PhysicsWorld*>::Iterator iter = mPhysicsWorldLookup.begin();
|
||||
for (; iter != mPhysicsWorldLookup.end(); iter++)
|
||||
{
|
||||
iter->value->destroyWorld();
|
||||
delete iter->value;
|
||||
}
|
||||
mPhysicsWorldLookup.clear();
|
||||
|
||||
delete this;
|
||||
}
|
||||
|
||||
PhysicsCollision* JoltPlugin::createCollision()
|
||||
{
|
||||
return new JoltCollision();
|
||||
}
|
||||
|
||||
PhysicsBody* JoltPlugin::createBody()
|
||||
{
|
||||
return new JoltBody();
|
||||
}
|
||||
|
||||
PhysicsPlayer* JoltPlugin::createPlayer()
|
||||
{
|
||||
return new JoltPlayer();
|
||||
}
|
||||
|
||||
bool JoltPlugin::isSimulationEnabled() const
|
||||
{
|
||||
bool ret = false;
|
||||
JoltWorld* world = static_cast<JoltWorld*>(getWorld(smClientWorldName));
|
||||
if (world)
|
||||
{
|
||||
ret = world->getEnabled();
|
||||
return ret;
|
||||
}
|
||||
|
||||
world = static_cast<JoltWorld*>(getWorld(smServerWorldName));
|
||||
if (world)
|
||||
{
|
||||
ret = world->getEnabled();
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void JoltPlugin::enableSimulation(const String& worldName, bool enable)
|
||||
{
|
||||
JoltWorld* world = static_cast<JoltWorld*>(getWorld(worldName));
|
||||
if (world)
|
||||
world->setEnabled(enable);
|
||||
}
|
||||
|
||||
|
||||
void JoltPlugin::setTimeScale(const F32 timeScale)
|
||||
{
|
||||
// Grab both the client and
|
||||
// server worlds and set their time
|
||||
// scales to the passed value.
|
||||
JoltWorld* world = static_cast<JoltWorld*>(getWorld(smClientWorldName));
|
||||
if (world)
|
||||
world->setEditorTimeScale(timeScale);
|
||||
|
||||
world = static_cast<JoltWorld*>(getWorld(smServerWorldName));
|
||||
if (world)
|
||||
world->setEditorTimeScale(timeScale);
|
||||
}
|
||||
|
||||
|
||||
const F32 JoltPlugin::getTimeScale() const
|
||||
{
|
||||
// Grab both the client and
|
||||
// server worlds and call
|
||||
// setEnabled( true ) on them.
|
||||
JoltWorld* world = static_cast<JoltWorld*>(getWorld(smClientWorldName));
|
||||
if (!world)
|
||||
{
|
||||
world = static_cast<JoltWorld*>(getWorld(smServerWorldName));
|
||||
if (!world)
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return world->getEditorTimeScale();
|
||||
}
|
||||
|
||||
bool JoltPlugin::createWorld(const String& worldName)
|
||||
{
|
||||
Map<StringNoCase, PhysicsWorld*>::Iterator iter = mPhysicsWorldLookup.find(worldName);
|
||||
PhysicsWorld* world = NULL;
|
||||
|
||||
iter != mPhysicsWorldLookup.end() ? world = (*iter).value : world = NULL;
|
||||
|
||||
if (world)
|
||||
{
|
||||
Con::errorf("BtPlugin::createWorld - %s world already exists!", worldName.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
world = new JoltWorld();
|
||||
|
||||
if (worldName.equal(smClientWorldName, String::NoCase))
|
||||
world->initWorld(false, ClientProcessList::get());
|
||||
else
|
||||
world->initWorld(true, ServerProcessList::get());
|
||||
|
||||
mPhysicsWorldLookup.insert(worldName, world);
|
||||
|
||||
return world != NULL;
|
||||
}
|
||||
|
||||
void JoltPlugin::destroyWorld(const String& worldName)
|
||||
{
|
||||
Map<StringNoCase, PhysicsWorld*>::Iterator iter = mPhysicsWorldLookup.find(worldName);
|
||||
if (iter == mPhysicsWorldLookup.end())
|
||||
return;
|
||||
|
||||
PhysicsWorld* world = (*iter).value;
|
||||
world->destroyWorld();
|
||||
|
||||
|
||||
mPhysicsWorldLookup.erase(iter);
|
||||
}
|
||||
|
||||
PhysicsWorld* JoltPlugin::getWorld(const String& worldName) const
|
||||
{
|
||||
if (mPhysicsWorldLookup.isEmpty())
|
||||
return NULL;
|
||||
|
||||
Map<StringNoCase, PhysicsWorld*>::ConstIterator iter = mPhysicsWorldLookup.find(worldName);
|
||||
|
||||
return iter != mPhysicsWorldLookup.end() ? (*iter).value : NULL;
|
||||
}
|
||||
|
||||
PhysicsWorld* JoltPlugin::getWorld() const
|
||||
{
|
||||
if (mPhysicsWorldLookup.size() == 0)
|
||||
return NULL;
|
||||
|
||||
Map<StringNoCase, PhysicsWorld*>::ConstIterator iter = mPhysicsWorldLookup.begin();
|
||||
return iter->value;
|
||||
}
|
||||
|
||||
U32 JoltPlugin::getWorldCount() const
|
||||
{
|
||||
return mPhysicsWorldLookup.size();
|
||||
}
|
||||
|
||||
#ifdef JPH_DEBUG_RENDERER
|
||||
|
||||
JoltDebugRenderer::JoltDebugRenderer()
|
||||
{
|
||||
JPH::DebugRenderer::Initialize();
|
||||
}
|
||||
|
||||
void JoltDebugRenderer::DrawLine(JPH::RVec3Arg inFrom, JPH::RVec3Arg inTo, JPH::ColorArg inColor)
|
||||
{
|
||||
DebugDrawer::get()->drawLine(joltCast(inFrom), joltCast(inTo), fromJolt(inColor));
|
||||
}
|
||||
|
||||
void JoltDebugRenderer::DrawTriangle(JPH::RVec3Arg inV1, JPH::RVec3Arg inV2, JPH::RVec3Arg inV3, JPH::ColorArg inColor, ECastShadow inCastShadow)
|
||||
{
|
||||
DebugDrawer::get()->drawTri(joltCast(inV1), joltCast(inV2), joltCast(inV3), fromJolt(inColor));
|
||||
}
|
||||
|
||||
JPH::DebugRenderer::Batch JoltDebugRenderer::CreateTriangleBatch(const Triangle* inTriangles, int inTriangleCount)
|
||||
{
|
||||
BatchImpl* batch = new BatchImpl;
|
||||
|
||||
if (inTriangles && inTriangleCount)
|
||||
batch->mTriangles.assign(inTriangles, inTriangles + inTriangleCount);
|
||||
|
||||
return batch;
|
||||
}
|
||||
|
||||
JPH::DebugRenderer::Batch JoltDebugRenderer::CreateTriangleBatch(const Vertex* inVertices, int inVertexCount, const JPH::uint32* inIndices, int inIndexCount)
|
||||
{
|
||||
BatchImpl* batch = new BatchImpl;
|
||||
if (inVertices == nullptr || inVertexCount == 0 || inIndices == nullptr || inIndexCount == 0)
|
||||
return batch;
|
||||
|
||||
// Convert indexed triangle list to triangle list
|
||||
batch->mTriangles.resize(inIndexCount / 3);
|
||||
for (size_t t = 0; t < batch->mTriangles.size(); ++t)
|
||||
{
|
||||
JPH::DebugRenderer::Triangle& triangle = batch->mTriangles[t];
|
||||
triangle.mV[0] = inVertices[inIndices[t * 3 + 0]];
|
||||
triangle.mV[1] = inVertices[inIndices[t * 3 + 1]];
|
||||
triangle.mV[2] = inVertices[inIndices[t * 3 + 2]];
|
||||
}
|
||||
|
||||
return batch;
|
||||
}
|
||||
|
||||
void JoltDebugRenderer::DrawGeometry(JPH::RMat44Arg inModelMatrix, const JPH::AABox& inWorldSpaceBounds, float inLODScaleSq, JPH::ColorArg inModelColor, const GeometryRef& inGeometry, ECullMode inCullMode, ECastShadow inCastShadow, EDrawMode inDrawMode)
|
||||
{
|
||||
const auto& lod = inGeometry->GetLOD(
|
||||
mFrameState.cameraPos,
|
||||
inWorldSpaceBounds,
|
||||
inLODScaleSq
|
||||
);
|
||||
|
||||
const BatchImpl* batch = static_cast<const BatchImpl*>(lod.mTriangleBatch.GetPtr());
|
||||
|
||||
if (!batch)
|
||||
return;
|
||||
|
||||
const U32 triCount = batch->mTriangles.size();
|
||||
if (triCount == 0)
|
||||
return;
|
||||
|
||||
if (!mFrameState.frustum.getBounds().isOverlapped(fromJolt(inWorldSpaceBounds)))
|
||||
return;
|
||||
|
||||
// Start one batched primitive build
|
||||
GFX->setupGenericShaders();
|
||||
GFXStateBlockDesc desc;
|
||||
desc.setZReadWrite(false);
|
||||
GFX->setStateBlockByDesc(desc);
|
||||
PrimBuild::begin(GFXLineList, triCount * 6);
|
||||
for (const auto& tri : batch->mTriangles)
|
||||
{
|
||||
JPH::Vec3 v0 = inModelMatrix * JPH::Vec3(tri.mV[0].mPosition);
|
||||
JPH::Vec3 v1 = inModelMatrix * JPH::Vec3(tri.mV[1].mPosition);
|
||||
JPH::Vec3 v2 = inModelMatrix * JPH::Vec3(tri.mV[2].mPosition);
|
||||
JPH::Color color = inModelColor * tri.mV[0].mColor;
|
||||
PrimBuild::color(fromJolt(color));
|
||||
// Edge 1
|
||||
PrimBuild::vertex3f(v0.GetX(), v0.GetY(), v0.GetZ());
|
||||
PrimBuild::vertex3f(v1.GetX(), v1.GetY(), v1.GetZ());
|
||||
|
||||
// Edge 2
|
||||
PrimBuild::vertex3f(v1.GetX(), v1.GetY(), v1.GetZ());
|
||||
PrimBuild::vertex3f(v2.GetX(), v2.GetY(), v2.GetZ());
|
||||
|
||||
// Edge 3
|
||||
PrimBuild::vertex3f(v2.GetX(), v2.GetY(), v2.GetZ());
|
||||
PrimBuild::vertex3f(v0.GetX(), v0.GetY(), v0.GetZ());
|
||||
}
|
||||
|
||||
PrimBuild::end(true);
|
||||
}
|
||||
#endif
|
||||
211
Engine/source/T3D/physics/jolt/joltPlugin.h
Normal file
211
Engine/source/T3D/physics/jolt/joltPlugin.h
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
#ifndef _T3D_JOLT_PLUGIN_H_
|
||||
#define _T3D_JOLT_PLUGIN_H_
|
||||
|
||||
#ifndef _T3D_PHYSICS_PHYSICSPLUGIN_H_
|
||||
#include "T3D/physics/physicsPlugin.h"
|
||||
#endif
|
||||
|
||||
#ifndef _SCENERENDERSTATE_H_
|
||||
#include "scene/sceneRenderState.h"
|
||||
#endif
|
||||
|
||||
#if defined(TORQUE_TOOLS) && defined(TORQUE_OS_WIN)
|
||||
#ifndef JPH_DEBUG_RENDERER
|
||||
#define JPH_DEBUG_RENDERER
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Save and undefine the macro if it exists
|
||||
#ifdef Offset
|
||||
#pragma push_macro("Offset")
|
||||
#undef Offset
|
||||
#endif
|
||||
|
||||
#include <Jolt/Jolt.h>
|
||||
#include <Jolt/Physics/PhysicsSystem.h>
|
||||
#include <Jolt/RegisterTypes.h>
|
||||
#include <Jolt/Core/TempAllocator.h>
|
||||
#include <Jolt/Core/StreamWrapper.h>
|
||||
#include <Jolt/Physics/Collision/RayCast.h>
|
||||
#include <Jolt/Physics/Collision/CastResult.h>
|
||||
#ifdef JPH_DEBUG_RENDERER
|
||||
#include <Jolt/Renderer/DebugRenderer.h>
|
||||
#endif
|
||||
#include <Jolt/Core/JobSystemThreadPool.h>
|
||||
#include <Jolt/Physics/PhysicsSettings.h>
|
||||
#include <Jolt/Physics/StateRecorderImpl.h>
|
||||
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/CylinderShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/MeshShape.h>
|
||||
#include <Jolt/Physics/Body/BodyCreationSettings.h>
|
||||
#include <Jolt/Physics/Constraints/SixDOFConstraint.h>
|
||||
#include <Jolt/Physics/Body/BodyActivationListener.h>
|
||||
|
||||
#ifdef Offset
|
||||
// Restore the original macro after includes
|
||||
#pragma pop_macro("Offset")
|
||||
#endif
|
||||
|
||||
inline Point3F joltCast(const JPH::Vec3& vec)
|
||||
{
|
||||
return Point3F(vec.GetX(), vec.GetY(), vec.GetZ());
|
||||
}
|
||||
|
||||
inline JPH::Vec3 joltCast(const Point3F& point)
|
||||
{
|
||||
return JPH::Vec3(point.x, point.y, point.z);
|
||||
}
|
||||
|
||||
inline QuatF joltCast(const JPH::Quat& quat)
|
||||
{
|
||||
/// The Torque quat has the opposite winding order.
|
||||
return QuatF(-quat.GetX(), -quat.GetY(), -quat.GetZ(), quat.GetW());
|
||||
}
|
||||
|
||||
inline JPH::Quat joltCast(const QuatF& quat)
|
||||
{
|
||||
/// The Torque quat has the opposite winding order.
|
||||
return JPH::Quat(-quat.x, -quat.y, -quat.z, quat.w);
|
||||
}
|
||||
|
||||
inline JPH::RMat44 joltCast(const MatrixF& m)
|
||||
{
|
||||
QuatF ang(m);
|
||||
return JPH::Mat44::sRotationTranslation(joltCast(ang), joltCast(m.getPosition()));
|
||||
}
|
||||
|
||||
inline MatrixF joltCast(const JPH::Mat44& xfm)
|
||||
{
|
||||
MatrixF out;
|
||||
|
||||
QuatF ang = joltCast(xfm.GetQuaternion());
|
||||
ang.setMatrix(&out);
|
||||
out.setPosition(joltCast(xfm.GetTranslation()));
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
inline void toJolt(const MatrixF& mat, JPH::RVec3& pos, JPH::Quat& rot)
|
||||
{
|
||||
Point3F p;
|
||||
p = mat.getPosition();
|
||||
|
||||
pos = joltCast(p);
|
||||
|
||||
QuatF q(mat);
|
||||
|
||||
rot = joltCast(q);
|
||||
}
|
||||
|
||||
inline JPH::AABox toJolt(const Box3F& box)
|
||||
{
|
||||
return JPH::AABox(
|
||||
joltCast(box.minExtents),
|
||||
joltCast(box.maxExtents)
|
||||
);
|
||||
}
|
||||
|
||||
inline Box3F fromJolt(const JPH::AABox& box)
|
||||
{
|
||||
return Box3F(
|
||||
joltCast(box.mMin),
|
||||
joltCast(box.mMax)
|
||||
);
|
||||
}
|
||||
|
||||
#ifndef _COLOR_H_
|
||||
#include "core/color.h"
|
||||
#endif
|
||||
|
||||
inline JPH::Color toJolt(const ColorI& c)
|
||||
{
|
||||
return JPH::Color(c.red, c.green, c.blue, c.alpha);
|
||||
}
|
||||
|
||||
|
||||
|
||||
inline ColorI fromJolt(const JPH::Color& c)
|
||||
{
|
||||
return ColorI(c.r, c.g, c.b, c.a);
|
||||
}
|
||||
|
||||
#ifdef JPH_DEBUG_RENDERER
|
||||
struct DebugRenderFrameState
|
||||
{
|
||||
Frustum frustum;
|
||||
JPH::Vec3 cameraPos;
|
||||
};
|
||||
|
||||
class JoltDebugRenderer final : public JPH::DebugRenderer
|
||||
{
|
||||
public:
|
||||
JPH_OVERRIDE_NEW_DELETE
|
||||
explicit JoltDebugRenderer();
|
||||
virtual void DrawLine(JPH::RVec3Arg inFrom, JPH::RVec3Arg inTo, JPH::ColorArg inColor) override;
|
||||
virtual void DrawTriangle(JPH::RVec3Arg inV1, JPH::RVec3Arg inV2, JPH::RVec3Arg inV3, JPH::ColorArg inColor, ECastShadow inCastShadow) override;
|
||||
virtual JPH::DebugRenderer::Batch CreateTriangleBatch(const Triangle* inTriangles, int inTriangleCount) override;
|
||||
virtual JPH::DebugRenderer::Batch CreateTriangleBatch(const Vertex* inVertices, int inVertexCount, const JPH::uint32* inIndices, int inIndexCount) override;
|
||||
virtual void DrawGeometry(JPH::RMat44Arg inModelMatrix, const JPH::AABox& inWorldSpaceBounds, float inLODScaleSq, JPH::ColorArg inModelColor, const GeometryRef& inGeometry, ECullMode inCullMode, ECastShadow inCastShadow, EDrawMode inDrawMode) override;
|
||||
virtual void DrawText3D(JPH::RVec3Arg inPosition, const JPH::string_view& inString, JPH::ColorArg inColor, float inHeight) override {};
|
||||
|
||||
|
||||
void SetFrameState(const DebugRenderFrameState& state)
|
||||
{
|
||||
mFrameState = state;
|
||||
}
|
||||
|
||||
/// Implementation specific batch object
|
||||
class BatchImpl : public JPH::RefTargetVirtual
|
||||
{
|
||||
public:
|
||||
JPH_OVERRIDE_NEW_DELETE
|
||||
|
||||
virtual void AddRef() override
|
||||
{
|
||||
++mRefCount;
|
||||
}
|
||||
virtual void Release() override
|
||||
{
|
||||
if (--mRefCount == 0)
|
||||
delete this;
|
||||
}
|
||||
|
||||
JPH::Array<JPH::DebugRenderer::Triangle> mTriangles;
|
||||
|
||||
private:
|
||||
JPH::atomic<U32> mRefCount = 0;
|
||||
};
|
||||
|
||||
private:
|
||||
DebugRenderFrameState mFrameState;
|
||||
};
|
||||
#endif
|
||||
|
||||
class JoltPlugin : public PhysicsPlugin
|
||||
{
|
||||
public:
|
||||
JoltPlugin();
|
||||
~JoltPlugin();
|
||||
|
||||
/// Create function for factory.
|
||||
static PhysicsPlugin* create();
|
||||
|
||||
// PhysicsPlugin
|
||||
virtual void destroyPlugin();
|
||||
virtual void reset();
|
||||
virtual PhysicsCollision* createCollision();
|
||||
virtual PhysicsBody* createBody();
|
||||
virtual PhysicsPlayer* createPlayer();
|
||||
virtual bool isSimulationEnabled() const;
|
||||
virtual void enableSimulation(const String& worldName, bool enable);
|
||||
virtual void setTimeScale(const F32 timeScale);
|
||||
virtual const F32 getTimeScale() const;
|
||||
virtual bool createWorld(const String& worldName);
|
||||
virtual void destroyWorld(const String& worldName);
|
||||
virtual PhysicsWorld* getWorld(const String& worldName) const;
|
||||
virtual PhysicsWorld* getWorld() const;
|
||||
virtual U32 getWorldCount() const;
|
||||
};
|
||||
|
||||
#endif // !_T3D_JOLT_PLUGIN_H_
|
||||
391
Engine/source/T3D/physics/jolt/joltWorld.cpp
Normal file
391
Engine/source/T3D/physics/jolt/joltWorld.cpp
Normal file
|
|
@ -0,0 +1,391 @@
|
|||
|
||||
#include "platform/platform.h"
|
||||
#include "T3D/physics/jolt/joltWorld.h"
|
||||
#include "T3D/physics/jolt/joltBody.h"
|
||||
#include "T3D/physics/jolt/joltPlayer.h"
|
||||
|
||||
#include "platform/profiler.h"
|
||||
#include "sim/netConnection.h"
|
||||
#include "console/console.h"
|
||||
#include "console/consoleTypes.h"
|
||||
#include "scene/sceneRenderState.h"
|
||||
#include "T3D/gameBase/gameProcess.h"
|
||||
|
||||
#include "T3D/physics/physicsUserData.h"
|
||||
|
||||
// Save and undefine the macro if it exists
|
||||
#ifdef Offset
|
||||
#pragma push_macro("Offset")
|
||||
#undef Offset
|
||||
#endif
|
||||
|
||||
#include <Jolt/Physics/Collision/RayCast.h>
|
||||
#include <Jolt/Physics/Collision/CastResult.h>
|
||||
#include <Jolt/Physics/Body/BodyLock.h>
|
||||
#include <Jolt/Physics/Body/Body.h>
|
||||
#include <Jolt/Physics/Collision/NarrowPhaseQuery.h>
|
||||
#include <Jolt/Physics/Collision/CollideShape.h>
|
||||
|
||||
#ifdef Offset
|
||||
// Restore the original macro after includes
|
||||
#pragma pop_macro("Offset")
|
||||
#endif
|
||||
|
||||
JoltWorld::JoltWorld()
|
||||
:mIsEnabled(false),
|
||||
mIsSimulating(false),
|
||||
mTickCount(0),
|
||||
mProcessList(NULL),
|
||||
mEditorTimeScale(1.0f)
|
||||
{
|
||||
mTempAllocator = nullptr;
|
||||
mJobSystem = nullptr;
|
||||
}
|
||||
|
||||
|
||||
JoltWorld::~JoltWorld()
|
||||
{
|
||||
}
|
||||
|
||||
bool JoltWorld::initWorld(bool isServer, ProcessList* processList)
|
||||
{
|
||||
const U32 cMaxBodies = 65536;
|
||||
const U32 cNumBodyMutexes = 1024;
|
||||
const U32 cMaxBodyPairs = 65536;
|
||||
const U32 cMaxContactConstraints = 10240;
|
||||
|
||||
mTempAllocator = new JPH::TempAllocatorImpl(10 * 1024 * 1024);
|
||||
|
||||
// thread pool of 2 threads?
|
||||
mJobSystem = new JPH::JobSystemThreadPool(
|
||||
JPH::cMaxPhysicsJobs,
|
||||
JPH::cMaxPhysicsBarriers,
|
||||
-1
|
||||
);
|
||||
|
||||
mPhysicsSystem.Init(
|
||||
cMaxBodies,
|
||||
cNumBodyMutexes,
|
||||
cMaxBodyPairs,
|
||||
cMaxContactConstraints,
|
||||
mBroadPhaseLayerInterface,
|
||||
mObjectVsBroadPhaseLayerFilter,
|
||||
mObjectLayerPairFilter
|
||||
);
|
||||
|
||||
mIsEnabled = true;
|
||||
|
||||
mPhysicsSystem.SetGravity(joltCast(mGravity));
|
||||
|
||||
mProcessList = processList;
|
||||
mProcessList->preTickSignal().notify(this, &JoltWorld::getPhysicsResults);
|
||||
mProcessList->postTickSignal().notify(this, &JoltWorld::tickPhysics, 1000.0f);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void JoltWorld::destroyWorldInternal()
|
||||
{
|
||||
// Stop ticking
|
||||
mIsEnabled = false;
|
||||
|
||||
// Remove all bodies
|
||||
JPH::BodyInterface& bi = mPhysicsSystem.GetBodyInterface();
|
||||
JPH::BodyIDVector bodies;
|
||||
mPhysicsSystem.GetBodies(bodies);
|
||||
|
||||
for (JPH::BodyID id : bodies)
|
||||
{
|
||||
if (bi.IsAdded(id))
|
||||
bi.RemoveBody(id);
|
||||
}
|
||||
|
||||
// Remove tick listeners
|
||||
if (mProcessList)
|
||||
{
|
||||
mProcessList->preTickSignal().remove(this, &JoltWorld::getPhysicsResults);
|
||||
mProcessList->postTickSignal().remove(this, &JoltWorld::tickPhysics);
|
||||
mProcessList = nullptr;
|
||||
}
|
||||
|
||||
// Delete job system and temp allocator
|
||||
delete mJobSystem;
|
||||
mJobSystem = nullptr;
|
||||
|
||||
delete mTempAllocator;
|
||||
mTempAllocator = nullptr;
|
||||
|
||||
delete this;
|
||||
}
|
||||
|
||||
void JoltWorld::destroyWorld()
|
||||
{
|
||||
mDestroyPending.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
class SensorExcludeFilter final : public JPH::BodyFilter
|
||||
{
|
||||
public:
|
||||
bool ShouldCollideLocked(const JPH::Body& inBody) const override
|
||||
{
|
||||
return !inBody.IsSensor();
|
||||
}
|
||||
};
|
||||
|
||||
bool JoltWorld::castRay(const Point3F& startPnt, const Point3F& endPnt, RayInfo* ri, const Point3F& impulse)
|
||||
{
|
||||
JPH::Vec3 startV(startPnt.x, startPnt.y, startPnt.z);
|
||||
JPH::Vec3 dir = JPH::Vec3(endPnt.x - startPnt.x, endPnt.y - startPnt.y, endPnt.z - startPnt.z);
|
||||
|
||||
F32 rayLength = dir.Length();
|
||||
|
||||
if (rayLength <= 0.0001f)
|
||||
return false;
|
||||
|
||||
JPH::RRayCast ray(startV, dir);
|
||||
JPH::RayCastResult result;
|
||||
|
||||
// Exclude sensor bodies: they are non-solid trigger volumes and must not
|
||||
// appear as solid hits to projectiles or other physics ray casts.
|
||||
SensorExcludeFilter sensorFilter;
|
||||
if (!mPhysicsSystem.GetNarrowPhaseQuery().CastRay(ray, result, {}, {}, sensorFilter))
|
||||
return false;
|
||||
|
||||
JPH::BodyLockRead lock(
|
||||
mPhysicsSystem.GetBodyLockInterface(),
|
||||
result.mBodyID
|
||||
);
|
||||
|
||||
if (!lock.Succeeded())
|
||||
return false;
|
||||
|
||||
dir /= rayLength;
|
||||
|
||||
const JPH::Body& body = lock.GetBody();
|
||||
|
||||
JPH::RVec3 hitPos = ray.GetPointOnRay(result.mFraction);
|
||||
|
||||
JPH::Vec3 normal = body.GetWorldSpaceSurfaceNormal(result.mSubShapeID2, hitPos);
|
||||
|
||||
if (ri)
|
||||
{
|
||||
ri->point.set(joltCast(hitPos));
|
||||
ri->normal.set(joltCast(normal));
|
||||
ri->distance = result.mFraction * rayLength;
|
||||
|
||||
// A body with no UserData means no associated SceneObject (e.g. internal
|
||||
// collision geometry). Return the hit but leave ri->object as null rather
|
||||
// than hiding the hit entirely.
|
||||
PhysicsUserData* userData = body.GetUserData()
|
||||
? PhysicsUserData::cast((void*)body.GetUserData())
|
||||
: nullptr;
|
||||
|
||||
ri->object = userData ? userData->getObject() : nullptr;
|
||||
ri->material = nullptr;
|
||||
ri->face = 0;
|
||||
ri->faceDot = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
PhysicsBody* JoltWorld::castRay(const Point3F& start, const Point3F& end, U32 bodyTypes)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void JoltWorld::explosion(const Point3F& pos, F32 radius, F32 forceMagnitude)
|
||||
{
|
||||
JPH::Vec3 center(pos.x, pos.y, pos.z);
|
||||
float radiusSq = radius * radius;
|
||||
|
||||
// Broadphase query: get all bodies in sphere
|
||||
JPH::SphereShape sphere(radius);
|
||||
|
||||
JPH::CollideShapeSettings settings;
|
||||
settings.mMaxSeparationDistance = 0.0f;
|
||||
|
||||
JPH::RMat44 transform = JPH::RMat44::sTranslation(center);
|
||||
|
||||
struct ExplosionCollector : public JPH::CollideShapeCollector
|
||||
{
|
||||
std::vector<JPH::BodyID> bodies;
|
||||
|
||||
void AddHit(const JPH::CollideShapeResult& result) override
|
||||
{
|
||||
bodies.push_back(result.mBodyID2);
|
||||
}
|
||||
};
|
||||
|
||||
ExplosionCollector collector;
|
||||
|
||||
mPhysicsSystem.GetNarrowPhaseQuery().CollideShape(
|
||||
&sphere,
|
||||
JPH::Vec3::sOne(),
|
||||
transform,
|
||||
settings,
|
||||
JPH::Vec3::sZero(),
|
||||
collector
|
||||
);
|
||||
|
||||
auto& bodyInterface = mPhysicsSystem.GetBodyInterface();
|
||||
|
||||
for (const JPH::BodyID& bodyID : collector.bodies)
|
||||
{
|
||||
if (!bodyInterface.IsAdded(bodyID))
|
||||
continue;
|
||||
|
||||
// Wake the body FIRST
|
||||
bodyInterface.ActivateBody(bodyID);
|
||||
|
||||
// Lock body safely
|
||||
JPH::BodyLockWrite lock(
|
||||
mPhysicsSystem.GetBodyLockInterface(),
|
||||
bodyID
|
||||
);
|
||||
|
||||
if (!lock.Succeeded())
|
||||
continue;
|
||||
|
||||
JPH::Body& body = lock.GetBody();
|
||||
|
||||
// Skip sensors and non-dynamic bodies — sensors are trigger volumes and
|
||||
// static/kinematic bodies don't respond to impulses.
|
||||
if (body.IsSensor() || !body.IsDynamic())
|
||||
continue;
|
||||
|
||||
JPH::Vec3 bodyPos = body.GetCenterOfMassPosition();
|
||||
JPH::Vec3 offset = bodyPos - center;
|
||||
|
||||
float distSq = offset.LengthSq();
|
||||
if (distSq > radiusSq || distSq <= 0.0001f)
|
||||
continue;
|
||||
|
||||
float dist = sqrtf(distSq);
|
||||
|
||||
// Normalize direction
|
||||
JPH::Vec3 dir = offset / dist;
|
||||
|
||||
// Falloff (linear)
|
||||
float falloff = 1.0f - (dist / radius);
|
||||
|
||||
// Final impulse
|
||||
JPH::Vec3 impulse = dir * (forceMagnitude * falloff);
|
||||
|
||||
// Apply impulse at center of mass
|
||||
body.AddImpulse(impulse);
|
||||
}
|
||||
}
|
||||
|
||||
void JoltWorld::onDebugDraw(const SceneRenderState* state)
|
||||
{
|
||||
#ifdef JPH_DEBUG_RENDERER
|
||||
if (!state->isDiffusePass()) return;
|
||||
|
||||
if (!mIsEnabled)
|
||||
return;
|
||||
|
||||
auto* renderer = static_cast<JoltDebugRenderer*>(JPH::DebugRenderer::sInstance);
|
||||
if (!renderer)
|
||||
return;
|
||||
|
||||
DebugRenderFrameState frame;
|
||||
frame.frustum = state->getCullingFrustum();
|
||||
frame.cameraPos = joltCast(state->getCameraPosition());
|
||||
|
||||
renderer->SetFrameState(frame);
|
||||
|
||||
JPH::BodyManager::DrawSettings settings;
|
||||
|
||||
settings.mDrawShape = true;
|
||||
settings.mDrawWorldTransform = false;
|
||||
settings.mDrawBoundingBox = true;
|
||||
settings.mDrawVelocity = false;
|
||||
settings.mDrawCenterOfMassTransform = true;
|
||||
settings.mDrawShapeColor = JPH::BodyManager::EShapeColor::SleepColor;
|
||||
|
||||
mPhysicsSystem.DrawBodies(settings, renderer);
|
||||
#endif
|
||||
}
|
||||
|
||||
void JoltWorld::tickPhysics(U32 elapsedMs)
|
||||
{
|
||||
// Perform deferred destruction
|
||||
if (mDestroyPending.exchange(false))
|
||||
{
|
||||
destroyWorldInternal();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mIsEnabled)
|
||||
return;
|
||||
|
||||
if (mResetPending.exchange(false))
|
||||
performReset();
|
||||
|
||||
const F32 dt = (F32)elapsedMs * 0.001f;
|
||||
|
||||
PROFILE_SCOPE(JoltWorld_TickPhysics);
|
||||
|
||||
mPhysicsSystem.Update(
|
||||
dt * mEditorTimeScale,
|
||||
smPhysicsMaxSubSteps, // collision steps
|
||||
mTempAllocator,
|
||||
mJobSystem
|
||||
);
|
||||
|
||||
mIsSimulating = true;
|
||||
}
|
||||
|
||||
void JoltWorld::getPhysicsResults()
|
||||
{
|
||||
if (!mIsSimulating)
|
||||
return;
|
||||
|
||||
PROFILE_SCOPE(JoltWorld_getPhysicsResults);
|
||||
|
||||
mIsSimulating = false;
|
||||
mTickCount++;
|
||||
}
|
||||
|
||||
void JoltWorld::performReset()
|
||||
{
|
||||
JPH::BodyInterface& bodyInterface = mPhysicsSystem.GetBodyInterface();
|
||||
|
||||
JPH::BodyIDVector bodies;
|
||||
mPhysicsSystem.GetBodies(bodies);
|
||||
|
||||
for (JPH::BodyID id : bodies)
|
||||
{
|
||||
if (!bodyInterface.IsAdded(id))
|
||||
continue;
|
||||
|
||||
bodyInterface.ActivateBody(id);
|
||||
|
||||
bodyInterface.SetLinearAndAngularVelocity(
|
||||
id,
|
||||
JPH::Vec3::sZero(),
|
||||
JPH::Vec3::sZero()
|
||||
);
|
||||
}
|
||||
|
||||
mPhysicsSystem.OptimizeBroadPhase();
|
||||
}
|
||||
|
||||
void JoltWorld::reset()
|
||||
{
|
||||
if (!mIsEnabled)
|
||||
return;
|
||||
|
||||
// we operate with a threading environment o not reset right away.
|
||||
mResetPending.store(true, std::memory_order_release);
|
||||
|
||||
}
|
||||
|
||||
void JoltWorld::setEnabled(bool enabled)
|
||||
{
|
||||
mIsEnabled = enabled;
|
||||
|
||||
if (!mIsEnabled)
|
||||
getPhysicsResults();
|
||||
}
|
||||
190
Engine/source/T3D/physics/jolt/joltWorld.h
Normal file
190
Engine/source/T3D/physics/jolt/joltWorld.h
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
#pragma once
|
||||
|
||||
#ifndef _JOLTWORLD_H_
|
||||
#define _JOLTWORLD_H_
|
||||
|
||||
#ifndef _T3D_PHYSICS_PHYSICSWORLD_H_
|
||||
#include "T3D/physics/physicsWorld.h"
|
||||
#endif
|
||||
|
||||
#ifndef _T3D_JOLT_PLUGIN_H_
|
||||
#include "T3D/physics/jolt/joltPlugin.h"
|
||||
#endif
|
||||
|
||||
|
||||
class ProcessList;
|
||||
class PhysicsBody;
|
||||
class JoltPlayer;
|
||||
|
||||
namespace Layers
|
||||
{
|
||||
static constexpr JPH::ObjectLayer NON_MOVING = 0;
|
||||
static constexpr JPH::ObjectLayer MOVING = 1;
|
||||
static constexpr JPH::ObjectLayer CHARACTER = 2;
|
||||
static constexpr JPH::ObjectLayer DEBRIS = 3;
|
||||
static constexpr JPH::ObjectLayer NUM_LAYERS = 4;
|
||||
}
|
||||
|
||||
namespace BroadPhaseLayers
|
||||
{
|
||||
static constexpr JPH::BroadPhaseLayer NON_MOVING(0);
|
||||
static constexpr JPH::BroadPhaseLayer MOVING(1);
|
||||
|
||||
static constexpr U32 NUM_LAYERS = 2;
|
||||
}
|
||||
|
||||
class BPLayerInterfaceImpl final : public JPH::BroadPhaseLayerInterface
|
||||
{
|
||||
public:
|
||||
|
||||
BPLayerInterfaceImpl()
|
||||
{
|
||||
mObjectToBroadPhase[Layers::NON_MOVING] = BroadPhaseLayers::NON_MOVING;
|
||||
mObjectToBroadPhase[Layers::MOVING] = BroadPhaseLayers::MOVING;
|
||||
mObjectToBroadPhase[Layers::CHARACTER] = BroadPhaseLayers::MOVING;
|
||||
mObjectToBroadPhase[Layers::DEBRIS] = BroadPhaseLayers::MOVING;
|
||||
}
|
||||
|
||||
virtual U32 GetNumBroadPhaseLayers() const override
|
||||
{
|
||||
return BroadPhaseLayers::NUM_LAYERS;
|
||||
}
|
||||
|
||||
virtual JPH::BroadPhaseLayer GetBroadPhaseLayer(JPH::ObjectLayer layer) const override
|
||||
{
|
||||
return mObjectToBroadPhase[layer];
|
||||
}
|
||||
|
||||
#if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
|
||||
|
||||
virtual const char* GetBroadPhaseLayerName(JPH::BroadPhaseLayer layer) const override
|
||||
{
|
||||
switch ((JPH::BroadPhaseLayer::Type)layer)
|
||||
{
|
||||
case 0: return "NON_MOVING";
|
||||
case 1: return "MOVING";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
private:
|
||||
|
||||
JPH::BroadPhaseLayer mObjectToBroadPhase[Layers::NUM_LAYERS];
|
||||
};
|
||||
|
||||
class ObjectLayerPairFilterImpl : public JPH::ObjectLayerPairFilter
|
||||
{
|
||||
public:
|
||||
|
||||
virtual bool ShouldCollide(
|
||||
JPH::ObjectLayer layer1,
|
||||
JPH::ObjectLayer layer2) const override
|
||||
{
|
||||
switch (layer1)
|
||||
{
|
||||
case Layers::NON_MOVING:
|
||||
return layer2 == Layers::MOVING
|
||||
|| layer2 == Layers::CHARACTER
|
||||
|| layer2 == Layers::DEBRIS;
|
||||
|
||||
case Layers::MOVING:
|
||||
return true;
|
||||
|
||||
case Layers::CHARACTER:
|
||||
return layer2 == Layers::NON_MOVING
|
||||
|| layer2 == Layers::MOVING
|
||||
|| layer2 == Layers::CHARACTER;
|
||||
|
||||
case Layers::DEBRIS:
|
||||
return layer2 == Layers::NON_MOVING; // Only Statics
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class ObjectVsBroadPhaseLayerFilterImpl : public JPH::ObjectVsBroadPhaseLayerFilter
|
||||
{
|
||||
public:
|
||||
|
||||
virtual bool ShouldCollide(
|
||||
JPH::ObjectLayer layer1,
|
||||
JPH::BroadPhaseLayer layer2) const override
|
||||
{
|
||||
switch (layer1)
|
||||
{
|
||||
case Layers::NON_MOVING:
|
||||
return layer2 == BroadPhaseLayers::MOVING;
|
||||
|
||||
case Layers::MOVING:
|
||||
case Layers::CHARACTER:
|
||||
return true;
|
||||
|
||||
case Layers::DEBRIS:
|
||||
return layer2 == BroadPhaseLayers::NON_MOVING;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class JoltWorld : public PhysicsWorld
|
||||
{
|
||||
protected:
|
||||
bool mIsEnabled;
|
||||
|
||||
U32 mTickCount;
|
||||
bool mIsSimulating;
|
||||
|
||||
std::atomic<bool> mResetPending{ false };
|
||||
std::atomic<bool> mDestroyPending{ false };
|
||||
|
||||
F32 mEditorTimeScale;
|
||||
JPH::PhysicsSystem mPhysicsSystem;
|
||||
JPH::TempAllocatorImpl* mTempAllocator;
|
||||
JPH::JobSystemThreadPool* mJobSystem;
|
||||
BPLayerInterfaceImpl mBroadPhaseLayerInterface;
|
||||
ObjectVsBroadPhaseLayerFilterImpl mObjectVsBroadPhaseLayerFilter;
|
||||
ObjectLayerPairFilterImpl mObjectLayerPairFilter;
|
||||
ProcessList* mProcessList;
|
||||
public:
|
||||
JoltWorld();
|
||||
virtual ~JoltWorld();
|
||||
|
||||
// PhysicWorld
|
||||
virtual bool initWorld(bool isServer, ProcessList* processList);
|
||||
void destroyWorldInternal();
|
||||
virtual void destroyWorld();
|
||||
virtual bool castRay(const Point3F& startPnt, const Point3F& endPnt, RayInfo* ri, const Point3F& impulse);
|
||||
virtual PhysicsBody* castRay(const Point3F& start, const Point3F& end, U32 bodyTypes);
|
||||
virtual void explosion(const Point3F& pos, F32 radius, F32 forceMagnitude);
|
||||
virtual void onDebugDraw(const SceneRenderState* state);
|
||||
virtual void reset();
|
||||
virtual bool isEnabled() const { return mIsEnabled; }
|
||||
|
||||
void tickPhysics(U32 elapsedMs);
|
||||
void getPhysicsResults();
|
||||
bool isWritable() const { return !mIsSimulating; }
|
||||
|
||||
void performReset();
|
||||
|
||||
void setEnabled(bool enabled);
|
||||
bool getEnabled() const { return mIsEnabled; }
|
||||
|
||||
void setEditorTimeScale(F32 timeScale) { mEditorTimeScale = timeScale; }
|
||||
const F32 getEditorTimeScale() const { return mEditorTimeScale; }
|
||||
JPH::PhysicsSystem* getPhysicsSystem() { return &mPhysicsSystem; }
|
||||
|
||||
const JPH::BroadPhaseLayerInterface& getBroadPhaseLayerInterface() const { return mBroadPhaseLayerInterface; };
|
||||
const JPH::ObjectLayerPairFilter& getObjectLayerPairFilter() const { return mObjectLayerPairFilter; }
|
||||
const JPH::ObjectVsBroadPhaseLayerFilter& getObjectVsBroadPhaseLayerFilter() const { return mObjectVsBroadPhaseLayerFilter; }
|
||||
JPH::TempAllocatorImpl* getTempAllocator() { return mTempAllocator; }
|
||||
ProcessList* getProcessList() { return mProcessList; }
|
||||
|
||||
};
|
||||
|
||||
#endif // !_JOLTWORLD_H_
|
||||
|
|
@ -47,8 +47,8 @@ Rigid::Rigid()
|
|||
friction = 0.5f;
|
||||
atRest = false;
|
||||
|
||||
sleepLinearThreshold = POINT_EPSILON;
|
||||
sleepAngThreshold = POINT_EPSILON;
|
||||
sleepLinearThreshold = 0.01f;
|
||||
sleepAngThreshold = 0.01f;
|
||||
sleepTimeThreshold = 0.75f;
|
||||
sleepTimer = 0.0f;
|
||||
}
|
||||
|
|
@ -76,8 +76,8 @@ void Rigid::integrate(F32 delta)
|
|||
{
|
||||
QuatF dq;
|
||||
F32 sinHalfAngle;
|
||||
mSinCos(angle * delta * -0.5f, sinHalfAngle, dq.w);
|
||||
sinHalfAngle *= 1.0f / angle;
|
||||
mSinCos(angle * -0.5f, sinHalfAngle, dq.w);
|
||||
sinHalfAngle *= delta / angle;
|
||||
dq.x = angVelocity.x * sinHalfAngle;
|
||||
dq.y = angVelocity.y * sinHalfAngle;
|
||||
dq.z = angVelocity.z * sinHalfAngle;
|
||||
|
|
@ -101,7 +101,7 @@ void Rigid::integrate(F32 delta)
|
|||
}
|
||||
|
||||
// 5. refresh ang velocity
|
||||
updateAngularVelocity(delta);
|
||||
updateAngularVelocity();
|
||||
|
||||
|
||||
// 6. CoM update
|
||||
|
|
@ -138,7 +138,7 @@ void Rigid::updateCenterOfMass()
|
|||
|
||||
void Rigid::applyImpulse(const Point3F &r, const Point3F &impulse)
|
||||
{
|
||||
if ((impulse.lenSquared() - mass) < POINT_EPSILON) return;
|
||||
if (impulse.lenSquared() < POINT_EPSILON) return;
|
||||
wake();
|
||||
|
||||
// Linear momentum and velocity
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ public:
|
|||
//
|
||||
void setSleepThresholds(F32 linVel2, F32 angVel2, F32 timeToSleep);
|
||||
void wake();
|
||||
TORQUE_FORCEINLINE void updateAngularVelocity(F32 delta) { Point3F deltaVel = angVelocity * delta; invWorldInertia.mulV(angMomentum, &deltaVel); }
|
||||
TORQUE_FORCEINLINE void updateAngularVelocity() { invWorldInertia.mulV(angMomentum, &angVelocity); }
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@
|
|||
#include "console/engineAPI.h"
|
||||
#include "T3D/physics/physicsPlugin.h"
|
||||
#include "T3D/physics/physicsCollision.h"
|
||||
#include "T3D/containerQuery.h"
|
||||
|
||||
IMPLEMENT_CO_DATABLOCK_V1(RigidShapeData);
|
||||
|
||||
|
|
@ -175,6 +176,7 @@ namespace {
|
|||
// The larger this number the less often the working list
|
||||
// will be updated due to motion, but any non-static shape
|
||||
// that moves into the query box will not be noticed.
|
||||
//
|
||||
// Client prediction
|
||||
const S32 sMaxWarpTicks = 3; // Max warp duration in ticks
|
||||
const S32 sMaxPredictionTicks = 30; // Number of ticks to predict
|
||||
|
|
@ -189,6 +191,8 @@ namespace {
|
|||
|
||||
const U32 sServerCollisionMask = sCollisionMoveMask; // ItemObjectType
|
||||
const U32 sClientCollisionMask = sCollisionMoveMask;
|
||||
bool smNoCorrections = false;
|
||||
bool smNoSmoothing = false;
|
||||
|
||||
void nonFilter(SceneObject* object,void *key)
|
||||
{
|
||||
|
|
@ -272,6 +276,7 @@ RigidShapeData::RigidShapeData()
|
|||
medSplashSoundVel = 2.0;
|
||||
hardSplashSoundVel = 3.0;
|
||||
enablePhysicsRep = true;
|
||||
isDynamic = false;
|
||||
|
||||
for (S32 i = 0; i < Sounds::MaxSounds; i++)
|
||||
INIT_SOUNDASSET_ARRAY(WaterSounds, i);
|
||||
|
|
@ -453,6 +458,7 @@ void RigidShapeData::packData(BitStream* stream)
|
|||
stream->write(medSplashSoundVel);
|
||||
stream->write(hardSplashSoundVel);
|
||||
stream->write(enablePhysicsRep);
|
||||
stream->write(isDynamic);
|
||||
|
||||
// write the water sound profiles
|
||||
for (U32 i = 0; i < Sounds::MaxSounds; ++i)
|
||||
|
|
@ -517,6 +523,7 @@ void RigidShapeData::unpackData(BitStream* stream)
|
|||
stream->read(&medSplashSoundVel);
|
||||
stream->read(&hardSplashSoundVel);
|
||||
stream->read(&enablePhysicsRep);
|
||||
stream->read(&isDynamic);
|
||||
|
||||
// write the water sound profiles
|
||||
for (U32 i = 0; i < Sounds::MaxSounds; ++i)
|
||||
|
|
@ -568,6 +575,12 @@ void RigidShapeData::initPersistFields()
|
|||
addGroup("Physics");
|
||||
addField("enablePhysicsRep", TypeBool, Offset(enablePhysicsRep, RigidShapeData),
|
||||
"@brief Creates a representation of the object in the physics plugin.\n");
|
||||
addField("isDynamic", TypeBool, Offset(isDynamic, RigidShapeData),
|
||||
"@brief When true and a physics plugin is active, the body is fully dynamic:\n"
|
||||
"the plugin owns collision detection, constraint solving and sleep.\n"
|
||||
"When false (default) the body is kinematic — the built-in Rigid integrator\n"
|
||||
"drives the simulation, which is correct for player-controlled shapes that\n"
|
||||
"receive Move input each tick.\n");
|
||||
addField("massCenter", TypePoint3F, Offset(massCenter, RigidShapeData), "Center of mass for rigid body.");
|
||||
addField("massBox", TypePoint3F, Offset(massBox, RigidShapeData), "Size of inertial box.");
|
||||
addFieldV("bodyRestitution", TypeRangedF32, Offset(body.restitution, RigidShapeData), &CommonValidators::PositiveFloat, "The percentage of kinetic energy kept by this object in a collision.");
|
||||
|
|
@ -652,7 +665,14 @@ RigidShape::RigidShape()
|
|||
mWorkingQueryBoxCountDown = sWorkingQueryBoxStaleThreshold;
|
||||
|
||||
mPhysicsRep = NULL;
|
||||
}
|
||||
// NEW — initialise PhysicsState members used by the dynamic path
|
||||
mState.position.set(0, 0, 0);
|
||||
mState.orientation.identity();
|
||||
mState.linVelocity.set(0, 0, 0);
|
||||
mState.angVelocity.set(0, 0, 0);
|
||||
mState.sleeping = false;
|
||||
mRenderState[0] = mRenderState[1] = mState;
|
||||
}
|
||||
|
||||
RigidShape::~RigidShape()
|
||||
{
|
||||
|
|
@ -799,16 +819,26 @@ void RigidShape::_createPhysics()
|
|||
return;
|
||||
|
||||
TSShape* shape = mShapeInstance->getShape();
|
||||
PhysicsCollision* colShape = NULL;
|
||||
colShape = shape->buildColShape(false, getScale());
|
||||
PhysicsCollision* colShape = shape->buildColShape(false, getScale());
|
||||
if (!colShape)
|
||||
return;
|
||||
|
||||
if (colShape)
|
||||
{
|
||||
PhysicsWorld* world = PHYSICSMGR->getWorld(isServerObject() ? "server" : "client");
|
||||
mPhysicsRep = PHYSICSMGR->createBody();
|
||||
mPhysicsRep->init(colShape, 0, PhysicsBody::BF_KINEMATIC, this, world);
|
||||
mPhysicsRep->setTransform(getTransform());
|
||||
}
|
||||
const bool dynamic = mDataBlock->isDynamic;
|
||||
|
||||
PhysicsWorld* world = PHYSICSMGR->getWorld(isServerObject() ? "server" : "client");
|
||||
mPhysicsRep = PHYSICSMGR->createBody();
|
||||
|
||||
mPhysicsRep->init(colShape, dynamic ? mDataBlock->mass : 0,
|
||||
dynamic ? 0 : PhysicsBody::BF_KINEMATIC,
|
||||
this, world);
|
||||
|
||||
mPhysicsRep->setMaterial(mDataBlock->body.restitution,
|
||||
mDataBlock->body.friction,
|
||||
mDataBlock->body.friction);
|
||||
|
||||
mPhysicsRep->setDamping(mDataBlock->minDrag, mDataBlock->minDrag);
|
||||
|
||||
mPhysicsRep->setTransform(getTransform());
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
|
@ -836,70 +866,156 @@ void RigidShape::processTick(const Move* move)
|
|||
mDelta.posVec.x = -mDelta.warpOffset.x;
|
||||
mDelta.posVec.y = -mDelta.warpOffset.y;
|
||||
mDelta.posVec.z = -mDelta.warpOffset.z;
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
||||
if (!move)
|
||||
{
|
||||
if (!move)
|
||||
if (isGhost())
|
||||
{
|
||||
if (isGhost())
|
||||
{
|
||||
// If we haven't run out of prediction time,
|
||||
// predict using the last known move.
|
||||
if (mPredictionCount-- <= 0)
|
||||
return;
|
||||
move = &mDelta.move;
|
||||
}
|
||||
else
|
||||
move = &NullMove;
|
||||
// If we haven't run out of prediction time,
|
||||
// predict using the last known move.
|
||||
if (mPredictionCount-- <= 0)
|
||||
return;
|
||||
move = &mDelta.move;
|
||||
}
|
||||
else
|
||||
move = &NullMove;
|
||||
}
|
||||
|
||||
// Process input move
|
||||
updateMove(move);
|
||||
|
||||
// =========================================================================
|
||||
// DYNAMIC BODY PATH — physics plugin owns all simulation
|
||||
// =========================================================================
|
||||
if (mPhysicsRep && mPhysicsRep->isDynamic())
|
||||
{
|
||||
// Single-player shortcut: mirror server state directly to avoid a full
|
||||
// network round-trip (mirrors PhysicsShape::processTick pattern).
|
||||
if (PHYSICSMGR->isSinglePlayer() && isClientObject() && getServerObject())
|
||||
{
|
||||
RigidShape* srv = static_cast<RigidShape*>(getServerObject());
|
||||
Parent::setTransform(srv->mState.getTransform());
|
||||
mRenderState[0] = srv->mRenderState[0];
|
||||
mRenderState[1] = srv->mRenderState[1];
|
||||
mState = srv->mState;
|
||||
// Keep mRigid in sync for anything that reads it (camera, sounds, etc.)
|
||||
mRigid.linVelocity = mState.linVelocity;
|
||||
mRigid.angVelocity = mState.angVelocity;
|
||||
mRigid.linPosition = mState.position;
|
||||
mRigid.angPosition = mState.orientation;
|
||||
mRigid.atRest = mState.sleeping;
|
||||
return;
|
||||
}
|
||||
|
||||
// Process input move
|
||||
updateMove(move);
|
||||
// Store previous render state for correction smoothing
|
||||
mRenderState[0] = mRenderState[1];
|
||||
Point3F errorDelta = mRenderState[1].position - mState.position;
|
||||
const bool doSmoothing = !errorDelta.isZero() && !smNoSmoothing;
|
||||
const bool wasSleeping = mState.sleeping;
|
||||
|
||||
// Save current rigid state interpolation
|
||||
mDelta.posVec = mRigid.linPosition;
|
||||
mDelta.rot[0] = mRigid.angPosition;
|
||||
// Freeze support: put the body to sleep when movement is disabled
|
||||
if (mDisableMove)
|
||||
{
|
||||
mPhysicsRep->setSleeping(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Pull the newly-integrated state from the physics plugin
|
||||
mPhysicsRep->getState(&mState);
|
||||
_updateContainerForces(); // water, zones
|
||||
}
|
||||
|
||||
// Update the physics based on the integration rate
|
||||
S32 count = mDataBlock->integration;
|
||||
--mWorkingQueryBoxCountDown;
|
||||
// Smooth any server correction back into the render state
|
||||
mRenderState[1] = mState;
|
||||
if (doSmoothing)
|
||||
{
|
||||
F32 blend = mClampF(errorDelta.len() / 20.0f, 0.1f, 0.9f);
|
||||
mRenderState[1].position.interpolate(mState.position,
|
||||
mRenderState[0].position, blend);
|
||||
mRenderState[1].orientation.interpolate(mState.orientation,
|
||||
mRenderState[0].orientation, blend);
|
||||
}
|
||||
|
||||
if (!mDisableMove)
|
||||
updateWorkingCollisionSet(getCollisionMask());
|
||||
for (U32 i = 0; i < count; i++)
|
||||
updatePos(TickSec / count);
|
||||
// Keep mRigid in sync for subsystems that read it directly
|
||||
mRigid.linPosition = mState.position;
|
||||
mRigid.angPosition = mState.orientation;
|
||||
mRigid.linVelocity = mState.linVelocity;
|
||||
mRigid.angVelocity = mState.angVelocity;
|
||||
mRigid.atRest = mState.sleeping;
|
||||
|
||||
// Wrap up interpolation info
|
||||
mDelta.pos = mRigid.linPosition;
|
||||
mDelta.posVec -= mRigid.linPosition;
|
||||
mDelta.rot[1] = mRigid.angPosition;
|
||||
if (!wasSleeping || !mState.sleeping)
|
||||
{
|
||||
// Update engine transform from physics state
|
||||
Parent::setTransform(mState.getTransform());
|
||||
|
||||
// Update container database
|
||||
setPosition(mRigid.linPosition, mRigid.angPosition);
|
||||
setMaskBits(PositionMask);
|
||||
updateContainer();
|
||||
if (isServerObject() && !smNoCorrections && !PHYSICSMGR->isSinglePlayer())
|
||||
setMaskBits(PositionMask);
|
||||
|
||||
//TODO: Only update when position has actually changed
|
||||
//no need to check if mDataBlock->enablePhysicsRep is false as mPhysicsRep will be NULL if it is
|
||||
if (mPhysicsRep)
|
||||
mPhysicsRep->moveKinematicTo(getTransform());
|
||||
updateContainer();
|
||||
}
|
||||
|
||||
if (isServerObject())
|
||||
{
|
||||
checkTriggers();
|
||||
notifyCollision();
|
||||
}
|
||||
return; // done — do NOT fall through to Rigid path
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// KINEMATIC / RIGID SIMULATION PATH (original behaviour, bug-fixed)
|
||||
// =========================================================================
|
||||
|
||||
mDelta.posVec = mRigid.linPosition;
|
||||
mDelta.rot[0] = mRigid.angPosition;
|
||||
|
||||
S32 count = mDataBlock->integration;
|
||||
--mWorkingQueryBoxCountDown;
|
||||
|
||||
if (!mDisableMove)
|
||||
updateWorkingCollisionSet(getCollisionMask());
|
||||
|
||||
for (U32 i = 0; i < count; i++)
|
||||
updatePos(TickSec / count);
|
||||
|
||||
mDelta.pos = mRigid.linPosition;
|
||||
mDelta.posVec -= mRigid.linPosition;
|
||||
mDelta.rot[1] = mRigid.angPosition;
|
||||
|
||||
setPosition(mRigid.linPosition, mRigid.angPosition);
|
||||
setMaskBits(PositionMask);
|
||||
updateContainer();
|
||||
|
||||
// Keep the kinematic physics body in sync so other dynamic actors see us
|
||||
if (mPhysicsRep)
|
||||
mPhysicsRep->moveKinematicTo(getTransform());
|
||||
}
|
||||
|
||||
void RigidShape::interpolateTick(F32 dt)
|
||||
{
|
||||
Parent::interpolateTick(dt);
|
||||
if ( isMounted() )
|
||||
return;
|
||||
if (isMounted()) return;
|
||||
|
||||
if(dt == 0.0f)
|
||||
if (mPhysicsRep && mPhysicsRep->isDynamic())
|
||||
{
|
||||
PhysicsState state;
|
||||
state.interpolate(mRenderState[1], mRenderState[0], dt);
|
||||
setRenderTransform(state.getTransform());
|
||||
mDelta.dt = dt;
|
||||
return;
|
||||
}
|
||||
|
||||
// Original Rigid path
|
||||
if (dt == 0.0f)
|
||||
setRenderPosition(mDelta.pos, mDelta.rot[1]);
|
||||
else
|
||||
{
|
||||
QuatF rot;
|
||||
rot.interpolate(mDelta.rot[1], mDelta.rot[0], dt);
|
||||
Point3F pos = mDelta.pos + mDelta.posVec * dt;
|
||||
setRenderPosition(pos,rot);
|
||||
setRenderPosition(pos, rot);
|
||||
}
|
||||
mDelta.dt = dt;
|
||||
}
|
||||
|
|
@ -1062,13 +1178,21 @@ void RigidShape::getCameraTransform(F32* pos,MatrixF* mat)
|
|||
|
||||
void RigidShape::getVelocity(const Point3F& r, Point3F* v)
|
||||
{
|
||||
mRigid.getVelocity(r, v);
|
||||
if (mPhysicsRep && mPhysicsRep->isDynamic())
|
||||
*v = mState.linVelocity;
|
||||
else
|
||||
mRigid.getVelocity(r, v);
|
||||
}
|
||||
|
||||
void RigidShape::applyImpulse(const Point3F &pos, const Point3F &impulse)
|
||||
{
|
||||
if (mPhysicsRep && mPhysicsRep->isDynamic())
|
||||
{
|
||||
mPhysicsRep->applyImpulse(pos, impulse);
|
||||
return;
|
||||
}
|
||||
Point3F r;
|
||||
mRigid.getOriginVector(pos,&r);
|
||||
mRigid.getOriginVector(pos, &r);
|
||||
mRigid.applyImpulse(r, impulse);
|
||||
}
|
||||
|
||||
|
|
@ -1104,6 +1228,17 @@ void RigidShape::setTransform(const MatrixF& newMat)
|
|||
Parent::setTransform(newMat);
|
||||
mRigid.atRest = false;
|
||||
mContacts.clear();
|
||||
|
||||
// For dynamic bodies, also keep mState consistent
|
||||
if (mPhysicsRep && mPhysicsRep->isDynamic())
|
||||
{
|
||||
mState.position = newMat.getPosition();
|
||||
mState.orientation.set(newMat);
|
||||
mRenderState[0] = mRenderState[1] = mState;
|
||||
}
|
||||
|
||||
if (mPhysicsRep)
|
||||
mPhysicsRep->setTransform(newMat); // covers both kinematic and dynamic
|
||||
}
|
||||
|
||||
void RigidShape::forceClientTransform()
|
||||
|
|
@ -1153,8 +1288,8 @@ void RigidShape::updatePos(F32 dt)
|
|||
if (mCollisionList.getCount())
|
||||
{
|
||||
F32 k = mRigid.getKineticEnergy();
|
||||
F32 G = mNetGravity* dt * TickMs / mDataBlock->integration;
|
||||
F32 Kg = mRigid.mass * G * G * TickSec;
|
||||
F32 G = mNetGravity * dt;
|
||||
F32 Kg = mRigid.mass * G * G;
|
||||
if (k < sRestTol * Kg && ++restCount > sRestCount)
|
||||
mRigid.setAtRest();
|
||||
}
|
||||
|
|
@ -1241,6 +1376,53 @@ void RigidShape::updatePos(F32 dt)
|
|||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void RigidShape::_updateContainerForces()
|
||||
{
|
||||
if (!mPhysicsRep || !mPhysicsRep->isDynamic())
|
||||
return;
|
||||
|
||||
ContainerQueryInfo info;
|
||||
info.box = getWorldBox();
|
||||
info.mass = mDataBlock->mass;
|
||||
|
||||
getContainer()->findObjects(getWorldBox(), WaterObjectType | PhysicalZoneObjectType,
|
||||
findRouter, &info);
|
||||
|
||||
// Base drag from the datablock's dragForce field
|
||||
F32 linDrag = mDataBlock->dragForce;
|
||||
F32 angDrag = mDataBlock->dragForce;
|
||||
|
||||
Point3F cmass = mPhysicsRep->getCMassPosition();
|
||||
|
||||
if (info.waterCoverage > 0.0f)
|
||||
{
|
||||
// Scale drag by water viscosity
|
||||
F32 waterScale = info.waterViscosity * 2.0f;
|
||||
F32 pow4 = mPow(info.waterCoverage, 0.25f);
|
||||
linDrag = mLerp(linDrag, linDrag * waterScale, pow4);
|
||||
angDrag = mLerp(angDrag, angDrag * waterScale, pow4);
|
||||
|
||||
// Buoyancy — uses ShapeBaseData::density (inherited by RigidShapeData)
|
||||
F32 density = mDataBlock->density;
|
||||
if (density > 0.0f)
|
||||
{
|
||||
F32 buoyancy = (info.waterDensity / density) * mPow(info.waterCoverage, 2.0f);
|
||||
// mNetGravity is signed (negative = downward in Torque Z-up).
|
||||
// Buoyancy opposes gravity, so the force is in the +Z direction.
|
||||
Point3F buoyancyForce(0.0f, 0.0f, buoyancy * -mNetGravity * TickSec * mDataBlock->mass);
|
||||
mPhysicsRep->applyImpulse(cmass, buoyancyForce);
|
||||
}
|
||||
}
|
||||
|
||||
mPhysicsRep->setDamping(linDrag, angDrag);
|
||||
|
||||
// Physical zone forces (wind, push, etc.)
|
||||
if (!info.appliedForce.isZero())
|
||||
mPhysicsRep->applyImpulse(cmass, info.appliedForce);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void RigidShape::updateForces(F32 dt)
|
||||
{
|
||||
if (mDisableMove)
|
||||
|
|
@ -1542,41 +1724,80 @@ void RigidShape::writePacketData(GameConnection *connection, BitStream *stream)
|
|||
{
|
||||
Parent::writePacketData(connection, stream);
|
||||
|
||||
mathWrite(*stream, mRigid.linPosition);
|
||||
if (!stream->writeFlag(mRigid.atRest))
|
||||
if (mDataBlock->isDynamic)
|
||||
{
|
||||
mathWrite(*stream, mRigid.angPosition);
|
||||
mathWrite(*stream, mRigid.linMomentum);
|
||||
mathWrite(*stream, mRigid.angMomentum);
|
||||
mathWrite(*stream, mState.position);
|
||||
if (!stream->writeFlag(mState.sleeping))
|
||||
{
|
||||
stream->writeQuat(mState.orientation, 9);
|
||||
stream->writeVector(mState.linVelocity, 1000.0f, 16, 9);
|
||||
stream->writeVector(mState.angVelocity, 10.0f, 10, 9);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mathWrite(*stream, mRigid.linPosition);
|
||||
if (!stream->writeFlag(mRigid.atRest))
|
||||
{
|
||||
mathWrite(*stream, mRigid.angPosition);
|
||||
mathWrite(*stream, mRigid.linMomentum);
|
||||
mathWrite(*stream, mRigid.angMomentum);
|
||||
}
|
||||
stream->writeFlag(mContacts.getCount() == 0);
|
||||
}
|
||||
stream->writeFlag(mContacts.getCount() == 0);
|
||||
|
||||
stream->writeFlag(mDisableMove);
|
||||
stream->setCompressionPoint(mRigid.linPosition);
|
||||
stream->setCompressionPoint(mDataBlock->isDynamic ? mState.position : mRigid.linPosition);
|
||||
}
|
||||
|
||||
void RigidShape::readPacketData(GameConnection *connection, BitStream *stream)
|
||||
{
|
||||
Parent::readPacketData(connection, stream);
|
||||
|
||||
mathRead(*stream, &mRigid.linPosition);
|
||||
if (stream->readFlag())
|
||||
if (mDataBlock->isDynamic)
|
||||
{
|
||||
mRigid.setAtRest();
|
||||
mathRead(*stream, &mState.position);
|
||||
if (stream->readFlag()) // sleeping
|
||||
{
|
||||
mState.sleeping = true;
|
||||
if (mPhysicsRep) mPhysicsRep->setSleeping(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
mState.sleeping = false;
|
||||
stream->readQuat(&mState.orientation, 9);
|
||||
stream->readVector(&mState.linVelocity, 1000.0f, 16, 9);
|
||||
stream->readVector(&mState.angVelocity, 10.0f, 10, 9);
|
||||
|
||||
if (mPhysicsRep && mPhysicsRep->isDynamic())
|
||||
{
|
||||
mPhysicsRep->setTransform(mState.getTransform());
|
||||
mPhysicsRep->setLinVelocity(mState.linVelocity);
|
||||
mPhysicsRep->setAngVelocity(mState.angVelocity);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mathRead(*stream, &mRigid.angPosition);
|
||||
mathRead(*stream, &mRigid.linMomentum);
|
||||
mathRead(*stream, &mRigid.angMomentum);
|
||||
mRigid.updateInertialTensor();
|
||||
mRigid.updateVelocity();
|
||||
mathRead(*stream, &mRigid.linPosition);
|
||||
if (stream->readFlag())
|
||||
{
|
||||
mRigid.setAtRest();
|
||||
}
|
||||
else
|
||||
{
|
||||
mathRead(*stream, &mRigid.angPosition);
|
||||
mathRead(*stream, &mRigid.linMomentum);
|
||||
mathRead(*stream, &mRigid.angMomentum);
|
||||
mRigid.updateInertialTensor();
|
||||
mRigid.updateVelocity();
|
||||
}
|
||||
if (stream->readFlag())
|
||||
mContacts.clear();
|
||||
}
|
||||
if (stream->readFlag())
|
||||
mContacts.clear();
|
||||
|
||||
mDisableMove = stream->readFlag();
|
||||
stream->setCompressionPoint(mRigid.linPosition);
|
||||
stream->setCompressionPoint(mDataBlock->isDynamic ? mState.position : mRigid.linPosition);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1597,16 +1818,32 @@ U32 RigidShape::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
|
|||
{
|
||||
stream->writeFlag(mask & ForceMoveMask);
|
||||
|
||||
stream->writeCompressedPoint(mRigid.linPosition);
|
||||
if (!stream->writeFlag(mRigid.atRest))
|
||||
if (mDataBlock->isDynamic)
|
||||
{
|
||||
mathWrite(*stream, mRigid.angPosition);
|
||||
mathWrite(*stream, mRigid.linMomentum);
|
||||
mathWrite(*stream, mRigid.angMomentum);
|
||||
// PhysicsState packet: position, orientation, sleeping, velocities.
|
||||
// mState was updated in processTick from mPhysicsRep->getState().
|
||||
stream->writeCompressedPoint(mState.position);
|
||||
stream->writeQuat(mState.orientation, 9);
|
||||
if (!stream->writeFlag(mState.sleeping))
|
||||
{
|
||||
stream->writeVector(mState.linVelocity, 1000.0f, 16, 9);
|
||||
stream->writeVector(mState.angVelocity, 10.0f, 10, 9);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Original Rigid momentum packet
|
||||
stream->writeCompressedPoint(mRigid.linPosition);
|
||||
if (!stream->writeFlag(mRigid.atRest))
|
||||
{
|
||||
mathWrite(*stream, mRigid.angPosition);
|
||||
mathWrite(*stream, mRigid.linMomentum);
|
||||
mathWrite(*stream, mRigid.angMomentum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(stream->writeFlag(mask & FreezeMask))
|
||||
|
||||
if (stream->writeFlag(mask & FreezeMask))
|
||||
stream->writeFlag(mDisableMove);
|
||||
|
||||
return retMask;
|
||||
|
|
@ -1614,93 +1851,137 @@ U32 RigidShape::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
|
|||
|
||||
void RigidShape::unpackUpdate(NetConnection *con, BitStream *stream)
|
||||
{
|
||||
Parent::unpackUpdate(con,stream);
|
||||
Parent::unpackUpdate(con, stream);
|
||||
|
||||
if (stream->readFlag())
|
||||
return;
|
||||
|
||||
mDelta.move.unpack(stream);
|
||||
|
||||
if (stream->readFlag())
|
||||
if (stream->readFlag()) // PositionMask
|
||||
{
|
||||
// Check if we need to jump to the given transform
|
||||
// rather than interpolate to it.
|
||||
bool forceUpdate = stream->readFlag();
|
||||
|
||||
mPredictionCount = sMaxPredictionTicks;
|
||||
F32 speed = mRigid.linVelocity.len();
|
||||
mDelta.warpRot[0] = mRigid.angPosition;
|
||||
|
||||
// Read in new position and momentum values
|
||||
stream->readCompressedPoint(&mRigid.linPosition);
|
||||
|
||||
if (stream->readFlag())
|
||||
if (mDataBlock->isDynamic)
|
||||
{
|
||||
mRigid.setAtRest();
|
||||
// --- Dynamic path ---
|
||||
PhysicsState newState;
|
||||
stream->readCompressedPoint(&newState.position);
|
||||
stream->readQuat(&newState.orientation, 9);
|
||||
newState.sleeping = stream->readFlag();
|
||||
if (!newState.sleeping)
|
||||
{
|
||||
stream->readVector(&newState.linVelocity, 1000.0f, 16, 9);
|
||||
stream->readVector(&newState.angVelocity, 10.0f, 10, 9);
|
||||
}
|
||||
|
||||
if (mPhysicsRep && mPhysicsRep->isDynamic())
|
||||
{
|
||||
if (forceUpdate)
|
||||
{
|
||||
// Hard snap — no smoothing, straight to the authoritative position
|
||||
mPhysicsRep->setTransform(newState.getTransform());
|
||||
}
|
||||
else if (!smNoCorrections)
|
||||
{
|
||||
// Soft correction — the physics body blends toward the new state
|
||||
mPhysicsRep->applyCorrection(newState.getTransform());
|
||||
}
|
||||
|
||||
mPhysicsRep->setSleeping(newState.sleeping);
|
||||
if (!newState.sleeping)
|
||||
{
|
||||
mPhysicsRep->setLinVelocity(newState.linVelocity);
|
||||
mPhysicsRep->setAngVelocity(newState.angVelocity);
|
||||
}
|
||||
// Re-read so mState reflects what the body is actually doing after
|
||||
// the correction (applyCorrection may blend rather than snap).
|
||||
mPhysicsRep->getState(&mState);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No live physics rep — store state for extrapolation / render
|
||||
mState = newState;
|
||||
}
|
||||
|
||||
if (forceUpdate || !isProperlyAdded())
|
||||
{
|
||||
Parent::setTransform(mState.getTransform());
|
||||
mRenderState[0] = mRenderState[1] = mState;
|
||||
}
|
||||
|
||||
// Sync mRigid so anything reading it sees sane values
|
||||
mRigid.linPosition = mState.position;
|
||||
mRigid.angPosition = mState.orientation;
|
||||
mRigid.linVelocity = mState.linVelocity;
|
||||
mRigid.angVelocity = mState.angVelocity;
|
||||
mRigid.atRest = mState.sleeping;
|
||||
mRigid.updateCenterOfMass();
|
||||
}
|
||||
else
|
||||
{
|
||||
mathRead(*stream, &mRigid.angPosition);
|
||||
mathRead(*stream, &mRigid.linMomentum);
|
||||
mathRead(*stream, &mRigid.angMomentum);
|
||||
mRigid.updateVelocity();
|
||||
}
|
||||
// --- Original Rigid (kinematic) path ---
|
||||
F32 speed = mRigid.linVelocity.len();
|
||||
mDelta.warpRot[0] = mRigid.angPosition;
|
||||
|
||||
if (!forceUpdate && isProperlyAdded())
|
||||
{
|
||||
// Determine number of ticks to warp based on the average
|
||||
// of the client and server velocities.
|
||||
Point3F cp = mDelta.pos + mDelta.posVec * mDelta.dt;
|
||||
mDelta.warpOffset = mRigid.linPosition - cp;
|
||||
|
||||
// Calc the distance covered in one tick as the average of
|
||||
// the old speed and the new speed from the server.
|
||||
F32 dt,as = (speed + mRigid.linVelocity.len()) * 0.5 * TickSec;
|
||||
|
||||
// Cal how many ticks it will take to cover the warp offset.
|
||||
// If it's less than what's left in the current tick, we'll just
|
||||
// warp in the remaining time.
|
||||
if (!as || (dt = mDelta.warpOffset.len() / as) > sMaxWarpTicks)
|
||||
dt = mDelta.dt + sMaxWarpTicks;
|
||||
stream->readCompressedPoint(&mRigid.linPosition);
|
||||
if (stream->readFlag())
|
||||
{
|
||||
mRigid.setAtRest();
|
||||
}
|
||||
else
|
||||
dt = (dt <= mDelta.dt)? mDelta.dt : mCeil(dt - mDelta.dt) + mDelta.dt;
|
||||
|
||||
// Adjust current frame interpolation
|
||||
if (mDelta.dt)
|
||||
{
|
||||
mDelta.pos = cp + (mDelta.warpOffset * (mDelta.dt / dt));
|
||||
mDelta.posVec = (cp - mDelta.pos) / mDelta.dt;
|
||||
QuatF cr;
|
||||
cr.interpolate(mDelta.rot[1],mDelta.rot[0],mDelta.dt);
|
||||
mDelta.rot[1].interpolate(cr,mRigid.angPosition,mDelta.dt / dt);
|
||||
mDelta.rot[0].extrapolate(mDelta.rot[1],cr,mDelta.dt);
|
||||
mathRead(*stream, &mRigid.angPosition);
|
||||
mathRead(*stream, &mRigid.linMomentum);
|
||||
mathRead(*stream, &mRigid.angMomentum);
|
||||
mRigid.updateVelocity();
|
||||
}
|
||||
|
||||
// Calculated multi-tick warp
|
||||
mDelta.warpCount = 0;
|
||||
mDelta.warpTicks = (S32)(mFloor(dt));
|
||||
if (mDelta.warpTicks)
|
||||
if (!forceUpdate && isProperlyAdded())
|
||||
{
|
||||
mDelta.warpOffset = mRigid.linPosition - mDelta.pos;
|
||||
mDelta.warpOffset /= mDelta.warpTicks;
|
||||
mDelta.warpRot[0] = mDelta.rot[1];
|
||||
mDelta.warpRot[1] = mRigid.angPosition;
|
||||
Point3F cp = mDelta.pos + mDelta.posVec * mDelta.dt;
|
||||
mDelta.warpOffset = mRigid.linPosition - cp;
|
||||
F32 dt, as = (speed + mRigid.linVelocity.len()) * 0.5f * TickSec;
|
||||
if (!as || (dt = mDelta.warpOffset.len() / as) > sMaxWarpTicks)
|
||||
dt = mDelta.dt + sMaxWarpTicks;
|
||||
else
|
||||
dt = (dt <= mDelta.dt) ? mDelta.dt : mCeil(dt - mDelta.dt) + mDelta.dt;
|
||||
|
||||
if (mDelta.dt)
|
||||
{
|
||||
mDelta.pos = cp + (mDelta.warpOffset * (mDelta.dt / dt));
|
||||
mDelta.posVec = (cp - mDelta.pos) / mDelta.dt;
|
||||
QuatF cr;
|
||||
cr.interpolate(mDelta.rot[1], mDelta.rot[0], mDelta.dt);
|
||||
mDelta.rot[1].interpolate(cr, mRigid.angPosition, mDelta.dt / dt);
|
||||
mDelta.rot[0].extrapolate(mDelta.rot[1], cr, mDelta.dt);
|
||||
}
|
||||
|
||||
mDelta.warpCount = 0;
|
||||
mDelta.warpTicks = (S32)(mFloor(dt));
|
||||
if (mDelta.warpTicks)
|
||||
{
|
||||
mDelta.warpOffset = mRigid.linPosition - mDelta.pos;
|
||||
mDelta.warpOffset /= mDelta.warpTicks;
|
||||
mDelta.warpRot[0] = mDelta.rot[1];
|
||||
mDelta.warpRot[1] = mRigid.angPosition;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mDelta.dt = 0;
|
||||
mDelta.pos = mRigid.linPosition;
|
||||
mDelta.posVec.set(0, 0, 0);
|
||||
mDelta.rot[1] = mDelta.rot[0] = mRigid.angPosition;
|
||||
mDelta.warpCount = mDelta.warpTicks = 0;
|
||||
setPosition(mRigid.linPosition, mRigid.angPosition);
|
||||
}
|
||||
mRigid.updateCenterOfMass();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set the shape to the server position
|
||||
mDelta.dt = 0;
|
||||
mDelta.pos = mRigid.linPosition;
|
||||
mDelta.posVec.set(0,0,0);
|
||||
mDelta.rot[1] = mDelta.rot[0] = mRigid.angPosition;
|
||||
mDelta.warpCount = mDelta.warpTicks = 0;
|
||||
setPosition(mRigid.linPosition, mRigid.angPosition);
|
||||
}
|
||||
mRigid.updateCenterOfMass();
|
||||
}
|
||||
|
||||
if(stream->readFlag())
|
||||
|
||||
if (stream->readFlag()) // FreezeMask
|
||||
mDisableMove = stream->readFlag();
|
||||
}
|
||||
|
||||
|
|
@ -1724,6 +2005,14 @@ void RigidShape::consoleInit()
|
|||
"The larger this number the less often the working list will be updated due to motion, but any non-static shape that "
|
||||
"moves into the query box will not be noticed.\n\n"
|
||||
"@ingroup GameObjects\n");
|
||||
|
||||
// NEW — mirrors PhysicsShape debug knobs for the dynamic path
|
||||
Con::addVariable("$RigidShape::noCorrections", TypeBool, &smNoCorrections,
|
||||
"@brief When true, the server will not send state corrections to the client "
|
||||
"for dynamic RigidShapes. Debug only.\n\n");
|
||||
Con::addVariable("$RigidShape::noSmoothing", TypeBool, &smNoSmoothing,
|
||||
"@brief When true, dynamic RigidShape clients snap to corrected positions "
|
||||
"instead of smoothly interpolating. Debug only.\n\n");
|
||||
}
|
||||
|
||||
void RigidShape::initPersistFields()
|
||||
|
|
@ -1857,12 +2146,20 @@ void RigidShape::reset()
|
|||
{
|
||||
mRigid.clearForces();
|
||||
mRigid.setAtRest();
|
||||
if (mPhysicsRep && mPhysicsRep->isDynamic())
|
||||
{
|
||||
mPhysicsRep->setLinVelocity(Point3F::Zero);
|
||||
mPhysicsRep->setAngVelocity(Point3F::Zero);
|
||||
mPhysicsRep->setSleeping(true);
|
||||
}
|
||||
}
|
||||
|
||||
void RigidShape::freezeSim(bool frozen)
|
||||
{
|
||||
mDisableMove = frozen;
|
||||
setMaskBits(FreezeMask);
|
||||
if (mPhysicsRep && mPhysicsRep->isDynamic())
|
||||
mPhysicsRep->setSleeping(frozen);
|
||||
}
|
||||
|
||||
DefineEngineMethod( RigidShape, reset, void, (),,
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ class RigidShapeData : public ShapeBaseData
|
|||
F32 splashVelEpsilon;
|
||||
|
||||
bool enablePhysicsRep;
|
||||
|
||||
bool isDynamic;
|
||||
|
||||
F32 dragForce;
|
||||
F32 vertFactor;
|
||||
|
|
@ -189,6 +189,10 @@ class RigidShape: public ShapeBase
|
|||
};
|
||||
|
||||
PhysicsBody* mPhysicsRep;
|
||||
/// Current physics state (used by the dynamic-body path).
|
||||
PhysicsState mState;
|
||||
/// Previous and current render states for the dynamic-body path.
|
||||
PhysicsState mRenderState[2];
|
||||
|
||||
StateDelta mDelta;
|
||||
S32 mPredictionCount; ///< Number of ticks to predict
|
||||
|
|
@ -243,6 +247,8 @@ class RigidShape: public ShapeBase
|
|||
|
||||
void updateForces(F32 dt);
|
||||
|
||||
void _updateContainerForces();
|
||||
|
||||
public:
|
||||
// Test code...
|
||||
static ClippedPolyList* sPolyList;
|
||||
|
|
|
|||
|
|
@ -2587,11 +2587,15 @@ void MeshRoad::_generateSegments()
|
|||
polylist.triangulate();
|
||||
|
||||
PhysicsCollision *colShape = PHYSICSMGR->createCollision();
|
||||
colShape->addTriangleMesh( polylist.mVertexList.address(),
|
||||
if (!colShape->addTriangleMesh(polylist.mVertexList.address(),
|
||||
polylist.mVertexList.size(),
|
||||
polylist.mIndexList.address(),
|
||||
polylist.mIndexList.size() / 3,
|
||||
MatrixF::Identity );
|
||||
getWorldTransform()))
|
||||
{
|
||||
// failed to create mesh, get out GET OUT NOWWWW
|
||||
return;
|
||||
}
|
||||
|
||||
PhysicsWorld *world = PHYSICSMGR->getWorld( isServerObject() ? "server" : "client" );
|
||||
mPhysicsRep = PHYSICSMGR->createBody();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue