Example #1
0
        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);
        }