public static Folder AppendCollisionAssets(UserAvatar avatar, string avatarType) { Folder collisionAssets; if (DEBUG_RAPID_ASSEMBLY) { collisionAssets = new Folder(); } else { collisionAssets = AppendCharacterAssets(avatar, avatarType, "COLLISION"); } // Replace the head mesh with a low-poly head DataModelMesh oldHeadMesh = collisionAssets.FindFirstChildOfClass <DataModelMesh>(); if (oldHeadMesh != null) { oldHeadMesh.Destroy(); } _ = new SpecialMesh() { MeshId = "rbxassetid://582002794", MeshType = MeshType.FileMesh, Scale = new Vector3(1, 1, 1), Offset = new Vector3(), Parent = collisionAssets }; return(collisionAssets); }
public static Asset ResolveHeadMeshAsset(DataModelMesh mesh) { Contract.Requires(mesh != null); string result = "Default"; if (mesh is BevelMesh) { BevelMesh bevelMesh = mesh as BevelMesh; BevelType bevelType = BevelType.Unknown; if (mesh is BlockMesh) { bevelType = BevelType.Block; } else if (mesh is CylinderMesh) { bevelType = BevelType.Cylinder; } Head match = Lookup.Keys .Where((head) => bevelType == head.BevelType) .Where((head) => head.FieldsMatchWith(bevelMesh)) .First(); if (match != null) { result = Lookup[match]; } mesh.Scale = new Vector3(1, 1, 1); } else if (mesh is SpecialMesh) { SpecialMesh specialMesh = mesh as SpecialMesh; if (specialMesh.MeshType == MeshType.Sphere) { result = "Perfection"; specialMesh.Scale = new Vector3(1, 1, 1); } } if (result == "Default") { mesh.Scale /= new Vector3(1.25f, 1.25f, 1.25f); } return(Asset.FromResource("Meshes/Heads/" + result + ".mesh")); }
public static Asset GetAvatarFace(Folder characterAssets) { // Check if this avatar is using an Rthro head with a texture overlay. Contract.Requires(characterAssets != null); Folder assembly = characterAssets.FindFirstChild <Folder>("ASSEMBLY"); if (assembly != null) { BasePart head = assembly.FindFirstChild <BasePart>("Head"); if (head != null) { SpecialMesh headMesh = head.FindFirstChildOfClass <SpecialMesh>(); if (headMesh != null && headMesh.TextureId != null) { string textureId = headMesh.TextureId; if (textureId.Length > 0) { // One last check to make sure this is *probably* an Rthro head. // The reason this check is necessary is due to the iBot Head, which has a texture and allows a face to be drawn on it. // I suspect Roblox will expand this behavior later, so I need to keep an eye on it. StringValue scaleType = head.FindFirstChild <StringValue>("AvatarPartScaleType"); if (scaleType != null && scaleType.Value != "Classic") { return(Asset.GetByAssetId(headMesh.TextureId)); } } } } } // Fall back to normal behavior. Decal face = characterAssets.FindFirstChild <Decal>("face"); Asset result; if (face != null && face.Texture != "rbxasset://textures/face.png") { result = Asset.GetByAssetId(face.Texture); } else { result = Asset.FromResource("Images/face.png"); } return(result); }
public static Mesh BakePart(Part part, Material material = null) { Mesh result = null; Asset meshAsset = null; Asset textureAsset = null; Vector3 scale = null; CFrame offset = null; if (material != null) { material.LinkedTo = part; material.Transparency = part.Transparency; material.Reflectance = part.Reflectance; } if (part.Transparency < 1) { if (part.IsA("MeshPart")) { MeshPart meshPart = (MeshPart)part; if (meshPart.MeshID == null) { string partName = meshPart.Name; if (StandardLimbs.ContainsKey(partName)) { meshAsset = StandardLimbs[partName]; } } else { meshAsset = Asset.GetByAssetId(meshPart.MeshID); } if (meshPart.TextureID != null) { textureAsset = Asset.GetByAssetId(meshPart.TextureID); } scale = meshPart.Size / meshPart.InitialSize; offset = part.CFrame; } else { offset = part.CFrame; SpecialMesh specialMesh = part.FindFirstChildOfClass <SpecialMesh>(); if (specialMesh != null && specialMesh.MeshType == MeshType.FileMesh) { meshAsset = Asset.GetByAssetId(specialMesh.MeshId); scale = specialMesh.Scale; offset *= new CFrame(specialMesh.Offset); if (material != null) { textureAsset = Asset.GetByAssetId(specialMesh.TextureId); material.VertexColor = specialMesh.VertexColor; } } else { DataModelMesh legacy = part.FindFirstChildOfClass <DataModelMesh>(); if (legacy != null) { meshAsset = Head.ResolveHeadMeshAsset(legacy); scale = legacy.Scale; offset *= new CFrame(legacy.Offset); } } } } else { // Just give it a blank mesh to eat for now. result = new Mesh(); } if (meshAsset != null) { if (material != null) { material.TextureAsset = textureAsset; } result = FromAsset(meshAsset); result.BakeGeometry(scale, offset); } return(result); }
public static Mesh BakePart(BasePart part, ValveMaterial material = null) { Contract.Requires(part != null); Mesh result = null; Asset meshAsset = null; Asset textureAsset = null; Vector3 scale = null; CFrame offset = null; if (material != null) { material.LinkedTo = part; material.Reflectance = part.Reflectance; material.Transparency = part.Transparency; } if (part.Transparency < 1) { if (part is MeshPart meshPart) { string meshId = meshPart.MeshId; if (meshId != null && meshId.Length > 0) { meshAsset = Asset.GetByAssetId(meshId); } else { string partName = meshPart.Name; StandardLimbs.TryGetValue(partName, out meshAsset); } if (meshPart.TextureID != null) { textureAsset = Asset.GetByAssetId(meshPart.TextureID); } scale = meshPart.Size / meshPart.InitialSize; offset = part.CFrame; } else { SpecialMesh specialMesh = part.FindFirstChildOfClass <SpecialMesh>(); offset = part.CFrame; if (specialMesh != null && specialMesh.MeshType == MeshType.FileMesh) { meshAsset = Asset.GetByAssetId(specialMesh.MeshId); offset *= new CFrame(specialMesh.Offset); scale = specialMesh.Scale; if (material != null) { textureAsset = Asset.GetByAssetId(specialMesh.TextureId); material.VertexColor = specialMesh.VertexColor; } } else { DataModelMesh legacy = part.FindFirstChildOfClass <DataModelMesh>(); if (legacy != null) { meshAsset = Head.ResolveHeadMeshAsset(legacy); offset *= new CFrame(legacy.Offset); scale = legacy.Scale; } } } } else { // Just give it a blank mesh to eat for now. result = new Mesh(); } if (meshAsset != null) { if (material != null) { material.TextureAsset = textureAsset; } result = FromAsset(meshAsset); result.BakeGeometry(scale, offset); } return(result); }
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); }