Merge pull request #1593 from marauder2k9-torque/AssimpImportFixes
Some checks are pending
Linux Build / ${{matrix.config.name}} (map[build_type:Release cc:gcc cxx:g++ generator:Ninja name:Ubuntu Latest GCC]) (push) Waiting to run
MacOSX Build / ${{matrix.config.name}} (map[build_type:Release cc:clang cxx:clang++ generator:Ninja name:MacOSX Latest Clang]) (push) Waiting to run
Windows Build / ${{matrix.config.name}} (map[build_type:Release cc:cl cxx:cl environment_script:C:/Program Files (x86)/Microsoft Visual Studio/2022/Enterprise/VC/Auxiliary/Build/vcvars64.bat generator:Visual Studio 17 2022 name:Windows Latest MSVC]) (push) Waiting to run

fix assimp import
This commit is contained in:
Brian Roberts 2025-11-25 19:10:53 -06:00 committed by GitHub
commit 95482349e6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 153 additions and 233 deletions

View file

@ -1359,16 +1359,19 @@ static bool enumDTSForImport(const char* shapePath, GuiTreeViewCtrl* tree)
stats.numMeshes++;
}
// Get material count
for (S32 i = 0; i < dtsShape->materialList->size(); i++)
if (dtsShape->materialList)
{
S32 matId = tree->insertItem(matsID, dtsShape->materialList->getMaterialName(i).c_str(), "", "", 0, 0);
stats.numMaterials++;
GFXTextureObject* difTex = dtsShape->materialList->getDiffuseTexture(i);
if (difTex)
// Get material count
for (S32 i = 0; i < dtsShape->materialList->size(); i++)
{
tree->insertItem(matId, difTex->getPath().c_str(), "", "", 0, 0);
S32 matId = tree->insertItem(matsID, dtsShape->materialList->getMaterialName(i).c_str(), "", "", 0, 0);
stats.numMaterials++;
GFXTextureObject* difTex = dtsShape->materialList->getDiffuseTexture(i);
if (difTex)
{
tree->insertItem(matId, difTex->getPath().c_str(), "", "", 0, 0);
}
}
}
@ -2003,7 +2006,7 @@ void AssetImporter::processShapeAsset(AssetImportObject* assetItem)
{
enumColladaForImport(filePath, shapeInfo, false);
}
else if (fileExt.compare("dts") == 0)
else if ((fileExt.compare("dts") == 0) || (fileExt.compare("dsq") == 0))
{
enumDTSForImport(filePath, shapeInfo);
}
@ -2097,7 +2100,7 @@ void AssetImporter::processShapeAnimationAsset(AssetImportObject* assetItem)
{
enumColladaForImport(filePath, shapeInfo, false);
}
else if (fileExt.compare("dts") == 0)
else if ((fileExt.compare("dts") == 0)|| (fileExt.compare("dsq") == 0))
{
enumDTSForImport(filePath, shapeInfo);
}

View file

