protected List <MiloObject> GetEntries(MiloObjectDir miloDir) { var entries = new List <MiloObject>(); GetEntries(miloDir, entries); return(entries); }
protected MiloObjectDirEntry ParseWorldDir(AwesomeReader ar, MiloObjectDir dir) { // Read past unknown stuff, big hack int unk1 = ar.ReadInt32(); int unk2 = ar.ReadInt32(); float unk3 = ar.ReadSingle(); string unk4 = ar.ReadString(); int unk5 = ar.ReadInt32(); int unk6 = ar.ReadInt32(); // Hack for project 9 var dirEntry = new MiloObjectDirEntry() { Name = dir.Name }; dirEntry.Version = ar.ReadInt32(); dirEntry.SubVersion = ar.ReadInt32(); dirEntry.ProjectName = ar.ReadString(); // Skip matrices + constants var matCount = ar.ReadInt32(); // Usually 7 ar.BaseStream.Position += (matCount * 48) + 9; // Read imported milos var importedMiloCount = ar.ReadInt32(); dirEntry.ImportedMiloPaths = Enumerable.Range(0, importedMiloCount) .Select(x => ar.ReadString()) .ToArray(); // Boolean, true when sub directory? ar.ReadBoolean(); // Sub directory names seem to be in reverse order of serialization... var subDirCount = ar.ReadInt32(); var subDirNames = Enumerable.Range(0, subDirCount) .Select(x => ar.ReadString()) .ToArray(); // Read subdirectories foreach (var _ in subDirNames.Reverse()) { var subDir = new MiloObjectDir(); ReadFromStream(ar, subDir); dirEntry.SubDirectories.Add(subDir); } ar.BaseStream.Position += 17; // 0'd data + ADDE return(dirEntry); }
protected MiloObject ParseDirEntryAsBlob(AwesomeReader ar, MiloObjectDir dir, string dirType) { // Reads data as a byte array var entrySize = GuessEntrySize(ar); var entryBytes = new MiloObjectBytes(dirType) { Name = dir.Name }; entryBytes.Data = ar.ReadBytes((int)entrySize); ar.BaseStream.Position += 4; return(entryBytes); }
protected void GetEntries(MiloObjectDir miloDir, List <MiloObject> entries) { if (miloDir.Type != "ObjectDir") { throw new UnsupportedMiloException($"Directory type of \"{miloDir.Type}\" found, expected \"ObjectDir\" expected"); } // Traverse sub directories if (!(miloDir.GetDirectoryEntry() is MiloObjectDirEntry dirEntry)) { throw new UnsupportedMiloException("Could not parse directory entry"); } foreach (var subDir in dirEntry.SubDirectories) { GetEntries(subDir, entries); } // Add entries in current directory entries.AddRange(miloDir.Entries); }
public IActionResult ExtractFilesFromArk([FromBody] ScanRequest request, bool extractMilos, bool extractDTAs, bool convertTextures) { Archive ark; if (!System.IO.File.Exists(request.InputPath)) { // Open as directory if available if (Directory.Exists(request.InputPath)) { ark = ArkFileSystem.FromDirectory(request.InputPath); } else { return(BadRequest($"File \"{request.InputPath}\" does not exist!")); } } else { // Open as archive ark = ArkFile.FromFile(request.InputPath); } if (request.OutputPath == null) { return(BadRequest($"Output directory cannot be null!")); } string CombinePath(string basePath, string path) { // Consistent slash basePath = (request.OutputPath ?? "").Replace("/", "\\"); path = (path ?? "").Replace("/", "\\"); Regex dotRegex = new Regex(@"[.]+[\\]"); if (dotRegex.IsMatch(path)) { // Replaces dotdot path path = dotRegex.Replace(path, x => $"({x.Value.Substring(0, x.Value.Length - 1)})\\"); } return(Path.Combine(basePath, path)); } string GetNonGenPath(string path) { // Consistent slash path = (path ?? "").Replace("/", "\\"); Regex genRegex = new Regex(@"gen\\[^\\]+$", RegexOptions.IgnoreCase); Regex platformRegex = new Regex(@"_[^_]+$", RegexOptions.IgnoreCase); // TODO: Revisit for Forge extensions if (genRegex.IsMatch(path)) { var splitPath = path.Split('\\'); var dir = string.Join("\\", splitPath.SkipLast(2)); var file = platformRegex.Replace(splitPath.Last(), ""); path = $"{dir}\\{file}"; } return(path); } void SaveAsFile(MiloObjectBytes miloEntry, string basePath) { var fileName = SanitizeFileName(miloEntry.Name); var filePath = basePath = Path.Combine(basePath, miloEntry.Type, fileName); if (!Directory.Exists(Path.GetDirectoryName(filePath))) { Directory.CreateDirectory(Path.GetDirectoryName(filePath)); } System.IO.File.WriteAllBytes(filePath, miloEntry.Data); Console.WriteLine($"Wrote \"{filePath}\""); } // Extract everything if (!extractDTAs && !extractMilos && !convertTextures) { foreach (var arkEntry in ark.Entries) { var filePath = CombinePath(request.OutputPath, arkEntry.FullPath); if (!Directory.Exists(Path.GetDirectoryName(filePath))) { Directory.CreateDirectory(Path.GetDirectoryName(filePath)); } using (var fs = System.IO.File.Open(filePath, FileMode.OpenOrCreate, FileAccess.Write)) { using (var stream = ark.GetArkEntryFileStream(arkEntry)) { stream.CopyTo(fs); } } Console.WriteLine($"Wrote \"{filePath}\""); } return(Ok()); } string SanitizeFileName(string fileName) { // Sanitize file name var invalidChars = new Regex($"[{new string(Path.GetInvalidFileNameChars())}]", RegexOptions.IgnoreCase); return(invalidChars.Replace(fileName, "")); } void ProcessMiloArkEntry(ArkEntry miloArkEntry) { var filePath = CombinePath(request.OutputPath, GetNonGenPath(miloArkEntry.FullPath)); using (var stream = ark.GetArkEntryFileStream(miloArkEntry)) { var milo = MiloFile.ReadFromStream(stream); var miloSerializer = new MiloSerializer(new SystemInfo() { Version = milo.Version, BigEndian = milo.BigEndian, Platform = Enum.Parse <Platform>(request.Platform) }); var miloDir = new MiloObjectDir(); using (var ms = new MemoryStream(milo.Data)) { miloSerializer.ReadFromStream(ms, miloDir); } if (convertTextures) { var textureEntries = miloDir.Entries .Where(x => x.Type == "Tex") .Select(x => x is Tex ? x as Tex : miloSerializer.ReadFromMiloObjectBytes <Tex>(x as MiloObjectBytes)) .Where(x => x.Bitmap != null && x.Bitmap.RawData?.Length > 0) .ToList(); if (textureEntries.Count <= 0) { return; } foreach (var texEntry in textureEntries) { var entryName = Path.GetFileNameWithoutExtension(SanitizeFileName(texEntry.Name)); var pngName = $"{entryName}.png"; var pngPath = Path.Combine(filePath, texEntry.Type, pngName); texEntry.Bitmap.SaveAs(miloSerializer.Info, pngPath); Console.WriteLine($"Wrote \"{pngPath}\""); // Write DTA script to file var scriptName = texEntry?.ScriptName ?? ""; if (texEntry.Script != null) { var dtaName = (string.IsNullOrEmpty(scriptName)) ? $"{entryName}.dta" : $"{entryName}_{scriptName}.dta"; var dtaPath = Path.Combine(filePath, texEntry.Type, dtaName); var parent = new ParentItem(ParentType.Default); foreach (var item in texEntry.Script.Items) { parent.Add(item); } texEntry.Script.Items.Clear(); texEntry.Script.Items.Add(parent); System.IO.File.WriteAllText(dtaPath, texEntry.Script.ToString()); Console.WriteLine($"Wrote \"{dtaPath}\""); } } } if (extractMilos) { if (miloDir.Entries.Count <= 0) { return; } if (!Directory.Exists(Path.GetDirectoryName(filePath))) { Directory.CreateDirectory(Path.GetDirectoryName(filePath)); } // Saves milo entries miloDir.Entries.ForEach(x => { SaveAsFile(x as MiloObjectBytes, filePath); Console.WriteLine($"Wrote \"{filePath}\""); }); if (miloDir.Extras.ContainsKey("DirectoryEntry")) { SaveAsFile(miloDir.Extras["DirectoryEntry"] as MiloObjectBytes, filePath); Console.WriteLine($"Wrote \"{filePath}\""); } } } } void ProcessBitmapArkEntry(ArkEntry bitmapArkEntry) { var arkPath = GetNonGenPath(bitmapArkEntry.FullPath); var filePath = CombinePath(request.OutputPath, $"{Path.GetDirectoryName(arkPath)}\\{Path.GetFileNameWithoutExtension(bitmapArkEntry.FileName)}.png"); using (var stream = ark.GetArkEntryFileStream(bitmapArkEntry)) { var miloSerializer = new MiloSerializer(new SystemInfo() { Version = 25, // 10 = gh1, 24 = gh2 BigEndian = false, // Even on PPC it's LE Platform = Enum.Parse <Platform>(request.Platform) }); var bitmap = miloSerializer.ReadFromStream <HMXBitmap>(stream); bitmap.SaveAs(miloSerializer.Info, filePath); Console.WriteLine($"Wrote \"{filePath}\""); } } // Extract milos if (extractMilos || convertTextures) { var miloEntries = ark.Entries.Where(x => _miloRegex.IsMatch(x.FullPath)); Parallel.ForEach(miloEntries, (entry) => { ProcessMiloArkEntry(entry); }); //foreach (var entry in miloEntries) ProcessMiloArkEntry(entry); } // Convert textures if (convertTextures) { var bitmapArkEntries = ark.Entries.Where(x => _bitmapRegex.IsMatch(x.FullPath)); Parallel.ForEach(bitmapArkEntries, (entry) => { ProcessBitmapArkEntry(entry); }); //foreach (var entry in bitmapArkEntries) ProcessBitmapArkEntry(entry); } return(Ok()); }
public override void ReadFromStream(AwesomeReader ar, ISerializable data) { var dir = data as MiloObjectDir; int version = ReadMagic(ar, data); string dirType = null, dirName = null; dir.Extras.Clear(); // Clears for good measure if (version >= 24) { // Parses directory type/name dirType = ar.ReadString(); dirName = FileHelper.SanitizePath(ar.ReadString()); //ar.BaseStream.Position += 8; // Skips string count + total length dir.Extras.Add("Num1", ar.ReadInt32()); dir.Extras.Add("Num2", ar.ReadInt32()); } int entryCount = ar.ReadInt32(); var entries = Enumerable.Range(0, entryCount).Select(x => new { Type = ar.ReadString(), Name = FileHelper.SanitizePath(ar.ReadString()) }).ToArray(); if (version == 10) { // Parses external resource paths? entryCount = ar.ReadInt32(); // Note: Entry can be empty var external = Enumerable.Range(0, entryCount) .Select(x => ar.ReadString()) .ToList(); dir.Extras.Add("ExternalResources", external); } else if (version == 25 && dirType == "ObjectDir") { // Hack for project 9 var dirEntry = new MiloObjectDirEntry() { Name = dirName }; dirEntry.Version = ar.ReadInt32(); dirEntry.SubVersion = ar.ReadInt32(); dirEntry.ProjectName = ar.ReadString(); // Skip matrices + constants var matCount = ar.ReadInt32(); // Usually 7 ar.BaseStream.Position += (matCount * 48) + 9; // Read imported milos var importedMiloCount = ar.ReadInt32(); dirEntry.ImportedMiloPaths = Enumerable.Range(0, importedMiloCount) .Select(x => ar.ReadString()) .ToArray(); // Boolean, true when sub directory? ar.ReadBoolean(); // Sub directory names seem to be in reverse order of serialization... var subDirCount = ar.ReadInt32(); var subDirNames = Enumerable.Range(0, subDirCount) .Select(x => ar.ReadString()) .ToArray(); // Read subdirectories foreach (var _ in subDirNames.Reverse()) { var subDir = new MiloObjectDir(); ReadFromStream(ar, subDir); dirEntry.SubDirectories.Add(subDir); } dir.Extras.Add("DirectoryEntry", dirEntry); ar.BaseStream.Position += 17; // 0'd data + ADDE } else if (version == 25 && (dirType == "WorldDir")) { // Read past unknown stuff, big hack int unk1 = ar.ReadInt32(); int unk2 = ar.ReadInt32(); float unk3 = ar.ReadSingle(); string unk4 = ar.ReadString(); int unk5 = ar.ReadInt32(); int unk6 = ar.ReadInt32(); // Hack for project 9 var dirEntry = new MiloObjectDirEntry() { Name = dirName }; dirEntry.Version = ar.ReadInt32(); dirEntry.SubVersion = ar.ReadInt32(); dirEntry.ProjectName = ar.ReadString(); // Skip matrices + constants var matCount = ar.ReadInt32(); // Usually 7 ar.BaseStream.Position += (matCount * 48) + 9; // Read imported milos var importedMiloCount = ar.ReadInt32(); dirEntry.ImportedMiloPaths = Enumerable.Range(0, importedMiloCount) .Select(x => ar.ReadString()) .ToArray(); // Boolean, true when sub directory? ar.ReadBoolean(); // Sub directory names seem to be in reverse order of serialization... var subDirCount = ar.ReadInt32(); var subDirNames = Enumerable.Range(0, subDirCount) .Select(x => ar.ReadString()) .ToArray(); // Read subdirectories foreach (var _ in subDirNames.Reverse()) { var subDir = new MiloObjectDir(); ReadFromStream(ar, subDir); dirEntry.SubDirectories.Add(subDir); } dir.Extras.Add("DirectoryEntry", dirEntry); ar.BaseStream.Position += 17; // 0'd data + ADDE } else if (version == 25 && (dirType == "RndDir" || dirType == "SynthDir")) { // Read past unknown stuff, big hack int unk1 = ar.ReadInt32(); // Hack for project 9 var dirEntry = new MiloObjectDirEntry() { Name = dirName }; dirEntry.Version = ar.ReadInt32(); dirEntry.SubVersion = ar.ReadInt32(); dirEntry.ProjectName = ar.ReadString(); // Skip matrices + constants var matCount = ar.ReadInt32(); // Usually 7 ar.BaseStream.Position += (matCount * 48) + 9; // Read imported milos var importedMiloCount = ar.ReadInt32(); dirEntry.ImportedMiloPaths = Enumerable.Range(0, importedMiloCount) .Select(x => ar.ReadString()) .ToArray(); // Boolean, true when sub directory? ar.ReadBoolean(); // Sub directory names seem to be in reverse order of serialization... var subDirCount = ar.ReadInt32(); var subDirNames = Enumerable.Range(0, subDirCount) .Select(x => ar.ReadString()) .ToArray(); // Read subdirectories foreach (var _ in subDirNames.Reverse()) { var subDir = new MiloObjectDir(); ReadFromStream(ar, subDir); dirEntry.SubDirectories.Add(subDir); } dir.Extras.Add("DirectoryEntry", dirEntry); ar.BaseStream.Position += 17; // 0'd data + ADDE } else if (version == 25 && (dirType == "PanelDir" || dirType == "VocalTrackDir" || dirType == "UILabelDir" || dirType == "UILabelDir" || dirType == "UIListDir" || dirType == "PitchArrowDir" || dirType == "Character")) { // Read past unknown stuff, big hack int unk1 = ar.ReadInt32(); int unk2 = ar.ReadInt32(); // Hack for project 9 var dirEntry = new MiloObjectDirEntry() { Name = dirName }; dirEntry.Version = ar.ReadInt32(); dirEntry.SubVersion = ar.ReadInt32(); dirEntry.ProjectName = ar.ReadString(); // Skip matrices + constants var matCount = ar.ReadInt32(); // Usually 7 ar.BaseStream.Position += (matCount * 48) + 9; // Read imported milos var importedMiloCount = ar.ReadInt32(); dirEntry.ImportedMiloPaths = Enumerable.Range(0, importedMiloCount) .Select(x => ar.ReadString()) .ToArray(); // Boolean, true when sub directory? ar.ReadBoolean(); // Sub directory names seem to be in reverse order of serialization... var subDirCount = ar.ReadInt32(); var subDirNames = Enumerable.Range(0, subDirCount) .Select(x => ar.ReadString()) .ToArray(); // Read subdirectories foreach (var _ in subDirNames.Reverse()) { var subDir = new MiloObjectDir(); ReadFromStream(ar, subDir); dirEntry.SubDirectories.Add(subDir); } dir.Extras.Add("DirectoryEntry", dirEntry); ar.BaseStream.Position += 17; // 0'd data + ADDE } else if (version == 25 && (dirType == "BackgroundDir" || dirType == "WorldInstance")) { // Read past unknown stuff, big hack int unk1 = ar.ReadInt32(); int unk2 = ar.ReadInt32(); int unk3 = ar.ReadInt32(); // Hack for project 9 var dirEntry = new MiloObjectDirEntry() { Name = dirName }; dirEntry.Version = ar.ReadInt32(); dirEntry.SubVersion = ar.ReadInt32(); dirEntry.ProjectName = ar.ReadString(); // Skip matrices + constants var matCount = ar.ReadInt32(); // Usually 7 ar.BaseStream.Position += (matCount * 48) + 9; // Read imported milos var importedMiloCount = ar.ReadInt32(); dirEntry.ImportedMiloPaths = Enumerable.Range(0, importedMiloCount) .Select(x => ar.ReadString()) .ToArray(); // Boolean, true when sub directory? ar.ReadBoolean(); // Sub directory names seem to be in reverse order of serialization... var subDirCount = ar.ReadInt32(); var subDirNames = Enumerable.Range(0, subDirCount) .Select(x => ar.ReadString()) .ToArray(); // Read subdirectories foreach (var _ in subDirNames.Reverse()) { var subDir = new MiloObjectDir(); ReadFromStream(ar, subDir); dirEntry.SubDirectories.Add(subDir); } dir.Extras.Add("DirectoryEntry", dirEntry); ar.BaseStream.Position += 17; // 0'd data + ADDE } else if (version == 25 && (dirType == "TrackPanelDir" || dirType == "H2HTrackPanelDir")) { // Read past unknown stuff, big hack int unk1 = ar.ReadInt32(); int unk2 = ar.ReadInt32(); int unk3 = ar.ReadInt32(); int unk4 = ar.ReadInt32(); // Hack for project 9 var dirEntry = new MiloObjectDirEntry() { Name = dirName }; dirEntry.Version = ar.ReadInt32(); dirEntry.SubVersion = ar.ReadInt32(); dirEntry.ProjectName = ar.ReadString(); // Skip matrices + constants var matCount = ar.ReadInt32(); // Usually 7 ar.BaseStream.Position += (matCount * 48) + 9; // Read imported milos var importedMiloCount = ar.ReadInt32(); dirEntry.ImportedMiloPaths = Enumerable.Range(0, importedMiloCount) .Select(x => ar.ReadString()) .ToArray(); // Boolean, true when sub directory? ar.ReadBoolean(); // Sub directory names seem to be in reverse order of serialization... var subDirCount = ar.ReadInt32(); var subDirNames = Enumerable.Range(0, subDirCount) .Select(x => ar.ReadString()) .ToArray(); // Read subdirectories foreach (var _ in subDirNames.Reverse()) { var subDir = new MiloObjectDir(); ReadFromStream(ar, subDir); dirEntry.SubDirectories.Add(subDir); } dir.Extras.Add("DirectoryEntry", dirEntry); ar.BaseStream.Position += 17; // 0'd data + ADDE } else if (version >= 24) { // GH2 and above // Reads data as a byte array var entrySize = GuessEntrySize(ar); var entryBytes = new MiloObjectBytes(dirType) { Name = dirName }; entryBytes.Data = ar.ReadBytes((int)entrySize); dir.Extras.Add("DirectoryEntry", entryBytes); ar.BaseStream.Position += 4; } foreach (var entry in entries) { var entryOffset = ar.BaseStream.Position; // TODO: De-serialize entries //try //{ // var miloEntry = ReadFromStream(ar.BaseStream, entry.Type); // miloEntry.Name = entry.Name; // dir.Entries.Add(miloEntry); // ar.BaseStream.Position += 4; // Skips padding // continue; //} //catch (Exception ex) //{ // // Catch exception and log? // ar.Basestream.Position = entryOffset; // Return to start //} // Reads data as a byte array var entrySize = GuessEntrySize(ar); var entryBytes = new MiloObjectBytes(entry.Type) { Name = entry.Name }; entryBytes.Data = ar.ReadBytes((int)entrySize); dir.Entries.Add(entryBytes); ar.BaseStream.Position += 4; } }
public static void ExportToGLTF(this MiloObjectDir milo, string path, AppState appState) { var serializer = appState.GetSerializer(); var pathDirectory = Path.GetDirectoryName(path); var textures = milo.Entries .Where(x => "Tex".Equals(x.Type, StringComparison.CurrentCultureIgnoreCase)) .Select(y => serializer.ReadFromMiloObjectBytes <Tex>(y as MiloObjectBytes)) .ToList(); var extTexs = textures.Where(x => x.UseExternal).ToList(); var miloDir = appState.GetWorkingDirectory().FullPath; // Update textures foreach (var texture in textures.Where(x => x.UseExternal && x.Bitmap == null)) // TODO: Figure out what UseExternal actually means { var texPath = Path.Combine(miloDir, MakeGenPath(texture.ExternalPath, appState.SystemInfo.Platform)); var bitmap = serializer.ReadFromFile <HMXBitmap>(texPath); texture.Bitmap = bitmap; texture.UseExternal = false; } var views = milo.Entries .Where(x => "View".Equals(x.Type, StringComparison.CurrentCultureIgnoreCase)) .Select(y => serializer.ReadFromMiloObjectBytes <View>(y as MiloObjectBytes)) .ToList(); views.AddRange(milo.Entries .Where(x => "Group".Equals(x.Type, StringComparison.CurrentCultureIgnoreCase)) .Select(y => serializer.ReadFromMiloObjectBytes <Group>(y as MiloObjectBytes)) .ToList()); var meshes = milo.Entries .Where(x => "Mesh".Equals(x.Type, StringComparison.CurrentCultureIgnoreCase)) .Select(y => serializer.ReadFromMiloObjectBytes <Mackiloha.Render.Mesh>(y as MiloObjectBytes)) .Where(z => !string.IsNullOrEmpty(z.Material)) // Don't care about bone meshes for now .ToList(); var trans = milo.Entries .Where(x => "Trans".Equals(x.Type, StringComparison.CurrentCultureIgnoreCase)) .Select(y => serializer.ReadFromMiloObjectBytes <Mackiloha.Render.TransStandalone>(y as MiloObjectBytes)) .ToList(); var materials = milo.Entries .Where(x => "Mat".Equals(x.Type, StringComparison.CurrentCultureIgnoreCase)) .Select(y => serializer.ReadFromMiloObjectBytes <Mackiloha.Render.Mat>(y as MiloObjectBytes)) //.Where(z => z.TextureEntries.Count > 0 && z.TextureEntries.Any(w => !string.IsNullOrEmpty(w.Texture))) // TODO: Idk? .ToList(); var cams = milo.Entries .Where(x => "Cam".Equals(x.Type, StringComparison.CurrentCultureIgnoreCase) && appState.SystemInfo.Version <= 10) .Select(y => serializer.ReadFromMiloObjectBytes <Cam>(y as MiloObjectBytes)) .ToList(); var environs = milo.Entries .Where(x => "Environ".Equals(x.Type, StringComparison.CurrentCultureIgnoreCase) && appState.SystemInfo.Version <= 10) .Select(y => serializer.ReadFromMiloObjectBytes <Environ>(y as MiloObjectBytes)) .ToList(); var miloEntries = textures .Union <MiloObject>(views) .Union(meshes) .Union(trans) .Union(materials) .Union(cams) .Union(environs) .ToList(); var transEntries = miloEntries .Where(x => x is ITrans) .ToList(); var drawEntries = miloEntries .Where(x => x is IDraw) .ToList(); /* var transforms = milo.Entries * .Where(x => x.Type.Equals("Trans", StringComparison.CurrentCultureIgnoreCase)) * .Select(y => * { * var trans = MiloOG.Trans.FromStream(new MemoryStream((y as MiloEntry).Data)); * trans.Name = y.Name; * return trans; * }).ToList(); */ var scene = new GLTF() { Asset = new Asset() { Generator = $"Mackiloha v{typeof(MiloExtensions).Assembly.GetName().Version}" }, Images = textures.Select(x => new Image() { Name = Path.GetFileNameWithoutExtension(x.Name) + ".png", Uri = Path.GetFileNameWithoutExtension(x.Name) + ".png" }).ToArray(), Samplers = new Sampler[] { new Sampler() { MagFilter = MagFilter.Linear, MinFilter = MinFilter.Nearest, WrapS = WrapMode.Repeat, WrapT = WrapMode.Repeat } }, Scene = 0 }; var currentOffset = 0; scene.Textures = textures.Select(x => new Texture() { Name = x.Name, Sampler = 0, Source = currentOffset++ }).ToArray(); var keyIdxPairs = Enumerable.Range(0, textures.Count).ToDictionary(x => textures[x].Name); scene.Materials = materials.Select(x => new Material() { Name = x.Name, PbrMetallicRoughness = new PbrMetallicRoughness() { BaseColorTexture = // Verify material has texture and exists in milo (x.TextureEntries.Any(w => !string.IsNullOrEmpty(w.Texture)) && keyIdxPairs.ContainsKey(x.TextureEntries.First(x => !string.IsNullOrEmpty(x.Texture)).Texture)) ? new BaseColorTexture() { // TODO: Figure out how to map multiple textures to single material Index = keyIdxPairs[x.TextureEntries.First(y => !string.IsNullOrEmpty(y.Texture)).Texture] } : null, BaseColorFactor = new Vector4 <double>(x.BaseColor.R, x.BaseColor.G, x.BaseColor.B, x.BaseColor.A), MetallicFactor = (x.TextureEntries.Any(w => !string.IsNullOrEmpty(w.Texture)) && x.TextureEntries.First(y => !string.IsNullOrEmpty(y.Texture)).Unknown2 == 2) ? 1 : 0, RoughnessFactor = (x.TextureEntries.Any(w => !string.IsNullOrEmpty(w.Texture)) && x.TextureEntries.First(y => !string.IsNullOrEmpty(y.Texture)).Unknown2 == 2) ? 0 : 1 }, EmissiveFactor = new Vector3 <double>(), AlphaMode = AlphaMode.Mask, // x.Blend == BlendFactor.One ? AlphaMode.Blend : AlphaMode.Opaque, DoubleSided = true }).ToArray(); if (!Directory.Exists(pathDirectory)) { // Create directory Directory.CreateDirectory(pathDirectory); } // Saves textures for (int i = 0; i < textures.Count; i++) { textures[i].Bitmap.SaveAs(serializer.Info, Path.Combine(pathDirectory, scene.Images[i].Uri)); } var accessors = new List <Accessor>(); var sceneMeshes = new List <GLTFTools.Mesh>(); int bufferSize12 = meshes.Select(x => x.Vertices.Count * 12 * 2).Sum(); // Verts + norms int bufferSize8 = meshes.Select(x => x.Vertices.Count * 8).Sum(); // UV int bufferSize4 = meshes.Select(x => x.Faces.Count * 6).Sum(); // Faces if (bufferSize4 % 4 != 0) { bufferSize4 += 4 - (bufferSize4 % 4); } scene.Buffers = new GLTFTools.Buffer[] { new GLTFTools.Buffer() { Name = Path.GetFileNameWithoutExtension(path), ByteLength = (bufferSize4 + bufferSize8 + bufferSize12), Uri = Path.GetFileNameWithoutExtension(path) + ".bin" } }; scene.BufferViews = new BufferView[] { new BufferView() { Name = "vertsAndNorms", ByteLength = bufferSize12, ByteOffset = 0, ByteStride = 12 }, new BufferView() { Name = "uvs", ByteLength = bufferSize8, ByteOffset = bufferSize12, ByteStride = 8 }, new BufferView() { Name = "faces", ByteLength = bufferSize4, ByteOffset = bufferSize12 + bufferSize8, ByteStride = null } }; int buffer12Offset = scene.BufferViews[0].ByteOffset.Value; int buffer8Offset = scene.BufferViews[1].ByteOffset.Value; int buffer4Offset = scene.BufferViews[2].ByteOffset.Value; var bw = new BinaryWriter(new MemoryStream(new byte[bufferSize12 + bufferSize8 + bufferSize4])); Dictionary <string, int> meshIndex = new Dictionary <string, int>(); currentOffset = 0; keyIdxPairs = Enumerable .Range(0, materials.Count) .ToDictionary(x => materials[x].Name); foreach (var mesh in meshes) { if (mesh.Vertices.Count <= 0 || mesh.Faces.Count <= 0) { continue; } meshIndex.Add(mesh.Name, currentOffset++); // Finds related material + texture var mat = materials.First(x => ((string)x.Name).Equals(mesh.Material, StringComparison.CurrentCultureIgnoreCase)); sceneMeshes.Add(new GLTFTools.Mesh() { Name = mesh.Name, Primitives = new MeshPrimitive[] { new MeshPrimitive() { Attributes = new MeshPrimitiveAttributes() { Position = accessors.Count, Normal = accessors.Count + 1, TextureCoordinate0 = accessors.Count + 2 }, Indices = accessors.Count + 3, Material = keyIdxPairs[mesh.Material], Mode = RenderMode.Triangles } } }); // Vertices accessors.Add(new Accessor() { Name = mesh.Name + "_positions", ComponentType = ComponentType.Float, Count = mesh.Vertices.Count, Min = new double[] { mesh.Vertices.Select(x => x.X).Min(), mesh.Vertices.Select(x => x.Y).Min(), mesh.Vertices.Select(x => x.Z).Min() }, Max = new double[] { mesh.Vertices.Select(x => x.X).Max(), mesh.Vertices.Select(x => x.Y).Max(), mesh.Vertices.Select(x => x.Z).Max() }, Type = GLType.Vector3, BufferView = 0, ByteOffset = buffer12Offset - scene.BufferViews[0].ByteOffset.Value }); bw.BaseStream.Seek(buffer12Offset, SeekOrigin.Begin); foreach (var vert in mesh.Vertices) { bw.Write(vert.X); bw.Write(vert.Y); bw.Write(vert.Z); } buffer12Offset = (int)bw.BaseStream.Position; // Normals accessors.Add(new Accessor() { Name = mesh.Name + "_normals", ComponentType = ComponentType.Float, Count = mesh.Vertices.Count, Min = new double[] { mesh.Vertices.Select(x => x.NormalX).Min(), mesh.Vertices.Select(x => x.NormalY).Min(), mesh.Vertices.Select(x => x.NormalZ).Min() }, Max = new double[] { mesh.Vertices.Select(x => x.NormalX).Max(), mesh.Vertices.Select(x => x.NormalY).Max(), mesh.Vertices.Select(x => x.NormalZ).Max() }, Type = GLType.Vector3, BufferView = 0, ByteOffset = buffer12Offset - scene.BufferViews[0].ByteOffset.Value }); bw.BaseStream.Seek(buffer12Offset, SeekOrigin.Begin); foreach (var vert in mesh.Vertices) { bw.Write(vert.NormalX); bw.Write(vert.NormalY); bw.Write(vert.NormalZ); } buffer12Offset = (int)bw.BaseStream.Position; // UV coordinates accessors.Add(new Accessor() { Name = mesh.Name + "_texcoords", ComponentType = ComponentType.Float, Count = mesh.Vertices.Count, Min = new double[] { mesh.Vertices.Select(x => x.U).Min(), mesh.Vertices.Select(x => x.V).Min() }, Max = new double[] { mesh.Vertices.Select(x => x.U).Max(), mesh.Vertices.Select(x => x.V).Max() }, Type = GLType.Vector2, BufferView = 1, ByteOffset = buffer8Offset - scene.BufferViews[1].ByteOffset.Value }); bw.BaseStream.Seek(buffer8Offset, SeekOrigin.Begin); foreach (var vert in mesh.Vertices) { bw.Write(vert.U); bw.Write(vert.V); } buffer8Offset = (int)bw.BaseStream.Position; // Faces accessors.Add(new Accessor() { Name = mesh.Name + "_indicies", ComponentType = ComponentType.UnsignedShort, Count = mesh.Faces.Count * 3, Min = new double[] { mesh.Faces.SelectMany(x => new [] { x.V1, x.V2, x.V3 }).Min() }, Max = new double[] { mesh.Faces.SelectMany(x => new [] { x.V1, x.V2, x.V3 }).Max() }, Type = GLType.Scalar, BufferView = 2, ByteOffset = buffer4Offset - scene.BufferViews[2].ByteOffset.Value }); bw.BaseStream.Seek(buffer4Offset, SeekOrigin.Begin); foreach (var face in mesh.Faces) { bw.Write(face.V1); bw.Write(face.V2); bw.Write(face.V3); } buffer4Offset = (int)bw.BaseStream.Position; } scene.Accessors = accessors.ToArray(); scene.Meshes = sceneMeshes.ToArray(); var nodes = new List <Node>(); var nodeIndex = new Dictionary <string, int>(); /* // TODO: Make milo objects with transforms data * MiloOG.AbstractEntry GetAbstractEntry(string name) * { * var entry = milo.Entries.FirstOrDefault(x => x.Name == name); * if (entry == null) return null; * * switch (entry.Type) * { * case "Mesh": * return meshes.First(y => y.Name == entry.Name); * case "Trans": * return transforms.First(y => y.Name == entry.Name); * case "View": * return views.First(y => y.Name == entry.Name); * default: * return null; * } * } */ /* Matrix4<float>? GetTransform(string transform) * { * var transEntry = milo.Entries.FirstOrDefault(y => y.Name == transform); * if (transEntry == null) return null; * * switch (transEntry.Type) * { * case "Mesh": * var mesh = meshes.First(y => y.Name == transEntry.Name); * return mesh.Mat2.ToGLMatrix(); * case "Trans": * var trans = transforms.First(y => y.Name == transEntry.Name); * return trans.Mat2.ToGLMatrix(); * case "View": * var view2 = views.First(y => y.Name == transEntry.Name); * return view2.Mat2.ToGLMatrix(); * default: * return null; * } * } */ /* string GetTransformName(MiloOG.AbstractEntry entry) * { * switch (entry.Type) * { * case "Mesh": * var mesh = meshes.First(y => y.Name == entry.Name); * return mesh.Transform; * case "Trans": * var trans = transforms.First(y => y.Name == entry.Name); * return trans.Name; * case "View": * var view = views.First(y => y.Name == entry.Name); * return view.Transform; * default: * return null; * } * } */ var shadowRegex = new System.Text.RegularExpressions.Regex("shadow[^.]*.mesh$", System.Text.RegularExpressions.RegexOptions.IgnoreCase); var children = drawEntries .Where(w => w is ITrans) // Use trans collection? .Select(x => new { Name = (string)x.Name, Trans = (string)(x as ITrans).Transform }) .Where(y => !string.IsNullOrEmpty(y.Trans)) .GroupBy(z => z.Trans) .ToDictionary(g => g.Key, g => g.Select(w => w.Name) .OrderBy(s => s) .Where(x => !shadowRegex.IsMatch(x)) // Removes shadow meshes for now .ToList()); /* foreach (var entry in meshes.Union<MiloObject>(views)) // TODO: Union w/ trans * { * var transName = (entry as Mackiloha.Render.Interfaces.ITrans) * .Trans * .Transform; * * if (!children.ContainsKey(transName)) * children.Add(transName, new List<string>(new string[] { entry.Name })); * else if (!children[transName].Contains(entry.Name)) * children[transName].Add(entry.Name); * } */ var rootIndex = new List <int>(); foreach (var key in children.Keys) { rootIndex.Add(nodes.Count); var transEntry = transEntries.FirstOrDefault(x => x.Name == key) as ITrans; var node = new Node() { Name = "Root_" + key, Mesh = meshIndex.ContainsKey(key) ? (int?)meshIndex[key] : null, Matrix = ToGLMatrix((transEntry != null) ? transEntry.Mat2 : Matrix4.Identity()), Children = Enumerable.Range(nodes.Count + 1, children[key].Count).ToArray() }; nodes.Add(node); foreach (var child in children[key]) { var subEntry = drawEntries.First(x => x.Name == child); var subNode = new Node() { Name = subEntry.Name, Mesh = meshIndex.ContainsKey(subEntry.Name) ? (int?)meshIndex[subEntry.Name] : null, //Matrix = ToGLMatrix(subEntry.Mat1) }; nodeIndex.Add(child, rootIndex.Last()); nodes.Add(subNode); } } int CreateNode(string name) // Returns index of node { if (nodeIndex.ContainsKey(name)) { return(nodeIndex[name]); } var entry = drawEntries.First(x => x.Name == name) as IDraw; var transformEntry = drawEntries.First(x => x.Name == (entry as ITrans).Transform); List <string> subNodes = entry.Drawables.Select(x => (string)x).ToList(); var node = new Node() { Name = name, Mesh = meshIndex.ContainsKey(name) ? (int?)meshIndex[name] : null, Matrix = ToGLMatrix((entry as ITrans).Mat1), //Matrix = GetTransform(entry.Transform), Children = (subNodes.Count > 0) ? subNodes.Select(x => CreateNode(x)).ToArray() : null }; nodeIndex.Add(name, nodes.Count); nodes.Add(node); return(nodeIndex[name]); } // foreach (var n in meshes.Union<MiloOG.AbstractEntry>(views).Union<MiloOG.AbstractEntry>(transforms)) CreateNode(n.Name); // // scene.Scene = 0; // scene.Scenes = new Scene[] { new Scene() { Nodes = Enumerable.Range(0, nodes.Count).ToArray() } }; // foreach (var view in views) CreateNode(view.Name); // // // Finds root node // var childrenNodes = nodes.SelectMany(x => x.Children ?? new int[0]).Distinct(); // var parentNodes = Enumerable.Range(0, nodes.Count); // var rootIdx = parentNodes.Except(childrenNodes).Single(); // // scene.Scene = 0; // scene.Scenes = new Scene[] { new Scene() { Nodes = new int[] { rootIdx } } }; List <string> GetAllSubs(MiloObject entry) { List <string> subsEntriesNames = (entry as IDraw).Drawables .Select(x => (string)x) .ToList(); var subEntries = subsEntriesNames .Select(x => drawEntries.FirstOrDefault(y => y.Name == x)) .Where(y => y != null) .ToList(); foreach (var subEntry in subEntries) { subsEntriesNames.AddRange(GetAllSubs(subEntry)); } return(subsEntriesNames); } scene.Scene = 0; //scene.Scenes = new Scene[] { new Scene() { Nodes = rootIndex.ToArray() } }; scene.Scenes = views .Select(x => new Scene() { Nodes = GetAllSubs(x) .Select(y => nodeIndex.ContainsKey(y) ? nodeIndex[y] : -1) .Where(z => z != -1) .Distinct() .ToArray() }) .OrderByDescending(x => x.Nodes.Length) .ToArray(); if (scene.Scenes.Length <= 0) { // Create scene from root notes scene.Scenes = new[]