/// <summary> /// /// </summary> private static void ZeroOutRotations(ProcessingCache cache, HSD_JOBJ root) { Dictionary <HSD_JOBJ, Matrix4> newWorldMatrices = new Dictionary <HSD_JOBJ, Matrix4>(); ZeroOutRotations(cache, newWorldMatrices, root, Matrix4.Identity, Matrix4.Identity); cache.jobjToWorldTransform = newWorldMatrices; cache.jobjToInverseTransform.Clear(); foreach (var v in newWorldMatrices) { cache.jobjToInverseTransform.Add(v.Key, v.Value.Inverted()); } }
/// <summary> /// /// </summary> /// <param name="settings"></param> /// <param name="material"></param> /// <returns></returns> private static HSD_MOBJ GenerateMaterial(ProcessingCache cache, ModelImportSettings settings, Material material) { var Mobj = new HSD_MOBJ(); Mobj.Material = new HSD_Material(); Mobj.Material.AmbientColorRGBA = 0x7F7F7FFF; Mobj.Material.DiffuseColorRGBA = 0xFFFFFFFF; Mobj.Material.SpecularColorRGBA = 0xFFFFFFFF; Mobj.Material.Shininess = 1; Mobj.Material.Alpha = 1; Mobj.RenderFlags = RENDER_MODE.ALPHA_COMPAT | RENDER_MODE.DIFFSE_VTX; // Properties if (settings.ImportMaterialInfo) { if (material.HasShininess) { Mobj.Material.Shininess = material.Shininess; } if (material.HasColorAmbient) { Mobj.Material.AMB_A = ColorFloatToByte(material.ColorAmbient.A); Mobj.Material.AMB_R = ColorFloatToByte(material.ColorAmbient.R); Mobj.Material.AMB_G = ColorFloatToByte(material.ColorAmbient.G); Mobj.Material.AMB_B = ColorFloatToByte(material.ColorAmbient.B); } if (material.HasColorDiffuse) { Mobj.Material.DIF_A = ColorFloatToByte(material.ColorDiffuse.A); Mobj.Material.DIF_R = ColorFloatToByte(material.ColorDiffuse.R); Mobj.Material.DIF_G = ColorFloatToByte(material.ColorDiffuse.G); Mobj.Material.DIF_B = ColorFloatToByte(material.ColorDiffuse.B); } if (material.HasColorSpecular) { Mobj.Material.SPC_A = ColorFloatToByte(material.ColorSpecular.A); Mobj.Material.SPC_R = ColorFloatToByte(material.ColorSpecular.R); Mobj.Material.SPC_G = ColorFloatToByte(material.ColorSpecular.G); Mobj.Material.SPC_B = ColorFloatToByte(material.ColorSpecular.B); } } // Textures if (settings.ImportTexture) { if (material.HasTextureDiffuse) { var texturePath = Path.Combine(cache.FolderPath, material.TextureDiffuse.FilePath); if (File.Exists(material.TextureDiffuse.FilePath)) { texturePath = material.TextureDiffuse.FilePath; } /// 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)) { Mobj.RenderFlags |= RENDER_MODE.TEX0; var tobj = TOBJConverter.ImportTOBJFromFile(texturePath, settings.TextureFormat, settings.PaletteFormat); tobj.Flags = TOBJ_FLAGS.LIGHTMAP_DIFFUSE | TOBJ_FLAGS.COORD_UV; if (settings.ShadingType == ShadingType.VertexColor || settings.ShadingType == ShadingType.Material) { tobj.Flags |= TOBJ_FLAGS.COLORMAP_MODULATE; } else { tobj.Flags |= TOBJ_FLAGS.COLORMAP_REPLACE; } tobj.GXTexGenSrc = 4; tobj.TexMapID = GXTexMapID.GX_TEXMAP0; tobj.WrapS = ToGXWrapMode(material.TextureDiffuse.WrapModeU); tobj.WrapT = ToGXWrapMode(material.TextureDiffuse.WrapModeV); Mobj.Textures = tobj; } } } return(Mobj); }
/// <summary> /// /// </summary> /// <returns></returns> private static HSD_DOBJ GetMeshes(ProcessingCache cache, ModelImportSettings settings, Scene scene, Node node) { HSD_DOBJ root = null; HSD_DOBJ prev = null; Console.WriteLine("Processing " + node.Name); foreach (int index in node.MeshIndices) { Mesh mesh = scene.Meshes[index]; var material = scene.Materials[mesh.MaterialIndex]; Console.WriteLine(mesh.Name + " " + material.Name); // Generate DOBJ HSD_DOBJ dobj = new HSD_DOBJ(); // hack to make dobjs merged by texture if (settings.ImportTexture && settings.ForceMergeObjects == ForceGroupModes.Texture && material.HasTextureDiffuse && cache.TextureToDOBJ.ContainsKey(material.TextureDiffuse.FilePath)) { dobj = cache.TextureToDOBJ[material.TextureDiffuse.FilePath]; } else { if (root == null) { root = dobj; } else { prev.Next = dobj; } prev = dobj; dobj.Mobj = GenerateMaterial(cache, settings, material); if (settings.ForceMergeObjects == ForceGroupModes.Texture && material.HasTextureDiffuse && settings.ImportTexture) { cache.TextureToDOBJ.Add(material.TextureDiffuse.FilePath, dobj); } } if (root != null && settings.ForceMergeObjects == ForceGroupModes.MeshGroup) { dobj = root; } // Assessment if (!mesh.HasFaces) { continue; } // Assess needed attributes based on the material MOBJ // reflective mobjs do not use uvs var hasReflection = false; // bump maps need tangents and bitangents var hasBump = false; 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; } } } List <GXAttribName> Attributes = new List <GXAttribName>(); // todo: rigging List <HSD_JOBJ>[] jobjs = new List <HSD_JOBJ> [mesh.Vertices.Count]; List <float>[] weights = new List <float> [mesh.Vertices.Count]; if (mesh.HasBones) { Attributes.Add(GXAttribName.GX_VA_PNMTXIDX); foreach (var v in mesh.Bones) { var jobj = cache.NameToJOBJ[v.Name]; if (!cache.EnvelopedJOBJs.Contains(jobj)) { cache.EnvelopedJOBJs.Add(jobj); } if (v.HasVertexWeights) { foreach (var vw in v.VertexWeights) { if (jobjs[vw.VertexID] == null) { jobjs[vw.VertexID] = new List <HSD_JOBJ>(); } if (weights[vw.VertexID] == null) { weights[vw.VertexID] = new List <float>(); } if (vw.Weight > 0) { jobjs[vw.VertexID].Add(jobj); weights[vw.VertexID].Add(vw.Weight); } } } } } if (hasReflection) { Attributes.Add(GXAttribName.GX_VA_TEX0MTXIDX); } if (mesh.HasVertices) { Attributes.Add(GXAttribName.GX_VA_POS); } if (mesh.HasVertexColors(0) && settings.ShadingType == ShadingType.VertexColor) { Attributes.Add(GXAttribName.GX_VA_CLR0); } //if (mesh.HasVertexColors(1) && settings.ImportVertexColors) // Attributes.Add(GXAttribName.GX_VA_CLR1); if (!hasBump && mesh.HasNormals && settings.ShadingType == ShadingType.Material) { Attributes.Add(GXAttribName.GX_VA_NRM); } if (hasBump) { Attributes.Add(GXAttribName.GX_VA_NBT); } if (mesh.HasTextureCoords(0) && !hasReflection) { Attributes.Add(GXAttribName.GX_VA_TEX0); } //if (mesh.HasTextureCoords(1)) // Attributes.Add(GXAttribName.GX_VA_TEX1); var vertices = new List <GX_Vertex>(); var jobjList = new List <HSD_JOBJ[]>(vertices.Count); var wList = new List <float[]>(vertices.Count); foreach (var face in mesh.Faces) { PrimitiveType faceMode; switch (face.IndexCount) { case 1: faceMode = PrimitiveType.Point; break; case 2: faceMode = PrimitiveType.Line; break; case 3: faceMode = PrimitiveType.Triangle; break; default: faceMode = PrimitiveType.Polygon; break; } if (faceMode != PrimitiveType.Triangle) { continue; //throw new NotSupportedException($"Non triangle primitive types not supported at this time {faceMode}"); } for (int i = 0; i < face.IndexCount; i++) { int indicie = face.Indices[i]; GX_Vertex vertex = new GX_Vertex(); if (mesh.HasBones) { jobjList.Add(jobjs[indicie].ToArray()); wList.Add(weights[indicie].ToArray()); // Single Binds Get Inverted var tkvert = new Vector3(mesh.Vertices[indicie].X, mesh.Vertices[indicie].Y, mesh.Vertices[indicie].Z) * settings.Scale; var tknrm = new Vector3(mesh.Normals[indicie].X, mesh.Normals[indicie].Y, mesh.Normals[indicie].Z); Vector3 tktan = Vector3.Zero; Vector3 tkbitan = Vector3.Zero; if (mesh.HasTangentBasis) { tktan = new Vector3(mesh.Tangents[indicie].X, mesh.Tangents[indicie].Y, mesh.Tangents[indicie].Z); tkbitan = new Vector3(mesh.BiTangents[indicie].X, mesh.BiTangents[indicie].Y, mesh.BiTangents[indicie].Z); } if (jobjs[indicie].Count == 1 || weights[indicie][0] == 1) { tkvert = Vector3.TransformPosition(tkvert, cache.jobjToInverseTransform[jobjs[indicie][0]]); tknrm = Vector3.TransformNormal(tknrm, cache.jobjToInverseTransform[jobjs[indicie][0]]); if (mesh.HasTangentBasis) { tktan = Vector3.TransformNormal(tktan, cache.jobjToInverseTransform[jobjs[indicie][0]]); tkbitan = Vector3.TransformNormal(tkbitan, cache.jobjToInverseTransform[jobjs[indicie][0]]); } } vertex.POS = GXTranslator.fromVector3(tkvert); vertex.NRM = GXTranslator.fromVector3(tknrm); vertex.TAN = GXTranslator.fromVector3(tktan); vertex.BITAN = GXTranslator.fromVector3(tkbitan); } else { if (mesh.HasVertices) { vertex.POS = new GXVector3(mesh.Vertices[indicie].X * settings.Scale, mesh.Vertices[indicie].Y * settings.Scale, mesh.Vertices[indicie].Z * settings.Scale); } if (mesh.HasNormals) { vertex.NRM = new GXVector3(mesh.Normals[indicie].X, mesh.Normals[indicie].Y, mesh.Normals[indicie].Z); } if (mesh.HasTangentBasis) { vertex.TAN = new GXVector3(mesh.Tangents[indicie].X, mesh.Tangents[indicie].Y, mesh.Tangents[indicie].Z); vertex.BITAN = new GXVector3(mesh.BiTangents[indicie].X, mesh.BiTangents[indicie].Y, mesh.BiTangents[indicie].Z); } } if (settings.InvertNormals) { vertex.NRM.X *= -1; vertex.NRM.Y *= -1; vertex.NRM.Z *= -1; } if (mesh.HasTextureCoords(0)) { vertex.TEX0 = new GXVector2( mesh.TextureCoordinateChannels[0][indicie].X, mesh.TextureCoordinateChannels[0][indicie].Y); } if (mesh.HasTextureCoords(1)) { vertex.TEX1 = new GXVector2( mesh.TextureCoordinateChannels[1][indicie].X, mesh.TextureCoordinateChannels[1][indicie].Y); } if (mesh.HasVertexColors(0)) { vertex.CLR0 = new GXColor4( mesh.VertexColorChannels[0][indicie].R, mesh.VertexColorChannels[0][indicie].G, mesh.VertexColorChannels[0][indicie].B, settings.ImportVertexAlpha ? mesh.VertexColorChannels[0][indicie].A : 1); } if (mesh.HasVertexColors(1)) { vertex.CLR0 = new GXColor4( mesh.VertexColorChannels[1][indicie].R, mesh.VertexColorChannels[1][indicie].G, mesh.VertexColorChannels[1][indicie].B, settings.ImportVertexAlpha ? mesh.VertexColorChannels[1][indicie].A : 1); } vertices.Add(vertex); } } HSD_POBJ pobj = null; if (mesh.HasBones) { pobj = cache.POBJGen.CreatePOBJsFromTriangleList(vertices, Attributes.ToArray(), jobjList, wList); } else { pobj = cache.POBJGen.CreatePOBJsFromTriangleList(vertices, Attributes.ToArray(), null); } if (pobj != null) { if (dobj.Pobj == null) { dobj.Pobj = pobj; } else { dobj.Pobj.Add(pobj); } } } return(root); }
/// <summary> /// Recursivly processing nodes and convert data into JOBJ /// </summary> /// <param name="scene"></param> /// <param name="node"></param> /// <returns></returns> private static HSD_JOBJ RecursiveProcess(ProcessingCache cache, ModelImportSettings settings, Scene scene, Node node) { if (node.Name.EndsWith("_end")) { return(null); } // node transform's translation is bugged Vector3D tr, s; Assimp.Quaternion q; node.Transform.Decompose(out s, out q, out tr); var translation = new Vector3(tr.X, tr.Y, tr.Z); var scale = new Vector3(s.X, s.Y, s.Z); var rotation = Math3D.ToEulerAngles(new OpenTK.Quaternion(q.X, q.Y, q.Z, q.W).Inverted()); if (settings.SetScaleToOne) { scale = Vector3.One; } translation *= settings.Scale; var t = Matrix4.CreateScale(scale) * Matrix4.CreateFromQuaternion(new OpenTK.Quaternion(q.X, q.Y, q.Z, q.W)) * Matrix4.CreateTranslation(translation); HSD_JOBJ jobj = new HSD_JOBJ(); if (node.Parent != null) { t = t * cache.jobjToWorldTransform[cache.NameToJOBJ[node.Parent.Name]]; } cache.NameToJOBJ.Add(node.Name, jobj); cache.jobjToWorldTransform.Add(jobj, t); cache.jobjToInverseTransform.Add(jobj, t.Inverted()); jobj.ClassName = node.Name; jobj.Flags = JOBJ_FLAG.CLASSICAL_SCALING; jobj.TX = translation.X; jobj.TY = translation.Y; jobj.TZ = translation.Z; jobj.RX = rotation.X; jobj.RY = rotation.Y; jobj.RZ = rotation.Z; jobj.SX = scale.X; jobj.SY = scale.Y; jobj.SZ = scale.Z; if (node.HasMeshes) { cache.MeshNodes.Add(node); } foreach (var child in node.Children) { Console.WriteLine(child.Name); jobj.AddChild(RecursiveProcess(cache, settings, scene, child)); } return(jobj); }
/// <summary> /// /// </summary> /// <param name="cache"></param> /// <param name="newWorldMatrices"></param> /// <param name="root"></param> /// <param name="parentTransform"></param> private static void ZeroOutRotations(ProcessingCache cache, Dictionary <HSD_JOBJ, Matrix4> newWorldMatrices, HSD_JOBJ root, Matrix4 oldParent, Matrix4 parentTransform) { var targetPoint = Vector3.TransformPosition(Vector3.Zero, cache.jobjToWorldTransform[root]); //targetPoint -= Vector3.TransformPosition(Vector3.Zero, oldParent); var trimName = root.ClassName.Replace("Armature_", ""); if (FighterDefaults.ContainsKey(trimName)) { root.TX = 0; root.TY = 0; root.TZ = 0; root.RX = FighterDefaults[trimName].X; root.RY = FighterDefaults[trimName].Y; root.RZ = FighterDefaults[trimName].Z; root.SX = 1; root.SY = 1; root.SZ = 1; } else { root.TX = 0; root.TY = 0; root.TZ = 0; root.RX = 0; root.RY = 0; root.RZ = 0; root.SX = 1; root.SY = 1; root.SZ = 1; } Matrix4 currentTransform = Matrix4.CreateScale(root.SX, root.SY, root.SZ) * Matrix4.CreateFromQuaternion(Math3D.FromEulerAngles(root.RZ, root.RY, root.RX)) * parentTransform; var relPoint = Vector3.TransformPosition(targetPoint, parentTransform.Inverted()); root.TX = relPoint.X; root.TY = relPoint.Y; root.TZ = relPoint.Z; if (trimName.Equals("TransN")) // special case { root.TX = 0; root.TY = 0; root.TZ = 0; } var finalTransform = Matrix4.CreateScale(root.SX, root.SY, root.SZ) * Matrix4.CreateFromQuaternion(Math3D.FromEulerAngles(root.RZ, root.RY, root.RX)) * Matrix4.CreateTranslation(root.TX, root.TY, root.TZ) * parentTransform; newWorldMatrices.Add(root, finalTransform); foreach (var c in root.Children) { ZeroOutRotations(cache, newWorldMatrices, c, cache.jobjToWorldTransform[root], finalTransform); } }
/// <summary> /// Imports supported model format into Root JOBJ /// </summary> /// <param name="filePath"></param> /// <returns></returns> public static HSD_JOBJ ImportModel(string filePath, ModelImportSettings settings = null) { // settings if (settings == null) { settings = new ModelImportSettings(); } ProcessingCache cache = new ProcessingCache(); cache.FolderPath = Path.GetDirectoryName(filePath); var processFlags = PostProcessPreset.TargetRealTimeMaximumQuality | PostProcessSteps.Triangulate | PostProcessSteps.LimitBoneWeights | PostProcessSteps.FlipWindingOrder | PostProcessSteps.CalculateTangentSpace; if (settings.FlipUVs) { processFlags |= PostProcessSteps.FlipUVs; } System.Diagnostics.Debug.WriteLine("Importing Model..."); // import AssimpContext importer = new AssimpContext(); if (settings.SmoothNormals) { importer.SetConfig(new NormalSmoothingAngleConfig(66.0f)); } importer.SetConfig(new VertexBoneWeightLimitConfig(4)); var importmodel = importer.ImportFile(filePath, processFlags); System.Diagnostics.Debug.WriteLine("Processing Nodes..."); // process nodes var rootNode = importmodel.RootNode; var rootjobj = RecursiveProcess(cache, settings, importmodel, importmodel.RootNode); // Clear rotations if (settings.ZeroOutRotationsAndApplyFighterTransforms) { ZeroOutRotations(cache, rootjobj); } // get root of skeleton rootjobj = rootjobj.Child; rootjobj.Flags |= JOBJ_FLAG.SKELETON_ROOT; // no need for excess nodes //if (filePath.ToLower().EndsWith(".obj")) rootjobj.Next = null; // process mesh System.Diagnostics.Debug.WriteLine("Processing Mesh..."); foreach (var mesh in cache.MeshNodes) { var Dobj = GetMeshes(cache, settings, importmodel, mesh); if (rootjobj.Dobj == null) { rootjobj.Dobj = Dobj; } else { rootjobj.Dobj.Add(Dobj); } System.Diagnostics.Debug.WriteLine($"Processing Mesh {rootjobj.Dobj.List.Count} {cache.MeshNodes.Count + 1}..."); rootjobj.Flags |= JOBJ_FLAG.OPA; //TODO: //if (c.Flags.HasFlag(JOBJ_FLAG.OPA) || c.Flags.HasFlag(JOBJ_FLAG.ROOT_OPA)) // jobj.Flags |= JOBJ_FLAG.ROOT_OPA; if (settings.ShadingType == ShadingType.Material) { rootjobj.Flags |= JOBJ_FLAG.LIGHTING; foreach (var dobj in rootjobj.Dobj.List) { dobj.Mobj.RenderFlags |= RENDER_MODE.DIFFUSE; } } } // SKELETON if (cache.EnvelopedJOBJs.Count > 0) { rootjobj.Flags |= JOBJ_FLAG.ENVELOPE_MODEL; } foreach (var jobj in cache.EnvelopedJOBJs) { jobj.Flags |= JOBJ_FLAG.SKELETON; jobj.InverseWorldTransform = Matrix4ToHSDMatrix(cache.jobjToInverseTransform[jobj]); } if (settings.ApplyNarutoMaterials) { ApplyNarutoMaterials(rootjobj); } // SAVE POBJ buffers System.Diagnostics.Debug.WriteLine("Saving Changes..."); cache.POBJGen.SaveChanges(); System.Diagnostics.Debug.WriteLine("Done!"); // done return(rootjobj); }