void TSShapeLoader::generateSkins() { Vector<AppMesh*> skins; for (int iObject = 0; iObject < shape->objects.size(); iObject++) { for (int iMesh = 0; iMesh < shape->objects[iObject].numMeshes; iMesh++) { AppMesh* mesh = appMeshes[shape->objects[iObject].startMeshIndex + iMesh]; if (mesh->isSkin()) skins.push_back(mesh); } } for (int 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 (int iBone = 0; iBone < skin->bones.size(); iBone++) { // Find the node that matches this bone skin->nodeIndex[iBone] = -1; for (int 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; } } } }
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; }
void TSShapeLoader::generateDefaultStates() { // Generate default object states (includes initial geometry) for (int 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 (int 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 (int 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::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 ); } } }
void TSShapeLoader::generateFrame(TSShape::Object& obj, F32 t, bool addFrame, bool addMatFrame) { for (int 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); } } }
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*)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(); shape->objects.last().nameIndex = shape->addName(meshNames[iMesh]); shape->objects.last().nodeIndex = subshape->objNodes[iMesh]; shape->objects.last().startMeshIndex = appMeshes.size(); shape->objects.last().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::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(); shape->nodes.last().nameIndex = shape->addName(nodeName); shape->nodes.last().parentIndex = parentIndex; shape->nodes.last().firstObject = -1; shape->nodes.last().firstChild = -1; shape->nodes.last().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 ); shape->details[detIndex].bbEquatorSteps = numEquatorSteps; shape->details[detIndex].bbPolarSteps = numPolarSteps; shape->details[detIndex].bbDetailLevel = dl; shape->details[detIndex].bbDimension = dim; shape->details[detIndex].bbIncludePoles = includePoles; shape->details[detIndex].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 (int iChild = 0; iChild < appNode->getNumChildNodes(); iChild++) recurseSubshape(appNode->getChildNode(iChild), myIndex, true); } }
bool AppSceneEnum::processNode(AppNode * node) { // Helper method to help rot nodes that we find in the scene. // At this stage we do not need to collect all the nodes // because the tree structure will still be there when we // build the shape. What we need to do right now is grab // the top of all the subtrees, any meshes hanging on the // root level (these will be lower detail levels, we don't // need to grab meshes on the sub-trees because they will // be found when we recurse into the sub-tree), the bounds // node, and any sequences. const char * name = node->getName(); const char * pname = node->getParentName(); AppConfig::PrintDump(PDPass1,avar("Processing Node %s with parent %s\r\n", name, pname)); AppSequence * seq = getSequence(node); if (seq) { sequences.push_back(seq); return true; } if (node->isDummy()) return false; if (isSubtree(node)) { // Add this node to the subtree list... AppConfig::PrintDump(PDPass1,avar("Found subtree starting at Node \"%s\"\r\n",name)); subtrees.push_back(node); return true; } // See if it is a bounding box. If so, save it as THE bounding // box for the scene if (node->isBounds()) { if (boundsNode) { setExportError("More than one bounds node found."); AppConfig::PrintDump(PDPass1,"More than one bounds node found.\r\n"); } else AppConfig::PrintDump(PDPass1,"Bounding box found\r\n"); boundsNode = node; return true; } // if we use this node, then be sure to return true so the caller doesn't delete it bool used = false; if (node->getNumMesh()!=0) { for (S32 i=0; i<node->getNumMesh(); i++) { AppMesh * mesh = node->getMesh(i); if (mesh->isSkin()) { AppConfig::PrintDump(PDPass1,avar("Skin \"%s\" with parent \"%s\" added to entry list\r\n",mesh->getName(),pname)); skins.push_back(mesh); used = true; } else { if (node->isParentRoot()) { AppConfig::PrintDump(PDPass1,avar("Mesh \"%s\" with parent \"%s\" added to entry list\r\n",mesh->getName(),pname)); meshNodes.push_back(node); meshes.push_back(mesh); used = true; } } } if (used) usedNodes.push_back(node); } return used; }