/// <summary> /// Converts internal Mesh fields into Halo 2 compatible resource format /// </summary> /// <param name="resource_out">returns with array of Resource-meta structs represnting the blocks in the resource</param> /// <returns>array of bytes which holds the serialized Halo 2 resource</returns> public byte[] Serialize(out DResource[] resource_out, out DCompressionRanges compression_ranges, DCompressionRanges input_compression = null) { /* Intent: Write out the this Model instance data into a format that * the halo 2 version of blam! engine can use. * The resources that we will be focusing on are ShaderGroups, Indices, * Vertex position, texcoord, tangent space vectors, and a simple bonemap.*/ Log.Info(@"Entering Model.Serialize()"); if (input_compression == null) { input_compression = GenerateCompressionData(); } // Check that we have compression data available before continuing compression_ranges = input_compression; DResource[] resource = new DResource[7]; // Create resource defintion array MemoryStream buffer = new MemoryStream(); BinaryWriter bin = new BinaryWriter(buffer); // BinaryWriter Log.Info(string.Format(@"Writing header_tag @{0}", bin.BaseStream.Position)); bin.WriteFourCC("blkh"); // Write the header_tag value bin.Write(0); // [uint] resource_data_size (reserve) // * Begin resource_header // size: 112 Log.Info(string.Format(@"Writing shader_groups_count = {0} @{1}", Primitives.Length, bin.BaseStream.Position)); bin.Write(Primitives.Length); // * 0x00: shader_groups_count; bin.Write(new byte[28]); // * 0x08: some unused thing... count. Log.Info(string.Format(@"Writing indices_count = {0} @{1}", Indices.Length, bin.BaseStream.Position)); bin.Write(Indices.Length); // * 0x20: indices_count; bin.Write(new byte[20]); bin.Write(3); // * 0x38: vertex_resource_count; //special bin.Write(new byte[40]); bin.Write(1); // * 0x64: bone_map_count; bin.Write(new byte[8]); Log.Info(string.Format(@"Resource data_start_offset = {0}", bin.BaseStream.Position)); var resource_data_start_offset = bin.BaseStream.Position; // This is the offset to which all DResource block_offsets are written from bin.WriteFourCC("rsrc"); // shader_group resource begin resource[0] = new DResource(0, 72, Primitives.Length * 72, (int)(bin.BaseStream.Position - resource_data_start_offset)); foreach (var group in Primitives) bin.Write(group); // shader_group data bin.WriteFourCC("rsrc"); // indices resource begin resource[1] = new DResource(32, sizeof(ushort), sizeof(ushort) * this.Indices.Length, (int)(bin.BaseStream.Position - resource_data_start_offset)); foreach (ushort index in this.Indices) // write each index ushort bin.Write(index); // pad to word boundary bin.WritePadding(4); bin.WriteFourCC("rsrc"); // vertex_data_header? resource[2] = new DResource(56, 32, 32 * 3, (int)(bin.BaseStream.Position - resource_data_start_offset)); bin.Write(VERTEX_RESOURCE_HEADER_DATA); // laziness TODO: write out proper headers here to allow for other types bin.WriteFourCC("rsrc"); resource[3] = new DResource(56, 0, this.Coordinates.Length * sizeof(ushort) * 3, (int)(bin.BaseStream.Position - resource_data_start_offset), true); { foreach (var vertex in this.Coordinates) { bin.Write(Deflate(input_compression.X, vertex.X)); bin.Write(Deflate(input_compression.Y, vertex.Y)); bin.Write(Deflate(input_compression.Z, vertex.Z)); } bin.WritePadding(4); } bin.WriteFourCC("rsrc"); resource[4] = new DResource(56, 1, this.TextureCoordinates.Length * sizeof(ushort) * 2, (int)(bin.BaseStream.Position - resource_data_start_offset), true); { foreach (var vertex in this.TextureCoordinates) { bin.Write(Deflate(input_compression.U, vertex.X)); bin.Write(Deflate(input_compression.V, vertex.Y)); // flip this still? } } bin.WriteFourCC("rsrc"); resource[5] = new DResource(56, 2, this.Normals.Length * sizeof(uint) * 3, (int)(bin.BaseStream.Position - resource_data_start_offset), true); for(int i = 0; i < this.Normals.Length;++i) { bin.Write((uint)(Vector3t)Normals[i]); //cast to vector3t is destructive... bin.Write((uint)(Vector3t)Tangents[i]); bin.Write((uint)(Vector3t)Bitangents[i]); } bin.WriteFourCC("rsrc"); resource[6] = new DResource(100, 1, 1, (int)(bin.BaseStream.Position - resource_data_start_offset)); bin.Write(0); // default bone-map (no bones) bin.WriteFourCC("blkf"); int resource_size = (int)bin.BaseStream.Position; bin.Seek(4, SeekOrigin.Begin); bin.Write(resource_size - 124); // debug dump #if DEBUG try { using (var file = File.OpenWrite(@"D:\halo_2\model_raw.bin")) { file.Write(buffer.ToArray(), 0, (int)buffer.Length); } } catch { } #endif // end debug dump // 2. create a sections meta file for this, a bounding box, heck a whole mesh, why not. resource_out = resource; return buffer.ToArray(); }
internal DCompressionRanges GenerateCompressionData() { DCompressionRanges compression = new DCompressionRanges(); compression.X = new Range(Coordinates[0].X, Coordinates[0].X); compression.Y = new Range(Coordinates[0].Y, Coordinates[0].Y); compression.Z = new Range(Coordinates[0].Z, Coordinates[0].Z); compression.U = new Range(TextureCoordinates[0].X, TextureCoordinates[0].X); compression.V = new Range(TextureCoordinates[0].Y, TextureCoordinates[0].Y); for (int i = 0; i < Coordinates.Length; ++i) { compression.X = Range.Include(compression.X, Coordinates[i].X); compression.Y = Range.Include(compression.Y, Coordinates[i].Y); compression.Z = Range.Include(compression.Z, Coordinates[i].Z); compression.U = Range.Include(compression.U, TextureCoordinates[i].X); compression.V = Range.Include(compression.V, TextureCoordinates[i].Y); } compression.Expand(0.0001f); return compression; }
/// <summary> /// Deserializes a Halo 2 formatted raw-resource block and initializes the Mesh object from it /// </summary> /// <param name="raw_data"></param> /// <param name="raw_resources"></param> /// <param name="compression_ranges"></param> /// <returns></returns> protected bool Load(byte[] raw_data, IEnumerable<DResource> raw_resources, DCompressionRanges compression_ranges, int header_size) { int[] vertex_resource_sizes = new int[0]; VertexResource[] vertex_resource_types = new VertexResource[0]; int stream_length = BitConverter.ToInt32(raw_data, 4); int first_address = 4 + 4 + header_size; /* 0:header_four_cc : "blkh" * 4:total_resource_data_length * 8:header_fields */ MemoryStream resource_stream = new MemoryStream(raw_data, first_address, stream_length, false); /* Create a stream containing the resource_data * from the raw_data byte array * */ BinaryReader binary_reader = new BinaryReader(resource_stream); /* Intent: switch each raw resource and load the mesh-related data from the resource_stream * */ foreach (var resource in raw_resources) { if (resource.first_ != 0) continue; // skip the vertex resources and other wierd resources int count = BitConverter.ToInt32(raw_data, 8 + resource.header_address); // get the header_value (which is 'count' of blocks for this resource)... resource_stream.Position = resource.resource_offset; // move stream to offset of resource data switch (resource.header_address) { #region Shader Groups // case: Shader Groups case 0: // initialize the shader_groups array; Primitives = new MeshPrimitive[count]; // read each block for (int i = 0; i < count; i++) { Primitives[i] = binary_reader.ReadDefinition<MeshPrimitive>(); } break; #endregion #region Indices case 32: Indices = new ushort[count]; for (int i = 0; i < count; i++) { if (resource.data_size__or__first_index != sizeof(short)) throw new Exception(":D"); Indices[i] = binary_reader.ReadUInt16(); } break; #endregion #region Vertex Resources pass-1 case 56: /* Process the first resource at offset 56 which should be the resource describing all * other vertex-data resources after it. Load from this resource the size and type enum * of the vertex_data resources. * */ switch (resource.first_) { case 0: vertex_resource_sizes = new int[count]; vertex_resource_types = new VertexResource[count]; for (int i = 0; i < count; i++) { byte[] buffer = binary_reader.ReadBytes(resource.data_size__or__first_index); vertex_resource_types[i] = (VertexResource)((buffer[0] << 8) | buffer[1]); vertex_resource_sizes[i] = buffer[1]; } break; } break; #endregion case 100: Nodes = binary_reader.ReadBytes(count); break; } } var vertex_resources = raw_resources.Where(x => x.first_ == 2).ToArray(); /* Intent: process vertex resources by type and load additional bone information if present * */ int vertex_count = vertex_resources[0].resource_length / vertex_resource_sizes[0]; #region Vertex Data Field Initialization /* Intent: intialize all fields to empty arrays to prevent the unintialized value compiler error, * switch through all the vertex_resources we are going to process and create an array to hold all * the members we will be reading. */ this.Coordinates = new Vector3[0]; this.TextureCoordinates = new Vector2[0]; this.Normals = new Vector3[0]; this.Tangents = new Vector3[0]; this.Bitangents = new Vector3[0]; this.VertexWeights = new VertexWeight[0]; foreach (var vertex_resource in vertex_resources) /* Switch through all the loaded * vertex_resources */ { switch (vertex_resource_types[vertex_resource.data_size__or__first_index]) { case VertexResource.coordinate_with_skinned_node: case VertexResource.coordinate_with_rigid_node: /* if the resource contains skeleton nodes: * initialize the VertexWeights array*/ this.VertexWeights = new VertexWeight[vertex_count]; goto case VertexResource.coordinate_compressed; /* also load a vertex coordinate array */ case VertexResource.coordinate_float: case VertexResource.coordinate_compressed: /* if the resource contains vertex coordinates: * initialize the VertexCoordinates array */ this.Coordinates = new Vector3[vertex_count]; break; case VertexResource.texture_coordinate_compressed: case VertexResource.texture_coordinate_float_pc: case VertexResource.texture_coordinate_float: /* if the resource contains texture coordinates: * initialize the TextureCoordinates array */ this.TextureCoordinates = new Vector2[vertex_count]; break; case VertexResource.tangent_space_unit_vectors_compressed: /* if the resource contains tangent-space data: * initialize the TBN vector arrays */ case VertexResource.tangent_space_unit_vectors_float: this.Normals = new Vector3[vertex_count]; this.Tangents = new Vector3[vertex_count]; this.Bitangents = new Vector3[vertex_count]; break; } } #endregion #region Vertex Data Loading foreach (var vertex_resource in vertex_resources) { binary_reader.BaseStream.Position = vertex_resource.resource_offset; var buffer = binary_reader.ReadBytes(vertex_resource.resource_length); var stride = vertex_resource_sizes[vertex_resource.data_size__or__first_index]; for (int i = 0; i < vertex_count; ++i) { switch (vertex_resource_types[vertex_resource.data_size__or__first_index]) { case VertexResource.coordinate_float: this.Coordinates[i] = new Vector3( BitConverter.ToSingle(buffer, i * stride), BitConverter.ToSingle(buffer, (i * stride) + 4), BitConverter.ToSingle(buffer, (i * stride) + 8)); break; case VertexResource.coordinate_compressed: this.Coordinates[i] = new Vector3( BitConverter.ToInt16(buffer, i * stride), BitConverter.ToInt16(buffer, (i * stride) + 2), BitConverter.ToInt16(buffer, (i * stride) + 4)); this.Coordinates[i].X = Inflate(this.Coordinates[i].X, compression_ranges.X); this.Coordinates[i].Y = Inflate(this.Coordinates[i].Y, compression_ranges.Y); this.Coordinates[i].Z = Inflate(this.Coordinates[i].Z, compression_ranges.Z); break; case VertexResource.coordinate_with_rigid_node: VertexWeights[i] = new VertexWeight(buffer[(i * stride) + 6]); goto case VertexResource.coordinate_compressed; case VertexResource.coordinate_with_skinned_node: var bone0 = buffer[(i * stride) + 6]; var bone1 = buffer[(i * stride) + 7]; var weight0 = (float)buffer[(i * stride) + 9] / (float)byte.MaxValue; var weight1 = (float)buffer[(i * stride) + 10] / (float)byte.MaxValue; VertexWeights[i] = new VertexWeight() { Bone0 = bone0, Bone1 = bone1, Bone0_weight = weight0, Bone1_weight = weight1 }; goto case VertexResource.coordinate_compressed; case VertexResource.texture_coordinate_float: case VertexResource.texture_coordinate_float_pc: this.TextureCoordinates[i] = new Vector2( BitConverter.ToSingle(buffer, i * stride), BitConverter.ToSingle(buffer, (i * stride) + 4)); break; case VertexResource.texture_coordinate_compressed: this.TextureCoordinates[i] = new Vector2( BitConverter.ToInt16(buffer, i * stride), BitConverter.ToInt16(buffer, (i * stride) + 2)); this.TextureCoordinates[i].X = Inflate(this.TextureCoordinates[i].X, compression_ranges.U); this.TextureCoordinates[i].Y = Inflate(this.TextureCoordinates[i].Y, compression_ranges.V); break; case VertexResource.tangent_space_unit_vectors_compressed: this.Normals[i] = (Vector3)new Vector3t(BitConverter.ToUInt32(buffer, i * stride)); this.Tangents[i] = (Vector3)new Vector3t(BitConverter.ToUInt32(buffer, (i * stride) + 4)); this.Bitangents[i] = (Vector3)new Vector3t(BitConverter.ToUInt32(buffer, i * (stride) + 8)); break; case VertexResource.tangent_space_unit_vectors_float: this.Normals[i] = new Vector3( BitConverter.ToSingle(buffer, 0 + i * stride), BitConverter.ToSingle(buffer, 4 + i * stride), BitConverter.ToSingle(buffer, 8 + i * stride)); this.Tangents[i] = new Vector3( BitConverter.ToSingle(buffer, 12 + i * stride), BitConverter.ToSingle(buffer, 16 + i * stride), BitConverter.ToSingle(buffer, 20 + i * stride)); this.Bitangents[i] = new Vector3( BitConverter.ToSingle(buffer, 24 + i * stride), BitConverter.ToSingle(buffer, 28 + i * stride), BitConverter.ToSingle(buffer, 32 + i * stride)); break; } } } #endregion return true; }