@ -47,106 +47,6 @@ S32 AssimpAppMesh::fixedSize = 2;
//------------------------------------------------------------------------------
void AssimpAppMesh::computeBounds(Box3F& bounds)
{
bounds = Box3F::Invalid;
if (isSkin())
{
// Compute bounds for skinned mesh
Vector<MatrixF> boneTransforms;
boneTransforms.setSize(nodeIndex.size());
// Calculate bone transformations
for (S32 iBone = 0; iBone < boneTransforms.size(); iBone++) {
MatrixF nodeMat = bones[iBone]->getNodeTransform(TSShapeLoader::DefaultTime);
TSShapeLoader::zapScale(nodeMat); // Remove scaling to ensure uniform transformation
boneTransforms[iBone].mul(nodeMat, initialTransforms[iBone]);
}
// Transform vertices using weighted bone transformations
Vector<Point3F> transformedVerts;
transformedVerts.setSize(initialVerts.size());
transformedVerts.fill(Point3F::Zero);
for (S32 iWeight = 0; iWeight < vertexIndex.size(); iWeight++) {
const S32 vertIndex = vertexIndex[iWeight];
const MatrixF& deltaTransform = boneTransforms[boneIndex[iWeight]];
Point3F weightedVert;
deltaTransform.mulP(initialVerts[vertIndex], &weightedVert);
weightedVert *= weight[iWeight];
transformedVerts[vertIndex] += weightedVert;
}
// Extend bounds using the transformed vertices
for (const auto& vert : transformedVerts) {
bounds.extend(vert);
}
}
else
{
MatrixF transform = getMeshTransform(TSShapeLoader::DefaultTime);
TSShapeLoader::zapScale(transform);
for (S32 iVert = 0; iVert < points.size(); iVert++)
{
Point3F p;
transform.mulP(points[iVert], &p);
bounds.extend(p);
}
}
}
TSMesh* AssimpAppMesh::constructTSMesh()
{
if (points.empty() || normals.empty() || primitives.empty() || indices.empty())
return NULL;
TSMesh* tsmesh;
if (isSkin())
{
TSSkinMesh* tsskin = new TSSkinMesh();
tsmesh = tsskin;
// Copy skin elements
tsskin->weight = weight;
tsskin->boneIndex = boneIndex;
tsskin->vertexIndex = vertexIndex;
tsskin->batchData.nodeIndex = nodeIndex;
tsskin->batchData.initialTransforms = initialTransforms;
tsskin->batchData.initialVerts = initialVerts;
tsskin->batchData.initialNorms = initialNorms;
}
else
{
tsmesh = new TSMesh();
}
// Copy mesh elements
tsmesh->mVerts = points;
tsmesh->mNorms = normals;
tsmesh->mTverts = uvs;
tsmesh->mPrimitives = primitives;
tsmesh->mIndices = indices;
tsmesh->mColors = colors;
tsmesh->mTverts2 = uv2s;
// Finish initializing the shape
computeBounds(tsmesh->mBounds);
tsmesh->setFlags(flags);
tsmesh->updateMeshFlags();
//tsmesh->computeBounds();
tsmesh->numFrames = numFrames;
tsmesh->numMatFrames = numMatFrames;
tsmesh->vertsPerFrame = vertsPerFrame;
tsmesh->createTangents(tsmesh->mVerts, tsmesh->mNorms);
tsmesh->mEncodedNorms.set(NULL, 0);
return tsmesh;
}
AssimpAppMesh::AssimpAppMesh(const struct aiMesh* mesh, AssimpAppNode* node)
: mMeshData(mesh), appNode(node)
{
@ -326,7 +226,6 @@ void AssimpAppMesh::lockMesh(F32 t, const MatrixF& objOffset)
bonePos /= scaleMult;
}
bonePos *= ColladaUtils::getOptions().unit * ColladaUtils::getOptions().formatScaleFactor;
boneTransform.setPosition(bonePos);
initialTransforms.push_back(boneTransform);

View file

