public static BoneKeyframe AssembleBones(StudioMdlWriter meshBuilder, BasePart rootPart)
        {
            Rbx2Source.Print("Building Skeleton...");

            BoneKeyframe kf = new BoneKeyframe();

            List <Bone> bones = kf.Bones;
            List <Node> nodes = meshBuilder.Nodes;

            Bone rootBone = new Bone(rootPart.Name, rootPart);

            rootBone.C0           = new CFrame();
            rootBone.IsAvatarBone = true;
            bones.Add(rootBone);

            Node rootNode = rootBone.Node;

            rootNode.NodeIndex = 0;
            nodes.Add(rootNode);

            // Assemble the base rig.
            BoneAssemblePrep prep = new BoneAssemblePrep(ref bones, ref nodes);

            GenerateBones(prep, rootPart.GetChildrenOfClass <Attachment>());

            // Assemble the accessories.
            prep.AllowNonRigs = true;
            GenerateBones(prep, prep.NonRigs.ToArray());

            // Apply the rig cframe data.
            ApplyBoneCFrames(rootPart);
            meshBuilder.Skeleton.Add(kf);

            return(kf);
        }
        public StudioMdlWriter AssembleModel(Folder characterAssets, AvatarScale scale, bool collisionModel = false)
        {
            StudioMdlWriter meshBuilder = new StudioMdlWriter();

            // Build Character
            Folder import   = RBXM.LoadFromAsset(R6AssemblyAsset);
            Folder assembly = import.FindFirstChild <Folder>("ASSEMBLY");

            assembly.Parent = characterAssets;

            BasePart head  = assembly.FindFirstChild <BasePart>("Head");
            BasePart torso = assembly.FindFirstChild <BasePart>("Torso");

            torso.CFrame = new CFrame();

            foreach (Instance asset in characterAssets.GetChildren())
            {
                if (asset.IsA("CharacterMesh") && !collisionModel)
                {
                    CharacterMesh characterMesh = (CharacterMesh)asset;
                    string        limbName      = LimbMatcher[characterMesh.BodyPart];

                    MeshPart limb = assembly.FindFirstChild <MeshPart>(limbName);
                    if (limb != null)
                    {
                        limb.MeshId = "rbxassetid://" + characterMesh.MeshId;
                    }
                }
                else if (asset.IsA("Accoutrement") && !collisionModel)
                {
                    PrepareAccessory(asset, assembly);
                }
                else if (asset.IsA("DataModelMesh"))
                {
                    OverwriteHead(asset, head);
                }
            }

            BoneKeyframe keyframe = AssembleBones(meshBuilder, torso);

            foreach (Bone bone in keyframe.Bones)
            {
                BuildAvatarGeometry(meshBuilder, bone);
            }

            return(meshBuilder);
        }
        public StudioMdlWriter AssembleModel(Folder characterAssets, AvatarScale scale, bool collisionModel = false)
        {
            Contract.Requires(characterAssets != null && scale != null);
            StudioMdlWriter meshBuilder = new StudioMdlWriter();

            // Build Character
            var    import   = R6AssemblyAsset.OpenAsModel();
            Folder assembly = import.FindFirstChild <Folder>("ASSEMBLY");

            assembly.Parent = characterAssets;

            BasePart head  = assembly.FindFirstChild <BasePart>("Head");
            BasePart torso = assembly.FindFirstChild <BasePart>("Torso");

            torso.CFrame = new CFrame();

            foreach (Instance asset in characterAssets.GetChildren())
            {
                if (asset is CharacterMesh && !collisionModel)
                {
                    var    characterMesh = asset as CharacterMesh;
                    string limbName      = LimbMatcher[characterMesh.BodyPart];

                    var limb = assembly.FindFirstChild <MeshPart>(limbName);
                    limb.MeshId = "rbxassetid://" + characterMesh.MeshId;
                }
                else if (asset is Accoutrement && !collisionModel)
                {
                    PrepareAccessory(asset, assembly);
                }
                else if (asset is DataModelMesh)
                {
                    OverwriteHead(asset as DataModelMesh, head);
                }
            }

            BoneKeyframe keyframe = AssembleBones(meshBuilder, torso);

            foreach (StudioBone bone in keyframe.Bones)
            {
                BuildAvatarGeometry(meshBuilder, bone);
            }

            return(meshBuilder);
        }
