vehicle.cpp

Engine/source/T3D/vehicles/vehicle.cpp

More...

Public Variables

Public Functions

ConsoleDocClass(Vehicle , "@brief Base functionality shared by all Vehicles (<a href="/coding/class/classflyingvehicle/">FlyingVehicle</a>, <a href="/coding/class/classhovervehicle/">HoverVehicle</a>, " "<a href="/coding/class/classwheeledvehicle/">WheeledVehicle</a>).\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "This object implements functionality shared by all <a href="/coding/class/classvehicle/">Vehicle</a> types, but should " "not be instantiated directly. Create <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> FlyingVehicle, <a href="/coding/class/classhovervehicle/">HoverVehicle</a> , or " "<a href="/coding/class/classwheeledvehicle/">WheeledVehicle</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">instead.\n</a>" " @note The model used <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> any <a href="/coding/class/classvehicle/">Vehicle</a> must include <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> collision mesh at detail " "<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1ab7d671599a7b25ca99a487fa341bc33a">size</a> -1.\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " @ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Vehicles\n</a>" )
ConsoleDocClass(VehicleData , "@brief Base properties shared by all Vehicles (<a href="/coding/class/classflyingvehicle/">FlyingVehicle</a>, <a href="/coding/class/classhovervehicle/">HoverVehicle</a>, " "<a href="/coding/class/classwheeledvehicle/">WheeledVehicle</a>).\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "This datablock defines properties shared by all <a href="/coding/class/classvehicle/">Vehicle</a> types, but should " "not be instantiated directly. Instead, set the desired properties in the " " FlyingVehicleData, <a href="/coding/class/classhovervehicledata/">HoverVehicleData</a> or <a href="/coding/class/structwheeledvehicledata/">WheeledVehicleData</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">datablock.\n</a>" " @section VehicleData_damage <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Damage\n\n</a>" "The <a href="/coding/class/structvehicledata/">VehicleData</a> class extends the basic energy/damage functionality provided " "by <a href="/coding/class/structshapebasedata/">ShapeBaseData</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> include damage from collisions, as well as particle " "emitters activated automatically when damage levels reach user specified " "<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">thresholds.\n\n</a>" "The example below shows how <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> setup <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> <a href="/coding/class/classvehicle/">Vehicle</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">to:\n</a>" "< ul >\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "< li >take damage when colliding with another <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">object\n</a>" "< li >emit gray smoke particles from two locations on the <a href="/coding/class/classvehicle/">Vehicle</a> when damaged above 50%</li >\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "< li >emit black smoke particles from two locations on the <a href="/coding/class/classvehicle/">Vehicle</a> when damaged above 85%</li >\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "< li >emit bubbles when any active damage emitter point is underwater</li >\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "</ul >\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" " @<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "//damage from <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">collisions\n</a>" " collDamageMultiplier)

Detailed Description

Public Variables

U32 sTriggerMask 

Public Functions

ConsoleDocClass(Vehicle , "@brief Base functionality shared by all Vehicles (<a href="/coding/class/classflyingvehicle/">FlyingVehicle</a>, <a href="/coding/class/classhovervehicle/">HoverVehicle</a>, " "<a href="/coding/class/classwheeledvehicle/">WheeledVehicle</a>).\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "This object implements functionality shared by all <a href="/coding/class/classvehicle/">Vehicle</a> types, but should " "not be instantiated directly. Create <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> FlyingVehicle, <a href="/coding/class/classhovervehicle/">HoverVehicle</a> , or " "<a href="/coding/class/classwheeledvehicle/">WheeledVehicle</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">instead.\n</a>" " @note The model used <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> any <a href="/coding/class/classvehicle/">Vehicle</a> must include <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> collision mesh at detail " "<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1ab7d671599a7b25ca99a487fa341bc33a">size</a> -1.\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " @ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Vehicles\n</a>" )

ConsoleDocClass(VehicleData , "@brief Base properties shared by all Vehicles (<a href="/coding/class/classflyingvehicle/">FlyingVehicle</a>, <a href="/coding/class/classhovervehicle/">HoverVehicle</a>, " "<a href="/coding/class/classwheeledvehicle/">WheeledVehicle</a>).\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "This datablock defines properties shared by all <a href="/coding/class/classvehicle/">Vehicle</a> types, but should " "not be instantiated directly. Instead, set the desired properties in the " " FlyingVehicleData, <a href="/coding/class/classhovervehicledata/">HoverVehicleData</a> or <a href="/coding/class/structwheeledvehicledata/">WheeledVehicleData</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">datablock.\n</a>" " @section VehicleData_damage <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Damage\n\n</a>" "The <a href="/coding/class/structvehicledata/">VehicleData</a> class extends the basic energy/damage functionality provided " "by <a href="/coding/class/structshapebasedata/">ShapeBaseData</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> include damage from collisions, as well as particle " "emitters activated automatically when damage levels reach user specified " "<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">thresholds.\n\n</a>" "The example below shows how <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> setup <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> <a href="/coding/class/classvehicle/">Vehicle</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">to:\n</a>" "< ul >\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "< li >take damage when colliding with another <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">object\n</a>" "< li >emit gray smoke particles from two locations on the <a href="/coding/class/classvehicle/">Vehicle</a> when damaged above 50%</li >\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "< li >emit black smoke particles from two locations on the <a href="/coding/class/classvehicle/">Vehicle</a> when damaged above 85%</li >\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "< li >emit bubbles when any active damage emitter point is underwater</li >\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "</ul >\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" " @<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "//damage from <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">collisions\n</a>" " collDamageMultiplier)

IMPLEMENT_CONOBJECT(Vehicle )

