feat(physics): wire physx sdk into build

This commit is contained in:
2026-04-15 12:22:15 +08:00
parent 5bf258df6d
commit 31f40e2cbb
2044 changed files with 752623 additions and 1 deletions

View File

@@ -0,0 +1,160 @@
// 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-2025 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 "vehicle2/commands/PxVehicleCommandParams.h"
#include "vehicle2/commands/PxVehicleCommandHelpers.h"
namespace physx
{
namespace vehicle2
{
static float interpolate(const PxReal* speedVals, const PxReal* responseVals, const PxU16 nb, const PxReal speed)
{
if (1 == nb)
{
return responseVals[0];
}
else
{
const PxReal smallestSpeed = speedVals[0];
const PxReal largestSpeed = speedVals[nb - 1];
if (smallestSpeed >= speed)
{
return responseVals[0];
}
else if (largestSpeed <= speed)
{
return responseVals[nb - 1];
}
else
{
PxU16 speedId = 0;
while ((speedVals[speedId] < speed) && (speedId < nb))
speedId++;
// Make sure that we stay in range.
PxU16 speedLowerId = speedId - 1;
PxU16 speeddUpperId = speedId;
if (nb == speedId)
speeddUpperId = nb - 1;
if (0 == speedId)
speedLowerId = 0;
return responseVals[speedLowerId] + (speed - speedVals[speedLowerId]) * (responseVals[speeddUpperId] - responseVals[speedLowerId]) / (speedVals[speeddUpperId] - speedVals[speedLowerId]);
}
}
}
PxReal PxVehicleNonLinearResponseCompute
(const PxReal commandValue, const PxReal speed, const PxU32 wheelId, const PxVehicleCommandResponseParams& responseParams)
{
const PxU16 nbResponsesAtSpeeds = responseParams.nonlinearResponse.nbSpeedResponses;
if (0 == nbResponsesAtSpeeds)
{
//Empty response table.
//Use linear interpolation.
return PxVehicleLinearResponseCompute(commandValue, wheelId, responseParams);
}
const PxReal* commandValues = responseParams.nonlinearResponse.commandValues;
const PxU16* speedResponsesPerCommandValue = responseParams.nonlinearResponse.speedResponsesPerCommandValue;
const PxU16* nbSpeedResponsesPerCommandValue = responseParams.nonlinearResponse.nbSpeedResponsesPerCommandValue;
const PxU16 nbCommandValues = responseParams.nonlinearResponse.nbCommandValues;
const PxReal* speedResponses = responseParams.nonlinearResponse.speedResponses;
PxReal normalisedResponse = 0.0f;
if ((1 == nbCommandValues) || (commandValues[0] >= commandValue))
{
//Input command value less than the smallest value in the response table or
//there is just a single command value in the response table.
//No need to interpolate response of two command values.
const PxReal* speeds = speedResponses + 2*speedResponsesPerCommandValue[0];
const PxReal* responseValues = speeds + nbSpeedResponsesPerCommandValue[0];
const PxU16 nb = nbSpeedResponsesPerCommandValue[0];
normalisedResponse = interpolate(speeds, responseValues, nb, speed);
}
else if (commandValues[nbCommandValues - 1] <= commandValue)
{
//Input command value greater than the largest value in the response table.
//No need to interpolate response of two command values.
const PxReal* speeds = speedResponses + 2*speedResponsesPerCommandValue[nbCommandValues - 1];
const PxReal* responseValues = speeds + nbSpeedResponsesPerCommandValue[nbCommandValues - 1];
const PxU16 nb = nbSpeedResponsesPerCommandValue[nbCommandValues - 1];
normalisedResponse = interpolate(speeds, responseValues, nb, speed);
}
else
{
// Find the id of the command value that is immediately above the input command
PxU16 commandId = 0;
while ((commandValues[commandId] < commandValue) && (commandId < nbCommandValues))
{
commandId++;
}
// Make sure that we stay in range.
PxU16 commandLowerId = commandId - 1;
PxU16 commandUpperId = commandId;
if (nbCommandValues == commandId)
commandUpperId = nbCommandValues - 1;
if (0 == commandId)
commandLowerId = 0;
if (commandUpperId != commandLowerId)
{
float zLower;
{
const PxReal* speeds = speedResponses + 2*speedResponsesPerCommandValue[commandLowerId];
const PxReal* responseValues = speeds + nbSpeedResponsesPerCommandValue[commandLowerId];
const PxU16 nb = nbSpeedResponsesPerCommandValue[commandLowerId];
zLower = interpolate(speeds, responseValues, nb, speed);
}
float zUpper;
{
const PxReal* speeds = speedResponses + 2*speedResponsesPerCommandValue[commandUpperId];
const PxReal* responseValues = speeds + nbSpeedResponsesPerCommandValue[commandUpperId];
const PxU16 nb = nbSpeedResponsesPerCommandValue[commandUpperId];
zUpper = interpolate(speeds, responseValues, nb, speed);
}
const PxReal commandUpper = commandValues[commandUpperId];
const PxReal commandLower = commandValues[commandLowerId];
normalisedResponse = zLower + (commandValue - commandLower) * (zUpper - zLower) / (commandUpper - commandLower);
}
else
{
const PxReal* speeds = speedResponses + 2*speedResponsesPerCommandValue[commandUpperId];
const PxReal* responseValues = speeds + nbSpeedResponsesPerCommandValue[commandUpperId];
const PxU16 nb = nbSpeedResponsesPerCommandValue[commandUpperId];
normalisedResponse = interpolate(speeds, responseValues, nb, speed);
}
}
return PxVehicleLinearResponseCompute(normalisedResponse, wheelId, responseParams);
}
} // namespace vehicle2
} // namespace physx

View File