示例#4
0
        /// <summary>
        /// Reads in an XNB stream and converts it to a ModelInfo object
        /// </summary>
        /// <param name="input">The stream from which the data will be read</param>
        /// <param name="existingInstance">Not used</param>
        /// <returns>The unserialized ModelAnimationCollection object</returns>
        protected override AnimationInfoCollection Read(ContentReader input, AnimationInfoCollection existingInstance)
        {
            AnimationInfoCollection dict = new AnimationInfoCollection();
            int numAnimations            = input.ReadInt32();

            // Read all the animations
            for (int i = 0; i < numAnimations; i++)
            {
                string animationName     = input.ReadString();
                int    numBoneAnimations = input.ReadInt32();

                List <BoneKeyframeCollection> animList
                    = new List <BoneKeyframeCollection>();

                // Read all the animation tracks for the current animation
                for (int j = 0; j < numBoneAnimations; j++)
                {
                    string boneName     = input.ReadString();
                    int    numKeyFrames = input.ReadInt32();
                    List <BoneKeyframe> boneAnimationList = new List <BoneKeyframe>();

                    // Read all the keyframes for the current animation track
                    for (int k = 0; k < numKeyFrames; k++)
                    {
                        BoneKeyframe frame = new BoneKeyframe(
                            input.ReadMatrix(),
                            input.ReadInt64());
                        boneAnimationList.Add(frame);
                    }
                    BoneKeyframeCollection boneAnimation =
                        new BoneKeyframeCollection(boneName, boneAnimationList);

                    animList.Add(boneAnimation);
                }
                AnimationInfo anim = new AnimationInfo(animationName,
                                                       new AnimationChannelCollection(animList));
                dict.Add(animationName, anim);
            }
            return(dict);
        }
