public static void EmptyOutFiles(string folder, bool recursive = true) { DirectoryInfo info = new DirectoryInfo(folder); info.Attributes = FileAttributes.Normal; foreach (FileInfo file in info.GetFiles()) { try { file.Attributes = FileAttributes.Normal; file.Delete(); } catch { Rbx2Source.Print("{0} is locked.", file.Name); } } if (recursive) { foreach (DirectoryInfo directory in info.GetDirectories()) { directory.Attributes = FileAttributes.Normal; EmptyOutFiles(directory.FullName, true); } } }
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 Task Run() { List <string> paramStrings = new List <string>(); foreach (UtilParameter parameter in parameters) { paramStrings.Add(parameter.ToString()); } ProcessStartInfo info = new ProcessStartInfo(); info.Arguments = string.Join(" ", paramStrings.ToArray()); info.FileName = appPath; info.CreateNoWindow = true; info.UseShellExecute = false; info.RedirectStandardOutput = true; Process process = Process.Start(info); StreamReader output = process.StandardOutput; Task runTask = Task.Run(() => { while (true) { Task <string> nextLineAsync = output.ReadLineAsync(); nextLineAsync.Wait(1000); string nextLine = nextLineAsync.Result; if (nextLine == null) { break; } Rbx2Source.Print(nextLine); } }); return(runTask); }
public static string PendCdn(string address, bool log = true) { string result = null; bool final = false; string dots = ".."; while (!final && dots.Length <= 13) { CdnPender pender = DownloadJSON <CdnPender>(address); final = pender.Final; result = pender.Url; if (!final) { dots += "."; if (log) { Rbx2Source.Print("Waiting for finalization of " + address + dots); } wait(1f); } } if (dots.Length > 13) { throw new Exception("CdnPender timed out after 10 retries! Roblox's servers may be overloaded right now.\nTry again after a few minutes!"); } return(result); }
public static void BuildAvatarGeometry(StudioMdlWriter meshBuilder, StudioBone bone) { Contract.Requires(meshBuilder != null && bone != null); string task = "BuildGeometry_" + bone.Node.Name; Rbx2Source.ScheduleTasks(task); Node node = bone.Node; BasePart part = bone.Part1; bool isAvatarLimb = bone.IsAvatarBone; string matName = part.Name; if (isAvatarLimb) { BodyPart?limb = GetLimb(part); if (!limb.HasValue) { throw new ArgumentException("Provided StudioBone did not point to a limb correctly."); } matName = Rbx2Source.GetEnumName(limb.Value); } var material = new ValveMaterial() { UseAvatarMap = isAvatarLimb }; Rbx2Source.Print("Building Geometry for {0}", part.Name); Rbx2Source.IncrementStack(); Mesh geometry = Mesh.BakePart(part, material); meshBuilder.Materials[matName] = material; for (int i = 0; i < geometry.NumFaces; i++) { Triangle tri = new Triangle() { Node = node, FaceIndex = i, Mesh = geometry, Material = matName }; meshBuilder.Triangles.Add(tri); } Rbx2Source.DecrementStack(); Rbx2Source.MarkTaskCompleted(task); }
public void Write(StringWriter buffer, int stack = 0) { string tab = ""; for (int i = 0; i < stack; i++) { tab += "\t"; } List <string> lineBuff = new List <string>(); lineBuff.Add(tab + "$" + Name.ToLower()); foreach (string option in Options) { lineBuff.Add(option); } Rbx2Source.Print("Writing Command {0}", string.Join(" ", lineBuff.ToArray())); bool hasParameters = (Params.Count > 0); if (hasParameters) { lineBuff.Add(tab + "{"); } string firstLine = string.Join(" ", lineBuff.ToArray()); buffer.WriteLine(tab + firstLine); if (hasParameters) { foreach (QCParam param in Params) { List <string> paramBuff = new List <string>(); paramBuff.Add(param.Name); foreach (string value in param.Values) { paramBuff.Add(value); } string result = string.Join(" ", paramBuff); buffer.WriteLine(tab + "\t" + result); } buffer.WriteLine("}\n"); } else { foreach (QCommand subCommand in SubCommands) { subCommand.Write(buffer, stack + 1); } } }
private static void AddParts(List <BasePart> parts, Instance scan) { foreach (BasePart part in scan.GetChildrenOfClass <BasePart>()) { if (part.Transparency < 1) { parts.Add(part); Rbx2Source.Print("Found Part {0}", part.Name); } } scan.ForEachChild(inst => AddParts(parts, inst)); }
public static void BuildAvatarGeometry(StudioMdlWriter meshBuilder, Bone bone) { string task = "BuildGeometry_" + bone.Node.Name; Rbx2Source.ScheduleTasks(task); Node node = bone.Node; Part part = bone.Part1; bool IsAvatarLimb = bone.IsAvatarBone; string materialName; if (IsAvatarLimb) { Limb limb = GetLimb(part); materialName = Rbx2Source.GetEnumName(limb); } else { materialName = part.Name; } Material material = new Material(); material.UseAvatarMap = IsAvatarLimb; Rbx2Source.Print("Building Geometry for {0}", part.Name); Rbx2Source.IncrementStack(); Mesh geometry = Mesh.BakePart(part, material); if (!meshBuilder.Materials.ContainsKey(materialName)) { meshBuilder.Materials.Add(materialName, material); } for (int i = 0; i < geometry.FaceCount; i++) { Triangle tri = new Triangle(); tri.Node = node; tri.Mesh = geometry; tri.FaceIndex = i; tri.Material = materialName; meshBuilder.Triangles.Add(tri); } Rbx2Source.DecrementStack(); Rbx2Source.MarkTaskCompleted(task); }
private static void AddParts(List <Part> parts, Instance scan) { foreach (Part part in scan.GetChildrenOfClass <Part>()) { if (part.Transparency < 1) { parts.Add(part); Rbx2Source.Print("Found Part {0}", part.Name); } } foreach (Instance inst in scan.GetChildren()) { AddParts(parts, inst); } }
public static void BuildAvatarGeometry(StudioMdlWriter meshBuilder, Bone bone) { string task = "BuildGeometry_" + bone.Node.Name; Rbx2Source.ScheduleTasks(task); Node node = bone.Node; BasePart part = bone.Part1; bool isAvatarLimb = bone.IsAvatarBone; string matName = part.Name; if (isAvatarLimb) { Limb limb = GetLimb(part); matName = Rbx2Source.GetEnumName(limb); } Material material = new Material(); material.UseAvatarMap = isAvatarLimb; Rbx2Source.Print("Building Geometry for {0}", part.Name); Rbx2Source.IncrementStack(); Mesh geometry = Mesh.BakePart(part, material); meshBuilder.Materials[matName] = material; for (int i = 0; i < geometry.NumFaces; i++) { Triangle tri = new Triangle() { Node = node, FaceIndex = i, Mesh = geometry, Material = matName }; meshBuilder.Triangles.Add(tri); } Rbx2Source.DecrementStack(); Rbx2Source.MarkTaskCompleted(task); }
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 Task RunWithOutput() { Process process = Run(); StreamReader output = process.StandardOutput; Task runTask = Task.Run(() => { while (true) { Task <string> nextLineAsync = output.ReadLineAsync(); nextLineAsync.Wait(1000); string nextLine = nextLineAsync.Result; if (nextLine == null) { break; } Rbx2Source.Print(nextLine); } }); return(runTask); }
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); }
public static async Task <string> Compile(GameInfo gameInfo, AssemblerData data) { Contract.Requires(gameInfo != null && data != null); if (!gameInfo.ReadyToUse) { throw new Exception("This gameinfo.txt file isn't ready to use!"); } Rbx2Source.PrintHeader("COMPILING MODEL"); #region Compile Model ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// string studioMdlPath = gameInfo.StudioMdlPath; ThirdPartyUtility studioMdl = new ThirdPartyUtility(studioMdlPath); studioMdl.AddParameter("game", gameInfo.GameDirectory); studioMdl.AddParameter("nop4"); studioMdl.AddFile(data.CompilerScript); await studioMdl.RunWithOutput(); Rbx2Source.MarkTaskCompleted("CompileModel"); ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #endregion Rbx2Source.PrintHeader("COMPILING TEXTURES"); #region Compile Textures ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// if (!File.Exists(vtfCompilerPath)) { byte[] vtfZip = ResourceUtility.GetResource("VTFCmd.zip"); using (MemoryStream extract = new MemoryStream(vtfZip)) using (ZipArchive archive = new ZipArchive(extract)) { foreach (ZipArchiveEntry entry in archive.Entries) { string name = entry.Name; string path = Path.Combine(utilityDir, name); using (Stream stream = entry.Open()) { byte[] file = FileUtility.ReadFullStream(stream); FileUtility.WriteFile(path, file); } } } } string pngWildcard = Path.Combine(data.TextureDirectory, "*.png"); vtfCompiler = new ThirdPartyUtility(vtfCompilerPath); vtfCompiler.AddParameter("resize"); vtfCompiler.AddParameter("folder", pngWildcard); vtfCompiler.AddParameter("format", "ABGR8888"); // No compression? THIS IS FINE vtfCompiler.AddParameter("output", data.MaterialDirectory); await vtfCompiler.RunWithOutput(); Rbx2Source.MarkTaskCompleted("CompileTextures"); ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #endregion Rbx2Source.PrintHeader("MOVING TEXTURES"); #region Move Textures ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// string gameDirectory = gameInfo.GameDirectory; string modelPath = Path.Combine(gameDirectory, "models", data.ModelName); string materialPath = Path.Combine(gameDirectory, "materials", "models", data.CompileDirectory); FileUtility.InitiateEmptyDirectories(materialPath); foreach (string filePath in Directory.GetFiles(data.MaterialDirectory)) { FileInfo info = new FileInfo(filePath); string fileName = info.Name; Rbx2Source.Print("Moving File: {0}", fileName); Rbx2Source.IncrementStack(); Rbx2Source.Print("From: {0}", filePath); string destFilePath = Path.Combine(materialPath, fileName); info.CopyTo(destFilePath); Rbx2Source.Print("To: {0}", destFilePath); Rbx2Source.DecrementStack(); } Rbx2Source.MarkTaskCompleted("MoveTextures"); ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #endregion return(modelPath); }
public static Asset Get(long assetId, string idPiece = "/asset/?ID=") { if (!assetCache.ContainsKey(assetId)) { string appData = Environment.GetEnvironmentVariable("AppData"); string assetCacheDir = Path.Combine(appData, "Rbx2Source", "AssetCache"); Directory.CreateDirectory(assetCacheDir); // Ping Roblox to figure out what this asset's cdn url is HttpWebRequest ping = WebRequest.CreateHttp("https://assetgame.roblox.com" + idPiece + assetId); ping.UserAgent = "Roblox"; ping.Method = "HEAD"; ping.AllowAutoRedirect = false; HttpWebResponse response = (HttpWebResponse)ping.GetResponse(); string location = response.GetResponseHeader("Location"); string identifier = location.Remove(0, 7).Replace(".rbxcdn.com/", "-") + "_64"; string cachedFile = Path.Combine(assetCacheDir, identifier); response.Close(); Asset asset = null; if (File.Exists(cachedFile)) { string cachedContent = File.ReadAllText(cachedFile); try { asset = JsonConvert.DeserializeObject <Asset>(cachedContent); Rbx2Source.Print("Fetched pre-cached asset {0}", assetId); } catch { // Corrupted file? if (File.Exists(cachedFile)) { File.Delete(cachedFile); } } } if (asset == null) { asset = new Asset(); WebClient http = new WebClient(); http.UseDefaultCredentials = true; http.Headers.Set(HttpRequestHeader.UserAgent, "Roblox"); http.Proxy = null; asset.Id = assetId; try { string productInfoJson = http.DownloadString("http://api.roblox.com/marketplace/productinfo?assetId=" + assetId); asset.ProductInfo = JsonConvert.DeserializeObject <ProductInfo>(productInfoJson); asset.ProductInfo.WindowsSafeName = FileUtility.MakeNameWindowsSafe(asset.ProductInfo.Name); asset.AssetType = asset.ProductInfo.AssetTypeId; } catch { ProductInfo dummyInfo = new ProductInfo(); dummyInfo.Name = "unknown_" + asset.Id; dummyInfo.WindowsSafeName = dummyInfo.Name; dummyInfo.AssetTypeId = AssetType.Model; } asset.CdnUrl = location; asset.CdnCacheId = identifier; asset.GetContent(); asset.Loaded = true; string serialized = JsonConvert.SerializeObject(asset, Formatting.None, new JsonSerializerSettings()); try { File.WriteAllText(cachedFile, serialized); Rbx2Source.Print("Precached AssetId {0}", assetId); } catch { // Oh well. Rbx2Source.Print("Failed to cache AssetId {0}", assetId); } } assetCache[assetId] = asset; } return(assetCache[assetId]); }
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 TextureAssembly AssembleTextures(TextureCompositor compositor, Dictionary <string, Material> materials) { TextureAssembly assembly = new TextureAssembly(); assembly.Images = new Dictionary <string, Image>(); assembly.MatLinks = new Dictionary <string, string>(); Bitmap uvMap = compositor.BakeTextureMap(); Rbx2Source.SetDebugImage(uvMap); ImageAttributes blankAtt = new ImageAttributes(); foreach (string materialName in materials.Keys) { Rbx2Source.Print("Building Material {0}", materialName); Material material = materials[materialName]; Image image = null; if (material.UseAvatarMap) { Limb limb; if (Enum.TryParse(materialName, out limb)) { Rectangle cropRegion = UVCrops[limb]; Size size = cropRegion.Size; int w = size.Width; int h = size.Height; Point origin = cropRegion.Location; int x = origin.X; int y = origin.Y; Bitmap newImg = new Bitmap(w, h); Graphics graphics = Graphics.FromImage(newImg); Rectangle dest = new Rectangle(Point.Empty, size); graphics.DrawImage(uvMap, dest, x, y, w, h, GraphicsUnit.Pixel, blankAtt); graphics.Dispose(); image = newImg; } } else { Asset texture = material.TextureAsset; if (texture != null) { byte[] textureData = texture.GetContent(); MemoryStream textureStream = new MemoryStream(textureData); image = Image.FromStream(textureStream); } } if (image != null) { assembly.Images.Add(materialName, image); assembly.MatLinks.Add(materialName, materialName); } else { Rbx2Source.Print("Missing Image for Material {0}?", materialName); } } return(assembly); }
public Bitmap BakeTextureMap() { var bitmap = new Bitmap(canvas.Width, canvas.Height); layers.Sort(); composed = 0; Rbx2Source.Print("Composing " + context + "..."); Rbx2Source.IncrementStack(); foreach (CompositData composit in layers) { var buffer = Graphics.FromImage(bitmap); var drawFlags = composit.DrawFlags; var canvas = composit.Rect; if (drawFlags.HasFlag(DrawFlags.Rect)) { if (drawFlags.HasFlag(DrawFlags.Color)) { composit.UseBrush(brush => buffer.FillRectangle(brush, canvas)); } else if (drawFlags.HasFlag(DrawFlags.Texture)) { Bitmap image = composit.GetTextureBitmap(); if (composit.FlipMode > 0) { image.RotateFlip(composit.FlipMode); } buffer.DrawImage(image, canvas); } } else if (drawFlags.HasFlag(DrawFlags.Guide)) { Mesh guide = composit.Guide; for (int face = 0; face < guide.NumFaces; face++) { Vertex[] verts = composit.GetGuideVerts(face); Point offset = canvas.Location; Point[] poly = verts .Select(vert => vert.ToPoint(canvas, offset)) .ToArray(); if (drawFlags.HasFlag(DrawFlags.Color)) { composit.UseBrush(brush => buffer.FillPolygon(brush, poly)); } else if (drawFlags.HasFlag(DrawFlags.Texture)) { Bitmap texture = composit.GetTextureBitmap(); Rectangle bbox = GetBoundingBox(poly); Point origin = bbox.Location; Bitmap drawLayer = new Bitmap(bbox.Width, bbox.Height); Point[] uv = verts .Select(vert => vert.ToUV(texture)) .ToArray(); int origin_X = origin.X, origin_Y = origin.Y; for (int x = bbox.Left; x < bbox.Right; x++) { for (int y = bbox.Top; y < bbox.Bottom; y++) { var pixel = new Point(x, y); var bcPoint = new BarycentricPoint(pixel, poly); if (bcPoint.InBounds()) { var uvPixel = bcPoint.ToCartesian(uv); Color color = texture.GetPixel(uvPixel.X, uvPixel.Y); drawLayer.SetPixel(x - origin_X, y - origin_Y, color); } } } buffer.DrawImage(drawLayer, origin); drawLayer.Dispose(); } } } Rbx2Source.Print("{0}/{1} layers composed...", ++composed, layers.Count); if (layers.Count > 2) { Rbx2Source.SetDebugImage(bitmap); } buffer.Dispose(); } Rbx2Source.Print("Done!"); Rbx2Source.DecrementStack(); return(bitmap); }
public static void WriteFile(string path, string content) { File.WriteAllText(path, content); Rbx2Source.Print("Wrote file: {0}", path); }
public Bitmap BakeTextureMap() { Bitmap bitmap = new Bitmap(canvas.Width, canvas.Height); layers.Sort(); composed = 0; Rbx2Source.Print("Composing " + context + "..."); Rbx2Source.IncrementStack(); foreach (CompositData composit in layers) { Graphics buffer = Graphics.FromImage(bitmap); DrawMode drawMode = composit.DrawMode; DrawType drawType = composit.DrawType; Rectangle compositCanvas = composit.Rect; if (drawMode == DrawMode.Rect) { if (drawType == DrawType.Color) { using (Brush brush = new SolidBrush(composit.DrawColor)) buffer.FillRectangle(brush, compositCanvas); } else if (drawType == DrawType.Texture) { Bitmap image = composit.GetTextureBitmap(); if (composit.FlipMode > 0) { image.RotateFlip(composit.FlipMode); } buffer.DrawImage(image, compositCanvas); } } else if (drawMode == DrawMode.Guide) { Mesh guide = composit.Guide; for (int face = 0; face < guide.FaceCount; face++) { Vertex[] verts = composit.GetGuideVerts(face); Point offset = compositCanvas.Location; Point vert_a = CompositUtil.VertexToPoint(verts[0], compositCanvas, offset); Point vert_b = CompositUtil.VertexToPoint(verts[1], compositCanvas, offset); Point vert_c = CompositUtil.VertexToPoint(verts[2], compositCanvas, offset); Point[] polygon = new Point[3] { vert_a, vert_b, vert_c }; if (drawType == DrawType.Color) { using (Brush brush = new SolidBrush(composit.DrawColor)) buffer.FillPolygon(brush, polygon); } else if (drawType == DrawType.Texture) { Bitmap texture = composit.GetTextureBitmap(); Rectangle bbox = CompositUtil.GetBoundingBox(vert_a, vert_b, vert_c); Point origin = bbox.Location; int width = bbox.Width; int height = bbox.Height; Bitmap drawLayer = new Bitmap(width, height); Point uv_a = CompositUtil.VertexToUV(verts[0], texture); Point uv_b = CompositUtil.VertexToUV(verts[1], texture); Point uv_c = CompositUtil.VertexToUV(verts[2], texture); for (int x = bbox.Left; x < bbox.Right; x++) { for (int y = bbox.Top; y < bbox.Bottom; y++) { Point pixel = new Point(x, y); BarycentricPoint bcPixel = CompositUtil.ToBarycentric(pixel, vert_a, vert_b, vert_c); if (CompositUtil.InTriangle(bcPixel)) { Point uvPixel = CompositUtil.ToCartesian(bcPixel, uv_a, uv_b, uv_c); Color color = texture.GetPixel(uvPixel.X, uvPixel.Y); drawLayer.SetPixel(x - origin.X, y - origin.Y, color); } } } buffer.DrawImage(drawLayer, origin); drawLayer.Dispose(); } } } Rbx2Source.Print("{0}/{1} layers composed...", ++composed, layers.Count); if (layers.Count > 2) { Rbx2Source.SetDebugImage(bitmap); } buffer.Dispose(); } Rbx2Source.Print("Done!"); Rbx2Source.DecrementStack(); return(bitmap); }
public static Asset Get(long assetId, string idPiece = "/asset/?ID=") { if (!assetCache.ContainsKey(assetId)) { string appData = Environment.GetEnvironmentVariable("LocalAppData"); string assetCacheDir = Path.Combine(appData, "Rbx2Source", "AssetCache"); Directory.CreateDirectory(assetCacheDir); // Ping Roblox to figure out what this asset's cdn url is Uri uri = new Uri("https://assetdelivery.roblox.com/v1" + idPiece + assetId); HttpWebRequest ping = WebRequest.CreateHttp(uri); ping.UserAgent = "RobloxStudio/WinInet"; ping.AllowAutoRedirect = false; Asset asset = null; string location = ""; string identifier = ""; string cachedFile = ""; try { using (var response = ping.GetResponse() as HttpWebResponse) { location = response.GetResponseHeader("Location"); identifier = location.Remove(0, 8).Replace(".rbxcdn.com/", "-"); cachedFile = assetCacheDir + '\\' + identifier.Replace('/', '\\'); if (File.Exists(cachedFile)) { string cachedContent = File.ReadAllText(cachedFile); try { asset = JsonConvert.DeserializeObject <Asset>(cachedContent); if (asset.Content.Length == 0) { asset = null; throw new Exception(); } Rbx2Source.Print("Fetched pre-cached asset {0}", assetId); } catch { // Corrupted file? if (File.Exists(cachedFile)) { Rbx2Source.Print("Deleting corrupted file {0}", cachedFile); File.Delete(cachedFile); } } } } } catch { Console.WriteLine("Failed to fetch {0}?", assetId); } if (asset == null) { WebClient http = new WebClient() { UseDefaultCredentials = true, Proxy = null }; http.Headers.Set(HttpRequestHeader.UserAgent, "RobloxStudio/WinInet"); asset = new Asset() { Id = assetId }; try { string productInfoJson = http.DownloadString("http://api.roblox.com/marketplace/productinfo?assetId=" + assetId); asset.ProductInfo = JsonConvert.DeserializeObject <ProductInfo>(productInfoJson); asset.ProductInfo.WindowsSafeName = FileUtility.MakeNameWindowsSafe(asset.ProductInfo.Name); asset.AssetType = asset.ProductInfo.AssetTypeId; } catch { string name = "unknown_" + asset.Id; ProductInfo dummyInfo = new ProductInfo() { Name = name, WindowsSafeName = name, AssetTypeId = AssetType.Model }; asset.ProductInfo = dummyInfo; } asset.CdnUrl = location; asset.CdnCacheId = identifier; asset.GetContent(); asset.Loaded = true; string serialized = JsonConvert.SerializeObject(asset, Formatting.None); try { File.WriteAllText(cachedFile, serialized); Rbx2Source.Print("Precached AssetId {0}", assetId); } catch { // Oh well. Rbx2Source.Print("Failed to cache AssetId {0}", assetId); } http.Dispose(); } assetCache[assetId] = asset; } return(assetCache[assetId]); }
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); }
public AssemblerData Assemble(long assetId) { Asset asset = Asset.Get(assetId); string assetName = asset.ProductInfo.WindowsSafeName.Trim(); string appData = Environment.GetEnvironmentVariable("LocalAppData"); string rbx2Source = Path.Combine(appData, "Rbx2Source"); string items = Path.Combine(rbx2Source, "Items"); string rootDir = Path.Combine(items, assetName); string modelDir = Path.Combine(rootDir, "Model"); string texturesDir = Path.Combine(rootDir, "Textures"); string materialsDir = Path.Combine(rootDir, "Materials"); FileUtility.InitiateEmptyDirectories(modelDir, texturesDir, materialsDir); Rbx2Source.ScheduleTasks("BuildModel", "BuildTextures", "BuildMaterials", "BuildCompilerScript"); Rbx2Source.PrintHeader("BUILDING MODEL"); #region Build Model /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// StudioMdlWriter writer = AssembleModel(asset); string studioMdl = writer.BuildFile(); string modelPath = Path.Combine(modelDir, "Asset.smd"); FileUtility.WriteFile(modelPath, studioMdl); string reference = writer.BuildFile(false); string refPath = Path.Combine(modelDir, "Reference.smd"); FileUtility.WriteFile(refPath, reference); Rbx2Source.MarkTaskCompleted("BuildModel"); /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #endregion Rbx2Source.PrintHeader("BUILDING TEXTURES"); #region Build Textures /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// var materials = writer.Materials; var textures = BindTextures(materials); var images = textures.Images; var compileDir = "roblox_assets/" + assetName; foreach (string imageName in images.Keys) { Rbx2Source.Print("Writing Image {0}", 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); } textures.MaterialDirectory = compileDir; Rbx2Source.MarkTaskCompleted("BuildTextures"); /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #endregion Rbx2Source.PrintHeader("WRITING MATERIAL FILES"); #region Write Materials /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// string mtlDir = "models/" + compileDir; var matLinks = textures.MatLinks; foreach (string matName in matLinks.Keys) { string vtfTarget = matLinks[matName]; string vmtPath = Path.Combine(materialsDir, matName + ".vmt"); ValveMaterial mat = materials[matName]; mat.SetVmtField("basetexture", mtlDir + '/' + vtfTarget); mat.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", assetName, "Asset.smd"); qc.Add("modelname", modelName); qc.Add("upaxis", "y"); qc.Add("cdmaterials", mtlDir); QuakeCItem phys = qc.Add("collisionjoints", "Asset.smd"); phys.AddSubItem("$mass", 115.0); phys.AddSubItem("$inertia", 2.00); phys.AddSubItem("$damping", 0.01); phys.AddSubItem("$rotdamping", 0.40); QuakeCItem refAnim = qc.Add("sequence", "reference", "Reference.smd"); refAnim.AddSubItem("fps", 1); refAnim.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 = rootDir, CompileDirectory = compileDir, TextureDirectory = texturesDir, MaterialDirectory = materialsDir }; return(data); }
public AssemblerData Assemble(object metadata) { long assetId = (long)metadata; Asset asset = Asset.Get(assetId); string assetName = asset.ProductInfo.WindowsSafeName; string appData = Environment.GetEnvironmentVariable("AppData"); string rbx2Source = Path.Combine(appData, "Rbx2Source"); string items = Path.Combine(rbx2Source, "Items"); string rootDir = Path.Combine(items, assetName); string modelDir = Path.Combine(rootDir, "Model"); string texturesDir = Path.Combine(rootDir, "Textures"); string materialsDir = Path.Combine(rootDir, "Materials"); FileUtility.InitiateEmptyDirectories(modelDir, texturesDir, materialsDir); Rbx2Source.ScheduleTasks("BuildModel", "BuildTextures", "BuildMaterials", "BuildCompilerScript"); // Build Model StudioMdlWriter writer = AssembleModel(asset); string studioMdl = writer.BuildFile(); string modelPath = Path.Combine(modelDir, "Asset.smd"); FileUtility.WriteFile(modelPath, studioMdl); // Build Reference Sequence Triangle[] triangles = writer.Triangles.ToArray(); writer.Triangles.Clear(); string reference = writer.BuildFile(); string refPath = Path.Combine(modelDir, "Reference.smd"); FileUtility.WriteFile(refPath, reference); Rbx2Source.MarkTaskCompleted("BuildModel"); // Build Textures Rbx2Source.PrintHeader("BUILDING TEXTURES"); Dictionary <string, Material> materials = writer.Materials; string compileDirectory = "roblox_assets/" + assetName; TextureAssembly texAssembly = AssembleTextures(materials); texAssembly.MaterialDirectory = compileDirectory; Dictionary <string, Image> images = texAssembly.Images; foreach (string imageName in images.Keys) { Rbx2Source.Print("Writing Image {0}", 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"); // Build Materials Rbx2Source.PrintHeader("BUILDING MATERIAL FILES"); string mtlDir = "models/" + compileDirectory; Dictionary <string, string> matLinks = texAssembly.MatLinks; Dictionary <string, Material> matLookup = new Dictionary <string, Material>(); foreach (string mtlName in matLinks.Keys) { Material mtl = materials[mtlName]; string vtfTarget = matLinks[mtlName]; string vmtPath = Path.Combine(materialsDir, mtlName + ".vmt"); if (!File.Exists(vmtPath)) { Rbx2Source.Print("Building VMT {0}.vmt", mtlName); ValveMaterial vmt = new ValveMaterial(mtl); vmt.SetField("basetexture", mtlDir + "/" + vtfTarget); string vmtContent = vmt.ToString(); FileUtility.WriteFile(vmtPath, vmtContent); matLookup[mtlName] = mtl; } } Rbx2Source.MarkTaskCompleted("BuildMaterials"); // Build Compiler Script Rbx2Source.PrintHeader("WRITING COMPILER SCRIPT"); QCWriter qc = new QCWriter(); QCommand model = new QCommand("body", assetName, "Asset.smd"); qc.AddCommand(model); string modelNameStr = compileDirectory + ".mdl"; qc.WriteBasicCmd("modelname", modelNameStr); qc.WriteBasicCmd("upaxis", "y"); qc.WriteBasicCmd("cdmaterials", mtlDir); QCommand collision = new QCommand("collisionjoints", "Asset.smd"); collision.AddParameter("$mass", 115.0); collision.AddParameter("$inertia", 2.00); collision.AddParameter("$damping", 0.01); collision.AddParameter("$rotdamping", 0.40); qc.AddCommand(collision); QCommand sequence = new QCommand("sequence", "reference", "Reference.smd"); sequence.AddParameter("fps", 1); sequence.AddParameter("loop"); 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 = rootDir; data.MaterialDirectory = materialsDir; data.TextureDirectory = texturesDir; data.CompileDirectory = compileDirectory; data.ModelName = modelNameStr; 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 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 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 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); }