@@ -0,0 +1,976 @@
// 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-2025 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/PxMemory.h"
#include "vehicle2/PxVehicleParams.h"
#include "vehicle2/PxVehicleFunctions.h"
#include "vehicle2/PxVehicleMaths.h"
#include "vehicle2/commands/PxVehicleCommandHelpers.h"
#include "vehicle2/drivetrain/PxVehicleDrivetrainFunctions.h"
#include "vehicle2/drivetrain/PxVehicleDrivetrainParams.h"
#include "vehicle2/drivetrain/PxVehicleDrivetrainStates.h"
#include "vehicle2/drivetrain/PxVehicleDrivetrainHelpers.h"
#include "vehicle2/tire/PxVehicleTireStates.h"
#include "vehicle2/wheel/PxVehicleWheelParams.h"
#include "vehicle2/wheel/PxVehicleWheelStates.h"
namespace physx
{
namespace vehicle2
{
void PxVehicleDirectDriveThrottleCommandResponseUpdate
(const PxReal throttle, const PxVehicleDirectDriveTransmissionCommandState& transmissionCommands, const PxReal longitudinalSpeed,
const PxU32 wheelId, const PxVehicleDirectDriveThrottleCommandResponseParams& responseParams,
PxReal& throttleResponse)
{
//The gearing decides how we will multiply the response.
PxF32 gearMultiplier = 0.0f;
switch (transmissionCommands.gear)
{
case PxVehicleDirectDriveTransmissionCommandState::eREVERSE:
gearMultiplier = -1.0f;
break;
case PxVehicleDirectDriveTransmissionCommandState::eNEUTRAL:
gearMultiplier = 0.0f;
break;
case PxVehicleDirectDriveTransmissionCommandState::eFORWARD:
gearMultiplier = 1.0f;
break;
}
throttleResponse = gearMultiplier * PxVehicleNonLinearResponseCompute(throttle, longitudinalSpeed, wheelId, responseParams);
}
void PxVehicleDirectDriveActuationStateUpdate
(const PxReal brakeTorque, const PxReal driveTorque,
PxVehicleWheelActuationState& actState)
{
PxMemZero(&actState, sizeof(PxVehicleWheelActuationState));
actState.isBrakeApplied = (brakeTorque != 0.0f);
actState.isDriveApplied = (driveTorque != 0.0f);
}
void PxVehicleDirectDriveUpdate
(const PxVehicleWheelParams& whlParams,
const PxVehicleWheelActuationState& actState,
const PxReal brkTorque, const PxReal drvTorque, const PxVehicleTireForce& trForce,
const PxF32 dt,
PxVehicleWheelRigidBody1dState& whlState)
{
//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)
const PxF32 wheelRotSpeed = whlState.rotationSpeed;
const PxF32 dtOverMOI = dt/whlParams.moi;
const PxF32 tireTorque = trForce.wheelTorque;
const PxF32 brakeTorque = -brkTorque*PxVehicleComputeSign(wheelRotSpeed);
const PxF32 driveTorque = drvTorque;
const PxF32 wheelDampingRate = whlParams.dampingRate;
//Integrate the wheel rotation speed.
const PxF32 newRotSpeedNoBrakelock = (wheelRotSpeed + dtOverMOI*(tireTorque + driveTorque + brakeTorque)) / (1.0f + wheelDampingRate*dtOverMOI);
//If the brake is applied and the sign flipped then lock the brake.
const bool isBrakeApplied = actState.isBrakeApplied;
const PxF32 newRotSpeedWithBrakelock = (isBrakeApplied && ((wheelRotSpeed*newRotSpeedNoBrakelock) <= 0)) ? 0.0f : newRotSpeedNoBrakelock;
whlState.rotationSpeed = newRotSpeedWithBrakelock;
}
void PxVehicleDifferentialStateUpdate
(const PxVehicleFourWheelDriveDifferentialLegacyParams& diffParams,
const PxVehicleArrayData<const PxVehicleWheelRigidBody1dState>& wheelStates,
PxVehicleDifferentialState& diffState)
{
diffState.setToDefault();
//4 wheels are connected.
diffState.connectedWheels[0] = diffParams.frontWheelIds[0];
diffState.connectedWheels[1] = diffParams.frontWheelIds[1];
diffState.connectedWheels[2] = diffParams.rearWheelIds[0];
diffState.connectedWheels[3] = diffParams.rearWheelIds[1];
diffState.nbConnectedWheels = 4;
//Compute the contributions to average speed and ratios of available torque.
PxVehicleLegacyDifferentialWheelSpeedContributionsCompute(diffParams, PxVehicleLimits::eMAX_NB_WHEELS,
diffState.aveWheelSpeedContributionAllWheels);
PxVehicleLegacyDifferentialTorqueRatiosCompute(diffParams, wheelStates, PxVehicleLimits::eMAX_NB_WHEELS,
diffState.torqueRatiosAllWheels);
}
PX_FORCE_INLINE PxF32 computeTargetRatio
(const PxF32 target, const PxF32 dt, const PxF32 strength, const PxF32 ratio)
{
const PxF32 targ = (PX_VEHICLE_FOUR_WHEEL_DIFFERENTIAL_MAXIMUM_STRENGTH == strength) ? target : PxMax(target, ratio - strength * dt);
return targ;
}
void PxVehicleDifferentialStateUpdate
(const PxVehicleAxleDescription& axleDescription, const PxVehicleFourWheelDriveDifferentialParams& diffParams,
const PxVehicleArrayData<const PxVehicleWheelRigidBody1dState>& wheelStates,
const PxReal dt,
PxVehicleDifferentialState& diffState, PxVehicleWheelConstraintGroupState& wheelConstraintGroupState)
{
diffState.setToDefault();
wheelConstraintGroupState.setToDefault();
PxU32 nbConnectedWheels = 0;
for (PxU32 i = 0; i < axleDescription.nbWheels; i++)
{
const PxU32 wheelId = axleDescription.wheelIdsInAxleOrder[i];
if (diffParams.torqueRatios[wheelId] != 0.0f)
{
diffState.connectedWheels[nbConnectedWheels] = wheelId;
diffState.aveWheelSpeedContributionAllWheels[wheelId] = diffParams.aveWheelSpeedRatios[wheelId];
diffState.torqueRatiosAllWheels[wheelId] = diffParams.aveWheelSpeedRatios[wheelId];
nbConnectedWheels++;
}
}
diffState.nbConnectedWheels = nbConnectedWheels;
if (0.0f == diffParams.frontBias && 0.0f == diffParams.rearBias && 0.0f == diffParams.centerBias)
return;
if (0.0f == diffParams.rate)
return;
bool frontBiasBreached = false;
PxF32 Rf = 0.0f;
PxF32 Tf = 0.0f;
{
const PxF32 bias = diffParams.frontBias;
if (bias >= 1.0f)
{
const PxU32 wheel0 = diffParams.frontWheelIds[0];
const PxU32 wheel1 = diffParams.frontWheelIds[1];
const PxReal w0 = wheelStates[wheel0].rotationSpeed;
const PxReal w1 = wheelStates[wheel1].rotationSpeed;
const PxReal aw0 = PxAbs(w0);
const PxReal aw1 = PxAbs(w1);
const PxReal sw0 = PxVehicleComputeSign(w0);
const PxReal sw1 = PxVehicleComputeSign(w1);
if ((w0 != 0.0f) && (sw0 == sw1))
{
const PxF32 ratio = PxMax(aw0, aw1) / PxMin(aw0, aw1);
frontBiasBreached = (ratio > bias);
//const PxF32 target = diffParams.frontTarget + (1.0f - diffParams.strength) * (ratio - diffParams.frontTarget);
const PxF32 target = computeTargetRatio(diffParams.frontTarget, dt, diffParams.rate, ratio);
Tf = (aw0 > aw1) ? 1.0f / target : target;
Rf = aw1 / aw0;
}
}
}
bool rearBiasBreached = false;
PxF32 Rr = 0.0f;
PxF32 Tr = 0.0f;
{
const PxF32 bias = diffParams.rearBias;
if (bias >= 1.0f)
{
const PxU32 wheel2 = diffParams.rearWheelIds[0];
const PxU32 wheel3 = diffParams.rearWheelIds[1];
const PxReal w2 = wheelStates[wheel2].rotationSpeed;
const PxReal w3 = wheelStates[wheel3].rotationSpeed;
const PxReal aw2 = PxAbs(w2);
const PxReal aw3 = PxAbs(w3);
const PxReal sw2 = PxVehicleComputeSign(w2);
const PxReal sw3 = PxVehicleComputeSign(w3);
if ((w2 != 0.0f) && (sw2 == sw3))
{
const PxF32 ratio = PxMax(aw2, aw3) / PxMin(aw2, aw3);
rearBiasBreached = (ratio > bias);
//const PxF32 target = diffParams.rearTarget + (1.0f - diffParams.strength) * (ratio - diffParams.rearTarget);
const PxF32 target = computeTargetRatio(diffParams.rearTarget, dt, diffParams.rate, ratio);
Tr = (aw2 > aw3) ? 1.0f / target : target;
Rr = aw3 / aw2;
}
}
}
bool centreBiasBrached = false;
//PxF32 Rc = 0.0f;
PxF32 Tc = 0.0f;
{
const PxF32 bias = diffParams.centerBias;
if(bias >= 1.0f)
{
const PxU32 wheel0 = diffParams.frontWheelIds[0];
const PxU32 wheel1 = diffParams.frontWheelIds[1];
const PxU32 wheel2 = diffParams.rearWheelIds[0];
const PxU32 wheel3 = diffParams.rearWheelIds[1];
const PxReal w0 = wheelStates[wheel0].rotationSpeed;
const PxReal w1 = wheelStates[wheel1].rotationSpeed;
const PxReal w2 = wheelStates[wheel2].rotationSpeed;
const PxReal w3 = wheelStates[wheel3].rotationSpeed;
const PxReal aw0 = PxAbs(w0);
const PxReal aw1 = PxAbs(w1);
const PxReal aw2 = PxAbs(w2);
const PxReal aw3 = PxAbs(w3);
const PxReal sw0 = PxVehicleComputeSign(w0);
const PxReal sw1 = PxVehicleComputeSign(w1);
const PxReal sw2 = PxVehicleComputeSign(w2);
const PxReal sw3 = PxVehicleComputeSign(w3);
if ((w0 != 0.0f) && (sw0 == sw1) && (sw0 == sw2) && (sw0 == sw3))
{
const PxF32 ratio = PxMax(aw0 + aw1, aw2 + aw3) / PxMin(aw0 + aw1, aw2 + aw3);
centreBiasBrached = (ratio > bias);
//const PxF32 target = diffParams.centerTarget + (1.0f - diffParams.strength) * (ratio - diffParams.centerTarget);
const PxF32 target = computeTargetRatio(diffParams.centerTarget, dt, diffParams.rate, ratio);
Tc = ((aw0 + aw1) > (aw2 + aw3)) ? 1.0f / target : target;
//Rc = (aw2 + aw3) / (aw0 + aw1);
}
}
}
if (centreBiasBrached)
{
PxF32 f = 0.0f;
PxF32 r = 0.0f;
if (frontBiasBreached && rearBiasBreached)
{
f = Tf;
r = Tr;
}
else if (frontBiasBreached)
{
f = Tf;
r = Rr;
}
else if (rearBiasBreached)
{
f = Rf;
r = Tr;
}
else
{
f = Rf;
r = Rr;
}
const PxU32 wheel0 = diffParams.frontWheelIds[0];
const PxU32 wheel1 = diffParams.frontWheelIds[1];
const PxU32 wheel2 = diffParams.rearWheelIds[0];
const PxU32 wheel3 = diffParams.rearWheelIds[1];
const PxU32 wheelIds[4] = { wheel0, wheel1, wheel2, wheel3 };
const PxF32 constraintMultipliers[4] = { 1.0f, f, Tc*(1 + f) / (1 + r), r*Tc*(1 + f) / (1 + r) };
wheelConstraintGroupState.addConstraintGroup(4, wheelIds, constraintMultipliers);
}
else
{
if (frontBiasBreached)
{
const PxU32* wheelIds = diffParams.frontWheelIds;
const PxF32 constraintMultipliers[2] = { 1.0f, Tf };
wheelConstraintGroupState.addConstraintGroup(2, wheelIds, constraintMultipliers);
}
if (rearBiasBreached)
{
const PxU32* wheelIds = diffParams.rearWheelIds;
const PxF32 constraintMultipliers[2] = { 1.0f, Tr };
wheelConstraintGroupState.addConstraintGroup(2, wheelIds, constraintMultipliers);
}
}
}
void PxVehicleDifferentialStateUpdate
(const PxVehicleAxleDescription& axleDescription, const PxVehicleMultiWheelDriveDifferentialParams& diffParams,
PxVehicleDifferentialState& diffState)
{
diffState.setToDefault();
PxU32 nbConnectedWheels = 0;
for (PxU32 i = 0; i < axleDescription.nbWheels; i++)
{
const PxU32 wheelId = axleDescription.wheelIdsInAxleOrder[i];
if (diffParams.torqueRatios[wheelId] != 0.0f)
{
diffState.connectedWheels[nbConnectedWheels] = wheelId;
diffState.aveWheelSpeedContributionAllWheels[wheelId] = diffParams.aveWheelSpeedRatios[wheelId];
diffState.torqueRatiosAllWheels[wheelId] = diffParams.aveWheelSpeedRatios[wheelId];
nbConnectedWheels++;
}
}
diffState.nbConnectedWheels = nbConnectedWheels;
}
void PxVehicleDifferentialStateUpdate
(const PxVehicleAxleDescription& axleDescription,
const PxVehicleArrayData<const PxVehicleWheelParams>& wheelParams, const PxVehicleTankDriveDifferentialParams& diffParams,
const PxReal thrust0, PxReal thrust1,
PxVehicleDifferentialState& diffState, PxVehicleWheelConstraintGroupState& wheelConstraintGroupState)
{
diffState.setToDefault();
wheelConstraintGroupState.setToDefault();
//Store the tank track id for each wheel.
//Store the thrust controller id for each wheel.
//Store 0xffffffff for wheels not in a tank track.
PxU32 tankTrackIds[PxVehicleLimits::eMAX_NB_WHEELS];
PxMemSet(tankTrackIds, 0xff, sizeof(tankTrackIds));
PxU32 thrustControllerIds[PxVehicleLimits::eMAX_NB_WHEELS];
PxMemSet(thrustControllerIds, 0xff, sizeof(thrustControllerIds));
for (PxU32 i = 0; i < diffParams.getNbTracks(); i++)
{
const PxU32 thrustControllerIndex = diffParams.getThrustControllerIndex(i);
for (PxU32 j = 0; j < diffParams.getNbWheelsInTrack(i); j++)
{
const PxU32 wheelId = diffParams.getWheelInTrack(j, i);
tankTrackIds[wheelId] = i;
thrustControllerIds[wheelId] = thrustControllerIndex;
}
}
//Treat every wheel connected to a tank track as connected to the differential but with zero torque split.
//If a wheel is not connected to the differential but is connected to a tank track then we treat that as a connected wheel with
//zero drive torque applied.
//We do this because we need to treat these wheels as being coupled to other wheels in the linear equations that solve engine+wheels
PxU32 nbConnectedWheels = 0;
const PxReal thrusts[2] = { thrust0, thrust1 };
for (PxU32 i = 0; i < axleDescription.nbWheels; i++)
{
const PxU32 wheelId = axleDescription.wheelIdsInAxleOrder[i];
const PxU32 tankTrackId = tankTrackIds[wheelId];
if (diffParams.torqueRatios[wheelId] != 0.0f && 0xffffffff != tankTrackId)
{
//Wheel connected to diff and in a tank track.
//Modify the default torque split using the relevant thrust.
const PxU32 thrustId = thrustControllerIds[wheelId];
const PxF32 torqueSplitMultiplier = thrusts[thrustId];
diffState.aveWheelSpeedContributionAllWheels[wheelId] = diffParams.aveWheelSpeedRatios[wheelId];
diffState.torqueRatiosAllWheels[wheelId] = diffParams.torqueRatios[wheelId] * torqueSplitMultiplier;
diffState.connectedWheels[nbConnectedWheels] = wheelId;
nbConnectedWheels++;
}
else if (diffParams.torqueRatios[wheelId] != 0.0f)
{
//Wheel connected to diff but not in a tank track.
//Use the default torque split.
diffState.aveWheelSpeedContributionAllWheels[wheelId] = diffParams.aveWheelSpeedRatios[wheelId];
diffState.torqueRatiosAllWheels[wheelId] = diffParams.torqueRatios[wheelId];
diffState.connectedWheels[nbConnectedWheels] = wheelId;
nbConnectedWheels++;
}
else if (0xffffffff != tankTrackId)
{
//Wheel not connected to diff but is in a tank track.
//Zero torque split.
diffState.aveWheelSpeedContributionAllWheels[wheelId] = 0.0f;
diffState.torqueRatiosAllWheels[wheelId] = 0.0f;
diffState.connectedWheels[nbConnectedWheels] = wheelId;
nbConnectedWheels++;
}
}
diffState.nbConnectedWheels = nbConnectedWheels;
//Add each tank track as a constraint group.
for (PxU32 i = 0; i < diffParams.getNbTracks(); i++)
{
const PxU32 nbWheelsInTrack = diffParams.getNbWheelsInTrack(i);
if (nbWheelsInTrack >= 2)
{
const PxU32* wheelsInTrack = diffParams.wheelIdsInTrackOrder + diffParams.trackToWheelIds[i];
PxF32 multipliers[PxVehicleLimits::eMAX_NB_WHEELS];
const PxU32 wheelId0 = wheelsInTrack[0];
const PxF32 wheelRadius0 = wheelParams[wheelId0].radius;
//for (PxU32 j = 0; j < 1; j++)
{
//j = 0 is a special case with multiplier = 1.0
multipliers[0] = 1.0f;
}
for (PxU32 j = 1; j < nbWheelsInTrack; j++)
{
const PxU32 wheelIdJ = wheelsInTrack[j];
const PxF32 wheelRadiusJ = wheelParams[wheelIdJ].radius;
multipliers[j] = wheelRadius0 / wheelRadiusJ;
}
wheelConstraintGroupState.addConstraintGroup(nbWheelsInTrack, wheelsInTrack, multipliers);
}
}
}
void PxVehicleEngineDriveActuationStateUpdate
(const PxVehicleAxleDescription& axleDescription,
const PxVehicleGearboxParams& gearboxParams,
const PxVehicleArrayData<const PxReal>& brakeResponseStates,
const PxVehicleEngineDriveThrottleCommandResponseState& throttleResponseState,
const PxVehicleGearboxState& gearboxState, const PxVehicleDifferentialState& diffState, const PxVehicleClutchCommandResponseState& clutchResponseState,
PxVehicleArrayData<PxVehicleWheelActuationState>& actuationStates)
{
//Which wheels receive drive torque from the engine?
const PxF32* diffTorqueRatios = diffState.torqueRatiosAllWheels;
//Work out the clutch strength.
//If the cutch strength is zero then none of the wheels receive drive torque from the engine.
const PxF32 K = PxVehicleClutchStrengthCompute(clutchResponseState, gearboxParams, gearboxState);
//Work out the applied throttle
const PxF32 appliedThrottle = throttleResponseState.commandResponse;
//Ready to set the boolean actuation state that is used to compute the tire slips.
//(Note: for a tire under drive or brake torque we compute the slip with a smaller denominator to accentuate the applied torque).
//(Note: for a tire that is not under drive or brake torque we compute the sip with a larger denominator to smooth the vehicle slowing down).
for(PxU32 i = 0; i < axleDescription.nbWheels; i++)
{
const PxU32 wheelId = axleDescription.wheelIdsInAxleOrder[i];
//Reset the actuation states.
PxVehicleWheelActuationState& actState = actuationStates[wheelId];
PxMemZero(&actState, sizeof(PxVehicleWheelActuationState));
const PxF32 brakeTorque = brakeResponseStates[wheelId];
const PxF32 diffTorqueRatio = diffTorqueRatios[wheelId];
const bool isIntentionToAccelerate = (0.0f == brakeTorque) && (K != 0.0f) && (diffTorqueRatio != 0.0f) && (appliedThrottle != 0.0f);
actState.isDriveApplied = isIntentionToAccelerate;
actState.isBrakeApplied = (brakeTorque!= 0.0f);
}
}
void PxVehicleGearCommandResponseUpdate
(const PxU32 targetGearCommand,
const PxVehicleGearboxParams& gearboxParams,
PxVehicleGearboxState& gearboxState)
{
//Check that we're not halfway through a gear change and we need to execute a change of gear.
//If we are halfway through a gear change then we cannot execute another until the first is complete.
//Check that the command stays in a legal gear range.
if ((gearboxState.currentGear == gearboxState.targetGear) && (targetGearCommand != gearboxState.currentGear) && (targetGearCommand < gearboxParams.nbRatios))
{
//We're not executing a gear change and we need to start one.
//Start the gear change by
//a)setting the target gear.
//b)putting vehicle in neutral
//c)set the switch time to PX_VEHICLE_GEAR_SWITCH_INITIATED to flag that we just started a gear change.
gearboxState.currentGear = gearboxParams.neutralGear;
gearboxState.targetGear = targetGearCommand;
gearboxState.gearSwitchTime = PX_VEHICLE_GEAR_SWITCH_INITIATED;
}
}
void PxVehicleAutoBoxUpdate
(const PxVehicleEngineParams& engineParams, const PxVehicleGearboxParams& gearboxParams, const PxVehicleAutoboxParams& autoboxParams,
const PxVehicleEngineState& engineState, const PxVehicleGearboxState& gearboxState,
const PxReal dt,
PxU32& targetGearCommand, PxVehicleAutoboxState& autoboxState,
PxReal& throttle)
{
if(targetGearCommand != PxVehicleEngineDriveTransmissionCommandState::eAUTOMATIC_GEAR)
{
autoboxState.activeAutoboxGearShift = false;
autoboxState.timeSinceLastShift = PX_VEHICLE_UNSPECIFIED_TIME_SINCE_LAST_SHIFT;
return;
}
//Current and target gear allow us to determine if a gear change is underway.
const PxU32 currentGear = gearboxState.currentGear;
const PxU32 targetGear = gearboxState.targetGear;
//Set to current target gear in case no gear change will be initiated.
targetGearCommand = targetGear;
//If the autobox triggered a gear change and the gear change is complete then
//reset the corresponding flag.
if(autoboxState.activeAutoboxGearShift && (currentGear == targetGear))
{
autoboxState.activeAutoboxGearShift = false;
}
//If the autobox triggered a gear change and the gear change is incomplete then
//turn off the throttle pedal. This happens in autoboxes
//to stop the driver revving the engine then damaging the
//clutch when the clutch re-engages at the end of the gear change.
if(autoboxState.activeAutoboxGearShift && (currentGear != targetGear))
{
throttle = 0.0f;
}
//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.
if (targetGear == currentGear)
{
const PxF32 autoBoxSwitchTime = autoboxState.timeSinceLastShift;
const PxF32 autoBoxLatencyTime = autoboxParams.latency;
if ((currentGear <= gearboxParams.neutralGear) && ((gearboxParams.neutralGear + 1) < gearboxParams.nbRatios))
{
// eAUTOMATIC_GEAR has been set while in neutral or one of the reverse gears
// => switch to first
targetGearCommand = gearboxParams.neutralGear + 1;
throttle = 0.0f;
autoboxState.timeSinceLastShift = 0.0f;
autoboxState.activeAutoboxGearShift = true;
}
else if (autoBoxSwitchTime > autoBoxLatencyTime)
{
const PxF32 normalisedEngineOmega = engineState.rotationSpeed/engineParams.maxOmega;
const PxU32 neutralGear = gearboxParams.neutralGear;
const PxU32 nbGears = gearboxParams.nbRatios;
const PxF32 upRatio = autoboxParams.upRatios[currentGear];
const PxF32 downRatio = autoboxParams.downRatios[currentGear];
//If revs too high and not in reverse/neutral and there is a higher gear then switch up.
//Note: never switch up from neutral to first
//Note: never switch up from reverse to neutral
//Note: never switch up from one reverse gear to another reverse gear.
PX_ASSERT(currentGear > neutralGear);
if ((normalisedEngineOmega > upRatio) && ((currentGear + 1) < nbGears))
{
targetGearCommand = currentGear + 1;
throttle = 0.0f;
autoboxState.timeSinceLastShift = 0.0f;
autoboxState.activeAutoboxGearShift = true;
}
//If revs too low and in gear higher than first then switch down.
//Note: never switch from forward to neutral
//Note: never switch from neutral to reverse
//Note: never switch from reverse gear to reverse gear.
if ((normalisedEngineOmega < downRatio) && (currentGear > (neutralGear + 1)))
{
targetGearCommand = currentGear - 1;
throttle = 0.0f;
autoboxState.timeSinceLastShift = 0.0f;
autoboxState.activeAutoboxGearShift = true;
}
}
else
{
autoboxState.timeSinceLastShift += dt;
}
}
else
{
autoboxState.timeSinceLastShift += dt;
}
}
void PxVehicleGearboxUpdate(const PxVehicleGearboxParams& gearboxParams, const PxF32 dt, PxVehicleGearboxState& gearboxState)
{
if(gearboxState.targetGear != gearboxState.currentGear)
{
//If we just started a gear change then set the timer to zero.
//This replicates legacy behaviour.
if(gearboxState.gearSwitchTime == PX_VEHICLE_GEAR_SWITCH_INITIATED)
gearboxState.gearSwitchTime = 0.0f;
else
gearboxState.gearSwitchTime += dt;
//If we've exceed the switch time then switch to the target gear
//and reset the timer.
if (gearboxState.gearSwitchTime > gearboxParams.switchTime)
{
gearboxState.currentGear = gearboxState.targetGear;
gearboxState.gearSwitchTime = PX_VEHICLE_NO_GEAR_SWITCH_PENDING;
}
}
}
void countConnectedWheelsNotInConstraintGroup
(const PxU32* connectedWheelIds, const PxU32 nbConnectedWheelIds, const PxVehicleWheelConstraintGroupState& constraintGroups,
PxU32 connectedWheelsNotInConstraintGroup[PxVehicleLimits::eMAX_NB_WHEELS], PxU32& nbConnectedWheelsNotInConstraintGroup)
{
//Record the constraint group for each wheel.
//0xffffffff is reserved for a wheel not in a constraint group.
PxU32 wheelConstraintGroupIds[PxVehicleLimits::eMAX_NB_WHEELS];
PxMemSet(wheelConstraintGroupIds, 0xffffffff, sizeof(wheelConstraintGroupIds));
for (PxU32 i = 0; i < constraintGroups.getNbConstraintGroups(); i++)
{
for (PxU32 j = 0; j < constraintGroups.getNbWheelsInConstraintGroup(i); j++)
{
const PxU32 wheelId = constraintGroups.getWheelInConstraintGroup(j, i);
wheelConstraintGroupIds[wheelId] = i;
}
}
//Iterate over all connected wheels and count the number not in a group.
for (PxU32 i = 0; i < nbConnectedWheelIds; i++)
{
const PxU32 wheelId = connectedWheelIds[i];
const PxU32 constraintGroupId = wheelConstraintGroupIds[wheelId];
if (0xffffffff == constraintGroupId)
{
connectedWheelsNotInConstraintGroup[nbConnectedWheelsNotInConstraintGroup] = wheelId;
nbConnectedWheelsNotInConstraintGroup++;
}
}
}
void PxVehicleEngineDrivetrainUpdate
(const PxVehicleAxleDescription& axleDescription,
const PxVehicleArrayData<const PxVehicleWheelParams>& wheelParams,
const PxVehicleEngineParams& engineParams, const PxVehicleClutchParams& clutchParams, const PxVehicleGearboxParams& gearboxParams,
const PxVehicleArrayData<const PxReal>& brakeResponseStates,
const PxVehicleArrayData<const PxVehicleWheelActuationState>& actuationStates,
const PxVehicleArrayData<const PxVehicleTireForce>& tireForces,
const PxVehicleGearboxState& gearboxState,
const PxVehicleEngineDriveThrottleCommandResponseState& throttleCommandResponseState, const PxVehicleClutchCommandResponseState& clutchCommandResponseState,
const PxVehicleDifferentialState& diffState, const PxVehicleWheelConstraintGroupState* constraintGroupState,
const PxReal DT,
PxVehicleArrayData<PxVehicleWheelRigidBody1dState>& wheelRigidbody1dStates,
PxVehicleEngineState& engineState,
PxVehicleClutchSlipState& clutchState)
{
const PxF32 K = PxVehicleClutchStrengthCompute(clutchCommandResponseState, gearboxParams, gearboxState);
const PxF32 G = PxVehicleGearRatioCompute(gearboxParams, gearboxState);
const PxF32 engineDriveTorque = PxVehicleEngineDriveTorqueCompute(engineParams, engineState, throttleCommandResponseState);
const PxF32 engineDampingRate = PxVehicleEngineDampingRateCompute(engineParams, gearboxParams, gearboxState, clutchCommandResponseState, throttleCommandResponseState);
//Arrange wheel parameters in they order they appear in the connected wheel list.
const PxU32* connectedWheelIds = diffState.connectedWheels;
const PxU32 nbConnectedWheelIds = diffState.nbConnectedWheels;
PxU32 wheelIdToConnectedWheelId[PxVehicleLimits::eMAX_NB_WHEELS];
PxF32 connectedWheelDiffTorqueRatios[PxVehicleLimits::eMAX_NB_WHEELS];
PxF32 connectedWheelGearings[PxVehicleLimits::eMAX_NB_WHEELS];
PxF32 connectedWheelAveWheelSpeedContributions[PxVehicleLimits::eMAX_NB_WHEELS];
PxF32 connectedWheelMois[PxVehicleLimits::eMAX_NB_WHEELS];
PxF32 connectedWheelRotSpeeds[PxVehicleLimits::eMAX_NB_WHEELS];
PxF32 connectedWheelBrakeTorques[PxVehicleLimits::eMAX_NB_WHEELS];
PxF32 connectedWheelTireTorques[PxVehicleLimits::eMAX_NB_WHEELS];
PxF32 connectedWheelDampingRates[PxVehicleLimits::eMAX_NB_WHEELS];
bool connectedWheelIsBrakeApplied[PxVehicleLimits::eMAX_NB_WHEELS];
//PxF32 connectedWheelRadii[PxVehicleLimits::eMAX_NB_WHEELS];
for(PxU32 i = 0; i < nbConnectedWheelIds; i++)
{
const PxU32 wheelId = connectedWheelIds[i];
wheelIdToConnectedWheelId[wheelId] = i;
connectedWheelDiffTorqueRatios[i] = PxAbs(diffState.torqueRatiosAllWheels[wheelId]);
connectedWheelGearings[i] = PxVehicleComputeSign(diffState.torqueRatiosAllWheels[wheelId]);
connectedWheelAveWheelSpeedContributions[i] = diffState.aveWheelSpeedContributionAllWheels[wheelId];
connectedWheelMois[i] = wheelParams[wheelId].moi;
connectedWheelRotSpeeds[i] = wheelRigidbody1dStates[wheelId].rotationSpeed;
connectedWheelBrakeTorques[i] = -PxVehicleComputeSign(wheelRigidbody1dStates[wheelId].rotationSpeed)*brakeResponseStates[wheelId];
connectedWheelTireTorques[i] = tireForces[wheelId].wheelTorque;
connectedWheelDampingRates[i] = wheelParams[wheelId].dampingRate;
connectedWheelIsBrakeApplied[i] = actuationStates[wheelId].isBrakeApplied;
//connectedWheelRadii[i] = wheelParams[wheelId].radius;
};
//Compute the clutch slip
clutchState.setToDefault();
PxF32 clutchSlip = 0;
{
PxF32 averageWheelSpeed = 0;
for (PxU32 i = 0; i < nbConnectedWheelIds; i++)
{
averageWheelSpeed += connectedWheelRotSpeeds[i] * connectedWheelAveWheelSpeedContributions[i];
}
clutchSlip = G*averageWheelSpeed - engineState.rotationSpeed;
}
clutchState.clutchSlip = clutchSlip;
//
//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);
PxVehicleMatrixNN M(nbConnectedWheelIds + 1);
PxVehicleVectorN b(nbConnectedWheelIds + 1);
PxVehicleVectorN result(nbConnectedWheelIds + 1);
const PxF32 KG = K * G;
const PxF32 KGG = K * G*G;
//Wheels
{
for (PxU32 i = 0; i < nbConnectedWheelIds; i++)
{
const PxF32 dt = DT / connectedWheelMois[i];
const PxF32 R = connectedWheelDiffTorqueRatios[i];
const PxF32 g = connectedWheelGearings[i];
const PxF32 dtKGGRg = dt * KGG*R*g;
for(PxU32 j = 0; j < nbConnectedWheelIds; j++)
{
M.set(i, j, dtKGGRg*connectedWheelAveWheelSpeedContributions[j]*connectedWheelGearings[j]);
}
M.set(i, i, 1.0f + dtKGGRg*connectedWheelAveWheelSpeedContributions[i]*connectedWheelGearings[i] + dt * connectedWheelDampingRates[i]);
M.set(i, nbConnectedWheelIds, -dt*KG*R*g);
b[i] = connectedWheelRotSpeeds[i] + dt * (connectedWheelBrakeTorques[i] + connectedWheelTireTorques[i]);
result[i] = connectedWheelRotSpeeds[i];
}
}
//Engine.
{
const PxF32 dt = DT / engineParams.moi;
const PxF32 dtKG = dt*K*G;
for(PxU32 j = 0; j < nbConnectedWheelIds; j++)
{
M.set(nbConnectedWheelIds, j, -dtKG * connectedWheelAveWheelSpeedContributions[j]* connectedWheelGearings[j]);
}
M.set(nbConnectedWheelIds, nbConnectedWheelIds, 1.0f + dt * (K + engineDampingRate));
b[nbConnectedWheelIds] = engineState.rotationSpeed + dt * engineDriveTorque;
result[nbConnectedWheelIds] = engineState.rotationSpeed;
}
if (constraintGroupState && constraintGroupState->getNbConstraintGroups() > 0)
{
const PxU32 nbConstraintGroups = constraintGroupState->getNbConstraintGroups();
//Count the wheels not in a constraint group.
PxU32 connectedWheelsNotInConstraintGroup[PxVehicleLimits::eMAX_NB_WHEELS];
PxU32 nbConnectedWheelsNotInConstraintGroup = 0;
countConnectedWheelsNotInConstraintGroup(
connectedWheelIds, nbConnectedWheelIds, *constraintGroupState,
connectedWheelsNotInConstraintGroup, nbConnectedWheelsNotInConstraintGroup);
//After applying constraint groups:
// number of columns remains nbConnectedWheelIds + 1
// each row will be of length nbConnectedWheelsNotInConstraintGroup + nbConstraintGroups + 1
PxVehicleMatrixNN A(nbConnectedWheelIds + 1);
for (PxU32 i = 0; i < nbConnectedWheelIds + 1; i++)
{
//1 entry for each wheel not in a constraint group.
for (PxU32 j = 0; j < nbConnectedWheelsNotInConstraintGroup; j++)
{
const PxU32 wheelId = connectedWheelsNotInConstraintGroup[j];
const PxU32 connectedWheelId = wheelIdToConnectedWheelId[wheelId];
const PxF32 MIJ = M.get(i, connectedWheelId);
A.set(i, j, MIJ);
}
//1 entry for each constraint group.
for (PxU32 j = nbConnectedWheelsNotInConstraintGroup; j < nbConnectedWheelsNotInConstraintGroup + nbConstraintGroups; j++)
{
const PxU32 constraintGroupId = (j - nbConnectedWheelsNotInConstraintGroup);
PxF32 sum = 0.0f;
//for (PxU32 k = 0; k < 1; k++)
{
//k = 0 is a special case with multiplier = 1.0.
const PxU32 wheelId = constraintGroupState->getWheelInConstraintGroup(0, constraintGroupId);
const PxF32 multiplier = 1.0f;
const PxU32 connectedWheelId = wheelIdToConnectedWheelId[wheelId];
const PxF32 MIK = M.get(i, connectedWheelId);
sum += MIK * multiplier;
}
for (PxU32 k = 1; k < constraintGroupState->getNbWheelsInConstraintGroup(constraintGroupId); k++)
{
const PxU32 wheelId = constraintGroupState->getWheelInConstraintGroup(k, constraintGroupId);
const PxF32 multiplier = constraintGroupState->getMultiplierInConstraintGroup(k, constraintGroupId);
const PxU32 connectedWheelId = wheelIdToConnectedWheelId[wheelId];
const PxF32 MIK = M.get(i, connectedWheelId);
sum += MIK*multiplier;
}
A.set(i, j, sum);
}
//1 entry for the engine.
{
const PxF32 MIJ = M.get(i, nbConnectedWheelIds);
A.set(i, nbConnectedWheelsNotInConstraintGroup + nbConstraintGroups, MIJ);
}
}
const PxU32 N = (nbConnectedWheelsNotInConstraintGroup + nbConstraintGroups + 1);
//Compute A^T * A
PxVehicleMatrixNN ATA(N);
for (PxU32 i = 0; i < N; i++)
{
for (PxU32 j = 0; j < N; j++)
{
PxF32 sum = 0.0f;
for (PxU32 k = 0; k < nbConnectedWheelIds + 1; k++)
{
sum += A.get(k, i)*A.get(k, j);
}
ATA.set(i, j, sum);
}
}
//Compute A^T*b;
PxVehicleVectorN ATb(N);
for (PxU32 i = 0; i < N; i++)
{
PxF32 sum = 0;
for (PxU32 j = 0; j < nbConnectedWheelIds + 1; j++)
{
sum += A.get(j, i)*b[j];
}
ATb[i] = sum;
}
//Solve it.
PxVehicleMatrixNNLUSolver solver;
PxVehicleVectorN result2(N);
solver.decomposeLU(ATA);
solver.solve(ATb, result2);
//Map from result2 back to result.
for (PxU32 j = 0; j < nbConnectedWheelsNotInConstraintGroup; j++)
{
const PxU32 wheelId = connectedWheelsNotInConstraintGroup[j];
const PxU32 connectedWheelId = wheelIdToConnectedWheelId[wheelId];
result[connectedWheelId] = result2[j];
}
for (PxU32 j = nbConnectedWheelsNotInConstraintGroup; j < nbConnectedWheelsNotInConstraintGroup + nbConstraintGroups; j++)
{
const PxU32 constraintGroupId = (j - nbConnectedWheelsNotInConstraintGroup);
//for (PxU32 k = 0; k < 1; k++)
{
//k = 0 is a special case with multiplier = 1.0
const PxU32 wheelId = constraintGroupState->getWheelInConstraintGroup(0, constraintGroupId);
const PxF32 multiplier = 1.0f;
const PxU32 connectedWheelId = wheelIdToConnectedWheelId[wheelId];
result[connectedWheelId] = multiplier * result2[j];
}
for (PxU32 k = 1; k < constraintGroupState->getNbWheelsInConstraintGroup(constraintGroupId); k++)
{
const PxU32 wheelId = constraintGroupState->getWheelInConstraintGroup(k, constraintGroupId);
const PxF32 multiplier = constraintGroupState->getMultiplierInConstraintGroup(k, constraintGroupId);
const PxU32 connectedWheelId = wheelIdToConnectedWheelId[wheelId];
result[connectedWheelId] = multiplier * result2[j];
}
}
{
result[nbConnectedWheelIds] = result2[nbConnectedWheelsNotInConstraintGroup + nbConstraintGroups];
}
}
else if (PxVehicleClutchAccuracyMode::eBEST_POSSIBLE == clutchParams.accuracyMode)
{
//Solve Aw=b
PxVehicleMatrixNNLUSolver solver;
solver.decomposeLU(M);
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
{
PxVehicleMatrixNGaussSeidelSolver solver;
solver.solve(clutchParams.estimateIterations, 1e-10f, M, 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 < nbConnectedWheelIds; i++)
{
result[i] = (connectedWheelIsBrakeApplied[i] && (connectedWheelRotSpeeds[i] * 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.
//The alternative would be to add constraints to the solver, which would be much more expensive.
result[nbConnectedWheelIds] = PxClamp(result[nbConnectedWheelIds], engineParams.idleOmega, engineParams.maxOmega);
//Copy back to the car's internal rotation speeds.
for (PxU32 i = 0; i < nbConnectedWheelIds; i++)
{
const PxU32 wheelId = connectedWheelIds[i];
wheelRigidbody1dStates[wheelId].rotationSpeed = result[i];
}
engineState.rotationSpeed = result[nbConnectedWheelIds];
//Update the undriven wheels.
bool isDrivenWheel[PxVehicleLimits::eMAX_NB_WHEELS];
PxMemZero(isDrivenWheel, sizeof(isDrivenWheel));
for(PxU32 i = 0; i < nbConnectedWheelIds; i++)
{
const PxU32 wheelId = connectedWheelIds[i];
isDrivenWheel[wheelId] = true;
}
for (PxU32 i = 0; i < axleDescription.nbWheels; i++)
{
const PxU32 wheelId = axleDescription.wheelIdsInAxleOrder[i];
if (!isDrivenWheel[wheelId])
{
PxVehicleDirectDriveUpdate(
wheelParams[wheelId],
actuationStates[wheelId],
brakeResponseStates[wheelId], 0.0f,
tireForces[wheelId], DT, wheelRigidbody1dStates[wheelId]);
}
}
}
void PxVehicleClutchCommandResponseLinearUpdate
(const PxReal clutchCommand,
const PxVehicleClutchCommandResponseParams& clutchResponseParams,
PxVehicleClutchCommandResponseState& clutchResponse)
{
clutchResponse.normalisedCommandResponse = (1.0f - clutchCommand);
clutchResponse.commandResponse = (1.0f - clutchCommand)*clutchResponseParams.maxResponse;
}
void PxVehicleEngineDriveThrottleCommandResponseLinearUpdate
(const PxVehicleCommandState& commands,
PxVehicleEngineDriveThrottleCommandResponseState& throttleResponse)
{
throttleResponse.commandResponse = commands.throttle;
}
} //namespace vehicle2
} //namespace physx

View File

@@ -0,0 +1,439 @@
// 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-2025 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 "vehicle2/PxVehicleParams.h"
#include "vehicle2/PxVehicleFunctions.h"
#include "vehicle2/PxVehicleMaths.h"
#include "vehicle2/drivetrain/PxVehicleDrivetrainHelpers.h"
#include "vehicle2/drivetrain/PxVehicleDrivetrainParams.h"
#include "vehicle2/wheel/PxVehicleWheelStates.h"
namespace physx
{
namespace vehicle2
{
void PxVehicleMatrixNNLUSolver::decomposeLU(const PxVehicleMatrixNN& A)
{
const PxU32 D = A.mSize;
mLU = A;
mDetM = 1.0f;
for (PxU32 k = 0; k < D - 1; ++k)
{
PxU32 pivot_row = k;
PxU32 pivot_col = k;
float abs_pivot_elem = 0.0f;
for (PxU32 c = k; c < D; ++c)
{
for (PxU32 r = k; r < D; ++r)
{
const PxF32 abs_elem = PxAbs(mLU.get(r, c));
if (abs_elem > abs_pivot_elem)
{
abs_pivot_elem = abs_elem;
pivot_row = r;
pivot_col = c;
}
}
}
mP[k] = pivot_row;
if (pivot_row != k)
{
mDetM = -mDetM;
for (PxU32 c = 0; c < D; ++c)
{
//swap(m_LU(k,c), m_LU(pivot_row,c));
const PxF32 pivotrowc = mLU.get(pivot_row, c);
mLU.set(pivot_row, c, mLU.get(k, c));
mLU.set(k, c, pivotrowc);
}
}
mQ[k] = pivot_col;
if (pivot_col != k)
{
mDetM = -mDetM;
for (PxU32 r = 0; r < D; ++r)
{
//swap(m_LU(r,k), m_LU(r,pivot_col));
const PxF32 rpivotcol = mLU.get(r, pivot_col);
mLU.set(r, pivot_col, mLU.get(r, k));
mLU.set(r, k, rpivotcol);
}
}
mDetM *= mLU.get(k, k);
if (mLU.get(k, k) != 0.0f)
{
for (PxU32 r = k + 1; r < D; ++r)
{
mLU.set(r, k, mLU.get(r, k) / mLU.get(k, k));
for (PxU32 c = k + 1; c < D; ++c)
{
//m_LU(r,c) -= m_LU(r,k)*m_LU(k,c);
const PxF32 rc = mLU.get(r, c);
const PxF32 rk = mLU.get(r, k);
const PxF32 kc = mLU.get(k, c);
mLU.set(r, c, rc - rk * kc);
}
}
}
}
mDetM *= mLU.get(D - 1, D - 1);
}
bool PxVehicleMatrixNNLUSolver::solve(const PxVehicleVectorN& b, PxVehicleVectorN& x) const
{
const PxU32 D = x.getSize();
if ((b.getSize() != x.getSize()) || (b.getSize() != mLU.getSize()) || (0.0f == mDetM))
{
for (PxU32 i = 0; i < D; i++)
{
x[i] = 0.0f;
}
return false;
}
x = b;
// Perform row permutation to get Pb
for (PxU32 i = 0; i < D - 1; ++i)
{
//swap(x(i), x(m_P[i]));
const PxF32 xp = x[mP[i]];
x[mP[i]] = x[i];
x[i] = xp;
}
// Forward substitute to get (L^-1)Pb
for (PxU32 r = 1; r < D; ++r)
{
for (PxU32 i = 0; i < r; ++i)
{
x[r] -= mLU.get(r, i)*x[i];
}
}
// Back substitute to get (U^-1)(L^-1)Pb
for (PxU32 r = D; r-- > 0;)
{
for (PxU32 i = r + 1; i < D; ++i)
{
x[r] -= mLU.get(r, i)*x[i];
}
x[r] /= mLU.get(r, r);
}
// Perform column permutation to get the solution (Q^T)(U^-1)(L^-1)Pb
for (PxU32 i = D - 1; i-- > 0;)
{
//swap(x(i), x(m_Q[i]));
const PxF32 xq = x[mQ[i]];
x[mQ[i]] = x[i];
x[i] = xq;
}
return true;
}
void PxVehicleMatrixNGaussSeidelSolver::solve(const PxU32 maxIterations, const PxF32 tolerance, const PxVehicleMatrixNN& A, const PxVehicleVectorN& b, PxVehicleVectorN& result) const
{
const PxU32 N = A.getSize();
PxVehicleVectorN DInv(N);
PxF32 bLength2 = 0.0f;
for (PxU32 i = 0; i < N; i++)
{
DInv[i] = 1.0f / A.get(i, i);
bLength2 += (b[i] * b[i]);
}
PxU32 iteration = 0;
PxF32 error = PX_MAX_F32;
while (iteration < maxIterations && tolerance < error)
{
for (PxU32 i = 0; i < N; i++)
{
PxF32 l = 0.0f;
for (PxU32 j = 0; j < i; j++)
{
l += A.get(i, j) * result[j];
}
PxF32 u = 0.0f;
for (PxU32 j = i + 1; j < N; j++)
{
u += A.get(i, j) * result[j];
}
result[i] = DInv[i] * (b[i] - l - u);
}
//Compute the error.
PxF32 rLength2 = 0;
for (PxU32 i = 0; i < N; i++)
{
PxF32 e = -b[i];
for (PxU32 j = 0; j < N; j++)
{
e += A.get(i, j) * result[j];
}
rLength2 += e * e;
}
error = (rLength2 / (bLength2 + 1e-10f));
iteration++;
}
}
bool PxVehicleMatrix33Solver::solve(const PxVehicleMatrixNN& A_, const PxVehicleVectorN& b_, PxVehicleVectorN& result) const
{
const PxF32 a = A_.get(0, 0);
const PxF32 b = A_.get(0, 1);
const PxF32 c = A_.get(0, 2);
const PxF32 d = A_.get(1, 0);
const PxF32 e = A_.get(1, 1);
const PxF32 f = A_.get(1, 2);
const PxF32 g = A_.get(2, 0);
const PxF32 h = A_.get(2, 1);
const PxF32 k = A_.get(2, 2);
const PxF32 detA = a * (e*k - f * h) - b * (k*d - f * g) + c * (d*h - e * g);
if (0.0f == detA)
{
return false;
}
const PxF32 detAInv = 1.0f / detA;
const PxF32 A = (e*k - f * h);
const PxF32 D = -(b*k - c * h);
const PxF32 G = (b*f - c * e);
const PxF32 B = -(d*k - f * g);
const PxF32 E = (a*k - c * g);
const PxF32 H = -(a*f - c * d);
const PxF32 C = (d*h - e * g);
const PxF32 F = -(a*h - b * g);
const PxF32 K = (a*e - b * d);
result[0] = detAInv * (A*b_[0] + D * b_[1] + G * b_[2]);
result[1] = detAInv * (B*b_[0] + E * b_[1] + H * b_[2]);
result[2] = detAInv * (C*b_[0] + F * b_[1] + K * b_[2]);
return true;
}
void PxVehicleLegacyDifferentialWheelSpeedContributionsCompute
(const PxVehicleFourWheelDriveDifferentialLegacyParams& diffParams,
const PxU32 nbWheels, PxReal* diffAveWheelSpeedContributions)
{
PxMemZero(diffAveWheelSpeedContributions, sizeof(PxReal) * nbWheels);
const PxU32 wheelIds[4] =
{
diffParams.frontWheelIds[0],
diffParams.frontWheelIds[1],
diffParams.rearWheelIds[0],
diffParams.rearWheelIds[1],
};
const PxF32 frontRearSplit = diffParams.frontRearSplit;
const PxF32 frontNegPosSplit = diffParams.frontNegPosSplit;
const PxF32 rearNegPosSplit = diffParams.rearNegPosSplit;
const PxF32 oneMinusFrontRearSplit = 1.0f - diffParams.frontRearSplit;
const PxF32 oneMinusFrontNegPosSplit = 1.0f - diffParams.frontNegPosSplit;
const PxF32 oneMinusRearNegPosSplit = 1.0f - diffParams.rearNegPosSplit;
switch (diffParams.type)
{
case PxVehicleFourWheelDriveDifferentialLegacyParams::eDIFF_TYPE_LS_4WD:
diffAveWheelSpeedContributions[wheelIds[0]] = frontRearSplit * frontNegPosSplit;
diffAveWheelSpeedContributions[wheelIds[1]] = frontRearSplit * oneMinusFrontNegPosSplit;
diffAveWheelSpeedContributions[wheelIds[2]] = oneMinusFrontRearSplit * rearNegPosSplit;
diffAveWheelSpeedContributions[wheelIds[3]] = oneMinusFrontRearSplit * oneMinusRearNegPosSplit;
break;
case PxVehicleFourWheelDriveDifferentialLegacyParams::eDIFF_TYPE_LS_FRONTWD:
diffAveWheelSpeedContributions[wheelIds[0]] = frontNegPosSplit;
diffAveWheelSpeedContributions[wheelIds[1]] = oneMinusFrontNegPosSplit;
diffAveWheelSpeedContributions[wheelIds[2]] = 0.0f;
diffAveWheelSpeedContributions[wheelIds[3]] = 0.0f;
break;
case PxVehicleFourWheelDriveDifferentialLegacyParams::eDIFF_TYPE_LS_REARWD:
diffAveWheelSpeedContributions[wheelIds[0]] = 0.0f;
diffAveWheelSpeedContributions[wheelIds[1]] = 0.0f;
diffAveWheelSpeedContributions[wheelIds[2]] = rearNegPosSplit;
diffAveWheelSpeedContributions[wheelIds[3]] = oneMinusRearNegPosSplit;
break;
default:
PX_ASSERT(false);
break;
}
PX_ASSERT(
((diffAveWheelSpeedContributions[wheelIds[0]] + diffAveWheelSpeedContributions[wheelIds[1]] + diffAveWheelSpeedContributions[wheelIds[2]] + diffAveWheelSpeedContributions[wheelIds[3]]) >= 0.999f) &&
((diffAveWheelSpeedContributions[wheelIds[0]] + diffAveWheelSpeedContributions[wheelIds[1]] + diffAveWheelSpeedContributions[wheelIds[2]] + diffAveWheelSpeedContributions[wheelIds[3]]) <= 1.001f));
}
PX_FORCE_INLINE void splitTorque
(const PxF32 w1, const PxF32 w2, const PxF32 diffBias, const PxF32 defaultSplitRatio,
PxF32* t1, PxF32* t2)
{
PX_ASSERT(PxVehicleComputeSign(w1) == PxVehicleComputeSign(w2) && 0.0f != PxVehicleComputeSign(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);
}
void PxVehicleLegacyDifferentialTorqueRatiosCompute
(const PxVehicleFourWheelDriveDifferentialLegacyParams& diffParams,
const PxVehicleArrayData<const PxVehicleWheelRigidBody1dState>& wheelOmegas,
const PxU32 nbWheels, PxReal* diffTorqueRatios)
{
PxMemZero(diffTorqueRatios, sizeof(PxReal) * nbWheels);
const PxU32 wheelIds[4] =
{
diffParams.frontWheelIds[0],
diffParams.frontWheelIds[1],
diffParams.rearWheelIds[0],
diffParams.rearWheelIds[1],
};
const PxF32 wfl = wheelOmegas[wheelIds[0]].rotationSpeed;
const PxF32 wfr = wheelOmegas[wheelIds[1]].rotationSpeed;
const PxF32 wrl = wheelOmegas[wheelIds[2]].rotationSpeed;
const PxF32 wrr = wheelOmegas[wheelIds[3]].rotationSpeed;
const PxF32 centreBias = diffParams.centerBias;
const PxF32 frontBias = diffParams.frontBias;
const PxF32 rearBias = diffParams.rearBias;
const PxF32 frontRearSplit = diffParams.frontRearSplit;
const PxF32 frontLeftRightSplit = diffParams.frontNegPosSplit;
const PxF32 rearLeftRightSplit = diffParams.rearNegPosSplit;
const PxF32 oneMinusFrontRearSplit = 1.0f - diffParams.frontRearSplit;
const PxF32 oneMinusFrontLeftRightSplit = 1.0f - diffParams.frontNegPosSplit;
const PxF32 oneMinusRearLeftRightSplit = 1.0f - diffParams.rearNegPosSplit;
const PxF32 swfl = PxVehicleComputeSign(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 (diffParams.type)
{
case PxVehicleFourWheelDriveDifferentialLegacyParams::eDIFF_TYPE_LS_4WD:
if (0.0f != swfl && swfl == PxVehicleComputeSign(wfr) && swfl == PxVehicleComputeSign(wrl) && swfl == PxVehicleComputeSign(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
{
torqueFrontLeft = frontRearSplit * frontLeftRightSplit;
torqueFrontRight = frontRearSplit * oneMinusFrontLeftRightSplit;
torqueRearLeft = oneMinusFrontRearSplit * rearLeftRightSplit;
torqueRearRight = oneMinusFrontRearSplit * oneMinusRearLeftRightSplit;
}
break;
case PxVehicleFourWheelDriveDifferentialLegacyParams::eDIFF_TYPE_LS_FRONTWD:
if (0.0f != swfl && swfl == PxVehicleComputeSign(wfr))
{
splitTorque(wfl, wfr, frontBias, frontLeftRightSplit, &torqueFrontLeft, &torqueFrontRight);
}
else
{
torqueFrontLeft = frontLeftRightSplit;
torqueFrontRight = oneMinusFrontLeftRightSplit;
}
break;
case PxVehicleFourWheelDriveDifferentialLegacyParams::eDIFF_TYPE_LS_REARWD:
if (0.0f != PxVehicleComputeSign(wrl) && PxVehicleComputeSign(wrl) == PxVehicleComputeSign(wrr))
{
splitTorque(wrl, wrr, rearBias, rearLeftRightSplit, &torqueRearLeft, &torqueRearRight);
}
else
{
torqueRearLeft = rearLeftRightSplit;
torqueRearRight = oneMinusRearLeftRightSplit;
}
break;
default:
PX_ASSERT(false);
break;
}
diffTorqueRatios[wheelIds[0]] = torqueFrontLeft;
diffTorqueRatios[wheelIds[1]] = torqueFrontRight;
diffTorqueRatios[wheelIds[2]] = torqueRearLeft;
diffTorqueRatios[wheelIds[3]] = torqueRearRight;
PX_ASSERT(((torqueFrontLeft + torqueFrontRight + torqueRearLeft + torqueRearRight) >= 0.999f) && ((torqueFrontLeft + torqueFrontRight + torqueRearLeft + torqueRearRight) <= 1.001f));
}
} //namespace vehicle2
} //namespace physx

View File

@@ -0,0 +1,350 @@
// 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-2025 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 "vehicle2/PxVehicleParams.h"
#include "vehicle2/commands/PxVehicleCommandStates.h"
#include "vehicle2/drivetrain/PxVehicleDrivetrainParams.h"
#include "vehicle2/drivetrain/PxVehicleDrivetrainStates.h"
#include "vehicle2/rigidBody/PxVehicleRigidBodyStates.h"
#include "vehicle2/physxActor/PxVehiclePhysXActorFunctions.h"
#include "vehicle2/physxActor/PxVehiclePhysXActorStates.h"
#include "vehicle2/physxConstraints/PxVehiclePhysXConstraintStates.h"
#include "vehicle2/physxConstraints/PxVehiclePhysXConstraintHelpers.h"
#include "vehicle2/wheel/PxVehicleWheelParams.h"
#include "vehicle2/wheel/PxVehicleWheelStates.h"
#include "PxRigidDynamic.h"
#include "PxArticulationLink.h"
#include "PxArticulationReducedCoordinate.h"
namespace physx
{
namespace vehicle2
{
void PxVehiclePhysxActorWakeup(
const PxVehicleCommandState& commands,
const PxVehicleEngineDriveTransmissionCommandState* transmissionCommands,
const PxVehicleGearboxParams* gearParams,
const PxVehicleGearboxState* gearState,
PxRigidBody& physxActor,
PxVehiclePhysXSteerState& physxSteerState)
{
PX_CHECK_AND_RETURN(!(physxActor.getRigidBodyFlags() & PxRigidBodyFlag::eKINEMATIC), "PxVehiclePhysxActorWakeup: physxActor is kinematic. This is not supported");
PxRigidDynamic* rd = physxActor.is<PxRigidDynamic>();
PxArticulationLink* link = physxActor.is<PxArticulationLink>();
bool intentToChangeState = ((commands.throttle != 0.0f) || ((PX_VEHICLE_UNSPECIFIED_STEER_STATE != physxSteerState.previousSteerCommand) && (commands.steer != physxSteerState.previousSteerCommand)));
if (!intentToChangeState && transmissionCommands)
{
PX_ASSERT(gearParams);
PX_ASSERT(gearState);
// Manual gear switch (includes going from neutral or reverse to automatic)
intentToChangeState = (gearState->currentGear == gearState->targetGear) &&
(((transmissionCommands->targetGear != PxVehicleEngineDriveTransmissionCommandState::eAUTOMATIC_GEAR) && (gearState->targetGear != transmissionCommands->targetGear)) ||
((transmissionCommands->targetGear == PxVehicleEngineDriveTransmissionCommandState::eAUTOMATIC_GEAR) && (gearState->currentGear <= gearParams->neutralGear)));
}
if(rd && rd->isSleeping() && intentToChangeState)
{
rd->wakeUp();
}
else if(link && link->getArticulation().isSleeping() && intentToChangeState)
{
link->getArticulation().wakeUp();
}
physxSteerState.previousSteerCommand = commands.steer;
}
bool PxVehiclePhysxActorSleepCheck
(const PxVehicleAxleDescription& axleDescription,
const PxRigidBody& physxActor,
const PxVehicleEngineParams* engineParams,
PxVehicleRigidBodyState& rigidBodyState,
PxVehiclePhysXConstraints& physxConstraints,
PxVehicleArrayData<PxVehicleWheelRigidBody1dState>& wheelRigidBody1dStates,
PxVehicleEngineState* engineState)
{
PX_CHECK_AND_RETURN_VAL(!(physxActor.getRigidBodyFlags() & PxRigidBodyFlag::eKINEMATIC), "PxVehiclePhysxActorSleepCheck: physxActor is kinematic. This is not supported", false);
const PxRigidDynamic* rd = physxActor.is<PxRigidDynamic>();
const PxArticulationLink* link = physxActor.is<PxArticulationLink>();
bool isSleeping = false;
if (rd && rd->isSleeping())
{
isSleeping = true;
}
else if (link && link->getArticulation().isSleeping())
{
isSleeping = true;
}
if (isSleeping)
{
// note: pose is not copied as isSleeping() implies that pose was not changed by the
// simulation step (see docu). If a user explicitly calls putToSleep or manually
// changes pose, it will only get reflected in the vehicle rigid body state once
// the body wakes up.
rigidBodyState.linearVelocity = PxVec3(PxZero);
rigidBodyState.angularVelocity = PxVec3(PxZero);
rigidBodyState.previousLinearVelocity = PxVec3(PxZero);
rigidBodyState.previousAngularVelocity = PxVec3(PxZero);
bool markConstraintsDirty = false;
for (PxU32 i = 0; i < axleDescription.nbWheels; i++)
{
const PxU32 wheelId = axleDescription.wheelIdsInAxleOrder[i];
PxVehicleWheelRigidBody1dState& wheelState = wheelRigidBody1dStates[wheelId];
wheelState.rotationSpeed = 0.0f;
wheelState.correctedRotationSpeed = 0.0f;
// disable constraints if there are active ones. The idea is that if something
// is crashing into a sleeping vehicle and waking it up, then the vehicle should
// be able to move if the impact was large enough. Thus, there is also no logic
// to reset the sticky tire timers, for example. If the impact was small, the
// constraints should potentially kick in again in the subsequent sim step.
PxVehiclePhysXConstraintState& constraintState = physxConstraints.constraintStates[wheelId];
if (constraintState.tireActiveStatus[PxVehicleTireDirectionModes::eLONGITUDINAL] ||
constraintState.tireActiveStatus[PxVehicleTireDirectionModes::eLATERAL] ||
constraintState.suspActiveStatus)
{
constraintState.tireActiveStatus[PxVehicleTireDirectionModes::eLONGITUDINAL] = false;
constraintState.tireActiveStatus[PxVehicleTireDirectionModes::eLATERAL] = false;
constraintState.suspActiveStatus = false;
markConstraintsDirty = true;
}
}
if (markConstraintsDirty)
PxVehicleConstraintsDirtyStateUpdate(physxConstraints);
if (engineState)
{
PX_ASSERT(engineParams);
engineState->rotationSpeed = engineParams->idleOmega;
}
}
return isSleeping;
}
PX_FORCE_INLINE static void setWakeCounter(const PxReal wakeCounter, PxRigidDynamic* rd, PxArticulationLink* link)
{
if (rd && (!(rd->getRigidBodyFlags() & PxRigidBodyFlag::eKINEMATIC)))
{
rd->setWakeCounter(wakeCounter);
}
else if (link)
{
link->getArticulation().setWakeCounter(wakeCounter);
}
}
void PxVehiclePhysxActorKeepAwakeCheck
(const PxVehicleAxleDescription& axleDescription,
const PxVehicleArrayData<const PxVehicleWheelParams>& wheelParams,
const PxVehicleArrayData<const PxVehicleWheelRigidBody1dState>& wheelRigidBody1dStates,
const PxReal wakeCounterThreshold,
const PxReal wakeCounterResetValue,
const PxVehicleGearboxState* gearState,
const PxReal* throttle,
PxRigidBody& physxActor)
{
PX_CHECK_AND_RETURN(!(physxActor.getRigidBodyFlags() & PxRigidBodyFlag::eKINEMATIC), "PxVehiclePhysxActorKeepAwakeCheck: physxActor is kinematic. This is not supported");
PxRigidDynamic* rd = physxActor.is<PxRigidDynamic>();
PxArticulationLink* link = physxActor.is<PxArticulationLink>();
PxReal wakeCounter = PX_MAX_REAL;
if (rd)
{
wakeCounter = rd->getWakeCounter();
}
else if (link)
{
wakeCounter = link->getArticulation().getWakeCounter();
}
if (wakeCounter < wakeCounterThreshold)
{
if ((throttle && ((*throttle) > 0.0f)) ||
(gearState && (gearState->currentGear != gearState->targetGear)))
{
setWakeCounter(wakeCounterResetValue, rd, link);
return;
}
for (PxU32 i = 0; i < axleDescription.nbWheels; i++)
{
const PxU32 wheelId = axleDescription.wheelIdsInAxleOrder[i];
const PxVehicleWheelParams& wheelParam = wheelParams[wheelId];
const PxVehicleWheelRigidBody1dState& wheelState = wheelRigidBody1dStates[wheelId];
// note: the translational part of the energy is ignored here as this is mainly for
// scenarios where there is almost no translation but the wheels are spinning
const PxReal normalizedEnergy = (0.5f * wheelState.correctedRotationSpeed * wheelState.correctedRotationSpeed *
wheelParam.moi) / wheelParam.mass;
PxReal sleepThreshold = PX_MAX_REAL;
if (rd)
{
sleepThreshold = rd->getSleepThreshold();
}
else if (link)
{
sleepThreshold = link->getArticulation().getSleepThreshold();
}
if (normalizedEnergy > sleepThreshold)
{
setWakeCounter(wakeCounterResetValue, rd, link);
return;
}
}
}
}
void PxVehicleReadRigidBodyStateFromPhysXActor
(const PxRigidBody& physxActor,
PxVehicleRigidBodyState& rigidBodyState)
{
PX_CHECK_AND_RETURN(!(physxActor.getRigidBodyFlags() & PxRigidBodyFlag::eKINEMATIC), "PxVehicleReadRigidBodyStateFromPhysXActor: physxActor is kinematic. This is not supported");
const PxRigidDynamic* rd = physxActor.is<PxRigidDynamic>();
const PxArticulationLink* link = physxActor.is<PxArticulationLink>();
PX_ASSERT(rd || link);
if(rd)
{
rigidBodyState.pose = physxActor.getGlobalPose()*physxActor.getCMassLocalPose();
rigidBodyState.angularVelocity = rd->getAngularVelocity();
rigidBodyState.linearVelocity = rd->getLinearVelocity();
}
else
{
rigidBodyState.pose = physxActor.getGlobalPose()*physxActor.getCMassLocalPose();
rigidBodyState.angularVelocity = link->getAngularVelocity();
rigidBodyState.linearVelocity = link->getLinearVelocity();
}
rigidBodyState.previousLinearVelocity = rigidBodyState.linearVelocity;
rigidBodyState.previousAngularVelocity = rigidBodyState.angularVelocity;
}
void PxVehicleWriteWheelLocalPoseToPhysXWheelShape
(const PxTransform& wheelLocalPose, const PxTransform& wheelShapeLocalPose, PxShape* shape)
{
if(!shape)
return;
PxRigidActor* ra = shape->getActor();
if(!ra)
return;
PxRigidBody* rb = ra->is<PxRigidBody>();
if(!rb)
return;
PX_CHECK_AND_RETURN(!(rb->getRigidBodyFlags() & PxRigidBodyFlag::eKINEMATIC), "PxVehicleWriteWheelLocalPoseToPhysXWheelShape: shape is attached to a kinematic actor. This is not supported");
//Local pose in actor frame.
const PxTransform& localPoseCMassFrame = wheelLocalPose*wheelShapeLocalPose;
const PxTransform cmassLocalPose = rb->getCMassLocalPose();
const PxTransform localPoseActorFrame = cmassLocalPose * localPoseCMassFrame;
//Apply the local pose to the shape.
shape->setLocalPose(localPoseActorFrame);
}
void PxVehicleWriteRigidBodyStateToPhysXActor
(const PxVehiclePhysXActorUpdateMode::Enum updateMode,
const PxVehicleRigidBodyState& rigidBodyState,
const PxReal dt,
PxRigidBody& rb)
{
PX_CHECK_AND_RETURN(!(rb.getRigidBodyFlags() & PxRigidBodyFlag::eKINEMATIC), "PxVehicleWriteRigidBodyStateToPhysXActor: physxActor is kinematic. This is not supported");
PxRigidDynamic* rd = rb.is<PxRigidDynamic>();
PxArticulationLink* link = rb.is<PxArticulationLink>();
PX_ASSERT(rd || link);
if(rb.getScene() && // check for scene to support immediate mode style vehicles
((rd && rd->isSleeping()) || (link && link->getArticulation().isSleeping())))
{
// note: sort of a safety mechanism to be able to keep running the full vehicle pipeline
// even if the physx actor fell asleep. Without it, the vehicle state can drift from
// physx actor state in the course of multiple simulation steps up to the point
// where the physx actor suddenly wakes up.
return;
}
switch (updateMode)
{
case PxVehiclePhysXActorUpdateMode::eAPPLY_VELOCITY:
{
PX_ASSERT(rd);
rd->setLinearVelocity(rigidBodyState.linearVelocity, false);
rd->setAngularVelocity(rigidBodyState.angularVelocity, false);
}
break;
case PxVehiclePhysXActorUpdateMode::eAPPLY_ACCELERATION:
{
const PxVec3 linAccel = (rigidBodyState.linearVelocity - rigidBodyState.previousLinearVelocity)/dt;
const PxVec3 angAccel = (rigidBodyState.angularVelocity - rigidBodyState.previousAngularVelocity)/dt;
if (rd)
{
rd->addForce(linAccel, PxForceMode::eACCELERATION, false);
rd->addTorque(angAccel, PxForceMode::eACCELERATION, false);
}
else
{
PX_ASSERT(link);
link->addForce(linAccel, PxForceMode::eACCELERATION, false);
link->addTorque(angAccel, PxForceMode::eACCELERATION, false);
}
}
break;
default:
break;
}
}
} //namespace vehicle2
} //namespace physx

View File

@@ -0,0 +1,188 @@
// 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-2025 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 "vehicle2/PxVehicleParams.h"
#include "vehicle2/rigidBody/PxVehicleRigidBodyParams.h"
#include "vehicle2/suspension/PxVehicleSuspensionHelpers.h"
#include "vehicle2/suspension/PxVehicleSuspensionParams.h"
#include "vehicle2/physxActor/PxVehiclePhysXActorStates.h"
#include "vehicle2/physxActor/PxVehiclePhysXActorHelpers.h"
#include "vehicle2/wheel/PxVehicleWheelParams.h"
#include "cooking/PxCooking.h"
#include "PxPhysics.h"
#include "PxRigidDynamic.h"
#include "PxArticulationReducedCoordinate.h"
#include "PxArticulationLink.h"
#include "PxScene.h"
#include "extensions/PxDefaultStreams.h"
namespace physx
{
namespace vehicle2
{
void createShapes(
const PxVehicleFrame& vehicleFrame,
const PxVehiclePhysXRigidActorShapeParams& rigidActorShapeParams,
const PxVehiclePhysXWheelParams& wheelParams, const PxVehiclePhysXWheelShapeParams& wheelShapeParams,
PxRigidBody* rd,
PxPhysics& physics, const PxCookingParams& params,
PxVehiclePhysXActor& vehiclePhysXActor)
{
//Create a shape for the vehicle body.
{
PxShape* shape = physics.createShape(rigidActorShapeParams.geometry, rigidActorShapeParams.material, true);
shape->setLocalPose(rigidActorShapeParams.localPose);
shape->setFlags(rigidActorShapeParams.flags);
shape->setSimulationFilterData(rigidActorShapeParams.simulationFilterData);
shape->setQueryFilterData(rigidActorShapeParams.queryFilterData);
rd->attachShape(*shape);
shape->release();
}
//Create shapes for wheels.
for (PxU32 i = 0; i < wheelParams.axleDescription.nbWheels; i++)
{
const PxU32 wheelId = wheelParams.axleDescription.wheelIdsInAxleOrder[i];
const PxF32 radius = wheelParams.wheelParams[wheelId].radius;
const PxF32 halfWidth = wheelParams.wheelParams[wheelId].halfWidth;
PxVec3 verts[32];
for (PxU32 k = 0; k < 16; k++)
{
const PxF32 lng = radius * PxCos(k*2.0f*PxPi / 16.0f);
const PxF32 lat = halfWidth;
const PxF32 vrt = radius * PxSin(k*2.0f*PxPi / 16.0f);
const PxVec3 pos0 = vehicleFrame.getFrame()*PxVec3(lng, lat, vrt);
const PxVec3 pos1 = vehicleFrame.getFrame()*PxVec3(lng, -lat, vrt);
verts[2 * k + 0] = pos0;
verts[2 * k + 1] = pos1;
}
// Create descriptor for convex mesh
PxConvexMeshDesc convexDesc;
convexDesc.points.count = 32;
convexDesc.points.stride = sizeof(PxVec3);
convexDesc.points.data = verts;
convexDesc.flags = PxConvexFlag::eCOMPUTE_CONVEX;
PxConvexMesh* convexMesh = NULL;
PxDefaultMemoryOutputStream buf;
if (PxCookConvexMesh(params, convexDesc, buf))
{
PxDefaultMemoryInputData id(buf.getData(), buf.getSize());
convexMesh = physics.createConvexMesh(id);
}
PxConvexMeshGeometry convexMeshGeom(convexMesh);
PxShape* wheelShape = physics.createShape(convexMeshGeom, wheelShapeParams.material, true);
wheelShape->setFlags(wheelShapeParams.flags);
wheelShape->setSimulationFilterData(wheelShapeParams.simulationFilterData);
wheelShape->setQueryFilterData(wheelShapeParams.queryFilterData);
rd->attachShape(*wheelShape);
wheelShape->release();
convexMesh->release();
vehiclePhysXActor.wheelShapes[wheelId] = wheelShape;
}
}
void PxVehiclePhysXActorCreate
(const PxVehicleFrame& vehicleFrame,
const PxVehiclePhysXRigidActorParams& rigidActorParams, const PxTransform& rigidActorCmassLocalPose,
const PxVehiclePhysXRigidActorShapeParams& rigidActorShapeParams,
const PxVehiclePhysXWheelParams& wheelParams, const PxVehiclePhysXWheelShapeParams& wheelShapeParams,
PxPhysics& physics, const PxCookingParams& params,
PxVehiclePhysXActor& vehiclePhysXActor)
{
PxRigidDynamic* rd = physics.createRigidDynamic(PxTransform(PxIdentity));
vehiclePhysXActor.rigidBody = rd;
PxVehiclePhysXActorConfigure(rigidActorParams, rigidActorCmassLocalPose, *rd);
createShapes(vehicleFrame, rigidActorShapeParams, wheelParams, wheelShapeParams, rd, physics, params, vehiclePhysXActor);
}
void PxVehiclePhysXActorConfigure
(const PxVehiclePhysXRigidActorParams& rigidActorParams, const PxTransform& rigidActorCmassLocalPose,
PxRigidBody& rigidBody)
{
rigidBody.setRigidBodyFlag(PxRigidBodyFlag::eKINEMATIC, false);
rigidBody.setCMassLocalPose(rigidActorCmassLocalPose);
rigidBody.setMass(rigidActorParams.rigidBodyParams.mass);
rigidBody.setMassSpaceInertiaTensor(rigidActorParams.rigidBodyParams.moi);
rigidBody.setActorFlag(PxActorFlag::eDISABLE_GRAVITY, true);
rigidBody.setName(rigidActorParams.physxActorName);
}
void PxVehiclePhysXArticulationLinkCreate
(const PxVehicleFrame& vehicleFrame,
const PxVehiclePhysXRigidActorParams& rigidActorParams, const PxTransform& rigidActorCmassLocalPose,
const PxVehiclePhysXRigidActorShapeParams& rigidActorShapeParams,
const PxVehiclePhysXWheelParams& wheelParams, const PxVehiclePhysXWheelShapeParams& wheelShapeParams,
PxPhysics& physics, const PxCookingParams& params,
PxVehiclePhysXActor& vehiclePhysXActor)
{
PxArticulationReducedCoordinate* art = physics.createArticulationReducedCoordinate();
PxArticulationLink* link = art->createLink(NULL, PxTransform(PxIdentity));
vehiclePhysXActor.rigidBody = link;
PxVehiclePhysXActorConfigure(rigidActorParams, rigidActorCmassLocalPose, *link);
createShapes(vehicleFrame, rigidActorShapeParams, wheelParams, wheelShapeParams, link, physics, params, vehiclePhysXActor);
}
void PxVehiclePhysXActorDestroy
(PxVehiclePhysXActor& vehiclePhysXActor)
{
PxRigidDynamic* rd = vehiclePhysXActor.rigidBody->is<PxRigidDynamic>();
PxArticulationLink* link = vehiclePhysXActor.rigidBody->is<PxArticulationLink>();
if(rd)
{
rd->release();
vehiclePhysXActor.rigidBody = NULL;
}
else if(link)
{
PxArticulationReducedCoordinate& articulation = link->getArticulation();
PxScene* scene = articulation.getScene();
if(scene)
{
scene->removeArticulation(articulation);
}
articulation.release();
vehiclePhysXActor.rigidBody = NULL;
}
}
} //namespace vehicle2
} //namespace physx

View File

@@ -0,0 +1,161 @@
// 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-2025 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 "vehicle2/PxVehicleParams.h"
#include "vehicle2/physxConstraints/PxVehiclePhysXConstraintFunctions.h"
#include "vehicle2/physxConstraints/PxVehiclePhysXConstraintHelpers.h"
#include "vehicle2/rigidBody/PxVehicleRigidBodyStates.h"
#include "vehicle2/suspension/PxVehicleSuspensionParams.h"
#include "vehicle2/suspension/PxVehicleSuspensionStates.h"
#include "vehicle2/tire/PxVehicleTireStates.h"
#include "vehicle2/physxConstraints/PxVehiclePhysXConstraintStates.h"
#include "vehicle2/physxConstraints/PxVehiclePhysXConstraintParams.h"
namespace physx
{
namespace vehicle2
{
PX_FORCE_INLINE PxVec3 computeAngular
(const PxVehicleSuspensionParams& suspParams,
const PxVehicleSuspensionComplianceState& suspComplianceState, const PxVehicleRigidBodyState& rigidBodyState,
const PxVec3& direction)
{
const PxVec3 cmOffset = rigidBodyState.pose.rotate(suspParams.suspensionAttachment.transform(suspComplianceState.tireForceAppPoint));
const PxVec3 angular = cmOffset.cross(direction);
// note: not normalized on purpose. The angular component should hold the raw cross product as that
// is needed for the type of constraint we want to set up (see vehicleConstraintSolverPrep).
return angular;
}
PX_FORCE_INLINE PxVec3 computeTireAngular
(const PxVehicleSuspensionParams& suspParams,
const PxVehicleSuspensionComplianceState& suspComplianceState, const PxVehicleTireDirectionState& trDirState, const PxVehicleRigidBodyState& rigidBodyState,
const PxVehicleTireDirectionModes::Enum direction)
{
return computeAngular(suspParams, suspComplianceState, rigidBodyState, trDirState.directions[direction]);
}
PX_FORCE_INLINE PxVec3 computeSuspAngular
(const PxVehicleSuspensionParams& suspParams,
const PxVehicleSuspensionComplianceState& suspComplianceState, const PxVehicleRigidBodyState& rigidBodyState,
const PxVec3& direction)
{
return computeAngular(suspParams, suspComplianceState, rigidBodyState, direction);
}
void PxVehiclePhysXConstraintStatesUpdate
(const PxVehicleSuspensionParams& suspParams,
const PxVehiclePhysXSuspensionLimitConstraintParams& suspensionLimitParams,
const PxVehicleSuspensionState& suspState, const PxVehicleSuspensionComplianceState& suspComplianceState,
const PxVec3& groundPlaneNormal,
const PxReal tireStickyDampingLong, const PxReal tireStickyDampingLat,
const PxVehicleTireDirectionState& trDirState, const PxVehicleTireStickyState& trStickyState,
const PxVehicleRigidBodyState& rigidBodyState,
PxVehiclePhysXConstraintState& cnstrtState)
{
cnstrtState.setToDefault();
//Sticky tire longitudinal
{
const bool isActive = trStickyState.activeStatus[PxVehicleTireDirectionModes::eLONGITUDINAL];
cnstrtState.tireActiveStatus[PxVehicleTireDirectionModes::eLONGITUDINAL] = isActive;
if (isActive)
{
cnstrtState.tireLinears[PxVehicleTireDirectionModes::eLONGITUDINAL] = trDirState.directions[PxVehicleTireDirectionModes::eLONGITUDINAL];
cnstrtState.tireAngulars[PxVehicleTireDirectionModes::eLONGITUDINAL] = computeTireAngular(suspParams, suspComplianceState, trDirState, rigidBodyState, PxVehicleTireDirectionModes::eLONGITUDINAL);
cnstrtState.tireDamping[PxVehicleTireDirectionModes::eLONGITUDINAL] = tireStickyDampingLong;
}
}
//Sticky tire lateral
{
const bool isActive = trStickyState.activeStatus[PxVehicleTireDirectionModes::eLATERAL];
cnstrtState.tireActiveStatus[PxVehicleTireDirectionModes::eLATERAL] = isActive;
if (isActive)
{
cnstrtState.tireLinears[PxVehicleTireDirectionModes::eLATERAL] = trDirState.directions[PxVehicleTireDirectionModes::eLATERAL];
cnstrtState.tireAngulars[PxVehicleTireDirectionModes::eLATERAL] = computeTireAngular(suspParams, suspComplianceState, trDirState, rigidBodyState, PxVehicleTireDirectionModes::eLATERAL);
cnstrtState.tireDamping[PxVehicleTireDirectionModes::eLATERAL] = tireStickyDampingLat;
}
}
//Suspension limit
{
if (suspState.separation >= 0.0f || PxVehiclePhysXSuspensionLimitConstraintParams::eNONE == suspensionLimitParams.directionForSuspensionLimitConstraint)
{
cnstrtState.suspActiveStatus = false;
}
else
{
// To maintain the wheel on the ground plane, the suspension is required to compress beyond the suspension compression limit
// or expand beyond maximum droop (for example, top of wheel being in collision with something).
// We manage the compression up to the limit with a suspension force.
// Everything beyond the limits is managed with an impulse applied to the rigid body via a constraint.
// The constraint attempts to resolve the geometric error declared in the separation state.
// We have two choices:
// 1) apply the impulse along the suspension dir (more like the effect of a bump stop spring)
// 2) apply the impulse along the ground normal (more like the effect of a real tire's contact wtih the ground).
if (PxVehiclePhysXSuspensionLimitConstraintParams::eROAD_GEOMETRY_NORMAL == suspensionLimitParams.directionForSuspensionLimitConstraint)
{
cnstrtState.suspActiveStatus = true;
cnstrtState.suspGeometricError = suspState.separation;
cnstrtState.suspLinear = groundPlaneNormal;
cnstrtState.suspAngular = computeSuspAngular(suspParams, suspComplianceState, rigidBodyState, groundPlaneNormal);
cnstrtState.restitution = suspensionLimitParams.restitution;
}
else
{
const PxVec3 suspDirWorldFrame = rigidBodyState.pose.rotate(suspParams.suspensionTravelDir);
const PxF32 projection = groundPlaneNormal.dot(suspDirWorldFrame);
if (projection != 0.0f)
{
cnstrtState.suspActiveStatus = true;
cnstrtState.suspGeometricError = suspState.separation;
const PxVec3 suspLinear = suspDirWorldFrame * PxSign(projection);
cnstrtState.suspLinear = suspLinear;
cnstrtState.suspAngular = computeSuspAngular(suspParams, suspComplianceState, rigidBodyState, suspLinear);
cnstrtState.restitution = suspensionLimitParams.restitution;
}
else
{
cnstrtState.suspActiveStatus = false;
}
}
}
}
}
} //namespace vehicle2
} //namespace physx

View File

@@ -0,0 +1,112 @@
// 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-2025 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/PxAllocator.h"
#include "vehicle2/PxVehicleParams.h"
#include "vehicle2/physxConstraints/PxVehiclePhysXConstraintStates.h"
#include "vehicle2/physxConstraints/PxVehiclePhysXConstraintHelpers.h"
#include "vehicle2/physxActor/PxVehiclePhysXActorStates.h"
#include "PxConstraintDesc.h"
#include "PxConstraint.h"
#include "PxPhysics.h"
#include "PxRigidDynamic.h"
#include "PxArticulationLink.h"
namespace physx
{
namespace vehicle2
{
PxConstraintShaderTable gVehicleConstraintTable =
{
vehicleConstraintSolverPrep,
visualiseVehicleConstraint,
PxConstraintFlag::Enum(0)
};
void PxVehicleConstraintsCreate(
const PxVehicleAxleDescription& axleDescription,
PxPhysics& physics, PxRigidBody& physxActor,
PxVehiclePhysXConstraints& vehicleConstraints)
{
vehicleConstraints.setToDefault();
//Each PxConstraint has a limit of 12 1d constraints.
//Each wheel has longitudinal, lateral and suspension limit degrees of freedom.
//This sums up to 3 dofs per wheel and 12 dofs per 4 wheels.
//4 wheels therefore equals 1 PxConstraint
//Iterate over each block of 4 wheels and create a PxConstraints for each block of 4.
PxU32 constraintIndex = 0;
for(PxU32 i = 0; i < axleDescription.getNbWheels(); i+= PxVehiclePhysXConstraintLimits::eNB_WHEELS_PER_PXCONSTRAINT)
{
void* memory = PX_ALLOC(sizeof(PxVehicleConstraintConnector), PxVehicleConstraintConnector);
PxVehicleConstraintConnector* pxConnector = PX_PLACEMENT_NEW(memory, PxVehicleConstraintConnector)(vehicleConstraints.constraintStates + i);
PxConstraint* pxConstraint = physics.createConstraint(&physxActor, NULL, *pxConnector, gVehicleConstraintTable, sizeof(PxVehiclePhysXConstraintState)*PxVehiclePhysXConstraintLimits::eNB_WHEELS_PER_PXCONSTRAINT);
vehicleConstraints.constraints[constraintIndex] = pxConstraint;
vehicleConstraints.constraintConnectors[constraintIndex] = pxConnector;
constraintIndex++;
}
}
void PxVehicleConstraintsDirtyStateUpdate
(PxVehiclePhysXConstraints& vehicleConstraints)
{
for (PxU32 i = 0; i < PxVehiclePhysXConstraintLimits::eNB_CONSTRAINTS_PER_VEHICLE; i++)
{
if (vehicleConstraints.constraints[i])
{
vehicleConstraints.constraints[i]->markDirty();
}
}
}
void PxVehicleConstraintsDestroy(
PxVehiclePhysXConstraints& vehicleConstraints)
{
for (PxU32 i = 0; i < PxVehiclePhysXConstraintLimits::eNB_CONSTRAINTS_PER_VEHICLE; i++)
{
if (vehicleConstraints.constraints[i])
{
vehicleConstraints.constraints[i]->release();
vehicleConstraints.constraints[i] = NULL;
}
if (vehicleConstraints.constraintConnectors[i])
{
vehicleConstraints.constraintConnectors[i]->~PxVehicleConstraintConnector();
PX_FREE(vehicleConstraints.constraintConnectors[i]);
vehicleConstraints.constraintConnectors[i] = NULL;
}
}
}
} //namespace vehicle2
} //namespace physx

View File

@@ -0,0 +1,231 @@
// 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-2025 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 "vehicle2/PxVehicleParams.h"
#include "vehicle2/roadGeometry/PxVehicleRoadGeometryState.h"
#include "vehicle2/physxRoadGeometry/PxVehiclePhysXRoadGeometryFunctions.h"
#include "vehicle2/physxRoadGeometry/PxVehiclePhysXRoadGeometryParams.h"
#include "vehicle2/physxRoadGeometry/PxVehiclePhysXRoadGeometryState.h"
#include "vehicle2/rigidBody/PxVehicleRigidBodyStates.h"
#include "vehicle2/suspension/PxVehicleSuspensionHelpers.h"
#include "extensions/PxRigidBodyExt.h"
#include "PxScene.h"
#include "PxShape.h"
#include "PxRigidActor.h"
#include "PxMaterial.h"
#include "geometry/PxMeshScale.h"
#include "geometry/PxConvexMeshGeometry.h"
#include "geometry/PxGeometryQuery.h"
namespace physx
{
namespace vehicle2
{
PX_FORCE_INLINE PxF32 computeMaterialFriction(const PxShape* hitShape, const PxU32 hitFaceIndex,
const PxVehiclePhysXMaterialFrictionParams& materialFrictionParams, PxMaterial*& hitMaterial)
{
PxBaseMaterial* baseMaterial = hitShape->getMaterialFromInternalFaceIndex(hitFaceIndex);
PX_ASSERT(!baseMaterial || baseMaterial->getConcreteType()==PxConcreteType::eMATERIAL);
hitMaterial = static_cast<PxMaterial*>(baseMaterial);
PxReal hitFriction = materialFrictionParams.defaultFriction;
for(PxU32 i = 0; i < materialFrictionParams.nbMaterialFrictions; i++)
{
if(materialFrictionParams.materialFrictions[i].material == hitMaterial)
{
hitFriction = materialFrictionParams.materialFrictions[i].friction;
break;
}
}
return hitFriction;
}
PX_FORCE_INLINE PxVec3 computeVelocity(const PxRigidActor& actor, const PxVec3& hitPoint)
{
return actor.is<PxRigidBody>() ? PxRigidBodyExt::getVelocityAtPos(*actor.is<PxRigidBody>(), hitPoint) : PxVec3(PxZero);
}
template<typename THitBuffer>
PX_FORCE_INLINE void copyHitInfo(const THitBuffer& hitBuffer, PxMaterial* hitMaterial,
PxVehiclePhysXRoadGeometryQueryState& physxRoadGeometryState)
{
physxRoadGeometryState.actor = hitBuffer.actor;
physxRoadGeometryState.shape = hitBuffer.shape;
physxRoadGeometryState.material = hitMaterial;
physxRoadGeometryState.hitPosition = hitBuffer.position;
}
void PxVehiclePhysXRoadGeometryQueryUpdate
(const PxVehicleWheelParams& wheelParams, const PxVehicleSuspensionParams& suspParams,
const PxVehiclePhysXRoadGeometryQueryType::Enum queryType,
PxQueryFilterCallback* filterCallback, const PxQueryFilterData& filterData,
const PxVehiclePhysXMaterialFrictionParams& materialFrictionParams,
const PxF32 steerAngle, const PxVehicleRigidBodyState& rigidBodyState,
const PxScene& scene, const PxConvexMesh* unitCylinderSweepMesh,
const PxVehicleFrame& frame,
PxVehicleRoadGeometryState& roadGeomState,
PxVehiclePhysXRoadGeometryQueryState* physxRoadGeometryState)
{
if(PxVehiclePhysXRoadGeometryQueryType::eRAYCAST == queryType)
{
//Assume no hits until we know otherwise.
roadGeomState.setToDefault();
//Compute the start pos, dir and length of raycast.
PxVec3 v, w;
PxF32 dist;
PxVehicleComputeSuspensionRaycast(frame, wheelParams, suspParams, steerAngle, rigidBodyState.pose, v, w, dist);
//Perform the raycast.
PxRaycastBuffer buff;
scene.raycast(v, w, dist, buff, PxHitFlag::eDEFAULT, filterData, filterCallback);
//Process the raycast result.
if(buff.hasBlock && buff.block.distance != 0.0f)
{
const PxPlane hitPlane(v + w * buff.block.distance, buff.block.normal);
roadGeomState.plane = hitPlane;
roadGeomState.hitState = true;
PxMaterial* hitMaterial;
roadGeomState.friction = computeMaterialFriction(buff.block.shape, buff.block.faceIndex, materialFrictionParams,
hitMaterial);
roadGeomState.velocity = computeVelocity(*buff.block.actor, buff.block.position);
if (physxRoadGeometryState)
{
copyHitInfo(buff.block, hitMaterial, *physxRoadGeometryState);
}
}
else
{
if (physxRoadGeometryState)
physxRoadGeometryState->setToDefault();
}
}
else if(PxVehiclePhysXRoadGeometryQueryType::eSWEEP == queryType)
{
PX_ASSERT(unitCylinderSweepMesh);
//Assume no hits until we know otherwise.
roadGeomState.setToDefault();
//Compute the start pose, dir and length of sweep.
PxTransform T;
PxVec3 w;
PxF32 dist;
PxVehicleComputeSuspensionSweep(frame, suspParams, steerAngle, rigidBodyState.pose, T, w, dist);
//Scale the unit cylinder.
const PxVec3 scale = PxVehicleComputeTranslation(frame, wheelParams.radius, wheelParams.halfWidth, wheelParams.radius).abs();
const PxMeshScale meshScale(scale, PxQuat(PxIdentity));
const PxConvexMeshGeometry convMeshGeom(const_cast<PxConvexMesh*>(unitCylinderSweepMesh), meshScale);
//Perform the sweep.
PxSweepBuffer buff;
scene.sweep(convMeshGeom, T, w, dist, buff, PxHitFlag::eDEFAULT | PxHitFlag::eMTD, filterData, filterCallback);
//Process the sweep result.
if (buff.hasBlock && buff.block.distance >= 0.0f)
{
//Sweep started outside scene geometry.
const PxPlane hitPlane(buff.block.position, buff.block.normal);
roadGeomState.plane = hitPlane;
roadGeomState.hitState = true;
PxMaterial* hitMaterial;
roadGeomState.friction = computeMaterialFriction(buff.block.shape, buff.block.faceIndex, materialFrictionParams,
hitMaterial);
roadGeomState.velocity = computeVelocity(*buff.block.actor, buff.block.position);
if (physxRoadGeometryState)
{
copyHitInfo(buff.block, hitMaterial, *physxRoadGeometryState);
}
}
else if (buff.hasBlock && buff.block.distance < 0.0f)
{
//The sweep started inside scene geometry.
//We want to have another go but this time starting outside the hit geometry because this is the most reliable
//way to get a hit plane.
//-buff.block.distance is the distance we need to move along buff.block.normal to be outside the hit geometry.
//Note that buff.block.distance can be a vanishingly small number. Moving along the normal by a vanishingly
//small number might not push us out of overlap due to numeric precision of the overlap test.
//We want to move a numerically significant distance to guarantee that we change the overlap status
//at the start pose of the sweep.
//We achieve this by choosing a minimum translation that is numerically significant.
//Any number will do but we choose the wheel radius because this ought to be a numerically significant value.
//We're only sweeping against the hit shape and not against the scene
//so we don't risk hitting other stuff by moving a numerically significant distance.
const PxVec3 unitDir = -buff.block.normal;
const PxF32 maxDist = PxMax(wheelParams.radius, -buff.block.distance);
const PxGeometry& geom0 = convMeshGeom;
const PxTransform pose0(T.p + buff.block.normal*(maxDist*1.01f), T.q);
const PxGeometry& geom1 = buff.block.shape->getGeometry();
const PxTransform pose1 = buff.block.actor->getGlobalPose()*buff.block.shape->getLocalPose();
PxGeomSweepHit buff2;
const bool b2 = PxGeometryQuery::sweep(
unitDir, maxDist*1.02f,
geom0, pose0, geom1, pose1, buff2, PxHitFlag::eDEFAULT | PxHitFlag::eMTD);
if (b2 && buff2.distance > 0.0f)
{
//Sweep started outside scene geometry.
const PxPlane hitPlane(buff2.position, buff2.normal);
roadGeomState.plane = hitPlane;
roadGeomState.hitState = true;
PxMaterial* hitMaterial;
roadGeomState.friction = computeMaterialFriction(buff.block.shape, buff.block.faceIndex, materialFrictionParams,
hitMaterial);
roadGeomState.velocity = computeVelocity(*buff.block.actor, buff.block.position);
if (physxRoadGeometryState)
{
copyHitInfo(buff.block, hitMaterial, *physxRoadGeometryState);
}
}
else
{
if (physxRoadGeometryState)
physxRoadGeometryState->setToDefault();
}
}
else
{
if (physxRoadGeometryState)
physxRoadGeometryState->setToDefault();
}
}
}
} //namespace vehicle2
} //namespace physx

View File

@@ -0,0 +1,87 @@
// 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-2025 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 "vehicle2/PxVehicleParams.h"
#include "vehicle2/physxRoadGeometry/PxVehiclePhysXRoadGeometryHelpers.h"
#include "cooking/PxConvexMeshDesc.h"
#include "cooking/PxCooking.h"
#include "extensions/PxDefaultStreams.h"
#include "PxPhysics.h"
namespace physx
{
namespace vehicle2
{
PxConvexMesh* PxVehicleUnitCylinderSweepMeshCreate
(const PxVehicleFrame& runtimeFrame, PxPhysics& physics, const PxCookingParams& params)
{
const PxMat33 mat33 = runtimeFrame.getFrame();
const PxQuat frame(mat33);
const PxReal radius = 1.0f;
const PxReal halfWidth = 1.0f;
#define NB_CIRCUMFERENCE_POINTS 64
PxVec3 points[2 * NB_CIRCUMFERENCE_POINTS];
for (PxU32 i = 0; i < NB_CIRCUMFERENCE_POINTS; i++)
{
const PxF32 cosTheta = PxCos(i * PxPi * 2.0f / float(NB_CIRCUMFERENCE_POINTS));
const PxF32 sinTheta = PxSin(i * PxPi * 2.0f / float(NB_CIRCUMFERENCE_POINTS));
const PxF32 x = radius * cosTheta;
const PxF32 z = radius * sinTheta;
points[2 * i + 0] = frame.rotate(PxVec3(x, -halfWidth, z));
points[2 * i + 1] = frame.rotate(PxVec3(x, +halfWidth, z));
}
// Create descriptor for convex mesh
PxConvexMeshDesc convexDesc;
convexDesc.points.count = sizeof(points)/sizeof(PxVec3);
convexDesc.points.stride = sizeof(PxVec3);
convexDesc.points.data = points;
convexDesc.flags = PxConvexFlag::eCOMPUTE_CONVEX;
PxConvexMesh* convexMesh = NULL;
PxDefaultMemoryOutputStream buf;
if (PxCookConvexMesh(params, convexDesc, buf))
{
PxDefaultMemoryInputData id(buf.getData(), buf.getSize());
convexMesh = physics.createConvexMesh(id);
}
return convexMesh;
}
void PxVehicleUnitCylinderSweepMeshDestroy(PxConvexMesh* mesh)
{
mesh->release();
}
} //namespace vehicle2
} //namespace physx

View File

@@ -0,0 +1,168 @@
// 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-2025 NVIDIA Corporation. All rights reserved.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
#pragma once
#include "vehicle2/pvd/PxVehiclePvdHelpers.h"
#include "VhPvdWriter.h"
#if !PX_DOXYGEN
namespace physx
{
namespace vehicle2
{
#endif
struct PxVehiclePvdAttributeHandles
{
#if PX_SUPPORT_OMNI_PVD
/////////////////////
//RIGID BODY
/////////////////////////
RigidBodyParams rigidBodyParams;
RigidBodyState rigidBodyState;
/////////////////////////
//SUSP STATE CALC PARAMS
/////////////////////////
SuspStateCalcParams suspStateCalcParams;
/////////////////////////
//CONTROL ATTRIBUTES
/////////////////////////
WheelResponseParams brakeCommandResponseParams;
WheelResponseParams steerCommandResponseParams;
AckermannParams ackermannParams;
WheelResponseStates brakeCommandResponseStates;
WheelResponseStates steerCommandResponseStates;
/////////////////////////////////
//WHEEL ATTACHMENT ATTRIBUTES
/////////////////////////////////
WheelParams wheelParams;
WheelActuationState wheelActuationState;
WheelRigidBody1dState wheelRigidBody1dState;
WheelLocalPoseState wheelLocalPoseState;
RoadGeometryState roadGeomState;
SuspParams suspParams;
SuspCompParams suspCompParams;
SuspForceParams suspForceParams;
SuspState suspState;
SuspCompState suspCompState;
SuspForce suspForce;
TireParams tireParams;
TireDirectionState tireDirectionState;
TireSpeedState tireSpeedState;
TireSlipState tireSlipState;
TireStickyState tireStickyState;
TireGripState tireGripState;
TireCamberState tireCamberState;
TireForce tireForce;
WheelAttachment wheelAttachment;
///////////////////////
//ANTIROLL BARS
///////////////////////
AntiRollParams antiRollParams;
AntiRollForce antiRollForce;
///////////////////////////////////
//DIRECT DRIVETRAIN
///////////////////////////////////
DirectDriveCommandState directDriveCommandState;
DirectDriveTransmissionCommandState directDriveTransmissionCommandState;
WheelResponseParams directDriveThrottleCommandResponseParams;
DirectDriveThrottleResponseState directDriveThrottleCommandResponseState;
DirectDrivetrain directDrivetrain;
//////////////////////////////////
//ENGINE DRIVETRAIN ATTRIBUTES
//////////////////////////////////
EngineDriveCommandState engineDriveCommandState;
EngineDriveTransmissionCommandState engineDriveTransmissionCommandState;
TankDriveTransmissionCommandState tankDriveTransmissionCommandState;
ClutchResponseParams clutchCommandResponseParams;
ClutchParams clutchParams;
EngineParams engineParams;
GearboxParams gearboxParams;
AutoboxParams autoboxParams;
MultiWheelDiffParams multiwheelDiffParams;
FourWheelDiffParams fourwheelDiffParams;
TankDiffParams tankDiffParams;
ClutchResponseState clutchResponseState;
ThrottleResponseState throttleResponseState;
EngineState engineState;
GearboxState gearboxState;
AutoboxState autoboxState;
DiffState diffState;
ClutchSlipState clutchSlipState;
EngineDrivetrain engineDrivetrain;
//////////////////////////////////////
//PHYSX WHEEL ATTACHMENT INTEGRATION
//////////////////////////////////////
PhysXSuspensionLimitConstraintParams physxSuspLimitConstraintParams;
PhysXWheelShape physxWheelShape;
PhysXRoadGeomState physxRoadGeomState;
PhysXConstraintState physxConstraintState;
PhysXMaterialFriction physxMaterialFriction;
PhysXWheelAttachment physxWheelAttachment;
////////////////////
//PHYSX RIGID ACTOR
////////////////////
PhysXRoadGeometryQueryParams physxRoadGeometryQueryParams;
PhysXRigidActor physxRigidActor;
PhysXSteerState physxSteerState;
//////////////////////////////////
//VEHICLE ATTRIBUTES
//////////////////////////////////
Vehicle vehicle;
#endif
};
#if !PX_DOXYGEN
} // namespace vehicle2
} // namespace physx
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,446 @@
// 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-2025 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 "vehicle2/pvd/PxVehiclePvdHelpers.h"
#include "foundation/PxAllocatorCallback.h"
#include "VhPvdAttributeHandles.h"
#include "VhPvdObjectHandles.h"
namespace physx
{
namespace vehicle2
{
#if PX_SUPPORT_OMNI_PVD
///////////////////////////////
//ATTRIBUTE REGISTRATION
///////////////////////////////
PxVehiclePvdAttributeHandles* PxVehiclePvdAttributesCreate(PxAllocatorCallback& allocator, OmniPvdWriter& omniWriter)
{
PxVehiclePvdAttributeHandles* attributeHandles =
reinterpret_cast<PxVehiclePvdAttributeHandles*>(
allocator.allocate(sizeof(PxVehiclePvdAttributeHandles), "PxVehiclePvdAttributeHandles", PX_FL));
PxMemZero(attributeHandles, sizeof(PxVehiclePvdAttributeHandles));
//Rigid body
attributeHandles->rigidBodyParams = registerRigidBodyParams(omniWriter);
attributeHandles->rigidBodyState = registerRigidBodyState(omniWriter);
//Susp state calc params.
attributeHandles->suspStateCalcParams = registerSuspStateCalcParams(omniWriter);
//Controls
attributeHandles->steerCommandResponseParams = registerSteerResponseParams(omniWriter);
attributeHandles->brakeCommandResponseParams = registerBrakeResponseParams(omniWriter);
attributeHandles->steerCommandResponseStates = registerSteerResponseStates(omniWriter);
attributeHandles->brakeCommandResponseStates = registerBrakeResponseStates(omniWriter);
attributeHandles->ackermannParams = registerAckermannParams(omniWriter);
//Wheel attachment
attributeHandles->wheelParams = registerWheelParams(omniWriter);
attributeHandles->wheelActuationState = registerWheelActuationState(omniWriter);
attributeHandles->wheelRigidBody1dState = registerWheelRigidBody1dState(omniWriter);
attributeHandles->wheelLocalPoseState = registerWheelLocalPoseState(omniWriter);
attributeHandles->roadGeomState = registerRoadGeomState(omniWriter);
attributeHandles->suspParams = registerSuspParams(omniWriter);
attributeHandles->suspCompParams = registerSuspComplianceParams(omniWriter);
attributeHandles->suspForceParams = registerSuspForceParams(omniWriter);
attributeHandles->suspState = registerSuspState(omniWriter);
attributeHandles->suspCompState = registerSuspComplianceState(omniWriter);
attributeHandles->suspForce = registerSuspForce(omniWriter);
attributeHandles->tireParams = registerTireParams(omniWriter);
attributeHandles->tireDirectionState = registerTireDirectionState(omniWriter);
attributeHandles->tireSpeedState = registerTireSpeedState(omniWriter);
attributeHandles->tireSlipState = registerTireSlipState(omniWriter);
attributeHandles->tireStickyState = registerTireStickyState(omniWriter);
attributeHandles->tireGripState = registerTireGripState(omniWriter);
attributeHandles->tireCamberState = registerTireCamberState(omniWriter);
attributeHandles->tireForce = registerTireForce(omniWriter);
attributeHandles->wheelAttachment = registerWheelAttachment(omniWriter);
//Antiroll
attributeHandles->antiRollParams = registerAntiRollParams(omniWriter);
attributeHandles->antiRollForce = registerAntiRollForce(omniWriter);
//Direct drivetrain
attributeHandles->directDriveThrottleCommandResponseParams = registerDirectDriveThrottleResponseParams(omniWriter);
attributeHandles->directDriveCommandState = registerDirectDriveCommandState(omniWriter);
attributeHandles->directDriveTransmissionCommandState = registerDirectDriveTransmissionCommandState(omniWriter);
attributeHandles->directDriveThrottleCommandResponseState = registerDirectDriveThrottleResponseState(omniWriter);
attributeHandles->directDrivetrain = registerDirectDrivetrain(omniWriter);
//Engine drivetrain
attributeHandles->engineDriveCommandState = registerEngineDriveCommandState(omniWriter);
attributeHandles->engineDriveTransmissionCommandState = registerEngineDriveTransmissionCommandState(omniWriter);
attributeHandles->tankDriveTransmissionCommandState = registerTankDriveTransmissionCommandState(omniWriter,
attributeHandles->engineDriveTransmissionCommandState.CH);
attributeHandles->clutchCommandResponseParams = registerClutchResponseParams(omniWriter);
attributeHandles->clutchParams = registerClutchParams(omniWriter);
attributeHandles->engineParams = registerEngineParams(omniWriter);
attributeHandles->gearboxParams = registerGearboxParams(omniWriter);
attributeHandles->autoboxParams = registerAutoboxParams(omniWriter);
attributeHandles->multiwheelDiffParams = registerMultiWheelDiffParams(omniWriter);
attributeHandles->fourwheelDiffParams = registerFourWheelDiffParams(omniWriter, attributeHandles->multiwheelDiffParams.CH);
attributeHandles->tankDiffParams = registerTankDiffParams(omniWriter, attributeHandles->multiwheelDiffParams.CH);
attributeHandles->clutchResponseState = registerClutchResponseState(omniWriter);
attributeHandles->throttleResponseState = registerThrottleResponseState(omniWriter);
attributeHandles->engineState = registerEngineState(omniWriter);
attributeHandles->gearboxState = registerGearboxState(omniWriter);
attributeHandles->autoboxState = registerAutoboxState(omniWriter);
attributeHandles->diffState = registerDiffState(omniWriter);
attributeHandles->clutchSlipState = registerClutchSlipState(omniWriter);
attributeHandles->engineDrivetrain = registerEngineDrivetrain(omniWriter);
//Physx wheel attachment
attributeHandles->physxSuspLimitConstraintParams = registerSuspLimitConstraintParams(omniWriter);
attributeHandles->physxWheelShape = registerPhysXWheelShape(omniWriter);
attributeHandles->physxRoadGeomState = registerPhysXRoadGeomState(omniWriter);
attributeHandles->physxConstraintState = registerPhysXConstraintState(omniWriter);
attributeHandles->physxWheelAttachment = registerPhysXWheelAttachment(omniWriter);
attributeHandles->physxMaterialFriction = registerPhysXMaterialFriction(omniWriter);
//Physx rigid actor
attributeHandles->physxRoadGeometryQueryParams = registerPhysXRoadGeometryQueryParams(omniWriter);
attributeHandles->physxRigidActor = registerPhysXRigidActor(omniWriter);
attributeHandles->physxSteerState = registerPhysXSteerState(omniWriter);
//Vehicle
attributeHandles->vehicle = registerVehicle(omniWriter);
return attributeHandles;
}
///////////////////////////////
//ATTRIBUTE DESTRUCTION
///////////////////////////////
void PxVehiclePvdAttributesRelease(PxAllocatorCallback& allocator, PxVehiclePvdAttributeHandles& attributeHandles)
{
allocator.deallocate(&attributeHandles);
}
////////////////////////////////////////
//OBJECT REGISTRATION
////////////////////////////////////////
PxVehiclePvdObjectHandles* PxVehiclePvdObjectCreate
(const PxU32 nbWheels, const PxU32 nbAntirolls, const PxU32 maxNbPhysXMaterialFrictions,
const OmniPvdContextHandle contextHandle,
PxAllocatorCallback& allocator)
{
const PxU32 byteSize =
sizeof(PxVehiclePvdObjectHandles) +
sizeof(OmniPvdObjectHandle)*nbWheels*(
1 + //OmniPvdObjectHandle* wheelAttachmentOHs;
1 + //OmniPvdObjectHandle* wheelParamsOHs;
1 + //OmniPvdObjectHandle* wheelActuationStateOHs;
1 + //OmniPvdObjectHandle* wheelRigidBody1dStateOHs;
1 + //OmniPvdObjectHandle* wheelLocalPoseStateOHs;
1 + //OmniPvdObjectHandle* roadGeomStateOHs;
1 + //OmniPvdObjectHandle* suspParamsOHs;
1 + //OmniPvdObjectHandle* suspCompParamsOHs;
1 + //OmniPvdObjectHandle* suspForceParamsOHs;
1 + //OmniPvdObjectHandle* suspStateOHs;
1 + //OmniPvdObjectHandle* suspCompStateOHs;
1 + //OmniPvdObjectHandle* suspForceOHs;
1 + //OmniPvdObjectHandle* tireParamsOHs;
1 + //OmniPvdObjectHandle* tireDirectionStateOHs;
1 + //OmniPvdObjectHandle* tireSpeedStateOHs;
1 + //OmniPvdObjectHandle* tireSlipStateOHs;
1 + //OmniPvdObjectHandle* tireStickyStateOHs;
1 + //OmniPvdObjectHandle* tireGripStateOHs;
1 + //OmniPvdObjectHandle* tireCamberStateOHs;
1 + //OmniPvdObjectHandle* tireForceOHs;
1 + //OmniPvdObjectHandle* physxWheelAttachmentOHs;
1 + //OmniPvdObjectHandle* physxWheelShapeOHs;
1 + //OmniPvdObjectHandle* physxConstraintParamOHs;
1 + //OmniPvdObjectHandle* physxConstraintStateOHs;
1 + //OmniPvdObjectHandle* physxRoadGeomStateOHs;
1 + //OmniPvdObjectHandle* physxMaterialFrictionSetOHs;
maxNbPhysXMaterialFrictions + //OmniPvdObjectHandle* physxMaterialFrictionOHs;
1) + //OmniPvdObjectHandle* physxRoadGeomQueryFilterDataOHs;
sizeof(OmniPvdObjectHandle)*nbAntirolls*(
1); //OmniPvdObjectHandle* antiRollParamOHs
PxU8* buffer = reinterpret_cast<PxU8*>(allocator.allocate(byteSize, "PxVehiclePvdObjectHandles", PX_FL));
#if PX_ENABLE_ASSERTS
PxU8* start = buffer;
#endif
PxMemZero(buffer, byteSize);
PxVehiclePvdObjectHandles* objectHandles = reinterpret_cast<PxVehiclePvdObjectHandles*>(buffer);
buffer += sizeof(PxVehiclePvdObjectHandles);
if(nbWheels != 0)
{
objectHandles->wheelAttachmentOHs = reinterpret_cast<OmniPvdObjectHandle*>(buffer);
buffer += sizeof(OmniPvdObjectHandle)*nbWheels;
objectHandles->wheelParamsOHs = reinterpret_cast<OmniPvdObjectHandle*>(buffer);
buffer += sizeof(OmniPvdObjectHandle)*nbWheels;
objectHandles->wheelActuationStateOHs = reinterpret_cast<OmniPvdObjectHandle*>(buffer);
buffer += sizeof(OmniPvdObjectHandle)*nbWheels;
objectHandles->wheelRigidBody1dStateOHs = reinterpret_cast<OmniPvdObjectHandle*>(buffer);
buffer += sizeof(OmniPvdObjectHandle)*nbWheels;
objectHandles->wheelLocalPoseStateOHs = reinterpret_cast<OmniPvdObjectHandle*>(buffer);
buffer += sizeof(OmniPvdObjectHandle)*nbWheels;
objectHandles->roadGeomStateOHs = reinterpret_cast<OmniPvdObjectHandle*>(buffer);
buffer += sizeof(OmniPvdObjectHandle)*nbWheels;
objectHandles->suspParamsOHs = reinterpret_cast<OmniPvdObjectHandle*>(buffer);
buffer += sizeof(OmniPvdObjectHandle)*nbWheels;
objectHandles->suspCompParamsOHs = reinterpret_cast<OmniPvdObjectHandle*>(buffer);
buffer += sizeof(OmniPvdObjectHandle)*nbWheels;
objectHandles->suspForceParamsOHs = reinterpret_cast<OmniPvdObjectHandle*>(buffer);
buffer += sizeof(OmniPvdObjectHandle)*nbWheels;
objectHandles->suspStateOHs = reinterpret_cast<OmniPvdObjectHandle*>(buffer);
buffer += sizeof(OmniPvdObjectHandle)*nbWheels;
objectHandles->suspCompStateOHs = reinterpret_cast<OmniPvdObjectHandle*>(buffer);
buffer += sizeof(OmniPvdObjectHandle)*nbWheels;
objectHandles->suspForceOHs = reinterpret_cast<OmniPvdObjectHandle*>(buffer);
buffer += sizeof(OmniPvdObjectHandle)*nbWheels;
objectHandles->tireParamsOHs = reinterpret_cast<OmniPvdObjectHandle*>(buffer);
buffer += sizeof(OmniPvdObjectHandle)*nbWheels;
objectHandles->tireDirectionStateOHs = reinterpret_cast<OmniPvdObjectHandle*>(buffer);
buffer += sizeof(OmniPvdObjectHandle)*nbWheels;
objectHandles->tireSpeedStateOHs = reinterpret_cast<OmniPvdObjectHandle*>(buffer);
buffer += sizeof(OmniPvdObjectHandle)*nbWheels;
objectHandles->tireSlipStateOHs = reinterpret_cast<OmniPvdObjectHandle*>(buffer);
buffer += sizeof(OmniPvdObjectHandle)*nbWheels;
objectHandles->tireStickyStateOHs = reinterpret_cast<OmniPvdObjectHandle*>(buffer);
buffer += sizeof(OmniPvdObjectHandle)*nbWheels;
objectHandles->tireGripStateOHs = reinterpret_cast<OmniPvdObjectHandle*>(buffer);
buffer += sizeof(OmniPvdObjectHandle)*nbWheels;
objectHandles->tireCamberStateOHs = reinterpret_cast<OmniPvdObjectHandle*>(buffer);
buffer += sizeof(OmniPvdObjectHandle)*nbWheels;
objectHandles->tireForceOHs = reinterpret_cast<OmniPvdObjectHandle*>(buffer);
buffer += sizeof(OmniPvdObjectHandle)*nbWheels;
objectHandles->physxWheelAttachmentOHs = reinterpret_cast<OmniPvdObjectHandle*>(buffer);
buffer += sizeof(OmniPvdObjectHandle)*nbWheels;
objectHandles->physxConstraintParamOHs = reinterpret_cast<OmniPvdObjectHandle*>(buffer);
buffer += sizeof(OmniPvdObjectHandle)*nbWheels;
objectHandles->physxWheelShapeOHs = reinterpret_cast<OmniPvdObjectHandle*>(buffer);
buffer += sizeof(OmniPvdObjectHandle)*nbWheels;
objectHandles->physxConstraintStateOHs = reinterpret_cast<OmniPvdObjectHandle*>(buffer);
buffer += sizeof(OmniPvdObjectHandle)*nbWheels;
objectHandles->physxRoadGeomStateOHs = reinterpret_cast<OmniPvdObjectHandle*>(buffer);
buffer += sizeof(OmniPvdObjectHandle)*nbWheels;
if(maxNbPhysXMaterialFrictions != 0)
{
objectHandles->physxMaterialFrictionSetOHs = reinterpret_cast<OmniPvdObjectHandle*>(buffer);
buffer += sizeof(OmniPvdObjectHandle)*nbWheels;
objectHandles->physxMaterialFrictionOHs = reinterpret_cast<OmniPvdObjectHandle*>(buffer);
buffer += sizeof(OmniPvdObjectHandle)*nbWheels*maxNbPhysXMaterialFrictions;
}
objectHandles->physxRoadGeomQueryFilterDataOHs = reinterpret_cast<OmniPvdObjectHandle*>(buffer);
buffer += sizeof(OmniPvdObjectHandle)*nbWheels;
}
if(nbAntirolls != 0)
{
objectHandles->antiRollParamOHs = reinterpret_cast<OmniPvdObjectHandle*>(buffer);
buffer += sizeof(OmniPvdObjectHandle)*nbAntirolls;
}
objectHandles->nbWheels = nbWheels;
objectHandles->nbPhysXMaterialFrictions = maxNbPhysXMaterialFrictions;
objectHandles->nbAntirolls = nbAntirolls;
objectHandles->contextHandle = contextHandle;
PX_ASSERT((start + byteSize) == buffer);
return objectHandles;
}
////////////////////////////////////
//OBJECT DESTRUCTION
////////////////////////////////////
PX_FORCE_INLINE void destroyObject
(OmniPvdWriter& omniWriter, OmniPvdContextHandle ch,
OmniPvdObjectHandle oh)
{
// note: "0" needs to be replaced with a marker for invalid object handle as soon as PVD
// provides it (and PxVehiclePvdObjectCreate needs to initialize accordingly or
// compile time assert that the value is 0 for now)
if(oh != 0)
omniWriter.destroyObject(ch, oh);
}
void PxVehiclePvdObjectRelease
(OmniPvdWriter& ow, PxAllocatorCallback& allocator, PxVehiclePvdObjectHandles& oh)
{
const OmniPvdContextHandle ch = oh.contextHandle;
//rigid body
destroyObject(ow, ch, oh.rigidBodyParamsOH);
destroyObject(ow, ch, oh.rigidBodyStateOH);
//susp state calc params
destroyObject(ow, ch, oh.suspStateCalcParamsOH);
//controls
for(PxU32 i = 0; i < 2; i++)
{
destroyObject(ow, ch, oh.brakeResponseParamOHs[i]);
}
destroyObject(ow, ch, oh.steerResponseParamsOH);
destroyObject(ow, ch, oh.brakeResponseStateOH);
destroyObject(ow, ch, oh.steerResponseStateOH);
destroyObject(ow, ch, oh.ackermannParamsOH);
//Wheel attachments
for(PxU32 i = 0; i < oh.nbWheels; i++)
{
destroyObject(ow, ch, oh.wheelParamsOHs[i]);
destroyObject(ow, ch, oh.wheelActuationStateOHs[i]);
destroyObject(ow, ch, oh.wheelRigidBody1dStateOHs[i]);
destroyObject(ow, ch, oh.wheelLocalPoseStateOHs[i]);
destroyObject(ow, ch, oh.suspParamsOHs[i]);
destroyObject(ow, ch, oh.suspCompParamsOHs[i]);
destroyObject(ow, ch, oh.suspForceParamsOHs[i]);
destroyObject(ow, ch, oh.suspStateOHs[i]);
destroyObject(ow, ch, oh.suspCompStateOHs[i]);
destroyObject(ow, ch, oh.suspForceOHs[i]);
destroyObject(ow, ch, oh.tireParamsOHs[i]);
destroyObject(ow, ch, oh.tireDirectionStateOHs[i]);
destroyObject(ow, ch, oh.tireSpeedStateOHs[i]);
destroyObject(ow, ch, oh.tireSlipStateOHs[i]);
destroyObject(ow, ch, oh.tireStickyStateOHs[i]);
destroyObject(ow, ch, oh.tireGripStateOHs[i]);
destroyObject(ow, ch, oh.tireCamberStateOHs[i]);
destroyObject(ow, ch, oh.tireForceOHs[i]);
destroyObject(ow, ch, oh.wheelAttachmentOHs[i]);
}
//Antiroll
for(PxU32 i = 0; i < oh.nbAntirolls; i++)
{
destroyObject(ow, ch, oh.antiRollParamOHs[i]);
}
destroyObject(ow, ch, oh.antiRollTorqueOH);
//direct drive
destroyObject(ow, ch, oh.directDriveCommandStateOH);
destroyObject(ow, ch, oh.directDriveTransmissionCommandStateOH);
destroyObject(ow, ch, oh.directDriveThrottleResponseParamsOH);
destroyObject(ow, ch, oh.directDriveThrottleResponseStateOH);
destroyObject(ow, ch, oh.directDrivetrainOH);
//engine drive
destroyObject(ow, ch, oh.engineDriveCommandStateOH);
destroyObject(ow, ch, oh.engineDriveTransmissionCommandStateOH);
destroyObject(ow, ch, oh.clutchResponseParamsOH);
destroyObject(ow, ch, oh.clutchParamsOH);
destroyObject(ow, ch, oh.engineParamsOH);
destroyObject(ow, ch, oh.gearboxParamsOH);
destroyObject(ow, ch, oh.autoboxParamsOH);
destroyObject(ow, ch, oh.differentialParamsOH);
destroyObject(ow, ch, oh.clutchResponseStateOH);
destroyObject(ow, ch, oh.engineDriveThrottleResponseStateOH);
destroyObject(ow, ch, oh.engineStateOH);
destroyObject(ow, ch, oh.gearboxStateOH);
destroyObject(ow, ch, oh.autoboxStateOH);
destroyObject(ow, ch, oh.diffStateOH);
destroyObject(ow, ch, oh.clutchSlipStateOH);
destroyObject(ow, ch, oh.engineDrivetrainOH);
//PhysX Wheel attachments
for(PxU32 i = 0; i < oh.nbWheels; i++)
{
destroyObject(ow, ch, oh.physxConstraintParamOHs[i]);
destroyObject(ow, ch, oh.physxWheelShapeOHs[i]);
destroyObject(ow, ch, oh.physxRoadGeomStateOHs[i]);
destroyObject(ow, ch, oh.physxConstraintStateOHs[i]);
for(PxU32 j = 0; j < oh.nbPhysXMaterialFrictions; j++)
{
const PxU32 id = i*oh.nbPhysXMaterialFrictions + j;
destroyObject(ow, ch, oh.physxMaterialFrictionOHs[id]);
}
destroyObject(ow, ch, oh.physxWheelAttachmentOHs[i]);
}
//Physx rigid actor
destroyObject(ow, ch, oh.physxRoadGeomQueryParamOH);
destroyObject(ow, ch, oh.physxRoadGeomQueryDefaultFilterDataOH);
for (PxU32 i = 0; i < oh.nbWheels; i++)
{
destroyObject(ow, ch, oh.physxRoadGeomQueryFilterDataOHs[i]);
// safe even if not using per wheel filter data since it should hold the
// invalid handle value and thus destroyObject will do nothing
}
destroyObject(ow, ch, oh.physxRigidActorOH);
destroyObject(ow, ch, oh.physxSteerStateOH);
//Free the memory.
allocator.deallocate(&oh);
}
#else //#if PX_SUPPORT_OMNI_PVD
PxVehiclePvdAttributeHandles* PxVehiclePvdAttributesCreate(PxAllocatorCallback& allocator, OmniPvdWriter& omniWriter)
{
PX_UNUSED(allocator);
PX_UNUSED(omniWriter);
return NULL;
}
void PxVehiclePvdAttributesRelease(PxAllocatorCallback& allocator, PxVehiclePvdAttributeHandles& attributeHandles)
{
PX_UNUSED(allocator);
PX_UNUSED(attributeHandles);
}
PxVehiclePvdObjectHandles* PxVehiclePvdObjectCreate
(const PxU32 nbWheels, const PxU32 nbAntirolls, const PxU32 maxNbPhysXMaterialFrictions,
const PxU64 contextHandle,
PxAllocatorCallback& allocator)
{
PX_UNUSED(nbWheels);
PX_UNUSED(nbAntirolls);
PX_UNUSED(maxNbPhysXMaterialFrictions);
PX_UNUSED(contextHandle);
PX_UNUSED(allocator);
return NULL;
}
void PxVehiclePvdObjectRelease
(OmniPvdWriter& ow, PxAllocatorCallback& allocator, PxVehiclePvdObjectHandles& oh)
{
PX_UNUSED(ow);
PX_UNUSED(allocator);
PX_UNUSED(oh);
}
#endif //#if PX_SUPPORT_OMNI_PVD
} // namespace vehicle2
} // namespace physx

View File

@@ -0,0 +1,136 @@
// 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-2025 NVIDIA Corporation. All rights reserved.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
#pragma once
#include "vehicle2/PxVehicleLimits.h"
#if PX_SUPPORT_OMNI_PVD
#include "OmniPvdWriter.h"
#endif
#include "foundation/PxMemory.h"
#if !PX_DOXYGEN
namespace physx
{
namespace vehicle2
{
#endif
struct PxVehiclePvdObjectHandles
{
#if PX_SUPPORT_OMNI_PVD
OmniPvdObjectHandle vehicleOH;
OmniPvdObjectHandle rigidBodyParamsOH;
OmniPvdObjectHandle rigidBodyStateOH;
OmniPvdObjectHandle suspStateCalcParamsOH;
OmniPvdObjectHandle brakeResponseParamOHs[2];
OmniPvdObjectHandle steerResponseParamsOH;
OmniPvdObjectHandle brakeResponseStateOH;
OmniPvdObjectHandle steerResponseStateOH;
OmniPvdObjectHandle ackermannParamsOH;
OmniPvdObjectHandle directDriveCommandStateOH;
OmniPvdObjectHandle directDriveTransmissionCommandStateOH;
OmniPvdObjectHandle directDriveThrottleResponseParamsOH;
OmniPvdObjectHandle directDriveThrottleResponseStateOH;
OmniPvdObjectHandle directDrivetrainOH;
OmniPvdObjectHandle engineDriveCommandStateOH;
OmniPvdObjectHandle engineDriveTransmissionCommandStateOH;
OmniPvdObjectHandle clutchResponseParamsOH;
OmniPvdObjectHandle clutchParamsOH;
OmniPvdObjectHandle engineParamsOH;
OmniPvdObjectHandle gearboxParamsOH;
OmniPvdObjectHandle autoboxParamsOH;
OmniPvdObjectHandle differentialParamsOH;
OmniPvdObjectHandle clutchResponseStateOH;
OmniPvdObjectHandle engineDriveThrottleResponseStateOH;
OmniPvdObjectHandle engineStateOH;
OmniPvdObjectHandle gearboxStateOH;
OmniPvdObjectHandle autoboxStateOH;
OmniPvdObjectHandle diffStateOH;
OmniPvdObjectHandle clutchSlipStateOH;
OmniPvdObjectHandle engineDrivetrainOH;
OmniPvdObjectHandle* wheelAttachmentOHs;
OmniPvdObjectHandle* wheelParamsOHs;
OmniPvdObjectHandle* wheelActuationStateOHs;
OmniPvdObjectHandle* wheelRigidBody1dStateOHs;
OmniPvdObjectHandle* wheelLocalPoseStateOHs;
OmniPvdObjectHandle* roadGeomStateOHs;
OmniPvdObjectHandle* suspParamsOHs;
OmniPvdObjectHandle* suspCompParamsOHs;
OmniPvdObjectHandle* suspForceParamsOHs;
OmniPvdObjectHandle* suspStateOHs;
OmniPvdObjectHandle* suspCompStateOHs;
OmniPvdObjectHandle* suspForceOHs;
OmniPvdObjectHandle* tireParamsOHs;
OmniPvdObjectHandle* tireDirectionStateOHs;
OmniPvdObjectHandle* tireSpeedStateOHs;
OmniPvdObjectHandle* tireSlipStateOHs;
OmniPvdObjectHandle* tireStickyStateOHs;
OmniPvdObjectHandle* tireGripStateOHs;
OmniPvdObjectHandle* tireCamberStateOHs;
OmniPvdObjectHandle* tireForceOHs;
OmniPvdObjectHandle* physxWheelAttachmentOHs;
OmniPvdObjectHandle* physxWheelShapeOHs;
OmniPvdObjectHandle* physxConstraintParamOHs;
OmniPvdObjectHandle* physxConstraintStateOHs;
OmniPvdObjectHandle* physxRoadGeomStateOHs;
OmniPvdObjectHandle physxSteerStateOH;
OmniPvdObjectHandle* physxMaterialFrictionSetOHs;
OmniPvdObjectHandle* physxMaterialFrictionOHs;
OmniPvdObjectHandle physxRoadGeomQueryParamOH;
OmniPvdObjectHandle physxRoadGeomQueryDefaultFilterDataOH;
OmniPvdObjectHandle* physxRoadGeomQueryFilterDataOHs;
OmniPvdObjectHandle physxRigidActorOH;
OmniPvdObjectHandle* antiRollParamOHs;
OmniPvdObjectHandle antiRollTorqueOH;
PxU32 nbWheels;
PxU32 nbPhysXMaterialFrictions;
PxU32 nbAntirolls;
OmniPvdContextHandle contextHandle;
#endif //PX_SUPPORT_OMNI_PVD
};
#if !PX_DOXYGEN
} // namespace vehicle2
} // namespace physx
#endif

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,150 @@
// 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-2025 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/PxMat33.h"
#include "foundation/PxTransform.h"
#include "vehicle2/PxVehicleParams.h"
#include "vehicle2/rigidBody/PxVehicleRigidBodyFunctions.h"
#include "vehicle2/rigidBody/PxVehicleRigidBodyStates.h"
#include "vehicle2/rigidBody/PxVehicleRigidBodyParams.h"
#include "vehicle2/suspension/PxVehicleSuspensionStates.h"
#include "vehicle2/tire/PxVehicleTireStates.h"
namespace physx
{
namespace vehicle2
{
PX_FORCE_INLINE void transformInertiaTensor(const PxVec3& invD, const PxMat33& M, PxMat33& mIInv)
{
const float axx = invD.x*M(0, 0), axy = invD.x*M(1, 0), axz = invD.x*M(2, 0);
const float byx = invD.y*M(0, 1), byy = invD.y*M(1, 1), byz = invD.y*M(2, 1);
const float czx = invD.z*M(0, 2), czy = invD.z*M(1, 2), czz = invD.z*M(2, 2);
mIInv(0, 0) = axx * M(0, 0) + byx * M(0, 1) + czx * M(0, 2);
mIInv(1, 1) = axy * M(1, 0) + byy * M(1, 1) + czy * M(1, 2);
mIInv(2, 2) = axz * M(2, 0) + byz * M(2, 1) + czz * M(2, 2);
mIInv(0, 1) = mIInv(1, 0) = axx * M(1, 0) + byx * M(1, 1) + czx * M(1, 2);
mIInv(0, 2) = mIInv(2, 0) = axx * M(2, 0) + byx * M(2, 1) + czx * M(2, 2);
mIInv(1, 2) = mIInv(2, 1) = axy * M(2, 0) + byy * M(2, 1) + czy * M(2, 2);
}
PX_FORCE_INLINE void integrateBody
(const PxF32 mass, const PxVec3& moi, const PxVec3& force, const PxVec3& torque, const PxF32 dt,
PxVec3& linvel, PxVec3& angvel, PxTransform& t)
{
const PxF32 inverseMass = 1.0f/mass;
const PxVec3 inverseMOI(1.0f/moi.x, 1.0f/moi.y, 1.0f/moi.z);
//Integrate linear velocity.
linvel += force * (inverseMass*dt);
//Integrate angular velocity.
PxMat33 inverseInertia;
transformInertiaTensor(inverseMOI, PxMat33(t.q), inverseInertia);
angvel += inverseInertia * (torque*dt);
//Integrate position.
t.p += linvel * dt;
//Integrate quaternion.
PxQuat wq(angvel.x, angvel.y, angvel.z, 0.0f);
PxQuat q = t.q;
PxQuat qdot = wq * q*(dt*0.5f);
q += qdot;
q.normalize();
t.q = q;
}
void PxVehicleRigidBodyUpdate
(const PxVehicleAxleDescription& axleDescription, const PxVehicleRigidBodyParams& rigidBodyParams,
const PxVehicleArrayData<const PxVehicleSuspensionForce>& suspensionForces,
const PxVehicleArrayData<const PxVehicleTireForce>& tireForces,
const PxVehicleAntiRollTorque* antiRollTorque,
const PxReal dt, const PxVec3& gravity,
PxVehicleRigidBodyState& rigidBodyState)
{
//Sum all the forces and torques.
const PxU32 nbAxles = axleDescription.getNbAxles();
PxVec3 force(PxZero);
PxVec3 torque(PxZero);
for (PxU32 i = 0; i < nbAxles; i++)
{
PxVec3 axleSuspForce(PxZero);
PxVec3 axleTireLongForce(PxZero);
PxVec3 axleTireLatForce(PxZero);
PxVec3 axleSuspTorque(PxZero);
PxVec3 axleTireLongTorque(PxZero);
PxVec3 axleTireLatTorque(PxZero);
for (PxU32 j = 0; j < axleDescription.getNbWheelsOnAxle(i); j++)
{
const PxU32 wheelId = axleDescription.getWheelOnAxle(j, i);
const PxVehicleSuspensionForce& suspForce = suspensionForces[wheelId];
const PxVehicleTireForce& tireForce = tireForces[wheelId];
axleSuspForce += suspForce.force;
axleTireLongForce += tireForce.forces[PxVehicleTireDirectionModes::eLONGITUDINAL];
axleTireLatForce += tireForce.forces[PxVehicleTireDirectionModes::eLATERAL];
axleSuspTorque += suspForce.torque;
axleTireLongTorque += tireForce.torques[PxVehicleTireDirectionModes::eLONGITUDINAL];
axleTireLatTorque += tireForce.torques[PxVehicleTireDirectionModes::eLATERAL];
}
const PxVec3 axleForce = axleSuspForce + axleTireLongForce + axleTireLatForce;
const PxVec3 axleTorque = axleSuspTorque + axleTireLongTorque + axleTireLatTorque;
force += axleForce;
torque += axleTorque;
}
force += gravity * rigidBodyParams.mass;
force += rigidBodyState.externalForce;
torque += rigidBodyState.externalTorque;
torque += (antiRollTorque ? antiRollTorque->antiRollTorque : PxVec3(PxZero));
//Rigid body params.
const PxF32 mass = rigidBodyParams.mass;
const PxVec3& moi = rigidBodyParams.moi;
//Perform the integration.
PxTransform& t = rigidBodyState.pose;
PxVec3& linvel = rigidBodyState.linearVelocity;
PxVec3& angvel = rigidBodyState.angularVelocity;
integrateBody(
mass, moi,
force, torque, dt,
linvel, angvel, t);
//Reset the accumulated external forces after using them.
rigidBodyState.externalForce = PxVec3(PxZero);
rigidBodyState.externalTorque = PxVec3(PxZero);
}
} //namespace vehicle2
} //namespace physx

View File

@@ -0,0 +1,96 @@
// 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-2025 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 "vehicle2/PxVehicleParams.h"
#include "vehicle2/commands/PxVehicleCommandHelpers.h"
#include "vehicle2/rigidBody/PxVehicleRigidBodyStates.h"
#include "vehicle2/steering/PxVehicleSteeringParams.h"
namespace physx
{
namespace vehicle2
{
void PxVehicleSteerCommandResponseUpdate
(const PxReal steer, const PxReal longitudinalSpeed,
const PxU32 wheelId, const PxVehicleSteerCommandResponseParams& responseParams,
PxReal& steerResponse)
{
PxReal sign = PxSign(steer);
steerResponse = sign * PxVehicleNonLinearResponseCompute(PxAbs(steer), longitudinalSpeed, wheelId, responseParams);
}
void PxVehicleAckermannSteerUpdate
(const PxReal steer,
const PxVehicleSteerCommandResponseParams& steerResponseParams, const PxVehicleSizedArrayData<const PxVehicleAckermannParams>& ackermannParams,
PxVehicleArrayData<PxReal>& steerResponseStates)
{
for (PxU32 i = 0; i < ackermannParams.size; i++)
{
const PxVehicleAckermannParams& ackParams = ackermannParams[i];
if (ackParams.strength > 0.0f)
{
//Axle yaw is the average of the two wheels.
const PxF32 axleYaw =
(PxVehicleLinearResponseCompute(steer, ackParams.wheelIds[0], steerResponseParams) +
PxVehicleLinearResponseCompute(steer, ackParams.wheelIds[1], steerResponseParams))*0.5f;
if (axleYaw != 0.0f)
{
//Work out the ackermann steer for +ve steer then swap and negate the steer angles if the steer is -ve.
//Uncorrected yaw angle.
//One of the wheels will adopt this angle.
//The other will be corrected.
const PxF32 posWheelYaw = PxAbs(axleYaw);
//Work out the yaw of the other wheel.
PxF32 negWheelCorrectedYaw;
{
const PxF32 dz = ackParams.wheelBase;
const PxF32 dx = ackParams.trackWidth + ackParams.wheelBase / PxTan(posWheelYaw);
const PxF32 negWheelPerfectYaw = PxAtan(dz / dx);
negWheelCorrectedYaw = posWheelYaw + ackParams.strength*(negWheelPerfectYaw - posWheelYaw);
}
//Now assign axleYaw and negWheelCorrectedYaw to the correct wheels with the correct signs.
const PxF32 negWheelFinalYaw = intrinsics::fsel(axleYaw, negWheelCorrectedYaw, -posWheelYaw);
const PxF32 posWheelFinalYaw = intrinsics::fsel(axleYaw, posWheelYaw, -negWheelCorrectedYaw);
//Apply the per axle distributions to each wheel on the axle that is affected by this Ackermann correction.
steerResponseStates[ackParams.wheelIds[0]] = negWheelFinalYaw;
steerResponseStates[ackParams.wheelIds[1]] = posWheelFinalYaw;
}
}
}
}
} //namespace vehicle2
} //namespace physx

View File

@@ -0,0 +1,717 @@
// 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-2025 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 "vehicle2/PxVehicleParams.h"
#include "vehicle2/roadGeometry/PxVehicleRoadGeometryState.h"
#include "vehicle2/rigidBody/PxVehicleRigidBodyStates.h"
#include "vehicle2/suspension/PxVehicleSuspensionParams.h"
#include "vehicle2/suspension/PxVehicleSuspensionFunctions.h"
#include "vehicle2/suspension/PxVehicleSuspensionHelpers.h"
namespace physx
{
namespace vehicle2
{
#define VH_SUSPENSION_NO_INTERSECTION_MARKER FLT_MIN
PX_FORCE_INLINE void computeJounceAndSeparation(const PxF32 depth, const PxF32 suspDirDotPlaneNormal,
const PxF32 suspTravelDist, const PxF32 previousJounce,
PxF32& jounce, PxF32& separation)
{
if (suspDirDotPlaneNormal != 0.0f)
{
if (depth <= 0.0f)
{
//There is overlap at max droop
const PxF32 suspDeltaToDepthZero = depth / suspDirDotPlaneNormal;
if ((suspDeltaToDepthZero > 0.0f) && (suspDeltaToDepthZero <= suspTravelDist))
{
//The wheel can be placed on the plane for a jounce between max droop and max compression.
jounce = suspDeltaToDepthZero;
separation = 0.0f;
}
else
{
if (suspDeltaToDepthZero > suspTravelDist)
{
//There is overlap even at max compression. Compute the depth at max
//compression.
jounce = suspTravelDist;
separation = (depth * (suspDeltaToDepthZero - suspTravelDist)) / suspDeltaToDepthZero;
}
else
{
PX_ASSERT(suspDeltaToDepthZero <= 0.0f);
//There is overlap at max droop and in addition, the suspension would have to expand
//beyond max droop to move out of plane contact. This scenario can be reached, for
//example, if a car rolls over or if something touches a wheel from "above".
jounce = 0.0f;
separation = depth;
}
}
}
else
{
//At max droop there is no overlap => let the suspension fully expand.
//Note that this check is important because without it, you can get unexpected
//behavior like the wheel compressing just so that it touches the plane again.
jounce = 0.0f;
separation = VH_SUSPENSION_NO_INTERSECTION_MARKER;
}
}
else
{
//The suspension direction and hit normal are perpendicular, thus no change to
//suspension jounce will change the distance to the plane.
if (depth >= 0.0f)
{
jounce = 0.0f;
separation = VH_SUSPENSION_NO_INTERSECTION_MARKER;
}
else
{
jounce = (previousJounce != PX_VEHICLE_UNSPECIFIED_JOUNCE) ? previousJounce : suspTravelDist;
separation = depth;
// note: since The suspension direction and hit normal are perpendicular, the
// depth will be the same for all jounce values
}
}
}
PX_FORCE_INLINE void intersectRayPlane
(const PxVehicleFrame& frame, const PxVehicleWheelParams& wheelParams, const PxVehicleSuspensionParams& suspParams,
const PxF32 steerAngle, const PxVehicleRoadGeometryState& roadGeomState, const PxVehicleRigidBodyState& rigidBodyState,
const PxF32 previousJounce,
PxVec3& suspDir, PxF32& jounce, PxF32& separation)
{
//Compute the position on the wheel surface along the suspension direction that is closest to
//the plane (for the purpose of computing the separation from the plane). The wheel center position
//is chosen at max droop (zero jounce).
PxVehicleSuspensionState suspState;
suspState.setToDefault(0.0f);
const PxTransform wheelPose = PxVehicleComputeWheelPose(frame, suspParams, suspState, 0.0f, 0.0f, steerAngle,
rigidBodyState.pose, 0.0f);
suspDir = PxVehicleComputeSuspensionDirection(suspParams, rigidBodyState.pose);
const PxPlane& hitPlane = roadGeomState.plane;
const PxF32 suspDirDotPlaneNormal = suspDir.dot(hitPlane.n);
PxVec3 wheelRefPoint;
if (suspDirDotPlaneNormal < 0.0f)
{
wheelRefPoint = wheelPose.p + (suspDir * wheelParams.radius);
//The "wheel bottom" has to be placed on the plane to resolve collision
}
else if (suspDirDotPlaneNormal > 0.0f)
{
wheelRefPoint = wheelPose.p - (suspDir * wheelParams.radius);
//The "wheel top" has to be placed on the plane to resolve collision
}
else
{
//The hit normal and susp dir are prependicular
//-> any point along the suspension direction will do to compute the depth
wheelRefPoint = wheelPose.p;
}
//Compute the penetration depth of the reference point with respect to the plane
const PxF32 depth = hitPlane.n.dot(wheelRefPoint) + hitPlane.d;
//How far along the susp dir do we have to move to place the wheel exactly on the plane.
computeJounceAndSeparation(depth, suspDirDotPlaneNormal, suspParams.suspensionTravelDist, previousJounce,
jounce, separation);
}
PX_FORCE_INLINE bool intersectPlanes(const PxPlane& a, const PxPlane& b, PxVec3& v, PxVec3& w)
{
const PxF32 n1x = a.n.x;
const PxF32 n1y = a.n.y;
const PxF32 n1z = a.n.z;
const PxF32 n1d = a.d;
const PxF32 n2x = b.n.x;
const PxF32 n2y = b.n.y;
const PxF32 n2z = b.n.z;
const PxF32 n2d = b.d;
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;
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;
return 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 true;
}
// This method computes how much a wheel cylinder object at max droop (fully elongated suspension)
// has to be pushed along the suspension direction to end up just touching the plane provided in
// the road geometry state.
//
// output:
// suspDir: suspension direction in the world frame
// jounce: the suspension jounce.
// jounce=0 the wheel is at max droop
// jounce>0 the suspension is compressed by length jounce (up to max compression = suspensionTravelDist)
//
// The jounce value will be set to 0 (in the case of no overlap) or to the previous jounce
// (in the case of overlap) for the following special cases:
// - the plane normal and the wheel lateral axis are parallel
// - the plane normal and the suspension direction are perpendicular
// separation: 0 if the suspension can move between max droop and max compression to place
// the wheel on the ground.
// A negative value denotes by how much the wheel cylinder penetrates (along the hit
// plane normal) into the ground for the computed jounce.
// A positive value denotes that the wheel does not touch the ground.
//
PX_FORCE_INLINE void intersectCylinderPlane
(const PxVehicleFrame& frame,
const PxVehicleWheelParams& whlParams, const PxVehicleSuspensionParams& suspParams,
const PxF32 steerAngle, const PxVehicleRoadGeometryState& roadGeomState, const PxVehicleRigidBodyState& rigidBodyState,
const PxF32 previousJounce,
PxVec3& suspDir, PxF32& jounce, PxF32& separation)
{
const PxPlane& hitPlane = roadGeomState.plane;
const PxF32 radius = whlParams.radius;
const PxF32 halfWidth = whlParams.halfWidth;
//Compute the wheel pose at zero jounce ie at max droop.
PxTransform wheelPoseAtZeroJounce;
{
PxTransform start;
PxF32 dist;
PxVehicleComputeSuspensionSweep(frame, suspParams, steerAngle, rigidBodyState.pose, start, suspDir, dist);
wheelPoseAtZeroJounce = PxTransform(start.p + suspDir*dist, start.q);
}
//Compute the plane of the wheel.
PxPlane wheelPlane;
{
wheelPlane = PxPlane(wheelPoseAtZeroJounce.p, wheelPoseAtZeroJounce.rotate(frame.getLatAxis()));
}
//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);
PxF32 depth;
if (intersectPlaneSuccess)
{
//Compute the position on the intersection edge that is closest to the wheel centre.
PxVec3 closestPointOnIntersectionEdge;
{
const PxVec3& p = wheelPoseAtZeroJounce.p;
const PxVec3& dir = intersectionEdgeW;
const PxVec3& v = intersectionEdgeV;
const PxF32 t = (p - v).dot(dir);
closestPointOnIntersectionEdge = v + dir * t;
}
//Compute the vector that joins the wheel centre to the intersection edge;
PxVec3 dir;
{
const PxF32 wheelCentreD = hitPlane.n.dot(wheelPoseAtZeroJounce.p) + hitPlane.d;
dir = ((wheelCentreD >= 0) ? closestPointOnIntersectionEdge - wheelPoseAtZeroJounce.p : wheelPoseAtZeroJounce.p - closestPointOnIntersectionEdge);
dir.normalize();
}
//Compute the point on the disc diameter that will be the closest to the hit plane or the deepest inside the hit plane.
const PxVec3 pos = wheelPoseAtZeroJounce.p + dir*radius;
//Now compute the maximum depth of the inside and outside discs against the plane.
{
const PxVec3& latDir = wheelPlane.n;
const PxF32 signDot = PxVehicleComputeSign(hitPlane.n.dot(latDir));
const PxVec3 deepestPos = pos - latDir * (signDot*halfWidth);
depth = hitPlane.n.dot(deepestPos) + hitPlane.d;
}
}
else
{
//The hit plane normal and the wheel lateral axis are parallel
//Now compute the maximum depth of the inside and outside discs against the plane.
const PxVec3& latDir = wheelPlane.n;
const PxF32 signDot = PxVehicleComputeSign(hitPlane.n.dot(latDir));
depth = hitPlane.d - halfWidth - (signDot*wheelPlane.d);
// examples:
// halfWidth = 0.5
//
// wheelPlane.d hitplane.d depth
//=============================================
// -3 -> -3.5 -> -1
// 3 <- -3.5 -> -1
//
// -3 -> 3.5 <- 0
// 3 <- 3.5 <- 0
//
}
//How far along the susp dir do we have to move to place the wheel exactly on the plane.
const PxF32 suspDirDotPlaneNormal = hitPlane.n.dot(suspDir);
computeJounceAndSeparation(depth, suspDirDotPlaneNormal, suspParams.suspensionTravelDist, previousJounce,
jounce, separation);
}
static void limitSuspensionExpansionVelocity
(const PxReal jounceSpeed, const PxReal previousJounceSpeed, const PxReal previousJounce,
const PxReal suspStiffness, const PxReal suspDamping,
const PxVec3& suspDirWorld, const PxReal wheelMass,
const PxReal dt, const PxVec3& gravity, const bool hasGroundHit,
PxVehicleSuspensionState& suspState)
{
PX_ASSERT(jounceSpeed < 0.0f); // don't call this method if the suspension is not expanding
// The suspension is expanding. Check if the suspension can expand fast enough to actually reach the
// target jounce within the given time step.
// Without the suspension elongating, the wheel would end up in the air. Compute the suspension force
// that pushes the wheel towards the ground. Note that gravity is ignored here as it applies to sprung
// mass and wheel equally.
const PxReal springForceAlongSuspDir = (previousJounce * suspStiffness);
const PxReal suspDirVelSpring = (springForceAlongSuspDir / wheelMass) * dt;
const PxReal dampingForceAlongSuspDir = (previousJounceSpeed * suspDamping); // note: negative jounce speed = expanding
const PxReal suspDirVelDamping = (dampingForceAlongSuspDir / wheelMass) * dt;
PxReal suspDirVel = suspDirVelSpring - previousJounceSpeed;
// add damping part but such that it does not flip the velocity sign (covering case of
// crazily high damping values)
const PxReal suspDirVelTmp = suspDirVel + suspDirVelDamping;
if (suspDirVel >= 0.0f)
suspDirVel = PxMax(0.0f, suspDirVelTmp);
else
suspDirVel = PxMin(0.0f, suspDirVelTmp);
const PxReal gravitySuspDir = gravity.dot(suspDirWorld);
PxReal velocityThreshold;
if (hasGroundHit)
{
velocityThreshold = (gravitySuspDir > 0.0f) ? suspDirVel + (gravitySuspDir * dt) : suspDirVel;
// gravity is considered too as it affects the wheel and can close the distance to the ground
// too. External forces acting on the sprung mass are ignored as those propagate
// through the suspension to the wheel.
// If gravity points in the opposite direction of the suspension travel direction, it is
// ignored. The suspension should never elongate more than what's given by the current delta
// jounce (defined by jounceSpeed, i.e., jounceSpeed < -suspDirVel has to hold all the time
// for the clamping to take place).
}
else
{
velocityThreshold = suspDirVel;
// if there was no hit detected, the gravity will not be taken into account since there is no
// ground to move towards.
}
if (jounceSpeed < (-velocityThreshold))
{
// The suspension can not expand fast enough to place the wheel on the ground. As a result,
// the scenario is interpreted as if there was no hit and the wheels end up in air. The
// jounce is adjusted based on the clamped velocity to not have it snap to the target immediately.
// note: could consider applying the suspension force to the sprung mass too but the complexity
// seems high enough already.
const PxReal expansionDelta = suspDirVel * dt;
const PxReal clampedJounce = previousJounce - expansionDelta;
PX_ASSERT(clampedJounce >= 0.0f);
// No need to cover the case of a negative jounce as the input jounce speed is expected to make
// sure that no negative jounce would result (and the speed considered here is smaller than the
// non-clamped input jounce speed).
if (suspState.separation >= 0.0f) // do not adjust separation if ground penetration was detected
{
suspState.separation = clampedJounce - suspState.jounce;
}
suspState.jounce = clampedJounce;
suspState.jounceSpeed = -suspDirVel;
}
}
void PxVehicleSuspensionStateUpdate
(const PxVehicleWheelParams& whlParams, const PxVehicleSuspensionParams& suspParams, const PxVehicleSuspensionStateCalculationParams& suspStateCalcParams,
const PxReal suspStiffness, const PxReal suspDamping,
const PxF32 steerAngle, const PxVehicleRoadGeometryState& roadGeomState, const PxVehicleRigidBodyState& rigidBodyState,
const PxReal dt, const PxVehicleFrame& frame, const PxVec3& gravity,
PxVehicleSuspensionState& suspState)
{
const PxReal prevJounce = suspState.jounce;
const PxReal prevJounceSpeed = suspState.jounceSpeed;
suspState.setToDefault(0.0f, VH_SUSPENSION_NO_INTERSECTION_MARKER);
if(!roadGeomState.hitState)
{
if (suspStateCalcParams.limitSuspensionExpansionVelocity && (prevJounce != PX_VEHICLE_UNSPECIFIED_JOUNCE))
{
if (prevJounce > 0.0f)
{
PX_ASSERT(suspState.jounce == 0.0f); // the expectation is that setToDefault() above does this
const PxReal jounceSpeed = (-prevJounce) / dt;
const PxVec3 suspDirWorld = PxVehicleComputeSuspensionDirection(suspParams, rigidBodyState.pose);
limitSuspensionExpansionVelocity(jounceSpeed, prevJounceSpeed, prevJounce,
suspStiffness, suspDamping, suspDirWorld, whlParams.mass, dt, gravity, false,
suspState);
}
}
return;
}
PxVec3 suspDir;
PxF32 currJounce;
PxF32 separation;
switch(suspStateCalcParams.suspensionJounceCalculationType)
{
case PxVehicleSuspensionJounceCalculationType::eRAYCAST:
{
//Compute the distance along the suspension direction that places the wheel on the ground plane.
intersectRayPlane(frame, whlParams, suspParams, steerAngle, roadGeomState, rigidBodyState,
prevJounce,
suspDir, currJounce, separation);
}
break;
case PxVehicleSuspensionJounceCalculationType::eSWEEP:
{
//Compute the distance along the suspension direction that places the wheel on the ground plane.
intersectCylinderPlane(frame, whlParams, suspParams, steerAngle, roadGeomState, rigidBodyState,
prevJounce,
suspDir, currJounce, separation);
}
break;
default:
{
PX_ASSERT(false);
currJounce = 0.0f;
separation = VH_SUSPENSION_NO_INTERSECTION_MARKER;
}
break;
}
suspState.jounce = currJounce;
suspState.jounceSpeed = (PX_VEHICLE_UNSPECIFIED_JOUNCE != prevJounce) ? (currJounce - prevJounce) / dt : 0.0f;
suspState.separation = separation;
if (suspStateCalcParams.limitSuspensionExpansionVelocity && (suspState.jounceSpeed < 0.0f))
{
limitSuspensionExpansionVelocity(suspState.jounceSpeed, prevJounceSpeed, prevJounce,
suspStiffness, suspDamping, suspDir, whlParams.mass, dt, gravity, true,
suspState);
}
}
void PxVehicleSuspensionComplianceUpdate
(const PxVehicleSuspensionParams& suspParams,
const PxVehicleSuspensionComplianceParams& compParams,
const PxVehicleSuspensionState& suspState,
PxVehicleSuspensionComplianceState& compState)
{
compState.setToDefault();
//Compliance is normalised in range (0,1) with:
//0 being fully elongated (ie jounce = 0)
//1 being fully compressed (ie jounce = maxTravelDist)
const PxF32 jounce = suspState.jounce;
const PxF32 maxTravelDist = suspParams.suspensionTravelDist;
const PxF32 r = jounce/maxTravelDist;
//Camber and toe relative the wheel reference pose.
{
compState.camber = compParams.wheelCamberAngle.interpolate(r);
compState.toe = compParams.wheelToeAngle.interpolate(r);
}
//Tire force application point as an offset from wheel reference pose.
{
compState.tireForceAppPoint = compParams.tireForceAppPoint.interpolate(r);
}
//Susp force application point as an offset from wheel reference pose.
{
compState.suspForceAppPoint = compParams.suspForceAppPoint.interpolate(r);
}
}
PX_FORCE_INLINE void setSuspensionForceAndTorque
(const PxVehicleSuspensionParams& suspParams,
const PxVehicleRoadGeometryState& roadGeomState, const PxVehicleSuspensionComplianceState& suspComplianceState, const PxVehicleRigidBodyState& rigidBodyState,
const PxF32 suspForceMagnitude, const PxF32 normalForceMagnitude,
PxVehicleSuspensionForce& suspForce)
{
const PxVec3 r = rigidBodyState.pose.rotate(suspParams.suspensionAttachment.transform(suspComplianceState.suspForceAppPoint));
const PxVec3 f = roadGeomState.plane.n*suspForceMagnitude;
suspForce.force = f;
suspForce.torque = r.cross(f);
suspForce.normalForce = normalForceMagnitude;
}
void PxVehicleSuspensionForceUpdate
(const PxVehicleSuspensionParams& suspParams,
const PxVehicleSuspensionForceParams& suspForceParams,
const PxVehicleRoadGeometryState& roadGeom, const PxVehicleSuspensionState& suspState,
const PxVehicleSuspensionComplianceState& compState, const PxVehicleRigidBodyState& rigidBodyState,
const PxVec3& gravity, const PxReal vehicleMass,
PxVehicleSuspensionForce& suspForces)
{
suspForces.setToDefault();
//If the wheel cannot touch the ground then carry on with zero force.
if (!PxVehicleIsWheelOnGround(suspState))
return;
//For the following computations, the external force Fe acting on the suspension
//is seen as the sum of two forces Fe0 and Fe1. Fe0 acts along the suspension
//direction while Fe1 is the part perpendicular to Fe0. The suspension spring
//force Fs0 works against Fe0, while the suspension "collision" force Fs1 works
//against Fe1 (can be seen as a force that is trying to push the suspension
//into the ground in a direction where the spring can not do anything against
//it because it's perpendicular to the spring direction).
//For the system to be at rest, we require Fs0 = -Fe0 and Fs1 = -Fe1
//The forces Fe0 and Fe1 can each be split further into parts that act along
//the ground patch normal and parts that act parallel to the ground patch.
//Fs0 and Fs1 work against the former as the ground prevents the suspension
//from penetrating. However, to work against the latter, friction would have
//to be considered. The current model does not do this (or rather: it is the
//tire model that deals with forces parallel to the ground patch). As a result,
//only the force parts of Fs0 and Fs1 perpendicular to the ground patch are
//considered here. Furthermore, Fs1 is set to zero, if the external force is
//not pushing the suspension towards the ground patch (imagine a vehicle with
//a "tilted" suspension direction "driving" up a wall. No "collision" force
//should be added in such a scenario).
PxF32 suspForceMagnitude = 0.0f;
{
const PxF32 jounce = suspState.jounce;
const PxF32 stiffness = suspForceParams.stiffness;
const PxF32 jounceSpeed = suspState.jounceSpeed;
const PxF32 damping = suspForceParams.damping;
const PxVec3 suspDirWorld = PxVehicleComputeSuspensionDirection(suspParams, rigidBodyState.pose);
const PxVec3 suspSpringForce = suspDirWorld * (-(jounce*stiffness + jounceSpeed*damping));
const PxF32 suspSpringForceProjected = roadGeom.plane.n.dot(suspSpringForce);
suspForceMagnitude = suspSpringForceProjected;
const PxVec3 comToSuspWorld = rigidBodyState.pose.rotate(suspParams.suspensionAttachment.p);
const PxVec3 externalForceLin = (gravity * vehicleMass) + rigidBodyState.externalForce;
const PxReal comToSuspDistSqr = comToSuspWorld.magnitudeSquared();
PxVec3 externalForceAng;
if (comToSuspDistSqr > 0.0f)
{
// t = r x F
// t x r = (r x F) x r = -[r x (r x F)] = -[((r o F) * r) - ((r o r) * F)]
// r and F perpendicular (r o F = 0) => = (r o r) * F = |r|^2 * F
externalForceAng = (rigidBodyState.externalTorque.cross(comToSuspWorld)) / comToSuspDistSqr;
}
else
externalForceAng = PxVec3(PxZero);
const PxVec3 externalForce = externalForceLin + externalForceAng;
const PxVec3 externalForceSusp = externalForce * (suspForceParams.sprungMass / vehicleMass);
if (roadGeom.plane.n.dot(externalForceSusp) < 0.0f)
{
const PxF32 suspDirExternalForceMagn = suspDirWorld.dot(externalForceSusp);
const PxVec3 collisionForce = externalForceSusp - (suspDirWorld * suspDirExternalForceMagn);
const PxF32 suspCollisionForceProjected = -roadGeom.plane.n.dot(collisionForce);
suspForceMagnitude += suspCollisionForceProjected;
}
}
setSuspensionForceAndTorque(suspParams, roadGeom, compState, rigidBodyState, suspForceMagnitude, suspForceMagnitude, suspForces);
}
void PxVehicleSuspensionLegacyForceUpdate
(const PxVehicleSuspensionParams& suspParams,
const PxVehicleSuspensionForceLegacyParams& suspForceParamsLegacy,
const PxVehicleRoadGeometryState& roadGeomState, const PxVehicleSuspensionState& suspState,
const PxVehicleSuspensionComplianceState& compState, const PxVehicleRigidBodyState& rigidBodyState,
const PxVec3& gravity,
PxVehicleSuspensionForce& suspForces)
{
suspForces.setToDefault();
//If the wheel cannot touch the ground then carry on with zero force.
if (!PxVehicleIsWheelOnGround(suspState))
return;
PxF32 suspForceMagnitude = 0.0f;
{
//Get the params for the legacy model.
//const PxF32 restDistance = suspForceParamsLegacy.restDistance;
const PxF32 sprungMass = suspForceParamsLegacy.sprungMass;
const PxF32 restDistance = suspForceParamsLegacy.restDistance;
//Get the non-legacy params.
const PxF32 stiffness = suspForceParamsLegacy.stiffness;
const PxF32 damperRate = suspForceParamsLegacy.damping;
const PxF32 maxTravelDist = suspParams.suspensionTravelDist;
//Suspension state.
const PxF32 jounce = suspState.jounce;
const PxF32 jounceSpeed = suspState.jounceSpeed;
//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 PxVec3 w = PxVehicleComputeSuspensionDirection(suspParams, rigidBodyState.pose);
const PxF32 gravitySuspDir = gravity.dot(w);
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,
sprungMass*alpha + //force to support sprung mass at zero jounce
stiffness*(jounce + restDistance - maxTravelDist)); //linear spring
suspensionForceW += jounceSpeed * damperRate; //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
suspForceMagnitude = roadGeomState.plane.n.dot(-w * suspensionForceW - TTimesBeta * sprungMass);
}
setSuspensionForceAndTorque(suspParams, roadGeomState, compState, rigidBodyState, suspForceMagnitude, suspForceMagnitude, suspForces);
}
void PxVehicleAntiRollForceUpdate
(const PxVehicleArrayData<const PxVehicleSuspensionParams>& suspensionParams,
const PxVehicleSizedArrayData<const PxVehicleAntiRollForceParams>& antiRollParams,
const PxVehicleArrayData<const PxVehicleSuspensionState>& suspensionStates,
const PxVehicleArrayData<const PxVehicleSuspensionComplianceState>& complianceStates,
const PxVehicleRigidBodyState& rigidBodyState,
PxVehicleAntiRollTorque& antiRollTorque)
{
antiRollTorque.setToDefault();
for (PxU32 i = 0; i < antiRollParams.size; i++)
{
if (antiRollParams[i].stiffness != 0.0f)
{
const PxU32 w0 = antiRollParams[i].wheel0;
const PxU32 w1 = antiRollParams[i].wheel1;
const bool w0InAir = (0.0f == suspensionStates[w0].jounce);
const bool w1InAir = (0.0f == suspensionStates[w1].jounce);
if (!w0InAir || !w1InAir)
{
//Compute the difference in jounce and compute the force.
const PxF32 w0Jounce = suspensionStates[w0].jounce;
const PxF32 w1Jounce = suspensionStates[w1].jounce;
const PxF32 antiRollForceMag = (w0Jounce - w1Jounce) * antiRollParams[i].stiffness;
//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];
const PxVec3& antiRollForceDir = suspensionParams[wheelId].suspensionTravelDir;
const PxVec3 antiRollForce = antiRollForceDir * antiRollForceMags[j];
const PxVec3 r = suspensionParams[wheelId].suspensionAttachment.transform(complianceStates[wheelId].suspForceAppPoint);
antiRollTorque.antiRollTorque += rigidBodyState.pose.rotate(r.cross(antiRollForce));
}
}
}
}
}
} //namespace vehicle2
} //namespace physx

View File

@@ -0,0 +1,214 @@
// 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-2025 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 "vehicle2/PxVehicleParams.h"
#include "vehicle2/PxVehicleMaths.h"
#include "vehicle2/suspension/PxVehicleSuspensionHelpers.h"
namespace physx
{
namespace vehicle2
{
#define DETERMINANT_THRESHOLD (1e-6f)
bool PxVehicleComputeSprungMasses(const PxU32 numSprungMasses, const PxVec3* sprungMassCoordinates, const PxReal totalMass, const PxVehicleAxes::Enum gravityDir, PxReal* sprungMasses)
{
if (numSprungMasses < 1)
return false;
if (numSprungMasses > PxVehicleLimits::eMAX_NB_WHEELS)
return false;
if (totalMass <= 0.0f)
return false;
if (!sprungMassCoordinates || !sprungMasses)
return false;
const PxVec3 centreOfMass(PxZero);
PxU32 gravityDirection = 0xffffffff;
switch (gravityDir)
{
case PxVehicleAxes::eNegX:
case PxVehicleAxes::ePosX:
gravityDirection = 0;
break;
case PxVehicleAxes::eNegY:
case PxVehicleAxes::ePosY:
gravityDirection = 1;
break;
case PxVehicleAxes::eNegZ:
case PxVehicleAxes::ePosZ:
gravityDirection = 2;
break;
default:
PX_ASSERT(false);
break;
}
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);
if (PxAbs(r0 - r1) <= DETERMINANT_THRESHOLD)
return false;
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;
PxVehicleMatrixNN A(3);
PxVehicleVectorN 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;
PxVehicleVectorN result(3);
PxVehicleMatrixNNLUSolver solver;
solver.decomposeLU(A);
if (PxAbs(solver.getDet()) <= DETERMINANT_THRESHOLD)
return false;
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
PxVehicleMatrixNN A(numSprungMasses + 3);
PxVehicleVectorN 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
PxVehicleVectorN result(numSprungMasses + 3);
PxVehicleMatrixNNLUSolver solver;
solver.decomposeLU(A);
solver.solve(b, result);
if (PxAbs(solver.getDet()) <= DETERMINANT_THRESHOLD)
return false;
for (PxU32 i = 0; i < numSprungMasses; i++)
{
sprungMasses[i] = result[i];
}
}
return true;
}
} //namespace vehicle2
} //namespace physx

View File

@@ -0,0 +1,720 @@
// 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-2025 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 "vehicle2/PxVehicleParams.h"
#include "vehicle2/drivetrain/PxVehicleDrivetrainStates.h"
#include "vehicle2/rigidBody/PxVehicleRigidBodyStates.h"
#include "vehicle2/roadGeometry/PxVehicleRoadGeometryState.h"
#include "vehicle2/suspension/PxVehicleSuspensionHelpers.h"
#include "vehicle2/tire/PxVehicleTireFunctions.h"
#include "vehicle2/tire/PxVehicleTireParams.h"
#include "vehicle2/wheel/PxVehicleWheelHelpers.h"
namespace physx
{
namespace vehicle2
{
PX_FORCE_INLINE PxF32 computeFilteredNormalisedTireLoad
(const PxF32 xmin, const PxF32 ymin, const PxF32 xmax, const PxF32 ymax, const PxF32 x)
{
if (x <= xmin)
{
return ymin;
}
else if (x >= xmax)
{
return ymax;
}
else
{
return (ymin + (x - xmin)*(ymax - ymin)/ (xmax - xmin));
}
}
void PxVehicleTireDirsLegacyUpdate
(const PxVehicleSuspensionParams& suspParams,
const PxReal steerAngle, const PxVehicleRoadGeometryState& rdGeomState, const PxVehicleRigidBodyState& rigidBodyState,
const PxVehicleFrame& frame,
PxVehicleTireDirectionState& trSlipDirs)
{
trSlipDirs.setToDefault();
//If there are no hits we'll have no ground plane.
if (!rdGeomState.hitState)
return;
//Compute wheel orientation with zero compliance, zero steer and zero
//rotation. Ignore rotation because it plays no role due to radial
//symmetry of wheel. Steer will be applied to the pose so we can
//ignore when computing the orientation. Compliance ought to be applied but
//we're not doing this in legacy code.
const PxQuat wheelOrientation = PxVehicleComputeWheelOrientation(frame, suspParams, 0.0f, 0.0f, 0.0f,
rigidBodyState.pose.q, 0.0f);
//We need lateral dir and hit norm to project wheel into ground plane.
const PxVec3 latDir = wheelOrientation.rotate(frame.getLatAxis());
const PxVec3& hitNorm = rdGeomState.plane.n;
//Compute the tire axes in the ground plane.
PxVec3 tLongRaw = latDir.cross(hitNorm);
PxVec3 tLatRaw = hitNorm.cross(tLongRaw);
tLongRaw.normalize();
tLatRaw.normalize();
//Rotate the tire using the steer angle.
const PxF32 yawAngle = steerAngle;
const PxF32 cosWheelSteer = PxCos(yawAngle);
const PxF32 sinWheelSteer = PxSin(yawAngle);
const PxVec3 tLong = tLongRaw * cosWheelSteer + tLatRaw * sinWheelSteer;
const PxVec3 tLat = tLatRaw * cosWheelSteer - tLongRaw * sinWheelSteer;
trSlipDirs.directions[PxVehicleTireDirectionModes::eLATERAL] = tLat;
trSlipDirs.directions[PxVehicleTireDirectionModes::eLONGITUDINAL] = tLong;
}
void PxVehicleTireDirsUpdate
(const PxVehicleSuspensionParams& suspParams,
const PxReal steerAngle, const PxVec3& groundNormal, bool isWheelOnGround,
const PxVehicleSuspensionComplianceState& compState,
const PxVehicleRigidBodyState& rigidBodyState,
const PxVehicleFrame& frame,
PxVehicleTireDirectionState& trSlipDirs)
{
trSlipDirs.setToDefault();
//Skip if the suspension could not push the wheel to the ground.
if (!isWheelOnGround)
return;
//Compute the wheel quaternion in the world frame.
//Ignore rotation because it plays no role due to radial symmetry of wheel.
const PxQuat wheelOrientation = PxVehicleComputeWheelOrientation(frame, suspParams, compState.camber, compState.toe, steerAngle,
rigidBodyState.pose.q, 0.0f);
//We need lateral dir and hit norm to project wheel into ground plane.
const PxVec3 latDir = wheelOrientation.rotate(frame.getLatAxis());
//Compute the tire axes in the ground plane.
PxVec3 lng = latDir.cross(groundNormal);
PxVec3 lat = groundNormal.cross(lng);
lng.normalize();
lat.normalize();
//Set the direction vectors.
trSlipDirs.directions[PxVehicleTireDirectionModes::eLATERAL] = lat;
trSlipDirs.directions[PxVehicleTireDirectionModes::eLONGITUDINAL] = lng;
}
PX_FORCE_INLINE PxF32 computeLateralSlip
(const PxF32 lngSpeed, const PxF32 latSpeed, const PxF32 minLatSlipDenominator)
{
const PxF32 latSlip = PxAtan(latSpeed / (PxAbs(lngSpeed) + minLatSlipDenominator));
return latSlip;
}
PX_FORCE_INLINE PxF32 computeLongitudionalSlip
(const bool useLegacyLongSlipCalculation,
const PxF32 longSpeed,
const PxF32 wheelOmega, const PxF32 wheelRadius,
const PxF32 minPassiveLongSlipDenominator, const PxF32 minActiveLongSlipDenominator,
const bool isAccelApplied, const bool isBrakeApplied)
{
const PxF32 longSpeedAbs = PxAbs(longSpeed);
const PxF32 wheelSpeed = wheelOmega * wheelRadius;
const PxF32 wheelSpeedAbs = PxAbs(wheelSpeed);
PxF32 lngSlip = 0.0f;
//If nothing is moving just avoid a divide by zero and set the long slip to zero.
if (longSpeed == 0 && wheelOmega == 0)
{
lngSlip = 0.0f;
}
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.
if (useLegacyLongSlipCalculation)
{
lngSlip = (wheelSpeed - longSpeed) / (PxMax(longSpeedAbs, wheelSpeedAbs) + minActiveLongSlipDenominator);
}
else
{
lngSlip = (wheelSpeed - longSpeed) / (longSpeedAbs + minActiveLongSlipDenominator);
}
}
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.
if (useLegacyLongSlipCalculation)
{
lngSlip = (wheelSpeed - longSpeed) / (PxMax(minPassiveLongSlipDenominator, PxMax(longSpeedAbs, wheelSpeedAbs)));
}
else
{
lngSlip = (wheelSpeed - longSpeed) / (longSpeedAbs + minPassiveLongSlipDenominator);
}
}
}
return lngSlip;
}
void PxVehicleTireSlipSpeedsUpdate
(const PxVehicleWheelParams& whlParams, const PxVehicleSuspensionParams& suspParams,
const PxF32 steerAngle, const PxVehicleSuspensionState& suspStates, const PxVehicleTireDirectionState& trSlipDirs,
const PxVehicleRigidBodyState& rigidBodyState, const PxVehicleRoadGeometryState& roadGeometryState,
const PxVehicleFrame& frame,
PxVehicleTireSpeedState& trSpeedState)
{
trSpeedState.setToDefault();
//Compute the position of the bottom of the wheel (placed on the ground plane).
PxVec3 wheelBottomPos;
{
PxVec3 v,w;
PxF32 dist;
PxVehicleComputeSuspensionRaycast(frame, whlParams, suspParams, steerAngle, rigidBodyState.pose, v, w, dist);
wheelBottomPos = v + w*(dist - suspStates.jounce);
}
//Compute the rigid body velocity at the bottom of the wheel (placed on the ground plane).
PxVec3 wheelBottomVel;
{
const PxVec3 r = wheelBottomPos - rigidBodyState.pose.p;
wheelBottomVel = rigidBodyState.linearVelocity + rigidBodyState.angularVelocity.cross(r) - roadGeometryState.velocity;
}
//Comput the velocities along lateral and longitudinal tire directions.
trSpeedState.speedStates[PxVehicleTireDirectionModes::eLONGITUDINAL] = wheelBottomVel.dot(trSlipDirs.directions[PxVehicleTireDirectionModes::eLONGITUDINAL]);
trSpeedState.speedStates[PxVehicleTireDirectionModes::eLATERAL] = wheelBottomVel.dot(trSlipDirs.directions[PxVehicleTireDirectionModes::eLATERAL]);
}
void vehicleTireSlipsUpdate
(const PxVehicleWheelParams& whlParams,
const PxVehicleTireSlipParams& trSlipParams, const bool useLegacyLongSlipCalculation,
const PxVehicleWheelActuationState& actState, PxVehicleTireSpeedState& trSpeedState, const PxVehicleWheelRigidBody1dState& whlRigidBody1dState,
PxVehicleTireSlipState& trSlipState)
{
trSlipState.setToDefault();
PxF32 latSlip = 0.0f;
{
const PxF32 lngSpeed = trSpeedState.speedStates[PxVehicleTireDirectionModes::eLONGITUDINAL];
const PxF32 latSpeed = trSpeedState.speedStates[PxVehicleTireDirectionModes::eLATERAL];
const PxF32 minLatSlipDenominator = trSlipParams.minLatSlipDenominator;
latSlip = computeLateralSlip(lngSpeed, latSpeed, minLatSlipDenominator);
}
PxF32 lngSlip = 0.0f;
{
const PxF32 lngSpeed = trSpeedState.speedStates[PxVehicleTireDirectionModes::eLONGITUDINAL];
const PxF32 wheelRotSpeed = whlRigidBody1dState.rotationSpeed;
const PxF32 wheelRadius = whlParams.radius;
const PxF32 minPassiveLngSlipDenominator = trSlipParams.minPassiveLongSlipDenominator;
const PxF32 minActiveLngSlipDenominator = trSlipParams.minActiveLongSlipDenominator;
const bool isBrakeApplied = actState.isBrakeApplied;
const bool isAccelApplied = actState.isDriveApplied;
lngSlip = computeLongitudionalSlip(
useLegacyLongSlipCalculation,
lngSpeed, wheelRotSpeed, wheelRadius,
minPassiveLngSlipDenominator, minActiveLngSlipDenominator,
isAccelApplied, isBrakeApplied);
}
trSlipState.slips[PxVehicleTireDirectionModes::eLATERAL] = latSlip;
trSlipState.slips[PxVehicleTireDirectionModes::eLONGITUDINAL] = lngSlip;
}
void PxVehicleTireSlipsUpdate
(const PxVehicleWheelParams& whlParams,
const PxVehicleTireSlipParams& trSlipParams,
const PxVehicleWheelActuationState& actState, PxVehicleTireSpeedState& trSpeedState, const PxVehicleWheelRigidBody1dState& whlRigidBody1dState,
PxVehicleTireSlipState& trSlipState)
{
vehicleTireSlipsUpdate(
whlParams, trSlipParams, false,
actState, trSpeedState, whlRigidBody1dState,
trSlipState);
}
void PxVehicleTireSlipsLegacyUpdate
(const PxVehicleWheelParams& whlParams,
const PxVehicleTireSlipParams& trSlipParams,
const PxVehicleWheelActuationState& actState, PxVehicleTireSpeedState& trSpeedState, const PxVehicleWheelRigidBody1dState& whlRigidBody1dState,
PxVehicleTireSlipState& trSlipState)
{
vehicleTireSlipsUpdate(
whlParams, trSlipParams, true,
actState, trSpeedState, whlRigidBody1dState,
trSlipState);
}
void PxVehicleTireCamberAnglesUpdate
(const PxVehicleSuspensionParams& suspParams,
const PxReal steerAngle, const PxVec3& groundNormal, bool isWheelOnGround,
const PxVehicleSuspensionComplianceState& compState,
const PxVehicleRigidBodyState& rigidBodyState,
const PxVehicleFrame& frame,
PxVehicleTireCamberAngleState& trCamberAngleState)
{
trCamberAngleState.setToDefault();
//Use zero camber if the suspension could not push the wheel to the ground.
if (!isWheelOnGround)
return;
//Compute the wheel quaternion in the world frame.
//Ignore rotation due to radial symmetry.
const PxQuat wheelOrientation = PxVehicleComputeWheelOrientation(frame, suspParams, compState.camber, compState.toe, steerAngle,
rigidBodyState.pose.q, 0.0f);
//Compute the axes of the wheel.
const PxVec3 latDir = wheelOrientation.rotate(frame.getLatAxis());
const PxVec3 lngDir = wheelOrientation.rotate(frame.getLngAxis());
//Project normal into lateral/vertical plane.
//Start with:
//n = lat*alpha + lng*beta + vrt*delta
//Want to work out
//T = n - lng*beta
// = n - lng*(n.dot(lng))
//Don't forget to normalise T.
//For the angle theta to look for we have:
//T.vrtDir = cos(theta)
//However, the cosine destroys the sign of the angle, thus we
//use:
//T.latDir = cos(pi/2 - theta) = sin(theta) (latDir and vrtDir are perpendicular)
const PxF32 beta = groundNormal.dot(lngDir);
PxVec3 T = groundNormal - lngDir * beta;
T.normalize();
const PxF32 sinTheta = T.dot(latDir);
const PxF32 theta = PxAsin(sinTheta);
trCamberAngleState.camberAngle = theta;
}
PX_FORCE_INLINE PxF32 updateLowLngSpeedTimer
(const PxF32 lngSpeed, const PxF32 wheelOmega, const PxF32 wheelRadius, const PxF32 lngThresholdSpeed,
const bool isIntentionToAccelerate, const PxF32 dt, const PxF32 lowLngSpeedTime)
{
//If the tire is rotating slowly and the longitudinal speed is slow then increment the slow longitudinal 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 newLowSpeedTime = 0.0f;
if ((PxAbs(lngSpeed) < lngThresholdSpeed) && (PxAbs(wheelOmega*wheelRadius) < lngThresholdSpeed) && !isIntentionToAccelerate)
{
newLowSpeedTime = lowLngSpeedTime + dt;
}
else
{
newLowSpeedTime = 0;
}
return newLowSpeedTime;
}
PX_FORCE_INLINE void activateStickyFrictionLngConstraint
(const PxF32 longSpeed, const PxF32 wheelOmega, const PxF32 lowLngSpeedTime, const bool isIntentionToBrake,
const PxF32 thresholdSpeed, const PxF32 thresholdTime,
bool& stickyTireActiveFlag)
{
//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).
//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 longitudinal speed at the tire contact point is vanishingly small.
//(2) the accumulated time of low longitudinal speed is greater than a threshold.
stickyTireActiveFlag = false;
if (((PxAbs(longSpeed) < thresholdSpeed) && (0.0f == wheelOmega) && isIntentionToBrake) || (lowLngSpeedTime > thresholdTime))
{
stickyTireActiveFlag = true;
}
}
PX_FORCE_INLINE PxF32 updateLowLatSpeedTimer
(const PxF32 latSpeed, const bool isIntentionToAccelerate, const PxF32 timestep, const PxF32 thresholdSpeed, const PxF32 lowSpeedTime)
{
//If the lateral speed is slow then increment the slow lateral 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 newLowSpeedTime = lowSpeedTime;
if ((PxAbs(latSpeed) < thresholdSpeed) && !isIntentionToAccelerate)
{
newLowSpeedTime += timestep;
}
else
{
newLowSpeedTime = 0;
}
return newLowSpeedTime;
}
PX_FORCE_INLINE void activateStickyFrictionLatConstraint
(const bool lowSpeedLngTimerActive,
const PxF32 lowLatSpeedTimer, const PxF32 thresholdTime,
bool& stickyTireActiveFlag)
{
//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).
//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 longitudinal speed timer is > 0.
//(2) the accumulated time of low longitudinal speed is greater than a threshold.
stickyTireActiveFlag = false;
if (lowSpeedLngTimerActive && (lowLatSpeedTimer > thresholdTime))
{
stickyTireActiveFlag = true;
}
}
void PxVehicleTireStickyStateUpdate
(const PxVehicleAxleDescription& axleDescription, const PxVehicleWheelParams& whlParams,
const PxVehicleTireStickyParams& trStickyParams,
const PxVehicleArrayData<const PxVehicleWheelActuationState>& actuationStates,
const PxVehicleTireGripState& trGripState, const PxVehicleTireSpeedState& trSpeedState, const PxVehicleWheelRigidBody1dState& whlState,
const PxReal dt,
PxVehicleTireStickyState& trStickyState)
{
trStickyState.activeStatus[PxVehicleTireDirectionModes::eLONGITUDINAL] = false;
trStickyState.activeStatus[PxVehicleTireDirectionModes::eLATERAL] = false;
//Only process sticky state if tire can generate force.
const PxF32 load = trGripState.load;
const PxF32 friction = trGripState.friction;
if(0 == load*friction)
{
trStickyState.lowSpeedTime[PxVehicleTireDirectionModes::eLONGITUDINAL] = 0.0f;
trStickyState.lowSpeedTime[PxVehicleTireDirectionModes::eLATERAL] = 0.0f;
return;
}
//Work out if any wheel is to have a drive applied to it.
bool isIntentionToAccelerate = false;
bool isIntentionToBrake = false;
for(PxU32 i = 0; i < axleDescription.nbWheels; i++)
{
const PxU32 wheelId = axleDescription.wheelIdsInAxleOrder[i];
if(actuationStates[wheelId].isDriveApplied)
isIntentionToAccelerate = true;
if (actuationStates[wheelId].isBrakeApplied)
isIntentionToBrake = true;
}
//Long sticky state.
bool lngTimerActive = false;
{
const PxF32 lngSpeed = trSpeedState.speedStates[PxVehicleTireDirectionModes::eLONGITUDINAL];
const PxF32 wheelOmega = whlState.rotationSpeed;
const PxF32 wheelRadius = whlParams.radius;
const PxF32 lngThresholdSpeed = trStickyParams.stickyParams[PxVehicleTireDirectionModes::eLONGITUDINAL].thresholdSpeed;
const PxF32 lngLowSpeedTime = trStickyState.lowSpeedTime[PxVehicleTireDirectionModes::eLONGITUDINAL];
const PxF32 newLowLngSpeedTime = updateLowLngSpeedTimer(
lngSpeed, wheelOmega, wheelRadius, lngThresholdSpeed,
isIntentionToAccelerate,
dt, lngLowSpeedTime);
lngTimerActive = (newLowLngSpeedTime > 0);
bool lngActiveState = false;
const PxF32 lngThresholdTime = trStickyParams.stickyParams[PxVehicleTireDirectionModes::eLONGITUDINAL].thresholdTime;
activateStickyFrictionLngConstraint(
lngSpeed, wheelOmega, newLowLngSpeedTime, isIntentionToBrake,
lngThresholdSpeed, lngThresholdTime,
lngActiveState);
trStickyState.lowSpeedTime[PxVehicleTireDirectionModes::eLONGITUDINAL] = newLowLngSpeedTime;
trStickyState.activeStatus[PxVehicleTireDirectionModes::eLONGITUDINAL] = lngActiveState;
}
//Lateral sticky state
{
const PxF32 latSpeed = trSpeedState.speedStates[PxVehicleTireDirectionModes::eLATERAL];
const PxF32 latThresholdSpeed = trStickyParams.stickyParams[PxVehicleTireDirectionModes::eLATERAL].thresholdSpeed;
const PxF32 latLowSpeedTime = trStickyState.lowSpeedTime[PxVehicleTireDirectionModes::eLATERAL];
const PxF32 latNewLowSpeedTime = updateLowLatSpeedTimer(latSpeed, isIntentionToAccelerate, dt, latThresholdSpeed, latLowSpeedTime);
bool latActiveState = false;
const PxF32 latThresholdTime = trStickyParams.stickyParams[PxVehicleTireDirectionModes::eLATERAL].thresholdTime;
activateStickyFrictionLatConstraint(lngTimerActive, latNewLowSpeedTime, latThresholdTime,
latActiveState);
trStickyState.lowSpeedTime[PxVehicleTireDirectionModes::eLATERAL] = latNewLowSpeedTime;
trStickyState.activeStatus[PxVehicleTireDirectionModes::eLATERAL] = latActiveState;
}
}
PX_FORCE_INLINE PxF32 computeTireLoad
(const PxVehicleTireForceParams& trForceParams, const PxVehicleSuspensionForce& suspForce)
{
//Compute the normalised load.
const PxF32 rawLoad = suspForce.normalForce;
const PxF32 restLoad = trForceParams.restLoad;
const PxF32 normalisedLoad = rawLoad / restLoad;
//Get the load filter params.
const PxF32 minNormalisedLoad = trForceParams.loadFilter[0][0];
const PxF32 minFilteredNormalisedLoad = trForceParams.loadFilter[0][1];
const PxF32 maxNormalisedLoad = trForceParams.loadFilter[1][0];
const PxF32 maxFilteredNormalisedLoad = trForceParams.loadFilter[1][1];
//Compute the filtered load.
const PxF32 filteredNormalisedLoad = computeFilteredNormalisedTireLoad(minNormalisedLoad, minFilteredNormalisedLoad, maxNormalisedLoad, maxFilteredNormalisedLoad, normalisedLoad);
const PxF32 filteredLoad = restLoad * filteredNormalisedLoad;
//Set the load after applying the filter.
return filteredLoad;
}
PX_FORCE_INLINE PxF32 computeTireFriction
(const PxVehicleTireForceParams& trForceParams,
const PxReal frictionCoefficient, const PxVehicleTireSlipState& tireSlipState)
{
//Interpolate the friction using the long slip value.
const PxF32 x0 = trForceParams.frictionVsSlip[0][0];
const PxF32 y0 = trForceParams.frictionVsSlip[0][1];
const PxF32 x1 = trForceParams.frictionVsSlip[1][0];
const PxF32 y1 = trForceParams.frictionVsSlip[1][1];
const PxF32 x2 = trForceParams.frictionVsSlip[2][0];
const PxF32 y2 = trForceParams.frictionVsSlip[2][1];
const PxF32 longSlipAbs = PxAbs(tireSlipState.slips[PxVehicleTireDirectionModes::eLONGITUDINAL]);
PxF32 mu;
if (longSlipAbs < x1)
{
mu = y0 + (y1 - y0)*(longSlipAbs - x0) / (x1 - x0);
}
else if (longSlipAbs < x2)
{
mu = y1 + (y2 - y1)*(longSlipAbs - x1) / (x2 - x1);
}
else
{
mu = y2;
}
PX_ASSERT(mu >= 0);
const PxF32 tireFriction = frictionCoefficient * mu;
return tireFriction;
}
void PxVehicleTireGripUpdate
(const PxVehicleTireForceParams& trForceParams,
const PxReal frictionCoefficient, bool isWheelOnGround, const PxVehicleSuspensionForce& suspForce,
const PxVehicleTireSlipState& trSlipState,
PxVehicleTireGripState& trGripState)
{
trGripState.setToDefault();
//If the wheel is not touching the ground then carry on with zero grip state.
if (!isWheelOnGround)
return;
//Compute load and friction.
trGripState.load = computeTireLoad(trForceParams, suspForce);
trGripState.friction = computeTireFriction(trForceParams, frictionCoefficient, trSlipState);
}
void PxVehicleTireSlipsAccountingForStickyStatesUpdate
(const PxVehicleTireStickyState& trStickyState,
PxVehicleTireSlipState& trSlipState)
{
if(trStickyState.activeStatus[PxVehicleTireDirectionModes::eLATERAL])
trSlipState.slips[PxVehicleTireDirectionModes::eLATERAL] = 0.f;
if (trStickyState.activeStatus[PxVehicleTireDirectionModes::eLONGITUDINAL])
trSlipState.slips[PxVehicleTireDirectionModes::eLONGITUDINAL] = 0.f;
}
////////////////////////////////////////////////////////////////////////////
//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 computeTireForceMichiganModel
(const PxVehicleTireForceParams& tireData,
const PxF32 tireFriction,
const PxF32 longSlipUnClamped, const PxF32 latSlipUnClamped, const PxF32 camberUnclamped,
const PxF32 wheelRadius,
const PxF32 tireLoad,
PxF32& wheelTorque, PxF32& tireLongForceMag, PxF32& tireLatForceMag, PxF32& tireAlignMoment)
{
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 minimumSlipThreshold = 1e-5f;
const PxF32 latSlip = PxAbs(latSlipUnClamped) >= minimumSlipThreshold ? latSlipUnClamped : 0.0f;
const PxF32 longSlip = PxAbs(longSlipUnClamped) >= minimumSlipThreshold ? longSlipUnClamped : 0.0f;
const PxF32 camber = PxAbs(camberUnclamped) >= minimumSlipThreshold ? camberUnclamped : 0.0f;
//Normalise the tire load.
const PxF32 restTireLoad = tireData.restLoad;
const PxF32 normalisedTireLoad = tireLoad/ restTireLoad;
//Compute the lateral stiffness
const PxF32 latStiff = (0.0f == tireData.latStiffX) ? tireData.latStiffY : tireData.latStiffY*smoothingFunction1(normalisedTireLoad*3.0f / tireData.latStiffX);
//Get the longitudinal stiffness
const PxF32 longStiff = tireData.longStiff;
//Get the camber stiffness.
const PxF32 camberStiff = tireData.camberStiff;
//If long slip/lat slip/camber are all zero than there will be zero tire force.
if ((0 == latSlip*latStiff) && (0 == longSlip*longStiff) && (0 == camber*camberStiff))
{
return;
}
//Carry on and compute the forces.
const PxF32 TEff = PxTan(latSlip + (camber * camberStiff / latStiff)); // "+" because we define camber stiffness as a positive value
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/longStiff;
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;
}
void PxVehicleTireForcesUpdate
(const PxVehicleWheelParams& whlParams, const PxVehicleSuspensionParams& suspParams,
const PxVehicleTireForceParams& trForceParams,
const PxVehicleSuspensionComplianceState& compState,
const PxVehicleTireGripState& trGripState, const PxVehicleTireDirectionState& trDirectionState,
const PxVehicleTireSlipState& trSlipState, const PxVehicleTireCamberAngleState& cmbAngleState,
const PxVehicleRigidBodyState& rigidBodyState,
PxVehicleTireForce& trForce)
{
trForce.setToDefault();
//If the tire can generate no force then carry on with zero force.
if(0 == trGripState.friction*trGripState.load)
return;
PxF32 wheelTorque = 0;
PxF32 tireLongForceMag = 0;
PxF32 tireLatForceMag = 0;
PxF32 tireAlignMoment = 0;
const PxF32 friction = trGripState.friction;
const PxF32 tireLoad = trGripState.load;
const PxF32 lngSlip = trSlipState.slips[PxVehicleTireDirectionModes::eLONGITUDINAL];
const PxF32 latSlip = trSlipState.slips[PxVehicleTireDirectionModes::eLATERAL];
const PxF32 camber = cmbAngleState.camberAngle;
const PxF32 wheelRadius = whlParams.radius;
computeTireForceMichiganModel(trForceParams, friction, lngSlip, latSlip, camber, wheelRadius, tireLoad,
wheelTorque, tireLongForceMag, tireLatForceMag, tireAlignMoment);
//Compute the forces.
const PxVec3 tireLongForce = trDirectionState.directions[PxVehicleTireDirectionModes::eLONGITUDINAL]*tireLongForceMag;
const PxVec3 tireLatForce = trDirectionState.directions[PxVehicleTireDirectionModes::eLATERAL]*tireLatForceMag;
//Compute the torques.
const PxVec3 r = rigidBodyState.pose.rotate(suspParams.suspensionAttachment.transform(compState.tireForceAppPoint));
const PxVec3 tireLongTorque = r.cross(tireLongForce);
const PxVec3 tireLatTorque = r.cross(tireLatForce);
//Set the torques.
trForce.forces[PxVehicleTireDirectionModes::eLONGITUDINAL] = tireLongForce;
trForce.torques[PxVehicleTireDirectionModes::eLONGITUDINAL] = tireLongTorque;
trForce.forces[PxVehicleTireDirectionModes::eLATERAL] = tireLatForce;
trForce.torques[PxVehicleTireDirectionModes::eLATERAL] = tireLatTorque;
trForce.aligningMoment = tireAlignMoment;
trForce.wheelTorque = wheelTorque;
}
} //namespace vehicle2
} //namespace physx

View File

@@ -0,0 +1,65 @@
// 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-2025 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 "vehicle2/tire/PxVehicleTireHelpers.h"
namespace physx
{
namespace vehicle2
{
bool PxVehicleAccelerationIntentCompute
(const PxVehicleAxleDescription& driveVehicleAxleDesc, const PxVehicleArrayData<const PxVehicleWheelActuationState>& driveVehicleActuationStates)
{
bool isIntentionToAccelerate = false;
for (PxU32 i = 0; i < driveVehicleAxleDesc.nbWheels; i++)
{
const PxU32 wheelId = driveVehicleAxleDesc.wheelIdsInAxleOrder[i];
isIntentionToAccelerate = !isIntentionToAccelerate ? driveVehicleActuationStates[wheelId].isDriveApplied : isIntentionToAccelerate;
}
return isIntentionToAccelerate;
}
void PxVehicleTireStickyStateReset
(const bool intentionToAccelerate,
const PxVehicleAxleDescription& towedVehicleAxleDesc,
PxVehicleArrayData<PxVehicleTireStickyState>& towedVehicleStickyState)
{
if (!intentionToAccelerate)
return;
for (PxU32 i = 0; i < towedVehicleAxleDesc.nbWheels; i++)
{
const PxU32 wheelId = towedVehicleAxleDesc.wheelIdsInAxleOrder[i];
towedVehicleStickyState[wheelId].setToDefault();
}
}
} //namespace vehicle2
} //namespace physx

View File

@@ -0,0 +1,92 @@
// 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-2025 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 "vehicle2/PxVehicleParams.h"
#include "vehicle2/suspension/PxVehicleSuspensionStates.h"
#include "vehicle2/tire/PxVehicleTireStates.h"
#include "vehicle2/wheel/PxVehicleWheelFunctions.h"
#include "vehicle2/wheel/PxVehicleWheelStates.h"
#include "vehicle2/wheel/PxVehicleWheelParams.h"
namespace physx
{
namespace vehicle2
{
void PxVehicleWheelRotationAngleUpdate
(const PxVehicleWheelParams& whlParams,
const PxVehicleWheelActuationState& actState, const PxVehicleSuspensionState& suspState, const PxVehicleTireSpeedState& trSpeedState,
const PxReal thresholdForwardSpeedForWheelAngleIntegration, const PxReal dt,
PxVehicleWheelRigidBody1dState& whlRigidBody1dState)
{
//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
const PxF32 jounce = suspState.jounce;
const bool isBrakeApplied = actState.isBrakeApplied;
const bool isDriveApplied = actState.isDriveApplied;
const PxF32 lngSpeed = trSpeedState.speedStates[PxVehicleTireDirectionModes::eLONGITUDINAL];
const PxF32 absLngSpeed = PxAbs(lngSpeed);
PxF32 wheelOmega = whlRigidBody1dState.rotationSpeed;
if (jounce > 0 && //(i) wheel touching ground
!isBrakeApplied && //(ii) no brake applied
!isDriveApplied && //(iii) no drive torque applied
(absLngSpeed < thresholdForwardSpeedForWheelAngleIntegration)) //(iv) low speed
{
const PxF32 wheelRadius = whlParams.radius;
const PxF32 alpha = absLngSpeed / thresholdForwardSpeedForWheelAngleIntegration;
wheelOmega = (lngSpeed/wheelRadius)*(1.0f - alpha) + wheelOmega * alpha;
}
whlRigidBody1dState.correctedRotationSpeed = wheelOmega;
//Integrate angle.
PxF32 newRotAngle = whlRigidBody1dState.rotationAngle + wheelOmega * dt;
//Clamp in range (-2*Pi,2*Pi)
newRotAngle = newRotAngle - (PxI32(newRotAngle / PxTwoPi) * PxTwoPi);
//Set the angle.
whlRigidBody1dState.rotationAngle = newRotAngle;
}
} //namespace vehicle2
} //namespace physx