mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-19 20:24:49 +00:00
1453 lines
42 KiB
C++
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()->mDefaultTranslations[node];
|
|
Quat16 defaultRot = mShapeInstance->getShape()->mDefaultRotations[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()->mDefaultTranslations[node];
|
|
Quat16 defaultRot = mShapeInstance->getShape()->mDefaultRotations[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()->mDefaultTranslations[node];
|
|
Quat16 defaultRot = mShapeInstance->getShape()->mDefaultRotations[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()->mDefaultTranslations[node];
|
|
Quat16 defaultRot = mShapeInstance->getShape()->mDefaultRotations[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, mDataBlock->mountedImagesBank);
|
|
|
|
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, mDataBlock->mountedImagesBank);
|
|
|
|
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();
|
|
}
|