mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-20 04:34:48 +00:00
5511 lines
169 KiB
C++
5511 lines
169 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.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
|
|
// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
|
|
// Copyright (C) 2015 Faust Logic, Inc.
|
|
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
|
|
|
|
#include "platform/platform.h"
|
|
#include "T3D/shapeBase.h"
|
|
|
|
#include "core/dnet.h"
|
|
#include "sfx/sfxSystem.h"
|
|
#include "sfx/sfxSource.h"
|
|
#include "sfx/sfxTrack.h"
|
|
#include "sfx/sfxDescription.h"
|
|
#include "T3D/sfx/sfx3DWorld.h"
|
|
#include "T3D/gameBase/gameConnection.h"
|
|
#include "console/consoleTypes.h"
|
|
#include "console/engineAPI.h"
|
|
#include "core/stream/bitStream.h"
|
|
#include "ts/tsPartInstance.h"
|
|
#include "ts/tsShapeInstance.h"
|
|
#include "ts/tsMaterialList.h"
|
|
#include "scene/sceneManager.h"
|
|
#include "scene/sceneRenderState.h"
|
|
#include "scene/sceneObjectLightingPlugin.h"
|
|
#include "T3D/fx/explosion.h"
|
|
#include "T3D/fx/cameraFXMgr.h"
|
|
#include "environment/waterBlock.h"
|
|
#include "T3D/debris.h"
|
|
#include "T3D/physicalZone.h"
|
|
#include "T3D/containerQuery.h"
|
|
#include "math/mathUtils.h"
|
|
#include "math/mMatrix.h"
|
|
#include "math/mTransform.h"
|
|
#include "math/mRandom.h"
|
|
#include "platform/profiler.h"
|
|
#include "gfx/gfxCubemap.h"
|
|
#include "gfx/gfxDrawUtil.h"
|
|
#include "gfx/gfxTransformSaver.h"
|
|
#include "renderInstance/renderPassManager.h"
|
|
#include "collision/earlyOutPolyList.h"
|
|
#include "core/resourceManager.h"
|
|
#include "scene/reflectionManager.h"
|
|
#include "gfx/sim/cubemapData.h"
|
|
#include "materials/materialManager.h"
|
|
#include "materials/materialFeatureTypes.h"
|
|
#include "renderInstance/renderOcclusionMgr.h"
|
|
#include "core/stream/fileStream.h"
|
|
#include "T3D/accumulationVolume.h"
|
|
#include "console/persistenceManager.h"
|
|
#include "AI/AIController.h"
|
|
|
|
IMPLEMENT_CO_DATABLOCK_V1(ShapeBaseData);
|
|
|
|
ConsoleDocClass( ShapeBaseData,
|
|
"@brief Defines properties for a ShapeBase object.\n\n"
|
|
"@see ShapeBase\n"
|
|
"@ingroup gameObjects\n"
|
|
);
|
|
|
|
IMPLEMENT_CALLBACK( ShapeBaseData, onEnabled, void, ( ShapeBase* obj, const char* lastState ), ( obj, lastState ),
|
|
"@brief Called when the object damage state changes to Enabled.\n\n"
|
|
"@param obj The ShapeBase object\n"
|
|
"@param lastState The previous damage state\n" );
|
|
|
|
IMPLEMENT_CALLBACK( ShapeBaseData, onDisabled, void, ( ShapeBase* obj, const char* lastState ), ( obj, lastState ),
|
|
"@brief Called when the object damage state changes to Disabled.\n\n"
|
|
"@param obj The ShapeBase object\n"
|
|
"@param lastState The previous damage state\n" );
|
|
|
|
IMPLEMENT_CALLBACK( ShapeBaseData, onDestroyed, void, ( ShapeBase* obj, const char* lastState ), ( obj, lastState ),
|
|
"@brief Called when the object damage state changes to Destroyed.\n\n"
|
|
"@param obj The ShapeBase object\n"
|
|
"@param lastState The previous damage state\n" );
|
|
|
|
IMPLEMENT_CALLBACK( ShapeBaseData, onImpact, void, ( ShapeBase* obj, SceneObject *collObj, VectorF vec, F32 len ), ( obj, collObj, vec, len ),
|
|
"@brief Called when we collide with another object beyond some impact speed.\n\n"
|
|
"The Player class makes use of this callback when a collision speed is more than PlayerData::minImpactSpeed.\n"
|
|
"@param obj The ShapeBase object\n"
|
|
"@param collObj The object we collided with\n"
|
|
"@param vec Collision impact vector\n"
|
|
"@param len Length of the impact vector\n" );
|
|
|
|
IMPLEMENT_CALLBACK( ShapeBaseData, onCollision, void, ( ShapeBase* obj, SceneObject *collObj, VectorF vec, F32 len ), ( obj, collObj, vec, len ),
|
|
"@brief Called when we collide with another object.\n\n"
|
|
"@param obj The ShapeBase object\n"
|
|
"@param collObj The object we collided with\n"
|
|
"@param vec Collision impact vector\n"
|
|
"@param len Length of the impact vector\n" );
|
|
|
|
IMPLEMENT_CALLBACK( ShapeBaseData, onDamage, void, ( ShapeBase* obj, F32 delta ), ( obj, delta ),
|
|
"@brief Called when the object is damaged.\n\n"
|
|
"@param obj The ShapeBase object\n"
|
|
"@param obj The ShapeBase object\n"
|
|
"@param delta The amount of damage received." );
|
|
|
|
IMPLEMENT_CALLBACK( ShapeBaseData, onTrigger, void, ( ShapeBase* obj, S32 index, bool state ), ( obj, index, state ),
|
|
"@brief Called when a move trigger input changes state.\n\n"
|
|
"@param obj The ShapeBase object\n"
|
|
"@param index Index of the trigger that changed\n"
|
|
"@param state New state of the trigger\n" );
|
|
|
|
IMPLEMENT_CALLBACK(ShapeBaseData, onEndSequence, void, (ShapeBase* obj, S32 slot, const char* name), (obj, slot, name),
|
|
"@brief Called when a thread playing a non-cyclic sequence reaches the end of the "
|
|
"sequence.\n\n"
|
|
"@param obj The ShapeBase object\n"
|
|
"@param slot Thread slot that finished playing\n"
|
|
"@param name Thread name that finished playing\n");
|
|
|
|
IMPLEMENT_CALLBACK( ShapeBaseData, onForceUncloak, void, ( ShapeBase* obj, const char* reason ), ( obj, reason ),
|
|
"@brief Called when the object is forced to uncloak.\n\n"
|
|
"@param obj The ShapeBase object\n"
|
|
"@param reason String describing why the object was uncloaked\n" );
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Timeout for non-looping sounds on a channel
|
|
static SimTime sAudioTimeout = 500;
|
|
F32 ShapeBase::sWhiteoutDec = 0.007f;
|
|
F32 ShapeBase::sDamageFlashDec = 0.02f;
|
|
F32 ShapeBase::sFullCorrectionDistance = 0.5f;
|
|
F32 ShapeBase::sCloakSpeed = 0.5;
|
|
U32 ShapeBase::sLastRenderFrame = 0;
|
|
|
|
static const char *sDamageStateName[] =
|
|
{
|
|
// Index by enum ShapeBase::DamageState
|
|
"Enabled",
|
|
"Disabled",
|
|
"Destroyed"
|
|
};
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
ShapeBaseData::ShapeBaseData()
|
|
:
|
|
shadowSize( 128 ),
|
|
shadowMaxVisibleDistance( 80.0f ),
|
|
shadowProjectionDistance( 10.0f ),
|
|
shadowSphereAdjust( 1.0f ),
|
|
cloakTexName( StringTable->EmptyString() ),
|
|
cubeDescId( 0 ),
|
|
reflectorDesc( NULL ),
|
|
debris( NULL ),
|
|
debrisID( 0 ),
|
|
explosion( NULL ),
|
|
explosionID( 0 ),
|
|
underwaterExplosion( NULL ),
|
|
underwaterExplosionID( 0 ),
|
|
mass( 1.0f ),
|
|
drag( 0.0f ),
|
|
density( 1.0f ),
|
|
maxEnergy( 0.0f ),
|
|
maxDamage( 1.0f ),
|
|
repairRate( 0.0033f ),
|
|
disabledLevel( 1.0f ),
|
|
destroyedLevel( 1.0f ),
|
|
cameraMaxDist( 0.0f ),
|
|
cameraMinDist( 0.2f ),
|
|
cameraDefaultFov( 75.0f ),
|
|
cameraMinFov( 5.0f ),
|
|
cameraMaxFov( 120.f ),
|
|
cameraCanBank( false ),
|
|
mountedImagesBank( false ),
|
|
mCRC( 0 ),
|
|
computeCRC( false ),
|
|
eyeNode( -1 ),
|
|
earNode( -1 ),
|
|
cameraNode( -1 ),
|
|
debrisDetail( -1 ),
|
|
damageSequence( -1 ),
|
|
hulkSequence( -1 ),
|
|
observeThroughObject( false ),
|
|
firstPersonOnly( false ),
|
|
useEyePoint( false ),
|
|
isInvincible( false ),
|
|
renderWhenDestroyed( true ),
|
|
inheritEnergyFromMount( false ),
|
|
mAIControllData(NULL)
|
|
{
|
|
INIT_ASSET(Shape);
|
|
INIT_ASSET(DebrisShape);
|
|
|
|
dMemset( mountPointNode, -1, sizeof( S32 ) * SceneObject::NumMountPoints );
|
|
remap_txr_tags = NULL;
|
|
remap_buffer = NULL;
|
|
silent_bbox_check = false;
|
|
}
|
|
|
|
ShapeBaseData::ShapeBaseData(const ShapeBaseData& other, bool temp_clone) : GameBaseData(other, temp_clone)
|
|
{
|
|
shadowSize = other.shadowSize;
|
|
shadowMaxVisibleDistance = other.shadowMaxVisibleDistance;
|
|
shadowProjectionDistance = other.shadowProjectionDistance;
|
|
shadowSphereAdjust = other.shadowSphereAdjust;
|
|
cloakTexName = other.cloakTexName;
|
|
CLONE_ASSET(Shape);
|
|
cubeDescName = other.cubeDescName;
|
|
cubeDescId = other.cubeDescId;
|
|
reflectorDesc = other.reflectorDesc;
|
|
debris = other.debris;
|
|
debrisID = other.debrisID; // -- for pack/unpack of debris ptr
|
|
CLONE_ASSET(DebrisShape);
|
|
explosion = other.explosion;
|
|
explosionID = other.explosionID; // -- for pack/unpack of explosion ptr
|
|
underwaterExplosion = other.underwaterExplosion;
|
|
underwaterExplosionID = other.underwaterExplosionID; // -- for pack/unpack of underwaterExplosion ptr
|
|
mass = other.mass;
|
|
drag = other.drag;
|
|
density = other.density;
|
|
maxEnergy = other.maxEnergy;
|
|
maxDamage = other.maxDamage;
|
|
repairRate = other.repairRate;
|
|
disabledLevel = other.disabledLevel;
|
|
destroyedLevel = other.destroyedLevel;
|
|
cameraMaxDist = other.cameraMaxDist;
|
|
cameraMinDist = other.cameraMinDist;
|
|
cameraDefaultFov = other.cameraDefaultFov;
|
|
cameraMinFov = other.cameraMinFov;
|
|
cameraMaxFov = other.cameraMaxFov;
|
|
cameraCanBank = other.cameraCanBank;
|
|
mountedImagesBank = other.mountedImagesBank;
|
|
mShape = other.mShape; // -- TSShape loaded using shapeName
|
|
mCRC = other.mCRC; // -- from shape, used to verify client shape
|
|
computeCRC = other.computeCRC;
|
|
eyeNode = other.eyeNode; // -- from shape node "eye"
|
|
earNode = other.earNode; // -- from shape node "ear"
|
|
cameraNode = other.cameraNode; // -- from shape node "cam"
|
|
dMemcpy(mountPointNode, other.mountPointNode, sizeof(mountPointNode)); // -- from shape nodes "mount#" 0-31
|
|
debrisDetail = other.debrisDetail; // -- from shape detail "Debris-17"
|
|
damageSequence = other.damageSequence; // -- from shape sequence "Damage"
|
|
hulkSequence = other.hulkSequence; // -- from shape sequence "Visibility"
|
|
observeThroughObject = other.observeThroughObject;
|
|
collisionDetails = other.collisionDetails; // -- calc from shape (this is a Vector copy)
|
|
collisionBounds = other.collisionBounds; // -- calc from shape (this is a Vector copy)
|
|
LOSDetails = other.LOSDetails; // -- calc from shape (this is a Vector copy)
|
|
firstPersonOnly = other.firstPersonOnly;
|
|
useEyePoint = other.useEyePoint;
|
|
isInvincible = other.isInvincible;
|
|
renderWhenDestroyed = other.renderWhenDestroyed;
|
|
inheritEnergyFromMount = other.inheritEnergyFromMount;
|
|
remap_txr_tags = other.remap_txr_tags;
|
|
remap_buffer = other.remap_buffer;
|
|
txr_tag_remappings = other.txr_tag_remappings;
|
|
silent_bbox_check = other.silent_bbox_check;
|
|
}
|
|
|
|
struct ShapeBaseDataProto
|
|
{
|
|
F32 mass;
|
|
F32 drag;
|
|
F32 density;
|
|
F32 maxEnergy;
|
|
F32 cameraMaxDist;
|
|
F32 cameraMinDist;
|
|
F32 cameraDefaultFov;
|
|
F32 cameraMinFov;
|
|
F32 cameraMaxFov;
|
|
|
|
|
|
ShapeBaseDataProto()
|
|
{
|
|
mass = 1;
|
|
drag = 0;
|
|
density = 1;
|
|
maxEnergy = 0;
|
|
cameraMaxDist = 0;
|
|
cameraMinDist = 0.2f;
|
|
cameraDefaultFov = 75.f;
|
|
cameraMinFov = 5.0f;
|
|
cameraMaxFov = 120.f;
|
|
}
|
|
};
|
|
|
|
static ShapeBaseDataProto gShapeBaseDataProto;
|
|
|
|
ShapeBaseData::~ShapeBaseData()
|
|
{
|
|
|
|
if (remap_buffer && !isTempClone())
|
|
dFree(remap_buffer);
|
|
}
|
|
|
|
bool ShapeBaseData::preload(bool server, String &errorStr)
|
|
{
|
|
if (!Parent::preload(server, errorStr))
|
|
return false;
|
|
bool shapeError = false;
|
|
|
|
// Resolve objects transmitted from server
|
|
if (!server) {
|
|
|
|
if( !explosion && explosionID != 0 )
|
|
{
|
|
if( Sim::findObject( explosionID, explosion ) == false)
|
|
{
|
|
Con::errorf( ConsoleLogEntry::General, "ShapeBaseData::preload: Invalid packet, bad datablockId(explosion): 0x%x", explosionID );
|
|
}
|
|
AssertFatal(!(explosion && ((explosionID < DataBlockObjectIdFirst) || (explosionID > DataBlockObjectIdLast))),
|
|
"ShapeBaseData::preload: invalid explosion data");
|
|
}
|
|
|
|
if( !underwaterExplosion && underwaterExplosionID != 0 )
|
|
{
|
|
if( Sim::findObject( underwaterExplosionID, underwaterExplosion ) == false)
|
|
{
|
|
Con::errorf( ConsoleLogEntry::General, "ShapeBaseData::preload: Invalid packet, bad datablockId(underwaterExplosion): 0x%x", underwaterExplosionID );
|
|
}
|
|
AssertFatal(!(underwaterExplosion && ((underwaterExplosionID < DataBlockObjectIdFirst) || (underwaterExplosionID > DataBlockObjectIdLast))),
|
|
"ShapeBaseData::preload: invalid underwaterExplosion data");
|
|
}
|
|
|
|
if( !debris && debrisID != 0 )
|
|
{
|
|
Sim::findObject( debrisID, debris );
|
|
AssertFatal(!(debris && ((debrisID < DataBlockObjectIdFirst) || (debrisID > DataBlockObjectIdLast))),
|
|
"ShapeBaseData::preload: invalid debris data");
|
|
}
|
|
|
|
if( bool(mDebrisShape))
|
|
{
|
|
TSShapeInstance* pDummy = new TSShapeInstance(mDebrisShape, !server);
|
|
delete pDummy;
|
|
}
|
|
}
|
|
|
|
S32 i;
|
|
U32 assetStatus = ShapeAsset::getAssetErrCode(mShapeAsset);
|
|
if (assetStatus == AssetBase::Ok || assetStatus == AssetBase::UsingFallback)
|
|
{
|
|
if (!server && !mShape->preloadMaterialList(mShape.getPath()) && NetConnection::filesWereDownloaded())
|
|
shapeError = true;
|
|
|
|
if(computeCRC)
|
|
{
|
|
Con::printf("Validation required for shape asset: %s", mShapeAsset.getAssetId());
|
|
|
|
Torque::FS::FileNodeRef fileRef = Torque::FS::GetFileNode(mShapeAsset->getShapePath());
|
|
|
|
if (!fileRef)
|
|
{
|
|
errorStr = String::ToString("ShapeBaseData: Couldn't load shape asset \"%s\"", mShapeAsset.getAssetId());
|
|
return false;
|
|
}
|
|
|
|
if(server)
|
|
mCRC = fileRef->getChecksum();
|
|
else if(mCRC != fileRef->getChecksum())
|
|
{
|
|
errorStr = String::ToString("Shape asset \"%s\" does not match version on server.", mShapeAsset.getAssetId());
|
|
return false;
|
|
}
|
|
}
|
|
// Resolve details and camera node indexes.
|
|
static const String sCollisionStr( "collision-" );
|
|
|
|
for (i = 0; i < mShape->details.size(); i++)
|
|
{
|
|
const String &name = mShape->names[mShape->details[i].nameIndex];
|
|
|
|
if (name.compare( sCollisionStr, sCollisionStr.length(), String::NoCase ) == 0)
|
|
{
|
|
collisionDetails.push_back(i);
|
|
collisionBounds.increment();
|
|
|
|
mShape->computeBounds(collisionDetails.last(), collisionBounds.last());
|
|
mShape->getAccelerator(collisionDetails.last());
|
|
|
|
if (!mShape->mBounds.isContained(collisionBounds.last()))
|
|
{
|
|
if (!silent_bbox_check)
|
|
Con::warnf("Warning: shape asset %s collision detail %d (Collision-%d) bounds exceed that of shape.", mShapeAsset.getAssetId(), collisionDetails.size() - 1, collisionDetails.last());
|
|
collisionBounds.last() = mShape->mBounds;
|
|
}
|
|
else if (collisionBounds.last().isValidBox() == false)
|
|
{
|
|
if (!silent_bbox_check)
|
|
Con::errorf("Error: shape asset %s-collision detail %d (Collision-%d) bounds box invalid!", mShapeAsset.getAssetId(), collisionDetails.size() - 1, collisionDetails.last());
|
|
collisionBounds.last() = mShape->mBounds;
|
|
}
|
|
|
|
// The way LOS works is that it will check to see if there is a LOS detail that matches
|
|
// the the collision detail + 1 + MaxCollisionShapes (this variable name should change in
|
|
// the future). If it can't find a matching LOS it will simply use the collision instead.
|
|
// We check for any "unmatched" LOS's further down
|
|
LOSDetails.increment();
|
|
|
|
String buff = String::ToString("LOS-%d", i + 1 + MaxCollisionShapes);
|
|
U32 los = mShape->findDetail(buff);
|
|
if (los == -1)
|
|
LOSDetails.last() = i;
|
|
else
|
|
LOSDetails.last() = los;
|
|
}
|
|
}
|
|
|
|
// Snag any "unmatched" LOS details
|
|
static const String sLOSStr( "LOS-" );
|
|
|
|
for (i = 0; i < mShape->details.size(); i++)
|
|
{
|
|
const String &name = mShape->names[mShape->details[i].nameIndex];
|
|
|
|
if (name.compare( sLOSStr, sLOSStr.length(), String::NoCase ) == 0)
|
|
{
|
|
// See if we already have this LOS
|
|
bool found = false;
|
|
for (U32 j = 0; j < LOSDetails.size(); j++)
|
|
{
|
|
if (LOSDetails[j] == i)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
LOSDetails.push_back(i);
|
|
}
|
|
}
|
|
|
|
debrisDetail = mShape->findDetail("Debris-17");
|
|
eyeNode = mShape->findNode("eye");
|
|
earNode = mShape->findNode( "ear" );
|
|
if( earNode == -1 )
|
|
earNode = eyeNode;
|
|
cameraNode = mShape->findNode("cam");
|
|
if (cameraNode == -1)
|
|
cameraNode = eyeNode;
|
|
|
|
// Resolve mount point node indexes
|
|
for (i = 0; i < SceneObject::NumMountPoints; i++) {
|
|
char fullName[256];
|
|
dSprintf(fullName,sizeof(fullName),"mount%d",i);
|
|
mountPointNode[i] = mShape->findNode(fullName);
|
|
}
|
|
|
|
// find the AIRepairNode - hardcoded to be the last node in the array...
|
|
mountPointNode[AIRepairNode] = mShape->findNode("AIRepairNode");
|
|
|
|
//
|
|
hulkSequence = mShape->findSequence("Visibility");
|
|
damageSequence = mShape->findSequence("Damage");
|
|
|
|
//
|
|
F32 w = mShape->mBounds.len_y() / 2;
|
|
if (cameraMaxDist < w)
|
|
cameraMaxDist = w;
|
|
// just parse up the string and collect the remappings in txr_tag_remappings.
|
|
if (!server && remap_txr_tags != NULL && remap_txr_tags != StringTable->insert(""))
|
|
{
|
|
txr_tag_remappings.clear();
|
|
if (remap_buffer)
|
|
dFree(remap_buffer);
|
|
|
|
remap_buffer = dStrdup(remap_txr_tags);
|
|
|
|
char* remap_token = dStrtok(remap_buffer, " \t");
|
|
while (remap_token != NULL)
|
|
{
|
|
char* colon = dStrchr(remap_token, ':');
|
|
if (colon)
|
|
{
|
|
*colon = '\0';
|
|
txr_tag_remappings.increment();
|
|
txr_tag_remappings.last().old_tag = remap_token;
|
|
txr_tag_remappings.last().new_tag = colon+1;
|
|
}
|
|
remap_token = dStrtok(NULL, " \t");
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!server)
|
|
{
|
|
/*
|
|
// grab all the hud images
|
|
for(U32 i = 0; i < NumHudRenderImages; i++)
|
|
{
|
|
if(hudImageNameFriendly[i] && hudImageNameFriendly[i][0])
|
|
hudImageFriendly[i] = TextureHandle(hudImageNameFriendly[i], BitmapTexture);
|
|
|
|
if(hudImageNameEnemy[i] && hudImageNameEnemy[i][0])
|
|
hudImageEnemy[i] = TextureHandle(hudImageNameEnemy[i], BitmapTexture);
|
|
}
|
|
*/
|
|
}
|
|
|
|
// Resolve CubeReflectorDesc.
|
|
if ( cubeDescName.isNotEmpty() )
|
|
{
|
|
Sim::findObject( cubeDescName, reflectorDesc );
|
|
}
|
|
else if( cubeDescId > 0 )
|
|
{
|
|
Sim::findObject( cubeDescId, reflectorDesc );
|
|
}
|
|
|
|
return !shapeError;
|
|
}
|
|
|
|
bool ShapeBaseData::_setMass( void* object, const char* index, const char* data )
|
|
{
|
|
ShapeBaseData* shape = reinterpret_cast< ShapeBaseData* >( object );
|
|
|
|
F32 mass = dAtof(data);
|
|
|
|
if (mass <= 0)
|
|
mass = 0.01f;
|
|
|
|
shape->mass = mass;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void ShapeBaseData::initPersistFields()
|
|
{
|
|
docsURL;
|
|
addGroup( "Shapes" );
|
|
INITPERSISTFIELD_SHAPEASSET(Shape, ShapeBaseData, "The source shape asset.");
|
|
addField("computeCRC", TypeBool, Offset(computeCRC, ShapeBaseData),
|
|
"If true, verify that the CRC of the client's shape model matches the "
|
|
"server's CRC for the shape model when loaded by the client.");
|
|
addField("silentBBoxValidation", TypeBool, Offset(silent_bbox_check, ShapeBaseData));
|
|
INITPERSISTFIELD_SHAPEASSET(DebrisShape, ShapeBaseData, "The shape asset to use for auto-generated breakups via blowup(). @note may not be functional.");
|
|
endGroup( "Shapes" );
|
|
addGroup("Movement");
|
|
addField("aiControllerData", TYPEID< AIControllerData >(), Offset(mAIControllData, ShapeBaseData),
|
|
"@brief ai controller used by these types of objects.\n\n");
|
|
endGroup("Movement");
|
|
|
|
addGroup("Particle Effects");
|
|
addField( "explosion", TYPEID< ExplosionData >(), Offset(explosion, ShapeBaseData),
|
|
"%Explosion to generate when this shape is blown up." );
|
|
addField( "underwaterExplosion", TYPEID< ExplosionData >(), Offset(underwaterExplosion, ShapeBaseData),
|
|
"%Explosion to generate when this shape is blown up underwater." );
|
|
addField( "debris", TYPEID< DebrisData >(), Offset(debris, ShapeBaseData),
|
|
"%Debris to generate when this shape is blown up." );
|
|
addField( "renderWhenDestroyed", TypeBool, Offset(renderWhenDestroyed, ShapeBaseData),
|
|
"Whether to render the shape when it is in the \"Destroyed\" damage state." );
|
|
endGroup("Particle Effects");
|
|
|
|
addGroup( "Physics" );
|
|
addProtectedFieldV("mass", TypeRangedF32, Offset(mass, ShapeBaseData), &_setMass, &defaultProtectedGetFn, &CommonValidators::PositiveFloat, "Shape mass.\nUsed in simulation of moving objects.\n" );
|
|
addFieldV( "drag", TypeRangedF32, Offset(drag, ShapeBaseData), &CommonValidators::PositiveNonZeroFloat,
|
|
"Drag factor.\nReduces velocity of moving objects." );
|
|
addFieldV( "density", TypeRangedF32, Offset(density, ShapeBaseData), &CommonValidators::PositiveNonZeroFloat,
|
|
"Shape density.\nUsed when computing buoyancy when in water.\n" );
|
|
endGroup( "Physics" );
|
|
|
|
addGroup( "Damage/Energy" );
|
|
addFieldV( "maxEnergy", TypeRangedF32, Offset(maxEnergy, ShapeBaseData), &CommonValidators::PositiveFloat,
|
|
"Maximum energy level for this object." );
|
|
addFieldV( "maxDamage", TypeRangedF32, Offset(maxDamage, ShapeBaseData), &CommonValidators::PositiveFloat,
|
|
"Maximum damage level for this object." );
|
|
addFieldV( "disabledLevel", TypeRangedF32, Offset(disabledLevel, ShapeBaseData), &CommonValidators::PositiveFloat,
|
|
"Damage level above which the object is disabled.\n"
|
|
"Currently unused." );
|
|
addFieldV( "destroyedLevel", TypeRangedF32, Offset(destroyedLevel, ShapeBaseData), &CommonValidators::PositiveFloat,
|
|
"Damage level above which the object is destroyed.\n"
|
|
"When the damage level increases above this value, the object damage "
|
|
"state is set to \"Destroyed\"." );
|
|
addFieldV( "repairRate", TypeRangedF32, Offset(repairRate, ShapeBaseData), &CommonValidators::PositiveFloat,
|
|
"Rate at which damage is repaired in damage units/tick.\n"
|
|
"This value is subtracted from the damage level until it reaches 0." );
|
|
addField( "inheritEnergyFromMount", TypeBool, Offset(inheritEnergyFromMount, ShapeBaseData),
|
|
"Flag controlling whether to manage our own energy level, or to use "
|
|
"the energy level of the object we are mounted to." );
|
|
addField( "isInvincible", TypeBool, Offset(isInvincible, ShapeBaseData),
|
|
"Invincible flag; when invincible, the object cannot be damaged or "
|
|
"repaired." );
|
|
endGroup( "Damage/Energy" );
|
|
|
|
addGroup( "Camera", "The settings used by the shape when it is the camera." );
|
|
addFieldV( "cameraMaxDist", TypeRangedF32, Offset(cameraMaxDist, ShapeBaseData), &CommonValidators::PositiveFloat,
|
|
"The maximum distance from the camera to the object.\n"
|
|
"Used when computing a custom camera transform for this object.\n\n"
|
|
"@see observeThroughObject" );
|
|
addFieldV( "cameraMinDist", TypeRangedF32, Offset(cameraMinDist, ShapeBaseData), &CommonValidators::PositiveFloat,
|
|
"The minimum distance from the camera to the object.\n"
|
|
"Used when computing a custom camera transform for this object.\n\n"
|
|
"@see observeThroughObject" );
|
|
addFieldV( "cameraDefaultFov", TypeRangedF32, Offset(cameraDefaultFov, ShapeBaseData), &CommonValidators::PosDegreeRange,
|
|
"The default camera vertical FOV in degrees." );
|
|
addFieldV( "cameraMinFov", TypeRangedF32, Offset(cameraMinFov, ShapeBaseData), &CommonValidators::PosDegreeRange,
|
|
"The minimum camera vertical FOV allowed in degrees." );
|
|
addFieldV( "cameraMaxFov", TypeRangedF32, Offset(cameraMaxFov, ShapeBaseData), &CommonValidators::PosDegreeRange,
|
|
"The maximum camera vertical FOV allowed in degrees." );
|
|
addField( "cameraCanBank", TypeBool, Offset(cameraCanBank, ShapeBaseData),
|
|
"If the derrived class supports it, allow the camera to bank." );
|
|
addField( "mountedImagesBank", TypeBool, Offset(mountedImagesBank, ShapeBaseData),
|
|
"Do mounted images bank along with the camera?" );
|
|
addField( "firstPersonOnly", TypeBool, Offset(firstPersonOnly, ShapeBaseData),
|
|
"Flag controlling whether the view from this object is first person "
|
|
"only." );
|
|
addField( "useEyePoint", TypeBool, Offset(useEyePoint, ShapeBaseData),
|
|
"Flag controlling whether the client uses this object's eye point to "
|
|
"view from." );
|
|
addField( "observeThroughObject", TypeBool, Offset(observeThroughObject, ShapeBaseData),
|
|
"Observe this object through its camera transform and default fov.\n"
|
|
"If true, when this object is the camera it can provide a custom camera "
|
|
"transform and FOV (instead of the default eye transform)." );
|
|
endGroup("Camera");
|
|
|
|
addGroup( "Reflection" );
|
|
addField( "cubeReflectorDesc", TypeRealString, Offset( cubeDescName, ShapeBaseData ),
|
|
"References a ReflectorDesc datablock that defines performance and quality properties for dynamic reflections.\n");
|
|
//addField( "reflectMaxRateMs", TypeS32, Offset( reflectMaxRateMs, ShapeBaseData ), "reflection will not be updated more frequently than this" );
|
|
//addField( "reflectMaxDist", TypeF32, Offset( reflectMaxDist, ShapeBaseData ), "distance at which reflection is never updated" );
|
|
//addField( "reflectMinDist", TypeF32, Offset( reflectMinDist, ShapeBaseData ), "distance at which reflection is always updated" );
|
|
//addField( "reflectDetailAdjust", TypeF32, Offset( reflectDetailAdjust, ShapeBaseData ), "scale up or down the detail level for objects rendered in a reflection" );
|
|
endGroup( "Reflection" );
|
|
|
|
addField("remapTextureTags", TypeString, Offset(remap_txr_tags, ShapeBaseData));
|
|
|
|
// disallow some field substitutions
|
|
onlyKeepClearSubstitutions("debris"); // subs resolving to "~~", or "~0" are OK
|
|
onlyKeepClearSubstitutions("explosion");
|
|
onlyKeepClearSubstitutions("underwaterExplosion");
|
|
Parent::initPersistFields();
|
|
/*
|
|
addGroup("BL Projected Shadows");
|
|
addField("shadowSize", TypeS32, Offset(shadowSize, ShapeBaseData),
|
|
"Size of the projected shadow texture (must be power of 2).");
|
|
addField("shadowMaxVisibleDistance", TypeF32, Offset(shadowMaxVisibleDistance, ShapeBaseData),
|
|
"Maximum distance at which shadow is visible (currently unused).");
|
|
addField("shadowProjectionDistance", TypeF32, Offset(shadowProjectionDistance, ShapeBaseData),
|
|
"Maximum height above ground to project shadow. If the object is higher "
|
|
"than this no shadow will be rendered.");
|
|
addField("shadowSphereAdjust", TypeF32, Offset(shadowSphereAdjust, ShapeBaseData),
|
|
"Scalar applied to the radius of spot shadows (initial radius is based "
|
|
"on the shape bounds but can be adjusted with this field).");
|
|
endGroup("BL Projected Shadows");
|
|
*/
|
|
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBaseData, checkDeployPos, bool, ( TransformF txfm ),,
|
|
"@brief Check if there is the space at the given transform is free to spawn into.\n\n"
|
|
|
|
"The shape's bounding box volume is used to check for collisions at the given world "
|
|
"transform. Only interior and static objects are checked for collision.\n"
|
|
|
|
"@param txfm Deploy transform to check\n"
|
|
"@return True if the space is free, false if there is already something in "
|
|
"the way.\n"
|
|
|
|
"@note This is a server side only check, and is not actually limited to spawning.\n")
|
|
{
|
|
if (bool(object->mShape) == false)
|
|
return false;
|
|
|
|
MatrixF mat = txfm.getMatrix();
|
|
|
|
Box3F objBox = object->mShape->mBounds;
|
|
Point3F boxCenter = (objBox.minExtents + objBox.maxExtents) * 0.5f;
|
|
objBox.minExtents = boxCenter + (objBox.minExtents - boxCenter) * 0.9f;
|
|
objBox.maxExtents = boxCenter + (objBox.maxExtents - boxCenter) * 0.9f;
|
|
|
|
Box3F wBox = objBox;
|
|
mat.mul(wBox);
|
|
|
|
EarlyOutPolyList polyList;
|
|
polyList.mNormal.set(0,0,0);
|
|
polyList.mPlaneList.clear();
|
|
polyList.mPlaneList.setSize(6);
|
|
polyList.mPlaneList[0].set(objBox.minExtents,VectorF(-1,0,0));
|
|
polyList.mPlaneList[1].set(objBox.maxExtents,VectorF(0,1,0));
|
|
polyList.mPlaneList[2].set(objBox.maxExtents,VectorF(1,0,0));
|
|
polyList.mPlaneList[3].set(objBox.minExtents,VectorF(0,-1,0));
|
|
polyList.mPlaneList[4].set(objBox.minExtents,VectorF(0,0,-1));
|
|
polyList.mPlaneList[5].set(objBox.maxExtents,VectorF(0,0,1));
|
|
|
|
for (U32 i = 0; i < 6; i++)
|
|
{
|
|
PlaneF temp;
|
|
mTransformPlane(mat, Point3F(1, 1, 1), polyList.mPlaneList[i], &temp);
|
|
polyList.mPlaneList[i] = temp;
|
|
}
|
|
|
|
if (gServerContainer.buildPolyList(PLC_Collision, wBox, StaticShapeObjectType, &polyList))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
|
|
DefineEngineMethod(ShapeBaseData, getDeployTransform, TransformF, ( Point3F pos, Point3F normal ),,
|
|
"@brief Helper method to get a transform from a position and vector (suitable for use with setTransform).\n\n"
|
|
"@param pos Desired transform position\n"
|
|
"@param normal Vector of desired direction\n"
|
|
"@return The deploy transform\n" )
|
|
{
|
|
normal.normalize();
|
|
|
|
VectorF xAxis;
|
|
if( mFabs(normal.z) > mFabs(normal.x) && mFabs(normal.z) > mFabs(normal.y))
|
|
mCross( VectorF( 0, 1, 0 ), normal, &xAxis );
|
|
else
|
|
mCross( VectorF( 0, 0, 1 ), normal, &xAxis );
|
|
|
|
VectorF yAxis;
|
|
mCross( normal, xAxis, &yAxis );
|
|
|
|
MatrixF testMat(true);
|
|
testMat.setColumn( 0, xAxis );
|
|
testMat.setColumn( 1, yAxis );
|
|
testMat.setColumn( 2, normal );
|
|
testMat.setPosition( pos );
|
|
|
|
return testMat;
|
|
}
|
|
|
|
void ShapeBaseData::packData(BitStream* stream)
|
|
{
|
|
Parent::packData(stream);
|
|
|
|
if(stream->writeFlag(computeCRC))
|
|
stream->write(mCRC);
|
|
|
|
stream->write(shadowSize);
|
|
stream->write(shadowMaxVisibleDistance);
|
|
stream->write(shadowProjectionDistance);
|
|
stream->write(shadowSphereAdjust);
|
|
|
|
PACKDATA_ASSET(Shape);
|
|
PACKDATA_ASSET(DebrisShape);
|
|
|
|
stream->writeString(cloakTexName);
|
|
if(stream->writeFlag(mass != gShapeBaseDataProto.mass))
|
|
stream->write(mass);
|
|
if(stream->writeFlag(drag != gShapeBaseDataProto.drag))
|
|
stream->write(drag);
|
|
if(stream->writeFlag(density != gShapeBaseDataProto.density))
|
|
stream->write(density);
|
|
if(stream->writeFlag(maxEnergy != gShapeBaseDataProto.maxEnergy))
|
|
stream->write(maxEnergy);
|
|
if(stream->writeFlag(cameraMaxDist != gShapeBaseDataProto.cameraMaxDist))
|
|
stream->write(cameraMaxDist);
|
|
if(stream->writeFlag(cameraMinDist != gShapeBaseDataProto.cameraMinDist))
|
|
stream->write(cameraMinDist);
|
|
cameraDefaultFov = mClampF(cameraDefaultFov, cameraMinFov, cameraMaxFov);
|
|
if(stream->writeFlag(cameraDefaultFov != gShapeBaseDataProto.cameraDefaultFov))
|
|
stream->write(cameraDefaultFov);
|
|
if(stream->writeFlag(cameraMinFov != gShapeBaseDataProto.cameraMinFov))
|
|
stream->write(cameraMinFov);
|
|
if(stream->writeFlag(cameraMaxFov != gShapeBaseDataProto.cameraMaxFov))
|
|
stream->write(cameraMaxFov);
|
|
stream->writeFlag(cameraCanBank);
|
|
stream->writeFlag(mountedImagesBank);
|
|
|
|
stream->writeFlag(observeThroughObject);
|
|
|
|
if( stream->writeFlag( debris != NULL ) )
|
|
{
|
|
stream->writeRangedU32(mPacked? SimObjectId((uintptr_t)debris):
|
|
debris->getId(),DataBlockObjectIdFirst,DataBlockObjectIdLast);
|
|
}
|
|
|
|
stream->writeFlag(isInvincible);
|
|
stream->writeFlag(renderWhenDestroyed);
|
|
|
|
if( stream->writeFlag( explosion != NULL ) )
|
|
{
|
|
stream->writeRangedU32( explosion->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast );
|
|
}
|
|
|
|
if( stream->writeFlag( underwaterExplosion != NULL ) )
|
|
{
|
|
stream->writeRangedU32( underwaterExplosion->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast );
|
|
}
|
|
|
|
stream->writeFlag(inheritEnergyFromMount);
|
|
stream->writeFlag(firstPersonOnly);
|
|
stream->writeFlag(useEyePoint);
|
|
|
|
if( stream->writeFlag( reflectorDesc != NULL ) )
|
|
{
|
|
stream->writeRangedU32( reflectorDesc->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast );
|
|
}
|
|
|
|
//stream->write(reflectPriority);
|
|
//stream->write(reflectMaxRateMs);
|
|
//stream->write(reflectMinDist);
|
|
//stream->write(reflectMaxDist);
|
|
//stream->write(reflectDetailAdjust);
|
|
stream->writeString(remap_txr_tags);
|
|
stream->writeFlag(silent_bbox_check);
|
|
}
|
|
|
|
void ShapeBaseData::unpackData(BitStream* stream)
|
|
{
|
|
Parent::unpackData(stream);
|
|
computeCRC = stream->readFlag();
|
|
if(computeCRC)
|
|
stream->read(&mCRC);
|
|
|
|
stream->read(&shadowSize);
|
|
stream->read(&shadowMaxVisibleDistance);
|
|
stream->read(&shadowProjectionDistance);
|
|
stream->read(&shadowSphereAdjust);
|
|
|
|
UNPACKDATA_ASSET(Shape);
|
|
UNPACKDATA_ASSET(DebrisShape);
|
|
|
|
cloakTexName = stream->readSTString();
|
|
if(stream->readFlag())
|
|
stream->read(&mass);
|
|
else
|
|
mass = gShapeBaseDataProto.mass;
|
|
|
|
if(stream->readFlag())
|
|
stream->read(&drag);
|
|
else
|
|
drag = gShapeBaseDataProto.drag;
|
|
|
|
if(stream->readFlag())
|
|
stream->read(&density);
|
|
else
|
|
density = gShapeBaseDataProto.density;
|
|
|
|
if(stream->readFlag())
|
|
stream->read(&maxEnergy);
|
|
else
|
|
maxEnergy = gShapeBaseDataProto.maxEnergy;
|
|
|
|
if(stream->readFlag())
|
|
stream->read(&cameraMaxDist);
|
|
else
|
|
cameraMaxDist = gShapeBaseDataProto.cameraMaxDist;
|
|
|
|
if(stream->readFlag())
|
|
stream->read(&cameraMinDist);
|
|
else
|
|
cameraMinDist = gShapeBaseDataProto.cameraMinDist;
|
|
|
|
if(stream->readFlag())
|
|
stream->read(&cameraDefaultFov);
|
|
else
|
|
cameraDefaultFov = gShapeBaseDataProto.cameraDefaultFov;
|
|
|
|
if(stream->readFlag())
|
|
stream->read(&cameraMinFov);
|
|
else
|
|
cameraMinFov = gShapeBaseDataProto.cameraMinFov;
|
|
|
|
if(stream->readFlag())
|
|
stream->read(&cameraMaxFov);
|
|
else
|
|
cameraMaxFov = gShapeBaseDataProto.cameraMaxFov;
|
|
|
|
cameraCanBank = stream->readFlag();
|
|
mountedImagesBank = stream->readFlag();
|
|
observeThroughObject = stream->readFlag();
|
|
|
|
if( stream->readFlag() )
|
|
{
|
|
debrisID = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast );
|
|
}
|
|
|
|
isInvincible = stream->readFlag();
|
|
renderWhenDestroyed = stream->readFlag();
|
|
|
|
if( stream->readFlag() )
|
|
{
|
|
explosionID = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast );
|
|
}
|
|
|
|
if( stream->readFlag() )
|
|
{
|
|
underwaterExplosionID = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast );
|
|
}
|
|
|
|
inheritEnergyFromMount = stream->readFlag();
|
|
firstPersonOnly = stream->readFlag();
|
|
useEyePoint = stream->readFlag();
|
|
|
|
if( stream->readFlag() )
|
|
{
|
|
cubeDescId = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast );
|
|
}
|
|
|
|
//stream->read(&reflectPriority);
|
|
//stream->read(&reflectMaxRateMs);
|
|
//stream->read(&reflectMinDist);
|
|
//stream->read(&reflectMaxDist);
|
|
//stream->read(&reflectDetailAdjust);
|
|
remap_txr_tags = stream->readSTString();
|
|
silent_bbox_check = stream->readFlag();
|
|
}
|
|
|
|
//
|
|
//
|
|
void ShapeBaseData::onShapeChanged()
|
|
{
|
|
reloadOnLocalClient();
|
|
}
|
|
|
|
void ShapeBaseData::onDebrisChanged()
|
|
{
|
|
reloadOnLocalClient();
|
|
}
|
|
//----------------------------------------------------------------------------
|
|
//----------------------------------------------------------------------------
|
|
|
|
Chunker<ShapeBase::CollisionTimeout> sTimeoutChunker;
|
|
ShapeBase::CollisionTimeout* ShapeBase::sFreeTimeoutList = 0;
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
IMPLEMENT_CO_NETOBJECT_V1(ShapeBase);
|
|
|
|
ConsoleDocClass( ShapeBase,
|
|
"@ingroup gameObjects"
|
|
);
|
|
|
|
IMPLEMENT_CALLBACK( ShapeBase, validateCameraFov, F32, (F32 fov), (fov),
|
|
"@brief Called on the server when the client has requested a FOV change.\n\n"
|
|
|
|
"When the client requests that its field of view should be changed (because "
|
|
"they want to use a sniper scope, for example) this new FOV needs to be validated "
|
|
"by the server. This method is called if it exists (it is optional) to validate "
|
|
"the requested FOV, and modify it if necessary. This could be as simple as checking "
|
|
"that the FOV falls within a correct range, to making sure that the FOV matches the "
|
|
"capabilities of the current weapon.\n\n"
|
|
|
|
"Following this method, ShapeBase ensures that the given FOV still falls within "
|
|
"the datablock's cameraMinFov and cameraMaxFov. If that is good enough for your "
|
|
"purposes, then you do not need to define the validateCameraFov() callback for "
|
|
"your ShapeBase.\n\n"
|
|
|
|
"@param fov The FOV that has been requested by the client.\n"
|
|
"@return The FOV as validated by the server.\n\n"
|
|
|
|
"@see ShapeBaseData\n\n");
|
|
|
|
ShapeBase::ShapeBase()
|
|
: mDataBlock( NULL ),
|
|
mIsAiControlled( false ),
|
|
mControllingObject( NULL ),
|
|
mAiPose( 0 ),
|
|
mMoveMotion( false ),
|
|
mShapeBaseMount( NULL ),
|
|
mShapeInstance( NULL ),
|
|
mConvexList( new Convex ),
|
|
mEnergy( 0.0f ),
|
|
mRechargeRate( 0.0f ),
|
|
mMass( 1.0f ),
|
|
mOneOverMass( 1.0f ),
|
|
mDrag( 0.0f ),
|
|
mBuoyancy( 0.0f ),
|
|
mLiquidHeight( 0.0f ),
|
|
mWaterCoverage( 0.0f ),
|
|
mAppliedForce( Point3F::Zero ),
|
|
mNetGravity( 1.0f ),
|
|
mDamageFlash( 0.0f ),
|
|
mWhiteOut( 0.0f ),
|
|
mFlipFadeVal( false ),
|
|
mTimeoutList( NULL ),
|
|
mDamage( 0.0f ),
|
|
mRepairRate( 0.0f ),
|
|
mRepairReserve( 0.0f ),
|
|
mDamageState( Enabled ),
|
|
mDamageThread( NULL ),
|
|
mHulkThread( NULL ),
|
|
damageDir( 0.0f, 0.0f, 1.0f ),
|
|
mCloaked( false ),
|
|
mCloakLevel( 0.0f ),
|
|
mFadeOut( true ),
|
|
mFading( false ),
|
|
mFadeVal( 1.0f ),
|
|
mFadeElapsedTime( 0.0f ),
|
|
mFadeTime( 1.0f ),
|
|
mFadeDelay( 0.0f ),
|
|
mCameraFov( 90.0f ),
|
|
mIsControlled( false ),
|
|
mLastRenderFrame( 0 ),
|
|
mLastRenderDistance( 0.0f ),
|
|
mAIController(NULL)
|
|
{
|
|
mTypeMask |= ShapeBaseObjectType | LightObjectType;
|
|
|
|
S32 i;
|
|
|
|
for (i = 0; i < MaxSoundThreads; i++) {
|
|
mSoundThread[i].play = false;
|
|
mSoundThread[i].asset = 0;
|
|
mSoundThread[i].sound = 0;
|
|
}
|
|
|
|
for (i = 0; i < MaxScriptThreads; i++) {
|
|
mScriptThread[i].sequence = -1;
|
|
mScriptThread[i].thread = 0;
|
|
mScriptThread[i].state = Thread::Stop;
|
|
mScriptThread[i].atEnd = false;
|
|
mScriptThread[i].timescale = 1.f;
|
|
mScriptThread[i].position = -1.f;
|
|
}
|
|
|
|
for (i = 0; i < MaxTriggerKeys; i++)
|
|
mTrigger[i] = false;
|
|
anim_clip_flags = 0;
|
|
last_anim_id = -1;
|
|
last_anim_tag = 0;
|
|
last_anim_lock_tag = 0;
|
|
saved_seq_id = -1;
|
|
saved_pos = 0.0f;
|
|
saved_rate = 1.0f;
|
|
}
|
|
|
|
|
|
ShapeBase::~ShapeBase()
|
|
{
|
|
SAFE_DELETE( mConvexList );
|
|
|
|
AssertFatal(mMount.link == 0,"ShapeBase::~ShapeBase: An object is still mounted");
|
|
if( mShapeInstance && (mShapeInstance->getDebrisRefCount() == 0) )
|
|
{
|
|
delete mShapeInstance;
|
|
mShapeInstance = NULL;
|
|
}
|
|
|
|
CollisionTimeout* ptr = mTimeoutList;
|
|
while (ptr) {
|
|
CollisionTimeout* cur = ptr;
|
|
ptr = ptr->next;
|
|
cur->next = sFreeTimeoutList;
|
|
sFreeTimeoutList = cur;
|
|
}
|
|
if (mAIController) mAIController->deleteObject();
|
|
}
|
|
|
|
void ShapeBase::initPersistFields()
|
|
{
|
|
docsURL;
|
|
addProtectedField( "skin", TypeRealString, Offset(mAppliedSkinName, ShapeBase), &_setFieldSkin, &_getFieldSkin,
|
|
"@brief The skin applied to the shape.\n\n"
|
|
|
|
"'Skinning' the shape effectively renames the material targets, allowing "
|
|
"different materials to be used on different instances of the same model. "
|
|
"Using getSkinName() and setSkinName() is equivalent to reading and "
|
|
"writing the skin field directly.\n\n"
|
|
|
|
"Any material targets that start with the old skin name have that part "
|
|
"of the name replaced with the new skin name. The initial old skin name is "
|
|
"\"base\". For example, if a new skin of \"blue\" was applied to a model "
|
|
"that had material targets <i>base_body</i> and <i>face</i>, the new targets "
|
|
"would be <i>blue_body</i> and <i>face</i>. Note that <i>face</i> was not "
|
|
"renamed since it did not start with the old skin name of \"base\".\n\n"
|
|
|
|
"To support models that do not use the default \"base\" naming convention, "
|
|
"you can also specify the part of the name to replace in the skin field "
|
|
"itself. For example, if a model had a material target called <i>shapemat</i>, "
|
|
"we could apply a new skin \"shape=blue\", and the material target would be "
|
|
"renamed to <i>bluemat</i> (note \"shape\" has been replaced with \"blue\").\n\n"
|
|
|
|
"Multiple skin updates can also be applied at the same time by separating "
|
|
"them with a semicolon. For example: \"base=blue;face=happy_face\".\n\n"
|
|
|
|
"Material targets are only renamed if an existing Material maps to that "
|
|
"name, or if there is a diffuse texture in the model folder with the same "
|
|
"name as the new target.\n\n" );
|
|
|
|
addField( "isAiControlled", TypeBool, Offset(mIsAiControlled, ShapeBase),
|
|
"@brief Is this object AI controlled.\n\n"
|
|
"If True then this object is considered AI controlled and not player controlled.\n" );
|
|
|
|
Parent::initPersistFields();
|
|
}
|
|
|
|
bool ShapeBase::_setFieldSkin( void *object, const char *index, const char *data )
|
|
{
|
|
ShapeBase* shape = static_cast<ShapeBase*>( object );
|
|
if ( shape )
|
|
shape->setSkinName( data );
|
|
return false;
|
|
}
|
|
|
|
const char *ShapeBase::_getFieldSkin( void *object, const char *data )
|
|
{
|
|
ShapeBase* shape = static_cast<ShapeBase*>( object );
|
|
return shape ? shape->getSkinName() : "";
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
bool ShapeBase::onAdd()
|
|
{
|
|
if( !Parent::onAdd() )
|
|
return false;
|
|
|
|
if( !mDataBlock )
|
|
{
|
|
Con::errorf( "ShapeBase::onAdd - no datablock on shape %i:%s (%s)",
|
|
getId(), getClassName(), getName() );
|
|
return false;
|
|
}
|
|
|
|
// Resolve sounds that arrived in the initial update
|
|
S32 i;
|
|
for (i = 0; i < MaxSoundThreads; i++)
|
|
updateAudioState(mSoundThread[i]);
|
|
|
|
for (i = 0; i < MaxScriptThreads; i++)
|
|
{
|
|
Thread& st = mScriptThread[i];
|
|
if(st.thread)
|
|
updateThread(st);
|
|
}
|
|
|
|
/*
|
|
if(mDataBlock->cloakTexName != StringTable->EmptyString())
|
|
mCloakTexture = TextureHandle(mDataBlock->cloakTexName, MeshTexture, false);
|
|
*/
|
|
// Accumulation and environment mapping
|
|
if (isClientObject() && mShapeInstance)
|
|
{
|
|
AccumulationVolume::addObject(this);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void ShapeBase::onRemove()
|
|
{
|
|
mConvexList->nukeList();
|
|
|
|
Parent::onRemove();
|
|
|
|
// Stop any running sounds on the client
|
|
if (isGhost())
|
|
for (S32 i = 0; i < MaxSoundThreads; i++)
|
|
stopAudio(i);
|
|
|
|
// Accumulation and environment mapping
|
|
if (isClientObject() && mShapeInstance)
|
|
{
|
|
if (mShapeInstance->hasAccumulation())
|
|
AccumulationVolume::removeObject(this);
|
|
}
|
|
|
|
if ( isClientObject() )
|
|
{
|
|
mCubeReflector.unregisterReflector();
|
|
}
|
|
}
|
|
|
|
|
|
void ShapeBase::onSceneRemove()
|
|
{
|
|
mConvexList->nukeList();
|
|
Parent::onSceneRemove();
|
|
}
|
|
|
|
bool ShapeBase::onNewDataBlock( GameBaseData *dptr, bool reload )
|
|
{
|
|
// need to destroy blend-clips or we crash
|
|
if (isGhost())
|
|
{
|
|
for (S32 i = 0; i < blend_clips.size(); i++)
|
|
{
|
|
if (blend_clips[i].thread)
|
|
mShapeInstance->destroyThread(blend_clips[i].thread);
|
|
blend_clips.erase_fast(i);
|
|
}
|
|
}
|
|
ShapeBaseData *prevDB = dynamic_cast<ShapeBaseData*>( mDataBlock );
|
|
|
|
bool isInitialDataBlock = (prevDB == 0);
|
|
|
|
if ( Parent::onNewDataBlock( dptr, reload ) == false )
|
|
return false;
|
|
|
|
mDataBlock = dynamic_cast<ShapeBaseData*>(dptr);
|
|
if (!mDataBlock)
|
|
return false;
|
|
|
|
setMaskBits(DamageMask);
|
|
mDamageThread = 0;
|
|
mHulkThread = 0;
|
|
|
|
// Even if loadShape succeeds, there may not actually be
|
|
// a shape assigned to this object.
|
|
if (bool(mDataBlock->mShape)) {
|
|
delete mShapeInstance;
|
|
if (isClientObject() && mDataBlock->txr_tag_remappings.size() > 0)
|
|
{
|
|
// temporarily substitute material tags with alternates
|
|
TSMaterialList* mat_list = mDataBlock->mShape->materialList;
|
|
if (mat_list)
|
|
{
|
|
for (S32 i = 0; i < mDataBlock->txr_tag_remappings.size(); i++)
|
|
{
|
|
ShapeBaseData::TextureTagRemapping* remap = &mDataBlock->txr_tag_remappings[i];
|
|
Vector<String>& mat_names = (Vector<String>&) mat_list->getMaterialNameList();
|
|
S32 old_tagLen = dStrlen(remap->old_tag);
|
|
for (S32 j = 0; j < mat_names.size(); j++)
|
|
{
|
|
if (mat_names[j].compare(remap->old_tag, old_tagLen, String::NoCase) == 0)
|
|
{
|
|
mat_names[j] = String(remap->new_tag);
|
|
mat_names[j].insert(0, '#');
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
mShapeInstance = new TSShapeInstance(mDataBlock->mShape, isClientObject());
|
|
if (isClientObject())
|
|
{
|
|
mShapeInstance->cloneMaterialList();
|
|
|
|
// restore the material tags to original form
|
|
if (mDataBlock->txr_tag_remappings.size() > 0)
|
|
{
|
|
TSMaterialList* mat_list = mDataBlock->mShape->materialList;
|
|
if (mat_list)
|
|
{
|
|
for (S32 i = 0; i < mDataBlock->txr_tag_remappings.size(); i++)
|
|
{
|
|
ShapeBaseData::TextureTagRemapping* remap = &mDataBlock->txr_tag_remappings[i];
|
|
Vector<String>& mat_names = (Vector<String>&) mat_list->getMaterialNameList();
|
|
S32 new_tagLen = dStrlen(remap->new_tag);
|
|
for (S32 j = 0; j < mat_names.size(); j++)
|
|
{
|
|
String::SizeType len = mat_names[j].length();
|
|
if (len > 1)
|
|
{
|
|
String temp_name = mat_names[j].substr(1, len - 1);
|
|
if (temp_name.compare(remap->new_tag, new_tagLen) == 0)
|
|
{
|
|
mat_names[j] = String(remap->old_tag);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
mObjBox = mDataBlock->mShape->mBounds;
|
|
resetWorldBox();
|
|
|
|
// Set the initial mesh hidden state.
|
|
mMeshHidden.setSize(mDataBlock->mShape->objects.size());
|
|
mMeshHidden.clear();
|
|
|
|
// Initialize the threads
|
|
for (U32 i = 0; i < MaxScriptThreads; i++) {
|
|
Thread& st = mScriptThread[i];
|
|
if (st.sequence != -1) {
|
|
// TG: Need to see about suppressing non-cyclic sounds
|
|
// if the sequences were activated before the object was
|
|
// ghosted.
|
|
// TG: Cyclic animations need to have a random pos if
|
|
// they were started before the object was ghosted.
|
|
|
|
// If there was something running on the old shape, the thread
|
|
// needs to be reset. Otherwise we assume that it's been
|
|
// initialized either by the constructor or from the server.
|
|
bool reset = st.thread != 0;
|
|
st.thread = 0;
|
|
|
|
// New datablock/shape may not actually HAVE this sequence.
|
|
// In that case stop playing it.
|
|
|
|
AssertFatal(prevDB != NULL, "ShapeBase::onNewDataBlock - how did you have a sequence playing without a prior datablock?");
|
|
|
|
const TSShape* prevShape = prevDB->mShape;
|
|
const TSShape::Sequence& prevSeq = prevShape->sequences[st.sequence];
|
|
const String& prevSeqName = prevShape->names[prevSeq.nameIndex];
|
|
|
|
st.sequence = mDataBlock->mShape->findSequence(prevSeqName);
|
|
|
|
if (st.sequence != -1)
|
|
{
|
|
setThreadSequence(i, st.sequence, reset);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mDataBlock->damageSequence != -1) {
|
|
mDamageThread = mShapeInstance->addThread();
|
|
mShapeInstance->setSequence(mDamageThread,
|
|
mDataBlock->damageSequence, 0);
|
|
}
|
|
if (mDataBlock->hulkSequence != -1) {
|
|
mHulkThread = mShapeInstance->addThread();
|
|
mShapeInstance->setSequence(mHulkThread,
|
|
mDataBlock->hulkSequence, 0);
|
|
}
|
|
|
|
if (isGhost())
|
|
{
|
|
// Reapply the current skin
|
|
mAppliedSkinName = "";
|
|
reSkin();
|
|
}
|
|
}
|
|
|
|
//
|
|
mEnergy = 0;
|
|
mDamage = 0;
|
|
mDamageState = Enabled;
|
|
mRepairReserve = 0;
|
|
updateMass();
|
|
updateDamageLevel();
|
|
updateDamageState();
|
|
|
|
mDrag = mDataBlock->drag;
|
|
mCameraFov = mDataBlock->cameraDefaultFov;
|
|
updateMass();
|
|
|
|
if( !isInitialDataBlock && mLightPlugin )
|
|
mLightPlugin->reset();
|
|
|
|
if ( isClientObject() )
|
|
{
|
|
mCubeReflector.unregisterReflector();
|
|
|
|
if ( mDataBlock->reflectorDesc )
|
|
mCubeReflector.registerReflector( this, mDataBlock->reflectorDesc );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ShapeBase::onDeleteNotify( SimObject *obj )
|
|
{
|
|
if ( obj == mCurrentWaterObject )
|
|
mCurrentWaterObject = NULL;
|
|
|
|
Parent::onDeleteNotify( obj );
|
|
}
|
|
|
|
void ShapeBase::onImpact(SceneObject* obj, const VectorF& vec)
|
|
{
|
|
if (!isGhost())
|
|
mDataBlock->onImpact_callback( this, obj, vec, vec.len() );
|
|
}
|
|
|
|
void ShapeBase::onImpact(const VectorF& vec)
|
|
{
|
|
if (!isGhost())
|
|
mDataBlock->onImpact_callback( this, NULL, vec, vec.len() );
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
void ShapeBase::processTick(const Move* move)
|
|
{
|
|
PROFILE_SCOPE( ShapeBase_ProcessTick );
|
|
|
|
// Energy management
|
|
if (mDamageState == Enabled && mDataBlock->inheritEnergyFromMount == false) {
|
|
F32 store = mEnergy;
|
|
mEnergy += mRechargeRate;
|
|
if (mEnergy > mDataBlock->maxEnergy)
|
|
mEnergy = mDataBlock->maxEnergy;
|
|
else
|
|
if (mEnergy < 0)
|
|
mEnergy = 0;
|
|
|
|
// Virtual setEnergyLevel is used here by some derived classes to
|
|
// decide whether they really want to set the energy mask bit.
|
|
if (mEnergy != store)
|
|
setEnergyLevel(mEnergy);
|
|
}
|
|
|
|
// Repair management
|
|
if (mDataBlock->isInvincible == false)
|
|
{
|
|
F32 store = mDamage;
|
|
mDamage -= mRepairRate;
|
|
mDamage = mClampF(mDamage, 0.f, mDataBlock->maxDamage);
|
|
|
|
if (mRepairReserve > mDamage)
|
|
mRepairReserve = mDamage;
|
|
if (mRepairReserve > 0.0)
|
|
{
|
|
F32 rate = getMin(mDataBlock->repairRate, mRepairReserve);
|
|
mDamage -= rate;
|
|
mRepairReserve -= rate;
|
|
}
|
|
|
|
if (store != mDamage)
|
|
{
|
|
updateDamageLevel();
|
|
if (isServerObject()) {
|
|
setMaskBits(DamageMask);
|
|
mDataBlock->onDamage_callback( this, mDamage - store );
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isServerObject()) {
|
|
// Server only...
|
|
advanceThreads(TickSec);
|
|
updateServerAudio();
|
|
|
|
// update wet state
|
|
setImageWetState(0, mWaterCoverage > 0.4); // more than 40 percent covered
|
|
|
|
// update motion state
|
|
mMoveMotion = false;
|
|
if (move && (move->x || move->y || move->z))
|
|
{
|
|
mMoveMotion = true;
|
|
}
|
|
for (S32 i = 0; i < MaxMountedImages; i++)
|
|
{
|
|
setImageMotionState(i, mMoveMotion);
|
|
}
|
|
|
|
if(mFading)
|
|
{
|
|
F32 dt = TickMs / 1000.0f;
|
|
F32 newFadeET = mFadeElapsedTime + dt;
|
|
if(mFadeElapsedTime < mFadeDelay && newFadeET >= mFadeDelay)
|
|
setMaskBits(CloakMask);
|
|
mFadeElapsedTime = newFadeET;
|
|
if(mFadeElapsedTime > mFadeTime + mFadeDelay)
|
|
{
|
|
mFadeVal = F32(!mFadeOut);
|
|
mFading = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Advance images
|
|
if (isServerObject())
|
|
{
|
|
for (S32 i = 0; i < MaxMountedImages; i++)
|
|
{
|
|
if (mMountedImageList[i].dataBlock)
|
|
updateImageState(i, TickSec);
|
|
}
|
|
}
|
|
|
|
// Call script on trigger state changes
|
|
if (move && mDataBlock && isServerObject())
|
|
{
|
|
for (S32 i = 0; i < MaxTriggerKeys; i++)
|
|
{
|
|
if (move->trigger[i] != mTrigger[i])
|
|
{
|
|
mTrigger[i] = move->trigger[i];
|
|
mDataBlock->onTrigger_callback( this, i, move->trigger[i] );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update the damage flash and the whiteout
|
|
//
|
|
if (mDamageFlash > 0.0)
|
|
{
|
|
mDamageFlash -= sDamageFlashDec;
|
|
if (mDamageFlash <= 0.0)
|
|
mDamageFlash = 0.0;
|
|
}
|
|
if (mWhiteOut > 0.0)
|
|
{
|
|
mWhiteOut -= sWhiteoutDec;
|
|
if (mWhiteOut <= 0.0)
|
|
mWhiteOut = 0.0;
|
|
}
|
|
|
|
if (isMounted()) {
|
|
MatrixF mat;
|
|
mMount.object->getMountTransform( mMount.node, mMount.xfm, &mat );
|
|
Parent::setTransform(mat);
|
|
}
|
|
}
|
|
|
|
void ShapeBase::advanceTime(F32 dt)
|
|
{
|
|
// On the client, the shape threads and images are
|
|
// advanced at framerate.
|
|
advanceThreads(dt);
|
|
updateAudioPos();
|
|
for (S32 i = 0; i < MaxMountedImages; i++)
|
|
if (mMountedImageList[i].dataBlock)
|
|
{
|
|
updateImageState(i, dt);
|
|
updateImageAnimation(i, dt);
|
|
}
|
|
|
|
// Cloaking
|
|
if (mCloaked && mCloakLevel != 1.0) {
|
|
if (sCloakSpeed <= 0.0f)
|
|
{
|
|
// Instantaneous
|
|
mCloakLevel = 1.0;
|
|
}
|
|
else
|
|
{
|
|
// Over time determined by sCloakSpeed
|
|
mCloakLevel += dt / sCloakSpeed;
|
|
if (mCloakLevel >= 1.0)
|
|
mCloakLevel = 1.0;
|
|
}
|
|
} else if (!mCloaked && mCloakLevel != 0.0) {
|
|
if (sCloakSpeed <= 0.0f)
|
|
{
|
|
// Instantaneous
|
|
mCloakLevel = 0.0;
|
|
}
|
|
else
|
|
{
|
|
// Over time determined by sCloakSpeed
|
|
mCloakLevel -= dt / sCloakSpeed;
|
|
if (mCloakLevel <= 0.0)
|
|
mCloakLevel = 0.0;
|
|
}
|
|
}
|
|
if(mFading)
|
|
{
|
|
mFadeElapsedTime += dt;
|
|
if(mFadeElapsedTime > mFadeTime)
|
|
{
|
|
mFadeVal = F32(!mFadeOut);
|
|
mFading = false;
|
|
}
|
|
else
|
|
{
|
|
mFadeVal = mFadeElapsedTime / mFadeTime;
|
|
if(mFadeOut)
|
|
mFadeVal = 1 - mFadeVal;
|
|
}
|
|
}
|
|
|
|
if (isMounted()) {
|
|
MatrixF mat;
|
|
mMount.object->getRenderMountTransform( 0.0f, mMount.node, mMount.xfm, &mat );
|
|
Parent::setRenderTransform(mat);
|
|
}
|
|
}
|
|
|
|
void ShapeBase::setControllingClient( GameConnection* client )
|
|
{
|
|
if( isGhost() && gSFX3DWorld )
|
|
{
|
|
if( gSFX3DWorld->getListener() == this && !client && getControllingClient() && getControllingClient()->isConnectionToServer() )
|
|
{
|
|
// We are the current listener and are no longer a controller object on the
|
|
// connection, so clear our listener status.
|
|
|
|
gSFX3DWorld->setListener( NULL );
|
|
}
|
|
else if( client && client->isConnectionToServer() && (getControllingObject() != this) )
|
|
{
|
|
// We're on the local client and not controlled by another object, so make
|
|
// us the current SFX listener.
|
|
|
|
gSFX3DWorld->setListener( this );
|
|
}
|
|
}
|
|
|
|
Parent::setControllingClient( client );
|
|
|
|
// Update all of the mounted images' shapes so they may
|
|
// optimize their animation threads.
|
|
for (U32 i=0; i<MaxMountedImages; ++i)
|
|
{
|
|
MountedImage& image = mMountedImageList[i];
|
|
image.updateDoAnimateAllShapes( this );
|
|
}
|
|
}
|
|
|
|
void ShapeBase::setControllingObject(ShapeBase* obj)
|
|
{
|
|
if (obj) {
|
|
setProcessTick(false);
|
|
// Even though we don't processTick, we still need to
|
|
// process after the controller in case anyone is mounted
|
|
// on this object.
|
|
processAfter(obj);
|
|
}
|
|
else {
|
|
setProcessTick(true);
|
|
clearProcessAfter();
|
|
// Catch the case of the controlling object actually
|
|
// mounted on this object.
|
|
if (mControllingObject->mMount.object == this)
|
|
mControllingObject->processAfter(this);
|
|
}
|
|
mControllingObject = obj;
|
|
}
|
|
|
|
ShapeBase* ShapeBase::getControlObject()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
void ShapeBase::setControlObject(ShapeBase*)
|
|
{
|
|
}
|
|
|
|
bool ShapeBase::isFirstPerson() const
|
|
{
|
|
// Always first person as far as the server is concerned.
|
|
if (!isGhost())
|
|
return true;
|
|
|
|
if (const GameConnection* con = getControllingClient())
|
|
return con->getControlObject() == this && con->isFirstPerson();
|
|
return false;
|
|
}
|
|
|
|
bool ShapeBase::isValidCameraFov(F32 fov)
|
|
{
|
|
return((fov >= mDataBlock->cameraMinFov) && (fov <= mDataBlock->cameraMaxFov));
|
|
}
|
|
|
|
void ShapeBase::setCameraFov(F32 fov)
|
|
{
|
|
// On server allow for script side checking
|
|
if ( !isGhost() && isMethod( "validateCameraFov" ) )
|
|
{
|
|
fov = validateCameraFov_callback( fov );
|
|
}
|
|
|
|
mCameraFov = mClampF(fov, mDataBlock->cameraMinFov, mDataBlock->cameraMaxFov);
|
|
}
|
|
|
|
void ShapeBase::onCameraScopeQuery(NetConnection *cr, CameraScopeQuery * query)
|
|
{
|
|
// update the camera query
|
|
query->camera = this;
|
|
|
|
if(GameConnection * con = dynamic_cast<GameConnection*>(cr))
|
|
{
|
|
// get the fov from the connection (in deg)
|
|
F32 fov;
|
|
if (con->getControlCameraFov(&fov))
|
|
{
|
|
query->fov = mDegToRad(fov/2);
|
|
query->sinFov = mSin(query->fov);
|
|
query->cosFov = mCos(query->fov);
|
|
}
|
|
}
|
|
|
|
// use eye rather than camera transform (good enough and faster)
|
|
MatrixF eyeTransform;
|
|
getEyeTransform(&eyeTransform);
|
|
eyeTransform.getColumn(3, &query->pos);
|
|
eyeTransform.getColumn(1, &query->orientation);
|
|
|
|
// Get the visible distance.
|
|
if (getSceneManager() != NULL)
|
|
query->visibleDistance = getSceneManager()->getVisibleDistance();
|
|
|
|
Parent::onCameraScopeQuery( cr, query );
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
F32 ShapeBase::getEnergyLevel()
|
|
{
|
|
if ( mDataBlock->inheritEnergyFromMount && mShapeBaseMount )
|
|
return mShapeBaseMount->getEnergyLevel();
|
|
return mEnergy;
|
|
}
|
|
|
|
F32 ShapeBase::getEnergyValue()
|
|
{
|
|
if ( mDataBlock->inheritEnergyFromMount && mShapeBaseMount )
|
|
{
|
|
F32 maxEnergy = mShapeBaseMount->mDataBlock->maxEnergy;
|
|
if ( maxEnergy > 0.f )
|
|
return (mShapeBaseMount->getEnergyLevel() / maxEnergy);
|
|
}
|
|
else
|
|
{
|
|
F32 maxEnergy = mDataBlock->maxEnergy;
|
|
if ( maxEnergy > 0.f )
|
|
return (mEnergy / mDataBlock->maxEnergy);
|
|
}
|
|
|
|
return 0.0f;
|
|
}
|
|
|
|
void ShapeBase::setEnergyLevel(F32 energy)
|
|
{
|
|
if (mDataBlock->inheritEnergyFromMount == false || !mShapeBaseMount) {
|
|
if (mDamageState == Enabled) {
|
|
mEnergy = (energy > mDataBlock->maxEnergy)?
|
|
mDataBlock->maxEnergy: (energy < 0)? 0: energy;
|
|
}
|
|
} else {
|
|
// Pass the set onto whatever we're mounted to...
|
|
if ( mShapeBaseMount )
|
|
{
|
|
mShapeBaseMount->setEnergyLevel(energy);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ShapeBase::setDamageLevel(F32 damage)
|
|
{
|
|
if (!mDataBlock->isInvincible) {
|
|
F32 store = mDamage;
|
|
mDamage = mClampF(damage, 0.f, mDataBlock->maxDamage);
|
|
|
|
if (store != mDamage) {
|
|
updateDamageLevel();
|
|
if (isServerObject()) {
|
|
setMaskBits(DamageMask);
|
|
mDataBlock->onDamage_callback( this, mDamage - store );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ShapeBase::updateContainer()
|
|
{
|
|
PROFILE_SCOPE( ShapeBase_updateContainer );
|
|
|
|
// Update container drag and buoyancy properties
|
|
|
|
// Set default values.
|
|
mDrag = mDataBlock->drag;
|
|
mBuoyancy = 0.0f;
|
|
mNetGravity = gGravity;
|
|
mAppliedForce.set(0,0,0);
|
|
|
|
ContainerQueryInfo info;
|
|
info.box = getWorldBox();
|
|
info.mass = getMass();
|
|
|
|
mContainer->findObjects(info.box, WaterObjectType|PhysicalZoneObjectType,findRouter,&info);
|
|
|
|
mWaterCoverage = info.waterCoverage;
|
|
mLiquidType = info.liquidType;
|
|
mLiquidHeight = info.waterHeight;
|
|
setCurrentWaterObject( info.waterObject );
|
|
|
|
// This value might be useful as a datablock value,
|
|
// This is what allows the player to stand in shallow water (below this coverage)
|
|
// without jiggling from buoyancy
|
|
if (mWaterCoverage >= 0.25f)
|
|
{
|
|
// water viscosity is used as drag for in water.
|
|
// ShapeBaseData drag is used for drag outside of water.
|
|
// Combine these two components to calculate this ShapeBase object's
|
|
// current drag.
|
|
mDrag = ( info.waterCoverage * info.waterViscosity ) +
|
|
( 1.0f - info.waterCoverage ) * mDataBlock->drag;
|
|
mBuoyancy = (info.waterDensity / mDataBlock->density) * info.waterCoverage;
|
|
}
|
|
|
|
mAppliedForce = info.appliedForce;
|
|
mNetGravity = (1.0-mBuoyancy)*info.gravityScale* gGravity;
|
|
|
|
//Con::printf( "WaterCoverage: %f", mWaterCoverage );
|
|
//Con::printf( "Drag: %f", mDrag );
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
void ShapeBase::applyRepair(F32 amount)
|
|
{
|
|
// Repair increases the repair reserve
|
|
if (amount > 0 && ((mRepairReserve += amount) > mDamage))
|
|
mRepairReserve = mDamage;
|
|
}
|
|
|
|
void ShapeBase::applyDamage(F32 amount)
|
|
{
|
|
if (amount > 0)
|
|
setDamageLevel(mDamage + amount);
|
|
}
|
|
|
|
F32 ShapeBase::getDamageValue()
|
|
{
|
|
// Return a 0-1 damage value.
|
|
return mDamage / mDataBlock->maxDamage;
|
|
}
|
|
|
|
F32 ShapeBase::getMaxDamage()
|
|
{
|
|
return mDataBlock->maxDamage;
|
|
}
|
|
|
|
void ShapeBase::updateDamageLevel()
|
|
{
|
|
if (mDamageThread) {
|
|
// mDamage is already 0-1 on the client
|
|
if (mDamage >= mDataBlock->destroyedLevel) {
|
|
if (getDamageState() == Destroyed)
|
|
mShapeInstance->setPos(mDamageThread, 0);
|
|
else
|
|
mShapeInstance->setPos(mDamageThread, 1);
|
|
} else {
|
|
mShapeInstance->setPos(mDamageThread, mDamage / mDataBlock->destroyedLevel);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
void ShapeBase::setDamageState(DamageState state)
|
|
{
|
|
if (mDamageState == state)
|
|
return;
|
|
|
|
bool invokeCallback = false;
|
|
const char* lastState = 0;
|
|
|
|
if (!isGhost()) {
|
|
if (state != getDamageState())
|
|
setMaskBits(DamageMask);
|
|
|
|
lastState = getDamageStateName();
|
|
switch (state) {
|
|
case Destroyed: {
|
|
if (mDamageState == Enabled)
|
|
{
|
|
setDamageState(Disabled);
|
|
|
|
// It is possible that the setDamageState() call above has already
|
|
// upgraded our state to Destroyed. If that is the case, no need
|
|
// to continue.
|
|
if (mDamageState == state)
|
|
return;
|
|
}
|
|
invokeCallback = true;
|
|
break;
|
|
}
|
|
case Disabled:
|
|
if (mDamageState == Enabled)
|
|
invokeCallback = true;
|
|
break;
|
|
case Enabled:
|
|
invokeCallback = true;
|
|
break;
|
|
default:
|
|
AssertFatal(false, "Invalid damage state");
|
|
break;
|
|
}
|
|
}
|
|
|
|
mDamageState = state;
|
|
if (mDamageState != Enabled) {
|
|
mRepairReserve = 0;
|
|
mEnergy = 0;
|
|
}
|
|
if (invokeCallback) {
|
|
// Like to call the scripts after the state has been intialize.
|
|
// This should only end up being called on the server.
|
|
switch (state) {
|
|
case Destroyed:
|
|
mDataBlock->onDestroyed_callback( this, lastState );
|
|
break;
|
|
case Disabled:
|
|
mDataBlock->onDisabled_callback( this, lastState );
|
|
break;
|
|
case Enabled:
|
|
mDataBlock->onEnabled_callback( this, lastState );
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
updateDamageState();
|
|
updateDamageLevel();
|
|
}
|
|
|
|
bool ShapeBase::setDamageState(const char* state)
|
|
{
|
|
for (S32 i = 0; i < NumDamageStates; i++)
|
|
if (!dStricmp(state,sDamageStateName[i])) {
|
|
setDamageState(DamageState(i));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const char* ShapeBase::getDamageStateName()
|
|
{
|
|
return sDamageStateName[mDamageState];
|
|
}
|
|
|
|
void ShapeBase::updateDamageState()
|
|
{
|
|
if (mHulkThread) {
|
|
F32 pos = (mDamageState == Destroyed) ? 1.0f : 0.0f;
|
|
if (mShapeInstance->getPos(mHulkThread) != pos) {
|
|
mShapeInstance->setPos(mHulkThread,pos);
|
|
|
|
if (isClientObject())
|
|
mShapeInstance->animate();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
void ShapeBase::blowUp()
|
|
{
|
|
Point3F center;
|
|
mObjBox.getCenter(¢er);
|
|
center += getPosition();
|
|
MatrixF trans = getTransform();
|
|
trans.setPosition( center );
|
|
|
|
// explode
|
|
Explosion* pExplosion = NULL;
|
|
|
|
if( pointInWater( (Point3F &)center ) && mDataBlock->underwaterExplosion )
|
|
{
|
|
pExplosion = new Explosion;
|
|
pExplosion->onNewDataBlock(mDataBlock->underwaterExplosion, false);
|
|
}
|
|
else
|
|
{
|
|
if (mDataBlock->explosion)
|
|
{
|
|
pExplosion = new Explosion;
|
|
pExplosion->onNewDataBlock(mDataBlock->explosion, false);
|
|
}
|
|
}
|
|
|
|
if( pExplosion )
|
|
{
|
|
pExplosion->setTransform(trans);
|
|
pExplosion->setInitialState(center, damageDir);
|
|
if (pExplosion->registerObject() == false)
|
|
{
|
|
Con::errorf(ConsoleLogEntry::General, "ShapeBase(%s)::explode: couldn't register explosion",
|
|
mDataBlock->getName() );
|
|
delete pExplosion;
|
|
pExplosion = NULL;
|
|
}
|
|
}
|
|
|
|
TSShapeInstance *debShape = NULL;
|
|
|
|
if( mDataBlock->mDebrisShape == NULL )
|
|
{
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
debShape = new TSShapeInstance( mDataBlock->mDebrisShape, true);
|
|
}
|
|
|
|
|
|
Vector< TSPartInstance * > partList;
|
|
TSPartInstance::breakShape( debShape, 0, partList, NULL, NULL, 0 );
|
|
|
|
if( !mDataBlock->debris )
|
|
{
|
|
mDataBlock->debris = new DebrisData;
|
|
}
|
|
|
|
// cycle through partlist and create debris pieces
|
|
for( U32 i=0; i<partList.size(); i++ )
|
|
{
|
|
//Point3F axis( 0.0, 0.0, 1.0 );
|
|
Point3F randomDir = MathUtils::randomDir( damageDir, 0, 50 );
|
|
|
|
Debris *debris = new Debris;
|
|
debris->setPartInstance( partList[i] );
|
|
debris->init( center, randomDir );
|
|
debris->onNewDataBlock( mDataBlock->debris, false );
|
|
debris->setTransform( trans );
|
|
|
|
if( !debris->registerObject() )
|
|
{
|
|
Con::warnf( ConsoleLogEntry::General, "Could not register debris for class: %s", mDataBlock->getName() );
|
|
delete debris;
|
|
debris = NULL;
|
|
}
|
|
else
|
|
{
|
|
debShape->incDebrisRefCount();
|
|
}
|
|
}
|
|
|
|
damageDir.set(0, 0, 1);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
void ShapeBase::onMount( SceneObject *obj, S32 node )
|
|
{
|
|
mConvexList->nukeList();
|
|
|
|
// Are we mounting to a ShapeBase object?
|
|
mShapeBaseMount = dynamic_cast<ShapeBase*>( obj );
|
|
|
|
Parent::onMount( obj, node );
|
|
}
|
|
|
|
void ShapeBase::onUnmount( SceneObject *obj, S32 node )
|
|
{
|
|
Parent::onUnmount( obj, node );
|
|
|
|
mShapeBaseMount = NULL;
|
|
}
|
|
|
|
Point3F ShapeBase::getAIRepairPoint()
|
|
{
|
|
if (mDataBlock->mountPointNode[ShapeBaseData::AIRepairNode] < 0)
|
|
return Point3F(0, 0, 0);
|
|
MatrixF xf(true);
|
|
getMountTransform( ShapeBaseData::AIRepairNode, MatrixF::Identity, &xf );
|
|
Point3F pos(0, 0, 0);
|
|
xf.getColumn(3,&pos);
|
|
return pos;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
void ShapeBase::getNodeTransform(const char* nodeName, MatrixF* outMat)
|
|
{
|
|
S32 nodeIDx = mDataBlock->getShapeResource()->findNode(nodeName);
|
|
const MatrixF& xfm = isMounted() ? mMount.xfm : MatrixF::Identity;
|
|
|
|
MatrixF nodeTransform(xfm);
|
|
const Point3F& scale = getScale();
|
|
if (nodeIDx != -1)
|
|
{
|
|
nodeTransform = mShapeInstance->mNodeTransforms[nodeIDx];
|
|
nodeTransform.mul(xfm);
|
|
}
|
|
// The position of the mount point needs to be scaled.
|
|
Point3F position = nodeTransform.getPosition();
|
|
position.convolve(scale);
|
|
nodeTransform.setPosition(position);
|
|
// Also we would like the object to be scaled to the model.
|
|
outMat->mul(mObjToWorld, nodeTransform);
|
|
return;
|
|
}
|
|
|
|
void ShapeBase::getNodeVector(const char* nodeName, VectorF* vec)
|
|
{
|
|
MatrixF mat;
|
|
getNodeTransform(nodeName, &mat);
|
|
|
|
mat.getColumn(1, vec);
|
|
}
|
|
|
|
void ShapeBase::getNodePoint(const char* nodeName, Point3F* pos)
|
|
{
|
|
MatrixF mat;
|
|
getNodeTransform(nodeName, &mat);
|
|
mat.getColumn(3, pos);
|
|
}
|
|
|
|
void ShapeBase::getEyeTransform(MatrixF* mat)
|
|
{
|
|
getEyeBaseTransform(mat, true);
|
|
}
|
|
|
|
void ShapeBase::getEyeBaseTransform(MatrixF* mat, bool includeBank)
|
|
{
|
|
// Returns eye to world space transform
|
|
S32 eyeNode = mDataBlock->eyeNode;
|
|
if (eyeNode != -1)
|
|
mat->mul(getTransform(), mShapeInstance->mNodeTransforms[eyeNode]);
|
|
else
|
|
*mat = getTransform();
|
|
}
|
|
|
|
void ShapeBase::getRenderEyeTransform(MatrixF* mat)
|
|
{
|
|
getRenderEyeBaseTransform(mat, true);
|
|
}
|
|
|
|
void ShapeBase::getRenderEyeBaseTransform(MatrixF* mat, bool includeBank)
|
|
{
|
|
// Returns eye to world space transform
|
|
S32 eyeNode = mDataBlock->eyeNode;
|
|
if (eyeNode != -1)
|
|
mat->mul(getRenderTransform(), mShapeInstance->mNodeTransforms[eyeNode]);
|
|
else
|
|
*mat = getRenderTransform();
|
|
}
|
|
|
|
void ShapeBase::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)
|
|
{
|
|
F32 min,max;
|
|
Point3F offset;
|
|
MatrixF eye,rot;
|
|
getCameraParameters(&min,&max,&offset,&rot);
|
|
getRenderEyeTransform(&eye);
|
|
mat->mul(eye,rot);
|
|
|
|
// Use the eye transform to orient the camera
|
|
VectorF vp,vec;
|
|
vp.x = vp.z = 0;
|
|
vp.y = -(max - min) * *pos;
|
|
eye.mulV(vp,&vec);
|
|
|
|
VectorF minVec;
|
|
vp.y = -min;
|
|
eye.mulV( vp, &minVec );
|
|
|
|
// Use the camera node's pos.
|
|
Point3F osp,sp;
|
|
if (mDataBlock->cameraNode != -1) {
|
|
mShapeInstance->mNodeTransforms[mDataBlock->cameraNode].getColumn(3,&osp);
|
|
|
|
// Scale the camera position before applying the transform
|
|
const Point3F& scale = getScale();
|
|
osp.convolve( scale );
|
|
|
|
getRenderTransform().mulP(osp,&sp);
|
|
}
|
|
else
|
|
getRenderTransform().getColumn(3,&sp);
|
|
|
|
// Make sure we don't extend the camera into anything solid
|
|
Point3F ep = sp + minVec + vec + offset;
|
|
disableCollision();
|
|
if (isMounted())
|
|
getObjectMount()->disableCollision();
|
|
RayInfo collision;
|
|
if( mContainer && mContainer->castRay(sp, ep,
|
|
(0xFFFFFFFF & ~(WaterObjectType |
|
|
GameBaseObjectType |
|
|
TriggerObjectType |
|
|
DefaultObjectType)),
|
|
&collision) == true) {
|
|
F32 vecLenSq = vec.lenSquared();
|
|
F32 adj = (-mDot(vec, collision.normal) / vecLenSq) * 0.1;
|
|
F32 newPos = getMax(0.0f, collision.t - adj);
|
|
if (newPos == 0.0f)
|
|
eye.getColumn(3,&ep);
|
|
else
|
|
ep = sp + offset + (vec * newPos);
|
|
}
|
|
mat->setColumn(3,ep);
|
|
if (isMounted())
|
|
getObjectMount()->enableCollision();
|
|
enableCollision();
|
|
}
|
|
else
|
|
{
|
|
getRenderEyeTransform(mat);
|
|
}
|
|
|
|
// Apply Camera FX.
|
|
mat->mul( gCamFXMgr.getTrans() );
|
|
}
|
|
|
|
void ShapeBase::getEyeCameraTransform(IDisplayDevice *displayDevice, U32 eyeId, MatrixF *outMat)
|
|
{
|
|
MatrixF temp(1);
|
|
Point3F eyePos;
|
|
Point3F rotEyePos;
|
|
|
|
DisplayPose newPose;
|
|
displayDevice->getFrameEyePose(&newPose, eyeId);
|
|
|
|
// Ok, basically we just need to add on newPose to the camera transform
|
|
// NOTE: currently we dont support third-person camera in this mode
|
|
MatrixF cameraTransform(1);
|
|
F32 fakePos = 0;
|
|
//cameraTransform = getRenderTransform(); // use this for controllers TODO
|
|
getCameraTransform(&fakePos, &cameraTransform);
|
|
|
|
temp = MatrixF(1);
|
|
newPose.orientation.setMatrix(&temp);
|
|
temp.setPosition(newPose.position);
|
|
|
|
*outMat = cameraTransform * temp;
|
|
}
|
|
|
|
void ShapeBase::getNodeTransform(const char* nodeName, const MatrixF& xfm, MatrixF* outMat)
|
|
{
|
|
if (!mShapeInstance)
|
|
return;
|
|
|
|
S32 nodeIDx = mDataBlock->getShapeResource()->findNode(nodeName);
|
|
|
|
MatrixF nodeTransform(xfm);
|
|
const Point3F& scale = getScale();
|
|
if (nodeIDx != -1)
|
|
{
|
|
nodeTransform = mShapeInstance->mNodeTransforms[nodeIDx];
|
|
nodeTransform.mul(xfm);
|
|
}
|
|
// The position of the mount point needs to be scaled.
|
|
Point3F position = nodeTransform.getPosition();
|
|
position.convolve(scale);
|
|
nodeTransform.setPosition(position);
|
|
// Also we would like the object to be scaled to the model.
|
|
outMat->mul(mObjToWorld, nodeTransform);
|
|
return;
|
|
}
|
|
|
|
void ShapeBase::getCameraParameters(F32 *min,F32* max,Point3F* off,MatrixF* rot)
|
|
{
|
|
*min = mDataBlock->cameraMinDist;
|
|
*max = mDataBlock->cameraMaxDist;
|
|
off->set(0,0,0);
|
|
rot->identity();
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
F32 ShapeBase::getDamageFlash() const
|
|
{
|
|
return mDamageFlash;
|
|
}
|
|
|
|
void ShapeBase::setDamageFlash(const F32 flash)
|
|
{
|
|
mDamageFlash = flash;
|
|
if (mDamageFlash < 0.0)
|
|
mDamageFlash = 0;
|
|
else if (mDamageFlash > 1.0)
|
|
mDamageFlash = 1.0;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
F32 ShapeBase::getWhiteOut() const
|
|
{
|
|
return mWhiteOut;
|
|
}
|
|
|
|
void ShapeBase::setWhiteOut(const F32 flash)
|
|
{
|
|
mWhiteOut = flash;
|
|
if (mWhiteOut < 0.0)
|
|
mWhiteOut = 0;
|
|
else if (mWhiteOut > 1.5)
|
|
mWhiteOut = 1.5;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
bool ShapeBase::onlyFirstPerson() const
|
|
{
|
|
return mDataBlock->firstPersonOnly;
|
|
}
|
|
|
|
bool ShapeBase::useObjsEyePoint() const
|
|
{
|
|
return mDataBlock->useEyePoint;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
void ShapeBase::setVelocity(const VectorF&)
|
|
{
|
|
}
|
|
|
|
void ShapeBase::applyImpulse(const Point3F&,const VectorF&)
|
|
{
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
void ShapeBase::playAudio(U32 slot, StringTableEntry assetId)
|
|
{
|
|
AssertFatal( slot < MaxSoundThreads, "ShapeBase::playAudio() bad slot index" );
|
|
if (AssetDatabase.isDeclaredAsset(assetId))
|
|
{
|
|
AssetPtr<SoundAsset> tempSoundAsset;
|
|
tempSoundAsset = assetId;
|
|
|
|
SoundThread& st = mSoundThread[slot];
|
|
if (tempSoundAsset && (!st.play || st.asset != tempSoundAsset))
|
|
{
|
|
setMaskBits(SoundMaskN << slot);
|
|
st.play = true;
|
|
st.asset = tempSoundAsset;
|
|
updateAudioState(st);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ShapeBase::stopAudio(U32 slot)
|
|
{
|
|
AssertFatal( slot < MaxSoundThreads, "ShapeBase::stopAudio() bad slot index" );
|
|
|
|
SoundThread& st = mSoundThread[slot];
|
|
if ( st.play )
|
|
{
|
|
st.play = false;
|
|
setMaskBits(SoundMaskN << slot);
|
|
updateAudioState(st);
|
|
}
|
|
}
|
|
|
|
void ShapeBase::updateServerAudio()
|
|
{
|
|
// Timeout non-looping sounds
|
|
for (S32 i = 0; i < MaxSoundThreads; i++) {
|
|
SoundThread& st = mSoundThread[i];
|
|
if (st.play && st.timeout && st.timeout < Sim::getCurrentTime()) {
|
|
clearMaskBits(SoundMaskN << i);
|
|
st.play = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ShapeBase::updateAudioState(SoundThread& st)
|
|
{
|
|
SFX_DELETE( st.sound );
|
|
|
|
if ( st.play && st.asset )
|
|
{
|
|
if ( isGhost() )
|
|
{
|
|
// if asset is valid, play
|
|
if (st.asset->isAssetValid() )
|
|
{
|
|
if (st.asset->load() == AssetBase::Ok)
|
|
{
|
|
st.sound = SFX->createSource(st.asset->getSFXTrack(), &getTransform());
|
|
if (st.sound)
|
|
st.sound->play();
|
|
}
|
|
}
|
|
else
|
|
st.play = false;
|
|
}
|
|
else
|
|
{
|
|
// Non-looping sounds timeout on the server
|
|
st.timeout = 0;
|
|
if ( !st.asset->getSfxDescription()->mIsLooping )
|
|
st.timeout = Sim::getCurrentTime() + sAudioTimeout;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// st.sound was not stopped before. If this causes issues remove.
|
|
st.play = false;
|
|
if (st.sound)
|
|
st.sound->stop();
|
|
}
|
|
}
|
|
|
|
void ShapeBase::updateAudioPos()
|
|
{
|
|
for (S32 i = 0; i < MaxSoundThreads; i++)
|
|
{
|
|
SFXSource* source = mSoundThread[i].sound;
|
|
if ( source )
|
|
source->setTransform( getTransform() );
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
const char *ShapeBase::getThreadSequenceName( U32 slot )
|
|
{
|
|
Thread& st = mScriptThread[slot];
|
|
if ( st.sequence == -1 )
|
|
{
|
|
// Invalid Animation.
|
|
return "";
|
|
}
|
|
|
|
// Name Index
|
|
const U32 nameIndex = getShape()->sequences[st.sequence].nameIndex;
|
|
|
|
// Return Name.
|
|
return getShape()->getName( nameIndex );
|
|
}
|
|
|
|
bool ShapeBase::setThreadSequence(U32 slot, S32 seq, bool reset)
|
|
{
|
|
Thread& st = mScriptThread[slot];
|
|
if (st.thread && st.sequence == seq && st.state == Thread::Play)
|
|
return true;
|
|
|
|
// Handle a -1 sequence, as this may be set when a thread has been destroyed.
|
|
if(seq == -1)
|
|
return true;
|
|
|
|
if (seq < MaxSequenceIndex) {
|
|
setMaskBits(ThreadMaskN << slot);
|
|
st.sequence = seq;
|
|
if (reset) {
|
|
st.state = Thread::Play;
|
|
st.atEnd = false;
|
|
st.timescale = 1.f;
|
|
st.position = 0.f;
|
|
}
|
|
if (mShapeInstance) {
|
|
if (!st.thread)
|
|
st.thread = mShapeInstance->addThread();
|
|
mShapeInstance->setSequence(st.thread,seq,st.position);
|
|
updateThread(st);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ShapeBase::updateThread(Thread& st)
|
|
{
|
|
switch (st.state)
|
|
{
|
|
case Thread::Stop:
|
|
{
|
|
mShapeInstance->setTimeScale( st.thread, 1.f );
|
|
mShapeInstance->setPos( st.thread, ( st.timescale > 0.f ) ? 1.0f : 0.0f );
|
|
} // Drop through to pause state
|
|
|
|
case Thread::Pause:
|
|
{
|
|
mShapeInstance->setTimeScale( st.thread, 0.f );
|
|
} break;
|
|
|
|
case Thread::Play:
|
|
{
|
|
if (st.atEnd)
|
|
{
|
|
mShapeInstance->setTimeScale(st.thread,1);
|
|
mShapeInstance->setPos( st.thread, ( st.timescale > 0.f ) ? 1.0f : 0.0f );
|
|
mShapeInstance->setTimeScale(st.thread,0);
|
|
st.state = Thread::Stop;
|
|
}
|
|
else
|
|
{
|
|
if ( st.position != -1.f )
|
|
{
|
|
mShapeInstance->setTimeScale( st.thread, 1.f );
|
|
mShapeInstance->setPos( st.thread, st.position );
|
|
}
|
|
|
|
mShapeInstance->setTimeScale(st.thread, st.timescale );
|
|
}
|
|
} break;
|
|
|
|
case Thread::Destroy:
|
|
{
|
|
st.atEnd = true;
|
|
st.sequence = -1;
|
|
if(st.thread)
|
|
{
|
|
mShapeInstance->destroyThread(st.thread);
|
|
st.thread = 0;
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
|
|
bool ShapeBase::stopThread(U32 slot)
|
|
{
|
|
Thread& st = mScriptThread[slot];
|
|
if (st.sequence != -1 && st.state != Thread::Stop) {
|
|
setMaskBits(ThreadMaskN << slot);
|
|
st.state = Thread::Stop;
|
|
updateThread(st);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ShapeBase::destroyThread(U32 slot)
|
|
{
|
|
Thread& st = mScriptThread[slot];
|
|
if (st.sequence != -1 && st.state != Thread::Destroy) {
|
|
setMaskBits(ThreadMaskN << slot);
|
|
st.state = Thread::Destroy;
|
|
updateThread(st);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ShapeBase::pauseThread(U32 slot)
|
|
{
|
|
Thread& st = mScriptThread[slot];
|
|
if (st.sequence != -1 && st.state != Thread::Pause) {
|
|
setMaskBits(ThreadMaskN << slot);
|
|
st.state = Thread::Pause;
|
|
updateThread(st);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ShapeBase::playThread(U32 slot)
|
|
{
|
|
Thread& st = mScriptThread[slot];
|
|
if (st.sequence != -1 && st.state != Thread::Play) {
|
|
setMaskBits(ThreadMaskN << slot);
|
|
st.state = Thread::Play;
|
|
updateThread(st);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ShapeBase::setThreadPosition( U32 slot, F32 pos )
|
|
{
|
|
Thread& st = mScriptThread[slot];
|
|
if (st.sequence != -1)
|
|
{
|
|
setMaskBits(ThreadMaskN << slot);
|
|
st.position = pos;
|
|
st.atEnd = false;
|
|
updateThread(st);
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ShapeBase::setThreadDir(U32 slot,bool forward)
|
|
{
|
|
Thread& st = mScriptThread[slot];
|
|
if (st.sequence != -1)
|
|
{
|
|
if ( ( st.timescale >= 0.f ) != forward )
|
|
{
|
|
setMaskBits(ThreadMaskN << slot);
|
|
st.timescale *= -1.f ;
|
|
st.atEnd = false;
|
|
updateThread(st);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ShapeBase::setThreadTimeScale( U32 slot, F32 timeScale )
|
|
{
|
|
Thread& st = mScriptThread[slot];
|
|
if (st.sequence != -1)
|
|
{
|
|
if (st.timescale != timeScale)
|
|
{
|
|
setMaskBits(ThreadMaskN << slot);
|
|
st.timescale = timeScale;
|
|
updateThread(st);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ShapeBase::advanceThreads(F32 dt)
|
|
{
|
|
for (U32 i = 0; i < MaxScriptThreads; i++) {
|
|
Thread& st = mScriptThread[i];
|
|
if (st.thread) {
|
|
if (!mShapeInstance->getShape()->sequences[st.sequence].isCyclic() && !st.atEnd &&
|
|
( ( st.timescale > 0.f )? mShapeInstance->getPos(st.thread) >= 1.0:
|
|
mShapeInstance->getPos(st.thread) <= 0)) {
|
|
st.atEnd = true;
|
|
updateThread(st);
|
|
if (!isGhost()) {
|
|
mDataBlock->onEndSequence_callback(this, i, this->getThreadSequenceName(i));
|
|
}
|
|
}
|
|
|
|
// Make sure the thread is still valid after the call to onEndSequence_callback().
|
|
// Someone could have called destroyThread() while in there.
|
|
if(st.thread)
|
|
{
|
|
mShapeInstance->advanceTime(dt,st.thread);
|
|
st.position = mShapeInstance->getPos(st.thread);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
/// Emit particles on the given emitter, if we're within triggerHeight above some static surface with a
|
|
/// material that has 'showDust' set to true. The particles will have a lifetime of 'numMilliseconds'
|
|
/// and be emitted at the given offset from the contact point having a direction of 'axis'.
|
|
|
|
void ShapeBase::emitDust( ParticleEmitter* emitter, F32 triggerHeight, const Point3F& offset, U32 numMilliseconds, const Point3F& axis )
|
|
{
|
|
if( !emitter )
|
|
return;
|
|
|
|
Point3F startPos = getPosition();
|
|
Point3F endPos = startPos + Point3F( 0.0, 0.0, - triggerHeight );
|
|
|
|
RayInfo rayInfo;
|
|
if( getContainer()->castRay( startPos, endPos, STATIC_COLLISION_TYPEMASK, &rayInfo ) )
|
|
{
|
|
Material* material = ( rayInfo.material ? dynamic_cast< Material* >( rayInfo.material->getMaterial() ) : 0 );
|
|
if( material && material->mShowDust )
|
|
{
|
|
LinearColorF colorList[ ParticleData::PDC_NUM_KEYS ];
|
|
|
|
for( U32 x = 0; x < getMin( Material::NUM_EFFECT_COLOR_STAGES, ParticleData::PDC_NUM_KEYS ); ++ x )
|
|
colorList[ x ] = material->mEffectColor[ x ];
|
|
for( U32 x = Material::NUM_EFFECT_COLOR_STAGES; x < ParticleData::PDC_NUM_KEYS; ++ x )
|
|
colorList[ x ].set( 1.0, 1.0, 1.0, 0.0 );
|
|
|
|
emitter->setColors( colorList );
|
|
|
|
Point3F contactPoint = rayInfo.point + offset;
|
|
emitter->emitParticles( contactPoint, true, ( axis == Point3F::Zero ? rayInfo.normal : axis ),
|
|
getVelocity(), numMilliseconds );
|
|
}
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
TSShape const* ShapeBase::getShape()
|
|
{
|
|
return mShapeInstance? mShapeInstance->getShape(): 0;
|
|
}
|
|
|
|
void ShapeBase::prepRenderImage( SceneRenderState *state )
|
|
{
|
|
_prepRenderImage( state, true, true );
|
|
}
|
|
|
|
void ShapeBase::_prepRenderImage( SceneRenderState *state,
|
|
bool renderSelf,
|
|
bool renderMountedImages )
|
|
{
|
|
PROFILE_SCOPE( ShapeBase_PrepRenderImage );
|
|
|
|
//if ( mIsCubemapUpdate )
|
|
// return false;
|
|
|
|
if( ( getDamageState() == Destroyed ) && ( !mDataBlock->renderWhenDestroyed ) )
|
|
return;
|
|
|
|
// We don't need to render if all the meshes are forced hidden.
|
|
if ( mMeshHidden.getSize() > 0 && mMeshHidden.testAll() )
|
|
return;
|
|
|
|
// If we're rendering shadows don't render the mounted
|
|
// images unless the shape is also rendered.
|
|
if ( state->isShadowPass() && !renderSelf )
|
|
return;
|
|
|
|
// If we're currently rendering our own reflection we
|
|
// don't want to render ourselves into it.
|
|
if ( mCubeReflector.isRendering() )
|
|
return;
|
|
|
|
// We force all the shapes to use the highest detail
|
|
// if we're the control object or mounted.
|
|
bool forceHighestDetail = false;
|
|
{
|
|
GameConnection *con = GameConnection::getConnectionToServer();
|
|
ShapeBase *co = NULL;
|
|
if(con && ( (co = dynamic_cast<ShapeBase*>(con->getControlObject())) != NULL) )
|
|
{
|
|
if(co == this || co->getObjectMount() == this)
|
|
forceHighestDetail = true;
|
|
}
|
|
}
|
|
|
|
mLastRenderFrame = sLastRenderFrame;
|
|
|
|
// get shape detail...we might not even need to be drawn
|
|
Point3F cameraOffset = getWorldBox().getClosestPoint( state->getDiffuseCameraPosition() ) - state->getDiffuseCameraPosition();
|
|
F32 dist = cameraOffset.len();
|
|
if (dist < 0.01f)
|
|
dist = 0.01f;
|
|
|
|
F32 invScale = (1.0f/getMax(getMax(mObjScale.x,mObjScale.y),mObjScale.z));
|
|
|
|
if (mShapeInstance)
|
|
{
|
|
if ( forceHighestDetail )
|
|
mShapeInstance->setCurrentDetail( 0 );
|
|
else
|
|
mShapeInstance->setDetailFromDistance( state, dist * invScale );
|
|
|
|
mShapeInstance->animate();
|
|
}
|
|
|
|
if ( ( mShapeInstance && mShapeInstance->getCurrentDetail() < 0 ) ||
|
|
( !mShapeInstance && !gShowBoundingBox ) )
|
|
{
|
|
// no, don't draw anything
|
|
return;
|
|
}
|
|
|
|
if( renderMountedImages )
|
|
{
|
|
for (U32 i = 0; i < MaxMountedImages; i++)
|
|
{
|
|
MountedImage& image = mMountedImageList[i];
|
|
U32 imageShapeIndex = getImageShapeIndex(image);
|
|
if (image.dataBlock && image.shapeInstance[imageShapeIndex])
|
|
{
|
|
// Select detail levels on mounted items but... always
|
|
// draw the control object's mounted images in high detail.
|
|
if ( forceHighestDetail )
|
|
image.shapeInstance[imageShapeIndex]->setCurrentDetail( 0 );
|
|
else
|
|
image.shapeInstance[imageShapeIndex]->setDetailFromDistance( state, dist * invScale );
|
|
|
|
if (!mIsZero( (1.0f - mCloakLevel) * mFadeVal))
|
|
{
|
|
prepBatchRender( state, i );
|
|
|
|
// Debug rendering of the mounted shape bounds.
|
|
if ( gShowBoundingBox )
|
|
{
|
|
ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
|
|
ri->renderDelegate.bind( this, &ShapeBase::_renderBoundingBox );
|
|
ri->objectIndex = i;
|
|
ri->type = RenderPassManager::RIT_Editor;
|
|
state->getRenderPass()->addInst( ri );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Debug rendering of the shape bounding box.
|
|
if ( gShowBoundingBox )
|
|
{
|
|
ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
|
|
ri->renderDelegate.bind( this, &ShapeBase::_renderBoundingBox );
|
|
ri->objectIndex = -1;
|
|
ri->type = RenderPassManager::RIT_Editor;
|
|
state->getRenderPass()->addInst( ri );
|
|
}
|
|
|
|
if ( mShapeInstance && renderSelf )
|
|
prepBatchRender( state, -1 );
|
|
|
|
calcClassRenderData();
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// prepBatchRender
|
|
//----------------------------------------------------------------------------
|
|
void ShapeBase::prepBatchRender(SceneRenderState* state, S32 mountedImageIndex )
|
|
{
|
|
// CHANGES IN HERE SHOULD BE DUPLICATED IN TSSTATIC!
|
|
|
|
GFXTransformSaver saver;
|
|
|
|
// Set up our TS render state.
|
|
TSRenderState rdata;
|
|
rdata.setSceneState( state );
|
|
if ( mCubeReflector.isEnabled() )
|
|
rdata.setCubemap( mCubeReflector.getCubemap() );
|
|
rdata.setFadeOverride( (1.0f - mCloakLevel) * mFadeVal );
|
|
|
|
// We might have some forward lit materials
|
|
// so pass down a query to gather lights.
|
|
LightQuery query;
|
|
query.init( getWorldSphere() );
|
|
rdata.setLightQuery( &query );
|
|
|
|
if( mountedImageIndex != -1 )
|
|
{
|
|
MountedImage& image = mMountedImageList[mountedImageIndex];
|
|
|
|
if( image.dataBlock && image.shapeInstance )
|
|
{
|
|
renderMountedImage( mountedImageIndex, rdata, state );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MatrixF mat = getRenderTransform();
|
|
mat.scale( mObjScale );
|
|
GFX->setWorldMatrix( mat );
|
|
|
|
if ( state->isDiffusePass() && mCubeReflector.isEnabled() && mCubeReflector.getOcclusionQuery() )
|
|
{
|
|
RenderPassManager *pass = state->getRenderPass();
|
|
|
|
OccluderRenderInst *ri = pass->allocInst<OccluderRenderInst>();
|
|
|
|
ri->type = RenderPassManager::RIT_Occluder;
|
|
ri->query = mCubeReflector.getOcclusionQuery();
|
|
mObjToWorld.mulP( mObjBox.getCenter(), &ri->position );
|
|
ri->scale.set( mObjBox.getExtents() );
|
|
ri->orientation = pass->allocUniqueXform( mObjToWorld );
|
|
ri->isSphere = false;
|
|
state->getRenderPass()->addInst( ri );
|
|
}
|
|
|
|
mShapeInstance->animate();
|
|
mShapeInstance->render( rdata );
|
|
}
|
|
}
|
|
|
|
void ShapeBase::renderMountedImage( U32 imageSlot, TSRenderState &rstate, SceneRenderState *state )
|
|
{
|
|
GFX->pushWorldMatrix();
|
|
|
|
MatrixF mat;
|
|
getRenderImageTransform(imageSlot, &mat, rstate.getSceneState()->isShadowPass());
|
|
GFX->setWorldMatrix( mat );
|
|
|
|
MountedImage& image = mMountedImageList[imageSlot];
|
|
U32 imageShapeIndex = getImageShapeIndex(image);
|
|
image.shapeInstance[imageShapeIndex]->animate();
|
|
image.shapeInstance[imageShapeIndex]->render( rstate );
|
|
|
|
GFX->popWorldMatrix();
|
|
}
|
|
|
|
void ShapeBase::_renderBoundingBox( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat )
|
|
{
|
|
// If we got an override mat then this some
|
|
// special rendering pass... skip out of it.
|
|
if ( overrideMat )
|
|
return;
|
|
|
|
GFXStateBlockDesc desc;
|
|
desc.setZReadWrite( true, false );
|
|
desc.setBlend( true );
|
|
desc.fillMode = GFXFillWireframe;
|
|
|
|
GFXDrawUtil *drawer = GFX->getDrawUtil();
|
|
|
|
if ( ri->objectIndex != -1 )
|
|
{
|
|
MountedImage &image = mMountedImageList[ ri->objectIndex ];
|
|
|
|
if ( image.shapeInstance )
|
|
{
|
|
MatrixF mat;
|
|
getRenderImageTransform( ri->objectIndex, &mat );
|
|
|
|
const Box3F &objBox = image.shapeInstance[getImageShapeIndex(image)]->getShape()->mBounds;
|
|
|
|
drawer->drawCube( desc, objBox, ColorI( 255, 255, 255 ), &mat );
|
|
}
|
|
}
|
|
else
|
|
drawer->drawCube( desc, mObjBox, ColorI( 255, 255, 255 ), &mRenderObjToWorld );
|
|
}
|
|
|
|
bool ShapeBase::castRay(const Point3F &start, const Point3F &end, RayInfo* info)
|
|
{
|
|
if (mShapeInstance)
|
|
{
|
|
RayInfo shortest;
|
|
shortest.t = 1e8;
|
|
|
|
info->object = NULL;
|
|
for (U32 i = 0; i < mDataBlock->LOSDetails.size(); i++)
|
|
{
|
|
mShapeInstance->animate(mDataBlock->LOSDetails[i]);
|
|
if (mShapeInstance->castRay(start, end, info, mDataBlock->LOSDetails[i]))
|
|
{
|
|
info->object = this;
|
|
if (info->t < shortest.t)
|
|
shortest = *info;
|
|
}
|
|
}
|
|
|
|
if (info->object == this)
|
|
{
|
|
// Copy out the shortest time...
|
|
*info = shortest;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool ShapeBase::castRayRendered(const Point3F &start, const Point3F &end, RayInfo* info)
|
|
{
|
|
if (mShapeInstance)
|
|
{
|
|
RayInfo localInfo;
|
|
mShapeInstance->animate();
|
|
bool res = mShapeInstance->castRayRendered(start, end, &localInfo, mShapeInstance->getCurrentDetail());
|
|
if (res)
|
|
{
|
|
*info = localInfo;
|
|
info->object = this;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
bool ShapeBase::buildPolyList(PolyListContext context, AbstractPolyList* polyList, const Box3F &, const SphereF &)
|
|
{
|
|
if ( !mShapeInstance )
|
|
return false;
|
|
|
|
polyList->setTransform(&mObjToWorld, mObjScale);
|
|
polyList->setObject(this);
|
|
|
|
if ( context == PLC_Selection )
|
|
{
|
|
mShapeInstance->animate();
|
|
mShapeInstance->buildPolyList(polyList,mShapeInstance->getCurrentDetail());
|
|
return true;
|
|
}
|
|
else if ( context == PLC_Export )
|
|
{
|
|
// Try to call on the client so we can export materials
|
|
ShapeBase* exportObj = this;
|
|
if ( isServerObject() && getClientObject() )
|
|
exportObj = dynamic_cast<ShapeBase*>(getClientObject());
|
|
|
|
S32 dl = 0;
|
|
exportObj->mShapeInstance->animate();
|
|
exportObj->mShapeInstance->buildPolyList(polyList, dl);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
bool ret = false;
|
|
for (U32 i = 0; i < mDataBlock->collisionDetails.size(); i++)
|
|
{
|
|
mShapeInstance->buildPolyList(polyList,mDataBlock->collisionDetails[i]);
|
|
ret = true;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
void ShapeBase::buildConvex(const Box3F& box, Convex* convex)
|
|
{
|
|
if (mShapeInstance == NULL)
|
|
return;
|
|
|
|
// These should really come out of a pool
|
|
mConvexList->collectGarbage();
|
|
|
|
Box3F realBox = box;
|
|
mWorldToObj.mul(realBox);
|
|
realBox.minExtents.convolveInverse(mObjScale);
|
|
realBox.maxExtents.convolveInverse(mObjScale);
|
|
|
|
if (realBox.isOverlapped(getObjBox()) == false)
|
|
return;
|
|
|
|
for (U32 i = 0; i < mDataBlock->collisionDetails.size(); i++)
|
|
{
|
|
Box3F newbox = mDataBlock->collisionBounds[i];
|
|
newbox.minExtents.convolve(mObjScale);
|
|
newbox.maxExtents.convolve(mObjScale);
|
|
mObjToWorld.mul(newbox);
|
|
if (box.isOverlapped(newbox) == false)
|
|
continue;
|
|
|
|
// See if this hull exists in the working set already...
|
|
Convex* cc = 0;
|
|
CollisionWorkingList& wl = convex->getWorkingList();
|
|
for (CollisionWorkingList* itr = wl.wLink.mNext; itr != &wl; itr = itr->wLink.mNext) {
|
|
if (itr->mConvex->getType() == ShapeBaseConvexType &&
|
|
(static_cast<ShapeBaseConvex*>(itr->mConvex)->pShapeBase == this &&
|
|
static_cast<ShapeBaseConvex*>(itr->mConvex)->hullId == i)) {
|
|
cc = itr->mConvex;
|
|
break;
|
|
}
|
|
}
|
|
if (cc)
|
|
continue;
|
|
|
|
// Create a new convex.
|
|
ShapeBaseConvex* cp = new ShapeBaseConvex;
|
|
mConvexList->registerObject(cp);
|
|
convex->addToWorkingList(cp);
|
|
cp->mObject = this;
|
|
cp->pShapeBase = this;
|
|
cp->hullId = i;
|
|
cp->box = mDataBlock->collisionBounds[i];
|
|
cp->transform = 0;
|
|
cp->findNodeTransform();
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
void ShapeBase::queueCollision( SceneObject *obj, const VectorF &vec)
|
|
{
|
|
// Add object to list of collisions.
|
|
SimTime time = Sim::getCurrentTime();
|
|
S32 num = obj->getId();
|
|
|
|
CollisionTimeout** adr = &mTimeoutList;
|
|
CollisionTimeout* ptr = mTimeoutList;
|
|
while (ptr) {
|
|
if (ptr->objectNumber == num) {
|
|
if (ptr->expireTime < time) {
|
|
ptr->expireTime = time + CollisionTimeoutValue;
|
|
ptr->object = obj;
|
|
ptr->vector = vec;
|
|
}
|
|
return;
|
|
}
|
|
// Recover expired entries
|
|
if (ptr->expireTime < time) {
|
|
CollisionTimeout* cur = ptr;
|
|
*adr = ptr->next;
|
|
ptr = ptr->next;
|
|
cur->next = sFreeTimeoutList;
|
|
sFreeTimeoutList = cur;
|
|
}
|
|
else {
|
|
adr = &ptr->next;
|
|
ptr = ptr->next;
|
|
}
|
|
}
|
|
|
|
// New entry for the object
|
|
if (sFreeTimeoutList != NULL)
|
|
{
|
|
ptr = sFreeTimeoutList;
|
|
sFreeTimeoutList = ptr->next;
|
|
ptr->next = NULL;
|
|
}
|
|
else
|
|
{
|
|
ptr = sTimeoutChunker.alloc();
|
|
}
|
|
|
|
ptr->object = obj;
|
|
ptr->objectNumber = obj->getId();
|
|
ptr->vector = vec;
|
|
ptr->expireTime = time + CollisionTimeoutValue;
|
|
ptr->next = mTimeoutList;
|
|
|
|
mTimeoutList = ptr;
|
|
}
|
|
|
|
void ShapeBase::notifyCollision()
|
|
{
|
|
// Notify all the objects that were just stamped during the queueing
|
|
// process.
|
|
SimTime expireTime = Sim::getCurrentTime() + CollisionTimeoutValue;
|
|
for (CollisionTimeout* ptr = mTimeoutList; ptr; ptr = ptr->next)
|
|
{
|
|
if (ptr->expireTime == expireTime && ptr->object)
|
|
{
|
|
SimObjectPtr<SceneObject> safePtr(ptr->object);
|
|
SimObjectPtr<ShapeBase> safeThis(this);
|
|
onCollision(ptr->object,ptr->vector);
|
|
ptr->object = 0;
|
|
|
|
if(!bool(safeThis))
|
|
return;
|
|
|
|
if(bool(safePtr))
|
|
safePtr->onCollision(this,ptr->vector);
|
|
|
|
if(!bool(safeThis))
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ShapeBase::onCollision( SceneObject *object, const VectorF &vec )
|
|
{
|
|
if (!isGhost())
|
|
mDataBlock->onCollision_callback( this, object, vec, vec.len() );
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
bool ShapeBase::pointInWater( Point3F &point )
|
|
{
|
|
if ( mCurrentWaterObject == NULL )
|
|
return false;
|
|
|
|
return mCurrentWaterObject->isUnderwater( point );
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
void ShapeBase::writePacketData(GameConnection *connection, BitStream *stream)
|
|
{
|
|
Parent::writePacketData(connection, stream);
|
|
|
|
stream->write(getEnergyLevel());
|
|
stream->write(mRechargeRate);
|
|
}
|
|
|
|
void ShapeBase::readPacketData(GameConnection *connection, BitStream *stream)
|
|
{
|
|
Parent::readPacketData(connection, stream);
|
|
|
|
F32 energy;
|
|
stream->read(&energy);
|
|
setEnergyLevel(energy);
|
|
|
|
stream->read(&mRechargeRate);
|
|
}
|
|
|
|
F32 ShapeBase::getUpdatePriority(CameraScopeQuery *camInfo, U32 updateMask, S32 updateSkips)
|
|
{
|
|
// If it's the scope object, must be high priority
|
|
if (camInfo->camera == this) {
|
|
// Most priorities are between 0 and 1, so this
|
|
// should be something larger.
|
|
return 10.0f;
|
|
}
|
|
if( camInfo->camera )
|
|
{
|
|
ShapeBase* camera = dynamic_cast< ShapeBase* >( camInfo->camera );
|
|
// see if the camera is mounted to this...
|
|
// if it is, this should have a high priority
|
|
if( camera && camera->getObjectMount() == this)
|
|
return 10.0f;
|
|
}
|
|
return Parent::getUpdatePriority(camInfo, updateMask, updateSkips);
|
|
}
|
|
|
|
U32 ShapeBase::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
|
|
{
|
|
U32 retMask = Parent::packUpdate(con, mask, stream);
|
|
|
|
if (mask & InitialUpdateMask) {
|
|
// mask off sounds that aren't playing
|
|
S32 i;
|
|
for (i = 0; i < MaxSoundThreads; i++)
|
|
if (!mSoundThread[i].play)
|
|
mask &= ~(SoundMaskN << i);
|
|
|
|
// mask off threads that aren't running
|
|
for (i = 0; i < MaxScriptThreads; i++)
|
|
if (mScriptThread[i].sequence == -1)
|
|
mask &= ~(ThreadMaskN << i);
|
|
|
|
// mask off images that aren't updated
|
|
for(i = 0; i < MaxMountedImages; i++)
|
|
if(!mMountedImageList[i].dataBlock)
|
|
mask &= ~(ImageMaskN << i);
|
|
}
|
|
|
|
if(!stream->writeFlag(mask & (NameMask | DamageMask | SoundMask | MeshHiddenMask |
|
|
ThreadMask | ImageMask | CloakMask | SkinMask)))
|
|
return retMask;
|
|
|
|
if (stream->writeFlag(mask & DamageMask)) {
|
|
stream->writeFloat(mClampF(mDamage / mDataBlock->maxDamage, 0.f, 1.f), DamageLevelBits);
|
|
stream->writeInt(mDamageState,NumDamageStateBits);
|
|
stream->writeNormalVector( damageDir, 8 );
|
|
}
|
|
|
|
if (stream->writeFlag(mask & ThreadMask)) {
|
|
for (S32 i = 0; i < MaxScriptThreads; i++) {
|
|
Thread& st = mScriptThread[i];
|
|
if (stream->writeFlag( (st.sequence != -1 || st.state == Thread::Destroy) && (mask & (ThreadMaskN << i)) ) ) {
|
|
stream->writeInt(st.sequence,ThreadSequenceBits);
|
|
stream->writeInt(st.state,2);
|
|
stream->write(st.timescale);
|
|
stream->write(st.position);
|
|
stream->writeFlag(st.atEnd);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (stream->writeFlag(mask & SoundMask)) {
|
|
for (S32 i = 0; i < MaxSoundThreads; i++) {
|
|
SoundThread& st = mSoundThread[i];
|
|
if (stream->writeFlag(mask & (SoundMaskN << i)))
|
|
if (stream->writeFlag(st.play))
|
|
{
|
|
NetStringHandle assetIdStr = st.asset->getAssetId();
|
|
con->packNetStringHandleU(stream, assetIdStr);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (stream->writeFlag(mask & ImageMask)) {
|
|
for (S32 i = 0; i < MaxMountedImages; i++)
|
|
if (stream->writeFlag(mask & (ImageMaskN << i))) {
|
|
MountedImage& image = mMountedImageList[i];
|
|
if (stream->writeFlag(image.dataBlock))
|
|
stream->writeInt(image.dataBlock->getId() - DataBlockObjectIdFirst,
|
|
DataBlockObjectIdBitSize);
|
|
con->packNetStringHandleU(stream, image.skinNameHandle);
|
|
con->packNetStringHandleU(stream, image.scriptAnimPrefix);
|
|
|
|
// Used to force the 1st person rendering on the client. This is required
|
|
// as this object could be ghosted to the client prior to its controlling client
|
|
// being set. Therefore there is a network tick when the object is in limbo...
|
|
stream->writeFlag(image.dataBlock && image.dataBlock->animateAllShapes && getControllingClient() == con);
|
|
|
|
stream->writeFlag(image.wet);
|
|
stream->writeFlag(image.motion);
|
|
stream->writeFlag(image.ammo);
|
|
stream->writeFlag(image.loaded);
|
|
stream->writeFlag(image.target);
|
|
stream->writeFlag(image.triggerDown);
|
|
stream->writeFlag(image.altTriggerDown);
|
|
|
|
for (U32 j=0; j<ShapeBaseImageData::MaxGenericTriggers; ++j)
|
|
{
|
|
stream->writeFlag(image.genericTrigger[j]);
|
|
}
|
|
|
|
stream->writeInt(image.fireCount,3);
|
|
stream->writeInt(image.altFireCount,3);
|
|
stream->writeInt(image.reloadCount,3);
|
|
stream->writeFlag(isImageFiring(i));
|
|
stream->writeFlag(isImageAltFiring(i));
|
|
stream->writeFlag(isImageReloading(i));
|
|
}
|
|
}
|
|
|
|
// Group some of the uncommon stuff together.
|
|
if (stream->writeFlag(mask & (NameMask | CloakMask | SkinMask | MeshHiddenMask ))) {
|
|
|
|
if (stream->writeFlag(mask & CloakMask))
|
|
{
|
|
// cloaking
|
|
stream->writeFlag( mCloaked );
|
|
|
|
// piggyback control update
|
|
stream->writeFlag(bool(getControllingClient()));
|
|
|
|
// fading
|
|
if(stream->writeFlag(mFading && mFadeElapsedTime >= mFadeDelay)) {
|
|
stream->writeFlag(mFadeOut);
|
|
stream->write(mFadeTime);
|
|
}
|
|
else
|
|
stream->writeFlag(mFadeVal == 1.0f);
|
|
}
|
|
if (stream->writeFlag(mask & NameMask)) {
|
|
con->packNetStringHandleU(stream, mShapeNameHandle);
|
|
}
|
|
|
|
if ( stream->writeFlag( mask & MeshHiddenMask ) )
|
|
stream->writeBits( mMeshHidden );
|
|
|
|
if (stream->writeFlag(mask & SkinMask))
|
|
con->packNetStringHandleU(stream, mSkinNameHandle);
|
|
}
|
|
|
|
return retMask;
|
|
}
|
|
|
|
void ShapeBase::unpackUpdate(NetConnection *con, BitStream *stream)
|
|
{
|
|
Parent::unpackUpdate(con, stream);
|
|
mLastRenderFrame = sLastRenderFrame; // make sure we get a process after the event...
|
|
|
|
if(!stream->readFlag())
|
|
return;
|
|
|
|
if (stream->readFlag()) {
|
|
mDamage = mClampF(stream->readFloat(DamageLevelBits) * mDataBlock->maxDamage, 0.f, mDataBlock->maxDamage);
|
|
DamageState prevState = mDamageState;
|
|
mDamageState = DamageState(stream->readInt(NumDamageStateBits));
|
|
stream->readNormalVector( &damageDir, 8 );
|
|
if (prevState != Destroyed && mDamageState == Destroyed && isProperlyAdded())
|
|
blowUp();
|
|
updateDamageLevel();
|
|
updateDamageState();
|
|
}
|
|
|
|
if (stream->readFlag()) {
|
|
for (S32 i = 0; i < MaxScriptThreads; i++) {
|
|
if (stream->readFlag()) {
|
|
Thread& st = mScriptThread[i];
|
|
U32 seq = stream->readInt(ThreadSequenceBits);
|
|
st.state = Thread::State(stream->readInt(2));
|
|
stream->read( &st.timescale );
|
|
stream->read( &st.position );
|
|
st.atEnd = stream->readFlag();
|
|
if (st.sequence != seq && st.state != Thread::Destroy)
|
|
setThreadSequence(i,seq,false);
|
|
else
|
|
updateThread(st);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( stream->readFlag() )
|
|
{
|
|
for ( S32 i = 0; i < MaxSoundThreads; i++ )
|
|
{
|
|
if ( stream->readFlag() )
|
|
{
|
|
SoundThread& st = mSoundThread[i];
|
|
st.play = stream->readFlag();
|
|
if ( st.play )
|
|
{
|
|
StringTableEntry temp = StringTable->insert(con->unpackNetStringHandleU(stream).getString());
|
|
if (AssetDatabase.isDeclaredAsset(temp))
|
|
{
|
|
AssetPtr<SoundAsset> tempSoundAsset;
|
|
tempSoundAsset = temp;
|
|
|
|
st.asset = temp;
|
|
}
|
|
}
|
|
|
|
if ( isProperlyAdded() )
|
|
updateAudioState( st );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Mounted Images
|
|
if (stream->readFlag()) {
|
|
for (S32 i = 0; i < MaxMountedImages; i++) {
|
|
if (stream->readFlag()) {
|
|
MountedImage& image = mMountedImageList[i];
|
|
ShapeBaseImageData* imageData = 0;
|
|
if (stream->readFlag()) {
|
|
SimObjectId id = stream->readInt(DataBlockObjectIdBitSize) +
|
|
DataBlockObjectIdFirst;
|
|
if (!Sim::findObject(id,imageData)) {
|
|
con->setLastError("Invalid packet (mounted images).");
|
|
return;
|
|
}
|
|
}
|
|
|
|
NetStringHandle skinDesiredNameHandle = con->unpackNetStringHandleU(stream);
|
|
|
|
NetStringHandle scriptDesiredAnimPrefix = con->unpackNetStringHandleU(stream);
|
|
|
|
image.forceAnimateAllShapes = stream->readFlag();
|
|
|
|
image.wet = stream->readFlag();
|
|
|
|
image.motion = stream->readFlag();
|
|
|
|
image.ammo = stream->readFlag();
|
|
|
|
image.loaded = stream->readFlag();
|
|
|
|
image.target = stream->readFlag();
|
|
|
|
image.triggerDown = stream->readFlag();
|
|
image.altTriggerDown = stream->readFlag();
|
|
|
|
for (U32 j=0; j<ShapeBaseImageData::MaxGenericTriggers; ++j)
|
|
{
|
|
image.genericTrigger[j] = stream->readFlag();
|
|
}
|
|
|
|
S32 count = stream->readInt(3);
|
|
S32 altCount = stream->readInt(3);
|
|
S32 reloadCount = stream->readInt(3);
|
|
|
|
bool datablockChange = image.dataBlock != imageData;
|
|
if (datablockChange || (image.skinNameHandle != skinDesiredNameHandle))
|
|
{
|
|
MountedImage& neoImage = mMountedImageList[i];
|
|
neoImage.scriptAnimPrefix = scriptDesiredAnimPrefix;
|
|
|
|
setImage( i, imageData,
|
|
skinDesiredNameHandle, neoImage.loaded,
|
|
neoImage.ammo, neoImage.triggerDown, neoImage.altTriggerDown,
|
|
neoImage.motion, neoImage.genericTrigger[0], neoImage.genericTrigger[1], neoImage.genericTrigger[2], neoImage.genericTrigger[3],
|
|
neoImage.target);
|
|
}
|
|
|
|
if (!datablockChange && image.scriptAnimPrefix != scriptDesiredAnimPrefix)
|
|
{
|
|
// We don't have a new image, but we do have a new script anim prefix to work with.
|
|
// Notify the image of this change.
|
|
MountedImage& animImage = mMountedImageList[i];
|
|
animImage.scriptAnimPrefix = scriptDesiredAnimPrefix;
|
|
updateAnimThread(i, getImageShapeIndex(animImage));
|
|
}
|
|
|
|
bool isFiring = stream->readFlag();
|
|
bool isAltFiring = stream->readFlag();
|
|
bool isReloading = stream->readFlag();
|
|
|
|
if (isProperlyAdded()) {
|
|
// Normal processing
|
|
bool processFiring = false;
|
|
if (count != image.fireCount)
|
|
{
|
|
image.fireCount = count;
|
|
setImageState(i,getImageFireState(i),true);
|
|
processFiring = true;
|
|
}
|
|
else if (altCount != image.altFireCount)
|
|
{
|
|
image.altFireCount = altCount;
|
|
setImageState(i,getImageAltFireState(i),true);
|
|
processFiring = true;
|
|
}
|
|
else if (reloadCount != image.reloadCount)
|
|
{
|
|
image.reloadCount = reloadCount;
|
|
setImageState(i,getImageReloadState(i),true);
|
|
}
|
|
|
|
if (processFiring && imageData)
|
|
{
|
|
if ( imageData->lightType == ShapeBaseImageData::WeaponFireLight )
|
|
image.lightStart = Sim::getCurrentTime();
|
|
}
|
|
|
|
updateImageState(i,0);
|
|
}
|
|
else
|
|
{
|
|
if(imageData)
|
|
{
|
|
// Initial state
|
|
image.fireCount = count;
|
|
image.altFireCount = altCount;
|
|
image.reloadCount = reloadCount;
|
|
if (isFiring)
|
|
setImageState(i,getImageFireState(i),true);
|
|
else if (isAltFiring)
|
|
setImageState(i,getImageAltFireState(i),true);
|
|
else if (isReloading)
|
|
setImageState(i,getImageReloadState(i),true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (stream->readFlag())
|
|
{
|
|
if(stream->readFlag()) // CloakMask and control
|
|
{
|
|
// Read cloaking state.
|
|
|
|
setCloakedState(stream->readFlag());
|
|
mIsControlled = stream->readFlag();
|
|
|
|
if (( mFading = stream->readFlag()) == true) {
|
|
mFadeOut = stream->readFlag();
|
|
if(mFadeOut)
|
|
mFadeVal = 1.0f;
|
|
else
|
|
mFadeVal = 0;
|
|
stream->read(&mFadeTime);
|
|
mFadeDelay = 0;
|
|
mFadeElapsedTime = 0;
|
|
}
|
|
else
|
|
mFadeVal = F32(stream->readFlag());
|
|
}
|
|
if (stream->readFlag()) { // NameMask
|
|
mShapeNameHandle = con->unpackNetStringHandleU(stream);
|
|
}
|
|
|
|
if ( stream->readFlag() ) // MeshHiddenMask
|
|
{
|
|
stream->readBits( &mMeshHidden );
|
|
_updateHiddenMeshes();
|
|
}
|
|
|
|
if (stream->readFlag()) // SkinMask
|
|
{
|
|
NetStringHandle skinDesiredNameHandle = con->unpackNetStringHandleU(stream);;
|
|
if (mSkinNameHandle != skinDesiredNameHandle)
|
|
{
|
|
mSkinNameHandle = skinDesiredNameHandle;
|
|
reSkin();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
void ShapeBase::forceUncloak(const char * reason)
|
|
{
|
|
AssertFatal(isServerObject(), "ShapeBase::forceUncloak: server only call");
|
|
if(!mCloaked)
|
|
return;
|
|
|
|
mDataBlock->onForceUncloak_callback( this, reason ? reason : "" );
|
|
}
|
|
|
|
void ShapeBase::setCloakedState(bool cloaked)
|
|
{
|
|
if (cloaked == mCloaked)
|
|
return;
|
|
|
|
if (isServerObject())
|
|
setMaskBits(CloakMask);
|
|
|
|
// Have to do this for the client, if we are ghosted over in the initial
|
|
// packet as cloaked, we set the state immediately to the extreme
|
|
if (isProperlyAdded() == false) {
|
|
mCloaked = cloaked;
|
|
if (mCloaked)
|
|
mCloakLevel = 1.0;
|
|
else
|
|
mCloakLevel = 0.0;
|
|
} else {
|
|
mCloaked = cloaked;
|
|
}
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
void ShapeBase::setHidden( bool hidden )
|
|
{
|
|
if( hidden != isHidden() )
|
|
{
|
|
Parent::setHidden( hidden );
|
|
|
|
if( hidden )
|
|
setProcessTick( false );
|
|
else
|
|
setProcessTick( true );
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
void ShapeBaseConvex::findNodeTransform()
|
|
{
|
|
S32 dl = pShapeBase->mDataBlock->collisionDetails[hullId];
|
|
|
|
TSShapeInstance* si = pShapeBase->getShapeInstance();
|
|
TSShape* shape = si->getShape();
|
|
|
|
const TSShape::Detail* detail = &shape->details[dl];
|
|
const S32 subs = detail->subShapeNum;
|
|
const S32 start = shape->subShapeFirstObject[subs];
|
|
const S32 end = start + shape->subShapeNumObjects[subs];
|
|
|
|
// Find the first object that contains a mesh for this
|
|
// detail level. There should only be one mesh per
|
|
// collision detail level.
|
|
for (S32 i = start; i < end; i++)
|
|
{
|
|
const TSShape::Object* obj = &shape->objects[i];
|
|
if (obj->numMeshes && detail->objectDetailNum < obj->numMeshes)
|
|
{
|
|
nodeTransform = &si->mNodeTransforms[obj->nodeIndex];
|
|
return;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
const MatrixF& ShapeBaseConvex::getTransform() const
|
|
{
|
|
// If the transform isn't specified, it's assumed to be the
|
|
// origin of the shape.
|
|
const MatrixF& omat = (transform != 0)? *transform: mObject->getTransform();
|
|
|
|
// Multiply on the mesh shape offset
|
|
// tg: Returning this static here is not really a good idea, but
|
|
// all this Convex code needs to be re-organized.
|
|
if (nodeTransform) {
|
|
static MatrixF mat;
|
|
mat.mul(omat,*nodeTransform);
|
|
return mat;
|
|
}
|
|
return omat;
|
|
}
|
|
|
|
Box3F ShapeBaseConvex::getBoundingBox() const
|
|
{
|
|
const MatrixF& omat = (transform != 0)? *transform: mObject->getTransform();
|
|
return getBoundingBox(omat, mObject->getScale());
|
|
}
|
|
|
|
Box3F ShapeBaseConvex::getBoundingBox(const MatrixF& mat, const Point3F& scale) const
|
|
{
|
|
Box3F newBox = box;
|
|
newBox.minExtents.convolve(scale);
|
|
newBox.maxExtents.convolve(scale);
|
|
mat.mul(newBox);
|
|
return newBox;
|
|
}
|
|
|
|
Point3F ShapeBaseConvex::support(const VectorF& v) const
|
|
{
|
|
TSShape::ConvexHullAccelerator* pAccel =
|
|
pShapeBase->mShapeInstance->getShape()->getAccelerator(pShapeBase->mDataBlock->collisionDetails[hullId]);
|
|
AssertFatal(pAccel != NULL, "Error, no accel!");
|
|
|
|
F32 currMaxDP = mDot(pAccel->vertexList[0], v);
|
|
U32 index = 0;
|
|
for (U32 i = 1; i < pAccel->numVerts; i++) {
|
|
F32 dp = mDot(pAccel->vertexList[i], v);
|
|
if (dp > currMaxDP) {
|
|
currMaxDP = dp;
|
|
index = i;
|
|
}
|
|
}
|
|
|
|
return pAccel->vertexList[index];
|
|
}
|
|
|
|
|
|
void ShapeBaseConvex::getFeatures(const MatrixF& mat, const VectorF& n, ConvexFeature* cf)
|
|
{
|
|
cf->material = 0;
|
|
cf->mObject = mObject;
|
|
|
|
TSShape::ConvexHullAccelerator* pAccel =
|
|
pShapeBase->mShapeInstance->getShape()->getAccelerator(pShapeBase->mDataBlock->collisionDetails[hullId]);
|
|
AssertFatal(pAccel != NULL, "Error, no accel!");
|
|
|
|
F32 currMaxDP = mDot(pAccel->vertexList[0], n);
|
|
U32 index = 0;
|
|
U32 i;
|
|
for (i = 1; i < pAccel->numVerts; i++) {
|
|
F32 dp = mDot(pAccel->vertexList[i], n);
|
|
if (dp > currMaxDP) {
|
|
currMaxDP = dp;
|
|
index = i;
|
|
}
|
|
}
|
|
|
|
const U8* emitString = pAccel->emitStrings[index];
|
|
U32 currPos = 0;
|
|
U32 numVerts = emitString[currPos++];
|
|
for (i = 0; i < numVerts; i++) {
|
|
cf->mVertexList.increment();
|
|
U32 vListIDx = emitString[currPos++];
|
|
mat.mulP(pAccel->vertexList[vListIDx], &cf->mVertexList.last());
|
|
}
|
|
|
|
U32 numEdges = emitString[currPos++];
|
|
for (i = 0; i < numEdges; i++) {
|
|
U32 ev0 = emitString[currPos++];
|
|
U32 ev1 = emitString[currPos++];
|
|
cf->mEdgeList.increment();
|
|
cf->mEdgeList.last().vertex[0] = ev0;
|
|
cf->mEdgeList.last().vertex[1] = ev1;
|
|
}
|
|
|
|
U32 numFaces = emitString[currPos++];
|
|
for (i = 0; i < numFaces; i++) {
|
|
cf->mFaceList.increment();
|
|
U32 plane = emitString[currPos++];
|
|
mat.mulV(pAccel->normalList[plane], &cf->mFaceList.last().normal);
|
|
for (U32 j = 0; j < 3; j++)
|
|
cf->mFaceList.last().vertex[j] = emitString[currPos++];
|
|
}
|
|
}
|
|
|
|
|
|
void ShapeBaseConvex::getPolyList(AbstractPolyList* list)
|
|
{
|
|
list->setTransform(&pShapeBase->getTransform(), pShapeBase->getScale());
|
|
list->setObject(pShapeBase);
|
|
|
|
pShapeBase->mShapeInstance->animate(pShapeBase->mDataBlock->collisionDetails[hullId]);
|
|
pShapeBase->mShapeInstance->buildPolyList(list,pShapeBase->mDataBlock->collisionDetails[hullId]);
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
bool ShapeBase::isInvincible()
|
|
{
|
|
if( mDataBlock )
|
|
{
|
|
return mDataBlock->isInvincible;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ShapeBase::startFade( F32 fadeTime, F32 fadeDelay, bool fadeOut )
|
|
{
|
|
setMaskBits(CloakMask);
|
|
mFadeElapsedTime = 0;
|
|
mFading = true;
|
|
if(fadeDelay < 0)
|
|
fadeDelay = 0;
|
|
if(fadeTime < 0)
|
|
fadeTime = 0;
|
|
mFadeTime = fadeTime;
|
|
mFadeDelay = fadeDelay;
|
|
mFadeOut = fadeOut;
|
|
mFadeVal = F32(mFadeOut);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
void ShapeBase::setShapeName(const char* name)
|
|
{
|
|
if (!isGhost()) {
|
|
if (name[0] != '\0') {
|
|
// Use tags for better network performance
|
|
// Should be a tag, but we'll convert to one if it isn't.
|
|
if (name[0] == StringTagPrefixByte)
|
|
mShapeNameHandle = NetStringHandle(U32(dAtoi(name + 1)));
|
|
else
|
|
mShapeNameHandle = NetStringHandle(name);
|
|
}
|
|
else {
|
|
mShapeNameHandle = NetStringHandle();
|
|
}
|
|
setMaskBits(NameMask);
|
|
}
|
|
}
|
|
|
|
void ShapeBase::setSkinName(const char* name)
|
|
{
|
|
if (!isGhost()) {
|
|
if (name[0] != '\0') {
|
|
// Use tags for better network performance
|
|
// Should be a tag, but we'll convert to one if it isn't.
|
|
if (name[0] == StringTagPrefixByte)
|
|
mSkinNameHandle = NetStringHandle(U32(dAtoi(name + 1)));
|
|
else
|
|
mSkinNameHandle = NetStringHandle(name);
|
|
}
|
|
else
|
|
mSkinNameHandle = NetStringHandle();
|
|
setMaskBits(SkinMask);
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
void ShapeBase::reSkin()
|
|
{
|
|
if (isGhost() && mShapeInstance)
|
|
{
|
|
if (mSkinNameHandle.isValidString())
|
|
{
|
|
mShapeInstance->resetMaterialList();
|
|
Vector<String> skins;
|
|
String(mSkinNameHandle.getString()).split(";", skins);
|
|
|
|
for (S32 i = 0; i < skins.size(); i++)
|
|
{
|
|
String oldSkin(mAppliedSkinName.c_str());
|
|
String newSkin(skins[i]);
|
|
|
|
// Check if the skin handle contains an explicit "old" base string. This
|
|
// allows all models to support skinning, even if they don't follow the
|
|
// "base_xxx" material naming convention.
|
|
S32 split = newSkin.find('='); // "old=new" format skin?
|
|
if (split != String::NPos)
|
|
{
|
|
oldSkin = newSkin.substr(0, split);
|
|
newSkin = newSkin.erase(0, split + 1);
|
|
}
|
|
else
|
|
{
|
|
oldSkin = "";
|
|
}
|
|
mShapeInstance->reSkin(newSkin, oldSkin);
|
|
mAppliedSkinName = newSkin;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mShapeInstance->reSkin("", mAppliedSkinName);
|
|
mAppliedSkinName = "";
|
|
}
|
|
}
|
|
}
|
|
|
|
void ShapeBase::setCurrentWaterObject( WaterObject *obj )
|
|
{
|
|
if ( obj )
|
|
deleteNotify( obj );
|
|
if ( mCurrentWaterObject )
|
|
clearNotify( mCurrentWaterObject );
|
|
|
|
mCurrentWaterObject = obj;
|
|
}
|
|
|
|
void ShapeBase::setTransform(const MatrixF & mat)
|
|
{
|
|
Parent::setTransform(mat);
|
|
|
|
// Accumulation and environment mapping
|
|
if (isClientObject() && mShapeInstance)
|
|
{
|
|
if (mShapeInstance->hasAccumulation())
|
|
AccumulationVolume::updateObject(this);
|
|
}
|
|
}
|
|
|
|
void ShapeBase::notifyCollisionCallbacks(SceneObject* obj, const VectorF& vel)
|
|
{
|
|
for (S32 i = 0; i < collision_callbacks.size(); i++)
|
|
if (collision_callbacks[i])
|
|
collision_callbacks[i]->collisionNotify(this, obj, vel);
|
|
}
|
|
|
|
void ShapeBase::registerCollisionCallback(CollisionEventCallback* ce_cb)
|
|
{
|
|
for (S32 i = 0; i < collision_callbacks.size(); i++)
|
|
if (collision_callbacks[i] == ce_cb)
|
|
return;
|
|
|
|
collision_callbacks.push_back(ce_cb);
|
|
}
|
|
|
|
void ShapeBase::unregisterCollisionCallback(CollisionEventCallback* ce_cb)
|
|
{
|
|
for (S32 i = 0; i < collision_callbacks.size(); i++)
|
|
if (collision_callbacks[i] == ce_cb)
|
|
{
|
|
collision_callbacks.erase(i);
|
|
return;
|
|
}
|
|
}
|
|
//--------------------------------------------------------------------------
|
|
//----------------------------------------------------------------------------
|
|
DefineEngineMethod( ShapeBase, setHidden, void, ( bool show ),,
|
|
"@brief Add or remove this object from the scene.\n\n"
|
|
"When removed from the scene, the object will not be processed or rendered.\n"
|
|
"@param show False to hide the object, true to re-show it\n\n" )
|
|
{
|
|
object->setHidden( show );
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, isHidden, bool, (),,
|
|
"Check if the object is hidden.\n"
|
|
"@return true if the object is hidden, false if visible.\n\n" )
|
|
{
|
|
return object->isHidden();
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
DefineEngineMethod( ShapeBase, playAudio, bool, ( S32 slot, StringTableEntry assetId),,
|
|
"@brief Attach a sound to this shape and start playing it.\n\n"
|
|
|
|
"@param slot Audio slot index for the sound (valid range is 0 - 3)\n" // 3 = ShapeBase::MaxSoundThreads-1
|
|
"@param track SFXTrack to play\n"
|
|
"@return true if the sound was attached successfully, false if failed\n\n"
|
|
|
|
"@see stopAudio()\n")
|
|
{
|
|
if (assetId && slot >= 0 && slot < ShapeBase::MaxSoundThreads) {
|
|
object->playAudio(slot, assetId);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, stopAudio, bool, ( S32 slot ),,
|
|
"@brief Stop a sound started with playAudio.\n\n"
|
|
|
|
"@param slot audio slot index (started with playAudio)\n"
|
|
"@return true if the sound was stopped successfully, false if failed\n\n"
|
|
|
|
"@see playAudio()\n")
|
|
{
|
|
if (slot >= 0 && slot < ShapeBase::MaxSoundThreads) {
|
|
object->stopAudio(slot);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
DefineEngineMethod( ShapeBase, playThread, bool, ( S32 slot, const char* name ), ( "" ),
|
|
"@brief Start a new animation thread, or restart one that has been paused or "
|
|
"stopped.\n\n"
|
|
|
|
"@param slot thread slot to play. Valid range is 0 - 3)\n" // 3 = ShapeBase::MaxScriptThreads-1
|
|
"@param name name of the animation sequence to play in this slot. If not "
|
|
"specified, the paused or stopped thread in this slot will be resumed.\n"
|
|
"@return true if successful, false if failed\n\n"
|
|
|
|
"@tsexample\n"
|
|
"%obj.playThread( 0, \"ambient\" ); // Play the ambient sequence in slot 0\n"
|
|
"%obj.setThreadTimeScale( 0, 0.5 ); // Play at half-speed\n"
|
|
"%obj.pauseThread( 0 ); // Pause the sequence\n"
|
|
"%obj.playThread( 0 ); // Resume playback\n"
|
|
"%obj.playThread( 0, \"spin\" ); // Replace the sequence in slot 0\n"
|
|
"@endtsexample\n"
|
|
|
|
"@see pauseThread()\n"
|
|
"@see stopThread()\n"
|
|
"@see setThreadDir()\n"
|
|
"@see setThreadTimeScale()\n"
|
|
"@see destroyThread()\n")
|
|
{
|
|
if (slot >= 0 && slot < ShapeBase::MaxScriptThreads) {
|
|
if (!dStrEqual(name, "")) {
|
|
if (object->getShape()) {
|
|
S32 seq = object->getShape()->findSequence(name);
|
|
if (seq != -1 && object->setThreadSequence(slot,seq))
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
if (object->playThread(slot))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, setThreadDir, bool, ( S32 slot, bool fwd ),,
|
|
"@brief Set the playback direction of an animation thread.\n\n"
|
|
|
|
"@param slot thread slot to modify\n"
|
|
"@param fwd true to play the animation forwards, false to play backwards\n"
|
|
"@return true if successful, false if failed\n\n"
|
|
|
|
"@see playThread()\n" )
|
|
{
|
|
if (slot >= 0 && slot < ShapeBase::MaxScriptThreads) {
|
|
if (object->setThreadDir(slot,fwd))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, setThreadTimeScale, bool, ( S32 slot, F32 scale ),,
|
|
"@brief Set the playback time scale of an animation thread.\n\n"
|
|
|
|
"@param slot thread slot to modify\n"
|
|
"@param scale new thread time scale (1=normal speed, 0.5=half speed etc)\n"
|
|
"@return true if successful, false if failed\n\n"
|
|
|
|
"@see playThread\n" )
|
|
{
|
|
if (slot >= 0 && slot < ShapeBase::MaxScriptThreads) {
|
|
if (object->setThreadTimeScale(slot,scale))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, setThreadPosition, bool, ( S32 slot, F32 pos ),,
|
|
"@brief Set the position within an animation thread.\n\n"
|
|
|
|
"@param slot thread slot to modify\n"
|
|
"@param pos position within thread\n"
|
|
"@return true if successful, false if failed\n\n"
|
|
|
|
"@see playThread\n" )
|
|
{
|
|
if (slot >= 0 && slot < ShapeBase::MaxScriptThreads) {
|
|
if (object->setThreadPosition(slot,pos))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, stopThread, bool, ( S32 slot ),,
|
|
"@brief Stop an animation thread.\n\n"
|
|
|
|
"If restarted using playThread, the animation "
|
|
"will start from the beginning again.\n"
|
|
"@param slot thread slot to stop\n"
|
|
"@return true if successful, false if failed\n\n"
|
|
|
|
"@see playThread\n" )
|
|
{
|
|
if (slot >= 0 && slot < ShapeBase::MaxScriptThreads) {
|
|
if (object->stopThread(slot))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, destroyThread, bool, ( S32 slot ),,
|
|
"@brief Destroy an animation thread, which prevents it from playing.\n\n"
|
|
|
|
"@param slot thread slot to destroy\n"
|
|
"@return true if successful, false if failed\n\n"
|
|
|
|
"@see playThread\n" )
|
|
{
|
|
if (slot >= 0 && slot < ShapeBase::MaxScriptThreads) {
|
|
if (object->destroyThread(slot))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, pauseThread, bool, ( S32 slot ),,
|
|
"@brief Pause an animation thread.\n\n"
|
|
|
|
"If restarted using playThread, the animation "
|
|
"will resume from the paused position.\n"
|
|
"@param slot thread slot to stop\n"
|
|
"@return true if successful, false if failed\n\n"
|
|
|
|
"@see playThread\n" )
|
|
{
|
|
if (slot >= 0 && slot < ShapeBase::MaxScriptThreads) {
|
|
if (object->pauseThread(slot))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
DefineEngineMethod( ShapeBase, mountImage, bool,
|
|
( ShapeBaseImageData* image, S32 slot, bool loaded, const char* skinTag ), ( true, "" ),
|
|
"@brief Mount a new Image.\n\n"
|
|
|
|
"@param image the Image to mount\n"
|
|
"@param slot Image slot to mount into (valid range is 0 - 3)\n"
|
|
"@param loaded initial loaded state for the Image\n"
|
|
"@param skinTag tagged string to reskin the mounted Image\n"
|
|
"@return true if successful, false if failed\n\n"
|
|
|
|
"@tsexample\n"
|
|
"%player.mountImage( PistolImage, 1 );\n"
|
|
"%player.mountImage( CrossbowImage, 0, false );\n"
|
|
"%player.mountImage( RocketLauncherImage, 0, true, 'blue' );\n"
|
|
"@endtsexample\n"
|
|
|
|
"@see unmountImage()\n"
|
|
"@see getMountedImage()\n"
|
|
"@see getPendingImage()\n"
|
|
"@see isImageMounted()\n")
|
|
{
|
|
if (image && slot >= 0 && slot < ShapeBase::MaxMountedImages) {
|
|
|
|
NetStringHandle team;
|
|
if (skinTag[0] == StringTagPrefixByte)
|
|
team = NetStringHandle(U32(dAtoi(skinTag+1)));
|
|
|
|
return object->mountImage( image, slot, loaded, team );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, unmountImage, bool, ( S32 slot ),,
|
|
"@brief Unmount the mounted Image in the specified slot.\n\n"
|
|
|
|
"@param slot Image slot to unmount\n"
|
|
"@return true if successful, false if failed\n\n"
|
|
|
|
"@see mountImage()\n")
|
|
{
|
|
if (slot >= 0 && slot < ShapeBase::MaxMountedImages)
|
|
return object->unmountImage(slot);
|
|
return false;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getMountedImage, S32, ( S32 slot ),,
|
|
"@brief Get the Image mounted in the specified slot.\n\n"
|
|
|
|
"@param slot Image slot to query\n"
|
|
"@return ID of the ShapeBaseImageData datablock mounted in the slot, or 0 "
|
|
"if no Image is mounted there.\n\n" )
|
|
{
|
|
if (slot >= 0 && slot < ShapeBase::MaxMountedImages)
|
|
if (ShapeBaseImageData* data = object->getMountedImage(slot))
|
|
return data->getId();
|
|
return 0;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getPendingImage, S32, ( S32 slot ),,
|
|
"@brief Get the Image that will be mounted next in the specified slot.\n\n"
|
|
|
|
"Calling mountImage when an Image is already mounted does one of two things: "
|
|
"<ol><li>Mount the new Image immediately, the old Image is discarded and "
|
|
"whatever state it was in is ignored.</li>"
|
|
"<li>If the current Image state does not allow Image changes, the new "
|
|
"Image is marked as pending, and will not be mounted until the current "
|
|
"state completes. eg. if the user changes weapons, you may wish to ensure "
|
|
"that the current weapon firing state plays to completion first.</li></ol>\n"
|
|
"This command retrieves the ID of the pending Image (2nd case above).\n"
|
|
|
|
"@param slot Image slot to query\n"
|
|
"@return ID of the pending ShapeBaseImageData datablock, or 0 if none.\n\n" )
|
|
{
|
|
if (slot >= 0 && slot < ShapeBase::MaxMountedImages)
|
|
if (ShapeBaseImageData* data = object->getPendingImage(slot))
|
|
return data->getId();
|
|
return 0;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, isImageFiring, bool, ( S32 slot ),,
|
|
"@brief Check if the current Image state is firing.\n\n"
|
|
|
|
"@param slot Image slot to query\n"
|
|
"@return true if the current Image state in this slot has the 'stateFire' flag set.\n" )
|
|
{
|
|
if (slot >= 0 && slot < ShapeBase::MaxMountedImages)
|
|
return object->isImageFiring(slot);
|
|
return false;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, isImageMounted, bool, ( ShapeBaseImageData* image ),,
|
|
"@brief Check if the given datablock is mounted to any slot on this object.\n\n"
|
|
|
|
"@param image ShapeBaseImageData datablock to query\n"
|
|
"@return true if the Image is mounted to any slot, false otherwise.\n\n" )
|
|
{
|
|
return (image && object->isImageMounted(image));
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getMountSlot, S32, ( ShapeBaseImageData* image ),,
|
|
"@brief Get the first slot the given datablock is mounted to on this object.\n\n"
|
|
|
|
"@param image ShapeBaseImageData datablock to query\n"
|
|
"@return index of the first slot the Image is mounted in, or -1 if the Image "
|
|
"is not mounted in any slot on this object.\n\n" )
|
|
|
|
{
|
|
return image ? object->getMountSlot(image) : -1;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getImageSkinTag, S32, ( S32 slot ),,
|
|
"@brief Get the skin tag ID for the Image mounted in the specified slot.\n\n"
|
|
|
|
"@param slot Image slot to query\n"
|
|
"@return the skinTag value passed to mountImage when the image was "
|
|
"mounted\n\n" )
|
|
{
|
|
if (slot >= 0 && slot < ShapeBase::MaxMountedImages)
|
|
return object->getImageSkinTag(slot).getIndex();
|
|
return -1;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getImageState, const char*, ( S32 slot ),,
|
|
"@brief Get the name of the current state of the Image in the specified slot.\n\n"
|
|
|
|
"@param slot Image slot to query\n"
|
|
"@return name of the current Image state, or \"Error\" if slot is invalid\n\n" )
|
|
{
|
|
if (slot >= 0 && slot < ShapeBase::MaxMountedImages)
|
|
return object->getImageState(slot);
|
|
return "Error";
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, hasImageState, bool, ( S32 slot, const char* state ),,
|
|
"@brief Check if the given state exists on the mounted Image.\n\n"
|
|
|
|
"@param slot Image slot to query\n"
|
|
"@param state Image state to check for\n"
|
|
"@return true if the Image has the requested state defined.\n\n" )
|
|
{
|
|
if (slot >= 0 && slot < ShapeBase::MaxMountedImages)
|
|
return object->hasImageState(slot, state);
|
|
return false;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getImageTrigger, bool, ( S32 slot ),,
|
|
"@brief Get the trigger state of the Image mounted in the specified slot.\n\n"
|
|
|
|
"@param slot Image slot to query\n"
|
|
"@return the Image's current trigger state\n\n" )
|
|
{
|
|
if (slot >= 0 && slot < ShapeBase::MaxMountedImages)
|
|
return object->getImageTriggerState(slot);
|
|
return false;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, setImageTrigger, bool, ( S32 slot, bool state ),,
|
|
"@brief Set the trigger state of the Image mounted in the specified slot.\n\n"
|
|
|
|
"@param slot Image slot to modify\n"
|
|
"@param state new trigger state for the Image\n"
|
|
"@return the Image's new trigger state\n\n" )
|
|
{
|
|
if (slot >= 0 && slot < ShapeBase::MaxMountedImages) {
|
|
object->setImageTriggerState(slot,state);
|
|
return object->getImageTriggerState(slot);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getImageGenericTrigger, bool, ( S32 slot, S32 trigger ),,
|
|
"@brief Get the generic trigger state of the Image mounted in the specified slot.\n\n"
|
|
|
|
"@param slot Image slot to query\n"
|
|
"@param trigger Generic trigger number\n"
|
|
"@return the Image's current generic trigger state\n\n" )
|
|
{
|
|
if (slot >= 0 && slot < ShapeBase::MaxMountedImages && trigger >= 0 && trigger < ShapeBaseImageData::MaxGenericTriggers)
|
|
return object->getImageGenericTriggerState(slot, trigger);
|
|
return false;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, setImageGenericTrigger, S32, ( S32 slot, S32 trigger, bool state ),,
|
|
"@brief Set the generic trigger state of the Image mounted in the specified slot.\n\n"
|
|
|
|
"@param slot Image slot to modify\n"
|
|
"@param trigger Generic trigger number\n"
|
|
"@param state new generic trigger state for the Image\n"
|
|
"@return the Image's new generic trigger state or -1 if there was a problem.\n\n" )
|
|
{
|
|
if (slot >= 0 && slot < ShapeBase::MaxMountedImages && trigger >= 0 && trigger < ShapeBaseImageData::MaxGenericTriggers) {
|
|
object->setImageGenericTriggerState(slot,trigger,state);
|
|
return object->getImageGenericTriggerState(slot,trigger);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getImageAltTrigger, bool, ( S32 slot ),,
|
|
"@brief Get the alt trigger state of the Image mounted in the specified slot.\n\n"
|
|
|
|
"@param slot Image slot to query\n"
|
|
"@return the Image's current alt trigger state\n\n" )
|
|
{
|
|
if (slot >= 0 && slot < ShapeBase::MaxMountedImages)
|
|
return object->getImageAltTriggerState(slot);
|
|
return false;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, setImageAltTrigger, bool, ( S32 slot, bool state ),,
|
|
"@brief Set the alt trigger state of the Image mounted in the specified slot.\n\n"
|
|
|
|
"@param slot Image slot to modify\n"
|
|
"@param state new alt trigger state for the Image\n"
|
|
"@return the Image's new alt trigger state\n\n" )
|
|
{
|
|
if (slot >= 0 && slot < ShapeBase::MaxMountedImages) {
|
|
object->setImageAltTriggerState(slot,state);
|
|
return object->getImageAltTriggerState(slot);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getImageAmmo, bool, ( S32 slot ),,
|
|
"@brief Get the ammo state of the Image mounted in the specified slot.\n\n"
|
|
|
|
"@param slot Image slot to query\n"
|
|
"@return the Image's current ammo state\n\n" )
|
|
{
|
|
if (slot >= 0 && slot < ShapeBase::MaxMountedImages)
|
|
return object->getImageAmmoState(slot);
|
|
return false;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, setImageAmmo, bool, ( S32 slot, bool state ),,
|
|
"@brief Set the ammo state of the Image mounted in the specified slot.\n\n"
|
|
|
|
"@param slot Image slot to modify\n"
|
|
"@param state new ammo state for the Image\n"
|
|
"@return the Image's new ammo state\n\n" )
|
|
{
|
|
if (slot >= 0 && slot < ShapeBase::MaxMountedImages) {
|
|
object->setImageAmmoState(slot,state);
|
|
return state;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getImageLoaded, bool, ( S32 slot ),,
|
|
"@brief Get the loaded state of the Image mounted in the specified slot.\n\n"
|
|
|
|
"@param slot Image slot to query\n"
|
|
"@return the Image's current loaded state\n\n" )
|
|
{
|
|
if (slot >= 0 && slot < ShapeBase::MaxMountedImages)
|
|
return object->getImageLoadedState(slot);
|
|
return false;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, setImageLoaded, bool, ( S32 slot, bool state ),,
|
|
"@brief Set the loaded state of the Image mounted in the specified slot.\n\n"
|
|
|
|
"@param slot Image slot to modify\n"
|
|
"@param state new loaded state for the Image\n"
|
|
"@return the Image's new loaded state\n\n" )
|
|
{
|
|
if (slot >= 0 && slot < ShapeBase::MaxMountedImages) {
|
|
object->setImageLoadedState(slot, state);
|
|
return state;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getImageTarget, bool, ( S32 slot ),,
|
|
"@brief Get the target state of the Image mounted in the specified slot.\n\n"
|
|
|
|
"@param slot Image slot to query\n"
|
|
"@return the Image's current target state\n\n" )
|
|
{
|
|
if (slot >= 0 && slot < ShapeBase::MaxMountedImages)
|
|
return object->getImageTargetState(slot);
|
|
return false;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, setImageTarget, bool, ( S32 slot, bool state ),,
|
|
"@brief Set the target state of the Image mounted in the specified slot.\n\n"
|
|
|
|
"@param slot Image slot to modify\n"
|
|
"@param state new target state for the Image\n"
|
|
"@return the Image's new target state\n\n" )
|
|
{
|
|
if (slot >= 0 && slot < ShapeBase::MaxMountedImages) {
|
|
object->setImageTargetState(slot,state);
|
|
return state;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getImageScriptAnimPrefix, const char*, ( S32 slot ),,
|
|
"@brief Get the script animation prefix of the Image mounted in the specified slot.\n\n"
|
|
|
|
"@param slot Image slot to query\n"
|
|
"@return the Image's current script animation prefix\n\n" )
|
|
{
|
|
if (slot >= 0 && slot < ShapeBase::MaxMountedImages)
|
|
return object->getImageScriptAnimPrefix(slot).getString();
|
|
return "";
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, setImageScriptAnimPrefix, void, ( S32 slot, const char* prefix ),,
|
|
"@brief Set the script animation prefix for the Image mounted in the specified slot.\n\n"
|
|
"This is used to further modify the prefix used when deciding which animation sequence to "
|
|
"play while this image is mounted.\n"
|
|
|
|
"@param slot Image slot to modify\n"
|
|
"@param prefix The prefix applied to the image\n" )
|
|
{
|
|
if (slot >= 0 && slot < ShapeBase::MaxMountedImages) {
|
|
|
|
NetStringHandle prefixHandle;
|
|
if (prefix[0] == StringTagPrefixByte)
|
|
prefixHandle = NetStringHandle(U32(dAtoi(prefix+1)));
|
|
|
|
object->setImageScriptAnimPrefix(slot, prefixHandle);
|
|
}
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getMuzzleVector, VectorF, ( S32 slot ),,
|
|
"@brief Get the muzzle vector of the Image mounted in the specified slot.\n\n"
|
|
|
|
"If the Image shape contains a node called 'muzzlePoint', then the muzzle "
|
|
"vector is the forward direction vector of that node's transform in world "
|
|
"space. If no such node is specified, the slot's mount node is used "
|
|
"instead.\n\n"
|
|
|
|
"If the correctMuzzleVector flag (correctMuzzleVectorTP in 3rd person) "
|
|
"is set in the Image, the muzzle vector is computed to point at whatever "
|
|
"object is right in front of the object's 'eye' node.\n"
|
|
|
|
"@param slot Image slot to query\n"
|
|
"@return the muzzle vector, or \"0 1 0\" if the slot is invalid\n\n" )
|
|
{
|
|
VectorF vec(0, 1, 0);
|
|
if (slot >= 0 && slot < ShapeBase::MaxMountedImages)
|
|
object->getMuzzleVector(slot, &vec);
|
|
|
|
return vec;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getMuzzlePoint, Point3F, ( S32 slot ),,
|
|
"@brief Get the muzzle position of the Image mounted in the specified slot.\n\n"
|
|
|
|
"If the Image shape contains a node called 'muzzlePoint', then the muzzle "
|
|
"position is the position of that node in world space. If no such node "
|
|
"is specified, the slot's mount node is used instead.\n"
|
|
|
|
"@param slot Image slot to query\n"
|
|
"@return the muzzle position, or \"0 0 0\" if the slot is invalid\n\n" )
|
|
{
|
|
Point3F pos(0, 0, 0);
|
|
if (slot >= 0 && slot < ShapeBase::MaxMountedImages)
|
|
object->getMuzzlePoint(slot, &pos);
|
|
|
|
return pos;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getSlotTransform, TransformF, ( S32 slot ),,
|
|
"@brief Get the world transform of the specified mount slot.\n\n"
|
|
|
|
"@param slot Image slot to query\n"
|
|
"@return the mount transform\n\n" )
|
|
{
|
|
MatrixF xf(true);
|
|
if (slot >= 0 && slot < ShapeBase::MaxMountedImages)
|
|
object->getMountTransform( slot, MatrixF::Identity, &xf );
|
|
|
|
return xf;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( ShapeBase, getAIRepairPoint, Point3F, (),,
|
|
"@brief Get the position at which the AI should stand to repair things.\n\n"
|
|
|
|
"If the shape defines a node called \"AIRepairNode\", this method will "
|
|
"return the current world position of that node, otherwise \"0 0 0\".\n"
|
|
"@return the AI repair position\n\n" )
|
|
{
|
|
return object->getAIRepairPoint();
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getVelocity, VectorF, (),,
|
|
"@brief Get the object's current velocity.\n\n"
|
|
|
|
"@return the current velocity\n\n" )
|
|
{
|
|
return object->getVelocity();
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, setVelocity, bool, ( Point3F vel ),,
|
|
"@brief Set the object's velocity.\n\n"
|
|
|
|
"@param vel new velocity for the object\n"
|
|
"@return true\n\n" )
|
|
{
|
|
object->setVelocity( vel );
|
|
return true;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, applyImpulse, bool, ( Point3F pos, Point3F vec ),,
|
|
"@brief Apply an impulse to the object.\n\n"
|
|
|
|
"@param pos world position of the impulse\n"
|
|
"@param vec impulse momentum (velocity * mass)\n"
|
|
"@return true\n\n" )
|
|
{
|
|
object->applyImpulse( pos, vec );
|
|
return true;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getEyeVector, VectorF, (),,
|
|
"@brief Get the forward direction of the 'eye' for this object.\n\n"
|
|
|
|
"If the object model has a node called 'eye', this method will return that "
|
|
"node's current forward direction vector, otherwise it will return the "
|
|
"object's current forward direction vector.\n"
|
|
|
|
"@return the eye vector for this object\n"
|
|
|
|
"@see getEyePoint\n"
|
|
"@see getEyeTransform\n" )
|
|
{
|
|
MatrixF mat;
|
|
object->getEyeTransform(&mat);
|
|
return mat.getForwardVector();
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getEyePoint, Point3F, (),,
|
|
"@brief Get the position of the 'eye' for this object.\n\n"
|
|
|
|
"If the object model has a node called 'eye', this method will return that "
|
|
"node's current world position, otherwise it will return the object's current "
|
|
"world position.\n"
|
|
|
|
"@return the eye position for this object\n"
|
|
|
|
"@see getEyeVector\n"
|
|
"@see getEyeTransform\n" )
|
|
{
|
|
MatrixF mat;
|
|
object->getEyeTransform(&mat);
|
|
return mat.getPosition();
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getEyeTransform, TransformF, (),,
|
|
"@brief Get the 'eye' transform for this object.\n\n"
|
|
|
|
"If the object model has a node called 'eye', this method will return that "
|
|
"node's current transform, otherwise it will return the object's current "
|
|
"transform.\n"
|
|
|
|
"@return the eye transform for this object\n"
|
|
|
|
"@see getEyeVector\n"
|
|
"@see getEyePoint\n" )
|
|
{
|
|
MatrixF mat;
|
|
object->getEyeTransform(&mat);
|
|
return mat;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getLookAtPoint, const char*, ( F32 distance, U32 typeMask ), ( 2000, 0xFFFFFFFF ),
|
|
"@brief Get the world position this object is looking at.\n\n"
|
|
|
|
"Casts a ray from the eye and returns information about what the ray hits.\n"
|
|
|
|
"@param distance maximum distance of the raycast\n"
|
|
"@param typeMask typeMask of objects to include for raycast collision testing\n"
|
|
"@return look-at information as \"Object HitX HitY HitZ [Material]\" or empty string for no hit\n\n"
|
|
|
|
"@tsexample\n"
|
|
"%lookat = %obj.getLookAtPoint();\n"
|
|
"echo( \"Looking at: \" @ getWords( %lookat, 1, 3 ) );\n"
|
|
"@endtsexample\n" )
|
|
{
|
|
MatrixF mat;
|
|
object->getEyeTransform( &mat );
|
|
|
|
// Get eye vector.
|
|
|
|
VectorF eyeVector;
|
|
mat.getColumn( 1, &eyeVector );
|
|
|
|
// Get eye position.
|
|
|
|
VectorF eyePos;
|
|
mat.getColumn( 3, &eyePos );
|
|
|
|
// Make sure the eye vector covers the distance.
|
|
|
|
eyeVector *= distance;
|
|
|
|
// Do a container search.
|
|
|
|
VectorF start = eyePos;
|
|
VectorF end = eyePos + eyeVector;
|
|
|
|
RayInfo ri;
|
|
if( !gServerContainer.castRay( start, end, typeMask, &ri ) || !ri.object )
|
|
return ""; // No hit.
|
|
|
|
// Gather hit info.
|
|
|
|
enum { BUFFER_SIZE = 256 };
|
|
char* buffer = Con::getReturnBuffer( BUFFER_SIZE );
|
|
if( ri.material )
|
|
dSprintf( buffer, BUFFER_SIZE, "%u %f %f %f %u",
|
|
ri.object->getId(),
|
|
ri.point.x,
|
|
ri.point.y,
|
|
ri.point.z,
|
|
ri.material->getMaterial()->getId() );
|
|
else
|
|
dSprintf( buffer, BUFFER_SIZE, "%u %f %f %f",
|
|
ri.object->getId(),
|
|
ri.point.x,
|
|
ri.point.y,
|
|
ri.point.z );
|
|
|
|
return buffer;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, setEnergyLevel, void, ( F32 level ),,
|
|
"@brief Set this object's current energy level.\n\n"
|
|
|
|
"@param level new energy level\n"
|
|
|
|
"@see getEnergyLevel()\n"
|
|
"@see getEnergyPercent()\n")
|
|
{
|
|
object->setEnergyLevel( level );
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getEnergyLevel, F32, (),,
|
|
"@brief Get the object's current energy level.\n\n"
|
|
|
|
"@return energy level\n"
|
|
|
|
"@see setEnergyLevel()\n")
|
|
{
|
|
return object->getEnergyLevel();
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getEnergyPercent, F32, (),,
|
|
"@brief Get the object's current energy level as a percentage of maxEnergy.\n\n"
|
|
"@return energyLevel / datablock.maxEnergy\n"
|
|
|
|
"@see setEnergyLevel()\n")
|
|
{
|
|
return object->getEnergyValue();
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, setDamageLevel, void, ( F32 level ),,
|
|
"@brief Set the object's current damage level.\n\n"
|
|
|
|
"@param level new damage level\n"
|
|
|
|
"@see getDamageLevel()\n"
|
|
"@see getDamagePercent()\n")
|
|
{
|
|
object->setDamageLevel( level );
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getDamageLevel, F32, (),,
|
|
"@brief Get the object's current damage level.\n\n"
|
|
|
|
"@return damage level\n"
|
|
|
|
"@see setDamageLevel()\n")
|
|
{
|
|
return object->getDamageLevel();
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getDamagePercent, F32, (),,
|
|
"@brief Get the object's current damage level as a percentage of maxDamage.\n\n"
|
|
|
|
"@return damageLevel / datablock.maxDamage\n"
|
|
|
|
"@see setDamageLevel()\n")
|
|
{
|
|
return object->getDamageValue();
|
|
}
|
|
|
|
DefineEngineMethod(ShapeBase, getMaxDamage, F32, (),,
|
|
"Get the object's maxDamage level.\n"
|
|
"@return datablock.maxDamage\n")
|
|
{
|
|
return object->getMaxDamage();
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, setDamageState, bool, ( const char* state ),,
|
|
"@brief Set the object's damage state.\n\n"
|
|
|
|
"@param state should be one of \"Enabled\", \"Disabled\", \"Destroyed\"\n"
|
|
"@return true if successful, false if failed\n"
|
|
|
|
"@see getDamageState()\n")
|
|
{
|
|
return object->setDamageState( state );
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getDamageState, const char*, (),,
|
|
"@brief Get the object's damage state.\n\n"
|
|
|
|
"@return the damage state; one of \"Enabled\", \"Disabled\", \"Destroyed\"\n"
|
|
|
|
"@see setDamageState()\n")
|
|
{
|
|
return object->getDamageStateName();
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, isDestroyed, bool, (),,
|
|
"@brief Check if the object is in the Destroyed damage state.\n\n"
|
|
|
|
"@return true if damage state is \"Destroyed\", false if not\n"
|
|
|
|
"@see isDisabled()\n"
|
|
"@see isEnabled()\n")
|
|
{
|
|
return object->isDestroyed();
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, isDisabled, bool, (),,
|
|
"@brief Check if the object is in the Disabled or Destroyed damage state.\n\n"
|
|
|
|
"@return true if damage state is not \"Enabled\", false if it is\n"
|
|
|
|
"@see isDestroyed()\n"
|
|
"@see isEnabled()\n")
|
|
{
|
|
return object->getDamageState() != ShapeBase::Enabled;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, isEnabled, bool, (),,
|
|
"@brief Check if the object is in the Enabled damage state.\n\n"
|
|
|
|
"@return true if damage state is \"Enabled\", false if not\n"
|
|
|
|
"@see isDestroyed()\n"
|
|
"@see isDisabled()\n")
|
|
{
|
|
return object->getDamageState() == ShapeBase::Enabled;
|
|
}
|
|
|
|
DefineEngineMethod(ShapeBase, blowUp, void, (),, "@brief Explodes an object into pieces.")
|
|
{
|
|
object->blowUp();
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, applyDamage, void, ( F32 amount ),,
|
|
"@brief Increment the current damage level by the specified amount.\n\n"
|
|
|
|
"@param amount value to add to current damage level\n" )
|
|
{
|
|
object->applyDamage( amount );
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, applyRepair, void, ( F32 amount ),,
|
|
"@brief Repair damage by the specified amount.\n\n"
|
|
|
|
"Note that the damage level is only reduced by repairRate per tick, so it may "
|
|
"take several ticks for the total repair to complete.\n"
|
|
|
|
"@param amount total repair value (subtracted from damage level over time)\n" )
|
|
{
|
|
object->applyRepair( amount );
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, setRepairRate, void, ( F32 rate ),,
|
|
"@brief Set amount to repair damage by each tick.\n\n"
|
|
|
|
"Note that this value is separate to the repairRate field in ShapeBaseData. "
|
|
"This value will be subtracted from the damage level each tick, whereas the "
|
|
"ShapeBaseData field limits how much of the applyRepair value is subtracted "
|
|
"each tick. Both repair types can be active at the same time.\n"
|
|
|
|
"@param rate value to subtract from damage level each tick (must be > 0)\n"
|
|
|
|
"@see getRepairRate()\n")
|
|
{
|
|
if(rate < 0)
|
|
rate = 0;
|
|
object->setRepairRate( rate );
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getRepairRate, F32, (),,
|
|
"@brief Get the per-tick repair amount.\n\n"
|
|
|
|
"@return the current value to be subtracted from damage level each tick\n"
|
|
|
|
"@see setRepairRate\n" )
|
|
{
|
|
return object->getRepairRate();
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, setRechargeRate, void, ( F32 rate ),,
|
|
"@brief Set the recharge rate.\n\n"
|
|
|
|
"The recharge rate is added to the object's current energy level each tick, "
|
|
"up to the maxEnergy level set in the ShapeBaseData datablock.\n"
|
|
|
|
"@param rate the recharge rate (per tick)\n"
|
|
|
|
"@see getRechargeRate()\n")
|
|
{
|
|
object->setRechargeRate( rate );
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getRechargeRate, F32, (),,
|
|
"@brief Get the current recharge rate.\n\n"
|
|
|
|
"@return the recharge rate (per tick)\n"
|
|
|
|
"@see setRechargeRate()\n")
|
|
{
|
|
return object->getRechargeRate();
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getControllingClient, S32, (),,
|
|
"@brief Get the client (if any) that controls this object.\n\n"
|
|
|
|
"The controlling client is the one that will send moves to us to act on.\n"
|
|
|
|
"@return the ID of the controlling GameConnection, or 0 if this object is not "
|
|
"controlled by any client.\n"
|
|
|
|
"@see GameConnection\n")
|
|
{
|
|
if (GameConnection* con = object->getControllingClient())
|
|
return con->getId();
|
|
return 0;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getControllingObject, S32, (),,
|
|
"@brief Get the object (if any) that controls this object.\n\n"
|
|
|
|
"@return the ID of the controlling ShapeBase object, or 0 if this object is "
|
|
"not controlled by another object.\n" )
|
|
{
|
|
if (ShapeBase* con = object->getControllingObject())
|
|
return con->getId();
|
|
return 0;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, canCloak, bool, (),,
|
|
"@brief Check if this object can cloak.\n\n"
|
|
"@return true\n"
|
|
|
|
"@note Not implemented as it always returns true.")
|
|
{
|
|
return true;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, setCloaked, void, ( bool cloak ),,
|
|
"@brief Set the cloaked state of this object.\n\n"
|
|
|
|
"When an object is cloaked it is not rendered.\n"
|
|
|
|
"@param cloak true to cloak the object, false to uncloak\n"
|
|
|
|
"@see isCloaked()\n")
|
|
{
|
|
if (object->isServerObject())
|
|
object->setCloakedState( cloak );
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, isCloaked, bool, (),,
|
|
"@brief Check if this object is cloaked.\n\n"
|
|
|
|
"@return true if cloaked, false if not\n"
|
|
|
|
"@see setCloaked()\n")
|
|
{
|
|
return object->getCloakedState();
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, setDamageFlash, void, ( F32 level ),,
|
|
"@brief Set the damage flash level.\n\n"
|
|
|
|
"Damage flash may be used as a postfx effect to flash the screen when the "
|
|
"client is damaged.\n"
|
|
|
|
"@note Relies on the flash postFx.\n"
|
|
|
|
"@param level flash level (0-1)\n"
|
|
|
|
"@see getDamageFlash()\n")
|
|
{
|
|
if (object->isServerObject())
|
|
object->setDamageFlash( level );
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getDamageFlash, F32, (),,
|
|
"@brief Get the damage flash level.\n\n"
|
|
|
|
"@return flash level\n"
|
|
|
|
"@see setDamageFlash\n" )
|
|
{
|
|
return object->getDamageFlash();
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, setWhiteOut, void, ( F32 level ),,
|
|
"@brief Set the white-out level.\n\n"
|
|
|
|
"White-out may be used as a postfx effect to brighten the screen in response "
|
|
"to a game event.\n"
|
|
|
|
"@note Relies on the flash postFx.\n"
|
|
|
|
"@param level flash level (0-1)\n"
|
|
|
|
"@see getWhiteOut()\n")
|
|
{
|
|
if (object->isServerObject())
|
|
object->setWhiteOut( level );
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getWhiteOut, F32, (),,
|
|
"@brief Get the white-out level.\n\n"
|
|
|
|
"@return white-out level\n"
|
|
|
|
"@see setWhiteOut\n" )
|
|
{
|
|
return object->getWhiteOut();
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getDefaultCameraFov, F32, (),,
|
|
"@brief Returns the default vertical field of view in degrees for this object if used as a camera.\n\n"
|
|
|
|
"@return Default FOV\n" )
|
|
{
|
|
if (object->isServerObject())
|
|
return object->getDefaultCameraFov();
|
|
return 0.0;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getCameraFov, F32, (),,
|
|
"@brief Returns the vertical field of view in degrees for this object if used as a camera.\n\n"
|
|
|
|
"@return current FOV as defined in ShapeBaseData::cameraDefaultFov\n" )
|
|
{
|
|
if (object->isServerObject())
|
|
return object->getCameraFov();
|
|
return 0.0;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, setCameraFov, void, ( F32 fov ),,
|
|
"@brief Set the vertical field of view in degrees for this object if used as a camera.\n\n"
|
|
|
|
"@param fov new FOV value\n" )
|
|
{
|
|
if (object->isServerObject())
|
|
object->setCameraFov( fov );
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, startFade, void, ( S32 time, S32 delay, bool fadeOut ),,
|
|
"@brief Fade the object in or out without removing it from the scene.\n\n"
|
|
|
|
"A faded out object is still in the scene and can still be collided with, "
|
|
"so if you want to disable collisions for this shape after it fades out "
|
|
"use setHidden to temporarily remove this shape from the scene.\n"
|
|
|
|
"@note Items have the ability to light their surroundings. When an Item with "
|
|
"an active light is fading out, the light it emits is correspondingly "
|
|
"reduced until it goes out. Likewise, when the item fades in, the light is "
|
|
"turned-up till it reaches it's normal brightntess.\n"
|
|
|
|
"@param time duration of the fade effect in ms\n"
|
|
"@param delay delay in ms before the fade effect begins\n"
|
|
"@param fadeOut true to fade-out to invisible, false to fade-in to full visibility\n" )
|
|
{
|
|
object->startFade( (F32)time / (F32)1000.0, delay / 1000.0, fadeOut );
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, setDamageVector, void, ( Point3F vec ),,
|
|
"@brief Set the damage direction vector.\n\n"
|
|
|
|
"Currently this is only used to initialise the explosion if this object "
|
|
"is blown up.\n"
|
|
|
|
"@param vec damage direction vector\n\n"
|
|
|
|
"@tsexample\n"
|
|
"%obj.setDamageVector( \"0 0 1\" );\n"
|
|
"@endtsexample\n" )
|
|
{
|
|
vec.normalize();
|
|
object->setDamageDir( vec );
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, setShapeName, void, ( const char* name ),,
|
|
"@brief Set the name of this shape.\n\n"
|
|
|
|
"@note This is the name of the shape object that is sent to the client, "
|
|
"not the DTS or DAE model filename.\n"
|
|
|
|
"@param name new name for the shape\n\n"
|
|
|
|
"@see getShapeName()\n")
|
|
{
|
|
object->setShapeName( name );
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getShapeName, const char*, (),,
|
|
"@brief Get the name of the shape.\n\n"
|
|
|
|
"@note This is the name of the shape object that is sent to the client, "
|
|
"not the DTS or DAE model filename.\n"
|
|
|
|
"@return the name of the shape\n\n"
|
|
|
|
"@see setShapeName()\n")
|
|
{
|
|
return object->getShapeName();
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, setSkinName, void, ( const char* name ),,
|
|
"@brief Apply a new skin to this shape.\n\n"
|
|
|
|
"'Skinning' the shape effectively renames the material targets, allowing "
|
|
"different materials to be used on different instances of the same model.\n\n"
|
|
|
|
"@param name name of the skin to apply\n\n"
|
|
|
|
"@see skin\n"
|
|
"@see getSkinName()\n")
|
|
{
|
|
object->setSkinName( name );
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getSkinName, const char*, (),,
|
|
"@brief Get the name of the skin applied to this shape.\n\n"
|
|
|
|
"@return the name of the skin\n\n"
|
|
|
|
"@see skin\n"
|
|
"@see setSkinName()\n")
|
|
{
|
|
return object->getSkinName();
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
void ShapeBase::consoleInit()
|
|
{
|
|
Con::addVariable("SB::DFDec", TypeF32, &sDamageFlashDec, "Speed to reduce the damage flash effect per tick.\n\n"
|
|
"@see ShapeBase::setDamageFlash()\n"
|
|
"@see ShapeBase::getDamageFlash()\n"
|
|
"@note Relies on the flash postFx.\n"
|
|
"@ingroup gameObjects\n");
|
|
Con::addVariable("SB::WODec", TypeF32, &sWhiteoutDec, "Speed to reduce the whiteout effect per tick.\n\n"
|
|
"@see ShapeBase::setWhiteOut()\n"
|
|
"@see ShapeBase::getWhiteOut"
|
|
"@note Relies on the flash postFx.\n"
|
|
"@ingroup gameObjects\n");
|
|
Con::addVariable("SB::FullCorrectionDistance", TypeF32, &sFullCorrectionDistance,
|
|
"@brief Distance at which a weapon's muzzle vector is fully corrected to match where the player is looking.\n\n"
|
|
"When a weapon image has correctMuzzleVector set and the Player is in 1st person, the muzzle vector from the "
|
|
"weapon is modified to match where the player is looking. Beyond the FullCorrectionDistance the muzzle vector "
|
|
"is always corrected. Between FullCorrectionDistance and the player, the weapon's muzzle vector is adjusted so that "
|
|
"the closer the aim point is to the player, the closer the muzzle vector is to the true (non-corrected) one.\n"
|
|
"@ingroup gameObjects\n");
|
|
Con::addVariable("SB::CloakSpeed", TypeF32, &sCloakSpeed,
|
|
"@brief Time to cloak, in seconds.\n\n"
|
|
"@ingroup gameObjects\n");
|
|
}
|
|
|
|
void ShapeBase::_updateHiddenMeshes()
|
|
{
|
|
if ( !mShapeInstance )
|
|
return;
|
|
|
|
// This may happen at some point in the future... lets
|
|
// detect it so that it can be fixed at that time.
|
|
AssertFatal( mMeshHidden.getSize() == mShapeInstance->mMeshObjects.size(),
|
|
"ShapeBase::_updateMeshVisibility() - Mesh visibility size mismatch!" );
|
|
|
|
for ( U32 i = 0; i < mMeshHidden.getSize(); i++ )
|
|
setMeshHidden( i, mMeshHidden.test( i ) );
|
|
}
|
|
|
|
void ShapeBase::setMeshHidden( const char *meshName, bool forceHidden )
|
|
{
|
|
setMeshHidden( mDataBlock->mShape->findObject( meshName ), forceHidden );
|
|
}
|
|
|
|
void ShapeBase::setMeshHidden( S32 meshIndex, bool forceHidden )
|
|
{
|
|
if ( meshIndex == -1 || meshIndex >= mMeshHidden.getSize() )
|
|
return;
|
|
|
|
if ( forceHidden )
|
|
mMeshHidden.set( meshIndex );
|
|
else
|
|
mMeshHidden.clear( meshIndex );
|
|
|
|
if ( mShapeInstance )
|
|
mShapeInstance->setMeshForceHidden( meshIndex, forceHidden );
|
|
|
|
setMaskBits( MeshHiddenMask );
|
|
}
|
|
|
|
void ShapeBase::setAllMeshesHidden( bool forceHidden )
|
|
{
|
|
if ( forceHidden )
|
|
mMeshHidden.set();
|
|
else
|
|
mMeshHidden.clear();
|
|
|
|
if ( mShapeInstance )
|
|
{
|
|
for ( U32 i = 0; i < mMeshHidden.getSize(); i++ )
|
|
mShapeInstance->setMeshForceHidden( i, forceHidden );
|
|
}
|
|
|
|
setMaskBits( MeshHiddenMask );
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, setAllMeshesHidden, void, ( bool hide ),,
|
|
"@brief Set the hidden state on all the shape meshes.\n\n"
|
|
|
|
"This allows you to hide all meshes in the shape, for example, and then only "
|
|
"enable a few.\n"
|
|
|
|
"@param hide new hidden state for all meshes\n\n" )
|
|
{
|
|
object->setAllMeshesHidden( hide );
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, setMeshHidden, void, ( const char* name, bool hide ),,
|
|
"@brief Set the hidden state on the named shape mesh.\n\n"
|
|
|
|
"@param name name of the mesh to hide/show\n"
|
|
"@param hide new hidden state for the mesh\n\n" )
|
|
{
|
|
object->setMeshHidden( name, hide );
|
|
}
|
|
|
|
// Some development-handy functions
|
|
#ifndef TORQUE_SHIPPING
|
|
|
|
void ShapeBase::dumpMeshVisibility()
|
|
{
|
|
if ( !mShapeInstance )
|
|
return;
|
|
|
|
const Vector<TSShapeInstance::MeshObjectInstance> &meshes = mShapeInstance->mMeshObjects;
|
|
|
|
for ( U32 i = 0; i < meshes.size(); i++)
|
|
{
|
|
const TSShapeInstance::MeshObjectInstance &mesh = meshes[i];
|
|
|
|
const String &meshName = mDataBlock->mShape->getMeshName( i );
|
|
|
|
Con::printf( "%d - %s - forceHidden = %s, visibility = %f",
|
|
i,
|
|
meshName.c_str(),
|
|
mesh.forceHidden ? "true" : "false",
|
|
mesh.visible );
|
|
}
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, dumpMeshVisibility, void, (),,
|
|
"@brief Print a list of visible and hidden meshes in the shape to the console "
|
|
"for debugging purposes.\n\n"
|
|
"@note Only in a SHIPPING build.\n")
|
|
{
|
|
object->dumpMeshVisibility();
|
|
}
|
|
|
|
#endif // #ifndef TORQUE_SHIPPING
|
|
|
|
//------------------------------------------------------------------------
|
|
//These functions are duplicated in tsStatic and shapeBase.
|
|
//They each function a little differently; but achieve the same purpose of gathering
|
|
//target names/counts without polluting simObject.
|
|
|
|
DefineEngineMethod( ShapeBase, getTargetName, const char*, ( S32 index ),,
|
|
"@brief Get the name of the indexed shape material.\n\n"
|
|
|
|
"@param index index of the material to get (valid range is 0 - getTargetCount()-1).\n"
|
|
"@return the name of the indexed material.\n\n"
|
|
|
|
"@see getTargetCount()\n")
|
|
{
|
|
ShapeBase *obj = dynamic_cast< ShapeBase* > ( object );
|
|
if(obj)
|
|
{
|
|
// Try to use the client object (so we get the reskinned targets in the Material Editor)
|
|
if ((ShapeBase*)obj->getClientObject())
|
|
obj = (ShapeBase*)obj->getClientObject();
|
|
|
|
return obj->getShapeInstance()->getTargetName(index);
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getTargetCount, S32, (),,
|
|
"@brief Get the number of materials in the shape.\n\n"
|
|
|
|
"@return the number of materials in the shape.\n\n"
|
|
|
|
"@see getTargetName()\n")
|
|
{
|
|
ShapeBase *obj = dynamic_cast< ShapeBase* > ( object );
|
|
if(obj)
|
|
{
|
|
// Try to use the client object (so we get the reskinned targets in the Material Editor)
|
|
if ((ShapeBase*)obj->getClientObject())
|
|
obj = (ShapeBase*)obj->getClientObject();
|
|
|
|
if (obj->getShapeInstance() != NULL)
|
|
return obj->getShapeInstance()->getTargetCount();
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, changeMaterial, void, ( const char* mapTo, Material* oldMat, Material* newMat ),,
|
|
"@brief Change one of the materials on the shape.\n\n"
|
|
|
|
"This method changes materials per mapTo with others. The material that "
|
|
"is being replaced is mapped to unmapped_mat as a part of this transition.\n"
|
|
|
|
"@note Warning, right now this only sort of works. It doesn't do a live "
|
|
"update like it should.\n"
|
|
|
|
"@param mapTo the name of the material target to remap (from getTargetName)\n"
|
|
"@param oldMat the old Material that was mapped \n"
|
|
"@param newMat the new Material to map\n\n"
|
|
|
|
"@tsexample\n"
|
|
"// remap the first material in the shape\n"
|
|
"%mapTo = %obj.getTargetName( 0 );\n"
|
|
"%obj.changeMaterial( %mapTo, 0, MyMaterial );\n"
|
|
"@endtsexample\n" )
|
|
{
|
|
// if no valid new material, theres no reason for doing this
|
|
if( !newMat )
|
|
{
|
|
Con::errorf("ShapeBase::changeMaterial failed: New material does not exist!");
|
|
return;
|
|
}
|
|
|
|
// initilize server/client versions
|
|
ShapeBase *serverObj = object;
|
|
ShapeBase *clientObj = dynamic_cast< ShapeBase* > ( object->getClientObject() );
|
|
|
|
// Check the mapTo name exists for this shape
|
|
S32 matIndex = serverObj->getShape()->materialList->getMaterialNameList().find_next(String(mapTo));
|
|
if (matIndex < 0)
|
|
{
|
|
Con::errorf("ShapeBase::changeMaterial failed: Invalid mapTo name '%s'", mapTo);
|
|
return;
|
|
}
|
|
|
|
// Lets remap the old material off, so as to let room for our current material room to claim its spot
|
|
if( oldMat )
|
|
oldMat->mMapTo = String("unmapped_mat");
|
|
|
|
newMat->mMapTo = mapTo;
|
|
|
|
// Map the material by name in the matmgr
|
|
MATMGR->mapMaterial( mapTo, newMat->getName() );
|
|
|
|
// Replace instances with the new material being traded in. For ShapeBase
|
|
// class we have to update the server/client objects separately so both
|
|
// represent our changes
|
|
delete serverObj->getShape()->materialList->mMatInstList[matIndex];
|
|
serverObj->getShape()->materialList->mMatInstList[matIndex] = newMat->createMatInstance();
|
|
if (clientObj)
|
|
{
|
|
delete clientObj->getShape()->materialList->mMatInstList[matIndex];
|
|
clientObj->getShape()->materialList->mMatInstList[matIndex] = newMat->createMatInstance();
|
|
}
|
|
|
|
// Finish up preparing the material instances for rendering
|
|
const GFXVertexFormat *flags = getGFXVertexFormat<GFXVertexPNTTB>();
|
|
FeatureSet features = MATMGR->getDefaultFeatures();
|
|
|
|
serverObj->getShape()->materialList->getMaterialInst(matIndex)->init( features, flags );
|
|
if (clientObj)
|
|
clientObj->getShapeInstance()->mMaterialList->getMaterialInst(matIndex)->init( features, flags );
|
|
}
|
|
|
|
DefineEngineMethod( ShapeBase, getModelFile, const char *, (),,
|
|
"@brief Get the model filename used by this shape.\n\n"
|
|
|
|
"@return the shape filename\n\n" )
|
|
{
|
|
GameBaseData * datablock = object->getDataBlock();
|
|
if( !datablock )
|
|
return String::EmptyString;
|
|
|
|
const char *fieldName = StringTable->insert( String("shapeFile") );
|
|
return datablock->getDataField( fieldName, NULL );
|
|
}
|
|
|
|
|
|
U32 ShapeBase::unique_anim_tag_counter = 1;
|
|
|
|
U32 ShapeBase::playBlendAnimation(S32 seq_id, F32 pos, F32 rate)
|
|
{
|
|
BlendThread blend_clip;
|
|
blend_clip.tag = ((unique_anim_tag_counter++) | BLENDED_CLIP);
|
|
blend_clip.thread = 0;
|
|
|
|
if (isClientObject())
|
|
{
|
|
blend_clip.thread = mShapeInstance->addThread();
|
|
mShapeInstance->setSequence(blend_clip.thread, seq_id, pos);
|
|
mShapeInstance->setTimeScale(blend_clip.thread, rate);
|
|
}
|
|
|
|
blend_clips.push_back(blend_clip);
|
|
|
|
return blend_clip.tag;
|
|
}
|
|
|
|
void ShapeBase::restoreBlendAnimation(U32 tag)
|
|
{
|
|
for (S32 i = 0; i < blend_clips.size(); i++)
|
|
{
|
|
if (blend_clips[i].tag == tag)
|
|
{
|
|
if (blend_clips[i].thread)
|
|
{
|
|
mShapeInstance->destroyThread(blend_clips[i].thread);
|
|
}
|
|
blend_clips.erase_fast(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
|
|
void ShapeBase::restoreAnimation(U32 tag)
|
|
{
|
|
if (!isClientObject())
|
|
return;
|
|
|
|
// check if this is a blended clip
|
|
if ((tag & BLENDED_CLIP) != 0)
|
|
{
|
|
restoreBlendAnimation(tag);
|
|
return;
|
|
}
|
|
|
|
if (tag != 0 && tag == last_anim_tag)
|
|
{
|
|
anim_clip_flags &= ~(ANIM_OVERRIDDEN | IS_DEATH_ANIM);
|
|
|
|
stopThread(0);
|
|
|
|
if (saved_seq_id != -1)
|
|
{
|
|
setThreadSequence(0, saved_seq_id);
|
|
setThreadPosition(0, saved_pos);
|
|
setThreadTimeScale(0, saved_rate);
|
|
setThreadDir(0, (saved_rate >= 0));
|
|
playThread(0);
|
|
|
|
saved_seq_id = -1;
|
|
saved_pos = 0.0f;
|
|
saved_rate = 1.0f;
|
|
}
|
|
|
|
last_anim_tag = 0;
|
|
last_anim_id = -1;
|
|
}
|
|
}
|
|
|
|
U32 ShapeBase::getAnimationID(const char* name)
|
|
{
|
|
const TSShape* ts_shape = getShape();
|
|
S32 seq_id = (ts_shape) ? ts_shape->findSequence(name) : -1;
|
|
return (seq_id >= 0) ? (U32) seq_id : BAD_ANIM_ID;
|
|
}
|
|
|
|
U32 ShapeBase::playAnimationByID(U32 anim_id, F32 pos, F32 rate, F32 trans, bool hold, bool wait, bool is_death_anim)
|
|
{
|
|
if (!isClientObject())
|
|
return 0;
|
|
|
|
if (anim_id == BAD_ANIM_ID)
|
|
return 0;
|
|
|
|
const TSShape* ts_shape = getShape();
|
|
if (!ts_shape)
|
|
return 0;
|
|
|
|
S32 seq_id = (S32) anim_id;
|
|
if (mShapeInstance->getShape()->sequences[seq_id].isBlend())
|
|
return playBlendAnimation(seq_id, pos, rate);
|
|
|
|
if (last_anim_tag == 0)
|
|
{
|
|
// try to save state of playing animation
|
|
Thread& st = mScriptThread[0];
|
|
if (st.sequence != -1)
|
|
{
|
|
saved_seq_id = st.sequence;
|
|
saved_pos = st.position;
|
|
saved_rate = st.timescale;
|
|
}
|
|
}
|
|
|
|
// START OR TRANSITION TO SEQUENCE HERE
|
|
setThreadSequence(0, seq_id);
|
|
setThreadPosition(0, pos);
|
|
setThreadTimeScale(0, rate);
|
|
setThreadDir(0, (rate >= 0));
|
|
playThread(0);
|
|
|
|
if (is_death_anim)
|
|
anim_clip_flags |= IS_DEATH_ANIM;
|
|
else
|
|
anim_clip_flags &= ~IS_DEATH_ANIM;
|
|
|
|
anim_clip_flags |= ANIM_OVERRIDDEN;
|
|
last_anim_tag = unique_anim_tag_counter++;
|
|
last_anim_id = anim_id;
|
|
|
|
return last_anim_tag;
|
|
}
|
|
|
|
F32 ShapeBase::getAnimationDurationByID(U32 anim_id)
|
|
{
|
|
if (anim_id == BAD_ANIM_ID)
|
|
return 0.0f;
|
|
|
|
S32 seq_id = (S32) anim_id;
|
|
if (seq_id >= 0 && seq_id < mDataBlock->mShape->sequences.size())
|
|
return mDataBlock->mShape->sequences[seq_id].duration;
|
|
|
|
return 0.0f;
|
|
}
|
|
|
|
bool ShapeBase::isBlendAnimation(const char* name)
|
|
{
|
|
U32 anim_id = getAnimationID(name);
|
|
if (anim_id == BAD_ANIM_ID)
|
|
return false;
|
|
|
|
S32 seq_id = (S32) anim_id;
|
|
if (seq_id >= 0 && seq_id < mDataBlock->mShape->sequences.size())
|
|
return mDataBlock->mShape->sequences[seq_id].isBlend();
|
|
|
|
return false;
|
|
}
|
|
|
|
const char* ShapeBase::getLastClipName(U32 clip_tag)
|
|
{
|
|
if (clip_tag != last_anim_tag)
|
|
return "";
|
|
|
|
S32 seq_id = (S32) last_anim_id;
|
|
|
|
S32 idx = mDataBlock->mShape->sequences[seq_id].nameIndex;
|
|
if (idx < 0 || idx >= mDataBlock->mShape->names.size())
|
|
return 0;
|
|
|
|
return mDataBlock->mShape->names[idx];
|
|
}
|
|
|
|
//
|
|
|
|
U32 ShapeBase::playAnimation(const char* name, F32 pos, F32 rate, F32 trans, bool hold, bool wait, bool is_death_anim)
|
|
{
|
|
return playAnimationByID(getAnimationID(name), pos, rate, trans, hold, wait, is_death_anim);
|
|
}
|
|
|
|
F32 ShapeBase::getAnimationDuration(const char* name)
|
|
{
|
|
return getAnimationDurationByID(getAnimationID(name));
|
|
}
|
|
|
|
void ShapeBase::setSelectionFlags(U8 flags)
|
|
{
|
|
Parent::setSelectionFlags(flags);
|
|
|
|
if (!mShapeInstance || !isClientObject())
|
|
return;
|
|
|
|
if (!mShapeInstance->ownMaterialList())
|
|
return;
|
|
|
|
TSMaterialList* pMatList = mShapeInstance->getMaterialList();
|
|
for (S32 j = 0; j < pMatList->size(); j++)
|
|
{
|
|
BaseMatInstance * bmi = pMatList->getMaterialInst(j);
|
|
bmi->setSelectionHighlighting(needsSelectionHighlighting());
|
|
}
|
|
}
|
|
|
|
DefineEngineMethod(ShapeBase, getNodeTransform, MatrixF, (const char* nodeName), ,
|
|
"@brief Get the node/bone transform.\n\n"
|
|
|
|
"@param node/bone name to query\n"
|
|
"@return the node position\n\n")
|
|
{
|
|
MatrixF mat;
|
|
object->getNodeTransform(nodeName, &mat);
|
|
return mat;
|
|
}
|
|
|
|
DefineEngineMethod(ShapeBase, getNodeVector, VectorF, (const char* nodeName), ,
|
|
"@brief Get the node/bone vector.\n\n"
|
|
|
|
"@param node/bone name to query\n"
|
|
"@return the node vector\n\n")
|
|
{
|
|
VectorF vec(0, 1, 0);
|
|
object->getNodeVector(nodeName, &vec);
|
|
|
|
return vec;
|
|
}
|
|
|
|
DefineEngineMethod(ShapeBase, getNodePoint, Point3F, (const char* nodeName), ,
|
|
"@brief Get the node/bone position.\n\n"
|
|
|
|
"@param node/bone name to query\n"
|
|
"@return the node position\n\n")
|
|
{
|
|
Point3F pos(0, 0, 0);
|
|
object->getNodePoint(nodeName, &pos);
|
|
|
|
return pos;
|
|
}
|
|
|
|
bool ShapeBase::setAIController(SimObjectId controller)
|
|
{
|
|
if (Sim::findObject(controller, mAIController) && mAIController->mControllerData)
|
|
{
|
|
mAIController->setAIInfo(this);
|
|
mTypeMask |= AIObjectType;
|
|
return true;
|
|
}
|
|
Con::errorf("unable to find AIController : %i", controller);
|
|
mAIController = NULL;
|
|
mTypeMask |= ~AIObjectType;
|
|
return false;
|
|
}
|
|
|
|
bool ShapeBase::getAIMove(Move* move)
|
|
{
|
|
if (!isServerObject()) return false;
|
|
if (isControlled()) return false; //something else is steering us, so use that one's controller
|
|
if (!(mTypeMask & VehicleObjectType || mTypeMask & PlayerObjectType)) return false; //only support players and vehicles for now
|
|
if (mAIController)
|
|
{
|
|
mAIController->getAIMove(move); //actual result
|
|
mTypeMask |= AIObjectType;
|
|
return true;
|
|
}
|
|
mAIController = NULL;
|
|
mTypeMask &= ~AIObjectType;
|
|
return false;
|
|
}
|
|
|
|
|
|
DefineEngineMethod(ShapeBase, setAIController, bool, (S32 controller), , "")
|
|
{
|
|
return object->setAIController(controller);
|
|
}
|
|
|
|
DefineEngineMethod(ShapeBase, getAIController, AIController*, (), , "")
|
|
{
|
|
return object->getAIController();
|
|
}
|