px3World.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  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::PxProfileZoneManager* Px3World::smProfileZoneManager = NULL;
  44. physx::PxDefaultCpuDispatcher* Px3World::smCpuDispatcher=NULL;
  45. Px3ConsoleStream* Px3World::smErrorCallback = NULL;
  46. physx::PxVisualDebuggerConnection* Px3World::smPvdConnection=NULL;
  47. physx::PxDefaultAllocator Px3World::smMemoryAlloc;
  48. //Physics timing
  49. F32 Px3World::smPhysicsStepTime = 1.0f/(F32)TickMs;
  50. U32 Px3World::smPhysicsMaxIterations = 4;
  51. Px3World::Px3World(): mScene( NULL ),
  52. mProcessList( NULL ),
  53. mIsSimulating( false ),
  54. mErrorReport( false ),
  55. mTickCount( 0 ),
  56. mIsEnabled( false ),
  57. mEditorTimeScale( 1.0f ),
  58. mAccumulator( 0 ),
  59. mControllerManager(NULL),
  60. mIsSceneLocked(false)
  61. {
  62. }
  63. Px3World::~Px3World()
  64. {
  65. }
  66. physx::PxCooking *Px3World::getCooking()
  67. {
  68. return smCooking;
  69. }
  70. void Px3World::setTiming(F32 stepTime,U32 maxIterations)
  71. {
  72. smPhysicsStepTime = stepTime;
  73. smPhysicsMaxIterations = maxIterations;
  74. }
  75. bool Px3World::restartSDK( bool destroyOnly, Px3World *clientWorld, Px3World *serverWorld)
  76. {
  77. // If either the client or the server still exist
  78. // then we cannot reset the SDK.
  79. if ( clientWorld || serverWorld )
  80. return false;
  81. if(smPvdConnection)
  82. smPvdConnection->release();
  83. if(smCooking)
  84. smCooking->release();
  85. if(smCpuDispatcher)
  86. smCpuDispatcher->release();
  87. // Destroy the existing SDK.
  88. if ( gPhysics3SDK )
  89. {
  90. PxCloseExtensions();
  91. gPhysics3SDK->release();
  92. }
  93. if(smErrorCallback)
  94. {
  95. SAFE_DELETE(smErrorCallback);
  96. }
  97. if(smFoundation)
  98. {
  99. smFoundation->release();
  100. SAFE_DELETE(smErrorCallback);
  101. }
  102. // If we're not supposed to restart... return.
  103. if ( destroyOnly )
  104. return true;
  105. bool memTrack = false;
  106. #ifdef TORQUE_DEBUG
  107. memTrack = true;
  108. #endif
  109. smErrorCallback = new Px3ConsoleStream;
  110. smFoundation = PxCreateFoundation(PX_PHYSICS_VERSION, smMemoryAlloc, *smErrorCallback);
  111. smProfileZoneManager = &physx::PxProfileZoneManager::createProfileZoneManager(smFoundation);
  112. gPhysics3SDK = PxCreatePhysics(PX_PHYSICS_VERSION, *smFoundation, physx::PxTolerancesScale(),memTrack,smProfileZoneManager);
  113. if ( !gPhysics3SDK )
  114. {
  115. Con::errorf( "PhysX3 failed to initialize!" );
  116. Platform::messageBox( Con::getVariable( "$appName" ),
  117. avar("PhysX3 could not be started!\r\n"),
  118. MBOk, MIStop );
  119. Platform::forceShutdown( -1 );
  120. // We shouldn't get here, but this shuts up
  121. // source diagnostic tools.
  122. return false;
  123. }
  124. if(!PxInitExtensions(*gPhysics3SDK))
  125. {
  126. Con::errorf( "PhysX3 failed to initialize extensions!" );
  127. Platform::messageBox( Con::getVariable( "$appName" ),
  128. avar("PhysX3 could not be started!\r\n"),
  129. MBOk, MIStop );
  130. Platform::forceShutdown( -1 );
  131. return false;
  132. }
  133. smCooking = PxCreateCooking(PX_PHYSICS_VERSION, *smFoundation, physx::PxCookingParams(physx::PxTolerancesScale()));
  134. if(!smCooking)
  135. {
  136. Con::errorf( "PhysX3 failed to initialize cooking!" );
  137. Platform::messageBox( Con::getVariable( "$appName" ),
  138. avar("PhysX3 could not be started!\r\n"),
  139. MBOk, MIStop );
  140. Platform::forceShutdown( -1 );
  141. return false;
  142. }
  143. #ifdef TORQUE_DEBUG
  144. physx::PxVisualDebuggerConnectionFlags connectionFlags(physx::PxVisualDebuggerExt::getAllConnectionFlags());
  145. smPvdConnection = physx::PxVisualDebuggerExt::createConnection(gPhysics3SDK->getPvdConnectionManager(),
  146. "localhost", 5425, 100, connectionFlags);
  147. #endif
  148. return true;
  149. }
  150. void Px3World::destroyWorld()
  151. {
  152. getPhysicsResults();
  153. // Release the tick processing signals.
  154. if ( mProcessList )
  155. {
  156. mProcessList->preTickSignal().remove( this, &Px3World::getPhysicsResults );
  157. mProcessList->postTickSignal().remove( this, &Px3World::tickPhysics );
  158. mProcessList = NULL;
  159. }
  160. if(mControllerManager)
  161. {
  162. mControllerManager->release();
  163. mControllerManager = NULL;
  164. }
  165. // Destroy the scene.
  166. if ( mScene )
  167. {
  168. // Release the scene.
  169. mScene->release();
  170. mScene = NULL;
  171. }
  172. }
  173. bool Px3World::initWorld( bool isServer, ProcessList *processList )
  174. {
  175. if ( !gPhysics3SDK )
  176. {
  177. Con::errorf( "Physx3World::init - PhysXSDK not initialized!" );
  178. return false;
  179. }
  180. mIsServer = isServer;
  181. physx::PxSceneDesc sceneDesc(gPhysics3SDK->getTolerancesScale());
  182. sceneDesc.gravity = px3Cast<physx::PxVec3>(mGravity);
  183. sceneDesc.userData = this;
  184. if(!sceneDesc.cpuDispatcher)
  185. {
  186. //Create shared cpu dispatcher
  187. if(!smCpuDispatcher)
  188. smCpuDispatcher = physx::PxDefaultCpuDispatcherCreate(PHYSICSMGR->getThreadCount());
  189. sceneDesc.cpuDispatcher = smCpuDispatcher;
  190. Con::printf("PhysX3 using Cpu: %d workers", smCpuDispatcher->getWorkerCount());
  191. }
  192. sceneDesc.flags |= physx::PxSceneFlag::eENABLE_CCD;
  193. sceneDesc.flags |= physx::PxSceneFlag::eENABLE_ACTIVETRANSFORMS;
  194. sceneDesc.filterShader = physx::PxDefaultSimulationFilterShader;
  195. mScene = gPhysics3SDK->createScene(sceneDesc);
  196. physx::PxDominanceGroupPair debrisDominance( 0.0f, 1.0f );
  197. mScene->setDominanceGroupPair(0,31,debrisDominance);
  198. mControllerManager = PxCreateControllerManager(*mScene);
  199. AssertFatal( processList, "Px3World::init() - We need a process list to create the world!" );
  200. mProcessList = processList;
  201. mProcessList->preTickSignal().notify( this, &Px3World::getPhysicsResults );
  202. mProcessList->postTickSignal().notify( this, &Px3World::tickPhysics, 1000.0f );
  203. return true;
  204. }
  205. // Most of this borrowed from bullet physics library, see btDiscreteDynamicsWorld.cpp
  206. bool Px3World::_simulate(const F32 dt)
  207. {
  208. int numSimulationSubSteps = 0;
  209. //fixed timestep with interpolation
  210. mAccumulator += dt;
  211. if (mAccumulator >= smPhysicsStepTime)
  212. {
  213. numSimulationSubSteps = int(mAccumulator / smPhysicsStepTime);
  214. mAccumulator -= numSimulationSubSteps * smPhysicsStepTime;
  215. }
  216. if (numSimulationSubSteps)
  217. {
  218. //clamp the number of substeps, to prevent simulation grinding spiralling down to a halt
  219. int clampedSimulationSteps = (numSimulationSubSteps > smPhysicsMaxIterations)? smPhysicsMaxIterations : numSimulationSubSteps;
  220. for (int i=0;i<clampedSimulationSteps;i++)
  221. {
  222. mScene->fetchResults(true);
  223. mScene->simulate(smPhysicsStepTime);
  224. }
  225. }
  226. mIsSimulating = true;
  227. return true;
  228. }
  229. void Px3World::tickPhysics( U32 elapsedMs )
  230. {
  231. if ( !mScene || !mIsEnabled )
  232. return;
  233. // Did we forget to call getPhysicsResults somewhere?
  234. AssertFatal( !mIsSimulating, "PhysX3World::tickPhysics() - Already simulating!" );
  235. // The elapsed time should be non-zero and
  236. // a multiple of TickMs!
  237. AssertFatal( elapsedMs != 0 &&
  238. ( elapsedMs % TickMs ) == 0 , "PhysX3World::tickPhysics() - Got bad elapsed time!" );
  239. PROFILE_SCOPE(Px3World_TickPhysics);
  240. // Convert it to seconds.
  241. const F32 elapsedSec = (F32)elapsedMs * 0.001f;
  242. mIsSimulating = _simulate(elapsedSec * mEditorTimeScale);
  243. //Con::printf( "%s PhysX3World::tickPhysics!", mIsServer ? "Client" : "Server" );
  244. }
  245. void Px3World::getPhysicsResults()
  246. {
  247. if ( !mScene || !mIsSimulating )
  248. return;
  249. PROFILE_SCOPE(Px3World_GetPhysicsResults);
  250. // Get results from scene.
  251. mScene->fetchResults(true);
  252. mIsSimulating = false;
  253. mTickCount++;
  254. // Con::printf( "%s PhysXWorld::getPhysicsResults!", this == smClientWorld ? "Client" : "Server" );
  255. }
  256. void Px3World::releaseWriteLocks()
  257. {
  258. Px3World *world = dynamic_cast<Px3World*>( PHYSICSMGR->getWorld( "server" ) );
  259. if ( world )
  260. world->releaseWriteLock();
  261. world = dynamic_cast<Px3World*>( PHYSICSMGR->getWorld( "client" ) );
  262. if ( world )
  263. world->releaseWriteLock();
  264. }
  265. void Px3World::releaseWriteLock()
  266. {
  267. if ( !mScene || !mIsSimulating )
  268. return;
  269. PROFILE_SCOPE(PxWorld_ReleaseWriteLock);
  270. // We use checkResults here to release the write lock
  271. // but we do not change the simulation flag or increment
  272. // the tick count... we may have gotten results, but the
  273. // simulation hasn't really ticked!
  274. mScene->checkResults( true );
  275. //AssertFatal( mScene->isWritable(), "PhysX3World::releaseWriteLock() - We should have been writable now!" );
  276. }
  277. void Px3World::lockScenes()
  278. {
  279. Px3World *world = dynamic_cast<Px3World*>(PHYSICSMGR->getWorld("server"));
  280. if (world)
  281. world->lockScene();
  282. world = dynamic_cast<Px3World*>(PHYSICSMGR->getWorld("client"));
  283. if (world)
  284. world->lockScene();
  285. }
  286. void Px3World::unlockScenes()
  287. {
  288. Px3World *world = dynamic_cast<Px3World*>(PHYSICSMGR->getWorld("server"));
  289. if (world)
  290. world->unlockScene();
  291. world = dynamic_cast<Px3World*>(PHYSICSMGR->getWorld("client"));
  292. if (world)
  293. world->unlockScene();
  294. }
  295. void Px3World::lockScene()
  296. {
  297. if (!mScene)
  298. return;
  299. if (mIsSceneLocked)
  300. {
  301. Con::printf("Px3World: Attempting to lock a scene that is already locked.");
  302. return;
  303. }
  304. mScene->lockWrite();
  305. mIsSceneLocked = true;
  306. }
  307. void Px3World::unlockScene()
  308. {
  309. if (!mScene)
  310. return;
  311. if (!mIsSceneLocked)
  312. {
  313. Con::printf("Px3World: Attempting to unlock a scene that is not locked.");
  314. return;
  315. }
  316. mScene->unlockWrite();
  317. mIsSceneLocked = false;
  318. }
  319. bool Px3World::castRay( const Point3F &startPnt, const Point3F &endPnt, RayInfo *ri, const Point3F &impulse )
  320. {
  321. physx::PxVec3 orig = px3Cast<physx::PxVec3>( startPnt );
  322. physx::PxVec3 dir = px3Cast<physx::PxVec3>( endPnt - startPnt );
  323. physx::PxF32 maxDist = dir.magnitude();
  324. dir.normalize();
  325. U32 groups = 0xffffffff;
  326. groups &= ~( PX3_TRIGGER ); // No trigger shapes!
  327. physx::PxHitFlags outFlags(physx::PxHitFlag::eDISTANCE | physx::PxHitFlag::eIMPACT | physx::PxHitFlag::eNORMAL);
  328. physx::PxQueryFilterData filterData(physx::PxQueryFlag::eSTATIC|physx::PxQueryFlag::eDYNAMIC);
  329. filterData.data.word0 = groups;
  330. physx::PxRaycastBuffer buf;
  331. if(!mScene->raycast(orig,dir,maxDist,buf,outFlags,filterData))
  332. return false;
  333. if(!buf.hasBlock)
  334. return false;
  335. const physx::PxRaycastHit hit = buf.block;
  336. physx::PxRigidActor *actor = hit.actor;
  337. PhysicsUserData *userData = PhysicsUserData::cast( actor->userData );
  338. if ( ri )
  339. {
  340. ri->object = ( userData != NULL ) ? userData->getObject() : NULL;
  341. if ( ri->object == NULL )
  342. ri->distance = hit.distance;
  343. ri->normal = px3Cast<Point3F>( hit.normal );
  344. ri->point = px3Cast<Point3F>( hit.position );
  345. ri->t = maxDist / hit.distance;
  346. }
  347. if ( impulse.isZero() ||
  348. !actor->isRigidDynamic() ||
  349. actor->is<physx::PxRigidDynamic>()->getRigidDynamicFlags() & physx::PxRigidDynamicFlag::eKINEMATIC )
  350. return true;
  351. physx::PxRigidBody *body = actor->is<physx::PxRigidBody>();
  352. physx::PxVec3 force = px3Cast<physx::PxVec3>( impulse );
  353. physx::PxRigidBodyExt::addForceAtPos(*body,force,hit.position,physx::PxForceMode::eIMPULSE);
  354. return true;
  355. }
  356. PhysicsBody* Px3World::castRay( const Point3F &start, const Point3F &end, U32 bodyTypes )
  357. {
  358. physx::PxVec3 orig = px3Cast<physx::PxVec3>( start );
  359. physx::PxVec3 dir = px3Cast<physx::PxVec3>( end - start );
  360. physx::PxF32 maxDist = dir.magnitude();
  361. dir.normalize();
  362. U32 groups = 0xFFFFFFFF;
  363. if ( !( bodyTypes & BT_Player ) )
  364. groups &= ~( PX3_PLAYER );
  365. // TODO: For now always skip triggers and debris,
  366. // but we should consider how game specifc this API
  367. // should be in the future.
  368. groups &= ~( PX3_TRIGGER ); // triggers
  369. groups &= ~( PX3_DEBRIS ); // debris
  370. physx::PxHitFlags outFlags(physx::PxHitFlag::eDISTANCE | physx::PxHitFlag::eIMPACT | physx::PxHitFlag::eNORMAL);
  371. physx::PxQueryFilterData filterData;
  372. if(bodyTypes & BT_Static)
  373. filterData.flags |= physx::PxQueryFlag::eSTATIC;
  374. if(bodyTypes & BT_Dynamic)
  375. filterData.flags |= physx::PxQueryFlag::eDYNAMIC;
  376. filterData.data.word0 = groups;
  377. physx::PxRaycastBuffer buf;
  378. if( !mScene->raycast(orig,dir,maxDist,buf,outFlags,filterData) )
  379. return NULL;
  380. if(!buf.hasBlock)
  381. return NULL;
  382. physx::PxRigidActor *actor = buf.block.actor;
  383. PhysicsUserData *userData = PhysicsUserData::cast( actor->userData );
  384. if( !userData )
  385. return NULL;
  386. return userData->getBody();
  387. }
  388. void Px3World::explosion( const Point3F &pos, F32 radius, F32 forceMagnitude )
  389. {
  390. physx::PxVec3 nxPos = px3Cast<physx::PxVec3>( pos );
  391. const physx::PxU32 bufferSize = 10;
  392. physx::PxSphereGeometry worldSphere(radius);
  393. physx::PxTransform pose(nxPos);
  394. physx::PxOverlapBufferN<bufferSize> buffer;
  395. if(!mScene->overlap(worldSphere,pose,buffer))
  396. return;
  397. for ( physx::PxU32 i = 0; i < buffer.nbTouches; i++ )
  398. {
  399. physx::PxRigidActor *actor = buffer.touches[i].actor;
  400. bool dynamic = actor->isRigidDynamic();
  401. if ( !dynamic )
  402. continue;
  403. bool kinematic = actor->is<physx::PxRigidDynamic>()->getRigidDynamicFlags() & physx::PxRigidDynamicFlag::eKINEMATIC;
  404. if ( kinematic )
  405. continue;
  406. physx::PxVec3 force = actor->getGlobalPose().p - nxPos;
  407. force.normalize();
  408. force *= forceMagnitude;
  409. physx::PxRigidBody *body = actor->is<physx::PxRigidBody>();
  410. physx::PxRigidBodyExt::addForceAtPos(*body,force,nxPos,physx::PxForceMode::eIMPULSE);
  411. }
  412. }
  413. void Px3World::setEnabled( bool enabled )
  414. {
  415. mIsEnabled = enabled;
  416. if ( !mIsEnabled )
  417. getPhysicsResults();
  418. }
  419. physx::PxController* Px3World::createController( physx::PxControllerDesc &desc )
  420. {
  421. if ( !mScene )
  422. return NULL;
  423. // We need the writelock!
  424. releaseWriteLock();
  425. physx::PxController* pController = mControllerManager->createController(desc);
  426. AssertFatal( pController, "Px3World::createController - Got a null!" );
  427. return pController;
  428. }
  429. static ColorI getDebugColor( physx::PxU32 packed )
  430. {
  431. ColorI col;
  432. col.blue = (packed)&0xff;
  433. col.green = (packed>>8)&0xff;
  434. col.red = (packed>>16)&0xff;
  435. col.alpha = 255;
  436. return col;
  437. }
  438. void Px3World::onDebugDraw( const SceneRenderState *state )
  439. {
  440. if ( !mScene )
  441. return;
  442. mScene->setVisualizationParameter(physx::PxVisualizationParameter::eSCALE,1.0f);
  443. mScene->setVisualizationParameter(physx::PxVisualizationParameter::eBODY_AXES,1.0f);
  444. mScene->setVisualizationParameter(physx::PxVisualizationParameter::eCOLLISION_SHAPES,1.0f);
  445. const physx::PxRenderBuffer *renderBuffer = &mScene->getRenderBuffer();
  446. if(!renderBuffer)
  447. return;
  448. // Render points
  449. {
  450. physx::PxU32 numPoints = renderBuffer->getNbPoints();
  451. const physx::PxDebugPoint *points = renderBuffer->getPoints();
  452. PrimBuild::begin( GFXPointList, numPoints );
  453. while ( numPoints-- )
  454. {
  455. PrimBuild::color( getDebugColor(points->color) );
  456. PrimBuild::vertex3fv(px3Cast<Point3F>(points->pos));
  457. points++;
  458. }
  459. PrimBuild::end();
  460. }
  461. // Render lines
  462. {
  463. physx::PxU32 numLines = renderBuffer->getNbLines();
  464. const physx::PxDebugLine *lines = renderBuffer->getLines();
  465. PrimBuild::begin( GFXLineList, numLines * 2 );
  466. while ( numLines-- )
  467. {
  468. PrimBuild::color( getDebugColor( lines->color0 ) );
  469. PrimBuild::vertex3fv( px3Cast<Point3F>(lines->pos0));
  470. PrimBuild::color( getDebugColor( lines->color1 ) );
  471. PrimBuild::vertex3fv( px3Cast<Point3F>(lines->pos1));
  472. lines++;
  473. }
  474. PrimBuild::end();
  475. }
  476. // Render triangles
  477. {
  478. physx::PxU32 numTris = renderBuffer->getNbTriangles();
  479. const physx::PxDebugTriangle *triangles = renderBuffer->getTriangles();
  480. PrimBuild::begin( GFXTriangleList, numTris * 3 );
  481. while ( numTris-- )
  482. {
  483. PrimBuild::color( getDebugColor( triangles->color0 ) );
  484. PrimBuild::vertex3fv( px3Cast<Point3F>(triangles->pos0) );
  485. PrimBuild::color( getDebugColor( triangles->color1 ) );
  486. PrimBuild::vertex3fv( px3Cast<Point3F>(triangles->pos1));
  487. PrimBuild::color( getDebugColor( triangles->color2 ) );
  488. PrimBuild::vertex3fv( px3Cast<Point3F>(triangles->pos2) );
  489. triangles++;
  490. }
  491. PrimBuild::end();
  492. }
  493. }
  494. //set simulation timing via script
  495. DefineEngineFunction( physx3SetSimulationTiming, void, ( F32 stepTime, U32 maxSteps ),, "Set simulation timing of the PhysX 3 plugin" )
  496. {
  497. Px3World::setTiming(stepTime,maxSteps);
  498. }