public override IOModel ImportFromFile(string filename) { var model = new IOModel(); var materials = new List <string>(); var positions = new List <Vector3>(); var textures = new Dictionary <int, Texture>(); using (var reader = new StreamReader(File.OpenRead(filename))) { var line = reader.ReadLine(); if (line.Trim() != "Metasequoia Document") { logger.Error("Not a valid Metasequoia file"); return(null); } while (!reader.EndOfStream) { line = reader.ReadLine().Trim(); if (line == "" || line == "}") { continue; } // Parse chunks var chunk = line.Split(' ')[0]; if (chunk == "Format") { } else if (chunk == "Thumbnail") { IgnoreChunk(reader); } else if (chunk == "Scene") { IgnoreChunk(reader); } else if (chunk == "Material") { var numMaterials = int.Parse(line.Split(' ')[1]); if (numMaterials == 0) { return(null); } for (var i = 0; i < numMaterials; i++) { var materialString = reader.ReadLine().Trim(); var tokensMaterial = materialString.Split(' '); var material = new IOMaterial(tokensMaterial[0]); for (var j = 0; j < tokensMaterial.Length; j++) { var texturePath = ""; if (tokensMaterial[j].StartsWith("tex")) { texturePath = tokensMaterial[j].Substring(5, tokensMaterial[j].Length - 7); if (texturePath != "") { string basePath = Path.GetDirectoryName(filename); if (!File.Exists(Path.Combine(basePath, texturePath))) { basePath = Path.Combine(Path.GetDirectoryName(filename), "Texture"); } if (!File.Exists(Path.Combine(basePath, texturePath))) { throw new FileNotFoundException("Texture " + texturePath + " could not be found"); } textures.Add(i, GetTexture(basePath, texturePath)); } material.Texture = textures[i]; break; } } model.Materials.Add(material); } } else if (chunk == "Object") { var name = line.Split(' ')[1]; var mesh = new IOMesh(name.Replace("\"", "")); var tokensName = mesh.Name.Split('_'); positions = new List <Vector3>(); if (name.Contains("TeRoom_")) { model.HasMultipleRooms = true; } var lastVertex = 0; var translation = Vector3.Zero; while (!reader.EndOfStream) { line = reader.ReadLine().Trim(); var tokens = line.Split(' '); if (tokens[0] == "translation" && model.HasMultipleRooms) { translation = ApplyAxesTransforms(new Vector3(ParseFloatCultureInvariant(tokens[1]), ParseFloatCultureInvariant(tokens[2]), ParseFloatCultureInvariant(tokens[3]))); } else if (tokens[0] == "vertex") { var numVertices = int.Parse(tokens[1]); for (var i = 0; i < numVertices; i++) { var tokensPosition = reader.ReadLine().Trim().Split(' '); var newPos = ApplyAxesTransforms(new Vector3(ParseFloatCultureInvariant(tokensPosition[0]), ParseFloatCultureInvariant(tokensPosition[1]), ParseFloatCultureInvariant(tokensPosition[2])) ); positions.Add(newPos); } line = reader.ReadLine().Trim(); } else if (tokens[0] == "face") { var numFaces = int.Parse(tokens[1]); for (var i = 0; i < numFaces; i++) { line = reader.ReadLine().Trim(); var numVerticesInFace = int.Parse(line.Substring(0, line.IndexOf(' '))); var poly = new IOPolygon(numVerticesInFace == 3 ? IOPolygonShape.Triangle : IOPolygonShape.Quad); // We MUST have vertices var stringVertices = GetSubBlock(line, "V"); if (stringVertices == "") { return(null); } var tokensVertices = stringVertices.Split(' '); for (var k = 0; k < numVerticesInFace; k++) { var index = int.Parse(tokensVertices[k]); mesh.Positions.Add(positions[index]); poly.Indices.Add(lastVertex); lastVertex++; } // Change vertex winding if (_settings.InvertFaces) { poly.Indices.Reverse(); } // UV var stringUV = GetSubBlock(line, "UV"); if (stringUV != "") { var tokensUV = stringUV.Split(' '); for (var k = 0; k < numVerticesInFace; k++) { var uv = ApplyUVTransform(new Vector2(ParseFloatCultureInvariant(tokensUV[2 * k]), ParseFloatCultureInvariant(tokensUV[2 * k + 1])), textures[0].Image.Width, textures[0].Image.Height); mesh.UV.Add(uv); } } // Colors var stringColor = GetSubBlock(line, "COL"); if (stringColor != "") { var tokensColor = stringColor.Split(' '); for (var k = 0; k < numVerticesInFace; k++) { var color = ApplyColorTransform(GetColor(long.Parse(tokensColor[k]))); mesh.Colors.Add(color); } } else { for (var k = 0; k < numVerticesInFace; k++) { var color = ApplyColorTransform(Vector4.One); mesh.Colors.Add(color); } } // Material index var stringMaterialIndex = GetSubBlock(line, "M"); var materialIndex = 0; if (stringMaterialIndex != "") { materialIndex = int.Parse(stringMaterialIndex); } // Add polygon to the submesh (and add submesh if not existing yet) var material = model.Materials[materialIndex]; if (!mesh.Submeshes.ContainsKey(material)) { mesh.Submeshes.Add(material, new IOSubmesh(material)); } mesh.Submeshes[material].Polygons.Add(poly); } line = reader.ReadLine().Trim(); } else if (tokens[0] == "vertexattr") { // section to ignore IgnoreChunk(reader); } else if (tokens[0] == "}") { break; } } model.Meshes.Add(mesh); } } } CalculateNormals(model); return(model); }
/// <summary> /// /// </summary> /// <returns></returns> public IOMaterial LoadMaterial(Material mat) { var effectURL = mat.Instance_Effect?.URL; if (effectURL == null) { return(null); } var effect = _collada.Library_Effects.Effect.ToList().Find(e => e.ID == ColladaHelper.SanitizeID(effectURL)); IOMaterial material = new IOMaterial() { Name = mat.ID, Label = mat.Name }; if (effect != null && effect.Profile_COMMON != null && effect.Profile_COMMON.Length > 0) { var prof = effect.Profile_COMMON[0]; var phong = prof.Technique.Phong; var blinn = prof.Technique.Blinn; var lambert = prof.Technique.Lambert; if (phong != null) { if (phong.Transparency != null) { material.Alpha = phong.Transparency.Float.Value; } if (phong.Shininess != null) { material.Shininess = phong.Shininess.Float.Value; } if (phong.Diffuse != null) { if (ReadEffectColorType(prof, phong.Diffuse, out Vector4 color, out IOTexture texture)) { material.DiffuseColor = color; } if (texture != null) { material.DiffuseMap = texture; } } if (phong.Ambient != null) { if (ReadEffectColorType(prof, phong.Ambient, out Vector4 color, out IOTexture texture)) { material.AmbientColor = color; } if (texture != null) { material.AmbientMap = texture; } } if (phong.Specular != null) { if (ReadEffectColorType(prof, phong.Specular, out Vector4 color, out IOTexture texture)) { material.SpecularColor = color; } if (texture != null) { material.SpecularMap = texture; } } if (phong.Reflective != null) { if (ReadEffectColorType(prof, phong.Reflective, out Vector4 color, out IOTexture texture)) { material.ReflectiveColor = color; } if (texture != null) { material.ReflectiveMap = texture; } } } if (lambert != null) { if (lambert.Transparency != null) { material.Alpha = lambert.Transparency.Float.Value; } if (lambert.Diffuse != null) { if (ReadEffectColorType(prof, lambert.Diffuse, out Vector4 color, out IOTexture texture)) { material.DiffuseColor = color; } if (texture != null) { material.DiffuseMap = texture; } } if (lambert.Ambient != null) { if (ReadEffectColorType(prof, lambert.Ambient, out Vector4 color, out IOTexture texture)) { material.AmbientColor = color; } if (texture != null) { material.AmbientMap = texture; } } if (lambert.Reflective != null) { if (ReadEffectColorType(prof, lambert.Reflective, out Vector4 color, out IOTexture texture)) { material.ReflectiveColor = color; } if (texture != null) { material.ReflectiveMap = texture; } } } if (blinn != null) { if (blinn.Transparency != null) { material.Alpha = blinn.Transparency.Float.Value; } if (blinn.Shininess != null) { material.Shininess = blinn.Shininess.Float.Value; } if (blinn.Diffuse != null) { if (ReadEffectColorType(prof, blinn.Diffuse, out Vector4 color, out IOTexture texture)) { material.DiffuseColor = color; } if (texture != null) { material.DiffuseMap = texture; } } if (blinn.Ambient != null) { if (ReadEffectColorType(prof, blinn.Ambient, out Vector4 color, out IOTexture texture)) { material.AmbientColor = color; } if (texture != null) { material.AmbientMap = texture; } } if (blinn.Specular != null) { if (ReadEffectColorType(prof, blinn.Specular, out Vector4 color, out IOTexture texture)) { material.SpecularColor = color; } if (texture != null) { material.SpecularMap = texture; } } if (blinn.Reflective != null) { if (ReadEffectColorType(prof, blinn.Reflective, out Vector4 color, out IOTexture texture)) { material.ReflectiveColor = color; } if (texture != null) { material.ReflectiveMap = texture; } } } } return(material); }
/// <summary> /// /// </summary> /// <param name="settings"></param> /// <param name="material"></param> /// <returns></returns> private HSD_MOBJ GenerateMaterial(IOMaterial material) { // create blank mobj var Mobj = new HSD_MOBJ(); Mobj.Material = new HSD_Material() { AMB_A = 0xFF, AMB_R = 0x7F, AMB_G = 0x7F, AMB_B = 0x7F, DiffuseColor = System.Drawing.Color.White, SpecularColor = System.Drawing.Color.White, Shininess = 50, Alpha = 1 }; // detect and set flags if (Settings.ImportVertexColor) { Mobj.RenderFlags |= RENDER_MODE.VERTEX; } if (Settings.EnableDiffuse) { Mobj.RenderFlags |= RENDER_MODE.DIFFUSE; } if (Settings.EnableConstant) { Mobj.RenderFlags |= RENDER_MODE.CONSTANT; } // Properties if (material != null && Settings.ImportMaterialInfo) { Mobj.Material.Shininess = material.Shininess; Mobj.Material.Alpha = material.Alpha; Mobj.Material.AMB_R = (byte)(material.AmbientColor.X * 255); Mobj.Material.AMB_G = (byte)(material.AmbientColor.Y * 255); Mobj.Material.AMB_B = (byte)(material.AmbientColor.Z * 255); Mobj.Material.AMB_A = (byte)(material.AmbientColor.W * 255); Mobj.Material.DIF_R = (byte)(material.DiffuseColor.X * 255); Mobj.Material.DIF_G = (byte)(material.DiffuseColor.Y * 255); Mobj.Material.DIF_B = (byte)(material.DiffuseColor.Z * 255); Mobj.Material.DIF_A = (byte)(material.DiffuseColor.W * 255); Mobj.Material.SPC_R = (byte)(material.SpecularColor.X * 255); Mobj.Material.SPC_G = (byte)(material.SpecularColor.Y * 255); Mobj.Material.SPC_B = (byte)(material.SpecularColor.Z * 255); Mobj.Material.SPC_A = (byte)(material.SpecularColor.W * 255); } // Textures if (material != null && Settings.ImportTexture) { if (material.DiffuseMap != null && !string.IsNullOrEmpty(material.DiffuseMap.FilePath)) { var texturePath = material.DiffuseMap.FilePath; if (texturePath.Contains("file://")) { texturePath = texturePath.Replace("file://", ""); } if (File.Exists(Path.Combine(_cache.FolderPath, texturePath))) { texturePath = Path.Combine(_cache.FolderPath, texturePath); } if (File.Exists(material.DiffuseMap.FilePath)) { texturePath = material.DiffuseMap.FilePath; } if (File.Exists(texturePath + ".png")) { texturePath = texturePath + ".png"; } var mobjPath = Path.Combine(Path.GetDirectoryName(texturePath), Path.GetFileNameWithoutExtension(texturePath)) + ".mobj"; if (Settings.ImportMOBJ && File.Exists(mobjPath)) { var dat = new HSDRaw.HSDRawFile(mobjPath); Mobj._s = dat.Roots[0].Data._s; return(Mobj); } else /// special mobj loading if (Path.GetExtension(texturePath).ToLower() == ".mobj") { var dat = new HSDRaw.HSDRawFile(texturePath); Mobj._s = dat.Roots[0].Data._s; return(Mobj); } else if (File.Exists(texturePath) && (texturePath.ToLower().EndsWith(".png") || texturePath.ToLower().EndsWith(".bmp"))) { Mobj.RenderFlags |= RENDER_MODE.TEX0; var tobj = TOBJConverter.ImportTOBJFromFile(texturePath, Settings.TextureFormat, Settings.PaletteFormat); tobj.Flags = TOBJ_FLAGS.LIGHTMAP_DIFFUSE | TOBJ_FLAGS.COORD_UV | TOBJ_FLAGS.COLORMAP_MODULATE; tobj.GXTexGenSrc = 4; tobj.TexMapID = GXTexMapID.GX_TEXMAP0; tobj.WrapS = ToGXWrapMode(material.DiffuseMap.WrapS); tobj.WrapT = ToGXWrapMode(material.DiffuseMap.WrapT); if (TOBJConverter.IsTransparent(tobj)) { _cache.HasXLU = true; Mobj.RenderFlags |= RENDER_MODE.XLU; tobj.Flags |= TOBJ_FLAGS.ALPHAMAP_MODULATE; } Mobj.Textures = tobj; } } } return(Mobj); }
/// <summary> /// /// </summary> /// <returns></returns> public List <IOMaterial> GetMaterials() { List <IOMaterial> materials = new List <IOMaterial>(); foreach (var m in _document.GetNodesByName("Material")) { // generate material IOMaterial mat = new IOMaterial() { Name = GetNameWithoutNamespace(m.Properties[NodeDescSize - 2].ToString()) }; // load material attributes if (m["ShadingModel"] != null && m["ShadingModel"].Properties[0].ToString().ToLower() == "phong")//ToLower Because it's sometimes "Phong" { var properties = m.GetNodesByName("P"); if (properties.Length == 0) { properties = m.GetNodesByName("Property"); } foreach (var prop in properties) { switch (prop.Properties[0].ToString()) { case "AmbientColor": mat.AmbientColor = GetColor(prop); break; case "DiffuseColor": mat.DiffuseColor = GetColor(prop); break; case "SpecularColor": mat.SpecularColor = GetColor(prop); break; case "ReflectionColor": mat.ReflectiveColor = GetColor(prop); break; case "Emissive": mat.EmissionColor = GetColor(prop); break; case "Shininess": mat.Shininess = (float)(double)prop.Properties[PropertyDescSize]; break; case "Opacity": mat.Alpha = (float)(double)prop.Properties[PropertyDescSize]; break; } } } // get textures var children = GetChildConnections(m).Where(e => e.Name == "Texture"); foreach (var t in children) { // the property is stored in the connections // for now always map it to diffuse mat.DiffuseMap = new IOTexture() { Name = t.Properties[NodeDescSize - 2].ToString(), FilePath = t["FileName"].Properties[0].ToString() }; } materials.Add(mat); } return(materials); }
/// <summary> /// /// </summary> /// <param name="filePath"></param> /// <returns></returns> public IOScene GetScene(string filePath) { IOScene scene = new IOScene(); IOModel model = new IOModel(); scene.Models.Add(model); using (FileStream stream = new FileStream(filePath, FileMode.Open)) using (StreamReader r = new StreamReader(stream)) { Dictionary <string, IOMesh> nameToMesh = new Dictionary <string, IOMesh>(); HashSet <IOBone> meshBones = new HashSet <IOBone>(); Dictionary <int, IOBone> idxToBone = new Dictionary <int, IOBone>(); Dictionary <int, int> idxToParent = new Dictionary <int, int>(); string mode = ""; int time = 0; while (!r.EndOfStream) { // read and clean line args var line = r.ReadLine().Trim(); var args = Regex.Replace(line, @"\s+", " ").Split(' '); // check for grouping switch (args[0]) { case "nodes": case "skeleton": case "triangles": case "end": mode = args[0]; break; } switch (mode) { case "nodes": if (args.Length >= 3) { args = line.Split('"'); var index = int.Parse(args[0].Trim()); var name = args[1]; var parentIndex = int.Parse(args[2].Trim()); IOBone bone = new IOBone() { Name = name }; idxToBone.Add(index, bone); idxToParent.Add(index, parentIndex); } break; case "skeleton": if (args.Length == 2 && args[0] == "time") { int.TryParse(args[1], out time); } if (args.Length == 7) { var index = int.Parse(args[0]); if (time == 0) { idxToBone[index].Translation = new System.Numerics.Vector3(float.Parse(args[1]), float.Parse(args[2]), float.Parse(args[3])); idxToBone[index].RotationEuler = new System.Numerics.Vector3(float.Parse(args[4]), float.Parse(args[5]), float.Parse(args[6])); } } break; case "triangles": { if (args.Length > 0 && args.Length < 9 && args[0] != "triangles") { var material = string.Join(" ", args); var v1 = ParseVertex(r.ReadLine(), idxToBone, out IOBone parent); var v2 = ParseVertex(r.ReadLine(), idxToBone, out parent); var v3 = ParseVertex(r.ReadLine(), idxToBone, out parent); var meshName = parent.Name + material; if (!meshBones.Contains(parent)) { meshBones.Add(parent); } meshName = Regex.Replace(meshName.Trim(), @"\s+", "_").Replace("#", ""); if (!nameToMesh.ContainsKey(meshName)) { // create and load material IOMaterial mat = new IOMaterial() { Name = material }; scene.Materials.Add(mat); // create io mesh var iomesh = new IOMesh() { Name = meshName }; // create triangle polygon iomesh.Polygons.Add(new IOPolygon() { MaterialName = material, PrimitiveType = IOPrimitive.TRIANGLE }); nameToMesh.Add(meshName, iomesh); } var mesh = nameToMesh[meshName]; mesh.Polygons[0].Indicies.Add(mesh.Vertices.Count); mesh.Polygons[0].Indicies.Add(mesh.Vertices.Count + 1); mesh.Polygons[0].Indicies.Add(mesh.Vertices.Count + 2); mesh.Vertices.Add(v1); mesh.Vertices.Add(v2); mesh.Vertices.Add(v3); } } break; } } // create skeleton hierarchy foreach (var bone in idxToBone) { var parent = idxToParent[bone.Key]; if (parent == -1) { if (meshBones.Count > 1 && meshBones.Contains(bone.Value)) { continue; } else { model.Skeleton.RootBones.Add(bone.Value); } } else { idxToBone[parent].AddChild(bone.Value); } } // dump mesh model.Meshes.AddRange(nameToMesh.Values); } return(scene); }
/// <summary> /// /// </summary> /// <param name="mat"></param> private void ProcessMaterial(IOMaterial mat) { // create phong shading var phong = new Phong() { Shininess = new FX_Common_Float_Or_Param_Type { Float = new IONET.Collada.Types.SID_Float() { sID = "shininess", Value = mat.Shininess } }, Transparency = new FX_Common_Float_Or_Param_Type() { Float = new IONET.Collada.Types.SID_Float() { sID = "transparency", Value = mat.Alpha } }, Reflectivity = new FX_Common_Float_Or_Param_Type() { Float = new IONET.Collada.Types.SID_Float() { sID = "reflectivity", Value = mat.Reflectivity } }, Ambient = GenerateTextureInfo("ambient", mat.AmbientColor, mat.AmbientMap), Diffuse = GenerateTextureInfo("diffuse", mat.DiffuseColor, mat.DiffuseMap), Specular = GenerateTextureInfo("specular", mat.SpecularColor, mat.SpecularMap), Emission = GenerateTextureInfo("emission", mat.EmissionColor, mat.EmissionMap), Reflective = GenerateTextureInfo("reflective", mat.ReflectiveColor, mat.ReflectiveMap), }; // create effect Effect effect = new Effect() { ID = GetUniqueID(mat.Name + "-effect"), Name = mat.Name, Profile_COMMON = new IONET.Collada.FX.Profiles.COMMON.Profile_COMMON[] { new IONET.Collada.FX.Profiles.COMMON.Profile_COMMON() { Technique = new IONET.Collada.FX.Profiles.COMMON.Effect_Technique_COMMON() { sID = "standard", Phong = phong }, } } }; // create material Material material = new Material() { ID = GetUniqueID(mat.Name), Name = mat.Name, Instance_Effect = new Instance_Effect() { URL = "#" + effect.ID } }; // add effect to effect library if (_collada.Library_Effects == null) { _collada.Library_Effects = new Library_Effects(); } if (_collada.Library_Effects.Effect == null) { _collada.Library_Effects.Effect = new Effect[0]; } Array.Resize(ref _collada.Library_Effects.Effect, _collada.Library_Effects.Effect.Length + 1); _collada.Library_Effects.Effect[_collada.Library_Effects.Effect.Length - 1] = effect; // add material to material library if (_collada.Library_Materials == null) { _collada.Library_Materials = new Library_Materials(); } if (_collada.Library_Materials.Material == null) { _collada.Library_Materials.Material = new Material[0]; } Array.Resize(ref _collada.Library_Materials.Material, _collada.Library_Materials.Material.Length + 1); _collada.Library_Materials.Material[_collada.Library_Materials.Material.Length - 1] = material; }
public IOModel GetIOModel() { IOModel outModel = new IOModel(); Mesh meshFile = null; Matl materialFile = null; foreach (FileNode n in Parent.Nodes) { if (n.Text.Equals(model.MeshString)) { meshFile = ((NumsbhNode)n).mesh; } if (n.Text.Equals(model.SkeletonFileName)) { outModel.Skeleton = (RSkeleton)((SkelNode)n).GetRenderableNode(); } if (n.Text.Equals(model.MaterialFileNames[0].MaterialFileName)) { materialFile = ((MatlNode)n).Material; } } Dictionary <string, int> indexByBoneName = new Dictionary <string, int>(); if (outModel.Skeleton != null) { for (int i = 0; i < outModel.Skeleton.Bones.Count; i++) { indexByBoneName.Add(outModel.Skeleton.Bones[i].Name, i); } } Dictionary <string, int> materialNameToIndex = new Dictionary <string, int>(); if (materialFile != null) { int materialIndex = 0; foreach (var entry in materialFile.Entries) { materialNameToIndex.Add(entry.ShaderLabel, materialIndex++); IOMaterial material = new IOMaterial { Name = entry.ShaderLabel }; outModel.Materials.Add(material); foreach (var attr in entry.Attributes) { if (attr.ParamId == MatlEnums.ParamId.Texture0) { IOTexture dif = new IOTexture { Name = attr.DataObject.ToString() }; material.DiffuseTexture = dif; } } } } if (meshFile != null) { SsbhVertexAccessor vertexAccessor = new SsbhVertexAccessor(meshFile); { SsbhRiggingAccessor riggingAccessor = new SsbhRiggingAccessor(meshFile); foreach (MeshObject obj in meshFile.Objects) { IOMesh outMesh = new IOMesh() { Name = obj.Name, }; outModel.Meshes.Add(outMesh); // get material if (materialFile != null) { foreach (var entry in model.ModelEntries) { if (entry.MeshName.Equals(obj.Name) && entry.SubIndex == obj.SubMeshIndex) { outMesh.MaterialIndex = materialNameToIndex[entry.MaterialLabel]; break; } } } IOVertex[] vertices = new IOVertex[obj.VertexCount]; for (int i = 0; i < vertices.Length; i++) { vertices[i] = new IOVertex(); } foreach (MeshAttribute attr in obj.Attributes) { SsbhVertexAttribute[] values = vertexAccessor.ReadAttribute(attr.AttributeStrings[0].Name, 0, obj.VertexCount, obj); if (attr.AttributeStrings[0].Name.Equals("Position0")) { outMesh.HasPositions = true; for (int i = 0; i < values.Length; i++) { vertices[i].Position = new OpenTK.Vector3(values[i].X, values[i].Y, values[i].Z); } } if (attr.AttributeStrings[0].Name.Equals("Normal0")) { outMesh.HasNormals = true; for (int i = 0; i < values.Length; i++) { vertices[i].Normal = new OpenTK.Vector3(values[i].X, values[i].Y, values[i].Z); } } // Flip UVs vertically for export. if (attr.AttributeStrings[0].Name.Equals("map1")) { outMesh.HasUV0 = true; for (int i = 0; i < values.Length; i++) { vertices[i].UV0 = new OpenTK.Vector2(values[i].X, 1 - values[i].Y); } } if (attr.AttributeStrings[0].Name.Equals("uvSet")) { outMesh.HasUV1 = true; for (int i = 0; i < values.Length; i++) { vertices[i].UV1 = new OpenTK.Vector2(values[i].X, 1 - values[i].Y); } } if (attr.AttributeStrings[0].Name.Equals("uvSet1")) { outMesh.HasUV2 = true; for (int i = 0; i < values.Length; i++) { vertices[i].UV2 = new OpenTK.Vector2(values[i].X, 1 - values[i].Y); } } if (attr.AttributeStrings[0].Name.Equals("uvSet2")) { outMesh.HasUV3 = true; for (int i = 0; i < values.Length; i++) { vertices[i].UV3 = new OpenTK.Vector2(values[i].X, 1 - values[i].Y); } } if (attr.AttributeStrings[0].Name.Equals("colorSet1")) { outMesh.HasColor = true; for (int i = 0; i < values.Length; i++) { vertices[i].Color = new OpenTK.Vector4(values[i].X, values[i].Y, values[i].Z, values[i].W) / 127f; } } } // Fix SingleBinds if (outModel.Skeleton != null && !obj.ParentBoneName.Equals("")) { int parentIndex = outModel.Skeleton.GetBoneIndex(obj.ParentBoneName); if (parentIndex != -1) { for (int i = 0; i < vertices.Length; i++) { vertices[i].Position = OpenTK.Vector3.TransformPosition(vertices[i].Position, outModel.Skeleton.Bones[parentIndex].WorldTransform); vertices[i].Normal = OpenTK.Vector3.TransformNormal(vertices[i].Normal, outModel.Skeleton.Bones[parentIndex].WorldTransform); vertices[i].BoneIndices.X = indexByBoneName[obj.ParentBoneName]; vertices[i].BoneWeights.X = 1; outMesh.HasBoneWeights = true; } } } // Apply Rigging SsbhVertexInfluence[] influences = riggingAccessor.ReadRiggingBuffer(obj.Name, (int)obj.SubMeshIndex); foreach (SsbhVertexInfluence influence in influences) { outMesh.HasBoneWeights = true; // Some influences refer to bones that don't exist in the skeleton. // _eff bones? if (!indexByBoneName.ContainsKey(influence.BoneName)) { continue; } if (vertices[influence.VertexIndex].BoneWeights.X == 0) { vertices[influence.VertexIndex].BoneIndices.X = indexByBoneName[influence.BoneName]; vertices[influence.VertexIndex].BoneWeights.X = influence.Weight; } else if (vertices[influence.VertexIndex].BoneWeights.Y == 0) { vertices[influence.VertexIndex].BoneIndices.Y = indexByBoneName[influence.BoneName]; vertices[influence.VertexIndex].BoneWeights.Y = influence.Weight; } else if (vertices[influence.VertexIndex].BoneWeights.Z == 0) { vertices[influence.VertexIndex].BoneIndices.Z = indexByBoneName[influence.BoneName]; vertices[influence.VertexIndex].BoneWeights.Z = influence.Weight; } else if (vertices[influence.VertexIndex].BoneWeights.W == 0) { vertices[influence.VertexIndex].BoneIndices.W = indexByBoneName[influence.BoneName]; vertices[influence.VertexIndex].BoneWeights.W = influence.Weight; } } outMesh.Vertices.AddRange(vertices); outMesh.Indices.AddRange(vertexAccessor.ReadIndices(0, obj.IndexCount, obj)); } } } return(outModel); }
public override bool ExportToFile(IOModel model, string filename) { var path = Path.GetDirectoryName(filename); var materialPath = path + "\\" + Path.GetFileNameWithoutExtension(filename) + ".mtl"; using (var writer = new StreamWriter(filename, false)) { writer.WriteLine("# Exported by Tomb Editor " + Assembly.GetExecutingAssembly().GetName().Version.ToString() + "\n"); writer.WriteLine("mtllib " + Path.GetFileNameWithoutExtension(filename) + ".mtl" + "\n"); // Optimize all data var poscol = new List <Vector3[]>(); var poscolIndices = new Dictionary <int, int>(); var normals = new List <Vector3>(); var normalIndices = new Dictionary <int, int>(); var uvs = new List <Vector2>(); var uvIndices = new Dictionary <int, int>(); // Pack positions and colours int meshCount = 0; foreach (var mesh in model.Meshes) { if (mesh.Positions.Count == 0) { continue; } for (int i = 0; i < mesh.Positions.Count; i++) { var position = ApplyAxesTransforms(mesh.Positions[i]); var colour = ApplyColorTransform(mesh.Colors.Count > i ? mesh.Colors[i] : new Vector4(2)).To3(); int found = -1; for (int j = 0; j < poscol.Count; j++) { if (poscol[j][0] == position && poscol[j][1] == colour) { found = j; break; } } if (found == -1) { found = poscol.Count; poscol.Add(new Vector3[2] { position, colour }); } poscolIndices.Add(i + meshCount, found); } meshCount += mesh.Positions.Count; } // Pack normals meshCount = 0; foreach (var mesh in model.Meshes) { if (mesh.Normals.Count == 0) { mesh.CalculateNormals(); } for (int i = 0; i < mesh.Normals.Count; i++) { var normal = mesh.Normals[i]; int found = -1; for (int j = 0; j < normals.Count; j++) { if (normals[j] == normal) { found = j; break; } } if (found == -1) { found = normals.Count; normals.Add(normal); } normalIndices.Add(i + meshCount, found); } meshCount += mesh.Normals.Count; } // Pack UV coords meshCount = 0; foreach (var mesh in model.Meshes) { if (mesh.UV.Count == 0) { continue; } for (int i = 0; i < mesh.UV.Count; i++) { var uv = mesh.UV[i]; int found = -1; for (int j = 0; j < uvs.Count; j++) { if (uvs[j] == uv) { found = j; break; } } if (found == -1) { found = uvs.Count; uvs.Add(uv); } uvIndices.Add(i + meshCount, found); } meshCount += mesh.UV.Count; } // Save vertices if (!_settings.UseVertexColor) { foreach (var pc in poscol) { writer.WriteLine("v " + pc[0].X.ToString(CultureInfo.InvariantCulture) + " " + pc[0].Y.ToString(CultureInfo.InvariantCulture) + " " + pc[0].Z.ToString(CultureInfo.InvariantCulture)); } } else { // Include vertex colour, it may be parsed correctly by some software, e.g. MeshMixer and MeshLab. foreach (var pc in poscol) { writer.WriteLine("v " + pc[0].X.ToString(CultureInfo.InvariantCulture) + " " + pc[0].Y.ToString(CultureInfo.InvariantCulture) + " " + pc[0].Z.ToString(CultureInfo.InvariantCulture) + " " + pc[1].X.ToString(CultureInfo.InvariantCulture) + " " + pc[1].Y.ToString(CultureInfo.InvariantCulture) + " " + pc[1].Z.ToString(CultureInfo.InvariantCulture)); } } writer.WriteLine("# " + poscol.Count + " vertices total.\n"); // Save normals foreach (var normal in normals) { writer.WriteLine("vn " + normal.X.ToString(CultureInfo.InvariantCulture) + " " + normal.Y.ToString(CultureInfo.InvariantCulture) + " " + normal.Z.ToString(CultureInfo.InvariantCulture)); } writer.WriteLine("# " + normals.Count + " normals total.\n"); // Save UVs foreach (var uv in uvs) { writer.WriteLine("vt " + uv.X.ToString(CultureInfo.InvariantCulture) + " " + (1.0f - uv.Y).ToString(CultureInfo.InvariantCulture) + " 0.000"); } writer.WriteLine("# " + uvs.Count + " UVs total.\n"); int pCount = 0; int uCount = 0; int nCount = 0; foreach (var mesh in model.Meshes) { writer.WriteLine("o " + mesh.Name + "\n"); // Save faces IOMaterial lastMaterial = null; int faceCount = 0; foreach (var submesh in mesh.Submeshes) { if (submesh.Value.Polygons.Count == 0) { continue; } if (lastMaterial == null || lastMaterial != submesh.Key) { lastMaterial = submesh.Key; writer.WriteLine("\n" + "usemtl " + lastMaterial.Name.ToString()); } foreach (var poly in submesh.Value.Polygons) { var indices = new List <int>(); indices.Add(poly.Indices[0]); indices.Add(poly.Indices[1]); indices.Add(poly.Indices[2]); if (poly.Shape == IOPolygonShape.Quad) { indices.Add(poly.Indices[3]); } // Change vertex winding if (_settings.InvertFaces) { indices.Reverse(); } var v1 = indices[0]; var v2 = indices[1]; var v3 = indices[2]; var v4 = (poly.Shape == IOPolygonShape.Quad ? indices[3] : 0); if (poly.Shape == IOPolygonShape.Triangle) { writer.WriteLine("f " + (poscolIndices[v1 + pCount] + 1) + "/" + (uvIndices [v1 + uCount] + 1) + "/" + (normalIndices[v1 + nCount] + 1) + " " + (poscolIndices[v2 + pCount] + 1) + "/" + (uvIndices [v2 + uCount] + 1) + "/" + (normalIndices[v2 + nCount] + 1) + " " + (poscolIndices[v3 + pCount] + 1) + "/" + (uvIndices [v3 + uCount] + 1) + "/" + (normalIndices[v3 + nCount] + 1)); } else { writer.WriteLine("f " + (poscolIndices[v1 + pCount] + 1) + "/" + (uvIndices [v1 + uCount] + 1) + "/" + (normalIndices[v1 + nCount] + 1) + " " + (poscolIndices[v2 + pCount] + 1) + "/" + (uvIndices [v2 + uCount] + 1) + "/" + (normalIndices[v2 + nCount] + 1) + " " + (poscolIndices[v3 + pCount] + 1) + "/" + (uvIndices [v3 + uCount] + 1) + "/" + (normalIndices[v3 + nCount] + 1) + " " + (poscolIndices[v4 + pCount] + 1) + "/" + (uvIndices [v4 + uCount] + 1) + "/" + (normalIndices[v4 + nCount] + 1)); } faceCount++; } } pCount += mesh.Positions.Count; uCount += mesh.UV.Count; nCount += mesh.Normals.Count; writer.WriteLine("# " + faceCount + " faces total.\n"); } } using (var writer = new StreamWriter(materialPath, false)) { writer.WriteLine("# Exported by Tomb Editor " + Assembly.GetExecutingAssembly().GetName().Version.ToString() + "\n"); foreach (var material in model.UsedMaterials) { writer.WriteLine("newmtl " + material.Name); writer.WriteLine(" Ka 1.000000 1.000000 1.000000"); writer.WriteLine(" Kd 1.000000 1.000000 1.000000"); writer.WriteLine(" Ni 1.000000"); writer.WriteLine(" Ns 10.000000"); writer.WriteLine(" d " + (material.AdditiveBlending ? "0.500000" : "1.000000")); writer.WriteLine(" illum 2"); writer.WriteLine(" map_Ka " + material.TexturePath); writer.WriteLine(" map_Kd " + material.TexturePath); writer.WriteLine("\n"); } } return(true); }
public override IOScene GetIOModel() { IOScene scene = new IOScene(); IOModel iomodel = new IOModel(); scene.Models.Add(iomodel); iomodel.Skeleton = ((SBSkeleton)Skeleton).ToIOSkeleton(); // bone indices List <SBHsdBone> bones = new List <SBHsdBone>(); foreach (SBHsdBone bone in Skeleton.Bones) { bones.Add(bone); } // gather textures Dictionary <HSDStruct, string> tobjToName = new Dictionary <HSDStruct, string>(); List <SBSurface> textures = new List <SBSurface>(); foreach (var tex in tobjToSurface) { tex.Value.Name = $"TOBJ_{textures.Count}"; tobjToName.Add(tex.Key, tex.Value.Name); textures.Add(tex.Value); } // process mesh foreach (SBHsdMesh me in GetMeshObjects()) { var dobj = me.DOBJ; var parent = Skeleton.Bones[0]; foreach (var b in Skeleton.Bones) { if (b is SBHsdBone bone) { if (bone.GetJOBJ().Dobj != null) { if (bone.GetJOBJ().Dobj.List.Contains(dobj)) { parent = b; break; } } } } var iomesh = new IOMesh(); iomesh.Name = me.Name; iomodel.Meshes.Add(iomesh); IOPolygon poly = new IOPolygon(); iomesh.Polygons.Add(poly); if (dobj.Pobj != null) { foreach (var pobj in dobj.Pobj.List) { var dl = pobj.ToDisplayList(); var vertices = GX_VertexAccessor.GetDecodedVertices(dl, pobj); HSD_Envelope[] bindGroups = null;; if (pobj.EnvelopeWeights != null) { bindGroups = pobj.EnvelopeWeights; } var offset = 0; foreach (var v in dl.Primitives) { List <GX_Vertex> strip = new List <GX_Vertex>(); for (int i = 0; i < v.Count; i++) { strip.Add(vertices[offset + i]); } offset += v.Count; iomesh.Vertices.AddRange(ConvertGXDLtoTriangleList(v.PrimitiveType, SBHsdMesh.GXVertexToHsdVertex(strip, bones, bindGroups), (SBHsdBone)parent)); } } } // flip faces for (int i = 0; i < iomesh.Vertices.Count; i += 3) { if (i + 2 < iomesh.Vertices.Count) { poly.Indicies.Add(i + 2); poly.Indicies.Add(i + 1); poly.Indicies.Add(i); } else { break; } } poly.MaterialName = iomesh.Name + "_material"; // create material IOMaterial mat = new IOMaterial(); mat.Name = iomesh.Name + "_material"; if (dobj.Mobj.Material != null) { mat.DiffuseColor = new System.Numerics.Vector4( dobj.Mobj.Material.DiffuseColor.R / 255f, dobj.Mobj.Material.DiffuseColor.G / 255f, dobj.Mobj.Material.DiffuseColor.B / 255f, dobj.Mobj.Material.DiffuseColor.A / 255f); mat.SpecularColor = new System.Numerics.Vector4( dobj.Mobj.Material.SpecularColor.R / 255f, dobj.Mobj.Material.SpecularColor.G / 255f, dobj.Mobj.Material.SpecularColor.B / 255f, dobj.Mobj.Material.SpecularColor.A / 255f); mat.AmbientColor = new System.Numerics.Vector4( dobj.Mobj.Material.AmbientColor.R / 255f, dobj.Mobj.Material.AmbientColor.G / 255f, dobj.Mobj.Material.AmbientColor.B / 255f, dobj.Mobj.Material.AmbientColor.A / 255f); mat.Alpha = dobj.Mobj.Material.Alpha; mat.Shininess = dobj.Mobj.Material.Shininess; } if (dobj.Mobj.Textures != null) { mat.DiffuseMap = new IOTexture() { Name = tobjToName[dobj.Mobj.Textures._s], FilePath = tobjToName[dobj.Mobj.Textures._s] } } ; scene.Materials.Add(mat); } return(scene); }
/// <summary> /// /// </summary> /// <param name="scene"></param> /// <param name="filePath"></param> private void LoadMaterialLibrary(IOScene scene, string filePath) { using (FileStream stream = new FileStream(filePath, FileMode.Open)) using (StreamReader r = new StreamReader(stream)) { IOMaterial currentMaterial = new IOMaterial(); while (!r.EndOfStream) { var args = Regex.Replace(r.ReadLine().Trim(), @"\s+", " ").Split(' '); if (args.Length == 0) { continue; } switch (args[0]) { case "newmtl": currentMaterial = new IOMaterial() { Name = args[1] }; scene.Materials.Add(currentMaterial); break; case "Ka": currentMaterial.AmbientColor = new Vector4(float.Parse(args[1]), float.Parse(args[2]), float.Parse(args[3]), 1); break; case "Kd": currentMaterial.DiffuseColor = new Vector4(float.Parse(args[1]), float.Parse(args[2]), float.Parse(args[3]), 1); break; case "Ks": currentMaterial.SpecularColor = new Vector4(float.Parse(args[1]), float.Parse(args[2]), float.Parse(args[3]), 1); break; case "Ns": currentMaterial.Shininess = float.Parse(args[1]); break; case "d": currentMaterial.Alpha = float.Parse(args[1]); break; case "Tr": currentMaterial.Alpha = 1 - float.Parse(args[1]); break; case "map_Ka": currentMaterial.AmbientMap = new IOTexture() { Name = currentMaterial.Name + "_texture", FilePath = string.Join(" ", args, 1, args.Length - 1) }; break; case "map_Kd": currentMaterial.DiffuseMap = new IOTexture() { Name = currentMaterial.Name + "_texture", FilePath = string.Join(" ", args, 1, args.Length - 1) }; break; case "map_Ks": currentMaterial.SpecularMap = new IOTexture() { Name = currentMaterial.Name + "_texture", FilePath = string.Join(" ", args, 1, args.Length - 1) }; break; } } } }
public override IOModel ImportFromFile(string filename) { string path = Path.GetDirectoryName(filename); // Use Assimp.NET for importing model AssimpContext context = new AssimpContext(); context.SetConfig(new NormalSmoothingAngleConfig(90.0f)); Scene scene = context.ImportFile(filename, PostProcessPreset.TargetRealTimeMaximumQuality); var newModel = new IOModel(); var textures = new Dictionary <int, Texture>(); if (_settings.ProcessGeometry) { var tmpList = new List <IOMesh>(); // Create the list of materials to load for (int i = 0; i < scene.Materials.Count; i++) { var mat = scene.Materials[i]; var material = new IOMaterial(mat.HasName ? mat.Name : "Material_" + i); var diffusePath = mat.HasTextureDiffuse ? mat.TextureDiffuse.FilePath : null; if (string.IsNullOrWhiteSpace(diffusePath)) { continue; } // Don't add materials with missing textures var texture = GetTexture(path, diffusePath); if (texture == null) { logger.Warn("Texture for material " + mat.Name + " is missing. Meshes referencing this material won't be imported."); continue; } else { textures.Add(i, texture); } // Create the new material material.Texture = textures[i]; material.AdditiveBlending = (mat.HasBlendMode && mat.BlendMode == global::Assimp.BlendMode.Additive) || mat.Opacity < 1.0f; material.DoubleSided = mat.HasTwoSided && mat.IsTwoSided; material.Shininess = mat.HasShininess ? (int)mat.Shininess : 0; newModel.Materials.Add(material); } var lastBaseVertex = 0; // Loop for each mesh loaded in scene foreach (var mesh in scene.Meshes) { // Discard nullmeshes if (!mesh.HasFaces || !mesh.HasVertices || mesh.VertexCount < 3 || mesh.TextureCoordinateChannelCount == 0 || !mesh.HasTextureCoords(0)) { logger.Warn("Mesh \"" + (mesh.Name ?? "") + "\" has no faces, no texture coordinates or wrong vertex count."); continue; } // Import only textured meshes with valid materials Texture faceTexture; if (!textures.TryGetValue(mesh.MaterialIndex, out faceTexture)) { logger.Warn("Mesh \"" + (mesh.Name ?? "") + "\" does have material index " + mesh.MaterialIndex + " which is unsupported or can't be found."); continue; } // Make sure we have appropriate material in list. If not, skip mesh and warn user. var material = newModel.Materials.FirstOrDefault(mat => mat.Name.Equals(scene.Materials[mesh.MaterialIndex].Name)); if (material == null) { logger.Warn("Can't find material with specified index (" + mesh.MaterialIndex + "). Probably you're missing textures or using non-diffuse materials only for this mesh."); continue; } // Assimp's mesh is our IOSubmesh so we import meshes with just one submesh var newMesh = new IOMesh(mesh.Name); var newSubmesh = new IOSubmesh(material); newMesh.Submeshes.Add(material, newSubmesh); bool hasColors = _settings.UseVertexColor && mesh.VertexColorChannelCount > 0 && mesh.HasVertexColors(0); bool hasNormals = mesh.HasNormals; // Additional integrity checks if ((mesh.VertexCount != mesh.TextureCoordinateChannels[0].Count) || (hasColors && mesh.VertexCount != mesh.VertexColorChannels[0].Count) || (hasNormals && mesh.VertexCount != mesh.Normals.Count)) { logger.Warn("Mesh \"" + (mesh.Name ?? "") + "\" data structure is inconsistent."); continue; } // Source data var positions = mesh.Vertices; var normals = mesh.Normals; var texCoords = mesh.TextureCoordinateChannels[0]; var colors = mesh.VertexColorChannels[0]; for (int i = 0; i < mesh.VertexCount; i++) { // Create position var position = new Vector3(positions[i].X, positions[i].Y, positions[i].Z); position = ApplyAxesTransforms(position); newMesh.Positions.Add(position); // Create normal if (hasNormals) { var normal = new Vector3(normals[i].X, normals[i].Y, normals[i].Z); normal = ApplyAxesTransforms(normal); newMesh.Normals.Add(normal); } else { newMesh.CalculateNormals(); } // Create UV var currentUV = new Vector2(texCoords[i].X, texCoords[i].Y); if (faceTexture != null) { currentUV = ApplyUVTransform(currentUV, faceTexture.Image.Width, faceTexture.Image.Height); } newMesh.UV.Add(currentUV); // Create colors if (hasColors) { var color = ApplyColorTransform(new Vector4(colors[i].R, colors[i].G, colors[i].B, colors[i].A)); newMesh.Colors.Add(color); } } // Add polygons foreach (var face in mesh.Faces) { if (face.IndexCount == 3) { var poly = new IOPolygon(IOPolygonShape.Triangle); poly.Indices.Add(lastBaseVertex + face.Indices[0]); poly.Indices.Add(lastBaseVertex + face.Indices[1]); poly.Indices.Add(lastBaseVertex + face.Indices[2]); if (_settings.InvertFaces) { poly.Indices.Reverse(); } newSubmesh.Polygons.Add(poly); } else if (face.IndexCount == 4) { var poly = new IOPolygon(IOPolygonShape.Quad); poly.Indices.Add(lastBaseVertex + face.Indices[0]); poly.Indices.Add(lastBaseVertex + face.Indices[1]); poly.Indices.Add(lastBaseVertex + face.Indices[2]); poly.Indices.Add(lastBaseVertex + face.Indices[3]); if (_settings.InvertFaces) { poly.Indices.Reverse(); } newSubmesh.Polygons.Add(poly); } } tmpList.Add(newMesh); } // Sort meshes by name, if specified if (_settings.SortByName) { tmpList = tmpList.OrderBy(m => m.Name, new CustomComparer <string>(NaturalComparer.Do)).ToList(); } foreach (var mesh in tmpList) { newModel.Meshes.Add(mesh); } } if (_settings.ProcessAnimations && scene.HasAnimations && scene.AnimationCount > 0) { // Find all mesh nodes to count against animation nodes var meshNameList = CollectMeshNodeNames(scene.RootNode); // Sort animations by name, if specified if (_settings.SortByName) { meshNameList = meshNameList.OrderBy(s => s, new CustomComparer <string>(NaturalComparer.Do)).ToList(); } // Loop through all animations and add appropriate ones. // Integrity check: there should be meshes and mesh count should be equal to unique mesh name count. if (scene.MeshCount <= 0 || scene.MeshCount != meshNameList.Count) { logger.Warn("Actual number of meshes doesn't correspond to mesh list. Animations won't be imported."); } else { for (int i = 0; i < scene.AnimationCount; i++) { var anim = scene.Animations[i]; // Integrity check: support only time-based node animations if (!anim.HasNodeAnimations || anim.DurationInTicks <= 0) { logger.Warn("Anim " + i + " isn't a valid type of animation for TR formats."); continue; } // Guess possible maximum frame and time var frameCount = 0; double maximumTime = 0; foreach (var node in anim.NodeAnimationChannels) { if (node.HasPositionKeys) { var maxNodeTime = node.PositionKeys.Max(key => key.Time); maximumTime = maximumTime >= maxNodeTime ? maximumTime : maxNodeTime; frameCount = frameCount >= node.PositionKeyCount ? frameCount : node.PositionKeyCount; } if (node.HasRotationKeys) { var maxNodeTime = node.RotationKeys.Max(key => key.Time); maximumTime = maximumTime >= maxNodeTime ? maximumTime : maxNodeTime; frameCount = frameCount >= node.RotationKeyCount ? frameCount : node.RotationKeyCount; } } // Calculate time multiplier var timeMult = (double)(frameCount - 1) / anim.DurationInTicks; // Integrity check: maximum frame time shouldn't excess duration if (timeMult * maximumTime >= frameCount) { logger.Warn("Anim " + i + " has frames outside of time limits and won't be imported."); continue; } IOAnimation ioAnim = new IOAnimation(string.IsNullOrEmpty(anim.Name) ? "Imported animation " + i : anim.Name, scene.MeshCount); // Precreate frames and set them to identity for (int j = 0; j < frameCount; j++) { ioAnim.Frames.Add(new IOFrame()); } // Precreate rotations and set them to identity // I am using generic foreach here instead of linq foreach because for some reason it // returns wrong amount of angles during enumeration with Enumerable.Repeat. foreach (var frame in ioAnim.Frames) { var angleList = Enumerable.Repeat(Vector3.Zero, scene.MeshCount); frame.Angles.AddRange(angleList); } // Search through all nodes and put data into corresponding frames. // It's not clear what should we do in case if multiple nodes refer to same mesh, but sometimes // it happens, e. g. in case of fbx format. In this case, we'll just add to existing values for now. foreach (var chan in anim.NodeAnimationChannels) { // Look if this channel belongs to any mesh in list. // If so, attribute it to appropriate frame. var chanIndex = meshNameList.IndexOf(item => chan.NodeName.Contains(item)); // Integrity check: no appropriate mesh found if (chanIndex < 0) { logger.Warn("Anim " + i + " channel " + chan.NodeName + " has no corresponding mesh in meshtree and will be ignored"); continue; } // Apply translation only if found channel belongs to root mesh. if (chanIndex == 0 && chan.HasPositionKeys && chan.PositionKeyCount > 0) { foreach (var key in chan.PositionKeys) { // Integrity check: frame shouldn't fall out of keyframe array bounds. var frameIndex = (int)Math.Round(key.Time * timeMult, MidpointRounding.AwayFromZero); if (frameIndex >= frameCount) { logger.Warn("Anim " + i + " channel " + chan.NodeName + " has a key outside of time limits and will be ignored."); continue; } float rX = key.Value.X; float rY = key.Value.Y; float rZ = key.Value.Z; if (_settings.SwapXY) { var temp = rX; rX = rY; rY = temp; } if (_settings.SwapXZ) { var temp = rX; rX = rZ; rZ = temp; } if (_settings.SwapYZ) { var temp = rY; rY = rZ; rZ = temp; } if (_settings.FlipX) { rX = -rX; } if (_settings.FlipY) { rY = -rY; } if (_settings.FlipZ) { rZ = -rZ; } ioAnim.Frames[frameIndex].Offset += new Vector3(rX, rY, rZ); } } if (chan.HasRotationKeys && chan.RotationKeyCount > 0) { foreach (var key in chan.RotationKeys) { // Integrity check: frame shouldn't fall out of keyframe array bounds. var frameIndex = (int)Math.Round(key.Time * timeMult, MidpointRounding.AwayFromZero); if (frameIndex >= frameCount) { logger.Warn("Anim " + i + " channel " + chan.NodeName + " has a key outside of time limits and will be ignored."); continue; } // Convert quaternions back to rotations. // This is similar to TRViewer's conversion routine. var quatI = System.Numerics.Quaternion.Identity; var quat = new System.Numerics.Quaternion(key.Value.X, key.Value.Z, key.Value.Y, -key.Value.W); quatI *= quat; var eulers = MathC.QuaternionToEuler(quatI); var rotation = new Vector3(eulers.X * 180.0f / (float)Math.PI, eulers.Y * 180.0f / (float)Math.PI, eulers.Z * 180.0f / (float)Math.PI); ioAnim.Frames[frameIndex].Angles[chanIndex] += MathC.NormalizeAngle(rotation); } } } newModel.Animations.Add(ioAnim); } } } return(newModel); }