Torque3D/Engine/source/T3D/turret/turretShape.cpp
2013-04-05 12:39:26 -04:00

1453 lines
42 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 "T3D/turret/turretShape.h"
#include "console/console.h"
#include "console/consoleTypes.h"
#include "console/engineAPI.h"
#include "core/stream/bitStream.h"
#include "math/mMath.h"
#include "math/mathIO.h"
#include "ts/tsShapeInstance.h"
#include "T3D/fx/cameraFXMgr.h"
#include "T3D/gameBase/gameConnection.h"
#include "T3D/physics/physicsBody.h"
//----------------------------------------------------------------------------
// 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);
const U32 sServerCollisionMask = (sClientCollisionMask);
// Trigger objects that are not normally collided with.
static U32 sTriggerMask = ItemObjectType |
TriggerObjectType |
CorpseObjectType;
//----------------------------------------------------------------------------
ImplementEnumType( TurretShapeFireLinkType,
"@brief How the weapons are linked to triggers for this TurretShape.\n\n"
"@ingroup gameObjects\n\n")
{ TurretShapeData::FireTogether, "FireTogether", "All weapons fire under trigger 0.\n" },
{ TurretShapeData::GroupedFire, "GroupedFire", "Weapon mounts 0,2 fire under trigger 0, mounts 1,3 fire under trigger 1.\n" },
{ TurretShapeData::IndividualFire, "IndividualFire", "Each weapon mount fires under its own trigger 0-3.\n" },
EndImplementEnumType;
IMPLEMENT_CO_DATABLOCK_V1(TurretShapeData);
ConsoleDocClass( TurretShapeData,
"@brief Defines properties for a TurretShape object.\n\n"
"@see TurretShape\n"
"@see TurretShapeData\n"
"@ingroup gameObjects\n"
);
IMPLEMENT_CALLBACK( TurretShapeData, onMountObject, void, ( TurretShape* turret, SceneObject* obj, S32 node ),( turret, obj, node ),
"@brief Informs the TurretShapeData object that a player is mounting it.\n\n"
"@param turret The TurretShape object.\n"
"@param obj The player that is mounting.\n"
"@param node The node the player is mounting to.\n"
"@note Server side only.\n"
);
IMPLEMENT_CALLBACK( TurretShapeData, onUnmountObject, void, ( TurretShape* turret, SceneObject* obj ),( turret, obj ),
"@brief Informs the TurretShapeData object that a player is unmounting it.\n\n"
"@param turret The TurretShape object.\n"
"@param obj The player that is unmounting.\n"
"@note Server side only.\n"
);
IMPLEMENT_CALLBACK( TurretShapeData, onStickyCollision, void, ( TurretShape* obj ),( obj ),
"@brief Informs the TurretData object that it is now sticking to another object.\n\n"
"This callback is only called if the TurretData::sticky property for this Turret is true.\n"
"@param obj The Turret object that is colliding.\n"
"@note Server side only.\n"
"@see TurretShape, TurretData\n"
);
TurretShapeData::TurretShapeData()
{
weaponLinkType = FireTogether;
shadowEnable = true;
zRotOnly = false;
startLoaded = true;
friction = 0;
elasticity = 0;
sticky = false;
gravityMod = 1.0;
maxVelocity = 25.0f;
density = 2;
drag = 0.5;
cameraOffset = 0;
maxHeading = 180.0f;
minPitch = 90.0f;
maxPitch = 90.0f;
headingRate = -1;
pitchRate = -1;
headingNode = -1;
pitchNode = -1;
for (U32 i=0; i<NumMirrorDirectionNodes; ++i)
{
pitchNodes[i] = -1;
headingNodes[i] = -1;
}
for (U32 i=0; i<ShapeBase::MaxMountedImages; ++i)
{
weaponMountNode[i] = -1;
}
}
void TurretShapeData::initPersistFields()
{
addField("zRotOnly", TypeBool, Offset(zRotOnly, TurretShapeData),
"@brief Should the turret allow only z rotations.\n\n"
"True indicates that the turret may only be rotated on its z axis, just like the Item class. "
"This keeps the turret always upright regardless of the surface it lands on.\n");
addField( "weaponLinkType", TYPEID< TurretShapeData::FireLinkType >(), Offset(weaponLinkType, TurretShapeData),
"@brief Set how the mounted weapons are linked and triggered.\n\n"
"<ul><li>FireTogether: All weapons fire under trigger 0.</li>"
"<li>GroupedFire: Weapon mounts 0,2 fire under trigger 0, mounts 1,3 fire under trigger 1.</li>"
"<li>IndividualFire: Each weapon mount fires under its own trigger 0-3.</li></ul>\n"
"@see TurretShapeFireLinkType");
addField("startLoaded", TypeBool, Offset(startLoaded, TurretShapeData),
"@brief Does the turret's mounted weapon(s) start in a loaded state.\n\n"
"True indicates that all mounted weapons start in a loaded state.\n"
"@see ShapeBase::setImageLoaded()");
addField("cameraOffset", TypeF32, Offset(cameraOffset, TurretShapeData),
"Vertical (Z axis) height of the camera above the turret." );
addField("maxHeading", TypeF32, Offset(maxHeading, TurretShapeData),
"@brief Maximum number of degrees to rotate from center.\n\n"
"A value of 180 or more degrees indicates the turret may rotate completely around.\n");
addField("minPitch", TypeF32, Offset(minPitch, TurretShapeData),
"@brief Minimum number of degrees to rotate down from straight ahead.\n\n");
addField("maxPitch", TypeF32, Offset(maxPitch, TurretShapeData),
"@brief Maximum number of degrees to rotate up from straight ahead.\n\n");
addField("headingRate", TypeF32, Offset(headingRate, TurretShapeData),
"@brief Degrees per second rotation.\n\n"
"A value of 0 means no rotation is allowed. A value less than 0 means the rotation is instantaneous.\n");
addField("pitchRate", TypeF32, Offset(pitchRate, TurretShapeData),
"@brief Degrees per second rotation.\n\n"
"A value of 0 means no rotation is allowed. A value less than 0 means the rotation is instantaneous.\n");
Parent::initPersistFields();
}
void TurretShapeData::packData(BitStream* stream)
{
Parent::packData(stream);
stream->writeFlag(zRotOnly);
stream->writeInt(weaponLinkType,NumFireLinkTypeBits);
stream->write(cameraOffset);
stream->write(maxHeading);
stream->write(minPitch);
stream->write(maxPitch);
stream->write(headingRate);
stream->write(pitchRate);
}
void TurretShapeData::unpackData(BitStream* stream)
{
Parent::unpackData(stream);
zRotOnly = stream->readFlag();
weaponLinkType = (FireLinkType)stream->readInt(NumFireLinkTypeBits);
stream->read(&cameraOffset);
stream->read(&maxHeading);
stream->read(&minPitch);
stream->read(&maxPitch);
stream->read(&headingRate);
stream->read(&pitchRate);
}
bool TurretShapeData::preload(bool server, String &errorStr)
{
if (!Parent::preload(server, errorStr))
return false;
// We have mShape at this point. Resolve nodes.
headingNode = mShape->findNode("heading");
pitchNode = mShape->findNode("pitch");
// Find any mirror pitch nodes
for (U32 i = 0; i < NumMirrorDirectionNodes; ++i)
{
char name[32];
dSprintf(name, 31, "pitch%d", i+1);
pitchNodes[i] = mShape->findNode(name);
dSprintf(name, 31, "heading%d", i+1);
headingNodes[i] = mShape->findNode(name);
}
// Resolve weapon mount point node indexes
for (U32 i = 0; i < ShapeBase::MaxMountedImages; i++) {
char fullName[256];
dSprintf(fullName,sizeof(fullName),"weaponMount%d",i);
weaponMountNode[i] = mShape->findNode(fullName);
}
// Recoil animations
recoilSequence[0] = mShape->findSequence("light_recoil");
recoilSequence[1] = mShape->findSequence("medium_recoil");
recoilSequence[2] = mShape->findSequence("heavy_recoil");
// Optional sequences used when the turret rotates
pitchSequence = mShape->findSequence("pitch");
headingSequence = mShape->findSequence("heading");
return true;
}
//----------------------------------------------------------------------------
IMPLEMENT_CO_NETOBJECT_V1(TurretShape);
ConsoleDocClass( TurretShape,
"@ingroup gameObjects\n"
);
TurretShape::TurretShape()
{
mTypeMask |= VehicleObjectType | DynamicShapeObjectType;
mDataBlock = 0;
allowManualRotation = true;
allowManualFire = true;
mTurretDelta.rot = Point3F(0.0f, 0.0f, 0.0f);
mTurretDelta.rotVec = VectorF(0.0f, 0.0f, 0.0f);
mTurretDelta.dt = 1;
mRot = mTurretDelta.rot;
mPitchAllowed = true;
mHeadingAllowed = true;
mPitchRate = -1;
mHeadingRate = -1;
mPitchUp = 0;
mPitchDown = 0;
mHeadingMax = mDegToRad(180.0f);
mRespawn = false;
mPitchThread = 0;
mHeadingThread = 0;
mSubclassTurretShapeHandlesScene = false;
// For the Item class
mSubclassItemHandlesScene = true;
}
TurretShape::~TurretShape()
{
}
//----------------------------------------------------------------------------
void TurretShape::initPersistFields()
{
addField("respawn", TypeBool, Offset(mRespawn, TurretShape),
"@brief Respawn the turret after it has been destroyed.\n\n"
"If true, the turret will respawn after it is destroyed.\n");
Parent::initPersistFields();
}
bool TurretShape::onAdd()
{
if( !Parent::onAdd() )
return false;
// Add this object to the scene
if (!mSubclassTurretShapeHandlesScene)
{
addToScene();
}
if (isServerObject() && !mSubclassTurretShapeHandlesScene)
{
scriptOnAdd();
}
return true;
}
void TurretShape::onRemove()
{
Parent::onRemove();
if (!mSubclassTurretShapeHandlesScene)
{
scriptOnRemove();
// Remove this object from the scene
removeFromScene();
}
}
bool TurretShape::onNewDataBlock(GameBaseData* dptr, bool reload)
{
mDataBlock = dynamic_cast<TurretShapeData*>(dptr);
if (!mDataBlock || !Parent::onNewDataBlock(dptr, reload))
return false;
// Mark these nodes for control by code only (will not animate in a sequence)
if (mDataBlock->headingNode != -1)
mShapeInstance->setNodeAnimationState(mDataBlock->headingNode, TSShapeInstance::MaskNodeHandsOff);
if (mDataBlock->pitchNode != -1)
mShapeInstance->setNodeAnimationState(mDataBlock->pitchNode, TSShapeInstance::MaskNodeHandsOff);
for (U32 i=0; i<TurretShapeData::NumMirrorDirectionNodes; ++i)
{
if (mDataBlock->pitchNodes[i] != -1)
{
mShapeInstance->setNodeAnimationState(mDataBlock->pitchNodes[i], TSShapeInstance::MaskNodeHandsOff);
}
if (mDataBlock->headingNodes[i] != -1)
{
mShapeInstance->setNodeAnimationState(mDataBlock->headingNodes[i], TSShapeInstance::MaskNodeHandsOff);
}
}
if (mIsZero(mDataBlock->pitchRate))
{
mPitchAllowed = false;
}
else
{
mPitchAllowed = true;
if (mDataBlock->pitchRate > 0)
{
mPitchRate = mDegToRad(mDataBlock->pitchRate);
}
else
{
mPitchRate = -1;
}
}
if (mIsZero(mDataBlock->headingRate))
{
mHeadingAllowed = false;
}
else
{
mHeadingAllowed = true;
if (mDataBlock->headingRate > 0)
{
mHeadingRate = mDegToRad(mDataBlock->headingRate);
}
else
{
mHeadingRate = -1;
}
}
mPitchUp = -mDegToRad(mDataBlock->maxPitch);
mPitchDown = mDegToRad(mDataBlock->minPitch);
mHeadingMax = mDegToRad(mDataBlock->maxHeading);
// Create Recoil thread if any recoil sequences are specified.
// Note that the server player does not play this animation.
mRecoilThread = 0;
if (isGhost())
for (U32 s = 0; s < TurretShapeData::NumRecoilSequences; s++)
if (mDataBlock->recoilSequence[s] != -1) {
mRecoilThread = mShapeInstance->addThread();
mShapeInstance->setSequence(mRecoilThread, mDataBlock->recoilSequence[s], 0);
mShapeInstance->setTimeScale(mRecoilThread, 0);
break;
}
// Reset the image state driven animation thread. This will be properly built
// in onImageStateAnimation() when needed.
mImageStateThread = 0;
// Optional rotation threads. These only play on the client.
mPitchThread = 0;
mHeadingThread = 0;
if (isGhost())
{
if (mDataBlock->pitchSequence != -1)
{
mPitchThread = mShapeInstance->addThread();
mShapeInstance->setSequence(mPitchThread, mDataBlock->pitchSequence, 0);
mShapeInstance->setTimeScale(mPitchThread, 0);
}
if (mDataBlock->headingSequence != -1)
{
mHeadingThread = mShapeInstance->addThread();
mShapeInstance->setSequence(mHeadingThread, mDataBlock->headingSequence, 0);
mShapeInstance->setTimeScale(mHeadingThread, 0);
}
}
if (!mSubclassTurretShapeHandlesScene)
{
scriptOnNewDataBlock();
}
return true;
}
//----------------------------------------------------------------------------
void TurretShape::updateAnimation(F32 dt)
{
if (mRecoilThread)
mShapeInstance->advanceTime(dt,mRecoilThread);
if (mImageStateThread)
mShapeInstance->advanceTime(dt,mImageStateThread);
// Update any pitch and heading threads
if (mPitchThread)
{
F32 d = mPitchDown - mPitchUp;
if (!mIsZero(d))
{
F32 pos = (mRot.x - mPitchUp) / d;
mShapeInstance->setPos(mPitchThread, mClampF(pos, 0.0f, 1.0f));
}
}
if (mHeadingThread)
{
F32 pos = 0.0f;
if (mHeadingMax < mDegToRad(180.0f))
{
F32 d = mHeadingMax * 2.0f;
if (!mIsZero(d))
{
pos = (mRot.z + mHeadingMax) / d;
}
}
else
{
pos = mRot.z / M_2PI;
if (pos < 0.0f)
{
// We don't want negative rotations to simply mirror the
// positive rotations but to animate into them as if -0.0
// is equivalent to 1.0.
pos = mFmod(pos, 1.0f) + 1.0f;
}
if (pos > 1.0f)
{
pos = mFmod(pos, 1.0f);
}
}
mShapeInstance->setPos(mHeadingThread, mClampF(pos, 0.0f, 1.0f));
}
}
//----------------------------------------------------------------------------
void TurretShape::onImage(U32 imageSlot, bool unmount)
{
// Clear out any previous image state animation
if (mImageStateThread)
{
mShapeInstance->destroyThread(mImageStateThread);
mImageStateThread = 0;
}
}
void TurretShape::onImageRecoil( U32, ShapeBaseImageData::StateData::RecoilState state )
{
if ( mRecoilThread )
{
if ( state != ShapeBaseImageData::StateData::NoRecoil )
{
S32 stateIndex = state - ShapeBaseImageData::StateData::LightRecoil;
if ( mDataBlock->recoilSequence[stateIndex] != -1 )
{
mShapeInstance->setSequence( mRecoilThread, mDataBlock->recoilSequence[stateIndex], 0 );
mShapeInstance->setTimeScale( mRecoilThread, 1 );
}
}
}
}
void TurretShape::onImageStateAnimation(U32 imageSlot, const char* seqName, bool direction, bool scaleToState, F32 stateTimeOutValue)
{
if (isGhost())
{
S32 seqIndex = mShapeInstance->getShape()->findSequence(seqName);
if (seqIndex != -1)
{
if (!mImageStateThread)
{
mImageStateThread = mShapeInstance->addThread();
}
mShapeInstance->setSequence( mImageStateThread, seqIndex, 0 );
F32 timeScale = (scaleToState && stateTimeOutValue) ?
mShapeInstance->getDuration(mImageStateThread) / stateTimeOutValue : 1.0f;
mShapeInstance->setTimeScale( mImageStateThread, direction ? timeScale : -timeScale );
}
}
}
//----------------------------------------------------------------------------
const char* TurretShape::getStateName()
{
if (mDamageState != Enabled)
return "Dead";
if (isMounted())
return "Mounted";
return "Ready";
}
void TurretShape::updateDamageLevel()
{
if (!isGhost())
setDamageState((mDamage >= mDataBlock->maxDamage)? Destroyed: Enabled);
if (mDamageThread)
mShapeInstance->setPos(mDamageThread, mDamage / mDataBlock->destroyedLevel);
}
//----------------------------------------------------------------------------
void TurretShape::processTick(const Move* move)
{
// Image Triggers
if (getAllowManualFire() && move && mDamageState == Enabled)
{
switch(mDataBlock->weaponLinkType)
{
case TurretShapeData::FireTogether:
{
setImageTriggerState(0,move->trigger[0]);
setImageTriggerState(1,move->trigger[0]);
setImageTriggerState(2,move->trigger[0]);
setImageTriggerState(3,move->trigger[0]);
setImageAltTriggerState(0,move->trigger[1]);
setImageAltTriggerState(1,move->trigger[1]);
setImageAltTriggerState(2,move->trigger[1]);
setImageAltTriggerState(3,move->trigger[1]);
break;
}
case TurretShapeData::GroupedFire:
{
setImageTriggerState(0,move->trigger[0]);
setImageTriggerState(1,move->trigger[1]);
setImageTriggerState(2,move->trigger[0]);
setImageTriggerState(3,move->trigger[1]);
break;
}
case TurretShapeData::IndividualFire:
{
setImageTriggerState(0,move->trigger[0]);
setImageTriggerState(1,move->trigger[1]);
setImageTriggerState(2,move->trigger[2]);
setImageTriggerState(3,move->trigger[3]);
break;
}
}
}
Parent::processTick(move);
// Change our type based on our rest state
if (mAtRest)
{
// At rest so we're static
mTypeMask &= ~DynamicShapeObjectType;
mTypeMask |= StaticObjectType | StaticShapeObjectType;
}
else
{
// Not at rest so we're dynamic
mTypeMask &= ~StaticObjectType;
mTypeMask &= ~StaticShapeObjectType;
mTypeMask |= DynamicShapeObjectType;
}
if (!isGhost())
updateAnimation(TickSec);
if (isMounted()) {
MatrixF mat;
mMount.object->getMountTransform( mMount.node, mMount.xfm, &mat );
ShapeBase::setTransform(mat);
ShapeBase::setRenderTransform(mat);
}
updateMove(move);
}
void TurretShape::interpolateTick(F32 dt)
{
Parent::interpolateTick(dt);
if (isMounted()) {
MatrixF mat;
mMount.object->getRenderMountTransform( dt, mMount.node, mMount.xfm, &mat );
ShapeBase::setRenderTransform(mat);
}
// Orientation
Point3F rot = mTurretDelta.rot + mTurretDelta.rotVec * dt;
// Make sure we don't interpolate past the limits
_applyLimits(rot);
_setRotation(rot);
}
void TurretShape::advanceTime(F32 dt)
{
// If there were any ShapeBase script threads that
// have played, then we need to update all code
// controlled nodes. This is done before the Parent
// call as script threads may play and be destroyed
// before our code is called.
bool updateNodes = false;
for (U32 i = 0; i < MaxScriptThreads; i++)
{
Thread& st = mScriptThread[i];
if (st.thread)
{
updateNodes = true;
break;
}
}
// If there is a recoil or image-based thread then
// we also need to update the nodes.
if (mRecoilThread || mImageStateThread)
updateNodes = true;
Parent::advanceTime(dt);
updateAnimation(dt);
if (updateNodes)
{
_updateNodes(mRot);
}
}
void TurretShape::setTransform( const MatrixF& mat )
{
if (mDataBlock && mDataBlock->zRotOnly)
{
// Allow Item::setTransform() to do the work
Parent::setTransform( mat );
}
else
{
// Do the transform work here to avoid Item's restriction on rotation
ShapeBase::setTransform( mat );
if ( !mStatic )
{
mAtRest = false;
mAtRestCounter = 0;
}
if ( mPhysicsRep )
mPhysicsRep->setTransform( getTransform() );
setMaskBits( Item::RotationMask | Item::PositionMask | Item::NoWarpMask );
}
}
void TurretShape::updateMove(const Move* move)
{
PROFILE_SCOPE( TurretShape_UpdateMove );
if (!move)
return;
Point3F vec, pos;
// Update orientation
mTurretDelta.rotVec = mRot;
VectorF rotVec(0, 0, 0);
if (getAllowManualRotation())
{
if (mPitchAllowed)
{
rotVec.x = move->pitch * 2.0f; // Assume that our -2PI to 2PI range was clamped to -PI to PI in script;
if (mPitchRate > 0)
{
rotVec.x *= mPitchRate * TickSec;
}
}
if (mHeadingAllowed)
{
rotVec.z = move->yaw * 2.0f; // Assume that our -2PI to 2PI range was clamped to -PI to PI in script
if (mHeadingRate > 0)
{
rotVec.z *= mHeadingRate * TickSec;
}
}
}
mRot.x += rotVec.x;
mRot.z += rotVec.z;
_applyLimits(mRot);
if (isServerObject())
{
// As this ends up animating shape nodes, we have no sense of a transform and
// render transform. Therefore we treat this as the true transform and leave the
// client shape node changes to interpolateTick() as the render transform. Otherwise
// on the client we'll have this node change from processTick() and then backstepping
// and catching up to the true node change in interpolateTick(), which causes the
// turret to stutter.
_setRotation( mRot );
}
else
{
// If on the client, calc delta for backstepping
mTurretDelta.rot = mRot;
mTurretDelta.rotVec = mTurretDelta.rotVec - mTurretDelta.rot;
}
setMaskBits(TurretUpdateMask);
}
bool TurretShape::getNodeTransform(S32 node, MatrixF& mat)
{
if (node == -1)
return false;
MatrixF nodeTransform = mShapeInstance->mNodeTransforms[node];
const Point3F& scale = getScale();
// The position of the node needs to be scaled.
Point3F position = nodeTransform.getPosition();
position.convolve( scale );
nodeTransform.setPosition( position );
mat.mul(mObjToWorld, nodeTransform);
return true;
}
bool TurretShape::getWorldNodeTransform(S32 node, MatrixF& mat)
{
MatrixF nodeMat;
if (!getNodeTransform(node, nodeMat))
return false;
nodeMat.affineInverse();
mat = nodeMat;
return true;
}
void TurretShape::_setRotation(const Point3F& rot)
{
_updateNodes(rot);
mShapeInstance->animate();
mRot = rot;
}
void TurretShape::_updateNodes(const Point3F& rot)
{
EulerF xRot(rot.x, 0.0f, 0.0f);
EulerF zRot(0.0f, 0.0f, rot.z);
// Set heading
S32 node = mDataBlock->headingNode;
if (node != -1)
{
MatrixF* mat = &mShapeInstance->mNodeTransforms[node];
Point3F defaultPos = mShapeInstance->getShape()->defaultTranslations[node];
Quat16 defaultRot = mShapeInstance->getShape()->defaultRotations[node];
QuatF qrot(zRot);
qrot *= defaultRot.getQuatF();
qrot.setMatrix( mat );
mat->setColumn(3, defaultPos);
}
// Set pitch
node = mDataBlock->pitchNode;
if (node != -1)
{
MatrixF* mat = &mShapeInstance->mNodeTransforms[node];
Point3F defaultPos = mShapeInstance->getShape()->defaultTranslations[node];
Quat16 defaultRot = mShapeInstance->getShape()->defaultRotations[node];
QuatF qrot(xRot);
qrot *= defaultRot.getQuatF();
qrot.setMatrix( mat );
mat->setColumn(3, defaultPos);
}
// Now the mirror direction nodes, if any
for (U32 i=0; i<TurretShapeData::NumMirrorDirectionNodes; ++i)
{
node = mDataBlock->pitchNodes[i];
if (node != -1)
{
MatrixF* mat = &mShapeInstance->mNodeTransforms[node];
Point3F defaultPos = mShapeInstance->getShape()->defaultTranslations[node];
Quat16 defaultRot = mShapeInstance->getShape()->defaultRotations[node];
QuatF qrot(xRot);
qrot *= defaultRot.getQuatF();
qrot.setMatrix( mat );
mat->setColumn(3, defaultPos);
}
node = mDataBlock->headingNodes[i];
if (node != -1)
{
MatrixF* mat = &mShapeInstance->mNodeTransforms[node];
Point3F defaultPos = mShapeInstance->getShape()->defaultTranslations[node];
Quat16 defaultRot = mShapeInstance->getShape()->defaultRotations[node];
QuatF qrot(zRot);
qrot *= defaultRot.getQuatF();
qrot.setMatrix( mat );
mat->setColumn(3, defaultPos);
}
}
mShapeInstance->setDirty(TSShapeInstance::TransformDirty);
}
void TurretShape::_applyLimits(Point3F& rot)
{
rot.x = mClampF(rot.x, mPitchUp, mPitchDown);
if (mHeadingMax < mDegToRad(180.0f))
{
rot.z = mClampF(rot.z, -mHeadingMax, mHeadingMax);
}
}
bool TurretShape::_outsideLimits(Point3F& rot)
{
if (rot.x < mPitchUp || rot.x > mPitchDown)
return true;
if (mHeadingMax < mDegToRad(180.0f))
{
if (rot.z < -mHeadingMax || rot.z > mHeadingMax)
return true;
}
return false;
}
//----------------------------------------------------------------------------
void TurretShape::mountObject( SceneObject *obj, S32 node, const MatrixF &xfm )
{
Parent::mountObject(obj, node, xfm);
if (isClientObject())
{
if (obj)
{
GameConnection* conn = obj->getControllingClient();
if (conn)
{
// Allow the client to set up any action maps, HUD, etc.
Con::executef("turretMountCallback", Con::getIntArg(getId()), Con::getIntArg(obj->getId()), Con::getIntArg(true));
}
}
}
else
{
mDataBlock->onMountObject_callback( this, obj, node );
}
}
void TurretShape::unmountObject( SceneObject *obj )
{
Parent::unmountObject(obj);
if (isClientObject())
{
if (obj)
{
GameConnection* conn = obj->getControllingClient();
if (conn)
{
// Allow the client to set up any action maps, HUD, etc.
Con::executef("turretMountCallback", Con::getIntArg(getId()), Con::getIntArg(obj->getId()), Con::getIntArg(false));
}
}
}
else
{
mDataBlock->onUnmountObject_callback( this, obj );
}
}
void TurretShape::onUnmount(ShapeBase*,S32)
{
// Make sure the client get's the final server pos of this turret.
setMaskBits(PositionMask);
}
//----------------------------------------------------------------------------
void TurretShape::getCameraParameters(F32 *min,F32* max,Point3F* off,MatrixF* rot)
{
*min = mDataBlock->cameraMinDist;
*max = mDataBlock->cameraMaxDist;
off->set(0,0,mDataBlock->cameraOffset);
rot->identity();
}
void TurretShape::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.
{
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;
if (mContainer->castRay(sp, ep,
~(WaterObjectType | GameBaseObjectType | DefaultObjectType | sTriggerMask),
&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 + (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 TurretShape::writePacketData(GameConnection *connection, BitStream *stream)
{
// Update client regardless of status flags.
Parent::writePacketData(connection, stream);
stream->write(mRot.x);
stream->write(mRot.z);
}
void TurretShape::readPacketData(GameConnection *connection, BitStream *stream)
{
Parent::readPacketData(connection, stream);
Point3F rot(0.0f, 0.0f, 0.0f);
stream->read(&rot.x);
stream->read(&rot.z);
_setRotation(rot);
mTurretDelta.rot = rot;
mTurretDelta.rotVec.set(0.0f, 0.0f, 0.0f);
}
U32 TurretShape::packUpdate(NetConnection *connection, U32 mask, BitStream *stream)
{
// Handle rotation ourselves (so it is not locked to the Z axis like for Items)
U32 retMask = Parent::packUpdate( connection, mask & (~Item::RotationMask), stream );
if (stream->writeFlag(mask & InitialUpdateMask)) {
stream->writeFlag(mRespawn);
}
if ( stream->writeFlag( mask & Item::RotationMask ) )
{
QuatF rot( mObjToWorld );
mathWrite( *stream, rot );
}
// 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((NetConnection*)getControllingClient() == connection && !(mask & InitialUpdateMask)))
return 0;
if (stream->writeFlag(mask & TurretUpdateMask))
{
stream->write(mRot.x);
stream->write(mRot.z);
stream->write(allowManualRotation);
stream->write(allowManualFire);
}
return retMask;
}
void TurretShape::unpackUpdate(NetConnection *connection, BitStream *stream)
{
Parent::unpackUpdate(connection,stream);
// InitialUpdateMask
if (stream->readFlag()) {
mRespawn = stream->readFlag();
}
// Item::RotationMask
if ( stream->readFlag() )
{
QuatF rot;
mathRead( *stream, &rot );
Point3F pos = mObjToWorld.getPosition();
rot.setMatrix( &mObjToWorld );
mObjToWorld.setPosition( pos );
}
// controlled by the client?
if(stream->readFlag())
return;
// TurretUpdateMask
if (stream->readFlag())
{
Point3F rot(0.0f, 0.0f, 0.0f);
stream->read(&rot.x);
stream->read(&rot.z);
_setRotation(rot);
// New delta for client side interpolation
mTurretDelta.rot = rot;
mTurretDelta.rotVec = VectorF(0.0f, 0.0f, 0.0f);
stream->read(&allowManualRotation);
stream->read(&allowManualFire);
}
}
//----------------------------------------------------------------------------
void TurretShape::getWeaponMountTransform( S32 index, const MatrixF &xfm, MatrixF *outMat )
{
// Returns mount point to world space transform
if ( index >= 0 && index < SceneObject::NumMountPoints) {
S32 ni = mDataBlock->weaponMountNode[index];
if (ni != -1) {
MatrixF mountTransform = mShapeInstance->mNodeTransforms[ni];
mountTransform.mul( xfm );
const Point3F& scale = getScale();
// The position of the mount point needs to be scaled.
Point3F position = mountTransform.getPosition();
position.convolve( scale );
mountTransform.setPosition( position );
// Also we would like the object to be scaled to the model.
outMat->mul(mObjToWorld, mountTransform);
return;
}
}
// Then let SceneObject handle it.
GrandParent::getMountTransform( index, xfm, outMat );
}
void TurretShape::getRenderWeaponMountTransform( F32 delta, S32 mountPoint, const MatrixF &xfm, MatrixF *outMat )
{
// Returns mount point to world space transform
if ( mountPoint >= 0 && mountPoint < SceneObject::NumMountPoints) {
S32 ni = mDataBlock->weaponMountNode[mountPoint];
if (ni != -1) {
MatrixF mountTransform = mShapeInstance->mNodeTransforms[ni];
mountTransform.mul( xfm );
const Point3F& scale = getScale();
// The position of the mount point needs to be scaled.
Point3F position = mountTransform.getPosition();
position.convolve( scale );
mountTransform.setPosition( position );
// Also we would like the object to be scaled to the model.
mountTransform.scale( scale );
outMat->mul(getRenderTransform(), mountTransform);
return;
}
}
// Then let SceneObject handle it.
GrandParent::getRenderMountTransform( delta, mountPoint, xfm, outMat );
}
void TurretShape::getImageTransform(U32 imageSlot,MatrixF* mat)
{
// Image transform in world space
MountedImage& image = mMountedImageList[imageSlot];
if (image.dataBlock) {
ShapeBaseImageData& data = *image.dataBlock;
MatrixF nmat;
if (data.useEyeOffset && isFirstPerson()) {
getEyeTransform(&nmat);
mat->mul(nmat,data.eyeOffset);
}
else {
getWeaponMountTransform( imageSlot, MatrixF::Identity, &nmat );
mat->mul(nmat,data.mountTransform[getImageShapeIndex(image)]);
}
}
else
*mat = mObjToWorld;
}
void TurretShape::getRenderImageTransform( U32 imageSlot, MatrixF* mat, bool noEyeOffset )
{
// Image transform in world space
MountedImage& image = mMountedImageList[imageSlot];
if (image.dataBlock)
{
ShapeBaseImageData& data = *image.dataBlock;
MatrixF nmat;
if ( !noEyeOffset && data.useEyeOffset && isFirstPerson() )
{
getRenderEyeTransform(&nmat);
mat->mul(nmat,data.eyeOffset);
}
else
{
getRenderWeaponMountTransform( 0.0f, imageSlot, MatrixF::Identity, &nmat );
mat->mul(nmat,data.mountTransform[getImageShapeIndex(image)]);
}
}
else
*mat = getRenderTransform();
}
void TurretShape::getImageTransform(U32 imageSlot,S32 node,MatrixF* mat)
{
// Same as ShapeBase::getImageTransform() other than getRenderWeaponMountTransform() below
// Image transform in world space
MountedImage& image = mMountedImageList[imageSlot];
if (image.dataBlock)
{
if (node != -1)
{
ShapeBaseImageData& data = *image.dataBlock;
U32 shapeIndex = getImageShapeIndex(image);
MatrixF nmat = image.shapeInstance[shapeIndex]->mNodeTransforms[node];
MatrixF mmat;
if (data.useEyeNode && isFirstPerson() && data.eyeMountNode[shapeIndex] != -1)
{
// We need to animate, even on the server, to make sure the nodes are in the correct location.
image.shapeInstance[shapeIndex]->animate();
MatrixF emat;
getEyeBaseTransform(&emat);
MatrixF mountTransform = image.shapeInstance[shapeIndex]->mNodeTransforms[data.eyeMountNode[shapeIndex]];
mountTransform.affineInverse();
mmat.mul(emat, mountTransform);
}
else if (data.useEyeOffset && isFirstPerson())
{
MatrixF emat;
getEyeTransform(&emat);
mmat.mul(emat,data.eyeOffset);
}
else
{
MatrixF emat;
getWeaponMountTransform( imageSlot, MatrixF::Identity, &emat );
mmat.mul(emat,data.mountTransform[shapeIndex]);
}
mat->mul(mmat, nmat);
}
else
getImageTransform(imageSlot,mat);
}
else
*mat = mObjToWorld;
}
void TurretShape::getRenderImageTransform(U32 imageSlot,S32 node,MatrixF* mat)
{
// Same as ShapeBase::getRenderImageTransform() other than getRenderWeaponMountTransform() below
// Image transform in world space
MountedImage& image = mMountedImageList[imageSlot];
if (image.dataBlock)
{
if (node != -1)
{
ShapeBaseImageData& data = *image.dataBlock;
U32 shapeIndex = getImageShapeIndex(image);
MatrixF nmat = image.shapeInstance[shapeIndex]->mNodeTransforms[node];
MatrixF mmat;
if ( data.useEyeNode && isFirstPerson() && data.eyeMountNode[shapeIndex] != -1 )
{
MatrixF emat;
getRenderEyeBaseTransform(&emat);
MatrixF mountTransform = image.shapeInstance[shapeIndex]->mNodeTransforms[data.eyeMountNode[shapeIndex]];
mountTransform.affineInverse();
mmat.mul(emat, mountTransform);
}
else if ( data.useEyeOffset && isFirstPerson() )
{
MatrixF emat;
getRenderEyeTransform(&emat);
mmat.mul(emat,data.eyeOffset);
}
else
{
MatrixF emat;
getRenderWeaponMountTransform( 0.0f, imageSlot, MatrixF::Identity, &emat );
mmat.mul(emat,data.mountTransform[shapeIndex]);
}
mat->mul(mmat, nmat);
}
else
getRenderImageTransform(imageSlot,mat);
}
else
*mat = getRenderTransform();
}
//----------------------------------------------------------------------------
void TurretShape::prepRenderImage( SceneRenderState *state )
{
// Skip the Item class rendering
_prepRenderImage( state, true, true );
}
void TurretShape::prepBatchRender( SceneRenderState *state, S32 mountedImageIndex )
{
Parent::prepBatchRender( state, mountedImageIndex );
if ( !gShowBoundingBox )
return;
//if ( mountedImageIndex != -1 )
//{
// ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
// ri->renderDelegate.bind( this, &Vehicle::_renderMuzzleVector );
// ri->objectIndex = mountedImageIndex;
// ri->type = RenderPassManager::RIT_Editor;
// state->getRenderPass()->addInst( ri );
// return;
//}
//ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
//ri->renderDelegate.bind( this, &Vehicle::_renderMassAndContacts );
//ri->type = RenderPassManager::RIT_Editor;
//state->getRenderPass()->addInst( ri );
}
//----------------------------------------------------------------------------
DefineEngineMethod( TurretShape, getAllowManualRotation, bool, (),,
"@brief Get if the turret is allowed to rotate through moves.\n\n"
"@return True if the turret is allowed to rotate through moves.\n" )
{
return object->getAllowManualRotation();
}
DefineEngineMethod( TurretShape, setAllowManualRotation, void, (bool allow),,
"@brief Set if the turret is allowed to rotate through moves.\n\n"
"@param allow If true then the turret may be rotated through moves.\n")
{
return object->setAllowManualRotation(allow);
}
DefineEngineMethod( TurretShape, getAllowManualFire, bool, (),,
"@brief Get if the turret is allowed to fire through moves.\n\n"
"@return True if the turret is allowed to fire through moves.\n" )
{
return object->getAllowManualFire();
}
DefineEngineMethod( TurretShape, setAllowManualFire, void, (bool allow),,
"@brief Set if the turret is allowed to fire through moves.\n\n"
"@param allow If true then the turret may be fired through moves.\n")
{
return object->setAllowManualFire(allow);
}
DefineEngineMethod( TurretShape, getState, const char*, (),,
"@brief Get the name of the turret's current state.\n\n"
"The state is one of the following:\n\n<ul>"
"<li>Dead - The TurretShape is destroyed.</li>"
"<li>Mounted - The TurretShape is mounted to an object such as a vehicle.</li>"
"<li>Ready - The TurretShape is free to move. The usual state.</li></ul>\n"
"@return The current state; one of: \"Dead\", \"Mounted\", \"Ready\"\n" )
{
return object->getStateName();
}
DefineEngineMethod( TurretShape, getTurretEulerRotation, Point3F, (),,
"@brief Get Euler rotation of this turret's heading and pitch nodes.\n\n"
"@return the orientation of the turret's heading and pitch nodes in the "
"form of rotations around the X, Y and Z axes in degrees.\n" )
{
Point3F euler = object->getTurretRotation();
// Convert to degrees.
euler.x = mRadToDeg( euler.x );
euler.y = mRadToDeg( euler.y );
euler.z = mRadToDeg( euler.z );
return euler;
}
DefineEngineMethod( TurretShape, setTurretEulerRotation, void, ( Point3F rot ),,
"@brief Set Euler rotation of this turret's heading and pitch nodes in degrees.\n\n"
"@param rot The rotation in degrees. The pitch is the X component and the "
"heading is the Z component. The Y component is ignored.\n")
{
object->setTurretRotation( rot );
}
DefineEngineMethod( TurretShape, doRespawn, bool, (),,
"@brief Does the turret respawn after it has been destroyed.\n\n"
"@returns True if the turret respawns.\n")
{
return object->doRespawn();
}