mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-20 04:34:48 +00:00
clustering work for datablocks for both consistent scanning for object parameters, as well as an eye towards orgainizing things to make reviewing what variations of components we'll be needing down the line clearer
1436 lines
42 KiB
C++
1436 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
|
|
// 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, ( SceneObject* 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, ( SceneObject* 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;
|
|
|
|
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;
|
|
U32 i = 0;
|
|
for (i=0; i<NumMirrorDirectionNodes; ++i)
|
|
{
|
|
pitchNodes[i] = -1;
|
|
headingNodes[i] = -1;
|
|
}
|
|
|
|
for (i=0; i<ShapeBase::MaxMountedImages; ++i)
|
|
{
|
|
weaponMountNode[i] = -1;
|
|
}
|
|
for (i = 0; i < NumRecoilSequences;i++)
|
|
recoilSequence[i] = -1;
|
|
pitchSequence = -1;
|
|
headingSequence = -1;
|
|
}
|
|
|
|
void TurretShapeData::initPersistFields()
|
|
{
|
|
addGroup("Steering");
|
|
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("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");
|
|
endGroup("Steering");
|
|
|
|
addGroup("Weapon State");
|
|
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()");
|
|
endGroup("Weapon State");
|
|
|
|
addGroup("Camera", "The settings used by the shape when it is the camera.");
|
|
addField("cameraOffset", TypeF32, Offset(cameraOffset, TurretShapeData),
|
|
"Vertical (Z axis) height of the camera above the turret." );
|
|
endGroup("Camera");
|
|
|
|
|
|
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 | TurretObjectType;
|
|
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;
|
|
mRecoilThread = NULL;
|
|
mImageStateThread = NULL;
|
|
}
|
|
|
|
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);
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
Parent::advanceTime(dt);
|
|
|
|
updateAnimation(dt);
|
|
|
|
_setRotation(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(SceneObject*,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->writeSignedFloat(mRot.x / M_2PI_F, 7);
|
|
stream->writeSignedFloat(mRot.z / M_2PI_F, 7);
|
|
}
|
|
|
|
void TurretShape::readPacketData(GameConnection *connection, BitStream *stream)
|
|
{
|
|
Parent::readPacketData(connection, stream);
|
|
|
|
Point3F rot(0.0f, 0.0f, 0.0f);
|
|
rot.x = stream->readSignedFloat(7) * M_2PI_F;
|
|
rot.z = stream->readSignedFloat(7) * M_2PI_F;
|
|
_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->writeSignedFloat(mRot.x / M_2PI_F, 7);
|
|
stream->writeSignedFloat(mRot.z / M_2PI_F, 7);
|
|
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);
|
|
rot.x = stream->readSignedFloat(7) * M_2PI_F;
|
|
rot.z = stream->readSignedFloat(7) * M_2PI_F;
|
|
_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 < ShapeBase::MaxMountedImages) {
|
|
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 < ShapeBase::MaxMountedImages) {
|
|
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();
|
|
}
|