/// <summary> /// /// </summary> /// <param name="mesh"></param> /// <returns></returns> private Node ProcessMesh(IOMesh mesh, IOModel model) { Node n = new Node() { Name = mesh.Name, sID = mesh.Name, ID = mesh.Name, Type = Node_Type.NODE }; var materials = mesh.Polygons.Select(e => e.MaterialName).Distinct(); if (mesh.HasEnvelopes()) { var geom = new Instance_Controller(); geom.URL = "#" + GenerateGeometryController(mesh, model.Skeleton); n.Instance_Controller = new Instance_Controller[] { geom }; n.Instance_Controller[0].Bind_Material = new IONET.Collada.FX.Materials.Bind_Material[] { new Bind_Material() { Technique_Common = new FX.Technique_Common.Technique_Common_Bind_Material() { Instance_Material = materials.Select(e => new Instance_Material_Geometry() { Symbol = e, Target = "#" + e }).ToArray() } } }; } else { var geom = new Instance_Geometry(); geom.URL = "#" + GenerateGeometry(mesh); n.Instance_Geometry = new Instance_Geometry[] { geom }; n.Instance_Geometry[0].Bind_Material = new IONET.Collada.FX.Materials.Bind_Material[] { new Bind_Material() { Technique_Common = new FX.Technique_Common.Technique_Common_Bind_Material() { Instance_Material = materials.Select(e => new Instance_Material_Geometry() { Symbol = e, Target = "#" + e }).ToArray() } } }; } return(n); }
/// <summary> /// Gets the model information in this scene as an IO Model /// </summary> /// <returns></returns> public override IOModel GetIOModel() { IOModel iomodel = new IOModel(); iomodel.Skeleton = (SBSkeleton)Skeleton; foreach (var mesh in Model.Meshes) { var iomesh = new IOMesh(); iomesh.Name = mesh.Name; iomodel.Meshes.Add(iomesh); iomesh.HasPositions = mesh.ExportAttributes.Contains(SSBHLib.Tools.MESHAttribute.Position0); iomesh.HasNormals = mesh.ExportAttributes.Contains(SSBHLib.Tools.MESHAttribute.Normal0); iomesh.HasUV0 = mesh.ExportAttributes.Contains(SSBHLib.Tools.MESHAttribute.map1); iomesh.HasUV1 = mesh.ExportAttributes.Contains(SSBHLib.Tools.MESHAttribute.uvSet); iomesh.HasUV2 = mesh.ExportAttributes.Contains(SSBHLib.Tools.MESHAttribute.uvSet1); iomesh.HasUV3 = mesh.ExportAttributes.Contains(SSBHLib.Tools.MESHAttribute.uvSet2); iomesh.HasColor = mesh.ExportAttributes.Contains(SSBHLib.Tools.MESHAttribute.colorSet1); iomesh.HasBoneWeights = true; iomesh.Indices.AddRange(mesh.Indices); foreach (var vertex in mesh.Vertices) { var iovertex = new IOVertex(); iovertex.Position = vertex.Position0; iovertex.Normal = vertex.Normal0; iovertex.Tangent = vertex.Tangent0; iovertex.UV0 = vertex.Map1; iovertex.UV1 = vertex.UvSet; iovertex.UV2 = vertex.UvSet1; iovertex.Color = vertex.ColorSet1; iovertex.BoneIndices = new Vector4(vertex.BoneIndices.X, vertex.BoneIndices.Y, vertex.BoneIndices.Z, vertex.BoneIndices.W); iovertex.BoneWeights = vertex.BoneWeights; // single bind fix if (mesh.ParentBone != "" && Skeleton != null) { var parentBone = Skeleton[mesh.ParentBone]; if (parentBone != null) { iovertex.Position = Vector3.TransformPosition(vertex.Position0, parentBone.WorldTransform); iovertex.Normal = Vector3.TransformNormal(vertex.Normal0, parentBone.WorldTransform); iovertex.BoneIndices = new Vector4(Skeleton.IndexOfBone(parentBone), 0, 0, 0); iovertex.BoneWeights = new Vector4(1, 0, 0, 0); } } iomesh.Vertices.Add(iovertex); } } return(iomodel); }
public static void CalculateTangentBitangent(this IOMesh mesh) { Vector3[] pos = mesh.Vertices.Select(e => new Vector3(e.Position.X, e.Position.Y, e.Position.Z)).ToArray(); Vector3[] nrm = mesh.Vertices.Select(e => new Vector3(e.Normal.X, e.Normal.Y, e.Normal.Z)).ToArray(); Vector2[] uvs = mesh.Vertices.Select(e => new Vector2(e.UVs[0].X, e.UVs[0].Y)).ToArray(); List <int> indices = new List <int>(); foreach (var poly in mesh.Polygons) { poly.ToTriangles(mesh); if (poly.PrimitiveType == IOPrimitive.TRIANGLE) { indices.AddRange(poly.Indicies); } } CalculateTangentsBitangents(pos, nrm, uvs, indices, out Vector3[] tan, out Vector3[] bitan);
/// <summary> /// /// </summary> /// <returns></returns> private string GenerateGeometry(IOMesh mesh) { // convert mesh to triangles to simplify mesh.MakeTriangles(); // create a unique geometry id var geomID = GetUniqueID(mesh.Name + "-geometry"); // create geometry element Geometry geom = new Geometry() { ID = geomID, Name = mesh.Name }; geom.Mesh = new Mesh(); // generate sources SourceGenerator srcgen = new SourceGenerator(); srcgen.AddSourceData( geomID, Input_Semantic.POSITION, mesh.Vertices.SelectMany(e => new float[] { e.Position.X, e.Position.Y, e.Position.Z }).ToArray()); srcgen.AddSourceData( geomID, Input_Semantic.NORMAL, mesh.Vertices.SelectMany(e => new float[] { e.Normal.X, e.Normal.Y, e.Normal.Z }).ToArray()); for (int i = 0; i < 7; i++) { if (mesh.HasUVSet(i)) { srcgen.AddSourceData( geomID, Input_Semantic.TEXCOORD, mesh.Vertices.SelectMany(e => new float[] { e.UVs[i].X, e.UVs[i].Y }).ToArray(), i); } } for (int i = 0; i < 7; i++) { if (mesh.HasColorSet(i)) { srcgen.AddSourceData( geomID, Input_Semantic.COLOR, mesh.Vertices.SelectMany(e => new float[] { e.Colors[i].X, e.Colors[i].Y, e.Colors[i].Z, e.Colors[i].W }).ToArray(), i); } } // fill in vertex info geom.Mesh.Vertices = new Vertices() { ID = GetUniqueID(mesh.Name + "-vertices"), Input = new Input_Unshared[] { new Input_Unshared() { Semantic = IONET.Collada.Enums.Input_Semantic.POSITION, source = "#" + srcgen.GetID(Input_Semantic.POSITION) } } }; // fill in triangles var polyIndex = 0; geom.Mesh.Triangles = new Triangles[mesh.Polygons.Count]; foreach (var poly in mesh.Polygons) { if (poly.PrimitiveType != IOPrimitive.TRIANGLE) { System.Diagnostics.Debug.WriteLine("Warning: " + poly.PrimitiveType + " not currently supported"); continue; } Triangles tri = new Triangles() { Count = poly.Indicies.Count / 3, Material = poly.MaterialName }; List <Input_Shared> inputs = new List <Input_Shared>(); inputs.Add(new Input_Shared() { Semantic = Input_Semantic.VERTEX, Offset = inputs.Count, source = "#" + geom.Mesh.Vertices.ID }); inputs.Add(new Input_Shared() { Semantic = Input_Semantic.NORMAL, Offset = inputs.Count, source = "#" + srcgen.GetID(Input_Semantic.NORMAL) }); for (int i = 0; i < 7; i++) { if (mesh.HasUVSet(i)) { inputs.Add(new Input_Shared() { Semantic = Input_Semantic.TEXCOORD, source = "#" + srcgen.GetID(Input_Semantic.TEXCOORD, i), Offset = inputs.Count, Set = i }); } } for (int i = 0; i < 7; i++) { if (mesh.HasColorSet(i)) { inputs.Add(new Input_Shared() { Semantic = Input_Semantic.COLOR, source = "#" + srcgen.GetID(Input_Semantic.COLOR, i), Offset = inputs.Count, Set = i }); } } tri.Input = inputs.ToArray(); tri.P = new IONET.Collada.Types.Int_Array_String() { Value_As_String = string.Join(" ", srcgen.Remap(poly.Indicies)) }; geom.Mesh.Triangles[polyIndex++] = tri; } // generate sources geom.Mesh.Source = srcgen.GetSources(); // add geometry element to document if (_collada.Library_Geometries == null) { _collada.Library_Geometries = new Library_Geometries(); } if (_collada.Library_Geometries.Geometry == null) { _collada.Library_Geometries.Geometry = new Geometry[0]; } Array.Resize(ref _collada.Library_Geometries.Geometry, _collada.Library_Geometries.Geometry.Length + 1); _collada.Library_Geometries.Geometry[_collada.Library_Geometries.Geometry.Length - 1] = geom; // return geometry id return(geomID); }
/// <summary> /// /// </summary> /// <param name="mesh"></param> /// <returns></returns> private string GenerateGeometryController(IOMesh mesh, IOSkeleton skeleton) { Controller con = new Controller() { ID = GetUniqueID(mesh.Name + "-controller"), Name = mesh.Name }; con.Skin = new Skin() { sourceid = "#" + GenerateGeometry(mesh) }; con.Skin.Bind_Shape_Matrix = new IONET.Collada.Types.Float_Array_String() { Value_As_String = "1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1" }; List <int> weightCounts = new List <int>(); List <int> binds = new List <int>(); List <string> boneNames = new List <string>(); List <float> boneBinds = new List <float>(); List <float> weights = new List <float>(); foreach (var v in mesh.Vertices) { weightCounts.Add(v.Envelope.Weights.Count); foreach (var bw in v.Envelope.Weights) { if (!boneNames.Contains(bw.BoneName)) { boneNames.Add(bw.BoneName); Matrix4x4.Invert(skeleton.GetBoneByName(bw.BoneName).WorldTransform, out Matrix4x4 mat); boneBinds.AddRange(new float[] { mat.M11, mat.M21, mat.M31, mat.M41, mat.M12, mat.M22, mat.M32, mat.M42, mat.M13, mat.M23, mat.M33, mat.M43, mat.M14, mat.M24, mat.M34, mat.M44 }); } if (!weights.Contains(bw.Weight)) { weights.Add(bw.Weight); } binds.Add(boneNames.IndexOf(bw.BoneName)); binds.Add(weights.IndexOf(bw.Weight)); } } var mid = GetUniqueID(mesh.Name + "-matrices"); var jid = GetUniqueID(mesh.Name + "-joints"); var wid = GetUniqueID(mesh.Name + "-weights"); var midarr = GetUniqueID(mesh.Name + "-matrices-array"); var jidarr = GetUniqueID(mesh.Name + "-joints-array"); var widarr = GetUniqueID(mesh.Name + "-weights-array"); con.Skin.Source = new Source[] { new Source() { ID = jid, Name_Array = new Name_Array() { Count = boneNames.Count, ID = jidarr, Value_Pre_Parse = string.Join(" ", boneNames) }, Technique_Common = new IONET.Collada.Core.Technique_Common.Technique_Common_Source() { Accessor = new Accessor() { Count = (uint)boneNames.Count, Source = "#" + jidarr, Param = new IONET.Collada.Core.Parameters.Param[] { new IONET.Collada.Core.Parameters.Param() { Type = "name" } }, Stride = 1 } } }, new Source() { ID = mid, Float_Array = new Float_Array() { Count = boneBinds.Count, ID = midarr, Value_As_String = string.Join(" ", boneBinds) }, Technique_Common = new IONET.Collada.Core.Technique_Common.Technique_Common_Source() { Accessor = new Accessor() { Count = (uint)boneBinds.Count / 16, Source = "#" + midarr, Param = new IONET.Collada.Core.Parameters.Param[] { new IONET.Collada.Core.Parameters.Param() { Type = "float4x4" } }, Stride = 16 } } }, new Source() { ID = wid, Float_Array = new Float_Array() { Count = weights.Count, ID = widarr, Value_As_String = string.Join(" ", weights) }, Technique_Common = new IONET.Collada.Core.Technique_Common.Technique_Common_Source() { Accessor = new Accessor() { Count = (uint)weights.Count, Source = "#" + widarr, Param = new IONET.Collada.Core.Parameters.Param[] { new IONET.Collada.Core.Parameters.Param() { Type = "float" } }, Stride = 1 } } }, }; con.Skin.Joints = new Joints() { Input = new Input_Unshared[] { new Input_Unshared() { Semantic = Input_Semantic.JOINT, source = "#" + jid }, new Input_Unshared() { Semantic = Input_Semantic.INV_BIND_MATRIX, source = "#" + mid }, } }; con.Skin.Vertex_Weights = new Vertex_Weights() { Count = weightCounts.Count, V = new IONET.Collada.Types.Int_Array_String() { Value_As_String = string.Join(" ", binds) }, VCount = new IONET.Collada.Types.Int_Array_String() { Value_As_String = string.Join(" ", weightCounts) }, Input = new Input_Shared[] { new Input_Shared() { Semantic = Input_Semantic.JOINT, source = "#" + jid, Offset = 0 }, new Input_Shared() { Semantic = Input_Semantic.WEIGHT, source = "#" + wid, Offset = 1 }, } }; // add geometry element to document if (_collada.Library_Controllers == null) { _collada.Library_Controllers = new Library_Controllers(); } if (_collada.Library_Controllers.Controller == null) { _collada.Library_Controllers.Controller = new Controller[0]; } Array.Resize(ref _collada.Library_Controllers.Controller, _collada.Library_Controllers.Controller.Length + 1); _collada.Library_Controllers.Controller[_collada.Library_Controllers.Controller.Length - 1] = con; return(con.ID); }
public override IOModel GetIOModel() { var iomodel = new IOModel(); iomodel.Skeleton = (SBSkeleton)Skeleton; List <SBHsdBone> bones = new List <SBHsdBone>(); foreach (SBHsdBone bone in Skeleton.Bones) { bones.Add(bone); } Dictionary <HSDStruct, string> tobjToName = new Dictionary <HSDStruct, string>(); foreach (var tex in tobjToSurface) { tex.Value.Name = $"TOBJ_{iomodel.Textures.Count}"; tobjToName.Add(tex.Key, tex.Value.Name); iomodel.Textures.Add(tex.Value); } 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); iomesh.HasPositions = true; iomesh.HasColor = true; iomesh.HasNormals = true; iomesh.HasBoneWeights = true; iomesh.HasUV0 = true; 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)); } } } iomesh.Optimize(); // flip faces var temp = iomesh.Indices.ToArray(); iomesh.Indices.Clear(); for (int i = 0; i < temp.Length; i += 3) { if (i + 2 < temp.Length) { iomesh.Indices.Add(temp[i + 2]); iomesh.Indices.Add(temp[i + 1]); iomesh.Indices.Add(temp[i]); } else { break; } } iomesh.MaterialIndex = iomodel.Materials.Count; IOMaterialPhong mat = new IOMaterialPhong(); mat.Name = iomesh.Name + "_material"; if (dobj.Mobj.Material != null) { mat.DiffuseColor = dobj.Mobj.Material.DiffuseColor; mat.SpecularColor = dobj.Mobj.Material.SpecularColor; mat.AmbientColor = dobj.Mobj.Material.AmbientColor; } if (dobj.Mobj.Textures != null) { mat.DiffuseTexture = tobjToName[dobj.Mobj.Textures._s]; } iomodel.Materials.Add(mat); } return(iomodel); }
/// <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); }
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); }
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> /// <param name="settings"></param> /// <param name="material"></param> /// <returns></returns> private HSD_MOBJ GenerateMaterial(IOMesh mesh, 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 && (mesh.HasColorSet(0) || mesh.HasColorSet(1))) { 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 = GXTexGenSrc.GX_TG_TEX0; 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); }
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="filePath"></param> /// <returns></returns> public IOScene GetScene(string filePath) { // create scene and model IOScene scene = new IOScene(); // load materials var mtlFile = Path.Combine(Path.GetDirectoryName(filePath), Path.GetFileNameWithoutExtension(filePath) + ".mtl"); if (File.Exists(mtlFile)) { LoadMaterialLibrary(scene, mtlFile); } // parse obj file using (FileStream stream = new FileStream(filePath, FileMode.Open)) using (StreamReader r = new StreamReader(stream)) { List <Vector3> v = new List <Vector3>(); List <Vector2> vt = new List <Vector2>(); List <Vector3> vn = new List <Vector3>(); List <Tuple <string, string, Dictionary <IOPrimitive, List <int[]> > > > objects = new List <Tuple <string, string, Dictionary <IOPrimitive, List <int[]> > > >(); var objName = "Mesh"; var matNam = ""; Dictionary <IOPrimitive, List <int[]> > polygons = new Dictionary <IOPrimitive, List <int[]> >(); while (!r.EndOfStream) { var args = Regex.Replace(r.ReadLine().Trim(), @"\s+", " ").Split(' '); if (args.Length > 0) { switch (args[0]) { case "v": v.Add(new Vector3( args.Length > 1 ? float.Parse(args[1]) : 0, args.Length > 2 ? float.Parse(args[2]) : 0, args.Length > 3 ? float.Parse(args[3]) : 0)); break; case "vt": vt.Add(new Vector2( args.Length > 1 ? float.Parse(args[1]) : 0, args.Length > 2 ? float.Parse(args[2]) : 0)); break; case "vn": vn.Add(new Vector3( args.Length > 1 ? float.Parse(args[1]) : 0, args.Length > 2 ? float.Parse(args[2]) : 0, args.Length > 3 ? float.Parse(args[3]) : 0)); break; case "f": var faces = ParseFaces(args); if (args.Length == 2) { // point if (!polygons.ContainsKey(IOPrimitive.POINT)) { polygons.Add(IOPrimitive.POINT, new List <int[]>()); } polygons[IOPrimitive.POINT].AddRange(faces); } if (args.Length == 3) { // line if (!polygons.ContainsKey(IOPrimitive.LINE)) { polygons.Add(IOPrimitive.LINE, new List <int[]>()); } polygons[IOPrimitive.LINE].AddRange(faces); } if (args.Length == 4) { // triangle if (!polygons.ContainsKey(IOPrimitive.TRIANGLE)) { polygons.Add(IOPrimitive.TRIANGLE, new List <int[]>()); } polygons[IOPrimitive.TRIANGLE].AddRange(faces); } if (args.Length == 5) { // quad if (!polygons.ContainsKey(IOPrimitive.QUAD)) { polygons.Add(IOPrimitive.QUAD, new List <int[]>()); } polygons[IOPrimitive.QUAD].AddRange(faces); } if (args.Length == 6) { // strip if (!polygons.ContainsKey(IOPrimitive.TRISTRIP)) { polygons.Add(IOPrimitive.TRISTRIP, new List <int[]>()); } polygons[IOPrimitive.TRISTRIP].AddRange(faces); } break; case "o": if (polygons.Count > 0) { objects.Add(new Tuple <string, string, Dictionary <IOPrimitive, List <int[]> > >(objName, matNam, polygons)); } objName = args[1]; matNam = ""; polygons = new Dictionary <IOPrimitive, List <int[]> >(); break; case "usemtl": matNam = args[1]; break; } } } objects.Add(new Tuple <string, string, Dictionary <IOPrimitive, List <int[]> > >(objName, matNam, polygons)); // generate model IOModel model = new IOModel(); scene.Models.Add(model); // dummy bone model.Skeleton.RootBones.Add(new Core.Skeleton.IOBone() { Name = "Root" }); // convert and add polygons foreach (var obj in objects) { IOMesh mesh = new IOMesh() { Name = obj.Item1 }; foreach (var p in obj.Item3) { IOPolygon poly = new IOPolygon() { PrimitiveType = p.Key, MaterialName = obj.Item2, }; for (int i = 0; i < p.Value.Count; i++) { var face = p.Value[i]; IOVertex vert = new IOVertex() { Position = face[0] != -1 ? v[face[0]] : Vector3.Zero, Normal = face[2] != -1 ? vn[face[2]] : Vector3.Zero, }; if (face[1] != -1) { vert.UVs.Add(vt[face[1]]); } poly.Indicies.Add(mesh.Vertices.Count); mesh.Vertices.Add(vert); } mesh.Polygons.Add(poly); } // add mesh to model model.Meshes.Add(mesh); } ; } return(scene); }
public IOModel ImportIOModel(string FileName) { IOModel model = new IOModel(); SBSkeleton skeleton = new SBSkeleton(); model.Skeleton = skeleton; var test = Fbx.FbxIO.ReadBinary(FileName); if (test.Version != Fbx.FbxVersion.v7_4) { throw new NotSupportedException($"Only FBX version 7.4 is currently supported: Imported version = {test.Version}"); } // global settings float Scale = 1; // read global settings var settings = test.GetNodesByName("GlobalSettings"); if (settings.Length != 0) { var prop70 = settings[0].GetNodesByName("Properties70"); if (prop70.Length != 0) { foreach (var property in prop70[0].Nodes) { if (property == null) { continue; } if (property.Properties.Count > 0 && property.Name == "P") { //TODO: this is inaccurate... //if (property.Properties[0].Equals("UnitScaleFactor")) // Scale = (float)(double)property.Properties[4]; } } } } FbxAccessor accessor = new FbxAccessor(FileName); //Bones var limbs = accessor.GetLimbNodes(); SBConsole.WriteLine($"Limb Node Count: {limbs.Length}"); foreach (var limb in limbs) { skeleton.AddRoot(ConvertLimbToSBBone(limb)); } foreach (var root in skeleton.Roots) { root.Scale *= Scale; } // Fast access to bone indices Dictionary <string, int> BoneNameToIndex = new Dictionary <string, int>(); foreach (var b in skeleton.Bones) { BoneNameToIndex.Add(b.Name, skeleton.IndexOfBone(b)); } // Mesh var models = accessor.GetModels(); SBConsole.WriteLine($"Model Node Count: {models.Count}"); int YupAxis = accessor.GetOriginalXAxis(); SBConsole.WriteLine("Yup: " + YupAxis); foreach (var mod in models) { // rotation 90 Matrix4 transform = (ImportSettings.Rotate90 ? Matrix4.CreateRotationX(-90 * DegToRag) : Matrix4.Identity) * GetModelTransform(mod); foreach (var geom in mod.Geometries) { IOMesh mesh = new IOMesh(); mesh.Name = mod.Name; model.Meshes.Add(mesh); // Create Rigging information Vector4[] BoneIndices = new Vector4[geom.Vertices.Length]; Vector4[] BoneWeights = new Vector4[geom.Vertices.Length]; foreach (var deformer in geom.Deformers) { //TODO: this shouldn't happen... if (!BoneNameToIndex.ContainsKey(deformer.Name)) { continue; } int index = BoneNameToIndex[deformer.Name]; for (int i = 0; i < deformer.Indices.Length; i++) { int vertexIndex = deformer.Indices[i]; for (int j = 0; j < 4; j++) { if (BoneWeights[vertexIndex][j] == 0) { BoneIndices[vertexIndex][j] = index; BoneWeights[vertexIndex][j] = (float)deformer.Weights[i]; break; } } } //SBConsole.WriteLine(deformer.Name + " " + deformer.Weights.Length + " " + deformer.Indices.Length + " " + index); } // Explanation: // negative values are used to indicate a stopping point for the face // so every 3rd index needed to be adjusted for (int i = 0; i < geom.Indices.Length; i += 3) { mesh.Indices.Add((uint)i); mesh.Indices.Add((uint)i + 1); mesh.Indices.Add((uint)i + 2); mesh.Vertices.Add(CreateVertex(transform, geom, i, BoneIndices, BoneWeights, Scale)); mesh.Vertices.Add(CreateVertex(transform, geom, i + 1, BoneIndices, BoneWeights, Scale)); mesh.Vertices.Add(CreateVertex(transform, geom, i + 2, BoneIndices, BoneWeights, Scale)); } mesh.HasPositions = true; //SBConsole.WriteLine(geom.Vertices.Length); foreach (var layer in geom.Layers) { switch (layer.Name) { case "LayerElementNormal": case "LayerElementUV": case "LayerElementColor": break; default: SBConsole.WriteLine(layer.Name + " " + layer.ReferenceInformationType + " " + layer.Data.Length + " " + (layer.ReferenceInformationType.Equals("IndexToDirect") ? layer.Indices.Length.ToString() : "")); break; } } mesh.Optimize(); } } return(model); }
/// <summary> /// /// </summary> /// <returns></returns> private void ProcessMesh(IOScene scene, IOMesh mesh, HSD_JOBJ rootnode) { HSD_JOBJ parent = rootnode; HashSet <HSD_JOBJ> nodes = new HashSet <HSD_JOBJ>(); foreach (var j in rootnode.BreathFirstList) { nodes.Add(j); } if (mesh.ParentBone != null && _cache.NameToJOBJ.ContainsKey(mesh.ParentBone.Name)) { parent = _cache.NameToJOBJ[mesh.ParentBone.Name]; } HSD_DOBJ root = null; HSD_DOBJ prev = null; //var skeleton = rootnode.BreathFirstList; Console.WriteLine("Processing " + mesh.Name); bool singleBinded = mesh.Name.Contains("SINGLE"); foreach (var poly in mesh.Polygons) { // Skip Empty Polygon if (poly.Indicies.Count == 0) { continue; } // convert to triangles poly.ToTriangles(mesh); if (poly.PrimitiveType != IOPrimitive.TRIANGLE) { continue; } // Generate DOBJ HSD_DOBJ dobj = new HSD_DOBJ(); if (Settings.ImportMeshNames) { dobj.ClassName = mesh.Name; } if (root == null) { root = dobj; } else { prev.Next = dobj; } prev = dobj; // generate material var material = scene.Materials.Find(e => e.Name == poly.MaterialName); dobj.Mobj = GenerateMaterial(material); Console.WriteLine(mesh.Name + " " + material?.Name); // reflective mobjs do not use uvs var hasReflection = false; // bump maps need tangents and bitangents var hasBump = false; // Assess needed attributes based on the material MOBJ if (mesh.Name.Contains("REFLECTIVE")) { hasReflection = true; } #if DEBUG if (Settings.MetalModel) { hasReflection = true; } #endif if (mesh.Name.Contains("BUMP")) { hasBump = true; } if (dobj.Mobj.Textures != null) { foreach (var t in dobj.Mobj.Textures.List) { if (t.Flags.HasFlag(TOBJ_FLAGS.COORD_REFLECTION)) { hasReflection = true; } if (t.Flags.HasFlag(TOBJ_FLAGS.BUMP)) { hasBump = true; } } } // assess attributes List <GXAttribName> Attributes = new List <GXAttribName>(); if (mesh.HasEnvelopes() && Settings.ImportRigging && !singleBinded) { Attributes.Add(GXAttribName.GX_VA_PNMTXIDX); if (hasReflection) { Attributes.Add(GXAttribName.GX_VA_TEX0MTXIDX); if (dobj.Mobj.Textures != null && dobj.Mobj.Textures.List.Count > 1) { Attributes.Add(GXAttribName.GX_VA_TEX1MTXIDX); } #if DEBUG if (Settings.MetalModel && !Attributes.Contains(GXAttribName.GX_VA_TEX1MTXIDX)) { Attributes.Add(GXAttribName.GX_VA_TEX1MTXIDX); } #endif } } Attributes.Add(GXAttribName.GX_VA_POS); if (hasBump) { Attributes.Add(GXAttribName.GX_VA_NBT); } else if (mesh.HasNormals && Settings.ImportNormals) { Attributes.Add(GXAttribName.GX_VA_NRM); } if (mesh.HasColorSet(0) && Settings.ImportVertexColor) { Attributes.Add(GXAttribName.GX_VA_CLR0); } if (mesh.HasColorSet(1) && Settings.ImportVertexColor) { Attributes.Add(GXAttribName.GX_VA_CLR1); } if (mesh.HasUVSet(0) && !hasReflection) { Attributes.Add(GXAttribName.GX_VA_TEX0); } if ((mesh.HasUVSet(1) || (dobj.Mobj.Textures != null && dobj.Mobj.Textures.List.Count > 1)) && !hasReflection) { Attributes.Add(GXAttribName.GX_VA_TEX1); } if ((mesh.HasUVSet(2) || (dobj.Mobj.Textures != null && dobj.Mobj.Textures.List.Count > 2)) && !hasReflection) { Attributes.Add(GXAttribName.GX_VA_TEX2); } if ((mesh.HasUVSet(3) || (dobj.Mobj.Textures != null && dobj.Mobj.Textures.List.Count > 3)) && !hasReflection) { Attributes.Add(GXAttribName.GX_VA_TEX3); } if ((mesh.HasUVSet(4) || (dobj.Mobj.Textures != null && dobj.Mobj.Textures.List.Count > 4)) && !hasReflection) { Attributes.Add(GXAttribName.GX_VA_TEX4); } if ((mesh.HasUVSet(5) || (dobj.Mobj.Textures != null && dobj.Mobj.Textures.List.Count > 5)) && !hasReflection) { Attributes.Add(GXAttribName.GX_VA_TEX5); } if ((mesh.HasUVSet(6) || (dobj.Mobj.Textures != null && dobj.Mobj.Textures.List.Count > 6)) && !hasReflection) { Attributes.Add(GXAttribName.GX_VA_TEX6); } if ((mesh.HasUVSet(7) || (dobj.Mobj.Textures != null && dobj.Mobj.Textures.List.Count > 7)) && !hasReflection) { Attributes.Add(GXAttribName.GX_VA_TEX7); } var vertices = new List <GX_Vertex>(); var jobjList = new List <HSD_JOBJ[]>(); var weightList = new List <float[]>(); foreach (var face in poly.Indicies) { var v = mesh.Vertices[face]; GX_Vertex vertex = new GX_Vertex(); var tkvert = new Vector3(v.Position.X, v.Position.Y, v.Position.Z); var tknrm = new Vector3(v.Normal.X, v.Normal.Y, v.Normal.Z); var tktan = new Vector3(v.Tangent.X, v.Tangent.Y, v.Tangent.Z); var tkbitan = new Vector3(v.Binormal.X, v.Binormal.Y, v.Binormal.Z); var parentTransform = _cache.jobjToWorldTransform[parent].Inverted(); if (_cache.jobjToWorldTransform[parent] != Matrix4.Identity) { tkvert = Vector3.TransformPosition(tkvert, parentTransform); tknrm = Vector3.TransformNormal(tknrm, parentTransform).Normalized(); tktan = Vector3.TransformNormal(tktan, parentTransform).Normalized(); tkbitan = Vector3.TransformNormal(tkbitan, parentTransform).Normalized(); } if (mesh.HasEnvelopes() && Settings.ImportRigging) { // create weighting lists List <float> weight = new List <float>(); List <HSD_JOBJ> bones = new List <HSD_JOBJ>(); if (v.Envelope.Weights.Count == 0) { weight.Add(1); bones.Add(rootnode); } if (v.Envelope.Weights.Count > 4) { throw new Exception($"Too many weights! {v.Envelope.Weights.Count} in {mesh.Name}"); } foreach (var bw in v.Envelope.Weights) { // check if skeleton actually contains bone if (_cache.NameToJOBJ.ContainsKey(bw.BoneName) && nodes.Contains(_cache.NameToJOBJ[bw.BoneName])) { // add envelope bones.Add(_cache.NameToJOBJ[bw.BoneName]); weight.Add(bw.Weight); // indicate enveloped jobjs if (!_cache.EnvelopedJOBJs.Contains(_cache.NameToJOBJ[bw.BoneName])) { _cache.EnvelopedJOBJs.Add(_cache.NameToJOBJ[bw.BoneName]); } } else { throw new Exception($"Bone not found \"{bw.BoneName}\" Weight: {bw.Weight} in {mesh.Name}"); } } jobjList.Add(bones.ToArray()); weightList.Add(weight.ToArray()); // invert single binds if (v.Envelope.Weights.Count == 1) { var inv = _cache.jobjToWorldTransform[_cache.NameToJOBJ[v.Envelope.Weights[0].BoneName]].Inverted(); tkvert = Vector3.TransformPosition(tkvert, inv); tknrm = Vector3.TransformNormal(tknrm, inv).Normalized(); tktan = Vector3.TransformNormal(tknrm, inv).Normalized(); tkbitan = Vector3.TransformNormal(tknrm, inv).Normalized(); } } vertex.POS = GXTranslator.fromVector3(tkvert); vertex.NRM = GXTranslator.fromVector3(tknrm.Normalized()); vertex.TAN = GXTranslator.fromVector3(tktan); vertex.BITAN = GXTranslator.fromVector3(tkbitan); if (Settings.InvertNormals) { vertex.NRM.X *= -1; vertex.NRM.Y *= -1; vertex.NRM.Z *= -1; vertex.TAN.X *= -1; vertex.TAN.Y *= -1; vertex.TAN.Z *= -1; vertex.BITAN.X *= -1; vertex.BITAN.Y *= -1; vertex.BITAN.Z *= -1; } if (mesh.HasUVSet(0)) { vertex.TEX0 = new GXVector2(v.UVs[0].X, v.UVs[0].Y); } if (mesh.HasUVSet(1)) { vertex.TEX1 = new GXVector2(v.UVs[1].X, v.UVs[1].Y); } if (mesh.HasUVSet(2)) { vertex.TEX2 = new GXVector2(v.UVs[2].X, v.UVs[2].Y); } if (mesh.HasUVSet(3)) { vertex.TEX3 = new GXVector2(v.UVs[3].X, v.UVs[3].Y); } if (mesh.HasUVSet(4)) { vertex.TEX4 = new GXVector2(v.UVs[4].X, v.UVs[4].Y); } if (mesh.HasUVSet(5)) { vertex.TEX5 = new GXVector2(v.UVs[5].X, v.UVs[5].Y); } if (mesh.HasUVSet(6)) { vertex.TEX6 = new GXVector2(v.UVs[6].X, v.UVs[6].Y); } if (mesh.HasUVSet(7)) { vertex.TEX7 = new GXVector2(v.UVs[7].X, v.UVs[7].Y); } if (mesh.HasColorSet(0)) { vertex.CLR0 = new GXColor4( v.Colors[0].X * (Settings.MultiplyVertexColorBy2 ? 2 : 1), v.Colors[0].Y * (Settings.MultiplyVertexColorBy2 ? 2 : 1), v.Colors[0].Z * (Settings.MultiplyVertexColorBy2 ? 2 : 1), Settings.ImportVertexAlpha ? v.Colors[0].W : 1); } if (mesh.HasColorSet(1)) { vertex.CLR1 = new GXColor4( v.Colors[1].X * (Settings.MultiplyVertexColorBy2 ? 2 : 1), v.Colors[1].Y * (Settings.MultiplyVertexColorBy2 ? 2 : 1), v.Colors[1].Z * (Settings.MultiplyVertexColorBy2 ? 2 : 1), Settings.ImportVertexAlpha ? v.Colors[1].W : 1); } vertices.Add(vertex); } // generate pobjs HSD_POBJ pobj = null; if (mesh.HasEnvelopes() && Settings.ImportRigging && !singleBinded) { pobj = _cache.POBJGen.CreatePOBJsFromTriangleList(vertices, Attributes.ToArray(), jobjList, weightList); } else { pobj = _cache.POBJGen.CreatePOBJsFromTriangleList(vertices, Attributes.ToArray(), null); } if (singleBinded && jobjList.Count > 0 && jobjList[0].Length > 0) { parent = jobjList[0][0]; } if (pobj != null) { if (dobj.Pobj == null) { dobj.Pobj = pobj; } else { dobj.Pobj.Add(pobj); } } } if (parent.Dobj == null) { parent.Dobj = root; } else { parent.Dobj.Add(root); } }
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); }
/// <summary> /// /// </summary> /// <returns></returns> public List <IOMesh> ExtractMesh() { // gather mesh objects var mesh = _document.GetNodesByName("Model").Where(e => e.Properties.Count >= NodeDescSize && e.Properties[NodeDescSize - 1].ToString() == "Mesh"); List <IOMesh> meshes = new List <IOMesh>(); var subdeformers = _document.GetNodesByName("SubDeformer"); // extract geometry information into iomesh foreach (var m in mesh) { // generate mesh IOMesh iomesh = new IOMesh() { Name = GetNameWithoutNamespace(m.Properties[NodeDescSize - 2].ToString()) }; meshes.Add(iomesh); // reverse search material var material = ""; var materials = GetChildConnections(m).Find(e => e.Name == "Material"); if (materials != null) { material = GetNameWithoutNamespace(materials.Properties[NodeDescSize - 2].ToString()); } // collect geometry information // in older fbx this was stored in the mesh node // in newer versions it's in its own node var geoms = _document.GetNodesByName("Geometry").Where(e => IsParentedTo(e.Properties[0].ToString(), m.Properties[0].ToString())).ToArray(); if (geoms.Length > 0) { foreach (var g in geoms) { ProcessGeometry(g, out IOPolygon poly, out List <IOVertex> verts); for (int i = 0; i < poly.Indicies.Count; i++) { poly.Indicies[i] += iomesh.Vertices.Count; } iomesh.Vertices.AddRange(verts); poly.MaterialName = material; iomesh.Polygons.Add(poly); } } else { ProcessGeometry(m, out IOPolygon poly, out List <IOVertex> verts); poly.MaterialName = material; iomesh.Vertices = verts; iomesh.Polygons.Add(poly); } // get transform for this node var tra = CreateBoneFromNode(m); iomesh.TransformVertices(tra.WorldTransform); } return(meshes); }
public IOModel GetIOModel() { IOModel outModel = new IOModel(); MESH meshFile = null; foreach (FileNode n in Parent.Nodes) { if (n.Text.Equals(_model.MeshString)) { meshFile = ((NUMSHB_Node)n).mesh; } if (n.Text.Equals(_model.SkeletonFileName)) { outModel.Skeleton = (RSkeleton)((SKEL_Node)n).GetRenderableNode(); } } 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); } } if (meshFile != null) { using (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); 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); } } 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, 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, 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, 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, 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); } } } // 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; } } } // Apply Rigging SSBHVertexInfluence[] influences = riggingAccessor.ReadRiggingBuffer(obj.Name, (int)obj.SubMeshIndex); foreach (SSBHVertexInfluence influence in influences) { // 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); }
/// <summary> /// /// </summary> /// <param name="n"></param> /// <param name="id"></param> /// <returns></returns> public IOMesh LoadGeometryFromID(Node n, string id, List <IOEnvelope> vertexEnvelopes = null) { // sanitize id = ColladaHelper.SanitizeID(id); // find geometry by id var geom = _collada.Library_Geometries.Geometry.First(e => e.ID == id); // not found if (geom == null) { return(null); } // create new mesh IOMesh mesh = new IOMesh() { Name = n.Name }; // create source manager helper SourceManager srcs = new SourceManager(); if (geom.Mesh.Source != null) { foreach (var src in geom.Mesh.Source) { srcs.AddSource(src); } } // load geomtry meshes if (geom.Mesh.Triangles != null) { foreach (var tri in geom.Mesh.Triangles) { var stride = tri.Input.Max(e => e.Offset) + 1; var poly = new IOPolygon() { PrimitiveType = IOPrimitive.TRIANGLE, MaterialName = tri.Material }; var p = tri.P.GetValues(); for (int i = 0; i < tri.Count * 3; i++) { IOVertex vertex = new IOVertex(); for (int j = 0; j < tri.Input.Length; j++) { var input = tri.Input[j]; var index = p[i * stride + input.Offset]; ProcessInput(input.Semantic, input.source, input.Set, vertex, geom.Mesh.Vertices, index, srcs, vertexEnvelopes); } poly.Indicies.Add(mesh.Vertices.Count); mesh.Vertices.Add(vertex); } mesh.Polygons.Add(poly); } } //TODO: collada trifan //TODO: collada tristrip //TODO: collada linestrip //TODO: collada polylist //TODO: collada polygon return(mesh); }
public IOModel ImportIOModel(string FileName) { IOModel model = new IOModel(); SBSkeleton skeleton = new SBSkeleton(); Dictionary <int, SBBone> indexToBone = new Dictionary <int, SBBone>(); Dictionary <string, IOMesh> materialToMesh = new Dictionary <string, IOMesh>(); List <SBBone> PostProcessBones = new List <SBBone>(); List <int> boneParents = new List <int>(); string[] lines = File.ReadAllLines(FileName); string CurrentSection = ""; for (int i = 0; i < lines.Length; i++) { string[] args = Regex.Replace(lines[i].Trim(), @"\s+", " ").Split(' '); if (args[0].Equals("end")) { CurrentSection = ""; } else if (args[0].Equals("time")) { } else if (args[0].Equals("nodes")) { CurrentSection = args[0]; } else if (args[0].Equals("skeleton")) { CurrentSection = args[0]; } else if (args[0].Equals("triangles")) { CurrentSection = args[0]; } else { switch (CurrentSection) { case "nodes": var bone = new SBBone(); bone.Name = args[1].Replace("\"", ""); Console.WriteLine(bone.Name + " " + args[2]); boneParents.Add(int.Parse(args[2])); PostProcessBones.Add(bone); indexToBone.Add(int.Parse(args[0]), bone); break; case "skeleton": var skel = PostProcessBones[int.Parse(args[0])]; skel.Transform = Matrix4.Identity; skel.X = float.Parse(args[1]); skel.Y = float.Parse(args[2]); skel.Z = float.Parse(args[3]); skel.RX = float.Parse(args[4]); skel.RY = float.Parse(args[5]); skel.RZ = float.Parse(args[6]); break; case "triangles": string material = args[0]; if (!materialToMesh.ContainsKey(material)) { var iomesh = new IOMesh(); iomesh.HasPositions = true; iomesh.HasNormals = true; iomesh.HasUV0 = true; iomesh.HasBoneWeights = true; iomesh.Name = material; materialToMesh.Add(material, iomesh); } var mesh = materialToMesh[material]; mesh.Vertices.Add(ReadVertex(Regex.Replace(lines[i + 1].Trim(), @"\s+", " ").Split(' '))); mesh.Vertices.Add(ReadVertex(Regex.Replace(lines[i + 2].Trim(), @"\s+", " ").Split(' '))); mesh.Vertices.Add(ReadVertex(Regex.Replace(lines[i + 3].Trim(), @"\s+", " ").Split(' '))); i += 3; break; } } } //PostProcessBones int boneIndex = 0; foreach (var bone in PostProcessBones) { if (boneParents[boneIndex] == -1) { skeleton.AddRoot(bone); } else { bone.Parent = indexToBone[boneParents[boneIndex]]; } boneIndex++; } model.Skeleton = skeleton; // finalize meshes foreach (var pair in materialToMesh) { model.Meshes.Add(pair.Value); pair.Value.Optimize(); SBConsole.WriteLine($"Imported {pair.Key} from SMD"); //finalize rigging for (int i = 0; i < pair.Value.Vertices.Count; i++) { var vertex = pair.Value.Vertices[i]; vertex.BoneIndices.X = model.Skeleton.IndexOfBone(indexToBone[(int)vertex.BoneIndices.X]); vertex.BoneIndices.Y = model.Skeleton.IndexOfBone(indexToBone[(int)vertex.BoneIndices.Y]); vertex.BoneIndices.Z = model.Skeleton.IndexOfBone(indexToBone[(int)vertex.BoneIndices.Z]); vertex.BoneIndices.W = model.Skeleton.IndexOfBone(indexToBone[(int)vertex.BoneIndices.W]); pair.Value.Vertices[i] = vertex; } } return(model); }