示例#5
0
            public BoneKeyframe GetOrCreateBoneKeyFrame(string boneName, int frame)
            {
                Dictionary <int, BoneKeyframe> framesForBone;

                if (frame > Length)
                {
                    Length = frame;
                }
                if (!BoneMotions.TryGetValue(boneName, out framesForBone))
                {
                    framesForBone = new Dictionary <int, BoneKeyframe>();
                    BoneMotions.Add(boneName, framesForBone);
                }
                BoneKeyframe boneKeyframe;

                if (!framesForBone.TryGetValue(frame, out boneKeyframe))
                {
                    boneKeyframe = new BoneKeyframe();
                    framesForBone.Add(frame, boneKeyframe);
                }
                return(boneKeyframe);
            }
        public static string Assemble(KeyframeSequence sequence, List <Bone> rig)
        {
            StudioMdlWriter animWriter = new StudioMdlWriter();
            List <Keyframe> keyframes  = new List <Keyframe>();

            Dictionary <string, Bone> boneLookup = new Dictionary <string, Bone>();
            List <Node> nodes = animWriter.Nodes;

            foreach (Bone bone in rig)
            {
                Node node = bone.Node;
                if (node != null)
                {
                    nodes.Add(node);
                    string boneName = node.Name;
                    if (!boneLookup.ContainsKey(boneName))
                    {
                        boneLookup.Add(boneName, bone);
                    }
                }
            }

            foreach (Keyframe kf in sequence.GetChildrenOfClass <Keyframe>())
            {
                Pose rootPart = kf.FindFirstChild <Pose>("HumanoidRootPart");

                if (rootPart != null)
                {
                    // We don't need the rootpart for this.
                    foreach (Pose subPose in rootPart.GetChildrenOfClass <Pose>())
                    {
                        subPose.Parent = kf;
                    }

                    rootPart.Destroy();
                }

                kf.Time /= sequence.TimeScale;
                keyframes.Add(kf);
            }

            keyframes.Sort(0, keyframes.Count, sorter);

            Keyframe lastKeyframe = keyframes[keyframes.Count - 1];
            float    fLength      = lastKeyframe.Time;
            int      frameCount   = ToFrameRate(fLength);

            // Animations in source are kinda dumb, because theres no frame interpolation.
            // I have to account for every single CFrame for every single frame.

            Dictionary <int, Dictionary <string, Pose> > keyframeMap = new Dictionary <int, Dictionary <string, Pose> >();

            for (int i = 0; i <= frameCount; i++)
            {
                keyframeMap[i] = new Dictionary <string, Pose>();
            }

            foreach (Keyframe kf in keyframes)
            {
                int frame = ToFrameRate(kf.Time);
                Dictionary <string, Pose> poseMap = keyframeMap[frame];
                List <Pose> poses = GatherPoses(kf);
                foreach (Pose pose in poses)
                {
                    poseMap[pose.Name] = pose;
                }
            }

            List <BoneKeyframe> boneKeyframes = animWriter.Skeleton;

            Keyframe baseFrame = keyframes[0];

            for (int i = 0; i < frameCount; i++)
            {
                BoneKeyframe frame = new BoneKeyframe();
                frame.Time = i;
                if (sequence.AvatarType == AvatarType.R15)
                {
                    frame.DeltaSequence = true;
                    frame.BaseRig       = rig;
                }
                List <Bone> bones = frame.Bones;
                foreach (Node node in nodes)
                {
                    PosePair closestPoses = GetClosestPoses(keyframeMap, i, node.Name);
                    float    current      = i;
                    float    min          = closestPoses.Min.Frame;
                    float    max          = closestPoses.Max.Frame;
                    float    alpha        = (min == max ? 0 : (current - min) / (max - min));
                    Pose     pose0        = closestPoses.Min.Pose;
                    Pose     pose1        = closestPoses.Max.Pose;
                    float    weight       = EasingUtil.GetEasing(pose1.PoseEasingStyle, pose1.PoseEasingDirection, 1 - alpha);

                    CFrame lastCFrame = pose0.CFrame;
                    CFrame nextCFrame = pose1.CFrame;

                    Bone   baseBone = boneLookup[node.Name];
                    CFrame c0       = baseBone.C0;
                    CFrame c1       = baseBone.C1;

                    CFrame interp = lastCFrame.lerp(nextCFrame, weight);
                    // some ugly manual fixes.
                    // todo: make this unnecessary :(
                    if (sequence.AvatarType == AvatarType.R6)
                    {
                        Vector3 pos = interp.p;
                        CFrame  rot = interp - pos;
                        if (node.Name == "Torso")
                        {
                            float[] ang = interp.toEulerAnglesXYZ();
                            rot = CFrame.Angles(ang[0], ang[2], ang[1]);
                            pos = new Vector3(pos.x, pos.z, pos.y);
                        }
                        else if (node.Name.StartsWith("Right"))
                        {
                            pos *= new Vector3(-1, 1, 1);
                        }

                        if (node.Name.Contains("Arm") || node.Name.Contains("Leg"))
                        {
                            pos = new Vector3(pos.z, pos.y, pos.x);
                        }

                        if (sequence.Name == "Climb" && node.Name.Contains("Leg")) // https://www.youtube.com/watch?v=vfJ7DqyDl9w
                        {
                            pos += new Vector3(-.1f, 0, 0);
                        }

                        interp = new CFrame(pos) * rot;
                    }
                    else if (sequence.AvatarType == AvatarType.R15)
                    {
                        if (node.Name.Contains("UpperArm"))
                        {
                            Vector3 pos = interp.p;
                            CFrame  rot = interp - pos;
                            float[] ang = rot.toEulerAnglesXYZ();
                            if (sequence.Name == "Climb" || sequence.Name == "Swim")
                            {
                                rot = CFrame.Angles(ang[0], -ang[2], -ang[1]);
                            }

                            interp = new CFrame(pos) * rot;
                        }
                    }

                    Bone bone = new Bone(node.Name, i, interp);
                    bone.Node = node;
                    bones.Add(bone);
                }
                boneKeyframes.Add(frame);
            }

            return(animWriter.BuildFile());
        }
        public StudioMdlWriter AssembleModel(Folder characterAssets, AvatarScale scale)
        {
            StudioMdlWriter meshBuilder = new StudioMdlWriter();

            // Build Character
            Folder import   = RBXM.LoadFromAsset(R15AssemblyAsset);
            Folder assembly = import.FindFirstChild <Folder>("ASSEMBLY");

            assembly.Parent = characterAssets;

            Part    head        = assembly.FindFirstChild <Part>("Head");
            Vector3 avatarScale = GetAvatarScale(scale);

            foreach (Instance asset in characterAssets.GetChildren())
            {
                if (asset.IsA("Part"))
                {
                    Part existing = assembly.FindFirstChild <Part>(asset.Name);
                    if (existing != null)
                    {
                        existing.Destroy();
                    }

                    asset.Parent = assembly;
                }
                else if (asset.IsA("Accoutrement"))
                {
                    PrepareAccessory(asset, assembly);
                }
                else if (asset.IsA("DataModelMesh"))
                {
                    OverwriteHead(asset, head);
                }
            }

            // Avatar Scaling

            foreach (Part part in assembly.GetChildrenOfClass <Part>())
            {
                Limb limb = GetLimb(part);
                if (limb != Limb.Unknown)
                {
                    part.Size *= avatarScale;

                    foreach (Attachment attachment in part.GetChildrenOfClass <Attachment>())
                    {
                        attachment.CFrame = CFrame.Scale(attachment.CFrame, avatarScale);
                    }
                }
            }

            Part torso = assembly.FindFirstChild <Part>("LowerTorso");

            torso.CFrame = new CFrame();

            BoneKeyframe keyframe = AssembleBones(meshBuilder, torso);
            List <Bone>  bones    = keyframe.Bones;

            // Build File Data.
            Rbx2Source.Print("Building Geometry...");
            Rbx2Source.IncrementStack();
            foreach (Bone bone in bones)
            {
                BuildAvatarGeometry(meshBuilder, bone);
            }

            Rbx2Source.DecrementStack();
            return(meshBuilder);
        }
