item.cpp 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386
  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/item.h"
  24. #include "core/stream/bitStream.h"
  25. #include "math/mMath.h"
  26. #include "console/console.h"
  27. #include "console/consoleTypes.h"
  28. #include "sim/netConnection.h"
  29. #include "collision/boxConvex.h"
  30. #include "collision/earlyOutPolyList.h"
  31. #include "collision/extrudedPolyList.h"
  32. #include "math/mPolyhedron.h"
  33. #include "math/mathIO.h"
  34. #include "lighting/lightInfo.h"
  35. #include "lighting/lightManager.h"
  36. #include "T3D/physics/physicsPlugin.h"
  37. #include "T3D/physics/physicsBody.h"
  38. #include "T3D/physics/physicsCollision.h"
  39. #include "ts/tsShapeInstance.h"
  40. #include "console/engineAPI.h"
  41. const F32 sRotationSpeed = 6.0f; // Secs/Rotation
  42. const F32 sAtRestVelocity = 0.15f; // Min speed after collision
  43. const S32 sCollisionTimeout = 15; // Timout value in ticks
  44. // Client prediction
  45. static F32 sMinWarpTicks = 0.5 ; // Fraction of tick at which instant warp occures
  46. static S32 sMaxWarpTicks = 3; // Max warp duration in ticks
  47. F32 Item::mGravity = -20.0f;
  48. const U32 sClientCollisionMask = (TerrainObjectType |
  49. StaticShapeObjectType |
  50. VehicleObjectType |
  51. PlayerObjectType);
  52. const U32 sServerCollisionMask = (sClientCollisionMask);
  53. const S32 Item::csmAtRestTimer = 64;
  54. //----------------------------------------------------------------------------
  55. IMPLEMENT_CO_DATABLOCK_V1(ItemData);
  56. ConsoleDocClass( ItemData,
  57. "@brief Stores properties for an individual Item type.\n\n"
  58. "Items represent an object in the world, usually one that the player will interact with. "
  59. "One example is a health kit on the group that is automatically picked up when the player "
  60. "comes into contact with it.\n\n"
  61. "ItemData provides the common properties for a set of Items. These properties include a "
  62. "DTS or DAE model used to render the Item in the world, its physical properties for when the "
  63. "Item interacts with the world (such as being tossed by the player), and any lights that emit "
  64. "from the Item.\n\n"
  65. "@tsexample\n"
  66. "datablock ItemData(HealthKitSmall)\n"
  67. "{\n"
  68. " category =\"Health\";\n"
  69. " className = \"HealthPatch\";\n"
  70. " shapeFile = \"art/shapes/items/kit/healthkit.dts\";\n"
  71. " gravityMod = \"1.0\";\n"
  72. " mass = 2;\n"
  73. " friction = 1;\n"
  74. " elasticity = 0.3;\n"
  75. " density = 2;\n"
  76. " drag = 0.5;\n"
  77. " maxVelocity = \"10.0\";\n"
  78. " emap = true;\n"
  79. " sticky = false;\n"
  80. " dynamicType = \"0\"\n;"
  81. " lightOnlyStatic = false;\n"
  82. " lightType = \"NoLight\";\n"
  83. " lightColor = \"1.0 1.0 1.0 1.0\";\n"
  84. " lightTime = 1000;\n"
  85. " lightRadius = 10.0;\n"
  86. " simpleServerCollision = true;"
  87. " // Dynamic properties used by the scripts\n\n"
  88. " pickupName = \"a small health kit\";\n"
  89. " repairAmount = 50;\n"
  90. "};\n"
  91. "@endtsexample\n"
  92. "@ingroup gameObjects\n"
  93. );
  94. ItemData::ItemData()
  95. {
  96. shadowEnable = true;
  97. friction = 0;
  98. elasticity = 0;
  99. sticky = false;
  100. gravityMod = 1.0;
  101. maxVelocity = 25.0f;
  102. density = 2;
  103. drag = 0.5;
  104. lightOnlyStatic = false;
  105. lightType = Item::NoLight;
  106. lightColor.set(1.f,1.f,1.f,1.f);
  107. lightTime = 1000;
  108. lightRadius = 10.f;
  109. simpleServerCollision = true;
  110. }
  111. ImplementEnumType( ItemLightType,
  112. "@brief The type of light the Item has\n\n"
  113. "@ingroup gameObjects\n\n")
  114. { Item::NoLight, "NoLight", "The item has no light attached.\n" },
  115. { Item::ConstantLight, "ConstantLight", "The item has a constantly emitting light attached.\n" },
  116. { Item::PulsingLight, "PulsingLight", "The item has a pulsing light attached.\n" }
  117. EndImplementEnumType;
  118. void ItemData::initPersistFields()
  119. {
  120. addField("friction", TypeF32, Offset(friction, ItemData), "A floating-point value specifying how much velocity is lost to impact and sliding friction.");
  121. addField("elasticity", TypeF32, Offset(elasticity, ItemData), "A floating-point value specifying how 'bouncy' this ItemData is.");
  122. addField("sticky", TypeBool, Offset(sticky, ItemData),
  123. "@brief If true, ItemData will 'stick' to any surface it collides with.\n\n"
  124. "When an item does stick to a surface, the Item::onStickyCollision() callback is called. The Item has methods to retrieve "
  125. "the world position and normal the Item is stuck to.\n"
  126. "@note Valid objects to stick to must be of StaticShapeObjectType.\n");
  127. addField("gravityMod", TypeF32, Offset(gravityMod, ItemData), "Floating point value to multiply the existing gravity with, just for this ItemData.");
  128. addField("maxVelocity", TypeF32, Offset(maxVelocity, ItemData), "Maximum velocity that this ItemData is able to move.");
  129. addField("lightType", TYPEID< Item::LightType >(), Offset(lightType, ItemData), "Type of light to apply to this ItemData. Options are NoLight, ConstantLight, PulsingLight. Default is NoLight." );
  130. addField("lightColor", TypeColorF, Offset(lightColor, ItemData),
  131. "@brief Color value to make this light. Example: \"1.0,1.0,1.0\"\n\n"
  132. "@see lightType\n");
  133. addField("lightTime", TypeS32, Offset(lightTime, ItemData),
  134. "@brief Time value for the light of this ItemData, used to control the pulse speed of the PulsingLight LightType.\n\n"
  135. "@see lightType\n");
  136. addField("lightRadius", TypeF32, Offset(lightRadius, ItemData),
  137. "@brief Distance from the center point of this ItemData for the light to affect\n\n"
  138. "@see lightType\n");
  139. addField("lightOnlyStatic", TypeBool, Offset(lightOnlyStatic, ItemData),
  140. "@brief If true, this ItemData will only cast a light if the Item for this ItemData has a static value of true.\n\n"
  141. "@see lightType\n");
  142. addField("simpleServerCollision", TypeBool, Offset(simpleServerCollision, ItemData),
  143. "@brief Determines if only simple server-side collision will be used (for pick ups).\n\n"
  144. "If set to true then only simple, server-side collision detection will be used. This is often the case "
  145. "if the item is used for a pick up object, such as ammo. If set to false then a full collision volume "
  146. "will be used as defined by the shape. The default is true.\n"
  147. "@note Only applies when using a physics library.\n"
  148. "@see TurretShape and ProximityMine for examples that should set this to false to allow them to be "
  149. "shot by projectiles.\n");
  150. Parent::initPersistFields();
  151. }
  152. void ItemData::packData(BitStream* stream)
  153. {
  154. Parent::packData(stream);
  155. stream->writeFloat(friction, 10);
  156. stream->writeFloat(elasticity, 10);
  157. stream->writeFlag(sticky);
  158. if(stream->writeFlag(gravityMod != 1.0))
  159. stream->writeFloat(gravityMod, 10);
  160. if(stream->writeFlag(maxVelocity != -1))
  161. stream->write(maxVelocity);
  162. if(stream->writeFlag(lightType != Item::NoLight))
  163. {
  164. AssertFatal(Item::NumLightTypes < (1 << 2), "ItemData: light type needs more bits");
  165. stream->writeInt(lightType, 2);
  166. stream->writeFloat(lightColor.red, 7);
  167. stream->writeFloat(lightColor.green, 7);
  168. stream->writeFloat(lightColor.blue, 7);
  169. stream->writeFloat(lightColor.alpha, 7);
  170. stream->write(lightTime);
  171. stream->write(lightRadius);
  172. stream->writeFlag(lightOnlyStatic);
  173. }
  174. stream->writeFlag(simpleServerCollision);
  175. }
  176. void ItemData::unpackData(BitStream* stream)
  177. {
  178. Parent::unpackData(stream);
  179. friction = stream->readFloat(10);
  180. elasticity = stream->readFloat(10);
  181. sticky = stream->readFlag();
  182. if(stream->readFlag())
  183. gravityMod = stream->readFloat(10);
  184. else
  185. gravityMod = 1.0;
  186. if(stream->readFlag())
  187. stream->read(&maxVelocity);
  188. else
  189. maxVelocity = -1;
  190. if(stream->readFlag())
  191. {
  192. lightType = stream->readInt(2);
  193. lightColor.red = stream->readFloat(7);
  194. lightColor.green = stream->readFloat(7);
  195. lightColor.blue = stream->readFloat(7);
  196. lightColor.alpha = stream->readFloat(7);
  197. stream->read(&lightTime);
  198. stream->read(&lightRadius);
  199. lightOnlyStatic = stream->readFlag();
  200. }
  201. else
  202. lightType = Item::NoLight;
  203. simpleServerCollision = stream->readFlag();
  204. }
  205. //----------------------------------------------------------------------------
  206. IMPLEMENT_CO_NETOBJECT_V1(Item);
  207. ConsoleDocClass( Item,
  208. "@brief Base Item class. Uses the ItemData datablock for common properties.\n\n"
  209. "Items represent an object in the world, usually one that the player will interact with. "
  210. "One example is a health kit on the group that is automatically picked up when the player "
  211. "comes into contact with it.\n\n"
  212. "@tsexample\n"
  213. "// This is the \"health patch\" dropped by a dying player.\n"
  214. "datablock ItemData(HealthKitPatch)\n"
  215. "{\n"
  216. " // Mission editor category, this datablock will show up in the\n"
  217. " // specified category under the \"shapes\" root category.\n"
  218. " category = \"Health\";\n\n"
  219. " className = \"HealthPatch\";\n\n"
  220. " // Basic Item properties\n"
  221. " shapeFile = \"art/shapes/items/patch/healthpatch.dts\";\n"
  222. " mass = 2;\n"
  223. " friction = 1;\n"
  224. " elasticity = 0.3;\n"
  225. " emap = true;\n\n"
  226. " // Dynamic properties used by the scripts\n"
  227. " pickupName = \"a health patch\";\n"
  228. " repairAmount = 50;\n"
  229. "};\n\n"
  230. "%obj = new Item()\n"
  231. "{\n"
  232. " dataBlock = HealthKitSmall;\n"
  233. " parentGroup = EWCreatorWindow.objectGroup;\n"
  234. " static = true;\n"
  235. " rotate = true;\n"
  236. "};\n"
  237. "@endtsexample\n\n"
  238. "@see ItemData\n"
  239. "@ingroup gameObjects\n"
  240. );
  241. IMPLEMENT_CALLBACK( Item, onStickyCollision, void, ( const char* objID ),( objID ),
  242. "@brief Informs the Item object that it is now sticking to another object.\n\n"
  243. "This callback is only called if the ItemData::sticky property for this Item is true.\n"
  244. "@param objID Object ID this Item object.\n"
  245. "@note Server side only.\n"
  246. "@see Item, ItemData\n"
  247. );
  248. IMPLEMENT_CALLBACK( Item, onEnterLiquid, void, ( const char* objID, const char* waterCoverage, const char* liquidType ),( objID, waterCoverage, liquidType ),
  249. "Informs an Item object that it has entered liquid, along with information about the liquid type.\n"
  250. "@param objID Object ID for this Item object.\n"
  251. "@param waterCoverage How much coverage of water this Item object has.\n"
  252. "@param liquidType The type of liquid that this Item object has entered.\n"
  253. "@note Server side only.\n"
  254. "@see Item, ItemData, WaterObject\n"
  255. );
  256. IMPLEMENT_CALLBACK( Item, onLeaveLiquid, void, ( const char* objID, const char* liquidType ),( objID, liquidType ),
  257. "Informs an Item object that it has left a liquid, along with information about the liquid type.\n"
  258. "@param objID Object ID for this Item object.\n"
  259. "@param liquidType The type of liquid that this Item object has left.\n"
  260. "@note Server side only.\n"
  261. "@see Item, ItemData, WaterObject\n"
  262. );
  263. Item::Item()
  264. {
  265. mTypeMask |= ItemObjectType | DynamicShapeObjectType;
  266. mDataBlock = 0;
  267. mStatic = false;
  268. mRotate = false;
  269. mVelocity = VectorF(0,0,0);
  270. mAtRest = true;
  271. mAtRestCounter = 0;
  272. mInLiquid = false;
  273. delta.warpTicks = 0;
  274. delta.dt = 1;
  275. mCollisionObject = 0;
  276. mCollisionTimeout = 0;
  277. mPhysicsRep = NULL;
  278. mConvex.init(this);
  279. mWorkingQueryBox.minExtents.set(-1e9, -1e9, -1e9);
  280. mWorkingQueryBox.maxExtents.set(-1e9, -1e9, -1e9);
  281. mLight = NULL;
  282. mSubclassItemHandlesScene = false;
  283. }
  284. Item::~Item()
  285. {
  286. SAFE_DELETE(mLight);
  287. }
  288. //----------------------------------------------------------------------------
  289. bool Item::onAdd()
  290. {
  291. if (!Parent::onAdd() || !mDataBlock)
  292. return false;
  293. if (mStatic)
  294. mAtRest = true;
  295. mObjToWorld.getColumn(3,&delta.pos);
  296. // Setup the box for our convex object...
  297. mObjBox.getCenter(&mConvex.mCenter);
  298. mConvex.mSize.x = mObjBox.len_x() / 2.0;
  299. mConvex.mSize.y = mObjBox.len_y() / 2.0;
  300. mConvex.mSize.z = mObjBox.len_z() / 2.0;
  301. mWorkingQueryBox.minExtents.set(-1e9, -1e9, -1e9);
  302. mWorkingQueryBox.maxExtents.set(-1e9, -1e9, -1e9);
  303. if( !isHidden() && !mSubclassItemHandlesScene )
  304. addToScene();
  305. if (isServerObject())
  306. {
  307. if (!mSubclassItemHandlesScene)
  308. scriptOnAdd();
  309. }
  310. else if (mDataBlock->lightType != NoLight)
  311. {
  312. mDropTime = Sim::getCurrentTime();
  313. }
  314. _updatePhysics();
  315. return true;
  316. }
  317. void Item::_updatePhysics()
  318. {
  319. SAFE_DELETE( mPhysicsRep );
  320. if ( !PHYSICSMGR )
  321. return;
  322. if (mDataBlock->simpleServerCollision)
  323. {
  324. // We only need the trigger on the server.
  325. if ( isServerObject() )
  326. {
  327. PhysicsCollision *colShape = PHYSICSMGR->createCollision();
  328. colShape->addBox( mObjBox.getExtents() * 0.5f, MatrixF::Identity );
  329. PhysicsWorld *world = PHYSICSMGR->getWorld( isServerObject() ? "server" : "client" );
  330. mPhysicsRep = PHYSICSMGR->createBody();
  331. mPhysicsRep->init( colShape, 0, PhysicsBody::BF_TRIGGER | PhysicsBody::BF_KINEMATIC, this, world );
  332. mPhysicsRep->setTransform( getTransform() );
  333. }
  334. }
  335. else
  336. {
  337. if ( !mShapeInstance )
  338. return;
  339. PhysicsCollision* colShape = mShapeInstance->getShape()->buildColShape( false, getScale() );
  340. if ( colShape )
  341. {
  342. PhysicsWorld *world = PHYSICSMGR->getWorld( isServerObject() ? "server" : "client" );
  343. mPhysicsRep = PHYSICSMGR->createBody();
  344. mPhysicsRep->init( colShape, 0, PhysicsBody::BF_KINEMATIC, this, world );
  345. mPhysicsRep->setTransform( getTransform() );
  346. }
  347. }
  348. }
  349. bool Item::onNewDataBlock( GameBaseData *dptr, bool reload )
  350. {
  351. mDataBlock = dynamic_cast<ItemData*>(dptr);
  352. if (!mDataBlock || !Parent::onNewDataBlock(dptr,reload))
  353. return false;
  354. if (!mSubclassItemHandlesScene)
  355. scriptOnNewDataBlock();
  356. if ( isProperlyAdded() )
  357. _updatePhysics();
  358. return true;
  359. }
  360. void Item::onRemove()
  361. {
  362. mWorkingQueryBox.minExtents.set(-1e9, -1e9, -1e9);
  363. mWorkingQueryBox.maxExtents.set(-1e9, -1e9, -1e9);
  364. SAFE_DELETE( mPhysicsRep );
  365. if (!mSubclassItemHandlesScene)
  366. {
  367. scriptOnRemove();
  368. removeFromScene();
  369. }
  370. Parent::onRemove();
  371. }
  372. void Item::onDeleteNotify( SimObject *obj )
  373. {
  374. if ( obj == mCollisionObject )
  375. {
  376. mCollisionObject = NULL;
  377. mCollisionTimeout = 0;
  378. }
  379. Parent::onDeleteNotify( obj );
  380. }
  381. // Lighting: -----------------------------------------------------------------
  382. void Item::registerLights(LightManager * lightManager, bool lightingScene)
  383. {
  384. if(lightingScene)
  385. return;
  386. if(mDataBlock->lightOnlyStatic && !mStatic)
  387. return;
  388. F32 intensity;
  389. switch(mDataBlock->lightType)
  390. {
  391. case ConstantLight:
  392. intensity = mFadeVal;
  393. break;
  394. case PulsingLight:
  395. {
  396. S32 delta = Sim::getCurrentTime() - mDropTime;
  397. intensity = 0.5f + 0.5f * mSin(M_PI_F * F32(delta) / F32(mDataBlock->lightTime));
  398. intensity = 0.15f + intensity * 0.85f;
  399. intensity *= mFadeVal; // fade out light on flags
  400. break;
  401. }
  402. default:
  403. return;
  404. }
  405. // Create a light if needed
  406. if (!mLight)
  407. {
  408. mLight = lightManager->createLightInfo();
  409. }
  410. mLight->setColor( mDataBlock->lightColor * intensity );
  411. mLight->setType( LightInfo::Point );
  412. mLight->setRange( mDataBlock->lightRadius );
  413. mLight->setPosition( getBoxCenter() );
  414. lightManager->registerGlobalLight( mLight, this );
  415. }
  416. //----------------------------------------------------------------------------
  417. Point3F Item::getVelocity() const
  418. {
  419. return mVelocity;
  420. }
  421. void Item::setVelocity(const VectorF& vel)
  422. {
  423. mVelocity = vel;
  424. // Clamp against the maximum velocity.
  425. if ( mDataBlock->maxVelocity > 0 )
  426. {
  427. F32 len = mVelocity.magnitudeSafe();
  428. if ( len > mDataBlock->maxVelocity )
  429. {
  430. Point3F excess = mVelocity * ( 1.0f - (mDataBlock->maxVelocity / len ) );
  431. mVelocity -= excess;
  432. }
  433. }
  434. setMaskBits(PositionMask);
  435. mAtRest = false;
  436. mAtRestCounter = 0;
  437. }
  438. void Item::applyImpulse(const Point3F&,const VectorF& vec)
  439. {
  440. // Items ignore angular velocity
  441. VectorF vel;
  442. vel.x = vec.x / mDataBlock->mass;
  443. vel.y = vec.y / mDataBlock->mass;
  444. vel.z = vec.z / mDataBlock->mass;
  445. setVelocity(vel);
  446. }
  447. void Item::setCollisionTimeout(ShapeBase* obj)
  448. {
  449. if (mCollisionObject)
  450. clearNotify(mCollisionObject);
  451. deleteNotify(obj);
  452. mCollisionObject = obj;
  453. mCollisionTimeout = sCollisionTimeout;
  454. setMaskBits(ThrowSrcMask);
  455. }
  456. //----------------------------------------------------------------------------
  457. void Item::processTick(const Move* move)
  458. {
  459. Parent::processTick(move);
  460. //
  461. if (mCollisionObject && !--mCollisionTimeout)
  462. mCollisionObject = 0;
  463. // Warp to catch up to server
  464. if (delta.warpTicks > 0)
  465. {
  466. delta.warpTicks--;
  467. // Set new pos.
  468. MatrixF mat = mObjToWorld;
  469. mat.getColumn(3,&delta.pos);
  470. delta.pos += delta.warpOffset;
  471. mat.setColumn(3,delta.pos);
  472. Parent::setTransform(mat);
  473. // Backstepping
  474. delta.posVec.x = -delta.warpOffset.x;
  475. delta.posVec.y = -delta.warpOffset.y;
  476. delta.posVec.z = -delta.warpOffset.z;
  477. }
  478. else
  479. {
  480. if (isServerObject() && mAtRest && (mStatic == false && mDataBlock->sticky == false))
  481. {
  482. if (++mAtRestCounter > csmAtRestTimer)
  483. {
  484. mAtRest = false;
  485. mAtRestCounter = 0;
  486. setMaskBits(PositionMask);
  487. }
  488. }
  489. if (!mStatic && !mAtRest && isHidden() == false)
  490. {
  491. updateVelocity(TickSec);
  492. updateWorkingCollisionSet(isGhost() ? sClientCollisionMask : sServerCollisionMask, TickSec);
  493. updatePos(isGhost() ? sClientCollisionMask : sServerCollisionMask, TickSec);
  494. }
  495. else
  496. {
  497. // Need to clear out last updatePos or warp interpolation
  498. delta.posVec.set(0,0,0);
  499. }
  500. }
  501. }
  502. void Item::interpolateTick(F32 dt)
  503. {
  504. Parent::interpolateTick(dt);
  505. // Client side interpolation
  506. Point3F pos = delta.pos + delta.posVec * dt;
  507. MatrixF mat = mRenderObjToWorld;
  508. mat.setColumn(3,pos);
  509. setRenderTransform(mat);
  510. delta.dt = dt;
  511. }
  512. //----------------------------------------------------------------------------
  513. void Item::setTransform(const MatrixF& mat)
  514. {
  515. Point3F pos;
  516. mat.getColumn(3,&pos);
  517. MatrixF tmat;
  518. if (!mRotate) {
  519. // Forces all rotation to be around the z axis
  520. VectorF vec;
  521. mat.getColumn(1,&vec);
  522. tmat.set(EulerF(0,0,-mAtan2(-vec.x,vec.y)));
  523. }
  524. else
  525. tmat.identity();
  526. tmat.setColumn(3,pos);
  527. Parent::setTransform(tmat);
  528. if (!mStatic)
  529. {
  530. mAtRest = false;
  531. mAtRestCounter = 0;
  532. }
  533. if ( mPhysicsRep )
  534. mPhysicsRep->setTransform( getTransform() );
  535. setMaskBits(RotationMask | PositionMask | NoWarpMask);
  536. }
  537. //----------------------------------------------------------------------------
  538. void Item::updateWorkingCollisionSet(const U32 mask, const F32 dt)
  539. {
  540. // It is assumed that we will never accelerate more than 10 m/s for gravity...
  541. //
  542. Point3F scaledVelocity = mVelocity * dt;
  543. F32 len = scaledVelocity.len();
  544. F32 newLen = len + (10 * dt);
  545. // Check to see if it is actually necessary to construct the new working list,
  546. // or if we can use the cached version from the last query. We use the x
  547. // component of the min member of the mWorkingQueryBox, which is lame, but
  548. // it works ok.
  549. bool updateSet = false;
  550. Box3F convexBox = mConvex.getBoundingBox(getTransform(), getScale());
  551. F32 l = (newLen * 1.1) + 0.1; // from Convex::updateWorkingList
  552. convexBox.minExtents -= Point3F(l, l, l);
  553. convexBox.maxExtents += Point3F(l, l, l);
  554. // Check containment
  555. {
  556. if (mWorkingQueryBox.minExtents.x != -1e9)
  557. {
  558. if (mWorkingQueryBox.isContained(convexBox) == false)
  559. {
  560. // Needed region is outside the cached region. Update it.
  561. updateSet = true;
  562. }
  563. else
  564. {
  565. // We can leave it alone, we're still inside the cached region
  566. }
  567. }
  568. else
  569. {
  570. // Must update
  571. updateSet = true;
  572. }
  573. }
  574. // Actually perform the query, if necessary
  575. if (updateSet == true)
  576. {
  577. mWorkingQueryBox = convexBox;
  578. mWorkingQueryBox.minExtents -= Point3F(2 * l, 2 * l, 2 * l);
  579. mWorkingQueryBox.maxExtents += Point3F(2 * l, 2 * l, 2 * l);
  580. disableCollision();
  581. if (mCollisionObject)
  582. mCollisionObject->disableCollision();
  583. mConvex.updateWorkingList(mWorkingQueryBox, mask);
  584. if (mCollisionObject)
  585. mCollisionObject->enableCollision();
  586. enableCollision();
  587. }
  588. }
  589. void Item::updateVelocity(const F32 dt)
  590. {
  591. // Acceleration due to gravity
  592. mVelocity.z += (mGravity * mDataBlock->gravityMod) * dt;
  593. F32 len;
  594. if (mDataBlock->maxVelocity > 0 && (len = mVelocity.len()) > (mDataBlock->maxVelocity * 1.05)) {
  595. Point3F excess = mVelocity * (1.0 - (mDataBlock->maxVelocity / len ));
  596. excess *= 0.1f;
  597. mVelocity -= excess;
  598. }
  599. // Container buoyancy & drag
  600. mVelocity.z -= mBuoyancy * (mGravity * mDataBlock->gravityMod * mGravityMod) * dt;
  601. mVelocity -= mVelocity * mDrag * dt;
  602. }
  603. void Item::updatePos(const U32 /*mask*/, const F32 dt)
  604. {
  605. // Try and move
  606. Point3F pos;
  607. mObjToWorld.getColumn(3,&pos);
  608. delta.posVec = pos;
  609. bool contact = false;
  610. bool nonStatic = false;
  611. bool stickyNotify = false;
  612. CollisionList collisionList;
  613. F32 time = dt;
  614. static Polyhedron sBoxPolyhedron;
  615. static ExtrudedPolyList sExtrudedPolyList;
  616. static EarlyOutPolyList sEarlyOutPolyList;
  617. MatrixF collisionMatrix(true);
  618. Point3F end = pos + mVelocity * time;
  619. U32 mask = isServerObject() ? sServerCollisionMask : sClientCollisionMask;
  620. // Part of our speed problem here is that we don't track contact surfaces, like we do
  621. // with the player. In order to handle the most common and performance impacting
  622. // instance of this problem, we'll use a ray cast to detect any contact surfaces below
  623. // us. This won't be perfect, but it only needs to catch a few of these to make a
  624. // big difference. We'll cast from the top center of the bounding box at the tick's
  625. // beginning to the bottom center of the box at the end.
  626. Point3F startCast((mObjBox.minExtents.x + mObjBox.maxExtents.x) * 0.5,
  627. (mObjBox.minExtents.y + mObjBox.maxExtents.y) * 0.5,
  628. mObjBox.maxExtents.z);
  629. Point3F endCast((mObjBox.minExtents.x + mObjBox.maxExtents.x) * 0.5,
  630. (mObjBox.minExtents.y + mObjBox.maxExtents.y) * 0.5,
  631. mObjBox.minExtents.z);
  632. collisionMatrix.setColumn(3, pos);
  633. collisionMatrix.mulP(startCast);
  634. collisionMatrix.setColumn(3, end);
  635. collisionMatrix.mulP(endCast);
  636. RayInfo rinfo;
  637. bool doToughCollision = true;
  638. disableCollision();
  639. if (mCollisionObject)
  640. mCollisionObject->disableCollision();
  641. if (getContainer()->castRay(startCast, endCast, mask, &rinfo))
  642. {
  643. F32 bd = -mDot(mVelocity, rinfo.normal);
  644. if (bd >= 0.0)
  645. {
  646. // Contact!
  647. if (mDataBlock->sticky && rinfo.object->getTypeMask() & (STATIC_COLLISION_TYPEMASK)) {
  648. mVelocity.set(0, 0, 0);
  649. mAtRest = true;
  650. mAtRestCounter = 0;
  651. stickyNotify = true;
  652. mStickyCollisionPos = rinfo.point;
  653. mStickyCollisionNormal = rinfo.normal;
  654. doToughCollision = false;;
  655. } else {
  656. // Subtract out velocity into surface and friction
  657. VectorF fv = mVelocity + rinfo.normal * bd;
  658. F32 fvl = fv.len();
  659. if (fvl) {
  660. F32 ff = bd * mDataBlock->friction;
  661. if (ff < fvl) {
  662. fv *= ff / fvl;
  663. fvl = ff;
  664. }
  665. }
  666. bd *= 1 + mDataBlock->elasticity;
  667. VectorF dv = rinfo.normal * (bd + 0.002);
  668. mVelocity += dv;
  669. mVelocity -= fv;
  670. // Keep track of what we hit
  671. contact = true;
  672. U32 typeMask = rinfo.object->getTypeMask();
  673. if (!(typeMask & StaticObjectType))
  674. nonStatic = true;
  675. if (isServerObject() && (typeMask & ShapeBaseObjectType)) {
  676. ShapeBase* col = static_cast<ShapeBase*>(rinfo.object);
  677. queueCollision(col,mVelocity - col->getVelocity());
  678. }
  679. }
  680. }
  681. }
  682. enableCollision();
  683. if (mCollisionObject)
  684. mCollisionObject->enableCollision();
  685. if (doToughCollision)
  686. {
  687. U32 count;
  688. for (count = 0; count < 3; count++)
  689. {
  690. // Build list from convex states here...
  691. end = pos + mVelocity * time;
  692. collisionMatrix.setColumn(3, end);
  693. Box3F wBox = getObjBox();
  694. collisionMatrix.mul(wBox);
  695. Box3F testBox = wBox;
  696. Point3F oldMin = testBox.minExtents;
  697. Point3F oldMax = testBox.maxExtents;
  698. testBox.minExtents.setMin(oldMin + (mVelocity * time));
  699. testBox.maxExtents.setMin(oldMax + (mVelocity * time));
  700. sEarlyOutPolyList.clear();
  701. sEarlyOutPolyList.mNormal.set(0,0,0);
  702. sEarlyOutPolyList.mPlaneList.setSize(6);
  703. sEarlyOutPolyList.mPlaneList[0].set(wBox.minExtents,VectorF(-1,0,0));
  704. sEarlyOutPolyList.mPlaneList[1].set(wBox.maxExtents,VectorF(0,1,0));
  705. sEarlyOutPolyList.mPlaneList[2].set(wBox.maxExtents,VectorF(1,0,0));
  706. sEarlyOutPolyList.mPlaneList[3].set(wBox.minExtents,VectorF(0,-1,0));
  707. sEarlyOutPolyList.mPlaneList[4].set(wBox.minExtents,VectorF(0,0,-1));
  708. sEarlyOutPolyList.mPlaneList[5].set(wBox.maxExtents,VectorF(0,0,1));
  709. CollisionWorkingList& eorList = mConvex.getWorkingList();
  710. CollisionWorkingList* eopList = eorList.wLink.mNext;
  711. while (eopList != &eorList) {
  712. if ((eopList->mConvex->getObject()->getTypeMask() & mask) != 0)
  713. {
  714. Box3F convexBox = eopList->mConvex->getBoundingBox();
  715. if (testBox.isOverlapped(convexBox))
  716. {
  717. eopList->mConvex->getPolyList(&sEarlyOutPolyList);
  718. if (sEarlyOutPolyList.isEmpty() == false)
  719. break;
  720. }
  721. }
  722. eopList = eopList->wLink.mNext;
  723. }
  724. if (sEarlyOutPolyList.isEmpty())
  725. {
  726. pos = end;
  727. break;
  728. }
  729. collisionMatrix.setColumn(3, pos);
  730. sBoxPolyhedron.buildBox(collisionMatrix, mObjBox, true);
  731. // Build extruded polyList...
  732. VectorF vector = end - pos;
  733. sExtrudedPolyList.extrude(sBoxPolyhedron, vector);
  734. sExtrudedPolyList.setVelocity(mVelocity);
  735. sExtrudedPolyList.setCollisionList(&collisionList);
  736. CollisionWorkingList& rList = mConvex.getWorkingList();
  737. CollisionWorkingList* pList = rList.wLink.mNext;
  738. while (pList != &rList) {
  739. if ((pList->mConvex->getObject()->getTypeMask() & mask) != 0)
  740. {
  741. Box3F convexBox = pList->mConvex->getBoundingBox();
  742. if (testBox.isOverlapped(convexBox))
  743. {
  744. pList->mConvex->getPolyList(&sExtrudedPolyList);
  745. }
  746. }
  747. pList = pList->wLink.mNext;
  748. }
  749. if (collisionList.getTime() < 1.0)
  750. {
  751. // Set to collision point
  752. F32 dt = time * collisionList.getTime();
  753. pos += mVelocity * dt;
  754. time -= dt;
  755. // Pick the most resistant surface
  756. F32 bd = 0;
  757. const Collision* collision = 0;
  758. for (int c = 0; c < collisionList.getCount(); c++) {
  759. const Collision &cp = collisionList[c];
  760. F32 dot = -mDot(mVelocity,cp.normal);
  761. if (dot > bd) {
  762. bd = dot;
  763. collision = &cp;
  764. }
  765. }
  766. if (collision && mDataBlock->sticky && collision->object->getTypeMask() & (STATIC_COLLISION_TYPEMASK)) {
  767. mVelocity.set(0, 0, 0);
  768. mAtRest = true;
  769. mAtRestCounter = 0;
  770. stickyNotify = true;
  771. mStickyCollisionPos = collision->point;
  772. mStickyCollisionNormal = collision->normal;
  773. break;
  774. } else {
  775. // Subtract out velocity into surface and friction
  776. if (collision) {
  777. VectorF fv = mVelocity + collision->normal * bd;
  778. F32 fvl = fv.len();
  779. if (fvl) {
  780. F32 ff = bd * mDataBlock->friction;
  781. if (ff < fvl) {
  782. fv *= ff / fvl;
  783. fvl = ff;
  784. }
  785. }
  786. bd *= 1 + mDataBlock->elasticity;
  787. VectorF dv = collision->normal * (bd + 0.002);
  788. mVelocity += dv;
  789. mVelocity -= fv;
  790. // Keep track of what we hit
  791. contact = true;
  792. U32 typeMask = collision->object->getTypeMask();
  793. if (!(typeMask & StaticObjectType))
  794. nonStatic = true;
  795. if (isServerObject() && (typeMask & ShapeBaseObjectType)) {
  796. ShapeBase* col = static_cast<ShapeBase*>(collision->object);
  797. queueCollision(col,mVelocity - col->getVelocity());
  798. }
  799. }
  800. }
  801. }
  802. else
  803. {
  804. pos = end;
  805. break;
  806. }
  807. }
  808. if (count == 3)
  809. {
  810. // Couldn't move...
  811. mVelocity.set(0, 0, 0);
  812. }
  813. }
  814. // If on the client, calculate delta for backstepping
  815. if (isGhost()) {
  816. delta.pos = pos;
  817. delta.posVec -= pos;
  818. delta.dt = 1;
  819. }
  820. // Update transform
  821. MatrixF mat = mObjToWorld;
  822. mat.setColumn(3,pos);
  823. Parent::setTransform(mat);
  824. enableCollision();
  825. if (mCollisionObject)
  826. mCollisionObject->enableCollision();
  827. updateContainer();
  828. if ( mPhysicsRep )
  829. mPhysicsRep->setTransform( mat );
  830. //
  831. if (contact) {
  832. // Check for rest condition
  833. if (!nonStatic && mVelocity.len() < sAtRestVelocity) {
  834. mVelocity.x = mVelocity.y = mVelocity.z = 0;
  835. mAtRest = true;
  836. mAtRestCounter = 0;
  837. }
  838. // Only update the client if we hit a non-static shape or
  839. // if this is our final rest pos.
  840. if (nonStatic || mAtRest)
  841. setMaskBits(PositionMask);
  842. }
  843. // Collision callbacks. These need to be processed whether we hit
  844. // anything or not.
  845. if (!isGhost())
  846. {
  847. SimObjectPtr<Item> safePtr(this);
  848. if (stickyNotify)
  849. {
  850. notifyCollision();
  851. if(bool(safePtr))
  852. onStickyCollision_callback( getIdString() );
  853. }
  854. else
  855. notifyCollision();
  856. // water
  857. if(bool(safePtr))
  858. {
  859. if(!mInLiquid && mWaterCoverage != 0.0f)
  860. {
  861. onEnterLiquid_callback( getIdString(), Con::getFloatArg(mWaterCoverage), mLiquidType.c_str() );
  862. mInLiquid = true;
  863. }
  864. else if(mInLiquid && mWaterCoverage == 0.0f)
  865. {
  866. onLeaveLiquid_callback(getIdString(), mLiquidType.c_str());
  867. mInLiquid = false;
  868. }
  869. }
  870. }
  871. }
  872. //----------------------------------------------------------------------------
  873. static MatrixF IMat(1);
  874. bool Item::buildPolyList(PolyListContext context, AbstractPolyList* polyList, const Box3F&, const SphereF&)
  875. {
  876. if ( context == PLC_Decal )
  877. return false;
  878. // Collision with the item is always against the item's object
  879. // space bounding box axis aligned in world space.
  880. Point3F pos;
  881. mObjToWorld.getColumn(3,&pos);
  882. IMat.setColumn(3,pos);
  883. polyList->setTransform(&IMat, mObjScale);
  884. polyList->setObject(this);
  885. polyList->addBox(mObjBox);
  886. return true;
  887. }
  888. //----------------------------------------------------------------------------
  889. U32 Item::packUpdate(NetConnection *connection, U32 mask, BitStream *stream)
  890. {
  891. U32 retMask = Parent::packUpdate(connection,mask,stream);
  892. if (stream->writeFlag(mask & InitialUpdateMask)) {
  893. stream->writeFlag(mRotate);
  894. stream->writeFlag(mStatic);
  895. if (stream->writeFlag(getScale() != Point3F(1, 1, 1)))
  896. mathWrite(*stream, getScale());
  897. }
  898. if (mask & ThrowSrcMask && mCollisionObject) {
  899. S32 gIndex = connection->getGhostIndex(mCollisionObject);
  900. if (stream->writeFlag(gIndex != -1))
  901. stream->writeInt(gIndex,NetConnection::GhostIdBitSize);
  902. }
  903. else
  904. stream->writeFlag(false);
  905. if (stream->writeFlag(mask & RotationMask && !mRotate)) {
  906. // Assumes rotation is about the Z axis
  907. AngAxisF aa(mObjToWorld);
  908. stream->writeFlag(aa.axis.z < 0);
  909. stream->write(aa.angle);
  910. }
  911. if (stream->writeFlag(mask & PositionMask)) {
  912. Point3F pos;
  913. mObjToWorld.getColumn(3,&pos);
  914. mathWrite(*stream, pos);
  915. if (!stream->writeFlag(mAtRest)) {
  916. mathWrite(*stream, mVelocity);
  917. }
  918. stream->writeFlag(!(mask & NoWarpMask));
  919. }
  920. return retMask;
  921. }
  922. void Item::unpackUpdate(NetConnection *connection, BitStream *stream)
  923. {
  924. Parent::unpackUpdate(connection,stream);
  925. // InitialUpdateMask
  926. if (stream->readFlag()) {
  927. mRotate = stream->readFlag();
  928. mStatic = stream->readFlag();
  929. if (stream->readFlag())
  930. mathRead(*stream, &mObjScale);
  931. else
  932. mObjScale.set(1, 1, 1);
  933. }
  934. // ThrowSrcMask && mCollisionObject
  935. if (stream->readFlag()) {
  936. S32 gIndex = stream->readInt(NetConnection::GhostIdBitSize);
  937. setCollisionTimeout(static_cast<ShapeBase*>(connection->resolveGhost(gIndex)));
  938. }
  939. MatrixF mat = mObjToWorld;
  940. // RotationMask && !mRotate
  941. if (stream->readFlag()) {
  942. // Assumes rotation is about the Z axis
  943. AngAxisF aa;
  944. aa.axis.set(0.0f, 0.0f, stream->readFlag() ? -1.0f : 1.0f);
  945. stream->read(&aa.angle);
  946. aa.setMatrix(&mat);
  947. Point3F pos;
  948. mObjToWorld.getColumn(3,&pos);
  949. mat.setColumn(3,pos);
  950. }
  951. // PositionMask
  952. if (stream->readFlag()) {
  953. Point3F pos;
  954. mathRead(*stream, &pos);
  955. F32 speed = mVelocity.len();
  956. if ((mAtRest = stream->readFlag()) == true)
  957. mVelocity.set(0.0f, 0.0f, 0.0f);
  958. else
  959. mathRead(*stream, &mVelocity);
  960. if (stream->readFlag() && isProperlyAdded()) {
  961. // Determin number of ticks to warp based on the average
  962. // of the client and server velocities.
  963. delta.warpOffset = pos - delta.pos;
  964. F32 as = (speed + mVelocity.len()) * 0.5f * TickSec;
  965. F32 dt = (as > 0.00001f) ? delta.warpOffset.len() / as: sMaxWarpTicks;
  966. delta.warpTicks = (S32)((dt > sMinWarpTicks)? getMax(mFloor(dt + 0.5f), 1.0f): 0.0f);
  967. if (delta.warpTicks)
  968. {
  969. // Setup the warp to start on the next tick, only the
  970. // object's position is warped.
  971. if (delta.warpTicks > sMaxWarpTicks)
  972. delta.warpTicks = sMaxWarpTicks;
  973. delta.warpOffset /= (F32)delta.warpTicks;
  974. }
  975. else {
  976. // Going to skip the warp, server and client are real close.
  977. // Adjust the frame interpolation to move smoothly to the
  978. // new position within the current tick.
  979. Point3F cp = delta.pos + delta.posVec * delta.dt;
  980. VectorF vec = delta.pos - cp;
  981. F32 vl = vec.len();
  982. if (vl) {
  983. F32 s = delta.posVec.len() / vl;
  984. delta.posVec = (cp - pos) * s;
  985. }
  986. delta.pos = pos;
  987. mat.setColumn(3,pos);
  988. }
  989. }
  990. else {
  991. // Set the item to the server position
  992. delta.warpTicks = 0;
  993. delta.posVec.set(0,0,0);
  994. delta.pos = pos;
  995. delta.dt = 0;
  996. mat.setColumn(3,pos);
  997. }
  998. }
  999. Parent::setTransform(mat);
  1000. }
  1001. DefineEngineMethod( Item, isStatic, bool, (),,
  1002. "@brief Is the object static (ie, non-movable)?\n\n"
  1003. "@return True if the object is static, false if it is not.\n"
  1004. "@tsexample\n"
  1005. "// Query the item on if it is or is not static.\n"
  1006. "%isStatic = %itemData.isStatic();\n\n"
  1007. "@endtsexample\n\n"
  1008. "@see static\n"
  1009. )
  1010. {
  1011. return object->isStatic();
  1012. }
  1013. DefineEngineMethod( Item, isAtRest, bool, (),,
  1014. "@brief Is the object at rest (ie, no longer moving)?\n\n"
  1015. "@return True if the object is at rest, false if it is not.\n"
  1016. "@tsexample\n"
  1017. "// Query the item on if it is or is not at rest.\n"
  1018. "%isAtRest = %item.isAtRest();\n\n"
  1019. "@endtsexample\n\n"
  1020. )
  1021. {
  1022. return object->isAtRest();
  1023. }
  1024. DefineEngineMethod( Item, isRotating, bool, (),,
  1025. "@brief Is the object still rotating?\n\n"
  1026. "@return True if the object is still rotating, false if it is not.\n"
  1027. "@tsexample\n"
  1028. "// Query the item on if it is or is not rotating.\n"
  1029. "%isRotating = %itemData.isRotating();\n\n"
  1030. "@endtsexample\n\n"
  1031. "@see rotate\n"
  1032. )
  1033. {
  1034. return object->isRotating();
  1035. }
  1036. DefineEngineMethod( Item, setCollisionTimeout, bool, (int ignoreColObj),(NULL),
  1037. "@brief Temporarily disable collisions against a specific ShapeBase object.\n\n"
  1038. "This is useful to prevent a player from immediately picking up an Item they have "
  1039. "just thrown. Only one object may be on the timeout list at a time. The timeout is "
  1040. "defined as 15 ticks.\n\n"
  1041. "@param objectID ShapeBase object ID to disable collisions against.\n"
  1042. "@return Returns true if the ShapeBase object requested could be found, false if it could not.\n"
  1043. "@tsexample\n"
  1044. "// Set the ShapeBase Object ID to disable collisions against\n"
  1045. "%ignoreColObj = %player.getID();\n\n"
  1046. "// Inform this Item object to ignore collisions temproarily against the %ignoreColObj.\n"
  1047. "%item.setCollisionTimeout(%ignoreColObj);\n\n"
  1048. "@endtsexample\n\n"
  1049. )
  1050. {
  1051. ShapeBase* source = NULL;
  1052. if (Sim::findObject(ignoreColObj,source)) {
  1053. object->setCollisionTimeout(source);
  1054. return true;
  1055. }
  1056. return false;
  1057. }
  1058. DefineEngineMethod( Item, getLastStickyPos, const char*, (),,
  1059. "@brief Get the position on the surface on which this Item is stuck.\n\n"
  1060. "@return Returns The XYZ position of where this Item is stuck.\n"
  1061. "@tsexample\n"
  1062. "// Acquire the position where this Item is currently stuck\n"
  1063. "%stuckPosition = %item.getLastStickPos();\n\n"
  1064. "@endtsexample\n\n"
  1065. "@note Server side only.\n"
  1066. )
  1067. {
  1068. char* ret = Con::getReturnBuffer(256);
  1069. if (object->isServerObject())
  1070. dSprintf(ret, 255, "%g %g %g",
  1071. object->mStickyCollisionPos.x,
  1072. object->mStickyCollisionPos.y,
  1073. object->mStickyCollisionPos.z);
  1074. else
  1075. dStrcpy(ret, "0 0 0");
  1076. return ret;
  1077. }
  1078. DefineEngineMethod( Item, getLastStickyNormal, const char *, (),,
  1079. "@brief Get the normal of the surface on which the object is stuck.\n\n"
  1080. "@return Returns The XYZ normal from where this Item is stuck.\n"
  1081. "@tsexample\n"
  1082. "// Acquire the position where this Item is currently stuck\n"
  1083. "%stuckPosition = %item.getLastStickPos();\n\n"
  1084. "@endtsexample\n\n"
  1085. "@note Server side only.\n"
  1086. )
  1087. {
  1088. char* ret = Con::getReturnBuffer(256);
  1089. if (object->isServerObject())
  1090. dSprintf(ret, 255, "%g %g %g",
  1091. object->mStickyCollisionNormal.x,
  1092. object->mStickyCollisionNormal.y,
  1093. object->mStickyCollisionNormal.z);
  1094. else
  1095. dStrcpy(ret, "0 0 0");
  1096. return ret;
  1097. }
  1098. //----------------------------------------------------------------------------
  1099. bool Item::_setStatic(void *object, const char *index, const char *data)
  1100. {
  1101. Item *i = static_cast<Item*>(object);
  1102. i->mAtRest = dAtob(data);
  1103. i->setMaskBits(InitialUpdateMask | PositionMask);
  1104. return true;
  1105. }
  1106. bool Item::_setRotate(void *object, const char *index, const char *data)
  1107. {
  1108. Item *i = static_cast<Item*>(object);
  1109. i->setMaskBits(InitialUpdateMask | RotationMask);
  1110. return true;
  1111. }
  1112. void Item::initPersistFields()
  1113. {
  1114. addGroup("Misc");
  1115. addProtectedField("static", TypeBool, Offset(mStatic, Item), &_setStatic, &defaultProtectedGetFn, "If true, the object is not moving in the world.\n");
  1116. addProtectedField("rotate", TypeBool, Offset(mRotate, Item), &_setRotate, &defaultProtectedGetFn, "If true, the object will automatically rotate around its Z axis.\n");
  1117. endGroup("Misc");
  1118. Parent::initPersistFields();
  1119. }
  1120. void Item::consoleInit()
  1121. {
  1122. Con::addVariable("Item::minWarpTicks",TypeF32,&sMinWarpTicks,
  1123. "@brief Fraction of tick at which instant warp occures on the client.\n\n"
  1124. "@ingroup GameObjects");
  1125. Con::addVariable("Item::maxWarpTicks",TypeS32,&sMaxWarpTicks,
  1126. "@brief When a warp needs to occur due to the client being too far off from the server, this is the "
  1127. "maximum number of ticks we'll allow the client to warp to catch up.\n\n"
  1128. "@ingroup GameObjects");
  1129. }
  1130. //----------------------------------------------------------------------------
  1131. void Item::prepRenderImage( SceneRenderState* state )
  1132. {
  1133. // Items do NOT render if destroyed
  1134. if (getDamageState() == Destroyed)
  1135. return;
  1136. Parent::prepRenderImage( state );
  1137. }
  1138. void Item::buildConvex(const Box3F& box, Convex* convex)
  1139. {
  1140. if (mShapeInstance == NULL)
  1141. return;
  1142. // These should really come out of a pool
  1143. mConvexList->collectGarbage();
  1144. if (box.isOverlapped(getWorldBox()) == false)
  1145. return;
  1146. // Just return a box convex for the entire shape...
  1147. Convex* cc = 0;
  1148. CollisionWorkingList& wl = convex->getWorkingList();
  1149. for (CollisionWorkingList* itr = wl.wLink.mNext; itr != &wl; itr = itr->wLink.mNext) {
  1150. if (itr->mConvex->getType() == BoxConvexType &&
  1151. itr->mConvex->getObject() == this) {
  1152. cc = itr->mConvex;
  1153. break;
  1154. }
  1155. }
  1156. if (cc)
  1157. return;
  1158. // Create a new convex.
  1159. BoxConvex* cp = new BoxConvex;
  1160. mConvexList->registerObject(cp);
  1161. convex->addToWorkingList(cp);
  1162. cp->init(this);
  1163. mObjBox.getCenter(&cp->mCenter);
  1164. cp->mSize.x = mObjBox.len_x() / 2.0f;
  1165. cp->mSize.y = mObjBox.len_y() / 2.0f;
  1166. cp->mSize.z = mObjBox.len_z() / 2.0f;
  1167. }
  1168. void Item::advanceTime(F32 dt)
  1169. {
  1170. Parent::advanceTime(dt);
  1171. if( mRotate )
  1172. {
  1173. F32 r = (dt / sRotationSpeed) * M_2PI;
  1174. Point3F pos = mRenderObjToWorld.getPosition();
  1175. MatrixF rotMatrix;
  1176. if( mRotate )
  1177. {
  1178. rotMatrix.set( EulerF( 0.0, 0.0, r ) );
  1179. }
  1180. else
  1181. {
  1182. rotMatrix.set( EulerF( r * 0.5, 0.0, r ) );
  1183. }
  1184. MatrixF mat = mRenderObjToWorld;
  1185. mat.setPosition( pos );
  1186. mat.mul( rotMatrix );
  1187. setRenderTransform(mat);
  1188. }
  1189. }