public H3D ToH3D(string TextureSearchPath = null) { H3D Output = new H3D(); H3DModel Model = new H3DModel(); Model.Name = "Model"; ushort MaterialIndex = 0; if (Skeleton.Count > 0) { Model.Flags = H3DModelFlags.HasSkeleton; } Model.BoneScaling = H3DBoneScaling.Maya; Model.MeshNodesVisibility.Add(true); foreach (SMDMesh Mesh in Meshes) { Vector3 MinVector = new Vector3(); Vector3 MaxVector = new Vector3(); Dictionary <PICAVertex, int> Vertices = new Dictionary <PICAVertex, int>(); List <H3DSubMesh> SubMeshes = new List <H3DSubMesh>(); Queue <PICAVertex> VerticesQueue = new Queue <PICAVertex>(); foreach (PICAVertex Vertex in Mesh.Vertices) { VerticesQueue.Enqueue(Vertex); } while (VerticesQueue.Count > 2) { List <ushort> Indices = new List <ushort>(); List <ushort> BoneIndices = new List <ushort>(); int TriCount = VerticesQueue.Count / 3; while (TriCount-- > 0) { PICAVertex[] Triangle = new PICAVertex[3]; Triangle[0] = VerticesQueue.Dequeue(); Triangle[1] = VerticesQueue.Dequeue(); Triangle[2] = VerticesQueue.Dequeue(); List <ushort> TempIndices = new List <ushort>(); for (int Tri = 0; Tri < 3; Tri++) { PICAVertex Vertex = Triangle[Tri]; for (int i = 0; i < 4; i++) { ushort Index = (ushort)Vertex.Indices[i]; if (!(BoneIndices.Contains(Index) || TempIndices.Contains(Index))) { TempIndices.Add(Index); } } } if (BoneIndices.Count + TempIndices.Count > 20) { VerticesQueue.Enqueue(Triangle[0]); VerticesQueue.Enqueue(Triangle[1]); VerticesQueue.Enqueue(Triangle[2]); continue; } for (int Tri = 0; Tri < 3; Tri++) { PICAVertex Vertex = Triangle[Tri]; for (int Index = 0; Index < 4; Index++) { int BoneIndex = BoneIndices.IndexOf((ushort)Vertex.Indices[Index]); if (BoneIndex == -1) { BoneIndex = BoneIndices.Count; BoneIndices.Add((ushort)Vertex.Indices[Index]); } Vertex.Indices[Index] = BoneIndex; } if (Vertices.ContainsKey(Vertex)) { Indices.Add((ushort)Vertices[Vertex]); } else { Indices.Add((ushort)Vertices.Count); if (Vertex.Position.X < MinVector.X) { MinVector.X = Vertex.Position.X; } if (Vertex.Position.Y < MinVector.Y) { MinVector.Y = Vertex.Position.Y; } if (Vertex.Position.Z < MinVector.Z) { MinVector.Z = Vertex.Position.Z; } if (Vertex.Position.X > MaxVector.X) { MaxVector.X = Vertex.Position.X; } if (Vertex.Position.Y > MaxVector.Y) { MaxVector.Y = Vertex.Position.Y; } if (Vertex.Position.Z > MaxVector.Z) { MaxVector.Z = Vertex.Position.Z; } Vertices.Add(Vertex, Vertices.Count); } } } SubMeshes.Add(new H3DSubMesh() { Skinning = H3DSubMeshSkinning.Smooth, BoneIndicesCount = (ushort)BoneIndices.Count, BoneIndices = BoneIndices.ToArray(), Indices = Indices.ToArray() }); } List <PICAAttribute> Attributes = PICAAttribute.GetAttributes( PICAAttributeName.Position, PICAAttributeName.Normal, PICAAttributeName.Color, PICAAttributeName.TexCoord0, PICAAttributeName.BoneIndex, PICAAttributeName.BoneWeight); //Mesh H3DMesh M = new H3DMesh(Vertices.Keys, Attributes, SubMeshes) { Skinning = H3DMeshSkinning.Smooth, MeshCenter = (MinVector + MaxVector) * 0.5f, MaterialIndex = MaterialIndex }; //Material string TexName = Path.GetFileNameWithoutExtension(Mesh.MaterialName); string MatName = $"Mat{MaterialIndex++.ToString("D5")}_{TexName}"; H3DMaterial Material = H3DMaterial.GetSimpleMaterial(Model.Name, MatName, TexName); Model.Materials.Add(Material); if (TextureSearchPath != null && !Output.Textures.Contains(TexName)) { string TextureFile = Path.Combine(TextureSearchPath, Mesh.MaterialName); if (File.Exists(TextureFile)) { Output.Textures.Add(new H3DTexture(TextureFile)); } } M.UpdateBoolUniforms(Material); Model.AddMesh(M); } //Build Skeleton foreach (SMDBone Bone in Skeleton) { SMDNode Node = Nodes[Bone.NodeIndex]; Model.Skeleton.Add(new H3DBone() { Name = Node.Name, ParentIndex = (short)Node.ParentIndex, Translation = Bone.Translation, Rotation = Bone.Rotation, Scale = Vector3.One }); } //Calculate Absolute Inverse Transforms for all bones foreach (H3DBone Bone in Model.Skeleton) { Bone.CalculateTransform(Model.Skeleton); } Output.Models.Add(Model); Output.CopyMaterials(); return(Output); }
public H3DModel ToH3DModel() { H3DModel Output = new H3DModel() { Name = Name }; //Skeleton foreach (GFBone Bone in Skeleton) { Output.Skeleton.Add(new H3DBone() { ParentIndex = (short)Skeleton.FindIndex(x => x.Name == Bone.Parent), Name = Bone.Name, Scale = Bone.Scale, Rotation = Bone.Rotation, Translation = Bone.Translation }); } foreach (H3DBone Bone in Output.Skeleton) { Bone.CalculateTransform(Output.Skeleton); Bone.Flags |= H3DBoneFlags.IsSegmentScaleCompensate; } if (Output.Skeleton.Count > 0) { Output.Flags = H3DModelFlags.HasSkeleton; } //Materials foreach (GFMaterial Material in Materials) { H3DMaterial Mat = new H3DMaterial(); H3DMaterialParams Params = Mat.MaterialParams; Mat.Name = Material.MaterialName; Params.FragmentFlags = H3DFragmentFlags.IsLUTReflectionEnabled; Array.Copy(Material.TextureSources, Params.TextureSources, 4); for (int Unit = 0; Unit < Material.TextureCoords.Length; Unit++) { string TextureName = Material.TextureCoords[Unit].Name; Mat.EnabledTextures[Unit] = TextureName != null; switch (Unit) { case 0: Mat.Texture0Name = TextureName; break; case 1: Mat.Texture1Name = TextureName; break; case 2: Mat.Texture2Name = TextureName; break; } //Texture Coords GFTextureMappingType MappingType = Material.TextureCoords[Unit].MappingType; Params.TextureCoords[Unit].MappingType = (H3DTextureMappingType)MappingType; Params.TextureCoords[Unit].Scale = Material.TextureCoords[Unit].Scale; Params.TextureCoords[Unit].Rotation = Material.TextureCoords[Unit].Rotation; Params.TextureCoords[Unit].Translation = Material.TextureCoords[Unit].Translation; //Texture Mapper Mat.TextureMappers[Unit].WrapU = (PICATextureWrap)Material.TextureCoords[Unit].WrapU; Mat.TextureMappers[Unit].WrapV = (PICATextureWrap)Material.TextureCoords[Unit].WrapV; Mat.TextureMappers[Unit].MagFilter = (H3DTextureMagFilter)Material.TextureCoords[Unit].MagFilter; Mat.TextureMappers[Unit].MinFilter = (H3DTextureMinFilter)Material.TextureCoords[Unit].MinFilter; Mat.TextureMappers[Unit].MinLOD = (byte)Material.TextureCoords[Unit].MinLOD; Mat.TextureMappers[Unit].BorderColor = Material.BorderColor[Unit]; } Params.EmissionColor = Material.EmissionColor; Params.AmbientColor = Material.AmbientColor; Params.DiffuseColor = Material.DiffuseColor; Params.Specular0Color = Material.Specular0Color; Params.Specular1Color = Material.Specular1Color; Params.Constant0Color = Material.Constant0Color; Params.Constant1Color = Material.Constant1Color; Params.Constant2Color = Material.Constant2Color; Params.Constant3Color = Material.Constant3Color; Params.Constant4Color = Material.Constant4Color; Params.Constant5Color = Material.Constant5Color; Params.BlendColor = Material.BlendColor; //HACK: It's usually 0 on Sun/Moon, this causes issues on some //models being rendered transparent (Shader differences). Params.DiffuseColor.A = 0xff; Params.ColorScale = 1f; Params.LUTInputAbsolute = Material.LUTInputAbsolute; Params.LUTInputSelection = Material.LUTInputSelection; Params.LUTInputScale = Material.LUTInputScale; Params.ColorOperation = Material.ColorOperation; Params.BlendFunction = Material.BlendFunction; Params.LogicalOperation = Material.LogicalOperation; Params.AlphaTest = Material.AlphaTest; Params.StencilTest = Material.StencilTest; Params.StencilOperation = Material.StencilOperation; Params.DepthColorMask = Material.DepthColorMask; Params.FaceCulling = Material.FaceCulling; Params.ColorBufferRead = Material.ColorBufferRead; Params.ColorBufferWrite = Material.ColorBufferWrite; Params.StencilBufferRead = Material.StencilBufferRead; Params.StencilBufferWrite = Material.StencilBufferWrite; Params.DepthBufferRead = Material.DepthBufferRead; Params.DepthBufferWrite = Material.DepthBufferWrite; if (Material.LUT0HashId != 0) { Params.LUTReflecRTableName = DefaultLUTName; Params.LUTReflecRSamplerName = GetLUTName(Material.LUT0HashId); } if (Material.LUT1HashId != 0) { Params.LUTReflecGTableName = DefaultLUTName; Params.LUTReflecGSamplerName = GetLUTName(Material.LUT1HashId); } if (Material.LUT2HashId != 0) { Params.LUTReflecBTableName = DefaultLUTName; Params.LUTReflecBSamplerName = GetLUTName(Material.LUT2HashId); } if (Material.BumpTexture != -1) { Params.BumpTexture = (byte)Material.BumpTexture; Params.BumpMode = H3DBumpMode.AsBump; } Params.Constant0Assignment = Material.Constant0Assignment; Params.Constant1Assignment = Material.Constant1Assignment; Params.Constant2Assignment = Material.Constant2Assignment; Params.Constant3Assignment = Material.Constant3Assignment; Params.Constant4Assignment = Material.Constant4Assignment; Params.Constant5Assignment = Material.Constant5Assignment; string VtxShaderName = Material.VtxShaderName; //Make shader names match X/Y/OR/AS shader names. if (VtxShaderName == "Poke" || VtxShaderName == "PokeNormal") { VtxShaderName = "PokePack"; } Params.ShaderReference = $"0@{VtxShaderName}"; Params.ModelReference = $"{Mat.Name}@{Name}"; /* * Add those for compatibility with the older BCH models. * It's worth noting that ShaderParam0 is usually used as "UVScale" on model that uses * geometry shader to make billboarded point sprites. On the new shader it have a * multiplication of the Color by 3, while the older one doesn't have such multiplication, * so for compatibility with the older shader, the easiest thing to do is just multiply the * scale by 3 to give the same results on the old shader. */ Params.MetaData = new H3DMetaData(); Params.MetaData.Add(new H3DMetaDataValue("EdgeType", Material.EdgeType)); Params.MetaData.Add(new H3DMetaDataValue("IDEdgeEnable", Material.IDEdgeEnable)); Params.MetaData.Add(new H3DMetaDataValue("EdgeID", Material.EdgeID)); Params.MetaData.Add(new H3DMetaDataValue("ProjectionType", Material.ProjectionType)); Params.MetaData.Add(new H3DMetaDataValue("RimPow", Material.RimPower)); Params.MetaData.Add(new H3DMetaDataValue("RimScale", Material.RimScale)); Params.MetaData.Add(new H3DMetaDataValue("PhongPow", Material.PhongPower)); Params.MetaData.Add(new H3DMetaDataValue("PhongScale", Material.PhongScale)); Params.MetaData.Add(new H3DMetaDataValue("IDEdgeOffsetEnable", Material.IDEdgeOffsetEnable)); Params.MetaData.Add(new H3DMetaDataValue("EdgeMapAlphaMask", Material.EdgeMapAlphaMask)); Params.MetaData.Add(new H3DMetaDataValue("BakeTexture0", Material.BakeTexture0)); Params.MetaData.Add(new H3DMetaDataValue("BakeTexture1", Material.BakeTexture1)); Params.MetaData.Add(new H3DMetaDataValue("BakeTexture2", Material.BakeTexture2)); Params.MetaData.Add(new H3DMetaDataValue("BakeConstant0", Material.BakeConstant0)); Params.MetaData.Add(new H3DMetaDataValue("BakeConstant1", Material.BakeConstant1)); Params.MetaData.Add(new H3DMetaDataValue("BakeConstant2", Material.BakeConstant2)); Params.MetaData.Add(new H3DMetaDataValue("BakeConstant3", Material.BakeConstant3)); Params.MetaData.Add(new H3DMetaDataValue("BakeConstant4", Material.BakeConstant4)); Params.MetaData.Add(new H3DMetaDataValue("BakeConstant5", Material.BakeConstant5)); Params.MetaData.Add(new H3DMetaDataValue("VertexShaderType", Material.VertexShaderType)); Params.MetaData.Add(new H3DMetaDataValue("ShaderParam0", Material.ShaderParam0 * 3)); Params.MetaData.Add(new H3DMetaDataValue("ShaderParam1", Material.ShaderParam1)); Params.MetaData.Add(new H3DMetaDataValue("ShaderParam2", Material.ShaderParam2)); Params.MetaData.Add(new H3DMetaDataValue("ShaderParam3", Material.ShaderParam3)); Output.Materials.Add(Mat); } //Meshes Output.MeshNodesTree = new H3DPatriciaTree(); foreach (GFMesh Mesh in Meshes) { //Note: GFModel have one Vertex Buffer for each SubMesh, //while on H3D all SubMeshes shares the same Vertex Buffer. //For this reason we need to store SubMeshes as Meshes on H3D. foreach (GFSubMesh SubMesh in Mesh.SubMeshes) { int NodeIndex = Output.MeshNodesTree.Find(Mesh.Name); if (NodeIndex == -1) { Output.MeshNodesTree.Add(Mesh.Name); Output.MeshNodesVisibility.Add(true); NodeIndex = Output.MeshNodesCount++; } List <H3DSubMesh> SubMeshes = new List <H3DSubMesh>(); ushort[] BoneIndices = new ushort[SubMesh.BoneIndicesCount]; for (int Index = 0; Index < BoneIndices.Length; Index++) { BoneIndices[Index] = SubMesh.BoneIndices[Index]; } H3DSubMeshSkinning SMSk = Output.Skeleton.Count > 0 ? H3DSubMeshSkinning.Smooth : H3DSubMeshSkinning.None; SubMeshes.Add(new H3DSubMesh() { Skinning = SMSk, BoneIndicesCount = SubMesh.BoneIndicesCount, BoneIndices = BoneIndices, Indices = SubMesh.Indices }); H3DMesh M = new H3DMesh( SubMesh.RawBuffer, SubMesh.VertexStride, SubMesh.Attributes, SubMesh.FixedAttributes, SubMeshes); M.Skinning = H3DMeshSkinning.Smooth; int MatIndex = Materials.FindIndex(x => x.MaterialName == SubMesh.Name); GFMaterial Mat = Materials[MatIndex]; M.MaterialIndex = (ushort)MatIndex; M.NodeIndex = (ushort)NodeIndex; M.Layer = Mat.RenderLayer; M.Priority = Mat.RenderPriority; M.UpdateBoolUniforms(Output.Materials[MatIndex]); Output.AddMesh(M); } } return(Output); }
public H3D ToH3D() { H3D Output = new H3D(); H3DModel Model = new H3DModel(); Model.MeshNodesTree = new H3DPatriciaTree(); Model.Flags = BoneIndicesGroups.Length > 0 ? H3DModelFlags.HasSkeleton : 0; Model.Name = "Model"; foreach (MTMaterial Mat in Materials) { H3DMaterial Mtl = H3DMaterial.GetSimpleMaterial( Model.Name, Mat.Name, Path.GetFileNameWithoutExtension(Mat.Texture0Name)); Mtl.MaterialParams.ColorOperation.BlendMode = Mat.AlphaBlend.BlendMode; Mtl.MaterialParams.BlendFunction = Mat.AlphaBlend.BlendFunction; Mtl.MaterialParams.DepthColorMask.RedWrite = Mat.AlphaBlend.RedWrite; Mtl.MaterialParams.DepthColorMask.GreenWrite = Mat.AlphaBlend.GreenWrite; Mtl.MaterialParams.DepthColorMask.BlueWrite = Mat.AlphaBlend.BlueWrite; Mtl.MaterialParams.DepthColorMask.AlphaWrite = Mat.AlphaBlend.AlphaWrite; Mtl.MaterialParams.DepthColorMask.Enabled = Mat.DepthStencil.DepthTest; Mtl.MaterialParams.DepthColorMask.DepthWrite = Mat.DepthStencil.DepthWrite; Mtl.MaterialParams.DepthColorMask.DepthFunc = Mat.DepthStencil.DepthFunc; Model.Materials.Add(Mtl); } ushort Index = 0; foreach (MTMesh Mesh in Meshes) { if (Mesh.RenderType != -1) { continue; } H3DMesh M = new H3DMesh( Mesh.RawBuffer, Mesh.VertexStride, Mesh.Attributes, null, null) { MaterialIndex = (ushort)Mesh.MaterialIndex, NodeIndex = Index, Priority = Mesh.RenderPriority }; byte[] BoneIndices = BoneIndicesGroups[Mesh.BoneIndicesIndex]; if ((Model.Flags & H3DModelFlags.HasSkeleton) != 0 && BoneIndices.Length > 0) { M.Skinning = H3DMeshSkinning.Smooth; PICAVertex[] Vertices = M.GetVertices(); for (int v = 0; v < Vertices.Length; v++) { Vector4 Position = Vector4.Zero; float WeightSum = 0; for (int i = 0; i < 4; i++) { if (Vertices[v].Weights[i] == 0) { break; } WeightSum += Vertices[v].Weights[i]; int bi = BoneIndicesGroups[Mesh.BoneIndicesIndex][Vertices[v].Indices[i]]; Vector4 Trans = Vector4.Zero; for (int b = bi; b != -1; b = Skeleton[b].ParentIndex) { Trans += new Vector4( Skeleton[b].LocalTransform.M41, Skeleton[b].LocalTransform.M42, Skeleton[b].LocalTransform.M43, 0); } Matrix4x4 WT = Skeleton[bi].WorldTransform; Vector3 P = new Vector3( Vertices[v].Position.X, Vertices[v].Position.Y, Vertices[v].Position.Z); Vector4 TP = Vector4.Transform(P, WT); Position += (TP + Trans) * Vertices[v].Weights[i]; } if (WeightSum < 1) { Position += Vertices[v].Position * (1 - WeightSum); } Vertices[v].Position = Position; } /* * Removes unused bone from bone indices list, also splits sub meshes on exceeding bones if * current Mesh uses more than 20 (BCH only supports up to 20). */ Queue <ushort> IndicesQueue = new Queue <ushort>(Mesh.Indices); while (IndicesQueue.Count > 0) { int Count = IndicesQueue.Count / 3; List <ushort> Indices = new List <ushort>(); List <int> Bones = new List <int>(); while (Count-- > 0) { ushort i0 = IndicesQueue.Dequeue(); ushort i1 = IndicesQueue.Dequeue(); ushort i2 = IndicesQueue.Dequeue(); List <int> TempBones = new List <int>(12); for (int j = 0; j < 4; j++) { int b0 = Vertices[i0].Indices[j]; int b1 = Vertices[i1].Indices[j]; int b2 = Vertices[i2].Indices[j]; if (!(Bones.Contains(b0) || TempBones.Contains(b0))) { TempBones.Add(b0); } if (!(Bones.Contains(b1) || TempBones.Contains(b1))) { TempBones.Add(b1); } if (!(Bones.Contains(b2) || TempBones.Contains(b2))) { TempBones.Add(b2); } } if (Bones.Count + TempBones.Count > 20) { IndicesQueue.Enqueue(i0); IndicesQueue.Enqueue(i1); IndicesQueue.Enqueue(i2); } else { Indices.Add(i0); Indices.Add(i1); Indices.Add(i2); Bones.AddRange(TempBones); } } H3DSubMesh SM = new H3DSubMesh(); SM.Skinning = H3DSubMeshSkinning.Smooth; SM.Indices = Indices.ToArray(); SM.BoneIndicesCount = (ushort)Bones.Count; for (int i = 0; i < Bones.Count; i++) { SM.BoneIndices[i] = BoneIndices[Bones[i]]; } bool[] Visited = new bool[Vertices.Length]; foreach (ushort i in Indices) { if (!Visited[i]) { Visited[i] = true; Vertices[i].Indices[0] = Bones.IndexOf(Vertices[i].Indices[0]); Vertices[i].Indices[1] = Bones.IndexOf(Vertices[i].Indices[1]); Vertices[i].Indices[2] = Bones.IndexOf(Vertices[i].Indices[2]); Vertices[i].Indices[3] = Bones.IndexOf(Vertices[i].Indices[3]); } } M.SubMeshes.Add(SM); } M.RawBuffer = VerticesConverter.GetBuffer(Vertices, M.Attributes); } else { M.SubMeshes.Add(new H3DSubMesh() { Indices = Mesh.Indices }); } Model.AddMesh(M); Model.MeshNodesTree.Add($"Mesh_{Index++}"); Model.MeshNodesVisibility.Add(true); } int BoneIndex = 0; foreach (MTBone Bone in Skeleton) { Model.Skeleton.Add(new H3DBone() { Name = $"Bone_{BoneIndex++}", ParentIndex = Bone.ParentIndex, Translation = Bone.Position, Scale = Vector3.One }); } foreach (H3DBone Bone in Model.Skeleton) { Bone.CalculateTransform(Model.Skeleton); } if (Model.Materials.Count == 0) { Model.Materials.Add(H3DMaterial.GetSimpleMaterial(Model.Name, "DummyMaterial", null)); } Output.Models.Add(Model); Output.CopyMaterials(); return(Output); }