public void AddBlendShapesToMesh(Mesh mesh) { Dictionary <string, BlendShape> map = new Dictionary <string, BlendShape>(); foreach (var x in BlendShapes) { BlendShape bs = null; if (!map.TryGetValue(x.Name, out bs)) { bs = new BlendShape(); bs.Positions = Positions.ToArray(); bs.Normals = Normals.ToArray(); bs.Tangents = Tangents.Select(y => (Vector3)y).ToArray(); bs.Name = x.Name; bs.FrameWeight = x.FrameWeight; map.Add(x.Name, bs); } var j = x.VertexOffset; for (int i = 0; i < x.Positions.Length; ++i, ++j) { bs.Positions[j] = x.Positions[i]; bs.Normals[j] = x.Normals[i]; bs.Tangents[j] = x.Tangents[i]; } } foreach (var kv in map) { //Debug.LogFormat("AddBlendShapeFrame: {0}", kv.Key); mesh.AddBlendShapeFrame(kv.Key, kv.Value.FrameWeight, kv.Value.Positions, kv.Value.Normals, kv.Value.Tangents); } }
/// <summary> /// Get normals for this object /// </summary> /// <returns>normals for object</returns> public Vector3[] GetNormals() { if (_recalculateNormals) { CalculateNormals(); } return(Normals.ToArray()); }
public IFileGeometry3D ApplyMatrix(ref Matrix4x4 matrix) { return(new GeometryData(Name, Positions.ToArray().Transform(ref matrix).ToList(), Normals.ToArray().Transform(ref matrix).ToList(), Colors.ToList(), Indices.ToList(), TextureCoors.ToList(), Topology )); }
public Primitive Clone() { return(new Primitive(LocalVertices.ToArray(), GlobalVertices.ToArray(), Normals.ToArray(), TextureCoords.ToArray(), Indexes.ToArray(), NormalIndexes.ToArray(), TextureCoordsIndexes.ToArray(), Pivot.Clone())); }
public ImmutableGeometryData Transform(Matrix4x4 matrix) { var p = Positions.ToArray().Transform(ref matrix); var n = Normals.ToArray().Transform(ref matrix); return(new ImmutableGeometryData(p.AsReadOnly(), n.AsReadOnly(), Indices) { Colors = Colors, TexCoor = TexCoor, Topology = Topology, IsModified = true, }); }
/*public void Clear() * { * this.Triangles.Clear(); * this.Vertices.Clear(); * this.Normals.Clear(); * this.UVs.Clear(); * }*/ public Mesh Build() { //RemoveCollapsedTriangles(); Mesh mesh = new Mesh(); mesh.vertices = Vertices.ToArray(); mesh.triangles = Triangles.ToArray(); mesh.normals = Normals.ToArray(); mesh.uv = UVs.ToArray(); mesh.uv2 = UVs.ToArray(); mesh.uv3 = UVs.ToArray(); //mesh.RecalculateNormals(); mesh.RecalculateBounds(); mesh.Optimize(); return(mesh); }
public Mesh CreateMesh() { Mesh mesh = new Mesh(); mesh.vertices = Vertices.ToArray(); mesh.triangles = indices.ToArray(); if (Normals.Count == Vertices.Count) { mesh.normals = Normals.ToArray(); } if (UVs.Count == Vertices.Count) { mesh.uv = UVs.ToArray(); } mesh.RecalculateBounds(); return(mesh); }
public override void Upload() { if (_faceInfos == null || _faceInfos.Count <= 0) { UploadedCount = 0; return; } GL.BindVertexArray(VaoId); if (UploadedCount == 0) { //0 positions GL.BindBuffer(BufferTarget.ArrayBuffer, BufferIds[0]); GL.BufferData(BufferTarget.ArrayBuffer, Positions.Count * Vector3.SizeInBytes, Positions.ToArray(), BufferUsageHint.StaticDraw); GL.EnableVertexAttribArray(0); GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 0, 0); //1 texCoords GL.BindBuffer(BufferTarget.ArrayBuffer, BufferIds[1]); GL.BufferData(BufferTarget.ArrayBuffer, TexCoords.Count * Vector4.SizeInBytes, TexCoords.ToArray(), BufferUsageHint.StaticDraw); GL.EnableVertexAttribArray(1); GL.VertexAttribPointer(1, 4, VertexAttribPointerType.Float, false, 0, 0); //2 normals GL.BindBuffer(BufferTarget.ArrayBuffer, BufferIds[2]); GL.BufferData(BufferTarget.ArrayBuffer, Normals.Count * Vector4.SizeInBytes, Normals.ToArray(), BufferUsageHint.StaticDraw); GL.EnableVertexAttribArray(2); GL.VertexAttribPointer(2, 4, VertexAttribPointerType.Float, false, 0, 0); //3 color GL.BindBuffer(BufferTarget.ArrayBuffer, BufferIds[3]); GL.BufferData(BufferTarget.ArrayBuffer, Colors.Count * Vector3.SizeInBytes, Colors.ToArray(), BufferUsageHint.StaticDraw); GL.EnableVertexAttribArray(3); GL.VertexAttribPointer(3, 3, VertexAttribPointerType.Float, false, 0, 0); //4 light GL.BindBuffer(BufferTarget.ArrayBuffer, BufferIds[4]); GL.BufferData(BufferTarget.ArrayBuffer, Lights.Count * Vector3.SizeInBytes, Lights.ToArray(), BufferUsageHint.StaticDraw); GL.EnableVertexAttribArray(4); GL.VertexAttribPointer(4, 3, VertexAttribPointerType.Float, false, 0, 0); } else { //0 positions GL.BindBuffer(BufferTarget.ArrayBuffer, BufferIds[0]); GL.BufferData(BufferTarget.ArrayBuffer, Positions.Count * Vector3.SizeInBytes, Positions.ToArray(), BufferUsageHint.StaticDraw); //1 texCoords GL.BindBuffer(BufferTarget.ArrayBuffer, BufferIds[1]); GL.BufferData(BufferTarget.ArrayBuffer, TexCoords.Count * Vector4.SizeInBytes, TexCoords.ToArray(), BufferUsageHint.StaticDraw); //2 normals GL.BindBuffer(BufferTarget.ArrayBuffer, BufferIds[2]); GL.BufferData(BufferTarget.ArrayBuffer, Normals.Count * Vector4.SizeInBytes, Normals.ToArray(), BufferUsageHint.StaticDraw); //3 color GL.BindBuffer(BufferTarget.ArrayBuffer, BufferIds[3]); GL.BufferData(BufferTarget.ArrayBuffer, Colors.Count * Vector3.SizeInBytes, Colors.ToArray(), BufferUsageHint.StaticDraw); //4 light GL.BindBuffer(BufferTarget.ArrayBuffer, BufferIds[4]); GL.BufferData(BufferTarget.ArrayBuffer, Lights.Count * Vector3.SizeInBytes, Lights.ToArray(), BufferUsageHint.StaticDraw); } UploadedCount = _faceInfos.Count * 6; _uploadedFaces = _faceInfos.ToArray(); var indices = new List <uint>(); foreach (var face in _uploadedFaces) { indices.AddRange(face.Indices); } GL.BindBuffer(BufferTarget.ElementArrayBuffer, IndicesId); GL.BufferData(BufferTarget.ElementArrayBuffer, UploadedCount * sizeof(uint), indices.ToArray(), BufferUsageHint.DynamicDraw); }
public MeshIntegrationResult Integrate(MeshEnumerateOption onlyBlendShapeRenderers) { var mesh = new Mesh(); if (Positions.Count > ushort.MaxValue) { Debug.LogFormat("exceed 65535 vertices: {0}", Positions.Count); mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; } mesh.vertices = Positions.ToArray(); mesh.normals = Normals.ToArray(); mesh.uv = UV.ToArray(); mesh.tangents = Tangents.ToArray(); mesh.boneWeights = BoneWeights.ToArray(); mesh.subMeshCount = SubMeshes.Count; for (var i = 0; i < SubMeshes.Count; ++i) { mesh.SetIndices(SubMeshes[i].Indices.ToArray(), MeshTopology.Triangles, i); } mesh.bindposes = BindPoses.ToArray(); // blendshape switch (onlyBlendShapeRenderers) { case MeshEnumerateOption.OnlyWithBlendShape: { AddBlendShapesToMesh(mesh); mesh.name = INTEGRATED_MESH_WITH_BLENDSHAPE_NAME; break; } case MeshEnumerateOption.All: { AddBlendShapesToMesh(mesh); mesh.name = INTEGRATED_MESH_ALL_NAME; break; } case MeshEnumerateOption.OnlyWithoutBlendShape: { mesh.name = INTEGRATED_MESH_WITHOUT_BLENDSHAPE_NAME; break; } } // meshName var meshNode = new GameObject(); switch (onlyBlendShapeRenderers) { case MeshEnumerateOption.OnlyWithBlendShape: { meshNode.name = INTEGRATED_MESH_WITH_BLENDSHAPE_NAME; break; } case MeshEnumerateOption.OnlyWithoutBlendShape: { meshNode.name = INTEGRATED_MESH_WITHOUT_BLENDSHAPE_NAME; break; } case MeshEnumerateOption.All: { meshNode.name = INTEGRATED_MESH_ALL_NAME; break; } } var integrated = meshNode.AddComponent <SkinnedMeshRenderer>(); integrated.sharedMesh = mesh; integrated.sharedMaterials = SubMeshes.Select(x => x.Material).ToArray(); integrated.bones = Bones.ToArray(); Result.IntegratedRenderer = integrated; Result.MeshMap.Integrated = mesh; return(Result); }
private List <Vertex> BuildVertList(ModelRoot Root, out List <mesh> meshes) { List <Vertex> verts = new List <Vertex>(); meshes = new List <mesh>(); foreach (var Node in Root.LogicalNodes) { if (Node.Mesh == null) { continue; } Mesh Mesh = Node.Mesh; mesh m = new mesh(); m.name = Mesh.Name; m.geoms = new List <geom>(); foreach (MeshPrimitive Primitive in Mesh.Primitives) { geom g = new geom(); g.strips = new List <strip>(); g.diffuse = Primitive.Material?.Channels?.First(channel => channel.Key == "BaseColor").Parameter ?? Vector4.One; var texture = Primitive.Material?.Channels?.FirstOrDefault(channel => channel.Key == "BaseColor").Texture; if (texture != null) { g.texture = texture.PrimaryImage.Name + ".png"; } GetVertexBuffer(Primitive, out List <Vector3> Vertices); GetNormalBuffer(Primitive, out List <Vector3> Normals); GetTexCoordBuffer(Primitive, out List <Vector2> Uvs); Vector3[] vs = Vertices.ToArray(); Vector3[] ns = Normals.ToArray(); Vector2[] uvs = Uvs.ToArray(); GetIndexBuffer(Primitive, out List <(int A, int B, int C)> Indices); //TODO stripify triangles foreach (var tri in Indices) { g.strips.Add(new strip { triangleCount = 1, vertexOffset = verts.Count }); Vector4 PosC = new Vector4(vs[tri.C].X, vs[tri.C].Y, vs[tri.C].Z, 1); Vector4 PosB = new Vector4(vs[tri.B].X, vs[tri.B].Y, vs[tri.B].Z, 1); Vector4 PosA = new Vector4(vs[tri.A].X, vs[tri.A].Y, vs[tri.A].Z, 1); PosC = Vector4.Transform(PosC, Node.WorldMatrix); PosB = Vector4.Transform(PosB, Node.WorldMatrix); PosA = Vector4.Transform(PosA, Node.WorldMatrix); verts.Add(new Vertex { X = PosC.X, Y = PosC.Y, Z = PosC.Z, NX = ns[tri.C].X, NY = ns[tri.C].Y, NZ = ns[tri.C].Z, U = uvs[tri.C].X, V = uvs[tri.C].Y }.Converted()); verts.Add(new Vertex { X = PosB.X, Y = PosB.Y, Z = PosB.Z, NX = ns[tri.B].X, NY = ns[tri.B].Y, NZ = ns[tri.B].Z, U = uvs[tri.B].X, V = uvs[tri.B].Y }.Converted()); verts.Add(new Vertex { X = PosA.X, Y = PosA.Y, Z = PosA.Z, NX = ns[tri.A].X, NY = ns[tri.A].Y, NZ = ns[tri.A].Z, U = uvs[tri.A].X, V = uvs[tri.A].Y }.Converted()); } m.geoms.Add(g); } meshes.Add(m); } return(verts); }
protected override void CreateGeometry() { base.CreateGeometry(); if (!GenerateGeometry) { return; } //create buffer descriptions var vDesc = new BufferDesc() { Width = (uint)Positions.Count, Format = Format.Float3, Type = BufferType.Input }; var nDesc = new BufferDesc() { Width = (uint)Normals.Count, Format = Format.Float3, Type = BufferType.Input }; var tcDesc = new BufferDesc() { Width = (uint)Texcoords.Count, Format = Format.Float2, Type = BufferType.Input }; // Create the buffers to hold our geometry data var vBuffer = new OptixBuffer(Context, vDesc); var nBuffer = new OptixBuffer(Context, nDesc); var tcBuffer = new OptixBuffer(Context, tcDesc); vBuffer.SetData <Vector3>(Positions.ToArray()); nBuffer.SetData <Vector3>(Normals.ToArray()); tcBuffer.SetData <Vector2>(Texcoords.ToArray()); List <GeometryInstance> instances = new List <GeometryInstance>(); foreach (ObjGroup group in Groups) { //empty group if (group.VIndices.Count == 0 && group.NIndices.Count == 0 && group.TIndices.Count == 0) { continue; } //ValidateGroup( group ); var normalsUseVIndices = GenerateNormals && group.NIndices.Count == 0 && Normals.Count > 0; if (normalsUseVIndices) { Debug.Assert(Normals.Count == Positions.Count); } var numNormIndices = normalsUseVIndices ? group.VIndices.Count : group.NIndices.Count; var viDesc = new BufferDesc { Width = (uint)group.VIndices.Count, Format = Format.Int3, Type = BufferType.Input }; var niDesc = new BufferDesc { Width = (uint)numNormIndices, Format = Format.Int3, Type = BufferType.Input }; var tiDesc = new BufferDesc { Width = (uint)group.TIndices.Count, Format = Format.Int3, Type = BufferType.Input }; var viBuffer = new OptixBuffer(Context, viDesc); var niBuffer = new OptixBuffer(Context, niDesc); var tiBuffer = new OptixBuffer(Context, tiDesc); viBuffer.SetData(group.VIndices.ToArray()); //if normals weren't in the obj and we genereated them, use the vertex indices niBuffer.SetData(normalsUseVIndices ? group.VIndices.ToArray() : group.NIndices.ToArray()); tiBuffer.SetData(group.TIndices.ToArray()); //create a geometry node and set the buffers var geometry = new Geometry(Context); geometry.IntersectionProgram = new OptixProgram(Context, IntersecitonProgPath, IntersecitonProgName); geometry.BoundingBoxProgram = new OptixProgram(Context, BoundingBoxProgPath, BoundingBoxProgName); geometry.PrimitiveCount = (uint)group.VIndices.Count; geometry["vertex_buffer"].Set(vBuffer); geometry["normal_buffer"].Set(nBuffer); geometry["texcoord_buffer"].Set(tcBuffer); geometry["vindex_buffer"].Set(viBuffer); geometry["nindex_buffer"].Set(niBuffer); geometry["tindex_buffer"].Set(tiBuffer); //create a geometry instance GeometryInstance instance = new GeometryInstance(Context); instance.Geometry = geometry; instance.AddMaterial(_materialResolveFunc(group.mtrl) ?? DefaultMaterial); if (group.mtrl != null) { ObjMaterial mtrl = mMtrls[group.mtrl]; instance["diffuse_color"].SetFloat3(ref mtrl.Kd); instance["emission_color"].SetFloat3(ref mtrl.Ke); } else { instance["diffuse_color"].Set(1.0f, 1.0f, 1.0f); } instances.Add(instance); } //create an acceleration structure for the geometry var accel = new Acceleration(Context, Builder, Traverser); if (Builder == AccelBuilder.Sbvh || Builder == AccelBuilder.TriangleKdTree) { accel.VertexBufferName = "vertex_buffer"; accel.IndexBufferName = "vindex_buffer"; } //now attach the instance and accel to the geometry group GeoGroup.Acceleration = accel; GeoGroup.AddChildren(instances); }
/// <summary> /// Converts the record/s into a Unity GameObject structure with meshes, /// materials etc and imports into the scene. /// </summary> public override void ImportIntoScene() { // Create an empty gameobject UnityGameObject = new GameObject(ID); // DuckbearLab: FIX! //UnityGameObject.transform.localScale = Vector3.one; // Apply transformations UnityGameObject.transform.localPosition = Position; UnityGameObject.transform.localRotation = Rotation; if (Scale != Vector3.one) { UnityGameObject.transform.localScale = Scale; } // Assign parent if (Parent != null && Parent is InterRecord) { // DuckbearLab: FIX! UnityGameObject.transform.SetParent((Parent as InterRecord).UnityGameObject.transform, false); //UnityGameObject.transform.parent = (Parent as InterRecord).UnityGameObject.transform; } // Add Comment if (!string.IsNullOrEmpty(Comment)) { UnityGameObject.AddComponent <UFLT.MonoBehaviours.Comment>().Value = Comment; } // Processes children base.ImportIntoScene(); // Create mesh if (Vertices != null && Vertices.Count > 0) { Mesh m = new Mesh(); m.name = ID; m.vertices = VertexPositions.ToArray(); m.normals = Normals.ToArray(); m.uv = UVS.ToArray(); // DuckbearLab: Fix for very large meshes if (VertexPositions.Count >= ushort.MaxValue) { m.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; } MeshRenderer mr = UnityGameObject.AddComponent <MeshRenderer>(); Material[] mats = new Material[SubMeshes.Count]; MeshFilter mf = UnityGameObject.AddComponent <MeshFilter>(); // Set submeshes m.subMeshCount = SubMeshes.Count; for (int i = 0; i < SubMeshes.Count; i++) { mats[i] = SubMeshes[i].Key.UnityMaterial; m.SetTriangles(SubMeshes[i].Value.ToArray(), i); } // DuckbearLab: OPTIMIZE! //mr.materials = mats; ; { bool equal = true; foreach (var material in mats) { if (material != mats[0]) { equal = false; } } if (equal && SubMeshes.Count > 1) { CombineInstance[] combine = new CombineInstance[SubMeshes.Count]; for (int i = 0; i < combine.Length; i++) { combine[i].mesh = m; combine[i].subMeshIndex = i; } var newMesh = new Mesh(); newMesh.name = ID; newMesh.CombineMeshes(combine, true, false); m = newMesh; Material[] newMats = new Material[1]; newMats[0] = mats[0]; mr.materials = newMats; } else { mr.materials = mats; } } #if UNITY_EDITOR UnityEditor.MeshUtility.Optimize(m); #endif mf.mesh = m; } }
internal void Write(BinaryDataWriter writer, FileVersion version) { long modelPosition = writer.Position; Offset positionArrayOffset = writer.ReserveOffset(); Offset normalArrayOffset = writer.ReserveOffset(); Offset triangleArrayOffset = writer.ReserveOffset(); Offset octreeOffset = writer.ReserveOffset(); if (version == FileVersion.VersionDS) { writer.WriteFx32(PrismThickness); writer.WriteVector3Fx32(MinCoordinate); writer.Write(CoordinateMask); writer.Write(CoordinateShift); writer.WriteFx32(SphereRadius); // Write the positions. positionArrayOffset.Satisfy((int)(writer.Position - modelPosition)); writer.WriteVector3Fx32s(Positions.ToArray()); // Write the normals. normalArrayOffset.Satisfy((int)(writer.Position - modelPosition)); writer.WriteVector3Fx16s(Normals.ToArray()); // Write the triangles. triangleArrayOffset.Satisfy((int)(writer.Position - modelPosition - 0x10)); writer.Write(Prisms, version); } else { writer.Write(PrismThickness); writer.Write(MinCoordinate); writer.Write(CoordinateMask); writer.Write(CoordinateShift); if (version > FileVersion.VersionGC) { writer.Write(SphereRadius); } // Write the positions. positionArrayOffset.Satisfy((int)(writer.Position - modelPosition)); writer.Write(Positions.ToArray()); // Write the normals. normalArrayOffset.Satisfy((int)(writer.Position - modelPosition)); writer.Write(Normals.ToArray()); // Write the triangles. if (version < FileVersion.Version2) { triangleArrayOffset.Satisfy((int)(writer.Position - modelPosition - 0x10)); } else { triangleArrayOffset.Satisfy((int)(writer.Position - modelPosition)); } writer.Write(Prisms, version); } // Write the octree. int octreeOffsetValue = (int)(writer.Position - modelPosition); octreeOffset.Satisfy(octreeOffsetValue); // Write the node keys, and compute the correct offsets into the triangle lists or to child nodes. // Nintendo writes child nodes behind the current node, so the children need to be queued. // In this implementation, empty triangle lists point to the same terminator behind the last node. int triangleListPos = GetNodeCount(PolygonOctreeRoots) * sizeof(uint); Queue <PolygonOctree[]> queuedNodes = new Queue <PolygonOctree[]>(); Dictionary <ushort[], int> indexPool = CreateIndexBuffer(queuedNodes); queuedNodes.Enqueue(PolygonOctreeRoots); while (queuedNodes.Count > 0) { PolygonOctree[] nodes = queuedNodes.Dequeue(); long offset = writer.Position - modelPosition - octreeOffsetValue; foreach (PolygonOctree node in nodes) { if (node.Children == null) { // Node is a leaf and points to triangle index list. ushort[] indices = node.TriangleIndices.ToArray(); int listPos = triangleListPos + indexPool[indices]; node.Key = (uint)ModelOctreeNode.Flags.Values | (uint)(listPos - offset - sizeof(ushort)); } else { // Node is a branch and points to 8 children. node.Key = (uint)(nodes.Length + queuedNodes.Count * 8) * sizeof(uint); queuedNodes.Enqueue(node.Children); } writer.Write(node.Key); } } foreach (var ind in indexPool) { //Last value skip. Uses terminator of previous index list if (ind.Key.Length == 0) { break; } //Save the index lists and terminator if (version < FileVersion.Version2) { for (int i = 0; i < ind.Key.Length; i++) { writer.Write((ushort)(ind.Key[i] + 1)); //-1 indexed } writer.Write((ushort)0); // Terminator } else { writer.Write(ind.Key); writer.Write((ushort)0xFFFF); // Terminator } } }
/// <summary> /// Saves the data into the given <paramref name="stream"/>. /// </summary> /// <param name="stream">The <see cref="Stream"/> to save the data to.</param> /// <param name="leaveOpen"><c>true</c> to leave <paramref name="stream"/> open after saving the instance. /// </param> public void Save(Stream stream, bool leaveOpen = false, ByteOrder Endianness = ByteOrder.LittleEndian) { using (BinaryDataWriter writer = new BinaryDataWriter(stream, leaveOpen)) { writer.ByteOrder = Endianness; long modelPosition = writer.Position; // Write the header. Offset positionArrayOffset = writer.ReserveOffset(); Offset normalArrayOffset = writer.ReserveOffset(); Offset triangleArrayOffset = writer.ReserveOffset(); Offset octreeOffset = writer.ReserveOffset(); writer.Write(30f); writer.Write(MinCoordinate); writer.Write(CoordinateMask); writer.Write(CoordinateShift); writer.Write(25f); // Write the positions. positionArrayOffset.Satisfy((int)(writer.Position - modelPosition)); writer.Write(Positions.ToArray()); // Write the normals. normalArrayOffset.Satisfy((int)(writer.Position - modelPosition)); writer.Write(Normals.ToArray()); // Write the triangles. triangleArrayOffset.Satisfy((int)(writer.Position - modelPosition)); writer.Write(Faces); // Write the octree. int octreeOffsetValue = (int)(writer.Position - modelPosition); octreeOffset.Satisfy(octreeOffsetValue); // Write the node keys, and compute the correct offsets into the triangle lists or to child nodes. // Nintendo writes child nodes behind the current node, so the children need to be queued. // In this implementation, empty triangle lists point to the same terminator behind the last node. // This could be further optimized by reusing equal parts of lists as Nintendo apparently did it. int emptyListPos = GetNodeCount(ModelOctreeRoots) * sizeof(uint); int triangleListPos = emptyListPos + sizeof(ushort); Queue <ModelOctreeNode[]> queuedNodes = new Queue <ModelOctreeNode[]>(); queuedNodes.Enqueue(ModelOctreeRoots); while (queuedNodes.Count > 0) { ModelOctreeNode[] nodes = queuedNodes.Dequeue(); long offset = writer.Position - modelPosition - octreeOffsetValue; foreach (ModelOctreeNode node in nodes) { if (node.Children == null) { // Node is a leaf and points to triangle index list. int listPos; if (node.TriangleIndices.Count == 0) { listPos = emptyListPos; } else { listPos = triangleListPos; triangleListPos += (node.TriangleIndices.Count + 1) * sizeof(ushort); } node.Key = (uint)ModelOctreeNode.Flags.Values | (uint)(listPos - offset - sizeof(ushort)); } else { // Node is a branch and points to 8 children. node.Key = (uint)(nodes.Length + queuedNodes.Count * 8) * sizeof(uint); queuedNodes.Enqueue(node.Children); } writer.Write(node.Key); } } // Iterate through the nodes again and write their triangle lists now. writer.Write((ushort)0xFFFF); // Terminator for all empty lists. queuedNodes.Enqueue(ModelOctreeRoots); while (queuedNodes.Count > 0) { ModelOctreeNode[] nodes = queuedNodes.Dequeue(); foreach (ModelOctreeNode node in nodes) { if (node.Children == null) { if (node.TriangleIndices.Count > 0) { // Node is a leaf and points to triangle index list. writer.Write(node.TriangleIndices); writer.Write((ushort)0xFFFF); } } else { // Node is a branch and points to 8 children. queuedNodes.Enqueue(node.Children); } } } } }