px3World.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2012 GarageGames, LLC
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to
  6. // deal in the Software without restriction, including without limitation the
  7. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  8. // sell copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. // IN THE SOFTWARE.
  21. //-----------------------------------------------------------------------------
  22. #include "platform/platform.h"
  23. #include "T3D/physics/physx3/px3World.h"
  24. #include "T3D/physics/physx3/px3.h"
  25. #include "T3D/physics/physx3/px3Plugin.h"
  26. #include "T3D/physics/physx3/px3Casts.h"
  27. #include "T3D/physics/physx3/px3Stream.h"
  28. #include "T3D/physics/physicsUserData.h"
  29. #include "console/engineAPI.h"
  30. #include "core/stream/bitStream.h"
  31. #include "platform/profiler.h"
  32. #include "sim/netConnection.h"
  33. #include "console/console.h"
  34. #include "console/consoleTypes.h"
  35. #include "core/util/safeDelete.h"
  36. #include "collision/collision.h"
  37. #include "T3D/gameBase/gameProcess.h"
  38. #include "gfx/sim/debugDraw.h"
  39. #include "gfx/primBuilder.h"
  40. physx::PxPhysics* gPhysics3SDK = NULL;
  41. physx::PxCooking* Px3World::smCooking = NULL;
  42. physx::PxFoundation* Px3World::smFoundation = NULL;
  43. physx::PxDefaultCpuDispatcher* Px3World::smCpuDispatcher = NULL;
  44. #ifndef TORQUE_OS_MAC
  45. physx::PxCudaContextManager* Px3World::smCudaContextManager = NULL;
  46. #endif
  47. Px3ConsoleStream* Px3World::smErrorCallback = NULL;
  48. physx::PxPvd* Px3World::smPvdConnection = NULL;
  49. physx::PxPvdTransport* Px3World::smPvdTransport = NULL;
  50. physx::PxDefaultAllocator Px3World::smMemoryAlloc;
  51. Px3World::Px3World() :
  52. mScene( NULL ),
  53. mIsEnabled( false ),
  54. mIsSimulating( false ),
  55. mIsServer( false ),
  56. mIsSceneLocked( false ),
  57. mTickCount( 0 ),
  58. mProcessList( NULL ),
  59. mEditorTimeScale( 1.0f ),
  60. mErrorReport( false ),
  61. mControllerManager(NULL),
  62. mRenderBuffer(NULL),
  63. mAccumulator( 0 )
  64. {
  65. }
  66. Px3World::~Px3World()
  67. {
  68. }
  69. physx::PxCooking *Px3World::getCooking()
  70. {
  71. return smCooking;
  72. }
  73. bool Px3World::restartSDK( bool destroyOnly, Px3World *clientWorld, Px3World *serverWorld)
  74. {
  75. // If either the client or the server still exist
  76. // then we cannot reset the SDK.
  77. if ( clientWorld || serverWorld )
  78. return false;
  79. #ifndef TORQUE_OS_MAC
  80. SafeReleasePhysx(smCudaContextManager);
  81. #endif
  82. SafeReleasePhysx(smCpuDispatcher);
  83. SafeReleasePhysx(smCooking);
  84. smGpuEnabled = false;
  85. // Destroy the existing SDK.
  86. if ( gPhysics3SDK )
  87. {
  88. PxCloseExtensions();
  89. SafeReleasePhysx(gPhysics3SDK);
  90. }
  91. SafeReleasePhysx(smPvdConnection);
  92. SafeReleasePhysx(smPvdTransport);
  93. SAFE_DELETE(smErrorCallback);
  94. SafeReleasePhysx(smFoundation);
  95. // If we're not supposed to restart... return.
  96. if ( destroyOnly )
  97. return true;
  98. bool memTrack = false;
  99. #ifdef TORQUE_DEBUG
  100. memTrack = false;
  101. #endif
  102. smErrorCallback = new Px3ConsoleStream;
  103. smFoundation = PxCreateFoundation(PX_FOUNDATION_VERSION, smMemoryAlloc, *smErrorCallback);
  104. smPvdConnection = PxCreatePvd(*smFoundation);
  105. gPhysics3SDK = PxCreatePhysics(PX_PHYSICS_VERSION, *smFoundation, physx::PxTolerancesScale(),memTrack, smPvdConnection);
  106. if ( !gPhysics3SDK )
  107. {
  108. Con::errorf( "PhysX3 failed to initialize!" );
  109. Platform::messageBox( Con::getVariable( "$appName" ), avar("PhysX3 could not be started!\r\n"), MBOk, MIStop );
  110. Platform::forceShutdown( -1 );
  111. // We shouldn't get here, but this shuts up
  112. // source diagnostic tools.
  113. return false;
  114. }
  115. if(!PxInitExtensions(*gPhysics3SDK, smPvdConnection))
  116. {
  117. Con::errorf( "PhysX3 failed to initialize extensions!" );
  118. Platform::messageBox( Con::getVariable( "$appName" ), avar("PhysX3 could not be started!\r\n"), MBOk, MIStop );
  119. Platform::forceShutdown( -1 );
  120. return false;
  121. }
  122. //no gpu support on macOS
  123. #ifndef TORQUE_OS_MAC
  124. //check if we are allowed to use gpu acceleration
  125. if (PhysicsPlugin::gpuAccelerationAllowed())
  126. {
  127. // attempt to create a cuda context manager - only works on nvidia gpu (SM 3.0+ i.e kepler or better)
  128. if (!smCpuDispatcher)
  129. {
  130. //check we have capable gpu, -1 means none found
  131. S32 suggestedGpu = PxGetSuggestedCudaDeviceOrdinal(*smErrorCallback);
  132. if (suggestedGpu != -1)
  133. {
  134. physx::PxCudaContextManagerDesc cudaContextManagerDesc;
  135. smCudaContextManager = PxCreateCudaContextManager(*smFoundation, cudaContextManagerDesc);
  136. if (smCudaContextManager)
  137. smGpuEnabled = true;
  138. }
  139. }
  140. }
  141. #endif
  142. //cpu dispatcher
  143. if (!smCpuDispatcher)
  144. smCpuDispatcher = physx::PxDefaultCpuDispatcherCreate(PHYSICSMGR->getThreadCount());
  145. physx::PxCookingParams params = physx::PxCookingParams(physx::PxTolerancesScale());
  146. params.meshWeldTolerance = 0.001f;
  147. params.meshPreprocessParams = physx::PxMeshPreprocessingFlags(physx::PxMeshPreprocessingFlag::eWELD_VERTICES);
  148. #ifndef TORQUE_OS_MAC
  149. if(smGpuEnabled)
  150. params.buildGPUData = true;
  151. #endif
  152. smCooking = PxCreateCooking(PX_PHYSICS_VERSION, *smFoundation, params);
  153. if(!smCooking)
  154. {
  155. Con::errorf( "PhysX3 failed to initialize cooking!" );
  156. Platform::messageBox( Con::getVariable( "$appName" ), avar("PhysX3 could not be started!\r\n"), MBOk, MIStop );
  157. Platform::forceShutdown( -1 );
  158. return false;
  159. }
  160. //TODO: enable/disable this from script
  161. #ifdef TORQUE_DEBUG
  162. if(!smPvdTransport)
  163. smPvdTransport = physx::PxDefaultPvdSocketTransportCreate("localhost", 5425, 100);
  164. smPvdConnection->connect(*smPvdTransport, physx::PxPvdInstrumentationFlag::eALL);
  165. #endif
  166. //use legacy heightfield
  167. //TODO: new method causing crashes on collision in debug build (unified HeightFields)
  168. PxRegisterLegacyHeightFields(*gPhysics3SDK);
  169. return true;
  170. }
  171. void Px3World::destroyWorld()
  172. {
  173. getPhysicsResults();
  174. // Release the tick processing signals.
  175. if ( mProcessList )
  176. {
  177. mProcessList->preTickSignal().remove( this, &Px3World::getPhysicsResults );
  178. mProcessList->postTickSignal().remove( this, &Px3World::tickPhysics );
  179. mProcessList = NULL;
  180. }
  181. SafeReleasePhysx(mControllerManager);
  182. // Destroy the scene.
  183. SafeReleasePhysx(mScene);
  184. }
  185. bool Px3World::initWorld( bool isServer, ProcessList *processList )
  186. {
  187. if ( !gPhysics3SDK )
  188. {
  189. Con::errorf( "Physx3World::init - PhysXSDK not initialized!" );
  190. return false;
  191. }
  192. mIsServer = isServer;
  193. physx::PxSceneDesc sceneDesc(gPhysics3SDK->getTolerancesScale());
  194. sceneDesc.gravity = px3Cast<physx::PxVec3>(mGravity);
  195. sceneDesc.userData = this;
  196. sceneDesc.cpuDispatcher = smCpuDispatcher;
  197. Con::printf("PhysX3 using Cpu: %d workers", smCpuDispatcher->getWorkerCount());
  198. #ifndef TORQUE_OS_MAC
  199. if (smGpuEnabled)
  200. {
  201. sceneDesc.flags |= physx::PxSceneFlag::eENABLE_GPU_DYNAMICS;
  202. sceneDesc.flags |= physx::PxSceneFlag::eENABLE_PCM;
  203. sceneDesc.broadPhaseType = physx::PxBroadPhaseType::eGPU;
  204. sceneDesc.gpuDispatcher = smCudaContextManager->getGpuDispatcher();
  205. Con::printf("PhysX3 using Gpu: %s", smCudaContextManager->getDeviceName());
  206. }
  207. #endif
  208. sceneDesc.flags |= physx::PxSceneFlag::eENABLE_CCD;
  209. sceneDesc.flags |= physx::PxSceneFlag::eENABLE_ACTIVETRANSFORMS;
  210. sceneDesc.filterShader = physx::PxDefaultSimulationFilterShader;
  211. mScene = gPhysics3SDK->createScene(sceneDesc);
  212. mRenderBuffer = const_cast<physx::PxRenderBuffer*>(&mScene->getRenderBuffer());
  213. physx::PxDominanceGroupPair debrisDominance( 0.0f, 1.0f );
  214. mScene->setDominanceGroupPair(0,31,debrisDominance);
  215. mControllerManager = PxCreateControllerManager(*mScene);
  216. AssertFatal( processList, "Px3World::init() - We need a process list to create the world!" );
  217. mProcessList = processList;
  218. mProcessList->preTickSignal().notify( this, &Px3World::getPhysicsResults );
  219. mProcessList->postTickSignal().notify( this, &Px3World::tickPhysics, 1000.0f );
  220. return true;
  221. }
  222. // Most of this borrowed from bullet physics library, see btDiscreteDynamicsWorld.cpp
  223. bool Px3World::_simulate(const F32 dt)
  224. {
  225. S32 numSimulationSubSteps = 0;
  226. //fixed timestep with interpolation
  227. mAccumulator += dt;
  228. if (mAccumulator >= smPhysicsStepTime)
  229. {
  230. numSimulationSubSteps = S32(mAccumulator / smPhysicsStepTime);
  231. mAccumulator -= numSimulationSubSteps * smPhysicsStepTime;
  232. }
  233. if (numSimulationSubSteps)
  234. {
  235. //clamp the number of substeps, to prevent simulation grinding spiralling down to a halt
  236. S32 clampedSimulationSteps = (numSimulationSubSteps > smPhysicsMaxSubSteps) ? smPhysicsMaxSubSteps : numSimulationSubSteps;
  237. for (S32 i=0;i<clampedSimulationSteps;i++)
  238. {
  239. if(i > 0)
  240. mScene->fetchResults(true);
  241. mScene->simulate(smPhysicsStepTime);
  242. }
  243. }
  244. mIsSimulating = true;
  245. return true;
  246. }
  247. void Px3World::tickPhysics( U32 elapsedMs )
  248. {
  249. if ( !mScene || !mIsEnabled )
  250. return;
  251. // Did we forget to call getPhysicsResults somewhere?
  252. AssertFatal( !mIsSimulating, "PhysX3World::tickPhysics() - Already simulating!" );
  253. // The elapsed time should be non-zero and
  254. // a multiple of TickMs!
  255. AssertFatal( elapsedMs != 0 &&
  256. ( elapsedMs % TickMs ) == 0 , "PhysX3World::tickPhysics() - Got bad elapsed time!" );
  257. PROFILE_SCOPE(Px3World_TickPhysics);
  258. // Convert it to seconds.
  259. const F32 elapsedSec = (F32)elapsedMs * 0.001f;
  260. mIsSimulating = _simulate(elapsedSec * mEditorTimeScale);
  261. //Con::printf( "%s PhysX3World::tickPhysics!", mIsServer ? "Client" : "Server" );
  262. }
  263. void Px3World::getPhysicsResults()
  264. {
  265. if ( !mScene || !mIsSimulating )
  266. return;
  267. PROFILE_SCOPE(Px3World_GetPhysicsResults);
  268. // Get results from scene.
  269. mScene->fetchResults(true);
  270. mIsSimulating = false;
  271. mTickCount++;
  272. }
  273. void Px3World::lockScenes()
  274. {
  275. Px3World *world = dynamic_cast<Px3World*>(PHYSICSMGR->getWorld("server"));
  276. if (world)
  277. world->lockScene();
  278. world = dynamic_cast<Px3World*>(PHYSICSMGR->getWorld("client"));
  279. if (world)
  280. world->lockScene();
  281. }
  282. void Px3World::unlockScenes()
  283. {
  284. Px3World *world = dynamic_cast<Px3World*>(PHYSICSMGR->getWorld("server"));
  285. if (world)
  286. world->unlockScene();
  287. world = dynamic_cast<Px3World*>(PHYSICSMGR->getWorld("client"));
  288. if (world)
  289. world->unlockScene();
  290. }
  291. void Px3World::lockScene()
  292. {
  293. if (!mScene)
  294. return;
  295. if (mIsSceneLocked)
  296. {
  297. Con::printf("Px3World: Attempting to lock a scene that is already locked.");
  298. return;
  299. }
  300. mScene->lockWrite();
  301. mIsSceneLocked = true;
  302. }
  303. void Px3World::unlockScene()
  304. {
  305. if (!mScene)
  306. return;
  307. if (!mIsSceneLocked)
  308. {
  309. Con::printf("Px3World: Attempting to unlock a scene that is not locked.");
  310. return;
  311. }
  312. mScene->unlockWrite();
  313. mIsSceneLocked = false;
  314. }
  315. bool Px3World::castRay( const Point3F &startPnt, const Point3F &endPnt, RayInfo *ri, const Point3F &impulse )
  316. {
  317. physx::PxVec3 orig = px3Cast<physx::PxVec3>( startPnt );
  318. physx::PxVec3 dir = px3Cast<physx::PxVec3>( endPnt - startPnt );
  319. physx::PxF32 maxDist = dir.magnitude();
  320. dir.normalize();
  321. U32 groups = 0xffffffff;
  322. groups &= ~( PX3_TRIGGER ); // No trigger shapes!
  323. physx::PxHitFlags outFlags(physx::PxHitFlag::eDISTANCE | physx::PxHitFlag::ePOSITION | physx::PxHitFlag::eNORMAL);
  324. physx::PxQueryFilterData filterData(physx::PxQueryFlag::eSTATIC|physx::PxQueryFlag::eDYNAMIC);
  325. filterData.data.word0 = groups;
  326. physx::PxRaycastBuffer buf;
  327. if(!mScene->raycast(orig,dir,maxDist,buf,outFlags,filterData))
  328. return false;
  329. if(!buf.hasBlock)
  330. return false;
  331. const physx::PxRaycastHit hit = buf.block;
  332. physx::PxRigidActor *actor = hit.actor;
  333. PhysicsUserData *userData = PhysicsUserData::cast( actor->userData );
  334. if ( ri )
  335. {
  336. ri->object = ( userData != NULL ) ? userData->getObject() : NULL;
  337. if ( ri->object == NULL )
  338. ri->distance = hit.distance;
  339. ri->normal = px3Cast<Point3F>( hit.normal );
  340. ri->point = px3Cast<Point3F>( hit.position );
  341. ri->t = maxDist / hit.distance;
  342. }
  343. if ( impulse.isZero() ||
  344. !actor->is<physx::PxRigidDynamic>() ||
  345. actor->is<physx::PxRigidDynamic>()->getRigidBodyFlags() & physx::PxRigidBodyFlag::eKINEMATIC )
  346. return true;
  347. physx::PxRigidBody *body = actor->is<physx::PxRigidBody>();
  348. physx::PxVec3 force = px3Cast<physx::PxVec3>( impulse );
  349. physx::PxRigidBodyExt::addForceAtPos(*body,force,hit.position,physx::PxForceMode::eIMPULSE);
  350. return true;
  351. }
  352. PhysicsBody* Px3World::castRay( const Point3F &start, const Point3F &end, U32 bodyTypes )
  353. {
  354. physx::PxVec3 orig = px3Cast<physx::PxVec3>( start );
  355. physx::PxVec3 dir = px3Cast<physx::PxVec3>( end - start );
  356. physx::PxF32 maxDist = dir.magnitude();
  357. dir.normalize();
  358. U32 groups = 0xFFFFFFFF;
  359. if ( !( bodyTypes & BT_Player ) )
  360. groups &= ~( PX3_PLAYER );
  361. // TODO: For now always skip triggers and debris,
  362. // but we should consider how game specifc this API
  363. // should be in the future.
  364. groups &= ~( PX3_TRIGGER ); // triggers
  365. groups &= ~( PX3_DEBRIS ); // debris
  366. physx::PxHitFlags outFlags(physx::PxHitFlag::eDISTANCE | physx::PxHitFlag::ePOSITION | physx::PxHitFlag::eNORMAL);
  367. physx::PxQueryFilterData filterData;
  368. if(bodyTypes & BT_Static)
  369. filterData.flags |= physx::PxQueryFlag::eSTATIC;
  370. if(bodyTypes & BT_Dynamic)
  371. filterData.flags |= physx::PxQueryFlag::eDYNAMIC;
  372. filterData.data.word0 = groups;
  373. physx::PxRaycastBuffer buf;
  374. if( !mScene->raycast(orig,dir,maxDist,buf,outFlags,filterData) )
  375. return NULL;
  376. if(!buf.hasBlock)
  377. return NULL;
  378. physx::PxRigidActor *actor = buf.block.actor;
  379. PhysicsUserData *userData = PhysicsUserData::cast( actor->userData );
  380. if( !userData )
  381. return NULL;
  382. return userData->getBody();
  383. }
  384. void Px3World::explosion( const Point3F &pos, F32 radius, F32 forceMagnitude )
  385. {
  386. physx::PxVec3 nxPos = px3Cast<physx::PxVec3>( pos );
  387. const physx::PxU32 bufferSize = 10;
  388. physx::PxSphereGeometry worldSphere(radius);
  389. physx::PxTransform pose(nxPos);
  390. physx::PxOverlapBufferN<bufferSize> buffer;
  391. if(!mScene->overlap(worldSphere,pose,buffer))
  392. return;
  393. for ( physx::PxU32 i = 0; i < buffer.nbTouches; i++ )
  394. {
  395. physx::PxRigidActor *actor = buffer.touches[i].actor;
  396. bool dynamic = actor->is<physx::PxRigidDynamic>();
  397. if ( !dynamic )
  398. continue;
  399. bool kinematic = actor->is<physx::PxRigidDynamic>()->getRigidBodyFlags() & physx::PxRigidBodyFlag::eKINEMATIC;
  400. if ( kinematic )
  401. continue;
  402. physx::PxVec3 force = actor->getGlobalPose().p - nxPos;
  403. force.normalize();
  404. force *= forceMagnitude;
  405. physx::PxRigidBody *body = actor->is<physx::PxRigidBody>();
  406. physx::PxRigidBodyExt::addForceAtPos(*body,force,nxPos,physx::PxForceMode::eIMPULSE);
  407. }
  408. }
  409. void Px3World::setEnabled( bool enabled )
  410. {
  411. mIsEnabled = enabled;
  412. if ( !mIsEnabled )
  413. getPhysicsResults();
  414. }
  415. physx::PxController* Px3World::createController( physx::PxControllerDesc &desc )
  416. {
  417. if ( !mScene )
  418. return NULL;
  419. physx::PxController* pController = mControllerManager->createController(desc);
  420. AssertFatal( pController, "Px3World::createController - Got a null!" );
  421. return pController;
  422. }
  423. static ColorI getDebugColor( physx::PxU32 packed )
  424. {
  425. ColorI col;
  426. col.blue = (packed)&0xff;
  427. col.green = (packed>>8)&0xff;
  428. col.red = (packed>>16)&0xff;
  429. col.alpha = 255;
  430. return col;
  431. }
  432. void Px3World::onDebugDraw( const SceneRenderState *state )
  433. {
  434. if ( !mScene || !mRenderBuffer)
  435. return;
  436. mScene->setVisualizationParameter(physx::PxVisualizationParameter::eSCALE,1.0f);
  437. mScene->setVisualizationParameter(physx::PxVisualizationParameter::eBODY_AXES,1.0f);
  438. mScene->setVisualizationParameter(physx::PxVisualizationParameter::eCOLLISION_SHAPES,1.0f);
  439. // Render points
  440. {
  441. physx::PxU32 numPoints = mRenderBuffer->getNbPoints();
  442. const physx::PxDebugPoint *points = mRenderBuffer->getPoints();
  443. PrimBuild::begin( GFXPointList, numPoints );
  444. while ( numPoints-- )
  445. {
  446. PrimBuild::color( getDebugColor(points->color) );
  447. PrimBuild::vertex3fv(px3Cast<Point3F>(points->pos));
  448. points++;
  449. }
  450. PrimBuild::end();
  451. }
  452. // Render lines
  453. {
  454. physx::PxU32 numLines = mRenderBuffer->getNbLines();
  455. const physx::PxDebugLine *lines = mRenderBuffer->getLines();
  456. PrimBuild::begin( GFXLineList, numLines * 2 );
  457. while ( numLines-- )
  458. {
  459. PrimBuild::color( getDebugColor( lines->color0 ) );
  460. PrimBuild::vertex3fv( px3Cast<Point3F>(lines->pos0));
  461. PrimBuild::color( getDebugColor( lines->color1 ) );
  462. PrimBuild::vertex3fv( px3Cast<Point3F>(lines->pos1));
  463. lines++;
  464. }
  465. PrimBuild::end();
  466. }
  467. // Render triangles
  468. {
  469. physx::PxU32 numTris = mRenderBuffer->getNbTriangles();
  470. const physx::PxDebugTriangle *triangles = mRenderBuffer->getTriangles();
  471. PrimBuild::begin( GFXTriangleList, numTris * 3 );
  472. while ( numTris-- )
  473. {
  474. PrimBuild::color( getDebugColor( triangles->color0 ) );
  475. PrimBuild::vertex3fv( px3Cast<Point3F>(triangles->pos0) );
  476. PrimBuild::color( getDebugColor( triangles->color1 ) );
  477. PrimBuild::vertex3fv( px3Cast<Point3F>(triangles->pos1));
  478. PrimBuild::color( getDebugColor( triangles->color2 ) );
  479. PrimBuild::vertex3fv( px3Cast<Point3F>(triangles->pos2) );
  480. triangles++;
  481. }
  482. PrimBuild::end();
  483. }
  484. }