Example #1
0
        public TextureBindings BindTextures(TextureCompositor compositor, Dictionary <string, ValveMaterial> materials)
        {
            Contract.Requires(compositor != null && materials != null);
            TextureBindings textureBinds = new TextureBindings();

            Bitmap uvMap = compositor.BakeTextureMap();

            Rbx2Source.SetDebugImage(uvMap);

            foreach (string matName in materials.Keys)
            {
                Rbx2Source.Print("Building Material {0}", matName);
                ValveMaterial material = materials[matName];
                Image         image    = null;

                if (material.UseAvatarMap)
                {
                    if (Enum.TryParse(matName, out BodyPart limb))
                    {
                        Rectangle cropRegion = UVCrops[limb];
                        image = TextureCompositor.CropBitmap(uvMap, cropRegion);
                    }
                }
                else
                {
                    Asset texture = material.TextureAsset;

                    if (texture != null)
                    {
                        byte[]       textureData   = texture.GetContent();
                        MemoryStream textureStream = new MemoryStream(textureData);

                        image = Image.FromStream(textureStream);
                        textureStream.Dispose();
                    }
                }

                textureBinds.BindTexture(matName, image);
            }

            return(textureBinds);
        }
        public AssemblerData Assemble(UserAvatar avatar)
        {
            Contract.Requires(avatar != null);

            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)
                {
                    var animId    = animIds[animName];
                    var animAsset = animId.GetAsset();
                    var import    = animAsset.OpenAsModel();

                    if (animId.AnimationType == AnimationType.R15AnimFolder)
                    {
                        Folder r15Anim = import.FindFirstChild <Folder>("R15Anim");

                        if (r15Anim != null)
                        {
                            foreach (Instance animDef in r15Anim.GetChildren())
                            {
                                if (animDef.Name == "idle")
                                {
                                    var anims = animDef.GetChildrenOfType <Animation>();

                                    if (anims.Length == 2)
                                    {
                                        var getLookAnim = anims.OrderBy((anim) =>
                                        {
                                            var weight = anim.FindFirstChild <NumberValue>("Weight");

                                            if (weight != null)
                                            {
                                                return(weight.Value);
                                            }

                                            return(0.0);
                                        });

                                        var lookAnim = getLookAnim.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];
                    var   import    = animAsset.OpenAsModel();

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

                    var avatarTypeRef = new StringValue()
                    {
                        Value  = $"{avatarType}",
                        Name   = "AvatarType",
                        Parent = sequence
                    };

                    string animation = AnimationBuilder.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");

                ValveMaterial 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.ToLowerInvariant(), "Animations/" + animName + ".smd");
                sequence.AddSubItem("fps", AnimationBuilder.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 TextureBindings BindTextures(TextureCompositor compositor, Dictionary <string, ValveMaterial> materials)
        {
            Contract.Requires(compositor != null && materials != null);
            TextureBindings textures = new TextureBindings();

            Bitmap core = compositor.BakeTextureMap();

            Rbx2Source.SetDebugImage(core);

            Bitmap head = TextureCompositor.CropBitmap(core, RECT_HEAD);

            textures.BindTexture("Head", head);

            Bitmap body            = TextureCompositor.CropBitmap(core, RECT_BODY);
            Folder characterAssets = compositor.CharacterAssets;

            Rbx2Source.Print("Processing Package Textures...");
            Rbx2Source.IncrementStack();

            // Collect CharacterMeshes
            var packagedLimbs = characterAssets
                                .GetChildrenOfType <CharacterMesh>()
                                .ToDictionary(mesh => mesh.BodyPart);

            // Compose the textures that will be used
            var limbOverlays = new Dictionary <BodyPart, long>();
            var limbBitmaps  = new Dictionary <long, Bitmap>()
            {
                { 0, body }
            };

            foreach (BodyPart limb in LimbMatcher.Keys)
            {
                // Head is already textured, ignore it.
                if (limb == BodyPart.Head)
                {
                    continue;
                }

                // Is there a CharacterMesh for this limb?
                if (packagedLimbs.ContainsKey(limb))
                {
                    // Check the CharacterMesh textures.
                    CharacterMesh mesh = packagedLimbs[limb];

                    if (mesh.OverlayTextureId > 0)
                    {
                        // Use the overlay texture for this limb.
                        long overlayId = mesh.OverlayTextureId;
                        limbOverlays.Add(limb, overlayId);

                        // Compose this overlay texture with the body texture if it doesn't exist yet.
                        if (!limbBitmaps.ContainsKey(overlayId))
                        {
                            Asset overlayAsset = Asset.Get(overlayId);

                            TextureCompositor overlayCompositor = new TextureCompositor(AvatarType.R6, RECT_FULL);
                            overlayCompositor.SetContext("Overlay Texture " + overlayId);
                            overlayCompositor.AppendTexture(overlayAsset, RECT_BODY, 1);
                            overlayCompositor.AppendTexture(body, RECT_BODY);

                            Bitmap overlayTex = overlayCompositor.BakeTextureMap(RECT_BODY);
                            limbBitmaps.Add(overlayId, overlayTex);
                        }

                        continue;
                    }
                    else if (mesh.BaseTextureId > 0)
                    {
                        // Use the base texture for this limb.
                        long baseId = mesh.BaseTextureId;
                        limbOverlays.Add(limb, baseId);

                        // Compose the base texture if it doesn't exist yet.
                        if (!limbBitmaps.ContainsKey(baseId))
                        {
                            Asset baseAsset = Asset.Get(baseId);

                            TextureCompositor baseCompositor = new TextureCompositor(AvatarType.R6, RECT_FULL);
                            baseCompositor.SetContext("Base Texture " + baseId);
                            baseCompositor.AppendTexture(baseAsset, RECT_BODY);

                            Bitmap baseTex = baseCompositor.BakeTextureMap(RECT_BODY);
                            limbBitmaps.Add(baseId, baseTex);
                        }

                        continue;
                    }
                }

                // If no continue statement is reached, fallback to using the body texture.
                // This occurs if the limb has no package, or the package limb has no textures.
                limbOverlays.Add(limb, 0);
            }

            // Add the images into the texture assembly.
            foreach (long id in limbBitmaps.Keys)
            {
                Bitmap bitmap  = limbBitmaps[id];
                string matName = GetBodyMatName(id);
                textures.BindTexture(matName, bitmap, false);
            }

            // Link the limbs to their textures.
            foreach (BodyPart limb in limbOverlays.Keys)
            {
                long   id      = limbOverlays[limb];
                string matName = GetBodyMatName(id);

                string limbName = Rbx2Source.GetEnumName(limb);
                textures.BindTextureAlias(limbName, matName);
            }

            // Handle the rest of the materials
            foreach (string matName in materials.Keys)
            {
                if (!textures.MatLinks.ContainsKey(matName))
                {
                    ValveMaterial material = materials[matName];
                    Asset         texture  = material.TextureAsset;

                    TextureCompositor matComp = new TextureCompositor(AvatarType.R6, RECT_ITEM);
                    matComp.SetContext("Accessory Texture " + matName);
                    matComp.AppendTexture(texture, RECT_ITEM);

                    Bitmap bitmap = matComp.BakeTextureMap();
                    textures.BindTexture(matName, bitmap);
                }
            }

            Rbx2Source.DecrementStack();
            return(textures);
        }
        public static TextureBindings BindTextures(Dictionary <string, ValveMaterial> materials)
        {
            Contract.Requires(materials != null);

            var textures = new TextureBindings();
            var images   = textures.Images;

            foreach (string mtlName in materials.Keys)
            {
                ValveMaterial material     = materials[mtlName];
                Asset         textureAsset = material.TextureAsset;

                if (textureAsset == null || textureAsset.Id == 9854798)
                {
                    var    linkedTo = material.LinkedTo;
                    Color3 color;

                    if (linkedTo.BrickColor != null)
                    {
                        BrickColor bc = linkedTo.BrickColor;
                        color = bc.Color;
                    }
                    else if (linkedTo.Color3uint8 != null)
                    {
                        color = linkedTo.Color3uint8;
                    }
                    else
                    {
                        BrickColor def = BrickColor.FromNumber(-1);
                        color = def.Color;
                    }

                    float r = color.R,
                          g = color.G,
                          b = color.B;

                    if (!images.ContainsKey("BrickColor"))
                    {
                        byte[] rawImg = ResourceUtility.GetResource("Images/BlankWhite.png");

                        using (MemoryStream imgStream = new MemoryStream(rawImg))
                        {
                            Image image = Image.FromStream(imgStream);
                            textures.BindTexture("BrickColor", image, false);
                        }
                    }

                    material.UseEnvMap   = true;
                    material.VertexColor = new Vector3(r, g, b);

                    textures.BindTextureAlias(mtlName, "BrickColor");
                }
                else
                {
                    byte[] rawImg = textureAsset.GetContent();

                    using (MemoryStream imgStream = new MemoryStream(rawImg))
                    {
                        Image image = Image.FromStream(imgStream);
                        textures.BindTexture(mtlName, image);
                    }
                }
            }

            return(textures);
        }