mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-20 04:34:48 +00:00
Added santizing of strings to filenames, object names and asset names for the importer process Added sanitizeString console function for above Added processing of terrainBlock object's fields for importer Added check to avoid updateTSShapeLoadProgress spam Adjusted folderPrefix logic to walk up directory to find assetName that isn't already in use.
1358 lines
45 KiB
C++
1358 lines
45 KiB
C++
//-----------------------------------------------------------------------------
|
|
// Copyright (c) 2012 GarageGames, LLC
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to
|
|
// deal in the Software without restriction, including without limitation the
|
|
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
// sell copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
// IN THE SOFTWARE.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "platform/platform.h"
|
|
#include "console/engineAPI.h"
|
|
#include "ts/loader/tsShapeLoader.h"
|
|
|
|
#include "core/volume.h"
|
|
#include "materials/materialList.h"
|
|
#include "materials/matInstance.h"
|
|
#include "materials/materialManager.h"
|
|
#include "ts/tsShapeInstance.h"
|
|
#include "ts/tsMaterialList.h"
|
|
|
|
MODULE_BEGIN( ShapeLoader )
|
|
MODULE_INIT_AFTER( GFX )
|
|
MODULE_INIT
|
|
{
|
|
TSShapeLoader::addFormat("Torque DTS", "dts");
|
|
TSShapeLoader::addFormat("Torque DSQ", "dsq");
|
|
}
|
|
MODULE_END;
|
|
|
|
const F32 TSShapeLoader::DefaultTime = -1.0f;
|
|
const F64 TSShapeLoader::MinFrameRate = 15.0f;
|
|
const F64 TSShapeLoader::MaxFrameRate = 60.0f;
|
|
const F64 TSShapeLoader::AppGroundFrameRate = 10.0f;
|
|
Torque::Path TSShapeLoader::shapePath;
|
|
|
|
Vector<TSShapeLoader::ShapeFormat> TSShapeLoader::smFormats;
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Utility functions
|
|
|
|
void TSShapeLoader::zapScale(MatrixF& mat)
|
|
{
|
|
Point3F invScale = mat.getScale();
|
|
invScale.x = invScale.x ? (1.0f / invScale.x) : 0;
|
|
invScale.y = invScale.y ? (1.0f / invScale.y) : 0;
|
|
invScale.z = invScale.z ? (1.0f / invScale.z) : 0;
|
|
mat.scale(invScale);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Shape utility functions
|
|
|
|
MatrixF TSShapeLoader::getLocalNodeMatrix(AppNode* node, F32 t)
|
|
{
|
|
MatrixF m1 = node->getNodeTransform(t);
|
|
|
|
// multiply by inverse scale at t=0
|
|
MatrixF m10 = node->getNodeTransform(DefaultTime);
|
|
m1.scale(Point3F(1.0f/m10.getScale().x, 1.0f/m10.getScale().y, 1.0f/m10.getScale().z));
|
|
|
|
if (node->mParentIndex >= 0)
|
|
{
|
|
AppNode *parent = appNodes[node->mParentIndex];
|
|
|
|
MatrixF m2 = parent->getNodeTransform(t);
|
|
|
|
// multiply by inverse scale at t=0
|
|
MatrixF m20 = parent->getNodeTransform(DefaultTime);
|
|
m2.scale(Point3F(1.0f/m20.getScale().x, 1.0f/m20.getScale().y, 1.0f/m20.getScale().z));
|
|
|
|
// get local transform by pre-multiplying by inverted parent transform
|
|
m1 = m2.inverse() * m1;
|
|
}
|
|
else if (boundsNode && node != boundsNode)
|
|
{
|
|
// make transform relative to bounds node transform at time=t
|
|
MatrixF mb = boundsNode->getNodeTransform(t);
|
|
zapScale(mb);
|
|
m1 = mb.inverse() * m1;
|
|
}
|
|
|
|
return m1;
|
|
}
|
|
|
|
void TSShapeLoader::generateNodeTransform(AppNode* node, F32 t, bool blend, F32 referenceTime,
|
|
QuatF& rot, Point3F& trans, QuatF& srot, Point3F& scale)
|
|
{
|
|
MatrixF m1 = getLocalNodeMatrix(node, t);
|
|
if (blend)
|
|
{
|
|
MatrixF m0 = getLocalNodeMatrix(node, referenceTime);
|
|
m1 = m0.inverse() * m1;
|
|
}
|
|
|
|
rot.set(m1);
|
|
trans = m1.getPosition();
|
|
srot.identity(); //@todo: srot not supported yet
|
|
scale = m1.getScale();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TSShapeLoader::updateProgress(S32 major, const char* msg, S32 numMinor, S32 minor)
|
|
{
|
|
// Calculate progress value
|
|
F32 progress = (F32)major / NumLoadPhases;
|
|
const char *progressMsg = msg;
|
|
|
|
if (numMinor)
|
|
{
|
|
progress += (minor * (1.0f / NumLoadPhases) / numMinor);
|
|
progressMsg = avar("%s (%d of %d)", msg, minor + 1, numMinor);
|
|
}
|
|
|
|
if(Con::isFunction("updateTSShapeLoadProgress"))
|
|
Con::executef("updateTSShapeLoadProgress", Con::getFloatArg(progress), progressMsg);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Shape creation entry point
|
|
|
|
TSShape* TSShapeLoader::generateShape(const Torque::Path& path)
|
|
{
|
|
shapePath = path;
|
|
shape = new TSShape();
|
|
|
|
shape->mExporterVersion = 124;
|
|
shape->mSmallestVisibleSize = 999999;
|
|
shape->mSmallestVisibleDL = 0;
|
|
shape->mReadVersion = 24;
|
|
shape->mFlags = 0;
|
|
shape->mSequencesConstructed = 0;
|
|
|
|
// Get all nodes, objects and sequences in the shape
|
|
updateProgress(Load_EnumerateScene, "Enumerating scene...");
|
|
enumerateScene();
|
|
if (!subshapes.size())
|
|
{
|
|
delete shape;
|
|
Con::errorf("Failed to load shape \"%s\", no subshapes found", path.getFullPath().c_str());
|
|
return NULL;
|
|
}
|
|
|
|
// Create the TSShape::Node hierarchy
|
|
generateSubshapes();
|
|
|
|
// Create objects (meshes and details)
|
|
generateObjects();
|
|
|
|
// Generate initial object states and node transforms
|
|
generateDefaultStates();
|
|
|
|
// Generate skins
|
|
generateSkins();
|
|
|
|
// Generate material list
|
|
generateMaterialList();
|
|
|
|
// Generate animation sequences
|
|
generateSequences();
|
|
|
|
// Sort detail levels and meshes
|
|
updateProgress(Load_InitShape, "Initialising shape...");
|
|
sortDetails();
|
|
|
|
// Install the TS memory helper into a TSShape object.
|
|
install();
|
|
|
|
return shape;
|
|
}
|
|
|
|
bool TSShapeLoader::processNode(AppNode* node)
|
|
{
|
|
// Detect bounds node
|
|
if ( node->isBounds() )
|
|
{
|
|
if ( boundsNode )
|
|
{
|
|
Con::warnf( "More than one bounds node found" );
|
|
return false;
|
|
}
|
|
boundsNode = node;
|
|
|
|
// Process bounds geometry
|
|
MatrixF boundsMat(boundsNode->getNodeTransform(DefaultTime));
|
|
boundsMat.inverse();
|
|
zapScale(boundsMat);
|
|
for (S32 iMesh = 0; iMesh < boundsNode->getNumMesh(); iMesh++)
|
|
{
|
|
AppMesh* mesh = boundsNode->getMesh(iMesh);
|
|
MatrixF transform = mesh->getMeshTransform(DefaultTime);
|
|
transform.mulL(boundsMat);
|
|
mesh->lockMesh(DefaultTime, transform);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Detect sequence markers
|
|
if ( node->isSequence() )
|
|
{
|
|
//appSequences.push_back(new AppSequence(node));
|
|
return false;
|
|
}
|
|
|
|
// Add this node to the subshape (create one if needed)
|
|
if ( subshapes.size() == 0 )
|
|
subshapes.push_back( new TSShapeLoader::Subshape );
|
|
|
|
subshapes.last()->branches.push_back( node );
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Nodes, meshes and skins
|
|
|
|
typedef bool (*NameCmpFunc)(const String&, const Vector<String>&, void*, void*);
|
|
|
|
bool cmpShapeName(const String& key, const Vector<String>& names, void* arg1, void* arg2)
|
|
{
|
|
for (S32 i = 0; i < names.size(); i++)
|
|
{
|
|
if (names[i].compare(key, 0, String::NoCase) == 0)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
String getUniqueName(const char* name, NameCmpFunc isNameUnique, const Vector<String>& names, void* arg1=0, void* arg2=0)
|
|
{
|
|
const S32 MAX_ITERATIONS = 0x10000; // maximum of 4 characters (A-P) will be appended
|
|
|
|
String suffix;
|
|
for (S32 i = 0; i < MAX_ITERATIONS; i++)
|
|
{
|
|
// Generate a suffix using the first 16 characters of the alphabet
|
|
suffix.clear();
|
|
for (S32 value = i; value != 0; value >>= 4)
|
|
suffix = suffix + (char)('A' + (value & 0xF));
|
|
|
|
String uname = name + suffix;
|
|
if (isNameUnique(uname, names, arg1, arg2))
|
|
return uname;
|
|
}
|
|
return name;
|
|
}
|
|
|
|
void TSShapeLoader::recurseSubshape(AppNode* appNode, S32 parentIndex, bool recurseChildren)
|
|
{
|
|
// Ignore local bounds nodes
|
|
if (appNode->isBounds())
|
|
return;
|
|
|
|
S32 subShapeNum = shape->subShapeFirstNode.size()-1;
|
|
Subshape* subshape = subshapes[subShapeNum];
|
|
|
|
// Check if we should collapse this node
|
|
S32 myIndex;
|
|
if (ignoreNode(appNode->getName()))
|
|
{
|
|
myIndex = parentIndex;
|
|
}
|
|
else
|
|
{
|
|
// Check that adding this node will not exceed the maximum node count
|
|
if (shape->nodes.size() >= MAX_TS_SET_SIZE)
|
|
return;
|
|
|
|
myIndex = shape->nodes.size();
|
|
String nodeName = getUniqueName(appNode->getName(), cmpShapeName, shape->names);
|
|
|
|
// Create the 3space node
|
|
shape->nodes.increment();
|
|
TSShape::Node& lastNode = shape->nodes.last();
|
|
lastNode.nameIndex = shape->addName(nodeName);
|
|
lastNode.parentIndex = parentIndex;
|
|
lastNode.firstObject = -1;
|
|
lastNode.firstChild = -1;
|
|
lastNode.nextSibling = -1;
|
|
|
|
// Add the AppNode to a matching list (so AppNodes can be accessed using 3space
|
|
// node indices)
|
|
appNodes.push_back(appNode);
|
|
appNodes.last()->mParentIndex = parentIndex;
|
|
|
|
// Check for NULL detail or AutoBillboard nodes (no children or geometry)
|
|
if ((appNode->getNumChildNodes() == 0) &&
|
|
(appNode->getNumMesh() == 0))
|
|
{
|
|
S32 size = 0x7FFFFFFF;
|
|
String dname(String::GetTrailingNumber(appNode->getName(), size));
|
|
|
|
if (dStrEqual(dname, "nulldetail") && (size != 0x7FFFFFFF))
|
|
{
|
|
shape->addDetail("detail", size, subShapeNum);
|
|
}
|
|
else if (appNode->isBillboard() && (size != 0x7FFFFFFF))
|
|
{
|
|
// AutoBillboard detail
|
|
S32 numEquatorSteps = 4;
|
|
S32 numPolarSteps = 0;
|
|
F32 polarAngle = 0.0f;
|
|
S32 dl = 0;
|
|
S32 dim = 64;
|
|
bool includePoles = true;
|
|
|
|
appNode->getInt("BB::EQUATOR_STEPS", numEquatorSteps);
|
|
appNode->getInt("BB::POLAR_STEPS", numPolarSteps);
|
|
appNode->getFloat("BB::POLAR_ANGLE", polarAngle);
|
|
appNode->getInt("BB::DL", dl);
|
|
appNode->getInt("BB::DIM", dim);
|
|
appNode->getBool("BB::INCLUDE_POLES", includePoles);
|
|
|
|
S32 detIndex = shape->addDetail( "bbDetail", size, -1 );
|
|
|
|
TSShape::Detail& detIndexDetail = shape->details[detIndex];
|
|
detIndexDetail.bbEquatorSteps = numEquatorSteps;
|
|
detIndexDetail.bbPolarSteps = numPolarSteps;
|
|
detIndexDetail.bbDetailLevel = dl;
|
|
detIndexDetail.bbDimension = dim;
|
|
detIndexDetail.bbIncludePoles = includePoles;
|
|
detIndexDetail.bbPolarAngle = polarAngle;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Collect geometry
|
|
for (U32 iMesh = 0; iMesh < appNode->getNumMesh(); iMesh++)
|
|
{
|
|
AppMesh* mesh = appNode->getMesh(iMesh);
|
|
if (!ignoreMesh(mesh->getName()))
|
|
{
|
|
subshape->objMeshes.push_back(mesh);
|
|
subshape->objNodes.push_back(mesh->isSkin() ? -1 : myIndex);
|
|
}
|
|
}
|
|
|
|
// Create children
|
|
if (recurseChildren)
|
|
{
|
|
for (S32 iChild = 0; iChild < appNode->getNumChildNodes(); iChild++)
|
|
recurseSubshape(appNode->getChildNode(iChild), myIndex, true);
|
|
}
|
|
}
|
|
|
|
void TSShapeLoader::generateSubshapes()
|
|
{
|
|
for (U32 iSub = 0; iSub < subshapes.size(); iSub++)
|
|
{
|
|
updateProgress(Load_GenerateSubshapes, "Generating subshapes...", subshapes.size(), iSub);
|
|
|
|
Subshape* subshape = subshapes[iSub];
|
|
|
|
// Recurse through the node hierarchy, adding 3space nodes and
|
|
// collecting geometry
|
|
S32 firstNode = shape->nodes.size();
|
|
shape->subShapeFirstNode.push_back(firstNode);
|
|
|
|
for (U32 iBranch = 0; iBranch < subshape->branches.size(); iBranch++)
|
|
recurseSubshape(subshape->branches[iBranch], -1, true);
|
|
|
|
shape->subShapeNumNodes.push_back(shape->nodes.size() - firstNode);
|
|
|
|
if (shape->nodes.size() >= MAX_TS_SET_SIZE)
|
|
{
|
|
Con::warnf("Shape exceeds the maximum node count (%d). Ignoring additional nodes.",
|
|
MAX_TS_SET_SIZE);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Custom name comparison function to compare mesh name and detail size
|
|
bool cmpMeshNameAndSize(const String& key, const Vector<String>& names, void* arg1, void* arg2)
|
|
{
|
|
const Vector<AppMesh*>& meshes = *(Vector<AppMesh*>*)arg1;
|
|
S32 meshSize = (intptr_t)arg2;
|
|
|
|
for (S32 i = 0; i < names.size(); i++)
|
|
{
|
|
if (names[i].compare(key, 0, String::NoCase) == 0)
|
|
{
|
|
if (meshes[i]->detailSize == meshSize)
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void TSShapeLoader::generateObjects()
|
|
{
|
|
for (S32 iSub = 0; iSub < subshapes.size(); iSub++)
|
|
{
|
|
Subshape* subshape = subshapes[iSub];
|
|
shape->subShapeFirstObject.push_back(shape->objects.size());
|
|
|
|
// Get the names and sizes of the meshes for this subshape
|
|
Vector<String> meshNames;
|
|
for (S32 iMesh = 0; iMesh < subshape->objMeshes.size(); iMesh++)
|
|
{
|
|
AppMesh* mesh = subshape->objMeshes[iMesh];
|
|
mesh->detailSize = 2;
|
|
String name = String::GetTrailingNumber( mesh->getName(), mesh->detailSize );
|
|
name = getUniqueName( name, cmpMeshNameAndSize, meshNames, &(subshape->objMeshes), (void*)(uintptr_t)mesh->detailSize );
|
|
meshNames.push_back( name );
|
|
|
|
// Fix up any collision details that don't have a negative detail level.
|
|
if ( dStrStartsWith(meshNames[iMesh], "Collision") ||
|
|
dStrStartsWith(meshNames[iMesh], "LOSCol") )
|
|
{
|
|
if (mesh->detailSize > 0)
|
|
mesh->detailSize = -mesh->detailSize;
|
|
}
|
|
}
|
|
|
|
// An 'object' is a collection of meshes with the same base name and
|
|
// different detail sizes. The object is attached to the node of the
|
|
// highest detail mesh.
|
|
|
|
// Sort the 3 arrays (objMeshes, objNodes, meshNames) by name and size
|
|
for (S32 i = 0; i < subshape->objMeshes.size()-1; i++)
|
|
{
|
|
for (S32 j = i+1; j < subshape->objMeshes.size(); j++)
|
|
{
|
|
if ((meshNames[i].compare(meshNames[j]) < 0) ||
|
|
((meshNames[i].compare(meshNames[j]) == 0) &&
|
|
(subshape->objMeshes[i]->detailSize < subshape->objMeshes[j]->detailSize)))
|
|
{
|
|
{
|
|
AppMesh* tmp = subshape->objMeshes[i];
|
|
subshape->objMeshes[i] = subshape->objMeshes[j];
|
|
subshape->objMeshes[j] = tmp;
|
|
}
|
|
{
|
|
S32 tmp = subshape->objNodes[i];
|
|
subshape->objNodes[i] = subshape->objNodes[j];
|
|
subshape->objNodes[j] = tmp;
|
|
}
|
|
{
|
|
String tmp = meshNames[i];
|
|
meshNames[i] = meshNames[j];
|
|
meshNames[j] = tmp;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now create objects
|
|
const String* lastName = 0;
|
|
for (S32 iMesh = 0; iMesh < subshape->objMeshes.size(); iMesh++)
|
|
{
|
|
AppMesh* mesh = subshape->objMeshes[iMesh];
|
|
|
|
if (!lastName || (meshNames[iMesh] != *lastName))
|
|
{
|
|
shape->objects.increment();
|
|
TSShape::Object& lastObject = shape->objects.last();
|
|
lastObject.nameIndex = shape->addName(meshNames[iMesh]);
|
|
lastObject.nodeIndex = subshape->objNodes[iMesh];
|
|
lastObject.startMeshIndex = appMeshes.size();
|
|
lastObject.numMeshes = 0;
|
|
lastName = &meshNames[iMesh];
|
|
}
|
|
|
|
// Add this mesh to the object
|
|
appMeshes.push_back(mesh);
|
|
shape->objects.last().numMeshes++;
|
|
|
|
// Set mesh flags
|
|
mesh->flags = 0;
|
|
if (mesh->isBillboard())
|
|
{
|
|
mesh->flags |= TSMesh::Billboard;
|
|
if (mesh->isBillboardZAxis())
|
|
mesh->flags |= TSMesh::BillboardZAxis;
|
|
}
|
|
|
|
// Set the detail name... do fixups for collision details.
|
|
const char* detailName = "detail";
|
|
if ( mesh->detailSize < 0 )
|
|
{
|
|
if ( dStrStartsWith(meshNames[iMesh], "Collision") ||
|
|
dStrStartsWith(meshNames[iMesh], "Col") )
|
|
detailName = "Collision";
|
|
else if (dStrStartsWith(meshNames[iMesh], "LOSCol"))
|
|
detailName = "LOS";
|
|
}
|
|
|
|
// Attempt to add the detail (will fail if it already exists)
|
|
S32 oldNumDetails = shape->details.size();
|
|
shape->addDetail(detailName, mesh->detailSize, iSub);
|
|
if (shape->details.size() > oldNumDetails)
|
|
{
|
|
Con::warnf("Object mesh \"%s\" has no matching detail (\"%s%d\" has"
|
|
" been added automatically)", mesh->getName(false), detailName, mesh->detailSize);
|
|
}
|
|
}
|
|
|
|
// Get object count for this subshape
|
|
shape->subShapeNumObjects.push_back(shape->objects.size() - shape->subShapeFirstObject.last());
|
|
}
|
|
}
|
|
|
|
void TSShapeLoader::generateSkins()
|
|
{
|
|
Vector<AppMesh*> skins;
|
|
for (S32 iObject = 0; iObject < shape->objects.size(); iObject++)
|
|
{
|
|
for (S32 iMesh = 0; iMesh < shape->objects[iObject].numMeshes; iMesh++)
|
|
{
|
|
AppMesh* mesh = appMeshes[shape->objects[iObject].startMeshIndex + iMesh];
|
|
if (mesh->isSkin())
|
|
skins.push_back(mesh);
|
|
}
|
|
}
|
|
|
|
for (S32 iSkin = 0; iSkin < skins.size(); iSkin++)
|
|
{
|
|
updateProgress(Load_GenerateSkins, "Generating skins...", skins.size(), iSkin);
|
|
|
|
// Get skin data (bones, vertex weights etc)
|
|
AppMesh* skin = skins[iSkin];
|
|
skin->lookupSkinData();
|
|
|
|
// Just copy initial verts and norms for now
|
|
skin->initialVerts.set(skin->points.address(), skin->vertsPerFrame);
|
|
skin->initialNorms.set(skin->normals.address(), skin->vertsPerFrame);
|
|
|
|
// Map bones to nodes
|
|
skin->nodeIndex.setSize(skin->bones.size());
|
|
for (S32 iBone = 0; iBone < skin->bones.size(); iBone++)
|
|
{
|
|
// Find the node that matches this bone
|
|
skin->nodeIndex[iBone] = -1;
|
|
for (S32 iNode = 0; iNode < appNodes.size(); iNode++)
|
|
{
|
|
if (appNodes[iNode]->isEqual(skin->bones[iBone]))
|
|
{
|
|
delete skin->bones[iBone];
|
|
skin->bones[iBone] = appNodes[iNode];
|
|
skin->nodeIndex[iBone] = iNode;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (skin->nodeIndex[iBone] == -1)
|
|
{
|
|
Con::warnf("Could not find bone %d. Defaulting to first node", iBone);
|
|
skin->nodeIndex[iBone] = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void TSShapeLoader::generateDefaultStates()
|
|
{
|
|
// Generate default object states (includes initial geometry)
|
|
for (S32 iObject = 0; iObject < shape->objects.size(); iObject++)
|
|
{
|
|
updateProgress(Load_GenerateDefaultStates, "Generating initial mesh and node states...",
|
|
shape->objects.size(), iObject);
|
|
|
|
TSShape::Object& obj = shape->objects[iObject];
|
|
|
|
// Calculate the objectOffset for each mesh at T=0
|
|
for (S32 iMesh = 0; iMesh < obj.numMeshes; iMesh++)
|
|
{
|
|
AppMesh* appMesh = appMeshes[obj.startMeshIndex + iMesh];
|
|
AppNode* appNode = obj.nodeIndex >= 0 ? appNodes[obj.nodeIndex] : boundsNode;
|
|
|
|
MatrixF meshMat(appMesh->getMeshTransform(DefaultTime));
|
|
MatrixF nodeMat(appMesh->isSkin() ? meshMat : appNode->getNodeTransform(DefaultTime));
|
|
|
|
zapScale(nodeMat);
|
|
|
|
appMesh->objectOffset = nodeMat.inverse() * meshMat;
|
|
}
|
|
|
|
generateObjectState(shape->objects[iObject], DefaultTime, true, true);
|
|
}
|
|
|
|
// Generate default node transforms
|
|
for (S32 iNode = 0; iNode < appNodes.size(); iNode++)
|
|
{
|
|
// Determine the default translation and rotation for the node
|
|
QuatF rot, srot;
|
|
Point3F trans, scale;
|
|
generateNodeTransform(appNodes[iNode], DefaultTime, false, 0, rot, trans, srot, scale);
|
|
|
|
// Add default node translation and rotation
|
|
addNodeRotation(rot, true);
|
|
addNodeTranslation(trans, true);
|
|
}
|
|
}
|
|
|
|
void TSShapeLoader::generateObjectState(TSShape::Object& obj, F32 t, bool addFrame, bool addMatFrame)
|
|
{
|
|
shape->objectStates.increment();
|
|
TSShape::ObjectState& state = shape->objectStates.last();
|
|
|
|
state.frameIndex = 0;
|
|
state.matFrameIndex = 0;
|
|
state.vis = mClampF(appMeshes[obj.startMeshIndex]->getVisValue(t), 0.0f, 1.0f);
|
|
|
|
if (addFrame || addMatFrame)
|
|
{
|
|
generateFrame(obj, t, addFrame, addMatFrame);
|
|
|
|
// set the frame number for the object state
|
|
state.frameIndex = appMeshes[obj.startMeshIndex]->numFrames - 1;
|
|
state.matFrameIndex = appMeshes[obj.startMeshIndex]->numMatFrames - 1;
|
|
}
|
|
}
|
|
|
|
void TSShapeLoader::generateFrame(TSShape::Object& obj, F32 t, bool addFrame, bool addMatFrame)
|
|
{
|
|
for (S32 iMesh = 0; iMesh < obj.numMeshes; iMesh++)
|
|
{
|
|
AppMesh* appMesh = appMeshes[obj.startMeshIndex + iMesh];
|
|
|
|
U32 oldNumPoints = appMesh->points.size();
|
|
U32 oldNumUvs = appMesh->uvs.size();
|
|
|
|
// Get the mesh geometry at time, 't'
|
|
// Geometry verts, normals and tverts can be animated (different set for
|
|
// each frame), but the TSDrawPrimitives stay the same, so the way lockMesh
|
|
// works is that it will only generate the primitives once, then after that
|
|
// will just append verts, normals and tverts each time it is called.
|
|
appMesh->lockMesh(t, appMesh->objectOffset);
|
|
|
|
// Calculate vertex normals if required
|
|
if (appMesh->normals.size() != appMesh->points.size())
|
|
appMesh->computeNormals();
|
|
|
|
// If this is the first call, set the number of points per frame
|
|
if (appMesh->numFrames == 0)
|
|
{
|
|
appMesh->vertsPerFrame = appMesh->points.size();
|
|
}
|
|
else
|
|
{
|
|
// Check frame topology => ie. that the right number of points, normals
|
|
// and tverts was added
|
|
if ((appMesh->points.size() - oldNumPoints) != appMesh->vertsPerFrame)
|
|
{
|
|
Con::warnf("Wrong number of points (%d) added at time=%f (expected %d)",
|
|
appMesh->points.size() - oldNumPoints, t, appMesh->vertsPerFrame);
|
|
addFrame = false;
|
|
}
|
|
if ((appMesh->normals.size() - oldNumPoints) != appMesh->vertsPerFrame)
|
|
{
|
|
Con::warnf("Wrong number of normals (%d) added at time=%f (expected %d)",
|
|
appMesh->normals.size() - oldNumPoints, t, appMesh->vertsPerFrame);
|
|
addFrame = false;
|
|
}
|
|
if ((appMesh->uvs.size() - oldNumUvs) != appMesh->vertsPerFrame)
|
|
{
|
|
Con::warnf("Wrong number of tverts (%d) added at time=%f (expected %d)",
|
|
appMesh->uvs.size() - oldNumUvs, t, appMesh->vertsPerFrame);
|
|
addMatFrame = false;
|
|
}
|
|
}
|
|
|
|
// Because lockMesh adds points, normals AND tverts each call, if we didn't
|
|
// actually want another frame or matFrame, we need to remove them afterwards.
|
|
// In the common case (we DO want the frame), we can do nothing => the
|
|
// points/normals/tverts are already in place!
|
|
if (addFrame)
|
|
{
|
|
appMesh->numFrames++;
|
|
}
|
|
else
|
|
{
|
|
appMesh->points.setSize(oldNumPoints);
|
|
appMesh->normals.setSize(oldNumPoints);
|
|
}
|
|
|
|
if (addMatFrame)
|
|
{
|
|
appMesh->numMatFrames++;
|
|
}
|
|
else
|
|
{
|
|
appMesh->uvs.setSize(oldNumPoints);
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Materials
|
|
|
|
/// Convert all Collada materials into a single TSMaterialList
|
|
void TSShapeLoader::generateMaterialList()
|
|
{
|
|
// Install the materials into the material list
|
|
shape->materialList = new TSMaterialList;
|
|
for (S32 iMat = 0; iMat < AppMesh::appMaterials.size(); iMat++)
|
|
{
|
|
updateProgress(Load_GenerateMaterials, "Generating materials...", AppMesh::appMaterials.size(), iMat);
|
|
|
|
AppMaterial* appMat = AppMesh::appMaterials[iMat];
|
|
shape->materialList->push_back(appMat->getName(), appMat->getFlags(), U32(-1), U32(-1), U32(-1), 1.0f, appMat->getReflectance());
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Animation Sequences
|
|
|
|
void TSShapeLoader::generateSequences()
|
|
{
|
|
for (S32 iSeq = 0; iSeq < appSequences.size(); iSeq++)
|
|
{
|
|
updateProgress(Load_GenerateSequences, "Generating sequences...", appSequences.size(), iSeq);
|
|
|
|
// Initialize the sequence
|
|
appSequences[iSeq]->setActive(true);
|
|
|
|
shape->sequences.increment();
|
|
TSShape::Sequence& seq = shape->sequences.last();
|
|
|
|
seq.nameIndex = shape->addName(appSequences[iSeq]->getName());
|
|
seq.toolBegin = appSequences[iSeq]->getStart();
|
|
seq.priority = appSequences[iSeq]->getPriority();
|
|
seq.flags = appSequences[iSeq]->getFlags();
|
|
|
|
// Compute duration and number of keyframes (then adjust time between frames to match)
|
|
seq.duration = appSequences[iSeq]->getEnd() - appSequences[iSeq]->getStart();
|
|
seq.numKeyframes = (S32)(seq.duration * appSequences[iSeq]->fps + 0.5f) + 1;
|
|
|
|
seq.sourceData.start = 0;
|
|
seq.sourceData.end = seq.numKeyframes-1;
|
|
seq.sourceData.total = seq.numKeyframes;
|
|
|
|
// Set membership arrays (ie. which nodes and objects are affected by this sequence)
|
|
setNodeMembership(seq, appSequences[iSeq]);
|
|
setObjectMembership(seq, appSequences[iSeq]);
|
|
|
|
// Generate keyframes
|
|
generateNodeAnimation(seq);
|
|
generateObjectAnimation(seq, appSequences[iSeq]);
|
|
generateGroundAnimation(seq, appSequences[iSeq]);
|
|
generateFrameTriggers(seq, appSequences[iSeq]);
|
|
|
|
// Set sequence flags
|
|
seq.dirtyFlags = 0;
|
|
if (seq.rotationMatters.testAll() || seq.translationMatters.testAll() || seq.scaleMatters.testAll())
|
|
seq.dirtyFlags |= TSShapeInstance::TransformDirty;
|
|
if (seq.visMatters.testAll())
|
|
seq.dirtyFlags |= TSShapeInstance::VisDirty;
|
|
if (seq.frameMatters.testAll())
|
|
seq.dirtyFlags |= TSShapeInstance::FrameDirty;
|
|
if (seq.matFrameMatters.testAll())
|
|
seq.dirtyFlags |= TSShapeInstance::MatFrameDirty;
|
|
|
|
// Set shape flags (only the most significant scale type)
|
|
U32 curVal = shape->mFlags & TSShape::AnyScale;
|
|
shape->mFlags &= ~(TSShape::AnyScale);
|
|
shape->mFlags |= getMax(curVal, seq.flags & TSShape::AnyScale); // take the larger value (can only convert upwards)
|
|
|
|
appSequences[iSeq]->setActive(false);
|
|
}
|
|
}
|
|
|
|
void TSShapeLoader::setNodeMembership(TSShape::Sequence& seq, const AppSequence* appSeq)
|
|
{
|
|
seq.rotationMatters.clearAll(); // node rotation (size = nodes.size())
|
|
seq.translationMatters.clearAll(); // node translation (size = nodes.size())
|
|
seq.scaleMatters.clearAll(); // node scale (size = nodes.size())
|
|
|
|
// This shouldn't be allowed, but check anyway...
|
|
if (seq.numKeyframes < 2)
|
|
return;
|
|
|
|
// Note: this fills the cache with current sequence data. Methods that get
|
|
// called later (e.g. generateNodeAnimation) use this info (and assume it's set).
|
|
fillNodeTransformCache(seq, appSeq);
|
|
|
|
// Test to see if the transform changes over the interval in order to decide
|
|
// whether to animate the transform in 3space. We don't use app's mechanism
|
|
// for doing this because it functions different in different apps and we do
|
|
// some special stuff with scale.
|
|
setRotationMembership(seq);
|
|
setTranslationMembership(seq);
|
|
setScaleMembership(seq);
|
|
}
|
|
|
|
void TSShapeLoader::setRotationMembership(TSShape::Sequence& seq)
|
|
{
|
|
for (S32 iNode = 0; iNode < appNodes.size(); iNode++)
|
|
{
|
|
// Check if any of the node rotations are different to
|
|
// the default rotation
|
|
QuatF defaultRot;
|
|
shape->defaultRotations[iNode].getQuatF(&defaultRot);
|
|
|
|
for (S32 iFrame = 0; iFrame < seq.numKeyframes; iFrame++)
|
|
{
|
|
if (nodeRotCache[iNode][iFrame] != defaultRot)
|
|
{
|
|
seq.rotationMatters.set(iNode);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void TSShapeLoader::setTranslationMembership(TSShape::Sequence& seq)
|
|
{
|
|
for (S32 iNode = 0; iNode < appNodes.size(); iNode++)
|
|
{
|
|
// Check if any of the node translations are different to
|
|
// the default translation
|
|
Point3F& defaultTrans = shape->defaultTranslations[iNode];
|
|
|
|
for (S32 iFrame = 0; iFrame < seq.numKeyframes; iFrame++)
|
|
{
|
|
if (!nodeTransCache[iNode][iFrame].equal(defaultTrans))
|
|
{
|
|
seq.translationMatters.set(iNode);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void TSShapeLoader::setScaleMembership(TSShape::Sequence& seq)
|
|
{
|
|
Point3F unitScale(1,1,1);
|
|
|
|
U32 arbitraryScaleCount = 0;
|
|
U32 alignedScaleCount = 0;
|
|
U32 uniformScaleCount = 0;
|
|
|
|
for (S32 iNode = 0; iNode < appNodes.size(); iNode++)
|
|
{
|
|
// Check if any of the node scales are not the unit scale
|
|
for (S32 iFrame = 0; iFrame < seq.numKeyframes; iFrame++)
|
|
{
|
|
Point3F& scale = nodeScaleCache[iNode][iFrame];
|
|
if (!unitScale.equal(scale))
|
|
{
|
|
// Determine what type of scale this is
|
|
if (!nodeScaleRotCache[iNode][iFrame].isIdentity())
|
|
arbitraryScaleCount++;
|
|
else if (scale.x != scale.y || scale.y != scale.z)
|
|
alignedScaleCount++;
|
|
else
|
|
uniformScaleCount++;
|
|
|
|
seq.scaleMatters.set(iNode);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Only one type of scale is animated
|
|
if (arbitraryScaleCount)
|
|
seq.flags |= TSShape::ArbitraryScale;
|
|
else if (alignedScaleCount)
|
|
seq.flags |= TSShape::AlignedScale;
|
|
else if (uniformScaleCount)
|
|
seq.flags |= TSShape::UniformScale;
|
|
}
|
|
|
|
void TSShapeLoader::setObjectMembership(TSShape::Sequence& seq, const AppSequence* appSeq)
|
|
{
|
|
seq.visMatters.clearAll(); // object visibility (size = objects.size())
|
|
seq.frameMatters.clearAll(); // vert animation (morph) (size = objects.size())
|
|
seq.matFrameMatters.clearAll(); // UV animation (size = objects.size())
|
|
|
|
for (S32 iObject = 0; iObject < shape->objects.size(); iObject++)
|
|
{
|
|
if (!appMeshes[shape->objects[iObject].startMeshIndex])
|
|
continue;
|
|
|
|
if (appMeshes[shape->objects[iObject].startMeshIndex]->animatesVis(appSeq))
|
|
seq.visMatters.set(iObject);
|
|
// Morph and UV animation has been deprecated
|
|
//if (appMeshes[shape->objects[iObject].startMeshIndex]->animatesFrame(appSeq))
|
|
//seq.frameMatters.set(iObject);
|
|
//if (appMeshes[shape->objects[iObject].startMeshIndex]->animatesMatFrame(appSeq))
|
|
//seq.matFrameMatters.set(iObject);
|
|
}
|
|
}
|
|
|
|
void TSShapeLoader::clearNodeTransformCache()
|
|
{
|
|
// clear out the transform caches
|
|
for (S32 i = 0; i < nodeRotCache.size(); i++)
|
|
delete [] nodeRotCache[i];
|
|
nodeRotCache.clear();
|
|
for (S32 i = 0; i < nodeTransCache.size(); i++)
|
|
delete [] nodeTransCache[i];
|
|
nodeTransCache.clear();
|
|
for (S32 i = 0; i < nodeScaleRotCache.size(); i++)
|
|
delete [] nodeScaleRotCache[i];
|
|
nodeScaleRotCache.clear();
|
|
for (S32 i = 0; i < nodeScaleCache.size(); i++)
|
|
delete [] nodeScaleCache[i];
|
|
nodeScaleCache.clear();
|
|
}
|
|
|
|
void TSShapeLoader::fillNodeTransformCache(TSShape::Sequence& seq, const AppSequence* appSeq)
|
|
{
|
|
// clear out the transform caches and set it up for this sequence
|
|
clearNodeTransformCache();
|
|
|
|
nodeRotCache.setSize(appNodes.size());
|
|
for (S32 i = 0; i < nodeRotCache.size(); i++)
|
|
nodeRotCache[i] = new QuatF[seq.numKeyframes];
|
|
nodeTransCache.setSize(appNodes.size());
|
|
for (S32 i = 0; i < nodeTransCache.size(); i++)
|
|
nodeTransCache[i] = new Point3F[seq.numKeyframes];
|
|
nodeScaleRotCache.setSize(appNodes.size());
|
|
for (S32 i = 0; i < nodeScaleRotCache.size(); i++)
|
|
nodeScaleRotCache[i] = new QuatF[seq.numKeyframes];
|
|
nodeScaleCache.setSize(appNodes.size());
|
|
for (S32 i = 0; i < nodeScaleCache.size(); i++)
|
|
nodeScaleCache[i] = new Point3F[seq.numKeyframes];
|
|
|
|
// get the node transforms for every frame
|
|
for (S32 iFrame = 0; iFrame < seq.numKeyframes; iFrame++)
|
|
{
|
|
F32 time = appSeq->getStart() + seq.duration * iFrame / getMax(1, seq.numKeyframes - 1);
|
|
for (S32 iNode = 0; iNode < appNodes.size(); iNode++)
|
|
{
|
|
generateNodeTransform(appNodes[iNode], time, seq.isBlend(), appSeq->getBlendRefTime(),
|
|
nodeRotCache[iNode][iFrame], nodeTransCache[iNode][iFrame],
|
|
nodeScaleRotCache[iNode][iFrame], nodeScaleCache[iNode][iFrame]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TSShapeLoader::addNodeRotation(QuatF& rot, bool defaultVal)
|
|
{
|
|
Quat16 rot16;
|
|
rot16.set(rot);
|
|
|
|
if (!defaultVal)
|
|
shape->nodeRotations.push_back(rot16);
|
|
else
|
|
shape->defaultRotations.push_back(rot16);
|
|
}
|
|
|
|
void TSShapeLoader::addNodeTranslation(Point3F& trans, bool defaultVal)
|
|
{
|
|
if (!defaultVal)
|
|
shape->nodeTranslations.push_back(trans);
|
|
else
|
|
shape->defaultTranslations.push_back(trans);
|
|
}
|
|
|
|
void TSShapeLoader::addNodeUniformScale(F32 scale)
|
|
{
|
|
shape->nodeUniformScales.push_back(scale);
|
|
}
|
|
|
|
void TSShapeLoader::addNodeAlignedScale(Point3F& scale)
|
|
{
|
|
shape->nodeAlignedScales.push_back(scale);
|
|
}
|
|
|
|
void TSShapeLoader::addNodeArbitraryScale(QuatF& qrot, Point3F& scale)
|
|
{
|
|
Quat16 rot16;
|
|
rot16.set(qrot);
|
|
shape->nodeArbitraryScaleRots.push_back(rot16);
|
|
shape->nodeArbitraryScaleFactors.push_back(scale);
|
|
}
|
|
|
|
void TSShapeLoader::generateNodeAnimation(TSShape::Sequence& seq)
|
|
{
|
|
seq.baseRotation = shape->nodeRotations.size();
|
|
seq.baseTranslation = shape->nodeTranslations.size();
|
|
seq.baseScale = (seq.flags & TSShape::ArbitraryScale) ? shape->nodeArbitraryScaleRots.size() :
|
|
(seq.flags & TSShape::AlignedScale) ? shape->nodeAlignedScales.size() :
|
|
shape->nodeUniformScales.size();
|
|
|
|
for (S32 iNode = 0; iNode < appNodes.size(); iNode++)
|
|
{
|
|
for (S32 iFrame = 0; iFrame < seq.numKeyframes; iFrame++)
|
|
{
|
|
if (seq.rotationMatters.test(iNode))
|
|
addNodeRotation(nodeRotCache[iNode][iFrame], false);
|
|
if (seq.translationMatters.test(iNode))
|
|
addNodeTranslation(nodeTransCache[iNode][iFrame], false);
|
|
if (seq.scaleMatters.test(iNode))
|
|
{
|
|
QuatF& rot = nodeScaleRotCache[iNode][iFrame];
|
|
Point3F scale = nodeScaleCache[iNode][iFrame];
|
|
|
|
if (seq.flags & TSShape::ArbitraryScale)
|
|
addNodeArbitraryScale(rot, scale);
|
|
else if (seq.flags & TSShape::AlignedScale)
|
|
addNodeAlignedScale(scale);
|
|
else if (seq.flags & TSShape::UniformScale)
|
|
addNodeUniformScale((scale.x+scale.y+scale.z)/3.0f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void TSShapeLoader::generateObjectAnimation(TSShape::Sequence& seq, const AppSequence* appSeq)
|
|
{
|
|
seq.baseObjectState = shape->objectStates.size();
|
|
|
|
for (S32 iObject = 0; iObject < shape->objects.size(); iObject++)
|
|
{
|
|
bool visMatters = seq.visMatters.test(iObject);
|
|
bool frameMatters = seq.frameMatters.test(iObject);
|
|
bool matFrameMatters = seq.matFrameMatters.test(iObject);
|
|
|
|
if (visMatters || frameMatters || matFrameMatters)
|
|
{
|
|
for (S32 iFrame = 0; iFrame < seq.numKeyframes; iFrame++)
|
|
{
|
|
F32 time = appSeq->getStart() + seq.duration * iFrame / getMax(1, seq.numKeyframes - 1);
|
|
generateObjectState(shape->objects[iObject], time, frameMatters, matFrameMatters);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void TSShapeLoader::generateGroundAnimation(TSShape::Sequence& seq, const AppSequence* appSeq)
|
|
{
|
|
seq.firstGroundFrame = shape->groundTranslations.size();
|
|
seq.numGroundFrames = 0;
|
|
|
|
if (!boundsNode)
|
|
return;
|
|
|
|
// Check if the bounds node is animated by this sequence
|
|
seq.numGroundFrames = (S32)((seq.duration + 0.25f/AppGroundFrameRate) * AppGroundFrameRate);
|
|
|
|
seq.flags |= TSShape::MakePath;
|
|
|
|
// Get ground transform at the start of the sequence
|
|
MatrixF invStartMat = boundsNode->getNodeTransform(appSeq->getStart());
|
|
zapScale(invStartMat);
|
|
invStartMat.inverse();
|
|
|
|
for (S32 iFrame = 0; iFrame < seq.numGroundFrames; iFrame++)
|
|
{
|
|
F32 time = appSeq->getStart() + seq.duration * iFrame / getMax(1, seq.numGroundFrames - 1);
|
|
|
|
// Determine delta bounds node transform at 't'
|
|
MatrixF mat = boundsNode->getNodeTransform(time);
|
|
zapScale(mat);
|
|
mat = invStartMat * mat;
|
|
|
|
// Add ground transform
|
|
Quat16 rotation;
|
|
rotation.set(QuatF(mat));
|
|
shape->groundTranslations.push_back(mat.getPosition());
|
|
shape->groundRotations.push_back(rotation);
|
|
}
|
|
}
|
|
|
|
void TSShapeLoader::generateFrameTriggers(TSShape::Sequence& seq, const AppSequence* appSeq)
|
|
{
|
|
// Initialize triggers
|
|
seq.firstTrigger = shape->triggers.size();
|
|
seq.numTriggers = appSeq->getNumTriggers();
|
|
if (!seq.numTriggers)
|
|
return;
|
|
|
|
seq.flags |= TSShape::MakePath;
|
|
|
|
// Add triggers
|
|
for (S32 iTrigger = 0; iTrigger < seq.numTriggers; iTrigger++)
|
|
{
|
|
shape->triggers.increment();
|
|
appSeq->getTrigger(iTrigger, shape->triggers.last());
|
|
}
|
|
|
|
// Track the triggers that get turned off by this shape...normally, triggers
|
|
// aren't turned on/off, just on...if we are a trigger that does both then we
|
|
// need to mark ourselves as such so that on/off can become off/on when sequence
|
|
// is played in reverse...
|
|
U32 offTriggers = 0;
|
|
for (S32 iTrigger = 0; iTrigger < seq.numTriggers; iTrigger++)
|
|
{
|
|
U32 state = shape->triggers[seq.firstTrigger+iTrigger].state;
|
|
if ((state & TSShape::Trigger::StateOn) == 0)
|
|
offTriggers |= (state & TSShape::Trigger::StateMask);
|
|
}
|
|
|
|
// We now know which states are turned off, set invert on all those (including when turned on)
|
|
for (int iTrigger = 0; iTrigger < seq.numTriggers; iTrigger++)
|
|
{
|
|
if (shape->triggers[seq.firstTrigger + iTrigger].state & offTriggers)
|
|
shape->triggers[seq.firstTrigger + iTrigger].state |= TSShape::Trigger::InvertOnReverse;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TSShapeLoader::sortDetails()
|
|
{
|
|
// Sort objects by: transparency, material index and node index
|
|
|
|
|
|
// Insert NULL meshes where required
|
|
for (S32 iSub = 0; iSub < subshapes.size(); iSub++)
|
|
{
|
|
Vector<S32> validDetails;
|
|
shape->getSubShapeDetails(iSub, validDetails);
|
|
|
|
for (S32 iDet = 0; iDet < validDetails.size(); iDet++)
|
|
{
|
|
TSShape::Detail &detail = shape->details[validDetails[iDet]];
|
|
if (detail.subShapeNum >= 0)
|
|
detail.objectDetailNum = iDet;
|
|
|
|
for (S32 iObj = shape->subShapeFirstObject[iSub];
|
|
iObj < (shape->subShapeFirstObject[iSub] + shape->subShapeNumObjects[iSub]);
|
|
iObj++)
|
|
{
|
|
TSShape::Object &object = shape->objects[iObj];
|
|
|
|
// Insert a NULL mesh for this detail level if required (ie. if the
|
|
// object does not already have a mesh with an equal or higher detail)
|
|
S32 meshIndex = (iDet < object.numMeshes) ? iDet : object.numMeshes-1;
|
|
|
|
if (appMeshes[object.startMeshIndex + meshIndex]->detailSize < shape->details[iDet].size)
|
|
{
|
|
// Add a NULL mesh
|
|
appMeshes.insert(object.startMeshIndex + iDet, NULL);
|
|
object.numMeshes++;
|
|
|
|
// Fixup the start index for the other objects
|
|
for (S32 k = iObj+1; k < shape->objects.size(); k++)
|
|
shape->objects[k].startMeshIndex++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Install into the TSShape, the shape is expected to be empty.
|
|
// Data is not copied, the TSShape is modified to point to memory
|
|
// managed by this object. This object is also bound to the TSShape
|
|
// object and will be deleted when it's deleted.
|
|
void TSShapeLoader::install()
|
|
{
|
|
// Arrays that are filled in by ts shape init, but need
|
|
// to be allocated beforehand.
|
|
shape->subShapeFirstTranslucentObject.setSize(shape->subShapeFirstObject.size());
|
|
|
|
// Construct TS sub-meshes
|
|
shape->meshes.setSize(appMeshes.size());
|
|
for (U32 m = 0; m < appMeshes.size(); m++)
|
|
shape->meshes[m] = appMeshes[m] ? appMeshes[m]->constructTSMesh() : NULL;
|
|
|
|
// Remove empty meshes and objects
|
|
for (S32 iObj = shape->objects.size()-1; iObj >= 0; iObj--)
|
|
{
|
|
TSShape::Object& obj = shape->objects[iObj];
|
|
for (S32 iMesh = obj.numMeshes-1; iMesh >= 0; iMesh--)
|
|
{
|
|
TSMesh *mesh = shape->meshes[obj.startMeshIndex + iMesh];
|
|
|
|
if (mesh && !mesh->mPrimitives.size())
|
|
{
|
|
S32 oldMeshCount = obj.numMeshes;
|
|
destructInPlace(mesh);
|
|
shape->removeMeshFromObject(iObj, iMesh);
|
|
iMesh -= (oldMeshCount - obj.numMeshes - 1); // handle when more than one mesh is removed
|
|
}
|
|
}
|
|
|
|
if (!obj.numMeshes)
|
|
shape->removeObject(shape->getName(obj.nameIndex));
|
|
}
|
|
|
|
// Add a dummy object if needed so the shape loads and renders ok
|
|
if (!shape->details.size())
|
|
{
|
|
shape->addDetail("detail", 2, 0);
|
|
shape->subShapeNumObjects.last() = 1;
|
|
|
|
shape->meshes.push_back(NULL);
|
|
|
|
shape->objects.increment();
|
|
|
|
TSShape::Object& lastObject = shape->objects.last();
|
|
lastObject.nameIndex = shape->addName("dummy");
|
|
lastObject.nodeIndex = 0;
|
|
lastObject.startMeshIndex = 0;
|
|
lastObject.numMeshes = 1;
|
|
|
|
shape->objectStates.increment();
|
|
shape->objectStates.last().frameIndex = 0;
|
|
shape->objectStates.last().matFrameIndex = 0;
|
|
shape->objectStates.last().vis = 1.0f;
|
|
}
|
|
|
|
// Update smallest visible detail
|
|
shape->mSmallestVisibleDL = -1;
|
|
shape->mSmallestVisibleSize = 999999;
|
|
for (S32 i = 0; i < shape->details.size(); i++)
|
|
{
|
|
if ((shape->details[i].size >= 0) &&
|
|
(shape->details[i].size < shape->mSmallestVisibleSize))
|
|
{
|
|
shape->mSmallestVisibleDL = i;
|
|
shape->mSmallestVisibleSize = shape->details[i].size;
|
|
}
|
|
}
|
|
|
|
computeBounds(shape->mBounds);
|
|
if (!shape->mBounds.isValidBox())
|
|
shape->mBounds = Box3F(1.0f);
|
|
|
|
shape->mBounds.getCenter(&shape->center);
|
|
shape->mRadius = (shape->mBounds.maxExtents - shape->center).len();
|
|
shape->tubeRadius = shape->mRadius;
|
|
|
|
shape->init();
|
|
shape->finalizeEditable();
|
|
}
|
|
|
|
void TSShapeLoader::computeBounds(Box3F& bounds)
|
|
{
|
|
// Compute the box that encloses the model geometry
|
|
bounds = Box3F::Invalid;
|
|
|
|
// Use bounds node geometry if present
|
|
if ( boundsNode && boundsNode->getNumMesh() )
|
|
{
|
|
for (S32 iMesh = 0; iMesh < boundsNode->getNumMesh(); iMesh++)
|
|
{
|
|
AppMesh* mesh = boundsNode->getMesh( iMesh );
|
|
if ( !mesh )
|
|
continue;
|
|
|
|
Box3F meshBounds;
|
|
mesh->computeBounds( meshBounds );
|
|
if ( meshBounds.isValidBox() )
|
|
bounds.intersect( meshBounds );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Compute bounds based on all geometry in the model
|
|
for (S32 iMesh = 0; iMesh < appMeshes.size(); iMesh++)
|
|
{
|
|
AppMesh* mesh = appMeshes[iMesh];
|
|
if ( !mesh )
|
|
continue;
|
|
|
|
Box3F meshBounds;
|
|
mesh->computeBounds( meshBounds );
|
|
if ( meshBounds.isValidBox() )
|
|
bounds.intersect( meshBounds );
|
|
}
|
|
}
|
|
}
|
|
|
|
TSShapeLoader::~TSShapeLoader()
|
|
{
|
|
clearNodeTransformCache();
|
|
|
|
// Clear shared AppMaterial list
|
|
for (S32 iMat = 0; iMat < AppMesh::appMaterials.size(); iMat++)
|
|
delete AppMesh::appMaterials[iMat];
|
|
AppMesh::appMaterials.clear();
|
|
|
|
// Delete Subshapes
|
|
delete boundsNode;
|
|
for (S32 iSub = 0; iSub < subshapes.size(); iSub++)
|
|
delete subshapes[iSub];
|
|
|
|
// Delete AppSequences
|
|
for (S32 iSeq = 0; iSeq < appSequences.size(); iSeq++)
|
|
delete appSequences[iSeq];
|
|
appSequences.clear();
|
|
}
|
|
|
|
// Static functions to handle supported formats for shape loader.
|
|
void TSShapeLoader::addFormat(String name, String extension)
|
|
{
|
|
ShapeFormat newFormat;
|
|
newFormat.mName = name;
|
|
newFormat.mExtension = extension;
|
|
smFormats.push_back(newFormat);
|
|
}
|
|
|
|
String TSShapeLoader::getFormatExtensions()
|
|
{
|
|
// "*.dsq TAB *.dae TAB
|
|
StringBuilder output;
|
|
for(U32 n = 0; n < smFormats.size(); ++n)
|
|
{
|
|
output.append("*.");
|
|
output.append(smFormats[n].mExtension);
|
|
output.append("\t");
|
|
}
|
|
return output.end();
|
|
}
|
|
|
|
String TSShapeLoader::getFormatFilters()
|
|
{
|
|
// "DSQ Files|*.dsq|COLLADA Files|*.dae|"
|
|
StringBuilder output;
|
|
for(U32 n = 0; n < smFormats.size(); ++n)
|
|
{
|
|
output.append(smFormats[n].mName);
|
|
output.append("|*.");
|
|
output.append(smFormats[n].mExtension);
|
|
output.append("|");
|
|
}
|
|
return output.end();
|
|
}
|
|
|
|
bool TSShapeLoader::isSupportedFormat(String extension)
|
|
{
|
|
String extLower = String::ToLower(extension);
|
|
for (U32 n = 0; n < smFormats.size(); ++n)
|
|
{
|
|
if (smFormats[n].mExtension.equal(extLower))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
DefineEngineFunction( getFormatExtensions, const char*, ( ),,
|
|
"Returns a list of supported shape format extensions separated by tabs."
|
|
"Example output: *.dsq TAB *.dae TAB")
|
|
{
|
|
return Con::getReturnBuffer(TSShapeLoader::getFormatExtensions());
|
|
}
|
|
|
|
DefineEngineFunction( getFormatFilters, const char*, ( ),,
|
|
"Returns a list of supported shape formats in filter form.\n"
|
|
"Example output: DSQ Files|*.dsq|COLLADA Files|*.dae|")
|
|
{
|
|
return Con::getReturnBuffer(TSShapeLoader::getFormatFilters());
|
|
}
|
|
|
|
DefineEngineFunction(isSupportedFormat, bool, (const char* extension), , "")
|
|
{
|
|
return TSShapeLoader::isSupportedFormat(extension);
|
|
}
|