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 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 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 <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_CALLBACK(VehicleData , onEnterLiquid , void , (Vehicle *obj, F32 coverage, const char *type) , (obj, coverage, type) , "Called when the vehicle enters <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">liquid.\n</a>" "@param obj the <a href="/coding/class/classvehicle/">Vehicle</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">object\n</a>" "@param coverage percentage of the vehicle's bounding box covered by the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">liquid\n</a>" "@param type type of liquid the vehicle has <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">entered\n</a>" )
IMPLEMENT_CALLBACK(VehicleData , onLeaveLiquid , void , (Vehicle *obj, const char *type) , (obj, type) , "Called when the vehicle leaves <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">liquid.\n</a>" "@param obj the <a href="/coding/class/classvehicle/">Vehicle</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">object\n</a>" "@param type type of liquid the vehicle has <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">left\n</a>" )

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 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 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 <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_CALLBACK(VehicleData , onEnterLiquid , void , (Vehicle *obj, F32 coverage, const char *type) , (obj, coverage, type) , "Called when the vehicle enters <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">liquid.\n</a>" "@param obj the <a href="/coding/class/classvehicle/">Vehicle</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">object\n</a>" "@param coverage percentage of the vehicle's bounding box covered by the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">liquid\n</a>" "@param type type of liquid the vehicle has <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">entered\n</a>" )

IMPLEMENT_CALLBACK(VehicleData , onLeaveLiquid , void , (Vehicle *obj, const char *type) , (obj, type) , "Called when the vehicle leaves <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">liquid.\n</a>" "@param obj the <a href="/coding/class/classvehicle/">Vehicle</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">object\n</a>" "@param type type of liquid the vehicle has <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">left\n</a>" )

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
  70const F32 sVehicleGravity = -20;
  71
  72// Physics and collision constants
  73static F32 sRestTol = 0.5;             // % of gravity energy to be at rest
  74static S32 sRestCount = 10;            // Consecutive ticks before comming to rest
  75
  76} // namespace {}
  77
  78// Trigger objects that are not normally collided with.
  79static U32 sTriggerMask = ItemObjectType     |
  80                          TriggerObjectType  |
  81                          CorpseObjectType;
  82
  83IMPLEMENT_CONOBJECT(VehicleData);
  84
  85ConsoleDocClass( VehicleData,
  86   "@brief Base properties shared by all Vehicles (FlyingVehicle, HoverVehicle, "
  87   "WheeledVehicle).\n\n"
  88   "This datablock defines properties shared by all Vehicle types, but should "
  89   "not be instantiated directly. Instead, set the desired properties in the "
  90   "FlyingVehicleData, HoverVehicleData or WheeledVehicleData datablock.\n"
  91
  92   "@section VehicleData_damage Damage\n\n"
  93
  94   "The VehicleData class extends the basic energy/damage functionality provided "
  95   "by ShapeBaseData to include damage from collisions, as well as particle "
  96   "emitters activated automatically when damage levels reach user specified "
  97   "thresholds.\n\n"
  98
  99   "The example below shows how to setup a Vehicle to:\n"
 100   "<ul>\n"
 101   "  <li>take damage when colliding with another object\n"
 102   "  <li>emit gray smoke particles from two locations on the Vehicle when damaged above 50%</li>\n"
 103   "  <li>emit black smoke particles from two locations on the Vehicle when damaged above 85%</li>\n"
 104   "  <li>emit bubbles when any active damage emitter point is underwater</li>\n"
 105   "</ul>\n\n"
 106
 107   "@tsexample\n"
 108   "// damage from collisions\n"
 109   "collDamageMultiplier = 0.05;\n"
 110   "collDamageThresholdVel = 15;\n\n"
 111
 112   "// damage levels\n"
 113   "damageLevelTolerance[0] = 0.5;\n"
 114   "damageEmitter[0] = GraySmokeEmitter;     // emitter used when damage is >= 50%\n"
 115   "damageLevelTolerance[1] = 0.85;\n"
 116   "damageEmitter[1] = BlackSmokeEmitter;    // emitter used when damage is >= 85%\n"
 117   "damageEmitter[2] = DamageBubbleEmitter;  // emitter used instead of damageEmitter[0:1]\n"
 118   "                                         // when offset point is underwater\n"
 119   "// emit offsets (used for all active damage level emitters)\n"
 120   "damageEmitterOffset[0] = \"0.5 3 1\";\n"
 121   "damageEmitterOffset[1] = \"-0.5 3 1\";\n"
 122   "numDmgEmitterAreas = 2;\n"
 123   "@endtsexample\n"
 124
 125   "@ingroup Vehicles\n"
 126);
 127
 128IMPLEMENT_CALLBACK( VehicleData, onEnterLiquid, void, ( Vehicle* obj, F32 coverage, const char* type ), ( obj, coverage, type ),
 129   "Called when the vehicle enters liquid.\n"
 130   "@param obj the Vehicle object\n"
 131   "@param coverage percentage of the vehicle's bounding box covered by the liquid\n"
 132   "@param type type of liquid the vehicle has entered\n" );
 133
 134IMPLEMENT_CALLBACK( VehicleData, onLeaveLiquid, void, ( Vehicle* obj, const char* type ), ( obj, type ),
 135   "Called when the vehicle leaves liquid.\n"
 136   "@param obj the Vehicle object\n"
 137   "@param type type of liquid the vehicle has left\n" );
 138
 139//----------------------------------------------------------------------------
 140
 141VehicleData::VehicleData()
 142{
 143   shadowEnable = true;
 144   shadowSize = 256;
 145   shadowProjectionDistance = 14.0f;
 146
 147
 148   body.friction = 0;
 149   body.restitution = 1;
 150
 151   minImpactSpeed = 25;
 152   softImpactSpeed = 25;
 153   hardImpactSpeed = 50;
 154   minRollSpeed = 0;
 155   maxSteeringAngle = M_PI_F/4.0f; // 45 deg.
 156
 157   cameraRoll = true;
 158   cameraLag = 0;
 159   cameraDecay = 0;
 160   cameraOffset = 0;
 161
 162   minDrag = 0;
 163   maxDrag = 0;
 164   integration = 1;
 165   collisionTol = 0.1f;
 166   contactTol = 0.1f;
 167   massCenter.set(0,0,0);
 168   massBox.set(0,0,0);
 169
 170   drag = 0.7f;
 171   density = 4;
 172
 173   jetForce = 500;
 174   jetEnergyDrain =  0.8f;
 175   minJetEnergy = 1;
 176
 177   steeringReturn = 0.0f;
 178   steeringReturnSpeedScale = 0.01f;
 179   powerSteering = false;
 180
 181   for (S32 i = 0; i < Body::MaxSounds; i++)
 182      body.sound[i] = 0;
 183
 184   dustEmitter = NULL;
 185   dustID = 0;
 186   triggerDustHeight = 3.0;
 187   dustHeight = 1.0;
 188
 189   dMemset( damageEmitterList, 0, sizeof( damageEmitterList ) );
 190   dMemset( damageEmitterOffset, 0, sizeof( damageEmitterOffset ) );
 191   dMemset( damageEmitterIDList, 0, sizeof( damageEmitterIDList ) );
 192   dMemset( damageLevelTolerance, 0, sizeof( damageLevelTolerance ) );
 193   dMemset( splashEmitterList, 0, sizeof( splashEmitterList ) );
 194   dMemset( splashEmitterIDList, 0, sizeof( splashEmitterIDList ) );
 195
 196   numDmgEmitterAreas = 0;
 197
 198   splashFreqMod = 300.0;
 199   splashVelEpsilon = 0.50;
 200   exitSplashSoundVel = 2.0;
 201   softSplashSoundVel = 1.0;
 202   medSplashSoundVel = 2.0;
 203   hardSplashSoundVel = 3.0;
 204
 205   dMemset(waterSound, 0, sizeof(waterSound));
 206
 207   collDamageThresholdVel = 20;
 208   collDamageMultiplier = 0.05f;
 209   enablePhysicsRep = true;
 210}
 211
 212
 213//----------------------------------------------------------------------------
 214
 215bool VehicleData::preload(bool server, String &errorStr)
 216{
 217   if (!Parent::preload(server, errorStr))
 218      return false;
 219
 220   // Vehicle objects must define a collision detail
 221   if (!collisionDetails.size() || collisionDetails[0] == -1)
 222   {
 223      Con::errorf("VehicleData::preload failed: Vehicle models must define a collision-1 detail");
 224      errorStr = String::ToString("VehicleData: Couldn't load shape \"%s\"",shapeName);
 225      return false;
 226   }
 227
 228   // Resolve objects transmitted from server
 229   if (!server) {
 230      for (S32 i = 0; i < Body::MaxSounds; i++)
 231         if (body.sound[i])
 232            Sim::findObject(SimObjectId((uintptr_t)body.sound[i]),body.sound[i]);
 233   }
 234
 235   if( !dustEmitter && dustID != 0 )
 236   {
 237      if( !Sim::findObject( dustID, dustEmitter ) )
 238      {
 239         Con::errorf( ConsoleLogEntry::General, "VehicleData::preload Invalid packet, bad datablockId(dustEmitter): 0x%x", dustID );
 240      }
 241   }
 242
 243   U32 i;
 244   for( i=0; i<VC_NUM_DAMAGE_EMITTERS; i++ )
 245   {
 246      if( !damageEmitterList[i] && damageEmitterIDList[i] != 0 )
 247      {
 248         if( !Sim::findObject( damageEmitterIDList[i], damageEmitterList[i] ) )
 249         {
 250            Con::errorf( ConsoleLogEntry::General, "VehicleData::preload Invalid packet, bad datablockId(damageEmitter): 0x%x", damageEmitterIDList[i] );
 251         }
 252      }
 253   }
 254
 255   for( i=0; i<VC_NUM_SPLASH_EMITTERS; i++ )
 256   {
 257      if( !splashEmitterList[i] && splashEmitterIDList[i] != 0 )
 258      {
 259         if( !Sim::findObject( splashEmitterIDList[i], splashEmitterList[i] ) )
 260         {
 261            Con::errorf( ConsoleLogEntry::General, "VehicleData::preload Invalid packet, bad datablockId(splashEmitter): 0x%x", splashEmitterIDList[i] );
 262         }
 263      }
 264   }
 265
 266   return true;
 267}
 268
 269
 270//----------------------------------------------------------------------------
 271
 272void VehicleData::packData(BitStream* stream)
 273{
 274   S32 i;
 275   Parent::packData(stream);
 276
 277   stream->write(body.restitution);
 278   stream->write(body.friction);
 279   for (i = 0; i < Body::MaxSounds; i++)
 280      if (stream->writeFlag(body.sound[i]))
 281         stream->writeRangedU32(packed? SimObjectId((uintptr_t)body.sound[i]):
 282                                body.sound[i]->getId(),DataBlockObjectIdFirst,
 283                                DataBlockObjectIdLast);
 284
 285   stream->write(minImpactSpeed);
 286   stream->write(softImpactSpeed);
 287   stream->write(hardImpactSpeed);
 288   stream->write(minRollSpeed);
 289   stream->write(maxSteeringAngle);
 290
 291   stream->write(maxDrag);
 292   stream->write(minDrag);
 293   stream->write(integration);
 294   stream->write(collisionTol);
 295   stream->write(contactTol);
 296   mathWrite(*stream,massCenter);
 297   mathWrite(*stream,massBox);
 298
 299   stream->write(jetForce);
 300   stream->write(jetEnergyDrain);
 301   stream->write(minJetEnergy);
 302
 303   stream->write(steeringReturn);
 304   stream->write(steeringReturnSpeedScale);
 305   stream->writeFlag(powerSteering);
 306
 307   stream->writeFlag(cameraRoll);
 308   stream->write(cameraLag);
 309   stream->write(cameraDecay);
 310   stream->write(cameraOffset);
 311
 312   stream->write( triggerDustHeight );
 313   stream->write( dustHeight );
 314
 315   stream->write( numDmgEmitterAreas );
 316
 317   stream->write(exitSplashSoundVel);
 318   stream->write(softSplashSoundVel);
 319   stream->write(medSplashSoundVel);
 320   stream->write(hardSplashSoundVel);
 321   stream->write(enablePhysicsRep);
 322
 323   // write the water sound profiles
 324   for(i = 0; i < MaxSounds; i++)
 325      if(stream->writeFlag(waterSound[i]))
 326         stream->writeRangedU32(waterSound[i]->getId(), DataBlockObjectIdFirst,  DataBlockObjectIdLast);
 327
 328   if (stream->writeFlag( dustEmitter ))
 329   {
 330      stream->writeRangedU32( dustEmitter->getId(), DataBlockObjectIdFirst,  DataBlockObjectIdLast );
 331   }
 332
 333   for (i = 0; i < VC_NUM_DAMAGE_EMITTERS; i++)
 334   {
 335      if( stream->writeFlag( damageEmitterList[i] != NULL ) )
 336      {
 337         stream->writeRangedU32( damageEmitterList[i]->getId(), DataBlockObjectIdFirst,  DataBlockObjectIdLast );
 338      }
 339   }
 340
 341   for (i = 0; i < VC_NUM_SPLASH_EMITTERS; i++)
 342   {
 343      if( stream->writeFlag( splashEmitterList[i] != NULL ) )
 344      {
 345         stream->writeRangedU32( splashEmitterList[i]->getId(), DataBlockObjectIdFirst,  DataBlockObjectIdLast );
 346      }
 347   }
 348
 349   for (S32 j = 0;  j < VC_NUM_DAMAGE_EMITTER_AREAS; j++)
 350   {
 351      stream->write( damageEmitterOffset[j].x );
 352      stream->write( damageEmitterOffset[j].y );
 353      stream->write( damageEmitterOffset[j].z );
 354   }
 355
 356   for (S32 k = 0; k < VC_NUM_DAMAGE_LEVELS; k++)
 357   {
 358      stream->write( damageLevelTolerance[k] );
 359   }
 360
 361   stream->write(splashFreqMod);
 362   stream->write(splashVelEpsilon);
 363
 364   stream->write(collDamageThresholdVel);
 365   stream->write(collDamageMultiplier);
 366}
 367
 368void VehicleData::unpackData(BitStream* stream)
 369{
 370   Parent::unpackData(stream);
 371
 372   stream->read(&body.restitution);
 373   stream->read(&body.friction);
 374   S32 i;
 375   for (i = 0; i < Body::MaxSounds; i++) {
 376      body.sound[i] = NULL;
 377      if (stream->readFlag())
 378         body.sound[i] = (SFXProfile*)stream->readRangedU32(DataBlockObjectIdFirst,
 379                                                              DataBlockObjectIdLast);
 380   }
 381
 382   stream->read(&minImpactSpeed);
 383   stream->read(&softImpactSpeed);
 384   stream->read(&hardImpactSpeed);
 385   stream->read(&minRollSpeed);
 386   stream->read(&maxSteeringAngle);
 387
 388   stream->read(&maxDrag);
 389   stream->read(&minDrag);
 390   stream->read(&integration);
 391   stream->read(&collisionTol);
 392   stream->read(&contactTol);
 393   mathRead(*stream,&massCenter);
 394   mathRead(*stream,&massBox);
 395
 396   stream->read(&jetForce);
 397   stream->read(&jetEnergyDrain);
 398   stream->read(&minJetEnergy);
 399
 400   stream->read(&steeringReturn);
 401   stream->read(&steeringReturnSpeedScale);
 402   powerSteering = stream->readFlag();
 403
 404   cameraRoll = stream->readFlag();
 405   stream->read(&cameraLag);
 406   stream->read(&cameraDecay);
 407   stream->read(&cameraOffset);
 408
 409   stream->read( &triggerDustHeight );
 410   stream->read( &dustHeight );
 411
 412   stream->read( &numDmgEmitterAreas );
 413
 414   stream->read(&exitSplashSoundVel);
 415   stream->read(&softSplashSoundVel);
 416   stream->read(&medSplashSoundVel);
 417   stream->read(&hardSplashSoundVel);
 418   stream->read(&enablePhysicsRep);
 419
 420   // write the water sound profiles
 421   for(i = 0; i < MaxSounds; i++)
 422      if(stream->readFlag())
 423      {
 424         U32 id = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
 425         waterSound[i] = dynamic_cast<SFXProfile*>( Sim::findObject(id) );
 426      }
 427
 428   if( stream->readFlag() )
 429   {
 430      dustID = (S32) stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
 431   }
 432
 433   for (i = 0; i < VC_NUM_DAMAGE_EMITTERS; i++)
 434   {
 435      if( stream->readFlag() )
 436      {
 437         damageEmitterIDList[i] = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast );
 438      }
 439   }
 440
 441   for (i = 0; i < VC_NUM_SPLASH_EMITTERS; i++)
 442   {
 443      if( stream->readFlag() )
 444      {
 445         splashEmitterIDList[i] = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast );
 446      }
 447   }
 448
 449   for( S32 j=0; j<VC_NUM_DAMAGE_EMITTER_AREAS; j++ )
 450   {
 451      stream->read( &damageEmitterOffset[j].x );
 452      stream->read( &damageEmitterOffset[j].y );
 453      stream->read( &damageEmitterOffset[j].z );
 454   }
 455
 456   for( S32 k=0; k<VC_NUM_DAMAGE_LEVELS; k++ )
 457   {
 458      stream->read( &damageLevelTolerance[k] );
 459   }
 460
 461   stream->read(&splashFreqMod);
 462   stream->read(&splashVelEpsilon);
 463
 464   stream->read(&collDamageThresholdVel);
 465   stream->read(&collDamageMultiplier);
 466}
 467
 468
 469//----------------------------------------------------------------------------
 470
 471void VehicleData::initPersistFields()
 472{
 473   addGroup("Physics");
 474   addField("enablePhysicsRep", TypeBool, Offset(enablePhysicsRep, VehicleData),
 475      "@brief Creates a representation of the object in the physics plugin.\n");
 476   endGroup("Physics");
 477
 478   addField( "jetForce", TypeF32, Offset(jetForce, VehicleData),
 479      "@brief Additional force applied to the vehicle when it is jetting.\n\n"
 480      "For WheeledVehicles, the force is applied in the forward direction. For "
 481      "FlyingVehicles, the force is applied in the thrust direction." );
 482   addField( "jetEnergyDrain", TypeF32, Offset(jetEnergyDrain, VehicleData),
 483      "@brief Energy amount to drain for each tick the vehicle is jetting.\n\n"
 484      "Once the vehicle's energy level reaches 0, it will no longer be able to jet." );
 485   addField( "minJetEnergy", TypeF32, Offset(minJetEnergy, VehicleData),
 486      "Minimum vehicle energy level to begin jetting." );
 487
 488   addField( "steeringReturn", TypeF32, Offset(steeringReturn, VehicleData),
 489      "Rate at which the vehicle's steering returns to forwards when it is moving." );
 490   addField( "steeringReturnSpeedScale", TypeF32, Offset(steeringReturnSpeedScale, VehicleData),
 491      "Amount of effect the vehicle's speed has on its rate of steering return." );
 492   addField( "powerSteering", TypeBool, Offset(powerSteering, VehicleData),
 493      "If true, steering does not auto-centre while the vehicle is being steered by its driver." );
 494
 495   addField( "massCenter", TypePoint3F, Offset(massCenter, VehicleData),
 496      "Defines the vehicle's center of mass (offset from the origin of the model)." );
 497   addField( "massBox", TypePoint3F, Offset(massBox, VehicleData),
 498      "@brief Define the box used to estimate the vehicle's moment of inertia.\n\n"
 499      "Currently only used by WheeledVehicle; other vehicle types use a "
 500      "unit sphere to compute inertia." );
 501   addField( "bodyRestitution", TypeF32, Offset(body.restitution, VehicleData),
 502      "Collision 'bounciness'.\nNormally in the range 0 (not bouncy at all) to "
 503      "1 (100% bounciness)." );
 504   addField( "bodyFriction", TypeF32, Offset(body.friction, VehicleData),
 505      "Collision friction coefficient.\nHow well this object will slide against "
 506      "objects it collides with." );
 507   addField( "softImpactSound", TYPEID< SFXProfile >(), Offset(body.sound[Body::SoftImpactSound], VehicleData),
 508      "@brief Sound to play on a 'soft' impact.\n\n"
 509      "This sound is played if the impact speed is < hardImpactSpeed and >= "
 510      "softImpactSpeed.\n\n"
 511      "@see softImpactSpeed" );
 512   addField( "hardImpactSound", TYPEID< SFXProfile >(), Offset(body.sound[Body::HardImpactSound], VehicleData),
 513      "@brief Sound to play on a 'hard' impact.\n\n"
 514      "This sound is played if the impact speed >= hardImpactSpeed.\n\n"
 515      "@see hardImpactSpeed" );
 516
 517   addField( "minImpactSpeed", TypeF32, Offset(minImpactSpeed, VehicleData),
 518      "Minimum collision speed for the onImpact callback to be invoked." );
 519   addField( "softImpactSpeed", TypeF32, Offset(softImpactSpeed, VehicleData),
 520      "Minimum collision speed for the softImpactSound to be played." );
 521   addField( "hardImpactSpeed", TypeF32, Offset(hardImpactSpeed, VehicleData),
 522      "Minimum collision speed for the hardImpactSound to be played." );
 523   addField( "minRollSpeed", TypeF32, Offset(minRollSpeed, VehicleData),
 524      "Unused" );
 525   addField( "maxSteeringAngle", TypeF32, Offset(maxSteeringAngle, VehicleData),
 526      "Maximum yaw (horizontal) and pitch (vertical) steering angle in radians." );
 527
 528   addField( "maxDrag", TypeF32, Offset(maxDrag, VehicleData),
 529      "Maximum drag coefficient.\nCurrently unused." );
 530   addField( "minDrag", TypeF32, Offset(minDrag, VehicleData),
 531      "Minimum drag coefficient.\nCurrently only used by FlyingVehicle." );
 532   addField( "integration", TypeS32, Offset(integration, VehicleData),
 533      "Number of integration steps per tick.\nIncrease this to improve simulation "
 534      "stability (also increases simulation processing time)." );
 535   addField( "collisionTol", TypeF32, Offset(collisionTol, VehicleData),
 536      "Minimum distance between objects for them to be considered as colliding." );
 537   addField( "contactTol", TypeF32, Offset(contactTol, VehicleData),
 538      "Maximum relative velocity between objects for collisions to be resolved "
 539      "as contacts.\nVelocities greater than this are handled as collisions." );
 540
 541   addField( "cameraRoll", TypeBool, Offset(cameraRoll, VehicleData),
 542      "If true, the camera will roll with the vehicle. If false, the camera will "
 543      "always have the positive Z axis as up." );
 544   addField( "cameraLag", TypeF32, Offset(cameraLag, VehicleData),
 545      "@brief How much the camera lags behind the vehicle depending on vehicle speed.\n\n"
 546      "Increasing this value will make the camera fall further behind the vehicle "
 547      "as it accelerates away.\n\n@see cameraDecay." );
 548   addField("cameraDecay",  TypeF32, Offset(cameraDecay, VehicleData),
 549      "How quickly the camera moves back towards the vehicle when stopped.\n\n"
 550      "@see cameraLag." );
 551   addField("cameraOffset", TypeF32, Offset(cameraOffset, VehicleData),
 552      "Vertical (Z axis) height of the camera above the vehicle." );
 553
 554   addField( "dustEmitter", TYPEID< ParticleEmitterData >(), Offset(dustEmitter, VehicleData),
 555      "Dust particle emitter.\n\n@see triggerDustHeight\n\n@see dustHeight");
 556   addField( "triggerDustHeight", TypeF32, Offset(triggerDustHeight, VehicleData),
 557      "@brief Maximum height above surface to emit dust particles.\n\n"
 558      "If the vehicle is less than triggerDustHeight above a static surface "
 559      "with a material that has 'showDust' set to true, the vehicle will emit "
 560      "particles from the dustEmitter." );
 561   addField( "dustHeight", TypeF32, Offset(dustHeight, VehicleData),
 562      "Height above ground at which to emit particles from the dustEmitter." );
 563
 564   addField( "damageEmitter", TYPEID< ParticleEmitterData >(), Offset(damageEmitterList, VehicleData), VC_NUM_DAMAGE_EMITTERS,
 565      "@brief Array of particle emitters used to generate damage (dust, smoke etc) "
 566      "effects.\n\n"
 567      "Currently, the first two emitters (indices 0 and 1) are used when the damage "
 568      "level exceeds the associated damageLevelTolerance. The 3rd emitter is used "
 569      "when the emitter point is underwater.\n\n"
 570      "@see damageEmitterOffset" );
 571   addField( "damageEmitterOffset", TypePoint3F, Offset(damageEmitterOffset, VehicleData), VC_NUM_DAMAGE_EMITTER_AREAS,
 572      "@brief Object space \"x y z\" offsets used to emit particles for the "
 573      "active damageEmitter.\n\n"
 574      "@tsexample\n"
 575      "// damage levels\n"
 576      "damageLevelTolerance[0] = 0.5;\n"
 577      "damageEmitter[0] = SmokeEmitter;\n"
 578      "// emit offsets (used for all active damage level emitters)\n"
 579      "damageEmitterOffset[0] = \"0.5 3 1\";\n"
 580      "damageEmitterOffset[1] = \"-0.5 3 1\";\n"
 581      "numDmgEmitterAreas = 2;\n"
 582      "@endtsexample\n" );
 583   addField( "damageLevelTolerance", TypeF32, Offset(damageLevelTolerance, VehicleData), VC_NUM_DAMAGE_LEVELS,
 584      "@brief Damage levels (as a percentage of maxDamage) above which to begin "
 585      "emitting particles from the associated damageEmitter.\n\n"
 586      "Levels should be in order of increasing damage.\n\n"
 587      "@see damageEmitterOffset" );
 588   addField( "numDmgEmitterAreas", TypeF32, Offset(numDmgEmitterAreas, VehicleData),
 589      "Number of damageEmitterOffset values to use for each damageEmitter.\n\n"
 590      "@see damageEmitterOffset" );
 591
 592   addField( "splashEmitter", TYPEID< ParticleEmitterData >(), Offset(splashEmitterList, VehicleData), VC_NUM_SPLASH_EMITTERS,
 593      "Array of particle emitters used to generate splash effects." );
 594   addField( "splashFreqMod",  TypeF32, Offset(splashFreqMod, VehicleData),
 595      "@brief Number of splash particles to generate based on vehicle speed.\n\n"
 596      "This value is multiplied by the current speed to determine how many "
 597      "particles to generate each frame." );
 598   addField( "splashVelEpsilon", TypeF32, Offset(splashVelEpsilon, VehicleData),
 599      "Minimum speed when moving through water to generate splash particles." );
 600
 601   addField( "exitSplashSoundVelocity", TypeF32, Offset(exitSplashSoundVel, VehicleData),
 602      "Minimum velocity when leaving the water for the exitingWater sound to play." );
 603   addField( "softSplashSoundVelocity", TypeF32, Offset(softSplashSoundVel, VehicleData),
 604      "Minimum velocity when entering the water for the imapactWaterEasy sound "
 605      "to play.\n\n@see impactWaterEasy" );
 606   addField( "mediumSplashSoundVelocity", TypeF32, Offset(medSplashSoundVel, VehicleData),
 607      "Minimum velocity when entering the water for the imapactWaterMedium sound "
 608      "to play.\n\n@see impactWaterMedium" );
 609   addField( "hardSplashSoundVelocity", TypeF32, Offset(hardSplashSoundVel, VehicleData),
 610      "Minimum velocity when entering the water for the imapactWaterHard sound "
 611      "to play.\n\n@see impactWaterHard" );
 612   addField( "exitingWater", TYPEID< SFXProfile >(), Offset(waterSound[ExitWater], VehicleData),
 613      "Sound to play when exiting the water." );
 614   addField( "impactWaterEasy", TYPEID< SFXProfile >(), Offset(waterSound[ImpactSoft], VehicleData),
 615      "Sound to play when entering the water with speed >= softSplashSoundVelocity "
 616      "and < mediumSplashSoundVelocity." );
 617   addField( "impactWaterMedium", TYPEID< SFXProfile >(), Offset(waterSound[ImpactMedium], VehicleData),
 618      "Sound to play when entering the water with speed >= mediumSplashSoundVelocity "
 619      "and < hardSplashSoundVelocity." );
 620   addField( "impactWaterHard", TYPEID< SFXProfile >(), Offset(waterSound[ImpactHard], VehicleData),
 621      "Sound to play when entering the water with speed >= hardSplashSoundVelocity." );
 622   addField( "waterWakeSound", TYPEID< SFXProfile >(), Offset(waterSound[Wake], VehicleData),
 623      "Looping sound to play while moving through the water." );
 624
 625   addField( "collDamageThresholdVel", TypeF32, Offset(collDamageThresholdVel, VehicleData),
 626      "Minimum collision velocity to cause damage to this vehicle.\nCurrently unused." );
 627   addField( "collDamageMultiplier", TypeF32, Offset(collDamageMultiplier, VehicleData),
 628      "@brief Damage to this vehicle after a collision (multiplied by collision "
 629      "velocity).\n\nCurrently unused." );
 630
 631   Parent::initPersistFields();
 632}
 633
 634
 635//----------------------------------------------------------------------------
 636//----------------------------------------------------------------------------
 637
 638//----------------------------------------------------------------------------
 639IMPLEMENT_CONOBJECT(Vehicle);
 640
 641ConsoleDocClass( Vehicle,
 642   "@brief Base functionality shared by all Vehicles (FlyingVehicle, HoverVehicle, "
 643   "WheeledVehicle).\n\n"
 644   "This object implements functionality shared by all Vehicle types, but should "
 645   "not be instantiated directly. Create a FlyingVehicle, HoverVehicle, or "
 646   "WheeledVehicle instead.\n"
 647   "@note The model used for any Vehicle must include a collision mesh at detail "
 648   "size -1.\n"
 649   "@ingroup Vehicles\n"
 650);
 651
 652Vehicle::Vehicle()
 653{
 654   mDataBlock = 0;
 655   mTypeMask |= VehicleObjectType | DynamicShapeObjectType;
 656
 657   mDelta.pos = Point3F(0,0,0);
 658   mDelta.posVec = Point3F(0,0,0);
 659   mDelta.warpTicks = mDelta.warpCount = 0;
 660   mDelta.dt = 1;
 661   mDelta.move = NullMove;
 662   mPredictionCount = 0;
 663   mDelta.cameraOffset.set(0,0,0);
 664   mDelta.cameraVec.set(0,0,0);
 665   mDelta.cameraRot.set(0,0,0);
 666   mDelta.cameraRotVec.set(0,0,0);
 667
 668   mRigid.linPosition.set(0, 0, 0);
 669   mRigid.linVelocity.set(0, 0, 0);
 670   mRigid.angPosition.identity();
 671   mRigid.angVelocity.set(0, 0, 0);
 672   mRigid.linMomentum.set(0, 0, 0);
 673   mRigid.angMomentum.set(0, 0, 0);
 674   mContacts.clear();
 675
 676   mSteering.set(0,0);
 677   mThrottle = 0;
 678   mJetting = false;
 679
 680   mCameraOffset.set(0,0,0);
 681
 682   dMemset( mDustEmitterList, 0, sizeof( mDustEmitterList ) );
 683   dMemset( mDamageEmitterList, 0, sizeof( mDamageEmitterList ) );
 684   dMemset( mSplashEmitterList, 0, sizeof( mSplashEmitterList ) );
 685
 686   mDisableMove = false;
 687   restCount = 0;
 688
 689   inLiquid = false;
 690   mWakeSound = NULL;
 691
 692   mWorkingQueryBox.minExtents.set(-1e9f, -1e9f, -1e9f);
 693   mWorkingQueryBox.maxExtents.set(-1e9f, -1e9f, -1e9f);
 694   mWorkingQueryBoxCountDown = sWorkingQueryBoxStaleThreshold;
 695
 696   mPhysicsRep = NULL;
 697}
 698
 699U32 Vehicle::getCollisionMask()
 700{
 701   AssertFatal(false, "Vehicle::getCollisionMask is pure virtual!");
 702   return 0;
 703}
 704
 705Point3F Vehicle::getVelocity() const
 706{
 707   return mRigid.linVelocity;
 708}
 709
 710void Vehicle::_createPhysics()
 711{
 712   SAFE_DELETE(mPhysicsRep);
 713
 714   if (!PHYSICSMGR || !mDataBlock->enablePhysicsRep)
 715      return;
 716
 717   TSShape *shape = mShapeInstance->getShape();
 718   PhysicsCollision *colShape = NULL;
 719   colShape = shape->buildColShape(false, getScale());
 720
 721   if (colShape)
 722   {
 723      PhysicsWorld *world = PHYSICSMGR->getWorld(isServerObject() ? "server" : "client");
 724      mPhysicsRep = PHYSICSMGR->createBody();
 725      mPhysicsRep->init(colShape, 0, PhysicsBody::BF_KINEMATIC, this, world);
 726      mPhysicsRep->setTransform(getTransform());
 727   }
 728}
 729//----------------------------------------------------------------------------
 730
 731bool Vehicle::onAdd()
 732{
 733   if (!Parent::onAdd())
 734      return false;
 735
 736   mWorkingQueryBox.minExtents.set(-1e9f, -1e9f, -1e9f);
 737   mWorkingQueryBox.maxExtents.set(-1e9f, -1e9f, -1e9f);
 738
 739   // When loading from a mission script, the base SceneObject's transform
 740   // will have been set and needs to be transfered to the rigid body.
 741   mRigid.setTransform(mObjToWorld);
 742
 743   // Initialize interpolation vars.
 744   mDelta.rot[1] = mDelta.rot[0] = mRigid.angPosition;
 745   mDelta.pos = mRigid.linPosition;
 746   mDelta.posVec = Point3F(0,0,0);
 747
 748   // Create Emitters on the client
 749   if( isClientObject() )
 750   {
 751      if( mDataBlock->dustEmitter )
 752      {
 753         for( S32 i=0; i<VehicleData::VC_NUM_DUST_EMITTERS; i++ )
 754         {
 755            mDustEmitterList[i] = new ParticleEmitter;
 756            mDustEmitterList[i]->onNewDataBlock( mDataBlock->dustEmitter, false );
 757            if( !mDustEmitterList[i]->registerObject() )
 758            {
 759               Con::warnf( ConsoleLogEntry::General, "Could not register dust emitter for class: %s", mDataBlock->getName() );
 760               delete mDustEmitterList[i];
 761               mDustEmitterList[i] = NULL;
 762            }
 763         }
 764      }
 765
 766      U32 j;
 767      for( j=0; j<VehicleData::VC_NUM_DAMAGE_EMITTERS; j++ )
 768      {
 769         if( mDataBlock->damageEmitterList[j] )
 770         {
 771            mDamageEmitterList[j] = new ParticleEmitter;
 772            mDamageEmitterList[j]->onNewDataBlock( mDataBlock->damageEmitterList[j], false );
 773            if( !mDamageEmitterList[j]->registerObject() )
 774            {
 775               Con::warnf( ConsoleLogEntry::General, "Could not register damage emitter for class: %s", mDataBlock->getName() );
 776               delete mDamageEmitterList[j];
 777               mDamageEmitterList[j] = NULL;
 778            }
 779
 780         }
 781      }
 782
 783      for( j=0; j<VehicleData::VC_NUM_SPLASH_EMITTERS; j++ )
 784      {
 785         if( mDataBlock->splashEmitterList[j] )
 786         {
 787            mSplashEmitterList[j] = new ParticleEmitter;
 788            mSplashEmitterList[j]->onNewDataBlock( mDataBlock->splashEmitterList[j], false );
 789            if( !mSplashEmitterList[j]->registerObject() )
 790            {
 791               Con::warnf( ConsoleLogEntry::General, "Could not register splash emitter for class: %s", mDataBlock->getName() );
 792               delete mSplashEmitterList[j];
 793               mSplashEmitterList[j] = NULL;
 794            }
 795
 796         }
 797      }
 798   }
 799
 800   // Create a new convex.
 801   AssertFatal(mDataBlock->collisionDetails[0] != -1, "Error, a vehicle must have a collision-1 detail!");
 802   mConvex.mObject    = this;
 803   mConvex.pShapeBase = this;
 804   mConvex.hullId     = 0;
 805   mConvex.box        = mObjBox;
 806   mConvex.box.minExtents.convolve(mObjScale);
 807   mConvex.box.maxExtents.convolve(mObjScale);
 808   mConvex.findNodeTransform();
 809
 810   _createPhysics();
 811
 812   return true;
 813}
 814
 815void Vehicle::onRemove()
 816{
 817   SAFE_DELETE(mPhysicsRep);
 818
 819   U32 i=0;
 820   for( i=0; i<VehicleData::VC_NUM_DUST_EMITTERS; i++ )
 821   {
 822      if( mDustEmitterList[i] )
 823      {
 824         mDustEmitterList[i]->deleteWhenEmpty();
 825         mDustEmitterList[i] = NULL;
 826      }
 827   }
 828
 829   for( i=0; i<VehicleData::VC_NUM_DAMAGE_EMITTERS; i++ )
 830   {
 831      if( mDamageEmitterList[i] )
 832      {
 833         mDamageEmitterList[i]->deleteWhenEmpty();
 834         mDamageEmitterList[i] = NULL;
 835      }
 836   }
 837
 838   for( i=0; i<VehicleData::VC_NUM_SPLASH_EMITTERS; i++ )
 839   {
 840      if( mSplashEmitterList[i] )
 841      {
 842         mSplashEmitterList[i]->deleteWhenEmpty();
 843         mSplashEmitterList[i] = NULL;
 844      }
 845   }
 846
 847   mWorkingQueryBox.minExtents.set(-1e9f, -1e9f, -1e9f);
 848   mWorkingQueryBox.maxExtents.set(-1e9f, -1e9f, -1e9f);
 849
 850   Parent::onRemove();
 851}
 852
 853
 854//----------------------------------------------------------------------------
 855
 856void Vehicle::processTick(const Move* move)
 857{
 858   PROFILE_SCOPE( Vehicle_ProcessTick );
 859
 860   Parent::processTick(move);
 861   if ( isMounted() )
 862      return;
 863
 864   // Warp to catch up to server
 865   if (mDelta.warpCount < mDelta.warpTicks)
 866   {
 867      mDelta.warpCount++;
 868
 869      // Set new pos.
 870      mObjToWorld.getColumn(3,&mDelta.pos);
 871      mDelta.pos += mDelta.warpOffset;
 872      mDelta.rot[0] = mDelta.rot[1];
 873      mDelta.rot[1].interpolate(mDelta.warpRot[0],mDelta.warpRot[1],F32(mDelta.warpCount)/<a href="/coding/class/classvehicle/#classvehicle_1abd1fd726e40d62a89eb401695db69d10">mDelta</a>.warpTicks);
 874      setPosition(mDelta.pos,mDelta.rot[1]);
 875
 876      // Pos backstepping
 877      mDelta.posVec.x = -mDelta.warpOffset.x;
 878      mDelta.posVec.y = -mDelta.warpOffset.y;
 879      mDelta.posVec.z = -mDelta.warpOffset.z;
 880   }
 881   else 
 882   {
 883      if (!move) 
 884      {
 885         if (isGhost()) 
 886         {
 887            // If we haven't run out of prediction time,
 888            // predict using the last known move.
 889            if (mPredictionCount-- <= 0)
 890               return;
 891            move = &mDelta.move;
 892         }
 893         else
 894            move = &NullMove;
 895      }
 896
 897      // Process input move
 898      updateMove(move);
 899
 900      // Save current rigid state interpolation
 901      mDelta.posVec = mRigid.linPosition;
 902      mDelta.rot[0] = mRigid.angPosition;
 903
 904      // Update the physics based on the integration rate
 905      S32 count = mDataBlock->integration;
 906      --mWorkingQueryBoxCountDown;
 907      updateWorkingCollisionSet(getCollisionMask());
 908      for (U32 i = 0; i < count; i++)
 909         updatePos(TickSec / count);
 910
 911      // Wrap up interpolation info
 912      mDelta.pos     = mRigid.linPosition;
 913      mDelta.posVec -= mRigid.linPosition;
 914      mDelta.rot[1]  = mRigid.angPosition;
 915
 916      // Update container database
 917      setPosition(mRigid.linPosition, mRigid.angPosition);
 918      setMaskBits(PositionMask);
 919      updateContainer();
 920
 921      //TODO: Only update when position has actually changed
 922      //no need to check if mDataBlock->enablePhysicsRep is false as mPhysicsRep will be NULL if it is
 923      if (mPhysicsRep)
 924         mPhysicsRep->moveKinematicTo(getTransform());
 925   }
 926}
 927
 928void Vehicle::interpolateTick(F32 dt)
 929{
 930   PROFILE_SCOPE( Vehicle_InterpolateTick );
 931
 932   Parent::interpolateTick(dt);
 933   if ( isMounted() )
 934      return;
 935
 936   if(dt == 0.0f)
 937      setRenderPosition(mDelta.pos, mDelta.rot[1]);
 938   else
 939   {
 940      QuatF rot;
 941      rot.interpolate(mDelta.rot[1], mDelta.rot[0], dt);
 942      Point3F pos = mDelta.pos + mDelta.posVec * dt;
 943      setRenderPosition(pos,rot);
 944   }
 945   mDelta.dt = dt;
 946}
 947
 948void Vehicle::advanceTime(F32 dt)
 949{
 950   PROFILE_SCOPE( Vehicle_AdvanceTime );
 951
 952   Parent::advanceTime(dt);
 953
 954   updateLiftoffDust( dt );
 955   updateDamageSmoke( dt );
 956   updateFroth(dt);
 957
 958   // Update 3rd person camera offset.  Camera update is done
 959   // here as it's a client side only animation.
 960   mCameraOffset -=
 961      (mCameraOffset * mDataBlock->cameraDecay +
 962      mRigid.linVelocity * mDataBlock->cameraLag) * dt;
 963}
 964
 965
 966//----------------------------------------------------------------------------
 967
 968bool Vehicle::onNewDataBlock(GameBaseData* dptr,bool reload)
 969{
 970   mDataBlock = dynamic_cast<VehicleData*>(dptr);
 971   if (!mDataBlock || !Parent::onNewDataBlock(dptr, reload))
 972      return false;
 973
 974   // Update Rigid Info
 975   mRigid.mass = mDataBlock->mass;
 976   mRigid.oneOverMass = 1 / mRigid.mass;
 977   mRigid.friction = mDataBlock->body.friction;
 978   mRigid.restitution = mDataBlock->body.restitution;
 979   mRigid.setCenterOfMass(mDataBlock->massCenter);
 980
 981   // Ignores massBox, just set sphere for now. Derived objects
 982   // can set what they want.
 983   mRigid.setObjectInertia();
 984
 985   if (isGhost()) 
 986   {
 987      // Create the sound ahead of time.  This reduces runtime
 988      // costs and makes the system easier to understand.
 989      SFX_DELETE( mWakeSound );
 990
 991      if ( mDataBlock->waterSound[VehicleData::Wake] )
 992         mWakeSound = SFX->createSource( mDataBlock->waterSound[VehicleData::Wake], &getTransform() );
 993   }
 994
 995   return true;
 996}
 997
 998
 999//----------------------------------------------------------------------------
