mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-07-08 13:14:33 +00:00
use simplified jolt character controller
CharacterVirtual controller from jolt has issues that makes it unusable for example if our character is touching the ground it will vibrate violently. Simplified character controller seems to be a bit better for the time being.
This commit is contained in:
parent
c81a809444
commit
c03cedec6f
2 changed files with 195 additions and 457 deletions
|
|
@ -4,20 +4,17 @@
|
|||
#include "scene/sceneObject.h"
|
||||
#include "T3D/player.h"
|
||||
|
||||
// we use capsule for player?
|
||||
#ifdef Offset
|
||||
#pragma push_macro("Offset")
|
||||
#undef Offset
|
||||
#endif
|
||||
|
||||
#include <Jolt/Physics/Collision/ShapeCast.h>
|
||||
#include <Jolt/Physics/Collision/Shape/CapsuleShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/RotatedTranslatedShape.h>
|
||||
#include <Jolt/Physics/Collision/CollideShape.h>
|
||||
#include <Jolt/Physics/Collision/CollisionCollectorImpl.h>
|
||||
#include <Jolt/Physics/Collision/Shape/RotatedTranslatedShape.h>
|
||||
#include <Jolt/Physics/Collision/BroadPhase/BroadPhaseLayer.h>
|
||||
|
||||
#ifdef Offset
|
||||
// Restore the original macro after includes
|
||||
#pragma pop_macro("Offset")
|
||||
#endif
|
||||
|
||||
|
|
@ -39,15 +36,14 @@ JoltPlayer::~JoltPlayer()
|
|||
{
|
||||
if (mCharacter)
|
||||
{
|
||||
// Remove the inner body from the physics world before the character is released.
|
||||
// Setting mCharacter=nullptr first would make disableCollision() a no-op.
|
||||
mWorld->getPhysicsSystem()->GetBodyInterface().SetUserData(
|
||||
mCharacter->GetBodyID(), 0);
|
||||
|
||||
if (mCollisionEnabled)
|
||||
disableCollision();
|
||||
|
||||
mCharacter->SetUserData(0);
|
||||
mCharacter = nullptr;
|
||||
mCharacter = NULL;
|
||||
}
|
||||
|
||||
mIsEnabled = false;
|
||||
}
|
||||
|
||||
|
|
@ -56,42 +52,41 @@ void JoltPlayer::setTransform(const MatrixF& xfm)
|
|||
if (!mCharacter)
|
||||
return;
|
||||
|
||||
JPH::Mat44 mat = joltCast(xfm);
|
||||
const Point3F pos = xfm.getPosition();
|
||||
const QuatF rot(xfm);
|
||||
|
||||
mCharacter->SetPosition(mat.GetTranslation());
|
||||
mCharacter->SetRotation(mat.GetQuaternion());
|
||||
// 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)
|
||||
{
|
||||
const JPH::Mat44 trans = mCharacter->GetWorldTransform();
|
||||
*outMatrix = joltCast(trans);
|
||||
*outMatrix = joltCast(mCharacter->GetWorldTransform());
|
||||
return *outMatrix;
|
||||
}
|
||||
|
||||
Box3F JoltPlayer::getWorldBounds()
|
||||
{
|
||||
JPH::AABox box = mCharacter->GetShape()->GetWorldSpaceBounds(mCharacter->GetCenterOfMassTransform(), JPH::Vec3::sOne());
|
||||
return Box3F(
|
||||
Point3F(joltCast(box.mMin)),
|
||||
Point3F(joltCast(box.mMax)));
|
||||
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)
|
||||
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;
|
||||
|
|
@ -99,59 +94,72 @@ void JoltPlayer::init(const char* type, const Point3F& size, F32 runSurfaceCos,
|
|||
mStepHeight = stepHeight;
|
||||
mMaxSlopeCos = runSurfaceCos;
|
||||
|
||||
F32 radius = mMax(size.x, size.y) * 0.5f;
|
||||
F32 height = size.z;// +0.5f;
|
||||
height = mMax(0.01f, height);
|
||||
const F32 radius = mMax(size.x, size.y) * 0.5f;
|
||||
F32 height = mMax(0.01f, size.z);
|
||||
|
||||
mOriginOffset = (0.5f * height);// +radius;
|
||||
JPH::Quat rotFix = JPH::Quat::sRotation(JPH::Vec3::sAxisX(), JPH::DegreesToRadians(90.0f)); // zup
|
||||
JPH::Ref<JPH::Shape> shape = JPH::RotatedTranslatedShapeSettings(JPH::Vec3(0, 0, mOriginOffset), rotFix, new JPH::CapsuleShape(0.5f * height, radius)).Create().Get();
|
||||
JPH::Ref<JPH::Shape> inner_shape = JPH::RotatedTranslatedShapeSettings(JPH::Vec3(0, 0, mOriginOffset), rotFix, new JPH::CapsuleShape(0.5f * 0.9f * height, 0.9f * radius)).Create().Get();
|
||||
mOriginOffset = (0.5f * height);
|
||||
|
||||
JPH::Ref<JPH::CharacterVirtualSettings> settings = new JPH::CharacterVirtualSettings();
|
||||
Con::printf("JoltPlayer::init - maxSlopeAngle: %f degrees", mRadToDeg(mAcos(runSurfaceCos)));
|
||||
// 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->mMaxStrength = 100.0f;
|
||||
settings->mShape = shape;
|
||||
settings->mMass = obj->getMass();
|
||||
settings->mBackFaceMode = JPH::EBackFaceMode::IgnoreBackFaces;
|
||||
settings->mCharacterPadding = 0.05f;
|
||||
settings->mPenetrationRecoverySpeed = 1.0f;
|
||||
settings->mMaxNumHits = 64;
|
||||
settings->mPredictiveContactDistance = 0.05f;
|
||||
settings->mSupportingVolume = JPH::Plane(JPH::Vec3::sAxisZ(), -(radius));
|
||||
settings->mEnhancedInternalEdgeRemoval = true;
|
||||
settings->mInnerBodyShape = inner_shape;
|
||||
settings->mInnerBodyLayer = Layers::CHARACTER;
|
||||
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);
|
||||
QuatF angPos(objXfm);
|
||||
Point3F pos;
|
||||
objXfm.getColumn(3, &pos);
|
||||
|
||||
mCharacter = new JPH::CharacterVirtual(
|
||||
mCharacter = new JPH::Character(
|
||||
settings,
|
||||
joltCast(pos),
|
||||
joltCast(angPos),
|
||||
0,
|
||||
reinterpret_cast<U64>(&mUserData),
|
||||
mWorld->getPhysicsSystem()
|
||||
);
|
||||
|
||||
getUserData().setObject(obj);
|
||||
//getUserData().setBody(this);
|
||||
|
||||
mCharacter->SetUserData(reinterpret_cast<U64>(&mUserData));
|
||||
// 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());
|
||||
mCharacter->SetListener(this);
|
||||
|
||||
getUserData().setObject(obj);
|
||||
|
||||
mCharacter->AddToPhysicsSystem(JPH::EActivation::Activate);
|
||||
setSimulationEnabled(true);
|
||||
}
|
||||
|
||||
void JoltPlayer::preUpdate(F32 dt)
|
||||
{
|
||||
// Intentionally empty. Ground velocity is refreshed inside move() on every
|
||||
// call. If the calling code ever starts calling preUpdate() before move(),
|
||||
// the refresh in move() will simply be a no-op on the same-frame data.
|
||||
if (!mCharacter || !mIsEnabled)
|
||||
return;
|
||||
|
||||
static const float cCollisionTolerance = 0.05f;
|
||||
mCharacter->PostSimulation(cCollisionTolerance);
|
||||
}
|
||||
|
||||
Point3F JoltPlayer::move(const VectorF& displacement, CollisionList& outCol)
|
||||
|
|
@ -162,34 +170,36 @@ Point3F JoltPlayer::move(const VectorF& displacement, CollisionList& outCol)
|
|||
if (!mIsEnabled)
|
||||
return joltCast(mCharacter->GetPosition());
|
||||
|
||||
const F32 dt = TickSec;
|
||||
JPH::Vec3 velocity = joltCast(displacement) / dt;
|
||||
// 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 auto& system = mWorld->getPhysicsSystem();
|
||||
const F32 dt = TickSec;
|
||||
JPH::Vec3 velocity = joltCast(displacement) / dt;
|
||||
const JPH::Vec3 up = mCharacter->GetUp();
|
||||
|
||||
mCharacter->UpdateGroundVelocity();
|
||||
|
||||
const JPH::CharacterVirtual::EGroundState groundState = mCharacter->GetGroundState();
|
||||
const bool isOnGround = groundState == JPH::CharacterVirtual::EGroundState::OnGround;
|
||||
const JPH::Character::EGroundState groundState = mCharacter->GetGroundState();
|
||||
const bool isOnGround =
|
||||
groundState == JPH::Character::EGroundState::OnGround;
|
||||
|
||||
if (isOnGround && !mCharacter->IsSlopeTooSteep(mCharacter->GetGroundNormal()))
|
||||
{
|
||||
JPH::Vec3 horizontalVel(velocity.GetX(), velocity.GetY(), 0.0f);
|
||||
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.00f);
|
||||
velocity.SetZ(0.0f);
|
||||
|
||||
JPH::Vec3 groundVel = mCharacter->GetGroundVelocity();
|
||||
// Threshold raised to filter static-surface floating-point residuals.
|
||||
// Carry platform / conveyor velocity
|
||||
const JPH::Vec3 groundVel = mCharacter->GetGroundVelocity();
|
||||
if (groundVel.LengthSq() > 0.01f)
|
||||
{
|
||||
// Always carry horizontal platform motion (conveyor belts, sliding floors).
|
||||
velocity.SetX(velocity.GetX() + groundVel.GetX());
|
||||
velocity.SetY(velocity.GetY() + groundVel.GetY());
|
||||
// Only carry Z from platforms that are meaningfully moving upward (elevators).
|
||||
// Static surfaces can produce tiny Z residuals that fight gravity resolution.
|
||||
// Only add upward elevator velocity, not static-surface residuals
|
||||
if (groundVel.GetZ() > 0.05f)
|
||||
velocity.SetZ(velocity.GetZ() + groundVel.GetZ());
|
||||
}
|
||||
|
|
@ -199,213 +209,121 @@ Point3F JoltPlayer::move(const VectorF& displacement, CollisionList& outCol)
|
|||
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);
|
||||
|
||||
//----------------------------------------
|
||||
// Substep
|
||||
//----------------------------------------
|
||||
const U32 iterations = 3;
|
||||
const F32 subDt = dt / iterations;
|
||||
|
||||
// Build settings once outside the loop — they don't change per-substep.
|
||||
JPH::CharacterVirtual::ExtendedUpdateSettings extSettings;
|
||||
extSettings.mWalkStairsStepUp = JPH::Vec3(0, 0, mStepHeight);
|
||||
extSettings.mStickToFloorStepDown = JPH::Vec3(0, 0, -mStepHeight);
|
||||
extSettings.mWalkStairsCosAngleForwardContact = mMaxSlopeCos;
|
||||
|
||||
for (U32 i = 0; i < iterations; ++i)
|
||||
// -----------------------------------------------------------------
|
||||
// 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)
|
||||
{
|
||||
mCharacter->ExtendedUpdate(
|
||||
subDt,
|
||||
system->GetGravity(),
|
||||
extSettings,
|
||||
system->GetDefaultBroadPhaseLayerFilter(Layers::CHARACTER),
|
||||
system->GetDefaultLayerFilter(Layers::CHARACTER),
|
||||
{},
|
||||
{},
|
||||
*mWorld->getTempAllocator()
|
||||
);
|
||||
}
|
||||
const JPH::BodyID groundBodyID = mCharacter->GetGroundBodyID();
|
||||
JPH::BodyLockRead lock(
|
||||
mWorld->getPhysicsSystem()->GetBodyLockInterface(), groundBodyID);
|
||||
|
||||
//----------------------------------------
|
||||
// Fill collision list from active contacts
|
||||
//----------------------------------------
|
||||
const JPH::CharacterVirtual::ContactList& contacts = mCharacter->GetActiveContacts();
|
||||
|
||||
for (const JPH::CharacterVirtual::Contact& c : contacts)
|
||||
{
|
||||
if (!c.mHadCollision)
|
||||
continue;
|
||||
|
||||
if (outCol.getCount() >= CollisionList::MaxCollisions)
|
||||
break;
|
||||
|
||||
JPH::BodyLockRead lock(system->GetBodyLockInterface(), c.mBodyB);
|
||||
if (!lock.Succeeded())
|
||||
continue;
|
||||
|
||||
const JPH::Body& body = lock.GetBody();
|
||||
PhysicsUserData* ud = reinterpret_cast<PhysicsUserData*>(body.GetUserData());
|
||||
|
||||
Collision& col = outCol.increment();
|
||||
col.point = joltCast(c.mPosition);
|
||||
col.normal = joltCast(c.mContactNormal);
|
||||
col.object = ud ? ud->getObject() : nullptr;
|
||||
col.material = nullptr; // material lookup not available yet
|
||||
col.distance = c.mDistance;
|
||||
col.face = 0;
|
||||
col.faceDot = mDot(col.normal, VectorF(0.f, 0.f, 1.f));
|
||||
}
|
||||
|
||||
// If on ground, ensure the ground body is represented even if it
|
||||
// wasn't caught in the contact list (CharacterVirtual separates these)
|
||||
if (mCharacter->GetGroundState() == JPH::CharacterVirtual::EGroundState::OnGround
|
||||
&& outCol.getCount() < CollisionList::MaxCollisions)
|
||||
{
|
||||
JPH::BodyLockRead lock(system->GetBodyLockInterface(), mCharacter->GetGroundBodyID());
|
||||
if (lock.Succeeded())
|
||||
{
|
||||
const JPH::Body& body = lock.GetBody();
|
||||
PhysicsUserData* ud = reinterpret_cast<PhysicsUserData*>(body.GetUserData());
|
||||
|
||||
// Avoid duplicating if already added via active contacts
|
||||
SceneObject* groundObj = ud ? ud->getObject() : nullptr;
|
||||
bool alreadyPresent = false;
|
||||
for (S32 i = 0; i < outCol.getCount(); ++i)
|
||||
{
|
||||
if (outCol[i].object == groundObj)
|
||||
{
|
||||
alreadyPresent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!alreadyPresent)
|
||||
{
|
||||
Collision& col = outCol.increment();
|
||||
col.point = joltCast(mCharacter->GetGroundPosition());
|
||||
col.normal = joltCast(mCharacter->GetGroundNormal());
|
||||
col.object = groundObj;
|
||||
col.material = nullptr;
|
||||
col.distance = 0.f;
|
||||
col.faceDot = mDot(col.normal, VectorF(0.f, 0.f, 1.f));
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------
|
||||
// Final position
|
||||
//----------------------------------------
|
||||
Point3F finalPos = joltCast(mCharacter->GetPosition());
|
||||
return finalPos;
|
||||
// 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
|
||||
void JoltPlayer::findContact(SceneObject** contactObject, VectorF* contactNormal,
|
||||
Vector<SceneObject*>* outOverlapObjects) const
|
||||
{
|
||||
if (!mCharacter || !mIsEnabled)
|
||||
return;
|
||||
|
||||
const bool isOnGround = mCharacter->GetGroundState() == JPH::CharacterVirtual::EGroundState::OnGround;
|
||||
F32 bestDot = -1.0f;
|
||||
const JPH::Vec3 up = mCharacter->GetUp();
|
||||
const auto& system = mWorld->getPhysicsSystem();
|
||||
|
||||
for (const JPH::CharacterVirtual::Contact& c : mCharacter->GetActiveContacts())
|
||||
// --- 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())
|
||||
{
|
||||
if (c.mBodyB.IsInvalid())
|
||||
continue;
|
||||
|
||||
if (c.mIsSensorB)
|
||||
JPH::BodyLockRead lock(system->GetBodyLockInterface(),
|
||||
mCharacter->GetGroundBodyID());
|
||||
if (lock.Succeeded())
|
||||
{
|
||||
JPH::BodyLockRead lock(mWorld->getPhysicsSystem()->GetBodyLockInterface(), c.mBodyB);
|
||||
if (!lock.Succeeded())
|
||||
continue;
|
||||
|
||||
const JPH::Body& body = lock.GetBody();
|
||||
PhysicsUserData* ud = (PhysicsUserData*)body.GetUserData();
|
||||
if (!ud)
|
||||
continue;
|
||||
|
||||
SceneObject* obj = ud->getObject();
|
||||
if (obj && !outOverlapObjects->contains(obj))
|
||||
outOverlapObjects->push_back(obj);
|
||||
continue; // do not use for physical contact normal
|
||||
}
|
||||
|
||||
|
||||
if (c.mHadCollision)
|
||||
{
|
||||
JPH::BodyLockRead lock(mWorld->getPhysicsSystem()->GetBodyLockInterface(), c.mBodyB);
|
||||
if (!lock.Succeeded())
|
||||
continue;
|
||||
|
||||
const JPH::Body& body = lock.GetBody();
|
||||
PhysicsUserData* ud = (PhysicsUserData*)body.GetUserData();
|
||||
if (!ud)
|
||||
continue;
|
||||
|
||||
VectorF normal = joltCast(c.mContactNormal);
|
||||
F32 dot = mDot(normal, VectorF(0, 0, 1));
|
||||
if (dot > bestDot)
|
||||
auto* ud = reinterpret_cast<PhysicsUserData*>(lock.GetBody().GetUserData());
|
||||
if (ud)
|
||||
{
|
||||
bestDot = dot;
|
||||
*contactObject = ud->getObject();
|
||||
*contactNormal = normal;
|
||||
*contactNormal = joltCast(mCharacter->GetGroundNormal());
|
||||
}
|
||||
SceneObject* obj = ud->getObject();
|
||||
if (obj && !outOverlapObjects->contains(obj))
|
||||
outOverlapObjects->push_back(obj);
|
||||
}
|
||||
}
|
||||
|
||||
if (isOnGround)
|
||||
// --- 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
|
||||
{
|
||||
JPH::BodyLockRead lock(mWorld->getPhysicsSystem()->GetBodyLockInterface(), mCharacter->GetGroundBodyID());
|
||||
if (!lock.Succeeded())
|
||||
return;
|
||||
OverlapCollector(const JPH::PhysicsSystem* inSystem,
|
||||
Vector<SceneObject*>& inOverlaps)
|
||||
: mSystem(inSystem), mOverlaps(inOverlaps) {
|
||||
}
|
||||
|
||||
const JPH::Body& body = lock.GetBody();
|
||||
PhysicsUserData* ud = (PhysicsUserData*)body.GetUserData();
|
||||
if (!ud)
|
||||
return;
|
||||
void AddHit(const JPH::CollideShapeResult& inResult) override
|
||||
{
|
||||
JPH::BodyLockRead lock(mSystem->GetBodyLockInterface(), inResult.mBodyID2);
|
||||
if (!lock.Succeeded())
|
||||
return;
|
||||
|
||||
*contactObject = ud->getObject();
|
||||
*contactNormal = joltCast(mCharacter->GetGroundNormal());
|
||||
}
|
||||
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;
|
||||
|
||||
//if (!mCharacter || !mWorld)
|
||||
// return false;
|
||||
|
||||
//const auto& system = mWorld->getPhysicsSystem();
|
||||
|
||||
//JPH::BodyFilter body_filter;
|
||||
//JPH::ShapeFilter shape_filter;
|
||||
|
||||
//F32 radius = getMax(nSize.x, nSize.y) * 0.5f;
|
||||
//F32 height = nSize.z - (radius * 2.0f);
|
||||
|
||||
//F32 halfHeight = 0.5f * height;
|
||||
//JPH::Vec3 shapeTranslation(0, 0, halfHeight + radius);
|
||||
|
||||
//JPH::Quat rotFix = JPH::Quat::sRotation(JPH::Vec3::sAxisX(), JPH::DegreesToRadians(90.0f)); // zup
|
||||
//JPH::Ref<JPH::Shape> shape = JPH::RotatedTranslatedShapeSettings(shapeTranslation, rotFix, new JPH::CapsuleShape(halfHeight, radius)).Create().Get();
|
||||
|
||||
//const float maxPenetration = 0.05f; // tweak tolerance
|
||||
|
||||
//return mCharacter->SetShape(
|
||||
// shape,
|
||||
// maxPenetration,
|
||||
// system->GetDefaultBroadPhaseLayerFilter(Layers::MOVING),
|
||||
// system->GetDefaultLayerFilter(Layers::MOVING),
|
||||
// body_filter,
|
||||
// shape_filter,
|
||||
// *mWorld->getTempAllocator());
|
||||
|
||||
}
|
||||
|
||||
void JoltPlayer::setSpacials(const Point3F& nPos, const Point3F& nSize)
|
||||
|
|
@ -413,34 +331,24 @@ void JoltPlayer::setSpacials(const Point3F& nPos, const Point3F& nSize)
|
|||
if (!mCharacter || !mWorld)
|
||||
return;
|
||||
|
||||
const auto& system = mWorld->getPhysicsSystem();
|
||||
const F32 radius = mMax(nSize.x, nSize.y) * 0.5f;
|
||||
const F32 height = mMax(0.01f, nSize.z);
|
||||
|
||||
JPH::BodyFilter body_filter;
|
||||
JPH::ShapeFilter shape_filter;
|
||||
mOriginOffset = 0.5f * height;
|
||||
|
||||
F32 radius = mMax(nSize.x, nSize.y) * 0.5f;
|
||||
F32 height = nSize.z;// +0.5f;
|
||||
height = mMax(0.01f, height);
|
||||
const JPH::Quat rotFix = JPH::Quat::sRotation(
|
||||
JPH::Vec3::sAxisX(), JPH::DegreesToRadians(90.0f));
|
||||
|
||||
mOriginOffset = (0.5f * height);// +radius;
|
||||
JPH::Quat rotFix = JPH::Quat::sRotation(JPH::Vec3::sAxisX(), JPH::DegreesToRadians(90.0f)); // zup
|
||||
JPH::Ref<JPH::Shape> shape = JPH::RotatedTranslatedShapeSettings(JPH::Vec3(0, 0, mOriginOffset), rotFix, new JPH::CapsuleShape(0.5f * height, radius)).Create().Get();
|
||||
JPH::Ref<JPH::Shape> inner_shape = JPH::RotatedTranslatedShapeSettings(JPH::Vec3(0, 0, mOriginOffset), rotFix, new JPH::CapsuleShape(0.5f * 0.9f * height, 0.9f * radius)).Create().Get();
|
||||
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();
|
||||
|
||||
const float maxPenetration = 0.05f; // tweak tolerance
|
||||
|
||||
if (mCharacter->SetShape(
|
||||
shape,
|
||||
maxPenetration,
|
||||
system->GetDefaultBroadPhaseLayerFilter(Layers::CHARACTER),
|
||||
system->GetDefaultLayerFilter(Layers::CHARACTER),
|
||||
body_filter,
|
||||
shape_filter,
|
||||
*mWorld->getTempAllocator())
|
||||
)
|
||||
{
|
||||
mCharacter->SetInnerBodyShape(inner_shape);
|
||||
}
|
||||
// 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()
|
||||
|
|
@ -449,15 +357,8 @@ void JoltPlayer::enableCollision()
|
|||
return;
|
||||
|
||||
mCollisionEnabled = true;
|
||||
|
||||
JPH::BodyID innerBodyID = mCharacter->GetInnerBodyID();
|
||||
if (innerBodyID.IsInvalid())
|
||||
return;
|
||||
|
||||
mWorld->getPhysicsSystem()->GetBodyInterface().AddBody(
|
||||
innerBodyID,
|
||||
JPH::EActivation::Activate
|
||||
);
|
||||
// Character IS a body — adding it back to the physics system re-enables it
|
||||
mCharacter->AddToPhysicsSystem(JPH::EActivation::Activate);
|
||||
}
|
||||
|
||||
void JoltPlayer::disableCollision()
|
||||
|
|
@ -466,121 +367,5 @@ void JoltPlayer::disableCollision()
|
|||
return;
|
||||
|
||||
mCollisionEnabled = false;
|
||||
|
||||
JPH::BodyID innerBodyID = mCharacter->GetInnerBodyID();
|
||||
if (innerBodyID.IsInvalid())
|
||||
return;
|
||||
|
||||
mWorld->getPhysicsSystem()->GetBodyInterface().RemoveBody(innerBodyID);
|
||||
}
|
||||
|
||||
void JoltPlayer::OnContactCommon(const JPH::CharacterVirtual* inCharacter, const JPH::BodyID& inBodyID2, const JPH::SubShapeID& inSubShapeID2, JPH::RVec3Arg inContactPosition, JPH::Vec3Arg inContactNormal, JPH::CharacterContactSettings& ioSettings)
|
||||
{
|
||||
if (inCharacter == mCharacter)
|
||||
{
|
||||
if (ioSettings.mCanPushCharacter && mWorld->getPhysicsSystem()->GetBodyInterface().GetMotionType(inBodyID2) != JPH::EMotionType::Static)
|
||||
{
|
||||
mAllowSliding = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void JoltPlayer::OnCharacterContactCommon(const JPH::CharacterVirtual* inCharacter, const JPH::CharacterVirtual* inOtherCharacter, const JPH::SubShapeID& inSubShapeID2, JPH::RVec3Arg inContactPosition, JPH::Vec3Arg inContactNormal, JPH::CharacterContactSettings& ioSettings)
|
||||
{
|
||||
if (inCharacter == mCharacter && ioSettings.mCanPushCharacter)
|
||||
mAllowSliding = true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// Character Contact Listener Callbacks
|
||||
//------------------------------------------------------------------
|
||||
|
||||
void JoltPlayer::OnAdjustBodyVelocity(const JPH::CharacterVirtual* inCharacter, const JPH::Body& inBody2, JPH::Vec3& ioLinearVelocity, JPH::Vec3& ioAngularVelocity)
|
||||
{
|
||||
}
|
||||
|
||||
bool JoltPlayer::OnContactValidate(const JPH::CharacterVirtual* inCharacter, const JPH::BodyID& inBodyID2, const JPH::SubShapeID& inSubShapeID2)
|
||||
{
|
||||
if (inCharacter != mCharacter)
|
||||
return true;
|
||||
|
||||
JPH::BodyLockRead lock(mWorld->getPhysicsSystem()->GetBodyLockInterface(), inBodyID2);
|
||||
if (lock.Succeeded() && lock.GetBody().IsSensor())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void JoltPlayer::OnContactAdded(const JPH::CharacterVirtual* inCharacter, const JPH::BodyID& inBodyID2, const JPH::SubShapeID& inSubShapeID2, JPH::RVec3Arg inContactPosition, JPH::Vec3Arg inContactNormal, JPH::CharacterContactSettings& ioSettings)
|
||||
{
|
||||
if (inCharacter != mCharacter)
|
||||
return;
|
||||
|
||||
// Sensors are already rejected by OnContactValidate so this callback will
|
||||
// never fire for them. No sensor check needed here.
|
||||
JPH::CharacterVirtual::ContactKey c(inBodyID2, inSubShapeID2);
|
||||
if (std::find(mActiveContacts.begin(), mActiveContacts.end(), c) != mActiveContacts.end())
|
||||
{
|
||||
Con::warnf("JoltPlayer::OnContactAdded - contact already present, skipping duplicate");
|
||||
return;
|
||||
}
|
||||
mActiveContacts.push_back(c);
|
||||
|
||||
OnContactCommon(inCharacter, inBodyID2, inSubShapeID2, inContactPosition, inContactNormal, ioSettings);
|
||||
}
|
||||
|
||||
void JoltPlayer::OnContactPersisted(const JPH::CharacterVirtual* inCharacter, const JPH::BodyID& inBodyID2, const JPH::SubShapeID& inSubShapeID2, JPH::RVec3Arg inContactPosition, JPH::Vec3Arg inContactNormal, JPH::CharacterContactSettings& ioSettings)
|
||||
{
|
||||
if (inCharacter != mCharacter)
|
||||
return;
|
||||
|
||||
// Sensors are already rejected by OnContactValidate — this callback will
|
||||
// not fire for them.
|
||||
JPH::CharacterVirtual::ContactKey c(inBodyID2, inSubShapeID2);
|
||||
if (std::find(mActiveContacts.begin(), mActiveContacts.end(), c) == mActiveContacts.end())
|
||||
{
|
||||
Con::warnf("JoltPlayer::OnContactPersisted - contact not in set, inserting as new");
|
||||
mActiveContacts.push_back(c);
|
||||
}
|
||||
|
||||
OnContactCommon(inCharacter, inBodyID2, inSubShapeID2, inContactPosition, inContactNormal, ioSettings);
|
||||
}
|
||||
|
||||
void JoltPlayer::OnContactRemoved(const JPH::CharacterVirtual* inCharacter, const JPH::BodyID& inBodyID2, const JPH::SubShapeID& inSubShapeID2)
|
||||
{
|
||||
if (inCharacter == mCharacter)
|
||||
{
|
||||
JPH::CharacterVirtual::ContactKey c(inBodyID2, inSubShapeID2);
|
||||
ContactSet::iterator it = std::find(mActiveContacts.begin(), mActiveContacts.end(), c);
|
||||
if (it == mActiveContacts.end())
|
||||
{
|
||||
// Can occur when the contact was never added due to an earlier ordering anomaly.
|
||||
Con::warnf("JoltPlayer::OnContactRemoved - contact not found, ignoring");
|
||||
return;
|
||||
}
|
||||
mActiveContacts.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void JoltPlayer::OnCharacterContactAdded(const JPH::CharacterVirtual* inCharacter, const JPH::CharacterVirtual* inOtherCharacter, const JPH::SubShapeID& inSubShapeID2, JPH::RVec3Arg inContactPosition, JPH::Vec3Arg inContactNormal, JPH::CharacterContactSettings& ioSettings)
|
||||
{
|
||||
}
|
||||
|
||||
void JoltPlayer::OnCharacterContactPersisted(const JPH::CharacterVirtual* inCharacter, const JPH::CharacterVirtual* inOtherCharacter, const JPH::SubShapeID& inSubShapeID2, JPH::RVec3Arg inContactPosition, JPH::Vec3Arg inContactNormal, JPH::CharacterContactSettings& ioSettings)
|
||||
{
|
||||
}
|
||||
|
||||
void JoltPlayer::OnCharacterContactRemoved(const JPH::CharacterVirtual* inCharacter, const JPH::CharacterID& inOtherCharacterID, const JPH::SubShapeID& inSubShapeID2)
|
||||
{
|
||||
}
|
||||
|
||||
void JoltPlayer::OnContactSolve(const JPH::CharacterVirtual* inCharacter, const JPH::BodyID& inBodyID2, const JPH::SubShapeID& inSubShapeID2, JPH::RVec3Arg inContactPosition, JPH::Vec3Arg inContactNormal, JPH::Vec3Arg inContactVelocity, const JPH::PhysicsMaterial* inContactMaterial, JPH::Vec3Arg inCharacterVelocity, JPH::Vec3& ioNewCharacterVelocity)
|
||||
{
|
||||
if (inCharacter != mCharacter)
|
||||
return;
|
||||
|
||||
// When not sliding and contact surface isn't moving and slope is walkable,
|
||||
// zero out velocity to prevent micro-jitter from solver residuals
|
||||
if (!mAllowSliding && inContactVelocity.IsNearZero() && !inCharacter->IsSlopeTooSteep(inContactNormal))
|
||||
ioNewCharacterVelocity = JPH::Vec3::sZero();
|
||||
mCharacter->RemoveFromPhysicsSystem();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,106 +11,59 @@
|
|||
#include "T3D/physics/jolt/joltWorld.h"
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#ifdef Offset
|
||||
#pragma push_macro("Offset")
|
||||
#undef Offset
|
||||
#endif
|
||||
|
||||
#include <Jolt/Physics/Character/CharacterVirtual.h>
|
||||
#include <Jolt/Physics/Character/Character.h>
|
||||
|
||||
#ifdef Offset
|
||||
// Restore the original macro after includes
|
||||
#pragma pop_macro("Offset")
|
||||
#endif
|
||||
|
||||
class JoltPlayer : public PhysicsPlayer, public JPH::CharacterContactListener
|
||||
class JoltPlayer : public PhysicsPlayer
|
||||
{
|
||||
protected:
|
||||
JoltWorld* mWorld;
|
||||
SceneObject* mObject;
|
||||
JPH::Ref<JPH::CharacterVirtual> mCharacter;
|
||||
JPH::Vec3 mDesiredVelocity;
|
||||
F32 mMaxSlopeCos;
|
||||
F32 mStepHeight;
|
||||
F32 mOriginOffset;
|
||||
/// Is the body participating in the physics simulation.
|
||||
bool mIsEnabled;
|
||||
bool mAllowSliding;
|
||||
bool mCollisionEnabled;
|
||||
JPH::Ref<JPH::Character> mCharacter;
|
||||
JPH::Vec3 mDesiredVelocity;
|
||||
F32 mMaxSlopeCos;
|
||||
F32 mStepHeight;
|
||||
F32 mOriginOffset;
|
||||
bool mIsEnabled;
|
||||
bool mAllowSliding;
|
||||
bool mCollisionEnabled;
|
||||
|
||||
using ContactSet = JPH::Array<JPH::CharacterVirtual::ContactKey>;
|
||||
ContactSet mActiveContacts;
|
||||
public:
|
||||
JoltPlayer();
|
||||
virtual ~JoltPlayer();
|
||||
|
||||
// PhysicsObject
|
||||
PhysicsWorld* getWorld() override { return mWorld; }
|
||||
void setTransform(const MatrixF& xfm) override;
|
||||
void setTransform(const MatrixF& xfm) override;
|
||||
MatrixF& getTransform(MatrixF* outMatrix) override;
|
||||
Box3F getWorldBounds() override;
|
||||
void setSimulationEnabled(bool enabled) override;
|
||||
bool isSimulationEnabled() override { return mIsEnabled; }
|
||||
Box3F getWorldBounds() override;
|
||||
void setSimulationEnabled(bool enabled) override;
|
||||
bool isSimulationEnabled() override { return mIsEnabled; }
|
||||
|
||||
// Physics Player.
|
||||
void init(const char* type,
|
||||
const Point3F& size,
|
||||
F32 runSurfaceCos,
|
||||
F32 stepHeight,
|
||||
SceneObject* obj,
|
||||
PhysicsWorld* world) override;
|
||||
// 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;
|
||||
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 enableCollision() override;
|
||||
void disableCollision() override;
|
||||
|
||||
JPH::Ref<JPH::CharacterVirtual> getCharacter() { return mCharacter; }
|
||||
|
||||
protected:
|
||||
// Common function to be called when contacts are added/persisted
|
||||
void OnContactCommon(const JPH::CharacterVirtual* inCharacter, const JPH::BodyID& inBodyID2, const JPH::SubShapeID& inSubShapeID2, JPH::RVec3Arg inContactPosition, JPH::Vec3Arg inContactNormal, JPH::CharacterContactSettings& ioSettings);
|
||||
void OnCharacterContactCommon(const JPH::CharacterVirtual* inCharacter, const JPH::CharacterVirtual* inOtherCharacter, const JPH::SubShapeID& inSubShapeID2, JPH::RVec3Arg inContactPosition, JPH::Vec3Arg inContactNormal, JPH::CharacterContactSettings& ioSettings);
|
||||
|
||||
|
||||
public: // contact listener
|
||||
/// Callback to adjust the velocity of a body as seen by the character. Can be adjusted to e.g. implement a conveyor belt or an inertial dampener system of a sci-fi space ship.
|
||||
virtual void OnAdjustBodyVelocity(const JPH::CharacterVirtual* inCharacter, const JPH::Body& inBody2, JPH::Vec3& ioLinearVelocity, JPH::Vec3& ioAngularVelocity) override;
|
||||
|
||||
// Called to check if the character can collide with a body at all. Return false to discard the contact
|
||||
// before it reaches the constraint solver, stair walking, or stick-to-floor algorithms.
|
||||
virtual bool OnContactValidate(const JPH::CharacterVirtual* inCharacter, const JPH::BodyID& inBodyID2, const JPH::SubShapeID& inSubShapeID2) override;
|
||||
|
||||
// Called whenever the character collides with a body.
|
||||
virtual void OnContactAdded(const JPH::CharacterVirtual* inCharacter, const JPH::BodyID& inBodyID2, const JPH::SubShapeID& inSubShapeID2, JPH::RVec3Arg inContactPosition, JPH::Vec3Arg inContactNormal, JPH::CharacterContactSettings& ioSettings) override;
|
||||
|
||||
// Called whenever the character persists colliding with a body.
|
||||
virtual void OnContactPersisted(const JPH::CharacterVirtual* inCharacter, const JPH::BodyID& inBodyID2, const JPH::SubShapeID& inSubShapeID2, JPH::RVec3Arg inContactPosition, JPH::Vec3Arg inContactNormal, JPH::CharacterContactSettings& ioSettings) override;
|
||||
|
||||
// Called whenever the character loses contact with a body.
|
||||
virtual void OnContactRemoved(const JPH::CharacterVirtual* inCharacter, const JPH::BodyID& inBodyID2, const JPH::SubShapeID& inSubShapeID2) override;
|
||||
|
||||
// Called whenever the character collides with a virtual character.
|
||||
virtual void OnCharacterContactAdded(const JPH::CharacterVirtual* inCharacter, const JPH::CharacterVirtual* inOtherCharacter, const JPH::SubShapeID& inSubShapeID2, JPH::RVec3Arg inContactPosition, JPH::Vec3Arg inContactNormal, JPH::CharacterContactSettings& ioSettings) override;
|
||||
|
||||
// Called whenever the character persists colliding with a virtual character.
|
||||
virtual void OnCharacterContactPersisted(const JPH::CharacterVirtual* inCharacter, const JPH::CharacterVirtual* inOtherCharacter, const JPH::SubShapeID& inSubShapeID2, JPH::RVec3Arg inContactPosition, JPH::Vec3Arg inContactNormal, JPH::CharacterContactSettings& ioSettings) override;
|
||||
|
||||
// Called whenever the character loses contact with a virtual character.
|
||||
virtual void OnCharacterContactRemoved(const JPH::CharacterVirtual* inCharacter, const JPH::CharacterID& inOtherCharacterID, const JPH::SubShapeID& inSubShapeID2) override;
|
||||
|
||||
// Called whenever the character movement is solved and a constraint is hit. Allows the listener to override the resulting character velocity (e.g. by preventing sliding along certain surfaces).
|
||||
virtual void OnContactSolve(const JPH::CharacterVirtual* inCharacter, const JPH::BodyID& inBodyID2, const JPH::SubShapeID& inSubShapeID2, JPH::RVec3Arg inContactPosition, JPH::Vec3Arg inContactNormal, JPH::Vec3Arg inContactVelocity, const JPH::PhysicsMaterial* inContactMaterial, JPH::Vec3Arg inCharacterVelocity, JPH::Vec3& ioNewCharacterVelocity) override;
|
||||
|
||||
|
||||
JPH::Ref<JPH::Character> getCharacter() { return mCharacter; }
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
#endif // _JOLTPLAYER_H_
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue