mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-20 12:44:46 +00:00
1959 lines
58 KiB
C++
1959 lines
58 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/tsStatic.h"
|
|
|
|
#include "core/resourceManager.h"
|
|
#include "core/stream/bitStream.h"
|
|
#include "scene/sceneRenderState.h"
|
|
#include "scene/sceneManager.h"
|
|
#include "scene/sceneObjectLightingPlugin.h"
|
|
#include "lighting/lightManager.h"
|
|
#include "math/mathIO.h"
|
|
#include "ts/tsShapeInstance.h"
|
|
#include "ts/tsMaterialList.h"
|
|
#include "console/consoleTypes.h"
|
|
#include "T3D/shapeBase.h"
|
|
#include "sim/netConnection.h"
|
|
#include "gfx/gfxDevice.h"
|
|
#include "gfx/gfxTransformSaver.h"
|
|
#include "ts/tsRenderState.h"
|
|
#include "collision/boxConvex.h"
|
|
#include "T3D/physics/physicsPlugin.h"
|
|
#include "T3D/physics/physicsBody.h"
|
|
#include "T3D/physics/physicsCollision.h"
|
|
#include "materials/materialDefinition.h"
|
|
#include "materials/materialManager.h"
|
|
#include "materials/matInstance.h"
|
|
#include "materials/materialFeatureData.h"
|
|
#include "materials/materialFeatureTypes.h"
|
|
#include "console/engineAPI.h"
|
|
#include "T3D/accumulationVolume.h"
|
|
|
|
#include "gui/editor/inspector/group.h"
|
|
#include "console/typeValidators.h"
|
|
using namespace Torque;
|
|
|
|
extern bool gEditingMission;
|
|
#ifdef TORQUE_AFX_ENABLED
|
|
#include "afx/ce/afxZodiacMgr.h"
|
|
#endif
|
|
|
|
IMPLEMENT_CO_NETOBJECT_V1(TSStatic);
|
|
|
|
ConsoleDocClass(TSStatic,
|
|
"@brief A static object derived from a 3D model file and placed within the game world.\n\n"
|
|
|
|
"TSStatic is the most basic 3D shape in Torque. Unlike StaticShape it doesn't make use of "
|
|
"a datablock. It derrives directly from SceneObject. This makes TSStatic extremely light "
|
|
"weight, which is why the Tools use this class when you want to drop in a DTS or DAE object.\n\n"
|
|
|
|
"While a TSStatic doesn't provide any motion -- it stays were you initally put it -- it does allow for "
|
|
"a single ambient animation sequence to play when the object is first added to the scene.\n\n"
|
|
|
|
"@tsexample\n"
|
|
"new TSStatic(Team1Base) {\n"
|
|
" shapeName = \"art/shapes/desertStructures/station01.dts\";\n"
|
|
" playAmbient = \"1\";\n"
|
|
" receiveSunLight = \"1\";\n"
|
|
" receiveLMLighting = \"1\";\n"
|
|
" useCustomAmbientLighting = \"0\";\n"
|
|
" customAmbientLighting = \"0 0 0 1\";\n"
|
|
" collisionType = \"Visible Mesh\";\n"
|
|
" decalType = \"Collision Mesh\";\n"
|
|
" allowPlayerStep = \"1\";\n"
|
|
" renderNormals = \"0\";\n"
|
|
" forceDetail = \"-1\";\n"
|
|
" position = \"315.18 -180.418 244.313\";\n"
|
|
" rotation = \"0 0 1 195.952\";\n"
|
|
" scale = \"1 1 1\";\n"
|
|
" isRenderEnabled = \"true\";\n"
|
|
" canSaveDynamicFields = \"1\";\n"
|
|
"};\n"
|
|
"@endtsexample\n"
|
|
|
|
"@ingroup gameObjects\n"
|
|
);
|
|
|
|
bool TSStatic::smUseStaticObjectFade = false;
|
|
F32 TSStatic::smStaticObjectFadeStart = 50;
|
|
F32 TSStatic::smStaticObjectFadeEnd = 75;
|
|
F32 TSStatic::smStaticObjectUnfadeableSize = 75;
|
|
|
|
TSStatic::TSStatic()
|
|
:
|
|
cubeDescId(0),
|
|
reflectorDesc(NULL)
|
|
{
|
|
mNetFlags.set(Ghostable | ScopeAlways);
|
|
|
|
mTypeMask |= StaticObjectType | StaticShapeObjectType;
|
|
|
|
mShapeName = "";
|
|
mShapeInstance = NULL;
|
|
|
|
mPlayAmbient = true;
|
|
mAmbientThread = NULL;
|
|
|
|
mAllowPlayerStep = false;
|
|
|
|
mConvexList = new Convex;
|
|
|
|
mRenderNormalScalar = 0;
|
|
mForceDetail = -1;
|
|
|
|
mMeshCulling = false;
|
|
mUseOriginSort = false;
|
|
|
|
mUseAlphaFade = false;
|
|
mAlphaFadeStart = 100.0f;
|
|
mAlphaFadeEnd = 150.0f;
|
|
mInvertAlphaFade = false;
|
|
mAlphaFade = 1.0f;
|
|
mPhysicsRep = NULL;
|
|
|
|
mCollisionType = CollisionMesh;
|
|
mDecalType = CollisionMesh;
|
|
|
|
mIgnoreZodiacs = false;
|
|
mHasGradients = false;
|
|
mInvertGradientRange = false;
|
|
mGradientRangeUser.set(0.0f, 180.0f);
|
|
#ifdef TORQUE_AFX_ENABLED
|
|
afxZodiacData::convertGradientRangeFromDegrees(mGradientRange, mGradientRangeUser);
|
|
#endif
|
|
mAnimOffset = 0.0f;
|
|
mAnimSpeed = 1.0f;
|
|
|
|
mShapeAsset = StringTable->EmptyString();
|
|
mShapeAssetId = StringTable->EmptyString();
|
|
}
|
|
|
|
TSStatic::~TSStatic()
|
|
{
|
|
delete mConvexList;
|
|
mConvexList = NULL;
|
|
}
|
|
|
|
ImplementEnumType(TSMeshType,
|
|
"Type of mesh data available in a shape.\n"
|
|
"@ingroup gameObjects")
|
|
{
|
|
TSStatic::None, "None", "No mesh data."
|
|
},
|
|
{ TSStatic::Bounds, "Bounds", "Bounding box of the shape." },
|
|
{ TSStatic::CollisionMesh, "Collision Mesh", "Specifically desingated \"collision\" meshes." },
|
|
{ TSStatic::VisibleMesh, "Visible Mesh", "Rendered mesh polygons." },
|
|
EndImplementEnumType;
|
|
|
|
FRangeValidator percentValidator(0.0f, 1.0f);
|
|
F32 AnimSpeedMax = 4.0f;
|
|
FRangeValidator speedValidator(0.0f, AnimSpeedMax);
|
|
|
|
void TSStatic::initPersistFields()
|
|
{
|
|
addFieldV("AnimOffset", TypeF32, Offset(mAnimOffset, TSStatic), &percentValidator,
|
|
"Percent Animation Offset.");
|
|
|
|
addFieldV("AnimSpeed", TypeF32, Offset(mAnimSpeed, TSStatic), &speedValidator,
|
|
"Percent Animation Speed.");
|
|
addGroup("Shape");
|
|
|
|
addProtectedField("shapeAsset", TypeShapeAssetId, Offset(mShapeAssetId, TSStatic),
|
|
&TSStatic::_setShapeAsset, &defaultProtectedGetFn,
|
|
"The source shape asset.");
|
|
|
|
addProtectedField("shapeName", TypeShapeFilename, Offset(mShapeName, TSStatic),
|
|
&TSStatic::_setShapeName, &defaultProtectedGetFn,
|
|
"%Path and filename of the model file (.DTS, .DAE) to use for this TSStatic. Legacy field. Any loose files assigned here will attempt to be auto-imported in as an asset.");
|
|
|
|
endGroup("Shape");
|
|
|
|
addGroup("Materials");
|
|
addProtectedField("skin", TypeRealString, Offset(mAppliedSkinName, TSStatic), &_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.\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");
|
|
endGroup("Materials");
|
|
|
|
addGroup("Rendering");
|
|
|
|
addField("playAmbient", TypeBool, Offset(mPlayAmbient, TSStatic),
|
|
"Enables automatic playing of the animation sequence named \"ambient\" (if it exists) when the TSStatic is loaded.");
|
|
addField("meshCulling", TypeBool, Offset(mMeshCulling, TSStatic),
|
|
"Enables detailed culling of meshes within the TSStatic. Should only be used "
|
|
"with large complex shapes like buildings which contain many submeshes.");
|
|
addField("originSort", TypeBool, Offset(mUseOriginSort, TSStatic),
|
|
"Enables translucent sorting of the TSStatic by its origin instead of the bounds.");
|
|
|
|
endGroup("Rendering");
|
|
|
|
addGroup("Reflection");
|
|
addField("cubeReflectorDesc", TypeRealString, Offset(cubeDescName, TSStatic),
|
|
"References a ReflectorDesc datablock that defines performance and quality properties for dynamic reflections.\n");
|
|
endGroup("Reflection");
|
|
|
|
addGroup("Collision");
|
|
|
|
addField("collisionType", TypeTSMeshType, Offset(mCollisionType, TSStatic),
|
|
"The type of mesh data to use for collision queries.");
|
|
addField("decalType", TypeTSMeshType, Offset(mDecalType, TSStatic),
|
|
"The type of mesh data used to clip decal polygons against.");
|
|
addField("allowPlayerStep", TypeBool, Offset(mAllowPlayerStep, TSStatic),
|
|
"@brief Allow a Player to walk up sloping polygons in the TSStatic (based on the collisionType).\n\n"
|
|
"When set to false, the slightest bump will stop the player from walking on top of the object.\n");
|
|
|
|
endGroup("Collision");
|
|
|
|
addGroup("AlphaFade");
|
|
addField("alphaFadeEnable", TypeBool, Offset(mUseAlphaFade, TSStatic), "Turn on/off Alpha Fade");
|
|
addField("alphaFadeStart", TypeF32, Offset(mAlphaFadeStart, TSStatic), "Distance of start Alpha Fade");
|
|
addField("alphaFadeEnd", TypeF32, Offset(mAlphaFadeEnd, TSStatic), "Distance of end Alpha Fade");
|
|
addField("alphaFadeInverse", TypeBool, Offset(mInvertAlphaFade, TSStatic), "Invert Alpha Fade's Start & End Distance");
|
|
endGroup("AlphaFade");
|
|
|
|
addGroup("Debug");
|
|
|
|
addField("renderNormals", TypeF32, Offset(mRenderNormalScalar, TSStatic),
|
|
"Debug rendering mode shows the normals for each point in the TSStatic's mesh.");
|
|
addField("forceDetail", TypeS32, Offset(mForceDetail, TSStatic),
|
|
"Forces rendering to a particular detail level.");
|
|
|
|
endGroup("Debug");
|
|
|
|
addGroup("AFX");
|
|
addField("ignoreZodiacs", TypeBool, Offset(mIgnoreZodiacs, TSStatic));
|
|
addField("useGradientRange", TypeBool, Offset(mHasGradients, TSStatic));
|
|
addField("gradientRange", TypePoint2F, Offset(mGradientRangeUser, TSStatic));
|
|
addField("invertGradientRange", TypeBool, Offset(mInvertGradientRange, TSStatic));
|
|
endGroup("AFX");
|
|
Parent::initPersistFields();
|
|
}
|
|
|
|
void TSStatic::consoleInit()
|
|
{
|
|
Parent::consoleInit();
|
|
|
|
// Vars for debug rendering while the RoadEditor is open, only used if smEditorOpen is true.
|
|
Con::addVariable("$pref::useStaticObjectFade", TypeBool, &TSStatic::smUseStaticObjectFade, "Indicates if all statics should utilize the distance-based object fadeout logic.\n");
|
|
Con::addVariable("$pref::staticObjectFadeStart", TypeF32, &TSStatic::smStaticObjectFadeStart, "Distance at which static object fading begins if $pref::useStaticObjectFade is on.\n");
|
|
Con::addVariable("$pref::staticObjectFadeEnd", TypeF32, &TSStatic::smStaticObjectFadeEnd, "Distance at which static object fading should have fully faded if $pref::useStaticObjectFade is on.\n");
|
|
Con::addVariable("$pref::staticObjectUnfadeableSize", TypeF32, &TSStatic::smStaticObjectUnfadeableSize, "Size of object where if the bounds is at or bigger than this, it will be ignored in the $pref::useStaticObjectFade logic. Useful for very large, distance-important objects.\n");
|
|
}
|
|
|
|
bool TSStatic::_setShapeAsset(void* obj, const char* index, const char* data)
|
|
{
|
|
TSStatic* ts = static_cast<TSStatic*>(obj);// ->setFile(FileName(data));
|
|
|
|
ts->mShapeAssetId = StringTable->insert(data);
|
|
|
|
return ts->setShapeAsset(ts->mShapeAssetId);
|
|
}
|
|
|
|
bool TSStatic::_setShapeName(void* obj, const char* index, const char* data)
|
|
{
|
|
TSStatic* ts = static_cast<TSStatic*>(obj);// ->setFile(FileName(data));
|
|
|
|
StringTableEntry assetId = ShapeAsset::getAssetIdByFilename(StringTable->insert(data));
|
|
if (assetId != StringTable->EmptyString())
|
|
{
|
|
//Special exception case. If we've defaulted to the 'no shape' mesh, don't save it out, we'll retain the original ids/paths so it doesn't break
|
|
//the TSStatic
|
|
if (ts->setShapeAsset(assetId))
|
|
{
|
|
if (assetId == StringTable->insert("Core_Rendering:noShape"))
|
|
{
|
|
ts->mShapeName = data;
|
|
ts->mShapeAssetId = StringTable->EmptyString();
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
ts->mShapeAssetId = assetId;
|
|
ts->mShapeName = StringTable->EmptyString();
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ts->mShapeAsset = StringTable->EmptyString();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool TSStatic::_setFieldSkin(void* object, const char* index, const char* data)
|
|
{
|
|
TSStatic* ts = static_cast<TSStatic*>(object);
|
|
if (ts)
|
|
ts->setSkinName(data);
|
|
return false;
|
|
}
|
|
|
|
const char* TSStatic::_getFieldSkin(void* object, const char* data)
|
|
{
|
|
TSStatic* ts = static_cast<TSStatic*>(object);
|
|
return ts ? ts->mSkinNameHandle.getString() : "";
|
|
}
|
|
|
|
void TSStatic::inspectPostApply()
|
|
{
|
|
// Apply any transformations set in the editor
|
|
Parent::inspectPostApply();
|
|
|
|
if (isServerObject())
|
|
{
|
|
setMaskBits(-1);
|
|
prepCollision();
|
|
}
|
|
|
|
_updateShouldTick();
|
|
}
|
|
|
|
bool TSStatic::onAdd()
|
|
{
|
|
PROFILE_SCOPE(TSStatic_onAdd);
|
|
|
|
if (isServerObject())
|
|
{
|
|
// Handle the old "usePolysoup" field
|
|
SimFieldDictionary* fieldDict = getFieldDictionary();
|
|
|
|
if (fieldDict)
|
|
{
|
|
StringTableEntry slotName = StringTable->insert("usePolysoup");
|
|
|
|
SimFieldDictionary::Entry* entry = fieldDict->findDynamicField(slotName);
|
|
|
|
if (entry)
|
|
{
|
|
// Was "usePolysoup" set?
|
|
bool usePolysoup = dAtob(entry->value);
|
|
|
|
// "usePolysoup" maps to the new VisibleMesh type
|
|
if (usePolysoup)
|
|
mCollisionType = VisibleMesh;
|
|
|
|
// Remove the field in favor on the new "collisionType" field
|
|
fieldDict->setFieldValue(slotName, "");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!Parent::onAdd())
|
|
return false;
|
|
|
|
// Setup the shape.
|
|
if (!_createShape())
|
|
{
|
|
Con::errorf("TSStatic::onAdd() - Shape creation failed!");
|
|
return false;
|
|
}
|
|
|
|
setRenderTransform(mObjToWorld);
|
|
|
|
// Register for the resource change signal.
|
|
//ResourceManager::get().getChangedSignal().notify(this, &TSStatic::_onResourceChanged);
|
|
|
|
addToScene();
|
|
|
|
if (isClientObject())
|
|
{
|
|
mCubeReflector.unregisterReflector();
|
|
|
|
if (reflectorDesc)
|
|
mCubeReflector.registerReflector(this, reflectorDesc);
|
|
}
|
|
|
|
_updateShouldTick();
|
|
|
|
// Accumulation and environment mapping
|
|
if (isClientObject() && mShapeInstance)
|
|
{
|
|
AccumulationVolume::addObject(this);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool TSStatic::setShapeAsset(const StringTableEntry shapeAssetId)
|
|
{
|
|
if (!mShapeAsset.isNull())
|
|
{
|
|
mShapeAsset->getChangedSignal().remove(this, &TSStatic::_onAssetChanged);
|
|
}
|
|
|
|
if (ShapeAsset::getAssetById(shapeAssetId, &mShapeAsset))
|
|
{
|
|
//Special exception case. If we've defaulted to the 'no shape' mesh, don't save it out, we'll retain the original ids/paths so it doesn't break
|
|
//the TSStatic
|
|
if (mShapeAsset.getAssetId() != StringTable->insert("Core_Rendering:noshape"))
|
|
{
|
|
mShapeName = StringTable->EmptyString();
|
|
|
|
mShapeAsset->getChangedSignal().notify(this, &TSStatic::_onAssetChanged);
|
|
}
|
|
|
|
_createShape();
|
|
|
|
setMaskBits(-1);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool TSStatic::_createShape()
|
|
{
|
|
// Cleanup before we create.
|
|
mCollisionDetails.clear();
|
|
mDecalDetails.clear();
|
|
mDecalDetailsPtr = 0;
|
|
mLOSDetails.clear();
|
|
SAFE_DELETE(mPhysicsRep);
|
|
SAFE_DELETE(mShapeInstance);
|
|
mAmbientThread = NULL;
|
|
mShape = NULL;
|
|
|
|
if(!mShapeAsset.isNull())
|
|
{
|
|
//Special-case handling, usually because we set noShape
|
|
mShape = mShapeAsset->getShapeResource();
|
|
}
|
|
|
|
if (!mShape)
|
|
{
|
|
Con::errorf("TSStatic::_createShape() - Shape Asset %s had no valid shape!", mShapeAsset.getAssetId());
|
|
return false;
|
|
}
|
|
|
|
if (isClientObject() &&
|
|
!mShape->preloadMaterialList(mShape.getPath()) &&
|
|
NetConnection::filesWereDownloaded())
|
|
return false;
|
|
|
|
mObjBox = mShape->mBounds;
|
|
resetWorldBox();
|
|
|
|
mShapeInstance = new TSShapeInstance(mShape, isClientObject());
|
|
if (isClientObject())
|
|
mShapeInstance->cloneMaterialList();
|
|
|
|
if (isGhost())
|
|
{
|
|
// Reapply the current skin
|
|
mAppliedSkinName = "";
|
|
reSkin();
|
|
}
|
|
|
|
prepCollision();
|
|
|
|
// Find the "ambient" animation if it exists
|
|
S32 ambientSeq = mShape->findSequence("ambient");
|
|
|
|
if (ambientSeq > -1 && !mAmbientThread)
|
|
mAmbientThread = mShapeInstance->addThread();
|
|
|
|
if ( mAmbientThread )
|
|
mShapeInstance->setSequence(mAmbientThread, ambientSeq, mAnimOffset);
|
|
|
|
// Resolve CubeReflectorDesc.
|
|
if (cubeDescName.isNotEmpty())
|
|
{
|
|
Sim::findObject(cubeDescName, reflectorDesc);
|
|
}
|
|
else if (cubeDescId > 0)
|
|
{
|
|
Sim::findObject(cubeDescId, reflectorDesc);
|
|
}
|
|
|
|
//Set up the material slot vars for easy manipulation
|
|
/*S32 materialCount = mShape->materialList->getMaterialNameList().size(); //mMeshAsset->getMaterialCount();
|
|
|
|
//Temporarily disabled until fixup of materialName->assetId lookup logic is sorted for easy persistance
|
|
if (isServerObject())
|
|
{
|
|
char matFieldName[128];
|
|
|
|
for (U32 i = 0; i < materialCount; i++)
|
|
{
|
|
StringTableEntry materialname = StringTable->insert(mShape->materialList->getMaterialName(i).c_str());
|
|
|
|
dSprintf(matFieldName, 128, "MaterialSlot%d", i);
|
|
StringTableEntry matFld = StringTable->insert(matFieldName);
|
|
|
|
setDataField(matFld, NULL, materialname);
|
|
}
|
|
}*/
|
|
|
|
return true;
|
|
}
|
|
|
|
void TSStatic::onDynamicModified(const char* slotName, const char* newValue)
|
|
{
|
|
if (FindMatch::isMatch("materialslot*", slotName, false))
|
|
{
|
|
if (!getShape())
|
|
return;
|
|
|
|
S32 slot = -1;
|
|
String outStr(String::GetTrailingNumber(slotName, slot));
|
|
|
|
if (slot == -1)
|
|
return;
|
|
|
|
//Safe to assume the inbound value for the material will be a MaterialAsset, so lets do a lookup on the name
|
|
MaterialAsset* matAsset = AssetDatabase.acquireAsset<MaterialAsset>(newValue);
|
|
if (!matAsset)
|
|
return;
|
|
|
|
bool found = false;
|
|
for (U32 i = 0; i < mChangingMaterials.size(); i++)
|
|
{
|
|
if (mChangingMaterials[i].slot == slot)
|
|
{
|
|
mChangingMaterials[i].matAsset = matAsset;
|
|
mChangingMaterials[i].assetId = newValue;
|
|
found = true;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
{
|
|
matMap newMatMap;
|
|
newMatMap.slot = slot;
|
|
newMatMap.matAsset = matAsset;
|
|
newMatMap.assetId = newValue;
|
|
|
|
mChangingMaterials.push_back(newMatMap);
|
|
}
|
|
|
|
setMaskBits(MaterialMask);
|
|
}
|
|
|
|
Parent::onDynamicModified(slotName, newValue);
|
|
}
|
|
|
|
void TSStatic::prepCollision()
|
|
{
|
|
// Let the client know that the collision was updated
|
|
setMaskBits(UpdateCollisionMask);
|
|
|
|
// Allow the ShapeInstance to prep its collision if it hasn't already
|
|
if (mShapeInstance)
|
|
mShapeInstance->prepCollision();
|
|
|
|
// Cleanup any old collision data
|
|
mCollisionDetails.clear();
|
|
mDecalDetails.clear();
|
|
mDecalDetailsPtr = 0;
|
|
mLOSDetails.clear();
|
|
mConvexList->nukeList();
|
|
|
|
if (mCollisionType == CollisionMesh || mCollisionType == VisibleMesh)
|
|
{
|
|
mShape->findColDetails(mCollisionType == VisibleMesh, &mCollisionDetails, &mLOSDetails);
|
|
if (mDecalType == mCollisionType)
|
|
{
|
|
mDecalDetailsPtr = &mCollisionDetails;
|
|
}
|
|
else if (mDecalType == CollisionMesh || mDecalType == VisibleMesh)
|
|
{
|
|
mShape->findColDetails(mDecalType == VisibleMesh, &mDecalDetails, 0);
|
|
mDecalDetailsPtr = &mDecalDetails;
|
|
}
|
|
}
|
|
else if (mDecalType == CollisionMesh || mDecalType == VisibleMesh)
|
|
{
|
|
mShape->findColDetails(mDecalType == VisibleMesh, &mDecalDetails, 0);
|
|
mDecalDetailsPtr = &mDecalDetails;
|
|
}
|
|
|
|
_updatePhysics();
|
|
}
|
|
|
|
void TSStatic::_updatePhysics()
|
|
{
|
|
SAFE_DELETE(mPhysicsRep);
|
|
|
|
if (!PHYSICSMGR || mCollisionType == None)
|
|
return;
|
|
|
|
PhysicsCollision* colShape = NULL;
|
|
if (mCollisionType == Bounds)
|
|
{
|
|
MatrixF offset(true);
|
|
offset.setPosition(mShape->center);
|
|
colShape = PHYSICSMGR->createCollision();
|
|
colShape->addBox(getObjBox().getExtents() * 0.5f * mObjScale, offset);
|
|
}
|
|
else
|
|
colShape = mShape->buildColShape(mCollisionType == VisibleMesh, getScale());
|
|
|
|
if (colShape)
|
|
{
|
|
PhysicsWorld* world = PHYSICSMGR->getWorld(isServerObject() ? "server" : "client");
|
|
mPhysicsRep = PHYSICSMGR->createBody();
|
|
mPhysicsRep->init(colShape, 0, 0, this, world);
|
|
mPhysicsRep->setTransform(getTransform());
|
|
}
|
|
}
|
|
|
|
void TSStatic::onRemove()
|
|
{
|
|
SAFE_DELETE(mPhysicsRep);
|
|
|
|
// Accumulation
|
|
if (isClientObject() && mShapeInstance)
|
|
{
|
|
if (mShapeInstance->hasAccumulation())
|
|
AccumulationVolume::removeObject(this);
|
|
}
|
|
|
|
mConvexList->nukeList();
|
|
|
|
removeFromScene();
|
|
|
|
// Remove the resource change signal.
|
|
//ResourceManager::get().getChangedSignal().remove(this, &TSStatic::_onResourceChanged);
|
|
|
|
delete mShapeInstance;
|
|
mShapeInstance = NULL;
|
|
|
|
mAmbientThread = NULL;
|
|
if (isClientObject())
|
|
mCubeReflector.unregisterReflector();
|
|
|
|
if(!mShapeAsset.isNull())
|
|
mShapeAsset->getChangedSignal().remove(this, &TSStatic::_onAssetChanged);
|
|
|
|
Parent::onRemove();
|
|
}
|
|
|
|
void TSStatic::_onResourceChanged(const Torque::Path& path)
|
|
{
|
|
if (path != Path(mShapeName))
|
|
return;
|
|
|
|
_createShape();
|
|
_updateShouldTick();
|
|
}
|
|
|
|
void TSStatic::_onAssetChanged()
|
|
{
|
|
_createShape();
|
|
_updateShouldTick();
|
|
}
|
|
|
|
void TSStatic::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 TSStatic::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 TSStatic::processTick(const Move* move)
|
|
{
|
|
if ( isServerObject() && mPlayAmbient && mAmbientThread )
|
|
{
|
|
mShapeInstance->setTimeScale(mAmbientThread, mAnimSpeed);
|
|
mShapeInstance->advanceTime( TickSec, mAmbientThread );
|
|
}
|
|
if (isMounted())
|
|
{
|
|
MatrixF mat(true);
|
|
mMount.object->getMountTransform(mMount.node, mMount.xfm, &mat);
|
|
setTransform(mat);
|
|
}
|
|
}
|
|
|
|
void TSStatic::interpolateTick(F32 delta)
|
|
{
|
|
}
|
|
|
|
void TSStatic::advanceTime(F32 dt)
|
|
{
|
|
if ( mPlayAmbient && mAmbientThread )
|
|
{
|
|
mShapeInstance->setTimeScale(mAmbientThread, mAnimSpeed);
|
|
mShapeInstance->advanceTime( dt, mAmbientThread );
|
|
}
|
|
|
|
if (isMounted())
|
|
{
|
|
MatrixF mat(true);
|
|
mMount.object->getRenderMountTransform(dt, mMount.node, mMount.xfm, &mat);
|
|
setRenderTransform(mat);
|
|
}
|
|
}
|
|
|
|
void TSStatic::_updateShouldTick()
|
|
{
|
|
bool shouldTick = (mPlayAmbient && mAmbientThread) || isMounted();
|
|
|
|
if (isTicking() != shouldTick)
|
|
setProcessTick(shouldTick);
|
|
}
|
|
|
|
void TSStatic::prepRenderImage(SceneRenderState* state)
|
|
{
|
|
if (!mShapeInstance)
|
|
return;
|
|
|
|
Point3F cameraOffset;
|
|
getRenderTransform().getColumn(3, &cameraOffset);
|
|
cameraOffset -= state->getDiffuseCameraPosition();
|
|
F32 dist = cameraOffset.len();
|
|
if (dist < 0.01f)
|
|
dist = 0.01f;
|
|
|
|
if (mUseAlphaFade)
|
|
{
|
|
mAlphaFade = 1.0f;
|
|
if ((mAlphaFadeStart < mAlphaFadeEnd) && mAlphaFadeStart > 0.1f)
|
|
{
|
|
if (mInvertAlphaFade)
|
|
{
|
|
if (dist <= mAlphaFadeStart)
|
|
{
|
|
return;
|
|
}
|
|
if (dist < mAlphaFadeEnd)
|
|
{
|
|
mAlphaFade = ((dist - mAlphaFadeStart) / (mAlphaFadeEnd - mAlphaFadeStart));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (dist >= mAlphaFadeEnd)
|
|
{
|
|
return;
|
|
}
|
|
if (dist > mAlphaFadeStart)
|
|
{
|
|
mAlphaFade -= ((dist - mAlphaFadeStart) / (mAlphaFadeEnd - mAlphaFadeStart));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (smUseStaticObjectFade)
|
|
{
|
|
F32 boundsLen = getWorldSphere().radius;
|
|
|
|
if (boundsLen < smStaticObjectUnfadeableSize)
|
|
{
|
|
F32 distAdjust = (boundsLen) / (smStaticObjectUnfadeableSize);
|
|
distAdjust = 1 - distAdjust;
|
|
|
|
dist *= distAdjust;
|
|
|
|
mAlphaFade = 1.0f;
|
|
if ((smStaticObjectFadeStart < smStaticObjectFadeEnd) && smStaticObjectFadeStart > 0.1f)
|
|
{
|
|
if (dist >= smStaticObjectFadeEnd)
|
|
{
|
|
return;
|
|
}
|
|
if (dist > smStaticObjectFadeStart)
|
|
{
|
|
mAlphaFade -= ((dist - smStaticObjectFadeStart) / (smStaticObjectFadeEnd - smStaticObjectFadeStart));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
F32 invScale = (1.0f / getMax(getMax(mObjScale.x, mObjScale.y), mObjScale.z));
|
|
|
|
// If we're currently rendering our own reflection we
|
|
// don't want to render ourselves into it.
|
|
if (mCubeReflector.isRendering())
|
|
return;
|
|
|
|
|
|
if (mForceDetail == -1)
|
|
mShapeInstance->setDetailFromDistance(state, dist * invScale);
|
|
else
|
|
mShapeInstance->setCurrentDetail(mForceDetail);
|
|
|
|
if (mShapeInstance->getCurrentDetail() < 0)
|
|
return;
|
|
|
|
GFXTransformSaver saver;
|
|
|
|
// Set up our TS render state.
|
|
TSRenderState rdata;
|
|
rdata.setSceneState(state);
|
|
rdata.setFadeOverride(1.0f);
|
|
rdata.setOriginSort(mUseOriginSort);
|
|
|
|
if (mCubeReflector.isEnabled())
|
|
rdata.setCubemap(mCubeReflector.getCubemap());
|
|
|
|
// Acculumation
|
|
rdata.setAccuTex(mAccuTex);
|
|
|
|
// If we have submesh culling enabled then prepare
|
|
// the object space frustum to pass to the shape.
|
|
Frustum culler;
|
|
if (mMeshCulling)
|
|
{
|
|
culler = state->getCullingFrustum();
|
|
MatrixF xfm(true);
|
|
xfm.scale(Point3F::One / getScale());
|
|
xfm.mul(getRenderWorldTransform());
|
|
xfm.mul(culler.getTransform());
|
|
culler.setTransform(xfm);
|
|
rdata.setCuller(&culler);
|
|
}
|
|
|
|
// We might have some forward lit materials
|
|
// so pass down a query to gather lights.
|
|
LightQuery query;
|
|
query.init(getWorldSphere());
|
|
rdata.setLightQuery(&query);
|
|
|
|
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);
|
|
}
|
|
|
|
if (mShapeInstance)
|
|
{
|
|
mShapeInstance->animate();
|
|
|
|
if (mUseAlphaFade || smUseStaticObjectFade)
|
|
{
|
|
mShapeInstance->setAlphaAlways(mAlphaFade);
|
|
S32 s = mShapeInstance->mMeshObjects.size();
|
|
|
|
for (S32 x = 0; x < s; x++)
|
|
{
|
|
mShapeInstance->mMeshObjects[x].visible = mAlphaFade;
|
|
}
|
|
}
|
|
}
|
|
mShapeInstance->render(rdata);
|
|
#ifdef TORQUE_AFX_ENABLED
|
|
if (!mIgnoreZodiacs && mDecalDetailsPtr != 0)
|
|
afxZodiacMgr::renderPolysoupZodiacs(state, this);
|
|
#endif
|
|
if (mRenderNormalScalar > 0)
|
|
{
|
|
ObjectRenderInst* ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
|
|
ri->renderDelegate.bind(this, &TSStatic::_renderNormals);
|
|
ri->type = RenderPassManager::RIT_Editor;
|
|
state->getRenderPass()->addInst(ri);
|
|
}
|
|
}
|
|
|
|
void TSStatic::_renderNormals(ObjectRenderInst* ri, SceneRenderState* state, BaseMatInstance* overrideMat)
|
|
{
|
|
PROFILE_SCOPE(TSStatic_RenderNormals);
|
|
|
|
GFXTransformSaver saver;
|
|
|
|
MatrixF mat = getRenderTransform();
|
|
mat.scale(mObjScale);
|
|
GFX->multWorld(mat);
|
|
|
|
S32 dl = mShapeInstance->getCurrentDetail();
|
|
mShapeInstance->renderDebugNormals(mRenderNormalScalar, dl);
|
|
}
|
|
|
|
void TSStatic::onScaleChanged()
|
|
{
|
|
Parent::onScaleChanged();
|
|
|
|
if (mPhysicsRep)
|
|
{
|
|
// If the editor is enabled delay the scale operation
|
|
// by a few milliseconds so that we're not rebuilding
|
|
// during an active scale drag operation.
|
|
if (gEditingMission)
|
|
mPhysicsRep->queueCallback(500, Delegate<void()>(this, &TSStatic::_updatePhysics));
|
|
else
|
|
_updatePhysics();
|
|
}
|
|
|
|
setMaskBits(ScaleMask);
|
|
}
|
|
|
|
void TSStatic::setTransform(const MatrixF& mat)
|
|
{
|
|
Parent::setTransform(mat);
|
|
if (!isMounted())
|
|
setMaskBits(TransformMask);
|
|
|
|
if (mPhysicsRep)
|
|
mPhysicsRep->setTransform(mat);
|
|
|
|
// Accumulation
|
|
if (isClientObject() && mShapeInstance)
|
|
{
|
|
if (mShapeInstance->hasAccumulation())
|
|
AccumulationVolume::updateObject(this);
|
|
}
|
|
|
|
// Since this is a static it's render transform changes 1
|
|
// to 1 with it's collision transform... no interpolation.
|
|
setRenderTransform(mat);
|
|
}
|
|
|
|
U32 TSStatic::packUpdate(NetConnection* con, U32 mask, BitStream* stream)
|
|
{
|
|
U32 retMask = Parent::packUpdate(con, mask, stream);
|
|
|
|
if (stream->writeFlag(mask & TransformMask))
|
|
mathWrite(*stream, getTransform());
|
|
|
|
if (stream->writeFlag(mask & ScaleMask))
|
|
{
|
|
// Only write one bit if the scale is one.
|
|
if (stream->writeFlag(mObjScale != Point3F::One))
|
|
mathWrite(*stream, mObjScale);
|
|
}
|
|
|
|
if (stream->writeFlag(mask & UpdateCollisionMask))
|
|
stream->write((U32)mCollisionType);
|
|
|
|
if (stream->writeFlag(mask & SkinMask))
|
|
con->packNetStringHandleU(stream, mSkinNameHandle);
|
|
|
|
if (stream->writeFlag(mask & AdvancedStaticOptionsMask))
|
|
{
|
|
stream->writeString(mShapeAsset.getAssetId());
|
|
stream->writeString(mShapeName);
|
|
|
|
stream->write((U32)mDecalType);
|
|
|
|
stream->writeFlag(mAllowPlayerStep);
|
|
stream->writeFlag(mMeshCulling);
|
|
stream->writeFlag(mUseOriginSort);
|
|
|
|
stream->write(mRenderNormalScalar);
|
|
|
|
stream->write(mForceDetail);
|
|
|
|
if (stream->writeFlag(mAnimOffset != 0.0f))
|
|
stream->writeFloat(mAnimOffset, 7);
|
|
|
|
if (stream->writeFlag(mAnimSpeed != 1.0f))
|
|
stream->writeSignedFloat(mAnimSpeed / AnimSpeedMax, 7);
|
|
|
|
stream->writeFlag(mPlayAmbient);
|
|
}
|
|
|
|
if (stream->writeFlag(mUseAlphaFade))
|
|
{
|
|
stream->write(mAlphaFadeStart);
|
|
stream->write(mAlphaFadeEnd);
|
|
stream->write(mInvertAlphaFade);
|
|
}
|
|
|
|
stream->writeFlag(mIgnoreZodiacs);
|
|
if (stream->writeFlag(mHasGradients))
|
|
{
|
|
stream->writeFlag(mInvertGradientRange);
|
|
stream->write(mGradientRange.x);
|
|
stream->write(mGradientRange.y);
|
|
}
|
|
if (mLightPlugin)
|
|
retMask |= mLightPlugin->packUpdate(this, AdvancedStaticOptionsMask, con, mask, stream);
|
|
|
|
if (stream->writeFlag(reflectorDesc != NULL))
|
|
{
|
|
stream->writeRangedU32(reflectorDesc->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast);
|
|
}
|
|
|
|
stream->write(mOverrideColor);
|
|
|
|
if (stream->writeFlag(mask & MaterialMask))
|
|
{
|
|
stream->writeInt(mChangingMaterials.size(), 16);
|
|
|
|
for (U32 i = 0; i < mChangingMaterials.size(); i++)
|
|
{
|
|
stream->writeInt(mChangingMaterials[i].slot, 16);
|
|
|
|
NetStringHandle matNameStr = mChangingMaterials[i].assetId.c_str();
|
|
con->packNetStringHandleU(stream, matNameStr);
|
|
}
|
|
|
|
mChangingMaterials.clear();
|
|
}
|
|
|
|
return retMask;
|
|
}
|
|
|
|
void TSStatic::unpackUpdate(NetConnection* con, BitStream* stream)
|
|
{
|
|
Parent::unpackUpdate(con, stream);
|
|
|
|
if (stream->readFlag()) // TransformMask
|
|
{
|
|
MatrixF mat;
|
|
mathRead(*stream, &mat);
|
|
setTransform(mat);
|
|
setRenderTransform(mat);
|
|
}
|
|
|
|
if (stream->readFlag()) // ScaleMask
|
|
{
|
|
if (stream->readFlag())
|
|
{
|
|
VectorF scale;
|
|
mathRead(*stream, &scale);
|
|
setScale(scale);
|
|
}
|
|
else
|
|
setScale(Point3F::One);
|
|
}
|
|
|
|
if (stream->readFlag()) // UpdateCollisionMask
|
|
{
|
|
U32 collisionType = CollisionMesh;
|
|
|
|
stream->read(&collisionType);
|
|
|
|
// Handle it if we have changed CollisionType's
|
|
if ((MeshType)collisionType != mCollisionType)
|
|
{
|
|
mCollisionType = (MeshType)collisionType;
|
|
|
|
if (isProperlyAdded() && mShapeInstance)
|
|
prepCollision();
|
|
}
|
|
}
|
|
|
|
if (stream->readFlag()) // SkinMask
|
|
{
|
|
NetStringHandle skinDesiredNameHandle = con->unpackNetStringHandleU(stream);;
|
|
if (mSkinNameHandle != skinDesiredNameHandle)
|
|
{
|
|
mSkinNameHandle = skinDesiredNameHandle;
|
|
reSkin();
|
|
}
|
|
}
|
|
|
|
if (stream->readFlag()) // AdvancedStaticOptionsMask
|
|
{
|
|
char buffer[256];
|
|
stream->readString(buffer);
|
|
setShapeAsset(StringTable->insert(buffer));
|
|
|
|
mShapeName = stream->readSTString();
|
|
|
|
stream->read((U32*)&mDecalType);
|
|
|
|
mAllowPlayerStep = stream->readFlag();
|
|
mMeshCulling = stream->readFlag();
|
|
mUseOriginSort = stream->readFlag();
|
|
|
|
stream->read(&mRenderNormalScalar);
|
|
|
|
stream->read(&mForceDetail);
|
|
|
|
if (stream->readFlag())
|
|
mAnimOffset = stream->readFloat(7);
|
|
|
|
if (stream->readFlag())
|
|
mAnimSpeed = stream->readSignedFloat(7) * AnimSpeedMax;
|
|
|
|
mPlayAmbient = stream->readFlag();
|
|
|
|
|
|
}
|
|
|
|
mUseAlphaFade = stream->readFlag();
|
|
if (mUseAlphaFade)
|
|
{
|
|
stream->read(&mAlphaFadeStart);
|
|
stream->read(&mAlphaFadeEnd);
|
|
stream->read(&mInvertAlphaFade);
|
|
}
|
|
|
|
mIgnoreZodiacs = stream->readFlag();
|
|
mHasGradients = stream->readFlag();
|
|
if (mHasGradients)
|
|
{
|
|
mInvertGradientRange = stream->readFlag();
|
|
stream->read(&mGradientRange.x);
|
|
stream->read(&mGradientRange.y);
|
|
}
|
|
if (mLightPlugin)
|
|
{
|
|
mLightPlugin->unpackUpdate(this, con, stream);
|
|
}
|
|
|
|
if (stream->readFlag())
|
|
{
|
|
cubeDescId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
|
|
}
|
|
|
|
stream->read(&mOverrideColor);
|
|
|
|
if (stream->readFlag())
|
|
{
|
|
mChangingMaterials.clear();
|
|
U32 materialCount = stream->readInt(16);
|
|
|
|
for (U32 i = 0; i < materialCount; i++)
|
|
{
|
|
matMap newMatMap;
|
|
newMatMap.slot = stream->readInt(16);
|
|
newMatMap.assetId = String(con->unpackNetStringHandleU(stream).getString());
|
|
|
|
//do the lookup, now
|
|
newMatMap.matAsset = AssetDatabase.acquireAsset<MaterialAsset>(newMatMap.assetId);
|
|
|
|
mChangingMaterials.push_back(newMatMap);
|
|
}
|
|
|
|
updateMaterials();
|
|
}
|
|
|
|
if (isProperlyAdded())
|
|
_updateShouldTick();
|
|
set_special_typing();
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
bool TSStatic::castRay(const Point3F& start, const Point3F& end, RayInfo* info)
|
|
{
|
|
if (mCollisionType == None)
|
|
return false;
|
|
|
|
if (!mShapeInstance)
|
|
return false;
|
|
|
|
if (mCollisionType == Bounds)
|
|
{
|
|
F32 fst;
|
|
if (!mObjBox.collideLine(start, end, &fst, &info->normal))
|
|
return false;
|
|
|
|
info->t = fst;
|
|
info->object = this;
|
|
info->point.interpolate(start, end, fst);
|
|
info->material = NULL;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
RayInfo shortest = *info;
|
|
RayInfo localInfo;
|
|
shortest.t = 1e8f;
|
|
localInfo.generateTexCoord = info->generateTexCoord;
|
|
|
|
for (U32 i = 0; i < mLOSDetails.size(); i++)
|
|
{
|
|
mShapeInstance->animate(mLOSDetails[i]);
|
|
|
|
if (mShapeInstance->castRayOpcode(mLOSDetails[i], start, end, &localInfo))
|
|
{
|
|
localInfo.object = this;
|
|
|
|
if (localInfo.t < shortest.t)
|
|
shortest = localInfo;
|
|
}
|
|
}
|
|
|
|
if (shortest.object == this)
|
|
{
|
|
// Copy out the shortest time...
|
|
*info = shortest;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool TSStatic::castRayRendered(const Point3F& start, const Point3F& end, RayInfo* info)
|
|
{
|
|
if (!mShapeInstance)
|
|
return false;
|
|
|
|
// Cast the ray against the currently visible detail
|
|
RayInfo localInfo;
|
|
if (info && info->generateTexCoord)
|
|
localInfo.generateTexCoord = true;
|
|
bool res = mShapeInstance->castRayOpcode(mShapeInstance->getCurrentDetail(), start, end, &localInfo);
|
|
|
|
if (res)
|
|
{
|
|
*info = localInfo;
|
|
info->object = this;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool TSStatic::buildPolyList(PolyListContext context, AbstractPolyList* polyList, const Box3F& box, const SphereF&)
|
|
{
|
|
if (!mShapeInstance)
|
|
return false;
|
|
|
|
// This is safe to set even if we're not outputing
|
|
polyList->setTransform(&mObjToWorld, mObjScale);
|
|
polyList->setObject(this);
|
|
|
|
if (context == PLC_Export)
|
|
{
|
|
// Use highest detail level
|
|
S32 dl = 0;
|
|
|
|
// Try to call on the client so we can export materials
|
|
if (isServerObject() && getClientObject())
|
|
dynamic_cast<TSStatic*>(getClientObject())->mShapeInstance->buildPolyList(polyList, dl);
|
|
else
|
|
mShapeInstance->buildPolyList(polyList, dl);
|
|
}
|
|
else if (context == PLC_Selection)
|
|
{
|
|
// Use the last rendered detail level
|
|
S32 dl = mShapeInstance->getCurrentDetail();
|
|
mShapeInstance->buildPolyListOpcode(dl, polyList, box);
|
|
}
|
|
else
|
|
{
|
|
// Figure out the mesh type we're looking for.
|
|
MeshType meshType = (context == PLC_Decal) ? mDecalType : mCollisionType;
|
|
|
|
if (meshType == None)
|
|
return false;
|
|
else if (meshType == Bounds)
|
|
polyList->addBox(mObjBox);
|
|
else if (meshType == VisibleMesh)
|
|
mShapeInstance->buildPolyList(polyList, 0);
|
|
else if (context == PLC_Decal && mDecalDetailsPtr != 0)
|
|
{
|
|
for (U32 i = 0; i < mDecalDetailsPtr->size(); i++)
|
|
mShapeInstance->buildPolyListOpcode((*mDecalDetailsPtr)[i], polyList, box);
|
|
}
|
|
else
|
|
{
|
|
// Everything else is done from the collision meshes
|
|
// which may be built from either the visual mesh or
|
|
// special collision geometry.
|
|
for (U32 i = 0; i < mCollisionDetails.size(); i++)
|
|
mShapeInstance->buildPolyListOpcode(mCollisionDetails[i], polyList, box);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool TSStatic::buildExportPolyList(ColladaUtils::ExportData* exportData, const Box3F& box, const SphereF&)
|
|
{
|
|
if (!mShapeInstance)
|
|
return false;
|
|
|
|
if (mCollisionType == Bounds)
|
|
{
|
|
ColladaUtils::ExportData::colMesh* colMesh;
|
|
exportData->colMeshes.increment();
|
|
colMesh = &exportData->colMeshes.last();
|
|
|
|
colMesh->mesh.setTransform(&mObjToWorld, mObjScale);
|
|
colMesh->mesh.setObject(this);
|
|
|
|
colMesh->mesh.addBox(mObjBox);
|
|
|
|
colMesh->colMeshName = String::ToString("ColBox%d-1", exportData->colMeshes.size());
|
|
}
|
|
else if (mCollisionType == VisibleMesh)
|
|
{
|
|
ColladaUtils::ExportData::colMesh* colMesh;
|
|
exportData->colMeshes.increment();
|
|
colMesh = &exportData->colMeshes.last();
|
|
|
|
colMesh->mesh.setTransform(&mObjToWorld, mObjScale);
|
|
colMesh->mesh.setObject(this);
|
|
|
|
mShapeInstance->buildPolyList(&colMesh->mesh, 0);
|
|
|
|
colMesh->colMeshName = String::ToString("ColMesh%d-1", exportData->colMeshes.size());
|
|
}
|
|
else if (mCollisionType == CollisionMesh)
|
|
{
|
|
// Everything else is done from the collision meshes
|
|
// which may be built from either the visual mesh or
|
|
// special collision geometry.
|
|
for (U32 i = 0; i < mCollisionDetails.size(); i++)
|
|
{
|
|
ColladaUtils::ExportData::colMesh* colMesh;
|
|
exportData->colMeshes.increment();
|
|
colMesh = &exportData->colMeshes.last();
|
|
|
|
colMesh->mesh.setTransform(&mObjToWorld, mObjScale);
|
|
colMesh->mesh.setObject(this);
|
|
|
|
mShapeInstance->buildPolyListOpcode(mCollisionDetails[i], &colMesh->mesh, box);
|
|
|
|
colMesh->colMeshName = String::ToString("ColMesh%d-1", exportData->colMeshes.size());
|
|
}
|
|
}
|
|
|
|
//Next, process the LOD levels and materials.
|
|
if (isServerObject() && getClientObject())
|
|
{
|
|
TSStatic* clientShape = dynamic_cast<TSStatic*>(getClientObject());
|
|
|
|
exportData->meshData.increment();
|
|
|
|
//Prep a meshData for this shape in particular
|
|
ColladaUtils::ExportData::meshLODData* meshData = &exportData->meshData.last();
|
|
|
|
//Fill out the info we'll need later to actually append our mesh data for the detail levels during the processing phase
|
|
meshData->shapeInst = clientShape->mShapeInstance;
|
|
meshData->originatingObject = this;
|
|
meshData->meshTransform = mObjToWorld;
|
|
meshData->scale = mObjScale;
|
|
|
|
//Iterate over all our detail levels
|
|
for (U32 i = 0; i < clientShape->mShapeInstance->getNumDetails(); i++)
|
|
{
|
|
TSShape::Detail detail = clientShape->mShapeInstance->getShape()->details[i];
|
|
|
|
String detailName = String::ToLower(clientShape->mShapeInstance->getShape()->getName(detail.nameIndex));
|
|
|
|
//Skip it if it's a collision or line of sight element
|
|
if (detailName.startsWith("col") || detailName.startsWith("los"))
|
|
continue;
|
|
|
|
meshData->meshDetailLevels.increment();
|
|
|
|
ColladaUtils::ExportData::detailLevel* curDetail = &meshData->meshDetailLevels.last();
|
|
|
|
//Make sure we denote the size this detail level has
|
|
curDetail->size = getNextPow2(detail.size);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void TSStatic::buildConvex(const Box3F& box, Convex* convex)
|
|
{
|
|
if (mCollisionType == None)
|
|
return;
|
|
|
|
if (mShapeInstance == NULL)
|
|
return;
|
|
|
|
// These should really come out of a pool
|
|
mConvexList->collectGarbage();
|
|
|
|
if (mCollisionType == Bounds)
|
|
{
|
|
// Just return a box convex for the entire shape...
|
|
Convex* cc = 0;
|
|
CollisionWorkingList& wl = convex->getWorkingList();
|
|
for (CollisionWorkingList* itr = wl.wLink.mNext; itr != &wl; itr = itr->wLink.mNext)
|
|
{
|
|
if (itr->mConvex->getType() == BoxConvexType &&
|
|
itr->mConvex->getObject() == this)
|
|
{
|
|
cc = itr->mConvex;
|
|
break;
|
|
}
|
|
}
|
|
if (cc)
|
|
return;
|
|
|
|
// Create a new convex.
|
|
BoxConvex* cp = new BoxConvex;
|
|
mConvexList->registerObject(cp);
|
|
convex->addToWorkingList(cp);
|
|
cp->init(this);
|
|
|
|
mObjBox.getCenter(&cp->mCenter);
|
|
cp->mSize.x = mObjBox.len_x() / 2.0f;
|
|
cp->mSize.y = mObjBox.len_y() / 2.0f;
|
|
cp->mSize.z = mObjBox.len_z() / 2.0f;
|
|
}
|
|
else // CollisionMesh || VisibleMesh
|
|
{
|
|
TSStaticPolysoupConvex::smCurObject = this;
|
|
|
|
for (U32 i = 0; i < mCollisionDetails.size(); i++)
|
|
mShapeInstance->buildConvexOpcode(mObjToWorld, mObjScale, mCollisionDetails[i], box, convex, mConvexList);
|
|
|
|
TSStaticPolysoupConvex::smCurObject = NULL;
|
|
}
|
|
}
|
|
|
|
SceneObject* TSStaticPolysoupConvex::smCurObject = NULL;
|
|
|
|
TSStaticPolysoupConvex::TSStaticPolysoupConvex()
|
|
: box(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f),
|
|
normal(0.0f, 0.0f, 0.0f, 0.0f),
|
|
idx(0),
|
|
mesh(NULL)
|
|
{
|
|
mType = TSPolysoupConvexType;
|
|
|
|
for (U32 i = 0; i < 4; ++i)
|
|
{
|
|
verts[i].set(0.0f, 0.0f, 0.0f);
|
|
}
|
|
}
|
|
|
|
Point3F TSStaticPolysoupConvex::support(const VectorF& vec) const
|
|
{
|
|
F32 bestDot = mDot(verts[0], vec);
|
|
|
|
const Point3F* bestP = &verts[0];
|
|
for (S32 i = 1; i < 4; i++)
|
|
{
|
|
F32 newD = mDot(verts[i], vec);
|
|
if (newD > bestDot)
|
|
{
|
|
bestDot = newD;
|
|
bestP = &verts[i];
|
|
}
|
|
}
|
|
|
|
return *bestP;
|
|
}
|
|
|
|
Box3F TSStaticPolysoupConvex::getBoundingBox() const
|
|
{
|
|
Box3F wbox = box;
|
|
wbox.minExtents.convolve(mObject->getScale());
|
|
wbox.maxExtents.convolve(mObject->getScale());
|
|
mObject->getTransform().mul(wbox);
|
|
return wbox;
|
|
}
|
|
|
|
Box3F TSStaticPolysoupConvex::getBoundingBox(const MatrixF& mat, const Point3F& scale) const
|
|
{
|
|
AssertISV(false, "TSStaticPolysoupConvex::getBoundingBox(m,p) - Not implemented. -- XEA");
|
|
return box;
|
|
}
|
|
|
|
void TSStaticPolysoupConvex::getPolyList(AbstractPolyList* list)
|
|
{
|
|
// Transform the list into object space and set the pointer to the object
|
|
MatrixF i(mObject->getTransform());
|
|
Point3F iS(mObject->getScale());
|
|
list->setTransform(&i, iS);
|
|
list->setObject(mObject);
|
|
|
|
// Add only the original collision triangle
|
|
S32 base = list->addPoint(verts[0]);
|
|
list->addPoint(verts[2]);
|
|
list->addPoint(verts[1]);
|
|
|
|
list->begin(0, (U32)idx ^ (uintptr_t)mesh);
|
|
list->vertex(base + 2);
|
|
list->vertex(base + 1);
|
|
list->vertex(base + 0);
|
|
list->plane(base + 0, base + 1, base + 2);
|
|
list->end();
|
|
}
|
|
|
|
void TSStaticPolysoupConvex::getFeatures(const MatrixF& mat, const VectorF& n, ConvexFeature* cf)
|
|
{
|
|
cf->material = 0;
|
|
cf->mObject = mObject;
|
|
|
|
// For a tetrahedron this is pretty easy... first
|
|
// convert everything into world space.
|
|
Point3F tverts[4];
|
|
mat.mulP(verts[0], &tverts[0]);
|
|
mat.mulP(verts[1], &tverts[1]);
|
|
mat.mulP(verts[2], &tverts[2]);
|
|
mat.mulP(verts[3], &tverts[3]);
|
|
|
|
// points...
|
|
S32 firstVert = cf->mVertexList.size();
|
|
cf->mVertexList.increment(); cf->mVertexList.last() = tverts[0];
|
|
cf->mVertexList.increment(); cf->mVertexList.last() = tverts[1];
|
|
cf->mVertexList.increment(); cf->mVertexList.last() = tverts[2];
|
|
cf->mVertexList.increment(); cf->mVertexList.last() = tverts[3];
|
|
|
|
// edges...
|
|
cf->mEdgeList.increment();
|
|
cf->mEdgeList.last().vertex[0] = firstVert + 0;
|
|
cf->mEdgeList.last().vertex[1] = firstVert + 1;
|
|
|
|
cf->mEdgeList.increment();
|
|
cf->mEdgeList.last().vertex[0] = firstVert + 1;
|
|
cf->mEdgeList.last().vertex[1] = firstVert + 2;
|
|
|
|
cf->mEdgeList.increment();
|
|
cf->mEdgeList.last().vertex[0] = firstVert + 2;
|
|
cf->mEdgeList.last().vertex[1] = firstVert + 0;
|
|
|
|
cf->mEdgeList.increment();
|
|
cf->mEdgeList.last().vertex[0] = firstVert + 3;
|
|
cf->mEdgeList.last().vertex[1] = firstVert + 0;
|
|
|
|
cf->mEdgeList.increment();
|
|
cf->mEdgeList.last().vertex[0] = firstVert + 3;
|
|
cf->mEdgeList.last().vertex[1] = firstVert + 1;
|
|
|
|
cf->mEdgeList.increment();
|
|
cf->mEdgeList.last().vertex[0] = firstVert + 3;
|
|
cf->mEdgeList.last().vertex[1] = firstVert + 2;
|
|
|
|
// triangles...
|
|
cf->mFaceList.increment();
|
|
cf->mFaceList.last().normal = PlaneF(tverts[2], tverts[1], tverts[0]);
|
|
cf->mFaceList.last().vertex[0] = firstVert + 2;
|
|
cf->mFaceList.last().vertex[1] = firstVert + 1;
|
|
cf->mFaceList.last().vertex[2] = firstVert + 0;
|
|
|
|
cf->mFaceList.increment();
|
|
cf->mFaceList.last().normal = PlaneF(tverts[1], tverts[0], tverts[3]);
|
|
cf->mFaceList.last().vertex[0] = firstVert + 1;
|
|
cf->mFaceList.last().vertex[1] = firstVert + 0;
|
|
cf->mFaceList.last().vertex[2] = firstVert + 3;
|
|
|
|
cf->mFaceList.increment();
|
|
cf->mFaceList.last().normal = PlaneF(tverts[2], tverts[1], tverts[3]);
|
|
cf->mFaceList.last().vertex[0] = firstVert + 2;
|
|
cf->mFaceList.last().vertex[1] = firstVert + 1;
|
|
cf->mFaceList.last().vertex[2] = firstVert + 3;
|
|
|
|
cf->mFaceList.increment();
|
|
cf->mFaceList.last().normal = PlaneF(tverts[0], tverts[2], tverts[3]);
|
|
cf->mFaceList.last().vertex[0] = firstVert + 0;
|
|
cf->mFaceList.last().vertex[1] = firstVert + 2;
|
|
cf->mFaceList.last().vertex[2] = firstVert + 3;
|
|
|
|
// All done!
|
|
}
|
|
|
|
void TSStatic::onMount(SceneObject* obj, S32 node)
|
|
{
|
|
Parent::onMount(obj, node);
|
|
_updateShouldTick();
|
|
}
|
|
|
|
void TSStatic::onUnmount(SceneObject* obj, S32 node)
|
|
{
|
|
Parent::onUnmount(obj, node);
|
|
setMaskBits(TransformMask);
|
|
_updateShouldTick();
|
|
}
|
|
|
|
U32 TSStatic::getNumDetails()
|
|
{
|
|
if (isServerObject() && getClientObject())
|
|
{
|
|
TSStatic* clientShape = dynamic_cast<TSStatic*>(getClientObject());
|
|
return clientShape->mShapeInstance->getNumDetails();
|
|
}
|
|
return 0;
|
|
};
|
|
|
|
void TSStatic::updateMaterials()
|
|
{
|
|
if (mChangingMaterials.empty() || !mShape)
|
|
return;
|
|
|
|
TSMaterialList* pMatList = mShapeInstance->getMaterialList();
|
|
|
|
String path;
|
|
if (mShapeAsset->isAssetValid())
|
|
path = mShapeAsset->getShapeFilename();
|
|
else
|
|
path = mShapeName;
|
|
|
|
pMatList->setTextureLookupPath(path);
|
|
|
|
bool found = false;
|
|
const Vector<String>& materialNames = pMatList->getMaterialNameList();
|
|
for (S32 i = 0; i < materialNames.size(); i++)
|
|
{
|
|
if (found)
|
|
break;
|
|
|
|
for (U32 m = 0; m < mChangingMaterials.size(); m++)
|
|
{
|
|
if (mChangingMaterials[m].slot == i)
|
|
{
|
|
//Fetch the actual material asset
|
|
pMatList->renameMaterial(i, mChangingMaterials[m].matAsset->getMaterialDefinitionName());
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
mChangingMaterials.clear();
|
|
|
|
// Initialize the material instances
|
|
mShapeInstance->initMaterialList();
|
|
}
|
|
|
|
void TSStatic::getUtilizedAssets(Vector<StringTableEntry>* usedAssetsList)
|
|
{
|
|
if(!mShapeAsset.isNull() && mShapeAsset->getAssetId() != StringTable->insert("Core_Rendering:noShape"))
|
|
usedAssetsList->push_back_unique(mShapeAsset->getAssetId());
|
|
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
//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.
|
|
|
|
void TSStatic::onInspect(GuiInspector* inspector)
|
|
{
|
|
if (mShapeAsset == nullptr)
|
|
return;
|
|
|
|
//Put the GameObject group before everything that'd be gameobject-effecting, for orginazational purposes
|
|
GuiInspectorGroup* materialGroup = inspector->findExistentGroup(StringTable->insert("Materials"));
|
|
if (!materialGroup)
|
|
return;
|
|
|
|
GuiControl* stack = dynamic_cast<GuiControl*>(materialGroup->findObjectByInternalName(StringTable->insert("Stack")));
|
|
|
|
//Do this on both the server and client
|
|
S32 materialCount = mShapeAsset->getShape()->materialList->getMaterialNameList().size(); //mMeshAsset->getMaterialCount();
|
|
|
|
if (isServerObject())
|
|
{
|
|
//we need to update the editor
|
|
/*for (U32 i = 0; i < mFields.size(); i++)
|
|
{
|
|
//find any with the materialslot title and clear them out
|
|
if (FindMatch::isMatch("MaterialSlot*", mFields[i].mFieldName, false))
|
|
{
|
|
setDataField(mFields[i].mFieldName, NULL, "");
|
|
mFields.erase(i);
|
|
continue;
|
|
}
|
|
}*/
|
|
|
|
//next, get a listing of our materials in the shape, and build our field list for them
|
|
char matFieldName[128];
|
|
|
|
for (U32 i = 0; i < materialCount; i++)
|
|
{
|
|
StringTableEntry materialname = StringTable->insert(mShapeAsset->getShape()->materialList->getMaterialName(i).c_str());
|
|
|
|
//Iterate through our assetList to find the compliant entry in our matList
|
|
for (U32 m = 0; m < mShapeAsset->getMaterialCount(); m++)
|
|
{
|
|
AssetPtr<MaterialAsset> matAsset = mShapeAsset->getMaterialAsset(m);
|
|
|
|
if (matAsset->getMaterialDefinitionName() == materialname)
|
|
{
|
|
dSprintf(matFieldName, 128, "MaterialSlot%d", i);
|
|
|
|
//addComponentField(matFieldName, "A material used in the shape file", "Material", matAsset->getAssetId(), "");
|
|
//Con::executef(this, "onConstructComponentField", mTargetComponent, field->mFieldName);
|
|
Con::printf("Added material field for MaterialSlot %d", i);
|
|
|
|
GuiInspectorField* fieldGui = materialGroup->constructField(TypeMaterialAssetPtr);
|
|
fieldGui->init(inspector, materialGroup);
|
|
|
|
fieldGui->setSpecialEditField(true);
|
|
fieldGui->setTargetObject(this);
|
|
|
|
StringTableEntry fldnm = StringTable->insert(matFieldName);
|
|
|
|
fieldGui->setSpecialEditVariableName(fldnm);
|
|
|
|
fieldGui->setInspectorField(NULL, fldnm);
|
|
fieldGui->setDocs("");
|
|
|
|
if (fieldGui->registerObject())
|
|
{
|
|
fieldGui->setValue(materialname);
|
|
|
|
stack->addObject(fieldGui);
|
|
}
|
|
else
|
|
{
|
|
SAFE_DELETE(fieldGui);
|
|
}
|
|
|
|
/*if (materialGroup->isMethod("onConstructField"))
|
|
{
|
|
//ensure our stack variable is bound if we need it
|
|
//Con::evaluatef("%d.stack = %d;", materialGroup->getId(), materialGroup->at(0)->getId());
|
|
|
|
Con::executef(materialGroup, "onConstructField", matFieldName,
|
|
matFieldName, "material", matFieldName,
|
|
materialname, "", "", this);
|
|
}*/
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DefineEngineMethod(TSStatic, getTargetName, const char*, (S32 index), (0),
|
|
"Get the name of the indexed shape material.\n"
|
|
"@param index index of the material to get (valid range is 0 - getTargetCount()-1).\n"
|
|
"@return the name of the indexed material.\n"
|
|
"@see getTargetCount()\n")
|
|
{
|
|
TSStatic* obj = dynamic_cast<TSStatic*> (object);
|
|
if (obj)
|
|
{
|
|
// Try to use the client object (so we get the reskinned targets in the Material Editor)
|
|
if ((TSStatic*)obj->getClientObject())
|
|
obj = (TSStatic*)obj->getClientObject();
|
|
|
|
return obj->getShapeInstance()->getTargetName(index);
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
DefineEngineMethod(TSStatic, getTargetCount, S32, (), ,
|
|
"Get the number of materials in the shape.\n"
|
|
"@return the number of materials in the shape.\n"
|
|
"@see getTargetName()\n")
|
|
{
|
|
TSStatic* obj = dynamic_cast<TSStatic*> (object);
|
|
if (obj)
|
|
{
|
|
// Try to use the client object (so we get the reskinned targets in the Material Editor)
|
|
if ((TSStatic*)obj->getClientObject())
|
|
obj = (TSStatic*)obj->getClientObject();
|
|
|
|
return obj->getShapeInstance()->getTargetCount();
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
// This method is able to change materials per map to with others. The material that is being replaced is being mapped to
|
|
// unmapped_mat as a part of this transition
|
|
|
|
DefineEngineMethod(TSStatic, changeMaterial, void, (const char* mapTo, Material* oldMat, Material* newMat), ("", nullAsType<Material*>(), nullAsType<Material*>()),
|
|
"@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("TSShape::changeMaterial failed: New material does not exist!");
|
|
return;
|
|
}
|
|
|
|
TSMaterialList* shapeMaterialList = object->getShape()->materialList;
|
|
|
|
// Check the mapTo name exists for this shape
|
|
S32 matIndex = shapeMaterialList->getMaterialNameList().find_next(String(mapTo));
|
|
if (matIndex < 0)
|
|
{
|
|
Con::errorf("TSShape::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. Lets make sure that we only
|
|
// target the specific targets per inst, this is actually doing more than we thought
|
|
delete shapeMaterialList->mMatInstList[matIndex];
|
|
shapeMaterialList->mMatInstList[matIndex] = newMat->createMatInstance();
|
|
|
|
// Finish up preparing the material instances for rendering
|
|
const GFXVertexFormat* flags = getGFXVertexFormat<GFXVertexPNTTB>();
|
|
FeatureSet features = MATMGR->getDefaultFeatures();
|
|
shapeMaterialList->getMaterialInst(matIndex)->init(features, flags);
|
|
}
|
|
|
|
DefineEngineMethod(TSStatic, getModelFile, const char*, (), ,
|
|
"@brief Get the model filename used by this shape.\n\n"
|
|
|
|
"@return the shape filename\n\n"
|
|
"@tsexample\n"
|
|
"// Acquire the model filename used on this shape.\n"
|
|
"%modelFilename = %obj.getModelFile();\n"
|
|
"@endtsexample\n"
|
|
)
|
|
{
|
|
return object->getShapeFileName();
|
|
}
|
|
|
|
void TSStatic::set_special_typing()
|
|
{
|
|
if (mCollisionType == VisibleMesh || mCollisionType == CollisionMesh)
|
|
mTypeMask |= InteriorLikeObjectType;
|
|
else
|
|
mTypeMask &= ~InteriorLikeObjectType;
|
|
}
|
|
|
|
void TSStatic::onStaticModified(const char* slotName, const char* newValue)
|
|
{
|
|
#ifdef TORQUE_AFX_ENABLED
|
|
if (slotName == afxZodiacData::GradientRangeSlot)
|
|
{
|
|
afxZodiacData::convertGradientRangeFromDegrees(mGradientRange, mGradientRangeUser);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
set_special_typing();
|
|
}
|
|
|
|
void TSStatic::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());
|
|
}
|
|
}
|
|
|