IMPLEMENT_CONOBJECT(VehicleData )

   1
   2//-----------------------------------------------------------------------------
   3// Copyright (c) 2012 GarageGames, LLC
   4//
   5// Permission is hereby granted, free of charge, to any person obtaining a copy
   6// of this software and associated documentation files (the "Software"), to
   7// deal in the Software without restriction, including without limitation the
   8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
   9// sell copies of the Software, and to permit persons to whom the Software is
  10// furnished to do so, subject to the following conditions:
  11//
  12// The above copyright notice and this permission notice shall be included in
  13// all copies or substantial portions of the Software.
  14//
  15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  21// IN THE SOFTWARE.
  22//-----------------------------------------------------------------------------
  23
  24#include "platform/platform.h"
  25#include "T3D/vehicles/vehicle.h"
  26
  27#include "math/mMath.h"
  28#include "console/simBase.h"
  29#include "console/console.h"
  30#include "console/consoleTypes.h"
  31#include "console/engineAPI.h"
  32#include "collision/clippedPolyList.h"
  33#include "collision/planeExtractor.h"
  34#include "core/stream/bitStream.h"
  35#include "core/dnet.h"
  36#include "T3D/gameBase/gameConnection.h"
  37#include "T3D/fx/cameraFXMgr.h"
  38#include "ts/tsShapeInstance.h"
  39#include "T3D/fx/particleEmitter.h"
  40#include "sfx/sfxSystem.h"
  41#include "sfx/sfxProfile.h"
  42#include "sfx/sfxSource.h"
  43#include "math/mathIO.h"
  44#include "scene/sceneRenderState.h"
  45#include "T3D/trigger.h"
  46#include "T3D/item.h"
  47#include "gfx/primBuilder.h"
  48#include "gfx/gfxDrawUtil.h"
  49#include "materials/materialDefinition.h"
  50#include "T3D/physics/physicsPlugin.h"
  51#include "T3D/physics/physicsBody.h"
  52#include "T3D/physics/physicsCollision.h"
  53
  54
  55namespace {
  56
  57static U32 sWorkingQueryBoxStaleThreshold = 10;    // The maximum number of ticks that go by before
  58                                                   // the mWorkingQueryBox is considered stale and
  59                                                   // needs updating.  Set to -1 to disable.
  60
  61static F32 sWorkingQueryBoxSizeMultiplier = 2.0f;  // How much larger should the mWorkingQueryBox be
  62                                                   // made when updating the working collision list.
  63                                                   // The larger this number the less often the working list
  64                                                   // will be updated due to motion, but any non-static shape
  65                                                   // that moves into the query box will not be noticed.
  66
  67// Client prediction
  68const S32 sMaxWarpTicks = 3;           // Max warp duration in ticks
  69const S32 sMaxPredictionTicks = 30;    // Number of ticks to predict
  70
  71// Physics and collision constants
  72static F32 sRestTol = 0.5;             // % of gravity energy to be at rest
  73static S32 sRestCount = 10;            // Consecutive ticks before comming to rest
  74
  75} // namespace {}
  76
  77// Trigger objects that are not normally collided with.
  78static U32 sTriggerMask = ItemObjectType     |
  79                          TriggerObjectType  |
  80                          CorpseObjectType;
  81
  82IMPLEMENT_CONOBJECT(VehicleData);
  83
  84ConsoleDocClass( VehicleData,
  85   "@brief Base properties shared by all Vehicles (FlyingVehicle, HoverVehicle, "
  86   "WheeledVehicle).\n\n"
  87   "This datablock defines properties shared by all Vehicle types, but should "
  88   "not be instantiated directly. Instead, set the desired properties in the "
  89   "FlyingVehicleData, HoverVehicleData or WheeledVehicleData datablock.\n"
  90
  91   "@section VehicleData_damage Damage\n\n"
  92
  93   "The VehicleData class extends the basic energy/damage functionality provided "
  94   "by ShapeBaseData to include damage from collisions, as well as particle "
  95   "emitters activated automatically when damage levels reach user specified "
  96   "thresholds.\n\n"
  97
  98   "The example below shows how to setup a Vehicle to:\n"
  99   "<ul>\n"
 100   "  <li>take damage when colliding with another object\n"
 101   "  <li>emit gray smoke particles from two locations on the Vehicle when damaged above 50%</li>\n"
 102   "  <li>emit black smoke particles from two locations on the Vehicle when damaged above 85%</li>\n"
 103   "  <li>emit bubbles when any active damage emitter point is underwater</li>\n"
 104   "</ul>\n\n"
 105
 106   "@tsexample\n"
 107   "// damage from collisions\n"
 108   "collDamageMultiplier = 0.05;\n"
 109   "collDamageThresholdVel = 15;\n\n"
 110
 111   "// damage levels\n"
 112   "damageLevelTolerance[0] = 0.5;\n"
 113   "damageEmitter[0] = GraySmokeEmitter;     // emitter used when damage is >= 50%\n"
 114   "damageLevelTolerance[1] = 0.85;\n"
 115   "damageEmitter[1] = BlackSmokeEmitter;    // emitter used when damage is >= 85%\n"
 116   "damageEmitter[2] = DamageBubbleEmitter;  // emitter used instead of damageEmitter[0:1]\n"
 117   "                                         // when offset point is underwater\n"
 118   "// emit offsets (used for all active damage level emitters)\n"
 119   "damageEmitterOffset[0] = \"0.5 3 1\";\n"
 120   "damageEmitterOffset[1] = \"-0.5 3 1\";\n"
 121   "numDmgEmitterAreas = 2;\n"
 122   "@endtsexample\n"
 123
 124   "@ingroup Vehicles\n"
 125);
 126
 127//----------------------------------------------------------------------------
 128
 129VehicleData::VehicleData()
 130{
 131   shadowEnable = true;
 132   shadowSize = 256;
 133   shadowProjectionDistance = 14.0f;
 134
 135
 136   body.friction = 0;
 137   body.restitution = 1;
 138
 139   minImpactSpeed = 25;
 140   softImpactSpeed = 25;
 141   hardImpactSpeed = 50;
 142   minRollSpeed = 0;
 143   maxSteeringAngle = M_PI_F/4.0f; // 45 deg.
 144
 145   cameraRoll = true;
 146   cameraLag = 0;
 147   cameraDecay = 0;
 148   cameraOffset = 0;
 149
 150   minDrag = 0;
 151   maxDrag = 0;
 152   integration = 1;
 153   collisionTol = 0.1f;
 154   contactTol = 0.1f;
 155   massCenter.set(0,0,0);
 156   massBox.set(0,0,0);
 157
 158   drag = 0.7f;
 159   density = 4;
 160
 161   jetForce = 500;
 162   jetEnergyDrain =  0.8f;
 163   minJetEnergy = 1;
 164
 165   steeringReturn = 0.0f;
 166   steeringReturnSpeedScale = 0.01f;
 167   powerSteering = false;
 168
 169   for (S32 i = 0; i < Body::MaxSounds; i++)
 170      body.sound[i] = 0;
 171
 172   dustEmitter = NULL;
 173   dustID = 0;
 174   triggerDustHeight = 3.0;
 175   dustHeight = 1.0;
 176
 177   dMemset( damageEmitterList, 0, sizeof( damageEmitterList ) );
 178   dMemset( damageEmitterOffset, 0, sizeof( damageEmitterOffset ) );
 179   dMemset( damageEmitterIDList, 0, sizeof( damageEmitterIDList ) );
 180   dMemset( damageLevelTolerance, 0, sizeof( damageLevelTolerance ) );
 181   dMemset( splashEmitterList, 0, sizeof( splashEmitterList ) );
 182   dMemset( splashEmitterIDList, 0, sizeof( splashEmitterIDList ) );
 183
 184   numDmgEmitterAreas = 0;
 185
 186   splashFreqMod = 300.0;
 187   splashVelEpsilon = 0.50;
 188   exitSplashSoundVel = 2.0;
 189   softSplashSoundVel = 1.0;
 190   medSplashSoundVel = 2.0;
 191   hardSplashSoundVel = 3.0;
 192
 193   dMemset(waterSound, 0, sizeof(waterSound));
 194
 195   collDamageThresholdVel = 20;
 196   collDamageMultiplier = 0.05f;
 197   enablePhysicsRep = true;
 198}
 199
 200
 201//----------------------------------------------------------------------------
 202
 203bool VehicleData::preload(bool server, String &errorStr)
 204{
 205   if (!Parent::preload(server, errorStr))
 206      return false;
 207
 208   // Vehicle objects must define a collision detail
 209   if (!collisionDetails.size() || collisionDetails[0] == -1)
 210   {
 211      Con::errorf("VehicleData::preload failed: Vehicle models must define a collision-1 detail");
 212      errorStr = String::ToString("VehicleData: Couldn't load shape \"%s\"", mShapeName);
 213      return false;
 214   }
 215
 216   // Resolve objects transmitted from server
 217   if (!server) {
 218      for (S32 i = 0; i < Body::MaxSounds; i++)
 219         if (body.sound[i])
 220            Sim::findObject(SimObjectId((uintptr_t)body.sound[i]),body.sound[i]);
 221   }
 222
 223   if( !dustEmitter && dustID != 0 )
 224   {
 225      if( !Sim::findObject( dustID, dustEmitter ) )
 226      {
 227         Con::errorf( ConsoleLogEntry::General, "VehicleData::preload Invalid packet, bad datablockId(dustEmitter): 0x%x", dustID );
 228      }
 229   }
 230
 231   U32 i;
 232   for( i=0; i<VC_NUM_DAMAGE_EMITTERS; i++ )
 233   {
 234      if( !damageEmitterList[i] && damageEmitterIDList[i] != 0 )
 235      {
 236         if( !Sim::findObject( damageEmitterIDList[i], damageEmitterList[i] ) )
 237         {
 238            Con::errorf( ConsoleLogEntry::General, "VehicleData::preload Invalid packet, bad datablockId(damageEmitter): 0x%x", damageEmitterIDList[i] );
 239         }
 240      }
 241   }
 242
 243   for( i=0; i<VC_NUM_SPLASH_EMITTERS; i++ )
 244   {
 245      if( !splashEmitterList[i] && splashEmitterIDList[i] != 0 )
 246      {
 247         if( !Sim::findObject( splashEmitterIDList[i], splashEmitterList[i] ) )
 248         {
 249            Con::errorf( ConsoleLogEntry::General, "VehicleData::preload Invalid packet, bad datablockId(splashEmitter): 0x%x", splashEmitterIDList[i] );
 250         }
 251      }
 252   }
 253
 254   return true;
 255}
 256
 257
 258//----------------------------------------------------------------------------
 259
 260void VehicleData::packData(BitStream* stream)
 261{
 262   S32 i;
 263   Parent::packData(stream);
 264
 265   stream->write(body.restitution);
 266   stream->write(body.friction);
 267   for (i = 0; i < Body::MaxSounds; i++)
 268      if (stream->writeFlag(body.sound[i]))
 269         stream->writeRangedU32(mPacked ? SimObjectId((uintptr_t)body.sound[i]):
 270                                body.sound[i]->getId(),DataBlockObjectIdFirst,
 271                                DataBlockObjectIdLast);
 272
 273   stream->write(minImpactSpeed);
 274   stream->write(softImpactSpeed);
 275   stream->write(hardImpactSpeed);
 276   stream->write(minRollSpeed);
 277   stream->write(maxSteeringAngle);
 278
 279   stream->write(maxDrag);
 280   stream->write(minDrag);
 281   stream->write(integration);
 282   stream->write(collisionTol);
 283   stream->write(contactTol);
 284   mathWrite(*stream,massCenter);
 285   mathWrite(*stream,massBox);
 286
 287   stream->write(jetForce);
 288   stream->write(jetEnergyDrain);
 289   stream->write(minJetEnergy);
 290
 291   stream->write(steeringReturn);
 292   stream->write(steeringReturnSpeedScale);
 293   stream->writeFlag(powerSteering);
 294
 295   stream->writeFlag(cameraRoll);
 296   stream->write(cameraLag);
 297   stream->write(cameraDecay);
 298   stream->write(cameraOffset);
 299
 300   stream->write( triggerDustHeight );
 301   stream->write( dustHeight );
 302
 303   stream->write( numDmgEmitterAreas );
 304
 305   stream->write(exitSplashSoundVel);
 306   stream->write(softSplashSoundVel);
 307   stream->write(medSplashSoundVel);
 308   stream->write(hardSplashSoundVel);
 309   stream->write(enablePhysicsRep);
 310
 311   // write the water sound profiles
 312   for(i = 0; i < MaxSounds; i++)
 313      if(stream->writeFlag(waterSound[i]))
 314         stream->writeRangedU32(waterSound[i]->getId(), DataBlockObjectIdFirst,  DataBlockObjectIdLast);
 315
 316   if (stream->writeFlag( dustEmitter ))
 317   {
 318      stream->writeRangedU32( dustEmitter->getId(), DataBlockObjectIdFirst,  DataBlockObjectIdLast );
 319   }
 320
 321   for (i = 0; i < VC_NUM_DAMAGE_EMITTERS; i++)
 322   {
 323      if( stream->writeFlag( damageEmitterList[i] != NULL ) )
 324      {
 325         stream->writeRangedU32( damageEmitterList[i]->getId(), DataBlockObjectIdFirst,  DataBlockObjectIdLast );
 326      }
 327   }
 328
 329   for (i = 0; i < VC_NUM_SPLASH_EMITTERS; i++)
 330   {
 331      if( stream->writeFlag( splashEmitterList[i] != NULL ) )
 332      {
 333         stream->writeRangedU32( splashEmitterList[i]->getId(), DataBlockObjectIdFirst,  DataBlockObjectIdLast );
 334      }
 335   }
 336
 337   for (S32 j = 0;  j < VC_NUM_DAMAGE_EMITTER_AREAS; j++)
 338   {
 339      stream->write( damageEmitterOffset[j].x );
 340      stream->write( damageEmitterOffset[j].y );
 341      stream->write( damageEmitterOffset[j].z );
 342   }
 343
 344   for (S32 k = 0; k < VC_NUM_DAMAGE_LEVELS; k++)
 345   {
 346      stream->write( damageLevelTolerance[k] );
 347   }
 348
 349   stream->write(splashFreqMod);
 350   stream->write(splashVelEpsilon);
 351
 352   stream->write(collDamageThresholdVel);
 353   stream->write(collDamageMultiplier);
 354}
 355
 356void VehicleData::unpackData(BitStream* stream)
 357{
 358   Parent::unpackData(stream);
 359
 360   stream->read(&body.restitution);
 361   stream->read(&body.friction);
 362   S32 i;
 363   for (i = 0; i < Body::MaxSounds; i++) {
 364      body.sound[i] = NULL;
 365      if (stream->readFlag())
 366         body.sound[i] = (SFXProfile*)(uintptr_t)stream->readRangedU32(DataBlockObjectIdFirst,
 367                                                              DataBlockObjectIdLast);
 368   }
 369
 370   stream->read(&minImpactSpeed);
 371   stream->read(&softImpactSpeed);
 372   stream->read(&hardImpactSpeed);
 373   stream->read(&minRollSpeed);
 374   stream->read(&maxSteeringAngle);
 375
 376   stream->read(&maxDrag);
 377   stream->read(&minDrag);
 378   stream->read(&integration);
 379   stream->read(&collisionTol);
 380   stream->read(&contactTol);
 381   mathRead(*stream,&massCenter);
 382   mathRead(*stream,&massBox);
 383
 384   stream->read(&jetForce);
 385   stream->read(&jetEnergyDrain);
 386   stream->read(&minJetEnergy);
 387
 388   stream->read(&steeringReturn);
 389   stream->read(&steeringReturnSpeedScale);
 390   powerSteering = stream->readFlag();
 391
 392   cameraRoll = stream->readFlag();
 393   stream->read(&cameraLag);
 394   stream->read(&cameraDecay);
 395   stream->read(&cameraOffset);
 396
 397   stream->read( &triggerDustHeight );
 398   stream->read( &dustHeight );
 399
 400   stream->read( &numDmgEmitterAreas );
 401
 402   stream->read(&exitSplashSoundVel);
 403   stream->read(&softSplashSoundVel);
 404   stream->read(&medSplashSoundVel);
 405   stream->read(&hardSplashSoundVel);
 406   stream->read(&enablePhysicsRep);
 407
 408   // write the water sound profiles
 409   for(i = 0; i < MaxSounds; i++)
 410      if(stream->readFlag())
 411      {
 412         U32 id = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
 413         waterSound[i] = dynamic_cast<SFXProfile*>( Sim::findObject(id) );
 414      }
 415
 416   if( stream->readFlag() )
 417   {
 418      dustID = (S32) stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
 419   }
 420
 421   for (i = 0; i < VC_NUM_DAMAGE_EMITTERS; i++)
 422   {
 423      if( stream->readFlag() )
 424      {
 425         damageEmitterIDList[i] = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast );
 426      }
 427   }
 428
 429   for (i = 0; i < VC_NUM_SPLASH_EMITTERS; i++)
 430   {
 431      if( stream->readFlag() )
 432      {
 433         splashEmitterIDList[i] = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast );
 434      }
 435   }
 436
 437   for( S32 j=0; j<VC_NUM_DAMAGE_EMITTER_AREAS; j++ )
 438   {
 439      stream->read( &damageEmitterOffset[j].x );
 440      stream->read( &damageEmitterOffset[j].y );
 441      stream->read( &damageEmitterOffset[j].z );
 442   }
 443
 444   for( S32 k=0; k<VC_NUM_DAMAGE_LEVELS; k++ )
 445   {
 446      stream->read( &damageLevelTolerance[k] );
 447   }
 448
 449   stream->read(&splashFreqMod);
 450   stream->read(&splashVelEpsilon);
 451
 452   stream->read(&collDamageThresholdVel);
 453   stream->read(&collDamageMultiplier);
 454}
 455
 456
 457//----------------------------------------------------------------------------
 458
 459void VehicleData::initPersistFields()
 460{
 461   addGroup("Physics");
 462   addField("enablePhysicsRep", TypeBool, Offset(enablePhysicsRep, VehicleData),
 463      "@brief Creates a representation of the object in the physics plugin.\n");
 464   endGroup("Physics");
 465
 466   addField( "jetForce", TypeF32, Offset(jetForce, VehicleData),
 467      "@brief Additional force applied to the vehicle when it is jetting.\n\n"
 468      "For WheeledVehicles, the force is applied in the forward direction. For "
 469      "FlyingVehicles, the force is applied in the thrust direction." );
 470   addField( "jetEnergyDrain", TypeF32, Offset(jetEnergyDrain, VehicleData),
 471      "@brief Energy amount to drain for each tick the vehicle is jetting.\n\n"
 472      "Once the vehicle's energy level reaches 0, it will no longer be able to jet." );
 473   addField( "minJetEnergy", TypeF32, Offset(minJetEnergy, VehicleData),
 474      "Minimum vehicle energy level to begin jetting." );
 475
 476   addField( "steeringReturn", TypeF32, Offset(steeringReturn, VehicleData),
 477      "Rate at which the vehicle's steering returns to forwards when it is moving." );
 478   addField( "steeringReturnSpeedScale", TypeF32, Offset(steeringReturnSpeedScale, VehicleData),
 479      "Amount of effect the vehicle's speed has on its rate of steering return." );
 480   addField( "powerSteering", TypeBool, Offset(powerSteering, VehicleData),
 481      "If true, steering does not auto-centre while the vehicle is being steered by its driver." );
 482
 483   addField( "massCenter", TypePoint3F, Offset(massCenter, VehicleData),
 484      "Defines the vehicle's center of mass (offset from the origin of the model)." );
 485   addField( "massBox", TypePoint3F, Offset(massBox, VehicleData),
 486      "@brief Define the box used to estimate the vehicle's moment of inertia.\n\n"
 487      "Currently only used by WheeledVehicle; other vehicle types use a "
 488      "unit sphere to compute inertia." );
 489   addField( "bodyRestitution", TypeF32, Offset(body.restitution, VehicleData),
 490      "Collision 'bounciness'.\nNormally in the range 0 (not bouncy at all) to "
 491      "1 (100% bounciness)." );
 492   addField( "bodyFriction", TypeF32, Offset(body.friction, VehicleData),
 493      "Collision friction coefficient.\nHow well this object will slide against "
 494      "objects it collides with." );
 495   addField( "softImpactSound", TYPEID< SFXProfile >(), Offset(body.sound[Body::SoftImpactSound], VehicleData),
 496      "@brief Sound to play on a 'soft' impact.\n\n"
 497      "This sound is played if the impact speed is < hardImpactSpeed and >= "
 498      "softImpactSpeed.\n\n"
 499      "@see softImpactSpeed" );
 500   addField( "hardImpactSound", TYPEID< SFXProfile >(), Offset(body.sound[Body::HardImpactSound], VehicleData),
 501      "@brief Sound to play on a 'hard' impact.\n\n"
 502      "This sound is played if the impact speed >= hardImpactSpeed.\n\n"
 503      "@see hardImpactSpeed" );
 504
 505   addField( "minImpactSpeed", TypeF32, Offset(minImpactSpeed, VehicleData),
 506      "Minimum collision speed for the onImpact callback to be invoked." );
 507   addField( "softImpactSpeed", TypeF32, Offset(softImpactSpeed, VehicleData),
 508      "Minimum collision speed for the softImpactSound to be played." );
 509   addField( "hardImpactSpeed", TypeF32, Offset(hardImpactSpeed, VehicleData),
 510      "Minimum collision speed for the hardImpactSound to be played." );
 511   addField( "minRollSpeed", TypeF32, Offset(minRollSpeed, VehicleData),
 512      "Unused" );
 513   addField( "maxSteeringAngle", TypeF32, Offset(maxSteeringAngle, VehicleData),
 514      "Maximum yaw (horizontal) and pitch (vertical) steering angle in radians." );
 515
 516   addField( "maxDrag", TypeF32, Offset(maxDrag, VehicleData),
 517      "Maximum drag coefficient.\nCurrently unused." );
 518   addField( "minDrag", TypeF32, Offset(minDrag, VehicleData),
 519      "Minimum drag coefficient.\nCurrently only used by FlyingVehicle." );
 520   addField( "integration", TypeS32, Offset(integration, VehicleData),
 521      "Number of integration steps per tick.\nIncrease this to improve simulation "
 522      "stability (also increases simulation processing time)." );
 523   addField( "collisionTol", TypeF32, Offset(collisionTol, VehicleData),
 524      "Minimum distance between objects for them to be considered as colliding." );
 525   addField( "contactTol", TypeF32, Offset(contactTol, VehicleData),
 526      "Maximum relative velocity between objects for collisions to be resolved "
 527      "as contacts.\nVelocities greater than this are handled as collisions." );
 528
 529   addField( "cameraRoll", TypeBool, Offset(cameraRoll, VehicleData),
 530      "If true, the camera will roll with the vehicle. If false, the camera will "
 531      "always have the positive Z axis as up." );
 532   addField( "cameraLag", TypeF32, Offset(cameraLag, VehicleData),
 533      "@brief How much the camera lags behind the vehicle depending on vehicle speed.\n\n"
 534      "Increasing this value will make the camera fall further behind the vehicle "
 535      "as it accelerates away.\n\n@see cameraDecay." );
 536   addField("cameraDecay",  TypeF32, Offset(cameraDecay, VehicleData),
 537      "How quickly the camera moves back towards the vehicle when stopped.\n\n"
 538      "@see cameraLag." );
 539   addField("cameraOffset", TypeF32, Offset(cameraOffset, VehicleData),
 540      "Vertical (Z axis) height of the camera above the vehicle." );
 541
 542   addField( "dustEmitter", TYPEID< ParticleEmitterData >(), Offset(dustEmitter, VehicleData),
 543      "Dust particle emitter.\n\n@see triggerDustHeight\n\n@see dustHeight");
 544   addField( "triggerDustHeight", TypeF32, Offset(triggerDustHeight, VehicleData),
 545      "@brief Maximum height above surface to emit dust particles.\n\n"
 546      "If the vehicle is less than triggerDustHeight above a static surface "
 547      "with a material that has 'showDust' set to true, the vehicle will emit "
 548      "particles from the dustEmitter." );
 549   addField( "dustHeight", TypeF32, Offset(dustHeight, VehicleData),
 550      "Height above ground at which to emit particles from the dustEmitter." );
 551
 552   addField( "damageEmitter", TYPEID< ParticleEmitterData >(), Offset(damageEmitterList, VehicleData), VC_NUM_DAMAGE_EMITTERS,
 553      "@brief Array of particle emitters used to generate damage (dust, smoke etc) "
 554      "effects.\n\n"
 555      "Currently, the first two emitters (indices 0 and 1) are used when the damage "
 556      "level exceeds the associated damageLevelTolerance. The 3rd emitter is used "
 557      "when the emitter point is underwater.\n\n"
 558      "@see damageEmitterOffset" );
 559   addField( "damageEmitterOffset", TypePoint3F, Offset(damageEmitterOffset, VehicleData), VC_NUM_DAMAGE_EMITTER_AREAS,
 560      "@brief Object space \"x y z\" offsets used to emit particles for the "
 561      "active damageEmitter.\n\n"
 562      "@tsexample\n"
 563      "// damage levels\n"
 564      "damageLevelTolerance[0] = 0.5;\n"
 565      "damageEmitter[0] = SmokeEmitter;\n"
 566      "// emit offsets (used for all active damage level emitters)\n"
 567      "damageEmitterOffset[0] = \"0.5 3 1\";\n"
 568      "damageEmitterOffset[1] = \"-0.5 3 1\";\n"
 569      "numDmgEmitterAreas = 2;\n"
 570      "@endtsexample\n" );
 571   addField( "damageLevelTolerance", TypeF32, Offset(damageLevelTolerance, VehicleData), VC_NUM_DAMAGE_LEVELS,
 572      "@brief Damage levels (as a percentage of maxDamage) above which to begin "
 573      "emitting particles from the associated damageEmitter.\n\n"
 574      "Levels should be in order of increasing damage.\n\n"
 575      "@see damageEmitterOffset" );
 576   addField( "numDmgEmitterAreas", TypeF32, Offset(numDmgEmitterAreas, VehicleData),
 577      "Number of damageEmitterOffset values to use for each damageEmitter.\n\n"
 578      "@see damageEmitterOffset" );
 579
 580   addField( "splashEmitter", TYPEID< ParticleEmitterData >(), Offset(splashEmitterList, VehicleData), VC_NUM_SPLASH_EMITTERS,
 581      "Array of particle emitters used to generate splash effects." );
 582   addField( "splashFreqMod",  TypeF32, Offset(splashFreqMod, VehicleData),
 583      "@brief Number of splash particles to generate based on vehicle speed.\n\n"
 584      "This value is multiplied by the current speed to determine how many "
 585      "particles to generate each frame." );
 586   addField( "splashVelEpsilon", TypeF32, Offset(splashVelEpsilon, VehicleData),
 587      "Minimum speed when moving through water to generate splash particles." );
 588
 589   addField( "exitSplashSoundVelocity", TypeF32, Offset(exitSplashSoundVel, VehicleData),
 590      "Minimum velocity when leaving the water for the exitingWater sound to play." );
 591   addField( "softSplashSoundVelocity", TypeF32, Offset(softSplashSoundVel, VehicleData),
 592      "Minimum velocity when entering the water for the imapactWaterEasy sound "
 593      "to play.\n\n@see impactWaterEasy" );
 594   addField( "mediumSplashSoundVelocity", TypeF32, Offset(medSplashSoundVel, VehicleData),
 595      "Minimum velocity when entering the water for the imapactWaterMedium sound "
 596      "to play.\n\n@see impactWaterMedium" );
 597   addField( "hardSplashSoundVelocity", TypeF32, Offset(hardSplashSoundVel, VehicleData),
 598      "Minimum velocity when entering the water for the imapactWaterHard sound "
 599      "to play.\n\n@see impactWaterHard" );
 600   addField( "exitingWater", TYPEID< SFXProfile >(), Offset(waterSound[ExitWater], VehicleData),
 601      "Sound to play when exiting the water." );
 602   addField( "impactWaterEasy", TYPEID< SFXProfile >(), Offset(waterSound[ImpactSoft], VehicleData),
 603      "Sound to play when entering the water with speed >= softSplashSoundVelocity "
 604      "and < mediumSplashSoundVelocity." );
 605   addField( "impactWaterMedium", TYPEID< SFXProfile >(), Offset(waterSound[ImpactMedium], VehicleData),
 606      "Sound to play when entering the water with speed >= mediumSplashSoundVelocity "
 607      "and < hardSplashSoundVelocity." );
 608   addField( "impactWaterHard", TYPEID< SFXProfile >(), Offset(waterSound[ImpactHard], VehicleData),
 609      "Sound to play when entering the water with speed >= hardSplashSoundVelocity." );
 610   addField( "waterWakeSound", TYPEID< SFXProfile >(), Offset(waterSound[Wake], VehicleData),
 611      "Looping sound to play while moving through the water." );
 612
 613   addField( "collDamageThresholdVel", TypeF32, Offset(collDamageThresholdVel, VehicleData),
 614      "Minimum collision velocity to cause damage to this vehicle.\nCurrently unused." );
 615   addField( "collDamageMultiplier", TypeF32, Offset(collDamageMultiplier, VehicleData),
 616      "@brief Damage to this vehicle after a collision (multiplied by collision "
 617      "velocity).\n\nCurrently unused." );
 618
 619   Parent::initPersistFields();
 620}
 621
 622
 623//----------------------------------------------------------------------------
 624//----------------------------------------------------------------------------
 625
 626//----------------------------------------------------------------------------
 627IMPLEMENT_CONOBJECT(Vehicle);
 628
 629ConsoleDocClass( Vehicle,
 630   "@brief Base functionality shared by all Vehicles (FlyingVehicle, HoverVehicle, "
 631   "WheeledVehicle).\n\n"
 632   "This object implements functionality shared by all Vehicle types, but should "
 633   "not be instantiated directly. Create a FlyingVehicle, HoverVehicle, or "
 634   "WheeledVehicle instead.\n"
 635   "@note The model used for any Vehicle must include a collision mesh at detail "
 636   "size -1.\n"
 637   "@ingroup Vehicles\n"
 638);
 639
 640Vehicle::Vehicle()
 641{
 642   mDataBlock = 0;
 643   mTypeMask |= VehicleObjectType | DynamicShapeObjectType;
 644
 645   mDelta.pos = Point3F(0,0,0);
 646   mDelta.posVec = Point3F(0,0,0);
 647   mDelta.warpTicks = mDelta.warpCount = 0;
 648   mDelta.dt = 1;
 649   mDelta.move = NullMove;
 650   mPredictionCount = 0;
 651   mDelta.cameraOffset.set(0,0,0);
 652   mDelta.cameraVec.set(0,0,0);
 653   mDelta.cameraRot.set(0,0,0);
 654   mDelta.cameraRotVec.set(0,0,0);
 655
 656   mRigid.linPosition.set(0, 0, 0);
 657   mRigid.linVelocity.set(0, 0, 0);
 658   mRigid.angPosition.identity();
 659   mRigid.angVelocity.set(0, 0, 0);
 660   mRigid.linMomentum.set(0, 0, 0);
 661   mRigid.angMomentum.set(0, 0, 0);
 662   mContacts.clear();
 663
 664   mSteering.set(0,0);
 665   mThrottle = 0;
 666   mJetting = false;
 667
 668   mCameraOffset.set(0,0,0);
 669
 670   dMemset( mDamageEmitterList, 0, sizeof( mDamageEmitterList ) );
 671
 672   mDisableMove = false;
 673   restCount = 0;
 674
 675   inLiquid = false;
 676   mWakeSound = NULL;
 677
 678   mWorkingQueryBox.minExtents.set(-1e9f, -1e9f, -1e9f);
 679   mWorkingQueryBox.maxExtents.set(-1e9f, -1e9f, -1e9f);
 680   mWorkingQueryBoxCountDown = sWorkingQueryBoxStaleThreshold;
 681
 682   mPhysicsRep = NULL;
 683}
 684
 685U32 Vehicle::getCollisionMask()
 686{
 687   AssertFatal(false, "Vehicle::getCollisionMask is pure virtual!");
 688   return 0;
 689}
 690
 691//----------------------------------------------------------------------------
 692
 693bool Vehicle::onAdd()
 694{
 695   if (!Parent::onAdd())
 696      return false;
 697
 698   mWorkingQueryBox.minExtents.set(-1e9f, -1e9f, -1e9f);
 699   mWorkingQueryBox.maxExtents.set(-1e9f, -1e9f, -1e9f);
 700
 701   // When loading from a mission script, the base SceneObject's transform
 702   // will have been set and needs to be transfered to the rigid body.
 703   mRigid.setTransform(mObjToWorld);
 704
 705   // Initialize interpolation vars.
 706   mDelta.rot[1] = mDelta.rot[0] = mRigid.angPosition;
 707   mDelta.pos = mRigid.linPosition;
 708   mDelta.posVec = Point3F(0,0,0);
 709
 710   // Create Emitters on the client
 711   if( isClientObject() )
 712   {
 713      U32 j;
 714      for( j=0; j<VehicleData::VC_NUM_DAMAGE_EMITTERS; j++ )
 715      {
 716         if( mDataBlock->damageEmitterList[j] )
 717         {
 718            mDamageEmitterList[j] = new ParticleEmitter;
 719            mDamageEmitterList[j]->onNewDataBlock( mDataBlock->damageEmitterList[j], false );
 720            if( !mDamageEmitterList[j]->registerObject() )
 721            {
 722               Con::warnf( ConsoleLogEntry::General, "Could not register damage emitter for class: %s", mDataBlock->getName() );
 723               delete mDamageEmitterList[j];
 724               mDamageEmitterList[j] = NULL;
 725            }
 726
 727         }
 728      }
 729   }
 730
 731   // Create a new convex.
 732   AssertFatal(mDataBlock->collisionDetails[0] != -1, "Error, a vehicle must have a collision-1 detail!");
 733   mConvex.mObject    = this;
 734   mConvex.pShapeBase = this;
 735   mConvex.hullId     = 0;
 736   mConvex.box        = mObjBox;
 737   mConvex.box.minExtents.convolve(mObjScale);
 738   mConvex.box.maxExtents.convolve(mObjScale);
 739   mConvex.findNodeTransform();
 740
 741   _createPhysics();
 742
 743   return true;
 744}
 745
 746void Vehicle::onRemove()
 747{
 748   SAFE_DELETE(mPhysicsRep);
 749
 750   U32 i=0;
 751
 752   for( i=0; i<VehicleData::VC_NUM_DAMAGE_EMITTERS; i++ )
 753   {
 754      if( mDamageEmitterList[i] )
 755      {
 756         mDamageEmitterList[i]->deleteWhenEmpty();
 757         mDamageEmitterList[i] = NULL;
 758      }
 759   }
 760
 761   mWorkingQueryBox.minExtents.set(-1e9f, -1e9f, -1e9f);
 762   mWorkingQueryBox.maxExtents.set(-1e9f, -1e9f, -1e9f);
 763
 764   Parent::onRemove();
 765}
 766
 767
 768//----------------------------------------------------------------------------
 769
 770void Vehicle::processTick(const Move* move)
 771{
 772   PROFILE_SCOPE( Vehicle_ProcessTick );
 773
 774   ShapeBase::processTick(move);
 775   if ( isMounted() )
 776      return;
 777
 778   // Warp to catch up to server
 779   if (mDelta.warpCount < mDelta.warpTicks)
 780   {
 781      mDelta.warpCount++;
 782
 783      // Set new pos.
 784      mObjToWorld.getColumn(3,&mDelta.pos);
 785      mDelta.pos += mDelta.warpOffset;
 786      mDelta.rot[0] = mDelta.rot[1];
 787      mDelta.rot[1].interpolate(mDelta.warpRot[0],mDelta.warpRot[1],F32(mDelta.warpCount)/<a href="/coding/class/classrigidshape/#classrigidshape_1a4a7d0e60ba7dff122314e6ac9034e3bb">mDelta</a>.warpTicks);
 788      setPosition(mDelta.pos,mDelta.rot[1]);
 789
 790      // Pos backstepping
 791      mDelta.posVec.x = -mDelta.warpOffset.x;
 792      mDelta.posVec.y = -mDelta.warpOffset.y;
 793      mDelta.posVec.z = -mDelta.warpOffset.z;
 794   }
 795   else 
 796   {
 797      if (!move) 
 798      {
 799         if (isGhost()) 
 800         {
 801            // If we haven't run out of prediction time,
 802            // predict using the last known move.
 803            if (mPredictionCount-- <= 0)
 804               return;
 805            move = &mDelta.move;
 806         }
 807         else
 808            move = &NullMove;
 809      }
 810
 811      // Process input move
 812      updateMove(move);
 813
 814      // Save current rigid state interpolation
 815      mDelta.posVec = mRigid.linPosition;
 816      mDelta.rot[0] = mRigid.angPosition;
 817
 818      // Update the physics based on the integration rate
 819      S32 count = mDataBlock->integration;
 820      --mWorkingQueryBoxCountDown;
 821
 822      if (!mDisableMove)
 823         updateWorkingCollisionSet(getCollisionMask());
 824      for (U32 i = 0; i < count; i++)
 825         updatePos(TickSec / count);
 826
 827      // Wrap up interpolation info
 828      mDelta.pos     = mRigid.linPosition;
 829      mDelta.posVec -= mRigid.linPosition;
 830      mDelta.rot[1]  = mRigid.angPosition;
 831
 832      // Update container database
 833      setPosition(mRigid.linPosition, mRigid.angPosition);
 834      setMaskBits(PositionMask);
 835      updateContainer();
 836
 837      //TODO: Only update when position has actually changed
 838      //no need to check if mDataBlock->enablePhysicsRep is false as mPhysicsRep will be NULL if it is
 839      if (mPhysicsRep)
 840         mPhysicsRep->moveKinematicTo(getTransform());
 841   }
 842}
 843
 844void Vehicle::advanceTime(F32 dt)
 845{
 846   PROFILE_SCOPE( Vehicle_AdvanceTime );
 847
 848   Parent::advanceTime(dt);
 849
 850   updateLiftoffDust( dt );
 851   updateDamageSmoke( dt );
 852}
 853
 854
 855//----------------------------------------------------------------------------
 856
 857bool Vehicle::onNewDataBlock(GameBaseData* dptr,bool reload)
 858{
 859   mDataBlock = dynamic_cast<VehicleData*>(dptr);
 860   if (!mDataBlock || !Parent::onNewDataBlock(dptr, reload))
 861      return false;
 862
 863   // Update Rigid Info
 864   mRigid.mass = mDataBlock->mass;
 865   mRigid.oneOverMass = 1 / mRigid.mass;
 866   mRigid.friction = mDataBlock->body.friction;
 867   mRigid.restitution = mDataBlock->body.restitution;
 868   mRigid.setCenterOfMass(mDataBlock->massCenter);
 869
 870   // Ignores massBox, just set sphere for now. Derived objects
 871   // can set what they want.
 872   mRigid.setObjectInertia();
 873
 874   if (isGhost()) 
 875   {
 876      // Create the sound ahead of time.  This reduces runtime
 877      // costs and makes the system easier to understand.
 878      SFX_DELETE( mWakeSound );
 879
 880      if ( mDataBlock->waterSound[VehicleData::Wake] )
 881         mWakeSound = SFX->createSource( mDataBlock->waterSound[VehicleData::Wake], &getTransform() );
 882   }
 883
 884   return true;
 885}
 886
 887
 888//----------------------------------------------------------------------------
 889
 890void Vehicle::getCameraParameters(F32 *min,F32* max,Point3F* off,MatrixF* rot)
 891{
 892   *min = mDataBlock->cameraMinDist;
 893   *max = mDataBlock->cameraMaxDist;
 894
 895   off->set(0,0,mDataBlock->cameraOffset);
 896   rot->identity();
 897}
 898
 899
 900//----------------------------------------------------------------------------
 901
 902void Vehicle::getCameraTransform(F32* pos, MatrixF* mat)
 903{
 904   // Returns camera to world space transform
 905   // Handles first person / third person camera position
 906   if (isServerObject() && mShapeInstance)
 907      mShapeInstance->animateNodeSubtrees(true);
 908
 909   if (*pos == 0) 
 910   {
 911      getRenderEyeTransform(mat);
 912   }
 913   else
 914   {
 915      // Get the shape's camera parameters.
 916      F32 min, max;
 917      MatrixF rot;
 918      Point3F offset;
 919      getCameraParameters(&min, &max, &offset, &rot);
 920
 921      // Start with the current eye position
 922      MatrixF eye;
 923      getRenderEyeTransform(&eye);
 924
 925      // Build a transform that points along the eye axis
 926      // but where the Z axis is always up.
 927      if (mDataBlock->cameraRoll)
 928         mat->mul(eye, rot);
 929      else
 930      {
 931         MatrixF cam(1);
 932         VectorF x, y, z(0, 0, 1);
 933         eye.getColumn(1, &y);
 934         mCross(y, z, &x);
 935         x.normalize();
 936         mCross(x, y, &z);
 937         z.normalize();
 938         cam.setColumn(0, x);
 939         cam.setColumn(1, y);
 940         cam.setColumn(2, z);
 941         mat->mul(cam, rot);
 942      }
 943
 944      // Camera is positioned straight back along the eye's -Y axis.
 945      // A ray is cast to make sure the camera doesn't go through
 946      // anything solid.
 947      VectorF vp, vec;
 948      vp.x = vp.z = 0;
 949      vp.y = -(max - min) * *pos;
 950      eye.mulV(vp, &vec);
 951
 952      // Use the camera node as the starting position if it exists.
 953      Point3F osp, sp;
 954      if (mDataBlock->cameraNode != -1)
 955      {
 956         mShapeInstance->mNodeTransforms[mDataBlock->cameraNode].getColumn(3, &osp);
 957         getRenderTransform().mulP(osp, &sp);
 958      }
 959      else
 960         eye.getColumn(3, &sp);
 961
 962      // Make sure we don't hit ourself...
 963      disableCollision();
 964      if (isMounted())
 965         getObjectMount()->disableCollision();
 966
 967      // Cast the ray into the container database to see if we're going
 968      // to hit anything.
 969      RayInfo collision;
 970      Point3F ep = sp + vec + offset + mCameraOffset;
 971      if (mContainer->castRay(sp, ep,
 972         ~(WaterObjectType | GameBaseObjectType | DefaultObjectType | sTriggerMask),
 973         &collision) == true) {
 974
 975         // Shift the collision point back a little to try and
 976         // avoid clipping against the front camera plane.
 977         F32 t = collision.t - (-mDot(vec, collision.normal) / vec.len()) * 0.1;
 978         if (t > 0.0f)
 979            ep = sp + offset + mCameraOffset + (vec * t);
 980         else
 981            eye.getColumn(3, &ep);
 982      }
 983      mat->setColumn(3, ep);
 984
 985      // Re-enable our collision.
 986      if (isMounted())
 987         getObjectMount()->enableCollision();
 988      enableCollision();
 989   }
 990
 991   // Apply Camera FX.
 992   mat->mul( gCamFXMgr.getTrans() );
 993}
 994
 995//----------------------------------------------------------------------------
 996
 997void Vehicle::updateMove(const Move* move)
 998{
 999   PROFILE_SCOPE( Vehicle_UpdateMove );
1000
1001   mDelta.move = *move;
1002
1003   // Image Triggers
1004   if (mDamageState == Enabled) {
1005      setImageTriggerState(0,move->trigger[0]);
1006      setImageTriggerState(1,move->trigger[1]);
1007   }
1008
1009   // Throttle
1010   if(!mDisableMove)
1011      mThrottle = move->y;
1012
1013   // Steering
1014   if (move != &NullMove) {
1015      F32 y = move->yaw;
1016      mSteering.x = mClampF(mSteering.x + y,-mDataBlock->maxSteeringAngle,
1017                            mDataBlock->maxSteeringAngle);
1018      F32 p = move->pitch;
1019      mSteering.y = mClampF(mSteering.y + p,-mDataBlock->maxSteeringAngle,
1020                            mDataBlock->maxSteeringAngle);
1021   }
1022   else {
1023      mSteering.x = 0;
1024      mSteering.y = 0;
1025   }
1026
1027   // Steering return
1028   if(mDataBlock->steeringReturn > 0.0f &&
1029      (!mDataBlock->powerSteering || (move->yaw == 0.0f && move->pitch == 0.0f)))
1030   {
1031      Point2F returnAmount(mSteering.x * mDataBlock->steeringReturn * TickSec,
1032                           mSteering.y * mDataBlock->steeringReturn * TickSec);
1033      if(mDataBlock->steeringReturnSpeedScale > 0.0f)
1034      {
1035         Point3F vel;
1036         mWorldToObj.mulV(getVelocity(), &vel);
1037         returnAmount += Point2F(mSteering.x * vel.y * mDataBlock->steeringReturnSpeedScale * TickSec,
1038                                 mSteering.y * vel.y * mDataBlock->steeringReturnSpeedScale * TickSec);
1039      }
1040      mSteering -= returnAmount;
1041   }
1042
1043   // Jetting flag
1044   if (move->trigger[3]) {
1045      if (!mJetting && getEnergyLevel() >= mDataBlock->minJetEnergy)
1046         mJetting = true;
1047      if (mJetting) {
1048         F32 newEnergy = getEnergyLevel() - mDataBlock->jetEnergyDrain;
1049         if (newEnergy < 0) {
1050            newEnergy = 0;
1051            mJetting = false;
1052         }
1053         setEnergyLevel(newEnergy);
1054      }
1055   }
1056   else
1057      mJetting = false;
1058}
1059
1060//----------------------------------------------------------------------------
1061/** Update the physics
1062*/
1063
1064void Vehicle::updatePos(F32 dt)
1065{
1066   PROFILE_SCOPE( Vehicle_UpdatePos );
1067
1068   Point3F origVelocity = mRigid.linVelocity;
1069
1070   // Update internal forces acting on the body.
1071   mRigid.clearForces();
1072   updateForces(dt);
1073
1074   // Update collision information based on our current pos.
1075   bool collided = false;
1076   if (!mRigid.atRest && !mDisableMove)
1077   {
1078      collided = updateCollision(dt);
1079
1080      // Now that all the forces have been processed, lets
1081      // see if we're at rest.  Basically, if the kinetic energy of
1082      // the vehicles is less than some percentage of the energy added
1083      // by gravity for a short period, we're considered at rest.
1084      // This should really be part of the rigid class...
1085      if (mCollisionList.getCount()) 
1086      {
1087         F32 k = mRigid.getKineticEnergy();
1088         F32 G = mNetGravity * dt;
1089         F32 Kg = 0.5 * mRigid.mass * G * G;
1090         if (k < sRestTol * Kg && ++restCount > sRestCount)
1091            mRigid.setAtRest();
1092      }
1093      else
1094         restCount = 0;
1095   }
1096
1097   // Integrate forward
1098   if (!mRigid.atRest && !mDisableMove)
1099      mRigid.integrate(dt);
1100
1101   // Deal with client and server scripting, sounds, etc.
1102   if (isServerObject()) {
1103
1104      // Check triggers and other objects that we normally don't
1105      // collide with.  This function must be called before notifyCollision
1106      // as it will queue collision.
1107      checkTriggers();
1108
1109      // Invoke the onCollision notify callback for all the objects
1110      // we've just hit.
1111      notifyCollision();
1112
1113      // Server side impact script callback
1114      if (collided) {
1115         VectorF collVec = mRigid.linVelocity - origVelocity;
1116         F32 collSpeed = collVec.len();
1117         if (collSpeed > mDataBlock->minImpactSpeed)
1118            onImpact(collVec);
1119      }
1120
1121      // Water script callbacks
1122      if (!inLiquid && mWaterCoverage != 0.0f) {
1123         mDataBlock->onEnterLiquid_callback( this, mWaterCoverage, mLiquidType.c_str() );
1124         inLiquid = true;
1125      }
1126      else if (inLiquid && mWaterCoverage == 0.0f) {
1127         mDataBlock->onLeaveLiquid_callback( this, mLiquidType.c_str() );
1128         inLiquid = false;
1129      }
1130
1131   }
1132   else {
1133
1134      // Play impact sounds on the client.
1135      if (collided) {
1136         F32 collSpeed = (mRigid.linVelocity - origVelocity).len();
1137         S32 impactSound = -1;
1138         if (collSpeed >= mDataBlock->hardImpactSpeed)
1139            impactSound = VehicleData::Body::HardImpactSound;
1140         else
1141            if (collSpeed >= mDataBlock->softImpactSpeed)
1142               impactSound = VehicleData::Body::SoftImpactSound;
1143
1144         if (impactSound != -1 && mDataBlock->body.sound[impactSound] != NULL)
1145            SFX->playOnce( mDataBlock->body.sound[impactSound], &getTransform() );
1146      }
1147
1148      // Water volume sounds
1149      F32 vSpeed = getVelocity().len();
1150      if (!inLiquid && mWaterCoverage >= 0.8f) {
1151         if (vSpeed >= mDataBlock->hardSplashSoundVel)
1152            SFX->playOnce( mDataBlock->waterSound[VehicleData::ImpactHard], &getTransform() );
1153         else
1154            if (vSpeed >= mDataBlock->medSplashSoundVel)
1155               SFX->playOnce( mDataBlock->waterSound[VehicleData::ImpactMedium], &getTransform() );
1156         else
1157            if (vSpeed >= mDataBlock->softSplashSoundVel)
1158               SFX->playOnce( mDataBlock->waterSound[VehicleData::ImpactSoft], &getTransform() );
1159         inLiquid = true;
1160      }
1161      else
1162         if(inLiquid && mWaterCoverage < 0.8f) {
1163            if (vSpeed >= mDataBlock->exitSplashSoundVel)
1164               SFX->playOnce( mDataBlock->waterSound[VehicleData::ExitWater], &getTransform() );
1165         inLiquid = false;
1166      }
1167   }
1168}
1169
1170
1171//----------------------------------------------------------------------------
1172
1173void Vehicle::updateForces(F32 /*dt*/)
1174{
1175   // Nothing here.
1176}
1177
1178
1179/** The callback used in by the checkTriggers() method.
1180   The checkTriggers method uses a container search which will
1181   invoke this callback on each obj that matches.
1182*/
1183void Vehicle::findCallback(SceneObject* obj,void *key)
1184{
1185   Vehicle* vehicle = reinterpret_cast<Vehicle*>(key);
1186   U32 objectMask = obj->getTypeMask();
1187
1188   // Check: triggers, corpses and items, basically the same things
1189   // that the player class checks for
1190   if (objectMask & TriggerObjectType) {
1191      Trigger* pTrigger = static_cast<Trigger*>(obj);
1192      pTrigger->potentialEnterObject(vehicle);
1193   }
1194   else if (objectMask & CorpseObjectType) {
1195      ShapeBase* col = static_cast<ShapeBase*>(obj);
1196      vehicle->queueCollision(col,vehicle->getVelocity() - col->getVelocity());
1197   }
1198   else if (objectMask & ItemObjectType) {
1199      Item* item = static_cast<Item*>(obj);
1200      if (vehicle != item->getCollisionObject())
1201         vehicle->queueCollision(item,vehicle->getVelocity() - item->getVelocity());
1202   }
1203}
1204
1205
1206//----------------------------------------------------------------------------
1207
1208void Vehicle::writePacketData(GameConnection *connection, BitStream *stream)
1209{
1210   Parent::writePacketData(connection, stream);
1211   mathWrite(*stream, mSteering);
1212
1213   mathWrite(*stream, mRigid.linPosition);
1214   mathWrite(*stream, mRigid.angPosition);
1215   mathWrite(*stream, mRigid.linMomentum);
1216   mathWrite(*stream, mRigid.angMomentum);
1217   stream->writeFlag(mRigid.atRest);
1218   stream->writeFlag(mContacts.getCount() == 0);
1219
1220   stream->writeFlag(mDisableMove);
1221   stream->setCompressionPoint(mRigid.linPosition);
1222}
1223
1224void Vehicle::readPacketData(GameConnection *connection, BitStream *stream)
1225{
1226   Parent::readPacketData(connection, stream);
1227   mathRead(*stream, &mSteering);
1228
1229   mathRead(*stream, &mRigid.linPosition);
1230   mathRead(*stream, &mRigid.angPosition);
1231   mathRead(*stream, &mRigid.linMomentum);
1232   mathRead(*stream, &mRigid.angMomentum);
1233   mRigid.atRest = stream->readFlag();
1234   if (stream->readFlag())
1235      mContacts.clear();
1236   mRigid.updateInertialTensor();
1237   mRigid.updateVelocity();
1238   mRigid.updateCenterOfMass();
1239
1240   mDisableMove = stream->readFlag();
1241   stream->setCompressionPoint(mRigid.linPosition);
1242}
1243
1244
1245//----------------------------------------------------------------------------
1246
1247U32 Vehicle::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
1248{
1249   U32 retMask = Parent::packUpdate(con, mask, stream);
1250
1251   stream->writeFlag(mJetting);
1252
1253   // The rest of the data is part of the control object packet update.
1254   // If we're controlled by this client, we don't need to send it.
1255   if (stream->writeFlag(getControllingClient() == con && !(mask & InitialUpdateMask)))
1256      return retMask;
1257
1258   F32 yaw = (mSteering.x + mDataBlock->maxSteeringAngle) / (2 * mDataBlock->maxSteeringAngle);
1259   F32 pitch = (mSteering.y + mDataBlock->maxSteeringAngle) / (2 * mDataBlock->maxSteeringAngle);
1260   stream->writeFloat(yaw,9);
1261   stream->writeFloat(pitch,9);
1262   mDelta.move.pack(stream);
1263
1264   if (stream->writeFlag(mask & PositionMask))
1265   {
1266      stream->writeCompressedPoint(mRigid.linPosition);
1267      mathWrite(*stream, mRigid.angPosition);
1268      mathWrite(*stream, mRigid.linMomentum);
1269      mathWrite(*stream, mRigid.angMomentum);
1270      stream->writeFlag(mRigid.atRest);
1271   }
1272
1273   
1274   stream->writeFloat(mClampF(getEnergyValue(), 0.f, 1.f), 8);
1275
1276   return retMask;
1277}
1278
1279void Vehicle::unpackUpdate(NetConnection *con, BitStream *stream)
1280{
1281   Parent::unpackUpdate(con,stream);
1282
1283   mJetting = stream->readFlag();
1284
1285   if (stream->readFlag())
1286      return;
1287
1288   F32 yaw = stream->readFloat(9);
1289   F32 pitch = stream->readFloat(9);
1290   mSteering.x = (2 * yaw * mDataBlock->maxSteeringAngle) - mDataBlock->maxSteeringAngle;
1291   mSteering.y = (2 * pitch * mDataBlock->maxSteeringAngle) - mDataBlock->maxSteeringAngle;
1292   mDelta.move.unpack(stream);
1293
1294   if (stream->readFlag()) 
1295   {
1296      mPredictionCount = sMaxPredictionTicks;
1297      F32 speed = mRigid.linVelocity.len();
1298      mDelta.warpRot[0] = mRigid.angPosition;
1299
1300      // Read in new position and momentum values
1301      stream->readCompressedPoint(&mRigid.linPosition);
1302      mathRead(*stream, &mRigid.angPosition);
1303      mathRead(*stream, &mRigid.linMomentum);
1304      mathRead(*stream, &mRigid.angMomentum);
1305      mRigid.atRest = stream->readFlag();
1306      mRigid.updateVelocity();
1307
1308      if (isProperlyAdded()) 
1309      {
1310         // Determine number of ticks to warp based on the average
1311         // of the client and server velocities.
1312         Point3F cp = mDelta.pos + mDelta.posVec * mDelta.dt;
1313         mDelta.warpOffset = mRigid.linPosition - cp;
1314
1315         // Calc the distance covered in one tick as the average of
1316         // the old speed and the new speed from the server.
1317         F32 dt,as = (speed + mRigid.linVelocity.len()) * 0.5 * TickSec;
1318
1319         // Cal how many ticks it will take to cover the warp offset.
1320         // If it's less than what's left in the current tick, we'll just
1321         // warp in the remaining time.
1322         if (!as || (dt = mDelta.warpOffset.len() / as) > sMaxWarpTicks)
1323            dt = mDelta.dt + sMaxWarpTicks;
1324         else
1325            dt = (dt <= mDelta.dt)? mDelta.dt : mCeil(dt - mDelta.dt) + mDelta.dt;
1326
1327         // Adjust current frame interpolation
1328         if (mDelta.dt) {
1329            mDelta.pos = cp + (mDelta.warpOffset * (mDelta.dt / dt));
1330            mDelta.posVec = (cp - mDelta.pos) / mDelta.dt;
1331            QuatF cr;
1332            cr.interpolate(mDelta.rot[1],mDelta.rot[0],mDelta.dt);
1333            mDelta.rot[1].interpolate(cr,mRigid.angPosition,mDelta.dt / dt);
1334            mDelta.rot[0].extrapolate(mDelta.rot[1],cr,mDelta.dt);
1335         }
1336
1337         // Calculated multi-tick warp
1338         mDelta.warpCount = 0;
1339         mDelta.warpTicks = (S32)(mFloor(dt));
1340         if (mDelta.warpTicks) 
1341         {
1342            mDelta.warpOffset = mRigid.linPosition - mDelta.pos;
1343            mDelta.warpOffset /= (F32)mDelta.warpTicks;
1344            mDelta.warpRot[0] = mDelta.rot[1];
1345            mDelta.warpRot[1] = mRigid.angPosition;
1346         }
1347      }
1348      else 
1349      {
1350         // Set the vehicle to the server position
1351         mDelta.dt  = 0;
1352         mDelta.pos = mRigid.linPosition;
1353         mDelta.posVec.set(0,0,0);
1354         mDelta.rot[1] = mDelta.rot[0] = mRigid.angPosition;
1355         mDelta.warpCount = mDelta.warpTicks = 0;
1356         setPosition(mRigid.linPosition, mRigid.angPosition);
1357      }
1358      mRigid.updateCenterOfMass();
1359   }
1360
1361   setEnergyLevel(stream->readFloat(8) * mDataBlock->maxEnergy);
1362}
1363
1364
1365//----------------------------------------------------------------------------
1366
1367void Vehicle::consoleInit()
1368{
1369   Con::addVariable("$vehicle::workingQueryBoxStaleThreshold",TypeS32,&sWorkingQueryBoxStaleThreshold, 
1370      "@brief The maximum number of ticks that go by before the mWorkingQueryBox is considered stale and needs updating.\n\n"
1371      "Other factors can cause the collision working query box to become invalidated, such as the vehicle moving far "
1372      "enough outside of this cached box.  The smaller this number, the more times the working list of triangles that are "
1373      "considered for collision is refreshed.  This has the greatest impact with colliding with high triangle count meshes.\n\n"
1374      "@note Set to -1 to disable any time-based forced check.\n\n"
1375      "@ingroup GameObjects\n");
1376
1377   Con::addVariable("$vehicle::workingQueryBoxSizeMultiplier",TypeF32,&sWorkingQueryBoxSizeMultiplier, 
1378      "@brief How much larger the mWorkingQueryBox should be made when updating the working collision list.\n\n"
1379      "The larger this number the less often the working list will be updated due to motion, but any non-static shape that "
1380      "moves into the query box will not be noticed.\n\n"
1381      "@ingroup GameObjects\n");
1382}
1383
1384void Vehicle::initPersistFields()
1385{
1386   addField( "disableMove", TypeBool, Offset(mDisableMove, Vehicle),
1387      "When this flag is set, the vehicle will ignore throttle changes." );
1388
1389   Parent::initPersistFields();
1390}
1391
1392
1393void Vehicle::mountObject(SceneObject *obj, S32 node, const MatrixF &xfm )
1394{
1395   Parent::mountObject( obj, node, xfm );
1396
1397   // Clear objects off the working list that are from objects mounted to us.
1398   //  (This applies mostly to players...)
1399   for ( CollisionWorkingList* itr = mConvex.getWorkingList().wLink.mNext; 
1400         itr != &mConvex.getWorkingList(); 
1401         itr = itr->wLink.mNext) 
1402   {
1403      if (itr->mConvex->getObject() == obj) 
1404      {
1405         CollisionWorkingList* cl = itr;
1406         itr = itr->wLink.mPrev;
1407         cl->free();
1408      }
1409   }
1410}
1411
1412//----------------------------------------------------------------------------
1413
1414void Vehicle::updateLiftoffDust( F32 dt )
1415{
1416   Point3F offset( 0.0, 0.0, mDataBlock->dustHeight );
1417   emitDust( mDustEmitterList[ 0 ], mDataBlock->triggerDustHeight, offset,
1418             ( U32 )( dt * 1000 ) );
1419}
1420
1421//----------------------------------------------------------------------------
1422
1423void Vehicle::updateDamageSmoke( F32 dt )
1424{
1425
1426   for( S32 j=<a href="/coding/class/structvehicledata/#structvehicledata_1a49944dd75022f526ef95014376c1c807a55f0cfbcfaa01fc5be4682b4aadbf0a6">VehicleData::VC_NUM_DAMAGE_LEVELS</a>-1; j>=0; j-- )
1427   {
1428      F32 damagePercent = mDamage / mDataBlock->maxDamage;
1429      if( damagePercent >= mDataBlock->damageLevelTolerance[j] )
1430      {
1431         for( S32 i=0; i<mDataBlock->numDmgEmitterAreas; i++ )
1432         {
1433            MatrixF trans = getTransform();
1434            Point3F offset = mDataBlock->damageEmitterOffset[i];
1435            trans.mulP( offset );
1436            Point3F emitterPoint = offset;
1437
1438            if( pointInWater(offset ) )
1439            {
1440               U32 emitterOffset = VehicleData::VC_BUBBLE_EMITTER;
1441               if( mDamageEmitterList[emitterOffset] )
1442               {
1443                  mDamageEmitterList[emitterOffset]->emitParticles( emitterPoint, emitterPoint, Point3F( 0.0, 0.0, 1.0 ), getVelocity(), (U32)( dt * 1000 ) );
1444               }
1445            }
1446            else
1447            {
1448               if( mDamageEmitterList[j] )
1449               {
1450                  mDamageEmitterList[j]->emitParticles( emitterPoint, emitterPoint, Point3F( 0.0, 0.0, 1.0 ), getVelocity(), (U32)(dt * 1000));
1451               }
1452            }
1453         }
1454         break;
1455      }
1456   }
1457
1458}
1459
1460
1461//--------------------------------------------------------------------------
1462void Vehicle::updateFroth( F32 dt )
1463{
1464   // update bubbles
1465   Point3F moveDir = getVelocity();
1466
1467   Point3F contactPoint;
1468   if( !collidingWithWater( contactPoint ) )
1469   {
1470      if ( mWakeSound )
1471         mWakeSound->stop();
1472      return;
1473   }
1474
1475   F32 speed = moveDir.len();
1476   if( speed < mDataBlock->splashVelEpsilon ) speed = 0.0;
1477
1478   U32 emitRate = (U32)(speed * mDataBlock->splashFreqMod * dt);
1479
1480   U32 i;
1481
1482   if ( mWakeSound )
1483   {
1484      if ( !mWakeSound->isPlaying() )
1485         mWakeSound->play();
1486
1487      mWakeSound->setTransform( getTransform() );
1488      mWakeSound->setVelocity( getVelocity() );
1489   }
1490
1491   for( i=0; i<VehicleData::VC_NUM_SPLASH_EMITTERS; i++ )
1492   {
1493      if( mSplashEmitterList[i] )
1494      {
1495         mSplashEmitterList[i]->emitParticles( contactPoint, contactPoint, Point3F( 0.0, 0.0, 1.0 ),
1496                                               moveDir, emitRate );
1497      }
1498   }
1499
1500}
1501
1502void Vehicle::prepBatchRender( SceneRenderState *state, S32 mountedImageIndex )
1503{
1504   Parent::prepBatchRender( state, mountedImageIndex );
1505
1506   if ( !gShowBoundingBox )
1507      return;
1508
1509   if ( mountedImageIndex != -1 )
1510   {
1511      ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
1512      ri->renderDelegate.bind( this, &Vehicle::_renderMuzzleVector );
1513      ri->objectIndex = mountedImageIndex;
1514      ri->type = RenderPassManager::RIT_Editor;
1515      state->getRenderPass()->addInst( ri );
1516      return;
1517   }
1518
1519   ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
1520   ri->renderDelegate.bind( this, &Vehicle::_renderMassAndContacts );
1521   ri->type = RenderPassManager::RIT_Editor;
1522   state->getRenderPass()->addInst( ri );
1523}
1524
1525void Vehicle::_renderMassAndContacts( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat )
1526{
1527   GFXStateBlockDesc desc;
1528   desc.setBlend(false, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha);
1529   desc.setZReadWrite(false,true);
1530   desc.fillMode = GFXFillWireframe;
1531
1532   // Render the mass center.   
1533   GFX->getDrawUtil()->drawCube(desc, Point3F(0.1f,0.1f,0.1f),mDataBlock->massCenter, ColorI(255, 255, 255), &mRenderObjToWorld);
1534
1535   // Now render all the contact points.
1536   for (S32 i = 0; i < mCollisionList.getCount(); i++)
1537   {
1538      const Collision& collision = mCollisionList[i];
1539      GFX->getDrawUtil()->drawCube(desc, Point3F(0.05f,0.05f,0.05f),collision.point, ColorI(0, 0, 255));
1540   }
1541
1542   // Finally render the normals as one big batch.
1543   PrimBuild::begin(GFXLineList, mCollisionList.getCount() * 2);
1544   for (S32 i = 0; i < mCollisionList.getCount(); i++)
1545   {
1546      const Collision& collision = mCollisionList[i];
1547      PrimBuild::color3f(1, 1, 1);
1548      PrimBuild::vertex3fv(collision.point);
1549      PrimBuild::vertex3fv(collision.point + collision.normal * 0.05f);
1550   }
1551   PrimBuild::end();
1552
1553   // Build and render the collision polylist which is returned
1554   // in the server's world space.
1555   ClippedPolyList polyList;
1556   polyList.mPlaneList.setSize(6);
1557   polyList.mPlaneList[0].set(getWorldBox().minExtents,VectorF(-1,0,0));
1558   polyList.mPlaneList[1].set(getWorldBox().minExtents,VectorF(0,-1,0));
1559   polyList.mPlaneList[2].set(getWorldBox().minExtents,VectorF(0,0,-1));
1560   polyList.mPlaneList[3].set(getWorldBox().maxExtents,VectorF(1,0,0));
1561   polyList.mPlaneList[4].set(getWorldBox().maxExtents,VectorF(0,1,0));
1562   polyList.mPlaneList[5].set(getWorldBox().maxExtents,VectorF(0,0,1));
1563   Box3F dummyBox;
1564   SphereF dummySphere;
1565   buildPolyList(PLC_Collision, &polyList, dummyBox, dummySphere);
1566   //polyList.render();
1567}
1568
1569void Vehicle::_renderMuzzleVector( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat )
1570{
1571   const U32 index = ri->objectIndex;
1572
1573   AssertFatal( index > 0 && index < MaxMountedImages, "Vehicle::_renderMuzzleVector() - Bad object index!" );
1574   AssertFatal( mMountedImageList[index].dataBlock, "Vehicle::_renderMuzzleVector() - Bad object index!" );
1575
1576   Point3F muzzlePoint, muzzleVector, endpoint;
1577   getMuzzlePoint(index, &muzzlePoint);
1578   getMuzzleVector(index, &muzzleVector);
1579   endpoint = muzzlePoint + muzzleVector * 250;
1580
1581   if (mSolidSB.isNull())
1582   {
1583      GFXStateBlockDesc desc;
1584      desc.setBlend(false, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha);
1585      desc.setZReadWrite(false);
1586      mSolidSB = GFX->createStateBlock(desc);
1587   }
1588
1589   GFX->setStateBlock(mSolidSB);
1590
1591   PrimBuild::begin(GFXLineList, 2);
1592
1593   PrimBuild::color4f(0, 1, 0, 1);
1594   PrimBuild::vertex3fv(muzzlePoint);
1595   PrimBuild::vertex3fv(endpoint);
1596
1597   PrimBuild::end();
1598}
1599