示例#8
0
        public static string Assemble(KeyframeSequence sequence, List <Bone> rig)
        {
            StudioMdlWriter animWriter = new StudioMdlWriter();
            List <Keyframe> keyframes  = new List <Keyframe>();

            var boneLookup = new Dictionary <string, Bone>();
            var nodes      = animWriter.Nodes;

            foreach (Bone bone in rig)
            {
                Node node = bone.Node;

                if (node != null)
                {
                    string boneName = node.Name;

                    if (!boneLookup.ContainsKey(boneName))
                    {
                        boneLookup.Add(boneName, bone);
                    }

                    nodes.Add(node);
                }
            }

            foreach (Keyframe kf in sequence.GetChildrenOfClass <Keyframe>())
            {
                Pose rootPart = kf.FindFirstChild <Pose>("HumanoidRootPart");

                if (rootPart != null)
                {
                    // We don't need the rootpart for this.
                    foreach (Pose subPose in rootPart.GetChildrenOfClass <Pose>())
                    {
                        subPose.Parent = kf;
                    }

                    rootPart.Destroy();
                }

                kf.Time /= sequence.TimeScale;
                keyframes.Add(kf);
            }

            keyframes.Sort(0, keyframes.Count, sorter);

            Keyframe lastKeyframe = keyframes[keyframes.Count - 1];

            float fLength    = lastKeyframe.Time;
            int   frameCount = ToFrameRate(fLength);

            // As far as I can tell, models in Source require you to store poses for every
            // single frame, so I need to fill in the gaps with interpolated pose CFrames.

            var keyframeMap = new Dictionary <int, Dictionary <string, Pose> >();

            foreach (Keyframe kf in keyframes)
            {
                int frame = ToFrameRate(kf.Time);
                var poses = GatherPoses(kf);

                var poseMap = poses.ToDictionary(pose => pose.Name);
                keyframeMap[frame] = poseMap;
            }

            // Make sure there are no holes in the data.
            for (int i = 0; i < frameCount; i++)
            {
                if (!keyframeMap.ContainsKey(i))
                {
                    var emptyState = new Dictionary <string, Pose>();
                    keyframeMap.Add(i, emptyState);
                }
            }

            List <BoneKeyframe> boneKeyframes = animWriter.Skeleton;

            for (int i = 0; i < frameCount; i++)
            {
                BoneKeyframe frame = new BoneKeyframe(i);
                List <Bone>  bones = frame.Bones;

                if (sequence.AvatarType == AvatarType.R15)
                {
                    frame.BaseRig       = rig;
                    frame.DeltaSequence = true;
                }

                foreach (Node node in nodes)
                {
                    PosePair closestPoses = GetClosestPoses(keyframeMap, i, node.Name);

                    float min = closestPoses.Min.Frame;
                    float max = closestPoses.Max.Frame;

                    float alpha = (min == max ? 0 : (i - min) / (max - min));

                    Pose pose0 = closestPoses.Min.Pose;
                    Pose pose1 = closestPoses.Max.Pose;

                    CFrame lastCFrame = pose0.CFrame;
                    CFrame nextCFrame = pose1.CFrame;

                    Bone   baseBone = boneLookup[node.Name];
                    CFrame interp   = lastCFrame.Lerp(nextCFrame, alpha);

                    // Make some patches to the interpolation offsets. Unfortunately I can't
                    // identify any single fix that I can apply to each joint, so I have to get crafty.
                    // At some point in the future, I want to find a more practical solution for this problem,
                    // but it is extremely difficult to isolate if any single solution exists.

                    if (sequence.AvatarType == AvatarType.R6)
                    {
                        Vector3 pos = interp.Position;
                        CFrame  rot = interp - pos;

                        if (node.Name == "Torso")
                        {
                            // Flip the YZ axis of the Torso.
                            float[] ang = interp.ToEulerAnglesXYZ();
                            rot = CFrame.Angles(ang[0], ang[2], ang[1]);
                            pos = new Vector3(pos.X, pos.Z, pos.Y);
                        }
                        else if (node.Name.StartsWith("Right"))
                        {
                            // X-axis is inverted for the right arm/leg.
                            pos *= new Vector3(-1, 1, 1);
                        }

                        if (node.Name.EndsWith("Arm") || node.Name.EndsWith("Leg"))
                        {
                            // Rotate position offset of the arms & legs 90* counter-clockwise.
                            pos = new Vector3(-pos.Z, pos.Y, pos.X);
                        }

                        if (node.Name != "Head")
                        {
                            rot = rot.Inverse();
                        }

                        interp = new CFrame(pos) * rot;
                    }
                    else if (sequence.AvatarType == AvatarType.R15)
                    {
                        float[] ang = interp.ToEulerAnglesXYZ();

                        // Cancel out the rotations
                        interp *= CFrame.Angles(-ang[0], -ang[1], -ang[2]);

                        // Patch the Y-axis
                        PatchAngles(ref interp, 1, ang);

                        // Patch the Z-axis
                        PatchAngles(ref interp, 2, ang);

                        // Patch the X-axis
                        PatchAngles(ref interp, 0, ang);
                    }

                    Bone bone = new Bone(node, interp);
                    bones.Add(bone);
                }

                boneKeyframes.Add(frame);
            }

            return(animWriter.BuildFile());
        }
