Torque3D/Engine/source/T3D/rigidShape.cpp
2014-10-24 09:17:48 +11:00

1735 lines
56 KiB
C++

//-----------------------------------------------------------------------------
// 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, const char* 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<SceneContainer::CallbackInfo*>(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<VC_NUM_SPLASH_EMITTERS; i++ )
{
if( !splashEmitterList[i] && splashEmitterIDList[i] != 0 )
{
if( !Sim::findObject( splashEmitterIDList[i], splashEmitterList[i] ) )
{
Con::errorf( ConsoleLogEntry::General, "RigidShapeData::preload Invalid packet, bad datablockId(splashEmitter): 0x%x", splashEmitterIDList[i] );
}
}
}
if (dragForce <= 0.01f)
{
Con::warnf("RigidShapeData::preload: dragForce must be at least 0.01");
dragForce = 0.01f;
}
if (vertFactor < 0.0f || vertFactor > 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; i<RigidShapeData::VC_NUM_DUST_EMITTERS; i++ )
{
mDustEmitterList[i] = new ParticleEmitter;
mDustEmitterList[i]->onNewDataBlock( 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; j<RigidShapeData::VC_NUM_SPLASH_EMITTERS; j++ )
{
if( mDataBlock->splashEmitterList[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; i<RigidShapeData::VC_NUM_DUST_EMITTERS; i++ )
{
if( mDustEmitterList[i] )
{
mDustEmitterList[i]->deleteWhenEmpty();
mDustEmitterList[i] = NULL;
}
}
for( i=0; i<RigidShapeData::VC_NUM_SPLASH_EMITTERS; i++ )
{
if( mSplashEmitterList[i] )
{
mSplashEmitterList[i]->deleteWhenEmpty();
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<RigidShapeData*>(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(), Con::getFloatArg(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<ShapeBase*>(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<ShapeBase*>(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<RigidShape*>(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<Trigger*>(obj);
pTrigger->potentialEnterObject(shape);
}
else if (objectMask & CorpseObjectType) {
ShapeBase* col = static_cast<ShapeBase*>(obj);
shape->queueCollision(col,shape->getVelocity() - col->getVelocity());
}
else if (objectMask & ItemObjectType) {
Item* item = static_cast<Item*>(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; i<RigidShapeData::VC_NUM_SPLASH_EMITTERS; i++ )
{
if( mSplashEmitterList[i] )
{
mSplashEmitterList[i]->emitParticles( 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<ObjectRenderInst>();
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();
}
}