/// <summary> /// /// </summary> /// <param name="filePath"></param> /// <returns></returns> public IOScene GetScene(string filePath) { IOScene scene = new IOScene(); scene.Animations.Add(MayaAnim.ImportAnimation(filePath, new ImportSettings())); return(scene); }
/// <summary> /// /// </summary> /// <param name="scene"></param> /// <param name="filePath"></param> private static void ExportMTL(IOScene scene, string filePath) { using (FileStream stream = File.OpenWrite(filePath)) using (StreamWriter w = new StreamWriter(stream)) { foreach (var mat in scene.Materials) { w.WriteLine($"newmtl {mat.Name}"); w.WriteLine(" illum 2"); w.WriteLine($" Ka {mat.AmbientColor.X} {mat.AmbientColor.Y} {mat.AmbientColor.Z}"); w.WriteLine($" Kd {mat.DiffuseColor.X} {mat.DiffuseColor.Y} {mat.DiffuseColor.Z}"); w.WriteLine($" Ks {mat.SpecularColor.X} {mat.SpecularColor.Y} {mat.SpecularColor.Z}"); w.WriteLine($" Ns {mat.Shininess}"); w.WriteLine($" d {mat.Alpha}"); w.WriteLine($" Tr {1 - mat.Alpha}"); if (mat.AmbientMap != null) { w.WriteLine($" map_Ka {mat.AmbientMap.FilePath}"); } if (mat.DiffuseMap != null) { w.WriteLine($" map_Kd {mat.DiffuseMap.FilePath}"); } if (mat.SpecularMap != null) { w.WriteLine($" map_Ks {mat.SpecularMap.FilePath}"); } } } }
/// <summary> /// /// </summary> /// <param name="filePath"></param> /// <returns></returns> public IOScene GetScene(string filePath) { FbxHelper helper = new FbxHelper(FbxIO.ReadBinary(filePath)); //TODO: FBX Version 2006-2010 unupported. (Anything < 7100 works. But textures aren't grabbed) //FBX Version 2011-2020 <-- Tested & Supported! //FBX Version 2011-2020 currently supported if (helper.Version < 7100) { throw new NotSupportedException($"FBX Version {helper.Version} not supported"); } IOScene scene = new IOScene(); IOModel model = new IOModel(); model.Skeleton = helper.GetSkeleton(); scene.Models.Add(model); model.Meshes.AddRange(helper.ExtractMesh()); scene.Materials.AddRange(helper.GetMaterials()); return(scene); }
/// <summary> /// /// </summary> /// <param name="scene"></param> /// <param name="filePath"></param> public void ExportScene(IOScene scene, string filePath, ExportSettings settings) { // create collada document _collada = new IONET.Collada.Collada(); _settings = settings; _collada.Version = "1.4.1"; // export materials foreach (var mat in scene.Materials) { ProcessMaterial(mat); } // initialize scene var visscene = new Visual_Scene { Name = scene.Name }; visscene.ID = scene.Name; // export models nodes List <Node> nodes = new List <Node>(); foreach (var mod in scene.Models) { nodes.AddRange(ProcessModel(mod)); } visscene.Node = nodes.ToArray(); // instance the scene var scene_instance = new Instance_Visual_Scene(); scene_instance.URL = "#" + visscene.Name; _collada.Library_Visual_Scene = new Library_Visual_Scenes(); _collada.Library_Visual_Scene.Visual_Scene = new Visual_Scene[] { visscene }; _collada.Scene = new Scene(); _collada.Scene.Visual_Scene = scene_instance; // initialize asset InitAsset(); // save to file _collada.SaveToFile(filePath); // cleanup _usedIDs.Clear(); _collada = null; }
/// <summary> /// /// </summary> /// <param name="filePath"></param> /// <returns></returns> public void ExportScene(IOScene scene, string filePath, ExportSettings settings) { if (scene.Models.Count == 0) { throw new Exception($"Failed to export animation! Model must have a skeleton to export!"); } MayaAnim.ExportAnimation(filePath, settings, scene.Animations[0], scene.Models[0].Skeleton); }
/// <summary> /// /// </summary> /// <param name="iomodel"></param> public override void FromIOModel(IOScene iomodel) { var model = iomodel.Models[0]; InvertSingleBinds(model); // dobjs to import to var dobjs = GetMeshObjects(); // clear pobjs foreach (SBHsdMesh m in GetMeshObjects()) { m.DOBJ.Pobj = null; } var attributeGroup = MakeRiggedAttributes(); var singleAttributeGroup = MakeSingleAttributes(); // get a compressor ready // the compressor will handle making the compressed attribute buffers POBJ_Generator compressor = new POBJ_Generator(); // import the iomeshes into their respective dobjs foreach (var iomesh in model.Meshes) { int dobjId = -1; int.TryParse(iomesh.Name.Replace("DOBJ_", ""), out dobjId); SBConsole.WriteLine(iomesh.Name + " imported:" + (dobjId != -1)); if (dobjId != -1) { var dobj = (SBHsdMesh)dobjs[dobjId]; dobj.ImportPOBJs(iomesh, (SBSkeleton)Skeleton, compressor, dobj.ParentBone == "JOBJ_0" ? attributeGroup : singleAttributeGroup); } } // finalizes and remakes the buffer compressor.SaveChanges(); // refresh everything RefreshRendering(); }
/// <summary> /// /// </summary> /// <param name="filePath"></param> /// <returns></returns> public IOScene GetScene(string filePath) { // generate a new scene IOScene scene = new IOScene(); // load collada file _collada = Collada.LoadFromFile(filePath); // failed to load collada file if (_collada == null) { return(scene); } // load material library's to scene if (_collada.Library_Materials != null) { foreach (var mat in _collada.Library_Materials.Material) { scene.Materials.Add(LoadMaterial(mat)); } } // look through all visual scene foreach (var colscene in _collada.Library_Visual_Scene.Visual_Scene) { // treat each scene as a "model" IOModel model = new IOModel() { Name = colscene.Name }; // scan skeletons List <string> skelIDs = new List <string>(); foreach (var v in colscene.Node) { if (GetSkeletonReferences(v, out List <string> joints)) { foreach (var j in joints) { if (!skelIDs.Contains(j)) { skelIDs.Add(j); } } } } // load nodes foreach (var v in colscene.Node) { LoadNodes(v, null, model, skelIDs); } // add model scene.Models.Add(model); } // cleanup _collada = null; // done return(scene); }
/// <summary> /// /// </summary> /// <param name="scene"></param> /// <param name="filePath"></param> public void ExportScene(IOScene scene, string filePath, ExportSettings settings) { if (scene.Models.Count == 0) { return; } using (FileStream stream = new FileStream(filePath, FileMode.Create)) using (StreamWriter w = new StreamWriter(stream)) { var model = scene.Models[0]; w.WriteLine("version 1"); var bones = model.Skeleton.BreathFirstOrder(); Dictionary <string, int> nodeToID = new Dictionary <string, int>(); w.WriteLine("nodes"); var index = 0; foreach (var b in bones) { nodeToID.Add(b.Name, index); w.WriteLine($"{index++} \"{b.Name}\" {model.Skeleton.IndexOf(b.Parent)}"); } foreach (var b in model.Meshes) { nodeToID.Add(b.Name, index); w.WriteLine($"{index++} \"{b.Name}\" {-1}"); } w.WriteLine("end"); w.WriteLine("skeleton"); w.WriteLine("time 0"); index = 0; foreach (var b in bones) { // since we can't store scale we have to improvise and extract the world scale Matrix4x4.Decompose(b.WorldTransform, out Vector3 uniformScale, out Quaternion rot, out Vector3 pos); w.WriteLine($"{index++} {b.TranslationX * uniformScale.X} {b.TranslationY * uniformScale.Y} {b.TranslationZ * uniformScale.Z} {b.RotationEuler.X} {b.RotationEuler.Y} {b.RotationEuler.Z}"); } foreach (var b in model.Meshes) { w.WriteLine($"{index++} 0 0 0 0 0 0"); } w.WriteLine("end"); if (model.Meshes.Count > 0) { w.WriteLine("triangles"); foreach (var m in model.Meshes) { // smd only supports triangles m.MakeTriangles(); // look though all poly groups for (int p = 0; p < m.Polygons.Count; p++) { var poly = m.Polygons[p]; for (int i = 0; i < poly.Indicies.Count; i += 3) { if (string.IsNullOrEmpty(poly.MaterialName)) { w.WriteLine($"{m.Name}_poly_{p}"); } else { w.WriteLine($"{poly.MaterialName}"); } { var v1 = m.Vertices[poly.Indicies[i]]; var env = v1.Envelope; w.WriteLine($"{nodeToID[m.Name]} {v1.Position.X} {v1.Position.Y} {v1.Position.Z} {v1.Normal.X} {v1.Normal.Y} {v1.Normal.Z} {(v1.UVs.Count > 0 ? v1.UVs[0].X : 0)} {(v1.UVs.Count > 0 ? v1.UVs[0].Y : 0)} {env.Weights.Count + " " + string.Join(" ", env.Weights.Select(e => nodeToID[e.BoneName] + " " + e.Weight))}"); } { var v1 = m.Vertices[poly.Indicies[i + 1]]; var env = v1.Envelope; w.WriteLine($"{nodeToID[m.Name]} {v1.Position.X} {v1.Position.Y} {v1.Position.Z} {v1.Normal.X} {v1.Normal.Y} {v1.Normal.Z} {(v1.UVs.Count > 0 ? v1.UVs[0].X : 0)} {(v1.UVs.Count > 0 ? v1.UVs[0].Y : 0)} {env.Weights.Count + " " + string.Join(" ", env.Weights.Select(e => nodeToID[e.BoneName] + " " + e.Weight))}"); } { var v1 = m.Vertices[poly.Indicies[i + 2]]; var env = v1.Envelope; w.WriteLine($"{nodeToID[m.Name]} {v1.Position.X} {v1.Position.Y} {v1.Position.Z} {v1.Normal.X} {v1.Normal.Y} {v1.Normal.Z} {(v1.UVs.Count > 0 ? v1.UVs[0].X : 0)} {(v1.UVs.Count > 0 ? v1.UVs[0].Y : 0)} {env.Weights.Count + " " + string.Join(" ", env.Weights.Select(e => nodeToID[e.BoneName] + " " + e.Weight))}"); } } } } w.WriteLine("end"); } } }
/// <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); } }
/// <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="scene"></param> /// <param name="filePath"></param> public void ExportScene(IOScene scene, string filePath, ExportSettings settings) { var matLib = filePath.Replace(".obj", ".mtl"); // sanatize material names scene.CleanMaterialNames(); ExportMTL(scene, matLib); using (FileStream stream = new FileStream(filePath, FileMode.Create)) using (StreamWriter w = new StreamWriter(stream)) { w.WriteLine($"mtllib {Path.GetFileName(matLib)}"); Dictionary <string, int> v_strToIndex = new Dictionary <string, int>(); Dictionary <string, int> vt_strToIndex = new Dictionary <string, int>(); Dictionary <string, int> vn_strToIndex = new Dictionary <string, int>(); foreach (var mod in scene.Models) { foreach (var mesh in mod.Meshes) { // make mesh triangles mesh.MakeTriangles(); // Dictionary <int, int> vmap = new Dictionary <int, int>(); Dictionary <int, int> vtmap = new Dictionary <int, int>(); Dictionary <int, int> vnmap = new Dictionary <int, int>(); // add vertices for (int i = 0; i < mesh.Vertices.Count; i++) { var v = mesh.Vertices[i]; // positions var vv = $"v {v.Position.X} {v.Position.Y} {v.Position.Z}"; if (!v_strToIndex.ContainsKey(vv)) { v_strToIndex.Add(vv, v_strToIndex.Count); w.WriteLine(vv); } vmap.Add(i, v_strToIndex[vv] + 1); // uvs if (v.UVs.Count > 0) { var vtv = $"vt {v.UVs[0].X} {v.UVs[0].Y}"; if (!vt_strToIndex.ContainsKey(vtv)) { vt_strToIndex.Add(vtv, vt_strToIndex.Count); w.WriteLine(vtv); } vtmap.Add(i, vt_strToIndex[vtv] + 1); } // normals var vnv = $"vn {v.Normal.X} {v.Normal.Y} {v.Normal.Z}"; if (!vn_strToIndex.ContainsKey(vnv)) { vn_strToIndex.Add(vnv, vn_strToIndex.Count); w.WriteLine(vnv); } vnmap.Add(i, vn_strToIndex[vnv] + 1); } // add polygons w.WriteLine($"o {mesh.Name}"); foreach (var poly in mesh.Polygons) { w.WriteLine($"g polygon_{mesh.Polygons.IndexOf(poly)}"); w.WriteLine($"usemtl {poly.MaterialName}"); for (int i = 0; i < poly.Indicies.Count; i += 3) { w.WriteLine( $"f {vmap[poly.Indicies[i]]}/{(vtmap.ContainsKey(poly.Indicies[i]) ? vtmap[poly.Indicies[i]].ToString() : "")}/{vnmap[poly.Indicies[i]]} " + $"{vmap[poly.Indicies[i + 1]]}/{(vtmap.ContainsKey(poly.Indicies[i + 1]) ? vtmap[poly.Indicies[i + 1]].ToString() : "")}/{vnmap[poly.Indicies[i + 1]]} " + $"{vmap[poly.Indicies[i + 2]]}/{(vtmap.ContainsKey(poly.Indicies[i + 2]) ? vtmap[poly.Indicies[i + 2]].ToString() : "")}/{vnmap[poly.Indicies[i + 2]]}"); } } } } } }
/// <summary> /// /// </summary> /// <param name="scene"></param> /// <param name="filePath"></param> public static void ExportScene(IOScene scene, string filePath, ExportSettings settings = null) { var ext = Path.GetExtension(filePath).ToLower(); if (settings == null) { settings = new ExportSettings(); } foreach (var l in SceneExporters) { foreach (var e in l.GetExtensions()) { if (e.Equals(ext)) { // apply post processing foreach (var model in scene.Models) { // smooth normals if (settings.SmoothNormals) { model.SmoothNormals(); } // post process mesh foreach (var m in model.Meshes) { // optimize vertices if (settings.Optimize) { m.Optimize(); } // vertex modifications if (settings.FlipUVs) { foreach (var v in m.Vertices) { // flip uvs if (settings.FlipUVs) { for (int i = 0; i < v.UVs.Count; i++) { v.UVs[i] = new System.Numerics.Vector2(v.UVs[i].X, 1 - v.UVs[i].Y); } } } } // flip winding order if (settings.FlipWindingOrder) { foreach (var p in m.Polygons) { p.ToTriangles(m); if (p.PrimitiveType == Core.Model.IOPrimitive.TRIANGLE) { for (int i = 0; i < p.Indicies.Count; i += 3) { var temp = p.Indicies[i + 1]; p.Indicies[i + 1] = p.Indicies[i + 2]; p.Indicies[i + 2] = temp; } } } } // reset envelopes foreach (var v in m.Vertices) { v.ResetEnvelope(model.Skeleton); } } } l.ExportScene(scene, filePath, settings); break; } } } }
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); }
/// <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; } } } }
/// <summary> /// Imports an IO model into the scene /// </summary> /// <param name="iomodel"></param> public virtual void FromIOModel(IOScene iomodel) { }