示例#9
0
        public StudioMdlWriter AssembleModel(Folder characterAssets, AvatarScale scale, bool collisionModel = false)
        {
            Contract.Requires(characterAssets != null);
            StudioMdlWriter meshBuilder = new StudioMdlWriter();

            // Build Character
            var    import   = R15AssemblyAsset.OpenAsModel();
            Folder assembly = import.FindFirstChild <Folder>("ASSEMBLY");

            BasePart head = assembly.FindFirstChild <BasePart>("Head");

            assembly.Parent = characterAssets;

            foreach (Instance asset in characterAssets.GetChildren())
            {
                if (asset is BasePart)
                {
                    BasePart existing = assembly.FindFirstChild <BasePart>(asset.Name);

                    if (existing != null)
                    {
                        existing.Destroy();
                    }

                    asset.Parent = assembly;
                }
                else if (asset is Folder && asset.Name == "R15ArtistIntent")
                {
                    foreach (BasePart child in asset.GetChildrenOfType <BasePart>())
                    {
                        BasePart existing = assembly.FindFirstChild <BasePart>(child.Name);

                        if (existing != null)
                        {
                            existing.Destroy();
                        }

                        child.Parent = assembly;
                    }
                }
                else if (asset is Accoutrement && !collisionModel)
                {
                    PrepareAccessory(asset, assembly);
                }
                else if (asset is DataModelMesh)
                {
                    OverwriteHead(asset as DataModelMesh, head);
                }
            }

            // Apply limb scaling
            var parts     = assembly.GetChildrenOfType <BasePart>();
            var attachMap = new Dictionary <string, Attachment>();

            var avatarParts = parts.Where((part) =>
            {
                var limb = GetLimb(part);
                return(limb.HasValue);
            });

            var accessoryParts = parts.Except(avatarParts);

            foreach (BasePart avatarPart in avatarParts)
            {
                Vector3 limbScale = ComputeLimbScale(scale, avatarPart);

                foreach (Attachment att in avatarPart.GetChildrenOfType <Attachment>())
                {
                    attachMap[att.Name] = att;
                }

                ScalePart(avatarPart, limbScale);
            }

            // Apply accessory scaling
            foreach (BasePart handle in accessoryParts)
            {
                Attachment handleAtt = handle.FindFirstChildOfClass <Attachment>();

                if (handleAtt != null)
                {
                    string attName = handleAtt.Name;

                    if (attachMap.ContainsKey(attName))
                    {
                        Attachment avatarAtt  = attachMap[attName];
                        BasePart   avatarPart = avatarAtt.Parent as BasePart;

                        Vector3 accessoryScale = ComputeAccessoryScale(scale, avatarPart, handle);
                        ScalePart(handle, accessoryScale);
                    }
                }
            }

            BasePart torso = assembly.FindFirstChild <BasePart>("LowerTorso");

            torso.CFrame = new CFrame();

            BoneKeyframe      keyframe = AssembleBones(meshBuilder, torso);
            List <StudioBone> bones    = keyframe.Bones;

            // Build File Data.
            Rbx2Source.Print("Building Geometry...");
            Rbx2Source.IncrementStack();

            foreach (StudioBone bone in bones)
            {
                BuildAvatarGeometry(meshBuilder, bone);
            }

            Rbx2Source.DecrementStack();
            return(meshBuilder);
        }
