Bone* BuildSkeleton(aiNode& aiNode, Model& model, Bone* parent) { Bone* bone = nullptr; auto iter = model.mBoneIndexMap.find(aiNode.mName.C_Str()); if(iter == model.mBoneIndexMap.end()) { bone = new Bone(); if(aiNode.mName.length > 0) { bone->name = aiNode.mName.C_Str(); } else { static u32 counter = 0; char buffer[128]; sprintf_s(buffer, 128, "Unknown%d", counter++); bone->name = buffer; } bone->index = model.mBones.size(); model.mBones.push_back(bone); model.mBoneIndexMap.insert(std::make_pair(bone->name, bone->index)); } else { bone = model.mBones[iter->second]; } bone->transform = *(Math::Matrix*)&(aiNode.mTransformation); bone->parent = parent; if (parent != nullptr) { bone->parentIndex = parent->index; } for(u32 i = 0; i < aiNode.mNumChildren; ++i) { Bone* child = BuildSkeleton(*(aiNode.mChildren[i]), model, bone); bone->children.push_back(child); } return bone; }
void ImportModel(const char* infileName, const char* outfileName) { Assimp::Importer importer; u32 flags = aiProcess_Triangulate | aiProcess_JoinIdenticalVertices | aiProcess_SortByPType | aiProcess_FlipUVs; const aiScene* scene = importer.ReadFile(infileName, flags); ASSERT(scene, "Failed to load model - %s", importer.GetErrorString()); FILE* pFile = nullptr; fopen_s(&pFile, outfileName, "w"); fprintf_s(pFile, "MeshCount: %d\n", scene->mNumMeshes); fprintf_s(pFile, "TextureCount: %d\n", scene->mNumMaterials); Model model; if(scene->HasMeshes()) { for(u32 meshIndex = 0; meshIndex < scene->mNumMeshes; ++meshIndex) { aiMesh* aiMesh = scene->mMeshes[meshIndex]; fprintf_s(pFile, "VertexCount: %d\n", aiMesh->mNumVertices); fprintf_s(pFile, "IndexCount: %d\n", aiMesh->mNumFaces * 3); Mesh* mesh = new Mesh(); Mesh::Vertex* vertices = new Mesh::Vertex[aiMesh->mNumVertices]; Mesh::Vertex* vertexIter = vertices; for(u32 i = 0; i < aiMesh->mNumVertices; ++i) { fprintf_s(pFile, "%f %f %f ", aiMesh->mVertices[i].x, aiMesh->mVertices[i].y, aiMesh->mVertices[i].z);// * 0.1f; if(aiMesh->HasNormals()) { fprintf_s(pFile, "%f %f %f\n", aiMesh->mNormals[i].x, aiMesh->mNormals[i].y, aiMesh->mNormals[i].z);// * 0.1f; } else { fprintf_s(pFile, "\n"); } vertexIter->position.x = aiMesh->mVertices[i].x;// * 0.1f; vertexIter->position.y = aiMesh->mVertices[i].y;// * 0.1f; vertexIter->position.z = aiMesh->mVertices[i].z;// * 0.1f; ++vertexIter; } if(aiMesh->HasNormals()) { vertexIter = vertices; for(u32 i = 0; i < aiMesh->mNumVertices; ++i) { vertexIter->normal.x = aiMesh->mNormals[i].x; vertexIter->normal.y = aiMesh->mNormals[i].y; vertexIter->normal.z = aiMesh->mNormals[i].z; ++vertexIter; } } const u32 uvChannelCount = aiMesh->GetNumUVChannels(); for (u32 i = 0; i < uvChannelCount; ++i) { Mesh::Vertex* vertexIter = vertices; for (u32 j = 0; j < aiMesh->mNumVertices; ++j) { fprintf_s(pFile, "%f %f\n", aiMesh->mTextureCoords[i][j].x, aiMesh->mTextureCoords[i][j].y); vertexIter->texcoord.x = aiMesh->mTextureCoords[i][j].x; vertexIter->texcoord.y = aiMesh->mTextureCoords[i][j].y; ++vertexIter; } // TODO - Only get the first set of uv for now break; } u16* indices = new u16[aiMesh->mNumFaces * 3]; u16* indexIter = indices; for(u32 i = 0; i < aiMesh->mNumFaces; ++i) { fprintf_s(pFile, "%d %d %d\n", aiMesh->mFaces[i].mIndices[0], aiMesh->mFaces[i].mIndices[1], aiMesh->mFaces[i].mIndices[2]); indexIter[0] = aiMesh->mFaces[i].mIndices[0]; indexIter[1] = aiMesh->mFaces[i].mIndices[1]; indexIter[2] = aiMesh->mFaces[i].mIndices[2]; indexIter += 3; } MeshBuilder::GenerateMesh(*mesh, vertices, aiMesh->mNumVertices, indices, aiMesh->mNumFaces * 3); model.mMeshes.push_back(mesh); SafeDeleteArray(vertices); SafeDeleteArray(indices); if(aiMesh->HasBones()) { VertexWeights& vertexWeights = mesh->GetVertexWeights(); vertexWeights.resize(aiMesh->mNumVertices); for(u32 i = 0; i < aiMesh->mNumBones; ++i) { aiBone* aiBone = aiMesh->mBones[i]; u32 boneIndex = 0; // see if we have already added this bone auto iter = model.mBoneIndexMap.find(aiBone->mName.C_Str()); if(iter != model.mBoneIndexMap.end()) { boneIndex = iter->second; } else { boneIndex = model.mBones.size(); Bone* newBone = new Bone(); ASSERT(aiBone->mName.length > 0, "Bone %d doesn't have a name!", boneIndex); newBone->name = aiBone->mName.C_Str(); newBone->index = boneIndex; newBone->offsetTransform = *(Math::Matrix*)&aiBone->mOffsetMatrix; model.mBones.push_back(newBone); model.mBoneIndexMap.insert(std::make_pair(aiBone->mName.C_Str(), boneIndex)); } for(u32 j = 0; j < aiBone->mNumWeights; ++j) { const aiVertexWeight& aiVertexWeight = aiBone->mWeights[j]; BoneWeight weight; weight.boneIndex = boneIndex; weight.weight = aiVertexWeight.mWeight; vertexWeights[aiVertexWeight.mVertexId].push_back(weight); } } } } } // Read material data if(scene->HasMaterials()) { for(u32 i = 0; i < scene->mNumMaterials; ++i) { aiMaterial* material = scene->mMaterials[i]; const u32 textureCount = material->GetTextureCount(aiTextureType_DIFFUSE); for(u32 j = 0; j < textureCount; ++j) { aiString texturePath; if(material->GetTexture(aiTextureType_DIFFUSE, j, &texturePath) == AI_SUCCESS) { fprintf_s(pFile, texturePath.C_Str()); fprintf_s(pFile, "\n"); } } } } // Read animation data if(scene->HasAnimations()) { model.mRoot = BuildSkeleton(*scene->mRootNode, model, nullptr); fprintf_s(pFile, "NumBones: %d\n", model.mBones.size()); fprintf_s(pFile, "NumAnimations: %d\n", scene->mNumAnimations); for(u32 animIndex = 0; animIndex < scene->mNumAnimations; ++animIndex) { aiAnimation* aiAnimation = scene->mAnimations[animIndex]; AnimationClip* animClip = new AnimationClip(); animClip->mName = aiAnimation->mName.C_Str(); animClip->mDuration = (f32)aiAnimation->mDuration; animClip->mTicksPerSecond = (f32)aiAnimation->mTicksPerSecond; if(animClip->mTicksPerSecond == 0.0f) { animClip->mTicksPerSecond = 1.0f; } fprintf_s(pFile, "%s\n", animClip->mName.c_str()); fprintf_s(pFile, "%f\n", animClip->mDuration); fprintf_s(pFile, "%f\n", animClip->mTicksPerSecond); fprintf_s(pFile, "NumChannels: %d\n", aiAnimation->mNumChannels); for(u32 boneAnimIndex = 0; boneAnimIndex < aiAnimation->mNumChannels; ++boneAnimIndex) { aiNodeAnim* aiNodeAnim = aiAnimation->mChannels[boneAnimIndex]; BoneAnimation* boneAnim = new BoneAnimation(); boneAnim->mBoneIndex = model.mBoneIndexMap.at(aiNodeAnim->mNodeName.C_Str()); fprintf_s(pFile, "%d\n", boneAnim->mBoneIndex); ASSERT(aiNodeAnim->mNumPositionKeys == aiNodeAnim->mNumRotationKeys, "Mismatched key count."); ASSERT(aiNodeAnim->mNumPositionKeys == aiNodeAnim->mNumScalingKeys, "Mismatched key count."); fprintf_s(pFile, "NumPositionKeys: %d\n", aiNodeAnim->mNumPositionKeys); for(u32 keyIndex = 0; keyIndex < aiNodeAnim->mNumPositionKeys; ++keyIndex) { const aiVectorKey& posKey = aiNodeAnim->mPositionKeys[keyIndex]; const aiQuatKey& rotKey = aiNodeAnim->mRotationKeys[keyIndex]; const aiVectorKey& scaleKey = aiNodeAnim->mScalingKeys[keyIndex]; ASSERT(posKey.mTime == rotKey.mTime, "Mismatched key time"); ASSERT(posKey.mTime == scaleKey.mTime, "Mismatched key time"); Keyframe* keyframe = new Keyframe(); keyframe->mTranslation = Math::Vector3(posKey.mValue.x, posKey.mValue.y, posKey.mValue.z); keyframe->mRotation = Math::Quaternion(rotKey.mValue.x, rotKey.mValue.y, rotKey.mValue.z, rotKey.mValue.w); keyframe->mScale = Math::Vector3(scaleKey.mValue.x, scaleKey.mValue.y, scaleKey.mValue.z); keyframe->mTime = (f32)posKey.mTime; fprintf_s(pFile, "%f %f %f\n", posKey.mValue.x, posKey.mValue.y, posKey.mValue.z); fprintf_s(pFile, "%f %f %f %f\n", rotKey.mValue.x, rotKey.mValue.y, rotKey.mValue.z, rotKey.mValue.w); fprintf_s(pFile, "%f %f %f\n", scaleKey.mValue.x, scaleKey.mValue.y, scaleKey.mValue.z); fprintf_s(pFile, "%f\n", (f32)posKey.mTime); boneAnim->mKeyframes.push_back(keyframe); } animClip->mBoneAnimations.push_back(boneAnim); } model.mAnimations.push_back(animClip); } } for(u32 boneIndex = 0; boneIndex < model.mBones.size(); ++boneIndex) { Bone* bone = model.mBones[boneIndex]; // -> name fprintf_s(pFile, "BoneName: %s", bone->name.c_str()); fprintf_s(pFile, "\n"); // -> parent index fprintf_s(pFile, "Parent: %d\n", bone->parentIndex); // -> children indices u16 size = bone->children.size(); fprintf_s(pFile, "NumberOfChildren: %d\n", size); for(u16 i = 0; i < size; ++i) { fprintf_s(pFile, "%d ", bone->children[i]->index); if(i == size - 1) { fprintf_s(pFile, "\n"); } } // -> transform fprintf_s(pFile, "Transform: %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f \n", bone->transform._11, bone->transform._12, bone->transform._13, bone->transform._14, bone->transform._21, bone->transform._22, bone->transform._23, bone->transform._24, bone->transform._31, bone->transform._32, bone->transform._33, bone->transform._34, bone->transform._41, bone->transform._42, bone->transform._43, bone->transform._44); // -> offset transform fprintf_s(pFile, "OffsetTransform: %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f \n", bone->offsetTransform._11, bone->offsetTransform._12, bone->offsetTransform._13, bone->offsetTransform._14, bone->offsetTransform._21, bone->offsetTransform._22, bone->offsetTransform._23, bone->offsetTransform._24, bone->offsetTransform._31, bone->offsetTransform._32, bone->offsetTransform._33, bone->offsetTransform._34, bone->offsetTransform._41, bone->offsetTransform._42, bone->offsetTransform._43, bone->offsetTransform._44); fprintf_s(pFile, "\n"); } for(u32 meshIndex = 0; meshIndex < model.mMeshes.size(); ++meshIndex) { Mesh* mesh = model.mMeshes[meshIndex]; const VertexWeights& vertexWeights = mesh->GetVertexWeights(); u32 numVertexWeights = vertexWeights.size(); fprintf_s(pFile, "NumVertexWeights: %d\n", numVertexWeights); for(u32 weightIndex = 0; weightIndex < numVertexWeights; ++weightIndex) { fprintf_s(pFile, "%d ", vertexWeights[weightIndex].size()); for(u32 bIndex = 0; bIndex < vertexWeights[weightIndex].size(); ++bIndex) { fprintf_s(pFile, "%d %f ", vertexWeights[weightIndex][bIndex].boneIndex, vertexWeights[weightIndex][bIndex].weight); } fprintf_s(pFile, "\n"); } } fclose(pFile); }
void ExportMd5Mesh(const CSkeletalMesh *Mesh) { guard(ExportMd5Mesh); int i; UObject *OriginalMesh = Mesh->OriginalMesh; if (!Mesh->Lods.Num()) { appNotify("Mesh %s has 0 lods", OriginalMesh->Name); return; } FArchive *Ar = CreateExportArchive(OriginalMesh, "%s.md5mesh", OriginalMesh->Name); if (!Ar) return; const CSkelMeshLod &Lod = Mesh->Lods[0]; Ar->Printf( "MD5Version 10\n" "commandline \"Created with UE Viewer\"\n" "\n" "numJoints %d\n" "numMeshes %d\n" "\n", Mesh->RefSkeleton.Num(), Lod.Sections.Num() ); // compute skeleton TArray<CCoords> BoneCoords; BuildSkeleton(BoneCoords, Mesh->RefSkeleton); // write joints Ar->Printf("joints {\n"); for (i = 0; i < Mesh->RefSkeleton.Num(); i++) { const CSkelMeshBone &B = Mesh->RefSkeleton[i]; const CCoords &BC = BoneCoords[i]; CVec3 BP; CQuat BO; BP = BC.origin; BO.FromAxis(BC.axis); if (BO.w < 0) BO.Negate(); // W-component of quaternion will be removed ... Ar->Printf( "\t\"%s\"\t%d ( %f %f %f ) ( %.10f %.10f %.10f )\n", *B.Name, (i == 0) ? -1 : B.ParentIndex, VECTOR_ARG(BP), BO.x, BO.y, BO.z ); #if 0 //!! if (i == 32 || i == 34) { CCoords BC; BC.origin = BP; BO.ToAxis(BC.axis); appNotify("Bone %d (%8.3f %8.3f %8.3f) - (%8.3f %8.3f %8.3f %8.3f)", i, VECTOR_ARG(BP), QUAT_ARG(BO)); #define C BC appNotify("INV : o=%8.3f %8.3f %8.3f", VECTOR_ARG(C.origin )); appNotify(" 0=%8.3f %8.3f %8.3f", VECTOR_ARG(C.axis[0])); appNotify(" 1=%8.3f %8.3f %8.3f", VECTOR_ARG(C.axis[1])); appNotify(" 2=%8.3f %8.3f %8.3f", VECTOR_ARG(C.axis[2])); #undef C // BO.Negate(); BO.w *= -1; BO.ToAxis(BC.axis); #define C BC appNotify("INV2 : o=%8.3f %8.3f %8.3f", VECTOR_ARG(C.origin )); appNotify(" 0=%8.3f %8.3f %8.3f", VECTOR_ARG(C.axis[0])); appNotify(" 1=%8.3f %8.3f %8.3f", VECTOR_ARG(C.axis[1])); appNotify(" 2=%8.3f %8.3f %8.3f", VECTOR_ARG(C.axis[2])); #undef C } #endif } Ar->Printf("}\n\n"); // collect weights information TArray<VertInfluences> Weights; // Point -> Influences Weights.AddZeroed(Lod.NumVerts); for (i = 0; i < Lod.NumVerts; i++) { const CSkelMeshVertex &V = Lod.Verts[i]; CVec4 UnpackedWeights; V.UnpackWeights(UnpackedWeights); for (int j = 0; j < NUM_INFLUENCES; j++) { if (V.Bone[j] < 0) break; VertInfluence *I = new (Weights[i].Inf) VertInfluence; I->Bone = V.Bone[j]; I->Weight = UnpackedWeights[j]; } } CIndexBuffer::IndexAccessor_t Index = Lod.Indices.GetAccessor(); // write meshes // we are using some terms here: // - "mesh vertex" is a vertex in Lod.Verts[] array, global for whole mesh // - "surcace vertex" is a vertex from the mesh stripped to only one (current) section for (int m = 0; m < Lod.Sections.Num(); m++) { const CMeshSection &Sec = Lod.Sections[m]; TArray<int> MeshVerts; // surface vertex -> mesh vertex TArray<int> BackWedge; // mesh vertex -> surface vertex TArray<bool> UsedVerts; // mesh vertex -> surface: used of not TArray<int> MeshWeights; // mesh vertex -> weight index MeshVerts.Empty(Lod.NumVerts); UsedVerts.AddZeroed(Lod.NumVerts); BackWedge.AddZeroed(Lod.NumVerts); MeshWeights.AddZeroed(Lod.NumVerts); // find verts and triangles for current material for (i = 0; i < Sec.NumFaces * 3; i++) { int idx = Index(i + Sec.FirstIndex); if (UsedVerts[idx]) continue; // vertex is already used in previous triangle UsedVerts[idx] = true; int locWedge = MeshVerts.Add(idx); BackWedge[idx] = locWedge; } // find influences int WeightIndex = 0; for (i = 0; i < Lod.NumVerts; i++) { if (!UsedVerts[i]) continue; MeshWeights[i] = WeightIndex; WeightIndex += Weights[i].Inf.Num(); } // mesh header const UUnrealMaterial *Tex = Sec.Material; if (Tex) { Ar->Printf( "mesh {\n" "\tshader \"%s\"\n\n", Tex->Name ); ExportObject(Tex); } else { Ar->Printf( "mesh {\n" "\tshader \"material_%d\"\n\n", m ); } // verts Ar->Printf("\tnumverts %d\n", MeshVerts.Num()); for (i = 0; i < MeshVerts.Num(); i++) { int iPoint = MeshVerts[i]; const CSkelMeshVertex &V = Lod.Verts[iPoint]; Ar->Printf("\tvert %d ( %f %f ) %d %d\n", i, V.UV.U, V.UV.V, MeshWeights[iPoint], Weights[iPoint].Inf.Num()); } // triangles Ar->Printf("\n\tnumtris %d\n", Sec.NumFaces); for (i = 0; i < Sec.NumFaces; i++) { Ar->Printf("\ttri %d", i); #if MIRROR_MESH for (int j = 2; j >= 0; j--) #else for (int j = 0; j < 3; j++) #endif Ar->Printf(" %d", BackWedge[Index(Sec.FirstIndex + i * 3 + j)]); Ar->Printf("\n"); } // weights Ar->Printf("\n\tnumweights %d\n", WeightIndex); int saveWeightIndex = WeightIndex; WeightIndex = 0; for (i = 0; i < Lod.NumVerts; i++) { if (!UsedVerts[i]) continue; for (int j = 0; j < Weights[i].Inf.Num(); j++) { const VertInfluence &I = Weights[i].Inf[j]; CVec3 v; v = Lod.Verts[i].Position; #if MIRROR_MESH v[1] *= -1; // y #endif BoneCoords[I.Bone].TransformPoint(v, v); Ar->Printf( "\tweight %d %d %f ( %f %f %f )\n", WeightIndex, I.Bone, I.Weight, VECTOR_ARG(v) ); WeightIndex++; } } assert(saveWeightIndex == WeightIndex); // mesh footer Ar->Printf("}\n"); } delete Ar; unguard; }
void USkelModel::Serialize(FArchive &Ar) { guard(USkelModel::Serialize); assert(Ar.IsLoading); // no saving ... Super::Serialize(Ar); // USkelModel data int nummeshes; int numjoints; int numframes; int numsequences; int numskins; int rootjoint; FVector PosOffset; // Offset of creature relative to base FRotator RotOffset; // Offset of creatures rotation TArray<RMesh> meshes; TArray<RJoint> joints; TArray<FRSkelAnimSeq> AnimSeqs; // Compressed animation data for sequence TArray<RAnimFrame> frames; Ar << nummeshes << numjoints << numframes << numsequences << numskins << rootjoint; Ar << meshes << joints << AnimSeqs << frames << PosOffset << RotOffset; int modelIdx; // create all meshes first, then fill them (for better view order) for (modelIdx = 0; modelIdx < meshes.Num(); modelIdx++) { // create new USkeletalMesh // use "CreateClass()" instead of "new USkeletalMesh" to allow this object to be // placed in GObjObjects array and be browsable in a viewer USkeletalMesh *sm = static_cast<USkeletalMesh*>(CreateClass("SkeletalMesh")); char nameBuf[256]; appSprintf(ARRAY_ARG(nameBuf), "%s_%d", Name, modelIdx); const char *name = appStrdupPool(nameBuf); Meshes.Add(sm); // setup UOnject sm->Name = name; sm->Package = Package; sm->PackageIndex = INDEX_NONE; // not really exported sm->Outer = NULL; } // create animation Anim = static_cast<UMeshAnimation*>(CreateClass("MeshAnimation")); Anim->Name = Name; Anim->Package = Package; Anim->PackageIndex = INDEX_NONE; // not really exported Anim->Outer = NULL; ConvertRuneAnimations(*Anim, joints, AnimSeqs); Anim->ConvertAnims(); //?? second conversion // get baseframe assert(strcmp(Anim->AnimSeqs[0].Name, "baseframe") == 0); const TArray<AnalogTrack> &BaseAnim = Anim->Moves[0].AnimTracks; // compute bone coordinates TArray<CCoords> BoneCoords; BuildSkeleton(BoneCoords, joints, BaseAnim); // setup meshes for (modelIdx = 0; modelIdx < meshes.Num(); modelIdx++) { int i, j; const RMesh &src = meshes[modelIdx]; USkeletalMesh *sm = Meshes[modelIdx]; sm->Animation = Anim; // setup ULodMesh sm->RotOrigin = RotOffset; sm->MeshScale.Set(1, 1, 1); sm->MeshOrigin = PosOffset; // copy skeleton sm->RefSkeleton.Empty(joints.Num()); for (i = 0; i < joints.Num(); i++) { const RJoint &J = joints[i]; FMeshBone *B = new(sm->RefSkeleton) FMeshBone; B->Name = J.name; B->Flags = 0; B->ParentIndex = (J.parent > 0) ? J.parent : 0; // -1 -> 0 // copy bone orientations from base animation frame B->BonePos.Orientation = BaseAnim[i].KeyQuat[0]; B->BonePos.Position = BaseAnim[i].KeyPos[0]; } // copy vertices int VertexCount = sm->VertexCount = src.verts.Num(); sm->Points.Empty(VertexCount); for (i = 0; i < VertexCount; i++) { const RVertex &v1 = src.verts[i]; FVector *V = new(sm->Points) FVector; // transform point from local bone space to model space BoneCoords[v1.joint1].UnTransformPoint(CVT(v1.point1), CVT(*V)); } // copy triangles and create wedges // here we create 3 wedges for each triangle. // it is possible to reduce number of wedges by finding duplicates, but we don't // need it here ... int TrisCount = src.tris.Num(); sm->Triangles.Empty(TrisCount); sm->Wedges.Empty(TrisCount * 3); int numMaterials = 0; // should detect real material count for (i = 0; i < TrisCount; i++) { const RTriangle &tri = src.tris[i]; // create triangle VTriangle *T = new(sm->Triangles) VTriangle; T->MatIndex = tri.polygroup; if (numMaterials <= tri.polygroup) numMaterials = tri.polygroup+1; // create wedges for (j = 0; j < 3; j++) { T->WedgeIndex[j] = sm->Wedges.Num(); FMeshWedge *W = new(sm->Wedges) FMeshWedge; W->iVertex = tri.vIndex[j]; W->TexUV = tri.tex[j]; } // reverse order of triangle vertices Exchange(T->WedgeIndex[0], T->WedgeIndex[1]); } // build influences for (i = 0; i < VertexCount; i++) { const RVertex &v1 = src.verts[i]; FVertInfluence *Inf = new(sm->VertInfluences) FVertInfluence; Inf->PointIndex = i; Inf->BoneIndex = v1.joint1; Inf->Weight = v1.weight1; if (Inf->Weight != 1.0f) { // influence for 2nd bone Inf = new(sm->VertInfluences) FVertInfluence; Inf->PointIndex = i; Inf->BoneIndex = v1.joint2; Inf->Weight = 1.0f - v1.weight1; } } // create materials for (i = 0; i < numMaterials; i++) { const char *texName = src.PolyGroupSkinNames[i]; FMeshMaterial *M1 = new(sm->Materials) FMeshMaterial; M1->PolyFlags = src.GroupFlags[i]; M1->TextureIndex = sm->Textures.Num(); if (strcmp(texName, "None") == 0) { // texture should be set from script sm->Textures.Add(NULL); continue; } // find texture in object's package int texExportIdx = Package->FindExport(texName); if (texExportIdx == INDEX_NONE) { appPrintf("ERROR: unable to find export \"%s\" for mesh \"%s\" (%d)\n", texName, Name, modelIdx); continue; } // load and remember texture UMaterial *Tex = static_cast<UMaterial*>(Package->CreateExport(texExportIdx)); sm->Textures.Add(Tex); } // setup UPrimitive properties using 1st animation frame // note: this->BoundingBox and this->BoundingSphere are null const RAnimFrame &F = frames[0]; assert(strcmp(AnimSeqs[0].Name, "baseframe") == 0 && AnimSeqs[0].StartFrame == 0); CVec3 mins, maxs; sm->BoundingBox = F.bounds; mins = CVT(F.bounds.Min); maxs = CVT(F.bounds.Max); CVec3 ¢er = CVT(sm->BoundingSphere); for (i = 0; i < 3; i++) center[i] = (mins[i] + maxs[i]) / 2; sm->BoundingSphere.R = VectorDistance(center, mins); // create CSkeletalMesh sm->ConvertMesh(); } unguard; }