|
@@ -47,7 +47,8 @@
|
|
|
#include "sfx/sfxSystem.h"
|
|
|
#include "T3D/fx/particleEmitter.h"
|
|
|
#include "console/engineAPI.h"
|
|
|
-
|
|
|
+#include "T3D/physics/physicsPlugin.h"
|
|
|
+#include "T3D/physics/physicsCollision.h"
|
|
|
|
|
|
IMPLEMENT_CO_DATABLOCK_V1(RigidShapeData);
|
|
|
|
|
@@ -152,45 +153,33 @@ ConsoleDocClass( RigidShape,
|
|
|
"@ingroup Physics\n"
|
|
|
);
|
|
|
|
|
|
+IMPLEMENT_CALLBACK(RigidShapeData, onEnterLiquid, void, (RigidShape* obj, F32 coverage, const char* type), (obj, coverage, type),
|
|
|
+ "Called when the vehicle enters liquid.\n"
|
|
|
+ "@param obj the Vehicle object\n"
|
|
|
+ "@param coverage percentage of the vehicle's bounding box covered by the liquid\n"
|
|
|
+ "@param type type of liquid the vehicle has entered\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"
|
|
|
-);
|
|
|
+IMPLEMENT_CALLBACK(RigidShapeData, onLeaveLiquid, void, (RigidShape* obj, const char* type), (obj, type),
|
|
|
+ "Called when the vehicle leaves liquid.\n"
|
|
|
+ "@param obj the Vehicle object\n"
|
|
|
+ "@param type type of liquid the vehicle has left\n");
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
+ static U32 sWorkingQueryBoxStaleThreshold = 10; // The maximum number of ticks that go by before
|
|
|
+ // the mWorkingQueryBox is considered stale and
|
|
|
+ // needs updating. Set to -1 to disable.
|
|
|
+
|
|
|
+ static F32 sWorkingQueryBoxSizeMultiplier = 2.0f; // How much larger should the mWorkingQueryBox be
|
|
|
+ // made when updating the working collision list.
|
|
|
+ // The larger this number the less often the working list
|
|
|
+ // will be updated due to motion, but any non-static shape
|
|
|
+ // that moves into the query box will not be noticed.
|
|
|
// 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
|
|
@@ -265,6 +254,7 @@ RigidShapeData::RigidShapeData()
|
|
|
softSplashSoundVel = 1.0;
|
|
|
medSplashSoundVel = 2.0;
|
|
|
hardSplashSoundVel = 3.0;
|
|
|
+ enablePhysicsRep = true;
|
|
|
|
|
|
dMemset(waterSound, 0, sizeof(waterSound));
|
|
|
|
|
@@ -390,6 +380,7 @@ void RigidShapeData::packData(BitStream* stream)
|
|
|
stream->write(softSplashSoundVel);
|
|
|
stream->write(medSplashSoundVel);
|
|
|
stream->write(hardSplashSoundVel);
|
|
|
+ stream->write(enablePhysicsRep);
|
|
|
|
|
|
// write the water sound profiles
|
|
|
for( U32 i = 0; i < MaxSounds; ++ i )
|
|
@@ -448,6 +439,7 @@ void RigidShapeData::unpackData(BitStream* stream)
|
|
|
stream->read(&softSplashSoundVel);
|
|
|
stream->read(&medSplashSoundVel);
|
|
|
stream->read(&hardSplashSoundVel);
|
|
|
+ stream->read(&enablePhysicsRep);
|
|
|
|
|
|
// write the water sound profiles
|
|
|
for( U32 i = 0; i < MaxSounds; ++ i )
|
|
@@ -477,6 +469,11 @@ void RigidShapeData::unpackData(BitStream* stream)
|
|
|
|
|
|
void RigidShapeData::initPersistFields()
|
|
|
{
|
|
|
+ addGroup("Physics");
|
|
|
+ addField("enablePhysicsRep", TypeBool, Offset(enablePhysicsRep, RigidShapeData),
|
|
|
+ "@brief Creates a representation of the object in the physics plugin.\n");
|
|
|
+ endGroup("Physics");
|
|
|
+
|
|
|
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.");
|
|
@@ -592,6 +589,12 @@ RigidShape::RigidShape()
|
|
|
restCount = 0;
|
|
|
|
|
|
inLiquid = false;
|
|
|
+
|
|
|
+ mWorkingQueryBox.minExtents.set(-1e9f, -1e9f, -1e9f);
|
|
|
+ mWorkingQueryBox.maxExtents.set(-1e9f, -1e9f, -1e9f);
|
|
|
+ mWorkingQueryBoxCountDown = sWorkingQueryBoxStaleThreshold;
|
|
|
+
|
|
|
+ mPhysicsRep = NULL;
|
|
|
}
|
|
|
|
|
|
RigidShape::~RigidShape()
|
|
@@ -619,6 +622,9 @@ bool RigidShape::onAdd()
|
|
|
if (!Parent::onAdd())
|
|
|
return false;
|
|
|
|
|
|
+ mWorkingQueryBox.minExtents.set(-1e9f, -1e9f, -1e9f);
|
|
|
+ mWorkingQueryBox.maxExtents.set(-1e9f, -1e9f, -1e9f);
|
|
|
+
|
|
|
// 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);
|
|
@@ -672,6 +678,7 @@ bool RigidShape::onAdd()
|
|
|
mConvex.box.minExtents.convolve(mObjScale);
|
|
|
mConvex.box.maxExtents.convolve(mObjScale);
|
|
|
mConvex.findNodeTransform();
|
|
|
+ _createPhysics();
|
|
|
|
|
|
addToScene();
|
|
|
|
|
@@ -722,14 +729,36 @@ void RigidShape::onRemove()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ mWorkingQueryBox.minExtents.set(-1e9f, -1e9f, -1e9f);
|
|
|
+ mWorkingQueryBox.maxExtents.set(-1e9f, -1e9f, -1e9f);
|
|
|
Parent::onRemove();
|
|
|
}
|
|
|
|
|
|
+void RigidShape::_createPhysics()
|
|
|
+{
|
|
|
+ SAFE_DELETE(mPhysicsRep);
|
|
|
|
|
|
-//----------------------------------------------------------------------------
|
|
|
+ if (!PHYSICSMGR || !mDataBlock->enablePhysicsRep)
|
|
|
+ return;
|
|
|
+
|
|
|
+ TSShape* shape = mShapeInstance->getShape();
|
|
|
+ PhysicsCollision* colShape = NULL;
|
|
|
+ colShape = shape->buildColShape(false, getScale());
|
|
|
+
|
|
|
+ if (colShape)
|
|
|
+ {
|
|
|
+ PhysicsWorld* world = PHYSICSMGR->getWorld(isServerObject() ? "server" : "client");
|
|
|
+ mPhysicsRep = PHYSICSMGR->createBody();
|
|
|
+ mPhysicsRep->init(colShape, 0, PhysicsBody::BF_KINEMATIC, this, world);
|
|
|
+ mPhysicsRep->setTransform(getTransform());
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
+//----------------------------------------------------------------------------
|
|
|
void RigidShape::processTick(const Move* move)
|
|
|
{
|
|
|
+ PROFILE_SCOPE(RigidShape_ProcessTick);
|
|
|
+
|
|
|
Parent::processTick(move);
|
|
|
if ( isMounted() )
|
|
|
return;
|
|
@@ -776,6 +805,8 @@ void RigidShape::processTick(const Move* move)
|
|
|
|
|
|
// Update the physics based on the integration rate
|
|
|
S32 count = mDataBlock->integration;
|
|
|
+ --mWorkingQueryBoxCountDown;
|
|
|
+
|
|
|
if (!mDisableMove)
|
|
|
updateWorkingCollisionSet(getCollisionMask());
|
|
|
for (U32 i = 0; i < count; i++)
|
|
@@ -790,6 +821,11 @@ void RigidShape::processTick(const Move* move)
|
|
|
setPosition(mRigid.linPosition, mRigid.angPosition);
|
|
|
setMaskBits(PositionMask);
|
|
|
updateContainer();
|
|
|
+
|
|
|
+ //TODO: Only update when position has actually changed
|
|
|
+ //no need to check if mDataBlock->enablePhysicsRep is false as mPhysicsRep will be NULL if it is
|
|
|
+ if (mPhysicsRep)
|
|
|
+ mPhysicsRep->moveKinematicTo(getTransform());
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -1036,6 +1072,8 @@ void RigidShape::enableCollision()
|
|
|
|
|
|
void RigidShape::updatePos(F32 dt)
|
|
|
{
|
|
|
+ PROFILE_SCOPE(Vehicle_UpdatePos);
|
|
|
+
|
|
|
Point3F origVelocity = mRigid.linVelocity;
|
|
|
|
|
|
// Update internal forces acting on the body.
|
|
@@ -1044,19 +1082,19 @@ void RigidShape::updatePos(F32 dt)
|
|
|
|
|
|
// Update collision information based on our current pos.
|
|
|
bool collided = false;
|
|
|
- if (!mRigid.atRest && !mDisableMove)
|
|
|
+ if (!mRigid.atRest && !mDisableMove)
|
|
|
{
|
|
|
collided = updateCollision(dt);
|
|
|
|
|
|
- // Now that all the forces have been processed, lets
|
|
|
+ // 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
|
|
|
+ // the rigid body 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())
|
|
|
+ if (mCollisionList.getCount())
|
|
|
{
|
|
|
F32 k = mRigid.getKineticEnergy();
|
|
|
- F32 G = sRigidShapeGravity * dt;
|
|
|
+ F32 G = mNetGravity * dt;
|
|
|
F32 Kg = 0.5 * mRigid.mass * G * G;
|
|
|
if (k < sRestTol * Kg && ++restCount > sRestCount)
|
|
|
mRigid.setAtRest();
|
|
@@ -1070,7 +1108,7 @@ void RigidShape::updatePos(F32 dt)
|
|
|
mRigid.integrate(dt);
|
|
|
|
|
|
// Deal with client and server scripting, sounds, etc.
|
|
|
- if (isServerObject())
|
|
|
+ if (isServerObject())
|
|
|
{
|
|
|
|
|
|
// Check triggers and other objects that we normally don't
|
|
@@ -1083,7 +1121,7 @@ void RigidShape::updatePos(F32 dt)
|
|
|
notifyCollision();
|
|
|
|
|
|
// Server side impact script callback
|
|
|
- if (collided)
|
|
|
+ if (collided)
|
|
|
{
|
|
|
VectorF collVec = mRigid.linVelocity - origVelocity;
|
|
|
F32 collSpeed = collVec.len();
|
|
@@ -1091,15 +1129,15 @@ void RigidShape::updatePos(F32 dt)
|
|
|
onImpact(collVec);
|
|
|
}
|
|
|
|
|
|
- // Water script callbacks
|
|
|
- if (!inLiquid && mWaterCoverage != 0.0f)
|
|
|
+ // Water script callbacks
|
|
|
+ if (!inLiquid && mWaterCoverage != 0.0f)
|
|
|
{
|
|
|
- onEnterLiquid_callback(getIdString(), mWaterCoverage, mLiquidType.c_str() );
|
|
|
+ mDataBlock->onEnterLiquid_callback(this, mWaterCoverage, mLiquidType.c_str());
|
|
|
inLiquid = true;
|
|
|
}
|
|
|
- else if (inLiquid && mWaterCoverage == 0.0f)
|
|
|
+ else if (inLiquid && mWaterCoverage == 0.0f)
|
|
|
{
|
|
|
- onLeaveLiquid_callback(getIdString(), mLiquidType.c_str() );
|
|
|
+ mDataBlock->onLeaveLiquid_callback(this, mLiquidType.c_str());
|
|
|
inLiquid = false;
|
|
|
}
|
|
|
|
|
@@ -1123,7 +1161,7 @@ void RigidShape::updatePos(F32 dt)
|
|
|
// Water volume sounds
|
|
|
F32 vSpeed = getVelocity().len();
|
|
|
if (!inLiquid && mWaterCoverage >= 0.8f) {
|
|
|
- if (vSpeed >= mDataBlock->hardSplashSoundVel)
|
|
|
+ if (vSpeed >= mDataBlock->hardSplashSoundVel)
|
|
|
SFX->playOnce(mDataBlock->waterSound[RigidShapeData::ImpactHard], &getTransform());
|
|
|
else
|
|
|
if (vSpeed >= mDataBlock->medSplashSoundVel)
|
|
@@ -1132,9 +1170,9 @@ void RigidShape::updatePos(F32 dt)
|
|
|
if (vSpeed >= mDataBlock->softSplashSoundVel)
|
|
|
SFX->playOnce(mDataBlock->waterSound[RigidShapeData::ImpactSoft], &getTransform());
|
|
|
inLiquid = true;
|
|
|
- }
|
|
|
+ }
|
|
|
else
|
|
|
- if(inLiquid && mWaterCoverage < 0.8f) {
|
|
|
+ if (inLiquid && mWaterCoverage < 0.8f) {
|
|
|
if (vSpeed >= mDataBlock->exitSplashSoundVel)
|
|
|
SFX->playOnce(mDataBlock->waterSound[RigidShapeData::ExitWater], &getTransform());
|
|
|
inLiquid = false;
|
|
@@ -1142,40 +1180,31 @@ void RigidShape::updatePos(F32 dt)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
|
|
-void RigidShape::updateForces(F32 /*dt*/)
|
|
|
+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;
|
|
|
+ Point3F force(0, 0, mRigid.mass * mNetGravity);
|
|
|
|
|
|
// Apply drag
|
|
|
- Point3F vDrag = mRigid.linVelocity;
|
|
|
- vDrag.convolve(Point3F(1, 1, mDataBlock->vertFactor));
|
|
|
- force -= vDrag * mDataBlock->dragForce;
|
|
|
+ Point3F vertDrag = mRigid.linVelocity*Point3F(1, 1, mDataBlock->vertFactor);
|
|
|
+ force -= vertDrag * 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;
|
|
|
+
|
|
|
+ // If we're still atRest, make sure we're not accumulating anything
|
|
|
+ if (mRigid.atRest)
|
|
|
+ mRigid.setAtRest();
|
|
|
}
|
|
|
|
|
|
|
|
@@ -1369,17 +1398,53 @@ bool RigidShape::resolveDisplacement(Rigid& ns,CollisionState *state, F32 dt)
|
|
|
|
|
|
void RigidShape::updateWorkingCollisionSet(const U32 mask)
|
|
|
{
|
|
|
+ PROFILE_SCOPE( Vehicle_UpdateWorkingCollisionSet );
|
|
|
+
|
|
|
+ // First, we need to adjust our velocity for possible acceleration. It is assumed
|
|
|
+ // that we will never accelerate more than 20 m/s for gravity, plus 30 m/s for
|
|
|
+ // jetting, and an equivalent 10 m/s for vehicle accel. We also assume that our
|
|
|
+ // working list is updated on a Tick basis, which means we only expand our box by
|
|
|
+ // the possible movement in that tick, plus some extra for caching purposes
|
|
|
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 to see if it is actually necessary to construct the new working list,
|
|
|
+ // or if we can use the cached version from the last query. We use the x
|
|
|
+ // component of the min member of the mWorkingQueryBox, which is lame, but
|
|
|
+ // it works ok.
|
|
|
+ bool updateSet = false;
|
|
|
+
|
|
|
+ // Check containment
|
|
|
+ if ((sWorkingQueryBoxStaleThreshold == -1 || mWorkingQueryBoxCountDown > 0) && mWorkingQueryBox.minExtents.x != -1e9f)
|
|
|
+ {
|
|
|
+ if (mWorkingQueryBox.isContained(convexBox) == false)
|
|
|
+ // Needed region is outside the cached region. Update it.
|
|
|
+ updateSet = true;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // Must update
|
|
|
+ updateSet = true;
|
|
|
+ }
|
|
|
|
|
|
+ // Actually perform the query, if necessary
|
|
|
+ if (updateSet == true)
|
|
|
+ {
|
|
|
+ mWorkingQueryBoxCountDown = sWorkingQueryBoxStaleThreshold;
|
|
|
+
|
|
|
+ const Point3F lPoint( sWorkingQueryBoxSizeMultiplier * l );
|
|
|
+ mWorkingQueryBox = convexBox;
|
|
|
+ mWorkingQueryBox.minExtents -= lPoint;
|
|
|
+ mWorkingQueryBox.maxExtents += lPoint;
|
|
|
+
|
|
|
+ disableCollision();
|
|
|
+ mConvex.updateWorkingList(mWorkingQueryBox, mask);
|
|
|
+ enableCollision();
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
/** Check collisions with trigger and items
|
|
@@ -1573,6 +1638,25 @@ void RigidShape::unpackUpdate(NetConnection *con, BitStream *stream)
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
|
|
+//----------------------------------------------------------------------------
|
|
|
+
|
|
|
+void RigidShape::consoleInit()
|
|
|
+{
|
|
|
+ Con::addVariable("$rigidPhysics::workingQueryBoxStaleThreshold", TypeS32, &sWorkingQueryBoxStaleThreshold,
|
|
|
+ "@brief The maximum number of ticks that go by before the mWorkingQueryBox is considered stale and needs updating.\n\n"
|
|
|
+ "Other factors can cause the collision working query box to become invalidated, such as the rigid body moving far "
|
|
|
+ "enough outside of this cached box. The smaller this number, the more times the working list of triangles that are "
|
|
|
+ "considered for collision is refreshed. This has the greatest impact with colliding with high triangle count meshes.\n\n"
|
|
|
+ "@note Set to -1 to disable any time-based forced check.\n\n"
|
|
|
+ "@ingroup GameObjects\n");
|
|
|
+
|
|
|
+ Con::addVariable("$rigidPhysics::workingQueryBoxSizeMultiplier", TypeF32, &sWorkingQueryBoxSizeMultiplier,
|
|
|
+ "@brief How much larger the mWorkingQueryBox should be made when updating the working collision list.\n\n"
|
|
|
+ "The larger this number the less often the working list will be updated due to motion, but any non-static shape that "
|
|
|
+ "moves into the query box will not be noticed.\n\n"
|
|
|
+ "@ingroup GameObjects\n");
|
|
|
+}
|
|
|
+
|
|
|
void RigidShape::initPersistFields()
|
|
|
{
|
|
|
Parent::initPersistFields();
|