1000
1001void Vehicle::getCameraParameters(F32 *min,F32* max,Point3F* off,MatrixF* rot)
1002{
1003   *min = mDataBlock->cameraMinDist;
1004   *max = mDataBlock->cameraMaxDist;
1005
1006   off->set(0,0,mDataBlock->cameraOffset);
1007   rot->identity();
1008}
1009
1010
1011//----------------------------------------------------------------------------
1012
1013void Vehicle::getCameraTransform(F32* pos,MatrixF* mat)
1014{
1015   // Returns camera to world space transform
1016   // Handles first person / third person camera position
1017   if (isServerObject() && mShapeInstance)
1018      mShapeInstance->animateNodeSubtrees(true);
1019
1020   if (*pos == 0) {
1021      getRenderEyeTransform(mat);
1022      return;
1023   }
1024
1025   // Get the shape's camera parameters.
1026   F32 min,max;
1027   MatrixF rot;
1028   Point3F offset;
1029   getCameraParameters(&min,&max,&offset,&rot);
1030
1031   // Start with the current eye position
1032   MatrixF eye;
1033   getRenderEyeTransform(&eye);
1034
1035   // Build a transform that points along the eye axis
1036   // but where the Z axis is always up.
1037   if (mDataBlock->cameraRoll)
1038      mat->mul(eye,rot);
1039   else 
1040   {
1041      MatrixF cam(1);
1042      VectorF x,y,z(0,0,1);
1043      eye.getColumn(1, &y);
1044      mCross(y, z, &x);
1045      x.normalize();
1046      mCross(x, y, &z);
1047      z.normalize();
1048      cam.setColumn(0,x);
1049      cam.setColumn(1,y);
1050      cam.setColumn(2,z);
1051      mat->mul(cam,rot);
1052   }
1053
1054   // Camera is positioned straight back along the eye's -Y axis.
1055   // A ray is cast to make sure the camera doesn't go through
1056   // anything solid.
1057   VectorF vp,vec;
1058   vp.x = vp.z = 0;
1059   vp.y = -(max - min) * *pos;
1060   eye.mulV(vp,&vec);
1061
1062   // Use the camera node as the starting position if it exists.
1063   Point3F osp,sp;
1064   if (mDataBlock->cameraNode != -1) 
1065   {
1066      mShapeInstance->mNodeTransforms[mDataBlock->cameraNode].getColumn(3,&osp);
1067      getRenderTransform().mulP(osp,&sp);
1068   }
1069   else
1070      eye.getColumn(3,&sp);
1071
1072   // Make sure we don't hit ourself...
1073   disableCollision();
1074   if (isMounted())
1075      getObjectMount()->disableCollision();
1076
1077   // Cast the ray into the container database to see if we're going
1078   // to hit anything.
1079   RayInfo collision;
1080   Point3F ep = sp + vec + offset + mCameraOffset;
1081   if (mContainer->castRay(sp, ep,
1082         ~(WaterObjectType | GameBaseObjectType | DefaultObjectType | sTriggerMask),
1083         &collision) == true) {
1084
1085      // Shift the collision point back a little to try and
1086      // avoid clipping against the front camera plane.
1087      F32 t = collision.t - (-mDot(vec, collision.normal) / vec.len()) * 0.1;
1088      if (t > 0.0f)
1089         ep = sp + offset + mCameraOffset + (vec * t);
1090      else
1091         eye.getColumn(3,&ep);
1092   }
1093   mat->setColumn(3,ep);
1094
1095   // Re-enable our collision.
1096   if (isMounted())
1097      getObjectMount()->enableCollision();
1098   enableCollision();
1099
1100   // Apply Camera FX.
1101   mat->mul( gCamFXMgr.getTrans() );
1102}
1103
1104
1105//----------------------------------------------------------------------------
1106
1107void Vehicle::getVelocity(const Point3F& r, Point3F* v)
1108{
1109   mRigid.getVelocity(r, v);
1110}
1111
1112void Vehicle::applyImpulse(const Point3F &pos, const Point3F &impulse)
1113{
1114   Point3F r;
1115   mRigid.getOriginVector(pos,&r);
1116   mRigid.applyImpulse(r, impulse);
1117}
1118
1119
1120//----------------------------------------------------------------------------
1121
1122void Vehicle::updateMove(const Move* move)
1123{
1124   PROFILE_SCOPE( Vehicle_UpdateMove );
1125
1126   mDelta.move = *move;
1127
1128   // Image Triggers
1129   if (mDamageState == Enabled) {
1130      setImageTriggerState(0,move->trigger[0]);
1131      setImageTriggerState(1,move->trigger[1]);
1132   }
1133
1134   // Throttle
1135   if(!mDisableMove)
1136      mThrottle = move->y;
1137
1138   // Steering
1139   if (move != &NullMove) {
1140      F32 y = move->yaw;
1141      mSteering.x = mClampF(mSteering.x + y,-mDataBlock->maxSteeringAngle,
1142                            mDataBlock->maxSteeringAngle);
1143      F32 p = move->pitch;
1144      mSteering.y = mClampF(mSteering.y + p,-mDataBlock->maxSteeringAngle,
1145                            mDataBlock->maxSteeringAngle);
1146   }
1147   else {
1148      mSteering.x = 0;
1149      mSteering.y = 0;
1150   }
1151
1152   // Steering return
1153   if(mDataBlock->steeringReturn > 0.0f &&
1154      (!mDataBlock->powerSteering || (move->yaw == 0.0f && move->pitch == 0.0f)))
1155   {
1156      Point2F returnAmount(mSteering.x * mDataBlock->steeringReturn * TickSec,
1157                           mSteering.y * mDataBlock->steeringReturn * TickSec);
1158      if(mDataBlock->steeringReturnSpeedScale > 0.0f)
1159      {
1160         Point3F vel;
1161         mWorldToObj.mulV(getVelocity(), &vel);
1162         returnAmount += Point2F(mSteering.x * vel.y * mDataBlock->steeringReturnSpeedScale * TickSec,
1163                                 mSteering.y * vel.y * mDataBlock->steeringReturnSpeedScale * TickSec);
1164      }
1165      mSteering -= returnAmount;
1166   }
1167
1168   // Jetting flag
1169   if (move->trigger[3]) {
1170      if (!mJetting && getEnergyLevel() >= mDataBlock->minJetEnergy)
1171         mJetting = true;
1172      if (mJetting) {
1173         F32 newEnergy = getEnergyLevel() - mDataBlock->jetEnergyDrain;
1174         if (newEnergy < 0) {
1175            newEnergy = 0;
1176            mJetting = false;
1177         }
1178         setEnergyLevel(newEnergy);
1179      }
1180   }
1181   else
1182      mJetting = false;
1183}
1184
1185
1186//----------------------------------------------------------------------------
1187
1188void Vehicle::setPosition(const Point3F& pos,const QuatF& rot)
1189{
1190   MatrixF mat;
1191   rot.setMatrix(&mat);
1192   mat.setColumn(3,pos);
1193   Parent::setTransform(mat);
1194}
1195
1196void Vehicle::setRenderPosition(const Point3F& pos, const QuatF& rot)
1197{
1198   MatrixF mat;
1199   rot.setMatrix(&mat);
1200   mat.setColumn(3,pos);
1201   Parent::setRenderTransform(mat);
1202}
1203
1204void Vehicle::setTransform(const MatrixF& newMat)
1205{
1206   mRigid.setTransform(newMat);
1207   Parent::setTransform(newMat);
1208   mRigid.atRest = false;
1209   mContacts.clear();
1210}
1211
1212
1213//-----------------------------------------------------------------------------
1214
1215void Vehicle::disableCollision()
1216{
1217   Parent::disableCollision();
1218   for (SceneObject* ptr = getMountList(); ptr; ptr = ptr->getMountLink())
1219      ptr->disableCollision();
1220}
1221
1222void Vehicle::enableCollision()
1223{
1224   Parent::enableCollision();
1225   for (SceneObject* ptr = getMountList(); ptr; ptr = ptr->getMountLink())
1226      ptr->enableCollision();
1227}
1228
1229
1230//----------------------------------------------------------------------------
1231/** Update the physics
1232*/
1233
1234void Vehicle::updatePos(F32 dt)
1235{
1236   PROFILE_SCOPE( Vehicle_UpdatePos );
1237
1238   Point3F origVelocity = mRigid.linVelocity;
1239
1240   // Update internal forces acting on the body.
1241   mRigid.clearForces();
1242   updateForces(dt);
1243
1244   // Update collision information based on our current pos.
1245   bool collided = false;
1246   if (!mRigid.atRest) {
1247      collided = updateCollision(dt);
1248
1249      // Now that all the forces have been processed, lets
1250      // see if we're at rest.  Basically, if the kinetic energy of
1251      // the vehicles is less than some percentage of the energy added
1252      // by gravity for a short period, we're considered at rest.
1253      // This should really be part of the rigid class...
1254      if (mCollisionList.getCount()) 
1255      {
1256         F32 k = mRigid.getKineticEnergy();
1257         F32 G = sVehicleGravity * dt;
1258         F32 Kg = 0.5 * mRigid.mass * G * G;
1259         if (k < sRestTol * Kg && ++restCount > sRestCount)
1260            mRigid.setAtRest();
1261      }
1262      else
1263         restCount = 0;
1264   }
1265
1266   // Integrate forward
1267   if (!mRigid.atRest)
1268      mRigid.integrate(dt);
1269
1270   // Deal with client and server scripting, sounds, etc.
1271   if (isServerObject()) {
1272
1273      // Check triggers and other objects that we normally don't
1274      // collide with.  This function must be called before notifyCollision
1275      // as it will queue collision.
1276      checkTriggers();
1277
1278      // Invoke the onCollision notify callback for all the objects
1279      // we've just hit.
1280      notifyCollision();
1281
1282      // Server side impact script callback
1283      if (collided) {
1284         VectorF collVec = mRigid.linVelocity - origVelocity;
1285         F32 collSpeed = collVec.len();
1286         if (collSpeed > mDataBlock->minImpactSpeed)
1287            onImpact(collVec);
1288      }
1289
1290      // Water script callbacks
1291      if (!inLiquid && mWaterCoverage != 0.0f) {
1292         mDataBlock->onEnterLiquid_callback( this, mWaterCoverage, mLiquidType.c_str() );
1293         inLiquid = true;
1294      }
1295      else if (inLiquid && mWaterCoverage == 0.0f) {
1296         mDataBlock->onLeaveLiquid_callback( this, mLiquidType.c_str() );
1297         inLiquid = false;
1298      }
1299
1300   }
1301   else {
1302
1303      // Play impact sounds on the client.
1304      if (collided) {
1305         F32 collSpeed = (mRigid.linVelocity - origVelocity).len();
1306         S32 impactSound = -1;
1307         if (collSpeed >= mDataBlock->hardImpactSpeed)
1308            impactSound = VehicleData::Body::HardImpactSound;
1309         else
1310            if (collSpeed >= mDataBlock->softImpactSpeed)
1311               impactSound = VehicleData::Body::SoftImpactSound;
1312
1313         if (impactSound != -1 && mDataBlock->body.sound[impactSound] != NULL)
1314            SFX->playOnce( mDataBlock->body.sound[impactSound], &getTransform() );
1315      }
1316
1317      // Water volume sounds
1318      F32 vSpeed = getVelocity().len();
1319      if (!inLiquid && mWaterCoverage >= 0.8f) {
1320         if (vSpeed >= mDataBlock->hardSplashSoundVel)
1321            SFX->playOnce( mDataBlock->waterSound[VehicleData::ImpactHard], &getTransform() );
1322         else
1323            if (vSpeed >= mDataBlock->medSplashSoundVel)
1324               SFX->playOnce( mDataBlock->waterSound[VehicleData::ImpactMedium], &getTransform() );
1325         else
1326            if (vSpeed >= mDataBlock->softSplashSoundVel)
1327               SFX->playOnce( mDataBlock->waterSound[VehicleData::ImpactSoft], &getTransform() );
1328         inLiquid = true;
1329      }
1330      else
1331         if(inLiquid && mWaterCoverage < 0.8f) {
1332            if (vSpeed >= mDataBlock->exitSplashSoundVel)
1333               SFX->playOnce( mDataBlock->waterSound[VehicleData::ExitWater], &getTransform() );
1334         inLiquid = false;
1335      }
1336   }
1337}
1338
1339
1340//----------------------------------------------------------------------------
1341
1342void Vehicle::updateForces(F32 /*dt*/)
1343{
1344   // Nothing here.
1345}
1346
1347
1348//-----------------------------------------------------------------------------
1349/** Update collision information
1350   Update the convex state and check for collisions. If the object is in
1351   collision, impact and contact forces are generated.
1352*/
1353
1354bool Vehicle::updateCollision(F32 dt)
1355{
1356   PROFILE_SCOPE( Vehicle_UpdateCollision );
1357
1358   // Update collision information
1359   MatrixF mat,cmat;
1360   mConvex.transform = &mat;
1361   mRigid.getTransform(&mat);
1362   cmat = mConvex.getTransform();
1363
1364   mCollisionList.clear();
1365   CollisionState *state = mConvex.findClosestState(cmat, getScale(), mDataBlock->collisionTol);
1366   if (state && state->dist <= mDataBlock->collisionTol) 
1367   {
1368      //resolveDisplacement(ns,state,dt);
1369      mConvex.getCollisionInfo(cmat, getScale(), &mCollisionList, mDataBlock->collisionTol);
1370   }
1371
1372   // Resolve collisions
1373   bool collided = resolveCollision(mRigid,mCollisionList);
1374   resolveContacts(mRigid,mCollisionList,dt);
1375   return collided;
1376}
1377
1378
1379//----------------------------------------------------------------------------
1380/** Resolve collision impacts
1381   Handle collision impacts, as opposed to contacts. Impulses are calculated based
1382   on standard collision resolution formulas.
1383*/
1384bool Vehicle::resolveCollision(Rigid&  ns,CollisionList& cList)
1385{
1386   PROFILE_SCOPE( Vehicle_ResolveCollision );
1387
1388   // Apply impulses to resolve collision
1389   bool collided = false;
1390   for (S32 i = 0; i < cList.getCount(); i++) 
1391   {
1392      Collision& c = cList[i];
1393      if (c.distance < mDataBlock->collisionTol) 
1394      {
1395         // Velocity into surface
1396         Point3F v,r;
1397         ns.getOriginVector(c.point,&r);
1398         ns.getVelocity(r,&v);
1399         F32 vn = mDot(v,c.normal);
1400
1401         // Only interested in velocities greater than sContactTol,
1402         // velocities less than that will be dealt with as contacts
1403         // "constraints".
1404         if (vn < -mDataBlock->contactTol) 
1405         {
1406
1407            // Apply impulses to the rigid body to keep it from
1408            // penetrating the surface.
1409            ns.resolveCollision(cList[i].point,
1410               cList[i].normal);
1411            collided  = true;
1412
1413            // Keep track of objects we collide with
1414            if (!isGhost() && c.object->getTypeMask() & ShapeBaseObjectType) 
1415            {
1416               ShapeBase* col = static_cast<ShapeBase*>(c.object);
1417               queueCollision(col,v - col->getVelocity());
1418            }
1419         }
1420      }
1421   }
1422
1423   return collided;
1424}
1425
1426//----------------------------------------------------------------------------
1427/** Resolve contact forces
1428   Resolve contact forces using the "penalty" method. Forces are generated based
1429   on the depth of penetration and the moment of inertia at the point of contact.
1430*/
1431bool Vehicle::resolveContacts(Rigid& ns,CollisionList& cList,F32 dt)
1432{
1433   PROFILE_SCOPE( Vehicle_ResolveContacts );
1434
1435   // Use spring forces to manage contact constraints.
1436   bool collided = false;
1437   Point3F t,p(0,0,0),l(0,0,0);
1438   for (S32 i = 0; i < cList.getCount(); i++) 
1439   {
1440      const Collision& c = cList[i];
1441      if (c.distance < mDataBlock->collisionTol) 
1442      {
1443
1444         // Velocity into the surface
1445         Point3F v,r;
1446         ns.getOriginVector(c.point,&r);
1447         ns.getVelocity(r,&v);
1448         F32 vn = mDot(v,c.normal);
1449
1450         // Only interested in velocities less than mDataBlock->contactTol,
1451         // velocities greater than that are dealt with as collisions.
1452         if (mFabs(vn) < mDataBlock->contactTol) 
1453         {
1454            collided = true;
1455
1456            // Penetration force. This is actually a spring which
1457            // will seperate the body from the collision surface.
1458            F32 zi = 2 * mFabs(mRigid.getZeroImpulse(r,c.normal));
1459            F32 s = (mDataBlock->collisionTol - c.distance) * zi - ((vn / mDataBlock->contactTol) * zi);
1460            Point3F f = c.normal * s;
1461
1462            // Friction impulse, calculated as a function of the
1463            // amount of force it would take to stop the motion
1464            // perpendicular to the normal.
1465            Point3F uv = v - (c.normal * vn);
1466            F32 ul = uv.len();
1467            if (s > 0 && ul) 
1468            {
1469               uv /= -ul;
1470               F32 u = ul * ns.getZeroImpulse(r,uv);
1471               s *= mRigid.friction;
1472               if (u > s)
1473                  u = s;
1474               f += uv * u;
1475            }
1476
1477            // Accumulate forces
1478            p += f;
1479            mCross(r,f,&t);
1480            l += t;
1481         }
1482      }
1483   }
1484
1485   // Contact constraint forces act over time...
1486   ns.linMomentum += p * dt;
1487   ns.angMomentum += l * dt;
1488   ns.updateVelocity();
1489   return true;
1490}
1491
1492
1493//----------------------------------------------------------------------------
1494
1495bool Vehicle::resolveDisplacement(Rigid& ns,CollisionState *state, F32 dt)
1496{
1497   PROFILE_SCOPE( Vehicle_ResolveDisplacement );
1498
1499   SceneObject* obj = (state->a->getObject() == this)?
1500       state->b->getObject(): state->a->getObject();
1501
1502   if (obj->isDisplacable() && ((obj->getTypeMask() & ShapeBaseObjectType) != 0))
1503   {
1504      // Try to displace the object by the amount we're trying to move
1505      Point3F objNewMom = ns.linVelocity * obj->getMass() * 1.1f;
1506      Point3F objOldMom = obj->getMomentum();
1507      Point3F objNewVel = objNewMom / obj->getMass();
1508
1509      Point3F myCenter;
1510      Point3F theirCenter;
1511      getWorldBox().getCenter(&myCenter);
1512      obj->getWorldBox().getCenter(&theirCenter);
1513      if (mDot(myCenter - theirCenter, objNewMom) >= 0.0f || objNewVel.len() < 0.01)
1514      {
1515         objNewMom = (theirCenter - myCenter);
1516         objNewMom.normalize();
1517         objNewMom *= 1.0f * obj->getMass();
1518         objNewVel = objNewMom / obj->getMass();
1519      }
1520
1521      obj->setMomentum(objNewMom);
1522      if (obj->displaceObject(objNewVel * 1.1f * dt) == true)
1523      {
1524         // Queue collision and change in velocity
1525         VectorF dv = (objOldMom - objNewMom) / obj->getMass();
1526         queueCollision(static_cast<ShapeBase*>(obj), dv);
1527         return true;
1528      }
1529   }
1530
1531   return false;
1532}
1533
1534
1535//----------------------------------------------------------------------------
1536
1537void Vehicle::updateWorkingCollisionSet(const U32 mask)
1538{
1539   PROFILE_SCOPE( Vehicle_UpdateWorkingCollisionSet );
1540
1541   // First, we need to adjust our velocity for possible acceleration.  It is assumed
1542   // that we will never accelerate more than 20 m/s for gravity, plus 30 m/s for
1543   // jetting, and an equivalent 10 m/s for vehicle accel.  We also assume that our
1544   // working list is updated on a Tick basis, which means we only expand our box by
1545   // the possible movement in that tick, plus some extra for caching purposes
1546   Box3F convexBox = mConvex.getBoundingBox(getTransform(), getScale());
1547   F32 len = (mRigid.linVelocity.len() + 50) * TickSec;
1548   F32 l = (len * 1.1) + 0.1;  // fudge factor
1549   convexBox.minExtents -= Point3F(l, l, l);
1550   convexBox.maxExtents += Point3F(l, l, l);
1551
1552   // Check to see if it is actually necessary to construct the new working list,
1553   // or if we can use the cached version from the last query.  We use the x
1554   // component of the min member of the mWorkingQueryBox, which is lame, but
1555   // it works ok.
1556   bool updateSet = false;
1557
1558   // Check containment
1559   if ((sWorkingQueryBoxStaleThreshold == -1 || mWorkingQueryBoxCountDown > 0) && mWorkingQueryBox.minExtents.x != -1e9f)
1560   {
1561      if (mWorkingQueryBox.isContained(convexBox) == false)
1562         // Needed region is outside the cached region.  Update it.
1563         updateSet = true;
1564   }
1565   else
1566   {
1567      // Must update
1568      updateSet = true;
1569   }
1570
1571   // Actually perform the query, if necessary
1572   if (updateSet == true)
1573   {
1574      mWorkingQueryBoxCountDown = sWorkingQueryBoxStaleThreshold;
1575
1576      const Point3F  lPoint( sWorkingQueryBoxSizeMultiplier * l );
1577      mWorkingQueryBox = convexBox;
1578      mWorkingQueryBox.minExtents -= lPoint;
1579      mWorkingQueryBox.maxExtents += lPoint;
1580
1581      disableCollision();
1582      mConvex.updateWorkingList(mWorkingQueryBox, mask);
1583      enableCollision();
1584   }
1585}
1586
1587
1588//----------------------------------------------------------------------------
1589/** Check collisions with trigger and items
1590   Perform a container search using the current bounding box
1591   of the main body, wheels are not included.  This method should
1592   only be called on the server.
1593*/
1594void Vehicle::checkTriggers()
1595{
1596   Box3F bbox = mConvex.getBoundingBox(getTransform(), getScale());
1597   gServerContainer.findObjects(bbox,sTriggerMask,findCallback,this);
1598}
1599
1600/** The callback used in by the checkTriggers() method.
1601   The checkTriggers method uses a container search which will
1602   invoke this callback on each obj that matches.
1603*/
1604void Vehicle::findCallback(SceneObject* obj,void *key)
1605{
1606   Vehicle* vehicle = reinterpret_cast<Vehicle*>(key);
1607   U32 objectMask = obj->getTypeMask();
1608
1609   // Check: triggers, corpses and items, basically the same things
1610   // that the player class checks for
1611   if (objectMask & TriggerObjectType) {
1612      Trigger* pTrigger = static_cast<Trigger*>(obj);
1613      pTrigger->potentialEnterObject(vehicle);
1614   }
1615   else if (objectMask & CorpseObjectType) {
1616      ShapeBase* col = static_cast<ShapeBase*>(obj);
1617      vehicle->queueCollision(col,vehicle->getVelocity() - col->getVelocity());
1618   }
1619   else if (objectMask & ItemObjectType) {
1620      Item* item = static_cast<Item*>(obj);
1621      if (vehicle != item->getCollisionObject())
1622         vehicle->queueCollision(item,vehicle->getVelocity() - item->getVelocity());
1623   }
1624}
1625
1626
1627//----------------------------------------------------------------------------
1628
1629void Vehicle::writePacketData(GameConnection *connection, BitStream *stream)
1630{
1631   Parent::writePacketData(connection, stream);
1632   mathWrite(*stream, mSteering);
1633
1634   mathWrite(*stream, mRigid.linPosition);
1635   mathWrite(*stream, mRigid.angPosition);
1636   mathWrite(*stream, mRigid.linMomentum);
1637   mathWrite(*stream, mRigid.angMomentum);
1638   stream->writeFlag(mRigid.atRest);
1639   stream->writeFlag(mContacts.getCount() == 0);
1640
1641   stream->writeFlag(mDisableMove);
1642   stream->setCompressionPoint(mRigid.linPosition);
1643}
1644
1645void Vehicle::readPacketData(GameConnection *connection, BitStream *stream)
1646{
1647   Parent::readPacketData(connection, stream);
1648   mathRead(*stream, &mSteering);
1649
1650   mathRead(*stream, &mRigid.linPosition);
1651   mathRead(*stream, &mRigid.angPosition);
1652   mathRead(*stream, &mRigid.linMomentum);
1653   mathRead(*stream, &mRigid.angMomentum);
1654   mRigid.atRest = stream->readFlag();
1655   if (stream->readFlag())
1656      mContacts.clear();
1657   mRigid.updateInertialTensor();
1658   mRigid.updateVelocity();
1659   mRigid.updateCenterOfMass();
1660
1661   mDisableMove = stream->readFlag();
1662   stream->setCompressionPoint(mRigid.linPosition);
1663}
1664
1665
1666//----------------------------------------------------------------------------
1667
1668U32 Vehicle::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
1669{
1670   U32 retMask = Parent::packUpdate(con, mask, stream);
1671
1672   stream->writeFlag(mJetting);
1673
1674   // The rest of the data is part of the control object packet update.
1675   // If we're controlled by this client, we don't need to send it.
1676   if (stream->writeFlag(getControllingClient() == con && !(mask & InitialUpdateMask)))
1677      return retMask;
1678
1679   F32 yaw = (mSteering.x + mDataBlock->maxSteeringAngle) / (2 * mDataBlock->maxSteeringAngle);
1680   F32 pitch = (mSteering.y + mDataBlock->maxSteeringAngle) / (2 * mDataBlock->maxSteeringAngle);
1681   stream->writeFloat(yaw,9);
1682   stream->writeFloat(pitch,9);
1683   mDelta.move.pack(stream);
1684
1685   if (stream->writeFlag(mask & PositionMask))
1686   {
1687      stream->writeCompressedPoint(mRigid.linPosition);
1688      mathWrite(*stream, mRigid.angPosition);
1689      mathWrite(*stream, mRigid.linMomentum);
1690      mathWrite(*stream, mRigid.angMomentum);
1691      stream->writeFlag(mRigid.atRest);
1692   }
1693
1694   
1695   stream->writeFloat(mClampF(getEnergyValue(), 0.f, 1.f), 8);
1696
1697   return retMask;
1698}
1699
1700void Vehicle::unpackUpdate(NetConnection *con, BitStream *stream)
1701{
1702   Parent::unpackUpdate(con,stream);
1703
1704   mJetting = stream->readFlag();
1705
1706   if (stream->readFlag())
1707      return;
1708
1709   F32 yaw = stream->readFloat(9);
1710   F32 pitch = stream->readFloat(9);
1711   mSteering.x = (2 * yaw * mDataBlock->maxSteeringAngle) - mDataBlock->maxSteeringAngle;
1712   mSteering.y = (2 * pitch * mDataBlock->maxSteeringAngle) - mDataBlock->maxSteeringAngle;
1713   mDelta.move.unpack(stream);
1714
1715   if (stream->readFlag()) 
1716   {
1717      mPredictionCount = sMaxPredictionTicks;
1718      F32 speed = mRigid.linVelocity.len();
1719      mDelta.warpRot[0] = mRigid.angPosition;
1720
1721      // Read in new position and momentum values
1722      stream->readCompressedPoint(&mRigid.linPosition);
1723      mathRead(*stream, &mRigid.angPosition);
1724      mathRead(*stream, &mRigid.linMomentum);
1725      mathRead(*stream, &mRigid.angMomentum);
1726      mRigid.atRest = stream->readFlag();
1727      mRigid.updateVelocity();
1728
1729      if (isProperlyAdded()) 
1730      {
1731         // Determine number of ticks to warp based on the average
1732         // of the client and server velocities.
1733         Point3F cp = mDelta.pos + mDelta.posVec * mDelta.dt;
1734         mDelta.warpOffset = mRigid.linPosition - cp;
1735
1736         // Calc the distance covered in one tick as the average of
1737         // the old speed and the new speed from the server.
1738         F32 dt,as = (speed + mRigid.linVelocity.len()) * 0.5 * TickSec;
1739
1740         // Cal how many ticks it will take to cover the warp offset.
1741         // If it's less than what's left in the current tick, we'll just
1742         // warp in the remaining time.
1743         if (!as || (dt = mDelta.warpOffset.len() / as) > sMaxWarpTicks)
1744            dt = mDelta.dt + sMaxWarpTicks;
1745         else
1746            dt = (dt <= mDelta.dt)? mDelta.dt : mCeil(dt - mDelta.dt) + mDelta.dt;
1747
1748         // Adjust current frame interpolation
1749         if (mDelta.dt) {
1750            mDelta.pos = cp + (mDelta.warpOffset * (mDelta.dt / dt));
1751            mDelta.posVec = (cp - mDelta.pos) / mDelta.dt;
1752            QuatF cr;
1753            cr.interpolate(mDelta.rot[1],mDelta.rot[0],mDelta.dt);
1754            mDelta.rot[1].interpolate(cr,mRigid.angPosition,mDelta.dt / dt);
1755            mDelta.rot[0].extrapolate(mDelta.rot[1],cr,mDelta.dt);
1756         }
1757
1758         // Calculated multi-tick warp
1759         mDelta.warpCount = 0;
1760         mDelta.warpTicks = (S32)(mFloor(dt));
1761         if (mDelta.warpTicks) 
1762         {
1763            mDelta.warpOffset = mRigid.linPosition - mDelta.pos;
1764            mDelta.warpOffset /= (F32)mDelta.warpTicks;
1765            mDelta.warpRot[0] = mDelta.rot[1];
1766            mDelta.warpRot[1] = mRigid.angPosition;
1767         }
1768      }
1769      else 
1770      {
1771         // Set the vehicle to the server position
1772         mDelta.dt  = 0;
1773         mDelta.pos = mRigid.linPosition;
1774         mDelta.posVec.set(0,0,0);
1775         mDelta.rot[1] = mDelta.rot[0] = mRigid.angPosition;
1776         mDelta.warpCount = mDelta.warpTicks = 0;
1777         setPosition(mRigid.linPosition, mRigid.angPosition);
1778      }
1779      mRigid.updateCenterOfMass();
1780   }
1781
1782   setEnergyLevel(stream->readFloat(8) * mDataBlock->maxEnergy);
1783}
1784
1785
1786//----------------------------------------------------------------------------
1787
1788void Vehicle::consoleInit()
1789{
1790   Con::addVariable("$vehicle::workingQueryBoxStaleThreshold",TypeS32,&sWorkingQueryBoxStaleThreshold, 
1791      "@brief The maximum number of ticks that go by before the mWorkingQueryBox is considered stale and needs updating.\n\n"
1792      "Other factors can cause the collision working query box to become invalidated, such as the vehicle moving far "
1793      "enough outside of this cached box.  The smaller this number, the more times the working list of triangles that are "
1794      "considered for collision is refreshed.  This has the greatest impact with colliding with high triangle count meshes.\n\n"
1795      "@note Set to -1 to disable any time-based forced check.\n\n"
1796      "@ingroup GameObjects\n");
1797
1798   Con::addVariable("$vehicle::workingQueryBoxSizeMultiplier",TypeF32,&sWorkingQueryBoxSizeMultiplier, 
1799      "@brief How much larger the mWorkingQueryBox should be made when updating the working collision list.\n\n"
1800      "The larger this number the less often the working list will be updated due to motion, but any non-static shape that "
1801      "moves into the query box will not be noticed.\n\n"
1802      "@ingroup GameObjects\n");
1803}
1804
1805void Vehicle::initPersistFields()
1806{
1807   addField( "disableMove", TypeBool, Offset(mDisableMove, Vehicle),
1808      "When this flag is set, the vehicle will ignore throttle changes." );
1809
1810   Parent::initPersistFields();
1811}
1812
1813
1814void Vehicle::mountObject(SceneObject *obj, S32 node, const MatrixF &xfm )
1815{
1816   Parent::mountObject( obj, node, xfm );
1817
1818   // Clear objects off the working list that are from objects mounted to us.
1819   //  (This applies mostly to players...)
1820   for ( CollisionWorkingList* itr = mConvex.getWorkingList().wLink.mNext; 
1821         itr != &mConvex.getWorkingList(); 
1822         itr = itr->wLink.mNext) 
1823   {
1824      if (itr->mConvex->getObject() == obj) 
1825      {
1826         CollisionWorkingList* cl = itr;
1827         itr = itr->wLink.mPrev;
1828         cl->free();
1829      }
1830   }
1831}
1832
1833//----------------------------------------------------------------------------
1834
1835void Vehicle::updateLiftoffDust( F32 dt )
1836{
1837   Point3F offset( 0.0, 0.0, mDataBlock->dustHeight );
1838   emitDust( mDustEmitterList[ 0 ], mDataBlock->triggerDustHeight, offset,
1839             ( U32 )( dt * 1000 ) );
1840}
1841
1842//----------------------------------------------------------------------------
1843
1844void Vehicle::updateDamageSmoke( F32 dt )
1845{
1846
1847   for( S32 j=<a href="/coding/class/structvehicledata/#structvehicledata_1a49944dd75022f526ef95014376c1c807a55f0cfbcfaa01fc5be4682b4aadbf0a6">VehicleData::VC_NUM_DAMAGE_LEVELS</a>-1; j>=0; j-- )
1848   {
1849      F32 damagePercent = mDamage / mDataBlock->maxDamage;
1850      if( damagePercent >= mDataBlock->damageLevelTolerance[j] )
1851      {
1852         for( S32 i=0; i<mDataBlock->numDmgEmitterAreas; i++ )
1853         {
1854            MatrixF trans = getTransform();
1855            Point3F offset = mDataBlock->damageEmitterOffset[i];
1856            trans.mulP( offset );
1857            Point3F emitterPoint = offset;
1858
1859            if( pointInWater(offset ) )
1860            {
1861               U32 emitterOffset = VehicleData::VC_BUBBLE_EMITTER;
1862               if( mDamageEmitterList[emitterOffset] )
1863               {
1864                  mDamageEmitterList[emitterOffset]->emitParticles( emitterPoint, emitterPoint, Point3F( 0.0, 0.0, 1.0 ), getVelocity(), (U32)( dt * 1000 ) );
1865               }
1866            }
1867            else
1868            {
1869               if( mDamageEmitterList[j] )
1870               {
1871                  mDamageEmitterList[j]->emitParticles( emitterPoint, emitterPoint, Point3F( 0.0, 0.0, 1.0 ), getVelocity(), (U32)(dt * 1000));
1872               }
1873            }
1874         }
1875         break;
1876      }
1877   }
1878
1879}
1880
1881
1882//--------------------------------------------------------------------------
1883void Vehicle::updateFroth( F32 dt )
1884{
1885   // update bubbles
1886   Point3F moveDir = getVelocity();
1887
1888   Point3F contactPoint;
1889   if( !collidingWithWater( contactPoint ) )
1890   {
1891      if ( mWakeSound )
1892         mWakeSound->stop();
1893      return;
1894   }
1895
1896   F32 speed = moveDir.len();
1897   if( speed < mDataBlock->splashVelEpsilon ) speed = 0.0;
1898
1899   U32 emitRate = (U32)(speed * mDataBlock->splashFreqMod * dt);
1900
1901   U32 i;
1902
1903   if ( mWakeSound )
1904   {
1905      if ( !mWakeSound->isPlaying() )
1906         mWakeSound->play();
1907
1908      mWakeSound->setTransform( getTransform() );
1909      mWakeSound->setVelocity( getVelocity() );
1910   }
1911
1912   for( i=0; i<VehicleData::VC_NUM_SPLASH_EMITTERS; i++ )
1913   {
1914      if( mSplashEmitterList[i] )
1915      {
1916         mSplashEmitterList[i]->emitParticles( contactPoint, contactPoint, Point3F( 0.0, 0.0, 1.0 ),
1917                                               moveDir, emitRate );
1918      }
1919   }
1920
1921}
1922
1923
1924//--------------------------------------------------------------------------
1925// Returns true if vehicle is intersecting a water surface (roughly)
1926//--------------------------------------------------------------------------
1927bool Vehicle::collidingWithWater( Point3F &waterHeight )
1928{
1929   Point3F curPos = getPosition();
1930
1931   F32 height = mFabs( mObjBox.maxExtents.z - mObjBox.minExtents.z );
1932
1933   RayInfo rInfo;
1934   if( gClientContainer.castRay( curPos + Point3F(0.0, 0.0, height), curPos, WaterObjectType, &rInfo) )
1935   {
1936      waterHeight = rInfo.point;
1937      return true;
1938   }
1939
1940   return false;
1941}
1942
1943void Vehicle::setEnergyLevel(F32 energy)
1944{
1945   Parent::setEnergyLevel(energy);
1946   setMaskBits(EnergyMask);
1947}
1948
1949void Vehicle::prepBatchRender( SceneRenderState *state, S32 mountedImageIndex )
1950{
1951   Parent::prepBatchRender( state, mountedImageIndex );
1952
1953   if ( !gShowBoundingBox )
1954      return;
1955
1956   if ( mountedImageIndex != -1 )
1957   {
1958      ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
1959      ri->renderDelegate.bind( this, &Vehicle::_renderMuzzleVector );
1960      ri->objectIndex = mountedImageIndex;
1961      ri->type = RenderPassManager::RIT_Editor;
1962      state->getRenderPass()->addInst( ri );
1963      return;
1964   }
1965
1966   ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
1967   ri->renderDelegate.bind( this, &Vehicle::_renderMassAndContacts );
1968   ri->type = RenderPassManager::RIT_Editor;
1969   state->getRenderPass()->addInst( ri );
1970}
1971
1972void Vehicle::_renderMassAndContacts( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat )
1973{
1974   GFXStateBlockDesc desc;
1975   desc.setBlend(false, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha);
1976   desc.setZReadWrite(false,true);
1977   desc.fillMode = GFXFillWireframe;
1978
1979   // Render the mass center.   
1980   GFX->getDrawUtil()->drawCube(desc, Point3F(0.1f,0.1f,0.1f),mDataBlock->massCenter, ColorI(255, 255, 255), &mRenderObjToWorld);
1981
1982   // Now render all the contact points.
1983   for (S32 i = 0; i < mCollisionList.getCount(); i++)
1984   {
1985      const Collision& collision = mCollisionList[i];
1986      GFX->getDrawUtil()->drawCube(desc, Point3F(0.05f,0.05f,0.05f),collision.point, ColorI(0, 0, 255));
1987   }
1988
1989   // Finally render the normals as one big batch.
1990   PrimBuild::begin(GFXLineList, mCollisionList.getCount() * 2);
1991   for (S32 i = 0; i < mCollisionList.getCount(); i++)
1992   {
1993      const Collision& collision = mCollisionList[i];
1994      PrimBuild::color3f(1, 1, 1);
1995      PrimBuild::vertex3fv(collision.point);
1996      PrimBuild::vertex3fv(collision.point + collision.normal * 0.05f);
1997   }
1998   PrimBuild::end();
1999
2000   // Build and render the collision polylist which is returned
2001   // in the server's world space.
2002   ClippedPolyList polyList;
2003   polyList.mPlaneList.setSize(6);
2004   polyList.mPlaneList[0].set(getWorldBox().minExtents,VectorF(-1,0,0));
2005   polyList.mPlaneList[1].set(getWorldBox().minExtents,VectorF(0,-1,0));
2006   polyList.mPlaneList[2].set(getWorldBox().minExtents,VectorF(0,0,-1));
2007   polyList.mPlaneList[3].set(getWorldBox().maxExtents,VectorF(1,0,0));
2008   polyList.mPlaneList[4].set(getWorldBox().maxExtents,VectorF(0,1,0));
2009   polyList.mPlaneList[5].set(getWorldBox().maxExtents,VectorF(0,0,1));
2010   Box3F dummyBox;
2011   SphereF dummySphere;
2012   buildPolyList(PLC_Collision, &polyList, dummyBox, dummySphere);
2013   //polyList.render();
2014}
2015
2016void Vehicle::_renderMuzzleVector( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat )
2017{
2018   const U32 index = ri->objectIndex;
2019
2020   AssertFatal( index > 0 && index < MaxMountedImages, "Vehicle::_renderMuzzleVector() - Bad object index!" );
2021   AssertFatal( mMountedImageList[index].dataBlock, "Vehicle::_renderMuzzleVector() - Bad object index!" );
2022
2023   Point3F muzzlePoint, muzzleVector, endpoint;
2024   getMuzzlePoint(index, &muzzlePoint);
2025   getMuzzleVector(index, &muzzleVector);
2026   endpoint = muzzlePoint + muzzleVector * 250;
2027
2028   if (mSolidSB.isNull())
2029   {
2030      GFXStateBlockDesc desc;
2031      desc.setBlend(false, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha);
2032      desc.setZReadWrite(false);
2033      mSolidSB = GFX->createStateBlock(desc);
2034   }
2035
2036   GFX->setStateBlock(mSolidSB);
2037
2038   PrimBuild::begin(GFXLineList, 2);
2039
2040   PrimBuild::color4f(0, 1, 0, 1);
2041   PrimBuild::vertex3fv(muzzlePoint);
2042   PrimBuild::vertex3fv(endpoint);
2043
2044   PrimBuild::end();
2045}
2046