Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Vehicle/VehicleCollisionTester.cpp
9912 views
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)1// SPDX-FileCopyrightText: 2021 Jorrit Rouwe2// SPDX-License-Identifier: MIT34#include <Jolt/Jolt.h>56#include <Jolt/Physics/Vehicle/VehicleCollisionTester.h>7#include <Jolt/Physics/Vehicle/VehicleConstraint.h>8#include <Jolt/Physics/Collision/RayCast.h>9#include <Jolt/Physics/Collision/ShapeCast.h>10#include <Jolt/Physics/Collision/CastResult.h>11#include <Jolt/Physics/Collision/Shape/SphereShape.h>12#include <Jolt/Physics/Collision/Shape/CylinderShape.h>13#include <Jolt/Physics/Collision/CollisionCollectorImpl.h>14#include <Jolt/Physics/PhysicsSystem.h>1516JPH_NAMESPACE_BEGIN1718bool VehicleCollisionTesterRay::Collide(PhysicsSystem &inPhysicsSystem, const VehicleConstraint &inVehicleConstraint, uint inWheelIndex, RVec3Arg inOrigin, Vec3Arg inDirection, const BodyID &inVehicleBodyID, Body *&outBody, SubShapeID &outSubShapeID, RVec3 &outContactPosition, Vec3 &outContactNormal, float &outSuspensionLength) const19{20const DefaultBroadPhaseLayerFilter default_broadphase_layer_filter = inPhysicsSystem.GetDefaultBroadPhaseLayerFilter(mObjectLayer);21const BroadPhaseLayerFilter &broadphase_layer_filter = mBroadPhaseLayerFilter != nullptr? *mBroadPhaseLayerFilter : default_broadphase_layer_filter;2223const DefaultObjectLayerFilter default_object_layer_filter = inPhysicsSystem.GetDefaultLayerFilter(mObjectLayer);24const ObjectLayerFilter &object_layer_filter = mObjectLayerFilter != nullptr? *mObjectLayerFilter : default_object_layer_filter;2526const IgnoreSingleBodyFilter default_body_filter(inVehicleBodyID);27const BodyFilter &body_filter = mBodyFilter != nullptr? *mBodyFilter : default_body_filter;2829const WheelSettings *wheel_settings = inVehicleConstraint.GetWheel(inWheelIndex)->GetSettings();30float wheel_radius = wheel_settings->mRadius;31float ray_length = wheel_settings->mSuspensionMaxLength + wheel_radius;32RRayCast ray { inOrigin, ray_length * inDirection };3334class MyCollector : public CastRayCollector35{36public:37MyCollector(PhysicsSystem &inPhysicsSystem, const RRayCast &inRay, Vec3Arg inUpDirection, float inCosMaxSlopeAngle) :38mPhysicsSystem(inPhysicsSystem),39mRay(inRay),40mUpDirection(inUpDirection),41mCosMaxSlopeAngle(inCosMaxSlopeAngle)42{43}4445virtual void AddHit(const RayCastResult &inResult) override46{47// Test if this collision is closer than the previous one48if (inResult.mFraction < GetEarlyOutFraction())49{50// Lock the body51BodyLockRead lock(mPhysicsSystem.GetBodyLockInterfaceNoLock(), inResult.mBodyID);52JPH_ASSERT(lock.Succeeded()); // When this runs all bodies are locked so this should not fail53const Body *body = &lock.GetBody();5455if (body->IsSensor())56return;5758// Test that we're not hitting a vertical wall59RVec3 contact_pos = mRay.GetPointOnRay(inResult.mFraction);60Vec3 normal = body->GetWorldSpaceSurfaceNormal(inResult.mSubShapeID2, contact_pos);61if (normal.Dot(mUpDirection) > mCosMaxSlopeAngle)62{63// Update early out fraction to this hit64UpdateEarlyOutFraction(inResult.mFraction);6566// Get the contact properties67mBody = body;68mSubShapeID2 = inResult.mSubShapeID2;69mContactPosition = contact_pos;70mContactNormal = normal;71}72}73}7475// Configuration76PhysicsSystem & mPhysicsSystem;77RRayCast mRay;78Vec3 mUpDirection;79float mCosMaxSlopeAngle;8081// Resulting closest collision82const Body * mBody = nullptr;83SubShapeID mSubShapeID2;84RVec3 mContactPosition;85Vec3 mContactNormal;86};8788RayCastSettings settings;8990MyCollector collector(inPhysicsSystem, ray, mUp, mCosMaxSlopeAngle);91inPhysicsSystem.GetNarrowPhaseQueryNoLock().CastRay(ray, settings, collector, broadphase_layer_filter, object_layer_filter, body_filter);92if (collector.mBody == nullptr)93return false;9495outBody = const_cast<Body *>(collector.mBody);96outSubShapeID = collector.mSubShapeID2;97outContactPosition = collector.mContactPosition;98outContactNormal = collector.mContactNormal;99outSuspensionLength = max(0.0f, ray_length * collector.GetEarlyOutFraction() - wheel_radius);100101return true;102}103104void VehicleCollisionTesterRay::PredictContactProperties(PhysicsSystem &inPhysicsSystem, const VehicleConstraint &inVehicleConstraint, uint inWheelIndex, RVec3Arg inOrigin, Vec3Arg inDirection, const BodyID &inVehicleBodyID, Body *&ioBody, SubShapeID &ioSubShapeID, RVec3 &ioContactPosition, Vec3 &ioContactNormal, float &ioSuspensionLength) const105{106// Recalculate the contact points assuming the contact point is on an infinite plane107const WheelSettings *wheel_settings = inVehicleConstraint.GetWheel(inWheelIndex)->GetSettings();108float d_dot_n = inDirection.Dot(ioContactNormal);109if (d_dot_n < -1.0e-6f)110{111// Reproject the contact position using the suspension ray and the plane formed by the contact position and normal112ioContactPosition = inOrigin + Vec3(ioContactPosition - inOrigin).Dot(ioContactNormal) / d_dot_n * inDirection;113114// The suspension length is simply the distance between the contact position and the suspension origin excluding the wheel radius115ioSuspensionLength = Clamp(Vec3(ioContactPosition - inOrigin).Dot(inDirection) - wheel_settings->mRadius, 0.0f, wheel_settings->mSuspensionMaxLength);116}117else118{119// If the normal is pointing away we assume there's no collision anymore120ioSuspensionLength = wheel_settings->mSuspensionMaxLength;121}122}123124bool VehicleCollisionTesterCastSphere::Collide(PhysicsSystem &inPhysicsSystem, const VehicleConstraint &inVehicleConstraint, uint inWheelIndex, RVec3Arg inOrigin, Vec3Arg inDirection, const BodyID &inVehicleBodyID, Body *&outBody, SubShapeID &outSubShapeID, RVec3 &outContactPosition, Vec3 &outContactNormal, float &outSuspensionLength) const125{126const DefaultBroadPhaseLayerFilter default_broadphase_layer_filter = inPhysicsSystem.GetDefaultBroadPhaseLayerFilter(mObjectLayer);127const BroadPhaseLayerFilter &broadphase_layer_filter = mBroadPhaseLayerFilter != nullptr? *mBroadPhaseLayerFilter : default_broadphase_layer_filter;128129const DefaultObjectLayerFilter default_object_layer_filter = inPhysicsSystem.GetDefaultLayerFilter(mObjectLayer);130const ObjectLayerFilter &object_layer_filter = mObjectLayerFilter != nullptr? *mObjectLayerFilter : default_object_layer_filter;131132const IgnoreSingleBodyFilter default_body_filter(inVehicleBodyID);133const BodyFilter &body_filter = mBodyFilter != nullptr? *mBodyFilter : default_body_filter;134135SphereShape sphere(mRadius);136sphere.SetEmbedded();137138const WheelSettings *wheel_settings = inVehicleConstraint.GetWheel(inWheelIndex)->GetSettings();139float wheel_radius = wheel_settings->mRadius;140float shape_cast_length = wheel_settings->mSuspensionMaxLength + wheel_radius - mRadius;141RShapeCast shape_cast(&sphere, Vec3::sOne(), RMat44::sTranslation(inOrigin), inDirection * shape_cast_length);142143ShapeCastSettings settings;144settings.mUseShrunkenShapeAndConvexRadius = true;145settings.mReturnDeepestPoint = true;146147class MyCollector : public CastShapeCollector148{149public:150MyCollector(PhysicsSystem &inPhysicsSystem, const RShapeCast &inShapeCast, Vec3Arg inUpDirection, float inCosMaxSlopeAngle) :151mPhysicsSystem(inPhysicsSystem),152mShapeCast(inShapeCast),153mUpDirection(inUpDirection),154mCosMaxSlopeAngle(inCosMaxSlopeAngle)155{156}157158virtual void AddHit(const ShapeCastResult &inResult) override159{160// Test if this collision is closer/deeper than the previous one161float early_out = inResult.GetEarlyOutFraction();162if (early_out < GetEarlyOutFraction())163{164// Lock the body165BodyLockRead lock(mPhysicsSystem.GetBodyLockInterfaceNoLock(), inResult.mBodyID2);166JPH_ASSERT(lock.Succeeded()); // When this runs all bodies are locked so this should not fail167const Body *body = &lock.GetBody();168169if (body->IsSensor())170return;171172// Test that we're not hitting a vertical wall173Vec3 normal = -inResult.mPenetrationAxis.Normalized();174if (normal.Dot(mUpDirection) > mCosMaxSlopeAngle)175{176// Update early out fraction to this hit177UpdateEarlyOutFraction(early_out);178179// Get the contact properties180mBody = body;181mSubShapeID2 = inResult.mSubShapeID2;182mContactPosition = mShapeCast.mCenterOfMassStart.GetTranslation() + inResult.mContactPointOn2;183mContactNormal = normal;184mFraction = inResult.mFraction;185}186}187}188189// Configuration190PhysicsSystem & mPhysicsSystem;191const RShapeCast & mShapeCast;192Vec3 mUpDirection;193float mCosMaxSlopeAngle;194195// Resulting closest collision196const Body * mBody = nullptr;197SubShapeID mSubShapeID2;198RVec3 mContactPosition;199Vec3 mContactNormal;200float mFraction;201};202203MyCollector collector(inPhysicsSystem, shape_cast, mUp, mCosMaxSlopeAngle);204inPhysicsSystem.GetNarrowPhaseQueryNoLock().CastShape(shape_cast, settings, shape_cast.mCenterOfMassStart.GetTranslation(), collector, broadphase_layer_filter, object_layer_filter, body_filter);205if (collector.mBody == nullptr)206return false;207208outBody = const_cast<Body *>(collector.mBody);209outSubShapeID = collector.mSubShapeID2;210outContactPosition = collector.mContactPosition;211outContactNormal = collector.mContactNormal;212outSuspensionLength = max(0.0f, shape_cast_length * collector.mFraction + mRadius - wheel_radius);213214return true;215}216217void VehicleCollisionTesterCastSphere::PredictContactProperties(PhysicsSystem &inPhysicsSystem, const VehicleConstraint &inVehicleConstraint, uint inWheelIndex, RVec3Arg inOrigin, Vec3Arg inDirection, const BodyID &inVehicleBodyID, Body *&ioBody, SubShapeID &ioSubShapeID, RVec3 &ioContactPosition, Vec3 &ioContactNormal, float &ioSuspensionLength) const218{219// Recalculate the contact points assuming the contact point is on an infinite plane220const WheelSettings *wheel_settings = inVehicleConstraint.GetWheel(inWheelIndex)->GetSettings();221float d_dot_n = inDirection.Dot(ioContactNormal);222if (d_dot_n < -1.0e-6f)223{224// Reproject the contact position using the suspension cast sphere and the plane formed by the contact position and normal225// This solves x = inOrigin + fraction * inDirection and (x - ioContactPosition) . ioContactNormal = mRadius for fraction226float oc_dot_n = Vec3(ioContactPosition - inOrigin).Dot(ioContactNormal);227float fraction = (mRadius + oc_dot_n) / d_dot_n;228ioContactPosition = inOrigin + fraction * inDirection - mRadius * ioContactNormal;229230// Calculate the new suspension length in the same way as the cast sphere normally does231ioSuspensionLength = Clamp(fraction + mRadius - wheel_settings->mRadius, 0.0f, wheel_settings->mSuspensionMaxLength);232}233else234{235// If the normal is pointing away we assume there's no collision anymore236ioSuspensionLength = wheel_settings->mSuspensionMaxLength;237}238}239240bool VehicleCollisionTesterCastCylinder::Collide(PhysicsSystem &inPhysicsSystem, const VehicleConstraint &inVehicleConstraint, uint inWheelIndex, RVec3Arg inOrigin, Vec3Arg inDirection, const BodyID &inVehicleBodyID, Body *&outBody, SubShapeID &outSubShapeID, RVec3 &outContactPosition, Vec3 &outContactNormal, float &outSuspensionLength) const241{242const DefaultBroadPhaseLayerFilter default_broadphase_layer_filter = inPhysicsSystem.GetDefaultBroadPhaseLayerFilter(mObjectLayer);243const BroadPhaseLayerFilter &broadphase_layer_filter = mBroadPhaseLayerFilter != nullptr? *mBroadPhaseLayerFilter : default_broadphase_layer_filter;244245const DefaultObjectLayerFilter default_object_layer_filter = inPhysicsSystem.GetDefaultLayerFilter(mObjectLayer);246const ObjectLayerFilter &object_layer_filter = mObjectLayerFilter != nullptr? *mObjectLayerFilter : default_object_layer_filter;247248const IgnoreSingleBodyFilter default_body_filter(inVehicleBodyID);249const BodyFilter &body_filter = mBodyFilter != nullptr? *mBodyFilter : default_body_filter;250251const WheelSettings *wheel_settings = inVehicleConstraint.GetWheel(inWheelIndex)->GetSettings();252float max_suspension_length = wheel_settings->mSuspensionMaxLength;253254// Get the wheel transform given that the cylinder rotates around the Y axis255RMat44 shape_cast_start = inVehicleConstraint.GetWheelWorldTransform(inWheelIndex, Vec3::sAxisY(), Vec3::sAxisX());256shape_cast_start.SetTranslation(inOrigin);257258// Construct a cylinder with the dimensions of the wheel259float wheel_half_width = 0.5f * wheel_settings->mWidth;260CylinderShape cylinder(wheel_half_width, wheel_settings->mRadius, min(wheel_half_width, wheel_settings->mRadius) * mConvexRadiusFraction);261cylinder.SetEmbedded();262263RShapeCast shape_cast(&cylinder, Vec3::sOne(), shape_cast_start, inDirection * max_suspension_length);264265ShapeCastSettings settings;266settings.mUseShrunkenShapeAndConvexRadius = true;267settings.mReturnDeepestPoint = true;268269class MyCollector : public CastShapeCollector270{271public:272MyCollector(PhysicsSystem &inPhysicsSystem, const RShapeCast &inShapeCast) :273mPhysicsSystem(inPhysicsSystem),274mShapeCast(inShapeCast)275{276}277278virtual void AddHit(const ShapeCastResult &inResult) override279{280// Test if this collision is closer/deeper than the previous one281float early_out = inResult.GetEarlyOutFraction();282if (early_out < GetEarlyOutFraction())283{284// Lock the body285BodyLockRead lock(mPhysicsSystem.GetBodyLockInterfaceNoLock(), inResult.mBodyID2);286JPH_ASSERT(lock.Succeeded()); // When this runs all bodies are locked so this should not fail287const Body *body = &lock.GetBody();288289if (body->IsSensor())290return;291292// Update early out fraction to this hit293UpdateEarlyOutFraction(early_out);294295// Get the contact properties296mBody = body;297mSubShapeID2 = inResult.mSubShapeID2;298mContactPosition = mShapeCast.mCenterOfMassStart.GetTranslation() + inResult.mContactPointOn2;299mContactNormal = -inResult.mPenetrationAxis.Normalized();300mFraction = inResult.mFraction;301}302}303304// Configuration305PhysicsSystem & mPhysicsSystem;306const RShapeCast & mShapeCast;307308// Resulting closest collision309const Body * mBody = nullptr;310SubShapeID mSubShapeID2;311RVec3 mContactPosition;312Vec3 mContactNormal;313float mFraction;314};315316MyCollector collector(inPhysicsSystem, shape_cast);317inPhysicsSystem.GetNarrowPhaseQueryNoLock().CastShape(shape_cast, settings, shape_cast.mCenterOfMassStart.GetTranslation(), collector, broadphase_layer_filter, object_layer_filter, body_filter);318if (collector.mBody == nullptr)319return false;320321outBody = const_cast<Body *>(collector.mBody);322outSubShapeID = collector.mSubShapeID2;323outContactPosition = collector.mContactPosition;324outContactNormal = collector.mContactNormal;325outSuspensionLength = max_suspension_length * collector.mFraction;326327return true;328}329330void VehicleCollisionTesterCastCylinder::PredictContactProperties(PhysicsSystem &inPhysicsSystem, const VehicleConstraint &inVehicleConstraint, uint inWheelIndex, RVec3Arg inOrigin, Vec3Arg inDirection, const BodyID &inVehicleBodyID, Body *&ioBody, SubShapeID &ioSubShapeID, RVec3 &ioContactPosition, Vec3 &ioContactNormal, float &ioSuspensionLength) const331{332// Recalculate the contact points assuming the contact point is on an infinite plane333const WheelSettings *wheel_settings = inVehicleConstraint.GetWheel(inWheelIndex)->GetSettings();334float d_dot_n = inDirection.Dot(ioContactNormal);335if (d_dot_n < -1.0e-6f)336{337// Wheel size338float half_width = 0.5f * wheel_settings->mWidth;339float radius = wheel_settings->mRadius;340341// Get the inverse local space contact normal for a cylinder pointing along Y342RMat44 wheel_transform = inVehicleConstraint.GetWheelWorldTransform(inWheelIndex, Vec3::sAxisY(), Vec3::sAxisX());343Vec3 inverse_local_normal = -wheel_transform.Multiply3x3Transposed(ioContactNormal);344345// Get the support point of this normal in local space of the cylinder346// See CylinderShape::Cylinder::GetSupport347float x = inverse_local_normal.GetX(), y = inverse_local_normal.GetY(), z = inverse_local_normal.GetZ();348float o = sqrt(Square(x) + Square(z));349Vec3 support_point;350if (o > 0.0f)351support_point = Vec3((radius * x) / o, Sign(y) * half_width, (radius * z) / o);352else353support_point = Vec3(0, Sign(y) * half_width, 0);354355// Rotate back to world space356support_point = wheel_transform.Multiply3x3(support_point);357358// Now we can use inOrigin + support_point as the start of a ray of our suspension to the contact plane359// as know that it is the first point on the wheel that will hit the plane360RVec3 origin = inOrigin + support_point;361362// Calculate contact position and suspension length, the is the same as VehicleCollisionTesterRay363// but we don't need to take the radius into account anymore364Vec3 oc(ioContactPosition - origin);365ioContactPosition = origin + oc.Dot(ioContactNormal) / d_dot_n * inDirection;366ioSuspensionLength = Clamp(oc.Dot(inDirection), 0.0f, wheel_settings->mSuspensionMaxLength);367}368else369{370// If the normal is pointing away we assume there's no collision anymore371ioSuspensionLength = wheel_settings->mSuspensionMaxLength;372}373}374375JPH_NAMESPACE_END376377378