private static void DrawModelWireframe(STGenericPolygonGroup p, ShaderProgram shader) { // use vertex color for wireframe color shader.SetInt("colorOverride", 1); GL.PolygonMode(MaterialFace.Front, PolygonMode.Line); GL.Enable(EnableCap.LineSmooth); GL.LineWidth(1.5f); GL.DrawElements(PrimitiveType.Triangles, p.displayFaceSize, DrawElementsType.UnsignedInt, p.Offset); GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill); shader.SetInt("colorOverride", 0); }
private static void DrawModelSelection(STGenericPolygonGroup p, ShaderProgram shader) { GL.Uniform1(shader["colorOverride"], 1); GL.PolygonMode(MaterialFace.Front, PolygonMode.Line); GL.Enable(EnableCap.LineSmooth); GL.LineWidth(1.3f); GL.DrawElements(PrimitiveType.Triangles, p.displayFaceSize, DrawElementsType.UnsignedInt, p.Offset); GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill); GL.Uniform1(shader["colorOverride"], 0); GL.DrawElements(PrimitiveType.Triangles, p.displayFaceSize, DrawElementsType.UnsignedInt, p.Offset); }
private void ToGenericMeshes(bool isBigEndian) { foreach (SubMeshInfo meshInfo in SubMeshInfos) { GenericRenderedObject genericMesh = new GenericRenderedObject(); genericMesh.Text = $"Mesh_{GenericMeshes.Count}"; GenericMeshes.Add(genericMesh); STGenericPolygonGroup polyGroup = new STGenericPolygonGroup(); genericMesh.PolygonGroups.Add(polyGroup); var mat = Materials[(int)meshInfo.TextureID]; mat.Text = $"Material_{meshInfo.TextureID}"; mat.Diffuse.TextureIndex = mat.TextureIndices[0][0]; polyGroup.Material = mat; Console.WriteLine($"TextureID {meshInfo.TextureID}"); Console.WriteLine($"MaterialID {meshInfo.MaterialID}"); //Set face type if (meshInfo.IndexBufferFormat == 3) { polyGroup.PrimativeType = STPrimitiveType.Triangles; } else if (meshInfo.IndexBufferFormat == 4) { polyGroup.PrimativeType = STPrimitiveType.TrangleStrips; } //Get faces var buffer = IndexBuffers[(int)meshInfo.IndexBufferID]; var indcies = buffer.GetIndices(isBigEndian, meshInfo.IndexBufferOffset, meshInfo.IndexBufferCount).ToArray(); for (int f = 0; f < indcies.Length; f++) { polyGroup.faces.Add((int)indcies[f]); } //Get vertices genericMesh.vertices.AddRange(GetVertices((int)meshInfo.VertexBufferID, (int)meshInfo.IndexIntoJointMap, meshInfo.VertexBufferOffset, isBigEndian)); } }
private static PrimitiveType GetPrimitiveType(STGenericPolygonGroup p) { switch (p.PrimativeType) { case STPrimitiveType.Triangles: return(PrimitiveType.Triangles); case STPrimitiveType.TrangleStrips: return(PrimitiveType.TriangleStrip); case STPrimitiveType.Quads: return(PrimitiveType.Quads); case STPrimitiveType.Points: return(PrimitiveType.Points); case STPrimitiveType.LineStrips: return(PrimitiveType.LineStrip); case STPrimitiveType.Lines: return(PrimitiveType.Lines); default: return(PrimitiveType.Triangles); } }
public void ReadVertexBuffers() { Nodes.Clear(); TreeNode skeletonNode = new TreeNode("Skeleton"); for (int t = 0; t < Skeleton?.bones.Count; t++) { if (Skeleton.bones[t].Parent == null) { skeletonNode.Nodes.Add(Skeleton.bones[t]); } } if (skeletonNode.Nodes.Count > 0) { Nodes.Add(skeletonNode); } using (var reader = new FileReader(DataDictionary.GetFile003Data())) { for (int i = 0; i < VertexBufferPointers.Count; i++) { LM2_Mesh mesh = Meshes[i]; RenderableMeshWrapper genericObj = new RenderableMeshWrapper(); genericObj.Mesh = mesh; genericObj.Text = $"Mesh {i}"; genericObj.SetMaterial(mesh.Material); RenderedMeshes.Add(genericObj); Nodes.Add(genericObj); DataDictionary.Renderer.Meshes.Add(genericObj); STGenericPolygonGroup polyGroup = new STGenericPolygonGroup(); genericObj.PolygonGroups.Add(polyGroup); using (reader.TemporarySeek(BufferStart + VertexBufferPointers[i], System.IO.SeekOrigin.Begin)) { var bufferNodeDebug = new DebugVisualBytes(reader.ReadBytes((int)80 * mesh.VertexCount)); bufferNodeDebug.Text = $"Buffer {mesh.DataFormat.ToString("x")}"; genericObj.Nodes.Add(bufferNodeDebug); } if (!LM2_Mesh.FormatInfos.ContainsKey(mesh.DataFormat)) { Console.WriteLine($"Unsupported data format! " + mesh.DataFormat.ToString("x")); continue; } else { var formatInfo = LM2_Mesh.FormatInfos[mesh.DataFormat]; if (formatInfo.BufferLength > 0) { reader.BaseStream.Position = BufferStart + mesh.IndexStartOffset; switch (mesh.IndexFormat) { case IndexFormat.Index_8: for (int f = 0; f < mesh.IndexCount; f++) { polyGroup.faces.Add(reader.ReadByte()); } break; case IndexFormat.Index_16: for (int f = 0; f < mesh.IndexCount; f++) { polyGroup.faces.Add(reader.ReadUInt16()); } break; } Console.WriteLine($"Mesh {genericObj.Text} Format {formatInfo.Format} BufferLength {formatInfo.BufferLength}"); uint bufferOffet = BufferStart + VertexBufferPointers[i]; /* for (int v = 0; v < mesh.VertexCount; v++) * { * reader.SeekBegin(bufferOffet + (v * formatInfo.BufferLength)); * * }*/ switch (formatInfo.Format) { case VertexDataFormat.Float16: for (int v = 0; v < mesh.VertexCount; v++) { reader.SeekBegin(bufferOffet + (v * formatInfo.BufferLength)); Vertex vert = new Vertex(); genericObj.vertices.Add(vert); vert.pos = new Vector3( UShortToFloatDecode(reader.ReadInt16()), UShortToFloatDecode(reader.ReadInt16()), UShortToFloatDecode(reader.ReadInt16())); Vector4 nrm = Read_8_8_8_8_Snorm(reader); vert.nrm = nrm.Xyz.Normalized(); vert.pos = Vector3.TransformPosition(vert.pos, mesh.Transform); vert.uv0 = NormalizeUvCoordsToFloat(reader.ReadUInt16(), reader.ReadUInt16()); if (formatInfo.BufferLength == 22) { Console.WriteLine("unk 1 " + reader.ReadUInt16()); Console.WriteLine("unk 2 " + reader.ReadUInt16()); Console.WriteLine("unk 3 " + reader.ReadUInt16()); Console.WriteLine("unk 4 " + reader.ReadUInt16()); } } break; case VertexDataFormat.Float32: for (int v = 0; v < mesh.VertexCount; v++) { reader.SeekBegin(bufferOffet + (v * formatInfo.BufferLength)); Vertex vert = new Vertex(); genericObj.vertices.Add(vert); vert.pos = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); vert.pos = Vector3.TransformPosition(vert.pos, mesh.Transform); } break; case VertexDataFormat.Float32_32: reader.BaseStream.Position = BufferStart + VertexBufferPointers[i] + 0x08; for (int v = 0; v < mesh.VertexCount; v++) { reader.SeekBegin(bufferOffet + (v * formatInfo.BufferLength)); Vertex vert = new Vertex(); genericObj.vertices.Add(vert); vert.pos = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); vert.pos = Vector3.TransformPosition(vert.pos, mesh.Transform); vert.uv0 = NormalizeUvCoordsToFloat(reader.ReadUInt16(), reader.ReadUInt16()); vert.uv1 = NormalizeUvCoordsToFloat(reader.ReadUInt16(), reader.ReadUInt16()); vert.col = Read_8_8_8_8_Unorm(reader); } break; case VertexDataFormat.Float32_32_32: for (int v = 0; v < mesh.VertexCount; v++) { reader.SeekBegin(bufferOffet + (v * formatInfo.BufferLength)); Vertex vert = new Vertex(); genericObj.vertices.Add(vert); vert.pos = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); vert.pos = Vector3.TransformPosition(vert.pos, mesh.Transform); Vector4 nrm = Read_8_8_8_8_Snorm(reader); vert.nrm = nrm.Xyz.Normalized(); vert.uv0 = NormalizeUvCoordsToFloat(reader.ReadUInt16(), reader.ReadUInt16()); vert.uv1 = NormalizeUvCoordsToFloat(reader.ReadUInt16(), reader.ReadUInt16()); if (formatInfo.BufferLength >= 0x1C) { vert.col = Read_8_8_8_8_Unorm(reader); } } break; } genericObj.TransformPosition(new Vector3(0), new Vector3(-90, 0, 0), new Vector3(1)); } } genericObj.RemoveDuplicateVertices(); } } }
private void LoadBMD(Model model) { Nodes.Clear(); ShapeFolder = new TreeNode("Shapes"); SkeletonFolder = new TreeNode("Skeleton"); MaterialFolder = new TreeNode("Materials"); TextureFolder = new BMDTextureFolder("Textures"); Nodes.Add(ShapeFolder); Nodes.Add(MaterialFolder); Nodes.Add(SkeletonFolder); Nodes.Add(TextureFolder); BMDFile = model; FillSkeleton(BMDFile.Scenegraph, Skeleton, BMDFile.Joints.FlatSkeleton); foreach (var bone in Skeleton.bones) { if (bone.Parent == null) { SkeletonFolder.Nodes.Add(bone); } } for (int i = 0; i < BMDFile.Shapes.Shapes.Count; i++) { var curShape = BMDFile.Shapes.Shapes[i]; var mat = new BMDMaterialWrapper(BMDFile.Materials.GetMaterial(i), BMDFile); MaterialFolder.Nodes.Add(mat); var shpWrapper = new BMDShapeWrapper(curShape, BMDFile, mat); shpWrapper.Text = $"Shape {i}"; ShapeFolder.Nodes.Add(shpWrapper); Renderer.Meshes.Add(shpWrapper); var polyGroup = new STGenericPolygonGroup(); shpWrapper.PolygonGroups.Add(polyGroup); var VertexAttributes = BMDFile.VertexData.Attributes; int vertexID = 0; int packetID = 0; foreach (var att in curShape.Descriptor.Attributes) { shpWrapper.Nodes.Add($"Attribute {att.Key} {att.Value.Item1}"); } foreach (SuperBMDLib.Geometry.Packet pack in curShape.Packets) { int primID = 0; foreach (SuperBMDLib.Geometry.Primitive prim in pack.Primitives) { List <SuperBMDLib.Geometry.Vertex> triVertices = J3DUtility.PrimitiveToTriangles(prim); for (int triIndex = 0; triIndex < triVertices.Count; triIndex += 3) { polyGroup.faces.AddRange(new int[] { vertexID + 2, vertexID + 1, vertexID }); for (int triVertIndex = 0; triVertIndex < 3; triVertIndex++) { SuperBMDLib.Geometry.Vertex vert = triVertices[triIndex + triVertIndex]; Vertex vertex = new Vertex(); vertex.pos = VertexAttributes.Positions[(int)vert.GetAttributeIndex(GXVertexAttribute.Position)]; shpWrapper.vertices.Add(vertex); if (curShape.Descriptor.CheckAttribute(GXVertexAttribute.Normal)) { vertex.nrm = VertexAttributes.Normals[(int)vert.NormalIndex]; } if (curShape.Descriptor.CheckAttribute(GXVertexAttribute.Color0)) { var color0 = VertexAttributes.Color_0[(int)vert.Color0Index]; vertex.col = new OpenTK.Vector4(color0.R, color0.G, color0.B, color0.A); } for (int j = 0; j < vert.VertexWeight.WeightCount; j++) { vertex.boneWeights.Add(vert.VertexWeight.Weights[j]); vertex.boneIds.Add(vert.VertexWeight.BoneIndices[j]); } if (vert.VertexWeight.WeightCount == 1) { if (BMDFile.SkinningEnvelopes.InverseBindMatrices.Count > vert.VertexWeight.BoneIndices[0]) { Matrix4 test = BMDFile.SkinningEnvelopes.InverseBindMatrices[vert.VertexWeight.BoneIndices[0]].Inverted(); test.Transpose(); vertex.pos = OpenTK.Vector3.TransformPosition(vertex.pos, test); vertex.nrm = OpenTK.Vector3.TransformNormal(vertex.nrm, test); } else { vertex.pos = OpenTK.Vector3.TransformPosition(vertex.pos, BMDFile.Joints.FlatSkeleton[vert.VertexWeight.BoneIndices[0]].TransformationMatrix); vertex.nrm = OpenTK.Vector3.TransformNormal(vertex.nrm, BMDFile.Joints.FlatSkeleton[vert.VertexWeight.BoneIndices[0]].TransformationMatrix); } } for (int texCoordNum = 0; texCoordNum < 8; texCoordNum++) { if (curShape.Descriptor.CheckAttribute(GXVertexAttribute.Tex0 + texCoordNum)) { switch (texCoordNum) { case 0: vertex.uv0 = VertexAttributes.TexCoord_0[(int)vert.TexCoord0Index]; break; case 1: vertex.uv1 = VertexAttributes.TexCoord_0[(int)vert.TexCoord0Index]; break; case 2: vertex.uv2 = VertexAttributes.TexCoord_0[(int)vert.TexCoord0Index]; break; } } } vertexID++; } } primID++; } packetID++; } } CorrectMaterialIndices(Renderer.Meshes, BMDFile.Scenegraph, BMDFile.Materials); for (int i = 0; i < BMDFile.Textures.Textures.Count; i++) { var texWrapper = new BMDTextureWrapper(BMDFile.Textures.Textures[i]); TextureFolder.Nodes.Add(texWrapper); Renderer.TextureList.Add(texWrapper); } }
public void Load(System.IO.Stream stream) { CanSave = true; Text = FileName; //Set renderer //Load it to a drawables list Renderer = new MDL_Renderer(); DrawableContainer.Name = FileName; DrawableContainer.Drawables.Add(Renderer); using (var reader = new FileReader(stream)) { reader.SetByteOrder(true); while (reader.EndOfStream == false) { long chunkStart = reader.Position; int opcode = reader.ReadInt32(); int lengthOfStruct = reader.ReadInt32(); // basic error checking if ((chunkStart & 0x1F) != 0) { throw new Exception($"Chunk start ({chunkStart}) not on boundary!"); } switch (opcode) { case 0x10: // VERTICES int vertexCount = reader.ReadInt32(); Vertices = new Vertex[vertexCount]; SkipPadding(reader, 0x20); for (int i = 0; i < vertexCount; i++) { float x = reader.ReadSingle(); float y = reader.ReadSingle(); float z = reader.ReadSingle(); Vertices[i] = new Vertex { pos = new Vector3(x, y, z) }; } SkipPadding(reader, 0x20); break; case 0x11: int vertexNormalCount = reader.ReadInt32(); VertexNormals = new Vertex[vertexNormalCount]; SkipPadding(reader, 0x20); for (int i = 0; i < vertexNormalCount; i++) { float x = reader.ReadSingle(); float y = reader.ReadSingle(); float z = reader.ReadSingle(); VertexNormals[i] = new Vertex { nrm = new Vector3(x, y, z) }; } SkipPadding(reader, 0x20); break; case 0x13: // COLOURS int colorCount = reader.ReadInt32(); Colors = new Vertex[colorCount]; SkipPadding(reader, 0x20); for (int i = 0; i < colorCount; i++) { byte x = reader.ReadByte(); byte y = reader.ReadByte(); byte z = reader.ReadByte(); byte w = reader.ReadByte(); Colors[i] = new Vertex { col = new Vector4(x, y, z, w) }; } SkipPadding(reader, 0x20); break; case 0x50: int meshCount = reader.ReadInt32(); SkipPadding(reader, 0x20); for (int i = 0; i < meshCount; i++) { //Create a renderable object for our mesh var renderedMesh = new GenericRenderedObject { Checked = true, ImageKey = "mesh", SelectedImageKey = "mesh", Text = $"Mesh {i}" }; Nodes.Add(renderedMesh); Renderer.Meshes.Add(renderedMesh); STGenericPolygonGroup polyGroup = new STGenericPolygonGroup(); renderedMesh.PolygonGroups.Add(polyGroup); reader.ReadInt32(); int vtxDescriptor = reader.ReadInt32(); int mtxGroupCount = reader.ReadInt32(); Console.WriteLine("mtxGroupCount " + mtxGroupCount); for (int a = 0; a < mtxGroupCount; a++) { int unkCount = reader.ReadInt32(); for (int unkIter = 0; unkIter < unkCount; unkIter++) { reader.ReadInt16(); } int dispListCount = reader.ReadInt32(); Console.WriteLine("dispListCount " + dispListCount); for (int b = 0; b < dispListCount; b++) { reader.ReadInt32(); reader.ReadInt32(); int displacementSize = reader.ReadInt32(); SkipPadding(reader, 0x20); long end_displist = reader.Position + displacementSize; Console.WriteLine("end_displist " + end_displist); Console.WriteLine("displacementSize " + displacementSize); Console.WriteLine("reader.Position " + reader.Position); while (reader.Position < end_displist) { byte faceOpCode = reader.ReadByte(); if (faceOpCode == 0x98 || faceOpCode == 0xA0) { short vCount = reader.ReadInt16(); int[] polys = new int[vCount]; for (int vc = 0; vc < vCount; vc++) { if ((vtxDescriptor & 0x1) == 0x1) { reader.ReadByte(); // Position Matrix } if ((vtxDescriptor & 0x2) == 0x2) { reader.ReadByte(); // tex1 matrix } ushort vtxPosIndex = reader.ReadUInt16(); uint normalID = 0; if (VertexNormals.Length > 0) { normalID = reader.ReadUInt16(); } uint colorID = 0; if ((vtxDescriptor & 0x4) == 0x4) { colorID = reader.ReadUInt16(); } int tmpVar = vtxDescriptor >> 3; uint texCoordID = 0; for (int c = 0; c < 8; c++) { if ((tmpVar & 0x1) == 0x1) { if (c == 0) { texCoordID = reader.ReadUInt16(); } } tmpVar >>= 1; } Vertex vert = new Vertex { pos = Vertices[vtxPosIndex].pos, nrm = VertexNormals[normalID].nrm, //col = Colors[colorID].col }; polys[vc] = renderedMesh.vertices.Count; renderedMesh.vertices.Add(vert); } List <Triangle> curPolys = ToTris(polys, faceOpCode); foreach (Triangle poly in curPolys) { Console.WriteLine($"{poly.A} {poly.B} {poly.C}"); polyGroup.faces.Add(poly.A); polyGroup.faces.Add(poly.B); polyGroup.faces.Add(poly.C); } } } } } Console.WriteLine("vertices " + renderedMesh.vertices.Count); Console.WriteLine("faces " + renderedMesh.PolygonGroups[0].faces.Count); Console.WriteLine("Vertices " + Vertices.Length); } break; default: reader.Seek(lengthOfStruct, System.IO.SeekOrigin.Current); break; } } } }
public GFBMaterial GetMaterial(STGenericPolygonGroup polygroup) { return(ParentModel.header.GenericMaterials[polygroup.MaterialIndex]); }
public void Load(System.IO.Stream stream) { DrawableContainer.Name = FileName; Renderer = new PunchOutWii_Renderer(); DrawableContainer.Drawables.Add(Renderer); Text = FileName; HeaderFile = new DictionaryFile(); HeaderFile.Read(new FileReader(stream), FilePath); var HashList = NLG_Common.HashNames; string DataFile = $"{FilePath.Replace(".dict", ".data")}"; if (System.IO.File.Exists(DataFile)) { using (var reader = new FileReader(DataFile, true)) { reader.SetByteOrder(true); TreeNode blocks = new TreeNode("Blocks"); TreeNode chunks = new TreeNode("Chunks"); TreeNode modelFolder = new TreeNode("Models"); foreach (var blockInfo in HeaderFile.Blocks) { ChunkViewer chunkNode = new ChunkViewer("block"); if (blockInfo.Size > 0) { blocks.Nodes.Add(chunkNode); } chunkNode.FileData = new SubStream(reader.BaseStream, blockInfo.Offset, blockInfo.Size); } List <PO_Texture> currentTextures = new List <PO_Texture>(); List <ModelFileData> modelData = new List <ModelFileData>(); ModelFileData currentModel = null; STTextureFolder textureFolder = new STTextureFolder("Textures"); Nodes.Add(blocks); Nodes.Add(chunks); Nodes.Add(textureFolder); Nodes.Add(modelFolder); foreach (var chunk in HeaderFile.DataChunks) { if (chunk.BlockIndex == -1) { continue; } ChunkViewer chunkNode = new ChunkViewer(chunk.Type.ToString("") + " " + chunk.Type.ToString("X")); chunks.Nodes.Add(chunkNode); var blockInfo = HeaderFile.Blocks[chunk.BlockIndex]; if (blockInfo.Offset + chunk.Offset + chunk.Size > reader.BaseStream.Length) { continue; } chunkNode.FileData = new SubStream(reader.BaseStream, blockInfo.Offset + chunk.Offset, chunk.Size); uint chunkPos = blockInfo.Offset + chunk.Offset; reader.SeekBegin(chunkPos); switch (chunk.Type) { case SectionMagic.MaterialData: currentModel = new ModelFileData(); currentModel.MaterialOffset = chunkPos; modelData.Add(currentModel); break; case SectionMagic.TextureHeaders: uint numTextures = chunk.Size / 96; for (int i = 0; i < numTextures; i++) { var tex = new PO_Texture(); tex.ImageKey = "texture"; tex.SelectedImageKey = "texture"; tex.Read(reader); tex.Text = tex.HashID.ToString("X"); if (HashList.ContainsKey(tex.HashID)) { tex.Text = HashList[tex.HashID]; } currentTextures.Add(tex); Renderer.TextureList.Add(tex.Text, tex); textureFolder.Nodes.Add(tex); } break; case SectionMagic.TextureData: for (int i = 0; i < currentTextures.Count; i++) { reader.SeekBegin(chunkPos + currentTextures[i].DataOffset); currentTextures[i].ImageData = reader.ReadBytes((int)currentTextures[i].ImageSize); } break; case SectionMagic.IndexData: currentModel.indexBufferOffset = chunkPos; break; case SectionMagic.VertexData: currentModel.vertexBufferOffset = chunkPos; break; case SectionMagic.MeshData: uint numMeshes = chunk.Size / 52; for (int i = 0; i < numMeshes; i++) { reader.SeekBegin(chunkPos + (i * 52)); PO_Mesh mesh = new PO_Mesh(reader); currentModel.meshes.Add(mesh); } break; case SectionMagic.VertexAttributePointerData: uint numAttributes = chunk.Size / 8; for (int i = 0; i < numAttributes; i++) { PO_VertexAttribute att = new PO_VertexAttribute(); att.Offset = reader.ReadUInt32(); att.Type = reader.ReadByte(); att.Stride = reader.ReadByte(); reader.ReadUInt16(); currentModel.attributes.Add(att); } break; case SectionMagic.ModelData: uint numModels = chunk.Size / 12; Console.WriteLine($"numModels {numModels}"); for (int i = 0; i < numModels; i++) { PO_Model mdl = new PO_Model(); mdl.ParentDictionary = this; mdl.HashID = reader.ReadUInt32(); mdl.NumMeshes = reader.ReadUInt32(); reader.ReadUInt32(); //0 currentModel.models.Add(mdl); } break; case SectionMagic.BoneData: STSkeleton Skeleton = new STSkeleton(); DrawableContainer.Drawables.Add(Skeleton); uint numBones = chunk.Size / 68; for (int i = 0; i < numBones; i++) { reader.SeekBegin(chunkPos + (i * 68)); uint HashID = reader.ReadUInt32(); reader.ReadUInt32(); //unk reader.ReadUInt32(); //unk reader.ReadUInt32(); //unk reader.ReadSingle(); //0 STBone bone = new STBone(Skeleton); var Scale = new OpenTK.Vector3( reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); reader.ReadSingle(); //0 bone.EulerRotation = new OpenTK.Vector3( reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); reader.ReadSingle(); //0 bone.Position = new OpenTK.Vector3( reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); reader.ReadSingle(); //1 bone.Text = HashID.ToString("X"); if (NLG_Common.HashNames.ContainsKey(HashID)) { bone.Text = NLG_Common.HashNames[HashID]; } else { Console.WriteLine($"bone hash {HashID}"); } bone.Scale = new Vector3(0.2f, 0.2f, 0.2f); bone.RotationType = STBone.BoneRotationType.Euler; Skeleton.bones.Add(bone); } Skeleton.reset(); Skeleton.update(); break; } } foreach (var modelFile in modelData) { int pointerIndex = 0; foreach (var model in modelFile.models) { model.Text = model.HashID.ToString("X"); if (HashList.ContainsKey(model.HashID)) { model.Text = HashList[model.HashID]; } modelFolder.Nodes.Add(model); for (int i = 0; i < model.NumMeshes; i++) { var mesh = modelFile.meshes[i]; RenderableMeshWrapper genericMesh = new RenderableMeshWrapper(); model.Nodes.Add(genericMesh); model.RenderedMeshes.Add(genericMesh); Renderer.Meshes.Add(genericMesh); genericMesh.Text = mesh.HashID.ToString("X"); if (HashList.ContainsKey(mesh.HashID)) { genericMesh.Text = HashList[mesh.HashID]; } string material = mesh.MaterialHashID.ToString("X"); if (HashList.ContainsKey(mesh.MaterialHashID)) { material = HashList[mesh.MaterialHashID]; } genericMesh.Nodes.Add(material); genericMesh.Material = new STGenericMaterial(); reader.SeekBegin(modelFile.MaterialOffset + mesh.MaterialOffset); switch (mesh.MaterailPreset) { case MaterailPresets.EnvDiffuseDamage: { uint diffuseMapHashID = reader.ReadUInt32(); uint diffuseMapParam = reader.ReadUInt32(); uint envSpecMapHashID = reader.ReadUInt32(); uint envSpecMapParam = reader.ReadUInt32(); uint specMapHashID = reader.ReadUInt32(); uint specMapParam = reader.ReadUInt32(); uint megaStrikeMapHashID = reader.ReadUInt32(); uint megaStrikeMapParam = reader.ReadUInt32(); uint dirtMapHashID = reader.ReadUInt32(); uint dirtMapParam = reader.ReadUInt32(); uint iceMapHashID = reader.ReadUInt32(); uint iceMapParam = reader.ReadUInt32(); string diffuseName = diffuseMapHashID.ToString("X"); if (HashList.ContainsKey(diffuseMapHashID)) { diffuseName = HashList[diffuseMapHashID]; } var texUnit = 1; genericMesh.Material.TextureMaps.Add(new STGenericMatTexture() { textureUnit = texUnit++, Type = STGenericMatTexture.TextureType.Diffuse, Name = diffuseName, }); } break; default: { uint diffuseMapHashID = reader.ReadUInt32(); string diffuseName = diffuseMapHashID.ToString("X"); if (HashList.ContainsKey(diffuseMapHashID)) { diffuseName = HashList[diffuseMapHashID]; } Console.WriteLine($"diffuseName {diffuseName}"); var texUnit = 1; genericMesh.Material.TextureMaps.Add(new STGenericMatTexture() { textureUnit = texUnit++, Type = STGenericMatTexture.TextureType.Diffuse, Name = diffuseName, }); } break; } Console.WriteLine($"mesh {i}"); STGenericPolygonGroup polyGroup = new STGenericPolygonGroup(); genericMesh.PolygonGroups.Add(polyGroup); reader.SeekBegin(modelFile.indexBufferOffset + mesh.IndexStartOffset); List <int> faces = new List <int>(); for (int f = 0; f < mesh.IndexCount; f++) { if (mesh.IndexFormat == 0) { polyGroup.faces.Add(reader.ReadUInt16()); } else { polyGroup.faces.Add(reader.ReadByte()); } } if (mesh.FaceType == PO_Mesh.PolygonType.TriangleStrips) { polyGroup.PrimativeType = STPrimitiveType.TrangleStrips; } else { polyGroup.PrimativeType = STPrimitiveType.Triangles; } for (int a = 0; a < mesh.NumAttributePointers; a++) { Console.WriteLine($"pointer {genericMesh.Text} { modelFile.vertexBufferOffset + modelFile.attributes[pointerIndex + a].Offset}"); } for (int v = 0; v < mesh.VertexCount; v++) { Vertex vert = new Vertex(); genericMesh.vertices.Add(vert); int attributeIndex = 0; for (int a = 0; a < mesh.NumAttributePointers; a++) { var pointer = modelFile.attributes[pointerIndex + a]; reader.SeekBegin(modelFile.vertexBufferOffset + pointer.Offset + (pointer.Stride * v)); if (attributeIndex == 0) { if (pointer.Stride == 12) { vert.pos = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); } } if (attributeIndex == 1) { if (pointer.Stride == 12) { vert.nrm = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); } } if (attributeIndex == 2) { if (pointer.Stride == 4) { vert.uv0 = new Vector2(reader.ReadUInt16() / 1024f, reader.ReadUInt16() / 1024f); } } /* if (pointer.Type == 0xD4) * { * vert.boneIds = new List<int>() { reader.ReadByte(), reader.ReadByte(), reader.ReadByte(), reader.ReadByte() }; * } * if (pointer.Type == 0xB0) * { * vert.boneWeights = new List<float>() { reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle() }; * }*/ attributeIndex++; } } genericMesh.TransformPosition(new Vector3(0), new Vector3(-90, 0, 0), new Vector3(1)); pointerIndex += mesh.NumAttributePointers; } } } } } }
public void ReadVertexBuffers() { Nodes.Clear(); using (var reader = new FileReader(DataDictionary.GetFileBufferData(), true)) { TreeNode texturesList = new TreeNode("Texture Maps"); TreeNode skeletonNode = new TreeNode("Skeleton"); for (int t = 0; t < Skeleton?.bones.Count; t++) { skeletonNode.Nodes.Add(Skeleton.bones[t]); } for (int t = 0; t < TextureHashes.Count; t++) { if (DataDictionary.Renderer.TextureList.ContainsKey(TextureHashes[t].ToString("x"))) { var tex = DataDictionary.Renderer.TextureList[TextureHashes[t].ToString("x")]; texturesList.Nodes.Add(new TreeNode(tex.Text) { ImageKey = tex.ImageKey, SelectedImageKey = tex.ImageKey, Tag = tex }); } else { Nodes.Add(TextureHashes[t].ToString("x")); } } if (skeletonNode.Nodes.Count > 0) { Nodes.Add(skeletonNode); } if (texturesList.Nodes.Count > 0) { Nodes.Add(texturesList); } for (int i = 0; i < Meshes.Count; i++) { LM3_Mesh mesh = Meshes[i]; RenderableMeshWrapper genericObj = new RenderableMeshWrapper(); genericObj.Mesh = mesh; genericObj.Checked = true; genericObj.Text = $"Mesh {i} {mesh.HashID.ToString("X")}"; if (NLG_Common.HashNames.ContainsKey(mesh.HashID)) { genericObj.Text = NLG_Common.HashNames[mesh.HashID]; } genericObj.SetMaterial(mesh.Material); RenderedMeshes.Add(genericObj); Nodes.Add(genericObj); DataDictionary.Renderer.Meshes.Add(genericObj); STGenericPolygonGroup polyGroup = new STGenericPolygonGroup(); genericObj.PolygonGroups.Add(polyGroup); uint vertexBufferPointer = VertexBufferPointers[i].VertexBufferPointer; uint weightTablePointer = VertexBufferPointers[i].WeightTablePointer; using (reader.TemporarySeek(BufferStart + vertexBufferPointer, System.IO.SeekOrigin.Begin)) { var bufferNodeDebug = new DebugVisualBytes(reader.ReadBytes((int)80 * (int)mesh.VertexCount)); bufferNodeDebug.Text = $"Buffer {mesh.DataFormat.ToString("x")}"; genericObj.Nodes.Add(bufferNodeDebug); } LM3_Mesh.FormatInfo formatInfo; if (!LM3_Mesh.FormatInfos.ContainsKey(mesh.DataFormat)) { Console.WriteLine($"Unsupported data format! " + mesh.DataFormat.ToString("x")); formatInfo = new LM3_Mesh.FormatInfo(VertexDataFormat.Float32_32_32, 0x30); // continue; } else { formatInfo = LM3_Mesh.FormatInfos[mesh.DataFormat]; } if (formatInfo.BufferLength > 0) { Console.WriteLine($"BufferStart {BufferStart} IndexStartOffset {mesh.IndexStartOffset}"); reader.BaseStream.Position = BufferStart + mesh.IndexStartOffset; switch (mesh.IndexFormat) { case IndexFormat.Index_8: for (int f = 0; f < mesh.IndexCount; f++) { polyGroup.faces.Add(reader.ReadByte()); } break; case IndexFormat.Index_16: for (int f = 0; f < mesh.IndexCount; f++) { polyGroup.faces.Add(reader.ReadUInt16()); } break; /* case IndexFormat.Index_32: * for (int f = 0; f < mesh.IndexCount; f++) * polyGroup.faces.Add((int)reader.ReadUInt32()); * break;*/ } Console.WriteLine($"Mesh {genericObj.Text} Format {formatInfo.Format} BufferLength {formatInfo.BufferLength}"); Console.WriteLine($"BufferStart {BufferStart} VertexBufferPointers {vertexBufferPointer}"); uint bufferOffet = BufferStart + vertexBufferPointer; /* for (int v = 0; v < mesh.VertexCount; v++) * { * reader.SeekBegin(bufferOffet + (v * formatInfo.BufferLength)); * * }*/ switch (formatInfo.Format) { case VertexDataFormat.Float16: for (int v = 0; v < mesh.VertexCount; v++) { reader.SeekBegin(bufferOffet + (v * formatInfo.BufferLength)); Vertex vert = new Vertex(); genericObj.vertices.Add(vert); vert.pos = new Vector3( UShortToFloatDecode(reader.ReadInt16()), UShortToFloatDecode(reader.ReadInt16()), UShortToFloatDecode(reader.ReadInt16())); Vector4 nrm = Read_8_8_8_8_Snorm(reader); vert.nrm = nrm.Xyz.Normalized(); vert.pos = Vector3.TransformPosition(vert.pos, mesh.Transform); vert.uv0 = NormalizeUvCoordsToFloat(reader.ReadUInt16(), reader.ReadUInt16()); if (formatInfo.BufferLength == 22) { Console.WriteLine("unk 1 " + reader.ReadUInt16()); Console.WriteLine("unk 2 " + reader.ReadUInt16()); Console.WriteLine("unk 3 " + reader.ReadUInt16()); Console.WriteLine("unk 4 " + reader.ReadUInt16()); } } break; case VertexDataFormat.Float32: for (int v = 0; v < mesh.VertexCount; v++) { reader.SeekBegin(bufferOffet + (v * formatInfo.BufferLength)); Vertex vert = new Vertex(); genericObj.vertices.Add(vert); vert.pos = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); vert.pos = Vector3.TransformPosition(vert.pos, mesh.Transform); } break; case VertexDataFormat.Float32_32: reader.BaseStream.Position = BufferStart + vertexBufferPointer + 0x08; for (int v = 0; v < mesh.VertexCount; v++) { reader.SeekBegin(bufferOffet + (v * formatInfo.BufferLength)); Vertex vert = new Vertex(); genericObj.vertices.Add(vert); vert.pos = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); vert.pos = Vector3.TransformPosition(vert.pos, mesh.Transform); vert.uv0 = NormalizeUvCoordsToFloat(reader.ReadUInt16(), reader.ReadUInt16()); vert.uv1 = NormalizeUvCoordsToFloat(reader.ReadUInt16(), reader.ReadUInt16()); vert.col = Read_8_8_8_8_Unorm(reader); } break; case VertexDataFormat.Float32_32_32: for (int v = 0; v < mesh.VertexCount; v++) { reader.SeekBegin(bufferOffet + (v * formatInfo.BufferLength)); Vertex vert = new Vertex(); genericObj.vertices.Add(vert); vert.pos = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); vert.pos = Vector3.TransformPosition(vert.pos, mesh.Transform); //Texture coordinates are stored between normals, WHY NLG var texCoordU = reader.ReadSingle(); vert.nrm = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); var texCoordV = reader.ReadSingle(); vert.tan = new Vector4(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); vert.uv0 = new Vector2(texCoordU, texCoordV); } break; } genericObj.TransformPosition(new Vector3(0), new Vector3(-90, 0, 0), new Vector3(1)); } if (weightTablePointer != uint.MaxValue) { using (reader.TemporarySeek(BufferStart + weightTablePointer, System.IO.SeekOrigin.Begin)) { byte maxIndex = 0; for (int v = 0; v < genericObj.vertices.Count; v++) { byte[] boneIndices = reader.ReadBytes(4); float[] boneWeights = reader.ReadSingles(4); for (int j = 0; j < 4; j++) { maxIndex = Math.Max(maxIndex, boneIndices[j]); // genericObj.vertices[v].boneIds.Add(boneIndices[j]); // genericObj.vertices[v].boneWeights.Add(boneWeights[j]); } } Console.WriteLine("maxIndex " + maxIndex); } } genericObj.RemoveDuplicateVertices(); } } }
public GFLXMaterialData GetMaterial(STGenericPolygonGroup polygroup) { return(ParentModel.GenericMaterials[polygroup.MaterialIndex]); }
/// <summary> /// Computes mesh and bone cloth drivers /// </summary> public void ComputeClothDrivers() { var boneList = G1MSkeleton.GenericSkeleton.bones; var nunProps = new List <NUNO.NUNOType0303Struct>(); uint nunoOffset = 0; if (NUNO != null) { nunoOffset = (uint)NUNO.NUNO0303StructList.Count; foreach (var nuno0303 in NUNO.NUNO0303StructList) { nunProps.Add(nuno0303); } } if (NUNV != null) { foreach (var nuno0303 in NUNV.NUNV0303StructList) { nunProps.Add(nuno0303); } } foreach (var prop in nunProps) { int boneStart = boneList.Count; var parentBone = Model.JointInfos[prop.BoneParentID - 1].JointIndices[0]; GenericRenderedObject mesh = new GenericRenderedObject(); mesh.Text = $"driver_{boneList.Count}"; mesh.Checked = true; Renderer.Meshes.Add(mesh); meshNode.Nodes.Add(mesh); var polyGroup = new STGenericPolygonGroup(); polyGroup.Material = new STGenericMaterial(); polyGroup.Material.Text = "driver_cloth"; polyGroup.PrimativeType = STPrimitiveType.Triangles; mesh.PolygonGroups.Add(polyGroup); for (int p = 0; p < prop.Points.Length; p++) { var point = prop.Points[p]; var link = prop.Influences[p]; STBone b = new STBone(G1MSkeleton.GenericSkeleton); b.Text = $"CP_{boneList.Count}"; b.FromTransform(OpenTK.Matrix4.Identity); b.Position = point.Xyz; b.parentIndex = link.P3; if (b.parentIndex == -1) { b.parentIndex = (int)parentBone; } else { b.parentIndex += boneStart; b.Position = OpenTK.Vector3.TransformPosition( point.Xyz, G1MSkeleton.GenericSkeleton.GetBoneTransform((int)parentBone) * G1MSkeleton.GenericSkeleton.GetBoneTransform(b.parentIndex).Inverted()); } boneList.Add(b); G1MSkeleton.GenericSkeleton.reset(); G1MSkeleton.GenericSkeleton.update(); mesh.vertices.Add(new Vertex() { pos = Vector3.TransformPosition(Vector3.Zero, G1MSkeleton.GenericSkeleton.GetBoneTransform(boneList.Count - 1)), boneWeights = new List <float>() { 1 }, boneIds = new List <int>() { boneList.Count - 1 }, }); if (link.P1 > 0 && link.P3 > 0) { polyGroup.faces.Add(p); polyGroup.faces.Add(link.P1); polyGroup.faces.Add(link.P3); } if (link.P2 > 0 && link.P4 > 0) { polyGroup.faces.Add(p); polyGroup.faces.Add(link.P2); polyGroup.faces.Add(link.P4); } } mesh.CalculateNormals(); } }
public void LoadFile(Model model, GFBMDL file, GFBMDL_Render Renderer) { Model = model; ParentFile = file; Renderer.Meshes.Clear(); for (int m = 0; m < Model.Materials?.Count; m++) { GenericMaterials.Add(new GFLXMaterialData(this, Model.Materials[m])); } List <int> SkinningIndices = new List <int>(); for (int b = 0; b < Model.Bones?.Count; b++) { var bone = Model.Bones[b]; Skeleton.bones.Add(new GFLXBone(this, bone)); if (bone.RigidCheck == null) { SkinningIndices.Add(b); } } Skeleton.reset(); Skeleton.update(); for (int g = 0; g < Model.Groups?.Count; g++) { var group = Model.Groups[g]; var mesh = Model.Meshes[g]; OpenTK.Matrix4 transform = OpenTK.Matrix4.Identity; GFLXMesh genericMesh = new GFLXMesh(this, group, mesh); genericMesh.Checked = true; genericMesh.ImageKey = "model"; genericMesh.SelectedImageKey = "model"; int boneIndex = (int)group.BoneIndex; if (boneIndex < Skeleton.bones.Count && boneIndex > 0) { genericMesh.BoneIndex = boneIndex; transform = Skeleton.bones[boneIndex].Transform; genericMesh.Text = Skeleton.bones[boneIndex].Text; } // if (group.MeshID < Skeleton.bones.Count && group.MeshID > 0) // genericMesh.Text = Skeleton.bones[(int)group.MeshID].Text; Renderer.Meshes.Add(genericMesh); GenericMeshes.Add(genericMesh); //Load the vertex data genericMesh.Transform = transform; genericMesh.vertices = GFLXMeshBufferHelper.LoadVertexData(mesh, transform, SkinningIndices); genericMesh.FlipUvsVertical(); //Load faces for (int p = 0; p < mesh.Polygons?.Count; p++) { var poly = mesh.Polygons[p]; var polygonGroup = new STGenericPolygonGroup(); polygonGroup.MaterialIndex = (int)poly.MaterialIndex; genericMesh.PolygonGroups.Add(polygonGroup); if (GenericMaterials.Count > poly.MaterialIndex) { polygonGroup.Material = GenericMaterials[(int)poly.MaterialIndex]; } for (int f = 0; f < poly.Faces?.Count; f++) { polygonGroup.faces.Add((int)poly.Faces[f]); } } } }
private void ReadMeshChunk(FileReader reader) { int meshCount = reader.ReadInt32(); SkipPadding(reader, 0x20); for (int mIdx = 0; mIdx < meshCount; mIdx++) { //Create a renderable object for our mesh var renderedMesh = new GenericRenderedObject { Checked = true, ImageKey = "mesh", SelectedImageKey = "mesh", Text = $"Mesh {mIdx}" }; Nodes.Add(renderedMesh); Renderer.Meshes.Add(renderedMesh); STGenericPolygonGroup polyGroup = new STGenericPolygonGroup(); renderedMesh.PolygonGroups.Add(polyGroup); renderedMesh.BoneIndex = reader.ReadInt32(); int vtxDescriptor = reader.ReadInt32(); int mtxGroupCount = reader.ReadInt32(); for (int mgIdx = 0; mgIdx < mtxGroupCount; mgIdx++) { int dependencyCount = reader.ReadInt32(); for (int ll = 0; ll < dependencyCount; ll++) { reader.ReadInt16(); } int dListCount = reader.ReadInt32(); for (int dlIdx = 0; dlIdx < dListCount; dlIdx++) { int flags = reader.ReadInt32(); int unk1 = reader.ReadInt32(); int dataSize = reader.ReadInt32(); SkipPadding(reader, 0x20); long endPosition = reader.Position + dataSize; while (reader.Position < endPosition) { byte faceType = reader.ReadByte(); if (faceType == 0x98 || faceType == 0xA0) { short faceCount = reader.ReadInt16(); int[] polygons = new int[faceCount]; for (int fIdx = 0; fIdx < faceCount; fIdx++) { if ((vtxDescriptor & 1) == 1) { reader.ReadByte(); // posmat index } if ((vtxDescriptor & 2) == 2) { reader.ReadByte(); // tex1 index } ushort vtxIdx = reader.ReadUInt16(); ushort nrmIdx = 0; if (VertexNormals.Length > 0) { nrmIdx = reader.ReadUInt16(); } ushort colIdx = 0; if ((vtxDescriptor & 4) == 4) { colIdx = reader.ReadUInt16(); } int txCoordIdx = 0; int txCoordDescriptor = vtxDescriptor >> 3; for (int tcoordIdx = 0; tcoordIdx < 8; tcoordIdx++) { if ((txCoordDescriptor & 1) == 0x1) { // Only read for the first texcoord txCoordIdx = reader.ReadInt16(); txCoordDescriptor >>= 1; } } Vertex newVertex = new Vertex { pos = Vertices[vtxIdx] }; int envIdx = 0; if (Envelopes.Length > envIdx) { for (int i = 0; i < Envelopes[envIdx].Indices?.Length; i++) { newVertex.boneIds.Add(Envelopes[envIdx].Indices[i]); newVertex.boneWeights.Add(Envelopes[envIdx].Weights[i]); } } if (VertexNormals != null) { newVertex.nrm = VertexNormals[nrmIdx]; } if (Colors != null) { newVertex.col = Colors[colIdx]; } polygons[fIdx] = renderedMesh.vertices.Count; renderedMesh.vertices.Add(newVertex); } List <Triangle> currentPolygons = ToTris(polygons, faceType); Console.WriteLine($"faceType {faceType} polygons {polygons.Length} "); foreach (Triangle triangle in currentPolygons) { if (faceType == 0x98) { polyGroup.faces.Add(triangle.B); polyGroup.faces.Add(triangle.C); polyGroup.faces.Add(triangle.A); } else { polyGroup.faces.Add(triangle.C); polyGroup.faces.Add(triangle.B); polyGroup.faces.Add(triangle.A); } } } } } } } }
public void Load(System.IO.Stream stream) { CanSave = false; Renderer = new CMB_Renderer(); DrawableContainer.Drawables.Add(Renderer); Skeleton = new STSkeleton(); //These models/skeletons come out massive so scale them with an overridden scale Skeleton.PreviewScale = Renderer.PreviewScale; Skeleton.BonePointScale = 40; Renderer.Skeleton = Skeleton; DrawableContainer.Drawables.Add(Skeleton); cmb.ReadCMB(stream); Text = cmb.Header.Name; DrawableContainer.Name = Text; //Load textures if (cmb.TexturesChunk != null) { texFolder = new TextureFolder("Texture"); TreeNode meshFolder = new TreeNode("Meshes"); TreeNode materialFolder = new TreeNode("Materials"); TreeNode skeletonFolder = new TreeNode("Skeleton"); bool HasTextures = cmb.TexturesChunk.Textures != null && cmb.TexturesChunk.Textures.Count != 0; bool HasMeshes = cmb.MeshesChunk.SHP.SEPDs != null && cmb.MeshesChunk.SHP.SEPDs.Count != 0; bool HasSkeleton = cmb.SkeletonChunk != null && cmb.SkeletonChunk.Bones.Count != 0; bool HasMaterials = cmb.MaterialsChunk != null && cmb.MaterialsChunk.Materials.Count != 0; if (HasSkeleton) { var bonesOrdered = cmb.SkeletonChunk.Bones.OrderBy(x => x.ID).ToList(); foreach (var bone in bonesOrdered) { STBone genericBone = new STBone(Skeleton); genericBone.parentIndex = bone.ParentID; genericBone.Checked = true; genericBone.Text = $"Bone {bone.ID}"; genericBone.RotationType = STBone.BoneRotationType.Euler; genericBone.Position = new OpenTK.Vector3( bone.Translation.X, bone.Translation.Y, bone.Translation.Z ); genericBone.EulerRotation = new OpenTK.Vector3( bone.Rotation.X, bone.Rotation.Y, bone.Rotation.Z ); genericBone.Scale = new OpenTK.Vector3( bone.Scale.X, bone.Scale.Y, bone.Scale.Z ); Skeleton.bones.Add(genericBone); } foreach (var bone in Skeleton.bones) { if (bone.Parent == null) { skeletonFolder.Nodes.Add(bone); } } Skeleton.reset(); Skeleton.update(); } if (HasTextures) { int texIndex = 0; foreach (var tex in cmb.TexturesChunk.Textures) { var texWrapper = new CTXB.TextureWrapper(new CTXB.Texture()); texWrapper.Text = $"Texture {texIndex++}"; texWrapper.ImageKey = "texture"; texWrapper.SelectedImageKey = texWrapper.ImageKey; if (tex.Name != string.Empty) { texWrapper.Text = tex.Name; } texWrapper.Width = tex.Width; texWrapper.Height = tex.Height; CTXB.Texture.TextureFormat Format = (CTXB.Texture.TextureFormat)((tex.DataType << 16) | tex.ImageFormat); texWrapper.Format = CTR_3DS.ConvertPICAToGenericFormat(CTXB.Texture.FormatList[Format]); texWrapper.ImageData = tex.ImageData; texFolder.Nodes.Add(texWrapper); Renderer.TextureList.Add(texWrapper); } } if (HasMaterials) { int materialIndex = 0; foreach (var mat in cmb.MaterialsChunk.Materials) { H3DMaterial H3D = ToH3DMaterial(mat); CMBMaterialWrapper material = new CMBMaterialWrapper(mat, this, H3D); material.Text = $"Material {materialIndex++}"; materialFolder.Nodes.Add(material); Materials.Add(material); bool HasDiffuse = false; foreach (var tex in mat.TextureMappers) { if (tex.TextureID != -1) { CMBTextureMapWrapper matTexture = new CMBTextureMapWrapper(tex, this); matTexture.TextureIndex = tex.TextureID; material.TextureMaps.Add(matTexture); if (tex.TextureID < Renderer.TextureList.Count && tex.TextureID >= 0) { matTexture.Name = Renderer.TextureList[tex.TextureID].Text; material.Nodes.Add(matTexture.Name); } if (!HasDiffuse && matTexture.Name != "bg_syadowmap") //Quick hack till i do texture env stuff { matTexture.Type = STGenericMatTexture.TextureType.Diffuse; HasDiffuse = true; } } } } } if (HasMeshes) { int MeshIndex = 0; foreach (var mesh in cmb.MeshesChunk.MSHS.Meshes) { STGenericMaterial mat = Materials[mesh.MaterialIndex]; CmbMeshWrapper genericMesh = new CmbMeshWrapper(mat); genericMesh.Text = $"Mesh_{MeshIndex++}_ID_{mesh.VisIndex}"; genericMesh.MaterialIndex = mesh.MaterialIndex; var shape = cmb.MeshesChunk.SHP.SEPDs[mesh.SEPDIndex]; genericMesh.Mesh = shape; List <ushort> SkinnedBoneTable = new List <ushort>(); foreach (var prim in shape.PRMS) { if (prim.BoneIndices != null) { SkinnedBoneTable.AddRange(prim.BoneIndices); } } //Now load the vertex and face data foreach (var prm in shape.PRMS) { if (shape.HasPosition) { int VertexCount = prm.VertexCount; for (int v = 0; v < VertexCount; v++) { Vertex vert = new Vertex(); vert.pos = new OpenTK.Vector3( prm.Vertices.Position[v].X, prm.Vertices.Position[v].Y, prm.Vertices.Position[v].Z); if (shape.HasNormal) { vert.nrm = new OpenTK.Vector3( prm.Vertices.Normal[v].X, prm.Vertices.Normal[v].Y, prm.Vertices.Normal[v].Z).Normalized(); } if (shape.HasColor) { vert.col = new OpenTK.Vector4( prm.Vertices.Color[v].X, prm.Vertices.Color[v].Y, prm.Vertices.Color[v].Z, prm.Vertices.Color[v].W).Normalized(); } if (shape.HasUV0) { vert.uv0 = new OpenTK.Vector2(prm.Vertices.UV0[v].X, -prm.Vertices.UV0[v].Y + 1); } if (shape.HasUV1) { vert.uv1 = new OpenTK.Vector2(prm.Vertices.UV1[v].X, -prm.Vertices.UV1[v].Y + 1); } if (shape.HasUV2) { vert.uv2 = new OpenTK.Vector2(prm.Vertices.UV2[v].X, -prm.Vertices.UV2[v].Y + 1); } if (prm.SkinningMode == SkinningMode.Smooth) { //Indices if (shape.HasIndices) { if (shape.BoneDimensionCount >= 1) { vert.boneIds.Add((int)prm.Vertices.BoneIndices[v].X); } if (shape.BoneDimensionCount >= 2) { vert.boneIds.Add((int)prm.Vertices.BoneIndices[v].Y); } if (shape.BoneDimensionCount >= 3) { vert.boneIds.Add((int)prm.Vertices.BoneIndices[v].Z); } if (shape.BoneDimensionCount >= 4) { vert.boneIds.Add((int)prm.Vertices.BoneIndices[v].W); } } //Weights if (shape.HasWeights) { if (shape.BoneDimensionCount >= 1) { vert.boneWeights.Add(prm.Vertices.BoneWeights[v].X); } if (shape.BoneDimensionCount >= 2) { vert.boneWeights.Add(prm.Vertices.BoneWeights[v].Y); } if (shape.BoneDimensionCount >= 3) { vert.boneWeights.Add(prm.Vertices.BoneWeights[v].Z); } if (shape.BoneDimensionCount >= 4) { vert.boneWeights.Add(prm.Vertices.BoneWeights[v].W); } } } if (prm.SkinningMode == SkinningMode.Rigid) { int boneId = (int)prm.Vertices.BoneIndices[v].X; vert.boneIds.Add(boneId); vert.boneWeights.Add(1); vert.pos = OpenTK.Vector3.TransformPosition(vert.pos, Skeleton.bones[boneId].Transform); if (shape.HasNormal) { vert.nrm = OpenTK.Vector3.TransformNormal(vert.nrm, Skeleton.bones[boneId].Transform); } } if (prm.SkinningMode == SkinningMode.Mixed) { int boneId = prm.BoneIndices[0]; vert.boneIds.Add(boneId); vert.boneWeights.Add(1); vert.pos = OpenTK.Vector3.TransformPosition(vert.pos, Skeleton.bones[boneId].Transform); if (shape.HasNormal) { vert.nrm = OpenTK.Vector3.TransformNormal(vert.nrm, Skeleton.bones[boneId].Transform); } } genericMesh.vertices.Add(vert); } } STGenericPolygonGroup group = new STGenericPolygonGroup(); genericMesh.PolygonGroups.Add(group); for (int i = 0; i < prm.FaceIndices.Count; i++) { group.faces.Add((int)prm.FaceIndices[i].X); group.faces.Add((int)prm.FaceIndices[i].Y); group.faces.Add((int)prm.FaceIndices[i].Z); } } Renderer.Meshes.Add(genericMesh); meshFolder.Nodes.Add(genericMesh); } } if (meshFolder.Nodes.Count > 0) { Nodes.Add(meshFolder); } if (skeletonFolder.Nodes.Count > 0) { Nodes.Add(skeletonFolder); } if (materialFolder.Nodes.Count > 0) { Nodes.Add(materialFolder); } if (texFolder.Nodes.Count > 0) { Nodes.Add(texFolder); } } }
public H3DMeshWrapper(BCH bch, H3DModelWrapper parentModel, H3DMesh mesh) : base() { ParentBCH = bch; ParentModel = parentModel; Mesh = mesh; ImageKey = "mesh"; SelectedImageKey = "mesh"; MaterialIndex = Mesh.MaterialIndex; foreach (var subMesh in mesh.SubMeshes) { STGenericPolygonGroup group = new STGenericPolygonGroup(); for (int i = 0; i < subMesh.Indices.Length; i++) { group.faces.Add(subMesh.Indices[i]); } group.PrimativeType = STPrimitiveType.Triangles; /* switch (subMesh.PrimitiveMode) * { * case SPICA.PICA.Commands.PICAPrimitiveMode.Triangles: * group.PrimativeType = STPrimitiveType.Triangles; * break; * case SPICA.PICA.Commands.PICAPrimitiveMode.TriangleStrip: * group.PrimativeType = STPrimitiveType.TrangleStrips; * break; * }*/ PolygonGroups.Add(group); } var vertices = mesh.GetVertices(); List <ushort> boneIndices = new List <ushort>(); foreach (var subMesh in mesh.SubMeshes) { if (subMesh.BoneIndicesCount > 0) { boneIndices.AddRange(subMesh.BoneIndices.ToArray()); } } for (int v = 0; v < vertices.Length; v++) { Vertex vertex = new Vertex(); vertex.pos = ConvertVector3(vertices[v].Position); vertex.nrm = ConvertVector3(vertices[v].Normal); vertex.tan = ConvertVector4(vertices[v].Tangent); vertex.uv0 = ConvertVector2(vertices[v].TexCoord0); vertex.uv1 = ConvertVector2(vertices[v].TexCoord1); vertex.uv2 = ConvertVector2(vertices[v].TexCoord2); vertex.col = ConvertVector4(vertices[v].Color); //Flip UVs vertex.uv0 = new Vector2(vertex.uv0.X, 1 - vertex.uv0.Y); if (boneIndices.Count > 0) { /* if (vertices[v].Indices.b0 != -1) vertex.boneIds.Add(boneIndices[vertices[v].Indices.b0]); * if (vertices[v].Indices.b1 != -1) vertex.boneIds.Add(boneIndices[vertices[v].Indices.b1]); * if (vertices[v].Indices.b2 != -1) vertex.boneIds.Add(boneIndices[vertices[v].Indices.b2]); * if (vertices[v].Indices.b3 != -1) vertex.boneIds.Add(boneIndices[vertices[v].Indices.b3]);*/ if (mesh.Skinning == H3DMeshSkinning.Rigid) { int index = boneIndices[vertices[v].Indices.b0]; vertex.pos = Vector3.TransformPosition(vertex.pos, parentModel.Skeleton.Renderable.bones[index].Transform); // vertex.nrm = Vector3.TransformNormal(vertex.nrm, parentModel.Skeleton.Renderable.bones[index].Transform); } /* vertex.boneWeights.Add(vertices[v].Weights.w0); * vertex.boneWeights.Add(vertices[v].Weights.w1); * vertex.boneWeights.Add(vertices[v].Weights.w2); * vertex.boneWeights.Add(vertices[v].Weights.w3);*/ } this.vertices.Add(vertex); } }
public void Load(System.IO.Stream stream) { Renderer = new Strikers_Renderer(); DrawableContainer.Name = FileName; DrawableContainer.Drawables.Add(Renderer); IsGamecube = Utils.GetExtension(FileName) == ".glg"; Text = FileName; using (var reader = new FileReader(stream)) { reader.SetByteOrder(true); reader.ReadUInt32(); //magic reader.ReadUInt32(); //fileSize //Create a list of all the major sections //The sections are not in order, so we must order them while parsing Dictionary <SectionMagic, SectionHeader> SectionLookup = new Dictionary <SectionMagic, SectionHeader>(); while (!reader.EndOfStream) { uint magic = reader.ReadUInt32(); uint sectionSize = reader.ReadUInt32(); long pos = reader.Position; Console.WriteLine($"Magic {(SectionMagic)magic} sectionSize {sectionSize}"); if (!SectionLookup.ContainsKey((SectionMagic)magic)) { SectionLookup.Add((SectionMagic)magic, new SectionHeader(sectionSize, pos)); } //This section will skip sub sections so don't do that if (magic != 0x8001B000) { reader.SeekBegin(pos + sectionSize); } if (!IsGamecube) { reader.Align(4); } } var HashList = NLG_Common.HashNames; //Now read our model properly //First get our model info Matrix4 TransformMatrix = Matrix4.Identity; uint totalNumMeshes = 0; uint modelSize = 12; if (IsGamecube) { modelSize = 16; } if (SectionLookup.ContainsKey(SectionMagic.ModelData)) { var modelHeader = SectionLookup[SectionMagic.ModelData]; for (int m = 0; m < modelHeader.Size / modelSize; m++) { reader.SeekBegin(modelHeader.Position + (m * modelSize)); if (IsGamecube) { totalNumMeshes += reader.ReadUInt32(); reader.ReadUInt32(); } else { reader.ReadUInt32(); totalNumMeshes += reader.ReadUInt32(); } } } uint meshIndex = 0; if (SectionLookup.ContainsKey(SectionMagic.ModelData)) { var modelHeader = SectionLookup[SectionMagic.ModelData]; reader.SeekBegin(modelHeader.Position); uint numModels = modelHeader.Size / modelSize; uint hashId = 0; for (int m = 0; m < numModels; m++) { Model model = new Model(); Nodes.Add(model); Models.Add(model); uint numMeshes = 0; reader.SeekBegin(modelHeader.Position + (m * modelSize)); if (IsGamecube) { numMeshes = reader.ReadUInt32(); hashId = reader.ReadUInt32(); } else { hashId = reader.ReadUInt32(); numMeshes = reader.ReadUInt32(); } model.Text = hashId.ToString("X"); if (HashList.ContainsKey(hashId)) { model.Text = HashList[hashId]; } if (SectionLookup.ContainsKey(SectionMagic.MeshData)) { var meshHeader = SectionLookup[SectionMagic.MeshData]; reader.SeekBegin(meshHeader.Position); for (int i = 0; i < numMeshes; i++) { if (IsGamecube) { uint meshSize = meshHeader.Size / totalNumMeshes; reader.SeekBegin(meshHeader.Position + ((meshIndex + i) * meshSize)); } else { reader.SeekBegin(meshHeader.Position + ((meshIndex + i) * 48)); } var mesh = new MeshData(reader, IsGamecube); model.Meshes.Add(mesh); } meshIndex += numMeshes; } } if (SectionLookup.ContainsKey(SectionMagic.MatrixData)) { var matSection = SectionLookup[SectionMagic.MatrixData]; reader.SeekBegin(matSection.Position); TransformMatrix = reader.ReadMatrix4(true); } List <VertexAttribute> Pointers = new List <VertexAttribute>(); if (SectionLookup.ContainsKey(SectionMagic.VertexAttributePointerData)) { var pointerSection = SectionLookup[SectionMagic.VertexAttributePointerData]; reader.SeekBegin(pointerSection.Position); int attributeSize = 8; if (IsGamecube) { attributeSize = 6; } for (int i = 0; i < pointerSection.Size / attributeSize; i++) { VertexAttribute pointer = new VertexAttribute(); if (IsGamecube) { pointer.Offset = reader.ReadUInt32(); pointer.Type = reader.ReadByte(); pointer.Stride = reader.ReadByte(); } else { pointer.Offset = reader.ReadUInt32(); pointer.Type = reader.ReadByte(); pointer.Stride = reader.ReadByte(); reader.ReadUInt16(); } Pointers.Add(pointer); } } int pointerIndex = 0; foreach (var model in Models) { for (int i = 0; i < model.Meshes.Count; i++) { RenderableMeshWrapper mesh = new RenderableMeshWrapper(); model.Nodes.Add(mesh); Renderer.Meshes.Add(mesh); mesh.Text = model.Meshes[i].HashID.ToString("X"); if (HashList.ContainsKey(model.Meshes[i].HashID)) { mesh.Text = HashList[model.Meshes[i].HashID]; } string material = model.Meshes[i].MaterialHashID.ToString("X"); if (HashList.ContainsKey(model.Meshes[i].MaterialHashID)) { material = HashList[model.Meshes[i].MaterialHashID]; } mesh.Nodes.Add(material); var faceSecton = SectionLookup[SectionMagic.IndexData]; var vertexSecton = SectionLookup[SectionMagic.VertexData]; STGenericPolygonGroup polyGroup = new STGenericPolygonGroup(); mesh.PolygonGroups.Add(polyGroup); reader.SeekBegin(faceSecton.Position + model.Meshes[i].IndexStartOffset); List <int> faces = new List <int>(); for (int f = 0; f < model.Meshes[i].IndexCount; f++) { if (model.Meshes[i].IndexFormat == 0) { polyGroup.faces.Add(reader.ReadUInt16()); } else { polyGroup.faces.Add(reader.ReadByte()); } } if (model.Meshes[i].FaceType == MeshData.PolygonType.TriangleStrips) { polyGroup.PrimativeType = STPrimitiveType.TrangleStrips; } else { polyGroup.PrimativeType = STPrimitiveType.Triangles; } if (IsGamecube) { uint size = Pointers[pointerIndex + 1].Offset - Pointers[pointerIndex].Offset; model.Meshes[i].VertexCount = (ushort)(size / Pointers[pointerIndex].Stride); } Console.WriteLine($"mesh {mesh.Text} {model.Meshes[i].VertexCount}"); for (int v = 0; v < model.Meshes[i].VertexCount; v++) { Vertex vert = new Vertex(); mesh.vertices.Add(vert); for (int a = 0; a < model.Meshes[i].NumAttributePointers; a++) { var pointer = Pointers[pointerIndex + a]; reader.SeekBegin(vertexSecton.Position + pointer.Offset + (pointer.Stride * v)); if (pointer.Type == 0) { if (pointer.Stride == 6) { vert.pos = new Vector3(reader.ReadInt16() / 1024f, reader.ReadInt16() / 1024f, reader.ReadInt16() / 1024f); } else if (pointer.Stride == 12) { vert.pos = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); } // vert.pos = Vector3.TransformPosition(vert.pos, TransformMatrix); } if (pointer.Type == 1) { if (pointer.Stride == 3) { vert.nrm = Read_8_8_8_Snorm(reader).Normalized(); } else if (pointer.Stride == 12) { vert.nrm = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); } } if (pointer.Type == 3) { vert.uv0 = new Vector2(reader.ReadUInt16() / 1024f, reader.ReadUInt16() / 1024f); } if (pointer.Type == 0x67) { vert.pos = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); } if (pointer.Type == 0xFE) { vert.nrm = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); } if (pointer.Type == 0x26) { vert.uv0 = new Vector2(reader.ReadUInt16() / 1024f, reader.ReadUInt16() / 1024f); } if (pointer.Type == 0xCC) { vert.uv0 = new Vector2(reader.ReadUInt16() / 1024f, reader.ReadUInt16() / 1024f); } if (pointer.Type == 0x17) { vert.uv1 = new Vector2(reader.ReadUInt16() / 1024f, reader.ReadUInt16() / 1024f); } if (pointer.Type == 0xD4) { vert.boneIds = new List <int>() { reader.ReadByte(), reader.ReadByte(), reader.ReadByte(), reader.ReadByte() }; } if (pointer.Type == 0xB0) { vert.boneWeights = new List <float>() { reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle() }; } } } mesh.TransformPosition(new Vector3(0), new Vector3(-90, 0, 0), new Vector3(1)); pointerIndex += model.Meshes[i].NumAttributePointers; } for (int i = 0; i < model.Meshes.Count; i++) { if (IsGamecube) { var renderedMesh = (RenderableMeshWrapper)Renderer.Meshes[i]; renderedMesh.Material = new STGenericMaterial(); string diffuseName = model.Meshes[i].TexturHashID.ToString("X"); if (HashList.ContainsKey(model.Meshes[i].TexturHashID)) { diffuseName = HashList[model.Meshes[i].TexturHashID]; } var texUnit = 1; renderedMesh.Material.TextureMaps.Add(new STGenericMatTexture() { textureUnit = texUnit++, Type = STGenericMatTexture.TextureType.Diffuse, Name = diffuseName, }); } if (SectionLookup.ContainsKey(SectionMagic.MaterialData)) { var materialSecton = SectionLookup[SectionMagic.MaterialData]; reader.SeekBegin(materialSecton.Position + model.Meshes[i].MaterialOffset); var renderedMesh = (RenderableMeshWrapper)Renderer.Meshes[i]; renderedMesh.Material = new STGenericMaterial(); switch (model.Meshes[i].MaterailPreset) { case MaterailPresets.EnvDiffuseDamage: { uint diffuseMapHashID = reader.ReadUInt32(); uint diffuseMapParam = reader.ReadUInt32(); string diffuseName = diffuseMapHashID.ToString("X"); if (HashList.ContainsKey(diffuseMapHashID)) { diffuseName = HashList[diffuseMapHashID]; } var texUnit = 1; renderedMesh.Material.TextureMaps.Add(new STGenericMatTexture() { textureUnit = texUnit++, Type = STGenericMatTexture.TextureType.Diffuse, Name = diffuseName, }); } break; default: { uint diffuseMapHashID = reader.ReadUInt32(); string diffuseName = diffuseMapHashID.ToString("X"); if (HashList.ContainsKey(diffuseMapHashID)) { diffuseName = HashList[diffuseMapHashID]; } var texUnit = 1; renderedMesh.Material.TextureMaps.Add(new STGenericMatTexture() { textureUnit = texUnit++, Type = STGenericMatTexture.TextureType.Diffuse, Name = diffuseName, }); } break; } } } } List <BoneListEntry> BoneLists = new List <BoneListEntry>(); List <uint> boneHashOrder = new List <uint>(); TreeNode parentBoneList = new TreeNode("Bone List"); Nodes.Add(parentBoneList); if (SectionLookup.ContainsKey(SectionMagic.SkeletonData)) { var skeletonSection = SectionLookup[SectionMagic.SkeletonData]; reader.SeekBegin(skeletonSection.Position); //Read all sub sections while (reader.Position < skeletonSection.Position + skeletonSection.Size) { uint magic = reader.ReadUInt32(); uint sectionSize = reader.ReadUInt32(); long pos = reader.Position; BoneListEntry entry = new BoneListEntry(); BoneLists.Add(entry); //Bone hashes appear for each mesh if it uses rigging //Meshes index these lists for rigging if ((SectionMagic)magic == SectionMagic.BoneHashes) { TreeNode boneListNode = new TreeNode("Mesh Bone List"); parentBoneList.Nodes.Add(boneListNode); uint numHashes = sectionSize / 4; for (int i = 0; i < numHashes; i++) { entry.Hashes.Add(reader.ReadUInt32()); if (IsGamecube) { reader.ReadUInt32(); } string hashName = entry.Hashes[i].ToString("X"); if (HashList.ContainsKey(entry.Hashes[i])) { hashName = HashList[entry.Hashes[i]]; } boneListNode.Nodes.Add(hashName); } } if ((SectionMagic)magic == SectionMagic.BoneData) { uint numBones = sectionSize / 68; for (int i = 0; i < numBones; i++) { reader.SeekBegin(pos + i * 68); BoneEntry bone = new BoneEntry(); bone.HashID = reader.ReadUInt32(); reader.ReadUInt32(); //unk reader.ReadUInt32(); //unk reader.ReadUInt32(); //unk reader.ReadSingle(); //0 bone.Scale = new Vector3( reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); reader.ReadSingle(); //0 bone.Rotate = new Vector3( reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); reader.ReadSingle(); //0 bone.Translate = new Vector3( reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); reader.ReadSingle(); //1 // bone.Translate = Vector3.TransformPosition(bone.Translate, TransformMatrix); entry.Bones.Add(bone); } } reader.SeekBegin(pos + sectionSize); } } List <BoneEntry> BoneListSorted = new List <BoneEntry>(); foreach (var hash in boneHashOrder) { foreach (var boneList in BoneLists) { foreach (var bone in boneList.Bones) { if (bone.HashID == hash) { BoneListSorted.Add(bone); } } } } STSkeleton skeleton = new STSkeleton(); DrawableContainer.Drawables.Add(skeleton); foreach (var boneList in BoneLists) { foreach (var bone in boneList.Bones) { STBone stBone = new STBone(skeleton); skeleton.bones.Add(stBone); stBone.Text = bone.HashID.ToString("X"); if (HashList.ContainsKey(bone.HashID)) { stBone.Text = HashList[bone.HashID]; } stBone.Position = bone.Translate; stBone.EulerRotation = bone.Rotate; stBone.Scale = new Vector3(0.2f, 0.2f, 0.2f); stBone.RotationType = STBone.BoneRotationType.Euler; } } skeleton.reset(); skeleton.update(); TreeNode skeletonNode = new TreeNode("Skeleton"); Nodes.Add(skeletonNode); foreach (var bone in skeleton.bones) { if (bone.Parent == null) { skeletonNode.Nodes.Add(bone); } } } } }