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