// 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 simple use of the physx vehicle sdk and demonstrates // how to simulate a vehicle with direct drive using parameters, states and // components maintained by the PhysX Vehicle SDK. Particlar attention is paid // to the simulation of a PhysX vehicle in a multi-threaded environment. // 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. //This snippet breaks the vehicle into into three distinct models: //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 angles. //3) a physx integration model that provides a representation of the vehicle in an associated physx scene. // It is a good idea to record and playback with pvd (PhysX Visual Debugger). //This snippet // **************************************************************************** #include #include "PxPhysicsAPI.h" #include "../snippetvehiclecommon/directdrivetrain/DirectDrivetrain.h" #include "../snippetvehiclecommon/serialization/BaseSerialization.h" #include "../snippetvehiclecommon/serialization/DirectDrivetrainSerialization.h" #include "../snippetvehiclecommon/SnippetVehicleHelpers.h" #include "../snippetutils/SnippetUtils.h" #include "../snippetcommon/SnippetPVD.h" #include "common/PxProfileZone.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; PxTaskManager* gTaskManager = NULL; //The path to the vehicle json files to be loaded. const char* gVehicleDataPath = NULL; //The vehicles with direct drivetrain #define NUM_VEHICLES 1024 DirectDriveVehicle gVehicles[NUM_VEHICLES]; PxVehiclePhysXActorBeginComponent* gPhysXBeginComponents[NUM_VEHICLES]; PxVehiclePhysXActorEndComponent* gPhysXEndComponents[NUM_VEHICLES]; #define NUM_WORKER_THREADS 4 #define UPDATE_BATCH_SIZE 1 #define NB_SUBSTEPS 1 //Vehicle simulation needs a simulation context //to store global parameters of the simulation such as //gravitational acceleration. PxVehiclePhysXSimulationContext gVehicleSimulationContext; //Gravitational acceleration const PxVec3 gGravity(0.0f, -9.81f, 0.0f); //The mapping between PxMaterial and friction. PxVehiclePhysXMaterialFriction gPhysXMaterialFrictions[16]; PxU32 gNbPhysXMaterialFrictions = 0; PxReal gPhysXDefaultMaterialFriction = 1.0f; //Give the vehicles a name so they can be identified in PVD. const char gVehicleName[] = "directDrive"; //A ground plane to drive on. PxRigidStatic* gGroundPlane = NULL; //Track the number of simulation steps. PxU32 gNbSimulateSteps = 0; //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.5f, 0.0f, 4.26f}, //throttle for 256 update steps at 60Hz }; 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. //Profile the different phases of a simulate step. struct UpdatePhases { enum Enum { eVEHICLE_PHYSX_BEGIN_COMPONENTS, eVEHICLE_UPDATE_COMPONENTS, eVEHICLE_PHYSX_END_COMPONENTS, ePHYSX_SCENE_SIMULATE, eMAX_NUM_UPDATE_STAGES }; }; static const char* gUpdatePhaseNames[UpdatePhases::eMAX_NUM_UPDATE_STAGES] = { "vehiclePhysXBeginComponents", "vehicleUpdateComponents", "vehiclePhysXEndComponents", "physXSceneSimulate" }; struct ProfileZones { PxU64 times[UpdatePhases::eMAX_NUM_UPDATE_STAGES]; ProfileZones() { for (int i = 0; i < UpdatePhases::eMAX_NUM_UPDATE_STAGES; ++i) times[i] = 0; } void print() { for (int i = 0; i < UpdatePhases::eMAX_NUM_UPDATE_STAGES; ++i) { float ms = SnippetUtils::getElapsedTimeInMilliseconds(times[i]); printf("%s: %f ms\n", gUpdatePhaseNames[i], PxF64(ms)); } } void zoneStart(UpdatePhases::Enum zoneId) { PxU64 time = SnippetUtils::getCurrentTimeCounterValue(); times[zoneId] -= time; } void zoneEnd(UpdatePhases::Enum zoneId) { PxU64 time = SnippetUtils::getCurrentTimeCounterValue(); times[zoneId] += time; } }; ProfileZones gProfileZones; class ScopedProfileZone { private: ScopedProfileZone(const ScopedProfileZone&); ScopedProfileZone& operator=(const ScopedProfileZone&); public: ScopedProfileZone(ProfileZones& zones, UpdatePhases::Enum zoneId) : mZones(zones) , mZoneId(zoneId) { zones.zoneStart(zoneId); } ~ScopedProfileZone() { mZones.zoneEnd(mZoneId); } private: ProfileZones& mZones; UpdatePhases::Enum mZoneId; }; #define SNIPPET_PROFILE_ZONE(zoneId) ScopedProfileZone PX_CONCAT(_scoped, __LINE__)(gProfileZones, zoneId) //TaskVehicleUpdates allows vehicle updates to be performed concurrently across //multiple threads. class TaskVehicleUpdates : public PxLightCpuTask { public: TaskVehicleUpdates() : PxLightCpuTask(), mTimestep(0), mGravity(PxVec3(0, 0, 0)), mThreadId(0xffffffff), mCommandProgress(0) { } void setThreadId(const PxU32 threadId) { mThreadId = threadId; } void setTimestep(const PxF32 timestep) { mTimestep = timestep; } void setGravity(const PxVec3& gravity) { mGravity = gravity; } void setCommandProgress(const PxU32 commandProgress) { mCommandProgress = commandProgress; } virtual void run() { PxU32 vehicleId = mThreadId * UPDATE_BATCH_SIZE; while (vehicleId < NUM_VEHICLES) { const PxU32 numToUpdate = PxMin(NUM_VEHICLES - vehicleId, static_cast(UPDATE_BATCH_SIZE)); for (PxU32 i = 0; i < numToUpdate; i++) { gVehicles[vehicleId + i].mCommandState.brakes[0] = gCommands[mCommandProgress].brake; gVehicles[vehicleId + i].mCommandState.nbBrakes = 1; gVehicles[vehicleId + i].mCommandState.throttle = gCommands[mCommandProgress].throttle; gVehicles[vehicleId + i].mCommandState.steer = gCommands[mCommandProgress].steer; gVehicles[vehicleId + i].mTransmissionCommandState.gear = PxVehicleDirectDriveTransmissionCommandState::eFORWARD; gVehicles[vehicleId + i].step(mTimestep, gVehicleSimulationContext); } vehicleId += NUM_WORKER_THREADS * UPDATE_BATCH_SIZE; } } virtual const char* getName() const { return "TaskVehicleUpdates"; } private: PxF32 mTimestep; PxVec3 mGravity; PxU32 mThreadId; PxU32 mCommandProgress; }; //TaskWait runs after all concurrent updates have completed. class TaskWait : public PxLightCpuTask { public: TaskWait(SnippetUtils::Sync* syncHandle) : PxLightCpuTask(), mSyncHandle(syncHandle) { } virtual void run() { } PX_INLINE void release() { PxLightCpuTask::release(); SnippetUtils::syncSet(mSyncHandle); } virtual const char* getName() const { return "TaskWait"; } private: SnippetUtils::Sync* mSyncHandle; }; void initPhysX() { gFoundation = PxCreateFoundation(PX_PHYSICS_VERSION, gAllocator, gErrorCallback); gPvd = PxCreatePvd(*gFoundation); PxPvdTransport* transport = PxDefaultPvdSocketTransportCreate(PVD_HOST, 5425, 10); gPvd->connect(*transport,PxPvdInstrumentationFlag::ePROFILE); gPhysics = PxCreatePhysics(PX_PHYSICS_VERSION, *gFoundation, PxTolerancesScale(), true, gPvd); PxSceneDesc sceneDesc(gPhysics->getTolerancesScale()); sceneDesc.gravity = gGravity; gDispatcher = PxDefaultCpuDispatcherCreate(NUM_WORKER_THREADS); sceneDesc.cpuDispatcher = gDispatcher; sceneDesc.filterShader = VehicleFilterShader; gScene = gPhysics->createScene(sceneDesc); PxPvdSceneClient* pvdClient = gScene->getScenePvdClient(); if(pvdClient) { pvdClient->setScenePvdFlag(PxPvdSceneFlag::eTRANSMIT_CONSTRAINTS, false); pvdClient->setScenePvdFlag(PxPvdSceneFlag::eTRANSMIT_CONTACTS, false); pvdClient->setScenePvdFlag(PxPvdSceneFlag::eTRANSMIT_SCENEQUERIES, false); } gMaterial = gPhysics->createMaterial(0.5f, 0.5f, 0.6f); ///////////////////////////////////////////// //Create a task manager that will be used to //update the vehicles concurrently across //multiple threads. ///////////////////////////////////////////// gTaskManager = PxTaskManager::createTaskManager(gFoundation->getErrorCallback(), gDispatcher); PxInitVehicleExtension(*gFoundation); } void cleanupPhysX() { PxCloseVehicleExtension(); PX_RELEASE(gTaskManager); 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 BaseVehicleParams baseParams; readBaseParamsFromJsonFile(gVehicleDataPath, "Base.json", baseParams); PhysXIntegrationParams physxParams; setPhysXIntegrationParams(baseParams.axleDescription, gPhysXMaterialFrictions, gNbPhysXMaterialFrictions, gPhysXDefaultMaterialFriction, physxParams); DirectDrivetrainParams directDrivetrainParams; readDirectDrivetrainParamsFromJsonFile(gVehicleDataPath, "DirectDrive.json", baseParams.axleDescription, directDrivetrainParams); //Create the params, states and component sequences for direct drive vehicles. //Take care not to add PxVehiclePhysXActorBeginComponent or PxVehiclePhysXActorEndComponent //to the sequences because are executed in a separate step. for (PxU32 i = 0; i < NUM_VEHICLES; i++) { //Set the vehicle params. //Every vehicle is identical. gVehicles[i].mBaseParams = baseParams; gVehicles[i].mPhysXParams = physxParams; gVehicles[i].mDirectDriveParams = directDrivetrainParams; //Set the states to default and create the component sequence. //Take care not to add PxVehiclePhysXActorBeginComponent and PxVehiclePhysXActorEndComponent //to the sequence because these are handled separately to take advantage of multi-threading. const bool addPhysXBeginAndEndComponentsToSequence = false; if (!gVehicles[i].initialize(*gPhysics, PxCookingParams(PxTolerancesScale()), *gMaterial, addPhysXBeginAndEndComponentsToSequence)) { return false; } //Force a known substep count per simulation step so that we have a perfect understanding of //the amount of computational effort involved in running the snippet. gVehicles[i].mComponentSequence.setSubsteps(gVehicles[i].mComponentSequenceSubstepGroupHandle, NB_SUBSTEPS); //Apply a start pose to the physx actor and add it to the physx scene. PxTransform pose(PxVec3(5.0f*(PxI32(i) - NUM_VEHICLES/2), 0.0f, 0.0f), PxQuat(PxIdentity)); gVehicles[i].setUpActor(*gScene, pose, gVehicleName); } //PhysX reads/writes require read/write locks that serialize executions. //Perform all physx reads/writes serially in a separate step to avoid serializing code that can take //advantage of multithreading. for (PxU32 i = 0; i < NUM_VEHICLES; i++) { gPhysXBeginComponents[i] = (static_cast(gVehicles + i)); gPhysXEndComponents[i] = (static_cast(gVehicles + i)); } //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; return true; } void cleanupVehicles() { for (PxU32 i = 0; i < NUM_VEHICLES; i++) { gVehicles[i].destroy(); } } bool initPhysics() { initPhysX(); initGroundPlane(); initMaterialFrictionTable(); if (!initVehicles()) return false; return true; } void cleanupPhysics() { cleanupVehicles(); cleanupGroundPlane(); cleanupPhysX(); printf("SnippetVehicleMultithreading done.\n"); } void concurrentVehicleUpdates(const PxReal timestep) { SnippetUtils::Sync* vehicleUpdatesComplete = SnippetUtils::syncCreate(); SnippetUtils::syncReset(vehicleUpdatesComplete); //Create tasks that will update the vehicles concurrently then wait until all vehicles //have completed their update. TaskWait taskWait(vehicleUpdatesComplete); TaskVehicleUpdates taskVehicleUpdates[NUM_WORKER_THREADS]; for (PxU32 i = 0; i < NUM_WORKER_THREADS; i++) { taskVehicleUpdates[i].setThreadId(i); taskVehicleUpdates[i].setTimestep(timestep); taskVehicleUpdates[i].setGravity(gScene->getGravity()); taskVehicleUpdates[i].setCommandProgress(gCommandProgress); } //Start the task manager. gTaskManager->resetDependencies(); gTaskManager->startSimulation(); //Perform a vehicle simulation step and profile each phase of the simulation. { //PhysX reads/writes require read/write locks that serialize executions. //Perform all physx reads/writes serially in a separate step to avoid serializing code that can take //advantage of multithreading. { SNIPPET_PROFILE_ZONE(UpdatePhases::eVEHICLE_PHYSX_BEGIN_COMPONENTS); for (PxU32 i = 0; i < NUM_VEHICLES; i++) { gPhysXBeginComponents[i]->update(timestep, gVehicleSimulationContext); } } //Multi-threaded update of direct drive vehicles. { SNIPPET_PROFILE_ZONE(UpdatePhases::eVEHICLE_UPDATE_COMPONENTS); //Update the vehicles concurrently then wait until all vehicles //have completed their update. taskWait.setContinuation(*gTaskManager, NULL); for (PxU32 i = 0; i < NUM_WORKER_THREADS; i++) { taskVehicleUpdates[i].setContinuation(&taskWait); } taskWait.removeReference(); for (PxU32 i = 0; i < NUM_WORKER_THREADS; i++) { taskVehicleUpdates[i].removeReference(); } //Wait for the signal that the work has been completed. SnippetUtils::syncWait(vehicleUpdatesComplete); //Release the sync handle SnippetUtils::syncRelease(vehicleUpdatesComplete); } //PhysX reads/writes require read/write locks that serialize executions. //Perform all physx reads/writes serially in a separate step to avoid serializing code that can take //advantage of multithreading. { SNIPPET_PROFILE_ZONE(UpdatePhases::eVEHICLE_PHYSX_END_COMPONENTS); for (PxU32 i = 0; i < NUM_VEHICLES; i++) { gPhysXEndComponents[i]->update(timestep, gVehicleSimulationContext); } } } } void stepPhysics() { if(gNbCommands == gCommandProgress) return; const PxF32 timestep = 0.0166667f; //Multithreaded update of all vehicles. concurrentVehicleUpdates(timestep); //Forward integrate the phsyx scene by a single timestep. SNIPPET_PROFILE_ZONE(UpdatePhases::ePHYSX_SCENE_SIMULATE); gScene->simulate(timestep); 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 += timestep; if(gCommandTime > gCommands[gCommandProgress].duration) { gCommandProgress++; gCommandTime = 0.0f; } gNbSimulateSteps++; } int snippetMain(int argc, const char*const* argv) { if(!parseVehicleDataPath(argc, argv, "SnippetVehicleMultithreading", 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; printf("Initialising ... \n"); if(initPhysics()) { printf("Simulating %d vehicles with %d threads \n", NUM_VEHICLES, NUM_WORKER_THREADS); while(gCommandProgress != gNbCommands) { stepPhysics(); } printf("Completed %d simulate steps with %d substeps per simulate step \n", gNbSimulateSteps, NB_SUBSTEPS); gProfileZones.print(); cleanupPhysics(); } return 0; }