public static Folder AppendCharacterAssets(UserAvatar avatar, string avatarType)
        {
            Rbx2Source.PrintHeader("GATHERING CHARACTER ASSETS");

            Folder      characterAssets = new Folder();
            List <long> assetIds        = avatar.AccessoryVersionIds;

            foreach (long id in assetIds)
            {
                Asset  asset        = Asset.Get(id, "/asset/?assetversionid=");
                Folder import       = RBXM.LoadFromAsset(asset);
                Folder typeSpecific = import.FindFirstChild <Folder>(avatarType);
                if (typeSpecific != null)
                {
                    import = typeSpecific;
                }

                foreach (Instance obj in import.GetChildren())
                {
                    obj.Parent = characterAssets;
                }
            }

            return(characterAssets);
        }
        public static Folder AppendCharacterAssets(UserAvatar avatar, string avatarType, string context = "CHARACTER")
        {
            Rbx2Source.PrintHeader("GATHERING " + context + " ASSETS");

            Folder characterAssets = new Folder();

            AssetInfo[] assets = avatar.Assets;

            foreach (AssetInfo info in assets)
            {
                long  id    = info.Id;
                Asset asset = Asset.Get(id);

                Folder import       = RBXM.LoadFromAsset(asset);
                Folder typeSpecific = import.FindFirstChild <Folder>(avatarType);

                if (typeSpecific != null)
                {
                    import = typeSpecific;
                }

                import.ForEachChild((obj) => obj.Parent = characterAssets);
            }

            return(characterAssets);
        }
        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 AssemblerData Assemble(UserAvatar avatar)
        {
            UserInfo userInfo = avatar.UserInfo;
            string   userName = FileUtility.MakeNameWindowsSafe(userInfo.Username);

            string appData = Environment.GetEnvironmentVariable("LocalAppData");
            string rbx2Src = Path.Combine(appData, "Rbx2Source");
            string avatars = Path.Combine(rbx2Src, "Avatars");
            string userBin = Path.Combine(avatars, userName);

            string modelDir     = Path.Combine(userBin, "Model");
            string anim8Dir     = Path.Combine(modelDir, "Animations");
            string texturesDir  = Path.Combine(userBin, "Textures");
            string materialsDir = Path.Combine(userBin, "Materials");

            FileUtility.InitiateEmptyDirectories(modelDir, anim8Dir, texturesDir, materialsDir);

            AvatarType          avatarType = avatar.PlayerAvatarType;
            ICharacterAssembler assembler;

            if (avatarType == AvatarType.R15)
            {
                assembler = new R15CharacterAssembler();
            }
            else
            {
                assembler = new R6CharacterAssembler();
            }

            string compileDir = "roblox_avatars/" + userName;

            string avatarTypeName  = Rbx2Source.GetEnumName(avatarType);
            Folder characterAssets = AppendCharacterAssets(avatar, avatarTypeName);

            Rbx2Source.ScheduleTasks
            (
                "BuildCharacter",
                "BuildCollisionModel",
                "BuildAnimations",
                "BuildTextures",
                "BuildMaterials",
                "BuildCompilerScript"
            );

            Rbx2Source.PrintHeader("BUILDING CHARACTER MODEL");
            #region Build Character Model
            ///////////////////////////////////////////////////////////////////////////////////////////////////////

            StudioMdlWriter writer = assembler.AssembleModel(characterAssets, avatar.Scales, DEBUG_RAPID_ASSEMBLY);

            string studioMdl = writer.BuildFile();
            string modelPath = Path.Combine(modelDir, "CharacterModel.smd");
            FileUtility.WriteFile(modelPath, studioMdl);

            string staticPose = writer.BuildFile(false);
            string refPath    = Path.Combine(modelDir, "ReferencePos.smd");
            FileUtility.WriteFile(refPath, staticPose);

            Rbx2Source.MarkTaskCompleted("BuildCharacter");

            ///////////////////////////////////////////////////////////////////////////////////////////////////////
            #endregion

            Rbx2Source.PrintHeader("BUILDING COLLISION MODEL");
            #region Build Character Collisions
            ///////////////////////////////////////////////////////////////////////////////////////////////////////

            Folder          collisionAssets = AppendCollisionAssets(avatar, avatarTypeName);
            StudioMdlWriter collisionWriter = assembler.AssembleModel(collisionAssets, avatar.Scales, true);

            string collisionModel = collisionWriter.BuildFile();
            string cmodelPath     = Path.Combine(modelDir, "CollisionModel.smd");
            FileUtility.WriteFile(cmodelPath, collisionModel);

            byte[] collisionJoints = assembler.CollisionModelScript;
            string cjointsPath     = Path.Combine(modelDir, "CollisionJoints.qc");

            FileUtility.WriteFile(cjointsPath, collisionJoints);
            Rbx2Source.MarkTaskCompleted("BuildCollisionModel");

            ///////////////////////////////////////////////////////////////////////////////////////////////////////
            #endregion

            Rbx2Source.PrintHeader("BUILDING CHARACTER ANIMATIONS");
            #region Build Character Animations
            ///////////////////////////////////////////////////////////////////////////////////////////////////////

            var animIds      = assembler.CollectAnimationIds(avatar);
            var compileAnims = new Dictionary <string, Asset>();

            if (animIds.Count > 0)
            {
                Rbx2Source.Print("Collecting Animations...");
                Rbx2Source.IncrementStack();

                Action <string, Asset> collectAnimation = (animName, animAsset) =>
                {
                    if (!compileAnims.ContainsKey(animName))
                    {
                        Rbx2Source.Print("Collected animation {0} with id {1}", animName, animAsset.Id);
                        compileAnims.Add(animName, animAsset);
                    }
                };

                foreach (string animName in animIds.Keys)
                {
                    AnimationId animId    = animIds[animName];
                    Asset       animAsset = animId.GetAsset();
                    Folder      import    = RBXM.LoadFromAsset(animAsset);

                    if (animId.AnimationType == AnimationType.R15AnimFolder)
                    {
                        Folder r15Anim = import.FindFirstChild <Folder>("R15Anim");
                        if (r15Anim != null)
                        {
                            foreach (Instance animDef in r15Anim.GetChildren())
                            {
                                if (animDef.Name == "idle")
                                {
                                    Animation[] anims = animDef.GetChildrenOfClass <Animation>();
                                    if (anims.Length == 2)
                                    {
                                        Animation lookAnim = anims.OrderBy(anim => anim.Weight).First();
                                        lookAnim.Destroy();

                                        Asset lookAsset = Asset.GetByAssetId(lookAnim.AnimationId);
                                        collectAnimation("Idle2", lookAsset);
                                    }
                                }

                                Animation compileAnim = animDef.FindFirstChildOfClass <Animation>();

                                if (compileAnim != null)
                                {
                                    Asset  compileAsset = Asset.GetByAssetId(compileAnim.AnimationId);
                                    string compileName  = animName;

                                    if (animDef.Name == "pose")
                                    {
                                        compileName = "Pose";
                                    }

                                    collectAnimation(compileName, compileAsset);
                                }
                            }
                        }
                    }
                    else
                    {
                        collectAnimation(animName, animAsset);
                    }
                }

                Rbx2Source.DecrementStack();
            }
            else
            {
                Rbx2Source.Print("No animations found :(");
            }

            if (compileAnims.Count > 0)
            {
                Rbx2Source.Print("Assembling Animations...");
                Rbx2Source.IncrementStack();

                foreach (string animName in compileAnims.Keys)
                {
                    Rbx2Source.Print("Building Animation {0}...", animName);

                    Asset  animAsset = compileAnims[animName];
                    Folder import    = RBXM.LoadFromAsset(animAsset);

                    KeyframeSequence sequence = import.FindFirstChildOfClass <KeyframeSequence>();
                    sequence.AvatarType = avatarType;
                    sequence.Name       = animName;

                    string animation = Animator.Assemble(sequence, writer.Skeleton[0].Bones);
                    string animPath  = Path.Combine(anim8Dir, animName + ".smd");

                    FileUtility.WriteFile(animPath, animation);
                }

                Rbx2Source.DecrementStack();
            }

            Rbx2Source.MarkTaskCompleted("BuildAnimations");

            ///////////////////////////////////////////////////////////////////////////////////////////////////////
            #endregion

            Rbx2Source.PrintHeader("BUILDING CHARACTER TEXTURES");
            #region Build Character Textures
            ///////////////////////////////////////////////////////////////////////////////////////////////////////

            var             materials = writer.Materials;
            TextureBindings textures;

            if (DEBUG_RAPID_ASSEMBLY)
            {
                textures = new TextureBindings();
                materials.Clear();
            }
            else
            {
                TextureCompositor texCompositor = assembler.ComposeTextureMap(characterAssets, avatar.BodyColors);
                textures = assembler.BindTextures(texCompositor, materials);
            }

            var images = textures.Images;
            textures.MaterialDirectory = compileDir;

            foreach (string imageName in images.Keys)
            {
                Rbx2Source.Print("Writing Image {0}.png", imageName);

                Image  image     = images[imageName];
                string imagePath = Path.Combine(texturesDir, imageName + ".png");

                try
                {
                    image.Save(imagePath, ImageFormat.Png);
                }
                catch
                {
                    Rbx2Source.Print("IMAGE {0}.png FAILED TO SAVE!", imageName);
                }

                FileUtility.LockFile(imagePath);
            }

            CompositData.FreeAllocatedTextures();
            Rbx2Source.MarkTaskCompleted("BuildTextures");

            ///////////////////////////////////////////////////////////////////////////////////////////////////////
            #endregion

            Rbx2Source.PrintHeader("WRITING MATERIAL FILES");
            #region Write Material Files
            ///////////////////////////////////////////////////////////////////////////////////////////////////////

            var matLinks = textures.MatLinks;

            foreach (string mtlName in matLinks.Keys)
            {
                Rbx2Source.Print("Building VMT {0}.vmt", mtlName);

                string targetVtf = matLinks[mtlName];
                string vmtPath   = Path.Combine(materialsDir, mtlName + ".vmt");

                Material mtl = materials[mtlName];
                mtl.SetVmtField("basetexture", "models/" + compileDir + "/" + targetVtf);
                mtl.WriteVmtFile(vmtPath);
            }

            Rbx2Source.MarkTaskCompleted("BuildMaterials");

            ///////////////////////////////////////////////////////////////////////////////////////////////////////
            #endregion

            Rbx2Source.PrintHeader("WRITING COMPILER SCRIPT");
            #region Write Compiler Script
            ///////////////////////////////////////////////////////////////////////////////////////////////////////

            string       modelName = compileDir + ".mdl";
            QuakeCWriter qc        = new QuakeCWriter();

            qc.Add("body", userName, "CharacterModel.smd");
            qc.Add("modelname", modelName);
            qc.Add("upaxis", "y");

            // Compute the floor level of the avatar.
            Folder assembly = characterAssets.FindFirstChild <Folder>("ASSEMBLY");

            if (assembly != null)
            {
                float  floor  = ComputeFloorLevel(assembly);
                string origin = "0 " + floor.ToInvariantString() + " 0";
                qc.Add("origin", origin);
            }

            qc.Add("cdmaterials", "models/" + compileDir);
            qc.Add("surfaceprop", "flesh");
            qc.Add("include", "CollisionJoints.qc");

            QuakeCItem refAnim = qc.Add("sequence", "reference", "ReferencePos.smd");
            refAnim.AddSubItem("fps", 1);
            refAnim.AddSubItem("loop");

            foreach (string animName in compileAnims.Keys)
            {
                QuakeCItem sequence = qc.Add("sequence", animName.ToLower(), "Animations/" + animName + ".smd");
                sequence.AddSubItem("fps", Animator.FrameRate);

                if (avatarType == AvatarType.R6)
                {
                    sequence.AddSubItem("delta");
                }

                sequence.AddSubItem("loop");
            }

            string qcFile = qc.ToString();
            string qcPath = Path.Combine(modelDir, "Compile.qc");

            FileUtility.WriteFile(qcPath, qcFile);
            Rbx2Source.MarkTaskCompleted("BuildCompilerScript");

            ///////////////////////////////////////////////////////////////////////////////////////////////////////
            #endregion

            AssemblerData data = new AssemblerData()
            {
                ModelData      = writer,
                ModelName      = modelName,
                TextureData    = textures,
                CompilerScript = qcPath,

                RootDirectory     = userBin,
                CompileDirectory  = compileDir,
                TextureDirectory  = texturesDir,
                MaterialDirectory = materialsDir,
            };

            return(data);
        }
        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);
        }
        public AssemblerData Assemble(object metadata)
        {
            UserAvatar avatar = metadata as UserAvatar;

            if (avatar == null)
            {
                throw new Exception("bad cast");
            }

            UserInfo userInfo = avatar.UserInfo;
            string   userName = FileUtility.MakeNameWindowsSafe(userInfo.Username);

            string appData    = Environment.GetEnvironmentVariable("AppData");
            string rbx2Source = Path.Combine(appData, "Rbx2Source");
            string avatars    = Path.Combine(rbx2Source, "Avatars");
            string userBin    = Path.Combine(avatars, userName);

            string modelDir     = Path.Combine(userBin, "Model");
            string animDir      = Path.Combine(modelDir, "Animations");
            string texturesDir  = Path.Combine(userBin, "Textures");
            string materialsDir = Path.Combine(userBin, "Materials");

            FileUtility.InitiateEmptyDirectories(modelDir, animDir, texturesDir, materialsDir);

            AvatarType          avatarType = avatar.ResolvedAvatarType;
            ICharacterAssembler assembler;

            if (avatarType == AvatarType.R15)
            {
                assembler = new R15CharacterAssembler();
            }
            else
            {
                assembler = new R6CharacterAssembler();
            }

            string avatarTypeName  = Rbx2Source.GetEnumName(avatar.ResolvedAvatarType);
            Folder characterAssets = AppendCharacterAssets(avatar, avatarTypeName);

            Rbx2Source.ScheduleTasks("BuildCharacter", "BuildCollisionModel", "BuildAnimations", "BuildTextures", "BuildMaterials", "BuildCompilerScript");
            Rbx2Source.PrintHeader("BUILDING CHARACTER MODEL");

            StudioMdlWriter writer = assembler.AssembleModel(characterAssets, avatar.Scales);

            string studioMdl = writer.BuildFile();
            string modelPath = Path.Combine(modelDir, "CharacterModel.smd");

            FileUtility.WriteFile(modelPath, studioMdl);

            // Clear the triangles so we can build a reference pose .smd file.
            writer.Triangles.Clear();
            string staticPose = writer.BuildFile();
            string refPath    = Path.Combine(modelDir, "ReferencePos.smd");

            FileUtility.WriteFile(refPath, staticPose);
            Rbx2Source.MarkTaskCompleted("BuildCharacter");

            Rbx2Source.PrintHeader("BUILDING COLLISION MODEL");

            Folder lowPoly = new Folder();

            SpecialMesh lowPolyHead = new SpecialMesh();

            lowPolyHead.MeshId   = "rbxassetid://582002794";
            lowPolyHead.MeshType = MeshType.FileMesh;
            lowPolyHead.Scale    = new Vector3(1, 1, 1);
            lowPolyHead.Offset   = new Vector3();
            lowPolyHead.Parent   = lowPoly;

            StudioMdlWriter collisionWriter = assembler.AssembleModel(lowPoly, avatar.Scales);

            string collisionModel = collisionWriter.BuildFile();
            string cmodelPath     = Path.Combine(modelDir, "CollisionModel.smd");

            FileUtility.WriteFile(cmodelPath, collisionModel);

            byte[] collisionJoints = assembler.CollisionModelScript;
            string cjointsPath     = Path.Combine(modelDir, "CollisionJoints.qc");

            FileUtility.WriteFile(cjointsPath, collisionJoints);
            Rbx2Source.MarkTaskCompleted("BuildCollisionModel");

            Rbx2Source.PrintHeader("BUILDING CHARACTER ANIMATIONS");
            Dictionary <string, string> animations = GatherAnimations(avatarType);

            if (animations.Count > 0)
            {
                foreach (string animName in animations.Keys)
                {
                    Rbx2Source.Print("Building Animation {0}", animName);
                    string           localAnimPath = animations[animName];
                    Asset            animAsset     = Asset.FromResource(localAnimPath);
                    Folder           import        = RBXM.LoadFromAsset(animAsset);
                    KeyframeSequence sequence      = import.FindFirstChildOfClass <KeyframeSequence>();
                    sequence.Name       = animName;
                    sequence.AvatarType = avatarType;
                    string animation = AnimationAssembler.Assemble(sequence, writer.Skeleton[0].Bones);
                    string animPath  = Path.Combine(animDir, animName + ".smd");
                    FileUtility.WriteFile(animPath, animation);
                }
            }
            else
            {
                Rbx2Source.Print("No animations found :(");
            }

            Rbx2Source.MarkTaskCompleted("BuildAnimations");
            Dictionary <string, Material> materials = writer.Materials;

            Rbx2Source.PrintHeader("BUILDING CHARACTER TEXTURES");

            string compileDirectory = "roblox_avatars/" + userName;

            TextureCompositor texCompositor = assembler.ComposeTextureMap(characterAssets, avatar.BodyColors);
            TextureAssembly   texAssembly   = assembler.AssembleTextures(texCompositor, materials);

            CompositData.FreeAllocatedTextures();
            texAssembly.MaterialDirectory = compileDirectory;

            Dictionary <string, Image> images = texAssembly.Images;

            foreach (string imageName in images.Keys)
            {
                Rbx2Source.Print("Writing Image {0}.png", imageName);
                Image  image     = images[imageName];
                string imagePath = Path.Combine(texturesDir, imageName + ".png");
                try
                {
                    image.Save(imagePath, ImageFormat.Png);
                }
                catch
                {
                    Rbx2Source.Print("IMAGE {0}.png FAILED TO SAVE!", imageName);
                }
                FileUtility.LockFile(imagePath);
            }

            Rbx2Source.MarkTaskCompleted("BuildTextures");
            Rbx2Source.PrintHeader("BUILDING MATERIAL FILES");

            Dictionary <string, string> matLinks = texAssembly.MatLinks;

            foreach (string mtlName in matLinks.Keys)
            {
                Rbx2Source.Print("Building VMT {0}.vmt", mtlName);
                string        targetVtf = matLinks[mtlName];
                Material      mtl       = materials[mtlName];
                ValveMaterial vmt       = new ValveMaterial(mtl);
                vmt.SetField("basetexture", "models/" + compileDirectory + "/" + targetVtf);
                string vmtPath    = Path.Combine(materialsDir, mtlName + ".vmt");
                string vmtContent = vmt.ToString();
                FileUtility.WriteFile(vmtPath, vmtContent);
            }

            Rbx2Source.MarkTaskCompleted("BuildMaterials");
            Rbx2Source.PrintHeader("WRITING COMPILER SCRIPT");

            QCWriter qc = new QCWriter();

            QCommand model = new QCommand("body", userName, "CharacterModel.smd");

            qc.AddCommand(model);

            string modelNameStr = compileDirectory + ".mdl";

            qc.WriteBasicCmd("modelname", modelNameStr);
            qc.WriteBasicCmd("upaxis", "y");

            string originStr = "";

            if (avatarType == AvatarType.R6)
            {
                originStr = "0 -30 0";
            }
            else
            {
                originStr = "0 " + (-23.5 * avatar.Scales.Height).ToString(Rbx2Source.NormalParse) + " 0";
            }

            qc.WriteBasicCmd("origin", originStr, false);
            qc.WriteBasicCmd("cdmaterials", "models/" + compileDirectory);
            qc.WriteBasicCmd("surfaceprop", "flesh");
            qc.WriteBasicCmd("include", "CollisionJoints.qc");

            QCommand reference = new QCommand("sequence", "reference", "ReferencePos.smd");

            reference.AddParameter("fps", "1");
            reference.AddParameter("loop");
            qc.AddCommand(reference);

            foreach (string animName in animations.Keys)
            {
                QCommand sequence = new QCommand("sequence", animName.ToLower(), "Animations/" + animName + ".smd");
                sequence.AddParameter("fps", AnimationAssembler.FrameRate.ToString());
                sequence.AddParameter("loop");
                if (avatarType == AvatarType.R6) // TODO: Find a work around so I can get rid of this.
                {
                    sequence.AddParameter("delta");
                }

                qc.AddCommand(sequence);
            }

            string qcFile = qc.BuildFile();
            string qcPath = Path.Combine(modelDir, "Compile.qc");

            FileUtility.WriteFile(qcPath, qcFile);
            Rbx2Source.MarkTaskCompleted("BuildCompilerScript");

            AssemblerData data = new AssemblerData();

            data.ModelData         = writer;
            data.TextureData       = texAssembly;
            data.CompilerScript    = qcPath;
            data.RootDirectory     = userBin;
            data.MaterialDirectory = materialsDir;
            data.TextureDirectory  = texturesDir;
            data.CompileDirectory  = compileDirectory;
            data.ModelName         = modelNameStr;

            return(data);
        }
Beispiel #7
0
        public static StudioMdlWriter AssembleModel(Asset asset)
        {
            Folder content = RBXM.LoadFromAsset(asset);

            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.IsA("MeshPart") || part.Name == "Handle")
                {
                    primaryPart = part;
                    break;
                }
            }

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

            primaryPart.Name = asset.ProductInfo.Name.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 <Bone> 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.
                Material material = new Material();
                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);

                    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.NumFaces; 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 StudioMdlWriter AssembleModel(Folder characterAssets, AvatarScale scale, bool collisionModel = false)
        {
            StudioMdlWriter meshBuilder = new StudioMdlWriter();

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

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

            assembly.Parent = characterAssets;

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

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

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

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

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

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

            var avatarParts    = parts.Where(part => GetLimb(part) != Limb.Unknown);
            var accessoryParts = parts.Except(avatarParts);

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

                foreach (Attachment att in avatarPart.GetChildrenOfClass <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 <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);
        }