void MsAppMesh::getSkinData() { // only generate the skin data once if (mSkinDataFetched) return; mSkinDataFetched = true; // skinned meshes are attached to more than 1 bone if (mMsNode->mBoneIndices.size() <= 1) return; // need to generate an array of bones that animate this mesh // Milkshape supports up to 4 bone weights per vertex - all other bones // are set to weight 0 msModel *model = mMsNode->getModel(); //----------------------------------------------------------------------- // add all bones attached to this mesh S32 numBones = mMsNode->mBoneIndices.size(); for (int i = 0; i < numBones;i++) { S32 boneIndex = mMsNode->mBoneIndices[i]; assert(boneIndex >= 0 && "Invalid bone index"); MilkshapeNode *node = new MilkshapeBone(boneIndex); mBones.push_back(new MsAppNode(node, true)); AppConfig::PrintDump(PDPass2,avar("Adding skin object from skin \"%s\" to bone \"%s\" (%i).\r\n",mMsNode->getName(),mBones[i]->getName(),i)); } //----------------------------------------------------------------------- // reset all weights to zero S32 numPoints = msMesh_GetVertexCount(mMsNode->getMsMesh()); mWeights.resize(numBones); for (int i = 0; i < (int)mWeights.size(); i++) { mWeights[i] = new std::vector<F32>; mWeights[i]->resize(numPoints); for (int j=0; j<numPoints; j++) (*mWeights[i])[j] = 0.0f; } // set weights for bones that affect vertices in this mesh for (int j = 0; j < numPoints; j++) { S32 indices[MS_BONES_PER_VERTEX_EX]; F32 weights[MS_BONES_PER_VERTEX_EX]; bool attached = false; // get the bone indices/weights of this vertex - they are stored a bit // strangely to keep the milkshape file format backwards compatible: // bone[0] = msVertex.nBoneIndex, weight[0] = msVertexEx.nBoneWeights[0] // bone[1] = msVertexEx.nBoneIndex[0], weight[1] = msVertexEx.nBoneWeights[1] // bone[2] = msVertexEx.nBoneIndex[1], weight[2] = msVertexEx.nBoneWeights[2] // bone[3] = msVertexEx.nBoneIndex[2], weight[3] = 1 - (sum_of_other_weights) for (int k = 0; k < MS_BONES_PER_VERTEX_EX; k++) { msVertexEx *vtx = msMesh_GetVertexExAt(mMsNode->getMsMesh(), j); // get the bone index if (k == 0) { msVertex *v = msMesh_GetVertexAt(mMsNode->getMsMesh(), j); indices[k] = msVertex_GetBoneIndex(v); } else { indices[k] = msVertexEx_GetBoneIndices(vtx, k - 1); } // get the bone weight if (indices[k] >= 0) { attached = true; if (k != (MS_BONES_PER_VERTEX_EX-1)) { weights[k] = (F32)msVertexEx_GetBoneWeights(vtx, k) / 100.f; } else { weights[k] = 1.0f - (weights[0] + weights[1] + weights[2]); } } else weights[k] = 0.0f; } // Force verts that are attached to a single bone to have 100% weight if (attached && (indices[1] == -1) && (weights[0] == 0.0f)) { weights[0] = 1.0f; } // if the vertex is not attached to any bones, attach it to the root bone if (!attached) { S32 boneIndex = mMsNode->getRootBoneIndex(); addBoneWeight(j, boneIndex, 1.0f); } else { // add weights for each bone for (int k = 0; k < MS_BONES_PER_VERTEX_EX; k++) { if (indices[k] < 0) continue; addBoneWeight(j, indices[k], weights[k]); } } } }
void MilkshapePlugin::doExportMesh(msModel* pModel) { // Create singletons Ogre::SkeletonManager skelMgr; Ogre::DefaultHardwareBufferManager defHWBufMgr; Ogre::LogManager& logMgr = Ogre::LogManager::getSingleton(); Ogre::MeshManager meshMgr; // // choose filename // OPENFILENAME ofn; memset (&ofn, 0, sizeof (OPENFILENAME)); char szFile[MS_MAX_PATH]; char szFileTitle[MS_MAX_PATH]; char szDefExt[32] = "mesh"; char szFilter[128] = "OGRE .mesh Files (*.mesh)\0*.mesh\0All Files (*.*)\0*.*\0\0"; szFile[0] = '\0'; szFileTitle[0] = '\0'; ofn.lStructSize = sizeof (OPENFILENAME); ofn.lpstrDefExt = szDefExt; ofn.lpstrFilter = szFilter; ofn.lpstrFile = szFile; ofn.nMaxFile = MS_MAX_PATH; ofn.lpstrFileTitle = szFileTitle; ofn.nMaxFileTitle = MS_MAX_PATH; ofn.Flags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST; ofn.lpstrTitle = "Export to OGRE Mesh"; if (!::GetSaveFileName (&ofn)) return /*0*/; logMgr.logMessage("Creating Mesh object..."); Ogre::MeshPtr ogreMesh = Ogre::MeshManager::getSingleton().create("export", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); logMgr.logMessage("Mesh object created."); bool foundBoneAssignment = false; // No shared geometry int i; int wh, numbones; int intweight[3], intbones[3]; size_t j; Ogre::Vector3 min, max, currpos; Ogre::Real maxSquaredRadius; bool first = true; for (i = 0; i < msModel_GetMeshCount (pModel); i++) { msMesh *pMesh = msModel_GetMeshAt (pModel, i); logMgr.logMessage("Creating SubMesh object..."); Ogre::SubMesh* ogreSubMesh = ogreMesh->createSubMesh(); logMgr.logMessage("SubMesh object created."); // Set material logMgr.logMessage("Getting SubMesh Material..."); int matIdx = msMesh_GetMaterialIndex(pMesh); if (matIdx == -1) { // No material, use blank ogreSubMesh->setMaterialName("BaseWhite"); logMgr.logMessage("No Material, using default 'BaseWhite'."); } else { msMaterial *pMat = msModel_GetMaterialAt(pModel, matIdx); ogreSubMesh->setMaterialName(pMat->szName); logMgr.logMessage("SubMesh Material Done."); } logMgr.logMessage("Setting up geometry..."); // Set up mesh geometry ogreSubMesh->vertexData = new Ogre::VertexData(); ogreSubMesh->vertexData->vertexCount = msMesh_GetVertexCount (pMesh); ogreSubMesh->vertexData->vertexStart = 0; Ogre::VertexBufferBinding* bind = ogreSubMesh->vertexData->vertexBufferBinding; Ogre::VertexDeclaration* decl = ogreSubMesh->vertexData->vertexDeclaration; // Always 1 texture layer, 2D coords #define POSITION_BINDING 0 #define NORMAL_BINDING 1 #define TEXCOORD_BINDING 2 decl->addElement(POSITION_BINDING, 0, Ogre::VET_FLOAT3, Ogre::VES_POSITION); decl->addElement(NORMAL_BINDING, 0, Ogre::VET_FLOAT3, Ogre::VES_NORMAL); decl->addElement(TEXCOORD_BINDING, 0, Ogre::VET_FLOAT2, Ogre::VES_TEXTURE_COORDINATES); // Create buffers Ogre::HardwareVertexBufferSharedPtr pbuf = Ogre::HardwareBufferManager::getSingleton(). createVertexBuffer(decl->getVertexSize(POSITION_BINDING), ogreSubMesh->vertexData->vertexCount, Ogre::HardwareBuffer::HBU_DYNAMIC, false); Ogre::HardwareVertexBufferSharedPtr nbuf = Ogre::HardwareBufferManager::getSingleton(). createVertexBuffer(decl->getVertexSize(NORMAL_BINDING), ogreSubMesh->vertexData->vertexCount, Ogre::HardwareBuffer::HBU_DYNAMIC, false); Ogre::HardwareVertexBufferSharedPtr tbuf = Ogre::HardwareBufferManager::getSingleton(). createVertexBuffer(decl->getVertexSize(TEXCOORD_BINDING), ogreSubMesh->vertexData->vertexCount, Ogre::HardwareBuffer::HBU_DYNAMIC, false); bind->setBinding(POSITION_BINDING, pbuf); bind->setBinding(NORMAL_BINDING, nbuf); bind->setBinding(TEXCOORD_BINDING, tbuf); ogreSubMesh->useSharedVertices = false; float* pPos = static_cast<float*>( pbuf->lock(Ogre::HardwareBuffer::HBL_DISCARD)); logMgr.logMessage("Doing positions and texture coords..."); for (j = 0; j < ogreSubMesh->vertexData->vertexCount; ++j) { logMgr.logMessage("Doing vertex " + Ogre::StringConverter::toString(j)); msVertex *pVertex = msMesh_GetVertexAt (pMesh, (int)j); msVertexEx *pVertexEx=msMesh_GetVertexExAt(pMesh, (int)j); msVec3 Vertex; msVertex_GetVertex (pVertex, Vertex); *pPos++ = Vertex[0]; *pPos++ = Vertex[1]; *pPos++ = Vertex[2]; // Deal with bounds currpos = Ogre::Vector3(Vertex[0], Vertex[1], Vertex[2]); if (first) { min = max = currpos; maxSquaredRadius = currpos.squaredLength(); first = false; } else { min.makeFloor(currpos); max.makeCeil(currpos); maxSquaredRadius = std::max(maxSquaredRadius, currpos.squaredLength()); } int boneIdx = msVertex_GetBoneIndex(pVertex); if (boneIdx != -1) { foundBoneAssignment = true; numbones = 1; intbones[0] = intbones[1] = intbones[2] = -1; intweight[0] = intweight[1] = intweight[2] = 0; for(wh = 0; wh < 3; ++wh) { intbones[wh] = msVertexEx_GetBoneIndices(pVertexEx, wh); if(intbones[wh] == -1) break; ++numbones; intweight[wh] = msVertexEx_GetBoneWeights(pVertexEx, wh); } // for(k) Ogre::VertexBoneAssignment vertAssign; vertAssign.boneIndex = boneIdx; vertAssign.vertexIndex = (unsigned int)j; if(numbones == 1) { vertAssign.weight = 1.0; } // single assignment else { vertAssign.weight=(Ogre::Real)intweight[0]/100.0; } ogreSubMesh->addBoneAssignment(vertAssign); if(numbones > 1) { // this somewhat contorted logic is because the first weight [0] matches to the bone assignment // located with pVertex. The next two weights [1][2] match up to the first two bones found // with pVertexEx [0][1]. The weight for the fourth bone, if present, is the unassigned weight for(wh = 0; wh < 3; wh++) { boneIdx = intbones[wh]; if(boneIdx == -1) break; vertAssign.boneIndex = boneIdx; vertAssign.vertexIndex = (unsigned int)j; if(wh == 2) { // fourth weight is 1.0-(sumoffirstthreeweights) vertAssign.weight = 1.0-(((Ogre::Real)intweight[0]/100.0)+ ((Ogre::Real)intweight[1]/100.0)+((Ogre::Real)intweight[2]/100.0)); } else { vertAssign.weight=(Ogre::Real)intweight[wh+1]; } ogreSubMesh->addBoneAssignment(vertAssign); } // for(k) } // if(numbones) } } pbuf->unlock(); float* pTex = static_cast<float*>( tbuf->lock(Ogre::HardwareBuffer::HBL_DISCARD)); logMgr.logMessage("Doing uvs, normals and indexes (v2)..."); // Aargh, Milkshape uses stupid separate normal indexes for the same vertex like 3DS // Normals aren't described per vertex but per triangle vertex index // Pain in the arse, we have to do vertex duplication again if normals differ at a vertex (non smooth) // WHY don't people realise this format is a pain for passing to 3D APIs in vertex buffers? float* pNorm = static_cast<float*>( nbuf->lock(Ogre::HardwareBuffer::HBL_DISCARD)); ogreSubMesh->indexData->indexCount = msMesh_GetTriangleCount (pMesh) * 3; // Always use 16-bit buffers, Milkshape can't handle more anyway Ogre::HardwareIndexBufferSharedPtr ibuf = Ogre::HardwareBufferManager::getSingleton(). createIndexBuffer(Ogre::HardwareIndexBuffer::IT_16BIT, ogreSubMesh->indexData->indexCount, Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY); ogreSubMesh->indexData->indexBuffer = ibuf; unsigned short *pIdx = static_cast<unsigned short*>( ibuf->lock(Ogre::HardwareBuffer::HBL_DISCARD)); for (j = 0; j < ogreSubMesh->indexData->indexCount; j+=3) { msTriangle *pTriangle = msMesh_GetTriangleAt (pMesh, (int)j/3); msTriangleEx *pTriangleEx=msMesh_GetTriangleExAt(pMesh, (int)j/3); word nIndices[3]; msTriangle_GetVertexIndices (pTriangle, nIndices); msVec3 Normal; msVec2 uv; int k, vertIdx; for (k = 0; k < 3; ++k) { vertIdx = nIndices[k]; // Face index pIdx[j+k] = vertIdx; // Vertex normals // For the moment, ignore any discrepancies per vertex msTriangleEx_GetNormal(pTriangleEx, k, &Normal[0]); msTriangleEx_GetTexCoord(pTriangleEx, k, &uv[0]); pTex[(vertIdx*2)]=uv[0]; pTex[(vertIdx*2)+1]=uv[1]; pNorm[(vertIdx*3)] = Normal[0]; pNorm[(vertIdx*3)+1] = Normal[1]; pNorm[(vertIdx*3)+2] = Normal[2]; } } // Faces nbuf->unlock(); ibuf->unlock(); tbuf->unlock(); // Now use Ogre's ability to reorganise the vertex buffers the best way Ogre::VertexDeclaration* newDecl = ogreSubMesh->vertexData->vertexDeclaration->getAutoOrganisedDeclaration( foundBoneAssignment, false); Ogre::BufferUsageList bufferUsages; for (size_t u = 0; u <= newDecl->getMaxSource(); ++u) bufferUsages.push_back(Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY); ogreSubMesh->vertexData->reorganiseBuffers(newDecl, bufferUsages); logMgr.logMessage("Geometry done."); } // SubMesh // Set bounds ogreMesh->_setBoundingSphereRadius(Ogre::Math::Sqrt(maxSquaredRadius)); ogreMesh->_setBounds(Ogre::AxisAlignedBox(min, max), false); // Keep hold of a Skeleton pointer for deletion later // Mesh uses Skeleton pointer for skeleton name Ogre::SkeletonPtr pSkel; if (exportSkeleton && foundBoneAssignment) { // export skeleton, also update mesh to point to it pSkel = doExportSkeleton(pModel, ogreMesh); } else if (!exportSkeleton && foundBoneAssignment) { // We've found bone assignments, but skeleton is not to be exported // Prompt the user to find the skeleton if (!locateSkeleton(ogreMesh)) return; } // Export logMgr.logMessage("Creating MeshSerializer.."); Ogre::MeshSerializer serializer; logMgr.logMessage("MeshSerializer created."); // Generate LODs if required if (generateLods) { // Build LOD depth list Ogre::Mesh::LodDistanceList distList; float depth = 0; for (unsigned short depthidx = 0; depthidx < numLods; ++depthidx) { depth += lodDepthIncrement; distList.push_back(depth); } ogreMesh->generateLodLevels(distList, lodReductionMethod, lodReductionAmount); } if (generateEdgeLists) { ogreMesh->buildEdgeList(); } if (generateTangents) { unsigned short src, dest; ogreMesh->suggestTangentVectorBuildParams(tangentSemantic, src, dest); ogreMesh->buildTangentVectors(tangentSemantic, src, dest, tangentsSplitMirrored, tangentsSplitRotated, tangentsUseParity); } // Export Ogre::String msg; msg = "Exporting mesh data to file '" + Ogre::String(szFile) + "'"; logMgr.logMessage(msg); serializer.exportMesh(ogreMesh.getPointer(), szFile); logMgr.logMessage("Export successful"); Ogre::MeshManager::getSingleton().remove(ogreMesh->getHandle()); if (!pSkel.isNull()) Ogre::SkeletonManager::getSingleton().remove(pSkel->getHandle()); if (exportMaterials && msModel_GetMaterialCount(pModel) > 0) { doExportMaterials(pModel); } }
AppMeshLock MsAppMesh::lockMesh(const AppTime & time, const Matrix<4,4,F32> &objectOffset) { msMesh *mesh = mMsNode->getMsMesh(); assert(mesh && "NULL milkshape mesh"); S32 lastMatIdx = -1; // start lists empty mFaces.clear(); mVerts.clear(); mTVerts.clear(); mIndices.clear(); mSmooth.clear(); mVertId.clear(); // start out with faces and crop data allocated mFaces.resize(msMesh_GetTriangleCount(mesh)); S32 vCount = msMesh_GetVertexCount(mesh); // Transform the vertices by the bounds and scale std::vector<Point3D> verts(vCount, Point3D()); for (int i = 0; i < vCount; i++) verts[i] = objectOffset * (getVert(mesh, i) * mMsNode->getScale()); int numTriangles = mFaces.size(); for (int i = 0; i < numTriangles; i++) { msTriangle *msFace = msMesh_GetTriangleAt(mesh, i); Primitive &tsFace = mFaces[i]; // set faces material index S32 matIndex = msMesh_GetMaterialIndex(mesh); tsFace.type = (matIndex >= 0) ? matIndex : Primitive::NoMaterial; tsFace.firstElement = mIndices.size(); tsFace.numElements = 3; tsFace.type |= Primitive::Triangles | Primitive::Indexed; // set vertex indices word vertIndices[3]; msTriangle_GetVertexIndices(msFace, vertIndices); Point3D vert0 = verts[vertIndices[0]]; Point3D vert1 = verts[vertIndices[1]]; Point3D vert2 = verts[vertIndices[2]]; Point3D norm0 = getNormal(mesh, i, 0); Point3D norm1 = getNormal(mesh, i, 1); Point3D norm2 = getNormal(mesh, i, 2); // set texture vertex indices Point2D tvert0 = getTVert(mesh, i, 0); Point2D tvert1 = getTVert(mesh, i, 1); Point2D tvert2 = getTVert(mesh, i, 2); // now add indices (switch order to be CW) mIndices.push_back(addVertex(vert0,norm0,tvert0,vertIndices[0])); mIndices.push_back(addVertex(vert2,norm2,tvert2,vertIndices[2])); mIndices.push_back(addVertex(vert1,norm1,tvert1,vertIndices[1])); // if the material is double-sided, add the backface as well if (!(tsFace.type & Primitive::NoMaterial)) { bool doubleSided = false; MilkshapeMaterial *msMat = mMsNode->getMaterial(); msMat->getUserPropBool("doubleSided", doubleSided); if (doubleSided) { Primitive backface = tsFace; backface.firstElement = mIndices.size(); mFaces.push_back(backface); // add verts with order reversed to get the backface mIndices.push_back(addVertex(vert0,-norm0,tvert0,vertIndices[0])); mIndices.push_back(addVertex(vert1,-norm1,tvert1,vertIndices[1])); mIndices.push_back(addVertex(vert2,-norm2,tvert2,vertIndices[2])); } } } return Parent::lockMesh(time,objectOffset); }