collisionTrigger.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  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/components/collision/collisionTrigger.h"
  24. #include "scene/sceneRenderState.h"
  25. #include "console/consoleTypes.h"
  26. #include "console/engineAPI.h"
  27. #include "collision/boxConvex.h"
  28. #include "core/stream/bitStream.h"
  29. #include "math/mathIO.h"
  30. #include "gfx/gfxTransformSaver.h"
  31. #include "renderInstance/renderPassManager.h"
  32. #include "gfx/gfxDrawUtil.h"
  33. #include "T3D/physics/physicsPlugin.h"
  34. #include "T3D/physics/physicsBody.h"
  35. #include "T3D/physics/physicsCollision.h"
  36. bool CollisionTrigger::smRenderCollisionTriggers = false;
  37. //-----------------------------------------------------------------------------
  38. //----------------------------------------------------------------------------
  39. //--------------------------------------------------------------------------
  40. IMPLEMENT_CO_NETOBJECT_V1(CollisionTrigger);
  41. ConsoleDocClass(CollisionTrigger,
  42. "@brief A CollisionTrigger is a volume of space that initiates script callbacks "
  43. "when objects pass through the CollisionTrigger.\n\n"
  44. "CollisionTriggerData provides the callbacks for the CollisionTrigger when an object enters, stays inside "
  45. "or leaves the CollisionTrigger's volume.\n\n"
  46. "@see CollisionTriggerData\n"
  47. "@ingroup gameObjects\n"
  48. );
  49. IMPLEMENT_CALLBACK(CollisionTrigger, onAdd, void, (U32 objectId), (objectId),
  50. "@brief Called when the CollisionTrigger is being created.\n\n"
  51. "@param objectId the object id of the CollisionTrigger being created\n");
  52. IMPLEMENT_CALLBACK(CollisionTrigger, onRemove, void, (U32 objectId), (objectId),
  53. "@brief Called just before the CollisionTrigger is deleted.\n\n"
  54. "@param objectId the object id of the CollisionTrigger being deleted\n");
  55. CollisionTrigger::CollisionTrigger()
  56. {
  57. // Don't ghost by default.
  58. mNetFlags.set(Ghostable | ScopeAlways);
  59. mTypeMask |= TriggerObjectType;
  60. mObjScale.set(1, 1, 1);
  61. mObjToWorld.identity();
  62. mWorldToObj.identity();
  63. mLastThink = 0;
  64. mCurrTick = 0;
  65. mConvexList = new Convex;
  66. mPhysicsRep = NULL;
  67. }
  68. CollisionTrigger::~CollisionTrigger()
  69. {
  70. delete mConvexList;
  71. mConvexList = NULL;
  72. SAFE_DELETE(mPhysicsRep);
  73. }
  74. bool CollisionTrigger::castRay(const Point3F &start, const Point3F &end, RayInfo* info)
  75. {
  76. // Collide against bounding box
  77. F32 st, et, fst = 0, fet = 1;
  78. F32 *bmin = &mObjBox.minExtents.x;
  79. F32 *bmax = &mObjBox.maxExtents.x;
  80. F32 const *si = &start.x;
  81. F32 const *ei = &end.x;
  82. for (S32 i = 0; i < 3; i++)
  83. {
  84. if (*si < *ei)
  85. {
  86. if (*si > *bmax || *ei < *bmin)
  87. return false;
  88. F32 di = *ei - *si;
  89. st = (*si < *bmin) ? (*bmin - *si) / di : 0;
  90. et = (*ei > *bmax) ? (*bmax - *si) / di : 1;
  91. }
  92. else
  93. {
  94. if (*ei > *bmax || *si < *bmin)
  95. return false;
  96. F32 di = *ei - *si;
  97. st = (*si > *bmax) ? (*bmax - *si) / di : 0;
  98. et = (*ei < *bmin) ? (*bmin - *si) / di : 1;
  99. }
  100. if (st > fst) fst = st;
  101. if (et < fet) fet = et;
  102. if (fet < fst)
  103. return false;
  104. bmin++; bmax++;
  105. si++; ei++;
  106. }
  107. info->normal = start - end;
  108. info->normal.normalizeSafe();
  109. getTransform().mulV(info->normal);
  110. info->t = fst;
  111. info->object = this;
  112. info->point.interpolate(start, end, fst);
  113. info->material = 0;
  114. return true;
  115. }
  116. //-----------------------------------------------------------------------------
  117. void CollisionTrigger::consoleInit()
  118. {
  119. Con::addVariable("$CollisionTrigger::renderCollisionTriggers", TypeBool, &smRenderCollisionTriggers,
  120. "@brief Forces all CollisionTrigger's to render.\n\n"
  121. "Used by the Tools and debug render modes.\n"
  122. "@ingroup gameObjects");
  123. }
  124. void CollisionTrigger::initPersistFields()
  125. {
  126. addField("polyhedron", TypeTriggerPolyhedron, Offset(mCollisionTriggerPolyhedron, CollisionTrigger),
  127. "@brief Defines a non-rectangular area for the CollisionTrigger.\n\n"
  128. "Rather than the standard rectangular bounds, this optional parameter defines a quadrilateral "
  129. "CollisionTrigger area. The quadrilateral is defined as a corner point followed by three vectors "
  130. "representing the edges extending from the corner.\n");
  131. addProtectedField("enterCommand", TypeCommand, Offset(mEnterCommand, CollisionTrigger), &setEnterCmd, &defaultProtectedGetFn,
  132. "The command to execute when an object enters this CollisionTrigger. Object id stored in %%obj. Maximum 1023 characters.");
  133. addProtectedField("leaveCommand", TypeCommand, Offset(mLeaveCommand, CollisionTrigger), &setLeaveCmd, &defaultProtectedGetFn,
  134. "The command to execute when an object leaves this CollisionTrigger. Object id stored in %%obj. Maximum 1023 characters.");
  135. addProtectedField("tickCommand", TypeCommand, Offset(mTickCommand, CollisionTrigger), &setTickCmd, &defaultProtectedGetFn,
  136. "The command to execute while an object is inside this CollisionTrigger. Maximum 1023 characters.");
  137. Parent::initPersistFields();
  138. }
  139. bool CollisionTrigger::setEnterCmd(void *object, const char *index, const char *data)
  140. {
  141. static_cast<CollisionTrigger*>(object)->setMaskBits(EnterCmdMask);
  142. return true; // to update the actual field
  143. }
  144. bool CollisionTrigger::setLeaveCmd(void *object, const char *index, const char *data)
  145. {
  146. static_cast<CollisionTrigger*>(object)->setMaskBits(LeaveCmdMask);
  147. return true; // to update the actual field
  148. }
  149. bool CollisionTrigger::setTickCmd(void *object, const char *index, const char *data)
  150. {
  151. static_cast<CollisionTrigger*>(object)->setMaskBits(TickCmdMask);
  152. return true; // to update the actual field
  153. }
  154. //--------------------------------------------------------------------------
  155. bool CollisionTrigger::onAdd()
  156. {
  157. if (!Parent::onAdd())
  158. return false;
  159. onAdd_callback(getId());
  160. Polyhedron temp = mCollisionTriggerPolyhedron;
  161. setTriggerPolyhedron(temp);
  162. addToScene();
  163. if (isServerObject())
  164. scriptOnAdd();
  165. return true;
  166. }
  167. void CollisionTrigger::onRemove()
  168. {
  169. onRemove_callback(getId());
  170. mConvexList->nukeList();
  171. removeFromScene();
  172. Parent::onRemove();
  173. }
  174. bool CollisionTrigger::onNewDataBlock(GameBaseData *dptr, bool reload)
  175. {
  176. return true;
  177. }
  178. void CollisionTrigger::onDeleteNotify(SimObject *obj)
  179. {
  180. GameBase* pScene = dynamic_cast<GameBase*>(obj);
  181. if (pScene != NULL)
  182. {
  183. for (U32 i = 0; i < mObjects.size(); i++)
  184. {
  185. if (pScene == mObjects[i])
  186. {
  187. mObjects.erase(i);
  188. //onLeaveCollisionTrigger_callback(this, pScene);
  189. break;
  190. }
  191. }
  192. }
  193. Parent::onDeleteNotify(obj);
  194. }
  195. void CollisionTrigger::inspectPostApply()
  196. {
  197. setTriggerPolyhedron(mCollisionTriggerPolyhedron);
  198. setMaskBits(PolyMask);
  199. Parent::inspectPostApply();
  200. }
  201. //--------------------------------------------------------------------------
  202. void CollisionTrigger::buildConvex(const Box3F& box, Convex* convex)
  203. {
  204. // These should really come out of a pool
  205. mConvexList->collectGarbage();
  206. Box3F realBox = box;
  207. mWorldToObj.mul(realBox);
  208. realBox.minExtents.convolveInverse(mObjScale);
  209. realBox.maxExtents.convolveInverse(mObjScale);
  210. if (realBox.isOverlapped(getObjBox()) == false)
  211. return;
  212. // Just return a box convex for the entire shape...
  213. Convex* cc = 0;
  214. CollisionWorkingList& wl = convex->getWorkingList();
  215. for (CollisionWorkingList* itr = wl.wLink.mNext; itr != &wl; itr = itr->wLink.mNext) {
  216. if (itr->mConvex->getType() == BoxConvexType &&
  217. itr->mConvex->getObject() == this) {
  218. cc = itr->mConvex;
  219. break;
  220. }
  221. }
  222. if (cc)
  223. return;
  224. // Create a new convex.
  225. BoxConvex* cp = new BoxConvex;
  226. mConvexList->registerObject(cp);
  227. convex->addToWorkingList(cp);
  228. cp->init(this);
  229. mObjBox.getCenter(&cp->mCenter);
  230. cp->mSize.x = mObjBox.len_x() / 2.0f;
  231. cp->mSize.y = mObjBox.len_y() / 2.0f;
  232. cp->mSize.z = mObjBox.len_z() / 2.0f;
  233. }
  234. //------------------------------------------------------------------------------
  235. void CollisionTrigger::setTransform(const MatrixF & mat)
  236. {
  237. Parent::setTransform(mat);
  238. if (mPhysicsRep)
  239. mPhysicsRep->setTransform(mat);
  240. if (isServerObject()) {
  241. MatrixF base(true);
  242. base.scale(Point3F(1.0 / mObjScale.x,
  243. 1.0 / mObjScale.y,
  244. 1.0 / mObjScale.z));
  245. base.mul(mWorldToObj);
  246. mClippedList.setBaseTransform(base);
  247. setMaskBits(TransformMask | ScaleMask);
  248. }
  249. }
  250. void CollisionTrigger::prepRenderImage(SceneRenderState *state)
  251. {
  252. // only render if selected or render flag is set
  253. if (!smRenderCollisionTriggers && !isSelected())
  254. return;
  255. ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
  256. ri->renderDelegate.bind(this, &CollisionTrigger::renderObject);
  257. ri->type = RenderPassManager::RIT_Editor;
  258. ri->translucentSort = true;
  259. ri->defaultKey = 1;
  260. state->getRenderPass()->addInst(ri);
  261. }
  262. void CollisionTrigger::renderObject(ObjectRenderInst *ri,
  263. SceneRenderState *state,
  264. BaseMatInstance *overrideMat)
  265. {
  266. if (overrideMat)
  267. return;
  268. GFXStateBlockDesc desc;
  269. desc.setZReadWrite(true, false);
  270. desc.setBlend(true);
  271. // CollisionTrigger polyhedrons are set up with outward facing normals and CCW ordering
  272. // so can't enable backface culling.
  273. desc.setCullMode(GFXCullNone);
  274. GFXTransformSaver saver;
  275. MatrixF mat = getRenderTransform();
  276. mat.scale(getScale());
  277. GFX->multWorld(mat);
  278. GFXDrawUtil *drawer = GFX->getDrawUtil();
  279. drawer->drawPolyhedron(desc, mCollisionTriggerPolyhedron, ColorI(255, 192, 0, 45));
  280. // Render wireframe.
  281. desc.setFillModeWireframe();
  282. drawer->drawPolyhedron(desc, mCollisionTriggerPolyhedron, ColorI::BLACK);
  283. }
  284. void CollisionTrigger::setTriggerPolyhedron(const Polyhedron& rPolyhedron)
  285. {
  286. mCollisionTriggerPolyhedron = rPolyhedron;
  287. if (mCollisionTriggerPolyhedron.mPointList.size() != 0) {
  288. mObjBox.minExtents.set(1e10, 1e10, 1e10);
  289. mObjBox.maxExtents.set(-1e10, -1e10, -1e10);
  290. for (U32 i = 0; i < mCollisionTriggerPolyhedron.mPointList.size(); i++) {
  291. mObjBox.minExtents.setMin(mCollisionTriggerPolyhedron.mPointList[i]);
  292. mObjBox.maxExtents.setMax(mCollisionTriggerPolyhedron.mPointList[i]);
  293. }
  294. }
  295. else {
  296. mObjBox.minExtents.set(-0.5, -0.5, -0.5);
  297. mObjBox.maxExtents.set(0.5, 0.5, 0.5);
  298. }
  299. MatrixF xform = getTransform();
  300. setTransform(xform);
  301. mClippedList.clear();
  302. mClippedList.mPlaneList = mCollisionTriggerPolyhedron.mPlaneList;
  303. // for (U32 i = 0; i < mClippedList.mPlaneList.size(); i++)
  304. // mClippedList.mPlaneList[i].neg();
  305. MatrixF base(true);
  306. base.scale(Point3F(1.0 / mObjScale.x,
  307. 1.0 / mObjScale.y,
  308. 1.0 / mObjScale.z));
  309. base.mul(mWorldToObj);
  310. mClippedList.setBaseTransform(base);
  311. SAFE_DELETE(mPhysicsRep);
  312. if (PHYSICSMGR)
  313. {
  314. PhysicsCollision *colShape = PHYSICSMGR->createCollision();
  315. MatrixF colMat(true);
  316. colMat.displace(Point3F(0, 0, mObjBox.getExtents().z * 0.5f * mObjScale.z));
  317. colShape->addBox(mObjBox.getExtents() * 0.5f * mObjScale, colMat);
  318. //MatrixF colMat( true );
  319. //colMat.scale( mObjScale );
  320. //colShape->addConvex( mCollisionTriggerPolyhedron.pointList.address(), mCollisionTriggerPolyhedron.pointList.size(), colMat );
  321. PhysicsWorld *world = PHYSICSMGR->getWorld(isServerObject() ? "server" : "client");
  322. mPhysicsRep = PHYSICSMGR->createBody();
  323. mPhysicsRep->init(colShape, 0, PhysicsBody::BF_TRIGGER | PhysicsBody::BF_KINEMATIC, this, world);
  324. mPhysicsRep->setTransform(getTransform());
  325. }
  326. }
  327. //--------------------------------------------------------------------------
  328. bool CollisionTrigger::testObject(GameBase* enter)
  329. {
  330. if (mCollisionTriggerPolyhedron.mPointList.size() == 0)
  331. return false;
  332. mClippedList.clear();
  333. SphereF sphere;
  334. sphere.center = (mWorldBox.minExtents + mWorldBox.maxExtents) * 0.5;
  335. VectorF bv = mWorldBox.maxExtents - sphere.center;
  336. sphere.radius = bv.len();
  337. enter->buildPolyList(PLC_Collision, &mClippedList, mWorldBox, sphere);
  338. return mClippedList.isEmpty() == false;
  339. }
  340. void CollisionTrigger::potentialEnterObject(GameBase* enter)
  341. {
  342. for (U32 i = 0; i < mObjects.size(); i++) {
  343. if (mObjects[i] == enter)
  344. return;
  345. }
  346. if (testObject(enter) == true) {
  347. mObjects.push_back(enter);
  348. deleteNotify(enter);
  349. if (!mEnterCommand.isEmpty())
  350. {
  351. String command = String("%obj = ") + enter->getIdString() + ";" + mEnterCommand;
  352. Con::evaluate(command.c_str());
  353. }
  354. //onEnterCollisionTrigger_callback(this, enter);
  355. }
  356. }
  357. void CollisionTrigger::processTick(const Move* move)
  358. {
  359. Parent::processTick(move);
  360. //
  361. if (mObjects.size() == 0)
  362. return;
  363. if (mLastThink + 100 < mCurrTick)
  364. {
  365. mCurrTick = 0;
  366. mLastThink = 0;
  367. for (S32 i = S32(mObjects.size() - 1); i >= 0; i--)
  368. {
  369. if (testObject(mObjects[i]) == false)
  370. {
  371. GameBase* remove = mObjects[i];
  372. mObjects.erase(i);
  373. clearNotify(remove);
  374. if (!mLeaveCommand.isEmpty())
  375. {
  376. String command = String("%obj = ") + remove->getIdString() + ";" + mLeaveCommand;
  377. Con::evaluate(command.c_str());
  378. }
  379. //onLeaveCollisionTrigger_callback(this, remove);
  380. }
  381. }
  382. if (!mTickCommand.isEmpty())
  383. Con::evaluate(mTickCommand.c_str());
  384. //if (mObjects.size() != 0)
  385. // onTickCollisionTrigger_callback(this);
  386. }
  387. else
  388. {
  389. mCurrTick += TickMs;
  390. }
  391. }
  392. //--------------------------------------------------------------------------
  393. U32 CollisionTrigger::packUpdate(NetConnection* con, U32 mask, BitStream* stream)
  394. {
  395. U32 i;
  396. U32 retMask = Parent::packUpdate(con, mask, stream);
  397. if (stream->writeFlag(mask & TransformMask))
  398. {
  399. stream->writeAffineTransform(mObjToWorld);
  400. }
  401. // Write the polyhedron
  402. if (stream->writeFlag(mask & PolyMask))
  403. {
  404. stream->write(mCollisionTriggerPolyhedron.mPointList.size());
  405. for (i = 0; i < mCollisionTriggerPolyhedron.mPointList.size(); i++)
  406. mathWrite(*stream, mCollisionTriggerPolyhedron.mPointList[i]);
  407. stream->write(mCollisionTriggerPolyhedron.mPlaneList.size());
  408. for (i = 0; i < mCollisionTriggerPolyhedron.mPlaneList.size(); i++)
  409. mathWrite(*stream, mCollisionTriggerPolyhedron.mPlaneList[i]);
  410. stream->write(mCollisionTriggerPolyhedron.mEdgeList.size());
  411. for (i = 0; i < mCollisionTriggerPolyhedron.mEdgeList.size(); i++) {
  412. const Polyhedron::Edge& rEdge = mCollisionTriggerPolyhedron.mEdgeList[i];
  413. stream->write(rEdge.face[0]);
  414. stream->write(rEdge.face[1]);
  415. stream->write(rEdge.vertex[0]);
  416. stream->write(rEdge.vertex[1]);
  417. }
  418. }
  419. if (stream->writeFlag(mask & EnterCmdMask))
  420. stream->writeLongString(CMD_SIZE - 1, mEnterCommand.c_str());
  421. if (stream->writeFlag(mask & LeaveCmdMask))
  422. stream->writeLongString(CMD_SIZE - 1, mLeaveCommand.c_str());
  423. if (stream->writeFlag(mask & TickCmdMask))
  424. stream->writeLongString(CMD_SIZE - 1, mTickCommand.c_str());
  425. return retMask;
  426. }
  427. void CollisionTrigger::unpackUpdate(NetConnection* con, BitStream* stream)
  428. {
  429. Parent::unpackUpdate(con, stream);
  430. U32 i, size;
  431. // Transform
  432. if (stream->readFlag())
  433. {
  434. MatrixF temp;
  435. stream->readAffineTransform(&temp);
  436. setTransform(temp);
  437. }
  438. // Read the polyhedron
  439. if (stream->readFlag())
  440. {
  441. Polyhedron tempPH;
  442. stream->read(&size);
  443. tempPH.mPointList.setSize(size);
  444. for (i = 0; i < tempPH.mPointList.size(); i++)
  445. mathRead(*stream, &tempPH.mPointList[i]);
  446. stream->read(&size);
  447. tempPH.mPlaneList.setSize(size);
  448. for (i = 0; i < tempPH.mPlaneList.size(); i++)
  449. mathRead(*stream, &tempPH.mPlaneList[i]);
  450. stream->read(&size);
  451. tempPH.mEdgeList.setSize(size);
  452. for (i = 0; i < tempPH.mEdgeList.size(); i++) {
  453. Polyhedron::Edge& rEdge = tempPH.mEdgeList[i];
  454. stream->read(&rEdge.face[0]);
  455. stream->read(&rEdge.face[1]);
  456. stream->read(&rEdge.vertex[0]);
  457. stream->read(&rEdge.vertex[1]);
  458. }
  459. setTriggerPolyhedron(tempPH);
  460. }
  461. if (stream->readFlag())
  462. {
  463. char buf[CMD_SIZE];
  464. stream->readLongString(CMD_SIZE - 1, buf);
  465. mEnterCommand = buf;
  466. }
  467. if (stream->readFlag())
  468. {
  469. char buf[CMD_SIZE];
  470. stream->readLongString(CMD_SIZE - 1, buf);
  471. mLeaveCommand = buf;
  472. }
  473. if (stream->readFlag())
  474. {
  475. char buf[CMD_SIZE];
  476. stream->readLongString(CMD_SIZE - 1, buf);
  477. mTickCommand = buf;
  478. }
  479. }
  480. //ConsoleMethod( CollisionTrigger, getNumObjects, S32, 2, 2, "")
  481. DefineEngineMethod(CollisionTrigger, getNumObjects, S32, (), ,
  482. "@brief Get the number of objects that are within the CollisionTrigger's bounds.\n\n"
  483. "@see getObject()\n")
  484. {
  485. return object->getNumCollisionTriggeringObjects();
  486. }
  487. //ConsoleMethod( CollisionTrigger, getObject, S32, 3, 3, "(int idx)")
  488. DefineEngineMethod(CollisionTrigger, getObject, S32, (S32 index), ,
  489. "@brief Retrieve the requested object that is within the CollisionTrigger's bounds.\n\n"
  490. "@param index Index of the object to get (range is 0 to getNumObjects()-1)\n"
  491. "@returns The SimObjectID of the object, or -1 if the requested index is invalid.\n"
  492. "@see getNumObjects()\n")
  493. {
  494. if (index >= object->getNumCollisionTriggeringObjects() || index < 0)
  495. return -1;
  496. else
  497. return object->getObject(U32(index))->getId();
  498. }