public static MilkShape Load(BinaryReader br) { var ms = new MilkShape(); var utility = new Utility(br, null, Endianness.Little); // Milkshape is tied to Win32, so definitely little endian // First comes the header (sizeof(ms3d_header_t) == 14) var magic = ReadString(utility, 10); if (magic != Magic) { throw new InvalidOperationException($"Milkshape Load: Bad magic, expected {Magic}, got {magic}"); } var version = utility.ReadU32(); if (version != 4) { throw new InvalidOperationException($"Milkshape Load: Unsupported version, expected 4, got {version}"); } // Then comes the number of vertices var nNumVertices = utility.ReadU16(); // Then come nNumVertices times ms3d_vertex_t structs (sizeof(ms3d_vertex_t) == 15) ms.Vertices = new List <ms3d_vertex_t>(nNumVertices); for (var v = 0; v < nNumVertices; v++) { var vertex = new ms3d_vertex_t(); vertex.Flags = (MilkshapeObjectFlags)utility.ReadByte(); // SELECTED | SELECTED2 | HIDDEN vertex.Position = Vector3.Read(utility); // NOTE: I'm merging the different specs / extended attributes here; will look confusing vertex.BoneIdsAndWeights[0].BoneId = utility.ReadSByte(); vertex.ReferenceCount = utility.ReadByte(); ms.Vertices.Add(vertex); } // Then comes the number of triangles var nNumTriangles = utility.ReadU16(); // 2 bytes // Then come nNumTriangles times ms3d_triangle_t structs (sizeof(ms3d_triangle_t) == 70) ms.Triangles = new List <ms3d_triangle_t>(nNumTriangles); for (var t = 0; t < nNumTriangles; t++) { ms.Triangles.Add(new ms3d_triangle_t { Flags = (MilkshapeObjectFlags)utility.ReadU16(), // SELECTED | SELECTED2 | HIDDEN VertexIndices = utility.ReadU16Ints(3), VertexNormals = new[] { Vector3.Read(utility), Vector3.Read(utility), Vector3.Read(utility) }, TextureCoordinates = ReadTextureCoordinates(utility), SmoothingGroup = utility.ReadByte(), // 1 - 32 GroupIndex = utility.ReadByte() // }); } // Then comes the number of groups var nNumGroups = utility.ReadU16(); // 2 bytes // Then come nNumGroups times groups (the sizeof a group is dynamic, because of triangleIndices is numtriangles long) ms.Groups = new List <ms3d_group_t>(nNumGroups); for (var g = 0; g < nNumGroups; g++) { var group = new ms3d_group_t(); group.Flags = (MilkshapeObjectFlags)utility.ReadByte(); // SELECTED | HIDDEN group.Name = ReadString(utility, 32); var numtriangles = utility.ReadU16(); group.TriangleIndices = utility.ReadU16Ints(numtriangles); group.MaterialIndex = utility.ReadSByte(); ms.Groups.Add(group); } // number of materials var nNumMaterials = utility.ReadU16(); // 2 bytes // Then come nNumMaterials times ms3d_material_t structs (sizeof(ms3d_material_t) == 361) ms.Materials = new List <ms3d_material_t>(nNumMaterials); for (var m = 0; m < nNumMaterials; m++) { ms.Materials.Add(new ms3d_material_t { Name = ReadString(utility, 32), Ambient = utility.ReadFloats(4), Diffuse = utility.ReadFloats(4), Specular = utility.ReadFloats(4), Emissive = utility.ReadFloats(4), Shininess = utility.ReadFloat(), Transparency = utility.ReadFloat(), Mode = utility.ReadSByte(), // NOTE: Examining a file written by MilkShape, I saw garbage beyond // the null terminator of these strings. Harmless, just FYI. Texture = ReadString(utility, 128), Alphamap = ReadString(utility, 128) }); } // save some keyframer data ms.fAnimationFPS = utility.ReadFloat(); ms.fCurrentTime = utility.ReadFloat(); ms.iTotalFrames = utility.ReadI32(); // number of joints var nNumJoints = utility.ReadU16(); // 2 bytes // Then come nNumJoints joints (the size of joints are dynamic, because each joint has a differnt count of keys ms.Joints = new List <ms3d_joint_t>(nNumJoints); for (var j = 0; j < nNumJoints; j++) { var joint = new ms3d_joint_t(); joint.Flags = (MilkshapeObjectFlags)utility.ReadByte(); joint.Name = ReadString(utility, 32); joint.ParentName = ReadString(utility, 32); joint.Rotation = Vector3.Read(utility); joint.Position = Vector3.Read(utility); var numKeyFramesRot = utility.ReadU16(); var numKeyFramesTrans = utility.ReadU16(); joint.KeyFramesRot = new ms3d_keyframe_rot_t[numKeyFramesRot]; for (var r = 0; r < numKeyFramesRot; r++) { joint.KeyFramesRot[r] = new ms3d_keyframe_rot_t { Time = utility.ReadFloat(), Rotation = Vector3.Read(utility) }; } joint.KeyFramesTrans = new ms3d_keyframe_pos_t[numKeyFramesTrans]; for (var t = 0; t < numKeyFramesTrans; t++) { joint.KeyFramesTrans[t] = new ms3d_keyframe_pos_t { Time = utility.ReadFloat(), Position = Vector3.Read(utility) }; } ms.Joints.Add(joint); } try { // subVersion specifying whether comment data exists var subVersion = utility.ReadU32(); if (subVersion == 1) { // Group comments ReadComments(utility, ms.Groups); // Material comments ReadComments(utility, ms.Materials); // Joint comments ReadComments(utility, ms.Joints); // Then comes the number of model comments, which is always 0 or 1 ReadComments(utility, new[] { ms }, true); } // subVersion specifying whether extended vertex data exists and to what extent subVersion = utility.ReadU32(); if (subVersion > 0) { // Then comes nNumVertices times ms3d_vertex_ex_t structs (sizeof(ms3d_vertex_ex_t) == 14) // NOTE: I'm merging extended vertex data spec stuff from this mess: // sbyte[] boneIds = new sbyte[3]; // index of joint or -1, if -1, then that weight is ignored, since subVersion 1 // byte[] weights = new byte[3]; // vertex weight ranging from 0 - 100, last weight is computed by 1.0 - sum(all weights), since subVersion 1 // // weight[0] is the weight for boneId in ms3d_vertex_t // // weight[1] is the weight for boneIds[0] // // weight[2] is the weight for boneIds[1] // // 1.0f - weight[0] - weight[1] - weight[2] is the weight for boneIds[2] // NOTE: "extra" depends in subVersion; 1 element if 2, 2 elements if 3 // uint[] extra = new uint[2]; // vertex extra, which can be used as color or anything else, since subVersion 3 for (var v = 0; v < nNumVertices; v++) { var vertex = ms.Vertices[v]; // These are ADDITIONAL bone Ids vertex.BoneIdsAndWeights[1].BoneId = utility.ReadSByte(); vertex.BoneIdsAndWeights[2].BoneId = utility.ReadSByte(); vertex.BoneIdsAndWeights[3].BoneId = utility.ReadSByte(); // These are WEIGHTS which were previously unavailable vertex.BoneIdsAndWeights[0].Weight = utility.ReadByte(); vertex.BoneIdsAndWeights[1].Weight = utility.ReadByte(); vertex.BoneIdsAndWeights[2].Weight = utility.ReadByte(); // Final bone weight is computed -- NOTE, spec says 1.0 - [...], but I think it meant 100 vertex.BoneIdsAndWeights[3].Weight = (byte)(100 - vertex.BoneIdsAndWeights[0].Weight - vertex.BoneIdsAndWeights[1].Weight - vertex.BoneIdsAndWeights[2].Weight); // How much "extra" data is here depends on subVersion... var extraCount = subVersion - 1; vertex.Extra = utility.ReadUInts(extraCount); } } // subVersion specifying whether joints have color subVersion = utility.ReadU32(); if (subVersion == 1) { for (var j = 0; j < nNumJoints; j++) { var joint = ms.Joints[j]; joint.Color = new ColorFloat { R = utility.ReadFloat(), G = utility.ReadFloat(), B = utility.ReadFloat(), A = 1.0f // Not stored }; } } // subVersion specifying whether model extended data exists subVersion = utility.ReadU32(); if (subVersion == 1) { ms.jointSize = utility.ReadFloat(); // joint size, since subVersion == 1 ms.transparencyMode = utility.ReadI32(); // 0 = simple, 1 = depth buffered with alpha ref, 2 = depth sorted triangles, since subVersion == 1 ms.alphaRef = utility.ReadFloat(); // alpha reference value for transparencyMode = 1, since subVersion == 1 } } catch (IndexOutOfRangeException) { // This is a dirty hack because any file that doesn't have the extended data // will throw IndexOutOfRangeException but I really should be doing EOF checks } return(ms); }
public static MilkShape ToMilkShape(SimplifiedModel sm) { var milkShape = new MilkShape(); // Convert the skeleton var bones = sm.Bones.Select(b => new ms3d_joint_t { Name = b.Name, ParentName = b.ParentName, Rotation = b.Rotation, Position = b.Translation, KeyFramesRot = new ms3d_keyframe_rot_t[0], KeyFramesTrans = new ms3d_keyframe_pos_t[0] }); var allVertices = new List <ms3d_vertex_t>(); var allTriangles = new List <ms3d_triangle_t>(); var allMaterials = new List <ms3d_material_t>(); var allGroups = new List <ms3d_group_t>(); foreach (var mesh in sm.Meshes) { // Get current vertex offset as we're accumulating them var vertexMeshOffset = allVertices.Count; // Get current triangle offset as we're grouping them var triangleOffset = allTriangles.Count; // Vertices belonging to this mesh var vertices = mesh.Vertices .Select(v => new ms3d_vertex_t { Position = TransformPositionByBone(v, v.BoneIndices, sm.Bones, false), BoneIdsAndWeights = GetBoneIndiciesAndWeights(v.BoneIndices, v.Weights), }); allVertices.AddRange(vertices); // Triangles belonging to this mesh var triangles = mesh.Triangles .Select(t => new ms3d_triangle_t { VertexIndices = new ushort[] { (ushort)(vertexMeshOffset + t.v1), (ushort)(vertexMeshOffset + t.v2), (ushort)(vertexMeshOffset + t.v3) }, VertexNormals = new Vector3[] { mesh.Vertices[t.v1].Normal, mesh.Vertices[t.v2].Normal, mesh.Vertices[t.v3].Normal }, TextureCoordinates = new Vector2[] { // NOTE: Textures are "upside-down", so this reverses them... make sure to undo that when saving... new Vector2(mesh.Vertices[t.v1].TexCoord.X, 1.0f - mesh.Vertices[t.v1].TexCoord.Y), new Vector2(mesh.Vertices[t.v2].TexCoord.X, 1.0f - mesh.Vertices[t.v2].TexCoord.Y), new Vector2(mesh.Vertices[t.v3].TexCoord.X, 1.0f - mesh.Vertices[t.v3].TexCoord.Y) }, GroupIndex = (byte)allGroups.Count }); allTriangles.AddRange(triangles); // Generate materials from the meshes sbyte materialIndex = -1; if (mesh.Texture != null) { materialIndex = (sbyte)allMaterials.Count; allMaterials.Add(new ms3d_material_t { Name = mesh.Texture.Name, Texture = mesh.Texture.Name + ".png" }); } // We're going to make the "mesh" into a "group" in MilkShape speak var group = new ms3d_group_t { Name = $"mesh{allGroups.Count}", TriangleIndices = Enumerable.Range(triangleOffset, triangles.Count()).Select(i => (ushort)i).ToArray(), MaterialIndex = materialIndex }; allGroups.Add(group); } milkShape.Vertices.AddRange(allVertices); milkShape.Triangles.AddRange(allTriangles); milkShape.Groups.AddRange(allGroups); milkShape.Materials.AddRange(allMaterials); milkShape.Joints.AddRange(bones); return(milkShape); }