@ -46,9 +46,6 @@ protected:
static S32 fixedSize; ///< The fixed detail size value for all geometry
public:
void computeBounds(Box3F& bounds) override;
TSMesh* constructTSMesh() override;
AssimpAppMesh(const struct aiMesh* mesh, AssimpAppNode* node);
~AssimpAppMesh()
{

View file

@ -84,6 +84,7 @@ MatrixF AssimpAppNode::getTransform(F32 time)
// no parent (ie. root level) => scale by global shape <unit>
mLastTransform.identity();
mLastTransform.scale(ColladaUtils::getOptions().unit * ColladaUtils::getOptions().formatScaleFactor);
ColladaUtils::convertTransform(mLastTransform);
}
// If this node is animated in the active sequence, fetch the animated transform
@ -176,12 +177,16 @@ Point3F AssimpAppNode::interpolateVectorKey(const aiVectorKey* keys, U32 numKeys
{
if (frameTime < keys[i].mTime)
{
Assimp::Interpolator<aiVectorKey> interp;
const aiVectorKey& next = keys[i];
const aiVectorKey& prev = keys[i - 1];
const F32 factor = (frameTime - keys[i - 1].mTime) / (keys[i].mTime - keys[i - 1].mTime);
Point3F start(keys[i - 1].mValue.x, keys[i - 1].mValue.y, keys[i - 1].mValue.z);
Point3F end(keys[i].mValue.x, keys[i].mValue.y, keys[i].mValue.z);
Point3F result;
result.interpolate(start, end, factor);
return result;
aiVector3D out;
interp(out, prev, next, factor);
return Point3F(out.x, out.y, out.z);
}
}
@ -194,6 +199,16 @@ QuatF AssimpAppNode::interpolateQuaternionKey(const aiQuatKey* keys, U32 numKeys
if (numKeys == 1) // Single keyframe: use it directly
return QuatF(keys[0].mValue.x, keys[0].mValue.y, keys[0].mValue.z, keys[0].mValue.w);
// Clamp frameTime to the bounds of the keyframes
if (frameTime <= keys[0].mTime) {
// Before the first keyframe, return the first key
return QuatF(keys[0].mValue.x, keys[0].mValue.y, keys[0].mValue.z, keys[0].mValue.w);
}
if (frameTime >= keys[numKeys - 1].mTime) {
// After the last keyframe, return the last key
return QuatF(keys[numKeys - 1].mValue.x, keys[numKeys - 1].mValue.y, keys[numKeys - 1].mValue.z, keys[numKeys - 1].mValue.w);
}
for (U32 i = 1; i < numKeys; ++i)
{
if (frameTime < keys[i].mTime)

View file

@ -15,7 +15,7 @@
AssimpAppSequence::AssimpAppSequence(aiAnimation* a)
: seqStart(0.0f), seqEnd(0.0f), mTimeMultiplier(1.0f)
{
fps = 30.0f;
fps = ColladaUtils::getOptions().animFPS;
// Deep copy animation structure
mAnim = new aiAnimation(*a);
mAnim->mChannels = new aiNodeAnim * [a->mNumChannels];

View file

@ -192,26 +192,35 @@ void AssimpShapeLoader::enumerateScene()
TSShapeLoader::updateProgress(TSShapeLoader::Load_ReadFile, "Reading File");
Con::printf("[ASSIMP] Attempting to load file: %s", shapePath.getFullPath().c_str());
// Define post-processing steps
U32 ppsteps = aiProcess_Triangulate | /*aiProcess_PreTransformVertices |*/ aiProcess_ConvertToLeftHanded & ~aiProcess_MakeLeftHanded;
const ColladaUtils::ImportOptions& opts = ColladaUtils::getOptions();
// Define post-processing steps
unsigned flags =
aiProcess_Triangulate |
aiProcess_JoinIdenticalVertices |
aiProcess_ValidateDataStructure |
aiProcess_ConvertToLeftHanded & ~aiProcess_MakeLeftHanded;
if (opts.convertLeftHanded) flags |= aiProcess_MakeLeftHanded;
if (opts.reverseWindingOrder) flags |= aiProcess_FlipWindingOrder;
if (opts.genUVCoords) flags |= aiProcess_GenUVCoords;
if (opts.transformUVCoords) flags |= aiProcess_TransformUVCoords;
if (opts.limitBoneWeights) flags |= aiProcess_LimitBoneWeights;
if (opts.calcTangentSpace) flags |= aiProcess_CalcTangentSpace;
if (opts.findInstances) flags |= aiProcess_FindInstances;
if (opts.removeRedundantMats) flags |= aiProcess_RemoveRedundantMaterials;
if (opts.joinIdenticalVerts) flags |= aiProcess_JoinIdenticalVertices;
if (opts.invertNormals) flags |= aiProcess_FixInfacingNormals;
if (opts.flipUVCoords) flags |= aiProcess_FlipUVs;
const auto& options = ColladaUtils::getOptions();
if (options.calcTangentSpace) ppsteps |= aiProcess_CalcTangentSpace;
if (options.joinIdenticalVerts) ppsteps |= aiProcess_JoinIdenticalVertices;
if (options.removeRedundantMats) ppsteps |= aiProcess_RemoveRedundantMaterials;
if (options.genUVCoords) ppsteps |= aiProcess_GenUVCoords;
if (options.transformUVCoords) ppsteps |= aiProcess_TransformUVCoords;
if (options.findInstances) ppsteps |= aiProcess_FindInstances;
if (options.limitBoneWeights) ppsteps |= aiProcess_LimitBoneWeights;
if (Con::getBoolVariable("$Assimp::OptimizeMeshes", false)) {
ppsteps |= aiProcess_OptimizeMeshes | aiProcess_OptimizeGraph;
flags |= aiProcess_OptimizeMeshes | aiProcess_OptimizeGraph;
}
if (Con::getBoolVariable("$Assimp::SplitLargeMeshes", false)) {
ppsteps |= aiProcess_SplitLargeMeshes;
flags |= aiProcess_SplitLargeMeshes;
}
ppsteps |= aiProcess_ValidateDataStructure;
struct aiLogStream shapeLog = aiGetPredefinedLogStream(aiDefaultLogStream_STDOUT, NULL);
shapeLog.callback = assimpLogCallback;
@ -221,15 +230,8 @@ void AssimpShapeLoader::enumerateScene()
aiEnableVerboseLogging(true);
#endif
/*mImporter.SetPropertyInteger(AI_CONFIG_PP_PTV_KEEP_HIERARCHY, 1);
mImporter.SetPropertyInteger(AI_CONFIG_PP_PTV_ADD_ROOT_TRANSFORMATION, 1);
mImporter.SetPropertyMatrix(AI_CONFIG_PP_PTV_ROOT_TRANSFORMATION, aiMatrix4x4(1, 0, 0, 0,
0, 0, -1, 0,
0, 1, 0, 0,
0, 0, 0, 1));*/
// Read the file
mScene = mImporter.ReadFile(shapePath.getFullPath().c_str(), ppsteps);
mScene = mImporter.ReadFile(shapePath.getFullPath().c_str(), flags);
if (!mScene || !mScene->mRootNode) {
Con::errorf("[ASSIMP] ERROR: Could not load file: %s", shapePath.getFullPath().c_str());
@ -245,26 +247,24 @@ void AssimpShapeLoader::enumerateScene()
debugSceneMetaData(mScene);
#endif
ColladaUtils::getOptions().upAxis = UPAXISTYPE_Y_UP; // default to Y up for assimp.
// Handle scaling
configureImportUnits();
// Format-specific adjustments
String fileExt = String::ToLower(shapePath.getExtension());
const aiImporterDesc* importerDescription = aiGetImporterDesc(fileExt.c_str());
if (importerDescription && dStrcmp(importerDescription->mName, "Autodesk FBX Importer") == 0) {
Con::printf("[ASSIMP] Detected FBX format, checking unit scale...");
F32 scaleFactor = ColladaUtils::getOptions().unit;
if (scaleFactor != 1.0f) {
Con::printf("[ASSIMP] Applying FBX scale factor: %f", scaleFactor);
scaleScene(mScene, scaleFactor);
}
else
{
scaleScene(mScene, 0.01f);
if (mScene->mMetaData) {
aiString fmt;
if (mScene->mMetaData->Get("SourceAsset_Format", fmt)) {
if (dStrstr(fmt.C_Str(), "FBX") != NULL) {
// FBX is always centimeters. Convert to meters.
ColladaUtils::getOptions().formatScaleFactor = 0.0100f;
Con::printf("[ASSIMP] FBX detected: applying 0.01 scale (cm -> m).");
}
}
}
ColladaUtils::getOptions().upAxis = UPAXISTYPE_Z_UP;
// Compute & apply axis conversion matrix
getRootAxisTransform();
for (U32 i = 0; i < mScene->mNumTextures; ++i) {
extractTexture(i, mScene->mTextures[i]);
}
@ -278,25 +278,20 @@ void AssimpShapeLoader::enumerateScene()
// Setup LOD checks
detectDetails();
aiMatrix4x4 sceneRoot = aiMatrix4x4(1, 0, 0, 0,
0, 0, -1, 0,
0, 1, 0, 0,
0, 0, 0, 1);
applyTransformation(mScene->mRootNode, sceneRoot);
// Process the scene graph
AssimpAppNode* rootNode = new AssimpAppNode(mScene, mScene->mRootNode, 0);
if (!processNode(rootNode)) {
delete rootNode;
aiNode* root = mScene->mRootNode;
for (S32 iNode = 0; iNode < root->mNumChildren; iNode++)
{
aiNode* child = root->mChildren[iNode];
AssimpAppNode* node = new AssimpAppNode(mScene, child);
if (!processNode(node)) {
delete node;
}
}
processAssimpNode(mScene->mRootNode, mScene, rootNode);
// Add a bounds node if none exists
if (!boundsNode) {
aiNode* reqNode = new aiNode("bounds");
mScene->mRootNode->addChildren(1, &reqNode);
reqNode->mTransformation = aiMatrix4x4();// *sceneRoot;
AssimpAppNode* appBoundsNode = new AssimpAppNode(mScene, reqNode);
if (!processNode(appBoundsNode)) {
@ -312,10 +307,16 @@ void AssimpShapeLoader::enumerateScene()
}
void AssimpShapeLoader::configureImportUnits() {
auto& options = ColladaUtils::getOptions();
auto& opts = ColladaUtils::getOptions();
// Configure unit scaling
if (options.unit <= 0.0f) {
if (opts.unit > 0.0f)
return;
// Try metadata for some formats
if (mScene->mMetaData)
{
F64 unitScaleFactor = 1.0;
if (!getMetaDouble("UnitScaleFactor", unitScaleFactor)) {
F32 floatVal;
@ -327,22 +328,59 @@ void AssimpShapeLoader::configureImportUnits() {
unitScaleFactor = static_cast<F64>(intVal);
}
}
options.unit = static_cast<F32>(unitScaleFactor);
opts.formatScaleFactor = unitScaleFactor;
unitScaleFactor = 1.0;
if (!getMetaDouble("OriginalUnitScaleFactor", unitScaleFactor)) {
F32 floatVal;
S32 intVal;
if (getMetaFloat("OriginalUnitScaleFactor", floatVal)) {
unitScaleFactor = static_cast<F64>(floatVal);
}
else if (getMetaInt("OriginalUnitScaleFactor", intVal)) {
unitScaleFactor = static_cast<F64>(intVal);
}
}
opts.unit = unitScaleFactor;
// FBX may use another property name
U32 unit = 0;
if (mScene->mMetaData->Get("Unit", unit))
{
opts.unit = (F32)unit;
}
F32 fps;
getMetaFloat("CustomFrameRate", fps);
opts.animFPS = fps;
}
}
void AssimpShapeLoader::processAssimpNode(const aiNode* node, const aiScene* scene, AssimpAppNode* parentNode)
void AssimpShapeLoader::getRootAxisTransform()
{
AssimpAppNode* currNode;
if (node == scene->mRootNode)
aiMetadata* meta = mScene->mMetaData;
if (!meta)
{
currNode = parentNode;
}
else
{
currNode = new AssimpAppNode(scene, node, parentNode);
processNode(currNode);
// assume y up
ColladaUtils::getOptions().upAxis = UPAXISTYPE_Y_UP;
return;
}
// Fetch metadata values
int upAxis = 1, upSign = 1;
int frontAxis = 2, frontSign = -1;
int coordAxis = 0, coordSign = 1;
meta->Get("UpAxis", upAxis);
meta->Get("UpAxisSign", upSign);
meta->Get("FrontAxis", frontAxis);
meta->Get("FrontAxisSign", frontSign);
meta->Get("CoordAxis", coordAxis);
meta->Get("CoordAxisSign", coordSign);
ColladaUtils::getOptions().upAxis = (domUpAxisType)upAxis;
}
void AssimpShapeLoader::processAnimations()
@ -353,21 +391,18 @@ void AssimpShapeLoader::processAnimations()
Vector<aiNodeAnim*> ambientChannels;
F32 duration = 0.0f;
F32 ticks = 0.0f;
F32 maxKeyTime = 0.0f;
if (mScene->mNumAnimations > 0)
{
for (U32 i = 0; i < mScene->mNumAnimations; ++i)
{
aiAnimation* anim = mScene->mAnimations[i];
ticks = anim->mTicksPerSecond;
duration = 0.0f;
for (U32 j = 0; j < anim->mNumChannels; j++)
{
aiNodeAnim* nodeAnim = anim->mChannels[j];
// Determine the maximum keyframe time for this animation
F32 maxKeyTime = 0.0f;
for (U32 k = 0; k < nodeAnim->mNumPositionKeys; k++) {
maxKeyTime = getMax(maxKeyTime, (F32)nodeAnim->mPositionKeys[k].mTime);
}
@ -387,7 +422,7 @@ void AssimpShapeLoader::processAnimations()
ambientSeq->mNumChannels = ambientChannels.size();
ambientSeq->mChannels = ambientChannels.address();
ambientSeq->mDuration = duration;
ambientSeq->mTicksPerSecond = ticks;
ambientSeq->mTicksPerSecond = ColladaUtils::getOptions().animFPS;
AssimpAppSequence* defaultAssimpSeq = new AssimpAppSequence(ambientSeq);
appSequences.push_back(defaultAssimpSeq);
@ -570,52 +605,13 @@ bool AssimpShapeLoader::fillGuiTreeView(const char* sourceShapePath, GuiTreeView
return true;
}
void AssimpShapeLoader::updateMaterialsScript(const Torque::Path &path)
{
return;
/*
Torque::Path scriptPath(path);
scriptPath.setFileName("materials");
scriptPath.setExtension(TORQUE_SCRIPT_EXTENSION);
// First see what materials we need to update
PersistenceManager persistMgr;
for ( U32 iMat = 0; iMat < AppMesh::appMaterials.size(); iMat++ )
{
AssimpAppMaterial *mat = dynamic_cast<AssimpAppMaterial*>( AppMesh::appMaterials[iMat] );
if ( mat )
{
Material *mappedMat;
if ( Sim::findObject( MATMGR->getMapEntry( mat->getName() ), mappedMat ) )
{
// Only update existing materials if forced to
if (ColladaUtils::getOptions().forceUpdateMaterials)
{
mat->initMaterial(scriptPath, mappedMat);
persistMgr.setDirty(mappedMat);
}
}
else
{
// Create a new material definition
persistMgr.setDirty( mat->createMaterial( scriptPath ), scriptPath.getFullPath() );
}
}
}
if ( persistMgr.getDirtyList().empty() )
return;
persistMgr.saveDirty();
*/
}
/// Check if an up-to-date cached DTS is available for this DAE file
bool AssimpShapeLoader::canLoadCachedDTS(const Torque::Path& path)
{
// Generate the cached filename
Torque::Path cachedPath(path);
cachedPath.setExtension("cached.dts");
if (String::compare(path.getExtension(), "dsq") != 0)
cachedPath.setExtension("cached.dts");
// Check if a cached DTS newer than this file is available
FileTime cachedModifyTime;
@ -631,7 +627,6 @@ bool AssimpShapeLoader::canLoadCachedDTS(const Torque::Path& path)
return true;
}
}
return false;
}
@ -919,7 +914,8 @@ TSShape* assimpLoadShape(const Torque::Path &path)
// TODO: add .cached.dts generation.
// Generate the cached filename
Torque::Path cachedPath(path);
cachedPath.setExtension("cached.dts");
if ( String::compare(path.getExtension(),"dsq") != 0)
cachedPath.setExtension("cached.dts");
// Check if an up-to-date cached DTS version of this file exists, and
// if so, use that instead.
@ -930,6 +926,17 @@ TSShape* assimpLoadShape(const Torque::Path &path)
if (cachedStream.getStatus() == Stream::Ok)
{
TSShape *shape = new TSShape;
if (String::compare(path.getExtension(), "dsq") == 0)
{
if (!shape->importSequences(&cachedStream, cachedPath.getFullPath()))
{
Con::errorf("assimpLoadShape: Load sequence file '%s' failed", cachedPath.getFullPath().c_str());
delete shape;
shape = NULL;
}
cachedStream.close();
return shape;
}
bool readSuccess = shape->read(&cachedStream);
cachedStream.close();
@ -1004,8 +1011,6 @@ TSShape* assimpLoadShape(const Torque::Path &path)
tss->write(&dtsStream);
}
}
loader.updateMaterialsScript(path);
}
loader.releaseImport();
return tss;

View file

@ -47,6 +47,9 @@ protected:
Assimp::Importer mImporter;
const aiScene* mScene;
// internal helpers
void getRootAxisTransform();
//bool processNode(AppNode* node) override;
bool ignoreNode(const String& name) override;
bool ignoreMesh(const String& name) override;
@ -54,7 +57,6 @@ protected:
void extractTexture(U32 index, aiTexture* pTex);
private:
void processAssimpNode(const aiNode* node, const aiScene* scene, AssimpAppNode* parentNode = nullptr);
void addNodeToTree(S32 parentItem, aiNode* node, GuiTreeViewCtrl* tree, U32& nodeCount);
void addMetaDataToTree(const aiMetadata* metaData, GuiTreeViewCtrl* tree);
bool getMetabool(const char* key, bool& boolVal);
@ -70,7 +72,6 @@ public:
void releaseImport();
void enumerateScene() override;
void configureImportUnits();
void updateMaterialsScript(const Torque::Path &path);
void processAnimations();
void computeBounds(Box3F& bounds) override;