public static void ExportADT(string file, BackgroundWorker exportworker = null) { if (exportworker == null) { exportworker = new BackgroundWorker { WorkerReportsProgress = true }; } var outdir = ConfigurationManager.AppSettings["outdir"]; var customCulture = (System.Globalization.CultureInfo)System.Threading.Thread.CurrentThread.CurrentCulture.Clone(); customCulture.NumberFormat.NumberDecimalSeparator = "."; System.Threading.Thread.CurrentThread.CurrentCulture = customCulture; var TileSize = 1600.0f / 3.0f; //533.333 var ChunkSize = TileSize / 16.0f; //33.333 var UnitSize = ChunkSize / 8.0f; //4.166666 var mapname = file.Replace("world/maps/", "").Substring(0, file.Replace("world/maps/", "").IndexOf("/")); var coord = file.Replace("world/maps/" + mapname + "/" + mapname, "").Replace(".adt", "").Split('_'); CASCLib.Logger.WriteLine("ADT glTF Exporter: Starting export of {0}..", file); if (!Directory.Exists(Path.Combine(outdir, Path.GetDirectoryName(file)))) { Directory.CreateDirectory(Path.Combine(outdir, Path.GetDirectoryName(file))); } exportworker.ReportProgress(0, "Loading ADT " + file); var reader = new ADTReader(); reader.LoadADT(file.Replace('/', '\\')); if (reader.adtfile.chunks == null) { CASCLib.Logger.WriteLine("ADT glTF Exporter: File {0} has no chunks, skipping export!", file); return; } var renderBatches = new List <Structs.RenderBatch>(); var materials = new Dictionary <int, string>(); var glTF = new glTF() { asset = new Asset() { version = "2.0", generator = "Marlamin's WoW Exporter " + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(), copyright = "Contents are owned by Blizzard Entertainment" } }; var initialChunkY = reader.adtfile.chunks[0].header.position.Y; var initialChunkX = reader.adtfile.chunks[0].header.position.X; ConfigurationManager.RefreshSection("appSettings"); var bakeQuality = ConfigurationManager.AppSettings["bakeQuality"]; var bufferViews = new List <BufferView>(); var accessorInfo = new List <Accessor>(); var meshes = new List <Mesh>(); glTF.buffers = new Buffer[1]; if (bakeQuality == "low" || bakeQuality == "medium") { glTF.images = new Image[1]; glTF.materials = new Material[1]; glTF.textures = new Texture[1]; } else if (bakeQuality == "high") { glTF.images = new Image[256]; glTF.materials = new Material[256]; glTF.textures = new Texture[256]; } var stream = new FileStream(Path.Combine(outdir, file.Replace(".adt", ".bin")), FileMode.OpenOrCreate); var writer = new BinaryWriter(stream); for (var c = 0; c < reader.adtfile.chunks.Count(); c++) { var chunk = reader.adtfile.chunks[c]; var localVertices = new Structs.Vertex[145]; for (int i = 0, idx = 0; i < 17; i++) { for (var j = 0; j < (((i % 2) != 0) ? 8 : 9); j++) { var v = new Structs.Vertex { Normal = new Vector3(chunk.normals.normal_2[idx] / 127f, chunk.normals.normal_0[idx] / 127f, chunk.normals.normal_1[idx] / 127f), Position = new Vector3(chunk.header.position.Y - (j * UnitSize), chunk.vertices.vertices[idx++] + chunk.header.position.Z, chunk.header.position.X - (i * UnitSize * 0.5f)) }; if ((i % 2) != 0) { v.Position.X -= 0.5f * UnitSize; } if (bakeQuality == "low" || bakeQuality == "medium") { v.TexCoord = new Vector2(-(v.Position.X - initialChunkX) / TileSize, -(v.Position.Z - initialChunkY) / TileSize); } else if (bakeQuality == "high") { v.TexCoord = new Vector2(-(v.Position.X - initialChunkX) / ChunkSize, -(v.Position.Z - initialChunkY) / ChunkSize); } localVertices[idx - 1] = v; } } var vPosBuffer = new BufferView() { buffer = 0, byteOffset = (uint)writer.BaseStream.Position, target = 34962 }; var minPosX = float.MaxValue; var minPosY = float.MaxValue; var minPosZ = float.MaxValue; var maxPosX = float.MinValue; var maxPosY = float.MinValue; var maxPosZ = float.MinValue; // Position buffer foreach (var vertex in localVertices) { writer.Write(vertex.Position.X); writer.Write(vertex.Position.Y); writer.Write(vertex.Position.Z); if (vertex.Position.X < minPosX) { minPosX = vertex.Position.X; } if (vertex.Position.Y < minPosY) { minPosY = vertex.Position.Y; } if (vertex.Position.Z < minPosZ) { minPosZ = vertex.Position.Z; } if (vertex.Position.X > maxPosX) { maxPosX = vertex.Position.X; } if (vertex.Position.Y > maxPosY) { maxPosY = vertex.Position.Y; } if (vertex.Position.Z > maxPosZ) { maxPosZ = vertex.Position.Z; } } vPosBuffer.byteLength = (uint)writer.BaseStream.Position - vPosBuffer.byteOffset; var posLoc = accessorInfo.Count(); accessorInfo.Add(new Accessor() { name = "vPos", bufferView = bufferViews.Count(), byteOffset = 0, componentType = 5126, count = 145, type = "VEC3", min = new float[] { minPosX, minPosY, minPosZ }, max = new float[] { maxPosX, maxPosY, maxPosZ } }); bufferViews.Add(vPosBuffer); // Normal buffer var normalBuffer = new BufferView() { buffer = 0, byteOffset = (uint)writer.BaseStream.Position, target = 34962 }; foreach (var vertex in localVertices) { writer.Write(vertex.Normal.X); writer.Write(vertex.Normal.Y); writer.Write(vertex.Normal.Z); } normalBuffer.byteLength = (uint)writer.BaseStream.Position - normalBuffer.byteOffset; var normalLoc = accessorInfo.Count(); accessorInfo.Add(new Accessor() { name = "vNormal", bufferView = bufferViews.Count(), byteOffset = 0, componentType = 5126, count = 145, type = "VEC3" }); bufferViews.Add(normalBuffer); // Texcoord buffer var texCoordBuffer = new BufferView() { buffer = 0, byteOffset = (uint)writer.BaseStream.Position, target = 34962 }; foreach (var vertex in localVertices) { writer.Write(vertex.TexCoord.X); writer.Write(vertex.TexCoord.Y); } texCoordBuffer.byteLength = (uint)writer.BaseStream.Position - texCoordBuffer.byteOffset; var texLoc = accessorInfo.Count(); accessorInfo.Add(new Accessor() { name = "vTex", bufferView = bufferViews.Count(), byteOffset = 0, componentType = 5126, count = 145, type = "VEC2" }); bufferViews.Add(texCoordBuffer); var indexBufferPos = bufferViews.Count(); // Stupid C# and its structs var holesHighRes = new byte[8]; holesHighRes[0] = chunk.header.holesHighRes_0; holesHighRes[1] = chunk.header.holesHighRes_1; holesHighRes[2] = chunk.header.holesHighRes_2; holesHighRes[3] = chunk.header.holesHighRes_3; holesHighRes[4] = chunk.header.holesHighRes_4; holesHighRes[5] = chunk.header.holesHighRes_5; holesHighRes[6] = chunk.header.holesHighRes_6; holesHighRes[7] = chunk.header.holesHighRes_7; var indicelist = new List <int>(); for (int j = 9, xx = 0, yy = 0; j < 145; j++, xx++) { if (xx >= 8) { xx = 0; ++yy; } var isHole = true; if ((chunk.header.flags & 0x10000) == 0) { var currentHole = (int)Math.Pow(2, Math.Floor(xx / 2f) * 1f + Math.Floor(yy / 2f) * 4f); if ((chunk.header.holesLowRes & currentHole) == 0) { isHole = false; } } else { if (((holesHighRes[yy] >> xx) & 1) == 0) { isHole = false; } } if (!isHole) { indicelist.AddRange(new int[] { j + 8, j, j - 9 }); indicelist.AddRange(new int[] { j - 9, j, j - 8 }); indicelist.AddRange(new int[] { j - 8, j, j + 9 }); indicelist.AddRange(new int[] { j + 9, j, j + 8 }); } if ((j + 1) % (9 + 8) == 0) { j += 9; } } accessorInfo.Add(new Accessor() { name = "indices", bufferView = indexBufferPos, byteOffset = 0, componentType = 5125, count = (uint)indicelist.Count(), type = "SCALAR" }); var indiceBuffer = new BufferView() { buffer = 0, byteOffset = (uint)writer.BaseStream.Position, target = 34963 }; for (var i = 0; i < indicelist.Count(); i++) { writer.Write(indicelist[i]); } indiceBuffer.byteLength = (uint)writer.BaseStream.Position - indiceBuffer.byteOffset; bufferViews.Add(indiceBuffer); var mesh = new Mesh(); mesh.primitives = new Primitive[1]; mesh.primitives[0].attributes = new Dictionary <string, int> { { "POSITION", posLoc }, { "NORMAL", normalLoc }, { "TEXCOORD_0", texLoc } }; mesh.primitives[0].indices = (uint)accessorInfo.Count() - 1; if (bakeQuality == "low" || bakeQuality == "medium") { mesh.primitives[0].material = 0; } else if (bakeQuality == "high") { mesh.primitives[0].material = (uint)c; } mesh.primitives[0].mode = 4; mesh.name = "MCNK #" + c; meshes.Add(mesh); glTF.buffers[0].byteLength = (uint)writer.BaseStream.Length; glTF.buffers[0].uri = Path.GetFileNameWithoutExtension(file) + ".bin"; if (bakeQuality == "low" || bakeQuality == "medium") { glTF.images[0].uri = Path.GetFileNameWithoutExtension(file) + ".png"; glTF.textures[0].sampler = 0; glTF.textures[0].source = 0; glTF.materials[0].pbrMetallicRoughness = new PBRMetallicRoughness(); glTF.materials[0].pbrMetallicRoughness.baseColorTexture = new TextureIndex(); glTF.materials[0].pbrMetallicRoughness.baseColorTexture.index = 0; glTF.materials[0].pbrMetallicRoughness.metallicFactor = 0.0f; glTF.materials[0].alphaMode = "OPAQUE"; glTF.materials[0].alphaCutoff = 0.0f; } else if (bakeQuality == "high") { glTF.images[c].uri = Path.GetFileNameWithoutExtension(file) + "_" + c + ".png"; glTF.textures[c].sampler = 0; glTF.textures[c].source = (int)c; glTF.materials[c].pbrMetallicRoughness = new PBRMetallicRoughness(); glTF.materials[c].pbrMetallicRoughness.baseColorTexture = new TextureIndex(); glTF.materials[c].pbrMetallicRoughness.baseColorTexture.index = (int)c; glTF.materials[c].pbrMetallicRoughness.metallicFactor = 0.0f; glTF.materials[c].alphaMode = "OPAQUE"; glTF.materials[c].alphaCutoff = 0.0f; } } writer.Close(); writer.Dispose(); glTF.bufferViews = bufferViews.ToArray(); glTF.accessors = accessorInfo.ToArray(); glTF.samplers = new Sampler[1]; glTF.samplers[0].minFilter = 9986; glTF.samplers[0].magFilter = 9729; glTF.samplers[0].wrapS = 10497; glTF.samplers[0].wrapT = 10497; glTF.scenes = new Scene[1]; glTF.scenes[0].name = Path.GetFileNameWithoutExtension(file); glTF.nodes = new Node[meshes.Count()]; var meshIDs = new List <int>(); for (var i = 0; i < meshes.Count(); i++) { glTF.nodes[i].name = meshes[i].name; glTF.nodes[i].mesh = i; meshIDs.Add(i); } glTF.scenes[0].nodes = meshIDs.ToArray(); glTF.meshes = meshes.ToArray(); glTF.scene = 0; exportworker.ReportProgress(95, "Writing to file.."); File.WriteAllText(Path.Combine(outdir, file.Replace(".adt", ".gltf")), JsonConvert.SerializeObject(glTF, Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore })); ConfigurationManager.RefreshSection("appSettings"); if (ConfigurationManager.AppSettings["exportWMO"] == "True" || ConfigurationManager.AppSettings["exportM2"] == "True") { if (ConfigurationManager.AppSettings["exportWMO"] == "True") { exportworker.ReportProgress(25, "Exporting WMOs"); for (var mi = 0; mi < reader.adtfile.objects.worldModels.entries.Count(); mi++) { var wmo = reader.adtfile.objects.worldModels.entries[mi]; var filename = reader.adtfile.objects.wmoNames.filenames[wmo.mwidEntry]; if (!File.Exists(Path.GetFileNameWithoutExtension(filename).ToLower() + ".gltf")) { WMOExporter.ExportWMO(filename.ToLower(), null, Path.Combine(outdir, Path.GetDirectoryName(file))); } } } if (ConfigurationManager.AppSettings["exportM2"] == "True") { exportworker.ReportProgress(50, "Exporting M2s"); for (var mi = 0; mi < reader.adtfile.objects.models.entries.Count(); mi++) { var doodad = reader.adtfile.objects.models.entries[mi]; var filename = reader.adtfile.objects.m2Names.filenames[doodad.mmidEntry]; if (!File.Exists(Path.GetFileNameWithoutExtension(filename).ToLower() + ".gltf")) { M2Exporter.ExportM2(filename.ToLower(), null, Path.Combine(outdir, Path.GetDirectoryName(file))); } } } } }
public static void exportWMO(string file, BackgroundWorker exportworker = null, string destinationOverride = null) { if (exportworker == null) { exportworker = new BackgroundWorker(); exportworker.WorkerReportsProgress = true; } Logger.WriteLine("WMO glTF Exporter: Loading file {0}...", file); exportworker.ReportProgress(5, "Reading WMO.."); var outdir = ConfigurationManager.AppSettings["outdir"]; var reader = new WMOReader(); reader.LoadWMO(file); System.Globalization.CultureInfo customCulture = (System.Globalization.CultureInfo)System.Threading.Thread.CurrentThread.CurrentCulture.Clone(); customCulture.NumberFormat.NumberDecimalSeparator = "."; System.Threading.Thread.CurrentThread.CurrentCulture = customCulture; if (destinationOverride == null) { if (!Directory.Exists(Path.Combine(outdir, Path.GetDirectoryName(file)))) { Directory.CreateDirectory(Path.Combine(outdir, Path.GetDirectoryName(file))); } } exportworker.ReportProgress(25, "Generating glTF.."); var glTF = new glTF() { asset = new Asset() { version = "2.0", generator = "Marlamin's WoW Exporter " + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(), copyright = "Contents are owned by Blizzard Entertainment", minVersion = "2.0" } }; var groups = new Structs.WMOGroup[reader.wmofile.group.Count()]; FileStream stream; if (destinationOverride == null) { stream = new FileStream(Path.Combine(outdir, file.Replace(".wmo", ".bin")), FileMode.OpenOrCreate); } else { stream = new FileStream(Path.Combine(destinationOverride, Path.GetFileNameWithoutExtension(file) + ".bin"), FileMode.OpenOrCreate); } var writer = new BinaryWriter(stream); var bufferViews = new List <BufferView>(); var accessorInfo = new List <Accessor>(); var meshes = new List <Mesh>(); for (int g = 0; g < reader.wmofile.group.Count(); g++) { if (reader.wmofile.group[g].mogp.vertices == null) { Console.WriteLine("Group has no vertices!"); continue; } for (int i = 0; i < reader.wmofile.groupNames.Count(); i++) { if (reader.wmofile.group[g].mogp.nameOffset == reader.wmofile.groupNames[i].offset) { groups[g].name = reader.wmofile.groupNames[i].name.Replace(" ", "_"); } } if (groups[g].name == "antiportal") { Console.WriteLine("Group is antiportal"); continue; } // Position bufferview var vPosBuffer = new BufferView() { buffer = 0, byteOffset = (uint)writer.BaseStream.Position, target = 34962 }; var minPosX = float.MaxValue; var minPosY = float.MaxValue; var minPosZ = float.MaxValue; var maxPosX = float.MinValue; var maxPosY = float.MinValue; var maxPosZ = float.MinValue; for (int i = 0; i < reader.wmofile.group[g].mogp.vertices.Count(); i++) { writer.Write(reader.wmofile.group[g].mogp.vertices[i].vector.X * -1); writer.Write(reader.wmofile.group[g].mogp.vertices[i].vector.Z); writer.Write(reader.wmofile.group[g].mogp.vertices[i].vector.Y); if (reader.wmofile.group[g].mogp.vertices[i].vector.X * -1 < minPosX) { minPosX = reader.wmofile.group[g].mogp.vertices[i].vector.X * -1; } if (reader.wmofile.group[g].mogp.vertices[i].vector.Z < minPosY) { minPosY = reader.wmofile.group[g].mogp.vertices[i].vector.Z; } if (reader.wmofile.group[g].mogp.vertices[i].vector.Y < minPosZ) { minPosZ = reader.wmofile.group[g].mogp.vertices[i].vector.Y; } if (reader.wmofile.group[g].mogp.vertices[i].vector.X * -1 > maxPosX) { maxPosX = reader.wmofile.group[g].mogp.vertices[i].vector.X * -1; } if (reader.wmofile.group[g].mogp.vertices[i].vector.Z > maxPosY) { maxPosY = reader.wmofile.group[g].mogp.vertices[i].vector.Z; } if (reader.wmofile.group[g].mogp.vertices[i].vector.Y > maxPosZ) { maxPosZ = reader.wmofile.group[g].mogp.vertices[i].vector.Y; } } vPosBuffer.byteLength = (uint)writer.BaseStream.Position - vPosBuffer.byteOffset; var posLoc = accessorInfo.Count(); accessorInfo.Add(new Accessor() { name = "vPos", bufferView = bufferViews.Count(), byteOffset = 0, componentType = 5126, count = (uint)reader.wmofile.group[g].mogp.vertices.Count(), type = "VEC3", min = new float[] { minPosX, minPosY, minPosZ }, max = new float[] { maxPosX, maxPosY, maxPosZ } }); bufferViews.Add(vPosBuffer); // Normal bufferview var normalBuffer = new BufferView() { buffer = 0, byteOffset = (uint)writer.BaseStream.Position, target = 34962 }; for (int i = 0; i < reader.wmofile.group[g].mogp.vertices.Count(); i++) { writer.Write(reader.wmofile.group[g].mogp.normals[i].normal.X); writer.Write(reader.wmofile.group[g].mogp.normals[i].normal.Z); writer.Write(reader.wmofile.group[g].mogp.normals[i].normal.Y); } normalBuffer.byteLength = (uint)writer.BaseStream.Position - normalBuffer.byteOffset; var normalLoc = accessorInfo.Count(); accessorInfo.Add(new Accessor() { name = "vNormal", bufferView = bufferViews.Count(), byteOffset = 0, componentType = 5126, count = (uint)reader.wmofile.group[g].mogp.vertices.Count(), type = "VEC3" }); bufferViews.Add(normalBuffer); // TexCoord bufferview var texCoordBuffer = new BufferView() { buffer = 0, byteOffset = (uint)writer.BaseStream.Position, target = 34962 }; for (int i = 0; i < reader.wmofile.group[g].mogp.vertices.Count(); i++) { writer.Write(reader.wmofile.group[g].mogp.textureCoords[0][i].X); writer.Write(reader.wmofile.group[g].mogp.textureCoords[0][i].Y); } texCoordBuffer.byteLength = (uint)writer.BaseStream.Position - texCoordBuffer.byteOffset; var texLoc = accessorInfo.Count(); accessorInfo.Add(new Accessor() { name = "vTex", bufferView = bufferViews.Count(), byteOffset = 0, componentType = 5126, count = (uint)reader.wmofile.group[g].mogp.vertices.Count(), type = "VEC2" }); bufferViews.Add(texCoordBuffer); var indexBufferPos = bufferViews.Count(); for (int i = 0; i < reader.wmofile.group[g].mogp.renderBatches.Count(); i++) { var batch = reader.wmofile.group[g].mogp.renderBatches[i]; accessorInfo.Add(new Accessor() { name = "indices", bufferView = indexBufferPos, byteOffset = batch.firstFace * 2, componentType = 5123, count = batch.numFaces, type = "SCALAR" }); var mesh = new Mesh(); mesh.name = groups[g].name + "_" + i; mesh.primitives = new Primitive[1]; mesh.primitives[0].attributes = new Dictionary <string, int> { { "POSITION", posLoc }, { "NORMAL", normalLoc }, { "TEXCOORD_0", texLoc } }; mesh.primitives[0].indices = (uint)accessorInfo.Count() - 1; if (batch.flags == 2) { mesh.primitives[0].material = (uint)batch.possibleBox2_3; } else { mesh.primitives[0].material = batch.materialID; } mesh.primitives[0].mode = 4; meshes.Add(mesh); } var indiceBuffer = new BufferView() { buffer = 0, byteOffset = (uint)writer.BaseStream.Position, target = 34963 }; for (int i = 0; i < reader.wmofile.group[g].mogp.indices.Count(); i++) { writer.Write(reader.wmofile.group[g].mogp.indices[i].indice); } indiceBuffer.byteLength = (uint)writer.BaseStream.Position - indiceBuffer.byteOffset; bufferViews.Add(indiceBuffer); } glTF.bufferViews = bufferViews.ToArray(); glTF.accessors = accessorInfo.ToArray(); glTF.buffers = new Buffer[1]; glTF.buffers[0].byteLength = (uint)writer.BaseStream.Length; glTF.buffers[0].uri = Path.GetFileNameWithoutExtension(file) + ".bin"; writer.Close(); writer.Dispose(); exportworker.ReportProgress(65, "Exporting textures.."); if (reader.wmofile.materials == null) { Logger.WriteLine("WMO glTF exporter: Materials empty"); return; } var materialCount = reader.wmofile.materials.Count(); glTF.images = new Image[materialCount]; glTF.textures = new Texture[materialCount]; glTF.materials = new Material[materialCount]; for (int i = 0; i < materialCount; i++) { for (int ti = 0; ti < reader.wmofile.textures.Count(); ti++) { if (reader.wmofile.textures[ti].startOffset == reader.wmofile.materials[i].texture1) { var textureFilename = Path.GetFileNameWithoutExtension(reader.wmofile.textures[ti].filename).ToLower(); glTF.images[i].uri = textureFilename + ".png"; glTF.textures[i].sampler = 0; glTF.textures[i].source = i; glTF.materials[i].name = textureFilename; glTF.materials[i].pbrMetallicRoughness = new PBRMetallicRoughness(); glTF.materials[i].pbrMetallicRoughness.baseColorTexture = new TextureIndex(); glTF.materials[i].pbrMetallicRoughness.baseColorTexture.index = i; glTF.materials[i].pbrMetallicRoughness.metallicFactor = 0.0f; switch (reader.wmofile.materials[i].blendMode) { case 0: glTF.materials[i].alphaMode = "OPAQUE"; glTF.materials[i].alphaCutoff = 0.0f; break; case 1: glTF.materials[i].alphaMode = "MASK"; glTF.materials[i].alphaCutoff = 0.90393700787f; break; case 2: glTF.materials[i].alphaMode = "MASK"; glTF.materials[i].alphaCutoff = 0.5f; break; default: glTF.materials[i].alphaMode = "OPAQUE"; glTF.materials[i].alphaCutoff = 0.0f; break; } var saveLocation = ""; if (destinationOverride == null) { saveLocation = Path.Combine(outdir, Path.GetDirectoryName(file), textureFilename + ".png"); } else { saveLocation = Path.Combine(outdir, destinationOverride, textureFilename + ".png"); } if (!File.Exists(saveLocation)) { var blpreader = new BLPReader(); blpreader.LoadBLP(reader.wmofile.textures[ti].filename); try { blpreader.bmp.Save(saveLocation); } catch (Exception e) { Console.WriteLine("Error exporting texture " + reader.wmofile.textures[ti].filename + ": " + e.Message); } } } } } glTF.samplers = new Sampler[1]; glTF.samplers[0].name = "Default Sampler"; glTF.samplers[0].minFilter = 9986; glTF.samplers[0].magFilter = 9729; glTF.samplers[0].wrapS = 10497; glTF.samplers[0].wrapT = 10497; glTF.scenes = new Scene[1]; glTF.scenes[0].name = Path.GetFileNameWithoutExtension(file); glTF.nodes = new Node[meshes.Count()]; var meshIDs = new List <int>(); for (var i = 0; i < meshes.Count(); i++) { glTF.nodes[i].name = meshes[i].name; glTF.nodes[i].mesh = i; meshIDs.Add(i); } glTF.scenes[0].nodes = meshIDs.ToArray(); glTF.meshes = meshes.ToArray(); glTF.scene = 0; string currentDoodadSetName = ""; for (int i = 0; i < reader.wmofile.doodadDefinitions.Count(); i++) { var doodadDefinition = reader.wmofile.doodadDefinitions[i]; foreach (var doodadSet in reader.wmofile.doodadSets) { if (doodadSet.firstInstanceIndex == i) { Console.WriteLine("At set: " + doodadSet.setName); currentDoodadSetName = doodadSet.setName.Replace("Set_", "").Replace("SET_", "").Replace("$DefaultGlobal", "Default"); } } foreach (var doodadNameEntry in reader.wmofile.doodadNames) { if (doodadNameEntry.startOffset == doodadDefinition.offset) { if (!File.Exists(Path.GetFileNameWithoutExtension(doodadNameEntry.filename).ToLower() + ".gltf")) { if (destinationOverride == null) { M2Exporter.exportM2(doodadNameEntry.filename.Replace(".MDX", ".M2").Replace(".MDL", ".M2").ToLower(), null, Path.Combine(outdir, Path.GetDirectoryName(file))); } else { M2Exporter.exportM2(doodadNameEntry.filename.Replace(".MDX", ".M2").Replace(".MDL", ".M2").ToLower(), null, destinationOverride); } } } } } exportworker.ReportProgress(95, "Writing to file.."); if (destinationOverride == null) { File.WriteAllText(Path.Combine(outdir, file.Replace(".wmo", ".gltf")), JsonConvert.SerializeObject(glTF, Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore })); } else { File.WriteAllText(Path.Combine(destinationOverride, Path.GetFileName(file.ToLower()).Replace(".wmo", ".gltf")), JsonConvert.SerializeObject(glTF, Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore })); } Logger.WriteLine("Done exporting WMO file!"); }
public static void ExportM2(string file, BackgroundWorker exportworker = null, string destinationOverride = null) { if (exportworker == null) { exportworker = new BackgroundWorker(); exportworker.WorkerReportsProgress = true; } Logger.WriteLine("M2 glTF Exporter: Loading file {0}...", file); exportworker.ReportProgress(5, "Reading M2.."); var outdir = ConfigurationManager.AppSettings["outdir"]; var reader = new M2Reader(); reader.LoadM2(file); var customCulture = (System.Globalization.CultureInfo)System.Threading.Thread.CurrentThread.CurrentCulture.Clone(); customCulture.NumberFormat.NumberDecimalSeparator = "."; System.Threading.Thread.CurrentThread.CurrentCulture = customCulture; if (destinationOverride == null) { if (!Directory.Exists(Path.Combine(outdir, Path.GetDirectoryName(file)))) { Directory.CreateDirectory(Path.Combine(outdir, Path.GetDirectoryName(file))); } } file = file.ToLower(); if (reader.model.vertices.Count() == 0) { Logger.WriteLine("M2 glTF Exporter: File {0} has no vertices, skipping export!", file); return; } exportworker.ReportProgress(25, "Generating glTF.."); var glTF = new glTF() { asset = new Asset() { version = "2.0", generator = "Marlamin's WoW Exporter " + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(), copyright = "Contents are owned by Blizzard Entertainment", minVersion = "2.0" } }; FileStream stream; if (destinationOverride == null) { stream = new FileStream(Path.Combine(outdir, file.Replace(".m2", ".bin")), FileMode.OpenOrCreate); } else { stream = new FileStream(Path.Combine(destinationOverride, Path.GetFileNameWithoutExtension(file).ToLower() + ".bin"), FileMode.OpenOrCreate); } var writer = new BinaryWriter(stream); var bufferViews = new List <BufferView>(); var accessorInfo = new List <Accessor>(); var meshes = new List <Mesh>(); // Position bufferview var vPosBuffer = new BufferView() { buffer = 0, byteOffset = (uint)writer.BaseStream.Position, target = 34962 }; var minPosX = float.MaxValue; var minPosY = float.MaxValue; var minPosZ = float.MaxValue; var maxPosX = float.MinValue; var maxPosY = float.MinValue; var maxPosZ = float.MinValue; for (var i = 0; i < reader.model.vertices.Count(); i++) { writer.Write(reader.model.vertices[i].position.X); writer.Write(reader.model.vertices[i].position.Z); writer.Write(reader.model.vertices[i].position.Y * -1); if (reader.model.vertices[i].position.X < minPosX) { minPosX = reader.model.vertices[i].position.X; } if (reader.model.vertices[i].position.Z < minPosY) { minPosY = reader.model.vertices[i].position.Z; } if (reader.model.vertices[i].position.Y * -1 < minPosZ) { minPosZ = reader.model.vertices[i].position.Y * -1; } if (reader.model.vertices[i].position.X > maxPosX) { maxPosX = reader.model.vertices[i].position.X; } if (reader.model.vertices[i].position.Z > maxPosY) { maxPosY = reader.model.vertices[i].position.Z; } if (reader.model.vertices[i].position.Y * -1 > maxPosZ) { maxPosZ = reader.model.vertices[i].position.Y * -1; } } vPosBuffer.byteLength = (uint)writer.BaseStream.Position - vPosBuffer.byteOffset; var posLoc = accessorInfo.Count(); accessorInfo.Add(new Accessor() { name = "vPos", bufferView = bufferViews.Count(), byteOffset = 0, componentType = 5126, count = (uint)reader.model.vertices.Count(), type = "VEC3", min = new float[] { minPosX, minPosY, minPosZ }, max = new float[] { maxPosX, maxPosY, maxPosZ } }); bufferViews.Add(vPosBuffer); // Normal bufferview var normalBuffer = new BufferView() { buffer = 0, byteOffset = (uint)writer.BaseStream.Position, target = 34962 }; for (var i = 0; i < reader.model.vertices.Count(); i++) { writer.Write(reader.model.vertices[i].normal.X); writer.Write(reader.model.vertices[i].normal.Z); writer.Write(reader.model.vertices[i].normal.Y); } normalBuffer.byteLength = (uint)writer.BaseStream.Position - normalBuffer.byteOffset; var normalLoc = accessorInfo.Count(); accessorInfo.Add(new Accessor() { name = "vNormal", bufferView = bufferViews.Count(), byteOffset = 0, componentType = 5126, count = (uint)reader.model.vertices.Count(), type = "VEC3" }); bufferViews.Add(normalBuffer); // TexCoord bufferview var texCoordBuffer = new BufferView() { buffer = 0, byteOffset = (uint)writer.BaseStream.Position, target = 34962 }; for (var i = 0; i < reader.model.vertices.Count(); i++) { writer.Write(reader.model.vertices[i].textureCoordX); writer.Write(reader.model.vertices[i].textureCoordY); } texCoordBuffer.byteLength = (uint)writer.BaseStream.Position - texCoordBuffer.byteOffset; var texLoc = accessorInfo.Count(); accessorInfo.Add(new Accessor() { name = "vTex", bufferView = bufferViews.Count(), byteOffset = 0, componentType = 5126, count = (uint)reader.model.vertices.Count(), type = "VEC2" }); bufferViews.Add(texCoordBuffer); // Joints bufferview var jointBuffer = new BufferView() { buffer = 0, byteOffset = (uint)writer.BaseStream.Position, target = 34962 }; for (var i = 0; i < reader.model.vertices.Count(); i++) { writer.Write(reader.model.vertices[i].boneIndices_0); writer.Write(reader.model.vertices[i].boneIndices_1); writer.Write(reader.model.vertices[i].boneIndices_2); writer.Write(reader.model.vertices[i].boneIndices_3); } jointBuffer.byteOffset = (uint)writer.BaseStream.Position - jointBuffer.byteOffset; var jointLoc = accessorInfo.Count(); accessorInfo.Add(new Accessor() { name = "vJoint", bufferView = bufferViews.Count(), byteOffset = 0, componentType = 5121, count = (uint)reader.model.vertices.Count(), type = "VEC4" }); bufferViews.Add(jointBuffer); // Weight bufferview var weightBuffer = new BufferView() { buffer = 0, byteOffset = (uint)writer.BaseStream.Position, target = 34962 }; for (var i = 0; i < reader.model.vertices.Count(); i++) { writer.Write(reader.model.vertices[i].boneWeight_0); writer.Write(reader.model.vertices[i].boneWeight_1); writer.Write(reader.model.vertices[i].boneWeight_2); writer.Write(reader.model.vertices[i].boneWeight_3); } weightBuffer.byteOffset = (uint)writer.BaseStream.Position - weightBuffer.byteOffset; var weightLoc = accessorInfo.Count(); accessorInfo.Add(new Accessor() { name = "vWeight", bufferView = bufferViews.Count(), byteOffset = 0, componentType = 5121, count = (uint)reader.model.vertices.Count(), type = "VEC4" }); bufferViews.Add(weightBuffer); // End of element bufferviews var indexBufferPos = bufferViews.Count(); var materialBlends = new Dictionary <int, ushort>(); for (var i = 0; i < reader.model.skins[0].submeshes.Count(); i++) { var batch = reader.model.skins[0].submeshes[i]; accessorInfo.Add(new Accessor() { name = "indices", bufferView = indexBufferPos, byteOffset = reader.model.skins[0].submeshes[i].startTriangle * 2, componentType = 5123, count = reader.model.skins[0].submeshes[i].nTriangles, type = "SCALAR" }); var mesh = new Mesh(); mesh.name = "Group #" + i; mesh.primitives = new Primitive[1]; mesh.primitives[0].attributes = new Dictionary <string, int> { { "POSITION", posLoc }, { "NORMAL", normalLoc }, { "TEXCOORD_0", texLoc }, { "JOINTS_0", jointLoc }, { "WEIGHTS_0", weightLoc } }; mesh.primitives[0].indices = (uint)accessorInfo.Count() - 1; mesh.primitives[0].mode = 4; meshes.Add(mesh); // Texture stuff for (var tu = 0; tu < reader.model.skins[0].textureunit.Count(); tu++) { if (reader.model.skins[0].textureunit[tu].submeshIndex == i) { mesh.primitives[0].material = reader.model.texlookup[reader.model.skins[0].textureunit[tu].texture].textureID; // todo if (!materialBlends.ContainsKey(i)) { // add texture materialBlends.Add(i, reader.model.renderflags[reader.model.skins[0].textureunit[tu].renderFlags].blendingMode); } else { // already exists Logger.WriteLine("Material " + mesh.primitives[0].material + " already exists in blend map with value " + materialBlends[i]); } } } } var indiceBuffer = new BufferView() { buffer = 0, byteOffset = (uint)writer.BaseStream.Position, target = 34963 }; for (var i = 0; i < reader.model.skins[0].triangles.Count(); i++) { var t = reader.model.skins[0].triangles[i]; writer.Write(t.pt1); writer.Write(t.pt2); writer.Write(t.pt3); } indiceBuffer.byteLength = (uint)writer.BaseStream.Position - indiceBuffer.byteOffset; bufferViews.Add(indiceBuffer); glTF.bufferViews = bufferViews.ToArray(); glTF.accessors = accessorInfo.ToArray(); glTF.buffers = new Buffer[1]; glTF.buffers[0].byteLength = (uint)writer.BaseStream.Length; glTF.buffers[0].uri = Path.GetFileNameWithoutExtension(file) + ".bin"; writer.Close(); writer.Dispose(); exportworker.ReportProgress(65, "Exporting textures.."); var materialCount = reader.model.textures.Count(); glTF.images = new Image[materialCount]; glTF.textures = new Texture[materialCount]; glTF.materials = new Material[materialCount]; var textureID = 0; var materials = new Structs.Material[reader.model.textures.Count()]; for (var i = 0; i < reader.model.textures.Count(); i++) { uint textureFileDataID = 840426; materials[i].flags = reader.model.textures[i].flags; switch (reader.model.textures[i].type) { case 0: textureFileDataID = CASC.getFileDataIdByName(reader.model.textures[i].filename); break; case 1: case 2: case 11: var fileDataID = CASC.getFileDataIdByName(file); var cdifilenames = WoWFormatLib.DBC.DBCHelper.getTexturesByModelFilename(fileDataID, (int)reader.model.textures[i].type); for (var ti = 0; ti < cdifilenames.Count(); ti++) { textureFileDataID = cdifilenames[0]; } break; default: Console.WriteLine(" Falling back to placeholder texture"); break; } materials[i].textureID = textureID + i; materials[i].filename = textureFileDataID.ToString(); glTF.materials[i].name = materials[i].filename; glTF.materials[i].pbrMetallicRoughness = new PBRMetallicRoughness(); glTF.materials[i].pbrMetallicRoughness.baseColorTexture = new TextureIndex(); glTF.materials[i].pbrMetallicRoughness.baseColorTexture.index = i; glTF.materials[i].pbrMetallicRoughness.metallicFactor = 0.0f; glTF.materials[i].alphaMode = "MASK"; glTF.materials[i].alphaCutoff = 0.5f; glTF.images[i].uri = "tex_" + materials[i].filename + ".png"; glTF.textures[i].sampler = 0; glTF.textures[i].source = i; var blpreader = new BLPReader(); blpreader.LoadBLP(textureFileDataID); try { if (destinationOverride == null) { blpreader.bmp.Save(Path.Combine(outdir, Path.GetDirectoryName(file), glTF.images[i].uri)); } else { blpreader.bmp.Save(Path.Combine(outdir, destinationOverride, glTF.images[i].uri)); } } catch (Exception e) { Console.WriteLine(e.Message); } } exportworker.ReportProgress(85, "Writing files.."); glTF.samplers = new Sampler[1]; glTF.samplers[0].name = "Default Sampler"; glTF.samplers[0].minFilter = 9986; glTF.samplers[0].magFilter = 9729; glTF.samplers[0].wrapS = 10497; glTF.samplers[0].wrapT = 10497; glTF.scenes = new Scene[1]; glTF.scenes[0].name = Path.GetFileNameWithoutExtension(file); glTF.nodes = new Node[meshes.Count()]; var meshIDs = new List <int>(); for (var i = 0; i < meshes.Count(); i++) { glTF.nodes[i].name = meshes[i].name; glTF.nodes[i].mesh = i; meshIDs.Add(i); } glTF.scenes[0].nodes = meshIDs.ToArray(); glTF.meshes = meshes.ToArray(); glTF.scene = 0; exportworker.ReportProgress(95, "Writing to file.."); if (destinationOverride == null) { File.WriteAllText(Path.Combine(outdir, file.Replace(".m2", ".gltf")), JsonConvert.SerializeObject(glTF, Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore })); } else { File.WriteAllText(Path.Combine(destinationOverride, Path.GetFileName(file.ToLower()).Replace(".m2", ".gltf")), JsonConvert.SerializeObject(glTF, Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore })); } /* * objsw.WriteLine("g " + Path.GetFileNameWithoutExtension(file)); * * foreach (var renderbatch in renderbatches) * { * var i = renderbatch.firstFace; * objsw.WriteLine("o " + Path.GetFileNameWithoutExtension(file) + renderbatch.groupID); * objsw.WriteLine("usemtl tex_" + materials[renderbatch.materialID].filename); * objsw.WriteLine("s 1"); * while (i < (renderbatch.firstFace + renderbatch.numFaces)) * { * objsw.WriteLine("f " + (indices[i] + 1) + "/" + (indices[i] + 1) + "/" + (indices[i] + 1) + " " + (indices[i + 1] + 1) + "/" + (indices[i + 1] + 1) + "/" + (indices[i + 1] + 1) + " " + (indices[i + 2] + 1) + "/" + (indices[i + 2] + 1) + "/" + (indices[i + 2] + 1)); * i = i + 3; * } * } * * objsw.Close(); * * // Only export phys when exporting a single M2, causes issues for some users when combined with WMO/ADT * if (destinationOverride == null) * { * exportworker.ReportProgress(90, "Exporting collision.."); * * objsw = new StreamWriter(Path.Combine(outdir, file.Replace(".m2", ".phys.obj"))); * * objsw.WriteLine("# Written by Marlamin's WoW Exporter. Original file: " + file); * * for (int i = 0; i < reader.model.boundingvertices.Count(); i++) * { * objsw.WriteLine("v " + * reader.model.boundingvertices[i].vertex.X + " " + * reader.model.boundingvertices[i].vertex.Z + " " + * -reader.model.boundingvertices[i].vertex.Y); * } * * for (int i = 0; i < reader.model.boundingtriangles.Count(); i++) * { * var t = reader.model.boundingtriangles[i]; * objsw.WriteLine("f " + (t.index_0 + 1) + " " + (t.index_1 + 1) + " " + (t.index_2 + 1)); * } * * objsw.Close(); * } * * // https://en.wikipedia.org/wiki/Wavefront_.obj_file#Basic_materials * // http://wiki.unity3d.com/index.php?title=ExportOBJ * // http://web.cse.ohio-state.edu/~hwshen/581/Site/Lab3_files/Labhelp_Obj_parser.htm */ }