FbxNode* ILDLMesh::CreateMesh( FbxScene* pScene, const char* pName ) { int nVerts = (int)m_vertex_list.size(); FbxMesh* lMesh = FbxMesh::Create(pScene,pName); lMesh->InitControlPoints( nVerts ); FbxVector4* lControlPoints = lMesh->GetControlPoints(); FbxGeometryElementNormal* lGeometryElementNormal= lMesh->CreateElementNormal(); lGeometryElementNormal->SetMappingMode(FbxGeometryElement::eByControlPoint); lGeometryElementNormal->SetReferenceMode(FbxGeometryElement::eDirect); int vi = 0, glidx = 0; for( ILDLVertexIter i = m_vertex_list.begin(); i != m_vertex_list.end(); ++i, vi++ ) { ILDLVertexPtr vp = (*i); vp->m_glIdx = glidx; vec3 coords = vp->getCoords(); lControlPoints[vi] = FbxVector4( coords[0], coords[1], coords[2] ); vec3 normal = vp->getNormal(); lGeometryElementNormal->GetDirectArray().Add( FbxVector4( normal[0], normal[1], normal[2] ) ); glidx++; } for( ILDLFaceIter i = m_face_list.begin(); i != m_face_list.end(); ++i ) { ILDLFacePtr fp = *i; ILDLFaceVertexPtr fvp = fp->front(); lMesh->BeginPolygon(-1, -1, -1, false); lMesh->AddPolygon( fvp->getVertexPtr()->m_glIdx ); fvp = fvp->next(); lMesh->AddPolygon( fvp->getVertexPtr()->m_glIdx ); fvp = fvp->next(); lMesh->AddPolygon( fvp->getVertexPtr()->m_glIdx ); lMesh->EndPolygon (); } FbxNode* lNode = FbxNode::Create(pScene,pName); lNode->SetNodeAttribute(lMesh); return lNode; }
// Converts a CC mesh to an FBX mesh static FbxNode* ToFbxMesh(ccGenericMesh* mesh, FbxScene* pScene) { if (!mesh) return 0; FbxMesh* lMesh = FbxMesh::Create(pScene, qPrintable(mesh->getName())); ccGenericPointCloud* cloud = mesh->getAssociatedCloud(); if (!cloud) return 0; unsigned vertCount = cloud->size(); unsigned faceCount = mesh->size(); // Create control points. { lMesh->InitControlPoints(vertCount); FbxVector4* lControlPoints = lMesh->GetControlPoints(); for (unsigned i=0; i<vertCount; ++i) { const CCVector3* P = cloud->getPoint(i); lControlPoints[i] = FbxVector4(P->x,P->y,P->z); } } ccMesh* asCCMesh = 0; if (mesh->isA(CC_MESH)) asCCMesh = static_cast<ccMesh*>(mesh); // normals if (mesh->hasNormals()) { FbxGeometryElementNormal* lGeometryElementNormal = lMesh->CreateElementNormal(); if (mesh->hasTriNormals()) { // We want to have one normal per vertex of each polygon, // so we set the mapping mode to eByPolygonVertex. lGeometryElementNormal->SetMappingMode(FbxGeometryElement::eByPolygonVertex); lGeometryElementNormal->SetReferenceMode(FbxGeometryElement::eIndexToDirect); lGeometryElementNormal->GetIndexArray().SetCount(faceCount*3); if (asCCMesh) { NormsIndexesTableType* triNorms = asCCMesh->getTriNormsTable(); assert(triNorms); for (unsigned i=0; i<triNorms->currentSize(); ++i) { const PointCoordinateType* N = ccNormalVectors::GetNormal(triNorms->getValue(i)); FbxVector4 Nfbx(N[0],N[1],N[2]); lGeometryElementNormal->GetDirectArray().Add(Nfbx); } for (unsigned j=0; j<faceCount; ++j) { int i1,i2,i3; asCCMesh->getTriangleNormalIndexes(j,i1,i2,i3); lGeometryElementNormal->GetIndexArray().SetAt(static_cast<int>(j)*3+0, i1); lGeometryElementNormal->GetIndexArray().SetAt(static_cast<int>(j)*3+1, i2); lGeometryElementNormal->GetIndexArray().SetAt(static_cast<int>(j)*3+2, i3); } } else { for (unsigned j=0; j<faceCount; ++j) { //we can't use the 'NormsIndexesTable' so we save all the normals of all the vertices CCVector3 Na,Nb,Nc; lGeometryElementNormal->GetDirectArray().Add(FbxVector4(Na.x,Na.y,Na.z)); lGeometryElementNormal->GetDirectArray().Add(FbxVector4(Nb.x,Nb.y,Nb.z)); lGeometryElementNormal->GetDirectArray().Add(FbxVector4(Nc.x,Nc.y,Nc.z)); mesh->getTriangleNormals(j,Na,Nb,Nc); lGeometryElementNormal->GetIndexArray().SetAt(static_cast<int>(j)*3+0, static_cast<int>(j)*3+0); lGeometryElementNormal->GetIndexArray().SetAt(static_cast<int>(j)*3+1, static_cast<int>(j)*3+1); lGeometryElementNormal->GetIndexArray().SetAt(static_cast<int>(j)*3+2, static_cast<int>(j)*3+2); } } } else { // We want to have one normal for each vertex (or control point), // so we set the mapping mode to eByControlPoint. lGeometryElementNormal->SetMappingMode(FbxGeometryElement::eByControlPoint); // The first method is to set the actual normal value // for every control point. lGeometryElementNormal->SetReferenceMode(FbxGeometryElement::eDirect); for (unsigned i=0; i<vertCount; ++i) { const PointCoordinateType* N = cloud->getPointNormal(i); FbxVector4 Nfbx(N[0],N[1],N[2]); lGeometryElementNormal->GetDirectArray().Add(Nfbx); } } } else { ccLog::Warning("[FBX] Mesh has no normal! You can manually compute them (select it then call \"Edit > Normals > Compute\")"); } // colors if (cloud->hasColors()) { FbxGeometryElementVertexColor* lGeometryElementVertexColor = lMesh->CreateElementVertexColor(); lGeometryElementVertexColor->SetMappingMode(FbxGeometryElement::eByControlPoint); lGeometryElementVertexColor->SetReferenceMode(FbxGeometryElement::eDirect); for (unsigned i=0; i<vertCount; ++i) { const colorType* C = cloud->getPointColor(i); FbxColor col( FbxDouble3( static_cast<double>(C[0])/MAX_COLOR_COMP, static_cast<double>(C[1])/MAX_COLOR_COMP, static_cast<double>(C[2])/MAX_COLOR_COMP ) ); lGeometryElementVertexColor->GetDirectArray().Add(col); } } // Set material mapping. //FbxGeometryElementMaterial* lMaterialElement = lMesh->CreateElementMaterial(); //lMaterialElement->SetMappingMode(FbxGeometryElement::eByPolygon); //lMaterialElement->SetReferenceMode(FbxGeometryElement::eIndexToDirect); // Create polygons. Assign material indices. { for (unsigned j=0; j<faceCount; ++j) { const CCLib::TriangleSummitsIndexes* tsi = mesh->getTriangleIndexes(j); lMesh->BeginPolygon(static_cast<int>(j)); lMesh->AddPolygon(tsi->i1); lMesh->AddPolygon(tsi->i2); lMesh->AddPolygon(tsi->i3); lMesh->EndPolygon(); } } FbxNode* lNode = FbxNode::Create(pScene,qPrintable(mesh->getName())); lNode->SetNodeAttribute(lMesh); //CreateMaterials(pScene, lMesh); return lNode; }
// Converts a CC mesh to an FBX mesh static FbxNode* ToFbxMesh(ccGenericMesh* mesh, FbxScene* pScene, QString filename, size_t meshIndex) { if (!mesh) return 0; FbxNode* lNode = FbxNode::Create(pScene,qPrintable(mesh->getName())); FbxMesh* lMesh = FbxMesh::Create(pScene, qPrintable(mesh->getName())); lNode->SetNodeAttribute(lMesh); ccGenericPointCloud* cloud = mesh->getAssociatedCloud(); if (!cloud) return 0; unsigned vertCount = cloud->size(); unsigned faceCount = mesh->size(); // Create control points. { lMesh->InitControlPoints(vertCount); FbxVector4* lControlPoints = lMesh->GetControlPoints(); for (unsigned i=0; i<vertCount; ++i) { const CCVector3* P = cloud->getPoint(i); lControlPoints[i] = FbxVector4(P->x,P->y,P->z); //lControlPoints[i] = FbxVector4(P->x,P->z,-P->y); //DGM: see loadFile (Y and Z are inverted) } } ccMesh* asCCMesh = 0; if (mesh->isA(CC_TYPES::MESH)) asCCMesh = static_cast<ccMesh*>(mesh); // normals if (mesh->hasNormals()) { FbxGeometryElementNormal* lGeometryElementNormal = lMesh->CreateElementNormal(); if (mesh->hasTriNormals()) { // We want to have one normal per vertex of each polygon, // so we set the mapping mode to eByPolygonVertex. lGeometryElementNormal->SetMappingMode(FbxGeometryElement::eByPolygonVertex); lGeometryElementNormal->SetReferenceMode(FbxGeometryElement::eIndexToDirect); lGeometryElementNormal->GetIndexArray().SetCount(faceCount*3); if (asCCMesh) { NormsIndexesTableType* triNorms = asCCMesh->getTriNormsTable(); assert(triNorms); for (unsigned i=0; i<triNorms->currentSize(); ++i) { const CCVector3& N = ccNormalVectors::GetNormal(triNorms->getValue(i)); FbxVector4 Nfbx(N.x,N.y,N.z); lGeometryElementNormal->GetDirectArray().Add(Nfbx); } for (unsigned j=0; j<faceCount; ++j) { int i1,i2,i3; asCCMesh->getTriangleNormalIndexes(j,i1,i2,i3); lGeometryElementNormal->GetIndexArray().SetAt(static_cast<int>(j)*3+0, i1); lGeometryElementNormal->GetIndexArray().SetAt(static_cast<int>(j)*3+1, i2); lGeometryElementNormal->GetIndexArray().SetAt(static_cast<int>(j)*3+2, i3); } } else { for (unsigned j=0; j<faceCount; ++j) { //we can't use the 'NormsIndexesTable' so we save all the normals of all the vertices CCVector3 Na,Nb,Nc; lGeometryElementNormal->GetDirectArray().Add(FbxVector4(Na.x,Na.y,Na.z)); lGeometryElementNormal->GetDirectArray().Add(FbxVector4(Nb.x,Nb.y,Nb.z)); lGeometryElementNormal->GetDirectArray().Add(FbxVector4(Nc.x,Nc.y,Nc.z)); mesh->getTriangleNormals(j,Na,Nb,Nc); lGeometryElementNormal->GetIndexArray().SetAt(static_cast<int>(j)*3+0, static_cast<int>(j)*3+0); lGeometryElementNormal->GetIndexArray().SetAt(static_cast<int>(j)*3+1, static_cast<int>(j)*3+1); lGeometryElementNormal->GetIndexArray().SetAt(static_cast<int>(j)*3+2, static_cast<int>(j)*3+2); } } } else { // We want to have one normal for each vertex (or control point), // so we set the mapping mode to eByControlPoint. lGeometryElementNormal->SetMappingMode(FbxGeometryElement::eByControlPoint); // The first method is to set the actual normal value // for every control point. lGeometryElementNormal->SetReferenceMode(FbxGeometryElement::eDirect); for (unsigned i=0; i<vertCount; ++i) { const CCVector3& N = cloud->getPointNormal(i); FbxVector4 Nfbx(N.x,N.y,N.z); lGeometryElementNormal->GetDirectArray().Add(Nfbx); } } } else { ccLog::Warning("[FBX] Mesh has no normal! You can manually compute them (select it then call \"Edit > Normals > Compute\")"); } // Set material mapping. bool hasMaterial = false; if (asCCMesh && asCCMesh->hasMaterials()) { const ccMaterialSet* matSet = asCCMesh->getMaterialSet(); size_t matCount = matSet->size(); //check if we have textures bool hasTextures = asCCMesh->hasTextures(); if (hasTextures) { //check that we actually have materials with textures as well! hasTextures = false; for (size_t i=0; i<matCount; ++i) { ccMaterial::CShared mat = matSet->at(i); if (mat->hasTexture()) { hasTextures = true; break; } } } static const char gDiffuseElementName[] = "DiffuseUV"; // Create UV for Diffuse channel if (hasTextures) { FbxGeometryElementUV* lUVDiffuseElement = lMesh->CreateElementUV(gDiffuseElementName); assert(lUVDiffuseElement != 0); lUVDiffuseElement->SetMappingMode(FbxGeometryElement::eByPolygonVertex); lUVDiffuseElement->SetReferenceMode(FbxGeometryElement::eIndexToDirect); //fill Direct Array const TextureCoordsContainer* texCoords = asCCMesh->getTexCoordinatesTable(); assert(texCoords); if (texCoords) { unsigned count = texCoords->currentSize(); lUVDiffuseElement->GetDirectArray().SetCount(static_cast<int>(count)); for (unsigned i=0; i<count; ++i) { const float* uv = texCoords->getValue(i); lUVDiffuseElement->GetDirectArray().SetAt(i,FbxVector2(uv[0],uv[1])); } } //fill Indexes Array assert(asCCMesh->hasPerTriangleTexCoordIndexes()); if (asCCMesh->hasPerTriangleTexCoordIndexes()) { unsigned triCount = asCCMesh->size(); lUVDiffuseElement->GetIndexArray().SetCount(static_cast<int>(3*triCount)); for (unsigned j=0; j<triCount; ++j) { int t1=0, t2=0, t3=0; asCCMesh->getTriangleTexCoordinatesIndexes(j, t1, t2, t3); lUVDiffuseElement->GetIndexArray().SetAt(j*3+0,t1); lUVDiffuseElement->GetIndexArray().SetAt(j*3+1,t2); lUVDiffuseElement->GetIndexArray().SetAt(j*3+2,t3); } } } //Textures used in this file QMap<QString,QString> texFilenames; //directory to save textures (if any) QFileInfo info(filename); QString textDirName = info.baseName() + QString(".fbm"); QDir baseDir = info.absoluteDir(); QDir texDir = QDir(baseDir.absolutePath() + QString("/") + textDirName); for (size_t i=0; i<matCount; ++i) { ccMaterial::CShared mat = matSet->at(i); FbxSurfacePhong *lMaterial = FbxSurfacePhong::Create(pScene, qPrintable(mat->getName())); const ccColor::Rgbaf& emission = mat->getEmission(); const ccColor::Rgbaf& ambient = mat->getAmbient(); const ccColor::Rgbaf& diffuse = mat->getDiffuseFront(); const ccColor::Rgbaf& specular = mat->getDiffuseFront(); lMaterial->Emissive.Set(FbxDouble3(emission.r,emission.g,emission.b)); lMaterial->Ambient .Set(FbxDouble3( ambient.r, ambient.g, ambient.b)); lMaterial->Diffuse .Set(FbxDouble3( diffuse.r, diffuse.g, diffuse.b)); lMaterial->Specular.Set(FbxDouble3(specular.r,specular.g,specular.b)); lMaterial->Shininess = mat->getShininessFront(); lMaterial->ShadingModel.Set("Phong"); if (hasTextures && mat->hasTexture()) { QString texFilename = mat->getTextureFilename(); //texture has not already been processed if (!texFilenames.contains(texFilename)) { //if necessary, we (try to) create a subfolder to store textures if (!texDir.exists()) { texDir = baseDir; if (texDir.mkdir(textDirName)) { texDir.cd(textDirName); } else { textDirName = QString(); ccLog::Warning("[FBX] Failed to create subfolder '%1' to store texture files (files will be stored next to the .fbx file)"); } } QFileInfo fileInfo(texFilename); QString baseTexName = fileInfo.fileName(); //add extension QString extension = QFileInfo(texFilename).suffix(); if (fileInfo.suffix().isEmpty()) baseTexName += QString(".png"); QString absoluteFilename = texDir.absolutePath() + QString("/") + baseTexName; ccLog::PrintDebug(QString("[FBX] Material '%1' texture: %2").arg(mat->getName()).arg(absoluteFilename)); texFilenames[texFilename] = absoluteFilename; } //mat.texture.save(absoluteFilename); // Set texture properties. FbxFileTexture* lTexture = FbxFileTexture::Create(pScene,"DiffuseTexture"); assert(!texFilenames[texFilename].isEmpty()); lTexture->SetFileName(qPrintable(texFilenames[texFilename])); lTexture->SetTextureUse(FbxTexture::eStandard); lTexture->SetMappingType(FbxTexture::eUV); lTexture->SetMaterialUse(FbxFileTexture::eModelMaterial); lTexture->SetSwapUV(false); lTexture->SetTranslation(0.0, 0.0); lTexture->SetScale(1.0, 1.0); lTexture->SetRotation(0.0, 0.0); lTexture->UVSet.Set(FbxString(gDiffuseElementName)); // Connect texture to the proper UV // don't forget to connect the texture to the corresponding property of the material lMaterial->Diffuse.ConnectSrcObject(lTexture); } int matIndex = lNode->AddMaterial(lMaterial); assert(matIndex == static_cast<int>(i)); } //don't forget to save the texture files { for (QMap<QString,QString>::ConstIterator it = texFilenames.begin(); it != texFilenames.end(); ++it) { const QImage image = ccMaterial::GetTexture(it.key()); image.mirrored().save(it.value()); } texFilenames.clear(); //don't need this anymore! } // Create 'triangle to material index' mapping { FbxGeometryElementMaterial* lMaterialElement = lMesh->CreateElementMaterial(); lMaterialElement->SetMappingMode(FbxGeometryElement::eByPolygon); lMaterialElement->SetReferenceMode(FbxGeometryElement::eIndexToDirect); } hasMaterial = true; } // colors if (cloud->hasColors()) { FbxGeometryElementVertexColor* lGeometryElementVertexColor = lMesh->CreateElementVertexColor(); lGeometryElementVertexColor->SetMappingMode(FbxGeometryElement::eByControlPoint); lGeometryElementVertexColor->SetReferenceMode(FbxGeometryElement::eDirect); lGeometryElementVertexColor->GetDirectArray().SetCount(vertCount); for (unsigned i=0; i<vertCount; ++i) { const colorType* C = cloud->getPointColor(i); FbxColor col( static_cast<double>(C[0])/ccColor::MAX, static_cast<double>(C[1])/ccColor::MAX, static_cast<double>(C[2])/ccColor::MAX ); lGeometryElementVertexColor->GetDirectArray().SetAt(i,col); } if (!hasMaterial) { //it seems that we have to create a fake material in order for the colors to be displayed (in Unity and FBX Review at least)! FbxSurfacePhong *lMaterial = FbxSurfacePhong::Create(pScene, "ColorMaterial"); lMaterial->Emissive.Set(FbxDouble3(0,0,0)); lMaterial->Ambient.Set(FbxDouble3(0,0,0)); lMaterial->Diffuse.Set(FbxDouble3(1,1,1)); lMaterial->Specular.Set(FbxDouble3(0,0,0)); lMaterial->Shininess = 0; lMaterial->ShadingModel.Set("Phong"); FbxGeometryElementMaterial* lMaterialElement = lMesh->CreateElementMaterial(); lMaterialElement->SetMappingMode(FbxGeometryElement::eAllSame); lMaterialElement->SetReferenceMode(FbxGeometryElement::eDirect); lNode->AddMaterial(lMaterial); } } // Create polygons { for (unsigned j=0; j<faceCount; ++j) { const CCLib::TriangleSummitsIndexes* tsi = mesh->getTriangleIndexes(j); int matIndex = hasMaterial ? asCCMesh->getTriangleMtlIndex(j) : -1; lMesh->BeginPolygon(matIndex); lMesh->AddPolygon(tsi->i1); lMesh->AddPolygon(tsi->i2); lMesh->AddPolygon(tsi->i3); lMesh->EndPolygon(); } } return lNode; }
void ExportFbxMesh(const Value& obj) { string name = obj["Name"].GetString(); FbxNode* pNode = FbxNode::Create(pManager, name.c_str()); FbxMesh* pMesh = FbxMesh::Create(pManager, name.c_str()); pNode->AddNodeAttribute(pMesh); pScene->GetRootNode()->AddChild(pNode); int numVertex = obj["NumVertex"].GetInt(); { pMesh->InitControlPoints(numVertex); FbxVector4* lControlPoints = pMesh->GetControlPoints(); const Value& pos = obj["Position"]; for (int i = 0; i < numVertex; i++) { double x = pos[i * 3 + 0].GetDouble(); x = -x; double y = pos[i * 3 + 1].GetDouble(); double z = pos[i * 3 + 2].GetDouble(); lControlPoints[i] = FbxVector4(x, y, z); } } { FbxGeometryElementNormal* lGeometryElementNormal = pMesh->CreateElementNormal(); lGeometryElementNormal->SetMappingMode(FbxGeometryElement::eByControlPoint); lGeometryElementNormal->SetReferenceMode(FbxGeometryElement::eDirect); FbxLayerElementArrayTemplate<FbxVector4>& array = lGeometryElementNormal->GetDirectArray(); const Value& normal = obj["Normal"]; for (int i = 0; i < numVertex; i++) { double x = normal[i * 3 + 0].GetDouble(); x = -x; double y = normal[i * 3 + 1].GetDouble(); double z = normal[i * 3 + 2].GetDouble(); array.Add(FbxVector4(x, y, z)); } } { FbxGeometryElementUV* lUVDiffuseElement = pMesh->CreateElementUV("DiffuseUV"); FBX_ASSERT(lUVDiffuseElement != NULL); lUVDiffuseElement->SetMappingMode(FbxGeometryElement::eByControlPoint); lUVDiffuseElement->SetReferenceMode(FbxGeometryElement::eDirect); FbxLayerElementArrayTemplate<FbxVector2>& array = lUVDiffuseElement->GetDirectArray(); const Value& v = obj["UV0"]; for (int i = 0; i < numVertex; i++) { double x = v[i * 2 + 0].GetDouble(); double y = v[i * 2 + 1].GetDouble(); array.Add(FbxVector2(x, y)); } } { const Value& color = obj["Color"]; if (!color.IsNull()) { FbxGeometryElementVertexColor* pColorElement = pMesh->CreateElementVertexColor(); FBX_ASSERT(pColorElement != NULL); pColorElement->SetMappingMode(FbxGeometryElement::eByControlPoint); pColorElement->SetReferenceMode(FbxGeometryElement::eDirect); FbxLayerElementArrayTemplate<FbxColor>& array = pColorElement->GetDirectArray(); for (int i = 0; i < numVertex; i++) { double r = color[i * 4 + 0].GetDouble(); double g = color[i * 4 + 1].GetDouble(); double b = color[i * 4 + 2].GetDouble(); double a = color[i * 4 + 3].GetDouble(); array.Add(FbxColor(r, g, b, a)); } } } { const Value& Indeices = obj["Indeices"]; for (uint32_t subMesh = 0; subMesh < Indeices.Size(); subMesh++) { const Value& index0 = Indeices[subMesh]; int numIndex = index0.Size(); printf("index %d\n", numIndex); for (int i = 0; i < numIndex / 3; i++) { pMesh->BeginPolygon(-1, -1, subMesh); int index[3] = { index0[i * 3 + 0].GetInt(), index0[i * 3 + 1].GetInt(), index0[i * 3 + 2].GetInt(), }; pMesh->AddPolygon(index[0]); pMesh->AddPolygon(index[2]); pMesh->AddPolygon(index[1]); pMesh->EndPolygon(); } } } ////////////////////////////////////////////////////////////////////////// // export skin const Value& boneIndex = obj["BoneIndex"]; if (!boneIndex.IsNull()) { if (fbxBones.empty()) { printf("no bones, can not export skin"); return; } const Value& boneWeight = obj["BoneWeight"]; vector<FbxCluster*> clusters(fbxBones.size(), NULL); for (uint32_t i = 0; i < fbxBones.size(); i++) { FbxCluster* pCluster = FbxCluster::Create(pScene, ""); pCluster->SetLink(fbxBones[i]); pCluster->SetLinkMode(FbxCluster::eTotalOne); clusters[i] = pCluster; } for (int i = 0; i < numVertex; i++) { for (int j = 0; j < 4; j++) { int bone = boneIndex[i * 4 + j].GetInt(); double weight = boneWeight[i * 4 + j].GetDouble(); clusters[bone]->AddControlPointIndex(i, weight); } } FbxSkin* lSkin = FbxSkin::Create(pScene, ""); FbxScene* p = pNode->GetScene(); FbxAMatrix modelMatrix = pNode->EvaluateGlobalTransform(); for (uint32_t i = 0; i < clusters.size(); i++) { clusters[i]->SetTransformMatrix(modelMatrix); FbxAMatrix boneMatrix = fbxBones[i]->EvaluateGlobalTransform(); clusters[i]->SetTransformLinkMatrix(boneMatrix); lSkin->AddCluster(clusters[i]); } pMesh->AddDeformer(lSkin); } }