aboutsummaryrefslogtreecommitdiff
path: root/PhysX_3.4/Source/PhysXVehicle/src/PxVehicleUpdate.cpp
diff options
context:
space:
mode:
authorgit perforce import user <a@b>2016-10-25 12:29:14 -0600
committerSheikh Dawood Abdul Ajees <Sheikh Dawood Abdul Ajees>2016-10-25 18:56:37 -0500
commit3dfe2108cfab31ba3ee5527e217d0d8e99a51162 (patch)
treefa6485c169e50d7415a651bf838f5bcd0fd3bfbd /PhysX_3.4/Source/PhysXVehicle/src/PxVehicleUpdate.cpp
downloadphysx-3.4-3dfe2108cfab31ba3ee5527e217d0d8e99a51162.tar.xz
physx-3.4-3dfe2108cfab31ba3ee5527e217d0d8e99a51162.zip
Initial commit:
PhysX 3.4.0 Update @ 21294896 APEX 1.4.0 Update @ 21275617 [CL 21300167]
Diffstat (limited to 'PhysX_3.4/Source/PhysXVehicle/src/PxVehicleUpdate.cpp')
-rw-r--r--PhysX_3.4/Source/PhysXVehicle/src/PxVehicleUpdate.cpp7641
1 files changed, 7641 insertions, 0 deletions
diff --git a/PhysX_3.4/Source/PhysXVehicle/src/PxVehicleUpdate.cpp b/PhysX_3.4/Source/PhysXVehicle/src/PxVehicleUpdate.cpp
new file mode 100644
index 00000000..56758d8e
--- /dev/null
+++ b/PhysX_3.4/Source/PhysXVehicle/src/PxVehicleUpdate.cpp
@@ -0,0 +1,7641 @@
+// This code contains NVIDIA Confidential Information and is disclosed to you
+// under a form of NVIDIA software license agreement provided separately to you.
+//
+// Notice
+// NVIDIA Corporation and its licensors retain all intellectual property and
+// proprietary rights in and to this software and related documentation and
+// any modifications thereto. Any use, reproduction, disclosure, or
+// distribution of this software and related documentation without an express
+// license agreement from NVIDIA Corporation is strictly prohibited.
+//
+// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES
+// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO
+// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT,
+// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE.
+//
+// Information and code furnished is believed to be accurate and reliable.
+// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such
+// information or for any infringement of patents or other rights of third parties that may
+// result from its use. No license is granted by implication or otherwise under any patent
+// or patent rights of NVIDIA Corporation. Details are subject to change without notice.
+// This code supersedes and replaces all information previously supplied.
+// NVIDIA Corporation products are not authorized for use as critical
+// components in life support devices or systems without express written approval of
+// NVIDIA Corporation.
+//
+// Copyright (c) 2008-2016 NVIDIA Corporation. All rights reserved.
+// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
+// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
+
+#include "foundation/PxProfiler.h"
+#include "foundation/PxQuat.h"
+#include "PxVehicleUpdate.h"
+#include "PxVehicleSuspWheelTire4.h"
+#include "PxVehicleDrive4W.h"
+#include "PxVehicleDriveNW.h"
+#include "PxVehicleDriveTank.h"
+#include "PxVehicleNoDrive.h"
+#include "PxVehicleSuspLimitConstraintShader.h"
+#include "PxVehicleDefaults.h"
+#include "PxVehicleUtil.h"
+#include "PxVehicleUtilTelemetry.h"
+#include "PxVehicleLinearMath.h"
+#include "PxShape.h"
+#include "PxRigidDynamic.h"
+#include "PxBatchQuery.h"
+#include "PxMaterial.h"
+#include "PxTolerancesScale.h"
+#include "PxRigidBodyExt.h"
+#include "PsFoundation.h"
+#include "PsUtilities.h"
+#include "CmBitMap.h"
+#include "CmUtils.h"
+#include "PxContactModifyCallback.h"
+#include "PsFPU.h"
+
+using namespace physx;
+using namespace Cm;
+
+
+//TODO: lsd - handle case where wheels are spinning in different directions.
+//TODO: ackermann - use faster approximate functions for PxTan/PxATan because the results don't need to be too accurate here.
+//TODO: tire lat slip - do we really use PxAbs(vz) as denominator, that's not in the paper?
+//TODO: toe vs jounce table.
+//TODO: pneumatic trail.
+//TODO: we probably need to have a graphics jounce and a physics jounce and
+//TODO: expose sticky friction values in api.
+//TODO: blend the graphics jounce towards the physics jounce to avoid graphical pops at kerbs etc.
+//TODO: better graph of friction vs slip. Need to account for negative slip and positive slip differences.
+
+namespace physx
+{
+
+////////////////////////////////////////////////////////////////////////////
+//Implementation of public api function PxVehicleSetBasisVectors
+////////////////////////////////////////////////////////////////////////////
+
+PxVec3 gRightDefault(1.f,0,0);
+PxVec3 gUpDefault(0,1.f,0);
+PxVec3 gForwardDefault(0,0,1.f);
+PxVec3 gRight;
+PxVec3 gUp;
+PxVec3 gForward;
+
+void PxVehicleSetBasisVectors(const PxVec3& up, const PxVec3& forward)
+{
+ gRight=up.cross(forward);
+ gUp=up;
+ gForward=forward;
+}
+
+////////////////////////////////////////////////////////////////////////////
+//Implementation of public api function PxVehicleSetUpdateMode
+////////////////////////////////////////////////////////////////////////////
+
+const bool gApplyForcesDefault = false;
+bool gApplyForces;
+
+void PxVehicleSetUpdateMode(PxVehicleUpdateMode::Enum vehicleUpdateMode)
+{
+ switch(vehicleUpdateMode)
+ {
+ case PxVehicleUpdateMode::eVELOCITY_CHANGE:
+ gApplyForces=false;
+ break;
+ case PxVehicleUpdateMode::eACCELERATION:
+ gApplyForces=true;
+ break;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////
+//Implementation of public api function PxVehicleSetSweepHitRejectionAngles
+////////////////////////////////////////////////////////////////////////////
+
+const PxF32 gPointRejectAngleThresholdDefault = 0.707f; //PxCos(PxPi*0.25f);
+const PxF32 gNormalRejectAngleThresholdDefault = 0.707f; //PxCos(PxPi*0.25f);
+PxF32 gPointRejectAngleThreshold;
+PxF32 gNormalRejectAngleThreshold;
+
+void PxVehicleSetSweepHitRejectionAngles(const PxF32 pointRejectAngle, const PxF32 normalRejectAngle)
+{
+ PX_CHECK_AND_RETURN(pointRejectAngle > 0.0f && pointRejectAngle < PxPi, "PxVehicleSetSweepHitRejectionAngles - pointRejectAngle must be in range (0, Pi)");
+ PX_CHECK_AND_RETURN(normalRejectAngle > 0.0f && normalRejectAngle < PxPi, "PxVehicleSetSweepHitRejectionAngles - normalRejectAngle must be in range (0, Pi)");
+ gPointRejectAngleThreshold = PxCos(pointRejectAngle);
+ gNormalRejectAngleThreshold = PxCos(normalRejectAngle);
+}
+
+////////////////////////////////////////////////////////////////////////////
+//Implementation of public api function PxVehicleSetSweepHitRejectionAngles
+////////////////////////////////////////////////////////////////////////////
+
+const PxF32 gMaxHitActorAccelerationDefault = PX_MAX_REAL;
+PxF32 gMaxHitActorAcceleration;
+
+void PxVehicleSetMaxHitActorAcceleration(const PxF32 maxHitActorAcceleration)
+{
+ PX_CHECK_AND_RETURN(maxHitActorAcceleration >= 0.0f, "PxVehicleSetMaxHitActorAcceleration - maxHitActorAcceleration must be greater than or equal to zero");
+ gMaxHitActorAcceleration = maxHitActorAcceleration;
+}
+
+////////////////////////////////////////////////////////////////////////////
+//Set all defaults from PxVehicleInitSDK
+////////////////////////////////////////////////////////////////////////////
+
+void setVehicleDefaults()
+{
+ gRight = gRightDefault;
+ gUp = gUpDefault;
+ gForward = gForwardDefault;
+
+ gApplyForces = gApplyForcesDefault;
+
+ gPointRejectAngleThreshold = gPointRejectAngleThresholdDefault;
+ gNormalRejectAngleThreshold = gNormalRejectAngleThresholdDefault;
+
+ gMaxHitActorAcceleration = gMaxHitActorAccelerationDefault;
+}
+
+////////////////////////////////////////////////////////////////////////////
+//Called from PxVehicleInitSDK/PxCloseVehicleSDK
+////////////////////////////////////////////////////////////////////////////
+
+//***********************
+
+PxF32 gThresholdForwardSpeedForWheelAngleIntegration=0;
+PxF32 gRecipThresholdForwardSpeedForWheelAngleIntegration=0;
+PxF32 gMinLatSpeedForTireModel=0;
+PxF32 gStickyTireFrictionThresholdSpeed=0;
+PxF32 gToleranceScaleLength=0;
+PxF32 gMinimumSlipThreshold=0;
+
+void setVehicleToleranceScale(const PxTolerancesScale& ts)
+{
+ gThresholdForwardSpeedForWheelAngleIntegration=5.0f*ts.length;
+ gRecipThresholdForwardSpeedForWheelAngleIntegration=1.0f/gThresholdForwardSpeedForWheelAngleIntegration;
+
+ gMinLatSpeedForTireModel=1.0f*ts.length;
+
+ gStickyTireFrictionThresholdSpeed=0.2f*ts.length;
+
+ gToleranceScaleLength=ts.length;
+
+ gMinimumSlipThreshold = 1e-5f;
+}
+
+void resetVehicleToleranceScale()
+{
+ gThresholdForwardSpeedForWheelAngleIntegration=0;
+ gRecipThresholdForwardSpeedForWheelAngleIntegration=0;
+
+ gMinLatSpeedForTireModel=0;
+
+ gStickyTireFrictionThresholdSpeed=0;
+
+ gToleranceScaleLength=0;
+
+ gMinimumSlipThreshold=0;
+}
+
+//***********************
+
+const PxSerializationRegistry* gSerializationRegistry=NULL;
+
+void setSerializationRegistryPtr(const PxSerializationRegistry* sr)
+{
+ gSerializationRegistry = sr;
+}
+
+const PxSerializationRegistry* resetSerializationRegistryPtr()
+{
+ const PxSerializationRegistry* tmp = gSerializationRegistry;
+ gSerializationRegistry = NULL;
+ return tmp;
+}
+
+////////////////////////////////////////////////////////////////////////////
+//Global values used to trigger and control sticky tire friction constraints.
+////////////////////////////////////////////////////////////////////////////
+
+const PxF32 gStickyTireFrictionForwardDamping=0.01f;
+const PxF32 gStickyTireFrictionSideDamping=0.1f;
+const PxF32 gLowForwardSpeedThresholdTime=1.0f;
+const PxF32 gLowSideSpeedThresholdTime=1.0f;
+
+////////////////////////////////////////////////////////////////////////////
+//Global values used to control max iteration count if estimate mode is chosen
+////////////////////////////////////////////////////////////////////////////
+
+const PxF32 gSolverTolerance = 1e-10f;
+
+////////////////////////////////////////////////////////////////////////////
+//Compute the sprung masses that satisfy the centre of mass and sprung mass coords.
+////////////////////////////////////////////////////////////////////////////
+
+#define DETERMINANT_THRESHOLD (1e-6f)
+
+void computeSprungMasses(const PxU32 numSprungMasses, const PxVec3* sprungMassCoordinates, const PxVec3& centreOfMass, const PxReal totalMass, const PxU32 gravityDirection, PxReal* sprungMasses)
+{
+#if PX_CHECKED
+ PX_CHECK_AND_RETURN(numSprungMasses > 0, "PxVehicleComputeSprungMasses: numSprungMasses must be greater than zero");
+ PX_CHECK_AND_RETURN(numSprungMasses <= PX_MAX_NB_WHEELS, "PxVehicleComputeSprungMasses: numSprungMasses must be less than or equal to 20");
+ for(PxU32 i=0;i<numSprungMasses;i++)
+ {
+ PX_CHECK_AND_RETURN(sprungMassCoordinates[i].isFinite(), "PxVehicleComputeSprungMasses: sprungMassCoordinates must all be valid coordinates");
+ }
+ PX_CHECK_AND_RETURN(totalMass > 0.0f, "PxVehicleComputeSprungMasses: totalMass must be greater than zero");
+ PX_CHECK_AND_RETURN(gravityDirection<=2, "PxVehicleComputeSprungMasses: gravityDirection must be 0 or 1 or 2");
+ PX_CHECK_AND_RETURN(sprungMasses, "PxVehicleComputeSprungMasses: sprungMasses must be a non-null pointer");
+#endif
+
+ if(1==numSprungMasses)
+ {
+ sprungMasses[0]=totalMass;
+ }
+ else if(2==numSprungMasses)
+ {
+ PxVec3 v=sprungMassCoordinates[0];
+ v[gravityDirection]=0;
+ PxVec3 w=sprungMassCoordinates[1]-sprungMassCoordinates[0];
+ w[gravityDirection]=0;
+ w.normalize();
+
+ PxVec3 cm=centreOfMass;
+ cm[gravityDirection]=0;
+ PxF32 t=w.dot(cm-v);
+ PxVec3 p=v+w*t;
+
+ PxVec3 x0=sprungMassCoordinates[0];
+ x0[gravityDirection]=0;
+ PxVec3 x1=sprungMassCoordinates[1];
+ x1[gravityDirection]=0;
+ const PxF32 r0=(x0-p).dot(w);
+ const PxF32 r1=(x1-p).dot(w);
+
+ PX_CHECK_AND_RETURN(PxAbs(r0-r1) > DETERMINANT_THRESHOLD, "PxVehicleComputeSprungMasses: Unable to determine sprung masses. Please check the values in sprungMassCoordinates.");
+
+ const PxF32 m0=totalMass*r1/(r1-r0);
+ const PxF32 m1=totalMass-m0;
+
+ sprungMasses[0]=m0;
+ sprungMasses[1]=m1;
+ }
+ else if(3==numSprungMasses)
+ {
+ const PxU32 d0=(gravityDirection+1)%3;
+ const PxU32 d1=(gravityDirection+2)%3;
+
+ MatrixNN A(3);
+ VectorN b(3);
+ A.set(0,0,sprungMassCoordinates[0][d0]);
+ A.set(0,1,sprungMassCoordinates[1][d0]);
+ A.set(0,2,sprungMassCoordinates[2][d0]);
+ A.set(1,0,sprungMassCoordinates[0][d1]);
+ A.set(1,1,sprungMassCoordinates[1][d1]);
+ A.set(1,2,sprungMassCoordinates[2][d1]);
+ A.set(2,0,1.f);
+ A.set(2,1,1.f);
+ A.set(2,2,1.f);
+ b[0]=totalMass*centreOfMass[d0];
+ b[1]=totalMass*centreOfMass[d1];
+ b[2]=totalMass;
+
+ VectorN result(3);
+ MatrixNNLUSolver solver;
+ solver.decomposeLU(A);
+ PX_CHECK_AND_RETURN(PxAbs(solver.getDet()) > DETERMINANT_THRESHOLD, "PxVehicleComputeSprungMasses: Unable to determine sprung masses. Please check the values in sprungMassCoordinates.");
+ solver.solve(b,result);
+
+ sprungMasses[0]=result[0];
+ sprungMasses[1]=result[1];
+ sprungMasses[2]=result[2];
+ }
+ else if(numSprungMasses>=4)
+ {
+ const PxU32 d0=(gravityDirection+1)%3;
+ const PxU32 d1=(gravityDirection+2)%3;
+
+ const PxF32 mbar = totalMass/(numSprungMasses*1.0f);
+
+ //See http://en.wikipedia.org/wiki/Lagrange_multiplier
+ //particularly the section on multiple constraints.
+
+ //3 Constraint equations.
+ //g0 = sum_ xi*mi=xcm
+ //g1 = sum_ zi*mi=zcm
+ //g2 = sum_ mi = totalMass
+ //Minimisation function to achieve solution with minimum mass variance.
+ //f = sum_ (mi - mave)^2
+ //Lagrange terms (N equations, N+3 unknowns)
+ //2*mi - xi*lambda0 - zi*lambda1 - 1*lambda2 = 2*mave
+
+ MatrixNN A(numSprungMasses+3);
+ VectorN b(numSprungMasses+3);
+
+ //g0, g1, g2
+ for(PxU32 i=0;i<numSprungMasses;i++)
+ {
+ A.set(0,i,sprungMassCoordinates[i][d0]); //g0
+ A.set(1,i,sprungMassCoordinates[i][d1]); //g1
+ A.set(2,i,1.0f); //g2
+ }
+ for(PxU32 i=numSprungMasses;i<numSprungMasses+3;i++)
+ {
+ A.set(0,i,0); //g0 independent of lambda0,lambda1,lambda2
+ A.set(1,i,0); //g1 independent of lambda0,lambda1,lambda2
+ A.set(2,i,0); //g2 independent of lambda0,lambda1,lambda2
+ }
+ b[0] = totalMass*(centreOfMass[d0]); //g0
+ b[1] = totalMass*(centreOfMass[d1]); //g1
+ b[2] = totalMass; //g2
+
+ //Lagrange terms.
+ for(PxU32 i=0;i<numSprungMasses;i++)
+ {
+ //Off-diagonal terms from the derivative of f
+ for(PxU32 j=0;j<numSprungMasses;j++)
+ {
+ A.set(i+3,j,0);
+ }
+ //Diagonal term from the derivative of f
+ A.set(i+3,i,2.f);
+
+ //Derivative of g
+ A.set(i+3,numSprungMasses+0,sprungMassCoordinates[i][d0]);
+ A.set(i+3,numSprungMasses+1,sprungMassCoordinates[i][d1]);
+ A.set(i+3,numSprungMasses+2,1.0f);
+
+ //rhs.
+ b[i+3] = 2*mbar;
+ }
+
+ //Solve Ax=b
+ VectorN result(numSprungMasses+3);
+ MatrixNNLUSolver solver;
+ solver.decomposeLU(A);
+ solver.solve(b,result);
+ PX_CHECK_AND_RETURN(PxAbs(solver.getDet()) > DETERMINANT_THRESHOLD, "PxVehicleComputeSprungMasses: Unable to determine sprung masses. Please check the values in sprungMassCoordinates.");
+
+ for(PxU32 i=0;i<numSprungMasses;i++)
+ {
+ sprungMasses[i]=result[i];
+ }
+ }
+
+#if PX_CHECKED
+ PxVec3 cm(0,0,0);
+ PxF32 m = 0;
+ for(PxU32 i=0;i<numSprungMasses;i++)
+ {
+ PX_CHECK_AND_RETURN(sprungMasses[i] >= 0, "PxVehicleComputeSprungMasses: Unable to determine sprung masses. Please check the values in sprungMassCoordinates.");
+ cm += sprungMassCoordinates[i]*sprungMasses[i];
+ m += sprungMasses[i];
+ }
+ cm *= (1.0f/totalMass);
+ PX_CHECK_AND_RETURN((PxAbs(totalMass - m)/totalMass) < 1e-3f, "PxVehicleComputeSprungMasses: Unable to determine sprung masses. Please check the values in sprungMassCoordinates.");
+ PxVec3 diff = cm - centreOfMass;
+ diff[gravityDirection]=0;
+ const PxF32 diffMag = diff.magnitude();
+ PX_CHECK_AND_RETURN(numSprungMasses <=2 || diffMag < 1e-3f, "PxVehicleComputeSprungMasses: Unable to determine sprung masses. Please check the values in sprungMassCoordinates.");
+#endif
+}
+
+////////////////////////////////////////////////////////////////////////////
+//Work out if all wheels are in the air.
+////////////////////////////////////////////////////////////////////////////
+
+bool PxVehicleIsInAir(const PxVehicleWheelQueryResult& vehWheelQueryResults)
+{
+ if(!vehWheelQueryResults.wheelQueryResults)
+ {
+ return true;
+ }
+
+ for(PxU32 i=0;i<vehWheelQueryResults.nbWheelQueryResults;i++)
+ {
+ if(!vehWheelQueryResults.wheelQueryResults[i].isInAir)
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////
+//Reject wheel contact points
+////////////////////////////////////////////////////////////////////////////
+
+PxU32 PxVehicleModifyWheelContacts
+(const PxVehicleWheels& vehicle, const PxU32 wheelId,
+ const PxF32 wheelTangentVelocityMultiplier, const PxReal maxImpulse,
+ PxContactModifyPair& contactModifyPair)
+{
+ const bool rejectPoints = true;
+ const bool rejectNormals = true;
+
+ PxU32 numIgnoredContacts = 0;
+
+ const PxRigidDynamic* vehActor = vehicle.getRigidDynamicActor();
+
+ //Is the vehicle represented by actor[0] or actor[1]?
+ PxTransform vehActorTransform;
+ PxTransform vehWheelTransform;
+ PxF32 normalMultiplier;
+ PxF32 targetVelMultiplier;
+ bool isOtherDynamic = false;
+ if(contactModifyPair.actor[0] == vehActor)
+ {
+ normalMultiplier = 1.0f;
+ targetVelMultiplier = 1.0f;
+ vehActorTransform = contactModifyPair.actor[0]->getGlobalPose();
+ vehWheelTransform = contactModifyPair.transform[0];
+ isOtherDynamic = contactModifyPair.actor[1] && contactModifyPair.actor[1]->is<PxRigidDynamic>();
+ }
+ else
+ {
+ PX_ASSERT(contactModifyPair.actor[1] == vehActor);
+ normalMultiplier = -1.0f;
+ targetVelMultiplier = -1.0f;
+ vehActorTransform = contactModifyPair.actor[1]->getGlobalPose();
+ vehWheelTransform = contactModifyPair.transform[1];
+ isOtherDynamic = contactModifyPair.actor[0] && contactModifyPair.actor[0]->is<PxRigidDynamic>();
+ }
+
+ //Compute the "right" vector of the transform.
+ const PxVec3 right = vehWheelTransform.q.rotate(gRight);
+
+ //The wheel transform includes rotation about the rolling axis.
+ //We want to compute the wheel transform at zero wheel rotation angle.
+ PxTransform correctedWheelShapeTransform;
+ {
+ const PxF32 wheelRotationAngle = vehicle.mWheelsDynData.getWheelRotationAngle(wheelId);
+ const PxQuat wheelRotateQuat(-wheelRotationAngle, right);
+ correctedWheelShapeTransform = PxTransform(vehWheelTransform.p, wheelRotateQuat*vehWheelTransform.q);
+ }
+
+ //Construct a plane for the wheel
+ //n.p + d = 0
+ PxPlane wheelPlane;
+ wheelPlane.n = right;
+ wheelPlane.d = -(right.dot(correctedWheelShapeTransform.p));
+
+ //Compute the suspension travel vector.
+ const PxVec3 suspTravelDir = correctedWheelShapeTransform.rotate(vehicle.mWheelsSimData.getSuspTravelDirection(wheelId));
+
+ //Get the wheel centre.
+ const PxVec3 wheelCentre = correctedWheelShapeTransform.p;
+
+ //Test each point.
+ PxContactSet& contactSet = contactModifyPair.contacts;
+ const PxU32 numContacts = contactSet.size();
+ for(PxU32 i = 0; i < numContacts; i++)
+ {
+ //Get the next contact point to analyse.
+ const PxVec3& contactPoint = contactSet.getPoint(i);
+ bool ignorePoint = false;
+
+ //Project the contact point on to the wheel plane.
+ const PxF32 distanceToPlane = wheelPlane.n.dot(contactPoint) + wheelPlane.d;
+ const PxVec3 contactPointOnPlane = contactPoint - wheelPlane.n*distanceToPlane;
+
+ //Construct a vector from the wheel centre to the contact point on the plane.
+ PxVec3 dir = contactPointOnPlane - wheelCentre;
+ const PxF32 effectiveRadius = dir.normalize();
+
+ if(!ignorePoint && rejectPoints)
+ {
+ //Work out the dot product of the suspension direction and the direction from wheel centre to contact point.
+ const PxF32 dotProduct = dir.dot(suspTravelDir);
+ if (dotProduct > gPointRejectAngleThreshold)
+ {
+ ignorePoint = true;
+ numIgnoredContacts++;
+ contactSet.ignore(i);
+ }
+ }
+
+ //Ignore contact normals that are near the raycast direction.
+ if(!ignorePoint && rejectNormals)
+ {
+ const PxVec3& contactNormal = contactSet.getNormal(i) * normalMultiplier;
+ const PxF32 dotProduct = -(contactNormal.dot(suspTravelDir));
+ if(dotProduct > gNormalRejectAngleThreshold)
+ {
+ ignorePoint = true;
+ numIgnoredContacts++;
+ contactSet.ignore(i);
+ }
+ }
+
+ //For points that remain we want to modify the contact speed to account for the spinning wheel.
+ //We also want the applied impulse to go through the suspension geometry so we set the contact point to be the suspension force
+ //application point.
+ if(!ignorePoint)
+ {
+ //Compute the tangent velocity.
+ //Retain only the component that lies perpendicular to the contact normal.
+ const PxF32 wheelRotationSpeed = vehicle.mWheelsDynData.getWheelRotationSpeed(wheelId);
+ const PxVec3 tangentVelocityDir = right.cross(dir);
+ PxVec3 tangentVelocity = tangentVelocityDir*(effectiveRadius*wheelRotationSpeed);
+ tangentVelocity -= contactSet.getNormal(i)*(tangentVelocity.dot(contactSet.getNormal(i)));
+
+ //We want to add velocity in the opposite direction to the tangent velocity.
+ const PxVec3 targetTangentVelocity = -wheelTangentVelocityMultiplier*tangentVelocity;
+
+ //Relative velocity is computed from actor0 - actor1
+ //If vehicle is actor 0 we want to add to the target velocity: targetVelocity = [(vel0 + targetTangentVelocity) - vel1] = vel0 - vel1 + targetTangentVelocity
+ //If vehicle is actor 1 we want to subtract from the target velocity: targetVelocity = [vel0 - (vel1 + targetTangentVelocity)] = vel0 - vel1 - targetTangentVelocity
+ const PxVec3 targetVelocity = targetTangentVelocity*targetVelMultiplier;
+
+ //Add the target velocity.
+ contactSet.setTargetVelocity(i, targetVelocity);
+
+ //Set the max impulse that can be applied.
+ //Only apply this if the wheel has hit a dynamic.
+ if (isOtherDynamic)
+ {
+ contactSet.setMaxImpulse(i, maxImpulse);
+ }
+
+ //Set the contact point to be the suspension force application point because all forces applied through the wheel go through the suspension geometry.
+ const PxVec3 suspAppPoint = vehActorTransform.transform(vehActor->getCMassLocalPose().p + vehicle.mWheelsSimData.getSuspForceAppPointOffset(wheelId));
+ contactSet.setPoint(i, suspAppPoint);
+ }
+ }
+
+ return numIgnoredContacts;
+}
+
+////////////////////////////////////////////////////////////////////////////
+//Enable a 4W vehicle in either tadpole or delta configuration.
+////////////////////////////////////////////////////////////////////////////
+
+void computeDirection(PxU32& rightDirection, PxU32& upDirection)
+{
+ //Work out the up and right vectors.
+ rightDirection = 0xffffffff;
+ if(gRight == PxVec3(1.f,0,0) || gRight == PxVec3(-1.f,0,0))
+ {
+ rightDirection = 0;
+ }
+ else if(gRight == PxVec3(0,1.f,0) || gRight == PxVec3(0,-1.f,0))
+ {
+ rightDirection = 1;
+ }
+ else if(gRight == PxVec3(0,0,1.f) || gRight == PxVec3(0,0,-1.f))
+ {
+ rightDirection = 2;
+ }
+ upDirection = 0xffffffff;
+ if(gUp== PxVec3(1.f,0,0) || gUp == PxVec3(-1.f,0,0))
+ {
+ upDirection = 0;
+ }
+ else if(gUp == PxVec3(0,1.f,0) || gUp == PxVec3(0,-1.f,0))
+ {
+ upDirection = 1;
+ }
+ else if(gUp == PxVec3(0,0,1.f) || gUp == PxVec3(0,0,-1.f))
+ {
+ upDirection = 2;
+ }
+}
+
+void enable3WMode(const PxU32 rightDirection, const PxU32 upDirection, const bool removeFrontWheel, PxVehicleWheelsSimData& wheelsSimData, PxVehicleWheelsDynData& wheelsDynData, PxVehicleDriveSimData4W& driveSimData)
+{
+ PX_ASSERT(rightDirection < 3);
+ PX_ASSERT(upDirection < 3);
+
+ const PxU32 wheelToRemove = removeFrontWheel ? PxVehicleDrive4WWheelOrder::eFRONT_LEFT : PxVehicleDrive4WWheelOrder::eREAR_LEFT;
+ const PxU32 wheelToModify = removeFrontWheel ? PxVehicleDrive4WWheelOrder::eFRONT_RIGHT : PxVehicleDrive4WWheelOrder::eREAR_RIGHT;
+
+ //Disable the wheel.
+ wheelsSimData.disableWheel(wheelToRemove);
+
+ //Make sure the wheel's corresponding PxShape does not get posed again.
+ wheelsSimData.setWheelShapeMapping(wheelToRemove, -1);
+
+ //Set the angular speed to 0.0f
+ wheelsDynData.setWheelRotationSpeed(wheelToRemove, 0.0f);
+
+ //Disable Ackermann steering.
+ //If the front wheel is to be removed and the front wheels can steer then disable Ackermann correction.
+ //If the rear wheel is to be removed and the rear wheels can steer then disable Ackermann correction.
+ if(removeFrontWheel &&
+ (wheelsSimData.getWheelData(PxVehicleDrive4WWheelOrder::eFRONT_RIGHT).mMaxSteer!=0.0f ||
+ wheelsSimData.getWheelData(PxVehicleDrive4WWheelOrder::eFRONT_LEFT).mMaxSteer!=0.0f))
+ {
+ PxVehicleAckermannGeometryData ackermannData=driveSimData.getAckermannGeometryData();
+ ackermannData.mAccuracy=0.0f;
+ driveSimData.setAckermannGeometryData(ackermannData);
+ }
+ if(!removeFrontWheel &&
+ (wheelsSimData.getWheelData(PxVehicleDrive4WWheelOrder::eREAR_RIGHT).mMaxSteer!=0.0f ||
+ wheelsSimData.getWheelData(PxVehicleDrive4WWheelOrder::eREAR_LEFT).mMaxSteer!=0.0f))
+ {
+ PxVehicleAckermannGeometryData ackermannData=driveSimData.getAckermannGeometryData();
+ ackermannData.mAccuracy=0.0f;
+ driveSimData.setAckermannGeometryData(ackermannData);
+ }
+
+ //We need to set up the differential to make sure that no drive torque is delivered to the disabled wheel.
+ PxVehicleDifferential4WData diffData =driveSimData.getDiffData();
+ if(PxVehicleDrive4WWheelOrder::eFRONT_RIGHT==wheelToModify)
+ {
+ diffData.mFrontBias=PX_MAX_F32;
+ diffData.mFrontLeftRightSplit=0.0f;
+ }
+ else
+ {
+ diffData.mRearBias=PX_MAX_F32;
+ diffData.mRearLeftRightSplit=0.0f;
+ }
+ driveSimData.setDiffData(diffData);
+
+ //Now reposition the disabled wheel so that it lies at the center of its axle.
+ //The following assumes that the front and rear axles lie along the x-axis.
+ {
+ PxVec3 wheelCentreOffset=wheelsSimData.getWheelCentreOffset(wheelToModify);
+ wheelCentreOffset[rightDirection]=0.0f;
+ wheelsSimData.setWheelCentreOffset(wheelToModify,wheelCentreOffset);
+
+ PxVec3 suspOffset=wheelsSimData.getSuspForceAppPointOffset(wheelToModify);
+ suspOffset[rightDirection]=0;
+ wheelsSimData.setSuspForceAppPointOffset(wheelToModify,suspOffset);
+
+ PxVec3 tireOffset=wheelsSimData.getTireForceAppPointOffset(wheelToModify);
+ tireOffset[rightDirection]=0;
+ wheelsSimData.setTireForceAppPointOffset(wheelToModify,tireOffset);
+ }
+
+ //Redistribute the mass supported by 4 wheels among the 3 remaining enabled wheels.
+ //Compute the total mass supported by all 4 wheels.
+ const PxF32 totalMass =
+ wheelsSimData.getSuspensionData(PxVehicleDrive4WWheelOrder::eFRONT_LEFT).mSprungMass +
+ wheelsSimData.getSuspensionData(PxVehicleDrive4WWheelOrder::eFRONT_RIGHT).mSprungMass +
+ wheelsSimData.getSuspensionData(PxVehicleDrive4WWheelOrder::eREAR_LEFT).mSprungMass +
+ wheelsSimData.getSuspensionData(PxVehicleDrive4WWheelOrder::eREAR_RIGHT).mSprungMass;
+ //Get the wheel cm offsets of the 3 enabled wheels.
+ PxVec3 cmOffsets[3]=
+ {
+ wheelsSimData.getWheelCentreOffset((wheelToRemove+1) % 4),
+ wheelsSimData.getWheelCentreOffset((wheelToRemove+2) % 4),
+ wheelsSimData.getWheelCentreOffset((wheelToRemove+3) % 4),
+ };
+ //Re-compute the sprung masses.
+ PxF32 sprungMasses[3];
+ computeSprungMasses(3, cmOffsets, PxVec3(0,0,0), totalMass, upDirection, sprungMasses);
+
+ //Now set the new sprung masses.
+ //Do this in a way that preserves the natural frequency and damping ratio of the spring.
+ for(PxU32 i=0;i<3;i++)
+ {
+ PxVehicleSuspensionData suspData = wheelsSimData.getSuspensionData((wheelToRemove+1+i) % 4);
+
+ const PxF32 oldSprungMass = suspData.mSprungMass;
+ const PxF32 oldStrength = suspData.mSpringStrength;
+ const PxF32 oldDampingRate = suspData.mSpringDamperRate;
+ const PxF32 oldNaturalFrequency = PxSqrt(oldStrength/oldSprungMass);
+
+ const PxF32 newSprungMass = sprungMasses[i];
+ const PxF32 newStrength = oldNaturalFrequency*oldNaturalFrequency*newSprungMass;
+ const PxF32 newDampingRate = oldDampingRate;
+
+ suspData.mSprungMass = newSprungMass;
+ suspData.mSpringStrength = newStrength;
+ suspData.mSpringDamperRate = newDampingRate;
+ wheelsSimData.setSuspensionData((wheelToRemove+1+i) % 4, suspData);
+ }
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+//Maximum number of allowed blocks of 4 wheels
+////////////////////////////////////////////////////////////////////////////
+
+#define PX_MAX_NB_SUSPWHEELTIRE4 (PX_MAX_NB_WHEELS >>2)
+
+
+////////////////////////////////////////////////////////////////////////////
+//Pointers to telemetry data.
+//Set to NULL if no telemetry data is to be recorded during a vehicle update.
+//Functions used throughout vehicle update to record specific vehicle data.
+////////////////////////////////////////////////////////////////////////////
+
+#if PX_DEBUG_VEHICLE_ON
+
+//Render data.
+PxVec3* gCarTireForceAppPoints=NULL;
+PxVec3* gCarSuspForceAppPoints=NULL;
+
+//Graph data
+PxF32* gCarWheelGraphData[PX_MAX_NB_WHEELS]={NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL};
+PxF32* gCarEngineGraphData=NULL;
+
+PX_FORCE_INLINE void updateGraphDataInternalWheelDynamics(const PxU32 startIndex, const PxF32* carWheelSpeeds)
+{
+ //Grab the internal rotation speeds for graphing before we update them.
+ if(gCarWheelGraphData[startIndex])
+ {
+ for(PxU32 i=0;i<4;i++)
+ {
+ PX_ASSERT((startIndex+i) < PX_MAX_NB_WHEELS);
+ PX_ASSERT(gCarWheelGraphData[startIndex+i]);
+ gCarWheelGraphData[startIndex+i][PxVehicleWheelGraphChannel::eWHEEL_OMEGA]=carWheelSpeeds[i];
+ }
+ }
+}
+
+PX_FORCE_INLINE void updateGraphDataInternalEngineDynamics(const PxF32 carEngineSpeed)
+{
+ if(gCarEngineGraphData)
+ gCarEngineGraphData[PxVehicleDriveGraphChannel::eENGINE_REVS]=carEngineSpeed;
+}
+
+PX_FORCE_INLINE void updateGraphDataControlInputs(const PxF32 accel, const PxF32 brake, const PxF32 handbrake, const PxF32 steerLeft, const PxF32 steerRight)
+{
+ if(gCarEngineGraphData)
+ {
+ gCarEngineGraphData[PxVehicleDriveGraphChannel::eACCEL_CONTROL]=accel;
+ gCarEngineGraphData[PxVehicleDriveGraphChannel::eBRAKE_CONTROL]=brake;
+ gCarEngineGraphData[PxVehicleDriveGraphChannel::eHANDBRAKE_CONTROL]=handbrake;
+ gCarEngineGraphData[PxVehicleDriveGraphChannel::eSTEER_LEFT_CONTROL]=steerLeft;
+ gCarEngineGraphData[PxVehicleDriveGraphChannel::eSTEER_RIGHT_CONTROL]=steerRight;
+ }
+}
+PX_FORCE_INLINE void updateGraphDataGearRatio(const PxF32 G)
+{
+ if(gCarEngineGraphData)
+ gCarEngineGraphData[PxVehicleDriveGraphChannel::eGEAR_RATIO]=G;
+}
+PX_FORCE_INLINE void updateGraphDataEngineDriveTorque(const PxF32 engineDriveTorque)
+{
+ if(gCarEngineGraphData)
+ gCarEngineGraphData[PxVehicleDriveGraphChannel::eENGINE_DRIVE_TORQUE]=engineDriveTorque;
+}
+PX_FORCE_INLINE void updateGraphDataClutchSlip(const PxF32* wheelSpeeds, const PxF32* aveWheelSpeedContributions, const PxF32 engineSpeed, const PxF32 G)
+{
+ if(gCarEngineGraphData)
+ {
+ PxF32 averageWheelSpeed=0;
+ for(PxU32 i=0;i<4;i++)
+ {
+ averageWheelSpeed+=wheelSpeeds[i]*aveWheelSpeedContributions[i];
+ }
+ averageWheelSpeed*=G;
+ gCarEngineGraphData[PxVehicleDriveGraphChannel::eCLUTCH_SLIP]=averageWheelSpeed-engineSpeed;
+ }
+}
+PX_FORCE_INLINE void updateGraphDataClutchSlipNW(const PxU32 numWheels4, const PxVehicleWheels4DynData* wheelsDynData, const PxF32* aveWheelSpeedContributions, const PxF32 engineSpeed, const PxF32 G)
+{
+ if(gCarEngineGraphData)
+ {
+ PxF32 averageWheelSpeed=0;
+ for(PxU32 i=0;i<numWheels4;i++)
+ {
+ averageWheelSpeed+=wheelsDynData[i].mWheelSpeeds[0]*aveWheelSpeedContributions[4*i+0];
+ averageWheelSpeed+=wheelsDynData[i].mWheelSpeeds[1]*aveWheelSpeedContributions[4*i+1];
+ averageWheelSpeed+=wheelsDynData[i].mWheelSpeeds[2]*aveWheelSpeedContributions[4*i+2];
+ averageWheelSpeed+=wheelsDynData[i].mWheelSpeeds[3]*aveWheelSpeedContributions[4*i+3];
+ }
+ averageWheelSpeed*=G;
+ gCarEngineGraphData[PxVehicleDriveGraphChannel::eCLUTCH_SLIP]=averageWheelSpeed-engineSpeed;
+ }
+}
+
+PX_FORCE_INLINE void zeroGraphDataWheels(const PxU32 startIndex, const PxU32 type)
+{
+ if(gCarWheelGraphData[0])
+ {
+ for(PxU32 i=0;i<4;i++)
+ {
+ PX_ASSERT((startIndex+i) < PX_MAX_NB_WHEELS);
+ PX_ASSERT(gCarWheelGraphData[startIndex+i]);
+ gCarWheelGraphData[startIndex+i][type]=0.0f;
+ }
+ }
+}
+PX_FORCE_INLINE void updateGraphDataSuspJounce(const PxU32 startIndex, const PxU32 wheel, const PxF32 jounce)
+{
+ if(gCarWheelGraphData[0])
+ {
+ PX_ASSERT((startIndex+wheel) < PX_MAX_NB_WHEELS);
+ PX_ASSERT(gCarWheelGraphData[startIndex+wheel]);
+ gCarWheelGraphData[startIndex+wheel][PxVehicleWheelGraphChannel::eJOUNCE]=jounce;
+ }
+}
+PX_FORCE_INLINE void updateGraphDataSuspForce(const PxU32 startIndex, const PxU32 wheel, const PxF32 springForce)
+{
+ if(gCarWheelGraphData[0])
+ {
+ PX_ASSERT((startIndex+wheel) < PX_MAX_NB_WHEELS);
+ PX_ASSERT(gCarWheelGraphData[startIndex+wheel]);
+ gCarWheelGraphData[startIndex+wheel][PxVehicleWheelGraphChannel::eSUSPFORCE]=springForce;
+ }
+}
+PX_FORCE_INLINE void updateGraphDataTireLoad(const PxU32 startIndex, const PxU32 wheel, const PxF32 filteredTireLoad)
+{
+ if(gCarWheelGraphData[0])
+ {
+ PX_ASSERT((startIndex+wheel) < PX_MAX_NB_WHEELS);
+ PX_ASSERT(gCarWheelGraphData[startIndex+wheel]);
+ gCarWheelGraphData[startIndex+wheel][PxVehicleWheelGraphChannel::eTIRELOAD]=filteredTireLoad;
+ }
+}
+PX_FORCE_INLINE void updateGraphDataNormTireLoad(const PxU32 startIndex, const PxU32 wheel, const PxF32 filteredNormalisedTireLoad)
+{
+ if(gCarWheelGraphData[0])
+ {
+ PX_ASSERT((startIndex+wheel) < PX_MAX_NB_WHEELS);
+ PX_ASSERT(gCarWheelGraphData[startIndex+wheel]);
+ gCarWheelGraphData[startIndex+wheel][PxVehicleWheelGraphChannel::eNORMALIZED_TIRELOAD]=filteredNormalisedTireLoad;
+ }
+}
+PX_FORCE_INLINE void updateGraphDataNormLongTireForce(const PxU32 startIndex, const PxU32 wheel, const PxF32 normForce)
+{
+ if(gCarWheelGraphData[0])
+ {
+ PX_ASSERT((startIndex+wheel) < PX_MAX_NB_WHEELS);
+ PX_ASSERT(gCarWheelGraphData[startIndex+wheel]);
+ gCarWheelGraphData[startIndex+wheel][PxVehicleWheelGraphChannel::eNORM_TIRE_LONG_FORCE]=normForce;
+ }
+}
+PX_FORCE_INLINE void updateGraphDataNormLatTireForce(const PxU32 startIndex, const PxU32 wheel, const PxF32 normForce)
+{
+ if(gCarWheelGraphData[0])
+ {
+ PX_ASSERT((startIndex+wheel) < PX_MAX_NB_WHEELS);
+ PX_ASSERT(gCarWheelGraphData[startIndex+wheel]);
+ gCarWheelGraphData[startIndex+wheel][PxVehicleWheelGraphChannel::eNORM_TIRE_LAT_FORCE]=normForce;
+ }
+}
+PX_FORCE_INLINE void updateGraphDataLatTireSlip(const PxU32 startIndex, const PxU32 wheel, const PxF32 latSlip)
+{
+ if(gCarWheelGraphData[0])
+ {
+ PX_ASSERT((startIndex+wheel) < PX_MAX_NB_WHEELS);
+ PX_ASSERT(gCarWheelGraphData[startIndex+wheel]);
+ gCarWheelGraphData[startIndex+wheel][PxVehicleWheelGraphChannel::eTIRE_LAT_SLIP]=latSlip;
+ }
+}
+PX_FORCE_INLINE void updateGraphDataLongTireSlip(const PxU32 startIndex, const PxU32 wheel, const PxF32 longSlip)
+{
+ if(gCarWheelGraphData[0])
+ {
+ PX_ASSERT((startIndex+wheel) < PX_MAX_NB_WHEELS);
+ PX_ASSERT(gCarWheelGraphData[startIndex+wheel]);
+ gCarWheelGraphData[startIndex+wheel][PxVehicleWheelGraphChannel::eTIRE_LONG_SLIP]=longSlip;
+ }
+}
+PX_FORCE_INLINE void updateGraphDataTireFriction(const PxU32 startIndex, const PxU32 wheel, const PxF32 friction)
+{
+ if(gCarWheelGraphData[0])
+ {
+ PX_ASSERT((startIndex+wheel) < PX_MAX_NB_WHEELS);
+ PX_ASSERT(gCarWheelGraphData[startIndex+wheel]);
+ gCarWheelGraphData[startIndex+wheel][PxVehicleWheelGraphChannel::eTIRE_FRICTION]=friction;
+ }
+}
+PX_FORCE_INLINE void updateGraphDataNormTireAligningMoment(const PxU32 startIndex, const PxU32 wheel, const PxF32 normAlignMoment)
+{
+ if(gCarWheelGraphData[0])
+ {
+ PX_ASSERT((startIndex+wheel) < PX_MAX_NB_WHEELS);
+ PX_ASSERT(gCarWheelGraphData[startIndex+wheel]);
+ gCarWheelGraphData[startIndex+wheel][PxVehicleWheelGraphChannel::eNORM_TIRE_ALIGNING_MOMENT]=normAlignMoment;
+ }
+}
+
+#endif //DEBUG_VEHICLE_ON
+
+
+////////////////////////////////////////////////////////////////////////////
+//Profile data structures.
+////////////////////////////////////////////////////////////////////////////
+
+
+#define PX_VEHICLE_PROFILE 0
+
+enum
+{
+ TIMER_ADMIN=0,
+ TIMER_GRAPHS,
+ TIMER_COMPONENTS_UPDATE,
+ TIMER_WHEELS,
+ TIMER_INTERNAL_DYNAMICS_SOLVER,
+ TIMER_POSTUPDATE1,
+ TIMER_POSTUPDATE2,
+ TIMER_POSTUPDATE3,
+ TIMER_ALL,
+ TIMER_RAYCASTS,
+ TIMER_SWEEPS,
+ MAX_NB_TIMERS
+};
+
+#if PX_VEHICLE_PROFILE
+
+bool gTimerStates[MAX_NB_TIMERS]={false,false,false,false,false,false,false,false,false,false,false};
+PxU64 gTimers[MAX_NB_TIMERS]={0,0,0,0,0,0,0,0,0,0,0};
+PxU64 gStartTimers[MAX_NB_TIMERS]={0,0,0,0,0,0,0,0,0,0,0};
+PxU64 gEndTimers[MAX_NB_TIMERS]={0,0,0,0,0,0,0,0,0,0,0};
+PxU32 gTimerCount=0;
+physx::Ps::Time gTimer;
+
+PX_FORCE_INLINE void START_TIMER(const PxU32 id)
+{
+ PX_ASSERT(!gTimerStates[id]);
+ gStartTimers[id]=gTimer.getCurrentCounterValue();
+ gTimerStates[id]=true;
+}
+
+PX_FORCE_INLINE void END_TIMER(const PxU32 id)
+{
+ PX_ASSERT(gTimerStates[id]);
+ gTimerStates[id]=false;
+ gEndTimers[id]=gTimer.getCurrentCounterValue();
+ gTimers[id]+=(gEndTimers[id]-gStartTimers[id]);
+}
+
+PX_FORCE_INLINE PxF32 getTimerFraction(const PxU32 id)
+{
+ return gTimers[id]/(1.0f*gTimers[TIMER_ALL]);
+}
+
+PX_FORCE_INLINE PxReal getTimerInMilliseconds(const PxU32 id)
+{
+ const PxU64 time=gTimers[id];
+ const PxU64 timein10sOfNs = gTimer.getBootCounterFrequency().toTensOfNanos(time);
+ return (timein10sOfNs/(gTimerCount*100*1.0f));
+}
+
+#else
+
+PX_FORCE_INLINE void START_TIMER(const PxU32)
+{
+}
+
+PX_FORCE_INLINE void END_TIMER(const PxU32)
+{
+}
+
+#endif
+
+
+////////////////////////////////////////////////////////////////////////////
+//Hash table of PxMaterial pointers used to associate each PxMaterial pointer
+//with a unique PxDrivableSurfaceType. PxDrivableSurfaceType is just an integer
+//representing an id but introducing this type allows different PxMaterial pointers
+//to be associated with the same surface type. The friction of a specific tire
+//touching a specific PxMaterial is found from a 2D table using the integers for
+//the tire type (stored in the tire) and drivable surface type (from the hash table).
+//It would be great to use PsHashSet for the hash table of PxMaterials but
+//PsHashSet will never, ever work on spu so this will need to do instead.
+//Perf isn't really critical so this will do in the meantime.
+//It is probably wasteful to compute the hash table each update
+//but this is really not an expensive operation so keeping the api as
+//simple as possible wins out at the cost of a relatively very small number of wasted cycles.
+////////////////////////////////////////////////////////////////////////////
+
+class VehicleSurfaceTypeHashTable
+{
+public:
+
+ VehicleSurfaceTypeHashTable(const PxVehicleDrivableSurfaceToTireFrictionPairs& pairs)
+ : mNbEntries(pairs.mNbSurfaceTypes),
+ mMaterials(pairs.mDrivableSurfaceMaterials),
+ mDrivableSurfaceTypes(pairs.mDrivableSurfaceTypes)
+ {
+ for(PxU32 i=0;i<eHASH_SIZE;i++)
+ {
+ mHeadIds[i]=PX_MAX_U32;
+ }
+ for(PxU32 i=0;i<eMAX_NB_KEYS;i++)
+ {
+ mNextIds[i]=PX_MAX_U32;
+ }
+
+ if(mNbEntries>0)
+ {
+ //Compute the number of bits to right-shift that gives the maximum number of unique hashes.
+ //Keep searching until we find either a set of completely unique hashes or a peak count of unique hashes.
+ PxU32 prevShift=0;
+ PxU32 shift=2;
+ PxU32 prevNumUniqueHashes=0;
+ PxU32 currNumUniqueHashes=0;
+ while( ((currNumUniqueHashes=computeNumUniqueHashes(shift)) > prevNumUniqueHashes) && currNumUniqueHashes!=mNbEntries)
+ {
+ prevNumUniqueHashes=currNumUniqueHashes;
+ prevShift=shift;
+ shift = (shift << 1);
+ }
+ if(currNumUniqueHashes!=mNbEntries)
+ {
+ //Stopped searching because we have gone past the peak number of unqiue hashes.
+ mShift = prevShift;
+ }
+ else
+ {
+ //Stopped searching because we found a unique hash for each key.
+ mShift = shift;
+ }
+
+ //Compute the hash values with the optimum shift.
+ for(PxU32 i=0;i<mNbEntries;i++)
+ {
+ const PxMaterial* const material=mMaterials[i];
+ const PxU32 hash=computeHash(material,mShift);
+ if(PX_MAX_U32==mHeadIds[hash])
+ {
+ mNextIds[i]=PX_MAX_U32;
+ mHeadIds[hash]=i;
+ }
+ else
+ {
+ mNextIds[i]=mHeadIds[hash];
+ mHeadIds[hash]=i;
+ }
+ }
+ }
+ }
+ ~VehicleSurfaceTypeHashTable()
+ {
+ }
+
+ PX_FORCE_INLINE PxU32 get(const PxMaterial* const key) const
+ {
+ PX_ASSERT(key);
+ const PxU32 hash=computeHash(key, mShift);
+ PxU32 id=mHeadIds[hash];
+ while(PX_MAX_U32!=id)
+ {
+ const PxMaterial* const mat=mMaterials[id];
+ if(key==mat)
+ {
+ return mDrivableSurfaceTypes[id].mType;
+ }
+ id=mNextIds[id];
+ }
+
+ return 0;
+ }
+
+private:
+
+ PxU32 mNbEntries;
+ const PxMaterial* const* mMaterials;
+ const PxVehicleDrivableSurfaceType* mDrivableSurfaceTypes;
+
+ static PX_FORCE_INLINE PxU32 computeHash(const PxMaterial* const key, const PxU32 shift)
+ {
+ const uintptr_t ptr = ((uintptr_t(key)) >> shift);
+ const uintptr_t hash = (ptr & (eHASH_SIZE-1));
+ return PxU32(hash);
+ }
+
+ PxU32 computeNumUniqueHashes(const PxU32 shift) const
+ {
+ PxU32 words[eHASH_SIZE >>5];
+ PxU8* bitmapBuffer[sizeof(BitMap)];
+ BitMap* bitmap=reinterpret_cast<BitMap*>(bitmapBuffer);
+ bitmap->setWords(words, eHASH_SIZE >>5);
+
+ PxU32 numUniqueHashes=0;
+ PxMemZero(words, sizeof(PxU32)*(eHASH_SIZE >>5));
+ for(PxU32 i=0;i<mNbEntries;i++)
+ {
+ const PxMaterial* const material=mMaterials[i];
+ const PxU32 hash=computeHash(material, shift);
+ if(!bitmap->test(hash))
+ {
+ bitmap->set(hash);
+ numUniqueHashes++;
+ }
+ }
+ return numUniqueHashes;
+ }
+
+ enum
+ {
+ eHASH_SIZE=PxVehicleDrivableSurfaceToTireFrictionPairs::eMAX_NB_SURFACE_TYPES
+ };
+ PxU32 mHeadIds[eHASH_SIZE];
+ enum
+ {
+ eMAX_NB_KEYS=PxVehicleDrivableSurfaceToTireFrictionPairs::eMAX_NB_SURFACE_TYPES
+ };
+ PxU32 mNextIds[eMAX_NB_KEYS];
+
+ PxU32 mShift;
+};
+
+
+////////////////////////////////////////////////////////////////////////////
+//Compute the suspension line raycast start point and direction.
+////////////////////////////////////////////////////////////////////////////
+
+PX_INLINE void computeSuspensionRaycast
+(const PxTransform& carChassisTrnsfm, const PxVec3& bodySpaceWheelCentreOffset, const PxVec3& bodySpaceSuspTravelDir, const PxF32 radius, const PxF32 maxBounce,
+ PxVec3& suspLineStart, PxVec3& suspLineDir)
+{
+ //Direction of raycast.
+ suspLineDir=carChassisTrnsfm.rotate(bodySpaceSuspTravelDir);
+
+ //Position at top of wheel at maximum compression.
+ suspLineStart=carChassisTrnsfm.transform(bodySpaceWheelCentreOffset);
+ suspLineStart-=suspLineDir*(radius+maxBounce);
+}
+
+PX_INLINE void computeSuspensionSweep
+(const PxTransform& carChassisTrnsfm,
+ const PxQuat& wheelLocalPoseRotation, const PxF32 wheelTheta,
+ const PxVec3 bodySpaceWheelCentreOffset, const PxVec3& bodySpaceSuspTravelDir, const PxF32 radius, const PxF32 maxBounce,
+ PxTransform& suspStartPose, PxVec3& suspLineDir)
+{
+ //Direction of raycast.
+ suspLineDir=carChassisTrnsfm.rotate(bodySpaceSuspTravelDir);
+
+ //Position of wheel at maximum compression.
+ suspStartPose.p = carChassisTrnsfm.transform(bodySpaceWheelCentreOffset);
+ suspStartPose.p -= suspLineDir*(radius + maxBounce);
+
+ //Rotation of wheel.
+ const PxVec3 right = wheelLocalPoseRotation.rotate(gRight);
+ const PxQuat negativeRotation(-wheelTheta, right);
+ suspStartPose.q = carChassisTrnsfm.q*(negativeRotation*wheelLocalPoseRotation);
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+//Functions used to integrate rigid body transform and velocity.
+//The sub-step system divides a specified timestep into N equal sub-steps
+//and integrates the velocity and transform each sub-step.
+//After all sub-steps are complete the velocity required to move the
+//associated PxRigidBody from the start transform to the transform at the end
+//of the timestep is computed and set. If the update mode is chosen to be
+//acceleration then the acceleration is computed/set that will move the rigid body
+//from the start to end transform. The PxRigidBody never actually has its transform
+//updated and only has its velocity or acceleration set at the very end of the timestep.
+////////////////////////////////////////////////////////////////////////////
+
+PX_INLINE void integrateBody
+(const PxF32 inverseMass, const PxVec3& invInertia, const PxVec3& force, const PxVec3& torque, const PxF32 dt,
+ PxVec3& v, PxVec3& w, PxTransform& t)
+{
+ //Integrate linear velocity.
+ v+=force*(inverseMass*dt);
+
+ //Integrate angular velocity.
+ PxMat33 inverseInertia;
+ transformInertiaTensor(invInertia, PxMat33(t.q), inverseInertia);
+ w+=inverseInertia*(torque*dt);
+
+ //Integrate position.
+ t.p+=v*dt;
+
+ //Integrate quaternion.
+ PxQuat wq(w.x,w.y,w.z,0.0f);
+ PxQuat q=t.q;
+ PxQuat qdot=wq*q*(dt*0.5f);
+ q+=qdot;
+ q.normalize();
+ t.q=q;
+}
+
+/*
+PX_INLINE void computeVelocity(const PxTransform& t1, const PxTransform& t2, const PxF32 invDT, PxVec3& v, PxVec3& w)
+{
+ //Linear velocity.
+ v = (t2.p - t1.p)*invDT;
+
+ //Angular velocity.
+ PxQuat qw = (t2.q - t1.q)*t1.q.getConjugate()*(2.0f*invDT);
+ w.x=qw.x;
+ w.y=qw.y;
+ w.z=qw.z;
+}
+*/
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+//Use fsel to compute the sign of a float: +1 for positive values, -1 for negative values
+/////////////////////////////////////////////////////////////////////////////////////////
+
+PX_FORCE_INLINE PxF32 computeSign(const PxF32 f)
+{
+ return physx::intrinsics::fsel(f, physx::intrinsics::fsel(-f, 0.0f, 1.0f), -1.0f);
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+//Get the accel/brake/handbrake as floats in range (0,1) from the inputs stored in the vehicle.
+//Equivalent for tank involving thrustleft/thrustright and brakeleft/brakeright.
+/////////////////////////////////////////////////////////////////////////////////////////
+
+
+PX_FORCE_INLINE void getVehicle4WControlValues(const PxVehicleDriveDynData& driveDynData, PxF32& accel, PxF32& brake, PxF32& handbrake, PxF32& steerLeft, PxF32& steerRight)
+{
+ accel=driveDynData.mControlAnalogVals[PxVehicleDrive4WControl::eANALOG_INPUT_ACCEL];
+ brake=driveDynData.mControlAnalogVals[PxVehicleDrive4WControl::eANALOG_INPUT_BRAKE];
+ handbrake=driveDynData.mControlAnalogVals[PxVehicleDrive4WControl::eANALOG_INPUT_HANDBRAKE];
+ steerLeft=driveDynData.mControlAnalogVals[PxVehicleDrive4WControl::eANALOG_INPUT_STEER_LEFT];
+ steerRight=driveDynData.mControlAnalogVals[PxVehicleDrive4WControl::eANALOG_INPUT_STEER_RIGHT];
+}
+
+PX_FORCE_INLINE void getVehicleNWControlValues(const PxVehicleDriveDynData& driveDynData, PxF32& accel, PxF32& brake, PxF32& handbrake, PxF32& steerLeft, PxF32& steerRight)
+{
+ accel=driveDynData.mControlAnalogVals[PxVehicleDriveNWControl::eANALOG_INPUT_ACCEL];
+ brake=driveDynData.mControlAnalogVals[PxVehicleDriveNWControl::eANALOG_INPUT_BRAKE];
+ handbrake=driveDynData.mControlAnalogVals[PxVehicleDriveNWControl::eANALOG_INPUT_HANDBRAKE];
+ steerLeft=driveDynData.mControlAnalogVals[PxVehicleDriveNWControl::eANALOG_INPUT_STEER_LEFT];
+ steerRight=driveDynData.mControlAnalogVals[PxVehicleDriveNWControl::eANALOG_INPUT_STEER_RIGHT];
+}
+
+PX_FORCE_INLINE void getTankControlValues(const PxVehicleDriveDynData& driveDynData, PxF32& accel, PxF32& brakeLeft, PxF32& brakeRight, PxF32& thrustLeft, PxF32& thrustRight)
+{
+ accel=driveDynData.mControlAnalogVals[PxVehicleDriveTankControl::eANALOG_INPUT_ACCEL];
+ brakeLeft=driveDynData.mControlAnalogVals[PxVehicleDriveTankControl::eANALOG_INPUT_BRAKE_LEFT];
+ brakeRight=driveDynData.mControlAnalogVals[PxVehicleDriveTankControl::eANALOG_INPUT_BRAKE_RIGHT];
+ thrustLeft=driveDynData.mControlAnalogVals[PxVehicleDriveTankControl::eANALOG_INPUT_THRUST_LEFT];
+ thrustRight=driveDynData.mControlAnalogVals[PxVehicleDriveTankControl::eANALOG_INPUT_THRUST_RIGHT];
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+//Process autobox to initiate automatic gear changes.
+//The autobox can be turned off and simulated externally by setting
+//the target gear as required prior to calling PxVehicleUpdates.
+////////////////////////////////////////////////////////////////////////////
+
+
+PX_FORCE_INLINE PxF32 processAutoBox(const PxU32 accelIndex, const PxF32 timestep, const PxVehicleDriveSimData& vehCoreSimData, PxVehicleDriveDynData& vehDynData)
+{
+ PX_ASSERT(vehDynData.getUseAutoGears());
+
+ PxF32 autoboxCompensatedAnalogAccel = vehDynData.mControlAnalogVals[accelIndex];
+
+ //If still undergoing a gear change triggered by the autobox
+ //then turn off the accelerator pedal. This happens in autoboxes
+ //to stop the driver revving the engine crazily then damaging the
+ //clutch when the clutch re-engages at the end of the gear change.
+ const PxU32 currentGear=vehDynData.getCurrentGear();
+ const PxU32 targetGear=vehDynData.getTargetGear();
+ if(targetGear!=currentGear && PxVehicleGearsData::eNEUTRAL==currentGear)
+ {
+ autoboxCompensatedAnalogAccel = 0;
+ }
+
+ //Only process the autobox if no gear change is underway and the time passed since the last autobox
+ //gear change is greater than the autobox latency.
+ PxF32 autoBoxSwitchTime=vehDynData.getAutoBoxSwitchTime();
+ const PxF32 autoBoxLatencyTime=vehCoreSimData.getAutoBoxData().mDownRatios[PxVehicleGearsData::eREVERSE];
+ if(targetGear==currentGear && autoBoxSwitchTime > autoBoxLatencyTime)
+ {
+ //Work out if the autobox wants to switch up or down.
+ const PxF32 normalisedEngineOmega=vehDynData.getEngineRotationSpeed()*vehCoreSimData.getEngineData().getRecipMaxOmega();
+ const PxVehicleAutoBoxData& autoBoxData=vehCoreSimData.getAutoBoxData();
+
+ bool gearUp=false;
+ if(normalisedEngineOmega > autoBoxData.mUpRatios[currentGear] && PxVehicleGearsData::eREVERSE!=currentGear)
+ {
+ //If revs too high and not in reverse and not undergoing a gear change then switch up.
+ gearUp=true;
+ }
+
+ bool gearDown=false;
+ if(normalisedEngineOmega < autoBoxData.mDownRatios[currentGear] && currentGear > PxVehicleGearsData::eFIRST)
+ {
+ //If revs too low and in gear greater than first and not undergoing a gear change then change down.
+ gearDown=true;
+ }
+
+ //Start the gear change and reset the time since the last autobox gear change.
+ if(gearUp || gearDown)
+ {
+ vehDynData.setGearUp(gearUp);
+ vehDynData.setGearDown(gearDown);
+ vehDynData.setAutoBoxSwitchTime(0.f);
+ }
+ }
+ else
+ {
+ autoBoxSwitchTime+=timestep;
+ vehDynData.setAutoBoxSwitchTime(autoBoxSwitchTime);
+ }
+
+ return autoboxCompensatedAnalogAccel;
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+//Process gear changes.
+//If target gear not equal to current gear then a gear change needs to start.
+//The gear change process begins by switching immediately in neutral and
+//staying there for a specified time. The process ends by setting current
+//gear equal to target gear when the gear switch time has passed.
+//This can be bypassed by always forcing target gear = current gear and then
+//externally managing gear changes prior to calling PxVehicleUpdates.
+////////////////////////////////////////////////////////////////////////////
+
+void processGears(const PxF32 timestep, const PxVehicleGearsData& gears, PxVehicleDriveDynData& car)
+{
+ //const PxVehicleGearsData& gears=car.mVehicleSimData.getGearsData();
+
+ //Process the gears.
+ if(car.getGearUp() && gears.mNbRatios-1!=car.getCurrentGear() && car.getCurrentGear()==car.getTargetGear())
+ {
+ //Car wants to go up a gear and can go up a gear and not already undergoing a gear change.
+ if(PxVehicleGearsData::eREVERSE==car.getCurrentGear())
+ {
+ //In reverse so switch to first through neutral.
+ car.setGearSwitchTime(0);
+ car.setTargetGear(PxVehicleGearsData::eFIRST);
+ car.setCurrentGear(PxVehicleGearsData::eNEUTRAL);
+ }
+ else if(PxVehicleGearsData::eNEUTRAL==car.getCurrentGear())
+ {
+ //In neutral so switch to first and stay in neutral.
+ car.setGearSwitchTime(0);
+ car.setTargetGear(PxVehicleGearsData::eFIRST);
+ car.setCurrentGear(PxVehicleGearsData::eNEUTRAL);
+ }
+ else
+ {
+ //Switch up a gear through neutral.
+ car.setGearSwitchTime(0);
+ car.setTargetGear(car.getCurrentGear() + 1);
+ car.setCurrentGear(PxVehicleGearsData::eNEUTRAL);
+ }
+ }
+ if(car.getGearDown() && PxVehicleGearsData::eREVERSE!=car.getCurrentGear() && car.getCurrentGear()==car.getTargetGear())
+ {
+ //Car wants to go down a gear and can go down a gear and not already undergoing a gear change
+ if(PxVehicleGearsData::eFIRST==car.getCurrentGear())
+ {
+ //In first so switch to reverse through neutral.
+ car.setGearSwitchTime(0);
+ car.setTargetGear(PxVehicleGearsData::eREVERSE);
+ car.setCurrentGear(PxVehicleGearsData::eNEUTRAL);
+ }
+ else if(PxVehicleGearsData::eNEUTRAL==car.getCurrentGear())
+ {
+ //In neutral so switch to reverse and stay in neutral.
+ car.setGearSwitchTime(0);
+ car.setTargetGear(PxVehicleGearsData::eREVERSE);
+ car.setCurrentGear(PxVehicleGearsData::eNEUTRAL);
+ }
+ else
+ {
+ //Switch down a gear through neutral.
+ car.setGearSwitchTime(0);
+ car.setTargetGear(car.getCurrentGear() - 1);
+ car.setCurrentGear(PxVehicleGearsData::eNEUTRAL);
+ }
+ }
+ if(car.getCurrentGear()!=car.getTargetGear())
+ {
+ if(car.getGearSwitchTime()>gears.mSwitchTime)
+ {
+ car.setCurrentGear(car.getTargetGear());
+ car.setGearSwitchTime(0);
+ car.setGearDown(false);
+ car.setGearUp(false);
+ }
+ else
+ {
+ car.setGearSwitchTime(car.getGearSwitchTime() + timestep);
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////
+//Helper functions to compute
+//1. the gear ratio from the current gear.
+//2. the drive torque from the state of the accelerator pedal and torque curve of available torque against engine speed.
+//3. engine damping rate (a blend between a rate when not accelerating and a rate when fully accelerating).
+//4. clutch strength (rate at which clutch will regulate difference between engine and averaged wheel speed).
+////////////////////////////////////////////////////////////////////////////
+
+PX_FORCE_INLINE PxF32 computeGearRatio(const PxVehicleGearsData& gearsData, const PxU32 currentGear)
+{
+ const PxF32 gearRatio=gearsData.mRatios[currentGear]*gearsData.mFinalRatio;
+ return gearRatio;
+}
+
+PX_FORCE_INLINE PxF32 computeEngineDriveTorque(const PxVehicleEngineData& engineData, const PxF32 omega, const PxF32 accel)
+{
+ const PxF32 engineDriveTorque=accel*engineData.mPeakTorque*engineData.mTorqueCurve.getYVal(omega*engineData.getRecipMaxOmega());
+ return engineDriveTorque;
+}
+
+PX_FORCE_INLINE PxF32 computeEngineDampingRate(const PxVehicleEngineData& engineData, const PxU32 gear, const PxF32 accel)
+{
+ const PxF32 fullThrottleDamping = engineData.mDampingRateFullThrottle;
+ const PxF32 zeroThrottleDamping = (PxVehicleGearsData::eNEUTRAL!=gear) ? engineData.mDampingRateZeroThrottleClutchEngaged : engineData.mDampingRateZeroThrottleClutchDisengaged;
+ const PxF32 engineDamping = zeroThrottleDamping + (fullThrottleDamping-zeroThrottleDamping)*accel;
+ return engineDamping;
+}
+
+PX_FORCE_INLINE PxF32 computeClutchStrength(const PxVehicleClutchData& clutchData, const PxU32 currentGear)
+{
+ return ((PxVehicleGearsData::eNEUTRAL!=currentGear) ? clutchData.mStrength : 0.0f);
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+//Limited slip differential.
+//Split the available drive torque as a fraction of the total between up to 4 driven wheels.
+//Compute the fraction that each wheel contributes to the averages wheel speed at the clutch.
+////////////////////////////////////////////////////////////////////////////
+
+PX_FORCE_INLINE void splitTorque
+(const PxF32 w1, const PxF32 w2, const PxF32 diffBias, const PxF32 defaultSplitRatio,
+ PxF32* t1, PxF32* t2)
+{
+ PX_ASSERT(computeSign(w1)==computeSign(w2) && 0.0f!=computeSign(w1));
+ const PxF32 w1Abs=PxAbs(w1);
+ const PxF32 w2Abs=PxAbs(w2);
+ const PxF32 omegaMax=PxMax(w1Abs,w2Abs);
+ const PxF32 omegaMin=PxMin(w1Abs,w2Abs);
+ const PxF32 delta=omegaMax-diffBias*omegaMin;
+ const PxF32 deltaTorque=physx::intrinsics::fsel(delta, delta/omegaMax , 0.0f);
+ const PxF32 f1=physx::intrinsics::fsel(w1Abs-w2Abs, defaultSplitRatio*(1.0f-deltaTorque), defaultSplitRatio*(1.0f+deltaTorque));
+ const PxF32 f2=physx::intrinsics::fsel(w1Abs-w2Abs, (1.0f-defaultSplitRatio)*(1.0f+deltaTorque), (1.0f-defaultSplitRatio)*(1.0f-deltaTorque));
+ const PxF32 denom=1.0f/(f1+f2);
+ *t1=f1*denom;
+ *t2=f2*denom;
+ PX_ASSERT((*t1 + *t2) >=0.999f && (*t1 + *t2) <=1.001f);
+}
+
+PX_FORCE_INLINE void computeDiffTorqueRatios
+(const PxVehicleDifferential4WData& diffData, const PxF32 handbrake, const PxF32* PX_RESTRICT wheelOmegas, PxF32* PX_RESTRICT diffTorqueRatios)
+{
+ //If the handbrake is on only deliver torque to the front wheels.
+ PxU32 type=diffData.mType;
+ if(handbrake>0)
+ {
+ switch(diffData.mType)
+ {
+ case PxVehicleDifferential4WData::eDIFF_TYPE_LS_4WD:
+ type=PxVehicleDifferential4WData::eDIFF_TYPE_LS_FRONTWD;
+ break;
+ case PxVehicleDifferential4WData::eDIFF_TYPE_OPEN_4WD:
+ type=PxVehicleDifferential4WData::eDIFF_TYPE_OPEN_FRONTWD;
+ break;
+ case PxVehicleDifferential4WData::eDIFF_TYPE_LS_FRONTWD:
+ case PxVehicleDifferential4WData::eDIFF_TYPE_LS_REARWD:
+ case PxVehicleDifferential4WData::eDIFF_TYPE_OPEN_FRONTWD:
+ case PxVehicleDifferential4WData::eDIFF_TYPE_OPEN_REARWD:
+ case PxVehicleDifferential4WData::eMAX_NB_DIFF_TYPES:
+ break;
+ }
+ }
+
+ const PxF32 wfl=wheelOmegas[PxVehicleDrive4WWheelOrder::eFRONT_LEFT];
+ const PxF32 wfr=wheelOmegas[PxVehicleDrive4WWheelOrder::eFRONT_RIGHT];
+ const PxF32 wrl=wheelOmegas[PxVehicleDrive4WWheelOrder::eREAR_LEFT];
+ const PxF32 wrr=wheelOmegas[PxVehicleDrive4WWheelOrder::eREAR_RIGHT];
+
+ const PxF32 centreBias=diffData.mCentreBias;
+ const PxF32 frontBias=diffData.mFrontBias;
+ const PxF32 rearBias=diffData.mRearBias;
+
+ const PxF32 frontRearSplit=diffData.mFrontRearSplit;
+ const PxF32 frontLeftRightSplit=diffData.mFrontLeftRightSplit;
+ const PxF32 rearLeftRightSplit=diffData.mRearLeftRightSplit;
+
+ const PxF32 oneMinusFrontRearSplit=1.0f-diffData.mFrontRearSplit;
+ const PxF32 oneMinusFrontLeftRightSplit=1.0f-diffData.mFrontLeftRightSplit;
+ const PxF32 oneMinusRearLeftRightSplit=1.0f-diffData.mRearLeftRightSplit;
+
+ const PxF32 swfl=computeSign(wfl);
+
+ //Split a torque of 1 between front and rear.
+ //Then split that torque between left and right.
+ PxF32 torqueFrontLeft=0;
+ PxF32 torqueFrontRight=0;
+ PxF32 torqueRearLeft=0;
+ PxF32 torqueRearRight=0;
+ switch(type)
+ {
+ case PxVehicleDifferential4WData::eDIFF_TYPE_LS_4WD:
+ if(0.0f!=swfl && swfl==computeSign(wfr) && swfl==computeSign(wrl) && swfl==computeSign(wrr))
+ {
+ PxF32 torqueFront,torqueRear;
+ const PxF32 omegaFront=PxAbs(wfl+wfr);
+ const PxF32 omegaRear=PxAbs(wrl+wrr);
+ splitTorque(omegaFront,omegaRear,centreBias,frontRearSplit,&torqueFront,&torqueRear);
+ splitTorque(wfl,wfr,frontBias,frontLeftRightSplit,&torqueFrontLeft,&torqueFrontRight);
+ splitTorque(wrl,wrr,rearBias,rearLeftRightSplit,&torqueRearLeft,&torqueRearRight);
+ torqueFrontLeft*=torqueFront;
+ torqueFrontRight*=torqueFront;
+ torqueRearLeft*=torqueRear;
+ torqueRearRight*=torqueRear;
+ }
+ else
+ {
+ //TODO: need to handle this case.
+ torqueFrontLeft=frontRearSplit*frontLeftRightSplit;
+ torqueFrontRight=frontRearSplit*oneMinusFrontLeftRightSplit;
+ torqueRearLeft=oneMinusFrontRearSplit*rearLeftRightSplit;
+ torqueRearRight=oneMinusFrontRearSplit*oneMinusRearLeftRightSplit;
+ }
+ break;
+
+ case PxVehicleDifferential4WData::eDIFF_TYPE_LS_FRONTWD:
+ if(0.0f!=swfl && swfl==computeSign(wfr))
+ {
+ splitTorque(wfl,wfr,frontBias,frontLeftRightSplit,&torqueFrontLeft,&torqueFrontRight);
+ }
+ else
+ {
+ torqueFrontLeft=frontLeftRightSplit;
+ torqueFrontRight=oneMinusFrontLeftRightSplit;
+ }
+ break;
+
+ case PxVehicleDifferential4WData::eDIFF_TYPE_LS_REARWD:
+
+ if(0.0f!=computeSign(wrl) && computeSign(wrl)==computeSign(wrr))
+ {
+ splitTorque(wrl,wrr,rearBias,rearLeftRightSplit,&torqueRearLeft,&torqueRearRight);
+ }
+ else
+ {
+ torqueRearLeft=rearLeftRightSplit;
+ torqueRearRight=oneMinusRearLeftRightSplit;
+ }
+ break;
+
+ case PxVehicleDifferential4WData::eDIFF_TYPE_OPEN_4WD:
+ torqueFrontLeft=frontRearSplit*frontLeftRightSplit;
+ torqueFrontRight=frontRearSplit*oneMinusFrontLeftRightSplit;
+ torqueRearLeft=oneMinusFrontRearSplit*rearLeftRightSplit;
+ torqueRearRight=oneMinusFrontRearSplit*oneMinusRearLeftRightSplit;
+ break;
+
+ case PxVehicleDifferential4WData::eDIFF_TYPE_OPEN_FRONTWD:
+ torqueFrontLeft=frontLeftRightSplit;
+ torqueFrontRight=oneMinusFrontLeftRightSplit;
+ break;
+
+ case PxVehicleDifferential4WData::eDIFF_TYPE_OPEN_REARWD:
+ torqueRearLeft=rearLeftRightSplit;
+ torqueRearRight=oneMinusRearLeftRightSplit;
+ break;
+
+ default:
+ PX_ASSERT(false);
+ break;
+ }
+
+ diffTorqueRatios[PxVehicleDrive4WWheelOrder::eFRONT_LEFT]=torqueFrontLeft;
+ diffTorqueRatios[PxVehicleDrive4WWheelOrder::eFRONT_RIGHT]=torqueFrontRight;
+ diffTorqueRatios[PxVehicleDrive4WWheelOrder::eREAR_LEFT]=torqueRearLeft;
+ diffTorqueRatios[PxVehicleDrive4WWheelOrder::eREAR_RIGHT]=torqueRearRight;
+
+ PX_ASSERT(((torqueFrontLeft+torqueFrontRight+torqueRearLeft+torqueRearRight) >= 0.999f) && ((torqueFrontLeft+torqueFrontRight+torqueRearLeft+torqueRearRight) <= 1.001f));
+}
+
+PX_FORCE_INLINE void computeDiffAveWheelSpeedContributions
+(const PxVehicleDifferential4WData& diffData, const PxF32 handbrake, PxF32* PX_RESTRICT diffAveWheelSpeedContributions)
+{
+ PxU32 type=diffData.mType;
+
+ //If the handbrake is on only deliver torque to the front wheels.
+ if(handbrake>0)
+ {
+ switch(diffData.mType)
+ {
+ case PxVehicleDifferential4WData::eDIFF_TYPE_LS_4WD:
+ type=PxVehicleDifferential4WData::eDIFF_TYPE_LS_FRONTWD;
+ break;
+ case PxVehicleDifferential4WData::eDIFF_TYPE_OPEN_4WD:
+ type=PxVehicleDifferential4WData::eDIFF_TYPE_OPEN_FRONTWD;
+ break;
+ case PxVehicleDifferential4WData::eDIFF_TYPE_LS_REARWD:
+ case PxVehicleDifferential4WData::eDIFF_TYPE_LS_FRONTWD:
+ case PxVehicleDifferential4WData::eDIFF_TYPE_OPEN_REARWD:
+ case PxVehicleDifferential4WData::eDIFF_TYPE_OPEN_FRONTWD:
+ case PxVehicleDifferential4WData::eMAX_NB_DIFF_TYPES:
+ break;
+ }
+ }
+
+ const PxF32 frontRearSplit=diffData.mFrontRearSplit;
+ const PxF32 frontLeftRightSplit=diffData.mFrontLeftRightSplit;
+ const PxF32 rearLeftRightSplit=diffData.mRearLeftRightSplit;
+
+ const PxF32 oneMinusFrontRearSplit=1.0f-diffData.mFrontRearSplit;
+ const PxF32 oneMinusFrontLeftRightSplit=1.0f-diffData.mFrontLeftRightSplit;
+ const PxF32 oneMinusRearLeftRightSplit=1.0f-diffData.mRearLeftRightSplit;
+
+ switch(type)
+ {
+ case PxVehicleDifferential4WData::eDIFF_TYPE_LS_4WD:
+ case PxVehicleDifferential4WData::eDIFF_TYPE_OPEN_4WD:
+ diffAveWheelSpeedContributions[PxVehicleDrive4WWheelOrder::eFRONT_LEFT]=frontRearSplit*frontLeftRightSplit;
+ diffAveWheelSpeedContributions[PxVehicleDrive4WWheelOrder::eFRONT_RIGHT]=frontRearSplit*oneMinusFrontLeftRightSplit;
+ diffAveWheelSpeedContributions[PxVehicleDrive4WWheelOrder::eREAR_LEFT]=oneMinusFrontRearSplit*rearLeftRightSplit;
+ diffAveWheelSpeedContributions[PxVehicleDrive4WWheelOrder::eREAR_RIGHT]=oneMinusFrontRearSplit*oneMinusRearLeftRightSplit;
+ break;
+ case PxVehicleDifferential4WData::eDIFF_TYPE_LS_FRONTWD:
+ case PxVehicleDifferential4WData::eDIFF_TYPE_OPEN_FRONTWD:
+ diffAveWheelSpeedContributions[PxVehicleDrive4WWheelOrder::eFRONT_LEFT]=frontLeftRightSplit;
+ diffAveWheelSpeedContributions[PxVehicleDrive4WWheelOrder::eFRONT_RIGHT]=oneMinusFrontLeftRightSplit;
+ diffAveWheelSpeedContributions[PxVehicleDrive4WWheelOrder::eREAR_LEFT]=0.0f;
+ diffAveWheelSpeedContributions[PxVehicleDrive4WWheelOrder::eREAR_RIGHT]=0.0f;
+ break;
+ case PxVehicleDifferential4WData::eDIFF_TYPE_LS_REARWD:
+ case PxVehicleDifferential4WData::eDIFF_TYPE_OPEN_REARWD:
+ diffAveWheelSpeedContributions[PxVehicleDrive4WWheelOrder::eFRONT_LEFT]=0.0f;
+ diffAveWheelSpeedContributions[PxVehicleDrive4WWheelOrder::eFRONT_RIGHT]=0.0f;
+ diffAveWheelSpeedContributions[PxVehicleDrive4WWheelOrder::eREAR_LEFT]=rearLeftRightSplit;
+ diffAveWheelSpeedContributions[PxVehicleDrive4WWheelOrder::eREAR_RIGHT]=oneMinusRearLeftRightSplit;
+ break;
+ default:
+ PX_ASSERT(false);
+ break;
+ }
+
+ PX_ASSERT((diffAveWheelSpeedContributions[PxVehicleDrive4WWheelOrder::eFRONT_LEFT] +
+ diffAveWheelSpeedContributions[PxVehicleDrive4WWheelOrder::eFRONT_RIGHT] +
+ diffAveWheelSpeedContributions[PxVehicleDrive4WWheelOrder::eREAR_LEFT] +
+ diffAveWheelSpeedContributions[PxVehicleDrive4WWheelOrder::eREAR_RIGHT]) >= 0.999f &&
+ (diffAveWheelSpeedContributions[PxVehicleDrive4WWheelOrder::eFRONT_LEFT] +
+ diffAveWheelSpeedContributions[PxVehicleDrive4WWheelOrder::eFRONT_RIGHT] +
+ diffAveWheelSpeedContributions[PxVehicleDrive4WWheelOrder::eREAR_LEFT] +
+ diffAveWheelSpeedContributions[PxVehicleDrive4WWheelOrder::eREAR_RIGHT]) <= 1.001f);
+}
+
+
+///////////////////////////////////////////////////////
+//Tank differential
+///////////////////////////////////////////////////////
+
+void computeTankDiff
+(const PxF32 thrustLeft, const PxF32 thrustRight,
+ const PxU32 numActiveWheels, const bool* activeWheelStates,
+ PxF32* aveWheelSpeedContributions, PxF32* diffTorqueRatios, PxF32* wheelGearings)
+{
+ const PxF32 thrustLeftAbs=PxAbs(thrustLeft);
+ const PxF32 thrustRightAbs=PxAbs(thrustRight);
+
+ //Work out now many left wheels are enabled.
+ PxF32 numLeftWheels=0.0f;
+ for(PxU32 i=0;i<numActiveWheels;i+=2)
+ {
+ if(activeWheelStates[i])
+ {
+ numLeftWheels+=1.0f;
+ }
+ }
+ const PxF32 invNumEnabledWheelsLeft = numLeftWheels > 0 ? 1.0f/numLeftWheels : 0.0f;
+
+ //Work out now many right wheels are enabled.
+ PxF32 numRightWheels=0.0f;
+ for(PxU32 i=1;i<numActiveWheels;i+=2)
+ {
+ if(activeWheelStates[i])
+ {
+ numRightWheels+=1.0f;
+ }
+ }
+ const PxF32 invNumEnabledWheelsRight = numRightWheels > 0 ? 1.0f/numRightWheels : 0.0f;
+
+ //Split the diff torque between left and right.
+ PxF32 diffTorqueRatioLeft=0.5f;
+ PxF32 diffTorqueRatioRight=0.5f;
+ if((thrustLeftAbs + thrustRightAbs) > 1e-3f)
+ {
+ const PxF32 thrustDiff = 0.5f*(thrustLeftAbs - thrustRightAbs)/(thrustLeftAbs + thrustRightAbs);
+ diffTorqueRatioLeft += thrustDiff;
+ diffTorqueRatioRight -= thrustDiff;
+
+ }
+ diffTorqueRatioLeft *= invNumEnabledWheelsLeft;
+ diffTorqueRatioRight *= invNumEnabledWheelsRight;
+
+ //Compute the per wheel gearing.
+ PxF32 wheelGearingLeft=1.0f;
+ PxF32 wheelGearingRight=1.0f;
+ if((thrustLeftAbs + thrustRightAbs) > 1e-3f)
+ {
+ wheelGearingLeft=computeSign(thrustLeft);
+ wheelGearingRight=computeSign(thrustRight);
+ }
+
+ //Compute the contribution of each wheel to the average speed at the clutch.
+ const PxF32 aveWheelSpeedContributionLeft = 0.5f*invNumEnabledWheelsLeft;
+ const PxF32 aveWheelSpeedContributionRight = 0.5f*invNumEnabledWheelsRight;
+
+ //Set all the left wheels.
+ for(PxU32 i=0;i<numActiveWheels;i+=2)
+ {
+ if(activeWheelStates[i])
+ {
+ aveWheelSpeedContributions[i]=aveWheelSpeedContributionLeft;
+ diffTorqueRatios[i]=diffTorqueRatioLeft;
+ wheelGearings[i]=wheelGearingLeft;
+ }
+ }
+ //Set all the right wheels.
+ for(PxU32 i=1;i<numActiveWheels;i+=2)
+ {
+ if(activeWheelStates[i])
+ {
+ aveWheelSpeedContributions[i]=aveWheelSpeedContributionRight;
+ diffTorqueRatios[i]=diffTorqueRatioRight;
+ wheelGearings[i]=wheelGearingRight;
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////
+//Compute a per-wheel accelerator pedal value.
+//These values are to blend the denominator normalised longitudinal slip at low speed
+//between a low value for wheels under drive torque and a high value for wheels with no
+//drive torque.
+//Using a high value allows the vehicle to come to rest smoothly.
+//Using a low value gives better thrust.
+////////////////////////////////////////////////////////////////////////////
+
+void computeIsAccelApplied(const PxF32* aveWheelSpeedContributions, bool* isAccelApplied)
+{
+ isAccelApplied[0] = aveWheelSpeedContributions[0] != 0.0f ? true : false;
+ isAccelApplied[1] = aveWheelSpeedContributions[1] != 0.0f ? true : false;
+ isAccelApplied[2] = aveWheelSpeedContributions[2] != 0.0f ? true : false;
+ isAccelApplied[3] = aveWheelSpeedContributions[3] != 0.0f ? true : false;
+}
+
+////////////////////////////////////////////////////////////////////////////
+//Ackermann correction to steer angles.
+////////////////////////////////////////////////////////////////////////////
+
+PX_FORCE_INLINE void computeAckermannSteerAngles
+(const PxF32 steer, const PxF32 steerGain,
+ const PxF32 ackermannAccuracy, const PxF32 width, const PxF32 axleSeparation,
+ PxF32* PX_RESTRICT leftAckermannSteerAngle, PxF32* PX_RESTRICT rightAckermannSteerAngle)
+{
+ PX_ASSERT(steer>=-1.01f && steer<=1.01f);
+ PX_ASSERT(steerGain<PxPi);
+
+ const PxF32 steerAngle=steer*steerGain;
+
+ if(0==steerAngle)
+ {
+ *leftAckermannSteerAngle=0;
+ *rightAckermannSteerAngle=0;
+ return;
+ }
+
+ //Work out the ackermann steer for +ve steer then swap and negate the steer angles if the steer is -ve.
+ //TODO: use faster approximate functions for PxTan/PxATan because the results don't need to be too accurate here.
+ const PxF32 rightSteerAngle=PxAbs(steerAngle);
+ const PxF32 dz=axleSeparation;
+ const PxF32 dx=width + dz/PxTan(rightSteerAngle);
+ const PxF32 leftSteerAnglePerfect=PxAtan(dz/dx);
+ const PxF32 leftSteerAngle=rightSteerAngle + ackermannAccuracy*(leftSteerAnglePerfect-rightSteerAngle);
+ *rightAckermannSteerAngle=physx::intrinsics::fsel(steerAngle, rightSteerAngle, -leftSteerAngle);
+ *leftAckermannSteerAngle=physx::intrinsics::fsel(steerAngle, leftSteerAngle, -rightSteerAngle);
+}
+
+PX_FORCE_INLINE void computeAckermannCorrectedSteerAngles
+(const PxVehicleDriveSimData4W& driveSimData, const PxVehicleWheels4SimData& wheelsSimData, const PxF32 steer,
+ PxF32* PX_RESTRICT steerAngles)
+{
+ const PxVehicleAckermannGeometryData& ackermannData=driveSimData.getAckermannGeometryData();
+ const PxF32 ackermannAccuracy=ackermannData.mAccuracy;
+ const PxF32 axleSeparation=ackermannData.mAxleSeparation;
+
+ {
+ const PxVehicleWheelData& wheelDataFL=wheelsSimData.getWheelData(PxVehicleDrive4WWheelOrder::eFRONT_LEFT);
+ const PxVehicleWheelData& wheelDataFR=wheelsSimData.getWheelData(PxVehicleDrive4WWheelOrder::eFRONT_RIGHT);
+ const PxF32 steerGainFront=PxMax(wheelDataFL.mMaxSteer,wheelDataFR.mMaxSteer);
+ const PxF32 frontWidth=ackermannData.mFrontWidth;
+ PxF32 frontLeftSteer,frontRightSteer;
+ computeAckermannSteerAngles(steer,steerGainFront,ackermannAccuracy,frontWidth,axleSeparation,&frontLeftSteer,&frontRightSteer);
+ steerAngles[PxVehicleDrive4WWheelOrder::eFRONT_LEFT]=wheelDataFL.mToeAngle+frontLeftSteer;
+ steerAngles[PxVehicleDrive4WWheelOrder::eFRONT_RIGHT]=wheelDataFR.mToeAngle+frontRightSteer;
+ }
+
+ {
+ const PxVehicleWheelData& wheelDataRL=wheelsSimData.getWheelData(PxVehicleDrive4WWheelOrder::eREAR_LEFT);
+ const PxVehicleWheelData& wheelDataRR=wheelsSimData.getWheelData(PxVehicleDrive4WWheelOrder::eREAR_RIGHT);
+ const PxF32 steerGainRear=PxMax(wheelDataRL.mMaxSteer,wheelDataRR.mMaxSteer);
+ const PxF32 rearWidth=ackermannData.mRearWidth;
+ PxF32 rearLeftSteer,rearRightSteer;
+ computeAckermannSteerAngles(steer,steerGainRear,ackermannAccuracy,rearWidth,axleSeparation,&rearLeftSteer,&rearRightSteer);
+ steerAngles[PxVehicleDrive4WWheelOrder::eREAR_LEFT]=wheelDataRL.mToeAngle-rearLeftSteer;
+ steerAngles[PxVehicleDrive4WWheelOrder::eREAR_RIGHT]=wheelDataRR.mToeAngle-rearRightSteer;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////
+//Compute the wheel active states
+////////////////////////////////////////////////////////////////////////////
+
+PX_FORCE_INLINE void computeWheelActiveStates(const PxU32 startId, PxU32* bitmapBuffer, bool* activeStates)
+{
+ PX_ASSERT(!activeStates[0] && !activeStates[1] && !activeStates[2] && !activeStates[3]);
+
+ BitMap bm;
+ bm.setWords(bitmapBuffer, ((PX_MAX_NB_WHEELS + 31) & ~31) >> 5);
+
+ if(bm.test(startId + 0))
+ {
+ activeStates[0]=true;
+ }
+ if(bm.test(startId + 1))
+ {
+ activeStates[1]=true;
+ }
+ if(bm.test(startId + 2))
+ {
+ activeStates[2]=true;
+ }
+ if(bm.test(startId + 3))
+ {
+ activeStates[3]=true;
+ }
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+//Compute the brake and handbrake torques for different vehicle types.
+//Also compute a boolean for each tire to know if the brake is applied or not.
+//Can't use a single function for all types because not all vehicle types have
+//handbrakes and the brake control mechanism is different for different vehicle
+//types.
+////////////////////////////////////////////////////////////////////////////
+
+PX_FORCE_INLINE void computeNoDriveBrakeTorques
+(const PxVehicleWheelData* PX_RESTRICT wheelDatas, const PxF32* PX_RESTRICT wheelOmegas, const PxF32* PX_RESTRICT rawBrakeTroques,
+ PxF32* PX_RESTRICT brakeTorques, bool* PX_RESTRICT isBrakeApplied)
+{
+ PX_UNUSED(wheelDatas);
+
+ const PxF32 sign0=computeSign(wheelOmegas[0]);
+ brakeTorques[0]=(-sign0*rawBrakeTroques[0]);
+ isBrakeApplied[0]=(rawBrakeTroques[0]!=0);
+
+ const PxF32 sign1=computeSign(wheelOmegas[1]);
+ brakeTorques[1]=(-sign1*rawBrakeTroques[1]);
+ isBrakeApplied[1]=(rawBrakeTroques[1]!=0);
+
+ const PxF32 sign2=computeSign(wheelOmegas[2]);
+ brakeTorques[2]=(-sign2*rawBrakeTroques[2]);
+ isBrakeApplied[2]=(rawBrakeTroques[2]!=0);
+
+ const PxF32 sign3=computeSign(wheelOmegas[3]);
+ brakeTorques[3]=(-sign3*rawBrakeTroques[3]);
+ isBrakeApplied[3]=(rawBrakeTroques[3]!=0);
+}
+
+PX_FORCE_INLINE void computeBrakeAndHandBrakeTorques
+(const PxVehicleWheelData* PX_RESTRICT wheelDatas, const PxF32* PX_RESTRICT wheelOmegas, const PxF32 brake, const PxF32 handbrake,
+ PxF32* PX_RESTRICT brakeTorques, bool* isBrakeApplied)
+{
+ //At zero speed offer no brake torque allowed.
+
+ const PxF32 sign0=computeSign(wheelOmegas[0]);
+ brakeTorques[0]=(-brake*sign0*wheelDatas[0].mMaxBrakeTorque-handbrake*sign0*wheelDatas[0].mMaxHandBrakeTorque);
+ isBrakeApplied[0]=((brake*wheelDatas[0].mMaxBrakeTorque+handbrake*wheelDatas[0].mMaxHandBrakeTorque)!=0);
+
+ const PxF32 sign1=computeSign(wheelOmegas[1]);
+ brakeTorques[1]=(-brake*sign1*wheelDatas[1].mMaxBrakeTorque-handbrake*sign1*wheelDatas[1].mMaxHandBrakeTorque);
+ isBrakeApplied[1]=((brake*wheelDatas[1].mMaxBrakeTorque+handbrake*wheelDatas[1].mMaxHandBrakeTorque)!=0);
+
+ const PxF32 sign2=computeSign(wheelOmegas[2]);
+ brakeTorques[2]=(-brake*sign2*wheelDatas[2].mMaxBrakeTorque-handbrake*sign2*wheelDatas[2].mMaxHandBrakeTorque);
+ isBrakeApplied[2]=((brake*wheelDatas[2].mMaxBrakeTorque+handbrake*wheelDatas[2].mMaxHandBrakeTorque)!=0);
+
+ const PxF32 sign3=computeSign(wheelOmegas[3]);
+ brakeTorques[3]=(-brake*sign3*wheelDatas[3].mMaxBrakeTorque-handbrake*sign3*wheelDatas[3].mMaxHandBrakeTorque);
+ isBrakeApplied[3]=((brake*wheelDatas[3].mMaxBrakeTorque+handbrake*wheelDatas[3].mMaxHandBrakeTorque)!=0);
+}
+
+PX_FORCE_INLINE void computeTankBrakeTorques
+(const PxVehicleWheelData* PX_RESTRICT wheelDatas, const PxF32* PX_RESTRICT wheelOmegas, const PxF32 brakeLeft, const PxF32 brakeRight,
+ PxF32* PX_RESTRICT brakeTorques, bool* isBrakeApplied)
+{
+ //At zero speed offer no brake torque allowed.
+
+ const PxF32 sign0=computeSign(wheelOmegas[0]);
+ brakeTorques[0]=(-brakeLeft*sign0*wheelDatas[0].mMaxBrakeTorque);
+ isBrakeApplied[0]=((brakeLeft*wheelDatas[0].mMaxBrakeTorque)!=0);
+
+ const PxF32 sign1=computeSign(wheelOmegas[1]);
+ brakeTorques[1]=(-brakeRight*sign1*wheelDatas[1].mMaxBrakeTorque);
+ isBrakeApplied[1]=((brakeRight*wheelDatas[1].mMaxBrakeTorque)!=0);
+
+ const PxF32 sign2=computeSign(wheelOmegas[2]);
+ brakeTorques[2]=(-brakeLeft*sign2*wheelDatas[2].mMaxBrakeTorque);
+ isBrakeApplied[2]=((brakeLeft*wheelDatas[2].mMaxBrakeTorque)!=0);
+
+ const PxF32 sign3=computeSign(wheelOmegas[3]);
+ brakeTorques[3]=(-brakeRight*sign3*wheelDatas[3].mMaxBrakeTorque);
+ isBrakeApplied[3]=((brakeRight*wheelDatas[3].mMaxBrakeTorque)!=0);
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+//Functions to compute inputs to tire force calculation.
+//1. Filter the normalised tire load to smooth any spikes in load.
+//2. Compute the tire lat and long directions in the ground plane.
+//3. Compute the tire lat and long slips.
+//4. Compute the friction from a graph of friction vs slip.
+////////////////////////////////////////////////////////////////////////////
+
+PX_FORCE_INLINE PxF32 computeFilteredNormalisedTireLoad(const PxVehicleTireLoadFilterData& filterData, const PxF32 normalisedLoad)
+{
+ if(normalisedLoad <= filterData.mMinNormalisedLoad)
+ {
+ return filterData.mMinFilteredNormalisedLoad;
+ }
+ else if(normalisedLoad >= filterData.mMaxNormalisedLoad)
+ {
+ return filterData.mMaxFilteredNormalisedLoad;
+ }
+ else
+ {
+ const PxF32 x=normalisedLoad;
+ const PxF32 xmin=filterData.mMinNormalisedLoad;
+ const PxF32 ymin=filterData.mMinFilteredNormalisedLoad;
+ const PxF32 ymax=filterData.mMaxFilteredNormalisedLoad;
+ const PxF32 recipXmaxMinusXMin=filterData.getDenominator();
+ return (ymin + (x-xmin)*(ymax-ymin)*recipXmaxMinusXMin);
+ }
+}
+
+PX_FORCE_INLINE void computeTireDirs(const PxVec3& chassisLatDir, const PxVec3& hitNorm, const PxF32 wheelSteerAngle, PxVec3& tireLongDir, PxVec3& tireLatDir)
+{
+ PX_ASSERT(chassisLatDir.magnitude()>0.999f && chassisLatDir.magnitude()<1.001f);
+ PX_ASSERT(hitNorm.magnitude()>0.999f && hitNorm.magnitude()<1.001f);
+
+ //Compute the tire axes in the ground plane.
+ PxVec3 tzRaw=chassisLatDir.cross(hitNorm);
+ PxVec3 txRaw=hitNorm.cross(tzRaw);
+ tzRaw.normalize();
+ txRaw.normalize();
+ //Rotate the tire using the steer angle.
+ const PxF32 cosWheelSteer=PxCos(wheelSteerAngle);
+ const PxF32 sinWheelSteer=PxSin(wheelSteerAngle);
+ const PxVec3 tz=tzRaw*cosWheelSteer + txRaw*sinWheelSteer;
+ const PxVec3 tx=txRaw*cosWheelSteer - tzRaw*sinWheelSteer;
+ tireLongDir=tz;
+ tireLatDir=tx;
+}
+
+PX_FORCE_INLINE void computeTireSlips
+(const PxF32 longSpeed, const PxF32 latSpeed, const PxF32 wheelOmega, const PxF32 wheelRadius, const PxF32 maxDenominator,
+ const bool isAccelApplied, const bool isBrakeApplied,
+ const bool isTank,
+ PxF32& longSlip, PxF32& latSlip)
+{
+ PX_ASSERT(maxDenominator>=0.0f);
+
+ const PxF32 longSpeedAbs=PxAbs(longSpeed);
+ const PxF32 wheelSpeed=wheelOmega*wheelRadius;
+ const PxF32 wheelSpeedAbs=PxAbs(wheelSpeed);
+
+ //Lateral slip is easy.
+ latSlip = PxAtan(latSpeed/(longSpeedAbs+gMinLatSpeedForTireModel));//TODO: do we really use PxAbs(vz) as denominator?
+
+ //If nothing is moving just avoid a divide by zero and set the long slip to zero.
+ if(longSpeed==0 && wheelOmega==0)
+ {
+ longSlip=0.0f;
+ return;
+ }
+
+ //Longitudinal slip is a bit harder because we can end up wtih zero on the denominator.
+ if(isTank)
+ {
+ if(isBrakeApplied || isAccelApplied)
+ {
+ //Wheel experiencing an applied torque.
+ //Use the raw denominator value plus an offset to avoid anything approaching a divide by zero.
+ //When accelerating from rest the small denominator will generate really quite large
+ //slip values, which will, in turn, generate large longitudinal forces. With large
+ //time-steps this might lead to a temporary oscillation in longSlip direction and an
+ //oscillation in wheel speed direction. The amplitude of the oscillation should be low
+ //unless the timestep is really large.
+ //There's not really an obvious solution to this without setting the denominator offset higher
+ //(or decreasing the timestep). Setting the denominator higher affects handling everywhere so
+ //settling for a potential temporary oscillation is probably the least worst compromise.
+ //Note that we always use longSpeedAbs as denominator because in order to turn on the spot the
+ //tank needs to get strong longitudinal force when it isn't moving but the wheels are slipping.
+ longSlip = (wheelSpeed - longSpeed)/(longSpeedAbs + 0.1f*gToleranceScaleLength);
+ }
+ else
+ {
+ //Wheel not experiencing an applied torque.
+ //If the denominator becomes too small then the longSlip becomes large and the longitudinal force
+ //can overshoot zero at large timesteps. This can be really noticeable so it's harder to justify
+ //not taking action. Further, the car isn't being actually driven so there is a strong case to fiddle
+ //with the denominator because it doesn't really affect active handling.
+ //Don't let the denominator fall below a user-specified value. This can be tuned upwards until the
+ //oscillation in the sign of longSlip disappears.
+ longSlip = (wheelSpeed - longSpeed)/(PxMax(maxDenominator, PxMax(longSpeedAbs,wheelSpeedAbs)));
+ }
+ }
+ else
+ {
+ if(isBrakeApplied || isAccelApplied)
+ {
+ //Wheel experiencing an applied torque.
+ //Use the raw denominator value plus an offset to avoid anything approaching a divide by zero.
+ //When accelerating from rest the small denominator will generate really quite large
+ //slip values, which will, in turn, generate large longitudinal forces. With large
+ //time-steps this might lead to a temporary oscillation in longSlip direction and an
+ //oscillation in wheel speed direction. The amplitude of the oscillation should be low
+ //unless the timestep is really large.
+ //There's not really an obvious solution to this without setting the denominator offset higher
+ //(or decreasing the timestep). Setting the denominator higher affects handling everywhere so
+ //settling for a potential temporary oscillation is probably the least worst compromise.
+ longSlip = (wheelSpeed - longSpeed)/(PxMax(longSpeedAbs,wheelSpeedAbs)+0.1f*gToleranceScaleLength);
+ }
+ else
+ {
+ //Wheel not experiencing an applied torque.
+ //If the denominator becomes too small then the longSlip becomes large and the longitudinal force
+ //can overshoot zero at large timesteps. This can be really noticeable so it's harder to justify
+ //not taking action. Further, the car isn't being actually driven so there is a strong case to fiddle
+ //with the denominator because it doesn't really affect active handling.
+ //Don't let the denominator fall below a user-specified value. This can be tuned upwards until the
+ //oscillation in the sign of longSlip disappears.
+ longSlip = (wheelSpeed - longSpeed)/(PxMax(maxDenominator,PxMax(longSpeedAbs,wheelSpeedAbs)));
+ }
+ }
+}
+
+PX_FORCE_INLINE void computeTireFriction(const PxVehicleTireData& tireData, const PxF32 longSlip, const PxF32 frictionMultiplier, PxF32& friction)
+{
+ const PxF32 x0=tireData.mFrictionVsSlipGraph[0][0];
+ const PxF32 y0=tireData.mFrictionVsSlipGraph[0][1];
+ const PxF32 x1=tireData.mFrictionVsSlipGraph[1][0];
+ const PxF32 y1=tireData.mFrictionVsSlipGraph[1][1];
+ const PxF32 x2=tireData.mFrictionVsSlipGraph[2][0];
+ const PxF32 y2=tireData.mFrictionVsSlipGraph[2][1];
+ const PxF32 recipx1Minusx0=tireData.getFrictionVsSlipGraphRecipx1Minusx0();
+ const PxF32 recipx2Minusx1=tireData.getFrictionVsSlipGraphRecipx2Minusx1();
+ const PxF32 longSlipAbs=PxAbs(longSlip);
+ PxF32 mu;
+ if(longSlipAbs<x1)
+ {
+ mu=y0 + (y1-y0)*(longSlipAbs-x0)*recipx1Minusx0;
+ }
+ else if(longSlipAbs<x2)
+ {
+ mu=y1 + (y2-y1)*(longSlipAbs-x1)*recipx2Minusx1;
+ }
+ else
+ {
+ mu=y2;
+ }
+ PX_ASSERT(mu>=0);
+ friction=mu*frictionMultiplier;
+}
+
+////////////////////////////////////////////////////////////////////////////
+//Sticky tire constraints.
+//Increment a timer each update that a tire has a very low longitudinal speed.
+//Activate a sticky constraint when the tire has had an unbroken low long speed
+//for at least a threshold time.
+//The longer the sticky constraint is active, the slower the target constraint speed
+//along the long dir. Quickly tends towards zero.
+//When the sticky constraint is activated set the long slip to zero and let
+//the sticky constraint take over.
+////////////////////////////////////////////////////////////////////////////
+
+PX_FORCE_INLINE void updateLowForwardSpeedTimer
+(const PxF32 longSpeed, const PxF32 wheelOmega, const PxF32 wheelRadius, const PxF32 recipWheelRadius, const bool isIntentionToAccelerate,
+ const PxF32 timestep, PxF32& lowForwardSpeedTime)
+{
+ PX_UNUSED(wheelRadius);
+ PX_UNUSED(recipWheelRadius);
+
+ //If the tire is rotating slowly and the forward speed is slow then increment the slow forward speed timer.
+ //If the intention of the driver is to accelerate the vehicle then reset the timer because the intention has been signalled NOT to bring
+ //the wheel to rest.
+ PxF32 longSpeedAbs=PxAbs(longSpeed);
+ if((longSpeedAbs<gStickyTireFrictionThresholdSpeed) && (PxAbs(wheelOmega)< gStickyTireFrictionThresholdSpeed*recipWheelRadius) && !isIntentionToAccelerate)
+ {
+ lowForwardSpeedTime+=timestep;
+ }
+ else
+ {
+ lowForwardSpeedTime=0;
+ }
+}
+
+PX_FORCE_INLINE void updateLowSideSpeedTimer
+(const PxF32 latSpeed, const bool isIntentionToAccelerate, const PxF32 timestep, PxF32& lowSideSpeedTime)
+{
+ //If the side speed is slow then increment the slow side speed timer.
+ //If the intention of the driver is to accelerate the vehicle then reset the timer because the intention has been signalled NOT to bring
+ //the wheel to rest.
+ PxF32 latSpeedAbs=PxAbs(latSpeed);
+ if((latSpeedAbs<gStickyTireFrictionThresholdSpeed) && !isIntentionToAccelerate)
+ {
+ lowSideSpeedTime+=timestep;
+ }
+ else
+ {
+ lowSideSpeedTime=0;
+ }
+}
+
+
+PX_FORCE_INLINE void activateStickyFrictionForwardConstraint
+(const PxF32 longSpeed, const PxF32 wheelOmega, const PxF32 lowForwardSpeedTime, const bool isIntentionToAccelerate,
+ bool& stickyTireActiveFlag, PxF32& stickyTireTargetSpeed)
+{
+ //Setup the sticky friction constraint to bring the vehicle to rest at the tire contact point.
+ //The idea here is to resolve the singularity of the tire long slip at low vz by replacing the long force with a velocity constraint.
+ //Only do this if we can guarantee that the intention is to bring the car to rest (no accel pedal applied).
+ //Smoothly reduce error to zero to avoid bringing car immediately to rest. This avoids graphical glitchiness.
+ //We're going to replace the longitudinal tire force with the sticky friction so set the long slip to zero to ensure zero long force.
+ //Apply sticky friction to this tire if
+ //(1) the wheel is locked (this means the brake/handbrake must be on) and the forward speed at the tire contact point is vanishingly small and
+ // the drive of vehicle has no intention to accelerate the vehicle.
+ //(2) the accumulated time of low forward speed is greater than a threshold.
+ PxF32 longSpeedAbs=PxAbs(longSpeed);
+ stickyTireActiveFlag=false;
+ stickyTireTargetSpeed=0.0f;
+ if((longSpeedAbs < gStickyTireFrictionThresholdSpeed && 0.0f==wheelOmega && !isIntentionToAccelerate) || lowForwardSpeedTime>gLowForwardSpeedThresholdTime)
+ {
+ stickyTireActiveFlag=true;
+ stickyTireTargetSpeed=longSpeed*gStickyTireFrictionForwardDamping;
+ }
+}
+
+PX_FORCE_INLINE void activateStickyFrictionSideConstraint
+(const PxF32 latSpeed, const PxF32 lowSpeedForwardTimer, const PxF32 lowSideSpeedTimer, const bool isIntentionToAccelerate,
+ bool& stickyTireActiveFlag, PxF32& stickyTireTargetSpeed)
+{
+ PX_UNUSED(latSpeed);
+ PX_UNUSED(isIntentionToAccelerate);
+
+ //Setup the sticky friction constraint to bring the vehicle to rest at the tire contact point.
+ //Only do this if we can guarantee that the intention is to bring the car to rest (no accel pedal applied).
+ //Smoothly reduce error to zero to avoid bringing car immediately to rest. This avoids graphical glitchiness.
+ //We're going to replace the lateral tire force with the sticky friction so set the lat slip to zero to ensure zero lat force.
+ //Apply sticky friction to this tire if
+ //(1) the low forward speed timer is > 0.
+ //(2) the accumulated time of low forward speed is greater than a threshold.
+ stickyTireActiveFlag=false;
+ stickyTireTargetSpeed=0.0f;
+ if((lowSpeedForwardTimer > 0) && lowSideSpeedTimer>gLowSideSpeedThresholdTime)
+ {
+ stickyTireActiveFlag=true;
+ stickyTireTargetSpeed=latSpeed*gStickyTireFrictionSideDamping;
+ }
+}
+
+
+
+////////////////////////////////////////////////////////////////////////////
+//Default tire force shader function.
+//Taken from Michigan tire model.
+//Computes tire long and lat forces plus the aligning moment arising from
+//the lat force and the torque to apply back to the wheel arising from the
+//long force (application of Newton's 3rd law).
+////////////////////////////////////////////////////////////////////////////
+
+
+#define ONE_TWENTYSEVENTH 0.037037f
+#define ONE_THIRD 0.33333f
+PX_FORCE_INLINE PxF32 smoothingFunction1(const PxF32 K)
+{
+ //Equation 20 in CarSimEd manual Appendix F.
+ //Looks a bit like a curve of sqrt(x) for 0<x<1 but reaching 1.0 on y-axis at K=3.
+ PX_ASSERT(K>=0.0f);
+ return PxMin(1.0f, K - ONE_THIRD*K*K + ONE_TWENTYSEVENTH*K*K*K);
+}
+PX_FORCE_INLINE PxF32 smoothingFunction2(const PxF32 K)
+{
+ //Equation 21 in CarSimEd manual Appendix F.
+ //Rises to a peak at K=0.75 and falls back to zero by K=3
+ PX_ASSERT(K>=0.0f);
+ return (K - K*K + ONE_THIRD*K*K*K - ONE_TWENTYSEVENTH*K*K*K*K);
+}
+
+void PxVehicleComputeTireForceDefault
+(const void* tireShaderData,
+ const PxF32 tireFriction,
+ const PxF32 longSlipUnClamped, const PxF32 latSlipUnClamped, const PxF32 camberUnclamped,
+ const PxF32 wheelOmega, const PxF32 wheelRadius, const PxF32 recipWheelRadius,
+ const PxF32 restTireLoad, const PxF32 normalisedTireLoad, const PxF32 tireLoad,
+ const PxF32 gravity, const PxF32 recipGravity,
+ PxF32& wheelTorque, PxF32& tireLongForceMag, PxF32& tireLatForceMag, PxF32& tireAlignMoment)
+{
+ PX_UNUSED(wheelOmega);
+ PX_UNUSED(recipWheelRadius);
+
+ const PxVehicleTireData& tireData=*reinterpret_cast<const PxVehicleTireData*>(tireShaderData);
+
+ PX_ASSERT(tireFriction>0);
+ PX_ASSERT(tireLoad>0);
+
+ wheelTorque=0.0f;
+ tireLongForceMag=0.0f;
+ tireLatForceMag=0.0f;
+ tireAlignMoment=0.0f;
+
+ //Clamp the slips to a minimum value.
+ const PxF32 latSlip = PxAbs(latSlipUnClamped) >= gMinimumSlipThreshold ? latSlipUnClamped : 0.0f;
+ const PxF32 longSlip = PxAbs(longSlipUnClamped) >= gMinimumSlipThreshold ? longSlipUnClamped : 0.0f;
+ const PxF32 camber = PxAbs(camberUnclamped) >= gMinimumSlipThreshold ? camberUnclamped : 0.0f;
+
+ //If long slip/lat slip/camber are all zero than there will be zero tire force.
+ if((0==latSlip)&&(0==longSlip)&&(0==camber))
+ {
+ return;
+ }
+
+ //Compute the lateral stiffness
+ const PxF32 latStiff=restTireLoad*tireData.mLatStiffY*smoothingFunction1(normalisedTireLoad*3.0f/tireData.mLatStiffX);
+
+ //Get the longitudinal stiffness
+ const PxF32 longStiff=tireData.mLongitudinalStiffnessPerUnitGravity*gravity;
+ const PxF32 recipLongStiff=tireData.getRecipLongitudinalStiffnessPerUnitGravity()*recipGravity;
+
+ //Get the camber stiffness.
+ const PxF32 camberStiff=tireData.mCamberStiffnessPerUnitGravity*gravity;
+
+ //Carry on and compute the forces.
+ const PxF32 TEff = PxTan(latSlip - camber*camberStiff/latStiff);
+ const PxF32 K = PxSqrt(latStiff*TEff*latStiff*TEff + longStiff*longSlip*longStiff*longSlip) /(tireFriction*tireLoad);
+ //const PxF32 KAbs=PxAbs(K);
+ PxF32 FBar = smoothingFunction1(K);//K - ONE_THIRD*PxAbs(K)*K + ONE_TWENTYSEVENTH*K*K*K;
+ PxF32 MBar = smoothingFunction2(K); //K - KAbs*K + ONE_THIRD*K*K*K - ONE_TWENTYSEVENTH*KAbs*K*K*K;
+ //Mbar = PxMin(Mbar, 1.0f);
+ PxF32 nu=1;
+ if(K <= 2.0f*PxPi)
+ {
+ const PxF32 latOverlLong=latStiff*recipLongStiff;
+ nu = 0.5f*(1.0f + latOverlLong - (1.0f - latOverlLong)*PxCos(K*0.5f));
+ }
+ const PxF32 FZero = tireFriction*tireLoad / (PxSqrt(longSlip*longSlip + nu*TEff*nu*TEff));
+ const PxF32 fz = longSlip*FBar*FZero;
+ const PxF32 fx = -nu*TEff*FBar*FZero;
+ //TODO: pneumatic trail.
+ const PxF32 pneumaticTrail=1.0f;
+ const PxF32 fMy= nu * pneumaticTrail * TEff * MBar * FZero;
+
+ //We can add the torque to the wheel.
+ wheelTorque=-fz*wheelRadius;
+ tireLongForceMag=fz;
+ tireLatForceMag=fx;
+ tireAlignMoment=fMy;
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+//Functions required to intersect the wheel with the hit plane
+//We support raycasts and sweeps.
+////////////////////////////////////////////////////////////////////////////
+
+bool intersectRayPlane
+(const PxTransform& carChassisTrnsfm,
+ const PxVec3& bodySpaceWheelCentreOffset, const PxVec3& bodySpaceSuspTravelDir,
+ const PxF32 width, const PxF32 radius, const PxF32 maxCompression,
+ const PxVec4& hitPlane,
+ PxF32& jounce, PxVec3& wheelBottomPos)
+{
+ const PxVec3 hitNorm = PxVec3(hitPlane.x, hitPlane.y, hitPlane.z);
+
+ //Compute the raycast start pos and direction.
+ PxVec3 v, w;
+ computeSuspensionRaycast(carChassisTrnsfm, bodySpaceWheelCentreOffset, bodySpaceSuspTravelDir, radius, maxCompression, v, w);
+
+ //If the raycast starts inside the hit plane then return false
+ if(hitPlane.x*v.x + hitPlane.y*v.y + hitPlane.z*v.z + hitPlane.w < 0.0f)
+ {
+ return false;
+ }
+
+ //Store a point through the centre of the wheel.
+ //We'll use this later to compute a position at the bottom of the wheel.
+ const PxVec3 pos = v;
+
+ //Work out if the inner or outer disc is deeper in the plane.
+ const PxVec3 latDir = carChassisTrnsfm.rotate(gRight);
+ const PxF32 signDot = computeSign(hitNorm.dot(latDir));
+ v -= latDir*(signDot*0.5f*width);
+
+ //Work out the point on the susp line that touches the intersection plane.
+ //n.(v+wt)+d=0 where n,d describe the plane; v,w describe the susp ray; t is the point on the susp line.
+ //t=-(n.v + d)/n.w
+ const PxF32 hitD = hitPlane.w;
+ const PxVec3& n = hitNorm;
+ const PxF32 d = hitD;
+ const PxF32 T=-(n.dot(v) + d)/(n.dot(w));
+
+ //The rest pos of the susp line is 2*radius + maxBounce.
+ const PxF32 restT = 2.0f*radius+maxCompression;
+
+ //Compute the spring compression ie the difference between T and restT.
+ //+ve means that the spring is compressed
+ //-ve means that the spring is elongated.
+ jounce = restT-T;
+
+ //Compute the bottom of the wheel.
+ //Always choose a point through the centre of the wheel.
+ wheelBottomPos = pos + w*(restT - jounce);
+
+ return true;
+}
+
+bool intersectPlanes(const PxVec4& a, const PxVec4& b, PxVec3& v, PxVec3& w)
+{
+ const PxF32 n1x = a.x;
+ const PxF32 n1y = a.y;
+ const PxF32 n1z = a.z;
+ const PxF32 n1d = a.w;
+
+ const PxF32 n2x = b.x;
+ const PxF32 n2y = b.y;
+ const PxF32 n2z = b.z;
+ const PxF32 n2d = b.w;
+
+ PxF32 dx = (n1y * n2z) - (n1z * n2y);
+ PxF32 dy = (n1z * n2x) - (n1x * n2z);
+ PxF32 dz = (n1x * n2y) - (n1y * n2x);
+
+ const PxF32 dx2 = dx * dx;
+ const PxF32 dy2 = dy * dy;
+ const PxF32 dz2 = dz * dz;
+
+ PxF32 px, py, pz;
+ bool success = true;
+ if ((dz2 > dy2) && (dz2 > dx2) && (dz2 > 0))
+ {
+ px = ((n1y * n2d) - (n2y * n1d)) / dz;
+ py = ((n2x * n1d) - (n1x * n2d)) / dz;
+ pz = 0;
+ }
+ else if ((dy2 > dx2) && (dy2 > 0))
+ {
+ px = -((n1z * n2d) - (n2z * n1d)) / dy;
+ py = 0;
+ pz = -((n2x * n1d) - (n1x * n2d)) / dy;
+ }
+ else if (dx2 > 0)
+ {
+ px = 0;
+ py = ((n1z * n2d) - (n2z * n1d)) / dx;
+ pz = ((n2y * n1d) - (n1y * n2d)) / dx;
+ }
+ else
+ {
+ px=0;
+ py=0;
+ pz=0;
+ success=false;
+ }
+
+ const PxF32 ld = PxSqrt(dx2 + dy2 + dz2);
+
+ dx /= ld;
+ dy /= ld;
+ dz /= ld;
+
+ w = PxVec3(dx,dy,dz);
+ v = PxVec3(px,py,pz);
+
+ return success;
+}
+
+bool intersectCylinderPlane
+(const PxTransform& wheelPoseAtZeroJounce, const PxVec3 suspDir,
+ const PxF32 width, const PxF32 radius, const PxF32 maxCompression,
+ const PxVec4& hitPlane,
+ const bool rejectFromThresholds,
+ PxF32& jounce, PxVec3& wheelBottomPos)
+{
+ PX_UNUSED(maxCompression);
+ PX_UNUSED(width);
+
+ //Reject based on the contact normal.
+ if (rejectFromThresholds)
+ {
+ if (suspDir.dot(-hitPlane.getXYZ()) < gNormalRejectAngleThreshold)
+ {
+ return false;
+ }
+ }
+
+ //Construct the wheel plane that contains the wheel disc.
+ PxVec4 wheelPlane;
+ {
+ const PxVec3 n = wheelPoseAtZeroJounce.rotate(gRight);
+ const PxF32 d = - n.dot(wheelPoseAtZeroJounce.p);
+ wheelPlane.x = n.x;
+ wheelPlane.y = n.y;
+ wheelPlane.z = n.z;
+ wheelPlane.w = d;
+ }
+
+ //Intersect the plane of the wheel with the hit plane.
+ //This generates an intersection edge.
+ PxVec3 intersectionEdgeV, intersectionEdgeW;
+ const bool intersectPlaneSuccess = intersectPlanes(wheelPlane, hitPlane, intersectionEdgeV, intersectionEdgeW);
+ if(!intersectPlaneSuccess)
+ {
+ jounce = 0.0f;
+ wheelBottomPos = PxVec3(0,0,0);
+ return false;
+ }
+
+ //Compute the position on the intersection edge that is closest to the wheel centre.
+ PxVec3 closestPointOnIntersectionEdge;
+ {
+ const PxVec3& p = wheelPoseAtZeroJounce.p;
+ const PxVec3& w = intersectionEdgeW;
+ const PxVec3& v = intersectionEdgeV;
+ const PxF32 t = (p - v).dot(w);
+ closestPointOnIntersectionEdge = v + w*t;
+ }
+
+ //Compute the vector that joins the wheel centre to the intersection edge;
+ PxVec3 dir;
+ {
+ const PxF32 wheelCentreD = hitPlane.x*wheelPoseAtZeroJounce.p.x + hitPlane.y*wheelPoseAtZeroJounce.p.y + hitPlane.z*wheelPoseAtZeroJounce.p.z + hitPlane.w;
+ dir = ((wheelCentreD >= 0) ? closestPointOnIntersectionEdge - wheelPoseAtZeroJounce.p : wheelPoseAtZeroJounce.p - closestPointOnIntersectionEdge);
+ dir.normalize();
+ }
+
+ //Now work out if we accept the hit.
+ //Compare dir with the suspension direction.
+ if (rejectFromThresholds)
+ {
+ if (suspDir.dot(dir) < gPointRejectAngleThreshold)
+ {
+ return false;
+ }
+ }
+
+ //Compute the point on the disc diameter that will be the closest to the hit plane or the deepest inside the hit plane.
+ PxVec3 pos;
+ {
+ pos = wheelPoseAtZeroJounce.p + dir*radius;
+ }
+
+ //If the sweep started inside the hit plane then return false
+ const PxVec3 startPos = pos - suspDir*(radius + maxCompression);
+ if(hitPlane.x*startPos.x + hitPlane.y*startPos.y + hitPlane.z*startPos.z + hitPlane.w < 0.0f)
+ {
+ return false;
+ }
+
+ //Now compute the maximum depth of the inside and outside discs against the plane.
+ PxF32 depth;
+ {
+ const PxVec3 latDir = wheelPoseAtZeroJounce.rotate(gRight);
+ const PxF32 signDot = computeSign(hitPlane.x*latDir.x + hitPlane.y*latDir.y + hitPlane.z*latDir.z);
+ const PxVec3 deepestPos = pos - latDir*(signDot*0.5f*width);
+ depth = hitPlane.x*deepestPos.x + hitPlane.y*deepestPos.y + hitPlane.z*deepestPos.z + hitPlane.w;
+ }
+
+
+ //How far along the susp dir do we have to move to place the wheel exactly on the plane.
+ const PxF32 t = -depth/(hitPlane.x*suspDir.x + hitPlane.y*suspDir.y + hitPlane.z*suspDir.z);
+
+ //+ve means that the spring is compressed
+ //-ve means that the spring is elongated.
+ jounce = -t;
+
+ //Compute a point at the bottom of the wheel that is at the centre.
+ wheelBottomPos = pos + suspDir*t;
+
+ //Finished.
+ return true;
+}
+
+bool intersectCylinderPlane
+(const PxTransform& carChassisTrnsfm,
+ const PxQuat& wheelLocalPoseRotation, const PxF32 wheelTheta,
+ const PxVec3& bodySpaceWheelCentreOffset, const PxVec3& bodySpaceSuspTravelDir, const PxF32 width, const PxF32 radius, const PxF32 maxCompression,
+ const PxVec4& hitPlane,
+ const bool rejectFromThresholds,
+ PxF32& jounce, PxVec3& wheelBottomPos)
+{
+ //Compute the pose of the wheel
+ PxTransform wheelPostsAtZeroJounce;
+ PxVec3 suspDir;
+ computeSuspensionSweep(
+ carChassisTrnsfm,
+ wheelLocalPoseRotation, wheelTheta,
+ bodySpaceWheelCentreOffset, bodySpaceSuspTravelDir, 0.0f, 0.0f,
+ wheelPostsAtZeroJounce, suspDir);
+
+ //Perform the intersection.
+ return intersectCylinderPlane
+ (wheelPostsAtZeroJounce, suspDir,
+ width, radius, maxCompression,
+ hitPlane,
+ rejectFromThresholds,
+ jounce, wheelBottomPos);
+}
+
+////////////////////////////////////////////////////////////////////////////
+//Structures used to process blocks of 4 wheels: process the raycast result,
+//compute the suspension and tire force, store a number of report variables
+//such as tire slip, hit shape, hit material, friction etc.
+////////////////////////////////////////////////////////////////////////////
+
+class PxVehicleTireForceCalculator4
+{
+public:
+
+ const void* mShaderData[4];
+ PxVehicleComputeTireForce mShader;
+private:
+};
+
+//This data structure is passed to processSuspTireWheels
+//and represents the data that is logically constant across all sub-steps of each dt update.
+struct ProcessSuspWheelTireConstData
+{
+ //We are integrating dt over N sub-steps.
+ //timeFraction is 1/N.
+ PxF32 timeFraction;
+ //We are integrating dt over N sub-steps.
+ //subTimeStep is dt/N.
+ PxF32 subTimeStep;
+ PxF32 recipSubTimeStep;
+
+ //Gravitational acceleration vector
+ PxVec3 gravity;
+ //Length of gravitational acceleration vector (saves a square root each time we need it)
+ PxF32 gravityMagnitude;
+ //Reciprocal length of gravitational acceleration vector (saves a square root and divide each time we need it).
+ PxF32 recipGravityMagnitude;
+
+ //True for tanks, false for all other vehicle types.
+ //Used when computing the longitudinal and lateral slips.
+ bool isTank;
+
+ //Minimum denominator allowed in longitudinal slip computation.
+ PxF32 minLongSlipDenominator;
+
+ //Pointer to physx actor that represents the vehicle.
+ const PxRigidDynamic* vehActor;
+
+ //Pointer to table of friction values for each combination of material and tire type.
+ const PxVehicleDrivableSurfaceToTireFrictionPairs* frictionPairs;
+};
+
+//This data structure is passed to processSuspTireWheels
+//and represents the data that is physically constant across each sub-steps of each dt update.
+struct ProcessSuspWheelTireInputData
+{
+public:
+
+ //True if the driver intends to pass drive torque to any wheel of the vehicle,
+ //even if none of the wheels in the block of 4 wheels processed in processSuspTireWheels are given drive torque.
+ //False if the driver does not intend the vehicle to accelerate.
+ //If the player intends to accelerate then no wheel will be given a sticky tire constraint.
+ //This data is actually logically constant.
+ bool isIntentionToAccelerate;
+
+ //True if a wheel has a non-zero diff torque, false if a wheel has zero diff torque.
+ //This data is actually logically constant.
+ const bool* isAccelApplied;
+
+ //True if a wheel has a non-zero brake torque, false if a wheel has zero brake torque.
+ //This data is actually logically constant.
+ const bool* isBrakeApplied;
+
+ //Steer angles of each wheel in radians.
+ //This data is actually logically constant.
+ const PxF32* steerAngles;
+
+ //True if the wheel is not disabled, false if wheel is disabled.
+ //This data is actually logically constant.
+ bool* activeWheelStates;
+
+ //Properties of the rigid body - transform.
+ //This data is actually logically constant.
+ PxTransform carChassisTrnsfm;
+ //Properties of the rigid body - linear velocity.
+ //This data is actually logically constant.
+ PxVec3 carChassisLinVel;
+ //Properties of the rigid body - angular velocity
+ //This data is actually logically constant.
+ PxVec3 carChassisAngVel;
+
+ //Properties of the wheel shapes at the last sweep.
+ const PxQuat* wheelLocalPoseRotations;
+ const PxF32* wheelThetas;
+
+ //Simulation data for the 4 wheels being processed in processSuspTireWheels
+ //This data is actually logically constant.
+ const PxVehicleWheels4SimData* vehWheels4SimData;
+
+ //Dynamics data for the 4 wheels being processed in processSuspTireWheels
+ //This data is a mixture of logically and physically constant.
+ //We could update some of the data in vehWheels4DynData in processSuspTireWheels
+ //but we choose to do it after. By specifying the non-constant data members explicitly
+ //in ProcessSuspWheelTireOutputData we are able to more easily keep a track of the
+ //constant and non-constant data members. After processSuspTireWheels is complete
+ //we explicitly transfer the updated data in ProcessSuspWheelTireOutputData to vehWheels4DynData.
+ //Examples are low long and lat forward speed timers.
+ const PxVehicleWheels4DynData* vehWheels4DynData;
+
+ //Shaders to calculate the tire forces.
+ //This data is actually logically constant.
+ const PxVehicleTireForceCalculator4* vehWheels4TireForceCalculator;
+
+ //Filter function to filter tire load.
+ //This data is actually logically constant.
+ const PxVehicleTireLoadFilterData* vehWheels4TireLoadFilterData;
+
+ //How many of the 4 wheels are real wheels (eg a 6-wheeled car has a
+ //block of 4 wheels then a 2nd block of 4 wheels with only 2 active wheels)
+ //This data is actually logically constant.
+ PxU32 numActiveWheels;
+};
+
+struct ProcessSuspWheelTireOutputData
+{
+public:
+
+ ProcessSuspWheelTireOutputData()
+ {
+ PxMemZero(this, sizeof(ProcessSuspWheelTireOutputData));
+ for(PxU32 i=0;i<4;i++)
+ {
+ isInAir[i]=true;
+ tireSurfaceTypes[i]=PxU32(PxVehicleDrivableSurfaceType::eSURFACE_TYPE_UNKNOWN);
+ }
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////////////////////
+ //The following data is stored so that it may be later passed to PxVehicleWheelQueryResult
+ /////////////////////////////////////////////////////////////////////////////////////////////
+
+ //Raycast start [most recent raycast start coord or (0,0,0) if using a cached raycast]
+ PxVec3 suspLineStarts[4];
+ //Raycast start [most recent raycast direction or (0,0,0) if using a cached raycast]
+ PxVec3 suspLineDirs[4];
+ //Raycast start [most recent raycast length or 0 if using a cached raycast]
+ PxF32 suspLineLengths[4];
+ //False if wheel cannot touch the ground.
+ bool isInAir[4];
+ //Actor hit by most recent raycast, NULL if using a cached raycast.
+ PxActor* tireContactActors[4];
+ //Shape hit by most recent raycast, NULL if using a cached raycast.
+ PxShape* tireContactShapes[4];
+ //Material hit by most recent raycast, NULL if using a cached raycast.
+ PxMaterial* tireSurfaceMaterials[4];
+ //Surface type of material hit by most recent raycast, eSURFACE_TYPE_UNKNOWN if using a cached raycast.
+ PxU32 tireSurfaceTypes[4];
+ //Contact point of raycast against either fresh contact plane from fresh raycast or cached contact plane.
+ PxVec3 tireContactPoints[4];
+ //Contact normal of raycast against either fresh contact plane from fresh raycast or cached contact plane.
+ PxVec3 tireContactNormals[4];
+ //Friction experienced by tire (value from friction table for surface/tire type combos multiplied by friction vs slip graph)
+ PxF32 frictions[4];
+ //Jounce experienced by suspension against fresh or cached contact plane.
+ PxF32 jounces[4];
+ //Suspension force to be applied to rigid body.
+ PxF32 suspensionSpringForces[4];
+ //Longitudinal direction of tire in the ground contact plane.
+ PxVec3 tireLongitudinalDirs[4];
+ //Lateral direction of tire in the ground contact plane.
+ PxVec3 tireLateralDirs[4];
+ //Longitudinal slip.
+ PxF32 longSlips[4];
+ //Lateral slip.
+ PxF32 latSlips[4];
+
+ //Forward speed of rigid body along tire longitudinal direction at tire base.
+ //Used later to blend the integrated wheel rotation angle between rolling speed and computed speed
+ //when the wheel rotation speeds become unreliable at low forward speeds.
+ PxF32 forwardSpeeds[4];
+
+ //Torque to be applied to wheel as 1d rigid body. Taken from the longitudinal tire force.
+ //(Newton's 3rd law means the longitudinal tire force must have an equal and opposite force).
+ //(The lateral tire force is assumed to be absorbed by the suspension geometry).
+ PxF32 tireTorques[4];
+
+ //Force to be applied to rigid body (accumulated across all 4 wheels/tires/suspensions).
+ PxVec3 chassisForce;
+ //Torque to be applied to rigid body (accumulated across all 4 wheels/tires/suspensions).
+ PxVec3 chassisTorque;
+
+ //Updated time spend at low forward speed.
+ //Needs copied back to vehWheels4DynData
+ PxF32 newLowForwardSpeedTimers[4];
+ //Updated time spend at low lateral speed.
+ //Needs copied back to vehWheels4DynData
+ PxF32 newLowSideSpeedTimers[4];
+
+ //Constraint data for sticky tire constraints and suspension limit constraints.
+ //Needs copied back to vehWheels4DynData
+ PxVehicleConstraintShader::VehicleConstraintData vehConstraintData;
+
+ //Store the details of the raycast hit results so that they may be re-used
+ //next update in the event that no raycast is performed.
+ //If no raycast was performed then the cached values are just re-copied here
+ //so that they can be recycled without having to do further tests on whether
+ //raycasts were performed or not.
+ //Needs copied back to vehWheels4DynData after the last call to processSuspTireWheels.
+ //The union of cached hit data and susp raycast data means we don't want to overwrite the
+ //raycast data until we don't need it any more.
+ PxU32 cachedHitCounts[4];
+ PxVec4 cachedHitPlanes[4];
+ PxF32 cachedHitDistances[4];
+ PxF32 cachedFrictionMultipliers[4];
+ PxU16 cachedHitQueryTypes[4];
+
+ //Store the details of the force applied to any dynamic actor hit by wheel raycasts.
+ PxRigidDynamic* hitActors[4];
+ PxVec3 hitActorForces[4];
+ PxVec3 hitActorForcePositions[4];
+};
+
+
+
+////////////////////////////////////////////////////////////////////////////
+//Monster function to
+//1. compute the tire/susp forces
+//2. compute the torque to apply to the 1D rigid body wheel arising from the long tire force
+//3. process the sticky tire friction constraints
+// (monitor and increment the low long + lat speed timers, compute data for the sticky tire constraint if necessary)
+//4. process the suspension limit constraints
+// (monitor the suspension jounce versus the suspension travel limit, compute the data for the suspension limit constraint if necessary).
+//5. record the contact plane so that it may be re-used in future updates in the absence of fresh raycasts.
+//6. record telemetry data (if necessary) and record data for reporting such as hit material, hit normal etc.
+////////////////////////////////////////////////////////////////////////////
+
+
+void storeHit
+(const ProcessSuspWheelTireConstData& constData, const ProcessSuspWheelTireInputData& inputData,
+ const PxU16 hitQueryType,
+ const PxLocationHit& hit, const PxVec4& hitPlane,
+ const PxU32 i,
+ PxU32* hitCounts4,
+ PxF32* hitDistances4,
+ PxVec4* hitPlanes4,
+ PxF32* hitFrictionMultipliers4,
+ PxU16* hitQueryTypes4,
+ PxShape** hitContactShapes4,
+ PxRigidActor** hitContactActors4,
+ PxMaterial** hitContactMaterials4,
+ PxU32* hitSurfaceTypes4,
+ PxVec3* hitContactPoints4,
+ PxVec3* hitContactNormals4,
+ PxU32* cachedHitCounts,
+ PxVec4* cachedHitPlanes,
+ PxF32* cachedHitDistances,
+ PxF32* cachedFrictionMultipliers,
+ PxU16* cachedHitQueryTypes)
+{
+ //Hit count.
+ hitCounts4[i] = 1;
+
+ //Hit distance.
+ hitDistances4[i] = hit.distance;
+
+ //Hit plane.
+ hitPlanes4[i] = hitPlane;
+
+ //Hit friction.
+ PxU32 surfaceType = 0;
+ PxMaterial* material = NULL;
+ {
+ //Only get the material if the raycast started outside the hit shape.
+ material = (hit.distance != 0.0f) ? hit.shape->getMaterialFromInternalFaceIndex(hit.faceIndex) : NULL;
+ }
+ //Hash table for quick lookup of drivable surface type from material.
+ const PxVehicleDrivableSurfaceToTireFrictionPairs* PX_RESTRICT frictionPairs = constData.frictionPairs;
+ VehicleSurfaceTypeHashTable surfaceTypeHashTable(*constData.frictionPairs);
+ if (NULL != material)
+ {
+ surfaceType = surfaceTypeHashTable.get(material);
+ }
+ const PxVehicleTireData& tire = inputData.vehWheels4SimData->getTireData(i);
+ const PxF32 frictionMultiplier = frictionPairs->getTypePairFriction(surfaceType, tire.mType);
+ PX_ASSERT(frictionMultiplier >= 0);
+ hitFrictionMultipliers4[i] = frictionMultiplier;
+
+ //Hit type.
+ hitQueryTypes4[i] = hitQueryType;
+
+ //Hit report.
+ hitContactShapes4[i] = hit.shape;
+ hitContactActors4[i] = hit.actor;
+ hitContactMaterials4[i] = material;
+ hitSurfaceTypes4[i] = surfaceType;
+ hitContactPoints4[i] = hit.position;
+ hitContactNormals4[i] = hit.normal;
+
+ //When we're finished here we need to copy this back to the vehicle.
+ cachedHitCounts[i] = 1;
+ cachedHitPlanes[i] = hitPlane;
+ cachedHitDistances[i] = hit.distance;
+ cachedFrictionMultipliers[i] = frictionMultiplier;
+ cachedHitQueryTypes[i] = hitQueryType;
+}
+
+void processSuspTireWheels
+(const PxU32 startWheelIndex,
+ const ProcessSuspWheelTireConstData& constData, const ProcessSuspWheelTireInputData& inputData,
+ ProcessSuspWheelTireOutputData& outputData)
+{
+ PX_SIMD_GUARD; //tzRaw.normalize(); in computeTireDirs threw a denorm exception on osx
+
+#if PX_DEBUG_VEHICLE_ON
+ PX_ASSERT(0==(startWheelIndex & 3));
+#endif
+
+#if PX_DEBUG_VEHICLE_ON
+ zeroGraphDataWheels(startWheelIndex,PxVehicleWheelGraphChannel::eJOUNCE);
+ zeroGraphDataWheels(startWheelIndex,PxVehicleWheelGraphChannel::eSUSPFORCE);
+ zeroGraphDataWheels(startWheelIndex,PxVehicleWheelGraphChannel::eTIRELOAD);
+ zeroGraphDataWheels(startWheelIndex,PxVehicleWheelGraphChannel::eNORMALIZED_TIRELOAD);
+ zeroGraphDataWheels(startWheelIndex,PxVehicleWheelGraphChannel::eNORM_TIRE_LONG_FORCE);
+ zeroGraphDataWheels(startWheelIndex,PxVehicleWheelGraphChannel::eNORM_TIRE_LAT_FORCE);
+ zeroGraphDataWheels(startWheelIndex,PxVehicleWheelGraphChannel::eTIRE_LONG_SLIP);
+ zeroGraphDataWheels(startWheelIndex,PxVehicleWheelGraphChannel::eTIRE_LAT_SLIP);
+ zeroGraphDataWheels(startWheelIndex,PxVehicleWheelGraphChannel::eTIRE_FRICTION);
+#endif
+
+ //Unpack the logically constant data.
+ const PxVec3& gravity=constData.gravity;
+ const PxF32 timeFraction=constData.timeFraction;
+ const PxF32 timeStep=constData.subTimeStep;
+ const PxF32 recipTimeStep=constData.recipSubTimeStep;
+ const PxF32 recipGravityMagnitude=constData.recipGravityMagnitude;
+ const PxF32 gravityMagnitude=constData.gravityMagnitude;
+ const bool isTank=constData.isTank;
+ const PxF32 minLongSlipDenominator=constData.minLongSlipDenominator;
+
+ //Unpack the input data (physically constant data).
+ const PxVehicleWheels4SimData& wheelsSimData=*inputData.vehWheels4SimData;
+ const PxVehicleWheels4DynData& wheelsDynData=*inputData.vehWheels4DynData;
+ const PxVehicleTireForceCalculator4& tireForceCalculator=*inputData.vehWheels4TireForceCalculator;
+ const PxVehicleTireLoadFilterData& tireLoadFilterData=*inputData.vehWheels4TireLoadFilterData;
+ //More constant data describing the 4 wheels under consideration.
+ const PxF32* PX_RESTRICT tireRestLoads=wheelsSimData.getTireRestLoadsArray();
+ const PxF32* PX_RESTRICT recipTireRestLoads=wheelsSimData.getRecipTireRestLoadsArray();
+ //Compute the right direction for later.
+ const PxTransform& carChassisTrnsfm=inputData.carChassisTrnsfm;
+ const PxVec3 latDir=inputData.carChassisTrnsfm.rotate(gRight);
+ //Unpack the linear and angular velocity of the rigid body.
+ const PxVec3& carChassisLinVel=inputData.carChassisLinVel;
+ const PxVec3& carChassisAngVel=inputData.carChassisAngVel;
+ //Wheel local poses
+ const PxQuat* PX_RESTRICT wheelLocalPoseRotations = inputData.wheelLocalPoseRotations;
+ const PxF32* PX_RESTRICT wheelThetas = inputData.wheelThetas;
+ //Inputs (accel, steer, brake).
+ const bool isIntentionToAccelerate=inputData.isIntentionToAccelerate;
+ const PxF32* steerAngles=inputData.steerAngles;
+ const bool* isBrakeApplied=inputData.isBrakeApplied;
+ const bool* isAccelApplied=inputData.isAccelApplied;
+ //Disabled/enabled wheel states.
+ const bool* activeWheelStates=inputData.activeWheelStates;
+ //Current low forward/side speed timers. Note that the updated timers
+ //are stored in newLowForwardSpeedTimers and newLowSideSpeedTimers.
+ const PxF32* PX_RESTRICT lowForwardSpeedTimers=wheelsDynData.mTireLowForwardSpeedTimers;
+ const PxF32* PX_RESTRICT lowSideSpeedTimers=wheelsDynData.mTireLowSideSpeedTimers;
+ //Susp jounces and speeds from previous call to processSuspTireWheels.
+ const PxF32* PX_RESTRICT prevJounces=wheelsDynData.mJounces;
+
+ //Unpack the output data (the data we are going to compute).
+ //Start with the data stored for reporting to PxVehicleWheelQueryResult.
+ //PxVec3* suspLineStarts=outputData.suspLineStarts;
+ //PxVec3* suspLineDirs=outputData.suspLineDirs;
+ //PxF32* suspLineLengths=outputData.suspLineLengths;
+ bool* isInAirs=outputData.isInAir;
+ PxActor** tireContactActors=outputData.tireContactActors;
+ PxShape** tireContactShapes=outputData.tireContactShapes;
+ PxMaterial** tireSurfaceMaterials=outputData.tireSurfaceMaterials;
+ PxU32* tireSurfaceTypes=outputData.tireSurfaceTypes;
+ PxVec3* tireContactPoints=outputData.tireContactPoints;
+ PxVec3* tireContactNormals=outputData.tireContactNormals;
+ PxF32* frictions=outputData.frictions;
+ PxF32* jounces=outputData.jounces;
+ PxF32* suspensionSpringForces=outputData.suspensionSpringForces;
+ PxVec3* tireLongitudinalDirs=outputData.tireLongitudinalDirs;
+ PxVec3* tireLateralDirs=outputData.tireLateralDirs;
+ PxF32* longSlips=outputData.longSlips;
+ PxF32* latSlips=outputData.latSlips;
+ //Now unpack the forward speeds that are used later to blend the integrated wheel
+ //rotation angle between rolling speed and computed speed when the wheel rotation
+ //speeds become unreliable at low forward speeds.
+ PxF32* forwardSpeeds=outputData.forwardSpeeds;
+ //Unpack the real outputs of this function (wheel torques to apply to 1d rigid body wheel and forces/torques
+ //to apply to 3d rigid body chassis).
+ PxF32* tireTorques=outputData.tireTorques;
+ PxVec3& chassisForce=outputData.chassisForce;
+ PxVec3& chassisTorque=outputData.chassisTorque;
+ //Unpack the low speed timers that will be computed.
+ PxF32* newLowForwardSpeedTimers=outputData.newLowForwardSpeedTimers;
+ PxF32* newLowSideSpeedTimers=outputData.newLowSideSpeedTimers;
+ //Unpack the constraint data for suspensions limit and sticky tire constraints.
+ //Susp limits.
+ bool* suspLimitActiveFlags=outputData.vehConstraintData.mSuspLimitData.mActiveFlags;
+ PxVec3* suspLimitDirs=outputData.vehConstraintData.mSuspLimitData.mDirs;
+ PxVec3* suspLimitCMOffsets=outputData.vehConstraintData.mSuspLimitData.mCMOffsets;
+ PxF32* suspLimitErrors=outputData.vehConstraintData.mSuspLimitData.mErrors;
+ //Longitudinal sticky tires.
+ bool* stickyTireForwardActiveFlags=outputData.vehConstraintData.mStickyTireForwardData.mActiveFlags;
+ PxVec3* stickyTireForwardDirs=outputData.vehConstraintData.mStickyTireForwardData.mDirs;
+ PxVec3* stickyTireForwardCMOffsets=outputData.vehConstraintData.mStickyTireForwardData.mCMOffsets;
+ PxF32* stickyTireForwardTargetSpeeds=outputData.vehConstraintData.mStickyTireForwardData.mTargetSpeeds;
+ //Lateral sticky tires.
+ bool* stickyTireSideActiveFlags=outputData.vehConstraintData.mStickyTireSideData.mActiveFlags;
+ PxVec3* stickyTireSideDirs=outputData.vehConstraintData.mStickyTireSideData.mDirs;
+ PxVec3* stickyTireSideCMOffsets=outputData.vehConstraintData.mStickyTireSideData.mCMOffsets;
+ PxF32* stickyTireSideTargetSpeeds=outputData.vehConstraintData.mStickyTireSideData.mTargetSpeeds;
+ //Hit data. Store the contact data so it can be reused.
+ PxU32* cachedHitCounts=outputData.cachedHitCounts;
+ PxVec4* cachedHitPlanes=outputData.cachedHitPlanes;
+ PxF32* cachedHitDistances=outputData.cachedHitDistances;
+ PxF32* cachedFrictionMultipliers=outputData.cachedFrictionMultipliers;
+ PxU16* cachedHitQueryTypes=outputData.cachedHitQueryTypes;
+ //Hit actor data.
+ PxRigidDynamic** hitActors=outputData.hitActors;
+ PxVec3* hitActorForces=outputData.hitActorForces;
+ PxVec3* hitActorForcePositions=outputData.hitActorForcePositions;
+
+ //Compute all the hit data (counts, distances, planes, frictions, actors, shapes, materials etc etc).
+ //If we just did a raycast/sweep then we need to compute all this from the hit reports.
+ //If we are using cached raycast/sweep results then just copy the cached hit result data.
+ PxU32 hitCounts4[4];
+ PxF32 hitDistances4[4];
+ PxVec4 hitPlanes4[4];
+ PxF32 hitFrictionMultipliers4[4];
+ PxU16 hitQueryTypes4[4];
+ PxShape* hitContactShapes4[4];
+ PxRigidActor* hitContactActors4[4];
+ PxMaterial* hitContactMaterials4[4];
+ PxU32 hitSurfaceTypes4[4];
+ PxVec3 hitContactPoints4[4];
+ PxVec3 hitContactNormals4[4];
+ const PxRaycastQueryResult* PX_RESTRICT raycastResults=inputData.vehWheels4DynData->mRaycastResults;
+ const PxSweepQueryResult* PX_RESTRICT sweepResults=inputData.vehWheels4DynData->mSweepResults;
+ if(raycastResults || sweepResults)
+ {
+ const PxU16 queryType = raycastResults ? 0u : 1u;
+
+ //If we have a blocking hit then always take that.
+ //If we don't have a blocking hit then search for the "best" hit from all the touches.
+ for(PxU32 i=0;i<inputData.numActiveWheels;i++)
+ {
+ //Test that raycasts issue blocking hits.
+ PX_CHECK_AND_RETURN(!raycastResults || (0 == raycastResults[i].nbTouches), "Raycasts must generate blocking hits");
+
+ PxU32 hitCount = 0;
+ if ((raycastResults && raycastResults[i].hasBlock) || (sweepResults && sweepResults[i].hasBlock))
+ {
+ //We have a blocking it so use that.
+ const PxLocationHit& hit = raycastResults ? static_cast<const PxLocationHit&>(raycastResults[i].block) : static_cast<const PxLocationHit&>(sweepResults[i].block);
+
+ //Test that the hit actor isn't the vehicle itself.
+ PX_CHECK_AND_RETURN(constData.vehActor != hit.actor, "Vehicle raycast has hit itself. Please check the filter data to avoid this.");
+
+ //Reject if the sweep started inside the hit shape.
+ if (hit.distance != 0)
+ {
+ //Compute the plane of the hit.
+ const PxVec3 hitPos = hit.position;
+ const PxVec3 hitNorm = hit.normal;
+ const PxF32 hitD = -hitNorm.dot(hitPos);
+ PxVec4 hitPlane(hitNorm, hitD);
+
+ //Store the hit data in the various arrays.
+ storeHit(constData, inputData,
+ queryType,
+ hit, hitPlane,
+ i,
+ hitCounts4,
+ hitDistances4,
+ hitPlanes4,
+ hitFrictionMultipliers4,
+ hitQueryTypes4,
+ hitContactShapes4,
+ hitContactActors4,
+ hitContactMaterials4,
+ hitSurfaceTypes4,
+ hitContactPoints4,
+ hitContactNormals4,
+ cachedHitCounts,
+ cachedHitPlanes,
+ cachedHitDistances,
+ cachedFrictionMultipliers,
+ cachedHitQueryTypes);
+
+ hitCount = 1;
+ }
+ }
+ else if (sweepResults && sweepResults[i].nbTouches)
+ {
+ //We need wheel info so that we can analyse the hit and reject/accept it.
+ //Get what we need now.
+ const PxVehicleWheelData& wheel = wheelsSimData.getWheelData(i);
+ const PxVehicleSuspensionData& susp = wheelsSimData.getSuspensionData(i);
+ const PxVec3& bodySpaceWheelCentreOffset = wheelsSimData.getWheelCentreOffset(i);
+ const PxVec3& bodySpaceSuspTravelDir = wheelsSimData.getSuspTravelDirection(i);
+ const PxQuat& wheelLocalPoseRotation = wheelLocalPoseRotations[i];
+ const PxF32 wheelTheta = wheelThetas[i];
+ const PxF32 width = wheel.mWidth;
+ const PxF32 radius = wheel.mRadius;
+ const PxF32 maxBounce = susp.mMaxCompression;
+
+ //Compute the global pose of the wheel at zero jounce.
+ PxTransform suspPose;
+ PxVec3 suspDir;
+ computeSuspensionSweep(
+ carChassisTrnsfm,
+ wheelLocalPoseRotation, wheelTheta,
+ bodySpaceWheelCentreOffset, bodySpaceSuspTravelDir, 0.0f, 0.0f,
+ suspPose, suspDir);
+
+ //Iterate over all touches and cache the deepest hit that we accept.
+ PxF32 bestTouchDistance = -PX_MAX_F32;
+ for (PxU32 j = 0; j < sweepResults[i].nbTouches; j++)
+ {
+ //Get the next candidate hit.
+ const PxLocationHit& hit = sweepResults[i].touches[j];
+
+ //Test that the hit actor isn't the vehicle itself.
+ PX_CHECK_AND_RETURN(constData.vehActor != hit.actor, "Vehicle raycast has hit itself. Please check the filter data to avoid this.");
+
+ //Reject if the sweep started inside the hit shape.
+ if (hit.distance != 0.0f)
+ {
+ //Compute the plane of the hit.
+ const PxVec3 hitPos = hit.position;
+ const PxVec3 hitNorm = hit.normal;
+ const PxF32 hitD = -hitNorm.dot(hitPos);
+ PxVec4 hitPlane(hitNorm, hitD);
+
+ //Intersect the wheel disc with the hit plane and compute the jounce required to move the wheel free of the hit plane.
+ PxF32 dx;
+ PxVec3 wheelBottomPos;
+ bool successIntersection =
+ intersectCylinderPlane
+ (suspPose, suspDir,
+ width, radius, maxBounce,
+ hitPlane,
+ true,
+ dx, wheelBottomPos);
+
+ //If we accept the intersection and it requires more jounce than previously encountered then
+ //store the hit.
+ if (successIntersection && dx > bestTouchDistance)
+ {
+ storeHit(constData, inputData,
+ queryType,
+ hit, hitPlane,
+ i,
+ hitCounts4,
+ hitDistances4,
+ hitPlanes4,
+ hitFrictionMultipliers4,
+ hitQueryTypes4,
+ hitContactShapes4,
+ hitContactActors4,
+ hitContactMaterials4,
+ hitSurfaceTypes4,
+ hitContactPoints4,
+ hitContactNormals4,
+ cachedHitCounts,
+ cachedHitPlanes,
+ cachedHitDistances,
+ cachedFrictionMultipliers,
+ cachedHitQueryTypes);
+
+ bestTouchDistance = dx;
+
+ hitCount = 1;
+ }
+ }
+ }
+ }
+
+ if(0 == hitCount)
+ {
+ hitCounts4[i]=0;
+ hitDistances4[i]=0;
+ hitPlanes4[i]=PxVec4(0,0,0,0);
+ hitFrictionMultipliers4[i]=0;
+ hitQueryTypes4[i]=0u;
+ hitContactShapes4[i]=NULL;
+ hitContactActors4[i]=NULL;
+ hitContactMaterials4[i]=NULL;
+ hitSurfaceTypes4[i]=PxU32(PxVehicleDrivableSurfaceType::eSURFACE_TYPE_UNKNOWN);
+ hitContactPoints4[i]=PxVec3(0,0,0);
+ hitContactNormals4[i]=PxVec3(0,0,0);
+
+ //When we're finished here we need to copy this back to the vehicle.
+ cachedHitCounts[i]=0;
+ cachedHitPlanes[i]=PxVec4(0,0,0,0);
+ cachedHitDistances[i]=0;
+ cachedFrictionMultipliers[i]=0;
+ cachedHitQueryTypes[i] = 0u;
+ }
+ }
+ }
+ else
+ {
+ //If we have no sq results then we must have a cached raycast hit result.
+ const PxVehicleWheels4DynData::CachedSuspLineSceneQuerytHitResult& cachedHitResult =
+ reinterpret_cast<const PxVehicleWheels4DynData::CachedSuspLineSceneQuerytHitResult&>(inputData.vehWheels4DynData->mQueryOrCachedHitResults);
+
+ for(PxU32 i=0;i<inputData.numActiveWheels;i++)
+ {
+ hitCounts4[i]=cachedHitResult.mCounts[i];
+ hitDistances4[i]=cachedHitResult.mDistances[i];
+ hitPlanes4[i]=cachedHitResult.mPlanes[i];
+ hitFrictionMultipliers4[i]=cachedHitResult.mFrictionMultipliers[i];
+ hitQueryTypes4[i] = cachedHitResult.mQueryTypes[i];
+ hitContactShapes4[i]=NULL;
+ hitContactActors4[i]=NULL;
+ hitContactMaterials4[i]=NULL;
+ hitSurfaceTypes4[i]=PxU32(PxVehicleDrivableSurfaceType::eSURFACE_TYPE_UNKNOWN);
+ hitContactPoints4[i]=PxVec3(0,0,0);
+ hitContactNormals4[i]=PxVec3(0,0,0);
+
+ //When we're finished here we need to copy this back to the vehicle.
+ cachedHitCounts[i]=cachedHitResult.mCounts[i];
+ cachedHitPlanes[i]=cachedHitResult.mPlanes[i];
+ cachedHitDistances[i]=cachedHitResult.mDistances[i];
+ cachedFrictionMultipliers[i]=cachedHitResult.mFrictionMultipliers[i];
+ cachedHitQueryTypes[i]=cachedHitResult.mQueryTypes[i];
+ }
+ }
+
+ //Iterate over all 4 wheels.
+ for(PxU32 i=0;i<4;i++)
+ {
+ //Constant data of the ith wheel.
+ const PxVehicleWheelData& wheel=wheelsSimData.getWheelData(i);
+ const PxVehicleSuspensionData& susp=wheelsSimData.getSuspensionData(i);
+ const PxVehicleTireData& tire=wheelsSimData.getTireData(i);
+ const PxVec3& bodySpaceWheelCentreOffset=wheelsSimData.getWheelCentreOffset(i);
+ const PxVec3& bodySpaceSuspTravelDir=wheelsSimData.getSuspTravelDirection(i);
+
+ //Take a copy of the low forward/side speed timer of the ith wheel (time spent at low forward/side speed)
+ //Do this so we can quickly tell if the low/side forward speed timer changes.
+ newLowForwardSpeedTimers[i]=lowForwardSpeedTimers[i];
+ newLowSideSpeedTimers[i]=lowSideSpeedTimers[i];
+
+ //Reset the graph of the jounce to max droop.
+ //This will get updated as we learn more about the suspension.
+#if PX_DEBUG_VEHICLE_ON
+ updateGraphDataSuspJounce(startWheelIndex, i,-susp.mMaxDroop);
+#endif
+
+ //Reset the jounce to max droop.
+ //This will get updated as we learn more about the suspension and tire.
+ PxF32 jounce=-susp.mMaxDroop;
+ jounces[i]=jounce;
+
+ //Deactivate the sticky tire and susp limit constraint.
+ //These will get updated as we learn more about the suspension and tire.
+ suspLimitActiveFlags[i]=false;
+ suspLimitErrors[i]=0.0f;
+ stickyTireForwardActiveFlags[i]=false;
+ stickyTireForwardTargetSpeeds[i]=0;
+ stickyTireSideActiveFlags[i]=false;
+ stickyTireSideTargetSpeeds[i]=0;
+
+ //The vehicle is in the air until we know otherwise.
+ isInAirs[i]=true;
+
+ //If there has been a hit then compute the suspension force and tire load.
+ //Ignore the hit if the raycast starts inside the hit shape (eg wheel completely underneath surface of a heightfield).
+ const bool activeWheelState=activeWheelStates[i];
+ const PxU32 numHits=hitCounts4[i];
+ const PxVec3 hitNorm(hitPlanes4[i].x, hitPlanes4[i].y,hitPlanes4[i].z);
+ const PxVec3 w = carChassisTrnsfm.q.rotate(bodySpaceSuspTravelDir);
+ if(activeWheelState && numHits > 0 && hitDistances4[i] != 0.0f && hitNorm.dot(w) < 0.0f)
+ {
+ //Get the friction multiplier from the combination of surface type and tire type.
+ const PxF32 frictionMultiplier=hitFrictionMultipliers4[i];
+ PX_ASSERT(frictionMultiplier>=0);
+
+ PxF32 dx;
+ PxVec3 wheelBottomPos;
+ bool successIntersection = true;
+ if(0 == hitQueryTypes4[i])
+ {
+ successIntersection = intersectRayPlane
+ (carChassisTrnsfm,
+ bodySpaceWheelCentreOffset, bodySpaceSuspTravelDir, wheel.mWidth, wheel.mRadius, susp.mMaxCompression,
+ hitPlanes4[i],
+ dx, wheelBottomPos);
+ }
+ else
+ {
+ PX_ASSERT(1 == hitQueryTypes4[i]);
+ successIntersection = intersectCylinderPlane
+ (carChassisTrnsfm,
+ wheelLocalPoseRotations[i], wheelThetas[i],
+ bodySpaceWheelCentreOffset, bodySpaceSuspTravelDir, wheel.mWidth, wheel.mRadius, susp.mMaxCompression,
+ hitPlanes4[i],
+ false,
+ dx, wheelBottomPos);
+ }
+
+ //If the spring is elongated past its max droop then the wheel isn't touching the ground.
+ //In this case the spring offers zero force and provides no support for the chassis/sprung mass.
+ //Only carry on computing the spring force if the wheel is touching the ground.
+ PX_ASSERT(susp.mMaxCompression>=0);
+ PX_ASSERT(susp.mMaxDroop>=0);
+ if(dx > -susp.mMaxDroop && successIntersection)
+ {
+ //We can record the hit shape, hit actor, hit material, hit surface type, hit point, and hit normal now because we've got a hit.
+ tireContactShapes[i]=hitContactShapes4[i];
+ tireContactActors[i]=hitContactActors4[i];
+ tireSurfaceMaterials[i]=hitContactMaterials4[i];
+ tireSurfaceTypes[i]=hitSurfaceTypes4[i];
+ tireContactPoints[i]=hitContactPoints4[i];
+ tireContactNormals[i]=hitContactNormals4[i];
+
+ //We know that the vehicle is not in the air.
+ isInAirs[i]=false;
+
+ //Clamp the spring compression so that it is never greater than the max bounce.
+ //Apply the susp limit constraint if the spring compression is greater than the max bounce.
+ suspLimitErrors[i] = dx - susp.mMaxCompression;
+ suspLimitActiveFlags[i] = (dx > susp.mMaxCompression);
+ suspLimitCMOffsets[i] = bodySpaceWheelCentreOffset;
+ suspLimitDirs[i] = bodySpaceSuspTravelDir;
+ jounce=PxMin(dx,susp.mMaxCompression);
+
+ //Store the jounce (having a local copy avoids lhs).
+ jounces[i]=jounce;
+
+ //Store the jounce in the graph.
+#if PX_DEBUG_VEHICLE_ON
+ updateGraphDataSuspJounce(startWheelIndex, i,jounce);
+#endif
+
+ //Compute the speed of the rigid body along the suspension travel dir at the
+ //bottom of the wheel.
+ const PxVec3 r=wheelBottomPos-carChassisTrnsfm.p;
+ PxVec3 wheelBottomVel=carChassisLinVel;
+ wheelBottomVel+=carChassisAngVel.cross(r);
+
+ //Modify the relative velocity at the wheel contact point if the hit actor is a dynamic.
+ PxRigidDynamic* dynamicHitActor=NULL;
+ PxVec3 hitActorVelocity(0,0,0);
+ if(hitContactActors4[i] && ((dynamicHitActor = hitContactActors4[i]->is<PxRigidDynamic>()) != NULL))
+ {
+ hitActorVelocity = PxRigidBodyExt::getVelocityAtPos(*dynamicHitActor,wheelBottomPos);
+ wheelBottomVel -= hitActorVelocity;
+ }
+
+ //Get the speed of the jounce.
+ const PxF32 jounceSpeed = (PX_MAX_F32 != prevJounces[i]) ? (jounce - prevJounces[i])*recipTimeStep : 0.0f;
+
+ //Decompose gravity into a term along w and a term perpendicular to w
+ //gravity = w*alpha + T*beta
+ //where T is a unit vector perpendicular to w; alpha and beta are scalars.
+ //The vector w*alpha*mass is the component of gravitational force that acts along the spring direction.
+ //The vector T*beta*mass is the component of gravitational force that will be resisted by the spring
+ //because the spring only supports a single degree of freedom along w.
+ //We only really need to know T*beta so don't bother calculating T or beta.
+ const PxF32 alpha = PxMax(0.0f, gravity.dot(w));
+ const PxVec3 TTimesBeta = (0.0f != alpha) ? gravity - w*alpha : PxVec3(0,0,0);
+ //Compute the magnitude of the force along w.
+ PxF32 suspensionForceW =
+ PxMax(0.0f,
+ susp.mSprungMass*alpha + //force to support sprung mass at zero jounce
+ susp.mSpringStrength*jounce); //linear spring
+ suspensionForceW += susp.mSpringDamperRate*jounceSpeed; //damping
+ //Compute the total force acting on the suspension.
+ //Remember that the spring force acts along -w.
+ //Remember to account for the term perpendicular to w and that it acts along -TTimesBeta
+ PxF32 suspensionForceMag = hitNorm.dot(-w*suspensionForceW - TTimesBeta*susp.mSprungMass);
+
+ //Apply the opposite force to the hit object.
+ //Clamp suspensionForceMag if required.
+ if (dynamicHitActor && !(dynamicHitActor->getRigidBodyFlags() & PxRigidBodyFlag::eKINEMATIC))
+ {
+ const PxF32 dynamicActorInvMass = dynamicHitActor->getInvMass();
+ const PxF32 dynamicActorMass = dynamicHitActor->getMass();
+ const PxF32 forceSign = computeSign(suspensionForceMag);
+ const PxF32 forceMag = PxAbs(suspensionForceMag);
+ const PxF32 clampedAccelMag = PxMin(forceMag*dynamicActorInvMass, gMaxHitActorAcceleration);
+ const PxF32 clampedForceMag = clampedAccelMag*dynamicActorMass*forceSign;
+ PX_ASSERT(clampedForceMag*suspensionForceMag >= 0.0f);
+
+ suspensionForceMag = clampedForceMag;
+
+ hitActors[i] = dynamicHitActor;
+ hitActorForces[i] = hitNorm*(-clampedForceMag*timeFraction);
+ hitActorForcePositions[i] = hitContactPoints4[i];
+ }
+
+ //Store the spring force now (having a local copy avoids lhs).
+ suspensionSpringForces[i] = suspensionForceMag;
+
+ //Store the spring force in the graph.
+#if PX_DEBUG_VEHICLE_ON
+ updateGraphDataSuspForce(startWheelIndex, i, suspensionForceMag);
+#endif
+
+ //Suspension force can be computed now.
+ const PxVec3 suspensionForce = hitNorm*suspensionForceMag;
+
+ //Torque from spring force.
+ const PxVec3 suspForceCMOffset = carChassisTrnsfm.rotate(wheelsSimData.getSuspForceAppPointOffset(i));
+ const PxVec3 suspensionTorque = suspForceCMOffset.cross(suspensionForce);
+
+ //Add the suspension force/torque to the chassis force/torque.
+ chassisForce+=suspensionForce;
+ chassisTorque+=suspensionTorque;
+
+ //Now compute the tire load.
+ const PxF32 tireLoad = suspensionForceMag;
+
+ //Normalize the tire load
+ //Now work out the normalized tire load.
+ const PxF32 normalisedTireLoad=tireLoad*recipGravityMagnitude*recipTireRestLoads[i];
+ //Filter the normalized tire load and compute the filtered tire load too.
+ const PxF32 filteredNormalisedTireLoad=computeFilteredNormalisedTireLoad(tireLoadFilterData,normalisedTireLoad);
+ const PxF32 filteredTireLoad=filteredNormalisedTireLoad*gravityMagnitude*tireRestLoads[i];
+
+#if PX_DEBUG_VEHICLE_ON
+ updateGraphDataTireLoad(startWheelIndex,i,filteredTireLoad);
+ updateGraphDataNormTireLoad(startWheelIndex,i,filteredNormalisedTireLoad);
+#endif
+
+ //Compute the lateral and longitudinal tire axes in the ground plane.
+ PxVec3 tireLongDir;
+ PxVec3 tireLatDir;
+ computeTireDirs(latDir,hitNorm,steerAngles[i],tireLongDir,tireLatDir);
+
+ //Store the tire long and lat dirs now (having a local copy avoids lhs).
+ tireLongitudinalDirs[i]= tireLongDir;
+ tireLateralDirs[i]=tireLatDir;
+
+ //Now compute the speeds along each of the tire axes.
+ const PxF32 tireLongSpeed=wheelBottomVel.dot(tireLongDir);
+ const PxF32 tireLatSpeed=wheelBottomVel.dot(tireLatDir);
+
+ //Store the forward speed (having a local copy avoids lhs).
+ forwardSpeeds[i]=tireLongSpeed;
+
+ //Now compute the slips along each axes.
+ const bool hasAccel=isAccelApplied[i];
+ const bool hasBrake=isBrakeApplied[i];
+ const PxF32 wheelOmega=wheelsDynData.mWheelSpeeds[i];
+ const PxF32 wheelRadius=wheel.mRadius;
+ PxF32 longSlip;
+ PxF32 latSlip;
+ computeTireSlips
+ (tireLongSpeed,tireLatSpeed,wheelOmega,wheelRadius,minLongSlipDenominator,
+ hasAccel,hasBrake,
+ isTank,
+ longSlip,latSlip);
+
+ //Store the lat and long slip (having local copies avoids lhs).
+ longSlips[i]=longSlip;
+ latSlips[i]=latSlip;
+
+ //Camber angle.
+ PxF32 camber=susp.mCamberAtRest;
+ if(jounce>0)
+ {
+ camber += jounce*susp.mCamberAtMaxCompression*susp.getRecipMaxCompression();
+ }
+ else
+ {
+ camber -= jounce*susp.mCamberAtMaxDroop*susp.getRecipMaxDroop();
+ }
+
+ //Compute the friction that will be experienced by the tire.
+ PxF32 friction;
+ computeTireFriction(tire,longSlip,frictionMultiplier,friction);
+
+ //Store the friction (having a local copy avoids lhs).
+ frictions[i]=friction;
+
+ if(filteredTireLoad*frictionMultiplier>0)
+ {
+ //Either tire forces or sticky tire friction constraint will be applied here.
+ const PxVec3 tireForceCMOffset = carChassisTrnsfm.rotate(wheelsSimData.getTireForceAppPointOffset(i));
+
+ PxF32 newLowForwardSpeedTimer;
+ {
+ //check the accel value here
+ //Update low forward speed timer.
+ const PxF32 recipWheelRadius=wheel.getRecipRadius();
+ newLowForwardSpeedTimer=newLowForwardSpeedTimers[i];
+ updateLowForwardSpeedTimer(tireLongSpeed,wheelOmega,wheelRadius,recipWheelRadius,isIntentionToAccelerate,timeStep,newLowForwardSpeedTimer);
+
+ //Activate sticky tire forward friction constraint if required.
+ //If sticky tire friction is active then set the longitudinal slip to zero because
+ //the sticky tire constraint will take care of the longitudinal component of motion.
+ bool stickyTireForwardActiveFlag=false;
+ PxF32 stickyTireForwardTargetSpeed=0.0f;
+ activateStickyFrictionForwardConstraint(tireLongSpeed,wheelOmega,newLowForwardSpeedTimer,isIntentionToAccelerate,stickyTireForwardActiveFlag,stickyTireForwardTargetSpeed);
+ stickyTireForwardTargetSpeed += hitActorVelocity.dot(tireLongDir);
+
+ //Store the sticky tire data (having local copies avoids lhs).
+ newLowForwardSpeedTimers[i] = newLowForwardSpeedTimer;
+ stickyTireForwardActiveFlags[i]=stickyTireForwardActiveFlag;
+ stickyTireForwardTargetSpeeds[i]=stickyTireForwardTargetSpeed;
+ stickyTireForwardDirs[i]=tireLongDir;
+ stickyTireForwardCMOffsets[i]=tireForceCMOffset;
+
+ //Deactivate the long slip if sticky tire constraint is active.
+ longSlip=(!stickyTireForwardActiveFlag ? longSlip : 0.0f);
+
+ //Store the long slip (having local copies avoids lhs).
+ longSlips[i]=longSlip;
+ }
+
+ PxF32 newLowSideSpeedTimer;
+ {
+ //check the accel value here
+ //Update low side speed timer.
+ newLowSideSpeedTimer=newLowSideSpeedTimers[i];
+ updateLowSideSpeedTimer(tireLatSpeed,isIntentionToAccelerate,timeStep,newLowSideSpeedTimer);
+
+ //Activate sticky tire side friction constraint if required.
+ //If sticky tire friction is active then set the lateral slip to zero because
+ //the sticky tire constraint will take care of the lateral component of motion.
+ bool stickyTireSideActiveFlag=false;
+ PxF32 stickyTireSideTargetSpeed=0.0f;
+ activateStickyFrictionSideConstraint(tireLatSpeed,newLowForwardSpeedTimer,newLowSideSpeedTimer,isIntentionToAccelerate,stickyTireSideActiveFlag,stickyTireSideTargetSpeed);
+ stickyTireSideTargetSpeed += hitActorVelocity.dot(tireLatDir);
+
+ //Store the sticky tire data (having local copies avoids lhs).
+ newLowSideSpeedTimers[i] = newLowSideSpeedTimer;
+ stickyTireSideActiveFlags[i]=stickyTireSideActiveFlag;
+ stickyTireSideTargetSpeeds[i]=stickyTireSideTargetSpeed;
+ stickyTireSideDirs[i]=tireLatDir;
+ stickyTireSideCMOffsets[i]=tireForceCMOffset;
+
+ //Deactivate the lat slip if sticky tire constraint is active.
+ latSlip=(!stickyTireSideActiveFlag ? latSlip : 0.0f);
+
+ //Store the long slip (having local copies avoids lhs).
+ latSlips[i]=latSlip;
+ }
+
+ //Compute the various tire torques.
+ PxF32 wheelTorque=0;
+ PxF32 tireLongForceMag=0;
+ PxF32 tireLatForceMag=0;
+ PxF32 tireAlignMoment=0;
+ const PxF32 restTireLoad=gravityMagnitude*tireRestLoads[i];
+ const PxF32 recipWheelRadius=wheel.getRecipRadius();
+ tireForceCalculator.mShader(
+ tireForceCalculator.mShaderData[i],
+ friction,
+ longSlip,latSlip,camber,
+ wheelOmega,wheelRadius,recipWheelRadius,
+ restTireLoad,filteredNormalisedTireLoad,filteredTireLoad,
+ gravityMagnitude, recipGravityMagnitude,
+ wheelTorque,tireLongForceMag,tireLatForceMag,tireAlignMoment);
+
+ //Store the tire torque ((having a local copy avoids lhs).
+ tireTorques[i]=wheelTorque;
+
+ //Apply the torque to the chassis.
+ //Compute the tire force to apply to the chassis.
+ const PxVec3 tireLongForce=tireLongDir*tireLongForceMag;
+ const PxVec3 tireLatForce=tireLatDir*tireLatForceMag;
+ const PxVec3 tireForce=tireLongForce+tireLatForce;
+ //Compute the torque to apply to the chassis.
+ const PxVec3 tireTorque=tireForceCMOffset.cross(tireForce);
+ //Add all the forces/torques together.
+ chassisForce+=tireForce;
+ chassisTorque+=tireTorque;
+
+ //Graph all the data we just computed.
+#if PX_DEBUG_VEHICLE_ON
+ if(gCarTireForceAppPoints)
+ gCarTireForceAppPoints[i]=carChassisTrnsfm.p + tireForceCMOffset;
+ if(gCarSuspForceAppPoints)
+ gCarSuspForceAppPoints[i]=carChassisTrnsfm.p + suspForceCMOffset;
+
+ if(gCarWheelGraphData[0])
+ {
+ updateGraphDataNormLongTireForce(startWheelIndex, i, PxAbs(tireLongForceMag)*normalisedTireLoad/tireLoad);
+ updateGraphDataNormLatTireForce(startWheelIndex, i, PxAbs(tireLatForceMag)*normalisedTireLoad/tireLoad);
+ updateGraphDataNormTireAligningMoment(startWheelIndex, i, tireAlignMoment*normalisedTireLoad/tireLoad);
+ updateGraphDataLongTireSlip(startWheelIndex, i,longSlips[i]);
+ updateGraphDataLatTireSlip(startWheelIndex, i,latSlips[i]);
+ updateGraphDataTireFriction(startWheelIndex, i,frictions[i]);
+ }
+#endif
+ }//filteredTireLoad*frictionMultiplier>0
+ }//if(dx > -susp.mMaxCompression)
+ }//if(numHits>0)
+ }//i
+}
+
+
+void procesAntiRollSuspension
+(const PxVehicleWheelsSimData& wheelsSimData,
+ const PxTransform& carChassisTransform, const PxWheelQueryResult* wheelQueryResults,
+ PxVec3& chassisTorque)
+{
+ const PxU32 numAntiRollBars = wheelsSimData.getNbAntiRollBars();
+ for(PxU32 i = 0; i < numAntiRollBars; i++)
+ {
+ const PxVehicleAntiRollBarData& antiRoll = wheelsSimData.getAntiRollBarData(i);
+ const PxU32 w0 = antiRoll.mWheel0;
+ const PxU32 w1 = antiRoll.mWheel1;
+
+ //At least one wheel must be on the ground for the anti-roll to work.
+ const bool w0InAir = wheelQueryResults[w0].isInAir;
+ const bool w1InAir = wheelQueryResults[w1].isInAir;
+ if(!w0InAir || !w1InAir)
+ {
+ //Compute the difference in jounce and compute the force.
+ const PxF32 w0Jounce = wheelQueryResults[w0].suspJounce;
+ const PxF32 w1Jounce = wheelQueryResults[w1].suspJounce;
+ const PxF32 antiRollForceMag = (w0Jounce - w1Jounce)*antiRoll.mStiffness;
+
+ //Apply the antiRollForce postiviely to wheel0, negatively to wheel 1
+ PxU32 wheelIds[2] = {0xffffffff, 0xffffffff};
+ PxF32 antiRollForceMags[2];
+ PxU32 numWheelIds = 0;
+ if(!w0InAir)
+ {
+ wheelIds[numWheelIds] = w0;
+ antiRollForceMags[numWheelIds] = -antiRollForceMag;
+ numWheelIds++;
+ }
+ if(!w1InAir)
+ {
+ wheelIds[numWheelIds] = w1;
+ antiRollForceMags[numWheelIds] = +antiRollForceMag;
+ numWheelIds++;
+ }
+
+ for(PxU32 j = 0; j < numWheelIds; j++)
+ {
+ const PxU32 wheelId = wheelIds[j];
+
+ //Force
+ const PxVec3 suspDir = carChassisTransform.q.rotate(wheelsSimData.getSuspTravelDirection(wheelId));
+ const PxVec3 antiRollForce = suspDir*antiRollForceMags[j];
+ //Torque
+ const PxVec3 r = carChassisTransform.q.rotate(wheelsSimData.getSuspForceAppPointOffset(wheelId));
+ const PxVec3 antiRollTorque = r.cross(antiRollForce);
+ chassisTorque += antiRollTorque;
+ }
+ }
+ }
+}
+
+
+
+////////////////////////////////////////////////////////////////////////////
+//Set the low long speed timers computed in processSuspTireWheels
+//Call immediately after completing processSuspTireWheels.
+////////////////////////////////////////////////////////////////////////////
+
+void updateLowSpeedTimers(const PxF32* PX_RESTRICT newLowSpeedTimers, PxF32* PX_RESTRICT lowSpeedTimers)
+{
+ for(PxU32 i=0;i<4;i++)
+ {
+ lowSpeedTimers[i]=(newLowSpeedTimers[i]!=lowSpeedTimers[i] ? newLowSpeedTimers[i] : 0.0f);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////
+//Set the jounce values computed in processSuspTireWheels
+//Call immediately after completing processSuspTireWheels.
+////////////////////////////////////////////////////////////////////////////
+void updateJounces(const PxF32* PX_RESTRICT jounces, PxF32* PX_RESTRICT prevJounces)
+{
+ for(PxU32 i=0;i<4;i++)
+ {
+ prevJounces[i] = jounces[i];
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//Set the hit plane, hit distance and hit friction multplier computed in processSuspTireWheels
+//Call immediately after completing processSuspTireWheels.
+////////////////////////////////////////////////////////////////////////////
+
+void updateCachedHitData
+(const PxU32* PX_RESTRICT cachedHitCounts, const PxVec4* PX_RESTRICT cachedHitPlanes, const PxF32* PX_RESTRICT cachedHitDistances, const PxF32* PX_RESTRICT cachedFrictionMultipliers, const PxU16* cachedQueryTypes,
+ PxVehicleWheels4DynData* wheels4DynData)
+{
+ if(wheels4DynData->mRaycastResults || wheels4DynData->mSweepResults)
+ {
+ wheels4DynData->mHasCachedRaycastHitPlane = true;
+ }
+
+ PxVehicleWheels4DynData::CachedSuspLineSceneQuerytHitResult* cachedRaycastHitResults =
+ reinterpret_cast<PxVehicleWheels4DynData::CachedSuspLineSceneQuerytHitResult*>(wheels4DynData->mQueryOrCachedHitResults);
+
+
+ for(PxU32 i=0;i<4;i++)
+ {
+ cachedRaycastHitResults->mCounts[i]=Ps::to16(cachedHitCounts[i]);
+ cachedRaycastHitResults->mPlanes[i]=cachedHitPlanes[i];
+ cachedRaycastHitResults->mDistances[i]=cachedHitDistances[i];
+ cachedRaycastHitResults->mFrictionMultipliers[i]=cachedFrictionMultipliers[i];
+ cachedRaycastHitResults->mQueryTypes[i] = cachedQueryTypes[i];
+ }
+}
+
+
+
+////////////////////////////////////////////////////////////////////////////
+//Solve the system of engine speed + wheel rotation speeds using an implicit integrator.
+//The following functions only compute the speed of wheels connected to the diff.
+//Worth going to the length of the implicit integrator because after gear changes
+//the difference in speed at the clutch can be hard to integrate.
+//Separate functions for 4W, NW and tank because the differential works in slightly
+//different ways. With driveNW we end up with (N+1)*(N+1) problem, with drive4W we end up
+//with 5*5 and with tanks we end up with just 3*3. Tanks use the method of least squares
+//to apply the rule that all left/right wheels have the same speed.
+//Remember that the following functions don't integrate wheels not connected to the diff
+//so these need integrated separately.
+////////////////////////////////////////////////////////////////////////////
+
+#if PX_CHECKED
+bool isValid(const MatrixNN& A, const VectorN& b, const VectorN& result)
+{
+ PX_ASSERT(A.getSize()==b.getSize());
+ PX_ASSERT(A.getSize()==result.getSize());
+ const PxU32 size=A.getSize();
+
+ //r=A*result-b
+ VectorN r(size);
+ for(PxU32 i=0;i<size;i++)
+ {
+ r[i]=-b[i];
+ for(PxU32 j=0;j<size;j++)
+ {
+ r[i]+=A.get(i,j)*result[j];
+ }
+ }
+
+ PxF32 rLength=0;
+ PxF32 bLength=0;
+ for(PxU32 i=0;i<size;i++)
+ {
+ rLength+=r[i]*r[i];
+ bLength+=b[i]*b[i];
+ }
+ const PxF32 error=PxSqrt(rLength/(bLength+1e-5f));
+ return (error<1e-5f);
+}
+#endif
+
+
+struct ImplicitSolverInput
+{
+ //dt/numSubSteps
+ PxF32 subTimeStep;
+
+ //Brake control value in range (0,1)
+ PxF32 brake;
+
+ //Handbrake control value in range (0,1)
+ PxF32 handBrake;
+
+ //Clutch strength
+ PxF32 K;
+
+ //Gear ratio.
+ PxF32 G;
+
+ PxVehicleClutchAccuracyMode::Enum accuracyMode;
+ PxU32 maxNumIterations;
+
+ //Engine drive torque
+ PxF32 engineDriveTorque;
+
+ //Engine damping rate.
+ PxF32 engineDampingRate;
+
+ //Fraction of available clutch torque to be delivered to each wheel.
+ const PxF32* diffTorqueRatios;
+
+ //Fractional contribution of each wheel to average wheel speed at clutch.
+ const PxF32* aveWheelSpeedContributions;
+
+ //Braking torque at each wheel (inlcudes handbrake torque).
+ const PxF32* brakeTorques;
+
+ //True per wheel brakeTorques[i] > 0, false if brakeTorques[i]==0
+ const bool* isBrakeApplied;
+
+ //Tire torques to apply to each 1d rigid body wheel.
+ const PxF32* tireTorques;
+
+ //Sim and dyn data.
+ PxU32 numWheels4;
+ PxU32 numActiveWheels;
+ const PxVehicleWheels4SimData* wheels4SimData;
+ const PxVehicleDriveSimData* driveSimData;
+};
+
+struct ImplicitSolverOutput
+{
+ PxVehicleWheels4DynData* wheelsDynData;
+ PxVehicleDriveDynData* driveDynData;
+};
+
+void solveDrive4WInternaDynamicsEnginePlusDrivenWheels
+(const ImplicitSolverInput& input, ImplicitSolverOutput* output)
+{
+ const PxF32 subTimestep = input.subTimeStep;
+ const PxF32 K = input.K;
+ const PxF32 G = input.G;
+ const PxVehicleClutchAccuracyMode::Enum accuracyMode = input.accuracyMode;
+ const PxU32 maxIterations = input.maxNumIterations;
+ const PxF32 engineDriveTorque = input.engineDriveTorque;
+ const PxF32 engineDampingRate = input.engineDampingRate;
+ const PxF32* PX_RESTRICT diffTorqueRatios = input.diffTorqueRatios;
+ const PxF32* PX_RESTRICT aveWheelSpeedContributions = input.aveWheelSpeedContributions;
+ const PxF32* PX_RESTRICT brakeTorques = input.brakeTorques;
+ const bool* PX_RESTRICT isBrakeApplied = input.isBrakeApplied;
+ const PxF32* PX_RESTRICT tireTorques = input.tireTorques;
+ const PxVehicleWheels4SimData& wheels4SimData = *input.wheels4SimData;
+ const PxVehicleDriveSimData4W& driveSimData = *static_cast<const PxVehicleDriveSimData4W*>(input.driveSimData);
+
+ PxVehicleDriveDynData* driveDynData = output->driveDynData;
+ PxVehicleWheels4DynData* wheels4DynData = output->wheelsDynData;
+
+ const PxF32 KG=K*G;
+ const PxF32 KGG=K*G*G;
+
+ MatrixNN A(4+1);
+ VectorN b(4+1);
+ VectorN result(4+1);
+
+ const PxVehicleEngineData& engineData=driveSimData.getEngineData();
+
+ const PxF32* PX_RESTRICT wheelSpeeds=wheels4DynData->mWheelSpeeds;
+ const PxF32 engineOmega=driveDynData->getEngineRotationSpeed();
+
+ //
+ //torque at clutch:
+ //tc = K*{G*[alpha0*w0 + alpha1*w1 + alpha2*w2 + ..... alpha(N-1)*w(N-1)] - wEng}
+ //where
+ //(i) G is the gearing ratio,
+ //(ii) alphai is the fractional contribution of the ith wheel to the average wheel speed at the clutch (alpha(i) is zero for undriven wheels)
+ //(iii) wi is the angular speed of the ith wheel
+ //(iv) K is the clutch strength
+ //(v) wEng is the angular speed of the engine
+
+ //torque applied to ith wheel is
+ //ti = G*gammai*tc + bt(i) + tt(i)
+ //where
+ //gammai is the fractional proportion of the clutch torque that the differential delivers to the ith wheel
+ //bt(i) is the brake torque applied to the ith wheel
+ //tt(i) is the tire torque applied to the ith wheel
+
+ //acceleration applied to ith wheel is
+ //ai = G*gammai*K*{G*[alpha0*w0 + alpha1*w1 alpha2*w2 + ..... alpha(N-1)*w(N-1)] - wEng}/Ii + (bt(i) + tt(i))/Ii
+ //wheer Ii is the moi of the ith wheel
+
+ //express ai as
+ //ai = [wi(t+dt) - wi(t)]/dt
+ //and rearrange
+ //wi(t+dt) - wi(t)] = dt*G*gammai*K*{G*[alpha0*w0(t+dt) + alpha1*w1(t+dt) + alpha2*w2(t+dt) + ..... alpha(N-1)*w(N-1)(t+dt)] - wEng(t+dt)}/Ii + dt*(bt(i) + tt(i))/Ii
+
+ //Do the same for tEng (torque applied to engine)
+ //tEng = -tc + engineDriveTorque
+ //where engineDriveTorque is the drive torque applied to the engine
+ //Assuming the engine has unit mass then
+ //wEng(t+dt) -wEng(t) = -dt*K*{G*[alpha0*w0(t+dt) + alpha1*w1(t+dt) + alpha2*w2(t+dt) + ..... alpha(N-1)*w(N-1(t+dt))] - wEng(t+dt)}/Ieng + dt*engineDriveTorque]/IEng
+
+ //Introduce the vector w=(w0,w1,w2....w(N-1), wEng)
+ //and re-express as a matrix after collecting all unknowns at (t+dt) and knowns at time t.
+ //A*w(t+dt)=b(t);
+
+ //Wheels.
+ {
+ for(PxU32 i=0;i<4;i++)
+ {
+ const PxF32 dt=subTimestep*wheels4SimData.getWheelData(i).getRecipMOI();
+ const PxF32 R=diffTorqueRatios[i];
+ const PxF32 dtKGGR=dt*KGG*R;
+ A.set(i,0,dtKGGR*aveWheelSpeedContributions[0]);
+ A.set(i,1,dtKGGR*aveWheelSpeedContributions[1]);
+ A.set(i,2,dtKGGR*aveWheelSpeedContributions[2]);
+ A.set(i,3,dtKGGR*aveWheelSpeedContributions[3]);
+ A.set(i,i,1.0f+dtKGGR*aveWheelSpeedContributions[i]+dt*wheels4SimData.getWheelData(i).mDampingRate);
+ A.set(i,4,-dt*KG*R);
+ b[i] = wheelSpeeds[i] + dt*(brakeTorques[i]+tireTorques[i]);
+ result[i] = wheelSpeeds[i];
+ }
+ }
+
+ //Engine.
+ {
+ const PxF32 dt=subTimestep*driveSimData.getEngineData().getRecipMOI();
+ const PxF32 dtKG=dt*K*G;
+ A.set(4,0,-dtKG*aveWheelSpeedContributions[0]);
+ A.set(4,1,-dtKG*aveWheelSpeedContributions[1]);
+ A.set(4,2,-dtKG*aveWheelSpeedContributions[2]);
+ A.set(4,3,-dtKG*aveWheelSpeedContributions[3]);
+ A.set(4,4,1.0f + dt*(K+engineDampingRate));
+ b[4] = engineOmega + dt*engineDriveTorque;
+ result[4] = engineOmega;
+ }
+
+ //Solve Aw=b
+ if(PxVehicleClutchAccuracyMode::eBEST_POSSIBLE == accuracyMode)
+ {
+ MatrixNNLUSolver solver;
+ solver.decomposeLU(A);
+ solver.solve(b,result);
+ PX_WARN_ONCE_IF(!isValid(A,b,result), "Unable to compute new PxVehicleDrive4W internal rotation speeds. Please check vehicle sim data, especially clutch strength; engine moi and damping; wheel moi and damping");
+ }
+ else
+ {
+ MatrixNGaussSeidelSolver solver;
+ solver.solve(maxIterations, gSolverTolerance, A, b, result);
+ }
+
+ //Check for sanity in the resultant internal rotation speeds.
+ //If the brakes are on and the wheels have switched direction then lock them at zero.
+ //A consequence of this quick fix is that locked wheels remain locked until the brake is entirely released.
+ //This isn't strictly mathematically or physically correct - a more accurate solution would either formulate the
+ //brake as a lcp problem or repeatedly solve with constraints that locked wheels remain at zero rotation speed.
+ //The physically correct solution will certainly be more expensive so let's live with the restriction that
+ //locked wheels remain locked until the brake is released.
+ //newOmega=result[i], oldOmega=wheelSpeeds[i], if newOmega*oldOmega<=0 and isBrakeApplied then lock wheel.
+ result[0]=(isBrakeApplied[0] && (wheelSpeeds[0]*result[0]<=0)) ? 0.0f : result[0];
+ result[1]=(isBrakeApplied[1] && (wheelSpeeds[1]*result[1]<=0)) ? 0.0f : result[1];
+ result[2]=(isBrakeApplied[2] && (wheelSpeeds[2]*result[2]<=0)) ? 0.0f : result[2];
+ result[3]=(isBrakeApplied[3] && (wheelSpeeds[3]*result[3]<=0)) ? 0.0f : result[3];
+ //Clamp the engine revs.
+ //Again, this is not physically or mathematically correct but the loss in behaviour will be hard to notice.
+ //The alternative would be to add constraints to the solver, which would be much more expensive.
+ result[4]=PxClamp(result[4],0.0f,engineData.mMaxOmega);
+
+ //Copy back to the car's internal rotation speeds.
+ wheels4DynData->mWheelSpeeds[0]=result[0];
+ wheels4DynData->mWheelSpeeds[1]=result[1];
+ wheels4DynData->mWheelSpeeds[2]=result[2];
+ wheels4DynData->mWheelSpeeds[3]=result[3];
+ driveDynData->setEngineRotationSpeed(result[4]);
+}
+
+void solveDriveNWInternalDynamicsEnginePlusDrivenWheels
+(const ImplicitSolverInput& input, ImplicitSolverOutput* output)
+{
+ const PxF32 subTimestep = input.subTimeStep;
+ //const PxF32 brake = input.brake;
+ //const PxF32 handbrake = input.handBrake;
+ const PxF32 K = input.K;
+ const PxF32 G = input.G;
+ const PxVehicleClutchAccuracyMode::Enum accuracyMode = input.accuracyMode;
+ const PxU32 maxIterations = input.maxNumIterations;
+ const PxF32 engineDriveTorque = input.engineDriveTorque;
+ const PxF32 engineDampingRate = input.engineDampingRate;
+ const PxF32* PX_RESTRICT diffTorqueRatios = input.diffTorqueRatios;
+ const PxF32* PX_RESTRICT aveWheelSpeedContributions = input.aveWheelSpeedContributions;
+ const PxF32* PX_RESTRICT brakeTorques = input.brakeTorques;
+ const bool* PX_RESTRICT isBrakeApplied = input.isBrakeApplied;
+ const PxF32* PX_RESTRICT tireTorques = input.tireTorques;
+ //const PxU32 numWheels4 = input.numWheels4;
+ const PxU32 numActiveWheels = input.numActiveWheels;
+ const PxVehicleWheels4SimData* PX_RESTRICT wheels4SimDatas = input.wheels4SimData;
+ const PxVehicleDriveSimDataNW& driveSimData = *static_cast<const PxVehicleDriveSimDataNW*>(input.driveSimData);
+
+ PxVehicleDriveDynData* driveDynData = output->driveDynData;
+ PxVehicleWheels4DynData* wheels4DynDatas = output->wheelsDynData;
+
+ const PxF32 KG=K*G;
+ const PxF32 KGG=K*G*G;
+
+ MatrixNN A(numActiveWheels+1);
+ VectorN b(numActiveWheels+1);
+ VectorN result(numActiveWheels+1);
+
+ const PxVehicleEngineData& engineData=driveSimData.getEngineData();
+ const PxF32 engineOmega=driveDynData->getEngineRotationSpeed();
+
+ //
+ //torque at clutch:
+ //tc = K*{G*[alpha0*w0 + alpha1*w1 + alpha2*w2 + ..... alpha(N-1)*w(N-1)] - wEng}
+ //where
+ //(i) G is the gearing ratio,
+ //(ii) alphai is the fractional contribution of the ith wheel to the average wheel speed at the clutch (alpha(i) is zero for undriven wheels)
+ //(iii) wi is the angular speed of the ith wheel
+ //(iv) K is the clutch strength
+ //(v) wEng is the angular speed of the engine
+
+ //torque applied to ith wheel is
+ //ti = G*gammai*tc + bt(i) + tt(i)
+ //where
+ //gammai is the fractional proportion of the clutch torque that the differential delivers to the ith wheel
+ //bt(i) is the brake torque applied to the ith wheel
+ //tt(i) is the tire torque applied to the ith wheel
+
+ //acceleration applied to ith wheel is
+ //ai = G*gammai*K*{G*[alpha0*w0 + alpha1*w1 alpha2*w2 + ..... alpha(N-1)*w(N-1)] - wEng}/Ii + (bt(i) + tt(i))/Ii
+ //wheer Ii is the moi of the ith wheel.
+
+ //express ai as
+ //ai = [wi(t+dt) - wi(t)]/dt
+ //and rearrange
+ //wi(t+dt) - wi(t)] = dt*G*gammai*K*{G*[alpha0*w0(t+dt) + alpha1*w1(t+dt) + alpha2*w2(t+dt) + ..... alpha(N-1)*w(N-1)(t+dt)] - wEng(t+dt)}/Ii + dt*(bt(i) + tt(i))/Ii
+
+ //Do the same for tEng (torque applied to engine)
+ //tEng = -tc + engineDriveTorque
+ //where engineDriveTorque is the drive torque applied to the engine
+ //Assuming the engine has unit mass then
+ //wEng(t+dt) -wEng(t) = -dt*K*{G*[alpha0*w0(t+dt) + alpha1*w1(t+dt) + alpha2*w2(t+dt) + ..... alpha(N-1)*w(N-1(t+dt))] - wEng(t+dt)}/Ieng + dt*engineDriveTorque/Ieng
+
+ //Introduce the vector w=(w0,w1,w2....w(N-1), wEng)
+ //and re-express as a matrix after collecting all unknowns at (t+dt) and knowns at time t.
+ //A*w(t+dt)=b(t);
+
+ //Wheels.
+ for(PxU32 i=0;i<numActiveWheels;i++)
+ {
+ const PxF32 dt=subTimestep*wheels4SimDatas[i>>2].getWheelData(i&3).getRecipMOI();
+ const PxF32 R=diffTorqueRatios[i];
+ const PxF32 dtKGGR=dt*KGG*R;
+
+ for(PxU32 j=0;j<numActiveWheels;j++)
+ {
+ A.set(i,j,dtKGGR*aveWheelSpeedContributions[j]);
+ }
+
+ A.set(i,i,1.0f+dtKGGR*aveWheelSpeedContributions[i]+dt*wheels4SimDatas[i>>2].getWheelData(i&3).mDampingRate);
+ A.set(i,numActiveWheels,-dt*KG*R);
+ b[i] = wheels4DynDatas[i>>2].mWheelSpeeds[i&3] + dt*(brakeTorques[i]+tireTorques[i]);
+ result[i] = wheels4DynDatas[i>>2].mWheelSpeeds[i&3];
+ }
+
+ //Engine.
+ {
+ const PxF32 dt=subTimestep*driveSimData.getEngineData().getRecipMOI();
+ const PxF32 dtKG=dt*K*G;
+ for(PxU32 i=0;i<numActiveWheels;i++)
+ {
+ A.set(numActiveWheels,i,-dtKG*aveWheelSpeedContributions[i]);
+ }
+ A.set(numActiveWheels,numActiveWheels,1.0f + dt*(K+engineDampingRate));
+ b[numActiveWheels] = engineOmega + dt*engineDriveTorque;
+ result[numActiveWheels] = engineOmega;
+ }
+
+ //Solve Aw=b
+ if(PxVehicleClutchAccuracyMode::eBEST_POSSIBLE == accuracyMode)
+ {
+ MatrixNNLUSolver solver;
+ solver.decomposeLU(A);
+ solver.solve(b,result);
+ PX_WARN_ONCE_IF(!isValid(A,b,result), "Unable to compute new PxVehicleDriveNW internal rotation speeds. Please check vehicle sim data, especially clutch strength; engine moi and damping; wheel moi and damping");
+ }
+ else
+ {
+ MatrixNGaussSeidelSolver solver;
+ solver.solve(maxIterations, gSolverTolerance, A, b, result);
+ }
+
+ //Check for sanity in the resultant internal rotation speeds.
+ //If the brakes are on and the wheels have switched direction then lock them at zero.
+ //A consequence of this quick fix is that locked wheels remain locked until the brake is entirely released.
+ //This isn't strictly mathematically or physically correct - a more accurate solution would either formulate the
+ //brake as a lcp problem or repeatedly solve with constraints that locked wheels remain at zero rotation speed.
+ //The physically correct solution will certainly be more expensive so let's live with the restriction that
+ //locked wheels remain locked until the brake is released.
+ //newOmega=result[i], oldOmega=wheelSpeeds[i], if newOmega*oldOmega<=0 and isBrakeApplied then lock wheel.
+ for(PxU32 i=0;i<numActiveWheels;i++)
+ {
+ result[i]=(isBrakeApplied[i] && (wheels4DynDatas[i>>2].mWheelSpeeds[i&3]*result[i]<=0)) ? 0.0f : result[i];
+ }
+ //Clamp the engine revs.
+ //Again, this is not physically or mathematically correct but the loss in behaviour will be hard to notice.
+ result[numActiveWheels]=PxClamp(result[numActiveWheels],0.0f,engineData.mMaxOmega);
+
+ //Copy back to the car's internal rotation speeds.
+ for(PxU32 i=0;i<numActiveWheels;i++)
+ {
+ wheels4DynDatas[i>>2].mWheelSpeeds[i&3]=result[i];
+ }
+ driveDynData->setEngineRotationSpeed(result[numActiveWheels]);
+}
+
+
+void solveTankInternaDynamicsEnginePlusDrivenWheels
+(const ImplicitSolverInput& input, const bool* PX_RESTRICT activeWheelStates, const PxF32* PX_RESTRICT wheelGearings, ImplicitSolverOutput* output)
+{
+ PX_SIMD_GUARD; // denormal exception triggered at oldOmega*newOmega on osx
+ const PxF32 subTimestep = input.subTimeStep;
+ const PxF32 K = input.K;
+ const PxF32 G = input.G;
+ const PxF32 engineDriveTorque = input.engineDriveTorque;
+ const PxF32 engineDampingRate = input.engineDampingRate;
+ const PxF32* PX_RESTRICT diffTorqueRatios = input.diffTorqueRatios;
+ const PxF32* PX_RESTRICT aveWheelSpeedContributions = input.aveWheelSpeedContributions;
+ const PxF32* PX_RESTRICT brakeTorques = input.brakeTorques;
+ const bool* PX_RESTRICT isBrakeApplied = input.isBrakeApplied;
+ const PxF32* PX_RESTRICT tireTorques = input.tireTorques;
+ const PxU32 numWheels4 = input.numWheels4;
+ const PxU32 numActiveWheels = input.numActiveWheels;
+ const PxVehicleWheels4SimData* PX_RESTRICT wheels4SimDatas = input.wheels4SimData;
+ const PxVehicleDriveSimData& driveSimData = *input.driveSimData;
+
+ PxVehicleWheels4DynData* PX_RESTRICT wheels4DynDatas = output->wheelsDynData;
+ PxVehicleDriveDynData* driveDynData = output->driveDynData;
+
+ const PxF32 KG=K*G;
+ const PxF32 KGG=K*G*G;
+
+ //Rearrange data in a single array rather than scattered in blocks of 4.
+ //This makes it easier later on.
+ PxF32 recipMOI[PX_MAX_NB_WHEELS];
+ PxF32 dampingRates[PX_MAX_NB_WHEELS];
+ PxF32 wheelSpeeds[PX_MAX_NB_WHEELS];
+ PxF32 wheelRecipRadii[PX_MAX_NB_WHEELS];
+
+ for(PxU32 i=0;i<numWheels4-1;i++)
+ {
+ const PxVehicleWheelData& wheelData0=wheels4SimDatas[i].getWheelData(0);
+ const PxVehicleWheelData& wheelData1=wheels4SimDatas[i].getWheelData(1);
+ const PxVehicleWheelData& wheelData2=wheels4SimDatas[i].getWheelData(2);
+ const PxVehicleWheelData& wheelData3=wheels4SimDatas[i].getWheelData(3);
+
+ recipMOI[4*i+0]=wheelData0.getRecipMOI();
+ recipMOI[4*i+1]=wheelData1.getRecipMOI();
+ recipMOI[4*i+2]=wheelData2.getRecipMOI();
+ recipMOI[4*i+3]=wheelData3.getRecipMOI();
+
+ dampingRates[4*i+0]=wheelData0.mDampingRate;
+ dampingRates[4*i+1]=wheelData1.mDampingRate;
+ dampingRates[4*i+2]=wheelData2.mDampingRate;
+ dampingRates[4*i+3]=wheelData3.mDampingRate;
+
+ wheelRecipRadii[4*i+0]=wheelData0.getRecipRadius();
+ wheelRecipRadii[4*i+1]=wheelData1.getRecipRadius();
+ wheelRecipRadii[4*i+2]=wheelData2.getRecipRadius();
+ wheelRecipRadii[4*i+3]=wheelData3.getRecipRadius();
+
+ const PxVehicleWheels4DynData& suspWheelTire4=wheels4DynDatas[i];
+ wheelSpeeds[4*i+0]=suspWheelTire4.mWheelSpeeds[0];
+ wheelSpeeds[4*i+1]=suspWheelTire4.mWheelSpeeds[1];
+ wheelSpeeds[4*i+2]=suspWheelTire4.mWheelSpeeds[2];
+ wheelSpeeds[4*i+3]=suspWheelTire4.mWheelSpeeds[3];
+ }
+ const PxU32 numInLastBlock = 4 - (4*numWheels4 - numActiveWheels);
+ for(PxU32 i=0;i<numInLastBlock;i++)
+ {
+ const PxVehicleWheelData& wheelData=wheels4SimDatas[numWheels4-1].getWheelData(i);
+ recipMOI[4*(numWheels4-1)+i]=wheelData.getRecipMOI();
+ dampingRates[4*(numWheels4-1)+i]=wheelData.mDampingRate;
+ wheelRecipRadii[4*(numWheels4-1)+i]=wheelData.getRecipRadius();
+
+ const PxVehicleWheels4DynData& suspWheelTire4=wheels4DynDatas[numWheels4-1];
+ wheelSpeeds[4*(numWheels4-1)+i]=suspWheelTire4.mWheelSpeeds[i];
+ }
+ const PxF32 wheelRadius0=wheels4SimDatas[0].getWheelData(0).mRadius;
+ const PxF32 wheelRadius1=wheels4SimDatas[0].getWheelData(1).mRadius;
+
+ //
+ //torque at clutch:
+ //tc = K*{G*[alpha0*w0 + alpha1*w1 + alpha2*w2 + ..... alpha(N-1)*w(N-1)] - wEng}
+ //where
+ //(i) G is the gearing ratio,
+ //(ii) alphai is the fractional contribution of the ith wheel to the average wheel speed at the clutch (alpha(i) is zero for undriven wheels)
+ //(iii) wi is the angular speed of the ith wheel
+ //(iv) K is the clutch strength
+ //(v) wEng is the angular speed of the engine
+
+ //torque applied to ith wheel is
+ //ti = G*gammai*tc + bt(i) + tt(i)
+ //where
+ //gammai is the fractional proportion of the clutch torque that the differential delivers to the ith wheel
+ //bt(i) is the brake torque applied to the ith wheel
+ //tt(i) is the tire torque applied to the ith wheel
+
+ //acceleration applied to ith wheel is
+ //ai = G*gammai*K*{G*[alpha0*w0 + alpha1*w1 alpha2*w2 + ..... alpha(N-1)*w(N-1)] - wEng}/Ii + (bt(i) + tt(i))/Ii
+ //wheer Ii is the moi of the ith wheel.
+
+ //express ai as
+ //ai = [wi(t+dt) - wi(t)]/dt
+ //and rearrange
+ //wi(t+dt) - wi(t)] = dt*G*gammai*K*{G*[alpha0*w0(t+dt) + alpha1*w1(t+dt) + alpha2*w2(t+dt) + ..... alpha(N-1)*w(N-1)(t+dt)] - wEng(t+dt)}/Ii + dt*(bt(i) + tt(i))/Ii
+
+ //Do the same for tEng (torque applied to engine)
+ //tEng = -tc + engineDriveTorque
+ //where engineDriveTorque is the drive torque applied to the engine
+ //Assuming the engine has unit mass then
+ //wEng(t+dt) -wEng(t) = -dt*K*{G*[alpha0*w0(t+dt) + alpha1*w1(t+dt) + alpha2*w2(t+dt) + ..... alpha(N-1)*w(N-1(t+dt))] - wEng(t+dt)}/Ieng + dt*engineDriveTorque/Ieng
+
+ //Introduce the vector w=(w0,w1,w2....w(N-1), wEng)
+ //and re-express as a matrix after collecting all unknowns at (t+dt) and knowns at time t.
+ //M*w(t+dt)=b(t);
+
+ //Matrix M and rhs vector b that we use to solve Mw=b.
+ MatrixNN M(numActiveWheels+1);
+ VectorN b(numActiveWheels+1);
+
+ //Wheels.
+ {
+ for(PxU32 i=0;i<numActiveWheels;i++)
+ {
+ const PxF32 dt=subTimestep*recipMOI[i];
+ const PxF32 R=diffTorqueRatios[i];
+ const PxF32 g=wheelGearings[i];
+ const PxF32 dtKGGRg=dt*KGG*R*g;
+ for(PxU32 j=0;j<numActiveWheels;j++)
+ {
+ M.set(i,j,dtKGGRg*aveWheelSpeedContributions[j]*wheelGearings[j]);
+ }
+ M.set(i,i,1.0f+dtKGGRg*aveWheelSpeedContributions[i]*wheelGearings[i]+dt*dampingRates[i]);
+ M.set(i,numActiveWheels,-dt*KG*R*g);
+ b[i] = wheelSpeeds[i] + dt*(brakeTorques[i]+tireTorques[i]);
+ }
+ }
+
+ //Engine.
+ {
+ const PxF32 engineOmega=driveDynData->getEngineRotationSpeed();
+
+ const PxF32 dt=subTimestep*driveSimData.getEngineData().getRecipMOI();
+ const PxF32 dtKG=dt*K*G;
+ for(PxU32 i=0;i<numActiveWheels;i++)
+ {
+ M.set(numActiveWheels,i,-dtKG*aveWheelSpeedContributions[i]*wheelGearings[i]);
+ }
+ M.set(numActiveWheels,numActiveWheels,1.0f + dt*(K+engineDampingRate));
+ b[numActiveWheels] = engineOmega + dt*engineDriveTorque;
+ }
+
+ //Now apply the constraints that all the odd numbers are equal and all the even numbers are equal.
+ //ie w2,w4,w6 are all equal to w0 and w3,w5,w7 are all equal to w1.
+ //That leaves (4*N+1) equations but only 3 unknowns: two wheels speeds and the engine speed.
+ //Substitute these extra constraints into the matrix.
+ MatrixNN A(numActiveWheels+1);
+ for(PxU32 i=0;i<numActiveWheels+1;i++)
+ {
+ PxF32 sum0=M.get(i,0+0);
+ PxF32 sum1=M.get(i,0+1);
+ for(PxU32 j=2;j<numActiveWheels;j+=2)
+ {
+ sum0+=M.get(i,j+0)*wheelRadius0*wheelRecipRadii[j+0];
+ sum1+=M.get(i,j+1)*wheelRadius1*wheelRecipRadii[j+1];
+ }
+ A.set(i,0,sum0);
+ A.set(i,1,sum1);
+ A.set(i,2,M.get(i,numActiveWheels));
+ }
+
+ //We have an over-determined problem because of the extra constraints
+ //on equal wheel speeds. Solve using the least squares method as in
+ //http://s-mat-pcs.oulu.fi/~mpa/matreng/ematr5_5.htm
+
+ //Compute A^T*A
+ //No longer using M.
+ MatrixNN& ATA = M;
+ ATA.setSize(3);
+ for(PxU32 i=0;i<3;i++)
+ {
+ for(PxU32 j=0;j<3;j++)
+ {
+ PxF32 sum=0.0f;
+ for(PxU32 k=0;k<numActiveWheels+1;k++)
+ {
+ //sum+=AT.get(i,k)*A.get(k,j);
+ sum+=A.get(k,i)*A.get(k,j);
+ }
+ ATA.set(i,j,sum);
+ }
+ }
+
+ //Compute A^T*b;
+ VectorN ATb(3);
+ for(PxU32 i=0;i<3;i++)
+ {
+ PxF32 sum=0;
+ for(PxU32 j=0;j<numActiveWheels+1;j++)
+ {
+ //sum+=AT.get(i,j)*b[j];
+ sum+=A.get(j,i)*b[j];
+ }
+ ATb[i]=sum;
+ }
+
+ //Solve (A^T*A)*x = A^T*b
+ VectorN result(3);
+ Matrix33Solver solver;
+ bool successfulSolver = solver.solve(ATA, ATb, result);
+ if(!successfulSolver)
+ {
+ PX_WARN_ONCE("Unable to compute new PxVehicleDriveTank internal rotation speeds. Please check vehicle sim data, especially clutch strength; engine moi and damping; wheel moi and damping");
+ return;
+ }
+
+ //Clamp the engine revs between zero and maxOmega
+ const PxF32 maxEngineOmega=driveSimData.getEngineData().mMaxOmega;
+ const PxF32 newEngineOmega=PxClamp(result[2],0.0f,maxEngineOmega);
+
+ //Apply the constraints on each of the equal wheel speeds.
+ PxF32 wheelSpeedResults[PX_MAX_NB_WHEELS];
+ wheelSpeedResults[0]=result[0];
+ wheelSpeedResults[1]=result[1];
+ for(PxU32 i=2;i<numActiveWheels;i+=2)
+ {
+ wheelSpeedResults[i+0]=result[0];
+ wheelSpeedResults[i+1]=result[1];
+ }
+
+ //Check for sanity in the resultant internal rotation speeds.
+ //If the brakes are on and the wheels have switched direction then lock them at zero.
+ //A consequence of this quick fix is that locked wheels remain locked until the brake is entirely released.
+ //This isn't strictly mathematically or physically correct - a more accurate solution would either formulate the
+ //brake as a lcp problem or repeatedly solve with constraints that locked wheels remain at zero rotation speed.
+ //The physically correct solution will certainly be more expensive so let's live with the restriction that
+ //locked wheels remain locked until the brake is released.
+ for(PxU32 i=0;i<numActiveWheels;i++)
+ {
+ const PxF32 oldOmega=wheelSpeeds[i];
+ const PxF32 newOmega=wheelSpeedResults[i];
+ const bool hasBrake=isBrakeApplied[i];
+ if(hasBrake && (oldOmega*newOmega <= 0))
+ {
+ wheelSpeedResults[i]=0.0f;
+ }
+ }
+
+
+ //Copy back to the car's internal rotation speeds.
+ for(PxU32 i=0;i<numWheels4-1;i++)
+ {
+ wheels4DynDatas[i].mWheelSpeeds[0] = activeWheelStates[4*i+0] ? wheelSpeedResults[4*i+0] : 0.0f;
+ wheels4DynDatas[i].mWheelSpeeds[1] = activeWheelStates[4*i+1] ? wheelSpeedResults[4*i+1] : 0.0f;
+ wheels4DynDatas[i].mWheelSpeeds[2] = activeWheelStates[4*i+2] ? wheelSpeedResults[4*i+2] : 0.0f;
+ wheels4DynDatas[i].mWheelSpeeds[3] = activeWheelStates[4*i+3] ? wheelSpeedResults[4*i+3] : 0.0f;
+ }
+ for(PxU32 i=0;i<numInLastBlock;i++)
+ {
+ wheels4DynDatas[numWheels4-1].mWheelSpeeds[i] = activeWheelStates[4*(numWheels4-1)+i] ? wheelSpeedResults[4*(numWheels4-1)+i] : 0.0f;
+ }
+ driveDynData->setEngineRotationSpeed(newEngineOmega);
+}
+
+////////////////////////////////////////////////////////////////////////////
+//Integrate wheel rotation speeds of wheels not connected to the differential.
+//Obviously, no wheels in a PxVehicleNoDrive are connected to a diff so all require
+//direct integration.
+//Only the first 4 wheels of a PxVehicleDrive4W are connected to the diff so
+//any extra wheels need direct integration.
+//All tank wheels are connected to the diff so none need integrated in a separate pass.
+//What about undriven wheels in a PxVehicleDriveNW? This vehicle type treats all
+//wheels as being connected to the diff but sets the diff contribution to zero for
+//all undriven wheels. No wheels from a PxVehicleNW need integrated in a separate pass.
+////////////////////////////////////////////////////////////////////////////
+
+void integrateNoDriveWheelSpeeds
+(const PxF32 subTimestep,
+ const PxF32* PX_RESTRICT brakeTorques, const bool* PX_RESTRICT isBrakeApplied, const PxF32* driveTorques, const PxF32* PX_RESTRICT tireTorques, const PxF32* PX_RESTRICT dampingRates,
+ const PxVehicleWheels4SimData& vehSuspWheelTire4SimData, PxVehicleWheels4DynData& vehSuspWheelTire4)
+{
+ //w(t+dt) = w(t) + (1/inertia)*(brakeTorque + driveTorque + tireTorque)*dt - (1/inertia)*damping*w(t)*dt ) (1)
+ //Apply implicit trick and rearrange.
+ //w(t+dt)[1 + (1/inertia)*damping*dt] = w(t) + (1/inertia)*(brakeTorque + driveTorque + tireTorque)*dt (2)
+
+ //Introduce (1/inertia)*dt to avoid duplication in (2)
+ PxF32 subTimeSteps[4] =
+ {
+ subTimestep*vehSuspWheelTire4SimData.getWheelData(0).getRecipMOI(),
+ subTimestep*vehSuspWheelTire4SimData.getWheelData(1).getRecipMOI(),
+ subTimestep*vehSuspWheelTire4SimData.getWheelData(2).getRecipMOI(),
+ subTimestep*vehSuspWheelTire4SimData.getWheelData(3).getRecipMOI()
+ };
+
+ //Integrate.
+ //w += torque*dt/inertia - damping*dt*w
+ //Use implicit integrate trick and rearrange
+ //w(t+dt) = [w(t) + torque*dt/inertia]/[1 + damping*dt]
+ const PxF32* PX_RESTRICT wheelSpeeds=vehSuspWheelTire4.mWheelSpeeds;
+ PxF32 result[4]=
+ {
+ (wheelSpeeds[0] + subTimeSteps[0]*(tireTorques[0] + driveTorques[0] + brakeTorques[0]))/(1.0f + dampingRates[0]*subTimeSteps[0]),
+ (wheelSpeeds[1] + subTimeSteps[1]*(tireTorques[1] + driveTorques[1] + brakeTorques[1]))/(1.0f + dampingRates[1]*subTimeSteps[1]),
+ (wheelSpeeds[2] + subTimeSteps[2]*(tireTorques[2] + driveTorques[2] + brakeTorques[2]))/(1.0f + dampingRates[2]*subTimeSteps[2]),
+ (wheelSpeeds[3] + subTimeSteps[3]*(tireTorques[3] + driveTorques[3] + brakeTorques[3]))/(1.0f + dampingRates[3]*subTimeSteps[3]),
+ };
+
+ //Check for sanity in the resultant internal rotation speeds.
+ //If the brakes are on and the wheels have switched direction then lock them at zero.
+ //newOmega=result[i], oldOmega=wheelSpeeds[i], if newOmega*oldOmega<=0 and isBrakeApplied then lock wheel.
+ result[0]=(isBrakeApplied[0] && (wheelSpeeds[0]*result[0]<=0)) ? 0.0f : result[0];
+ result[1]=(isBrakeApplied[1] && (wheelSpeeds[1]*result[1]<=0)) ? 0.0f : result[1];
+ result[2]=(isBrakeApplied[2] && (wheelSpeeds[2]*result[2]<=0)) ? 0.0f : result[2];
+ result[3]=(isBrakeApplied[3] && (wheelSpeeds[3]*result[3]<=0)) ? 0.0f : result[3];
+
+ //Copy back to the car's internal rotation speeds.
+ vehSuspWheelTire4.mWheelSpeeds[0]=result[0];
+ vehSuspWheelTire4.mWheelSpeeds[1]=result[1];
+ vehSuspWheelTire4.mWheelSpeeds[2]=result[2];
+ vehSuspWheelTire4.mWheelSpeeds[3]=result[3];
+}
+
+void integrateUndriveWheelRotationSpeeds
+(const PxF32 subTimestep,
+ const PxF32 brake, const PxF32 handbrake, const PxF32* PX_RESTRICT tireTorques, const PxF32* PX_RESTRICT brakeTorques,
+ const PxVehicleWheels4SimData& vehSuspWheelTire4SimData, PxVehicleWheels4DynData& vehSuspWheelTire4)
+{
+ for(PxU32 i=0;i<4;i++)
+ {
+ //Compute the new angular speed of the wheel.
+ const PxF32 oldOmega=vehSuspWheelTire4.mWheelSpeeds[i];
+ const PxF32 dtI = subTimestep*vehSuspWheelTire4SimData.getWheelData(i).getRecipMOI();
+ const PxF32 gamma = vehSuspWheelTire4SimData.getWheelData(i).mDampingRate;
+ const PxF32 newOmega=(oldOmega+dtI*(tireTorques[i]+brakeTorques[i]))/(1.0f + gamma*dtI);
+
+ //Has the brake been applied? It's hard to tell from brakeTorques[j] because that
+ //will be zero if the wheel is locked. Work it out from the brake and handbrake data.
+ const PxF32 brakeGain=vehSuspWheelTire4SimData.getWheelData(i).mMaxBrakeTorque;
+ const PxF32 handbrakeGain=vehSuspWheelTire4SimData.getWheelData(i).mMaxHandBrakeTorque;
+
+ //Work out if the wheel should be locked.
+ const bool brakeApplied=((brake*brakeGain + handbrake*handbrakeGain)!=0.0f);
+ const bool wheelReversed=(oldOmega*newOmega <=0);
+ const bool wheelLocked=(brakeApplied && wheelReversed);
+
+ //Lock the wheel or apply its new angular speed.
+ if(!wheelLocked)
+ {
+ vehSuspWheelTire4.mWheelSpeeds[i]=newOmega;
+ }
+ else
+ {
+ vehSuspWheelTire4.mWheelSpeeds[i]=0.0f;
+ }
+ }
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+//Pose the wheels.
+//First integrate the wheel rotation angles and clamp them to a range (-10*pi, 10*pi)
+//PxVehicleNoDrive has a different way of telling if a wheel is driven by a drive torque so has a separate function.
+//Use the wheel steer/rotation/camber angle and suspension jounce to compute the local transform of each wheel.
+////////////////////////////////////////////////////////////////////////////
+
+void integrateWheelRotationAngles
+(const PxF32 timestep,
+ const PxF32 K, const PxF32 G, const PxF32 engineDriveTorque,
+ const PxF32* PX_RESTRICT jounces, const PxF32* PX_RESTRICT diffTorqueRatios, const PxF32* PX_RESTRICT forwardSpeeds, const bool* isBrakeApplied,
+ const PxVehicleDriveSimData& vehCoreSimData, const PxVehicleWheels4SimData& vehSuspWheelTire4SimData,
+ PxVehicleDriveDynData& vehCore, PxVehicleWheels4DynData& vehSuspWheelTire4)
+{
+ PX_SIMD_GUARD; //denorm exception on newRotAngle=wheelRotationAngles[j]+wheelOmega*timestep; on osx
+
+ PX_UNUSED(vehCore);
+ PX_UNUSED(vehCoreSimData);
+
+ const PxF32 KG=K*G;
+
+ PxF32* PX_RESTRICT wheelSpeeds=vehSuspWheelTire4.mWheelSpeeds;
+ PxF32* PX_RESTRICT wheelRotationAngles=vehSuspWheelTire4.mWheelRotationAngles;
+ PxF32* PX_RESTRICT correctedWheelSpeeds = vehSuspWheelTire4.mCorrectedWheelSpeeds;
+
+ for(PxU32 j=0;j<4;j++)
+ {
+ //At low vehicle forward speeds we have some numerical difficulties getting the
+ //wheel rotation speeds to be correct due to the tire model's difficulties at low vz.
+ //The solution is to blend between the rolling speed at the wheel and the wheel's actual rotation speed.
+ //If the wheel is
+ //(i) in the air or,
+ //(ii) under braking torque or,
+ //(iii) driven by the engine through the gears and diff
+ //then always use the wheel's actual rotation speed.
+ //Just to be clear, this means we will blend when the wheel
+ //(i) is on the ground and
+ //(ii) has no brake applied and
+ //(iii) has no drive torque applied from the clutch and
+ //(iv) is at low forward speed
+ PxF32 wheelOmega=wheelSpeeds[j];
+ if(jounces[j] > -vehSuspWheelTire4SimData.getSuspensionData(j).mMaxDroop && //(i) wheel touching ground
+ false==isBrakeApplied[j] && //(ii) no brake applied
+ 0.0f==diffTorqueRatios[j]*KG*engineDriveTorque && //(iii) no drive torque applied
+ PxAbs(forwardSpeeds[j])<gThresholdForwardSpeedForWheelAngleIntegration) //(iv) low speed
+ {
+ const PxF32 recipWheelRadius=vehSuspWheelTire4SimData.getWheelData(j).getRecipRadius();
+ const PxF32 alpha=PxAbs(forwardSpeeds[j])*gRecipThresholdForwardSpeedForWheelAngleIntegration;
+ wheelOmega = (forwardSpeeds[j]*recipWheelRadius)*(1.0f-alpha) + wheelOmega*alpha;
+ }
+
+ PxF32 newRotAngle=wheelRotationAngles[j]+wheelOmega*timestep;
+ //Clamp the wheel rotation angle to a range (-10*pi,10*pi) to stop it getting crazily big.
+ newRotAngle=physx::intrinsics::fsel(newRotAngle-10*PxPi, newRotAngle-10*PxPi, physx::intrinsics::fsel(-newRotAngle-10*PxPi, newRotAngle + 10*PxPi, newRotAngle));
+ wheelRotationAngles[j]=newRotAngle;
+ correctedWheelSpeeds[j]=wheelOmega;
+ }
+}
+
+void integrateNoDriveWheelRotationAngles
+(const PxF32 timestep,
+ const PxF32* PX_RESTRICT driveTorques,
+ const PxF32* PX_RESTRICT jounces, const PxF32* PX_RESTRICT forwardSpeeds, const bool* isBrakeApplied,
+ const PxVehicleWheels4SimData& vehSuspWheelTire4SimData,
+ PxVehicleWheels4DynData& vehSuspWheelTire4)
+{
+ PxF32* PX_RESTRICT wheelSpeeds=vehSuspWheelTire4.mWheelSpeeds;
+ PxF32* PX_RESTRICT wheelRotationAngles=vehSuspWheelTire4.mWheelRotationAngles;
+ PxF32* PX_RESTRICT correctedWheelSpeeds=vehSuspWheelTire4.mCorrectedWheelSpeeds;
+
+ for(PxU32 j=0;j<4;j++)
+ {
+ //At low vehicle forward speeds we have some numerical difficulties getting the
+ //wheel rotation speeds to be correct due to the tire model's difficulties at low vz.
+ //The solution is to blend between the rolling speed at the wheel and the wheel's actual rotation speed.
+ //If the wheel is
+ //(i) in the air or,
+ //(ii) under braking torque or,
+ //(iii) driven by a drive torque
+ //then always use the wheel's actual rotation speed.
+ //Just to be clear, this means we will blend when the wheel
+ //(i) is on the ground and
+ //(ii) has no brake applied and
+ //(iii) has no drive torque and
+ //(iv) is at low forward speed
+ PxF32 wheelOmega=wheelSpeeds[j];
+ if(jounces[j] > -vehSuspWheelTire4SimData.getSuspensionData(j).mMaxDroop && //(i) wheel touching ground
+ false==isBrakeApplied[j] && //(ii) no brake applied
+ 0.0f==driveTorques[j] && //(iii) no drive torque applied
+ PxAbs(forwardSpeeds[j])<gThresholdForwardSpeedForWheelAngleIntegration) //(iv) low speed
+ {
+ const PxF32 recipWheelRadius=vehSuspWheelTire4SimData.getWheelData(j).getRecipRadius();
+ const PxF32 alpha=PxAbs(forwardSpeeds[j])*gRecipThresholdForwardSpeedForWheelAngleIntegration;
+ wheelOmega = (forwardSpeeds[j]*recipWheelRadius)*(1.0f-alpha) + wheelOmega*alpha;
+
+ //TODO: maybe just set the car wheel omega to the blended value?
+ //Not sure about this bit.
+ //Turned this off because it added energy to the car at very small timesteps.
+ //wheelSpeeds[j]=wheelOmega;
+ }
+
+ PxF32 newRotAngle=wheelRotationAngles[j]+wheelOmega*timestep;
+ //Clamp the wheel rotation angle to a range (-10*pi,10*pi) to stop it getting crazily big.
+ newRotAngle=physx::intrinsics::fsel(newRotAngle-10*PxPi, newRotAngle-10*PxPi, physx::intrinsics::fsel(-newRotAngle-10*PxPi, newRotAngle + 10*PxPi, newRotAngle));
+ wheelRotationAngles[j]=newRotAngle;
+ correctedWheelSpeeds[j]=wheelOmega;
+ }
+}
+
+void computeWheelLocalPoses
+(const PxVehicleWheels4SimData& wheelsSimData,
+ const PxVehicleWheels4DynData& wheelsDynData,
+ const PxWheelQueryResult* wheelQueryResults,
+ const PxU32 numWheelsToPose,
+ const PxTransform& vehChassisCMLocalPose,
+ PxTransform* localPoses)
+{
+ const PxF32* PX_RESTRICT rotAngles=wheelsDynData.mWheelRotationAngles;
+
+ const PxVec3 cmOffset=vehChassisCMLocalPose.p;
+
+ const PxVec3 forward = gRight.cross(gUp);
+
+ for(PxU32 i=0;i<numWheelsToPose;i++)
+ {
+ const PxF32 jounce=wheelQueryResults[i].suspJounce;
+
+ //Compute the camber angle.
+ const PxVehicleSuspensionData& suspData=wheelsSimData.getSuspensionData(i);
+ PxF32 camberAngle=suspData.mCamberAtRest;
+ if(jounce > 0.0f)
+ {
+ camberAngle += jounce*suspData.mCamberAtMaxCompression*suspData.getRecipMaxCompression();
+ }
+ else
+ {
+ camberAngle -= jounce*suspData.mCamberAtMaxDroop*suspData.getRecipMaxDroop();
+ }
+
+ //Compute the transform of the wheel shapes.
+ const PxVec3 pos=cmOffset+wheelsSimData.getWheelCentreOffset(i)-wheelsSimData.getSuspTravelDirection(i)*jounce;
+ const PxQuat quat(wheelQueryResults[i].steerAngle, gUp);
+ const PxQuat quat2(camberAngle, quat.rotate(forward));
+ const PxQuat quat3=quat2*quat;
+ const PxQuat quat4(rotAngles[i],quat3.rotate(gRight));
+ const PxTransform t(pos,quat4*quat3);
+ localPoses[i] = t;
+ }
+}
+
+void poseWheels
+(const PxVehicleWheels4SimData& wheelsSimData,
+ const PxTransform* localPoses,
+ const PxU32 numWheelsToPose,
+ PxRigidDynamic* vehActor)
+{
+ PxShape* shapeBuffer[128];
+ vehActor->getShapes(shapeBuffer,128,0);
+
+ for(PxU32 i=0;i<numWheelsToPose;i++)
+ {
+ const PxI32 shapeIndex = wheelsSimData.getWheelShapeMapping(i);
+ if(shapeIndex != -1)
+ {
+ PxShape* currShape = NULL;
+ if(shapeIndex < 128)
+ {
+ currShape = shapeBuffer[shapeIndex];
+ }
+ else
+ {
+ PxShape* shapeBuffer2[1];
+ vehActor->getShapes(shapeBuffer2,1,PxU32(shapeIndex));
+ currShape = shapeBuffer2[0];
+ }
+ PX_ASSERT(currShape);
+ currShape->setLocalPose(localPoses[i]);
+ }
+ }
+}
+
+
+
+////////////////////////////////////////////////////////////////////////////
+//Update each vehicle type with a special function
+////////////////////////////////////////////////////////////////////////////
+
+class PxVehicleUpdate
+{
+public:
+
+#if PX_DEBUG_VEHICLE_ON
+ static void updateSingleVehicleAndStoreTelemetryData(
+ const PxF32 timestep, const PxVec3& gravity, const PxVehicleDrivableSurfaceToTireFrictionPairs& vehicleDrivableSurfaceToTireFrictionPairs,
+ PxVehicleWheels* focusVehicle, PxVehicleWheelQueryResult* vehWheelQueryResults, PxVehicleTelemetryData& telemetryData);
+#endif
+
+ static void update(
+ const PxF32 timestep, const PxVec3& gravity, const PxVehicleDrivableSurfaceToTireFrictionPairs& vehicleDrivableSurfaceToTireFrictionPairs,
+ const PxU32 numVehicles, PxVehicleWheels** vehicles, PxVehicleWheelQueryResult* wheelQueryResults, PxVehicleConcurrentUpdateData* vehicleConcurrentUpdates);
+
+ static void updatePost(
+ const PxVehicleConcurrentUpdateData* vehicleConcurrentUpdates, const PxU32 numVehicles, PxVehicleWheels** vehicles);
+
+ static void suspensionRaycasts(
+ PxBatchQuery* batchQuery,
+ const PxU32 numVehicles, PxVehicleWheels** vehicles, const PxU32 numSceneQueryResults, PxRaycastQueryResult* sceneQueryResults,
+ const bool* vehiclesToRaycast);
+
+ static void suspensionSweeps(
+ PxBatchQuery* batchQuery,
+ const PxU32 numVehicles, PxVehicleWheels** vehicles,
+ const PxU32 numSceneQueryResults, PxSweepQueryResult* sceneQueryResults, const PxU16 nbHitsPerQuery,
+ const bool* vehiclesToRaycast,
+ const PxF32 sweepWidthScale, const PxF32 sweepRadiusScale);
+
+ static void updateDrive4W(
+ const PxF32 timestep,
+ const PxVec3& gravity, const PxF32 gravityMagnitude, const PxF32 recipGravityMagnitude,
+ const PxVehicleDrivableSurfaceToTireFrictionPairs& drivableSurfaceToTireFrictionPairs,
+ PxVehicleDrive4W* vehDrive4W, PxVehicleWheelQueryResult* vehWheelQueryResults, PxVehicleConcurrentUpdateData* vehConcurrentUpdates);
+
+ static void updateDriveNW(
+ const PxF32 timestep,
+ const PxVec3& gravity, const PxF32 gravityMagnitude, const PxF32 recipGravityMagnitude,
+ const PxVehicleDrivableSurfaceToTireFrictionPairs& drivableSurfaceToTireFrictionPairs,
+ PxVehicleDriveNW* vehDriveNW, PxVehicleWheelQueryResult* vehWheelQueryResults, PxVehicleConcurrentUpdateData* vehConcurrentUpdates);
+
+ static void updateTank(
+ const PxF32 timestep,
+ const PxVec3& gravity, const PxF32 gravityMagnitude, const PxF32 recipGravityMagnitude,
+ const PxVehicleDrivableSurfaceToTireFrictionPairs& drivableSurfaceToTireFrictionPairs,
+ PxVehicleDriveTank* vehDriveTank, PxVehicleWheelQueryResult* vehWheelQueryResults, PxVehicleConcurrentUpdateData* vehConcurrentUpdates);
+
+ static void updateNoDrive(
+ const PxF32 timestep,
+ const PxVec3& gravity, const PxF32 gravityMagnitude, const PxF32 recipGravityMagnitude,
+ const PxVehicleDrivableSurfaceToTireFrictionPairs& drivableSurfaceToTireFrictionPairs,
+ PxVehicleNoDrive* vehDriveTank, PxVehicleWheelQueryResult* vehWheelQueryResults, PxVehicleConcurrentUpdateData* vehConcurrentUpdates);
+
+ static PxU32 computeNumberOfSubsteps(const PxVehicleWheelsSimData& wheelsSimData, const PxVec3& linVel, const PxTransform& globalPose, const PxVec3& forward)
+ {
+ const PxVec3 z=globalPose.q.rotate(forward);
+ const PxF32 vz=PxAbs(linVel.dot(z));
+ const PxF32 thresholdVz=wheelsSimData.mThresholdLongitudinalSpeed;
+ const PxU32 lowCount=wheelsSimData.mLowForwardSpeedSubStepCount;
+ const PxU32 highCount=wheelsSimData.mHighForwardSpeedSubStepCount;
+ const PxU32 count=(vz<thresholdVz ? lowCount : highCount);
+ return count;
+ }
+
+ PX_INLINE static void setInternalDynamicsToZero(PxVehicleWheelsDynData& wheels)
+ {
+ const PxU32 nbWheels4 = (wheels.mNbActiveWheels + 3) >> 2;
+ PxVehicleWheels4DynData* wheels4 = wheels.getWheel4DynData();
+ for(PxU32 i = 0; i < nbWheels4; i++)
+ {
+ wheels4[i].setInternalDynamicsToZero();
+ }
+ }
+
+ PX_INLINE static void setInternalDynamicsToZero(PxVehicleDriveDynData& drive)
+ {
+ drive.setEngineRotationSpeed(0.0f);
+ }
+
+ PX_INLINE static void setInternalDynamicsToZero(PxVehicleNoDrive* veh)
+ {
+ setInternalDynamicsToZero(veh->mWheelsDynData);
+ }
+
+ PX_INLINE static void setInternalDynamicsToZero(PxVehicleDrive4W* veh)
+ {
+ setInternalDynamicsToZero(veh->mWheelsDynData);
+ setInternalDynamicsToZero(veh->mDriveDynData);
+ }
+
+ PX_INLINE static void setInternalDynamicsToZero(PxVehicleDriveNW* veh)
+ {
+ veh->mDriveDynData.setEngineRotationSpeed(0.0f);
+ setInternalDynamicsToZero(veh->mWheelsDynData);
+ setInternalDynamicsToZero(veh->mDriveDynData);
+ }
+
+ PX_INLINE static void setInternalDynamicsToZero(PxVehicleDriveTank* veh)
+ {
+ setInternalDynamicsToZero(veh->mWheelsDynData);
+ setInternalDynamicsToZero(veh->mDriveDynData);
+ }
+
+
+ PX_INLINE static bool isOnDynamicActor(const PxVehicleWheelsSimData& wheelsSimData, const PxVehicleWheelsDynData& wheelsDynData)
+ {
+ const PxU32 numWheels4 = wheelsSimData.mNbWheels4;
+ const PxVehicleWheels4DynData* PX_RESTRICT wheels4DynDatas = wheelsDynData.mWheels4DynData;
+
+ for(PxU32 i=0;i<numWheels4;i++)
+ {
+ const PxRaycastQueryResult* raycastResults = wheels4DynDatas[i].mRaycastResults;
+ const PxSweepQueryResult* sweepResults = wheels4DynDatas[i].mSweepResults;
+
+ for(PxU32 j=0;j<4;j++)
+ {
+ if(!wheelsSimData.getIsWheelDisabled(4*i + j))
+ {
+ const PxU32 hitCount = PxU32(raycastResults ? raycastResults[j].hasBlock : sweepResults[j].hasBlock);
+ const PxLocationHit& hit = raycastResults ? static_cast<const PxLocationHit&>(raycastResults[j].block) : static_cast<const PxLocationHit&>(sweepResults[j].block);
+ if(hitCount && hit.actor && hit.actor->is<PxRigidDynamic>())
+ {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ PX_INLINE static void storeRaycasts(const PxVehicleWheels4DynData& dynData, PxWheelQueryResult* wheelQueryResults)
+ {
+ if(dynData.mRaycastResults)
+ {
+ for(PxU32 i=0;i<4;i++)
+ {
+ const PxVehicleWheels4DynData::SuspLineRaycast& raycast =
+ reinterpret_cast<const PxVehicleWheels4DynData::SuspLineRaycast&>(dynData.mQueryOrCachedHitResults);
+
+ wheelQueryResults[i].suspLineStart=raycast.mStarts[i];
+ wheelQueryResults[i].suspLineDir=raycast.mDirs[i];
+ wheelQueryResults[i].suspLineLength=raycast.mLengths[i];
+ }
+ }
+ else if(dynData.mSweepResults)
+ {
+ for(PxU32 i=0;i<4;i++)
+ {
+ const PxVehicleWheels4DynData::SuspLineSweep& sweep =
+ reinterpret_cast<const PxVehicleWheels4DynData::SuspLineSweep&>(dynData.mQueryOrCachedHitResults);
+
+ wheelQueryResults[i].suspLineStart=sweep.mStartPose[i].p;
+ wheelQueryResults[i].suspLineDir=sweep.mDirs[i];
+ wheelQueryResults[i].suspLineLength=sweep.mLengths[i];
+ }
+ }
+ else
+ {
+ for(PxU32 i=0;i<4;i++)
+ {
+ wheelQueryResults[i].suspLineStart=PxVec3(0,0,0);
+ wheelQueryResults[i].suspLineDir=PxVec3(0,0,0);
+ wheelQueryResults[i].suspLineLength=0;
+ }
+ }
+ }
+
+ PX_INLINE static void storeSuspWheelTireResults
+ (const ProcessSuspWheelTireOutputData& outputData, const PxF32* steerAngles, PxWheelQueryResult* wheelQueryResults, const PxU32 numWheels)
+ {
+ for(PxU32 i=0;i<numWheels;i++)
+ {
+ wheelQueryResults[i].isInAir=outputData.isInAir[i];
+ wheelQueryResults[i].tireContactActor=outputData.tireContactActors[i];
+ wheelQueryResults[i].tireContactShape=outputData.tireContactShapes[i];
+ wheelQueryResults[i].tireSurfaceMaterial=outputData.tireSurfaceMaterials[i];
+ wheelQueryResults[i].tireSurfaceType=outputData.tireSurfaceTypes[i];
+ wheelQueryResults[i].tireContactPoint=outputData.tireContactPoints[i];
+ wheelQueryResults[i].tireContactNormal=outputData.tireContactNormals[i];
+ wheelQueryResults[i].tireFriction=outputData.frictions[i];
+ wheelQueryResults[i].suspJounce=outputData.jounces[i];
+ wheelQueryResults[i].suspSpringForce=outputData.suspensionSpringForces[i];
+ wheelQueryResults[i].tireLongitudinalDir=outputData.tireLongitudinalDirs[i];
+ wheelQueryResults[i].tireLateralDir=outputData.tireLateralDirs[i];
+ wheelQueryResults[i].longitudinalSlip=outputData.longSlips[i];
+ wheelQueryResults[i].lateralSlip=outputData.latSlips[i];
+ wheelQueryResults[i].steerAngle=steerAngles[i];
+ }
+ }
+
+ PX_INLINE static void storeHitActorForces(const ProcessSuspWheelTireOutputData& outputData, PxVehicleWheelConcurrentUpdateData* wheelConcurrentUpdates, const PxU32 numWheels)
+ {
+ for(PxU32 i=0;i<numWheels;i++)
+ {
+ wheelConcurrentUpdates[i].hitActor=outputData.hitActors[i];
+ wheelConcurrentUpdates[i].hitActorForce+=outputData.hitActorForces[i];
+ wheelConcurrentUpdates[i].hitActorForcePosition=outputData.hitActorForcePositions[i];
+ }
+ }
+
+
+ static void shiftOrigin(const PxVec3& shift, const PxU32 numVehicles, PxVehicleWheels** vehicles);
+};
+
+void PxVehicleUpdate::updateDrive4W(
+const PxF32 timestep,
+const PxVec3& gravity, const PxF32 gravityMagnitude, const PxF32 recipGravityMagnitude,
+const PxVehicleDrivableSurfaceToTireFrictionPairs& drivableSurfaceToTireFrictionPairs,
+PxVehicleDrive4W* vehDrive4W, PxVehicleWheelQueryResult* vehWheelQueryResults, PxVehicleConcurrentUpdateData* vehConcurrentUpdates)
+{
+ PX_SIMD_GUARD; // denorm exception in transformInertiaTensor() on osx
+
+ START_TIMER(TIMER_ADMIN);
+
+ PX_CHECK_AND_RETURN(
+ vehDrive4W->mDriveDynData.mControlAnalogVals[PxVehicleDrive4WControl::eANALOG_INPUT_ACCEL]>-0.01f &&
+ vehDrive4W->mDriveDynData.mControlAnalogVals[PxVehicleDrive4WControl::eANALOG_INPUT_ACCEL]<1.01f,
+ "Illegal vehicle control value - accel must be in range (0,1)");
+ PX_CHECK_AND_RETURN(
+ vehDrive4W->mDriveDynData.mControlAnalogVals[PxVehicleDrive4WControl::eANALOG_INPUT_BRAKE]>-0.01f &&
+ vehDrive4W->mDriveDynData.mControlAnalogVals[PxVehicleDrive4WControl::eANALOG_INPUT_BRAKE]<1.01f,
+ "Illegal vehicle control value - brake must be in range (0,1)");
+ PX_CHECK_AND_RETURN(
+ vehDrive4W->mDriveDynData.mControlAnalogVals[PxVehicleDrive4WControl::eANALOG_INPUT_HANDBRAKE]>-0.01f &&
+ vehDrive4W->mDriveDynData.mControlAnalogVals[PxVehicleDrive4WControl::eANALOG_INPUT_HANDBRAKE]<1.01f,
+ "Illegal vehicle control value - handbrake must be in range (0,1)");
+ PX_CHECK_AND_RETURN(
+ vehDrive4W->mDriveDynData.mControlAnalogVals[PxVehicleDrive4WControl::eANALOG_INPUT_STEER_LEFT]>-1.01f &&
+ vehDrive4W->mDriveDynData.mControlAnalogVals[PxVehicleDrive4WControl::eANALOG_INPUT_STEER_LEFT]<1.01f,
+ "Illegal vehicle control value - left steer must be in range (-1,1)");
+ PX_CHECK_AND_RETURN(
+ vehDrive4W->mDriveDynData.mControlAnalogVals[PxVehicleDrive4WControl::eANALOG_INPUT_STEER_RIGHT]>-1.01f &&
+ vehDrive4W->mDriveDynData.mControlAnalogVals[PxVehicleDrive4WControl::eANALOG_INPUT_STEER_RIGHT]<1.01f,
+ "Illegal vehicle control value - right steer must be in range (-1,1)");
+ PX_CHECK_AND_RETURN(
+ PxAbs(vehDrive4W->mDriveDynData.mControlAnalogVals[PxVehicleDrive4WControl::eANALOG_INPUT_STEER_RIGHT]-
+ vehDrive4W->mDriveDynData.mControlAnalogVals[PxVehicleDrive4WControl::eANALOG_INPUT_STEER_LEFT])<1.01f,
+ "Illegal vehicle control value - right steer value minus left steer value must be in range (-1,1)");
+ PX_CHECK_AND_RETURN(
+ !(vehDrive4W->getRigidDynamicActor()->getRigidBodyFlags() & PxRigidBodyFlag::eKINEMATIC),
+ "Attempting to update a drive4W with a kinematic actor - this isn't allowed");
+ PX_CHECK_AND_RETURN(
+ NULL==vehWheelQueryResults || vehWheelQueryResults->nbWheelQueryResults >= vehDrive4W->mWheelsSimData.getNbWheels(),
+ "nbWheelQueryResults must always be greater than or equal to number of wheels in corresponding vehicle");
+ PX_CHECK_AND_RETURN(
+ NULL==vehConcurrentUpdates || vehConcurrentUpdates->nbConcurrentWheelUpdates >= vehDrive4W->mWheelsSimData.getNbWheels(),
+ "vehConcurrentUpdates->nbConcurrentWheelUpdates must always be greater than or equal to number of wheels in corresponding vehicle");
+
+#if PX_CHECKED
+ {
+ //Check that the sense of left/right and forward/rear is true.
+ const PxVec3 fl=vehDrive4W->mWheelsSimData.mWheels4SimData[0].getWheelCentreOffset(PxVehicleDrive4WWheelOrder::eFRONT_LEFT);
+ const PxVec3 fr=vehDrive4W->mWheelsSimData.mWheels4SimData[0].getWheelCentreOffset(PxVehicleDrive4WWheelOrder::eFRONT_RIGHT);
+ const PxVec3 rl=vehDrive4W->mWheelsSimData.mWheels4SimData[0].getWheelCentreOffset(PxVehicleDrive4WWheelOrder::eREAR_LEFT);
+ const PxVec3 rr=vehDrive4W->mWheelsSimData.mWheels4SimData[0].getWheelCentreOffset(PxVehicleDrive4WWheelOrder::eREAR_RIGHT);
+ const PxVec3 right=gRight;
+ const PxF32 s0=computeSign((fr-fl).dot(right));
+ const PxF32 s1=computeSign((rr-rl).dot(right));
+ PX_CHECK_AND_RETURN(0==s0 || 0==s1 || s0==s1, "PxVehicle4W does not obey the rule that the eFRONT_RIGHT/eREAR_RIGHT wheels are to the right of the eFRONT_LEFT/eREAR_LEFT wheels");
+ }
+#endif
+
+ END_TIMER(TIMER_ADMIN);
+ START_TIMER(TIMER_GRAPHS);
+
+#if PX_DEBUG_VEHICLE_ON
+ for(PxU32 i=0;i<vehDrive4W->mWheelsSimData.mNbWheels4;i++)
+ {
+ updateGraphDataInternalWheelDynamics(4*i,vehDrive4W->mWheelsDynData.mWheels4DynData[i].mWheelSpeeds);
+ }
+ updateGraphDataInternalEngineDynamics(vehDrive4W->mDriveDynData.getEngineRotationSpeed());
+#endif
+
+ END_TIMER(TIMER_GRAPHS);
+ START_TIMER(TIMER_ADMIN);
+
+ //Unpack the vehicle.
+ //Unpack the 4W simulation and instanced dynamics components.
+ const PxVehicleWheels4SimData* wheels4SimDatas=vehDrive4W->mWheelsSimData.mWheels4SimData;
+ const PxVehicleTireLoadFilterData& tireLoadFilterData=vehDrive4W->mWheelsSimData.mNormalisedLoadFilter;
+ PxVehicleWheels4DynData* wheels4DynDatas=vehDrive4W->mWheelsDynData.mWheels4DynData;
+ const PxU32 numWheels4=vehDrive4W->mWheelsSimData.mNbWheels4;
+ const PxU32 numActiveWheels=vehDrive4W->mWheelsSimData.mNbActiveWheels;
+ const PxU32 numActiveWheelsInLast4=4-(4*numWheels4 - numActiveWheels);
+ const PxVehicleDriveSimData4W driveSimData=vehDrive4W->mDriveSimData;
+ PxVehicleDriveDynData& driveDynData=vehDrive4W->mDriveDynData;
+ PxRigidDynamic* vehActor=vehDrive4W->mActor;
+
+ //We need to store that data we are going to write to actors so we can do this at the end in one go with fewer write locks.
+ PxVehicleWheelConcurrentUpdateData wheelConcurrentUpdates[PX_MAX_NB_WHEELS];
+ PxVehicleConcurrentUpdateData vehicleConcurrentUpdates;
+ vehicleConcurrentUpdates.nbConcurrentWheelUpdates = numActiveWheels;
+ vehicleConcurrentUpdates.concurrentWheelUpdates = wheelConcurrentUpdates;
+
+ //Test if a non-zero drive torque was applied or if a non-zero steer angle was applied.
+ bool finiteInputApplied=false;
+ if(0!=driveDynData.getAnalogInput(PxVehicleDrive4WControl::eANALOG_INPUT_STEER_LEFT) ||
+ 0!=driveDynData.getAnalogInput(PxVehicleDrive4WControl::eANALOG_INPUT_STEER_RIGHT) ||
+ 0!=driveDynData.getAnalogInput(PxVehicleDrive4WControl::eANALOG_INPUT_ACCEL) ||
+ driveDynData.getGearDown() || driveDynData.getGearUp())
+ {
+ finiteInputApplied=true;
+ }
+
+ //Awake or sleep.
+ if(vehActor->isSleeping())
+ {
+ if(finiteInputApplied)
+ {
+ //Driving inputs so we need the actor to start moving.
+ vehicleConcurrentUpdates.wakeup = true;
+ }
+ else if(isOnDynamicActor(vehDrive4W->mWheelsSimData, vehDrive4W->mWheelsDynData))
+ {
+ //Driving on dynamic so we need to keep moving.
+ vehicleConcurrentUpdates.wakeup = true;
+ }
+ else
+ {
+ //No driving inputs and the actor is asleep.
+ //Set internal dynamics to zero.
+ setInternalDynamicsToZero(vehDrive4W);
+ if(vehConcurrentUpdates) vehConcurrentUpdates->staySleeping = true;
+ return;
+ }
+ }
+
+ //In each block of 4 wheels record how many wheels are active.
+ PxU32 numActiveWheelsPerBlock4[PX_MAX_NB_SUSPWHEELTIRE4]={0,0,0,0,0};
+ numActiveWheelsPerBlock4[0]=PxMin(numActiveWheels,PxU32(4));
+ for(PxU32 i=1;i<numWheels4-1;i++)
+ {
+ numActiveWheelsPerBlock4[i]=4;
+ }
+ numActiveWheelsPerBlock4[numWheels4-1]=numActiveWheelsInLast4;
+ PX_ASSERT(numActiveWheels == numActiveWheelsPerBlock4[0] + numActiveWheelsPerBlock4[1] + numActiveWheelsPerBlock4[2] + numActiveWheelsPerBlock4[3] + numActiveWheelsPerBlock4[4]);
+
+ //Organise the shader data in blocks of 4.
+ PxVehicleTireForceCalculator4 tires4ForceCalculators[PX_MAX_NB_SUSPWHEELTIRE4];
+ for(PxU32 i=0;i<numWheels4;i++)
+ {
+ tires4ForceCalculators[i].mShaderData[0]=vehDrive4W->mWheelsDynData.mTireForceCalculators->mShaderData[4*i+0];
+ tires4ForceCalculators[i].mShaderData[1]=vehDrive4W->mWheelsDynData.mTireForceCalculators->mShaderData[4*i+1];
+ tires4ForceCalculators[i].mShaderData[2]=vehDrive4W->mWheelsDynData.mTireForceCalculators->mShaderData[4*i+2];
+ tires4ForceCalculators[i].mShaderData[3]=vehDrive4W->mWheelsDynData.mTireForceCalculators->mShaderData[4*i+3];
+ tires4ForceCalculators[i].mShader=vehDrive4W->mWheelsDynData.mTireForceCalculators->mShader;
+ }
+
+ //Mark the constraints as dirty to force them to be updated in the sdk.
+ for(PxU32 i=0;i<numWheels4;i++)
+ {
+ wheels4DynDatas[i].getVehicletConstraintShader().mConstraint->markDirty();
+ }
+
+ //We need to store data to pose the wheels at the end.
+ PxWheelQueryResult wheelQueryResults[PX_MAX_NB_WHEELS];
+
+ END_TIMER(TIMER_ADMIN);
+ START_TIMER(TIMER_COMPONENTS_UPDATE);
+
+ //Center of mass local pose.
+ PxTransform carChassisCMLocalPose;
+ //Compute the transform of the center of mass.
+ PxTransform origCarChassisTransform;
+ PxTransform carChassisTransform;
+ //Inverse mass and inertia to apply the tire/suspension forces as impulses.
+ PxF32 inverseChassisMass;
+ PxVec3 inverseInertia;
+ //Linear and angular velocity.
+ PxVec3 carChassisLinVel;
+ PxVec3 carChassisAngVel;
+ {
+ carChassisCMLocalPose = vehActor->getCMassLocalPose();
+ origCarChassisTransform = vehActor->getGlobalPose().transform(carChassisCMLocalPose);
+ carChassisTransform = origCarChassisTransform;
+ const PxF32 chassisMass = vehActor->getMass();
+ inverseChassisMass = 1.0f/chassisMass;
+ inverseInertia = vehActor->getMassSpaceInvInertiaTensor();
+ carChassisLinVel = vehActor->getLinearVelocity();
+ carChassisAngVel = vehActor->getAngularVelocity();
+ }
+
+ //Get the local poses of the wheel shapes.
+ //These are the poses from the last frame and equal to the poses used for the raycast we will process.
+ PxQuat wheelLocalPoseRotations[PX_MAX_NB_WHEELS];
+ PxF32 wheelThetas[PX_MAX_NB_WHEELS];
+ {
+ for (PxU32 i = 0; i < numActiveWheels; i++)
+ {
+ const PxI32 shapeId = vehDrive4W->mWheelsSimData.getWheelShapeMapping(i);
+ if (-1 != shapeId)
+ {
+ PxShape* shape = NULL;
+ vehActor->getShapes(&shape, 1, PxU32(shapeId));
+ wheelLocalPoseRotations[i] = shape->getLocalPose().q;
+ wheelThetas[i] = vehDrive4W->mWheelsDynData.getWheelRotationAngle(i);
+ }
+ }
+ }
+
+ //Update the auto-box and decide whether to change gear up or down.
+ PxF32 autoboxCompensatedAnalogAccel = driveDynData.mControlAnalogVals[PxVehicleDrive4WControl::eANALOG_INPUT_ACCEL];
+ if(driveDynData.getUseAutoGears())
+ {
+ autoboxCompensatedAnalogAccel = processAutoBox(PxVehicleDrive4WControl::eANALOG_INPUT_ACCEL,timestep,driveSimData,driveDynData);
+ }
+
+ //Process gear-up/gear-down commands.
+ {
+ const PxVehicleGearsData& gearsData=driveSimData.getGearsData();
+ processGears(timestep,gearsData,driveDynData);
+ }
+
+ //Clutch strength;
+ PxF32 K;
+ {
+ const PxVehicleClutchData& clutchData=driveSimData.getClutchData();
+ const PxU32 currentGear=driveDynData.getCurrentGear();
+ K=computeClutchStrength(clutchData, currentGear);
+ }
+
+ //Clutch accuracy
+ PxVehicleClutchAccuracyMode::Enum clutchAccuracyMode;
+ PxU32 clutchMaxIterations;
+ {
+ const PxVehicleClutchData& clutchData=driveSimData.getClutchData();
+ clutchAccuracyMode = clutchData.mAccuracyMode;
+ clutchMaxIterations = clutchData.mEstimateIterations;
+ }
+
+ //Gear ratio.
+ PxF32 G;
+ PxU32 currentGear;
+ {
+ const PxVehicleGearsData& gearsData=driveSimData.getGearsData();
+ currentGear=driveDynData.getCurrentGear();
+ G=computeGearRatio(gearsData,currentGear);
+#if PX_DEBUG_VEHICLE_ON
+ updateGraphDataGearRatio(G);
+#endif
+ }
+
+ //Retrieve control values from vehicle controls.
+ PxF32 accel,brake,handbrake,steerLeft,steerRight;
+ PxF32 steer;
+ bool isIntentionToAccelerate;
+ {
+ getVehicle4WControlValues(driveDynData,accel,brake,handbrake,steerLeft,steerRight);
+ steer=steerRight-steerLeft;
+ accel=autoboxCompensatedAnalogAccel;
+ isIntentionToAccelerate = (accel>0.0f && 0.0f==brake && 0.0f==handbrake && PxVehicleGearsData::eNEUTRAL != currentGear);
+#if PX_DEBUG_VEHICLE_ON
+ updateGraphDataControlInputs(accel,brake,handbrake,steerLeft,steerRight);
+#endif
+ }
+
+ //Active wheels (wheels which have not been disabled).
+ bool activeWheelStates[4]={false,false,false,false};
+ {
+ computeWheelActiveStates(4*0, vehDrive4W->mWheelsSimData.mActiveWheelsBitmapBuffer, activeWheelStates);
+ }
+
+ //Get the drive wheels (the first 4 wheels are the drive wheels).
+ const PxVehicleWheels4SimData& wheels4SimData=wheels4SimDatas[0];
+ PxVehicleWheels4DynData& wheels4DynData=wheels4DynDatas[0];
+ const PxVehicleTireForceCalculator4& tires4ForceCalculator=tires4ForceCalculators[0];
+
+ //Contribution of each driven wheel to average wheel speed at clutch.
+ //With 4 driven wheels the average wheel speed at clutch is
+ //wAve = alpha0*w0 + alpha1*w1 + alpha2*w2 + alpha3*w3.
+ //This next bit of code computes alpha0,alpha1,alpha2,alpha3.
+ //For rear wheel drive alpha0=alpha1=0
+ //For front wheel drive alpha2=alpha3=0
+ PxF32 aveWheelSpeedContributions[4]={0.0f,0.0f,0.0f,0.0f};
+ {
+ const PxVehicleDifferential4WData& diffData=driveSimData.getDiffData();
+ computeDiffAveWheelSpeedContributions(diffData,handbrake,aveWheelSpeedContributions);
+
+#if PX_DEBUG_VEHICLE_ON
+ updateGraphDataClutchSlip(wheels4DynData.mWheelSpeeds,aveWheelSpeedContributions,driveDynData.getEngineRotationSpeed(),G);
+#endif
+
+ PX_CHECK_AND_RETURN(
+ (activeWheelStates[0] || 0.0f==aveWheelSpeedContributions[0]) &&
+ (activeWheelStates[1] || 0.0f==aveWheelSpeedContributions[1]) &&
+ (activeWheelStates[2] || 0.0f==aveWheelSpeedContributions[2]) &&
+ (activeWheelStates[3] || 0.0f==aveWheelSpeedContributions[3]),
+ "PxVehicleDifferential4WData must be configured so that no torque is delivered to a disabled wheel");
+ }
+
+ //Compute a per-wheel accelerator pedal value.
+ bool isAccelApplied[4]={false,false,false,false};
+ if(isIntentionToAccelerate)
+ {
+ PX_ASSERT(accel>0);
+ computeIsAccelApplied(aveWheelSpeedContributions, isAccelApplied);
+ }
+
+ //Ackermann-corrected steering angles.
+ //http://en.wikipedia.org/wiki/Ackermann_steering_geometry
+ PxF32 steerAngles[4]={0.0f,0.0f,0.0f,0.0f};
+ {
+ computeAckermannCorrectedSteerAngles(driveSimData,wheels4SimData,steer,steerAngles);
+ }
+
+ END_TIMER(TIMER_COMPONENTS_UPDATE);
+ START_TIMER(TIMER_ADMIN);
+
+ //Store the susp line raycast data.
+ for(PxU32 i=0;i<numWheels4;i++)
+ {
+ storeRaycasts(wheels4DynDatas[i], &wheelQueryResults[4*i]);
+ }
+
+ //Ready to do the update.
+ PxVec3 carChassisLinVelOrig=carChassisLinVel;
+ PxVec3 carChassisAngVelOrig=carChassisAngVel;
+ const PxU32 numSubSteps=computeNumberOfSubsteps(vehDrive4W->mWheelsSimData,carChassisLinVel,carChassisTransform,gForward);
+ const PxF32 timeFraction=1.0f/(1.0f*numSubSteps);
+ const PxF32 subTimestep=timestep*timeFraction;
+ const PxF32 recipSubTimeStep=1.0f/subTimestep;
+ const PxF32 recipTimestep=1.0f/timestep;
+ const PxF32 minLongSlipDenominator=vehDrive4W->mWheelsSimData.mMinLongSlipDenominator;
+ ProcessSuspWheelTireConstData constData={timeFraction, subTimestep, recipSubTimeStep, gravity, gravityMagnitude, recipGravityMagnitude, false, minLongSlipDenominator, vehActor, &drivableSurfaceToTireFrictionPairs};
+
+ END_TIMER(TIMER_ADMIN);
+
+ for(PxU32 k=0;k<numSubSteps;k++)
+ {
+ //Set the force and torque for the current update to zero.
+ PxVec3 chassisForce(0,0,0);
+ PxVec3 chassisTorque(0,0,0);
+
+ START_TIMER(TIMER_COMPONENTS_UPDATE);
+
+ //Update the drive/steer wheels and engine.
+ {
+ //Compute the brake torques.
+ PxF32 brakeTorques[4]={0.0f,0.0f,0.0f,0.0f};
+ bool isBrakeApplied[4]={false,false,false,false};
+ computeBrakeAndHandBrakeTorques
+ (&wheels4SimData.getWheelData(0),wheels4DynData.mWheelSpeeds,brake,handbrake,
+ brakeTorques,isBrakeApplied);
+
+ END_TIMER(TIMER_COMPONENTS_UPDATE);
+ START_TIMER(TIMER_WHEELS);
+
+ //Compute jounces, slips, tire forces, suspension forces etc.
+ ProcessSuspWheelTireInputData inputData=
+ {
+ isIntentionToAccelerate, isAccelApplied, isBrakeApplied, steerAngles, activeWheelStates,
+ carChassisTransform, carChassisLinVel, carChassisAngVel,
+ wheelLocalPoseRotations, wheelThetas, &wheels4SimData, &wheels4DynData, &tires4ForceCalculator, &tireLoadFilterData, numActiveWheelsPerBlock4[0]
+ };
+ ProcessSuspWheelTireOutputData outputData;
+ processSuspTireWheels(0, constData, inputData, outputData);
+ updateLowSpeedTimers(outputData.newLowForwardSpeedTimers, const_cast<PxF32*>(inputData.vehWheels4DynData->mTireLowForwardSpeedTimers));
+ updateLowSpeedTimers(outputData.newLowSideSpeedTimers, const_cast<PxF32*>(inputData.vehWheels4DynData->mTireLowSideSpeedTimers));
+ updateJounces(outputData.jounces, const_cast<PxF32*>(inputData.vehWheels4DynData->mJounces));
+ if((numSubSteps-1) == k)
+ {
+ updateCachedHitData(outputData.cachedHitCounts, outputData.cachedHitPlanes, outputData.cachedHitDistances, outputData.cachedFrictionMultipliers, outputData.cachedHitQueryTypes, &wheels4DynData);
+ }
+ chassisForce+=outputData.chassisForce;
+ chassisTorque+=outputData.chassisTorque;
+ if(0 == k)
+ {
+ wheels4DynData.mVehicleConstraints->mData=outputData.vehConstraintData;
+ }
+ storeSuspWheelTireResults(outputData, inputData.steerAngles, &wheelQueryResults[4*0], numActiveWheelsPerBlock4[0]);
+ storeHitActorForces(outputData, &vehicleConcurrentUpdates.concurrentWheelUpdates[4*0], numActiveWheelsPerBlock4[0]);
+
+ END_TIMER(TIMER_WHEELS);
+ START_TIMER(TIMER_INTERNAL_DYNAMICS_SOLVER);
+
+ //Diff torque ratios needed (how we split the torque between the drive wheels).
+ //The sum of the torque ratios is always 1.0f.
+ //The drive torque delivered to each wheel is the total available drive torque multiplied by the
+ //diff torque ratio for each wheel.
+ PxF32 diffTorqueRatios[4]={0.0f,0.0f,0.0f,0.0f};
+ computeDiffTorqueRatios(driveSimData.getDiffData(),handbrake,wheels4DynData.mWheelSpeeds,diffTorqueRatios);
+
+ PX_CHECK_AND_RETURN(
+ (activeWheelStates[0] || 0.0f==diffTorqueRatios[0]) &&
+ (activeWheelStates[1] || 0.0f==diffTorqueRatios[1]) &&
+ (activeWheelStates[2] || 0.0f==diffTorqueRatios[2]) &&
+ (activeWheelStates[3] || 0.0f==diffTorqueRatios[3]),
+ "PxVehicleDifferential4WData must be configured so that no torque is delivered to a disabled wheel");
+
+ PxF32 engineDriveTorque;
+ {
+ const PxVehicleEngineData& engineData=driveSimData.getEngineData();
+ const PxF32 engineOmega=driveDynData.getEngineRotationSpeed();
+ engineDriveTorque=computeEngineDriveTorque(engineData,engineOmega,accel);
+ #if PX_DEBUG_VEHICLE_ON
+ updateGraphDataEngineDriveTorque(engineDriveTorque);
+ #endif
+ }
+
+ PxF32 engineDampingRate;
+ {
+ const PxVehicleEngineData& engineData=driveSimData.getEngineData();
+ engineDampingRate=computeEngineDampingRate(engineData,currentGear,accel);
+ }
+
+ //Update the wheel and engine speeds - 5x5 matrix coupling engine and wheels.
+ ImplicitSolverInput implicitSolverInput=
+ {
+ subTimestep,
+ brake, handbrake,
+ K, G,
+ clutchAccuracyMode, clutchMaxIterations,
+ engineDriveTorque, engineDampingRate,
+ diffTorqueRatios, aveWheelSpeedContributions,
+ brakeTorques, isBrakeApplied, outputData.tireTorques,
+ 1, 4,
+ &wheels4SimData, &driveSimData
+ };
+ ImplicitSolverOutput implicitSolverOutput=
+ {
+ &wheels4DynData, &driveDynData
+ };
+ solveDrive4WInternaDynamicsEnginePlusDrivenWheels(implicitSolverInput, &implicitSolverOutput);
+
+ END_TIMER(TIMER_INTERNAL_DYNAMICS_SOLVER);
+ START_TIMER(TIMER_POSTUPDATE1);
+
+ //Integrate wheel rotation angle (theta += omega*dt)
+ integrateWheelRotationAngles
+ (subTimestep,
+ K,G,engineDriveTorque,
+ outputData.jounces,diffTorqueRatios,outputData.forwardSpeeds,isBrakeApplied,
+ driveSimData,wheels4SimData,
+ driveDynData,wheels4DynData);
+ }
+
+ END_TIMER(TIMER_POSTUPDATE1);
+
+ //////////////////////////////////////////////////////////////////////////
+ //susp and tire forces from extra wheels (non-driven wheels)
+ //////////////////////////////////////////////////////////////////////////
+ for(PxU32 j=1;j<numWheels4;j++)
+ {
+ //Only the driven wheels can steer but the non-drive wheels can still have a toe angle.
+ const PxVehicleWheelData& wheelData0=wheels4SimDatas[j].getWheelData(0);
+ const PxVehicleWheelData& wheelData1=wheels4SimDatas[j].getWheelData(1);
+ const PxVehicleWheelData& wheelData2=wheels4SimDatas[j].getWheelData(2);
+ const PxVehicleWheelData& wheelData3=wheels4SimDatas[j].getWheelData(3);
+ const PxF32 toe0=wheelData0.mToeAngle;
+ const PxF32 toe1=wheelData1.mToeAngle;
+ const PxF32 toe2=wheelData2.mToeAngle;
+ const PxF32 toe3=wheelData3.mToeAngle;
+ PxF32 extraWheelSteerAngles[4]={toe0,toe1,toe2,toe3};
+
+ //Only the driven wheels are connected to the diff.
+ PxF32 extraWheelsDiffTorqueRatios[4]={0.0f,0.0f,0.0f,0.0f};
+ bool extraIsAccelApplied[4]={false,false,false,false};
+
+ //The extra wheels do have brakes.
+ PxF32 extraWheelBrakeTorques[4]={0.0f,0.0f,0.0f,0.0f};
+ bool extraIsBrakeApplied[4]={false,false,false,false};
+ computeBrakeAndHandBrakeTorques
+ (&wheels4SimDatas[j].getWheelData(0),wheels4DynDatas[j].mWheelSpeeds,brake,handbrake,
+ extraWheelBrakeTorques,extraIsBrakeApplied);
+
+ //The extra wheels can be disabled or enabled.
+ bool extraWheelActiveStates[4]={false,false,false,false};
+ computeWheelActiveStates(4*j, vehDrive4W->mWheelsSimData.mActiveWheelsBitmapBuffer, extraWheelActiveStates);
+
+ ProcessSuspWheelTireInputData extraInputData=
+ {
+ isIntentionToAccelerate, extraIsAccelApplied, extraIsBrakeApplied, extraWheelSteerAngles, extraWheelActiveStates,
+ carChassisTransform, carChassisLinVel, carChassisAngVel,
+ &wheelLocalPoseRotations[j], &wheelThetas[j], &wheels4SimDatas[j], &wheels4DynDatas[j], &tires4ForceCalculators[j], &tireLoadFilterData, numActiveWheelsPerBlock4[j],
+ };
+ ProcessSuspWheelTireOutputData extraOutputData;
+ processSuspTireWheels(4*j, constData, extraInputData, extraOutputData);
+ updateLowSpeedTimers(extraOutputData.newLowForwardSpeedTimers, const_cast<PxF32*>(extraInputData.vehWheels4DynData->mTireLowForwardSpeedTimers));
+ updateLowSpeedTimers(extraOutputData.newLowSideSpeedTimers, const_cast<PxF32*>(extraInputData.vehWheels4DynData->mTireLowSideSpeedTimers));
+ updateJounces(extraOutputData.jounces, const_cast<PxF32*>(extraInputData.vehWheels4DynData->mJounces));
+ if((numSubSteps-1) == k)
+ {
+ updateCachedHitData(extraOutputData.cachedHitCounts, extraOutputData.cachedHitPlanes, extraOutputData.cachedHitDistances, extraOutputData.cachedFrictionMultipliers, extraOutputData.cachedHitQueryTypes, &wheels4DynDatas[j]);
+ }
+ chassisForce+=extraOutputData.chassisForce;
+ chassisTorque+=extraOutputData.chassisTorque;
+ if(0 == k)
+ {
+ wheels4DynDatas[j].mVehicleConstraints->mData=extraOutputData.vehConstraintData;
+ }
+ storeSuspWheelTireResults(extraOutputData, extraInputData.steerAngles, &wheelQueryResults[4*j], numActiveWheelsPerBlock4[j]);
+ storeHitActorForces(extraOutputData, &vehicleConcurrentUpdates.concurrentWheelUpdates[4*j], numActiveWheelsPerBlock4[j]);
+
+ //Integrate the tire torques (omega += (tireTorque + brakeTorque)*dt)
+ integrateUndriveWheelRotationSpeeds(subTimestep, brake, handbrake, extraOutputData.tireTorques, extraWheelBrakeTorques, wheels4SimDatas[j], wheels4DynDatas[j]);
+
+ //Integrate wheel rotation angle (theta += omega*dt)
+ integrateWheelRotationAngles
+ (subTimestep,
+ 0,0,0,
+ extraOutputData.jounces,extraWheelsDiffTorqueRatios,extraOutputData.forwardSpeeds,extraIsBrakeApplied,
+ driveSimData,wheels4SimDatas[j],
+ driveDynData,wheels4DynDatas[j]);
+ }
+
+ START_TIMER(TIMER_POSTUPDATE2);
+
+ //Apply the anti-roll suspension.
+ procesAntiRollSuspension(vehDrive4W->mWheelsSimData, carChassisTransform, wheelQueryResults, chassisTorque);
+
+ //Integrate one sustep.
+ integrateBody(inverseChassisMass, inverseInertia ,chassisForce, chassisTorque, subTimestep, carChassisLinVel, carChassisAngVel, carChassisTransform);
+
+ END_TIMER(TIMER_POSTUPDATE2);
+ }
+
+ START_TIMER(TIMER_POSTUPDATE3);
+
+ //Set the new chassis linear/angular velocity.
+ if(!gApplyForces)
+ {
+ vehicleConcurrentUpdates.linearMomentumChange = carChassisLinVel;
+ vehicleConcurrentUpdates.angularMomentumChange = carChassisAngVel;
+ }
+ else
+ {
+ //integration steps are:
+ //v = v0 + a*dt (1)
+ //x = x0 + v*dt (2)
+ //Sub (2) into (1.
+ //x = x0 + v0*dt + a*dt*dt;
+ //Rearrange for a
+ //a = (x -x0 - v0*dt)/(dt*dt) = [(x-x0)/dt - v0/dt]
+ //Rearrange again with v = (x-x0)/dt
+ //a = (v - v0)/dt
+ vehicleConcurrentUpdates.linearMomentumChange = (carChassisLinVel-carChassisLinVelOrig)*recipTimestep;;
+ vehicleConcurrentUpdates.angularMomentumChange = (carChassisAngVel-carChassisAngVelOrig)*recipTimestep;;
+ }
+
+ //Compute and pose the wheels from jounces, rotations angles, and steer angles.
+ PxTransform localPoses0[4] = {PxTransform(PxIdentity), PxTransform(PxIdentity), PxTransform(PxIdentity), PxTransform(PxIdentity)};
+ computeWheelLocalPoses(wheels4SimDatas[0],wheels4DynDatas[0],&wheelQueryResults[4*0],numActiveWheelsPerBlock4[0],carChassisCMLocalPose,localPoses0);
+ //Copy the poses to the wheelQueryResults
+ wheelQueryResults[4*0 + 0].localPose = localPoses0[0];
+ wheelQueryResults[4*0 + 1].localPose = localPoses0[1];
+ wheelQueryResults[4*0 + 2].localPose = localPoses0[2];
+ wheelQueryResults[4*0 + 3].localPose = localPoses0[3];
+ //Copy the poses to the concurrent update data.
+ vehicleConcurrentUpdates.concurrentWheelUpdates[4*0 + 0].localPose = localPoses0[0];
+ vehicleConcurrentUpdates.concurrentWheelUpdates[4*0 + 1].localPose = localPoses0[1];
+ vehicleConcurrentUpdates.concurrentWheelUpdates[4*0 + 2].localPose = localPoses0[2];
+ vehicleConcurrentUpdates.concurrentWheelUpdates[4*0 + 3].localPose = localPoses0[3];
+ for(PxU32 i=1;i<numWheels4;i++)
+ {
+ PxTransform localPoses[4] = {PxTransform(PxIdentity), PxTransform(PxIdentity), PxTransform(PxIdentity), PxTransform(PxIdentity)};
+ computeWheelLocalPoses(wheels4SimDatas[i],wheels4DynDatas[i],&wheelQueryResults[4*i],numActiveWheelsPerBlock4[i],carChassisCMLocalPose,localPoses);
+ //Copy the poses to the wheelQueryResults
+ wheelQueryResults[4*i + 0].localPose = localPoses[0];
+ wheelQueryResults[4*i + 1].localPose = localPoses[1];
+ wheelQueryResults[4*i + 2].localPose = localPoses[2];
+ wheelQueryResults[4*i + 3].localPose = localPoses[3];
+ //Copy the poses to the concurrent update data.
+ vehicleConcurrentUpdates.concurrentWheelUpdates[4*i + 0].localPose = localPoses[0];
+ vehicleConcurrentUpdates.concurrentWheelUpdates[4*i + 1].localPose = localPoses[1];
+ vehicleConcurrentUpdates.concurrentWheelUpdates[4*i + 2].localPose = localPoses[2];
+ vehicleConcurrentUpdates.concurrentWheelUpdates[4*i + 3].localPose = localPoses[3];
+ }
+
+ if(vehWheelQueryResults && vehWheelQueryResults->wheelQueryResults)
+ {
+ PxMemCopy(vehWheelQueryResults->wheelQueryResults, wheelQueryResults, sizeof(PxWheelQueryResult)*numActiveWheels);
+ }
+
+ if(vehConcurrentUpdates)
+ {
+ //Copy across to input data structure so that writes can be applied later.
+ PxMemCopy(vehConcurrentUpdates->concurrentWheelUpdates, vehicleConcurrentUpdates.concurrentWheelUpdates, sizeof(PxVehicleWheelConcurrentUpdateData)*numActiveWheels);
+ vehConcurrentUpdates->linearMomentumChange = vehicleConcurrentUpdates.linearMomentumChange;
+ vehConcurrentUpdates->angularMomentumChange = vehicleConcurrentUpdates.angularMomentumChange;
+ vehConcurrentUpdates->staySleeping = vehicleConcurrentUpdates.staySleeping;
+ vehConcurrentUpdates->wakeup = vehicleConcurrentUpdates.wakeup;
+ }
+ else
+ {
+ //Apply the writes immediately.
+ PxVehicleWheels* vehWheels[1]={vehDrive4W};
+ PxVehiclePostUpdates(&vehicleConcurrentUpdates, 1, vehWheels);
+ }
+
+ END_TIMER(TIMER_POSTUPDATE3);
+}
+
+void PxVehicleUpdate::updateDriveNW
+(const PxF32 timestep,
+ const PxVec3& gravity, const PxF32 gravityMagnitude, const PxF32 recipGravityMagnitude,
+ const PxVehicleDrivableSurfaceToTireFrictionPairs& drivableSurfaceToTireFrictionPairs,
+ PxVehicleDriveNW* vehDriveNW, PxVehicleWheelQueryResult* vehWheelQueryResults, PxVehicleConcurrentUpdateData* vehConcurrentUpdates)
+{
+ PX_SIMD_GUARD; // denorm exception triggered in transformInertiaTensor() on osx
+
+ START_TIMER(TIMER_ADMIN);
+
+ PX_CHECK_AND_RETURN(
+ vehDriveNW->mDriveDynData.mControlAnalogVals[PxVehicleDriveNWControl::eANALOG_INPUT_ACCEL]>-0.01f &&
+ vehDriveNW->mDriveDynData.mControlAnalogVals[PxVehicleDriveNWControl::eANALOG_INPUT_ACCEL]<1.01f,
+ "Illegal vehicle control value - accel must be in range (0,1)");
+ PX_CHECK_AND_RETURN(
+ vehDriveNW->mDriveDynData.mControlAnalogVals[PxVehicleDriveNWControl::eANALOG_INPUT_BRAKE]>-0.01f &&
+ vehDriveNW->mDriveDynData.mControlAnalogVals[PxVehicleDriveNWControl::eANALOG_INPUT_BRAKE]<1.01f,
+ "Illegal vehicle control value - brake must be in range (0,1)");
+ PX_CHECK_AND_RETURN(
+ vehDriveNW->mDriveDynData.mControlAnalogVals[PxVehicleDriveNWControl::eANALOG_INPUT_HANDBRAKE]>-0.01f &&
+ vehDriveNW->mDriveDynData.mControlAnalogVals[PxVehicleDriveNWControl::eANALOG_INPUT_HANDBRAKE]<1.01f,
+ "Illegal vehicle control value - handbrake must be in range (0,1)");
+ PX_CHECK_AND_RETURN(
+ vehDriveNW->mDriveDynData.mControlAnalogVals[PxVehicleDriveNWControl::eANALOG_INPUT_STEER_LEFT]>-1.01f &&
+ vehDriveNW->mDriveDynData.mControlAnalogVals[PxVehicleDriveNWControl::eANALOG_INPUT_STEER_LEFT]<1.01f,
+ "Illegal vehicle control value - left steer must be in range (-1,1)");
+ PX_CHECK_AND_RETURN(
+ vehDriveNW->mDriveDynData.mControlAnalogVals[PxVehicleDriveNWControl::eANALOG_INPUT_STEER_RIGHT]>-1.01f &&
+ vehDriveNW->mDriveDynData.mControlAnalogVals[PxVehicleDriveNWControl::eANALOG_INPUT_STEER_RIGHT]<1.01f,
+ "Illegal vehicle control value - right steer must be in range (-1,1)");
+ PX_CHECK_AND_RETURN(
+ PxAbs(vehDriveNW->mDriveDynData.mControlAnalogVals[PxVehicleDriveNWControl::eANALOG_INPUT_STEER_RIGHT]-
+ vehDriveNW->mDriveDynData.mControlAnalogVals[PxVehicleDriveNWControl::eANALOG_INPUT_STEER_LEFT])<1.01f,
+ "Illegal vehicle control value - right steer value minus left steer value must be in range (-1,1)");
+ PX_CHECK_AND_RETURN(
+ !(vehDriveNW->getRigidDynamicActor()->getRigidBodyFlags() & PxRigidBodyFlag::eKINEMATIC),
+ "Attempting to update a drive4W with a kinematic actor - this isn't allowed");
+ PX_CHECK_AND_RETURN(
+ NULL==vehWheelQueryResults || vehWheelQueryResults->nbWheelQueryResults >= vehDriveNW->mWheelsSimData.getNbWheels(),
+ "nbWheelQueryResults must always be greater than or equal to number of wheels in corresponding vehicle");
+
+#if PX_CHECKED
+ for(PxU32 i=0;i<vehDriveNW->mWheelsSimData.getNbWheels();i++)
+ {
+ PX_CHECK_AND_RETURN(!vehDriveNW->mWheelsSimData.getIsWheelDisabled(i) || !vehDriveNW->mDriveSimData.getDiffData().getIsDrivenWheel(i),
+ "PxVehicleDifferentialNWData must be configured so that no torque is delivered to a disabled wheel");
+ }
+#endif
+
+ END_TIMER(TIMER_ADMIN);
+ START_TIMER(TIMER_GRAPHS);
+
+#if PX_DEBUG_VEHICLE_ON
+ for(PxU32 i=0;i<vehDriveNW->mWheelsSimData.mNbWheels4;i++)
+ {
+ updateGraphDataInternalWheelDynamics(4*i,vehDriveNW->mWheelsDynData.mWheels4DynData[i].mWheelSpeeds);
+ }
+ updateGraphDataInternalEngineDynamics(vehDriveNW->mDriveDynData.getEngineRotationSpeed());
+#endif
+
+ END_TIMER(TIMER_GRAPHS);
+ START_TIMER(TIMER_ADMIN);
+
+ //Unpack the vehicle.
+ //Unpack the NW simulation and instanced dynamics components.
+ const PxVehicleWheels4SimData* wheels4SimDatas=vehDriveNW->mWheelsSimData.mWheels4SimData;
+ const PxVehicleTireLoadFilterData& tireLoadFilterData=vehDriveNW->mWheelsSimData.mNormalisedLoadFilter;
+ PxVehicleWheels4DynData* wheels4DynDatas=vehDriveNW->mWheelsDynData.mWheels4DynData;
+ const PxU32 numWheels4=vehDriveNW->mWheelsSimData.mNbWheels4;
+ const PxU32 numActiveWheels=vehDriveNW->mWheelsSimData.mNbActiveWheels;
+ const PxU32 numActiveWheelsInLast4=4-(4*numWheels4 - numActiveWheels);
+ const PxVehicleDriveSimDataNW driveSimData=vehDriveNW->mDriveSimData;
+ PxVehicleDriveDynData& driveDynData=vehDriveNW->mDriveDynData;
+ PxRigidDynamic* vehActor=vehDriveNW->mActor;
+
+ //We need to store that data we are going to write to actors so we can do this at the end in one go with fewer write locks.
+ PxVehicleWheelConcurrentUpdateData wheelConcurrentUpdates[PX_MAX_NB_WHEELS];
+ PxVehicleConcurrentUpdateData vehicleConcurrentUpdates;
+ vehicleConcurrentUpdates.nbConcurrentWheelUpdates = numActiveWheels;
+ vehicleConcurrentUpdates.concurrentWheelUpdates = wheelConcurrentUpdates;
+
+ //In each block of 4 wheels record how many wheels are active.
+ PxU32 numActiveWheelsPerBlock4[PX_MAX_NB_SUSPWHEELTIRE4]={0,0,0,0,0};
+ numActiveWheelsPerBlock4[0]=PxMin(numActiveWheels,PxU32(4));
+ for(PxU32 i=1;i<numWheels4-1;i++)
+ {
+ numActiveWheelsPerBlock4[i]=4;
+ }
+ numActiveWheelsPerBlock4[numWheels4-1]=numActiveWheelsInLast4;
+ PX_ASSERT(numActiveWheels == numActiveWheelsPerBlock4[0] + numActiveWheelsPerBlock4[1] + numActiveWheelsPerBlock4[2] + numActiveWheelsPerBlock4[3] + numActiveWheelsPerBlock4[4]);
+
+
+ //Test if a non-zero drive torque was applied or if a non-zero steer angle was applied.
+ bool finiteInputApplied=false;
+ if(0!=driveDynData.getAnalogInput(PxVehicleDrive4WControl::eANALOG_INPUT_STEER_LEFT) ||
+ 0!=driveDynData.getAnalogInput(PxVehicleDrive4WControl::eANALOG_INPUT_STEER_RIGHT) ||
+ 0!=driveDynData.getAnalogInput(PxVehicleDrive4WControl::eANALOG_INPUT_ACCEL) ||
+ driveDynData.getGearDown() || driveDynData.getGearUp())
+ {
+ finiteInputApplied=true;
+ }
+
+ //Awake or sleep.
+ {
+ if(vehActor->isSleeping())
+ {
+ if(finiteInputApplied)
+ {
+ //Driving inputs so we need the actor to start moving.
+ vehicleConcurrentUpdates.wakeup = true;
+ }
+ else if(isOnDynamicActor(vehDriveNW->mWheelsSimData, vehDriveNW->mWheelsDynData))
+ {
+ //Driving on dynamic so we need to keep moving.
+ vehicleConcurrentUpdates.wakeup = true;
+ }
+ else
+ {
+ //No driving inputs and the actor is asleep.
+ //Set internal dynamics to sleep.
+ setInternalDynamicsToZero(vehDriveNW);
+ if(vehConcurrentUpdates) vehConcurrentUpdates->staySleeping = true;
+ return;
+ }
+ }
+ }
+
+ //Organise the shader data in blocks of 4.
+ PxVehicleTireForceCalculator4 tires4ForceCalculators[PX_MAX_NB_SUSPWHEELTIRE4];
+ for(PxU32 i=0;i<numWheels4;i++)
+ {
+ tires4ForceCalculators[i].mShaderData[0]=vehDriveNW->mWheelsDynData.mTireForceCalculators->mShaderData[4*i+0];
+ tires4ForceCalculators[i].mShaderData[1]=vehDriveNW->mWheelsDynData.mTireForceCalculators->mShaderData[4*i+1];
+ tires4ForceCalculators[i].mShaderData[2]=vehDriveNW->mWheelsDynData.mTireForceCalculators->mShaderData[4*i+2];
+ tires4ForceCalculators[i].mShaderData[3]=vehDriveNW->mWheelsDynData.mTireForceCalculators->mShaderData[4*i+3];
+ tires4ForceCalculators[i].mShader=vehDriveNW->mWheelsDynData.mTireForceCalculators->mShader;
+ }
+
+ //Mark the constraints as dirty to force them to be updated in the sdk.
+ for(PxU32 i=0;i<numWheels4;i++)
+ {
+ wheels4DynDatas[i].getVehicletConstraintShader().mConstraint->markDirty();
+ }
+
+ //Need to store report data to pose the wheels.
+ PxWheelQueryResult wheelQueryResults[PX_MAX_NB_WHEELS];
+
+ END_TIMER(TIMER_ADMIN);
+ START_TIMER(TIMER_COMPONENTS_UPDATE);
+
+ //Center of mass local pose.
+ PxTransform carChassisCMLocalPose;
+ //Compute the transform of the center of mass.
+ PxTransform origCarChassisTransform;
+ PxTransform carChassisTransform;
+ //Inverse mass and inertia to apply the tire/suspension forces as impulses.
+ PxF32 inverseChassisMass;
+ PxVec3 inverseInertia;
+ //Linear and angular velocity.
+ PxVec3 carChassisLinVel;
+ PxVec3 carChassisAngVel;
+ {
+ carChassisCMLocalPose = vehActor->getCMassLocalPose();
+ origCarChassisTransform = vehActor->getGlobalPose().transform(carChassisCMLocalPose);
+ carChassisTransform = origCarChassisTransform;
+ const PxF32 chassisMass = vehActor->getMass();
+ inverseChassisMass = 1.0f/chassisMass;
+ inverseInertia = vehActor->getMassSpaceInvInertiaTensor();
+ carChassisLinVel = vehActor->getLinearVelocity();
+ carChassisAngVel = vehActor->getAngularVelocity();
+ }
+
+ //Get the local poses of the wheel shapes.
+ //These are the poses from the last frame and equal to the poses used for the raycast we will process.
+ PxQuat wheelLocalPoseRotations[PX_MAX_NB_WHEELS];
+ PxF32 wheelThetas[PX_MAX_NB_WHEELS];
+ {
+ for (PxU32 i = 0; i < numActiveWheels; i++)
+ {
+ const PxI32 shapeId = vehDriveNW->mWheelsSimData.getWheelShapeMapping(i);
+ if (-1 != shapeId)
+ {
+ PxShape* shape = NULL;
+ vehActor->getShapes(&shape, 1, PxU32(shapeId));
+ wheelLocalPoseRotations[i] = shape->getLocalPose().q;
+ wheelThetas[i] = vehDriveNW->mWheelsDynData.getWheelRotationAngle(i);
+ }
+ }
+ }
+
+ //Update the auto-box and decide whether to change gear up or down.
+ PxF32 autoboxCompensatedAnalogAccel = driveDynData.mControlAnalogVals[PxVehicleDriveNWControl::eANALOG_INPUT_ACCEL];
+ if(driveDynData.getUseAutoGears())
+ {
+ autoboxCompensatedAnalogAccel = processAutoBox(PxVehicleDriveNWControl::eANALOG_INPUT_ACCEL,timestep,driveSimData,driveDynData);
+ }
+
+ //Process gear-up/gear-down commands.
+ {
+ const PxVehicleGearsData& gearsData=driveSimData.getGearsData();
+ processGears(timestep,gearsData,driveDynData);
+ }
+
+ //Clutch strength.
+ PxF32 K;
+ {
+ const PxVehicleClutchData& clutchData=driveSimData.getClutchData();
+ const PxU32 currentGear=driveDynData.getCurrentGear();
+ K=computeClutchStrength(clutchData, currentGear);
+ }
+
+ //Clutch accuracy.
+ PxVehicleClutchAccuracyMode::Enum clutchAccuracyMode;
+ PxU32 clutchMaxIterations;
+ {
+ const PxVehicleClutchData& clutchData=driveSimData.getClutchData();
+ clutchAccuracyMode=clutchData.mAccuracyMode;
+ clutchMaxIterations=clutchData.mEstimateIterations;
+ }
+
+ //Gear ratio.
+ PxF32 G;
+ PxU32 currentGear;
+ {
+ const PxVehicleGearsData& gearsData=driveSimData.getGearsData();
+ currentGear=driveDynData.getCurrentGear();
+ G=computeGearRatio(gearsData,currentGear);
+#if PX_DEBUG_VEHICLE_ON
+ updateGraphDataGearRatio(G);
+#endif
+ }
+
+ //Retrieve control values from vehicle controls.
+ PxF32 accel,brake,handbrake,steerLeft,steerRight;
+ PxF32 steer;
+ bool isIntentionToAccelerate;
+ {
+ getVehicleNWControlValues(driveDynData,accel,brake,handbrake,steerLeft,steerRight);
+ steer=steerRight-steerLeft;
+ accel=autoboxCompensatedAnalogAccel;
+ isIntentionToAccelerate = (accel>0.0f && 0.0f==brake && 0.0f==handbrake && PxVehicleGearsData::eNEUTRAL != currentGear);
+#if PX_DEBUG_VEHICLE_ON
+ updateGraphDataControlInputs(accel,brake,handbrake,steerLeft,steerRight);
+#endif
+ }
+
+ //Compute the wheels that are disabled or enabled.
+ bool activeWheelStates[PX_MAX_NB_WHEELS];
+ PxMemSet(activeWheelStates, 0, sizeof(bool)*PX_MAX_NB_WHEELS);
+ for(PxU32 i=0;i<numWheels4;i++)
+ {
+ computeWheelActiveStates(4*i, vehDriveNW->mWheelsSimData.mActiveWheelsBitmapBuffer, &activeWheelStates[4*i]);
+ }
+
+ //Contribution of each driven wheel to average wheel speed at clutch.
+ //For NW drive equal torque split is supported.
+ const PxVehicleDifferentialNWData diffData=driveSimData.getDiffData();
+ const PxF32 invNumDrivenWheels=diffData.mInvNbDrivenWheels;
+ PxF32 aveWheelSpeedContributions[PX_MAX_NB_WHEELS];
+ PxMemSet(aveWheelSpeedContributions, 0, sizeof(PxF32)*PX_MAX_NB_WHEELS);
+ for(PxU32 i=0;i<numActiveWheels;i++)
+ {
+ aveWheelSpeedContributions[i] = diffData.getIsDrivenWheel(i) ? invNumDrivenWheels : 0;
+ }
+#if PX_DEBUG_VEHICLE_ON
+ updateGraphDataClutchSlipNW(numWheels4,wheels4DynDatas,aveWheelSpeedContributions,driveDynData.getEngineRotationSpeed(),G);
+#endif
+
+ //Compute a per-wheel accelerator pedal value.
+ bool isAccelApplied[PX_MAX_NB_WHEELS];
+ PxMemSet(isAccelApplied, 0, sizeof(bool)*PX_MAX_NB_WHEELS);
+ if(isIntentionToAccelerate)
+ {
+ PX_ASSERT(accel>0);
+ for(PxU32 i=0;i<numWheels4;i++)
+ {
+ computeIsAccelApplied(&aveWheelSpeedContributions[4*i],&isAccelApplied[4*i]);
+ }
+ }
+
+ //Steer angles.
+ PxF32 steerAngles[PX_MAX_NB_WHEELS];
+ PxMemSet(steerAngles, 0, sizeof(PxF32)*PX_MAX_NB_WHEELS);
+ for(PxU32 i=0;i<numActiveWheels;i++)
+ {
+ const PxVehicleWheelData& wheelData=vehDriveNW->mWheelsSimData.getWheelData(i);
+ const PxF32 steerGain=wheelData.mMaxSteer;
+ const PxF32 toe=wheelData.mToeAngle;
+ steerAngles[i]=steerGain*steer + toe;
+ }
+
+ //Diff torque ratios needed (how we split the torque between the drive wheels).
+ //The sum of the torque ratios is always 1.0f.
+ //The drive torque delivered to each wheel is the total available drive torque multiplied by the
+ //diff torque ratio for each wheel.
+ PxF32 diffTorqueRatios[PX_MAX_NB_WHEELS];
+ PxMemSet(diffTorqueRatios, 0, sizeof(PxF32)*PX_MAX_NB_WHEELS);
+ for(PxU32 i=0;i<numActiveWheels;i++)
+ {
+ diffTorqueRatios[i] = diffData.getIsDrivenWheel(i) ? invNumDrivenWheels : 0.0f;
+ }
+
+ END_TIMER(TIMER_COMPONENTS_UPDATE);
+ START_TIMER(TIMER_ADMIN);
+
+ //Store the susp line raycast data.
+ for(PxU32 i=0;i<numWheels4;i++)
+ {
+ storeRaycasts(wheels4DynDatas[i], &wheelQueryResults[4*i]);
+ }
+
+ //Ready to do the update.
+ PxVec3 carChassisLinVelOrig=carChassisLinVel;
+ PxVec3 carChassisAngVelOrig=carChassisAngVel;
+ const PxU32 numSubSteps=computeNumberOfSubsteps(vehDriveNW->mWheelsSimData,carChassisLinVel,carChassisTransform,gForward);
+ const PxF32 timeFraction=1.0f/(1.0f*numSubSteps);
+ const PxF32 subTimestep=timestep*timeFraction;
+ const PxF32 recipSubTimeStep=1.0f/subTimestep;
+ const PxF32 recipTimestep=1.0f/timestep;
+ const PxF32 minLongSlipDenominator=vehDriveNW->mWheelsSimData.mMinLongSlipDenominator;
+ ProcessSuspWheelTireConstData constData={timeFraction, subTimestep, recipSubTimeStep, gravity, gravityMagnitude, recipGravityMagnitude, false, minLongSlipDenominator, vehActor, &drivableSurfaceToTireFrictionPairs};
+
+ END_TIMER(TIMER_ADMIN);
+
+ for(PxU32 k=0;k<numSubSteps;k++)
+ {
+ //Set the force and torque for the current update to zero.
+ PxVec3 chassisForce(0,0,0);
+ PxVec3 chassisTorque(0,0,0);
+
+ START_TIMER(TIMER_COMPONENTS_UPDATE);
+
+ PxF32 brakeTorques[PX_MAX_NB_WHEELS];
+ bool isBrakeApplied[PX_MAX_NB_WHEELS];
+ PxMemSet(brakeTorques, 0, sizeof(PxF32)*PX_MAX_NB_WHEELS);
+ PxMemSet(isBrakeApplied, false, sizeof(bool)*PX_MAX_NB_WHEELS);
+ for(PxU32 i=0;i<numWheels4;i++)
+ {
+ computeBrakeAndHandBrakeTorques(&wheels4SimDatas[i].getWheelData(0),wheels4DynDatas[i].mWheelSpeeds,brake,handbrake,&brakeTorques[4*i],&isBrakeApplied[4*i]);
+ }
+
+ END_TIMER(TIMER_COMPONENTS_UPDATE);
+ START_TIMER(TIMER_WHEELS);
+
+ ProcessSuspWheelTireOutputData outputData[PX_MAX_NB_SUSPWHEELTIRE4];
+ for(PxU32 i=0;i<numWheels4;i++)
+ {
+ ProcessSuspWheelTireInputData inputData=
+ {
+ isIntentionToAccelerate, &isAccelApplied[4*i], &isBrakeApplied[4*i], &steerAngles[4*i], &activeWheelStates[4*i],
+ carChassisTransform, carChassisLinVel, carChassisAngVel,
+ &wheelLocalPoseRotations[i], &wheelThetas[i], &wheels4SimDatas[i], &wheels4DynDatas[i], &tires4ForceCalculators[i], &tireLoadFilterData, numActiveWheelsPerBlock4[i]
+ };
+ processSuspTireWheels(4*i, constData, inputData, outputData[i]);
+ updateLowSpeedTimers(outputData[i].newLowForwardSpeedTimers, const_cast<PxF32*>(inputData.vehWheels4DynData->mTireLowForwardSpeedTimers));
+ updateLowSpeedTimers(outputData[i].newLowSideSpeedTimers, const_cast<PxF32*>(inputData.vehWheels4DynData->mTireLowSideSpeedTimers));
+ updateJounces(outputData[i].jounces, const_cast<PxF32*>(inputData.vehWheels4DynData->mJounces));
+ if((numSubSteps-1) == k)
+ {
+ updateCachedHitData(outputData[i].cachedHitCounts, outputData[i].cachedHitPlanes, outputData[i].cachedHitDistances, outputData[i].cachedFrictionMultipliers, outputData[i].cachedHitQueryTypes, &wheels4DynDatas[i]);
+ }
+ chassisForce+=outputData[i].chassisForce;
+ chassisTorque+=outputData[i].chassisTorque;
+ if(0 == k)
+ {
+ wheels4DynDatas[i].mVehicleConstraints->mData=outputData[i].vehConstraintData;
+ }
+ storeSuspWheelTireResults(outputData[i], inputData.steerAngles, &wheelQueryResults[4*i], numActiveWheelsPerBlock4[i]);
+ storeHitActorForces(outputData[i], &vehicleConcurrentUpdates.concurrentWheelUpdates[4*i], numActiveWheelsPerBlock4[i]);
+ }
+
+ //Store the tire torques in a single array.
+ PxF32 tireTorques[PX_MAX_NB_WHEELS];
+ for(PxU32 i=0;i<numWheels4;i++)
+ {
+ tireTorques[4*i+0]=outputData[i].tireTorques[0];
+ tireTorques[4*i+1]=outputData[i].tireTorques[1];
+ tireTorques[4*i+2]=outputData[i].tireTorques[2];
+ tireTorques[4*i+3]=outputData[i].tireTorques[3];
+ }
+
+ END_TIMER(TIMER_WHEELS);
+ START_TIMER(TIMER_INTERNAL_DYNAMICS_SOLVER);
+
+ PxF32 engineDriveTorque;
+ {
+ const PxVehicleEngineData& engineData=driveSimData.getEngineData();
+ const PxF32 engineOmega=driveDynData.getEngineRotationSpeed();
+ engineDriveTorque=computeEngineDriveTorque(engineData,engineOmega,accel);
+#if PX_DEBUG_VEHICLE_ON
+ updateGraphDataEngineDriveTorque(engineDriveTorque);
+#endif
+ }
+
+ PxF32 engineDampingRate;
+ {
+ const PxVehicleEngineData& engineData=driveSimData.getEngineData();
+ engineDampingRate=computeEngineDampingRate(engineData,currentGear,accel);
+ }
+
+ //Update the wheel and engine speeds - (N+1)*(N+1) matrix coupling engine and wheels.
+ ImplicitSolverInput implicitSolverInput=
+ {
+ subTimestep,
+ brake, handbrake,
+ K, G,
+ clutchAccuracyMode, clutchMaxIterations,
+ engineDriveTorque, engineDampingRate,
+ diffTorqueRatios, aveWheelSpeedContributions,
+ brakeTorques, isBrakeApplied, tireTorques,
+ numWheels4, numActiveWheels,
+ wheels4SimDatas, &driveSimData
+ };
+ ImplicitSolverOutput implicitSolverOutput=
+ {
+ wheels4DynDatas, &driveDynData
+ };
+ solveDriveNWInternalDynamicsEnginePlusDrivenWheels(implicitSolverInput, &implicitSolverOutput);
+
+ END_TIMER(TIMER_INTERNAL_DYNAMICS_SOLVER);
+ START_TIMER(TIMER_POSTUPDATE1);
+
+ //Integrate wheel rotation angle (theta += omega*dt)
+ for(PxU32 i=0;i<numWheels4;i++)
+ {
+ integrateWheelRotationAngles
+ (subTimestep,
+ K,G,engineDriveTorque,
+ outputData[i].jounces,&diffTorqueRatios[4*i],outputData[i].forwardSpeeds,&isBrakeApplied[4*i],
+ driveSimData,wheels4SimDatas[i],
+ driveDynData,wheels4DynDatas[i]);
+ }
+
+ END_TIMER(TIMER_POSTUPDATE1);
+ START_TIMER(TIMER_POSTUPDATE2);
+
+ //Apply the anti-roll suspension.
+ procesAntiRollSuspension(vehDriveNW->mWheelsSimData, carChassisTransform, wheelQueryResults, chassisTorque);
+
+ //Integrate the chassis velocity by applying the accumulated force and torque.
+ integrateBody(inverseChassisMass, inverseInertia, chassisForce, chassisTorque, subTimestep, carChassisLinVel, carChassisAngVel, carChassisTransform);
+
+ END_TIMER(TIMER_POSTUPDATE2);
+ }
+
+ //Set the new chassis linear/angular velocity.
+ if(!gApplyForces)
+ {
+ vehicleConcurrentUpdates.linearMomentumChange = carChassisLinVel;
+ vehicleConcurrentUpdates.angularMomentumChange = carChassisAngVel;
+ }
+ else
+ {
+ //integration steps are:
+ //v = v0 + a*dt (1)
+ //x = x0 + v*dt (2)
+ //Sub (2) into (1.
+ //x = x0 + v0*dt + a*dt*dt;
+ //Rearrange for a
+ //a = (x -x0 - v0*dt)/(dt*dt) = [(x-x0)/dt - v0/dt]
+ //Rearrange again with v = (x-x0)/dt
+ //a = (v - v0)/dt
+ vehicleConcurrentUpdates.linearMomentumChange = (carChassisLinVel-carChassisLinVelOrig)*recipTimestep;
+ vehicleConcurrentUpdates.angularMomentumChange = (carChassisAngVel-carChassisAngVelOrig)*recipTimestep;
+ }
+
+ START_TIMER(TIMER_POSTUPDATE3);
+
+ //Pose the wheels from jounces, rotations angles, and steer angles.
+ PxTransform localPoses0[4] = {PxTransform(PxIdentity), PxTransform(PxIdentity), PxTransform(PxIdentity), PxTransform(PxIdentity)};
+ computeWheelLocalPoses(wheels4SimDatas[0],wheels4DynDatas[0],&wheelQueryResults[4*0],numActiveWheelsPerBlock4[0],carChassisCMLocalPose,localPoses0);
+ wheelQueryResults[4*0 + 0].localPose = localPoses0[0];
+ wheelQueryResults[4*0 + 1].localPose = localPoses0[1];
+ wheelQueryResults[4*0 + 2].localPose = localPoses0[2];
+ wheelQueryResults[4*0 + 3].localPose = localPoses0[3];
+ vehicleConcurrentUpdates.concurrentWheelUpdates[4*0 + 0].localPose = localPoses0[0];
+ vehicleConcurrentUpdates.concurrentWheelUpdates[4*0 + 1].localPose = localPoses0[1];
+ vehicleConcurrentUpdates.concurrentWheelUpdates[4*0 + 2].localPose = localPoses0[2];
+ vehicleConcurrentUpdates.concurrentWheelUpdates[4*0 + 3].localPose = localPoses0[3];
+ for(PxU32 i=1;i<numWheels4;i++)
+ {
+ PxTransform localPoses[4] = {PxTransform(PxIdentity), PxTransform(PxIdentity), PxTransform(PxIdentity), PxTransform(PxIdentity)};
+ computeWheelLocalPoses(wheels4SimDatas[i],wheels4DynDatas[i],&wheelQueryResults[4*i],numActiveWheelsPerBlock4[i],carChassisCMLocalPose,localPoses);
+ wheelQueryResults[4*i + 0].localPose = localPoses[0];
+ wheelQueryResults[4*i + 1].localPose = localPoses[1];
+ wheelQueryResults[4*i + 2].localPose = localPoses[2];
+ wheelQueryResults[4*i + 3].localPose = localPoses[3];
+ vehicleConcurrentUpdates.concurrentWheelUpdates[4*i + 0].localPose = localPoses[0];
+ vehicleConcurrentUpdates.concurrentWheelUpdates[4*i + 1].localPose = localPoses[1];
+ vehicleConcurrentUpdates.concurrentWheelUpdates[4*i + 2].localPose = localPoses[2];
+ vehicleConcurrentUpdates.concurrentWheelUpdates[4*i + 3].localPose = localPoses[3];
+ }
+
+ if(vehWheelQueryResults && vehWheelQueryResults->wheelQueryResults)
+ {
+ PxMemCopy(vehWheelQueryResults->wheelQueryResults, wheelQueryResults, sizeof(PxWheelQueryResult)*numActiveWheels);
+ }
+
+ if(vehConcurrentUpdates)
+ {
+ //Copy across to input data structure so that writes can be applied later.
+ PxMemCopy(vehConcurrentUpdates->concurrentWheelUpdates, vehicleConcurrentUpdates.concurrentWheelUpdates, sizeof(PxVehicleWheelConcurrentUpdateData)*numActiveWheels);
+ vehConcurrentUpdates->linearMomentumChange = vehicleConcurrentUpdates.linearMomentumChange;
+ vehConcurrentUpdates->angularMomentumChange = vehicleConcurrentUpdates.angularMomentumChange;
+ vehConcurrentUpdates->staySleeping = vehicleConcurrentUpdates.staySleeping;
+ vehConcurrentUpdates->wakeup = vehicleConcurrentUpdates.wakeup;
+ }
+ else
+ {
+ //Apply the writes immediately.
+ PxVehicleWheels* vehWheels[1]={vehDriveNW};
+ PxVehiclePostUpdates(&vehicleConcurrentUpdates, 1, vehWheels);
+ }
+
+ END_TIMER(TIMER_POSTUPDATE3);
+}
+
+void PxVehicleUpdate::updateTank
+(const PxF32 timestep,
+ const PxVec3& gravity, const PxF32 gravityMagnitude, const PxF32 recipGravityMagnitude,
+ const PxVehicleDrivableSurfaceToTireFrictionPairs& drivableSurfaceToTireFrictionPairs,
+ PxVehicleDriveTank* vehDriveTank, PxVehicleWheelQueryResult* vehWheelQueryResults, PxVehicleConcurrentUpdateData* vehConcurrentUpdates)
+{
+ PX_SIMD_GUARD; // denorm exception in transformInertiaTensor()
+
+ PX_CHECK_AND_RETURN(
+ vehDriveTank->mDriveDynData.mControlAnalogVals[PxVehicleDriveTankControl::eANALOG_INPUT_ACCEL]>-0.01f &&
+ vehDriveTank->mDriveDynData.mControlAnalogVals[PxVehicleDriveTankControl::eANALOG_INPUT_ACCEL]<1.01f,
+ "Illegal tank control value - accel must be in range (0,1)" );
+ PX_CHECK_AND_RETURN(
+ vehDriveTank->mDriveDynData.mControlAnalogVals[PxVehicleDriveTankControl::eANALOG_INPUT_BRAKE_LEFT]>-0.01f &&
+ vehDriveTank->mDriveDynData.mControlAnalogVals[PxVehicleDriveTankControl::eANALOG_INPUT_BRAKE_LEFT]<1.01f,
+ "Illegal tank control value - left brake must be in range (0,1)");
+ PX_CHECK_AND_RETURN(
+ vehDriveTank->mDriveDynData.mControlAnalogVals[PxVehicleDriveTankControl::eANALOG_INPUT_BRAKE_RIGHT]>-0.01f &&
+ vehDriveTank->mDriveDynData.mControlAnalogVals[PxVehicleDriveTankControl::eANALOG_INPUT_BRAKE_RIGHT]<1.01f,
+ "Illegal tank control right value - right brake must be in range (0,1)");
+ PX_CHECK_AND_RETURN(
+ vehDriveTank->mDriveDynData.mControlAnalogVals[PxVehicleDriveTankControl::eANALOG_INPUT_THRUST_LEFT]>-1.01f &&
+ vehDriveTank->mDriveDynData.mControlAnalogVals[PxVehicleDriveTankControl::eANALOG_INPUT_THRUST_LEFT]<1.01f,
+ "Illegal tank control value - left thrust must be in range (-1,1)");
+ PX_CHECK_AND_RETURN(
+ vehDriveTank->mDriveDynData.mControlAnalogVals[PxVehicleDriveTankControl::eANALOG_INPUT_THRUST_RIGHT]>-1.01f &&
+ vehDriveTank->mDriveDynData.mControlAnalogVals[PxVehicleDriveTankControl::eANALOG_INPUT_THRUST_RIGHT]<1.01f,
+ "Illegal tank control value - right thrust must be in range (-1,1)");
+ PX_CHECK_AND_RETURN(
+ PxVehicleDriveTankControlModel::eSPECIAL==vehDriveTank->mDriveModel ||
+ (vehDriveTank->mDriveDynData.mControlAnalogVals[PxVehicleDriveTankControl::eANALOG_INPUT_THRUST_LEFT]>-0.01f &&
+ vehDriveTank->mDriveDynData.mControlAnalogVals[PxVehicleDriveTankControl::eANALOG_INPUT_THRUST_LEFT]<1.01f),
+ "Illegal tank control value - left thrust must be in range (-1,1)");
+ PX_CHECK_AND_RETURN(
+ PxVehicleDriveTankControlModel::eSPECIAL==vehDriveTank->mDriveModel ||
+ (vehDriveTank->mDriveDynData.mControlAnalogVals[PxVehicleDriveTankControl::eANALOG_INPUT_THRUST_RIGHT]>-0.01f &&
+ vehDriveTank->mDriveDynData.mControlAnalogVals[PxVehicleDriveTankControl::eANALOG_INPUT_THRUST_RIGHT]<1.01f),
+ "Illegal tank control value - right thrust must be in range (-1,1)");
+ PX_CHECK_AND_RETURN(
+ PxVehicleDriveTankControlModel::eSPECIAL==vehDriveTank->mDriveModel ||
+ 0.0f==
+ vehDriveTank->mDriveDynData.mControlAnalogVals[PxVehicleDriveTankControl::eANALOG_INPUT_THRUST_LEFT]*
+ vehDriveTank->mDriveDynData.mControlAnalogVals[PxVehicleDriveTankControl::eANALOG_INPUT_BRAKE_LEFT],
+ "Illegal tank control value - thrust left and brake left simultaneously non-zero in standard drive mode");
+ PX_CHECK_AND_RETURN(
+ PxVehicleDriveTankControlModel::eSPECIAL==vehDriveTank->mDriveModel ||
+ 0.0f==
+ vehDriveTank->mDriveDynData.mControlAnalogVals[PxVehicleDriveTankControl::eANALOG_INPUT_THRUST_RIGHT]*
+ vehDriveTank->mDriveDynData.mControlAnalogVals[PxVehicleDriveTankControl::eANALOG_INPUT_BRAKE_RIGHT],
+ "Illegal tank control value - thrust right and brake right simultaneously non-zero in standard drive mode");
+ PX_CHECK_AND_RETURN(
+ !(vehDriveTank->getRigidDynamicActor()->getRigidBodyFlags() & PxRigidBodyFlag::eKINEMATIC),
+ "Attempting to update a tank with a kinematic actor - this isn't allowed");
+ PX_CHECK_AND_RETURN(
+ NULL==vehWheelQueryResults || vehWheelQueryResults->nbWheelQueryResults >= vehDriveTank->mWheelsSimData.getNbWheels(),
+ "nbWheelQueryResults must always be greater than or equal to number of wheels in corresponding vehicle");
+
+#if PX_CHECKED
+ {
+ PxVec3 fl=vehDriveTank->mWheelsSimData.getWheelCentreOffset(PxVehicleDriveTankWheelOrder::eFRONT_LEFT);
+ PxVec3 fr=vehDriveTank->mWheelsSimData.getWheelCentreOffset(PxVehicleDriveTankWheelOrder::eFRONT_RIGHT);
+ const PxVec3 right=gRight;
+ const PxF32 s0=computeSign((fr-fl).dot(right));
+ for(PxU32 i=PxVehicleDriveTankWheelOrder::e1ST_FROM_FRONT_LEFT;i<vehDriveTank->mWheelsSimData.getNbWheels();i+=2)
+ {
+ PxVec3 rl=vehDriveTank->mWheelsSimData.getWheelCentreOffset(i);
+ PxVec3 rr=vehDriveTank->mWheelsSimData.getWheelCentreOffset(i+1);
+ const PxF32 t0=computeSign((rr-rl).dot(right));
+ PX_CHECK_AND_RETURN(s0==t0 || 0==s0 || 0==t0, "Tank wheels must be ordered with odd wheels on one side and even wheels on the other side");
+ }
+ }
+#endif
+
+
+#if PX_DEBUG_VEHICLE_ON
+ for(PxU32 i=0;i<vehDriveTank->mWheelsSimData.mNbWheels4;i++)
+ {
+ updateGraphDataInternalWheelDynamics(4*i,vehDriveTank->mWheelsDynData.mWheels4DynData[i].mWheelSpeeds);
+ }
+ updateGraphDataInternalEngineDynamics(vehDriveTank->mDriveDynData.getEngineRotationSpeed());
+#endif
+
+ //Unpack the tank simulation and instanced dynamics components.
+ const PxVehicleWheels4SimData* wheels4SimDatas=vehDriveTank->mWheelsSimData.mWheels4SimData;
+ const PxVehicleTireLoadFilterData& tireLoadFilterData=vehDriveTank->mWheelsSimData.mNormalisedLoadFilter;
+ PxVehicleWheels4DynData* wheels4DynDatas=vehDriveTank->mWheelsDynData.mWheels4DynData;
+ const PxU32 numWheels4=vehDriveTank->mWheelsSimData.mNbWheels4;
+ const PxU32 numActiveWheels=vehDriveTank->mWheelsSimData.mNbActiveWheels;
+ const PxU32 numActiveWheelsInLast4=4-(4*numWheels4-numActiveWheels);
+ const PxVehicleDriveSimData driveSimData=vehDriveTank->mDriveSimData;
+ PxVehicleDriveDynData& driveDynData=vehDriveTank->mDriveDynData;
+ PxRigidDynamic* vehActor=vehDriveTank->mActor;
+
+ //We need to store that data we are going to write to actors so we can do this at the end in one go with fewer write locks.
+ PxVehicleWheelConcurrentUpdateData wheelConcurrentUpdates[PX_MAX_NB_WHEELS];
+ PxVehicleConcurrentUpdateData vehicleConcurrentUpdates;
+ vehicleConcurrentUpdates.nbConcurrentWheelUpdates = numActiveWheels;
+ vehicleConcurrentUpdates.concurrentWheelUpdates = wheelConcurrentUpdates;
+
+ //Test if a non-zero drive torque was applied or if a non-zero steer angle was applied.
+ bool finiteInputApplied=false;
+ if(0!=driveDynData.getAnalogInput(PxVehicleDriveTankControl::eANALOG_INPUT_THRUST_LEFT) ||
+ 0!=driveDynData.getAnalogInput(PxVehicleDriveTankControl::eANALOG_INPUT_THRUST_RIGHT) ||
+ 0!=driveDynData.getAnalogInput(PxVehicleDriveTankControl::eANALOG_INPUT_ACCEL) ||
+ driveDynData.getGearDown() || driveDynData.getGearUp())
+ {
+ finiteInputApplied=true;
+ }
+
+ //Awake or sleep.
+ {
+ if(vehActor->isSleeping())
+ {
+ if(finiteInputApplied)
+ {
+ //Driving inputs so we need the actor to start moving.
+ vehicleConcurrentUpdates.wakeup = true;
+ }
+ else if(isOnDynamicActor(vehDriveTank->mWheelsSimData, vehDriveTank->mWheelsDynData))
+ {
+ //Driving on dynamic so we need to keep moving.
+ vehicleConcurrentUpdates.wakeup = true;
+ }
+ else
+ {
+ //No driving inputs and the actor is asleep.
+ //Set internal dynamics to sleep.
+ setInternalDynamicsToZero(vehDriveTank);
+ if(vehConcurrentUpdates) vehConcurrentUpdates->staySleeping = true;
+ return;
+ }
+ }
+ }
+
+ //In each block of 4 wheels record how many wheels are active.
+ PxU32 numActiveWheelsPerBlock4[PX_MAX_NB_SUSPWHEELTIRE4]={0,0,0,0,0};
+ numActiveWheelsPerBlock4[0]=PxMin(numActiveWheels,PxU32(4));
+ for(PxU32 i=1;i<numWheels4-1;i++)
+ {
+ numActiveWheelsPerBlock4[i]=4;
+ }
+ numActiveWheelsPerBlock4[numWheels4-1]=numActiveWheelsInLast4;
+ PX_ASSERT(numActiveWheels == numActiveWheelsPerBlock4[0] + numActiveWheelsPerBlock4[1] + numActiveWheelsPerBlock4[2] + numActiveWheelsPerBlock4[3] + numActiveWheelsPerBlock4[4]);
+
+ //Organise the shader data in blocks of 4.
+ PxVehicleTireForceCalculator4 tires4ForceCalculators[PX_MAX_NB_SUSPWHEELTIRE4];
+ for(PxU32 i=0;i<numWheels4;i++)
+ {
+ tires4ForceCalculators[i].mShaderData[0]=vehDriveTank->mWheelsDynData.mTireForceCalculators->mShaderData[4*i+0];
+ tires4ForceCalculators[i].mShaderData[1]=vehDriveTank->mWheelsDynData.mTireForceCalculators->mShaderData[4*i+1];
+ tires4ForceCalculators[i].mShaderData[2]=vehDriveTank->mWheelsDynData.mTireForceCalculators->mShaderData[4*i+2];
+ tires4ForceCalculators[i].mShaderData[3]=vehDriveTank->mWheelsDynData.mTireForceCalculators->mShaderData[4*i+3];
+ tires4ForceCalculators[i].mShader=vehDriveTank->mWheelsDynData.mTireForceCalculators->mShader;
+ }
+
+ //Mark the suspension/tire constraints as dirty to force them to be updated in the sdk.
+ for(PxU32 i=0;i<numWheels4;i++)
+ {
+ wheels4DynDatas[i].getVehicletConstraintShader().mConstraint->markDirty();
+ }
+
+ //Need to store report data to pose the wheels.
+ PxWheelQueryResult wheelQueryResults[PX_MAX_NB_WHEELS];
+
+
+ //Center of mass local pose.
+ PxTransform carChassisCMLocalPose;
+ //Compute the transform of the center of mass.
+ PxTransform origCarChassisTransform;
+ PxTransform carChassisTransform;
+ //Inverse mass and inertia to apply the tire/suspension forces as impulses.
+ PxF32 inverseChassisMass;
+ PxVec3 inverseInertia;
+ //Linear and angular velocity.
+ PxVec3 carChassisLinVel;
+ PxVec3 carChassisAngVel;
+ {
+ carChassisCMLocalPose = vehActor->getCMassLocalPose();
+ origCarChassisTransform = vehActor->getGlobalPose().transform(vehActor->getCMassLocalPose());
+ carChassisTransform = origCarChassisTransform;
+ const PxF32 chassisMass = vehActor->getMass();
+ inverseChassisMass = 1.0f/chassisMass;
+ inverseInertia = vehActor->getMassSpaceInvInertiaTensor();
+ carChassisLinVel = vehActor->getLinearVelocity();
+ carChassisAngVel = vehActor->getAngularVelocity();
+ }
+
+ //Get the local poses of the wheel shapes.
+ //These are the poses from the last frame and equal to the poses used for the raycast we will process.
+ PxQuat wheelLocalPoseRotations[PX_MAX_NB_WHEELS];
+ PxF32 wheelThetas[PX_MAX_NB_WHEELS];
+ {
+ for (PxU32 i = 0; i < numActiveWheels; i++)
+ {
+ const PxI32 shapeId = vehDriveTank->mWheelsSimData.getWheelShapeMapping(i);
+ if (-1 != shapeId)
+ {
+ PxShape* shape = NULL;
+ vehActor->getShapes(&shape, 1, PxU32(shapeId));
+ wheelLocalPoseRotations[i] = shape->getLocalPose().q;
+ wheelThetas[i] = vehDriveTank->mWheelsDynData.getWheelRotationAngle(i);
+ }
+ }
+ }
+
+
+ //Retrieve control values from vehicle controls.
+ PxF32 accel,brakeLeft,brakeRight,thrustLeft,thrustRight;
+ {
+ getTankControlValues(driveDynData,accel,brakeLeft,brakeRight,thrustLeft,thrustRight);
+#if PX_DEBUG_VEHICLE_ON
+ updateGraphDataControlInputs(accel,brakeLeft,brakeRight,thrustLeft,thrustRight);
+#endif
+ }
+
+ //Update the auto-box and decide whether to change gear up or down.
+ //If the tank is supposed to turn sharply don't process the auto-box.
+ bool useAutoGears;
+ if(vehDriveTank->getDriveModel()==PxVehicleDriveTankControlModel::eSPECIAL)
+ {
+ useAutoGears = driveDynData.getUseAutoGears() ? ((((thrustRight*thrustLeft) >= 0.0f) || (0.0f==thrustLeft && 0.0f==thrustRight)) ? true : false) : false;
+ }
+ else
+ {
+ useAutoGears = driveDynData.getUseAutoGears() ? (thrustRight*brakeLeft>0 || thrustLeft*brakeRight>0 ? false : true) : false;
+ }
+ if(useAutoGears)
+ {
+ processAutoBox(PxVehicleDriveTankControl::eANALOG_INPUT_ACCEL,timestep,driveSimData,driveDynData);
+ }
+
+ //Process gear-up/gear-down commands.
+ {
+ const PxVehicleGearsData& gearsData=driveSimData.getGearsData();
+ processGears(timestep,gearsData,driveDynData);
+ }
+
+ //Clutch strength;
+ PxF32 K;
+ {
+ const PxVehicleClutchData& clutchData=driveSimData.getClutchData();
+ const PxU32 currentGear=driveDynData.getCurrentGear();
+ K=computeClutchStrength(clutchData, currentGear);
+ }
+
+ //Gear ratio.
+ PxF32 G;
+ PxU32 currentGear;
+ {
+ const PxVehicleGearsData& gearsData=driveSimData.getGearsData();
+ currentGear=driveDynData.getCurrentGear();
+ G=computeGearRatio(gearsData,currentGear);
+#if PX_DEBUG_VEHICLE_ON
+ updateGraphDataGearRatio(G);
+#endif
+ }
+
+ bool isIntentionToAccelerate;
+ {
+ const PxF32 thrustLeftAbs=PxAbs(thrustLeft);
+ const PxF32 thrustRightAbs=PxAbs(thrustRight);
+ isIntentionToAccelerate = (accel*(thrustLeftAbs+thrustRightAbs)>0 && PxVehicleGearsData::eNEUTRAL != currentGear);
+ }
+
+
+ //Compute the wheels that are enabled/disabled.
+ bool activeWheelStates[PX_MAX_NB_WHEELS];
+ PxMemZero(activeWheelStates, sizeof(bool)*PX_MAX_NB_WHEELS);
+ for(PxU32 i=0;i<numWheels4;i++)
+ {
+ computeWheelActiveStates(4*i, vehDriveTank->mWheelsSimData.mActiveWheelsBitmapBuffer, &activeWheelStates[4*i]);
+ }
+
+ //Set up contribution of each wheel to the average wheel speed at the clutch
+ //Set up the torque ratio delivered by the diff to each wheel.
+ //Set the sign of the gearing applied to the left and right wheels.
+ PxF32 aveWheelSpeedContributions[PX_MAX_NB_WHEELS];
+ PxF32 diffTorqueRatios[PX_MAX_NB_WHEELS];
+ PxF32 wheelGearings[PX_MAX_NB_WHEELS];
+ PxMemZero(aveWheelSpeedContributions, sizeof(PxF32)*PX_MAX_NB_WHEELS);
+ PxMemZero(diffTorqueRatios, sizeof(PxF32)*PX_MAX_NB_WHEELS);
+ PxMemZero(wheelGearings, sizeof(PxF32)*PX_MAX_NB_WHEELS);
+ computeTankDiff
+ (thrustLeft, thrustRight,
+ numActiveWheels, activeWheelStates,
+ aveWheelSpeedContributions, diffTorqueRatios, wheelGearings);
+
+ //Compute an accelerator pedal value per wheel.
+ bool isAccelApplied[PX_MAX_NB_WHEELS];
+ PxMemZero(isAccelApplied, sizeof(bool)*PX_MAX_NB_WHEELS);
+ if(isIntentionToAccelerate)
+ {
+ PX_ASSERT(accel>0);
+ for(PxU32 i=0;i<numWheels4;i++)
+ {
+ computeIsAccelApplied(&aveWheelSpeedContributions[4*i], &isAccelApplied[4*i]);
+ }
+ }
+
+
+#if PX_DEBUG_VEHICLE_ON
+ updateGraphDataClutchSlip(wheels4DynDatas[0].mWheelSpeeds,aveWheelSpeedContributions,driveDynData.getEngineRotationSpeed(),G);
+#endif
+
+ //Ackermann-corrected steer angles
+ //For tanks this is always zero because they turn by torque delivery rather than a steering mechanism.
+ PxF32 steerAngles[PX_MAX_NB_WHEELS];
+ PxMemZero(steerAngles, sizeof(PxF32)*PX_MAX_NB_WHEELS);
+
+ //Store the susp line raycast data.
+ for(PxU32 i=0;i<numWheels4;i++)
+ {
+ storeRaycasts(wheels4DynDatas[i], &wheelQueryResults[4*i]);
+ }
+
+ //Ready to do the update.
+ PxVec3 carChassisLinVelOrig=carChassisLinVel;
+ PxVec3 carChassisAngVelOrig=carChassisAngVel;
+ const PxU32 numSubSteps=computeNumberOfSubsteps(vehDriveTank->mWheelsSimData,carChassisLinVel,carChassisTransform,gForward);
+ const PxF32 timeFraction=1.0f/(1.0f*numSubSteps);
+ const PxF32 subTimestep=timestep*timeFraction;
+ const PxF32 recipSubTimeStep=1.0f/subTimestep;
+ const PxF32 recipTimestep=1.0f/timestep;
+ const PxF32 minLongSlipDenominator=vehDriveTank->mWheelsSimData.mMinLongSlipDenominator;
+ ProcessSuspWheelTireConstData constData={timeFraction, subTimestep, recipSubTimeStep, gravity, gravityMagnitude, recipGravityMagnitude, true, minLongSlipDenominator, vehActor, &drivableSurfaceToTireFrictionPairs};
+
+ for(PxU32 k=0;k<numSubSteps;k++)
+ {
+ //Set the force and torque for the current update to zero.
+ PxVec3 chassisForce(0,0,0);
+ PxVec3 chassisTorque(0,0,0);
+
+ //Compute the brake torques.
+ PxF32 brakeTorques[PX_MAX_NB_WHEELS];
+ bool isBrakeApplied[PX_MAX_NB_WHEELS];
+ PxMemZero(brakeTorques, sizeof(PxF32)*PX_MAX_NB_WHEELS);
+ PxMemZero(isBrakeApplied, sizeof(bool)*PX_MAX_NB_WHEELS);
+ for(PxU32 i=0;i<numWheels4;i++)
+ {
+ computeTankBrakeTorques
+ (&wheels4SimDatas[i].getWheelData(0),wheels4DynDatas[i].mWheelSpeeds,brakeLeft,brakeRight,
+ &brakeTorques[i*4],&isBrakeApplied[i*4]);
+ }
+
+ //Compute jounces, slips, tire forces, suspension forces etc.
+ ProcessSuspWheelTireOutputData outputData[PX_MAX_NB_SUSPWHEELTIRE4];
+ for(PxU32 i=0;i<numWheels4;i++)
+ {
+ ProcessSuspWheelTireInputData inputData=
+ {
+ isIntentionToAccelerate, &isAccelApplied[i*4], &isBrakeApplied[i*4], &steerAngles[i*4], &activeWheelStates[4*i],
+ carChassisTransform, carChassisLinVel, carChassisAngVel,
+ &wheelLocalPoseRotations[i], &wheelThetas[i], &wheels4SimDatas[i], &wheels4DynDatas[i], &tires4ForceCalculators[i], &tireLoadFilterData, numActiveWheelsPerBlock4[i],
+ };
+ processSuspTireWheels(i*4, constData, inputData, outputData[i]);
+ updateLowSpeedTimers(outputData[i].newLowForwardSpeedTimers, const_cast<PxF32*>(inputData.vehWheels4DynData->mTireLowForwardSpeedTimers));
+ updateLowSpeedTimers(outputData[i].newLowSideSpeedTimers, const_cast<PxF32*>(inputData.vehWheels4DynData->mTireLowSideSpeedTimers));
+ updateJounces(outputData[i].jounces, const_cast<PxF32*>(inputData.vehWheels4DynData->mJounces));
+ if((numSubSteps-1) == k)
+ {
+ updateCachedHitData(outputData[i].cachedHitCounts, outputData[i].cachedHitPlanes, outputData[i].cachedHitDistances, outputData[i].cachedFrictionMultipliers, outputData[i].cachedHitQueryTypes, &wheels4DynDatas[i]);
+ }
+ chassisForce+=outputData[i].chassisForce;
+ chassisTorque+=outputData[i].chassisTorque;
+ if(0 == k)
+ {
+ wheels4DynDatas[i].mVehicleConstraints->mData=outputData[i].vehConstraintData;
+ }
+ storeSuspWheelTireResults(outputData[i], inputData.steerAngles, &wheelQueryResults[4*i], numActiveWheelsPerBlock4[i]);
+ storeHitActorForces(outputData[i], &vehicleConcurrentUpdates.concurrentWheelUpdates[4*i], numActiveWheelsPerBlock4[i]);
+ }
+
+ //Copy the tire torques to a single array.
+ PxF32 tireTorques[PX_MAX_NB_WHEELS];
+ for(PxU32 i=0;i<numWheels4;i++)
+ {
+ tireTorques[4*i+0]=outputData[i].tireTorques[0];
+ tireTorques[4*i+1]=outputData[i].tireTorques[1];
+ tireTorques[4*i+2]=outputData[i].tireTorques[2];
+ tireTorques[4*i+3]=outputData[i].tireTorques[3];
+ }
+
+ PxF32 engineDriveTorque;
+ {
+ const PxVehicleEngineData& engineData=driveSimData.getEngineData();
+ const PxF32 engineOmega=driveDynData.getEngineRotationSpeed();
+ engineDriveTorque=computeEngineDriveTorque(engineData,engineOmega,accel);
+#if PX_DEBUG_VEHICLE_ON
+ updateGraphDataEngineDriveTorque(engineDriveTorque);
+#endif
+ }
+
+ PxF32 engineDampingRate;
+ {
+ const PxVehicleEngineData& engineData=driveSimData.getEngineData();
+ engineDampingRate=computeEngineDampingRate(engineData,currentGear,accel);
+ }
+
+ //Update the wheel and engine speeds - 5x5 matrix coupling engine and wheels.
+ ImplicitSolverInput implicitSolverInput =
+ {
+ subTimestep,
+ 0.0f, 0.0f,
+ K, G,
+ PxVehicleClutchAccuracyMode::eBEST_POSSIBLE, 0,
+ engineDriveTorque, engineDampingRate,
+ diffTorqueRatios, aveWheelSpeedContributions,
+ brakeTorques, isBrakeApplied, tireTorques,
+ numWheels4, numActiveWheels,
+ wheels4SimDatas, &driveSimData
+ };
+ ImplicitSolverOutput implicitSolverOutput =
+ {
+ wheels4DynDatas, &driveDynData
+ };
+ solveTankInternaDynamicsEnginePlusDrivenWheels(implicitSolverInput, activeWheelStates, wheelGearings, &implicitSolverOutput);
+
+ //Integrate wheel rotation angle (theta += omega*dt)
+ for(PxU32 i=0;i<numWheels4;i++)
+ {
+ integrateWheelRotationAngles
+ (subTimestep,
+ K,G,engineDriveTorque,
+ outputData[i].jounces,diffTorqueRatios,outputData[i].forwardSpeeds,isBrakeApplied,
+ driveSimData,wheels4SimDatas[i],
+ driveDynData,wheels4DynDatas[i]);
+ }
+
+ //Apply the anti-roll suspension.
+ procesAntiRollSuspension(vehDriveTank->mWheelsSimData, carChassisTransform, wheelQueryResults, chassisTorque);
+
+ //Integrate the chassis velocity by applying the accumulated force and torque.
+ integrateBody(inverseChassisMass, inverseInertia, chassisForce, chassisTorque, subTimestep, carChassisLinVel, carChassisAngVel, carChassisTransform);
+ }
+
+ //Set the new chassis linear/angular velocity.
+ if(!gApplyForces)
+ {
+ vehicleConcurrentUpdates.linearMomentumChange = carChassisLinVel;
+ vehicleConcurrentUpdates.angularMomentumChange = carChassisAngVel;
+ }
+ else
+ {
+ //integration steps are:
+ //v = v0 + a*dt (1)
+ //x = x0 + v*dt (2)
+ //Sub (2) into (1.
+ //x = x0 + v0*dt + a*dt*dt;
+ //Rearrange for a
+ //a = (x -x0 - v0*dt)/(dt*dt) = [(x-x0)/dt - v0/dt]
+ //Rearrange again with v = (x-x0)/dt
+ //a = (v - v0)/dt
+ vehicleConcurrentUpdates.linearMomentumChange = (carChassisLinVel-carChassisLinVelOrig)*recipTimestep;
+ vehicleConcurrentUpdates.angularMomentumChange = (carChassisAngVel-carChassisAngVelOrig)*recipTimestep;
+ }
+
+ //Pose the wheels from jounces, rotations angles, and steer angles.
+ PxTransform localPoses0[4] = {PxTransform(PxIdentity), PxTransform(PxIdentity), PxTransform(PxIdentity), PxTransform(PxIdentity)};
+ computeWheelLocalPoses(wheels4SimDatas[0],wheels4DynDatas[0],&wheelQueryResults[4*0],numActiveWheelsPerBlock4[0],carChassisCMLocalPose,localPoses0);
+ wheelQueryResults[4*0 + 0].localPose = localPoses0[0];
+ wheelQueryResults[4*0 + 1].localPose = localPoses0[1];
+ wheelQueryResults[4*0 + 2].localPose = localPoses0[2];
+ wheelQueryResults[4*0 + 3].localPose = localPoses0[3];
+ vehicleConcurrentUpdates.concurrentWheelUpdates[4*0 + 0].localPose = localPoses0[0];
+ vehicleConcurrentUpdates.concurrentWheelUpdates[4*0 + 1].localPose = localPoses0[1];
+ vehicleConcurrentUpdates.concurrentWheelUpdates[4*0 + 2].localPose = localPoses0[2];
+ vehicleConcurrentUpdates.concurrentWheelUpdates[4*0 + 3].localPose = localPoses0[3];
+ for(PxU32 i=1;i<numWheels4;i++)
+ {
+ PxTransform localPoses[4] = {PxTransform(PxIdentity), PxTransform(PxIdentity), PxTransform(PxIdentity), PxTransform(PxIdentity)};
+ computeWheelLocalPoses(wheels4SimDatas[i],wheels4DynDatas[i],&wheelQueryResults[4*i],numActiveWheelsPerBlock4[i],carChassisCMLocalPose,localPoses);
+ wheelQueryResults[4*i + 0].localPose = localPoses[0];
+ wheelQueryResults[4*i + 1].localPose = localPoses[1];
+ wheelQueryResults[4*i + 2].localPose = localPoses[2];
+ wheelQueryResults[4*i + 3].localPose = localPoses[3];
+ vehicleConcurrentUpdates.concurrentWheelUpdates[4*i + 0].localPose = localPoses[0];
+ vehicleConcurrentUpdates.concurrentWheelUpdates[4*i + 1].localPose = localPoses[1];
+ vehicleConcurrentUpdates.concurrentWheelUpdates[4*i + 2].localPose = localPoses[2];
+ vehicleConcurrentUpdates.concurrentWheelUpdates[4*i + 3].localPose = localPoses[3];
+ }
+
+ if(vehWheelQueryResults && vehWheelQueryResults->wheelQueryResults)
+ {
+ PxMemCopy(vehWheelQueryResults->wheelQueryResults, wheelQueryResults, sizeof(PxWheelQueryResult)*numActiveWheels);
+ }
+
+ if(vehConcurrentUpdates)
+ {
+ //Copy across to input data structure so that writes can be applied later.
+ PxMemCopy(vehConcurrentUpdates->concurrentWheelUpdates, vehicleConcurrentUpdates.concurrentWheelUpdates, sizeof(PxVehicleWheelConcurrentUpdateData)*numActiveWheels);
+ vehConcurrentUpdates->linearMomentumChange = vehicleConcurrentUpdates.linearMomentumChange;
+ vehConcurrentUpdates->angularMomentumChange = vehicleConcurrentUpdates.angularMomentumChange;
+ vehConcurrentUpdates->staySleeping = vehicleConcurrentUpdates.staySleeping;
+ vehConcurrentUpdates->wakeup = vehicleConcurrentUpdates.wakeup;
+ }
+ else
+ {
+ //Apply the writes immediately.
+ PxVehicleWheels* vehWheels[1]={vehDriveTank};
+ PxVehiclePostUpdates(&vehicleConcurrentUpdates, 1, vehWheels);
+ }
+}
+
+void PxVehicleUpdate::updateNoDrive
+(const PxF32 timestep,
+ const PxVec3& gravity, const PxF32 gravityMagnitude, const PxF32 recipGravityMagnitude,
+ const PxVehicleDrivableSurfaceToTireFrictionPairs& drivableSurfaceToTireFrictionPairs,
+ PxVehicleNoDrive* vehNoDrive, PxVehicleWheelQueryResult* vehWheelQueryResults, PxVehicleConcurrentUpdateData* vehConcurrentUpdates)
+{
+ PX_SIMD_GUARD; // denorm exception in transformInertiaTensor() on osx
+
+ PX_CHECK_AND_RETURN(
+ !(vehNoDrive->getRigidDynamicActor()->getRigidBodyFlags() & PxRigidBodyFlag::eKINEMATIC),
+ "Attempting to update a PxVehicleNoDrive with a kinematic actor - this isn't allowed");
+
+ PX_CHECK_AND_RETURN(
+ NULL==vehWheelQueryResults || vehWheelQueryResults->nbWheelQueryResults >= vehNoDrive->mWheelsSimData.getNbWheels(),
+ "nbWheelQueryResults must always be greater than or equal to number of wheels in corresponding vehicle");
+
+#if PX_CHECKED
+ for(PxU32 i=0;i<vehNoDrive->mWheelsSimData.getNbWheels();i++)
+ {
+ PX_CHECK_AND_RETURN(
+ !vehNoDrive->mWheelsSimData.getIsWheelDisabled(i) || 0==vehNoDrive->getDriveTorque(i),
+ "Disabled wheels should have zero drive torque applied to them.");
+ }
+#endif
+
+#if PX_DEBUG_VEHICLE_ON
+ for(PxU32 i=0;i<vehNoDrive->mWheelsSimData.mNbWheels4;i++)
+ {
+ updateGraphDataInternalWheelDynamics(4*i,vehNoDrive->mWheelsDynData.mWheels4DynData[i].mWheelSpeeds);
+ }
+#endif
+
+ //Unpack the tank simulation and instanced dynamics components.
+ const PxVehicleWheels4SimData* wheels4SimDatas=vehNoDrive->mWheelsSimData.mWheels4SimData;
+ const PxVehicleTireLoadFilterData& tireLoadFilterData=vehNoDrive->mWheelsSimData.mNormalisedLoadFilter;
+ PxVehicleWheels4DynData* wheels4DynDatas=vehNoDrive->mWheelsDynData.mWheels4DynData;
+ const PxU32 numWheels4=vehNoDrive->mWheelsSimData.mNbWheels4;
+ const PxU32 numActiveWheels=vehNoDrive->mWheelsSimData.mNbActiveWheels;
+ const PxU32 numActiveWheelsInLast4=4-(4*numWheels4-numActiveWheels);
+ PxRigidDynamic* vehActor=vehNoDrive->mActor;
+
+ //We need to store that data we are going to write to actors so we can do this at the end in one go with fewer write locks.
+ PxVehicleWheelConcurrentUpdateData wheelConcurrentUpdates[PX_MAX_NB_WHEELS];
+ PxVehicleConcurrentUpdateData vehicleConcurrentUpdates;
+ vehicleConcurrentUpdates.nbConcurrentWheelUpdates = numActiveWheels;
+ vehicleConcurrentUpdates.concurrentWheelUpdates = wheelConcurrentUpdates;
+
+ //Test if a non-zero drive torque was applied or if a non-zero steer angle was applied.
+ bool finiteInputApplied=false;
+ for(PxU32 i=0;i<numActiveWheels;i++)
+ {
+ if(vehNoDrive->getDriveTorque(i) != 0.0f)
+ {
+ finiteInputApplied=true;
+ break;
+ }
+ if(vehNoDrive->getSteerAngle(i)!=0.0f)
+ {
+ finiteInputApplied=true;
+ break;
+ }
+ }
+
+ //Wake or sleep.
+ {
+ if(vehActor->isSleeping())
+ {
+ if(finiteInputApplied)
+ {
+ //Driving inputs so we need the actor to start moving.
+ vehicleConcurrentUpdates.wakeup = true;
+ }
+ else if(isOnDynamicActor(vehNoDrive->mWheelsSimData, vehNoDrive->mWheelsDynData))
+ {
+ //Driving on dynamic so we need to keep moving.
+ vehicleConcurrentUpdates.wakeup = true;
+ }
+ else
+ {
+ //No driving inputs and the actor is asleep.
+ //Set internal dynamics to sleep.
+ setInternalDynamicsToZero(vehNoDrive);
+ if(vehConcurrentUpdates) vehConcurrentUpdates->staySleeping = true;
+ return;
+ }
+ }
+ }
+
+ //In each block of 4 wheels record how many wheels are active.
+ PxU32 numActiveWheelsPerBlock4[PX_MAX_NB_SUSPWHEELTIRE4]={0,0,0,0,0};
+ numActiveWheelsPerBlock4[0]=PxMin(numActiveWheels,PxU32(4));
+ for(PxU32 i=1;i<numWheels4-1;i++)
+ {
+ numActiveWheelsPerBlock4[i]=4;
+ }
+ numActiveWheelsPerBlock4[numWheels4-1]=numActiveWheelsInLast4;
+ PX_ASSERT(numActiveWheels == numActiveWheelsPerBlock4[0] + numActiveWheelsPerBlock4[1] + numActiveWheelsPerBlock4[2] + numActiveWheelsPerBlock4[3] + numActiveWheelsPerBlock4[4]);
+
+ //Organise the shader data in blocks of 4.
+ PxVehicleTireForceCalculator4 tires4ForceCalculators[PX_MAX_NB_SUSPWHEELTIRE4];
+ for(PxU32 i=0;i<numWheels4;i++)
+ {
+ tires4ForceCalculators[i].mShaderData[0]=vehNoDrive->mWheelsDynData.mTireForceCalculators->mShaderData[4*i+0];
+ tires4ForceCalculators[i].mShaderData[1]=vehNoDrive->mWheelsDynData.mTireForceCalculators->mShaderData[4*i+1];
+ tires4ForceCalculators[i].mShaderData[2]=vehNoDrive->mWheelsDynData.mTireForceCalculators->mShaderData[4*i+2];
+ tires4ForceCalculators[i].mShaderData[3]=vehNoDrive->mWheelsDynData.mTireForceCalculators->mShaderData[4*i+3];
+ tires4ForceCalculators[i].mShader=vehNoDrive->mWheelsDynData.mTireForceCalculators->mShader;
+ }
+
+ //Mark the suspension/tire constraints as dirty to force them to be updated in the sdk.
+ for(PxU32 i=0;i<numWheels4;i++)
+ {
+ wheels4DynDatas[i].getVehicletConstraintShader().mConstraint->markDirty();
+ }
+
+ //Need to store report data to pose the wheels.
+ PxWheelQueryResult wheelQueryResults[PX_MAX_NB_WHEELS];
+
+ //Center of mass local pose.
+ PxTransform carChassisCMLocalPose;
+ //Compute the transform of the center of mass.
+ PxTransform origCarChassisTransform;
+ PxTransform carChassisTransform;
+ //Inverse mass and inertia to apply the tire/suspension forces as impulses.
+ PxF32 inverseChassisMass;
+ PxVec3 inverseInertia;
+ //Linear and angular velocity.
+ PxVec3 carChassisLinVel;
+ PxVec3 carChassisAngVel;
+ {
+ carChassisCMLocalPose = vehActor->getCMassLocalPose();
+ origCarChassisTransform = vehActor->getGlobalPose().transform(carChassisCMLocalPose);
+ carChassisTransform = origCarChassisTransform;
+ const PxF32 chassisMass = vehActor->getMass();
+ inverseChassisMass = 1.0f/chassisMass;
+ inverseInertia = vehActor->getMassSpaceInvInertiaTensor();
+ carChassisLinVel = vehActor->getLinearVelocity();
+ carChassisAngVel = vehActor->getAngularVelocity();
+ }
+
+ //Get the local poses of the wheel shapes.
+ //These are the poses from the last frame and equal to the poses used for the raycast we will process.
+ PxQuat wheelLocalPoseRotations[PX_MAX_NB_WHEELS];
+ PxF32 wheelThetas[PX_MAX_NB_WHEELS];
+ {
+ for (PxU32 i = 0; i < numActiveWheels; i++)
+ {
+ const PxI32 shapeId = vehNoDrive->mWheelsSimData.getWheelShapeMapping(i);
+ if (-1 != shapeId)
+ {
+ PxShape* shape = NULL;
+ vehActor->getShapes(&shape, 1, PxU32(shapeId));
+ wheelLocalPoseRotations[i] = shape->getLocalPose().q;
+ wheelThetas[i] = vehNoDrive->mWheelsDynData.getWheelRotationAngle(i);
+ }
+ }
+ }
+
+ PxF32 maxAccel=0;
+ PxF32 maxBrake=0;
+ for(PxU32 i=0;i<numActiveWheels;i++)
+ {
+ maxAccel = PxMax(PxAbs(vehNoDrive->mDriveTorques[i]), maxAccel);
+ maxBrake = PxMax(PxAbs(vehNoDrive->mBrakeTorques[i]), maxBrake);
+ }
+ const bool isIntentionToAccelerate = (maxAccel>0.0f && 0.0f==maxBrake);
+
+ //Store the susp line raycast data.
+ for(PxU32 i=0;i<numWheels4;i++)
+ {
+ storeRaycasts(wheels4DynDatas[i], &wheelQueryResults[4*i]);
+ }
+
+ //Ready to do the update.
+ PxVec3 carChassisLinVelOrig=carChassisLinVel;
+ PxVec3 carChassisAngVelOrig=carChassisAngVel;
+ const PxU32 numSubSteps=computeNumberOfSubsteps(vehNoDrive->mWheelsSimData,carChassisLinVel,carChassisTransform,gForward);
+ const PxF32 timeFraction=1.0f/(1.0f*numSubSteps);
+ const PxF32 subTimestep=timestep*timeFraction;
+ const PxF32 recipSubTimeStep=1.0f/subTimestep;
+ const PxF32 recipTimestep=1.0f/timestep;
+ const PxF32 minLongSlipDenominator=vehNoDrive->mWheelsSimData.mMinLongSlipDenominator;
+ ProcessSuspWheelTireConstData constData={timeFraction, subTimestep, recipSubTimeStep, gravity, gravityMagnitude, recipGravityMagnitude, false, minLongSlipDenominator, vehActor, &drivableSurfaceToTireFrictionPairs};
+
+ for(PxU32 k=0;k<numSubSteps;k++)
+ {
+ //Set the force and torque for the current update to zero.
+ PxVec3 chassisForce(0,0,0);
+ PxVec3 chassisTorque(0,0,0);
+
+ for(PxU32 i=0;i<numWheels4;i++)
+ {
+ //Get the raw input torques.
+ const PxF32* PX_RESTRICT rawBrakeTorques=&vehNoDrive->mBrakeTorques[4*i];
+ const PxF32* PX_RESTRICT rawSteerAngles=&vehNoDrive->mSteerAngles[4*i];
+ const PxF32* PX_RESTRICT rawDriveTorques=&vehNoDrive->mDriveTorques[4*i];
+
+ //Work out which wheels are enabled.
+ bool activeWheelStates[4]={false,false,false,false};
+ computeWheelActiveStates(4*i, vehNoDrive->mWheelsSimData.mActiveWheelsBitmapBuffer, activeWheelStates);
+
+ const PxVehicleWheels4SimData& wheels4SimData=wheels4SimDatas[i];
+ PxVehicleWheels4DynData& wheels4DynData=wheels4DynDatas[i];
+
+ //Compute the brake torques.
+ PxF32 brakeTorques[4]={0.0f,0.0f,0.0f,0.0f};
+ bool isBrakeApplied[4]={false,false,false,false};
+ computeNoDriveBrakeTorques
+ (wheels4SimData.mWheels,wheels4DynData.mWheelSpeeds,rawBrakeTorques,
+ brakeTorques,isBrakeApplied);
+
+ //Compute the per wheel accel pedal values.
+ bool isAccelApplied[4]={false,false,false,false};
+ if(isIntentionToAccelerate)
+ {
+ computeIsAccelApplied(rawDriveTorques, isAccelApplied);
+ }
+
+ //Compute jounces, slips, tire forces, suspension forces etc.
+ ProcessSuspWheelTireInputData inputData=
+ {
+ isIntentionToAccelerate, isAccelApplied, isBrakeApplied, rawSteerAngles, activeWheelStates,
+ carChassisTransform, carChassisLinVel, carChassisAngVel,
+ &wheelLocalPoseRotations[i], &wheelThetas[i], &wheels4SimData, &wheels4DynData, &tires4ForceCalculators[i], &tireLoadFilterData, numActiveWheelsPerBlock4[i]
+ };
+ ProcessSuspWheelTireOutputData outputData;
+ processSuspTireWheels(4*i, constData, inputData, outputData);
+ updateLowSpeedTimers(outputData.newLowForwardSpeedTimers, const_cast<PxF32*>(inputData.vehWheels4DynData->mTireLowForwardSpeedTimers));
+ updateLowSpeedTimers(outputData.newLowSideSpeedTimers, const_cast<PxF32*>(inputData.vehWheels4DynData->mTireLowSideSpeedTimers));
+ updateJounces(outputData.jounces, const_cast<PxF32*>(inputData.vehWheels4DynData->mJounces));
+ if((numSubSteps-1) == k)
+ {
+ updateCachedHitData(outputData.cachedHitCounts, outputData.cachedHitPlanes, outputData.cachedHitDistances, outputData.cachedFrictionMultipliers, outputData.cachedHitQueryTypes, &wheels4DynData);
+ }
+ chassisForce+=outputData.chassisForce;
+ chassisTorque+=outputData.chassisTorque;
+ if(0 == k)
+ {
+ wheels4DynDatas[i].mVehicleConstraints->mData=outputData.vehConstraintData;
+ }
+ storeSuspWheelTireResults(outputData, inputData.steerAngles, &wheelQueryResults[4*i], numActiveWheelsPerBlock4[i]);
+ storeHitActorForces(outputData, &vehicleConcurrentUpdates.concurrentWheelUpdates[4*i], numActiveWheelsPerBlock4[i]);
+
+ //Integrate wheel speeds.
+ const PxF32 wheelDampingRates[4]=
+ {
+ wheels4SimData.getWheelData(0).mDampingRate,
+ wheels4SimData.getWheelData(1).mDampingRate,
+ wheels4SimData.getWheelData(2).mDampingRate,
+ wheels4SimData.getWheelData(3).mDampingRate
+ };
+ integrateNoDriveWheelSpeeds(
+ subTimestep,
+ brakeTorques,isBrakeApplied,rawDriveTorques,outputData.tireTorques,wheelDampingRates,
+ wheels4SimData,wheels4DynData);
+
+ integrateNoDriveWheelRotationAngles(
+ subTimestep,
+ rawDriveTorques,
+ outputData.jounces, outputData.forwardSpeeds, isBrakeApplied,
+ wheels4SimData,
+ wheels4DynData);
+ }
+
+ //Apply the anti-roll suspension.
+ procesAntiRollSuspension(vehNoDrive->mWheelsSimData, carChassisTransform, wheelQueryResults, chassisTorque);
+
+ //Integrate the chassis velocity by applying the accumulated force and torque.
+ integrateBody(inverseChassisMass, inverseInertia, chassisForce, chassisTorque, subTimestep, carChassisLinVel, carChassisAngVel, carChassisTransform);
+ }
+
+ //Set the new chassis linear/angular velocity.
+ if(!gApplyForces)
+ {
+ vehicleConcurrentUpdates.linearMomentumChange = carChassisLinVel;
+ vehicleConcurrentUpdates.angularMomentumChange = carChassisAngVel;
+ }
+ else
+ {
+ //integration steps are:
+ //v = v0 + a*dt (1)
+ //x = x0 + v*dt (2)
+ //Sub (2) into (1.
+ //x = x0 + v0*dt + a*dt*dt;
+ //Rearrange for a
+ //a = (x -x0 - v0*dt)/(dt*dt) = [(x-x0)/dt - v0/dt]
+ //Rearrange again with v = (x-x0)/dt
+ //a = (v - v0)/dt
+ vehicleConcurrentUpdates.linearMomentumChange = (carChassisLinVel-carChassisLinVelOrig)*recipTimestep;
+ vehicleConcurrentUpdates.angularMomentumChange = (carChassisAngVel-carChassisAngVelOrig)*recipTimestep;
+ }
+
+ //Pose the wheels from jounces, rotations angles, and steer angles.
+ PxTransform localPoses0[4] = {PxTransform(PxIdentity), PxTransform(PxIdentity), PxTransform(PxIdentity), PxTransform(PxIdentity)};
+ computeWheelLocalPoses(wheels4SimDatas[0],wheels4DynDatas[0],&wheelQueryResults[4*0],numActiveWheelsPerBlock4[0],carChassisCMLocalPose,localPoses0);
+ wheelQueryResults[4*0 + 0].localPose = localPoses0[0];
+ wheelQueryResults[4*0 + 1].localPose = localPoses0[1];
+ wheelQueryResults[4*0 + 2].localPose = localPoses0[2];
+ wheelQueryResults[4*0 + 3].localPose = localPoses0[3];
+ vehicleConcurrentUpdates.concurrentWheelUpdates[4*0 + 0].localPose = localPoses0[0];
+ vehicleConcurrentUpdates.concurrentWheelUpdates[4*0 + 1].localPose = localPoses0[1];
+ vehicleConcurrentUpdates.concurrentWheelUpdates[4*0 + 2].localPose = localPoses0[2];
+ vehicleConcurrentUpdates.concurrentWheelUpdates[4*0 + 3].localPose = localPoses0[3];
+ for(PxU32 i=1;i<numWheels4;i++)
+ {
+ PxTransform localPoses[4] = {PxTransform(PxIdentity), PxTransform(PxIdentity), PxTransform(PxIdentity), PxTransform(PxIdentity)};
+ computeWheelLocalPoses(wheels4SimDatas[i],wheels4DynDatas[i],&wheelQueryResults[4*i],numActiveWheelsPerBlock4[i],carChassisCMLocalPose,localPoses);
+ wheelQueryResults[4*i + 0].localPose = localPoses[0];
+ wheelQueryResults[4*i + 1].localPose = localPoses[1];
+ wheelQueryResults[4*i + 2].localPose = localPoses[2];
+ wheelQueryResults[4*i + 3].localPose = localPoses[3];
+ vehicleConcurrentUpdates.concurrentWheelUpdates[4*i + 0].localPose = localPoses[0];
+ vehicleConcurrentUpdates.concurrentWheelUpdates[4*i + 1].localPose = localPoses[1];
+ vehicleConcurrentUpdates.concurrentWheelUpdates[4*i + 2].localPose = localPoses[2];
+ vehicleConcurrentUpdates.concurrentWheelUpdates[4*i + 3].localPose = localPoses[3];
+ }
+
+ if(vehWheelQueryResults && vehWheelQueryResults->wheelQueryResults)
+ {
+ PxMemCopy(vehWheelQueryResults->wheelQueryResults, wheelQueryResults, sizeof(PxWheelQueryResult)*numActiveWheels);
+ }
+
+ if(vehConcurrentUpdates)
+ {
+ //Copy across to input data structure so that writes can be applied later.
+ PxMemCopy(vehConcurrentUpdates->concurrentWheelUpdates, vehicleConcurrentUpdates.concurrentWheelUpdates, sizeof(PxVehicleWheelConcurrentUpdateData)*numActiveWheels);
+ vehConcurrentUpdates->linearMomentumChange = vehicleConcurrentUpdates.linearMomentumChange;
+ vehConcurrentUpdates->angularMomentumChange = vehicleConcurrentUpdates.angularMomentumChange;
+ vehConcurrentUpdates->staySleeping = vehicleConcurrentUpdates.staySleeping;
+ vehConcurrentUpdates->wakeup = vehicleConcurrentUpdates.wakeup;
+ }
+ else
+ {
+ //Apply the writes immediately.
+ PxVehicleWheels* vehWheels[1]={vehNoDrive};
+ PxVehiclePostUpdates(&vehicleConcurrentUpdates, 1, vehWheels);
+ }
+}
+
+
+void PxVehicleUpdate::shiftOrigin(const PxVec3& shift, const PxU32 numVehicles, PxVehicleWheels** vehicles)
+{
+ for(PxU32 i=0; i < numVehicles; i++)
+ {
+ //Get the current car.
+ PxVehicleWheels& veh = *vehicles[i];
+ PxVehicleWheels4DynData* PX_RESTRICT wheels4DynData=veh.mWheelsDynData.mWheels4DynData;
+ const PxU32 numWheels4=veh.mWheelsSimData.mNbWheels4;
+
+ //Blocks of 4 wheels.
+ for(PxU32 j=0; j < numWheels4; j++)
+ {
+ bool activeWheelStates[4]={false,false,false,false};
+ computeWheelActiveStates(4*j, veh.mWheelsSimData.mActiveWheelsBitmapBuffer, activeWheelStates);
+
+ if (wheels4DynData[j].mRaycastResults) // this is set when a query has been scheduled
+ {
+ PxVehicleWheels4DynData::SuspLineRaycast& raycast =
+ reinterpret_cast<PxVehicleWheels4DynData::SuspLineRaycast&>(wheels4DynData[j].mQueryOrCachedHitResults);
+
+ for(PxU32 k=0; k < 4; k++)
+ {
+ if (activeWheelStates[k])
+ {
+ raycast.mStarts[k] -= shift;
+
+ if (wheels4DynData[j].mRaycastResults[k].hasBlock)
+ const_cast<PxVec3&>(wheels4DynData[j].mRaycastResults[k].block.position) -= shift;
+ }
+ }
+ }
+ else if(wheels4DynData[i].mSweepResults)
+ {
+ PxVehicleWheels4DynData::SuspLineSweep& sweep =
+ reinterpret_cast<PxVehicleWheels4DynData::SuspLineSweep&>(wheels4DynData[j].mQueryOrCachedHitResults);
+
+ for(PxU32 k=0; k < 4; k++)
+ {
+ if (activeWheelStates[k])
+ {
+ sweep.mStartPose[k].p -= shift;
+
+ if (wheels4DynData[j].mSweepResults[k].hasBlock)
+ const_cast<PxVec3&>(wheels4DynData[j].mSweepResults[k].block.position) -= shift;
+ }
+ }
+ }
+ }
+ }
+}
+
+}//namespace physx
+
+#if PX_DEBUG_VEHICLE_ON
+
+/////////////////////////////////////////////////////////////////////////////////
+//Update a single vehicle of any type and record the associated telemetry data.
+/////////////////////////////////////////////////////////////////////////////////
+
+void PxVehicleUpdate::updateSingleVehicleAndStoreTelemetryData
+(const PxF32 timestep, const PxVec3& gravity, const PxVehicleDrivableSurfaceToTireFrictionPairs& vehicleDrivableSurfaceToTireFrictionPairs,
+ PxVehicleWheels* vehWheels, PxVehicleWheelQueryResult* vehWheelQueryResults, PxVehicleTelemetryData& telemetryData)
+{
+ START_TIMER(TIMER_ALL);
+
+ PX_CHECK_MSG(gravity.magnitude()>0, "gravity vector must have non-zero length");
+ PX_CHECK_MSG(timestep>0, "timestep must be greater than zero");
+ PX_CHECK_AND_RETURN(gThresholdForwardSpeedForWheelAngleIntegration>0, "PxInitVehicleSDK needs to be called before ever calling PxVehicleUpdateSingleVehicleAndStoreTelemetryData");
+ PX_CHECK_MSG(vehWheels->mWheelsSimData.getNbWheels()==telemetryData.getNbWheelGraphs(), "vehicle and telemetry data need to have the same number of wheels");
+ PX_CHECK_AND_RETURN(NULL==vehWheelQueryResults || vehWheelQueryResults->nbWheelQueryResults >= vehWheels->mWheelsSimData.getNbWheels(),
+ "nbWheelQueryResults must always be greater than or equal to number of wheels in corresponding vehicle");
+
+#if PX_CHECKED
+ for(PxU32 i=0;i<vehWheels->mWheelsSimData.mNbWheels4;i++)
+ {
+ PX_CHECK_MSG(vehWheels->mWheelsDynData.mWheels4DynData[i].mRaycastResults || vehWheels->mWheelsDynData.mWheels4DynData[i].mSweepResults,
+ "Need to call PxVehicleSuspensionRaycasts or PxVehicleSuspensionSweeps before trying to update");
+ }
+ for(PxU32 i=0;i<vehWheels->mWheelsSimData.mNbActiveWheels;i++)
+ {
+ PX_CHECK_MSG(vehWheels->mWheelsDynData.mTireForceCalculators->mShaderData[i], "Need to set non-null tire force shader data ptr");
+ }
+ PX_CHECK_MSG(vehWheels->mWheelsDynData.mTireForceCalculators->mShader, "Need to set non-null tire force shader function");
+
+ for(PxU32 i=0;i<vehWheels->mWheelsSimData.mNbActiveWheels;i++)
+ {
+ PX_CHECK_AND_RETURN(!vehWheels->mWheelsSimData.getIsWheelDisabled(i) || -1==vehWheels->mWheelsSimData.getWheelShapeMapping(i),
+ "Disabled wheels must not be associated with a PxShape: use setWheelShapeMapping to remove the association");
+ PX_CHECK_AND_RETURN(!vehWheels->mWheelsSimData.getIsWheelDisabled(i) || 0==vehWheels->mWheelsDynData.getWheelRotationSpeed(i),
+ "Disabled wheels must have zero rotation speed: use setWheelRotationSpeed to set the wheel to zero rotation speed");
+ }
+#endif
+
+ PxF32 engineGraphData[PxVehicleDriveGraphChannel::eMAX_NB_DRIVE_CHANNELS];
+ PxMemZero(&engineGraphData[0], PxVehicleDriveGraphChannel::eMAX_NB_DRIVE_CHANNELS*sizeof(PxF32));
+ gCarEngineGraphData=engineGraphData;
+
+ PxF32 wheelGraphData[PX_MAX_NB_WHEELS][PxVehicleWheelGraphChannel::eMAX_NB_WHEEL_CHANNELS];
+ PxMemZero(&wheelGraphData[0][0], PX_MAX_NB_WHEELS*PxVehicleWheelGraphChannel::eMAX_NB_WHEEL_CHANNELS*sizeof(PxF32));
+ for(PxU32 i=0;i<4*vehWheels->mWheelsSimData.mNbWheels4;i++)
+ {
+ gCarWheelGraphData[i]=wheelGraphData[i];
+ }
+ for(PxU32 i=4*vehWheels->mWheelsSimData.mNbWheels4; i<PX_MAX_NB_WHEELS;i++)
+ {
+ gCarWheelGraphData[i]=NULL;
+ }
+
+ PxVec3 suspForceAppPoints[PX_MAX_NB_WHEELS];
+ PxMemZero(suspForceAppPoints, PX_MAX_NB_WHEELS*sizeof(PxVec3));
+ gCarSuspForceAppPoints=suspForceAppPoints;
+
+ PxVec3 tireForceAppPoints[PX_MAX_NB_WHEELS];
+ PxMemZero(tireForceAppPoints, PX_MAX_NB_WHEELS*sizeof(PxVec3));
+ gCarTireForceAppPoints=tireForceAppPoints;
+
+ const PxF32 gravityMagnitude=gravity.magnitude();
+ const PxF32 recipGravityMagnitude=1.0f/gravityMagnitude;
+
+ switch(vehWheels->mType)
+ {
+ case PxVehicleTypes::eDRIVE4W:
+ {
+ PxVehicleDrive4W* vehDrive4W=static_cast<PxVehicleDrive4W*>(vehWheels);
+
+ PxVehicleUpdate::updateDrive4W(
+ timestep,
+ gravity, gravityMagnitude, recipGravityMagnitude,
+ vehicleDrivableSurfaceToTireFrictionPairs,
+ vehDrive4W, vehWheelQueryResults, NULL);
+
+ for(PxU32 i=0;i<vehWheels->mWheelsSimData.mNbActiveWheels;i++)
+ {
+ telemetryData.mWheelGraphs[i].updateTimeSlice(wheelGraphData[i]);
+ telemetryData.mSuspforceAppPoints[i]=suspForceAppPoints[i];
+ telemetryData.mTireforceAppPoints[i]=tireForceAppPoints[i];
+ }
+ telemetryData.mEngineGraph->updateTimeSlice(engineGraphData);
+ }
+ break;
+ case PxVehicleTypes::eDRIVENW:
+ {
+ PxVehicleDriveNW* vehDriveNW=static_cast<PxVehicleDriveNW*>(vehWheels);
+
+ PxVehicleUpdate::updateDriveNW(
+ timestep,
+ gravity, gravityMagnitude, recipGravityMagnitude,
+ vehicleDrivableSurfaceToTireFrictionPairs,
+ vehDriveNW, vehWheelQueryResults, NULL);
+
+ for(PxU32 i=0;i<vehWheels->mWheelsSimData.mNbActiveWheels;i++)
+ {
+ telemetryData.mWheelGraphs[i].updateTimeSlice(wheelGraphData[i]);
+ telemetryData.mSuspforceAppPoints[i]=suspForceAppPoints[i];
+ telemetryData.mTireforceAppPoints[i]=tireForceAppPoints[i];
+ }
+ telemetryData.mEngineGraph->updateTimeSlice(engineGraphData);
+ }
+ break;
+
+ case PxVehicleTypes::eDRIVETANK:
+ {
+ PxVehicleDriveTank* vehDriveTank=static_cast<PxVehicleDriveTank*>(vehWheels);
+
+ PxVehicleUpdate::updateTank(
+ timestep,
+ gravity,gravityMagnitude,recipGravityMagnitude,
+ vehicleDrivableSurfaceToTireFrictionPairs,
+ vehDriveTank, vehWheelQueryResults, NULL);
+
+ for(PxU32 i=0;i<vehWheels->mWheelsSimData.mNbActiveWheels;i++)
+ {
+ telemetryData.mWheelGraphs[i].updateTimeSlice(wheelGraphData[i]);
+ telemetryData.mSuspforceAppPoints[i]=suspForceAppPoints[i];
+ telemetryData.mTireforceAppPoints[i]=tireForceAppPoints[i];
+ }
+ telemetryData.mEngineGraph->updateTimeSlice(engineGraphData);
+ }
+ break;
+ case PxVehicleTypes::eNODRIVE:
+ {
+ PxVehicleNoDrive* vehDriveNoDrive=static_cast<PxVehicleNoDrive*>(vehWheels);
+
+ PxVehicleUpdate::updateNoDrive(
+ timestep,
+ gravity,gravityMagnitude,recipGravityMagnitude,
+ vehicleDrivableSurfaceToTireFrictionPairs,
+ vehDriveNoDrive, vehWheelQueryResults, NULL);
+
+ for(PxU32 i=0;i<vehWheels->mWheelsSimData.mNbActiveWheels;i++)
+ {
+ telemetryData.mWheelGraphs[i].updateTimeSlice(wheelGraphData[i]);
+ telemetryData.mSuspforceAppPoints[i]=suspForceAppPoints[i];
+ telemetryData.mTireforceAppPoints[i]=tireForceAppPoints[i];
+ }
+ }
+ break;
+
+ default:
+ PX_CHECK_MSG(false, "updateSingleVehicleAndStoreTelemetryData - unsupported vehicle type");
+ break;
+ }
+
+ END_TIMER(TIMER_ALL);
+
+#if PX_VEHICLE_PROFILE
+
+ gTimerCount++;
+ if(10==gTimerCount)
+ {
+
+ /*
+ printf("%f %f %f %f %f %f %f %f %f\n",
+ localTimers[TIMER_ADMIN]/(1.0f*localTimers[TIMER_ALL]),
+ localTimers[TIMER_GRAPHS]/(1.0f*localTimers[TIMER_ALL]),
+ localTimers[TIMER_COMPONENTS_UPDATE]/(1.0f*localTimers[TIMER_ALL]),
+ localTimers[TIMER_WHEELS]/(1.0f*localTimers[TIMER_ALL]),
+ localTimers[TIMER_INTERNAL_DYNAMICS_SOLVER]/(1.0f*localTimers[TIMER_ALL]),
+ localTimers[TIMER_POSTUPDATE1]/(1.0f*localTimers[TIMER_ALL]),
+ localTimers[TIMER_POSTUPDATE2]/(1.0f*localTimers[TIMER_ALL]),
+ localTimers[TIMER_POSTUPDATE3]/(1.0f*localTimers[TIMER_ALL]),
+ floatTimeIn10sOfNs);
+ */
+
+ printf("%f %f %f %f %f %f \n",
+ getTimerFraction(TIMER_WHEELS),
+ getTimerFraction(TIMER_INTERNAL_DYNAMICS_SOLVER),
+ getTimerFraction(TIMER_POSTUPDATE2),
+ getTimerFraction(TIMER_POSTUPDATE3),
+ getTimerInMilliseconds(TIMER_ALL),
+ getTimerInMilliseconds(TIMER_RAYCASTS));
+
+ gTimerCount=0;
+ for(PxU32 i=0;i<MAX_NB_TIMERS;i++)
+ {
+ gTimers[i]=0;
+ }
+ }
+
+#endif
+}
+
+void physx::PxVehicleUpdateSingleVehicleAndStoreTelemetryData
+(const PxReal timestep, const PxVec3& gravity, const physx::PxVehicleDrivableSurfaceToTireFrictionPairs& vehicleDrivableSurfaceToTireFrictionPairs,
+ PxVehicleWheels* focusVehicle, PxVehicleWheelQueryResult* wheelQueryResults, PxVehicleTelemetryData& telemetryData)
+{
+ PxVehicleUpdate::updateSingleVehicleAndStoreTelemetryData
+ (timestep, gravity, vehicleDrivableSurfaceToTireFrictionPairs, focusVehicle, wheelQueryResults, telemetryData);
+}
+
+#endif
+
+////////////////////////////////////////////////////////////
+//Update an array of vehicles of any type
+////////////////////////////////////////////////////////////
+
+void PxVehicleUpdate::update
+(const PxF32 timestep, const PxVec3& gravity, const PxVehicleDrivableSurfaceToTireFrictionPairs& vehicleDrivableSurfaceToTireFrictionPairs,
+ const PxU32 numVehicles, PxVehicleWheels** vehicles, PxVehicleWheelQueryResult* vehicleWheelQueryResults, PxVehicleConcurrentUpdateData* vehicleConcurrentUpdates)
+{
+ PX_CHECK_AND_RETURN(gravity.magnitude()>0, "gravity vector must have non-zero length");
+ PX_CHECK_AND_RETURN(timestep>0, "timestep must be greater than zero");
+ PX_CHECK_AND_RETURN(gThresholdForwardSpeedForWheelAngleIntegration>0, "PxInitVehicleSDK needs to be called before ever calling PxVehicleUpdates");
+
+#if PX_CHECKED
+ for(PxU32 i=0;i<numVehicles;i++)
+ {
+ const PxVehicleWheels* const vehWheels=vehicles[i];
+ for(PxU32 j=0;j<vehWheels->mWheelsSimData.mNbWheels4;j++)
+ {
+ PX_CHECK_MSG(
+ vehWheels->mWheelsDynData.mWheels4DynData[j].mRaycastResults ||
+ vehWheels->mWheelsDynData.mWheels4DynData[j].mSweepResults ||
+ vehWheels->mWheelsDynData.mWheels4DynData[0].mHasCachedRaycastHitPlane ||
+ (vehWheels->mWheelsSimData.getIsWheelDisabled(4*j+0) &&
+ vehWheels->mWheelsSimData.getIsWheelDisabled(4*j+1) &&
+ vehWheels->mWheelsSimData.getIsWheelDisabled(4*j+2) &&
+ vehWheels->mWheelsSimData.getIsWheelDisabled(4*j+3)),
+ "Need to call PxVehicleSuspensionRaycasts or PxVehicleSuspensionSweeps at least once before trying to update");
+ }
+ for(PxU32 j=0;j<vehWheels->mWheelsSimData.mNbActiveWheels;j++)
+ {
+ PX_CHECK_MSG(vehWheels->mWheelsDynData.mTireForceCalculators->mShaderData[j], "Need to set non-null tire force shader data ptr");
+ }
+ PX_CHECK_MSG(vehWheels->mWheelsDynData.mTireForceCalculators->mShader, "Need to set non-null tire force shader function");
+
+ PX_CHECK_AND_RETURN(NULL==vehicleWheelQueryResults || vehicleWheelQueryResults[i].nbWheelQueryResults >= vehicles[i]->mWheelsSimData.getNbWheels(),
+ "nbWheelQueryResults must always be greater than or equal to number of wheels in corresponding vehicle");
+
+ for(PxU32 j=0;j<vehWheels->mWheelsSimData.mNbActiveWheels;j++)
+ {
+ PX_CHECK_AND_RETURN(!vehWheels->mWheelsSimData.getIsWheelDisabled(j) || -1==vehWheels->mWheelsSimData.getWheelShapeMapping(j),
+ "Disabled wheels must not be associated with a PxShape: use setWheelShapeMapping to remove the association");
+
+ PX_CHECK_AND_RETURN(!vehWheels->mWheelsSimData.getIsWheelDisabled(j) || 0==vehWheels->mWheelsDynData.getWheelRotationSpeed(j),
+ "Disabled wheels must have zero rotation speed: use setWheelRotationSpeed to set the wheel to zero rotation speed");
+ }
+
+ PX_CHECK_AND_RETURN(!vehicleConcurrentUpdates || (vehicleConcurrentUpdates[i].concurrentWheelUpdates && vehicleConcurrentUpdates[i].nbConcurrentWheelUpdates >= vehicles[i]->mWheelsSimData.getNbWheels()),
+ "vehicleConcurrentUpdates is illegally configured with either null pointers or with insufficient memory for successful concurrent updates.");
+
+ for(PxU32 j=0; j < vehWheels->mWheelsSimData.mNbActiveAntiRollBars; j++)
+ {
+ const PxVehicleAntiRollBarData antiRoll = vehWheels->mWheelsSimData.getAntiRollBarData(j);
+ PX_CHECK_AND_RETURN(!vehWheels->mWheelsSimData.getIsWheelDisabled(antiRoll.mWheel0), "Wheel0 of antiroll bar is disabled. This is not supported.");
+ PX_CHECK_AND_RETURN(!vehWheels->mWheelsSimData.getIsWheelDisabled(antiRoll.mWheel1), "Wheel1 of antiroll bar is disabled. This is not supported.");
+ }
+ }
+#endif
+
+#if PX_DEBUG_VEHICLE_ON
+ gCarEngineGraphData=NULL;
+ for(PxU32 j=0;j<PX_MAX_NB_WHEELS;j++)
+ {
+ gCarWheelGraphData[j]=NULL;
+ }
+ gCarSuspForceAppPoints=NULL;
+ gCarTireForceAppPoints=NULL;
+#endif
+
+ const PxF32 gravityMagnitude=gravity.magnitude();
+ const PxF32 recipGravityMagnitude=1.0f/gravityMagnitude;
+
+ for(PxU32 i=0;i<numVehicles;i++)
+ {
+ PxVehicleWheels* vehWheels=vehicles[i];
+ PxVehicleWheelQueryResult* vehWheelQueryResults = vehicleWheelQueryResults ? &vehicleWheelQueryResults[i] : NULL;
+ PxVehicleConcurrentUpdateData* vehConcurrentUpdateData = vehicleConcurrentUpdates ? &vehicleConcurrentUpdates[i] : NULL;
+ switch(vehWheels->mType)
+ {
+ case PxVehicleTypes::eDRIVE4W:
+ {
+ PxVehicleDrive4W* vehDrive4W=static_cast<PxVehicleDrive4W*>(vehWheels);
+
+ PxVehicleUpdate::updateDrive4W(
+ timestep,
+ gravity,gravityMagnitude,recipGravityMagnitude,
+ vehicleDrivableSurfaceToTireFrictionPairs,
+ vehDrive4W, vehWheelQueryResults, vehConcurrentUpdateData);
+ }
+ break;
+
+ case PxVehicleTypes::eDRIVENW:
+ {
+ PxVehicleDriveNW* vehDriveNW=static_cast<PxVehicleDriveNW*>(vehWheels);
+
+ PxVehicleUpdate::updateDriveNW(
+ timestep,
+ gravity,gravityMagnitude,recipGravityMagnitude,
+ vehicleDrivableSurfaceToTireFrictionPairs,
+ vehDriveNW, vehWheelQueryResults, vehConcurrentUpdateData);
+ }
+ break;
+
+ case PxVehicleTypes::eDRIVETANK:
+ {
+ PxVehicleDriveTank* vehDriveTank=static_cast<PxVehicleDriveTank*>(vehWheels);
+
+ PxVehicleUpdate::updateTank(
+ timestep,
+ gravity,gravityMagnitude,recipGravityMagnitude,
+ vehicleDrivableSurfaceToTireFrictionPairs,
+ vehDriveTank, vehWheelQueryResults, vehConcurrentUpdateData);
+ }
+ break;
+
+ case PxVehicleTypes::eNODRIVE:
+ {
+ PxVehicleNoDrive* vehDriveNoDrive=static_cast<PxVehicleNoDrive*>(vehWheels);
+
+ PxVehicleUpdate::updateNoDrive(
+ timestep,
+ gravity,gravityMagnitude,recipGravityMagnitude,
+ vehicleDrivableSurfaceToTireFrictionPairs,
+ vehDriveNoDrive, vehWheelQueryResults, vehConcurrentUpdateData);
+ }
+ break;
+
+ default:
+ PX_CHECK_MSG(false, "update - unsupported vehicle type");
+ break;
+ }
+ }
+}
+
+
+void PxVehicleUpdate::updatePost
+(const PxVehicleConcurrentUpdateData* vehicleConcurrentUpdates, const PxU32 numVehicles, PxVehicleWheels** vehicles)
+{
+ PX_CHECK_AND_RETURN(vehicleConcurrentUpdates, "vehicleConcurrentUpdates must be non-null.");
+
+#if PX_CHECKED
+ for(PxU32 i=0;i<numVehicles;i++)
+ {
+ PxVehicleWheels* vehWheels=vehicles[i];
+ for(PxU32 j=0;j<vehWheels->mWheelsSimData.mNbActiveWheels;j++)
+ {
+ PX_CHECK_AND_RETURN(!vehWheels->mWheelsSimData.getIsWheelDisabled(j) || -1==vehWheels->mWheelsSimData.getWheelShapeMapping(j),
+ "Disabled wheels must not be associated with a PxShape: use setWheelShapeMapping to remove the association");
+
+ PX_CHECK_AND_RETURN(!vehWheels->mWheelsSimData.getIsWheelDisabled(j) || 0==vehWheels->mWheelsDynData.getWheelRotationSpeed(j),
+ "Disabled wheels must have zero rotation speed: use setWheelRotationSpeed to set the wheel to zero rotation speed");
+
+ PX_CHECK_AND_RETURN(vehicleConcurrentUpdates[i].concurrentWheelUpdates && vehicleConcurrentUpdates[i].nbConcurrentWheelUpdates >= vehWheels->mWheelsSimData.getNbWheels(),
+ "vehicleConcurrentUpdates is illegally configured with either null pointers or insufficient memory for successful concurrent vehicle updates.");
+ }
+ }
+#endif
+
+ for(PxU32 i=0;i<numVehicles;i++)
+ {
+ //Get the ith vehicle and its actor.
+ PxVehicleWheels* vehWheels=vehicles[i];
+ PxRigidDynamic* vehActor = vehWheels->getRigidDynamicActor();
+
+ //Get the concurrent update data for the ith vehicle.
+ //This contains the data that couldn't get updated concurrently and now must be
+ //set sequentially.
+ const PxVehicleConcurrentUpdateData& vehicleConcurrentUpdate = vehicleConcurrentUpdates[i];
+
+ //Test if the actor is to remain sleeping.
+ //If the actor is to remain sleeping then do nothing.
+ if(!vehicleConcurrentUpdate.staySleeping)
+ {
+ //Wake the vehicle's actor up as required.
+ if(vehicleConcurrentUpdate.wakeup)
+ {
+ vehActor->wakeUp();
+ }
+
+ //Apply momentum changes to vehicle's actor
+ if(!gApplyForces)
+ {
+ vehActor->setLinearVelocity(vehicleConcurrentUpdate.linearMomentumChange, false);
+ vehActor->setAngularVelocity(vehicleConcurrentUpdate.angularMomentumChange, false);
+ }
+ else
+ {
+ vehActor->addForce(vehicleConcurrentUpdate.linearMomentumChange, PxForceMode::eACCELERATION, false);
+ vehActor->addTorque(vehicleConcurrentUpdate.angularMomentumChange, PxForceMode::eACCELERATION, false);
+ }
+
+ //In each block of 4 wheels record how many wheels are active.
+ const PxU32 numActiveWheels=vehWheels->mWheelsSimData.mNbActiveWheels;
+ const PxU32 numWheels4 = vehWheels->mWheelsSimData.getNbWheels4();
+ const PxU32 numActiveWheelsInLast4=4-(4*numWheels4 - numActiveWheels);
+ PxU32 numActiveWheelsPerBlock4[PX_MAX_NB_SUSPWHEELTIRE4]={0,0,0,0,0};
+ numActiveWheelsPerBlock4[0]=PxMin(numActiveWheels,PxU32(4));
+ for(PxU32 j=1;j<numWheels4-1;j++)
+ {
+ numActiveWheelsPerBlock4[j]=4;
+ }
+ numActiveWheelsPerBlock4[numWheels4-1]=numActiveWheelsInLast4;
+ PX_ASSERT(numActiveWheels == numActiveWheelsPerBlock4[0] + numActiveWheelsPerBlock4[1] + numActiveWheelsPerBlock4[2] + numActiveWheelsPerBlock4[3] + numActiveWheelsPerBlock4[4]);
+
+ //Apply the local poses to the shapes of the vehicle's actor that represent wheels.
+ for(PxU32 j=0;j<numWheels4;j++)
+ {
+ PxTransform localPoses[4]=
+ {
+ vehicleConcurrentUpdate.concurrentWheelUpdates[j*4 + 0].localPose,
+ vehicleConcurrentUpdate.concurrentWheelUpdates[j*4 + 1].localPose,
+ vehicleConcurrentUpdate.concurrentWheelUpdates[j*4 + 2].localPose,
+ vehicleConcurrentUpdate.concurrentWheelUpdates[j*4 + 3].localPose
+ };
+ poseWheels(vehWheels->mWheelsSimData.mWheels4SimData[j],localPoses,numActiveWheelsPerBlock4[j],vehActor);
+ }
+
+ //Apply forces to dynamic actors hit by the wheels.
+ for(PxU32 j=0;j<numActiveWheels;j++)
+ {
+ PxRigidDynamic* hitActor=vehicleConcurrentUpdate.concurrentWheelUpdates[j].hitActor;
+ if(hitActor)
+ {
+ const PxVec3& hitForce=vehicleConcurrentUpdate.concurrentWheelUpdates[j].hitActorForce;
+ const PxVec3& hitForcePosition=vehicleConcurrentUpdate.concurrentWheelUpdates[j].hitActorForcePosition;
+ PxRigidBodyExt::addForceAtPos(*hitActor,hitForce,hitForcePosition);
+ }
+ }
+ }
+ }
+}
+
+
+void physx::PxVehicleUpdates
+(const PxReal timestep, const PxVec3& gravity, const PxVehicleDrivableSurfaceToTireFrictionPairs& vehicleDrivableSurfaceToTireFrictionPairs,
+ const PxU32 numVehicles, PxVehicleWheels** vehicles, PxVehicleWheelQueryResult* vehicleWheelQueryResults, PxVehicleConcurrentUpdateData* vehicleConcurrentUpdates)
+{
+ PX_PROFILE_ZONE("PxVehicleUpdates::ePROFILE_UPDATES",0);
+ PxVehicleUpdate::update(timestep, gravity, vehicleDrivableSurfaceToTireFrictionPairs, numVehicles, vehicles, vehicleWheelQueryResults, vehicleConcurrentUpdates);
+}
+
+void physx::PxVehiclePostUpdates
+(const PxVehicleConcurrentUpdateData* vehicleConcurrentUpdates, const PxU32 numVehicles, PxVehicleWheels** vehicles)
+{
+ PX_PROFILE_ZONE("PxVehicleUpdates::ePROFILE_POSTUPDATES",0);
+ PxVehicleUpdate::updatePost(vehicleConcurrentUpdates, numVehicles, vehicles);
+}
+
+void physx::PxVehicleShiftOrigin(const PxVec3& shift, const PxU32 numVehicles, PxVehicleWheels** vehicles)
+{
+ PxVehicleUpdate::shiftOrigin(shift, numVehicles, vehicles);
+}
+
+///////////////////////////////////////////////////////////////////////////////////
+//The following functions issue a single batch of suspension raycasts for an array of vehicles of any type.
+//The buffer of sceneQueryResults is distributed among the vehicles in the array
+//for use in the next PxVehicleUpdates call.
+///////////////////////////////////////////////////////////////////////////////////
+
+void PxVehicleWheels4SuspensionRaycasts
+(PxBatchQuery* batchQuery,
+ const PxVehicleWheels4SimData& wheels4SimData, PxVehicleWheels4DynData& wheels4DynData,
+ const PxQueryFilterData* carFilterData, const bool* activeWheelStates, const PxU32 numActiveWheels,
+ PxRigidDynamic* vehActor)
+{
+ //Get the transform of the chassis.
+ PxTransform carChassisTrnsfm=vehActor->getGlobalPose().transform(vehActor->getCMassLocalPose());
+
+ //Add a raycast for each wheel.
+ for(PxU32 j=0;j<numActiveWheels;j++)
+ {
+ const PxVehicleSuspensionData& susp=wheels4SimData.getSuspensionData(j);
+ const PxVehicleWheelData& wheel=wheels4SimData.getWheelData(j);
+
+ const PxVec3& bodySpaceSuspTravelDir=wheels4SimData.getSuspTravelDirection(j);
+ PxVec3 bodySpaceWheelCentreOffset=wheels4SimData.getWheelCentreOffset(j);
+ PxF32 maxDroop=susp.mMaxDroop;
+ PxF32 maxBounce=susp.mMaxCompression;
+ PxF32 radius=wheel.mRadius;
+ PX_ASSERT(maxBounce>=0);
+ PX_ASSERT(maxDroop>=0);
+
+ if(!activeWheelStates[j])
+ {
+ //For disabled wheels just issue a raycast of almost zero length.
+ //This should be very cheap and ought to hit nothing.
+ bodySpaceWheelCentreOffset=PxVec3(0,0,0);
+ maxDroop=1e-5f*gToleranceScaleLength;
+ maxBounce=1e-5f*gToleranceScaleLength;
+ radius=1e-5f*gToleranceScaleLength;
+ }
+
+ PxVec3 suspLineStart;
+ PxVec3 suspLineDir;
+ computeSuspensionRaycast(carChassisTrnsfm,bodySpaceWheelCentreOffset,bodySpaceSuspTravelDir,radius,maxBounce,suspLineStart,suspLineDir);
+
+ //Total length from top of wheel at max compression to bottom of wheel at max droop.
+ PxF32 suspLineLength=radius + maxBounce + maxDroop + radius;
+ //Add another radius on for good measure.
+ suspLineLength+=radius;
+
+ //Store the susp line ray for later use.
+ PxVehicleWheels4DynData::SuspLineRaycast& raycast =
+ reinterpret_cast<PxVehicleWheels4DynData::SuspLineRaycast&>(wheels4DynData.mQueryOrCachedHitResults);
+ raycast.mStarts[j]=suspLineStart;
+ raycast.mDirs[j]=suspLineDir;
+ raycast.mLengths[j]=suspLineLength;
+
+ //Add the raycast to the scene query.
+ batchQuery->raycast(
+ suspLineStart, suspLineDir, suspLineLength, 0,
+ PxHitFlag::ePOSITION|PxHitFlag::eNORMAL|PxHitFlag::eDISTANCE|PxHitFlag::eUV, carFilterData[j]);
+ }
+}
+
+void PxVehicleUpdate::suspensionRaycasts(PxBatchQuery* batchQuery, const PxU32 numVehicles, PxVehicleWheels** vehicles, const PxU32 numSceneQueryResults, PxRaycastQueryResult* sceneQueryResults, const bool* vehiclesToRaycast)
+{
+ START_TIMER(TIMER_RAYCASTS);
+
+ //Reset all hit counts to zero.
+ for(PxU32 i=0;i<numSceneQueryResults;i++)
+ {
+ sceneQueryResults[i].hasBlock=false;
+ }
+
+ PxRaycastQueryResult* sqres=sceneQueryResults;
+
+ const PxQueryFlags flags = PxQueryFlag::eSTATIC|PxQueryFlag::eDYNAMIC|PxQueryFlag::ePREFILTER;
+ PxQueryFilterData carFilterData[4];
+ carFilterData[0].flags=flags;
+ carFilterData[1].flags=flags;
+ carFilterData[2].flags=flags;
+ carFilterData[3].flags=flags;
+
+ //Work out the rays for the suspension line raycasts and perform all the raycasts.
+ for(PxU32 i=0;i<numVehicles;i++)
+ {
+ //Get the current car.
+ PxVehicleWheels& veh=*vehicles[i];
+ const PxVehicleWheels4SimData* PX_RESTRICT wheels4SimData=veh.mWheelsSimData.mWheels4SimData;
+ PxVehicleWheels4DynData* PX_RESTRICT wheels4DynData=veh.mWheelsDynData.mWheels4DynData;
+ const PxU32 numWheels4=((veh.mWheelsSimData.mNbActiveWheels & ~3) >> 2);
+ const PxU32 numActiveWheels=veh.mWheelsSimData.mNbActiveWheels;
+ const PxU32 numActiveWheelsInLast4=numActiveWheels-4*numWheels4;
+ PxRigidDynamic* vehActor=veh.mActor;
+
+ //Set the results pointer and start the raycasts.
+ PX_ASSERT(numActiveWheelsInLast4<4);
+
+ //Blocks of 4 wheels.
+ for(PxU32 j=0;j<numWheels4;j++)
+ {
+ bool activeWheelStates[4]={false,false,false,false};
+ computeWheelActiveStates(4*j, veh.mWheelsSimData.mActiveWheelsBitmapBuffer, activeWheelStates);
+
+ wheels4DynData[j].mRaycastResults=NULL;
+ wheels4DynData[j].mSweepResults=NULL;
+
+ if(NULL==vehiclesToRaycast || vehiclesToRaycast[i])
+ {
+ if((sceneQueryResults + numSceneQueryResults) >= (sqres+4))
+ {
+ carFilterData[0].data=wheels4SimData[j].getSceneQueryFilterData(0);
+ carFilterData[1].data=wheels4SimData[j].getSceneQueryFilterData(1);
+ carFilterData[2].data=wheels4SimData[j].getSceneQueryFilterData(2);
+ carFilterData[3].data=wheels4SimData[j].getSceneQueryFilterData(3);
+ wheels4DynData[j].mRaycastResults=sqres;
+ PxVehicleWheels4SuspensionRaycasts(batchQuery,wheels4SimData[j],wheels4DynData[j],carFilterData,activeWheelStates,4,vehActor);
+ }
+ else
+ {
+ PX_CHECK_MSG(false, "PxVehicleUpdate::suspensionRaycasts - numSceneQueryResults not big enough to support one raycast hit report per wheel. Increase size of sceneQueryResults");
+ }
+ sqres+=4;
+ }
+ }
+ //Remainder that don't make up a block of 4.
+ if(numActiveWheelsInLast4>0)
+ {
+ const PxU32 j=numWheels4;
+
+ bool activeWheelStates[4]={false,false,false,false};
+ computeWheelActiveStates(4*j, veh.mWheelsSimData.mActiveWheelsBitmapBuffer, activeWheelStates);
+
+ wheels4DynData[j].mRaycastResults=NULL;
+ wheels4DynData[j].mSweepResults=NULL;
+
+ if(NULL==vehiclesToRaycast || vehiclesToRaycast[i])
+ {
+ if((sceneQueryResults + numSceneQueryResults) >= (sqres+numActiveWheelsInLast4))
+ {
+ if(0<numActiveWheelsInLast4) carFilterData[0].data=wheels4SimData[j].getSceneQueryFilterData(0);
+ if(1<numActiveWheelsInLast4) carFilterData[1].data=wheels4SimData[j].getSceneQueryFilterData(1);
+ if(2<numActiveWheelsInLast4) carFilterData[2].data=wheels4SimData[j].getSceneQueryFilterData(2);
+ wheels4DynData[j].mRaycastResults=sqres;
+ PxVehicleWheels4SuspensionRaycasts(batchQuery,wheels4SimData[j],wheels4DynData[j],carFilterData,activeWheelStates,numActiveWheelsInLast4,vehActor);
+ }
+ else
+ {
+ PX_CHECK_MSG(false, "PxVehicleUpdate::suspensionRaycasts - numSceneQueryResults not bit enough to support one raycast hit report per wheel. Increase size of sceneQueryResults");
+ }
+ sqres+=numActiveWheelsInLast4;
+ }
+ }
+ }
+
+ batchQuery->execute();
+
+ END_TIMER(TIMER_RAYCASTS);
+}
+
+void physx::PxVehicleSuspensionRaycasts(PxBatchQuery* batchQuery, const PxU32 numVehicles, PxVehicleWheels** vehicles, const PxU32 numSceneQueryesults, PxRaycastQueryResult* sceneQueryResults, const bool* vehiclesToRaycast)
+{
+ PX_PROFILE_ZONE("PxVehicleSuspensionRaycasts::ePROFILE_RAYCASTS",0);
+ PxVehicleUpdate::suspensionRaycasts(batchQuery, numVehicles, vehicles, numSceneQueryesults, sceneQueryResults, vehiclesToRaycast);
+}
+
+
+void PxVehicleWheels4SuspensionSweeps
+(PxBatchQuery* batchQuery,
+ const PxVehicleWheels4SimData& wheels4SimData, PxVehicleWheels4DynData& wheels4DynData,
+ const PxQueryFilterData* carFilterData, const bool* activeWheelStates, const PxU32 numActiveWheels,
+ const PxU16 nbHitsPerQuery,
+ const PxI32* wheelShapeIds,
+ PxRigidDynamic* vehActor,
+ const PxF32 sweepWidthScale, const PxF32 sweepRadiusScale)
+{
+ PX_UNUSED(sweepWidthScale);
+ PX_UNUSED(sweepRadiusScale);
+
+ //Get the transform of the chassis.
+ PxTransform carChassisTrnsfm=vehActor->getGlobalPose().transform(vehActor->getCMassLocalPose());
+
+ //Add a raycast for each wheel.
+ for(PxU32 j=0;j<numActiveWheels;j++)
+ {
+ const PxVehicleSuspensionData& susp = wheels4SimData.getSuspensionData(j);
+ const PxVehicleWheelData& wheel = wheels4SimData.getWheelData(j);
+
+ PxShape* wheelShape;
+ vehActor->getShapes(&wheelShape, 1, PxU32(wheelShapeIds[j]));
+
+ PxGeometryHolder suspGeometry;
+ if (PxGeometryType::eCONVEXMESH == wheelShape->getGeometryType())
+ {
+ PxConvexMeshGeometry convMeshGeom;
+ wheelShape->getConvexMeshGeometry(convMeshGeom);
+ convMeshGeom.scale.scale =
+ PxVec3(
+ gRight.x*sweepWidthScale + (gUp.x + gForward.x)*sweepRadiusScale,
+ gRight.y*sweepWidthScale + (gUp.y + gForward.y)*sweepRadiusScale,
+ gRight.z*sweepWidthScale + (gUp.z + gForward.z)*sweepRadiusScale);
+ suspGeometry.storeAny(convMeshGeom);
+ }
+ else if (PxGeometryType::eCAPSULE == wheelShape->getGeometryType())
+ {
+ PxCapsuleGeometry capsuleGeom;
+ wheelShape->getCapsuleGeometry(capsuleGeom);
+ capsuleGeom.halfHeight *= sweepWidthScale;
+ capsuleGeom.radius *= sweepRadiusScale;
+ suspGeometry.storeAny(capsuleGeom);
+ }
+ else
+ {
+ PX_ASSERT(PxGeometryType::eSPHERE == wheelShape->getGeometryType());
+ PxSphereGeometry sphereGeom;
+ wheelShape->getSphereGeometry(sphereGeom);
+ sphereGeom.radius *= sweepRadiusScale;
+ suspGeometry.storeAny(sphereGeom);
+ }
+
+ const PxQuat wheelLocalPoseRotation = wheelShape->getLocalPose().q;
+ const PxF32 wheelTheta = wheels4DynData.mWheelRotationAngles[j];
+
+ const PxVec3& bodySpaceSuspTravelDir = wheels4SimData.getSuspTravelDirection(j);
+ PxVec3 bodySpaceWheelCentreOffset = wheels4SimData.getWheelCentreOffset(j);
+ PxF32 maxDroop = susp.mMaxDroop;
+ PxF32 maxBounce = susp.mMaxCompression;
+ PxF32 radius = wheel.mRadius;
+ PX_ASSERT(maxBounce >= 0);
+ PX_ASSERT(maxDroop >= 0);
+
+ if(!activeWheelStates[j])
+ {
+ //For disabled wheels just issue a raycast of almost zero length.
+ //This should be very cheap and ought to hit nothing.
+ bodySpaceWheelCentreOffset = PxVec3(0,0,0);
+ maxDroop = 1e-5f*gToleranceScaleLength;
+ maxBounce = 1e-5f*gToleranceScaleLength;
+ radius = 1e-5f*gToleranceScaleLength;
+ }
+
+ PxTransform suspPoseStart;
+ PxVec3 suspLineDir;
+ computeSuspensionSweep(
+ carChassisTrnsfm,
+ wheelLocalPoseRotation, wheelTheta,
+ bodySpaceWheelCentreOffset, bodySpaceSuspTravelDir, radius, maxBounce,
+ suspPoseStart, suspLineDir);
+ const PxF32 suspLineLength = radius + maxBounce + maxDroop + radius;
+
+ //Store the susp line ray for later use.
+ PxVehicleWheels4DynData::SuspLineSweep& sweep =
+ reinterpret_cast<PxVehicleWheels4DynData::SuspLineSweep&>(wheels4DynData.mQueryOrCachedHitResults);
+ sweep.mStartPose[j] = suspPoseStart;
+ sweep.mDirs[j] = suspLineDir;
+ sweep.mLengths[j] = suspLineLength;
+ sweep.mGometries[j] = suspGeometry;
+
+ //Add the raycast to the scene query.
+ batchQuery->sweep(sweep.mGometries[j].any(),
+ suspPoseStart, suspLineDir, suspLineLength, nbHitsPerQuery,
+ PxHitFlag::ePOSITION|PxHitFlag::eNORMAL|PxHitFlag::eDISTANCE|PxHitFlag::eUV,
+ carFilterData[j]);
+ }
+}
+
+
+void PxVehicleUpdate::suspensionSweeps
+(PxBatchQuery* batchQuery,
+ const PxU32 numVehicles, PxVehicleWheels** vehicles,
+ const PxU32 numSceneQueryResults, PxSweepQueryResult* sceneQueryResults, const PxU16 nbHitsPerQuery,
+ const bool* vehiclesToSweep,
+ const PxF32 sweepWidthScale, const PxF32 sweepRadiusScale)
+{
+ PX_CHECK_MSG(sweepWidthScale > 0.0f, "PxVehicleUpdate::suspensionSweeps - sweepWidthScale must be greater than 0.0");
+ PX_CHECK_MSG(sweepRadiusScale > 0.0f, "PxVehicleUpdate::suspensionSweeps - sweepRadiusScale must be greater than 0.0");
+
+ START_TIMER(TIMER_SWEEPS);
+
+ //Reset all hit counts to zero.
+ for(PxU32 i=0;i<numSceneQueryResults;i++)
+ {
+ sceneQueryResults[i].hasBlock=false;
+ }
+
+ PxSweepQueryResult* sqres=sceneQueryResults;
+
+ const PxQueryFlags flags = PxQueryFlag::eSTATIC|PxQueryFlag::eDYNAMIC|PxQueryFlag::ePREFILTER|PxQueryFlag::ePOSTFILTER;
+ PxQueryFilterData carFilterData[4];
+ carFilterData[0].flags=flags;
+ carFilterData[1].flags=flags;
+ carFilterData[2].flags=flags;
+ carFilterData[3].flags=flags;
+
+ //Work out the rays for the suspension line raycasts and perform all the raycasts.
+ for(PxU32 i=0;i<numVehicles;i++)
+ {
+ //Get the current car.
+ PxVehicleWheels& veh=*vehicles[i];
+ const PxVehicleWheels4SimData* PX_RESTRICT wheels4SimData=veh.mWheelsSimData.mWheels4SimData;
+ PxVehicleWheels4DynData* PX_RESTRICT wheels4DynData=veh.mWheelsDynData.mWheels4DynData;
+ const PxU32 numWheels4=((veh.mWheelsSimData.mNbActiveWheels & ~3) >> 2);
+ const PxU32 numActiveWheels=veh.mWheelsSimData.mNbActiveWheels;
+ const PxU32 numActiveWheelsInLast4=numActiveWheels-4*numWheels4;
+ PxRigidDynamic* vehActor=veh.mActor;
+
+ //Set the results pointer and start the raycasts.
+ PX_ASSERT(numActiveWheelsInLast4<4);
+
+ //Get the shape ids for the wheels.
+ PxI32 wheelShapeIds[PX_MAX_NB_WHEELS];
+ PxMemSet(wheelShapeIds, 0xff, sizeof(PxI32)*PX_MAX_NB_WHEELS);
+ for(PxU32 j = 0; j < veh.mWheelsSimData.getNbWheels(); j++)
+ {
+ PX_CHECK_AND_RETURN(veh.mWheelsSimData.getWheelShapeMapping(j) != -1, "PxVehicleUpdate::suspensionSweeps - trying to sweep a shape that doesn't exist.");
+ wheelShapeIds[j] = veh.mWheelsSimData.getWheelShapeMapping(j);
+ }
+
+ //Blocks of 4 wheels.
+ for(PxU32 j=0;j<numWheels4;j++)
+ {
+ bool activeWheelStates[4]={false,false,false,false};
+ computeWheelActiveStates(4*j, veh.mWheelsSimData.mActiveWheelsBitmapBuffer, activeWheelStates);
+
+ const PxI32* wheelShapeIds4 = wheelShapeIds + 4*j;
+
+ wheels4DynData[j].mRaycastResults=NULL;
+ wheels4DynData[j].mSweepResults=NULL;
+
+ if(NULL==vehiclesToSweep || vehiclesToSweep[i])
+ {
+ if((sceneQueryResults + numSceneQueryResults) >= (sqres+4))
+ {
+ carFilterData[0].data=wheels4SimData[j].getSceneQueryFilterData(0);
+ carFilterData[1].data=wheels4SimData[j].getSceneQueryFilterData(1);
+ carFilterData[2].data=wheels4SimData[j].getSceneQueryFilterData(2);
+ carFilterData[3].data=wheels4SimData[j].getSceneQueryFilterData(3);
+ wheels4DynData[j].mSweepResults=sqres;
+ PxVehicleWheels4SuspensionSweeps(
+ batchQuery,
+ wheels4SimData[j], wheels4DynData[j],
+ carFilterData, activeWheelStates, 4,
+ nbHitsPerQuery,
+ wheelShapeIds4,
+ vehActor,
+ sweepWidthScale, sweepRadiusScale);
+ }
+ else
+ {
+ PX_CHECK_MSG(false, "PxVehicleUpdate::suspensionRaycasts - numSceneQueryResults not big enough to support one raycast hit report per wheel. Increase size of sceneQueryResults");
+ }
+ sqres+=4;
+ }
+ }
+ //Remainder that don't make up a block of 4.
+ if(numActiveWheelsInLast4>0)
+ {
+ const PxU32 j=numWheels4;
+
+ bool activeWheelStates[4]={false,false,false,false};
+ computeWheelActiveStates(4*j, veh.mWheelsSimData.mActiveWheelsBitmapBuffer, activeWheelStates);
+
+ const PxI32* wheelShapeIds4 = wheelShapeIds + 4*j;
+
+ wheels4DynData[j].mRaycastResults=NULL;
+ wheels4DynData[j].mSweepResults=NULL;
+
+ if(NULL==vehiclesToSweep || vehiclesToSweep[i])
+ {
+ if((sceneQueryResults + numSceneQueryResults) >= (sqres+numActiveWheelsInLast4))
+ {
+ if(0<numActiveWheelsInLast4) carFilterData[0].data=wheels4SimData[j].getSceneQueryFilterData(0);
+ if(1<numActiveWheelsInLast4) carFilterData[1].data=wheels4SimData[j].getSceneQueryFilterData(1);
+ if(2<numActiveWheelsInLast4) carFilterData[2].data=wheels4SimData[j].getSceneQueryFilterData(2);
+ wheels4DynData[j].mRaycastResults=NULL;
+ wheels4DynData[j].mSweepResults=sqres;
+ PxVehicleWheels4SuspensionSweeps(
+ batchQuery,
+ wheels4SimData[j], wheels4DynData[j],
+ carFilterData, activeWheelStates, numActiveWheelsInLast4,
+ nbHitsPerQuery,
+ wheelShapeIds4,
+ vehActor,
+ sweepWidthScale, sweepRadiusScale);
+ }
+ else
+ {
+ PX_CHECK_MSG(false, "PxVehicleUpdate::suspensionSweeps - numSceneQueryResults not bit enough to support one sweep hit report per wheel. Increase size of sceneQueryResults");
+ }
+ sqres+=numActiveWheelsInLast4;
+ }
+ }
+ }
+
+ batchQuery->execute();
+
+ END_TIMER(TIMER_SWEEPS);
+}
+
+namespace physx
+{
+ void PxVehicleSuspensionSweeps
+ (PxBatchQuery* batchQuery,
+ const PxU32 nbVehicles, PxVehicleWheels** vehicles,
+ const PxU32 nbSceneQueryResults, PxSweepQueryResult* sceneQueryResults, const PxU16 nbHitsPerQuery,
+ const bool* vehiclesToSweep,
+ const PxF32 sweepWidthScale, const PxF32 sweepRadiusScale)
+ {
+ PX_PROFILE_ZONE("PxVehicleSuspensionSweeps::ePROFILE_SWEEPS",0);
+ PxVehicleUpdate::suspensionSweeps(
+ batchQuery, nbVehicles, vehicles, nbSceneQueryResults, sceneQueryResults, nbHitsPerQuery, vehiclesToSweep, sweepWidthScale, sweepRadiusScale);
+ }
+}
+
+