//----------------------------------------------------------------------------- // 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::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&, void*, void*); bool cmpShapeName(const String& key, const Vector& 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& 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& names, void* arg1, void* arg2) { const Vector& meshes = *(Vector*)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 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 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 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); }