mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-20 20:54:46 +00:00
1) got rid of evey class having it's own gravity 2) rigidshape inheritance simplifications 3) gravitymod from physicszones taking buoyancy into account natively (we still track raw bouyancy to cancel it out for player) 4) disableMove used throughout 5) items can now also be influenced by the appliedforce from physicszones
1399 lines
44 KiB
C++
1399 lines
44 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/item.h"
|
|
|
|
#include "core/stream/bitStream.h"
|
|
#include "math/mMath.h"
|
|
#include "console/console.h"
|
|
#include "console/consoleTypes.h"
|
|
#include "sim/netConnection.h"
|
|
#include "collision/boxConvex.h"
|
|
#include "collision/earlyOutPolyList.h"
|
|
#include "collision/extrudedPolyList.h"
|
|
#include "math/mPolyhedron.h"
|
|
#include "math/mathIO.h"
|
|
#include "lighting/lightInfo.h"
|
|
#include "lighting/lightManager.h"
|
|
#include "T3D/physics/physicsPlugin.h"
|
|
#include "T3D/physics/physicsBody.h"
|
|
#include "T3D/physics/physicsCollision.h"
|
|
#include "ts/tsShapeInstance.h"
|
|
#include "console/engineAPI.h"
|
|
|
|
|
|
const F32 sRotationSpeed = 6.0f; // Secs/Rotation
|
|
const F32 sAtRestVelocity = 0.15f; // Min speed after collision
|
|
const S32 sCollisionTimeout = 15; // Timout value in ticks
|
|
|
|
// Client prediction
|
|
static F32 sMinWarpTicks = 0.5 ; // Fraction of tick at which instant warp occures
|
|
static S32 sMaxWarpTicks = 3; // Max warp duration in ticks
|
|
|
|
const U32 sClientCollisionMask = (TerrainObjectType |
|
|
StaticShapeObjectType |
|
|
VehicleObjectType |
|
|
PlayerObjectType);
|
|
|
|
const U32 sServerCollisionMask = (sClientCollisionMask);
|
|
|
|
const S32 Item::csmAtRestTimer = 64;
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
IMPLEMENT_CO_DATABLOCK_V1(ItemData);
|
|
|
|
ConsoleDocClass( ItemData,
|
|
"@brief Stores properties for an individual Item type.\n\n"
|
|
|
|
"Items represent an object in the world, usually one that the player will interact with. "
|
|
"One example is a health kit on the group that is automatically picked up when the player "
|
|
"comes into contact with it.\n\n"
|
|
|
|
"ItemData provides the common properties for a set of Items. These properties include a "
|
|
"DTS or DAE model used to render the Item in the world, its physical properties for when the "
|
|
"Item interacts with the world (such as being tossed by the player), and any lights that emit "
|
|
"from the Item.\n\n"
|
|
|
|
"@tsexample\n"
|
|
"datablock ItemData(HealthKitSmall)\n"
|
|
"{\n"
|
|
" category =\"Health\";\n"
|
|
" className = \"HealthPatch\";\n"
|
|
" shapeFile = \"art/shapes/items/kit/healthkit.dts\";\n"
|
|
" gravityMod = \"1.0\";\n"
|
|
" mass = 2;\n"
|
|
" friction = 1;\n"
|
|
" elasticity = 0.3;\n"
|
|
" density = 2;\n"
|
|
" drag = 0.5;\n"
|
|
" maxVelocity = \"10.0\";\n"
|
|
" emap = true;\n"
|
|
" sticky = false;\n"
|
|
" dynamicType = \"0\"\n;"
|
|
" lightOnlyStatic = false;\n"
|
|
" lightType = \"NoLight\";\n"
|
|
" lightColor = \"1.0 1.0 1.0 1.0\";\n"
|
|
" lightTime = 1000;\n"
|
|
" lightRadius = 10.0;\n"
|
|
" simpleServerCollision = true;"
|
|
" // Dynamic properties used by the scripts\n\n"
|
|
" pickupName = \"a small health kit\";\n"
|
|
" repairAmount = 50;\n"
|
|
"};\n"
|
|
"@endtsexample\n"
|
|
|
|
"@ingroup gameObjects\n"
|
|
);
|
|
|
|
|
|
ItemData::ItemData()
|
|
{
|
|
shadowEnable = true;
|
|
|
|
|
|
friction = 0;
|
|
elasticity = 0;
|
|
|
|
sticky = false;
|
|
gravityMod = 1.0;
|
|
maxVelocity = 25.0f;
|
|
|
|
density = 2;
|
|
drag = 0.5;
|
|
|
|
lightOnlyStatic = false;
|
|
lightType = Item::NoLight;
|
|
lightColor.set(1.f,1.f,1.f,1.f);
|
|
lightTime = 1000;
|
|
lightRadius = 10.f;
|
|
|
|
simpleServerCollision = true;
|
|
}
|
|
|
|
ImplementEnumType( ItemLightType,
|
|
"@brief The type of light the Item has\n\n"
|
|
"@ingroup gameObjects\n\n")
|
|
{ Item::NoLight, "NoLight", "The item has no light attached.\n" },
|
|
{ Item::ConstantLight, "ConstantLight", "The item has a constantly emitting light attached.\n" },
|
|
{ Item::PulsingLight, "PulsingLight", "The item has a pulsing light attached.\n" }
|
|
EndImplementEnumType;
|
|
|
|
void ItemData::initPersistFields()
|
|
{
|
|
addField("friction", TypeF32, Offset(friction, ItemData), "A floating-point value specifying how much velocity is lost to impact and sliding friction.");
|
|
addField("elasticity", TypeF32, Offset(elasticity, ItemData), "A floating-point value specifying how 'bouncy' this ItemData is.");
|
|
addField("sticky", TypeBool, Offset(sticky, ItemData),
|
|
"@brief If true, ItemData will 'stick' to any surface it collides with.\n\n"
|
|
"When an item does stick to a surface, the Item::onStickyCollision() callback is called. The Item has methods to retrieve "
|
|
"the world position and normal the Item is stuck to.\n"
|
|
"@note Valid objects to stick to must be of StaticShapeObjectType.\n");
|
|
addField("gravityMod", TypeF32, Offset(gravityMod, ItemData), "Floating point value to multiply the existing gravity with, just for this ItemData.");
|
|
addField("maxVelocity", TypeF32, Offset(maxVelocity, ItemData), "Maximum velocity that this ItemData is able to move.");
|
|
|
|
addField("lightType", TYPEID< Item::LightType >(), Offset(lightType, ItemData), "Type of light to apply to this ItemData. Options are NoLight, ConstantLight, PulsingLight. Default is NoLight." );
|
|
addField("lightColor", TypeColorF, Offset(lightColor, ItemData),
|
|
"@brief Color value to make this light. Example: \"1.0,1.0,1.0\"\n\n"
|
|
"@see lightType\n");
|
|
addField("lightTime", TypeS32, Offset(lightTime, ItemData),
|
|
"@brief Time value for the light of this ItemData, used to control the pulse speed of the PulsingLight LightType.\n\n"
|
|
"@see lightType\n");
|
|
addField("lightRadius", TypeF32, Offset(lightRadius, ItemData),
|
|
"@brief Distance from the center point of this ItemData for the light to affect\n\n"
|
|
"@see lightType\n");
|
|
addField("lightOnlyStatic", TypeBool, Offset(lightOnlyStatic, ItemData),
|
|
"@brief If true, this ItemData will only cast a light if the Item for this ItemData has a static value of true.\n\n"
|
|
"@see lightType\n");
|
|
|
|
addField("simpleServerCollision", TypeBool, Offset(simpleServerCollision, ItemData),
|
|
"@brief Determines if only simple server-side collision will be used (for pick ups).\n\n"
|
|
"If set to true then only simple, server-side collision detection will be used. This is often the case "
|
|
"if the item is used for a pick up object, such as ammo. If set to false then a full collision volume "
|
|
"will be used as defined by the shape. The default is true.\n"
|
|
"@note Only applies when using a physics library.\n"
|
|
"@see TurretShape and ProximityMine for examples that should set this to false to allow them to be "
|
|
"shot by projectiles.\n");
|
|
|
|
Parent::initPersistFields();
|
|
}
|
|
|
|
void ItemData::packData(BitStream* stream)
|
|
{
|
|
Parent::packData(stream);
|
|
stream->writeFloat(friction, 10);
|
|
stream->writeFloat(elasticity, 10);
|
|
stream->writeFlag(sticky);
|
|
if(stream->writeFlag(gravityMod != 1.0))
|
|
stream->writeFloat(gravityMod, 10);
|
|
if(stream->writeFlag(maxVelocity != -1))
|
|
stream->write(maxVelocity);
|
|
|
|
if(stream->writeFlag(lightType != Item::NoLight))
|
|
{
|
|
AssertFatal(Item::NumLightTypes < (1 << 2), "ItemData: light type needs more bits");
|
|
stream->writeInt(lightType, 2);
|
|
stream->writeFloat(lightColor.red, 7);
|
|
stream->writeFloat(lightColor.green, 7);
|
|
stream->writeFloat(lightColor.blue, 7);
|
|
stream->writeFloat(lightColor.alpha, 7);
|
|
stream->write(lightTime);
|
|
stream->write(lightRadius);
|
|
stream->writeFlag(lightOnlyStatic);
|
|
}
|
|
|
|
stream->writeFlag(simpleServerCollision);
|
|
}
|
|
|
|
void ItemData::unpackData(BitStream* stream)
|
|
{
|
|
Parent::unpackData(stream);
|
|
friction = stream->readFloat(10);
|
|
elasticity = stream->readFloat(10);
|
|
sticky = stream->readFlag();
|
|
if(stream->readFlag())
|
|
gravityMod = stream->readFloat(10);
|
|
else
|
|
gravityMod = 1.0;
|
|
|
|
if(stream->readFlag())
|
|
stream->read(&maxVelocity);
|
|
else
|
|
maxVelocity = -1;
|
|
|
|
if(stream->readFlag())
|
|
{
|
|
lightType = stream->readInt(2);
|
|
lightColor.red = stream->readFloat(7);
|
|
lightColor.green = stream->readFloat(7);
|
|
lightColor.blue = stream->readFloat(7);
|
|
lightColor.alpha = stream->readFloat(7);
|
|
stream->read(&lightTime);
|
|
stream->read(&lightRadius);
|
|
lightOnlyStatic = stream->readFlag();
|
|
}
|
|
else
|
|
lightType = Item::NoLight;
|
|
|
|
simpleServerCollision = stream->readFlag();
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
IMPLEMENT_CO_NETOBJECT_V1(Item);
|
|
|
|
ConsoleDocClass( Item,
|
|
"@brief Base Item class. Uses the ItemData datablock for common properties.\n\n"
|
|
|
|
"Items represent an object in the world, usually one that the player will interact with. "
|
|
"One example is a health kit on the group that is automatically picked up when the player "
|
|
"comes into contact with it.\n\n"
|
|
|
|
"@tsexample\n"
|
|
"// This is the \"health patch\" dropped by a dying player.\n"
|
|
"datablock ItemData(HealthKitPatch)\n"
|
|
"{\n"
|
|
" // Mission editor category, this datablock will show up in the\n"
|
|
" // specified category under the \"shapes\" root category.\n"
|
|
" category = \"Health\";\n\n"
|
|
" className = \"HealthPatch\";\n\n"
|
|
" // Basic Item properties\n"
|
|
" shapeFile = \"art/shapes/items/patch/healthpatch.dts\";\n"
|
|
" mass = 2;\n"
|
|
" friction = 1;\n"
|
|
" elasticity = 0.3;\n"
|
|
" emap = true;\n\n"
|
|
" // Dynamic properties used by the scripts\n"
|
|
" pickupName = \"a health patch\";\n"
|
|
" repairAmount = 50;\n"
|
|
"};\n\n"
|
|
|
|
"%obj = new Item()\n"
|
|
"{\n"
|
|
" dataBlock = HealthKitSmall;\n"
|
|
" parentGroup = EWCreatorWindow.objectGroup;\n"
|
|
" static = true;\n"
|
|
" rotate = true;\n"
|
|
"};\n"
|
|
"@endtsexample\n\n"
|
|
|
|
"@see ItemData\n"
|
|
|
|
"@ingroup gameObjects\n"
|
|
);
|
|
|
|
IMPLEMENT_CALLBACK( Item, onStickyCollision, void, ( const char* objID ),( objID ),
|
|
"@brief Informs the Item object that it is now sticking to another object.\n\n"
|
|
"This callback is only called if the ItemData::sticky property for this Item is true.\n"
|
|
"@param objID Object ID this Item object.\n"
|
|
"@note Server side only.\n"
|
|
"@see Item, ItemData\n"
|
|
);
|
|
|
|
IMPLEMENT_CALLBACK( Item, onEnterLiquid, void, ( const char* objID, F32 waterCoverage, const char* liquidType ),( objID, waterCoverage, liquidType ),
|
|
"Informs an Item object that it has entered liquid, along with information about the liquid type.\n"
|
|
"@param objID Object ID for this Item object.\n"
|
|
"@param waterCoverage How much coverage of water this Item object has.\n"
|
|
"@param liquidType The type of liquid that this Item object has entered.\n"
|
|
"@note Server side only.\n"
|
|
"@see Item, ItemData, WaterObject\n"
|
|
);
|
|
|
|
IMPLEMENT_CALLBACK( Item, onLeaveLiquid, void, ( const char* objID, const char* liquidType ),( objID, liquidType ),
|
|
"Informs an Item object that it has left a liquid, along with information about the liquid type.\n"
|
|
"@param objID Object ID for this Item object.\n"
|
|
"@param liquidType The type of liquid that this Item object has left.\n"
|
|
"@note Server side only.\n"
|
|
"@see Item, ItemData, WaterObject\n"
|
|
);
|
|
|
|
|
|
Item::Item()
|
|
{
|
|
mTypeMask |= ItemObjectType | DynamicShapeObjectType;
|
|
mDataBlock = 0;
|
|
mStatic = false;
|
|
mRotate = false;
|
|
mVelocity = VectorF(0,0,0);
|
|
mAtRest = true;
|
|
mAtRestCounter = 0;
|
|
mInLiquid = false;
|
|
mDelta.warpTicks = 0;
|
|
mDelta.dt = 1;
|
|
mCollisionObject = 0;
|
|
mCollisionTimeout = 0;
|
|
mPhysicsRep = NULL;
|
|
|
|
mConvex.init(this);
|
|
mWorkingQueryBox.minExtents.set(-1e9, -1e9, -1e9);
|
|
mWorkingQueryBox.maxExtents.set(-1e9, -1e9, -1e9);
|
|
|
|
mLight = NULL;
|
|
|
|
mSubclassItemHandlesScene = false;
|
|
}
|
|
|
|
Item::~Item()
|
|
{
|
|
SAFE_DELETE(mLight);
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
bool Item::onAdd()
|
|
{
|
|
if (!Parent::onAdd() || !mDataBlock)
|
|
return false;
|
|
|
|
if (mStatic)
|
|
mAtRest = true;
|
|
mObjToWorld.getColumn(3,&mDelta.pos);
|
|
|
|
// Setup the box for our convex object...
|
|
mObjBox.getCenter(&mConvex.mCenter);
|
|
mConvex.mSize.x = mObjBox.len_x() / 2.0;
|
|
mConvex.mSize.y = mObjBox.len_y() / 2.0;
|
|
mConvex.mSize.z = mObjBox.len_z() / 2.0;
|
|
mWorkingQueryBox.minExtents.set(-1e9, -1e9, -1e9);
|
|
mWorkingQueryBox.maxExtents.set(-1e9, -1e9, -1e9);
|
|
|
|
if( !isHidden() && !mSubclassItemHandlesScene )
|
|
addToScene();
|
|
|
|
if (isServerObject())
|
|
{
|
|
if (!mSubclassItemHandlesScene)
|
|
scriptOnAdd();
|
|
}
|
|
else if (mDataBlock->lightType != NoLight)
|
|
{
|
|
mDropTime = Sim::getCurrentTime();
|
|
}
|
|
|
|
_updatePhysics();
|
|
|
|
return true;
|
|
}
|
|
|
|
void Item::_updatePhysics()
|
|
{
|
|
SAFE_DELETE( mPhysicsRep );
|
|
|
|
if ( !PHYSICSMGR )
|
|
return;
|
|
|
|
if (mDataBlock->simpleServerCollision)
|
|
{
|
|
// We only need the trigger on the server.
|
|
if ( isServerObject() )
|
|
{
|
|
PhysicsCollision *colShape = PHYSICSMGR->createCollision();
|
|
colShape->addBox( mObjBox.getExtents() * 0.5f, MatrixF::Identity );
|
|
|
|
PhysicsWorld *world = PHYSICSMGR->getWorld( isServerObject() ? "server" : "client" );
|
|
mPhysicsRep = PHYSICSMGR->createBody();
|
|
mPhysicsRep->init( colShape, 0, PhysicsBody::BF_TRIGGER | PhysicsBody::BF_KINEMATIC, this, world );
|
|
mPhysicsRep->setTransform( getTransform() );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( !mShapeInstance )
|
|
return;
|
|
|
|
PhysicsCollision* colShape = mShapeInstance->getShape()->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() );
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Item::onNewDataBlock( GameBaseData *dptr, bool reload )
|
|
{
|
|
mDataBlock = dynamic_cast<ItemData*>(dptr);
|
|
if (!mDataBlock || !Parent::onNewDataBlock(dptr,reload))
|
|
return false;
|
|
|
|
if (!mSubclassItemHandlesScene)
|
|
scriptOnNewDataBlock();
|
|
|
|
if ( isProperlyAdded() )
|
|
_updatePhysics();
|
|
|
|
return true;
|
|
}
|
|
|
|
void Item::onRemove()
|
|
{
|
|
mWorkingQueryBox.minExtents.set(-1e9, -1e9, -1e9);
|
|
mWorkingQueryBox.maxExtents.set(-1e9, -1e9, -1e9);
|
|
|
|
SAFE_DELETE( mPhysicsRep );
|
|
|
|
if (!mSubclassItemHandlesScene)
|
|
{
|
|
scriptOnRemove();
|
|
removeFromScene();
|
|
}
|
|
|
|
Parent::onRemove();
|
|
}
|
|
|
|
void Item::onDeleteNotify( SimObject *obj )
|
|
{
|
|
if ( obj == mCollisionObject )
|
|
{
|
|
mCollisionObject = NULL;
|
|
mCollisionTimeout = 0;
|
|
}
|
|
|
|
Parent::onDeleteNotify( obj );
|
|
}
|
|
|
|
// Lighting: -----------------------------------------------------------------
|
|
|
|
void Item::registerLights(LightManager * lightManager, bool lightingScene)
|
|
{
|
|
if(lightingScene)
|
|
return;
|
|
|
|
if(mDataBlock->lightOnlyStatic && !mStatic)
|
|
return;
|
|
|
|
F32 intensity;
|
|
switch(mDataBlock->lightType)
|
|
{
|
|
case ConstantLight:
|
|
intensity = mFadeVal;
|
|
break;
|
|
|
|
case PulsingLight:
|
|
{
|
|
S32 delta = Sim::getCurrentTime() - mDropTime;
|
|
intensity = 0.5f + 0.5f * mSin(M_PI_F * F32(delta) / F32(mDataBlock->lightTime));
|
|
intensity = 0.15f + intensity * 0.85f;
|
|
intensity *= mFadeVal; // fade out light on flags
|
|
break;
|
|
}
|
|
|
|
default:
|
|
return;
|
|
}
|
|
|
|
// Create a light if needed
|
|
if (!mLight)
|
|
{
|
|
mLight = lightManager->createLightInfo();
|
|
}
|
|
mLight->setColor( mDataBlock->lightColor * intensity );
|
|
mLight->setType( LightInfo::Point );
|
|
mLight->setRange( mDataBlock->lightRadius );
|
|
mLight->setPosition( getBoxCenter() );
|
|
|
|
lightManager->registerGlobalLight( mLight, this );
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
Point3F Item::getVelocity() const
|
|
{
|
|
return mVelocity;
|
|
}
|
|
|
|
void Item::setVelocity(const VectorF& vel)
|
|
{
|
|
mVelocity = vel;
|
|
|
|
// Clamp against the maximum velocity.
|
|
if ( mDataBlock->maxVelocity > 0 )
|
|
{
|
|
F32 len = mVelocity.magnitudeSafe();
|
|
if ( len > mDataBlock->maxVelocity )
|
|
{
|
|
Point3F excess = mVelocity * ( 1.0f - (mDataBlock->maxVelocity / len ) );
|
|
mVelocity -= excess;
|
|
}
|
|
}
|
|
|
|
setMaskBits(PositionMask);
|
|
mAtRest = false;
|
|
mAtRestCounter = 0;
|
|
}
|
|
|
|
void Item::applyImpulse(const Point3F&,const VectorF& vec)
|
|
{
|
|
// Items ignore angular velocity
|
|
VectorF vel;
|
|
vel.x = vec.x / mDataBlock->mass;
|
|
vel.y = vec.y / mDataBlock->mass;
|
|
vel.z = vec.z / mDataBlock->mass;
|
|
setVelocity(vel);
|
|
}
|
|
|
|
void Item::setCollisionTimeout(ShapeBase* obj)
|
|
{
|
|
if (mCollisionObject)
|
|
clearNotify(mCollisionObject);
|
|
deleteNotify(obj);
|
|
mCollisionObject = obj;
|
|
mCollisionTimeout = sCollisionTimeout;
|
|
setMaskBits(ThrowSrcMask);
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
void Item::processTick(const Move* move)
|
|
{
|
|
Parent::processTick(move);
|
|
|
|
if ( isMounted() )
|
|
return;
|
|
|
|
//
|
|
if (mCollisionObject && !--mCollisionTimeout)
|
|
mCollisionObject = 0;
|
|
|
|
// Warp to catch up to server
|
|
if (mDelta.warpTicks > 0)
|
|
{
|
|
mDelta.warpTicks--;
|
|
|
|
// Set new pos.
|
|
MatrixF mat = mObjToWorld;
|
|
mat.getColumn(3,&mDelta.pos);
|
|
mDelta.pos += mDelta.warpOffset;
|
|
mat.setColumn(3, mDelta.pos);
|
|
Parent::setTransform(mat);
|
|
|
|
// Backstepping
|
|
mDelta.posVec.x = -mDelta.warpOffset.x;
|
|
mDelta.posVec.y = -mDelta.warpOffset.y;
|
|
mDelta.posVec.z = -mDelta.warpOffset.z;
|
|
}
|
|
else
|
|
{
|
|
if (isServerObject() && mAtRest && (mStatic == false && mDataBlock->sticky == false))
|
|
{
|
|
if (++mAtRestCounter > csmAtRestTimer)
|
|
{
|
|
mAtRest = false;
|
|
mAtRestCounter = 0;
|
|
setMaskBits(PositionMask);
|
|
}
|
|
}
|
|
|
|
if (!mStatic && !mAtRest && isHidden() == false)
|
|
{
|
|
updateVelocity(TickSec);
|
|
updateWorkingCollisionSet(isGhost() ? sClientCollisionMask : sServerCollisionMask, TickSec);
|
|
updatePos(isGhost() ? sClientCollisionMask : sServerCollisionMask, TickSec);
|
|
}
|
|
else
|
|
{
|
|
// Need to clear out last updatePos or warp interpolation
|
|
mDelta.posVec.set(0,0,0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Item::interpolateTick(F32 dt)
|
|
{
|
|
Parent::interpolateTick(dt);
|
|
if ( isMounted() )
|
|
return;
|
|
|
|
// Client side interpolation
|
|
Point3F pos = mDelta.pos + mDelta.posVec * dt;
|
|
MatrixF mat = mRenderObjToWorld;
|
|
mat.setColumn(3,pos);
|
|
setRenderTransform(mat);
|
|
mDelta.dt = dt;
|
|
// PATHSHAPE
|
|
updateRenderChangesByParent();
|
|
// PATHSHAPE END
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
void Item::setTransform(const MatrixF& mat)
|
|
{
|
|
Point3F pos;
|
|
mat.getColumn(3,&pos);
|
|
MatrixF tmat;
|
|
if (!mRotate) {
|
|
// Forces all rotation to be around the z axis
|
|
VectorF vec;
|
|
mat.getColumn(1,&vec);
|
|
tmat.set(EulerF(0,0,-mAtan2(-vec.x,vec.y)));
|
|
}
|
|
else
|
|
tmat.identity();
|
|
tmat.setColumn(3,pos);
|
|
Parent::setTransform(tmat);
|
|
if (!mStatic)
|
|
{
|
|
mAtRest = false;
|
|
mAtRestCounter = 0;
|
|
}
|
|
|
|
if ( mPhysicsRep )
|
|
mPhysicsRep->setTransform( getTransform() );
|
|
|
|
setMaskBits(RotationMask | PositionMask | NoWarpMask);
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
void Item::updateWorkingCollisionSet(const U32 mask, const F32 dt)
|
|
{
|
|
// It is assumed that we will never accelerate more than 10 m/s for gravity...
|
|
//
|
|
Point3F scaledVelocity = mVelocity * dt;
|
|
F32 len = scaledVelocity.len();
|
|
F32 newLen = len + (10 * dt);
|
|
|
|
// 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;
|
|
|
|
Box3F convexBox = mConvex.getBoundingBox(getTransform(), getScale());
|
|
F32 l = (newLen * 1.1) + 0.1; // from Convex::updateWorkingList
|
|
convexBox.minExtents -= Point3F(l, l, l);
|
|
convexBox.maxExtents += Point3F(l, l, l);
|
|
|
|
// Check containment
|
|
{
|
|
if (mWorkingQueryBox.minExtents.x != -1e9)
|
|
{
|
|
if (mWorkingQueryBox.isContained(convexBox) == false)
|
|
{
|
|
// Needed region is outside the cached region. Update it.
|
|
updateSet = true;
|
|
}
|
|
else
|
|
{
|
|
// We can leave it alone, we're still inside the cached region
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Must update
|
|
updateSet = true;
|
|
}
|
|
}
|
|
|
|
// Actually perform the query, if necessary
|
|
if (updateSet == true)
|
|
{
|
|
mWorkingQueryBox = convexBox;
|
|
mWorkingQueryBox.minExtents -= Point3F(2 * l, 2 * l, 2 * l);
|
|
mWorkingQueryBox.maxExtents += Point3F(2 * l, 2 * l, 2 * l);
|
|
|
|
disableCollision();
|
|
if (mCollisionObject)
|
|
mCollisionObject->disableCollision();
|
|
|
|
mConvex.updateWorkingList(mWorkingQueryBox, mask);
|
|
|
|
if (mCollisionObject)
|
|
mCollisionObject->enableCollision();
|
|
enableCollision();
|
|
}
|
|
}
|
|
|
|
void Item::updateVelocity(const F32 dt)
|
|
{
|
|
// Container buoyancy & drag
|
|
// Acceleration due to gravity
|
|
mVelocity.z += (mNetGravity * mDataBlock->gravityMod) * dt;
|
|
mVelocity -= mVelocity * mDrag * dt;
|
|
|
|
// Add in physical zone force
|
|
mVelocity += mAppliedForce;
|
|
|
|
F32 len;
|
|
if (mDataBlock->maxVelocity > 0 && (len = mVelocity.len()) > (mDataBlock->maxVelocity * 1.05)) {
|
|
Point3F excess = mVelocity * (1.0 - (mDataBlock->maxVelocity / len ));
|
|
excess *= 0.1f;
|
|
mVelocity -= excess;
|
|
}
|
|
}
|
|
|
|
|
|
void Item::updatePos(const U32 /*mask*/, const F32 dt)
|
|
{
|
|
// Try and move
|
|
Point3F pos;
|
|
mObjToWorld.getColumn(3,&pos);
|
|
mDelta.posVec = pos;
|
|
|
|
bool contact = false;
|
|
bool nonStatic = false;
|
|
bool stickyNotify = false;
|
|
CollisionList collisionList;
|
|
F32 time = dt;
|
|
|
|
static Polyhedron sBoxPolyhedron;
|
|
static ExtrudedPolyList sExtrudedPolyList;
|
|
static EarlyOutPolyList sEarlyOutPolyList;
|
|
MatrixF collisionMatrix(true);
|
|
Point3F end = pos + mVelocity * time;
|
|
U32 mask = isServerObject() ? sServerCollisionMask : sClientCollisionMask;
|
|
|
|
// Part of our speed problem here is that we don't track contact surfaces, like we do
|
|
// with the player. In order to handle the most common and performance impacting
|
|
// instance of this problem, we'll use a ray cast to detect any contact surfaces below
|
|
// us. This won't be perfect, but it only needs to catch a few of these to make a
|
|
// big difference. We'll cast from the top center of the bounding box at the tick's
|
|
// beginning to the bottom center of the box at the end.
|
|
Point3F startCast((mObjBox.minExtents.x + mObjBox.maxExtents.x) * 0.5,
|
|
(mObjBox.minExtents.y + mObjBox.maxExtents.y) * 0.5,
|
|
mObjBox.maxExtents.z);
|
|
Point3F endCast((mObjBox.minExtents.x + mObjBox.maxExtents.x) * 0.5,
|
|
(mObjBox.minExtents.y + mObjBox.maxExtents.y) * 0.5,
|
|
mObjBox.minExtents.z);
|
|
collisionMatrix.setColumn(3, pos);
|
|
collisionMatrix.mulP(startCast);
|
|
collisionMatrix.setColumn(3, end);
|
|
collisionMatrix.mulP(endCast);
|
|
RayInfo rinfo;
|
|
bool doToughCollision = true;
|
|
disableCollision();
|
|
if (mCollisionObject)
|
|
mCollisionObject->disableCollision();
|
|
if (getContainer()->castRay(startCast, endCast, mask, &rinfo))
|
|
{
|
|
F32 bd = -mDot(mVelocity, rinfo.normal);
|
|
|
|
if (bd >= 0.0)
|
|
{
|
|
// Contact!
|
|
if (mDataBlock->sticky && rinfo.object->getTypeMask() & (STATIC_COLLISION_TYPEMASK)) {
|
|
mVelocity.set(0, 0, 0);
|
|
mAtRest = true;
|
|
mAtRestCounter = 0;
|
|
stickyNotify = true;
|
|
mStickyCollisionPos = rinfo.point;
|
|
mStickyCollisionNormal = rinfo.normal;
|
|
doToughCollision = false;;
|
|
} else {
|
|
// Subtract out velocity into surface and friction
|
|
VectorF fv = mVelocity + rinfo.normal * bd;
|
|
F32 fvl = fv.len();
|
|
if (fvl) {
|
|
F32 ff = bd * mDataBlock->friction;
|
|
if (ff < fvl) {
|
|
fv *= ff / fvl;
|
|
fvl = ff;
|
|
}
|
|
}
|
|
bd *= 1 + mDataBlock->elasticity;
|
|
VectorF dv = rinfo.normal * (bd + 0.002);
|
|
mVelocity += dv;
|
|
mVelocity -= fv;
|
|
|
|
// Keep track of what we hit
|
|
contact = true;
|
|
U32 typeMask = rinfo.object->getTypeMask();
|
|
if (!(typeMask & StaticObjectType))
|
|
nonStatic = true;
|
|
if (isServerObject() && (typeMask & ShapeBaseObjectType)) {
|
|
ShapeBase* col = static_cast<ShapeBase*>(rinfo.object);
|
|
queueCollision(col,mVelocity - col->getVelocity());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
enableCollision();
|
|
if (mCollisionObject)
|
|
mCollisionObject->enableCollision();
|
|
|
|
if (doToughCollision)
|
|
{
|
|
U32 count;
|
|
for (count = 0; count < 3; count++)
|
|
{
|
|
// Build list from convex states here...
|
|
end = pos + mVelocity * time;
|
|
|
|
|
|
collisionMatrix.setColumn(3, end);
|
|
Box3F wBox = getObjBox();
|
|
collisionMatrix.mul(wBox);
|
|
Box3F testBox = wBox;
|
|
Point3F oldMin = testBox.minExtents;
|
|
Point3F oldMax = testBox.maxExtents;
|
|
testBox.minExtents.setMin(oldMin + (mVelocity * time));
|
|
testBox.maxExtents.setMin(oldMax + (mVelocity * time));
|
|
|
|
sEarlyOutPolyList.clear();
|
|
sEarlyOutPolyList.mNormal.set(0,0,0);
|
|
sEarlyOutPolyList.mPlaneList.setSize(6);
|
|
sEarlyOutPolyList.mPlaneList[0].set(wBox.minExtents,VectorF(-1,0,0));
|
|
sEarlyOutPolyList.mPlaneList[1].set(wBox.maxExtents,VectorF(0,1,0));
|
|
sEarlyOutPolyList.mPlaneList[2].set(wBox.maxExtents,VectorF(1,0,0));
|
|
sEarlyOutPolyList.mPlaneList[3].set(wBox.minExtents,VectorF(0,-1,0));
|
|
sEarlyOutPolyList.mPlaneList[4].set(wBox.minExtents,VectorF(0,0,-1));
|
|
sEarlyOutPolyList.mPlaneList[5].set(wBox.maxExtents,VectorF(0,0,1));
|
|
|
|
CollisionWorkingList& eorList = mConvex.getWorkingList();
|
|
CollisionWorkingList* eopList = eorList.wLink.mNext;
|
|
while (eopList != &eorList) {
|
|
if ((eopList->mConvex->getObject()->getTypeMask() & mask) != 0)
|
|
{
|
|
Box3F convexBox = eopList->mConvex->getBoundingBox();
|
|
if (testBox.isOverlapped(convexBox))
|
|
{
|
|
eopList->mConvex->getPolyList(&sEarlyOutPolyList);
|
|
if (sEarlyOutPolyList.isEmpty() == false)
|
|
break;
|
|
}
|
|
}
|
|
eopList = eopList->wLink.mNext;
|
|
}
|
|
if (sEarlyOutPolyList.isEmpty())
|
|
{
|
|
pos = end;
|
|
break;
|
|
}
|
|
|
|
collisionMatrix.setColumn(3, pos);
|
|
sBoxPolyhedron.buildBox(collisionMatrix, mObjBox, true);
|
|
|
|
// Build extruded polyList...
|
|
VectorF vector = end - pos;
|
|
sExtrudedPolyList.extrude(sBoxPolyhedron, vector);
|
|
sExtrudedPolyList.setVelocity(mVelocity);
|
|
sExtrudedPolyList.setCollisionList(&collisionList);
|
|
|
|
CollisionWorkingList& rList = mConvex.getWorkingList();
|
|
CollisionWorkingList* pList = rList.wLink.mNext;
|
|
while (pList != &rList) {
|
|
if ((pList->mConvex->getObject()->getTypeMask() & mask) != 0)
|
|
{
|
|
Box3F convexBox = pList->mConvex->getBoundingBox();
|
|
if (testBox.isOverlapped(convexBox))
|
|
{
|
|
pList->mConvex->getPolyList(&sExtrudedPolyList);
|
|
}
|
|
}
|
|
pList = pList->wLink.mNext;
|
|
}
|
|
|
|
if (collisionList.getTime() < 1.0)
|
|
{
|
|
// Set to collision point
|
|
F32 cdt = time * collisionList.getTime();
|
|
pos += mVelocity * cdt;
|
|
time -= cdt;
|
|
|
|
// Pick the most resistant surface
|
|
F32 bd = 0;
|
|
const Collision* collision = 0;
|
|
for (S32 c = 0; c < collisionList.getCount(); c++) {
|
|
const Collision &cp = collisionList[c];
|
|
F32 dot = -mDot(mVelocity,cp.normal);
|
|
if (dot > bd) {
|
|
bd = dot;
|
|
collision = &cp;
|
|
}
|
|
}
|
|
|
|
if (collision && mDataBlock->sticky && collision->object->getTypeMask() & (STATIC_COLLISION_TYPEMASK)) {
|
|
mVelocity.set(0, 0, 0);
|
|
mAtRest = true;
|
|
mAtRestCounter = 0;
|
|
stickyNotify = true;
|
|
mStickyCollisionPos = collision->point;
|
|
mStickyCollisionNormal = collision->normal;
|
|
break;
|
|
} else {
|
|
// Subtract out velocity into surface and friction
|
|
if (collision) {
|
|
VectorF fv = mVelocity + collision->normal * bd;
|
|
F32 fvl = fv.len();
|
|
if (fvl) {
|
|
F32 ff = bd * mDataBlock->friction;
|
|
if (ff < fvl) {
|
|
fv *= ff / fvl;
|
|
fvl = ff;
|
|
}
|
|
}
|
|
bd *= 1 + mDataBlock->elasticity;
|
|
VectorF dv = collision->normal * (bd + 0.002);
|
|
mVelocity += dv;
|
|
mVelocity -= fv;
|
|
|
|
// Keep track of what we hit
|
|
contact = true;
|
|
U32 typeMask = collision->object->getTypeMask();
|
|
if (!(typeMask & StaticObjectType))
|
|
nonStatic = true;
|
|
if (isServerObject() && (typeMask & ShapeBaseObjectType)) {
|
|
ShapeBase* col = static_cast<ShapeBase*>(collision->object);
|
|
queueCollision(col,mVelocity - col->getVelocity());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pos = end;
|
|
break;
|
|
}
|
|
}
|
|
if (count == 3)
|
|
{
|
|
// Couldn't move...
|
|
mVelocity.set(0, 0, 0);
|
|
}
|
|
}
|
|
|
|
// If on the client, calculate delta for backstepping
|
|
if (isGhost()) {
|
|
mDelta.pos = pos;
|
|
mDelta.posVec -= pos;
|
|
mDelta.dt = 1;
|
|
}
|
|
|
|
// Update transform
|
|
MatrixF mat = mObjToWorld;
|
|
mat.setColumn(3,pos);
|
|
Parent::setTransform(mat);
|
|
enableCollision();
|
|
if (mCollisionObject)
|
|
mCollisionObject->enableCollision();
|
|
updateContainer();
|
|
|
|
if ( mPhysicsRep )
|
|
mPhysicsRep->setTransform( mat );
|
|
|
|
//
|
|
if (contact) {
|
|
// Check for rest condition
|
|
if (!nonStatic && mVelocity.len() < sAtRestVelocity) {
|
|
mVelocity.x = mVelocity.y = mVelocity.z = 0;
|
|
mAtRest = true;
|
|
mAtRestCounter = 0;
|
|
}
|
|
|
|
// Only update the client if we hit a non-static shape or
|
|
// if this is our final rest pos.
|
|
if (nonStatic || mAtRest)
|
|
setMaskBits(PositionMask);
|
|
}
|
|
|
|
// Collision callbacks. These need to be processed whether we hit
|
|
// anything or not.
|
|
if (!isGhost())
|
|
{
|
|
SimObjectPtr<Item> safePtr(this);
|
|
if (stickyNotify)
|
|
{
|
|
notifyCollision();
|
|
if(bool(safePtr))
|
|
onStickyCollision_callback( getIdString() );
|
|
}
|
|
else
|
|
notifyCollision();
|
|
|
|
// water
|
|
if(bool(safePtr))
|
|
{
|
|
if(!mInLiquid && mWaterCoverage != 0.0f)
|
|
{
|
|
onEnterLiquid_callback( getIdString(), mWaterCoverage, mLiquidType.c_str() );
|
|
mInLiquid = true;
|
|
}
|
|
else if(mInLiquid && mWaterCoverage == 0.0f)
|
|
{
|
|
onLeaveLiquid_callback(getIdString(), mLiquidType.c_str());
|
|
mInLiquid = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
static MatrixF IMat(1);
|
|
|
|
bool Item::buildPolyList(PolyListContext context, AbstractPolyList* polyList, const Box3F&, const SphereF&)
|
|
{
|
|
if ( context == PLC_Decal )
|
|
return false;
|
|
|
|
// Collision with the item is always against the item's object
|
|
// space bounding box axis aligned in world space.
|
|
Point3F pos;
|
|
mObjToWorld.getColumn(3,&pos);
|
|
IMat.setColumn(3,pos);
|
|
polyList->setTransform(&IMat, mObjScale);
|
|
polyList->setObject(this);
|
|
polyList->addBox(mObjBox);
|
|
return true;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
U32 Item::packUpdate(NetConnection *connection, U32 mask, BitStream *stream)
|
|
{
|
|
U32 retMask = Parent::packUpdate(connection,mask,stream);
|
|
|
|
if (stream->writeFlag(mask & InitialUpdateMask)) {
|
|
stream->writeFlag(mRotate);
|
|
stream->writeFlag(mStatic);
|
|
if (stream->writeFlag(getScale() != Point3F(1, 1, 1)))
|
|
mathWrite(*stream, getScale());
|
|
}
|
|
|
|
if (mask & ThrowSrcMask && mCollisionObject) {
|
|
S32 gIndex = connection->getGhostIndex(mCollisionObject);
|
|
if (stream->writeFlag(gIndex != -1))
|
|
stream->writeInt(gIndex,NetConnection::GhostIdBitSize);
|
|
}
|
|
else
|
|
stream->writeFlag(false);
|
|
|
|
if (stream->writeFlag(mask & RotationMask && !mRotate)) {
|
|
// Assumes rotation is about the Z axis
|
|
AngAxisF aa(mObjToWorld);
|
|
stream->writeFlag(aa.axis.z < 0);
|
|
stream->write(aa.angle);
|
|
}
|
|
|
|
if (stream->writeFlag(mask & PositionMask)) {
|
|
Point3F pos;
|
|
mObjToWorld.getColumn(3,&pos);
|
|
mathWrite(*stream, pos);
|
|
if (!stream->writeFlag(mAtRest)) {
|
|
mathWrite(*stream, mVelocity);
|
|
}
|
|
stream->writeFlag(!(mask & NoWarpMask));
|
|
}
|
|
return retMask;
|
|
}
|
|
|
|
void Item::unpackUpdate(NetConnection *connection, BitStream *stream)
|
|
{
|
|
Parent::unpackUpdate(connection,stream);
|
|
|
|
// InitialUpdateMask
|
|
if (stream->readFlag()) {
|
|
mRotate = stream->readFlag();
|
|
mStatic = stream->readFlag();
|
|
if (stream->readFlag())
|
|
mathRead(*stream, &mObjScale);
|
|
else
|
|
mObjScale.set(1, 1, 1);
|
|
}
|
|
|
|
// ThrowSrcMask && mCollisionObject
|
|
if (stream->readFlag()) {
|
|
S32 gIndex = stream->readInt(NetConnection::GhostIdBitSize);
|
|
setCollisionTimeout(static_cast<ShapeBase*>(connection->resolveGhost(gIndex)));
|
|
}
|
|
|
|
MatrixF mat = mObjToWorld;
|
|
|
|
// RotationMask && !mRotate
|
|
if (stream->readFlag()) {
|
|
// Assumes rotation is about the Z axis
|
|
AngAxisF aa;
|
|
aa.axis.set(0.0f, 0.0f, stream->readFlag() ? -1.0f : 1.0f);
|
|
stream->read(&aa.angle);
|
|
aa.setMatrix(&mat);
|
|
Point3F pos;
|
|
mObjToWorld.getColumn(3,&pos);
|
|
mat.setColumn(3,pos);
|
|
}
|
|
|
|
// PositionMask
|
|
if (stream->readFlag()) {
|
|
Point3F pos;
|
|
mathRead(*stream, &pos);
|
|
F32 speed = mVelocity.len();
|
|
if ((mAtRest = stream->readFlag()) == true)
|
|
mVelocity.set(0.0f, 0.0f, 0.0f);
|
|
else
|
|
mathRead(*stream, &mVelocity);
|
|
|
|
if (stream->readFlag() && isProperlyAdded()) {
|
|
// Determin number of ticks to warp based on the average
|
|
// of the client and server velocities.
|
|
mDelta.warpOffset = pos - mDelta.pos;
|
|
F32 as = (speed + mVelocity.len()) * 0.5f * TickSec;
|
|
F32 dt = (as > 0.00001f) ? mDelta.warpOffset.len() / as: sMaxWarpTicks;
|
|
mDelta.warpTicks = (S32)((dt > sMinWarpTicks)? getMax(mFloor(dt + 0.5f), 1.0f): 0.0f);
|
|
|
|
if (mDelta.warpTicks)
|
|
{
|
|
// Setup the warp to start on the next tick, only the
|
|
// object's position is warped.
|
|
if (mDelta.warpTicks > sMaxWarpTicks)
|
|
mDelta.warpTicks = sMaxWarpTicks;
|
|
mDelta.warpOffset /= (F32)mDelta.warpTicks;
|
|
}
|
|
else {
|
|
// Going to skip the warp, server and client are real close.
|
|
// Adjust the frame interpolation to move smoothly to the
|
|
// new position within the current tick.
|
|
Point3F cp = mDelta.pos + mDelta.posVec * mDelta.dt;
|
|
VectorF vec = mDelta.pos - cp;
|
|
F32 vl = vec.len();
|
|
if (vl) {
|
|
F32 s = mDelta.posVec.len() / vl;
|
|
mDelta.posVec = (cp - pos) * s;
|
|
}
|
|
mDelta.pos = pos;
|
|
mat.setColumn(3,pos);
|
|
}
|
|
}
|
|
else {
|
|
// Set the item to the server position
|
|
mDelta.warpTicks = 0;
|
|
mDelta.posVec.set(0,0,0);
|
|
mDelta.pos = pos;
|
|
mDelta.dt = 0;
|
|
mat.setColumn(3,pos);
|
|
}
|
|
}
|
|
Parent::setTransform(mat);
|
|
}
|
|
|
|
DefineEngineMethod( Item, isStatic, bool, (),,
|
|
"@brief Is the object static (ie, non-movable)?\n\n"
|
|
"@return True if the object is static, false if it is not.\n"
|
|
"@tsexample\n"
|
|
"// Query the item on if it is or is not static.\n"
|
|
"%isStatic = %itemData.isStatic();\n\n"
|
|
"@endtsexample\n\n"
|
|
"@see static\n"
|
|
)
|
|
{
|
|
return object->isStatic();
|
|
}
|
|
|
|
DefineEngineMethod( Item, isAtRest, bool, (),,
|
|
"@brief Is the object at rest (ie, no longer moving)?\n\n"
|
|
"@return True if the object is at rest, false if it is not.\n"
|
|
"@tsexample\n"
|
|
"// Query the item on if it is or is not at rest.\n"
|
|
"%isAtRest = %item.isAtRest();\n\n"
|
|
"@endtsexample\n\n"
|
|
)
|
|
{
|
|
return object->isAtRest();
|
|
}
|
|
|
|
DefineEngineMethod( Item, isRotating, bool, (),,
|
|
"@brief Is the object still rotating?\n\n"
|
|
"@return True if the object is still rotating, false if it is not.\n"
|
|
"@tsexample\n"
|
|
"// Query the item on if it is or is not rotating.\n"
|
|
"%isRotating = %itemData.isRotating();\n\n"
|
|
"@endtsexample\n\n"
|
|
"@see rotate\n"
|
|
)
|
|
{
|
|
return object->isRotating();
|
|
}
|
|
|
|
DefineEngineMethod( Item, setCollisionTimeout, bool, (S32 ignoreColObj),,
|
|
"@brief Temporarily disable collisions against a specific ShapeBase object.\n\n"
|
|
|
|
"This is useful to prevent a player from immediately picking up an Item they have "
|
|
"just thrown. Only one object may be on the timeout list at a time. The timeout is "
|
|
"defined as 15 ticks.\n\n"
|
|
|
|
"@param objectID ShapeBase object ID to disable collisions against.\n"
|
|
"@return Returns true if the ShapeBase object requested could be found, false if it could not.\n"
|
|
|
|
"@tsexample\n"
|
|
"// Set the ShapeBase Object ID to disable collisions against\n"
|
|
"%ignoreColObj = %player.getID();\n\n"
|
|
"// Inform this Item object to ignore collisions temproarily against the %ignoreColObj.\n"
|
|
"%item.setCollisionTimeout(%ignoreColObj);\n\n"
|
|
"@endtsexample\n\n"
|
|
)
|
|
{
|
|
ShapeBase* source = NULL;
|
|
if (Sim::findObject(ignoreColObj,source)) {
|
|
object->setCollisionTimeout(source);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
DefineEngineMethod( Item, getLastStickyPos, const char*, (),,
|
|
"@brief Get the position on the surface on which this Item is stuck.\n\n"
|
|
"@return Returns The XYZ position of where this Item is stuck.\n"
|
|
"@tsexample\n"
|
|
"// Acquire the position where this Item is currently stuck\n"
|
|
"%stuckPosition = %item.getLastStickPos();\n\n"
|
|
"@endtsexample\n\n"
|
|
"@note Server side only.\n"
|
|
)
|
|
{
|
|
static const U32 bufSize = 256;
|
|
char* ret = Con::getReturnBuffer(bufSize);
|
|
if (object->isServerObject())
|
|
dSprintf(ret, bufSize, "%g %g %g",
|
|
object->mStickyCollisionPos.x,
|
|
object->mStickyCollisionPos.y,
|
|
object->mStickyCollisionPos.z);
|
|
else
|
|
dStrcpy(ret, "0 0 0", bufSize);
|
|
|
|
return ret;
|
|
}
|
|
|
|
DefineEngineMethod( Item, getLastStickyNormal, const char *, (),,
|
|
"@brief Get the normal of the surface on which the object is stuck.\n\n"
|
|
"@return Returns The XYZ normal from where this Item is stuck.\n"
|
|
"@tsexample\n"
|
|
"// Acquire the position where this Item is currently stuck\n"
|
|
"%stuckPosition = %item.getLastStickPos();\n\n"
|
|
"@endtsexample\n\n"
|
|
"@note Server side only.\n"
|
|
)
|
|
{
|
|
static const U32 bufSize = 256;
|
|
char* ret = Con::getReturnBuffer(bufSize);
|
|
if (object->isServerObject())
|
|
dSprintf(ret, bufSize, "%g %g %g",
|
|
object->mStickyCollisionNormal.x,
|
|
object->mStickyCollisionNormal.y,
|
|
object->mStickyCollisionNormal.z);
|
|
else
|
|
dStrcpy(ret, "0 0 0", bufSize);
|
|
|
|
return ret;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
bool Item::_setStatic(void *object, const char *index, const char *data)
|
|
{
|
|
Item *i = static_cast<Item*>(object);
|
|
i->mAtRest = dAtob(data);
|
|
i->setMaskBits(InitialUpdateMask | PositionMask);
|
|
return true;
|
|
}
|
|
|
|
bool Item::_setRotate(void *object, const char *index, const char *data)
|
|
{
|
|
Item *i = static_cast<Item*>(object);
|
|
i->setMaskBits(InitialUpdateMask | RotationMask);
|
|
return true;
|
|
}
|
|
|
|
void Item::initPersistFields()
|
|
{
|
|
addGroup("Misc");
|
|
addProtectedField("static", TypeBool, Offset(mStatic, Item), &_setStatic, &defaultProtectedGetFn, "If true, the object is not moving in the world.\n");
|
|
addProtectedField("rotate", TypeBool, Offset(mRotate, Item), &_setRotate, &defaultProtectedGetFn, "If true, the object will automatically rotate around its Z axis.\n");
|
|
endGroup("Misc");
|
|
|
|
Parent::initPersistFields();
|
|
}
|
|
|
|
void Item::consoleInit()
|
|
{
|
|
Con::addVariable("Item::minWarpTicks",TypeF32,&sMinWarpTicks,
|
|
"@brief Fraction of tick at which instant warp occures on the client.\n\n"
|
|
"@ingroup GameObjects");
|
|
Con::addVariable("Item::maxWarpTicks",TypeS32,&sMaxWarpTicks,
|
|
"@brief When a warp needs to occur due to the client being too far off from the server, this is the "
|
|
"maximum number of ticks we'll allow the client to warp to catch up.\n\n"
|
|
"@ingroup GameObjects");
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
void Item::prepRenderImage( SceneRenderState* state )
|
|
{
|
|
// Items do NOT render if destroyed
|
|
if (getDamageState() == Destroyed)
|
|
return;
|
|
|
|
Parent::prepRenderImage( state );
|
|
}
|
|
|
|
void Item::buildConvex(const Box3F& box, Convex* convex)
|
|
{
|
|
if (mShapeInstance == NULL)
|
|
return;
|
|
|
|
// These should really come out of a pool
|
|
mConvexList->collectGarbage();
|
|
|
|
if (box.isOverlapped(getWorldBox()) == false)
|
|
return;
|
|
|
|
// Just return a box convex for the entire shape...
|
|
Convex* cc = 0;
|
|
CollisionWorkingList& wl = convex->getWorkingList();
|
|
for (CollisionWorkingList* itr = wl.wLink.mNext; itr != &wl; itr = itr->wLink.mNext) {
|
|
if (itr->mConvex->getType() == BoxConvexType &&
|
|
itr->mConvex->getObject() == this) {
|
|
cc = itr->mConvex;
|
|
break;
|
|
}
|
|
}
|
|
if (cc)
|
|
return;
|
|
|
|
// Create a new convex.
|
|
BoxConvex* cp = new BoxConvex;
|
|
mConvexList->registerObject(cp);
|
|
convex->addToWorkingList(cp);
|
|
cp->init(this);
|
|
|
|
mObjBox.getCenter(&cp->mCenter);
|
|
cp->mSize.x = mObjBox.len_x() / 2.0f;
|
|
cp->mSize.y = mObjBox.len_y() / 2.0f;
|
|
cp->mSize.z = mObjBox.len_z() / 2.0f;
|
|
}
|
|
|
|
void Item::advanceTime(F32 dt)
|
|
{
|
|
Parent::advanceTime(dt);
|
|
if ( isMounted() )
|
|
return;
|
|
|
|
if( mRotate )
|
|
{
|
|
F32 r = (dt / sRotationSpeed) * M_2PI;
|
|
Point3F pos = mRenderObjToWorld.getPosition();
|
|
MatrixF rotMatrix;
|
|
if( mRotate )
|
|
{
|
|
rotMatrix.set( EulerF( 0.0, 0.0, r ) );
|
|
}
|
|
else
|
|
{
|
|
rotMatrix.set( EulerF( r * 0.5, 0.0, r ) );
|
|
}
|
|
MatrixF mat = mRenderObjToWorld;
|
|
mat.setPosition( pos );
|
|
mat.mul( rotMatrix );
|
|
setRenderTransform(mat);
|
|
}
|
|
|
|
}
|