Files
XCEngine/engine/third_party/physx/snippets/snippetvehiclecustomtire/SnippetVehicleCustomTire.cpp

404 lines
16 KiB
C++
Raw Normal View History

// 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.
// ****************************************************************************
// This snippet illustrates how to change the tire model using custom vehicle components.
//
// Vehicles are made of parameters, states and components.
// Parameters describe the configuration of a vehicle. Examples are vehicle mass, wheel radius
// and suspension stiffness.
// States describe the instantaneous dynamic state of a vehicle. Examples are engine revs, wheel
// yaw angle and tire slip angles.
// Components forward integrate the dynamic state of the vehicle, given the previous vehicle state
// and the vehicle's parameterisation.
// Components update dynamic state by invoking reusable functions in a particular sequence.
// An example component is a rigid body component that updates the linear and angular velocity of
// the vehicle's rigid body given the instantaneous forces and torques of the suspension and tire
// states.
// The pipeline of vehicle computation is a sequence of components that run in order. For example,
// one component might compute the plane under the wheel by performing a scene query against the
// world geometry. The next component in the sequence might compute the suspension compression required
// to place the wheel on the surface of the hit plane. Following this, another component might compute
// the suspension force that arises from that compression. The rigid body component, as discussed earlier,
// can then forward integrate the rigid body's linear velocity using the suspension force.
// Custom combinations of parameter, state and component allow different behaviours to be simulated with
// different simulation fidelities. For example, a suspension component that implements a linear force
// response with respect to its compression state could be replaced with one that imlements a non-linear
// response. The replacement component would consume the same suspension compression state data and
// would output the same suspension force data structure. In this example, the change has been localised
// to the component that converts suspension compression to force and to the parameterisation that governs
// that conversion.
// Another combination example could be the replacement of the tire component from a low fidelity model to
// a high fidelty model such as Pacejka. The low and high fidelity components consume the same state data
// (tire slip, load, friction) and output the same state data for the tire forces. Again, the
// change has been localised to the component that converts slip angle to tire force and the
// parameterisation that governs the conversion.
//The PhysX Vehicle SDK presents a maintained set of parameters, states and components. The maintained
//set of parameters, states and components may be combined on their own or combined with custom parameters,
//states and components.
// Custom combinations of parameter, state and component allow different behaviours to be simulated with
// different simulation fidelities. In this example, the change has been localised to the tire component
// to replace the low fidelity model with the high fidelty .
// The low and high fidelity components consume the same state data (tire slip, load, friction) and
// output the same state data for the tire forces. Again, the change has been localised to the component
// that converts slip angle to tire force and the parameterisation that governs the conversion.
// This snippet demonstrates how to modify the vehicle component pipeline to include a custom tire model.
// The vehicle is then a mixture of custom components and components maintained by the PhysX Vehicle SDK.
// In this instance, the custom component implements a version of the Magic Formula Tire Model (also
// known as Pacejka) to compute the tire forces. The Magic Formula Tire Model uses measurements on
// real tires to infer the parameters of predefined formulas such that the resulting graphs will fit closely
// to the measurement points.
//This snippet organises the components into four distinct groups.
//1) a base vehicle model that describes the mechanical configuration of suspensions, tires, wheels and an
// associated rigid body.
//2) a direct drive drivetrain model that forwards input controls to wheel torques and steer angles.
//3) a physx integration model that provides a representation of the vehicle in an associated physx scene.
//4) a custom tire model
// It is a good idea to record and playback with pvd (PhysX Visual Debugger).
// ****************************************************************************
#include <ctype.h>
#include "PxPhysicsAPI.h"
#include "../snippetvehiclecommon/serialization/BaseSerialization.h"
#include "../snippetvehiclecommon/serialization/DirectDrivetrainSerialization.h"
#include "../snippetvehiclecommon/SnippetVehicleHelpers.h"
#include "../snippetcommon/SnippetPVD.h"
#include "CustomTireVehicle.h"
using namespace physx;
using namespace physx::vehicle2;
using namespace snippetvehicle;
//PhysX management class instances.
PxDefaultAllocator gAllocator;
PxDefaultErrorCallback gErrorCallback;
PxFoundation* gFoundation = NULL;
PxPhysics* gPhysics = NULL;
PxDefaultCpuDispatcher* gDispatcher = NULL;
PxScene* gScene = NULL;
PxMaterial* gMaterial = NULL;
PxPvd* gPvd = NULL;
//The path to the vehicle json files to be loaded.
const char* gVehicleDataPath = NULL;
//The vehicle with the custom tire component
CustomTireVehicle gVehicle;
//Vehicle simulation needs a simulation context
//to store global parameters of the simulation such as
//gravitational acceleration.
PxVehiclePhysXSimulationContext gVehicleSimulationContext;
//If sweeps are used for wheel vs. ground collision detection, then
//a cylinder mesh (with unit size) needs to be provided to do the sweep.
PxConvexMesh* gUnitCylinderSweepMesh = NULL;
//Gravitational acceleration
const PxVec3 gGravity(0.0f, -9.81f, 0.0f);
//The timestep of the simulation
const PxReal gTimestep = 1.0f / 60.0f;
//The timestep for the tire model simulation. For the Magic Formula
//Tire Model a high update rate is recommended, like 1 kHz.
const PxReal gTireModelTimestep = 1.0f / 1000.0f;
//The mapping between PxMaterial and friction.
PxVehiclePhysXMaterialFriction gPhysXMaterialFrictions[16];
PxU32 gNbPhysXMaterialFrictions = 0;
PxReal gPhysXDefaultMaterialFriction = 1.0f;
//Give the vehicle a name so it can be identified in PVD.
const char gVehicleName[] = "CustomTireVehicle";
//Commands are issued to the vehicle in a pre-choreographed sequence.
struct Command
{
PxF32 brake;
PxF32 throttle;
PxF32 steer;
PxF32 duration;
};
Command gCommands[] =
{
{0.0f, 0.0f, 0.0f, 2.0f}, //fall on ground and come to rest for 2 seconds
{0.0f, 0.5f, 0.0f, 5.0f}, //throttle for 5 seconds
{0.5f, 0.0f, 0.0f, 5.0f}, //brake for 5 seconds
{0.0f, 0.5f, 0.0f, 5.0f}, //throttle for 5 seconds
{0.0f, 0.1f, 0.5f, 5.0f} //light throttle and steer for 5 seconds.
};
const PxU32 gNbCommands = sizeof(gCommands) / sizeof(Command);
PxReal gCommandTime = 0.0f; //Time spent on current command
PxU32 gCommandProgress = 0; //The id of the current command.
//A ground plane to drive on.
PxRigidStatic* gGroundPlane = NULL;
void initPhysX()
{
gFoundation = PxCreateFoundation(PX_PHYSICS_VERSION, gAllocator, gErrorCallback);
gPvd = PxCreatePvd(*gFoundation);
PxPvdTransport* transport = PxDefaultPvdSocketTransportCreate(PVD_HOST, 5425, 10);
gPvd->connect(*transport, PxPvdInstrumentationFlag::eALL);
gPhysics = PxCreatePhysics(PX_PHYSICS_VERSION, *gFoundation, PxTolerancesScale(), true, gPvd);
PxSceneDesc sceneDesc(gPhysics->getTolerancesScale());
sceneDesc.gravity = gGravity;
PxU32 numWorkers = 1;
gDispatcher = PxDefaultCpuDispatcherCreate(numWorkers);
sceneDesc.cpuDispatcher = gDispatcher;
sceneDesc.filterShader = VehicleFilterShader;
gScene = gPhysics->createScene(sceneDesc);
PxPvdSceneClient* pvdClient = gScene->getScenePvdClient();
if (pvdClient)
{
pvdClient->setScenePvdFlag(PxPvdSceneFlag::eTRANSMIT_CONSTRAINTS, true);
pvdClient->setScenePvdFlag(PxPvdSceneFlag::eTRANSMIT_CONTACTS, true);
pvdClient->setScenePvdFlag(PxPvdSceneFlag::eTRANSMIT_SCENEQUERIES, true);
}
gMaterial = gPhysics->createMaterial(0.5f, 0.5f, 0.6f);
PxInitVehicleExtension(*gFoundation);
}
void cleanupPhysX()
{
PxCloseVehicleExtension();
PX_RELEASE(gMaterial);
PX_RELEASE(gScene);
PX_RELEASE(gDispatcher);
PX_RELEASE(gPhysics);
if (gPvd)
{
PxPvdTransport* transport = gPvd->getTransport();
PX_RELEASE(gPvd);
PX_RELEASE(transport);
}
PX_RELEASE(gFoundation);
}
void initGroundPlane()
{
gGroundPlane = PxCreatePlane(*gPhysics, PxPlane(0, 1, 0, 0), *gMaterial);
for (PxU32 i = 0; i < gGroundPlane->getNbShapes(); i++)
{
PxShape* shape = NULL;
gGroundPlane->getShapes(&shape, 1, i);
shape->setFlag(PxShapeFlag::eSCENE_QUERY_SHAPE, true);
shape->setFlag(PxShapeFlag::eSIMULATION_SHAPE, false);
shape->setFlag(PxShapeFlag::eTRIGGER_SHAPE, false);
}
gScene->addActor(*gGroundPlane);
}
void cleanupGroundPlane()
{
gGroundPlane->release();
}
void initMaterialFrictionTable()
{
//Each physx material can be mapped to a tire friction value on a per tire basis.
//If a material is encountered that is not mapped to a friction value, the friction value used is the specified default value.
//In this snippet there is only a single material so there can only be a single mapping between material and friction.
//In this snippet the same mapping is used by all tires.
gPhysXMaterialFrictions[0].friction = 1.0f;
gPhysXMaterialFrictions[0].material = gMaterial;
gPhysXDefaultMaterialFriction = 1.0f;
gNbPhysXMaterialFrictions = 1;
}
bool initVehicles()
{
//Load the params from json or set directly.
readBaseParamsFromJsonFile(gVehicleDataPath, "Base.json", gVehicle.mBaseParams);
setPhysXIntegrationParams(gVehicle.mBaseParams.axleDescription,
gPhysXMaterialFrictions, gNbPhysXMaterialFrictions, gPhysXDefaultMaterialFriction,
gVehicle.mPhysXParams);
readDirectDrivetrainParamsFromJsonFile(gVehicleDataPath, "DirectDrive.json",
gVehicle.mBaseParams.axleDescription, gVehicle.mDirectDriveParams);
//Set the states to default.
PxCookingParams cookingParams(gPhysics->getTolerancesScale());
if (!gVehicle.initialize(*gPhysics, cookingParams, *gMaterial))
{
return false;
}
gVehicle.mCommandState.nbBrakes = 1;
gVehicle.mTransmissionCommandState.gear = PxVehicleDirectDriveTransmissionCommandState::eFORWARD;
//Apply a start pose to the physx actor and add it to the physx scene.
PxTransform pose(PxVec3(-5.0f, 0.5f, 0.0f), PxQuat(PxIdentity));
gVehicle.setUpActor(*gScene, pose, gVehicleName);
//Disabling sleeping in this snippet to show stability at rest is reached independent of
//sleeping.
gVehicle.mPhysXState.physxActor.rigidBody->is<PxRigidDynamic>()->setSleepThreshold(0.0f);
gVehicle.mPhysXState.physxActor.rigidBody->is<PxRigidDynamic>()->setStabilizationThreshold(0.0f);
//Set the substep count to match the targeted tire model simulation timestep
PxU8 substepCount = static_cast<PxU8>(gTimestep / gTireModelTimestep);
gVehicle.mComponentSequence.setSubsteps(gVehicle.mComponentSequenceSubstepGroupHandle, substepCount);
//Set up the simulation context.
//The snippet is set up with
//a) z as the longitudinal axis
//b) x as the lateral axis
//c) y as the vertical axis.
//d) metres as the lengthscale.
gVehicleSimulationContext.setToDefault();
gVehicleSimulationContext.frame.lngAxis = PxVehicleAxes::ePosZ;
gVehicleSimulationContext.frame.latAxis = PxVehicleAxes::ePosX;
gVehicleSimulationContext.frame.vrtAxis = PxVehicleAxes::ePosY;
gVehicleSimulationContext.scale.scale = 1.0f;
gVehicleSimulationContext.gravity = gGravity;
gVehicleSimulationContext.physxScene = gScene;
gVehicleSimulationContext.physxActorUpdateMode = PxVehiclePhysXActorUpdateMode::eAPPLY_ACCELERATION;
gUnitCylinderSweepMesh = PxVehicleUnitCylinderSweepMeshCreate(gVehicleSimulationContext.frame,
*gPhysics, cookingParams);
if (!gUnitCylinderSweepMesh)
{
return false;
}
gVehicleSimulationContext.physxUnitCylinderSweepMesh = gUnitCylinderSweepMesh;
//Larger lateral damping factor than default to avoid drift when nearly rest
gVehicleSimulationContext.tireStickyParams.stickyParams[PxVehicleTireDirectionModes::eLATERAL].damping = 1.0f;
return true;
}
void cleanupVehicles()
{
if (gUnitCylinderSweepMesh)
PxVehicleUnitCylinderSweepMeshDestroy(gUnitCylinderSweepMesh);
gVehicle.destroy();
}
bool initPhysics()
{
initPhysX();
initGroundPlane();
initMaterialFrictionTable();
if (!initVehicles())
return false;
return true;
}
void cleanupPhysics()
{
cleanupVehicles();
cleanupGroundPlane();
cleanupPhysX();
printf("SnippetVehicleCustomTire done.\n");
}
void stepPhysics()
{
if (gNbCommands == gCommandProgress)
return;
//Apply the brake, throttle and steer to the command state of the direct drive vehicle.
const Command& command = gCommands[gCommandProgress];
gVehicle.mCommandState.brakes[0] = command.brake;
gVehicle.mCommandState.throttle = command.throttle;
gVehicle.mCommandState.steer = command.steer;
//Forward integrate the vehicle by a single timestep.
gVehicle.step(gTimestep, gVehicleSimulationContext);
//Forward integrate the phsyx scene by a single timestep.
gScene->simulate(gTimestep);
gScene->fetchResults(true);
//Increment the time spent on the current command.
//Move to the next command in the list if enough time has lapsed.
gCommandTime += gTimestep;
if (gCommandTime > gCommands[gCommandProgress].duration)
{
gCommandProgress++;
gCommandTime = 0.0f;
}
}
int snippetMain(int argc, const char *const* argv)
{
if (!parseVehicleDataPath(argc, argv, "SnippetVehicleCustomTire", gVehicleDataPath))
return 1;
//Check that we can read from the json file before continuing.
BaseVehicleParams baseParams;
if (!readBaseParamsFromJsonFile(gVehicleDataPath, "Base.json", baseParams))
return 1;
//Check that we can read from the json file before continuing.
DirectDrivetrainParams directDrivetrainParams;
if (!readDirectDrivetrainParamsFromJsonFile(gVehicleDataPath, "DirectDrive.json",
baseParams.axleDescription, directDrivetrainParams))
return 1;
#ifdef RENDER_SNIPPET
extern void renderLoop(const char*);
renderLoop("PhysX Snippet Vehicle Custom Tire");
#else
if(initPhysics())
{
while(gCommandProgress != gNbCommands)
{
stepPhysics();
}
cleanupPhysics();
}
#endif
return 0;
}