示例#10
0
        public static StudioMdlWriter AssembleModel(Asset asset)
        {
            Contract.Requires(asset != null);

            var content = asset.OpenAsModel();

            Rbx2Source.ScheduleTasks("GatherParts", "BuildMesh");

            List <BasePart> parts = new List <BasePart>();

            AddParts(parts, content);

            if (parts.Count == 0)
            {
                throw new Exception("No parts were found inside of this asset!");
            }

            BasePart primaryPart = null;

            foreach (BasePart part in parts)
            {
                if (part is MeshPart || part.Name == "Handle")
                {
                    primaryPart = part;
                    break;
                }
            }

            if (primaryPart == null) // k lol
            {
                primaryPart = parts[0];
            }

            primaryPart.Name = asset.ProductInfo.WindowsSafeName.Trim();

            // Mark the primaryPart's location as the center.
            CFrame rootCoord = primaryPart.CFrame;

            foreach (BasePart part in parts)
            {
                part.CFrame = rootCoord.ToObjectSpace(part.CFrame);
            }

            Rbx2Source.MarkTaskCompleted("GatherParts");
            Rbx2Source.PrintHeader("BUILDING MESH");

            StudioMdlWriter writer = new StudioMdlWriter();

            BoneKeyframe skeleton = new BoneKeyframe();

            writer.Skeleton.Add(skeleton);

            List <StudioBone> bones = skeleton.Bones;
            List <Node>       nodes = writer.Nodes;

            List <Triangle> triangles         = writer.Triangles;
            int             numAssembledParts = 0;

            var materials  = writer.Materials;
            var nameCounts = new Dictionary <string, int>();

            foreach (BasePart part in parts)
            {
                // Make sure this part has a unique name.
                string name = part.Name;

                if (nameCounts.ContainsKey(name))
                {
                    int count = ++nameCounts[name];
                    name     += count.ToInvariantString();
                    part.Name = name;
                }
                else
                {
                    nameCounts[name] = 0;
                }

                // Assemble the part.
                var  material = new ValveMaterial();
                Mesh geometry = Mesh.BakePart(part, material);

                if (geometry != null && geometry.NumFaces > 0)
                {
                    string task = "BuildGeometry_" + name;
                    Rbx2Source.ScheduleTasks(task);
                    Rbx2Source.Print("Building Geometry for {0}", name);

                    var bone = new StudioBone(name, primaryPart, part)
                    {
                        C0 = part.CFrame
                    };
                    bones.Add(bone);

                    Node node = bone.Node;
                    nodes.Add(node);

                    int faceStride;
                    materials.Add(name, material);

                    if (geometry.HasLODs)
                    {
                        faceStride = geometry.LODs[1];
                    }
                    else
                    {
                        faceStride = geometry.NumFaces;
                    }

                    for (int i = 0; i < faceStride; i++)
                    {
                        Triangle tri = new Triangle()
                        {
                            Node      = node,
                            Mesh      = geometry,
                            FaceIndex = i,
                            Material  = name,
                        };

                        triangles.Add(tri);
                    }

                    Rbx2Source.MarkTaskCompleted(task);
                    numAssembledParts++;
                }
            }


            Rbx2Source.MarkTaskCompleted("BuildMesh");
            return(writer);
        }
        public static StudioMdlWriter AssembleModel(Asset asset)
        {
            Folder content = RBXM.LoadFromAsset(asset);

            Rbx2Source.ScheduleTasks("GatherParts", "BuildMesh");
            Rbx2Source.PrintHeader("GATHERING PARTS");

            List <Part> parts = new List <Part>();

            AddParts(parts, content);

            if (parts.Count == 0)
            {
                throw new Exception("No parts were found inside of this asset!");
            }

            Part primaryPart = null;

            foreach (Part part in parts)
            {
                if (part.IsA("MeshPart") || part.Name == "Handle")
                {
                    primaryPart = part;
                    break;
                }
            }

            if (primaryPart == null) // k lol
            {
                primaryPart = parts[0];
            }

            primaryPart.Name = asset.ProductInfo.Name;

            // Mark the primaryPart's location as the center.
            CFrame rootCoord = primaryPart.CFrame;

            foreach (Part part in parts)
            {
                part.CFrame = rootCoord.toObjectSpace(part.CFrame);
            }

            Rbx2Source.MarkTaskCompleted("GatherParts");
            Rbx2Source.PrintHeader("BUILDING MESH");

            StudioMdlWriter writer = new StudioMdlWriter();

            BoneKeyframe skeleton = new BoneKeyframe();

            writer.Skeleton.Add(skeleton);

            List <Bone>     bones     = skeleton.Bones;
            List <Node>     nodes     = writer.Nodes;
            List <Triangle> triangles = writer.Triangles;

            Dictionary <string, Material> materials  = writer.Materials;
            Dictionary <string, int>      nameCounts = new Dictionary <string, int>();

            int numAssembledParts = 0;

            foreach (Part part in parts)
            {
                // Make sure this part has a unique name.
                string name = part.Name;
                if (nameCounts.ContainsKey(name))
                {
                    int count = ++nameCounts[name];
                    name     += count.ToString();
                    part.Name = name;
                }
                else
                {
                    nameCounts[name] = 0;
                }

                // Assemble the part.
                Material material = new Material();
                Mesh     geometry = Mesh.BakePart(part, material);

                if (geometry != null && geometry.FaceCount > 0)
                {
                    string task = "BuildGeometry_" + name;
                    Rbx2Source.ScheduleTasks(task);
                    Rbx2Source.Print("Building Geometry for {0}", name);

                    Bone bone = new Bone(name, primaryPart, part);
                    bone.C0 = part.CFrame;
                    bones.Add(bone);

                    Node node = bone.Node;
                    nodes.Add(node);

                    materials.Add(name, material);

                    for (int i = 0; i < geometry.FaceCount; i++)
                    {
                        Triangle tri = new Triangle();
                        tri.Node      = node;
                        tri.Mesh      = geometry;
                        tri.FaceIndex = i;
                        tri.Material  = name;
                        triangles.Add(tri);
                    }

                    Rbx2Source.MarkTaskCompleted(task);
                    numAssembledParts++;
                }
            }


            Rbx2Source.MarkTaskCompleted("BuildMesh");
            return(writer);
        }