//----------------------------------------------------------------------------- // Copyright (c) 2012 GarageGames, LLC // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //----------------------------------------------------------------------------- #include "platform/platform.h" #include "T3D/rigidShape.h" #include "app/game.h" #include "math/mMath.h" #include "console/simBase.h" #include "console/console.h" #include "console/consoleTypes.h" #include "collision/clippedPolyList.h" #include "collision/planeExtractor.h" #include "T3D/gameBase/moveManager.h" #include "core/stream/bitStream.h" #include "core/dnet.h" #include "T3D/gameBase/gameConnection.h" #include "ts/tsShapeInstance.h" #include "math/mathIO.h" #include "scene/sceneRenderState.h" #include "scene/sceneManager.h" #include "T3D/fx/cameraFXMgr.h" #include "T3D/trigger.h" #include "T3D/item.h" #include "gfx/primBuilder.h" #include "gfx/gfxDrawUtil.h" #include "sfx/sfxTypes.h" #include "sfx/sfxSystem.h" #include "T3D/fx/particleEmitter.h" #include "console/engineAPI.h" IMPLEMENT_CO_DATABLOCK_V1(RigidShapeData); ConsoleDocClass( RigidShapeData, "@brief Defines the physics properties for an individual RigidShapeData physics object.\n\n" "@tsexample\n" " datablock RigidShapeData( BouncingBoulder )\n" " {\n" " category = \"RigidShape\";\n" "\n" " shapeFile = \"~/data/shapes/boulder/boulder.dts\";\n" " emap = true;\n" "\n" " // Rigid Body\n" " mass = 500;\n" " massCenter = \"0 0 0\"; // Center of mass for rigid body\n" " massBox = \"0 0 0\"; // Size of box used for moment of inertia,\n" " // if zero it defaults to object bounding box\n" " drag = 0.2; // Drag coefficient\n" " bodyFriction = 0.2;\n" " bodyRestitution = 0.1;\n" " minImpactSpeed = 5; // Impacts over this invoke the script callback\n" " softImpactSpeed = 5; // Play SoftImpact Sound\n" " hardImpactSpeed = 15; // Play HardImpact Sound\n" " integration = 4; // Physics integration: TickSec/Rate\n" " collisionTol = 0.1; // Collision distance tolerance\n" " contactTol = 0.1; // Contact velocity tolerance\n" "\n" " minRollSpeed = 10;\n" "\n" " maxDrag = 0.5;\n" " minDrag = 0.01;\n" "\n" " dustHeight = 10;\n" "\n" " dragForce = 0.05;\n" " vertFactor = 0.05;\n" " };\n" "@endtsexample\n\n" "@see RigidShape\n" "@see ShapeBase\n\n" "@ingroup Physics\n" ); IMPLEMENT_CO_NETOBJECT_V1(RigidShape); ConsoleDocClass( RigidShape, "@brief The RigidShape class implements rigid-body physics for DTS objects in the world.\n\n" "\"Rigid body physics\" refers to a system whereby objects are assumed to have a finite size,\n" "equally distributed masses, and where deformations of the objects themselves are not accounted for.\n" "Uses the RigidShape class to control its physics.\n\n" "@tsexample\n" " datablock RigidShapeData( BouncingBoulder )\n" " {\n" " category = \"RigidShape\";\n" "\n" " shapeFile = \"~/data/shapes/boulder/boulder.dts\";\n" " emap = true;\n" "\n" " // Rigid Body\n" " mass = 500;\n" " massCenter = \"0 0 0\"; // Center of mass for rigid body\n" " massBox = \"0 0 0\"; // Size of box used for moment of inertia,\n" " // if zero it defaults to object bounding box\n" " drag = 0.2; // Drag coefficient\n" " bodyFriction = 0.2;\n" " bodyRestitution = 0.1;\n" " minImpactSpeed = 5; // Impacts over this invoke the script callback\n" " softImpactSpeed = 5; // Play SoftImpact Sound\n" " hardImpactSpeed = 15; // Play HardImpact Sound\n" " integration = 4; // Physics integration: TickSec/Rate\n" " collisionTol = 0.1; // Collision distance tolerance\n" " contactTol = 0.1; // Contact velocity tolerance\n" "\n" " minRollSpeed = 10;\n" "\n" " maxDrag = 0.5;\n" " minDrag = 0.01;\n" "\n" " dustHeight = 10;\n" "\n" " dragForce = 0.05;\n" " vertFactor = 0.05;\n" " };\n" "\n" " new RigidShape()\n" " {\n" " dataBlock = \"BouncingBoulder\";\n" " parentGroup = EWCreatorWindow.objectGroup;\n" " };\n" "@endtsexample\n\n" "@see RigidShapeData\n" "@see ShapeBase\n\n" "@ingroup Physics\n" ); IMPLEMENT_CALLBACK( RigidShape, onEnterLiquid, void, ( const char* objId, F32 waterCoverage, const char* liquidType ), ( objId, waterCoverage, liquidType ), "@brief Called whenever this RigidShape object enters liquid.\n\n" "@param objId The ID of the rigidShape object.\n" "@param waterCoverage Amount of water coverage the RigidShape has.\n" "@param liquidType Type of liquid that was entered.\n\n" "@tsexample\n" "// The RigidShape object falls in a body of liquid, causing the callback to occur.\n" "RigidShape::onEnterLiquid(%this,%objId,%waterCoverage,%liquidType)\n" " {\n" " // Code to run whenever this callback occurs.\n" " }\n" "@endtsexample\n\n" "@see ShapeBase\n\n" ); IMPLEMENT_CALLBACK( RigidShape, onLeaveLiquid, void, ( const char* objId, const char* liquidType ),( objId, liquidType ), "@brief Called whenever the RigidShape object exits liquid.\n\n" "@param objId The ID of the RigidShape object.\n" "@param liquidType Type if liquid that was exited.\n\n" "@tsexample\n" "// The RigidShape object exits in a body of liquid, causing the callback to occur.\n" "RigidShape::onLeaveLiquid(%this,%objId,%liquidType)\n" " {\n" " // Code to run whenever this callback occurs.\n" " }\n" "@endtsexample\n\n" "@see ShapeBase\n\n" ); //---------------------------------------------------------------------------- namespace { const U32 sMoveRetryCount = 3; // Client prediction const S32 sMaxWarpTicks = 3; // Max warp duration in ticks const S32 sMaxPredictionTicks = 30; // Number of ticks to predict const F32 sRigidShapeGravity = -20; // Physics and collision constants static F32 sRestTol = 0.5; // % of gravity energy to be at rest static S32 sRestCount = 10; // Consecutive ticks before comming to rest const U32 sCollisionMoveMask = ( TerrainObjectType | PlayerObjectType | StaticShapeObjectType | VehicleObjectType | VehicleBlockerObjectType ); const U32 sServerCollisionMask = sCollisionMoveMask; // ItemObjectType const U32 sClientCollisionMask = sCollisionMoveMask; void nonFilter(SceneObject* object,void *key) { SceneContainer::CallbackInfo* info = reinterpret_cast(key); object->buildPolyList(info->context,info->polyList,info->boundingBox,info->boundingSphere); } } // namespace {} // Trigger objects that are not normally collided with. static U32 sTriggerMask = ItemObjectType | TriggerObjectType | CorpseObjectType; //---------------------------------------------------------------------------- RigidShapeData::RigidShapeData() { shadowEnable = true; body.friction = 0; body.restitution = 1; minImpactSpeed = 25; softImpactSpeed = 25; hardImpactSpeed = 50; minRollSpeed = 0; cameraRoll = true; cameraLag = 0; cameraDecay = 0; cameraOffset = 0; minDrag = 0; maxDrag = 0; integration = 1; collisionTol = 0.1f; contactTol = 0.1f; massCenter.set(0,0,0); massBox.set(0,0,0); drag = 0.7f; density = 4; for (S32 i = 0; i < Body::MaxSounds; i++) body.sound[i] = 0; dustEmitter = NULL; dustID = 0; dustHeight = 1.0; dMemset( splashEmitterList, 0, sizeof( splashEmitterList ) ); dMemset( splashEmitterIDList, 0, sizeof( splashEmitterIDList ) ); splashFreqMod = 300.0; splashVelEpsilon = 0.50; exitSplashSoundVel = 2.0; softSplashSoundVel = 1.0; medSplashSoundVel = 2.0; hardSplashSoundVel = 3.0; dMemset(waterSound, 0, sizeof(waterSound)); dragForce = 0; vertFactor = 0.25; dustTrailEmitter = NULL; dustTrailID = 0; } RigidShapeData::~RigidShapeData() { } //---------------------------------------------------------------------------- bool RigidShapeData::onAdd() { if(!Parent::onAdd()) return false; return true; } bool RigidShapeData::preload(bool server, String &errorStr) { if (!Parent::preload(server, errorStr)) return false; // RigidShape objects must define a collision detail if (!collisionDetails.size() || collisionDetails[0] == -1) { Con::errorf("RigidShapeData::preload failed: Rigid shapes must define a collision-1 detail"); errorStr = String::ToString("RigidShapeData: Couldn't load shape \"%s\"",shapeName); return false; } // Resolve objects transmitted from server if (!server) { for (S32 i = 0; i < Body::MaxSounds; i++) sfxResolve( &body.sound[ i ], errorStr ); } if( !dustEmitter && dustID != 0 ) { if( !Sim::findObject( dustID, dustEmitter ) ) { Con::errorf( ConsoleLogEntry::General, "RigidShapeData::preload Invalid packet, bad datablockId(dustEmitter): 0x%x", dustID ); } } U32 i; for( i=0; i 1.0f) { Con::warnf("RigidShapeData::preload: vert factor must be [0, 1]"); vertFactor = vertFactor < 0.0f ? 0.0f : 1.0f; } if( !dustTrailEmitter && dustTrailID != 0 ) { if( !Sim::findObject( dustTrailID, dustTrailEmitter ) ) { Con::errorf( ConsoleLogEntry::General, "RigidShapeData::preload Invalid packet, bad datablockId(dustTrailEmitter): 0x%x", dustTrailID ); } } return true; } //---------------------------------------------------------------------------- void RigidShapeData::packData(BitStream* stream) { Parent::packData(stream); stream->write(body.restitution); stream->write(body.friction); for( U32 i = 0; i < Body::MaxSounds; ++ i ) sfxWrite( stream, body.sound[ i ] ); stream->write(minImpactSpeed); stream->write(softImpactSpeed); stream->write(hardImpactSpeed); stream->write(minRollSpeed); stream->write(maxDrag); stream->write(minDrag); stream->write(integration); stream->write(collisionTol); stream->write(contactTol); mathWrite(*stream,massCenter); mathWrite(*stream,massBox); stream->writeFlag(cameraRoll); stream->write(cameraLag); stream->write(cameraDecay); stream->write(cameraOffset); stream->write( dustHeight ); stream->write(exitSplashSoundVel); stream->write(softSplashSoundVel); stream->write(medSplashSoundVel); stream->write(hardSplashSoundVel); // write the water sound profiles for( U32 i = 0; i < MaxSounds; ++ i ) sfxWrite( stream, waterSound[ i ] ); if (stream->writeFlag( dustEmitter )) stream->writeRangedU32( dustEmitter->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast ); for( U32 i = 0; i < VC_NUM_SPLASH_EMITTERS; ++ i ) { if( stream->writeFlag( splashEmitterList[i] != NULL ) ) stream->writeRangedU32( splashEmitterList[i]->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast ); } stream->write(splashFreqMod); stream->write(splashVelEpsilon); stream->write(dragForce); stream->write(vertFactor); if (stream->writeFlag( dustTrailEmitter )) stream->writeRangedU32( dustTrailEmitter->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast ); } void RigidShapeData::unpackData(BitStream* stream) { Parent::unpackData(stream); stream->read(&body.restitution); stream->read(&body.friction); for( U32 i = 0; i < Body::MaxSounds; i++) sfxRead( stream, &body.sound[ i ] ); stream->read(&minImpactSpeed); stream->read(&softImpactSpeed); stream->read(&hardImpactSpeed); stream->read(&minRollSpeed); stream->read(&maxDrag); stream->read(&minDrag); stream->read(&integration); stream->read(&collisionTol); stream->read(&contactTol); mathRead(*stream,&massCenter); mathRead(*stream,&massBox); cameraRoll = stream->readFlag(); stream->read(&cameraLag); stream->read(&cameraDecay); stream->read(&cameraOffset); stream->read( &dustHeight ); stream->read(&exitSplashSoundVel); stream->read(&softSplashSoundVel); stream->read(&medSplashSoundVel); stream->read(&hardSplashSoundVel); // write the water sound profiles for( U32 i = 0; i < MaxSounds; ++ i ) sfxRead( stream, &waterSound[ i ] ); if( stream->readFlag() ) dustID = (S32) stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); for( U32 i = 0; i < VC_NUM_SPLASH_EMITTERS; ++ i ) { if( stream->readFlag() ) splashEmitterIDList[i] = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast ); } stream->read(&splashFreqMod); stream->read(&splashVelEpsilon); stream->read(&dragForce); stream->read(&vertFactor); if( stream->readFlag() ) dustTrailID = (S32) stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); } //---------------------------------------------------------------------------- void RigidShapeData::initPersistFields() { addField("massCenter", TypePoint3F, Offset(massCenter, RigidShapeData), "Center of mass for rigid body."); addField("massBox", TypePoint3F, Offset(massBox, RigidShapeData), "Size of inertial box."); addField("bodyRestitution", TypeF32, Offset(body.restitution, RigidShapeData), "The percentage of kinetic energy kept by this object in a collision."); addField("bodyFriction", TypeF32, Offset(body.friction, RigidShapeData), "How much friction this object has. Lower values will cause the object to appear to be more slippery."); addField("minImpactSpeed", TypeF32, Offset(minImpactSpeed, RigidShapeData), "Minimum collision speed to classify collision as impact (triggers onImpact on server object)." ); addField("softImpactSpeed", TypeF32, Offset(softImpactSpeed, RigidShapeData), "Minimum speed at which this object must be travelling for the soft impact sound to be played."); addField("hardImpactSpeed", TypeF32, Offset(hardImpactSpeed, RigidShapeData), "Minimum speed at which the object must be travelling for the hard impact sound to be played."); addField("minRollSpeed", TypeF32, Offset(minRollSpeed, RigidShapeData)); addField("maxDrag", TypeF32, Offset(maxDrag, RigidShapeData), "Maximum drag available to this object."); addField("minDrag", TypeF32, Offset(minDrag, RigidShapeData), "Minimum drag available to this object."); addField("integration", TypeS32, Offset(integration, RigidShapeData), "Number of physics steps to process per tick."); addField("collisionTol", TypeF32, Offset(collisionTol, RigidShapeData), "Collision distance tolerance."); addField("contactTol", TypeF32, Offset(contactTol, RigidShapeData), "Contact velocity tolerance."); addGroup( "Forces" ); addField("dragForce", TypeF32, Offset(dragForce, RigidShapeData), "Used to simulate the constant drag acting on the object"); addField("vertFactor", TypeF32, Offset(vertFactor, RigidShapeData), "The scalar applied to the vertical portion of the velocity drag acting on a object."); endGroup( "Forces" ); addGroup( "Particle Effects" ); addField("dustEmitter", TYPEID< ParticleEmitterData >(), Offset(dustEmitter, RigidShapeData), "Array of pointers to ParticleEmitterData datablocks which will be used to emit particles at object/terrain contact point.\n"); addField("triggerDustHeight", TypeF32, Offset(triggerDustHeight, RigidShapeData), "Maximum height from the ground at which the object will generate dust.\n"); addField("dustHeight", TypeF32, Offset(dustHeight, RigidShapeData), "Height of dust effects.\n"); addField("dustTrailEmitter", TYPEID< ParticleEmitterData >(), Offset(dustTrailEmitter, RigidShapeData), "Particle emitter used to create a dust trail for the moving object.\n"); addField("splashEmitter", TYPEID< ParticleEmitterData >(), Offset(splashEmitterList, RigidShapeData), VC_NUM_SPLASH_EMITTERS, "Array of pointers to ParticleEmitterData datablocks which will generate splash effects.\n"); addField("splashFreqMod", TypeF32, Offset(splashFreqMod, RigidShapeData), "The simulated frequency modulation of a splash generated by this object. Multiplied along with speed and time elapsed when determining splash emition rate.\n"); addField("splashVelEpsilon", TypeF32, Offset(splashVelEpsilon, RigidShapeData), "The threshold speed at which we consider the object's movement to have stopped when updating splash effects.\n"); endGroup( "Particle Effects" ); addGroup( "Sounds" ); addField("softImpactSound", TypeSFXTrackName, Offset(body.sound[Body::SoftImpactSound], RigidShapeData), "Sound to play when body impacts with at least softImageSpeed but less than hardImpactSpeed." ); addField("hardImpactSound", TypeSFXTrackName, Offset(body.sound[Body::HardImpactSound], RigidShapeData), "Sound to play when body impacts with at least hardImpactSpeed." ); addField("exitSplashSoundVelocity", TypeF32, Offset(exitSplashSoundVel, RigidShapeData), "The minimum velocity at which the exit splash sound will be played when emerging from water.\n"); addField("softSplashSoundVelocity", TypeF32, Offset(softSplashSoundVel, RigidShapeData),"The minimum velocity at which the soft splash sound will be played when impacting water.\n"); addField("mediumSplashSoundVelocity", TypeF32, Offset(medSplashSoundVel, RigidShapeData), "The minimum velocity at which the medium splash sound will be played when impacting water.\n"); addField("hardSplashSoundVelocity", TypeF32, Offset(hardSplashSoundVel, RigidShapeData), "The minimum velocity at which the hard splash sound will be played when impacting water.\n"); addField("exitingWater", TypeSFXTrackName, Offset(waterSound[ExitWater], RigidShapeData), "The AudioProfile will be used to produce sounds when emerging from water.\n"); addField("impactWaterEasy", TypeSFXTrackName, Offset(waterSound[ImpactSoft], RigidShapeData), "The AudioProfile will be used to produce sounds when a soft impact with water occurs.\n"); addField("impactWaterMedium", TypeSFXTrackName, Offset(waterSound[ImpactMedium], RigidShapeData), "The AudioProfile will be used to produce sounds when a medium impact with water occurs.\n"); addField("impactWaterHard", TypeSFXTrackName, Offset(waterSound[ImpactHard], RigidShapeData), "The AudioProfile will be used to produce sounds when a hard impact with water occurs.\n"); addField("waterWakeSound", TypeSFXTrackName, Offset(waterSound[Wake], RigidShapeData), "The AudioProfile will be used to produce sounds when a water wake is displayed.\n"); endGroup( "Sounds" ); addGroup( "Camera" ); addField("cameraRoll", TypeBool, Offset(cameraRoll, RigidShapeData), "Specifies whether the camera's rotation matrix, and the render eye transform are multiplied during camera updates.\n"); addField("cameraLag", TypeF32, Offset(cameraLag, RigidShapeData), "Scalar amount by which the third person camera lags the object, relative to the object's linear velocity.\n"); addField("cameraDecay", TypeF32, Offset(cameraDecay, RigidShapeData), "Scalar rate at which the third person camera offset decays, per tick.\n"); addField("cameraOffset", TypeF32, Offset(cameraOffset, RigidShapeData), "The vertical offset of the object's camera.\n"); endGroup( "Camera" ); Parent::initPersistFields(); } //---------------------------------------------------------------------------- //---------------------------------------------------------------------------- //---------------------------------------------------------------------------- RigidShape::RigidShape() { mNetFlags.set(Ghostable); mDustTrailEmitter = NULL; mDataBlock = 0; // [rene, 27-Apr-11] WTH is a RigidShape a vehicle??? mTypeMask |= VehicleObjectType | DynamicShapeObjectType; mDelta.pos = Point3F(0,0,0); mDelta.posVec = Point3F(0,0,0); mDelta.warpTicks = mDelta.warpCount = 0; mDelta.dt = 1; mDelta.move = NullMove; mPredictionCount = 0; mDelta.cameraOffset.set(0,0,0); mDelta.cameraVec.set(0,0,0); mDelta.cameraRot.set(0,0,0); mDelta.cameraRotVec.set(0,0,0); mRigid.linPosition.set(0, 0, 0); mRigid.linVelocity.set(0, 0, 0); mRigid.angPosition.identity(); mRigid.angVelocity.set(0, 0, 0); mRigid.linMomentum.set(0, 0, 0); mRigid.angMomentum.set(0, 0, 0); mContacts.clear(); mCameraOffset.set(0,0,0); dMemset( mDustEmitterList, 0, sizeof( mDustEmitterList ) ); dMemset( mSplashEmitterList, 0, sizeof( mSplashEmitterList ) ); mDisableMove = false; // start frozen by default restCount = 0; inLiquid = false; } RigidShape::~RigidShape() { // } U32 RigidShape::getCollisionMask() { if (isServerObject()) return sServerCollisionMask; else return sClientCollisionMask; } Point3F RigidShape::getVelocity() const { return mRigid.linVelocity; } //---------------------------------------------------------------------------- bool RigidShape::onAdd() { if (!Parent::onAdd()) return false; // When loading from a mission script, the base SceneObject's transform // will have been set and needs to be transfered to the rigid body. mRigid.setTransform(mObjToWorld); // Initialize interpolation vars. mDelta.rot[1] = mDelta.rot[0] = mRigid.angPosition; mDelta.pos = mRigid.linPosition; mDelta.posVec = Point3F(0,0,0); // Create Emitters on the client if( isClientObject() ) { if( mDataBlock->dustEmitter ) { for( U32 i=0; ionNewDataBlock( mDataBlock->dustEmitter, false ); if( !mDustEmitterList[i]->registerObject() ) { Con::warnf( ConsoleLogEntry::General, "Could not register dust emitter for class: %s", mDataBlock->getName() ); delete mDustEmitterList[i]; mDustEmitterList[i] = NULL; } } } for( U32 j=0; jsplashEmitterList[j] ) { mSplashEmitterList[j] = new ParticleEmitter; mSplashEmitterList[j]->onNewDataBlock( mDataBlock->splashEmitterList[j], false ); if( !mSplashEmitterList[j]->registerObject() ) { Con::warnf( ConsoleLogEntry::General, "Could not register splash emitter for class: %s", mDataBlock->getName() ); delete mSplashEmitterList[j]; mSplashEmitterList[j] = NULL; } } } } // Create a new convex. AssertFatal(mDataBlock->collisionDetails[0] != -1, "Error, a rigid shape must have a collision-1 detail!"); mConvex.mObject = this; mConvex.pShapeBase = this; mConvex.hullId = 0; mConvex.box = mObjBox; mConvex.box.minExtents.convolve(mObjScale); mConvex.box.maxExtents.convolve(mObjScale); mConvex.findNodeTransform(); addToScene(); if( !isServerObject() ) { if( mDataBlock->dustTrailEmitter ) { mDustTrailEmitter = new ParticleEmitter; mDustTrailEmitter->onNewDataBlock( mDataBlock->dustTrailEmitter, false ); if( !mDustTrailEmitter->registerObject() ) { Con::warnf( ConsoleLogEntry::General, "Could not register dust emitter for class: %s", mDataBlock->getName() ); delete mDustTrailEmitter; mDustTrailEmitter = NULL; } } } if (isServerObject()) scriptOnAdd(); return true; } void RigidShape::onRemove() { scriptOnRemove(); removeFromScene(); U32 i=0; for( i=0; ideleteWhenEmpty(); mDustEmitterList[i] = NULL; } } for( i=0; ideleteWhenEmpty(); mSplashEmitterList[i] = NULL; } } Parent::onRemove(); } //---------------------------------------------------------------------------- void RigidShape::processTick(const Move* move) { Parent::processTick(move); // Warp to catch up to server if (mDelta.warpCount < mDelta.warpTicks) { mDelta.warpCount++; // Set new pos. mObjToWorld.getColumn(3,&mDelta.pos); mDelta.pos += mDelta.warpOffset; mDelta.rot[0] = mDelta.rot[1]; mDelta.rot[1].interpolate(mDelta.warpRot[0],mDelta.warpRot[1],F32(mDelta.warpCount)/mDelta.warpTicks); setPosition(mDelta.pos,mDelta.rot[1]); // Pos backstepping mDelta.posVec.x = -mDelta.warpOffset.x; mDelta.posVec.y = -mDelta.warpOffset.y; mDelta.posVec.z = -mDelta.warpOffset.z; } else { if (!move) { if (isGhost()) { // If we haven't run out of prediction time, // predict using the last known move. if (mPredictionCount-- <= 0) return; move = &mDelta.move; } else move = &NullMove; } // Process input move updateMove(move); // Save current rigid state interpolation mDelta.posVec = mRigid.linPosition; mDelta.rot[0] = mRigid.angPosition; // Update the physics based on the integration rate S32 count = mDataBlock->integration; if (!mDisableMove) updateWorkingCollisionSet(getCollisionMask()); for (U32 i = 0; i < count; i++) updatePos(TickSec / count); // Wrap up interpolation info mDelta.pos = mRigid.linPosition; mDelta.posVec -= mRigid.linPosition; mDelta.rot[1] = mRigid.angPosition; // Update container database setPosition(mRigid.linPosition, mRigid.angPosition); setMaskBits(PositionMask); updateContainer(); } } void RigidShape::interpolateTick(F32 dt) { Parent::interpolateTick(dt); if(dt == 0.0f) setRenderPosition(mDelta.pos, mDelta.rot[1]); else { QuatF rot; rot.interpolate(mDelta.rot[1], mDelta.rot[0], dt); Point3F pos = mDelta.pos + mDelta.posVec * dt; setRenderPosition(pos,rot); } mDelta.dt = dt; } void RigidShape::advanceTime(F32 dt) { Parent::advanceTime(dt); updateFroth(dt); // Update 3rd person camera offset. Camera update is done // here as it's a client side only animation. mCameraOffset -= (mCameraOffset * mDataBlock->cameraDecay + mRigid.linVelocity * mDataBlock->cameraLag) * dt; } //---------------------------------------------------------------------------- bool RigidShape::onNewDataBlock(GameBaseData* dptr, bool reload) { mDataBlock = dynamic_cast(dptr); if (!mDataBlock || !Parent::onNewDataBlock(dptr, reload)) return false; // Update Rigid Info mRigid.mass = mDataBlock->mass; mRigid.oneOverMass = 1 / mRigid.mass; mRigid.friction = mDataBlock->body.friction; mRigid.restitution = mDataBlock->body.restitution; mRigid.setCenterOfMass(mDataBlock->massCenter); // Ignores massBox, just set sphere for now. Derived objects // can set what they want. mRigid.setObjectInertia(); scriptOnNewDataBlock(); return true; } //---------------------------------------------------------------------------- void RigidShape::getCameraParameters(F32 *min,F32* max,Point3F* off,MatrixF* rot) { *min = mDataBlock->cameraMinDist; *max = mDataBlock->cameraMaxDist; off->set(0,0,mDataBlock->cameraOffset); rot->identity(); } //---------------------------------------------------------------------------- void RigidShape::getCameraTransform(F32* pos,MatrixF* mat) { // Returns camera to world space transform // Handles first person / third person camera position if (isServerObject() && mShapeInstance) mShapeInstance->animateNodeSubtrees(true); if (*pos == 0) { getRenderEyeTransform(mat); return; } // Get the shape's camera parameters. F32 min,max; MatrixF rot; Point3F offset; getCameraParameters(&min,&max,&offset,&rot); // Start with the current eye position MatrixF eye; getRenderEyeTransform(&eye); // Build a transform that points along the eye axis // but where the Z axis is always up. if (mDataBlock->cameraRoll) mat->mul(eye,rot); else { MatrixF cam(1); VectorF x,y,z(0,0,1); eye.getColumn(1, &y); mCross(y, z, &x); x.normalize(); mCross(x, y, &z); z.normalize(); cam.setColumn(0,x); cam.setColumn(1,y); cam.setColumn(2,z); mat->mul(cam,rot); } // Camera is positioned straight back along the eye's -Y axis. // A ray is cast to make sure the camera doesn't go through // anything solid. VectorF vp,vec; vp.x = vp.z = 0; vp.y = -(max - min) * *pos; eye.mulV(vp,&vec); // Use the camera node as the starting position if it exists. Point3F osp,sp; if (mDataBlock->cameraNode != -1) { mShapeInstance->mNodeTransforms[mDataBlock->cameraNode].getColumn(3,&osp); getRenderTransform().mulP(osp,&sp); } else eye.getColumn(3,&sp); // Make sure we don't hit ourself... disableCollision(); if (isMounted()) getObjectMount()->disableCollision(); // Cast the ray into the container database to see if we're going // to hit anything. RayInfo collision; Point3F ep = sp + vec + offset + mCameraOffset; if (mContainer->castRay(sp, ep, ~(WaterObjectType | GameBaseObjectType | DefaultObjectType), &collision) == true) { // Shift the collision point back a little to try and // avoid clipping against the front camera plane. F32 t = collision.t - (-mDot(vec, collision.normal) / vec.len()) * 0.1; if (t > 0.0f) ep = sp + offset + mCameraOffset + (vec * t); else eye.getColumn(3,&ep); } mat->setColumn(3,ep); // Re-enable our collision. if (isMounted()) getObjectMount()->enableCollision(); enableCollision(); // Apply Camera FX. mat->mul( gCamFXMgr.getTrans() ); } //---------------------------------------------------------------------------- void RigidShape::getVelocity(const Point3F& r, Point3F* v) { mRigid.getVelocity(r, v); } void RigidShape::applyImpulse(const Point3F &pos, const Point3F &impulse) { Point3F r; mRigid.getOriginVector(pos,&r); mRigid.applyImpulse(r, impulse); } //---------------------------------------------------------------------------- void RigidShape::updateMove(const Move* move) { mDelta.move = *move; } //---------------------------------------------------------------------------- void RigidShape::setPosition(const Point3F& pos,const QuatF& rot) { MatrixF mat; rot.setMatrix(&mat); mat.setColumn(3,pos); Parent::setTransform(mat); } void RigidShape::setRenderPosition(const Point3F& pos, const QuatF& rot) { MatrixF mat; rot.setMatrix(&mat); mat.setColumn(3,pos); Parent::setRenderTransform(mat); } void RigidShape::setTransform(const MatrixF& newMat) { mRigid.setTransform(newMat); Parent::setTransform(newMat); mRigid.atRest = false; mContacts.clear(); } void RigidShape::forceClientTransform() { setMaskBits(ForceMoveMask); } //----------------------------------------------------------------------------- void RigidShape::disableCollision() { Parent::disableCollision(); } void RigidShape::enableCollision() { Parent::enableCollision(); } //---------------------------------------------------------------------------- /** Update the physics */ void RigidShape::updatePos(F32 dt) { Point3F origVelocity = mRigid.linVelocity; // Update internal forces acting on the body. mRigid.clearForces(); updateForces(dt); // Update collision information based on our current pos. bool collided = false; if (!mRigid.atRest && !mDisableMove) { collided = updateCollision(dt); // Now that all the forces have been processed, lets // see if we're at rest. Basically, if the kinetic energy of // the shape is less than some percentage of the energy added // by gravity for a short period, we're considered at rest. // This should really be part of the rigid class... if (mCollisionList.getCount()) { F32 k = mRigid.getKineticEnergy(); F32 G = sRigidShapeGravity * dt; F32 Kg = 0.5 * mRigid.mass * G * G; if (k < sRestTol * Kg && ++restCount > sRestCount) mRigid.setAtRest(); } else restCount = 0; } // Integrate forward if (!mRigid.atRest && !mDisableMove) mRigid.integrate(dt); // Deal with client and server scripting, sounds, etc. if (isServerObject()) { // Check triggers and other objects that we normally don't // collide with. This function must be called before notifyCollision // as it will queue collision. checkTriggers(); // Invoke the onCollision notify callback for all the objects // we've just hit. notifyCollision(); // Server side impact script callback if (collided) { VectorF collVec = mRigid.linVelocity - origVelocity; F32 collSpeed = collVec.len(); if (collSpeed > mDataBlock->minImpactSpeed) onImpact(collVec); } // Water script callbacks if (!inLiquid && mWaterCoverage != 0.0f) { onEnterLiquid_callback(getIdString(), mWaterCoverage, mLiquidType.c_str() ); inLiquid = true; } else if (inLiquid && mWaterCoverage == 0.0f) { onLeaveLiquid_callback(getIdString(), mLiquidType.c_str() ); inLiquid = false; } } else { // Play impact sounds on the client. if (collided) { F32 collSpeed = (mRigid.linVelocity - origVelocity).len(); S32 impactSound = -1; if (collSpeed >= mDataBlock->hardImpactSpeed) impactSound = RigidShapeData::Body::HardImpactSound; else if (collSpeed >= mDataBlock->softImpactSpeed) impactSound = RigidShapeData::Body::SoftImpactSound; if (impactSound != -1 && mDataBlock->body.sound[impactSound] != NULL) SFX->playOnce(mDataBlock->body.sound[impactSound], &getTransform()); } // Water volume sounds F32 vSpeed = getVelocity().len(); if (!inLiquid && mWaterCoverage >= 0.8f) { if (vSpeed >= mDataBlock->hardSplashSoundVel) SFX->playOnce(mDataBlock->waterSound[RigidShapeData::ImpactHard], &getTransform()); else if (vSpeed >= mDataBlock->medSplashSoundVel) SFX->playOnce(mDataBlock->waterSound[RigidShapeData::ImpactMedium], &getTransform()); else if (vSpeed >= mDataBlock->softSplashSoundVel) SFX->playOnce(mDataBlock->waterSound[RigidShapeData::ImpactSoft], &getTransform()); inLiquid = true; } else if(inLiquid && mWaterCoverage < 0.8f) { if (vSpeed >= mDataBlock->exitSplashSoundVel) SFX->playOnce(mDataBlock->waterSound[RigidShapeData::ExitWater], &getTransform()); inLiquid = false; } } } //---------------------------------------------------------------------------- void RigidShape::updateForces(F32 /*dt*/) { if (mDisableMove) return; Point3F gravForce(0, 0, sRigidShapeGravity * mRigid.mass * mGravityMod); MatrixF currTransform; mRigid.getTransform(&currTransform); Point3F torque(0, 0, 0); Point3F force(0, 0, 0); Point3F vel = mRigid.linVelocity; // Gravity force += gravForce; // Apply drag Point3F vDrag = mRigid.linVelocity; vDrag.convolve(Point3F(1, 1, mDataBlock->vertFactor)); force -= vDrag * mDataBlock->dragForce; // Add in physical zone force force += mAppliedForce; // Container buoyancy & drag force += Point3F(0, 0,-mBuoyancy * sRigidShapeGravity * mRigid.mass * mGravityMod); force -= mRigid.linVelocity * mDrag; torque -= mRigid.angMomentum * mDrag; mRigid.force = force; mRigid.torque = torque; } //----------------------------------------------------------------------------- /** Update collision information Update the convex state and check for collisions. If the object is in collision, impact and contact forces are generated. */ bool RigidShape::updateCollision(F32 dt) { // Update collision information MatrixF mat,cmat; mConvex.transform = &mat; mRigid.getTransform(&mat); cmat = mConvex.getTransform(); mCollisionList.clear(); CollisionState *state = mConvex.findClosestState(cmat, getScale(), mDataBlock->collisionTol); if (state && state->dist <= mDataBlock->collisionTol) { //resolveDisplacement(ns,state,dt); mConvex.getCollisionInfo(cmat, getScale(), &mCollisionList, mDataBlock->collisionTol); } // Resolve collisions bool collided = resolveCollision(mRigid,mCollisionList); resolveContacts(mRigid,mCollisionList,dt); return collided; } //---------------------------------------------------------------------------- /** Resolve collision impacts Handle collision impacts, as opposed to contacts. Impulses are calculated based on standard collision resolution formulas. */ bool RigidShape::resolveCollision(Rigid& ns,CollisionList& cList) { // Apply impulses to resolve collision bool colliding, collided = false; do { colliding = false; for (S32 i = 0; i < cList.getCount(); i++) { Collision& c = cList[i]; if (c.distance < mDataBlock->collisionTol) { // Velocity into surface Point3F v,r; ns.getOriginVector(c.point,&r); ns.getVelocity(r,&v); F32 vn = mDot(v,c.normal); // Only interested in velocities greater than sContactTol, // velocities less than that will be dealt with as contacts // "constraints". if (vn < -mDataBlock->contactTol) { // Apply impulses to the rigid body to keep it from // penetrating the surface. ns.resolveCollision(cList[i].point, cList[i].normal); colliding = collided = true; // Keep track of objects we collide with if (!isGhost() && c.object->getTypeMask() & ShapeBaseObjectType) { ShapeBase* col = static_cast(c.object); queueCollision(col,v - col->getVelocity()); } } } } } while (colliding); return collided; } //---------------------------------------------------------------------------- /** Resolve contact forces Resolve contact forces using the "penalty" method. Forces are generated based on the depth of penetration and the moment of inertia at the point of contact. */ bool RigidShape::resolveContacts(Rigid& ns,CollisionList& cList,F32 dt) { // Use spring forces to manage contact constraints. bool collided = false; Point3F t,p(0,0,0),l(0,0,0); for (S32 i = 0; i < cList.getCount(); i++) { Collision& c = cList[i]; if (c.distance < mDataBlock->collisionTol) { // Velocity into the surface Point3F v,r; ns.getOriginVector(c.point,&r); ns.getVelocity(r,&v); F32 vn = mDot(v,c.normal); // Only interested in velocities less than mDataBlock->contactTol, // velocities greater than that are dealt with as collisions. if (mFabs(vn) < mDataBlock->contactTol) { collided = true; // Penetration force. This is actually a spring which // will seperate the body from the collision surface. F32 zi = 2 * mFabs(mRigid.getZeroImpulse(r,c.normal)); F32 s = (mDataBlock->collisionTol - c.distance) * zi - ((vn / mDataBlock->contactTol) * zi); Point3F f = c.normal * s; // Friction impulse, calculated as a function of the // amount of force it would take to stop the motion // perpendicular to the normal. Point3F uv = v - (c.normal * vn); F32 ul = uv.len(); if (s > 0 && ul) { uv /= -ul; F32 u = ul * ns.getZeroImpulse(r,uv); s *= mRigid.friction; if (u > s) u = s; f += uv * u; } // Accumulate forces p += f; mCross(r,f,&t); l += t; } } } // Contact constraint forces act over time... ns.linMomentum += p * dt; ns.angMomentum += l * dt; ns.updateVelocity(); return true; } //---------------------------------------------------------------------------- bool RigidShape::resolveDisplacement(Rigid& ns,CollisionState *state, F32 dt) { SceneObject* obj = (state->a->getObject() == this)? state->b->getObject(): state->a->getObject(); if (obj->isDisplacable() && ((obj->getTypeMask() & ShapeBaseObjectType) != 0)) { // Try to displace the object by the amount we're trying to move Point3F objNewMom = ns.linVelocity * obj->getMass() * 1.1f; Point3F objOldMom = obj->getMomentum(); Point3F objNewVel = objNewMom / obj->getMass(); Point3F myCenter; Point3F theirCenter; getWorldBox().getCenter(&myCenter); obj->getWorldBox().getCenter(&theirCenter); if (mDot(myCenter - theirCenter, objNewMom) >= 0.0f || objNewVel.len() < 0.01) { objNewMom = (theirCenter - myCenter); objNewMom.normalize(); objNewMom *= 1.0f * obj->getMass(); objNewVel = objNewMom / obj->getMass(); } obj->setMomentum(objNewMom); if (obj->displaceObject(objNewVel * 1.1f * dt) == true) { // Queue collision and change in velocity VectorF dv = (objOldMom - objNewMom) / obj->getMass(); queueCollision(static_cast(obj), dv); return true; } } return false; } //---------------------------------------------------------------------------- void RigidShape::updateWorkingCollisionSet(const U32 mask) { Box3F convexBox = mConvex.getBoundingBox(getTransform(), getScale()); F32 len = (mRigid.linVelocity.len() + 50) * TickSec; F32 l = (len * 1.1) + 0.1; // fudge factor convexBox.minExtents -= Point3F(l, l, l); convexBox.maxExtents += Point3F(l, l, l); disableCollision(); mConvex.updateWorkingList(convexBox, mask); enableCollision(); } //---------------------------------------------------------------------------- /** Check collisions with trigger and items Perform a container search using the current bounding box of the main body, wheels are not included. This method should only be called on the server. */ void RigidShape::checkTriggers() { Box3F bbox = mConvex.getBoundingBox(getTransform(), getScale()); gServerContainer.findObjects(bbox,sTriggerMask,findCallback,this); } /** The callback used in by the checkTriggers() method. The checkTriggers method uses a container search which will invoke this callback on each obj that matches. */ void RigidShape::findCallback(SceneObject* obj,void *key) { RigidShape* shape = reinterpret_cast(key); U32 objectMask = obj->getTypeMask(); // Check: triggers, corpses and items, basically the same things // that the player class checks for if (objectMask & TriggerObjectType) { Trigger* pTrigger = static_cast(obj); pTrigger->potentialEnterObject(shape); } else if (objectMask & CorpseObjectType) { ShapeBase* col = static_cast(obj); shape->queueCollision(col,shape->getVelocity() - col->getVelocity()); } else if (objectMask & ItemObjectType) { Item* item = static_cast(obj); if (shape != item->getCollisionObject()) shape->queueCollision(item,shape->getVelocity() - item->getVelocity()); } } //---------------------------------------------------------------------------- void RigidShape::writePacketData(GameConnection *connection, BitStream *stream) { Parent::writePacketData(connection, stream); mathWrite(*stream, mRigid.linPosition); mathWrite(*stream, mRigid.angPosition); mathWrite(*stream, mRigid.linMomentum); mathWrite(*stream, mRigid.angMomentum); stream->writeFlag(mRigid.atRest); stream->writeFlag(mContacts.getCount() == 0); stream->writeFlag(mDisableMove); stream->setCompressionPoint(mRigid.linPosition); } void RigidShape::readPacketData(GameConnection *connection, BitStream *stream) { Parent::readPacketData(connection, stream); mathRead(*stream, &mRigid.linPosition); mathRead(*stream, &mRigid.angPosition); mathRead(*stream, &mRigid.linMomentum); mathRead(*stream, &mRigid.angMomentum); mRigid.atRest = stream->readFlag(); if (stream->readFlag()) mContacts.clear(); mRigid.updateInertialTensor(); mRigid.updateVelocity(); mDisableMove = stream->readFlag(); stream->setCompressionPoint(mRigid.linPosition); } //---------------------------------------------------------------------------- U32 RigidShape::packUpdate(NetConnection *con, U32 mask, BitStream *stream) { U32 retMask = Parent::packUpdate(con, mask, stream); // The rest of the data is part of the control object packet update. // If we're controlled by this client, we don't need to send it. if (stream->writeFlag(getControllingClient() == con && !(mask & InitialUpdateMask))) return retMask; mDelta.move.pack(stream); if (stream->writeFlag(mask & PositionMask)) { stream->writeFlag(mask & ForceMoveMask); stream->writeCompressedPoint(mRigid.linPosition); mathWrite(*stream, mRigid.angPosition); mathWrite(*stream, mRigid.linMomentum); mathWrite(*stream, mRigid.angMomentum); stream->writeFlag(mRigid.atRest); } if(stream->writeFlag(mask & FreezeMask)) stream->writeFlag(mDisableMove); return retMask; } void RigidShape::unpackUpdate(NetConnection *con, BitStream *stream) { Parent::unpackUpdate(con,stream); if (stream->readFlag()) return; mDelta.move.unpack(stream); if (stream->readFlag()) { // Check if we need to jump to the given transform // rather than interpolate to it. bool forceUpdate = stream->readFlag(); mPredictionCount = sMaxPredictionTicks; F32 speed = mRigid.linVelocity.len(); mDelta.warpRot[0] = mRigid.angPosition; // Read in new position and momentum values stream->readCompressedPoint(&mRigid.linPosition); mathRead(*stream, &mRigid.angPosition); mathRead(*stream, &mRigid.linMomentum); mathRead(*stream, &mRigid.angMomentum); mRigid.atRest = stream->readFlag(); mRigid.updateVelocity(); if (!forceUpdate && isProperlyAdded()) { // Determine number of ticks to warp based on the average // of the client and server velocities. Point3F cp = mDelta.pos + mDelta.posVec * mDelta.dt; mDelta.warpOffset = mRigid.linPosition - cp; // Calc the distance covered in one tick as the average of // the old speed and the new speed from the server. F32 dt,as = (speed + mRigid.linVelocity.len()) * 0.5 * TickSec; // Cal how many ticks it will take to cover the warp offset. // If it's less than what's left in the current tick, we'll just // warp in the remaining time. if (!as || (dt = mDelta.warpOffset.len() / as) > sMaxWarpTicks) dt = mDelta.dt + sMaxWarpTicks; else dt = (dt <= mDelta.dt)? mDelta.dt : mCeil(dt - mDelta.dt) + mDelta.dt; // Adjust current frame interpolation if (mDelta.dt) { mDelta.pos = cp + (mDelta.warpOffset * (mDelta.dt / dt)); mDelta.posVec = (cp - mDelta.pos) / mDelta.dt; QuatF cr; cr.interpolate(mDelta.rot[1],mDelta.rot[0],mDelta.dt); mDelta.rot[1].interpolate(cr,mRigid.angPosition,mDelta.dt / dt); mDelta.rot[0].extrapolate(mDelta.rot[1],cr,mDelta.dt); } // Calculated multi-tick warp mDelta.warpCount = 0; mDelta.warpTicks = (S32)(mFloor(dt)); if (mDelta.warpTicks) { mDelta.warpOffset = mRigid.linPosition - mDelta.pos; mDelta.warpOffset /= mDelta.warpTicks; mDelta.warpRot[0] = mDelta.rot[1]; mDelta.warpRot[1] = mRigid.angPosition; } } else { // Set the shape to the server position mDelta.dt = 0; mDelta.pos = mRigid.linPosition; mDelta.posVec.set(0,0,0); mDelta.rot[1] = mDelta.rot[0] = mRigid.angPosition; mDelta.warpCount = mDelta.warpTicks = 0; setPosition(mRigid.linPosition, mRigid.angPosition); } } if(stream->readFlag()) mDisableMove = stream->readFlag(); } //---------------------------------------------------------------------------- void RigidShape::initPersistFields() { Parent::initPersistFields(); } //---------------------------------------------------------------------------- void RigidShape::updateLiftoffDust( F32 dt ) { Point3F offset( 0.0, 0.0, mDataBlock->dustHeight ); emitDust( mDustEmitterList[ 0 ], mDataBlock->triggerDustHeight, offset, ( U32 )( dt * 1000 ) ); } //-------------------------------------------------------------------------- void RigidShape::updateFroth( F32 dt ) { // update bubbles Point3F moveDir = getVelocity(); Point3F contactPoint; F32 speed = moveDir.len(); if( speed < mDataBlock->splashVelEpsilon ) speed = 0.0; U32 emitRate = (U32)(speed * mDataBlock->splashFreqMod * dt); U32 i; for( i=0; iemitParticles( contactPoint, contactPoint, Point3F( 0.0, 0.0, 1.0 ), moveDir, emitRate ); } } } //-------------------------------------------------------------------------- // Returns true if shape is intersecting a water surface (roughly) //-------------------------------------------------------------------------- bool RigidShape::collidingWithWater( Point3F &waterHeight ) { Point3F curPos = getPosition(); F32 height = mFabs( mObjBox.maxExtents.z - mObjBox.minExtents.z ); RayInfo rInfo; if( gClientContainer.castRay( curPos + Point3F(0.0, 0.0, height), curPos, WaterObjectType, &rInfo) ) { waterHeight = rInfo.point; return true; } return false; } void RigidShape::setEnergyLevel(F32 energy) { Parent::setEnergyLevel(energy); setMaskBits(EnergyMask); } void RigidShape::prepBatchRender( SceneRenderState *state, S32 mountedImageIndex ) { Parent::prepBatchRender( state, mountedImageIndex ); if ( !gShowBoundingBox ) return; ObjectRenderInst *ri = state->getRenderPass()->allocInst(); ri->renderDelegate.bind( this, &RigidShape::_renderMassAndContacts ); ri->type = RenderPassManager::RIT_Editor; state->getRenderPass()->addInst( ri ); } void RigidShape::_renderMassAndContacts( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat ) { // Box for the center of Mass GFXStateBlockDesc desc; desc.setBlend(false, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha); desc.setZReadWrite(false); desc.fillMode = GFXFillWireframe; GFX->getDrawUtil()->drawCube( desc, Point3F(0.1f,0.1f,0.1f), mDataBlock->massCenter, ColorI(255, 255, 255), &mRenderObjToWorld ); // Collision points... for (S32 i = 0; i < mCollisionList.getCount(); i++) { const Collision& collision = mCollisionList[i]; GFX->getDrawUtil()->drawCube( desc, Point3F(0.05f,0.05f,0.05f), collision.point, ColorI(0, 0, 255) ); } // Render the normals as one big batch... PrimBuild::begin(GFXLineList, mCollisionList.getCount() * 2); for (S32 i = 0; i < mCollisionList.getCount(); i++) { const Collision& collision = mCollisionList[i]; PrimBuild::color3f(1, 1, 1); PrimBuild::vertex3fv(collision.point); PrimBuild::vertex3fv(collision.point + collision.normal * 0.05f); } PrimBuild::end(); // Build and render the collision polylist which is returned // in the server's world space. ClippedPolyList polyList; polyList.mPlaneList.setSize(6); polyList.mPlaneList[0].set(getWorldBox().minExtents,VectorF(-1,0,0)); polyList.mPlaneList[1].set(getWorldBox().minExtents,VectorF(0,-1,0)); polyList.mPlaneList[2].set(getWorldBox().minExtents,VectorF(0,0,-1)); polyList.mPlaneList[3].set(getWorldBox().maxExtents,VectorF(1,0,0)); polyList.mPlaneList[4].set(getWorldBox().maxExtents,VectorF(0,1,0)); polyList.mPlaneList[5].set(getWorldBox().maxExtents,VectorF(0,0,1)); Box3F dummyBox; SphereF dummySphere; buildPolyList(PLC_Collision, &polyList, dummyBox, dummySphere); //polyList.render(); } void RigidShape::reset() { mRigid.clearForces(); mRigid.setAtRest(); } void RigidShape::freezeSim(bool frozen) { mDisableMove = frozen; setMaskBits(FreezeMask); } DefineEngineMethod( RigidShape, reset, void, (),, "@brief Clears physic forces from the shape and sets it at rest.\n\n" "@tsexample\n" "// Inform the RigidShape object to reset.\n" "%thisRigidShape.reset();\n" "@endtsexample\n\n" "@see ShapeBaseData") { object->reset(); } DefineEngineMethod( RigidShape, freezeSim, void, (bool isFrozen),, "@brief Enables or disables the physics simulation on the RigidShape object.\n\n" "@param isFrozen Boolean frozen state to set the object.\n" "@tsexample\n" "// Define the frozen state.\n" "%isFrozen = \"true\";\n\n" "// Inform the object of the defined frozen state\n" "%thisRigidShape.freezeSim(%isFrozen);\n" "@endtsexample\n\n" "@see ShapeBaseData") { object->freezeSim(isFrozen); } DefineEngineMethod( RigidShape, forceClientTransform, void, (),, "@brief Forces the client to jump to the RigidShape's transform rather then warp to it.\n\n") { if(object->isServerObject()) { object->forceClientTransform(); } }