7712 lines
311 KiB
C++
7712 lines
311 KiB
C++
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions
|
|
// are met:
|
|
// * Redistributions of source code must retain the above copyright
|
|
// notice, this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above copyright
|
|
// notice, this list of conditions and the following disclaimer in the
|
|
// documentation and/or other materials provided with the distribution.
|
|
// * Neither the name of NVIDIA CORPORATION nor the names of its
|
|
// contributors may be used to endorse or promote products derived
|
|
// from this software without specific prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
|
|
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
|
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
//
|
|
// Copyright (c) 2008-2019 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/PxQuat.h"
|
|
#include "common/PxProfileZone.h"
|
|
#include "common/PxTolerancesScale.h"
|
|
#include "vehicle/PxVehicleUpdate.h"
|
|
#include "vehicle/PxVehicleDrive4W.h"
|
|
#include "vehicle/PxVehicleDriveNW.h"
|
|
#include "vehicle/PxVehicleDriveTank.h"
|
|
#include "vehicle/PxVehicleNoDrive.h"
|
|
#include "vehicle/PxVehicleUtil.h"
|
|
#include "vehicle/PxVehicleUtilTelemetry.h"
|
|
#include "extensions/PxRigidBodyExt.h"
|
|
#include "PxShape.h"
|
|
#include "PxRigidDynamic.h"
|
|
#include "PxBatchQuery.h"
|
|
#include "PxMaterial.h"
|
|
#include "PxContactModifyCallback.h"
|
|
|
|
#include "PxVehicleSuspLimitConstraintShader.h"
|
|
#include "PxVehicleSuspWheelTire4.h"
|
|
#include "PxVehicleDefaults.h"
|
|
#include "PxVehicleLinearMath.h"
|
|
|
|
#include "PsFoundation.h"
|
|
#include "PsUtilities.h"
|
|
#include "PsFPU.h"
|
|
#include "CmBitMap.h"
|
|
#include "CmUtils.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)
|
|
{
|
|
PX_UNUSED(width);
|
|
|
|
//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;
|
|
|
|
//Remove this code because we handle tire width with sweeps now.
|
|
//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 = PxVec3(hitPlane.x, hitPlane.y, hitPlane.z);
|
|
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;
|
|
|
|
//Flags related to wheel simulation (see PxVehicleWheelsSimFlags)
|
|
PxU32 wheelsSimFlags;
|
|
};
|
|
|
|
//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;
|
|
const PxU32 wheelsSimFlags = constData.wheelsSimFlags;
|
|
|
|
//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;
|
|
|
|
//Set the cmass rotation straight away (we might need this, we might not but we don't know that yet so just set it).
|
|
outputData.vehConstraintData.mCMassRotation = constData.vehActor->getCMassLocalPose().q;
|
|
|
|
//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];
|
|
|
|
//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] = (w.dot(hitNorm))*(-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.
|
|
PxF32 jounceSpeed;
|
|
PxF32 previousJounce;
|
|
if (PX_MAX_F32 != prevJounces[i])
|
|
{
|
|
jounceSpeed = (jounce - prevJounces[i])*recipTimeStep;
|
|
previousJounce = prevJounces[i];
|
|
}
|
|
else
|
|
{
|
|
jounceSpeed = 0.0f;
|
|
previousJounce = jounce;
|
|
}
|
|
|
|
const PxF32 gravitySuspDir = gravity.dot(w);
|
|
bool computeSuspensionForce = true;
|
|
if ((wheelsSimFlags & PxVehicleWheelsSimFlag::eLIMIT_SUSPENSION_EXPANSION_VELOCITY) && (jounceSpeed < 0.0f) && (jounce <= 0.0f))
|
|
{
|
|
//Suspension is expanding and not compressed (the latter helps to avoid the suspension not being able to carry the sprung mass
|
|
//when many substeps are used because the expected velocity from the suspension spring might always be slightly below the velocity
|
|
//needed to reach the new jounce).
|
|
|
|
//Check if the suspension can expand fast enough to keep pushing the wheel to the ground.
|
|
|
|
const PxF32 distToMaxDroop = previousJounce + susp.mMaxDroop; // signs chosen to point along the suspension travel direction
|
|
//The vehicle is considered to be in air until the suspension expands to the ground, thus the max droop is used
|
|
//as the rest length of the spring.
|
|
|
|
//Without the suspension elongating, the wheel would end up in the air. Compute the force that pushes the
|
|
//wheel towards the ground. Note that gravity is ignored here as it applies to chassis and wheel equally.
|
|
//Furthermore, the suspension start point (sprung mass) and the wheel are assumed to move with roughly the
|
|
//same velocity, hence, damping is ignored too.
|
|
const PxF32 springForceAlongSuspDir = distToMaxDroop * susp.mSpringStrength;
|
|
|
|
const PxF32 suspDirVelWheel = ((springForceAlongSuspDir / wheel.mMass) + gravitySuspDir) * timeStep;
|
|
|
|
if (jounceSpeed < (-suspDirVelWheel))
|
|
{
|
|
//The suspension can not push the wheel fast enough onto the ground, so the vehicle will leave the ground.
|
|
//Hence, no spring forces should get applied.
|
|
//note: could consider applying -springForceAlongSuspDir to the chassis but this is not done in the other
|
|
// scenarios where the vehicle is in the air either.
|
|
|
|
computeSuspensionForce = false;
|
|
jounce = PxMin(previousJounce - (suspDirVelWheel * timeStep), susp.mMaxCompression);
|
|
}
|
|
}
|
|
|
|
if (computeSuspensionForce)
|
|
{
|
|
//We know that the vehicle is not in the air.
|
|
isInAirs[i]=false;
|
|
|
|
//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, gravitySuspDir);
|
|
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 += jounceSpeed * susp.mSpringDamperRate; //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(computeSuspensionForce)
|
|
}//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();
|
|
carChassisCMLocalPose.q = PxQuat(PxIdentity);
|
|
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, vehDrive4W->mWheelsSimData.mFlags};
|
|
|
|
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();
|
|
carChassisCMLocalPose.q = PxQuat(PxIdentity);
|
|
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, vehDriveNW->mWheelsSimData.mFlags};
|
|
|
|
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();
|
|
carChassisCMLocalPose.q = PxQuat(PxIdentity);
|
|
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 = 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, vehDriveTank->mWheelsSimData.mFlags};
|
|
|
|
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();
|
|
carChassisCMLocalPose.q = PxQuat(PxIdentity);
|
|
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, vehNoDrive->mWheelsSimData.mFlags};
|
|
|
|
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 massXform = vehActor->getCMassLocalPose();
|
|
massXform.q = PxQuat(PxIdentity);
|
|
PxTransform carChassisTrnsfm = vehActor->getGlobalPose().transform(massXform);
|
|
|
|
//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::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 big 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(
|
|
PxAbs(gRight.x*sweepWidthScale + (gUp.x + gForward.x)*sweepRadiusScale),
|
|
PxAbs(gRight.y*sweepWidthScale + (gUp.y + gForward.y)*sweepRadiusScale),
|
|
PxAbs(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::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 big 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);
|
|
}
|
|
}
|
|
|
|
|