vehicle.cpp
Engine/source/T3D/vehicles/vehicle.cpp
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
