public static void exportWMO(string file, BackgroundWorker exportworker = null) { if (exportworker == null) { exportworker = new BackgroundWorker(); } Console.WriteLine("Loading WMO file.."); exportworker.ReportProgress(5, "Reading WMO.."); var outdir = ConfigurationManager.AppSettings["outdir"]; WMOReader reader = new WMOReader(); reader.LoadWMO(file); // TODO: Support doodads! /* * for (int i = 0; i < reader.wmofile.doodadNames.Count(); i++) * { * //Console.WriteLine(reader.wmofile.doodadNames[i].filename); * //reader.wmofile.doodadDefinitions[i]. * //reader.wmofile.doodadDefinitions[i]. * } */ exportworker.ReportProgress(30, "Reading WMO.."); uint totalVertices = 0; var groups = new Structs.WMOGroup[reader.wmofile.group.Count()]; for (int g = 0; g < reader.wmofile.group.Count(); g++) { Console.WriteLine("Loading group #" + 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; } groups[g].verticeOffset = totalVertices; groups[g].vertices = new Structs.Vertex[reader.wmofile.group[g].mogp.vertices.Count()]; for (int i = 0; i < reader.wmofile.group[g].mogp.vertices.Count(); i++) { groups[g].vertices[i].Position = new Vector3(reader.wmofile.group[g].mogp.vertices[i].vector.X * -1, reader.wmofile.group[g].mogp.vertices[i].vector.Z, reader.wmofile.group[g].mogp.vertices[i].vector.Y); groups[g].vertices[i].Normal = new Vector3(reader.wmofile.group[g].mogp.normals[i].normal.X, reader.wmofile.group[g].mogp.normals[i].normal.Z, reader.wmofile.group[g].mogp.normals[i].normal.Y); groups[g].vertices[i].TexCoord = new Vector2(reader.wmofile.group[g].mogp.textureCoords[0][i].X, reader.wmofile.group[g].mogp.textureCoords[0][i].Y); totalVertices++; } var indicelist = new List <uint>(); for (int i = 0; i < reader.wmofile.group[g].mogp.indices.Count(); i++) { indicelist.Add(reader.wmofile.group[g].mogp.indices[i].indice); } groups[g].indices = indicelist.ToArray(); } exportworker.ReportProgress(55, "Exporting textures.."); // Create output directory if (!Directory.Exists(Path.Combine(outdir, Path.GetDirectoryName(file)))) { Directory.CreateDirectory(Path.Combine(outdir, Path.GetDirectoryName(file))); } var mtlsb = new StringBuilder(); var textureID = 0; if (reader.wmofile.materials == null) { Console.WriteLine("Materials empty"); return; } var materials = new Structs.Material[reader.wmofile.materials.Count()]; for (int i = 0; i < reader.wmofile.materials.Count(); i++) { for (int ti = 0; ti < reader.wmofile.textures.Count(); ti++) { if (reader.wmofile.textures[ti].startOffset == reader.wmofile.materials[i].texture1) { //materials[i].textureID = BLPLoader.LoadTexture(reader.wmofile.textures[ti].filename, cache); materials[i].textureID = textureID + i; materials[i].filename = Path.GetFileNameWithoutExtension(reader.wmofile.textures[ti].filename); if (reader.wmofile.materials[i].blendMode == 0) { materials[i].transparent = false; } else { materials[i].transparent = true; } if (!File.Exists(Path.Combine(outdir, Path.GetDirectoryName(file), materials[i].filename + ".png"))) { var blpreader = new BLPReader(); blpreader.LoadBLP(reader.wmofile.textures[ti].filename); try { blpreader.bmp.Save(Path.Combine(outdir, Path.GetDirectoryName(file), materials[i].filename + ".png")); } catch (Exception e) { Console.WriteLine(e.Message); } } textureID++; } } } //No idea how MTL files really work yet. Needs more investigation. foreach (var material in materials) { mtlsb.Append("newmtl " + material.filename + "\n"); mtlsb.Append("Ns 96.078431\n"); mtlsb.Append("Ka 1.000000 1.000000 1.000000\n"); mtlsb.Append("Kd 0.640000 0.640000 0.640000\n"); mtlsb.Append("Ks 0.000000 0.000000 0.000000\n"); mtlsb.Append("Ke 0.000000 0.000000 0.000000\n"); mtlsb.Append("Ni 1.000000\n"); mtlsb.Append("d 1.000000\n"); mtlsb.Append("illum 2\n"); mtlsb.Append("map_Kd " + material.filename + ".png\n"); if (material.transparent) { mtlsb.Append("map_d " + material.filename + ".png\n"); } } File.WriteAllText(Path.Combine(outdir, file.Replace(".wmo", ".mtl")), mtlsb.ToString()); exportworker.ReportProgress(75, "Exporting model.."); int numRenderbatches = 0; //Get total amount of render batches for (int i = 0; i < reader.wmofile.group.Count(); i++) { if (reader.wmofile.group[i].mogp.renderBatches == null) { continue; } numRenderbatches = numRenderbatches + reader.wmofile.group[i].mogp.renderBatches.Count(); } int rb = 0; for (int g = 0; g < reader.wmofile.group.Count(); g++) { groups[g].renderBatches = new Structs.RenderBatch[numRenderbatches]; var group = reader.wmofile.group[g]; if (group.mogp.renderBatches == null) { continue; } for (int i = 0; i < group.mogp.renderBatches.Count(); i++) { var batch = group.mogp.renderBatches[i]; groups[g].renderBatches[rb].firstFace = batch.firstFace; groups[g].renderBatches[rb].numFaces = batch.numFaces; if (batch.flags == 2) { groups[g].renderBatches[rb].materialID = (uint)batch.possibleBox2_3; } else { groups[g].renderBatches[rb].materialID = batch.materialID; } groups[g].renderBatches[rb].blendType = reader.wmofile.materials[batch.materialID].blendMode; groups[g].renderBatches[rb].groupID = (uint)g; rb++; } } exportworker.ReportProgress(95, "Writing files.."); var objsw = new StreamWriter(Path.Combine(outdir, file.Replace(".wmo", ".obj"))); objsw.WriteLine("# Written by Marlamin's WoW OBJExporter. Original file: " + file); objsw.WriteLine("mtllib " + Path.GetFileNameWithoutExtension(file) + ".mtl"); foreach (var group in groups) { if (group.vertices == null) { continue; } Console.WriteLine("Writing " + group.name); objsw.WriteLine("g " + group.name); foreach (var vertex in group.vertices) { objsw.WriteLine("v " + vertex.Position.X + " " + vertex.Position.Y + " " + vertex.Position.Z); objsw.WriteLine("vt " + vertex.TexCoord.X + " " + -vertex.TexCoord.Y); objsw.WriteLine("vn " + vertex.Normal.X + " " + vertex.Normal.Y + " " + vertex.Normal.Z); } var indices = group.indices; foreach (var renderbatch in group.renderBatches) { var i = renderbatch.firstFace; if (renderbatch.numFaces > 0) { objsw.WriteLine("usemtl " + materials[renderbatch.materialID].filename); objsw.WriteLine("s 1"); while (i < (renderbatch.firstFace + renderbatch.numFaces)) { objsw.WriteLine("f " + (indices[i] + group.verticeOffset + 1) + "/" + (indices[i] + group.verticeOffset + 1) + "/" + (indices[i] + group.verticeOffset + 1) + " " + (indices[i + 1] + group.verticeOffset + 1) + "/" + (indices[i + 1] + group.verticeOffset + 1) + "/" + (indices[i + 1] + group.verticeOffset + 1) + " " + (indices[i + 2] + group.verticeOffset + 1) + "/" + (indices[i + 2] + group.verticeOffset + 1) + "/" + (indices[i + 2] + group.verticeOffset + 1)); i = i + 3; } } } } objsw.Close(); Console.WriteLine("Done loading WMO file!"); }
public static void ExportWMO(uint filedataid, BackgroundWorker exportworker = null, string destinationOverride = null, ushort doodadSetExportID = ushort.MaxValue, string filename = "") { if (exportworker == null) { exportworker = new BackgroundWorker(); exportworker.WorkerReportsProgress = true; } if (string.IsNullOrEmpty(filename)) { if (!Listfile.TryGetFilename(filedataid, out filename)) { CASCLib.Logger.WriteLine("Warning! Could not find filename for " + filedataid + "!"); } } Console.WriteLine("Loading WMO file.."); exportworker.ReportProgress(5, "Reading WMO.."); var outdir = ConfigurationManager.AppSettings["outdir"]; var wmo = new WMOReader().LoadWMO(filedataid); var customCulture = (System.Globalization.CultureInfo)System.Threading.Thread.CurrentThread.CurrentCulture.Clone(); customCulture.NumberFormat.NumberDecimalSeparator = "."; System.Threading.Thread.CurrentThread.CurrentCulture = customCulture; exportworker.ReportProgress(30, "Reading WMO.."); uint totalVertices = 0; var groups = new Structs.WMOGroup[wmo.group.Count()]; for (var g = 0; g < wmo.group.Count(); g++) { Console.WriteLine("Loading group #" + g); if (wmo.group[g].mogp.vertices == null) { Console.WriteLine("Group has no vertices!"); continue; } for (var i = 0; i < wmo.groupNames.Count(); i++) { if (wmo.group[g].mogp.nameOffset == wmo.groupNames[i].offset) { groups[g].name = wmo.groupNames[i].name.Replace(" ", "_"); } } if (groups[g].name == "antiportal") { Console.WriteLine("Group is antiportal"); continue; } groups[g].verticeOffset = totalVertices; groups[g].vertices = new Structs.Vertex[wmo.group[g].mogp.vertices.Count()]; for (var i = 0; i < wmo.group[g].mogp.vertices.Count(); i++) { groups[g].vertices[i].Position = new Structs.Vector3D() { X = wmo.group[g].mogp.vertices[i].vector.X * -1, Y = wmo.group[g].mogp.vertices[i].vector.Z, Z = wmo.group[g].mogp.vertices[i].vector.Y }; groups[g].vertices[i].Normal = new Structs.Vector3D() { X = wmo.group[g].mogp.normals[i].normal.X, Y = wmo.group[g].mogp.normals[i].normal.Z, Z = wmo.group[g].mogp.normals[i].normal.Y }; groups[g].vertices[i].TexCoord = new Structs.Vector2D() { X = wmo.group[g].mogp.textureCoords[0][i].X, Y = wmo.group[g].mogp.textureCoords[0][i].Y }; totalVertices++; } var indicelist = new List <uint>(); for (var i = 0; i < wmo.group[g].mogp.indices.Count(); i++) { indicelist.Add(wmo.group[g].mogp.indices[i].indice); } groups[g].indices = indicelist.ToArray(); } if (destinationOverride == null) { // Create output directory if (!string.IsNullOrEmpty(filename)) { if (!Directory.Exists(Path.Combine(outdir, Path.GetDirectoryName(filename)))) { Directory.CreateDirectory(Path.Combine(outdir, Path.GetDirectoryName(filename))); } } else { if (!Directory.Exists(outdir)) { Directory.CreateDirectory(outdir); } } } StreamWriter doodadSW; if (destinationOverride == null) { if (!string.IsNullOrEmpty(filename)) { doodadSW = new StreamWriter(Path.Combine(outdir, Path.GetDirectoryName(filename), Path.GetFileNameWithoutExtension(filename.Replace(" ", "")) + "_ModelPlacementInformation.csv")); } else { doodadSW = new StreamWriter(Path.Combine(outdir, Path.GetDirectoryName(filename), filedataid + "_ModelPlacementInformation.csv")); } } else { if (!string.IsNullOrEmpty(filename)) { doodadSW = new StreamWriter(Path.Combine(outdir, destinationOverride, Path.GetFileNameWithoutExtension(filename).Replace(" ", "") + "_ModelPlacementInformation.csv")); } else { doodadSW = new StreamWriter(Path.Combine(outdir, destinationOverride, filedataid + "_ModelPlacementInformation.csv")); } } exportworker.ReportProgress(55, "Exporting doodads.."); doodadSW.WriteLine("ModelFile;PositionX;PositionY;PositionZ;RotationW;RotationX;RotationY;RotationZ;ScaleFactor;DoodadSet"); for (var i = 0; i < wmo.doodadSets.Count(); i++) { var doodadSet = wmo.doodadSets[i]; var currentDoodadSetName = doodadSet.setName.Replace("Set_", "").Replace("SET_", "").Replace("$DefaultGlobal", "Default"); if (doodadSetExportID != ushort.MaxValue) { if (i != 0 && i != doodadSetExportID) { Console.WriteLine("Skipping doodadset with ID " + i + " (" + currentDoodadSetName + ") because export filter is set to " + doodadSetExportID); continue; } } Console.WriteLine("At doodadset " + i + " (" + currentDoodadSetName + ")"); for (var j = doodadSet.firstInstanceIndex; j < (doodadSet.firstInstanceIndex + doodadSet.numDoodads); j++) { var doodadDefinition = wmo.doodadDefinitions[j]; var doodadFilename = ""; uint doodadFileDataID = 0; if (wmo.doodadIds != null) { doodadFileDataID = wmo.doodadIds[doodadDefinition.offset]; if (!Listfile.TryGetFilename(doodadFileDataID, out doodadFilename)) { CASCLib.Logger.WriteLine("Could not find filename for " + doodadFileDataID + ", setting filename to filedataid.."); doodadFilename = doodadFileDataID.ToString(); } } else { CASCLib.Logger.WriteLine("Warning!! File " + filename + " ID: " + filedataid + " still has filenames!"); foreach (var doodadNameEntry in wmo.doodadNames) { if (doodadNameEntry.startOffset == doodadDefinition.offset) { doodadFilename = doodadNameEntry.filename.Replace(".MDX", ".M2").Replace(".MDL", ".M2").ToLower(); if (!Listfile.TryGetFileDataID(doodadFilename, out doodadFileDataID)) { CASCLib.Logger.WriteLine("Error! Could not find filedataid for " + doodadFilename + "!"); continue; } } } } if (destinationOverride == null) { if (!string.IsNullOrEmpty(doodadFilename)) { if (!File.Exists(Path.Combine(outdir, Path.GetDirectoryName(filename), Path.GetFileNameWithoutExtension(doodadFilename) + ".obj"))) { M2Exporter.ExportM2(doodadFileDataID, null, Path.Combine(outdir, Path.GetDirectoryName(filename)), doodadFilename); } if (File.Exists(Path.Combine(outdir, Path.GetDirectoryName(filename), Path.GetFileNameWithoutExtension(doodadFilename) + ".obj"))) { doodadSW.WriteLine(Path.GetFileNameWithoutExtension(doodadFilename) + ".obj;" + doodadDefinition.position.X.ToString("F09") + ";" + doodadDefinition.position.Y.ToString("F09") + ";" + doodadDefinition.position.Z.ToString("F09") + ";" + doodadDefinition.rotation.W.ToString("F15") + ";" + doodadDefinition.rotation.X.ToString("F15") + ";" + doodadDefinition.rotation.Y.ToString("F15") + ";" + doodadDefinition.rotation.Z.ToString("F15") + ";" + doodadDefinition.scale + ";" + currentDoodadSetName); } } else { if (!File.Exists(Path.Combine(outdir, doodadFileDataID + ".obj"))) { M2Exporter.ExportM2(doodadFileDataID, null, outdir, doodadFilename); } if (File.Exists(Path.Combine(outdir, doodadFileDataID + ".obj"))) { doodadSW.WriteLine(doodadFileDataID + ".obj;" + doodadDefinition.position.X.ToString("F09") + ";" + doodadDefinition.position.Y.ToString("F09") + ";" + doodadDefinition.position.Z.ToString("F09") + ";" + doodadDefinition.rotation.W.ToString("F15") + ";" + doodadDefinition.rotation.X.ToString("F15") + ";" + doodadDefinition.rotation.Y.ToString("F15") + ";" + doodadDefinition.rotation.Z.ToString("F15") + ";" + doodadDefinition.scale + ";" + currentDoodadSetName); } } } else { if (!string.IsNullOrEmpty(doodadFilename)) { if (!File.Exists(Path.Combine(destinationOverride, Path.GetFileNameWithoutExtension(doodadFilename) + ".obj"))) { M2Exporter.ExportM2(doodadFileDataID, null, destinationOverride, doodadFilename); } if (File.Exists(Path.Combine(destinationOverride, Path.GetFileNameWithoutExtension(doodadFilename) + ".obj"))) { doodadSW.WriteLine(Path.GetFileNameWithoutExtension(doodadFilename) + ".obj;" + doodadDefinition.position.X.ToString("F09") + ";" + doodadDefinition.position.Y.ToString("F09") + ";" + doodadDefinition.position.Z.ToString("F09") + ";" + doodadDefinition.rotation.W.ToString("F15") + ";" + doodadDefinition.rotation.X.ToString("F15") + ";" + doodadDefinition.rotation.Y.ToString("F15") + ";" + doodadDefinition.rotation.Z.ToString("F15") + ";" + doodadDefinition.scale + ";" + currentDoodadSetName); } } else { if (!File.Exists(Path.Combine(destinationOverride, doodadFileDataID + ".obj"))) { M2Exporter.ExportM2(doodadFileDataID, null, destinationOverride, doodadFilename); } if (File.Exists(Path.Combine(destinationOverride, doodadFileDataID + ".obj"))) { doodadSW.WriteLine(doodadFileDataID + ".obj;" + doodadDefinition.position.X.ToString("F09") + ";" + doodadDefinition.position.Y.ToString("F09") + ";" + doodadDefinition.position.Z.ToString("F09") + ";" + doodadDefinition.rotation.W.ToString("F15") + ";" + doodadDefinition.rotation.X.ToString("F15") + ";" + doodadDefinition.rotation.Y.ToString("F15") + ";" + doodadDefinition.rotation.Z.ToString("F15") + ";" + doodadDefinition.scale + ";" + currentDoodadSetName); } } } } } doodadSW.Close(); exportworker.ReportProgress(65, "Exporting textures.."); var mtlsb = new StringBuilder(); var textureID = 0; if (wmo.materials == null) { CASCLib.Logger.WriteLine("Unable to find materials for WMO " + filedataid + ", not exporting!"); return; } var materials = new Structs.Material[wmo.materials.Count()]; var extraMaterials = new List <Structs.Material>(); for (var i = 0; i < wmo.materials.Count(); i++) { var blpreader = new BLPReader(); if (wmo.textures == null) { if (Listfile.TryGetFilename(wmo.materials[i].texture1, out var textureFilename)) { materials[i].filename = Path.GetFileNameWithoutExtension(textureFilename); } else { materials[i].filename = wmo.materials[i].texture1.ToString(); } blpreader.LoadBLP(wmo.materials[i].texture1); } else { for (var ti = 0; ti < wmo.textures.Count(); ti++) { if (wmo.textures[ti].startOffset == wmo.materials[i].texture1) { materials[i].filename = Path.GetFileNameWithoutExtension(wmo.textures[ti].filename); blpreader.LoadBLP(wmo.textures[ti].filename); } } } materials[i].textureID = textureID + i; if (wmo.materials[i].blendMode == 0) { materials[i].transparent = false; } else { materials[i].transparent = true; } materials[i].blendMode = wmo.materials[i].blendMode; materials[i].shaderID = wmo.materials[i].shader; materials[i].terrainType = wmo.materials[i].groundType; var saveLocation = ""; if (destinationOverride == null) { if (!string.IsNullOrEmpty(filename)) { saveLocation = Path.Combine(outdir, Path.GetDirectoryName(filename), materials[i].filename + ".png"); } else { saveLocation = Path.Combine(outdir, materials[i].filename + ".png"); } } else { saveLocation = Path.Combine(outdir, destinationOverride, materials[i].filename + ".png"); } if (!File.Exists(saveLocation)) { try { if (materials[i].transparent) { blpreader.bmp.Save(saveLocation); } else { blpreader.bmp.Clone(new Rectangle(0, 0, blpreader.bmp.Width, blpreader.bmp.Height), PixelFormat.Format32bppRgb).Save(saveLocation); } } catch (Exception e) { CASCLib.Logger.WriteLine("Exception while saving BLP " + materials[i].filename + ": " + e.Message); } } textureID++; // Extra materials // Texture 2 if (CASC.FileExists(wmo.materials[i].texture2)) { var tex2mat = new Structs.Material(); if (wmo.textures == null) { if (Listfile.TryGetFilename(wmo.materials[i].texture2, out var textureFilename)) { tex2mat.filename = Path.GetFileNameWithoutExtension(textureFilename); } else { tex2mat.filename = wmo.materials[i].texture2.ToString(); } blpreader.LoadBLP(wmo.materials[i].texture2); } else { for (var ti = 0; ti < wmo.textures.Count(); ti++) { if (wmo.textures[ti].startOffset == wmo.materials[i].texture2) { tex2mat.filename = Path.GetFileNameWithoutExtension(wmo.textures[ti].filename); blpreader.LoadBLP(wmo.textures[ti].filename); } } } if (destinationOverride == null) { if (!string.IsNullOrEmpty(filename)) { saveLocation = Path.Combine(outdir, Path.GetDirectoryName(filename), tex2mat.filename + ".png"); } else { saveLocation = Path.Combine(outdir, tex2mat.filename + ".png"); } } else { saveLocation = Path.Combine(outdir, destinationOverride, tex2mat.filename + ".png"); } if (!File.Exists(saveLocation)) { try { blpreader.bmp.Save(saveLocation); } catch (Exception e) { CASCLib.Logger.WriteLine("Exception while saving BLP " + tex2mat.filename + ": " + e.Message); } } extraMaterials.Add(tex2mat); } // Texture 3 if (CASC.FileExists(wmo.materials[i].texture3)) { var tex3mat = new Structs.Material(); if (wmo.textures == null) { if (Listfile.TryGetFilename(wmo.materials[i].texture3, out var textureFilename)) { tex3mat.filename = Path.GetFileNameWithoutExtension(textureFilename); } else { tex3mat.filename = wmo.materials[i].texture3.ToString(); } blpreader.LoadBLP(wmo.materials[i].texture3); } else { for (var ti = 0; ti < wmo.textures.Count(); ti++) { if (wmo.textures[ti].startOffset == wmo.materials[i].texture3) { tex3mat.filename = Path.GetFileNameWithoutExtension(wmo.textures[ti].filename); blpreader.LoadBLP(wmo.textures[ti].filename); } } } if (destinationOverride == null) { if (!string.IsNullOrEmpty(filename)) { saveLocation = Path.Combine(outdir, Path.GetDirectoryName(filename), tex3mat.filename + ".png"); } else { saveLocation = Path.Combine(outdir, tex3mat.filename + ".png"); } } else { saveLocation = Path.Combine(outdir, destinationOverride, tex3mat.filename + ".png"); } if (!File.Exists(saveLocation)) { try { blpreader.bmp.Save(saveLocation); } catch (Exception e) { CASCLib.Logger.WriteLine("Exception while saving BLP " + tex3mat.filename + ": " + e.Message); } } extraMaterials.Add(tex3mat); } } //No idea how MTL files really work yet. Needs more investigation. foreach (var material in materials) { mtlsb.Append("newmtl " + material.filename + "\n"); mtlsb.Append("Ns 96.078431\n"); mtlsb.Append("Ka 1.000000 1.000000 1.000000\n"); mtlsb.Append("Kd 0.640000 0.640000 0.640000\n"); mtlsb.Append("Ks 0.000000 0.000000 0.000000\n"); mtlsb.Append("Ke 0.000000 0.000000 0.000000\n"); mtlsb.Append("Ni 1.000000\n"); mtlsb.Append("d 1.000000\n"); mtlsb.Append("illum 1\n"); mtlsb.Append("map_Kd " + material.filename + ".png\n"); if (material.transparent) { mtlsb.Append("map_d " + material.filename + ".png\n"); } if (ConfigurationManager.AppSettings["textureMetadata"] == "True") { mtlsb.Append("blend " + material.blendMode + "\n"); mtlsb.Append("shader " + material.shaderID + "\n"); mtlsb.Append("terrain " + material.terrainType + "\n"); } } foreach (var material in extraMaterials) { mtlsb.Append("newmtl " + material.filename + "\n"); mtlsb.Append("Ns 96.078431\n"); mtlsb.Append("Ka 1.000000 1.000000 1.000000\n"); mtlsb.Append("Kd 0.640000 0.640000 0.640000\n"); mtlsb.Append("Ks 0.000000 0.000000 0.000000\n"); mtlsb.Append("Ke 0.000000 0.000000 0.000000\n"); mtlsb.Append("Ni 1.000000\n"); mtlsb.Append("d 1.000000\n"); mtlsb.Append("illum 1\n"); mtlsb.Append("map_Kd " + material.filename + ".png\n"); if (material.transparent) { mtlsb.Append("map_d " + material.filename + ".png\n"); } } if (!string.IsNullOrEmpty(filename)) { if (destinationOverride == null) { File.WriteAllText(Path.Combine(outdir, filename.Replace(".wmo", ".mtl")), mtlsb.ToString()); } else { File.WriteAllText(Path.Combine(outdir, destinationOverride, Path.GetFileName(filename.ToLower()).Replace(".wmo", ".mtl")), mtlsb.ToString()); } } else { if (destinationOverride == null) { File.WriteAllText(Path.Combine(outdir, filedataid + ".mtl"), mtlsb.ToString()); } else { File.WriteAllText(Path.Combine(outdir, destinationOverride, filedataid + ".mtl"), mtlsb.ToString()); } } exportworker.ReportProgress(75, "Exporting model.."); var numRenderbatches = 0; //Get total amount of render batches for (var i = 0; i < wmo.group.Count(); i++) { if (wmo.group[i].mogp.renderBatches == null) { continue; } numRenderbatches = numRenderbatches + wmo.group[i].mogp.renderBatches.Count(); } var rb = 0; for (var g = 0; g < wmo.group.Count(); g++) { groups[g].renderBatches = new Structs.RenderBatch[numRenderbatches]; var group = wmo.group[g]; if (group.mogp.renderBatches == null) { continue; } for (var i = 0; i < group.mogp.renderBatches.Count(); i++) { var batch = group.mogp.renderBatches[i]; groups[g].renderBatches[rb].firstFace = batch.firstFace; groups[g].renderBatches[rb].numFaces = batch.numFaces; if (batch.flags == 2) { groups[g].renderBatches[rb].materialID = (uint)batch.possibleBox2_3; } else { groups[g].renderBatches[rb].materialID = batch.materialID; } groups[g].renderBatches[rb].blendType = wmo.materials[batch.materialID].blendMode; groups[g].renderBatches[rb].groupID = (uint)g; rb++; } } exportworker.ReportProgress(95, "Writing files.."); StreamWriter objsw; if (!string.IsNullOrEmpty(filename)) { if (destinationOverride == null) { objsw = new StreamWriter(Path.Combine(outdir, filename.Replace(".wmo", ".obj"))); } else { objsw = new StreamWriter(Path.Combine(outdir, destinationOverride, Path.GetFileName(filename.ToLower()).Replace(".wmo", ".obj"))); } objsw.WriteLine("# Written by Marlamin's WoW Export Tools. Original file: " + filename); objsw.WriteLine("mtllib " + Path.GetFileNameWithoutExtension(filename) + ".mtl"); } else { if (destinationOverride == null) { objsw = new StreamWriter(Path.Combine(outdir, filedataid + ".obj")); } else { objsw = new StreamWriter(Path.Combine(outdir, destinationOverride, filedataid + ".obj")); } objsw.WriteLine("# Written by Marlamin's WoW Export Tools. Original file id: " + filedataid); objsw.WriteLine("mtllib " + filedataid + ".mtl"); } foreach (var group in groups) { if (group.vertices == null) { continue; } Console.WriteLine("Writing " + group.name); objsw.WriteLine("g " + group.name); foreach (var vertex in group.vertices) { objsw.WriteLine("v " + vertex.Position.X + " " + vertex.Position.Y + " " + vertex.Position.Z); objsw.WriteLine("vt " + vertex.TexCoord.X + " " + (vertex.TexCoord.Y - 1) * -1); objsw.WriteLine("vn " + (-vertex.Normal.X).ToString("F12") + " " + vertex.Normal.Y.ToString("F12") + " " + vertex.Normal.Z.ToString("F12")); } var indices = group.indices; foreach (var renderbatch in group.renderBatches) { var i = renderbatch.firstFace; if (renderbatch.numFaces > 0) { objsw.WriteLine("usemtl " + materials[renderbatch.materialID].filename); objsw.WriteLine("s 1"); while (i < (renderbatch.firstFace + renderbatch.numFaces)) { objsw.WriteLine("f " + (indices[i] + group.verticeOffset + 1) + "/" + (indices[i] + group.verticeOffset + 1) + "/" + (indices[i] + group.verticeOffset + 1) + " " + (indices[i + 1] + group.verticeOffset + 1) + "/" + (indices[i + 1] + group.verticeOffset + 1) + "/" + (indices[i + 1] + group.verticeOffset + 1) + " " + (indices[i + 2] + group.verticeOffset + 1) + "/" + (indices[i + 2] + group.verticeOffset + 1) + "/" + (indices[i + 2] + group.verticeOffset + 1)); i = i + 3; } } } } objsw.Close(); Console.WriteLine("Done loading WMO 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 exportWMO(string file, BackgroundWorker exportworker = null, string destinationOverride = null, ushort doodadSetExportID = ushort.MaxValue) { if (exportworker == null) { exportworker = new BackgroundWorker(); exportworker.WorkerReportsProgress = true; } Console.WriteLine("Loading WMO file.."); exportworker.ReportProgress(5, "Reading WMO.."); var outdir = ConfigurationManager.AppSettings["outdir"]; var wmo = new WMOReader().LoadWMO(file); var customCulture = (System.Globalization.CultureInfo)System.Threading.Thread.CurrentThread.CurrentCulture.Clone(); customCulture.NumberFormat.NumberDecimalSeparator = "."; System.Threading.Thread.CurrentThread.CurrentCulture = customCulture; exportworker.ReportProgress(30, "Reading WMO.."); uint totalVertices = 0; var groups = new Structs.WMOGroup[wmo.group.Count()]; for (var g = 0; g < wmo.group.Count(); g++) { Console.WriteLine("Loading group #" + g); if (wmo.group[g].mogp.vertices == null) { Console.WriteLine("Group has no vertices!"); continue; } for (var i = 0; i < wmo.groupNames.Count(); i++) { if (wmo.group[g].mogp.nameOffset == wmo.groupNames[i].offset) { groups[g].name = wmo.groupNames[i].name.Replace(" ", "_"); } } if (groups[g].name == "antiportal") { Console.WriteLine("Group is antiportal"); continue; } groups[g].verticeOffset = totalVertices; groups[g].vertices = new Structs.Vertex[wmo.group[g].mogp.vertices.Count()]; for (var i = 0; i < wmo.group[g].mogp.vertices.Count(); i++) { groups[g].vertices[i].Position = new Vector3(wmo.group[g].mogp.vertices[i].vector.X * -1, wmo.group[g].mogp.vertices[i].vector.Z, wmo.group[g].mogp.vertices[i].vector.Y); groups[g].vertices[i].Normal = new Vector3(wmo.group[g].mogp.normals[i].normal.X, wmo.group[g].mogp.normals[i].normal.Z, wmo.group[g].mogp.normals[i].normal.Y); groups[g].vertices[i].TexCoord = new Vector2(wmo.group[g].mogp.textureCoords[0][i].X, wmo.group[g].mogp.textureCoords[0][i].Y); totalVertices++; } var indicelist = new List <uint>(); for (var i = 0; i < wmo.group[g].mogp.indices.Count(); i++) { indicelist.Add(wmo.group[g].mogp.indices[i].indice); } groups[g].indices = indicelist.ToArray(); } if (destinationOverride == null) { // Create output directory if (!Directory.Exists(Path.Combine(outdir, Path.GetDirectoryName(file)))) { Directory.CreateDirectory(Path.Combine(outdir, Path.GetDirectoryName(file))); } } StreamWriter doodadSW; if (destinationOverride == null) { doodadSW = new StreamWriter(Path.Combine(outdir, Path.GetDirectoryName(file), Path.GetFileNameWithoutExtension(file).Replace(" ", "") + "_ModelPlacementInformation.csv")); } else { doodadSW = new StreamWriter(Path.Combine(outdir, destinationOverride, Path.GetFileNameWithoutExtension(file).Replace(" ", "") + "_ModelPlacementInformation.csv")); } exportworker.ReportProgress(55, "Exporting doodads.."); doodadSW.WriteLine("ModelFile;PositionX;PositionY;PositionZ;RotationW;RotationX;RotationY;RotationZ;ScaleFactor;DoodadSet"); for (var i = 0; i < wmo.doodadSets.Count(); i++) { var doodadSet = wmo.doodadSets[i]; var currentDoodadSetName = doodadSet.setName.Replace("Set_", "").Replace("SET_", "").Replace("$DefaultGlobal", "Default"); if (doodadSetExportID != ushort.MaxValue) { //if (i != 0 && i != doodadSetExportID) // Is 0 always exported? Double check? if (i != doodadSetExportID) { Console.WriteLine("Skipping doodadset with ID " + i + " (" + currentDoodadSetName + ") because export filter is set to " + doodadSetExportID); continue; } } Console.WriteLine("At doodadset " + i + " (" + currentDoodadSetName + ")"); for (var j = doodadSet.firstInstanceIndex; j < (doodadSet.firstInstanceIndex + doodadSet.numDoodads); j++) { var doodadDefinition = wmo.doodadDefinitions[j]; if (wmo.doodadIds != null) { var doodadFileDataID = wmo.doodadIds[doodadDefinition.offset]; if (destinationOverride == null) { if (!File.Exists(Path.Combine(outdir, Path.GetDirectoryName(file), doodadFileDataID + ".obj"))) { M2Exporter.ExportM2(doodadFileDataID, null, Path.Combine(outdir, Path.GetDirectoryName(file))); } if (File.Exists(Path.Combine(outdir, Path.GetDirectoryName(file), doodadFileDataID + ".obj"))) { doodadSW.WriteLine(doodadFileDataID + ".obj;" + doodadDefinition.position.X.ToString("F09") + ";" + doodadDefinition.position.Y.ToString("F09") + ";" + doodadDefinition.position.Z.ToString("F09") + ";" + doodadDefinition.rotation.W.ToString("F15") + ";" + doodadDefinition.rotation.X.ToString("F15") + ";" + doodadDefinition.rotation.Y.ToString("F15") + ";" + doodadDefinition.rotation.Z.ToString("F15") + ";" + doodadDefinition.scale + ";" + currentDoodadSetName); } } else { if (!File.Exists(Path.Combine(destinationOverride, doodadFileDataID + ".obj"))) { M2Exporter.ExportM2(doodadFileDataID, null, destinationOverride); } if (File.Exists(Path.Combine(destinationOverride, doodadFileDataID + ".obj"))) { doodadSW.WriteLine(doodadFileDataID + ".obj;" + doodadDefinition.position.X.ToString("F09") + ";" + doodadDefinition.position.Y.ToString("F09") + ";" + doodadDefinition.position.Z.ToString("F09") + ";" + doodadDefinition.rotation.W.ToString("F15") + ";" + doodadDefinition.rotation.X.ToString("F15") + ";" + doodadDefinition.rotation.Y.ToString("F15") + ";" + doodadDefinition.rotation.Z.ToString("F15") + ";" + doodadDefinition.scale + ";" + currentDoodadSetName); } } } else { foreach (var doodadNameEntry in wmo.doodadNames) { if (doodadNameEntry.startOffset == doodadDefinition.offset) { var doodadFileName = doodadNameEntry.filename.Replace(".MDX", ".M2").Replace(".MDL", ".M2"); if (destinationOverride == null) { if (!File.Exists(Path.Combine(outdir, Path.GetDirectoryName(file), Path.GetFileName(doodadFileName.ToLower()).Replace(".m2", ".obj")))) { M2Exporter.ExportM2(doodadFileName, null, Path.Combine(outdir, Path.GetDirectoryName(file))); } if (File.Exists(Path.Combine(outdir, Path.GetDirectoryName(file), Path.GetFileName(doodadFileName.ToLower()).Replace(".m2", ".obj")))) { doodadSW.WriteLine(Path.GetFileNameWithoutExtension(doodadNameEntry.filename).ToLower() + ".obj;" + doodadDefinition.position.X.ToString("F09") + ";" + doodadDefinition.position.Y.ToString("F09") + ";" + doodadDefinition.position.Z.ToString("F09") + ";" + doodadDefinition.rotation.W.ToString("F15") + ";" + doodadDefinition.rotation.X.ToString("F15") + ";" + doodadDefinition.rotation.Y.ToString("F15") + ";" + doodadDefinition.rotation.Z.ToString("F15") + ";" + doodadDefinition.scale + ";" + currentDoodadSetName); } } else { if (!File.Exists(Path.Combine(destinationOverride, Path.GetFileName(doodadFileName.ToLower()).Replace(".m2", ".obj")))) { M2Exporter.ExportM2(doodadNameEntry.filename.Replace(".MDX", ".M2").Replace(".MDL", ".M2"), null, destinationOverride); } if (File.Exists(Path.Combine(destinationOverride, Path.GetFileName(doodadFileName.ToLower()).Replace(".m2", ".obj")))) { doodadSW.WriteLine(Path.GetFileNameWithoutExtension(doodadNameEntry.filename).ToLower() + ".obj;" + doodadDefinition.position.X.ToString("F09") + ";" + doodadDefinition.position.Y.ToString("F09") + ";" + doodadDefinition.position.Z.ToString("F09") + ";" + doodadDefinition.rotation.W.ToString("F15") + ";" + doodadDefinition.rotation.X.ToString("F15") + ";" + doodadDefinition.rotation.Y.ToString("F15") + ";" + doodadDefinition.rotation.Z.ToString("F15") + ";" + doodadDefinition.scale + ";" + currentDoodadSetName); } } break; } } } } } doodadSW.Close(); exportworker.ReportProgress(65, "Exporting textures.."); var mtlsb = new StringBuilder(); var textureID = 0; if (wmo.materials == null) { Console.WriteLine("Materials empty"); return; } var materials = new Structs.Material[wmo.materials.Count()]; for (var i = 0; i < wmo.materials.Count(); i++) { if (wmo.textures == null) { materials[i].textureID = textureID + i; materials[i].filename = wmo.materials[i].texture1.ToString(); if (wmo.materials[i].blendMode == 0) { materials[i].transparent = false; } else { materials[i].transparent = true; } if (!File.Exists(Path.Combine(outdir, Path.GetDirectoryName(file), materials[i].filename + ".png"))) { var blpreader = new BLPReader(); blpreader.LoadBLP(wmo.materials[i].texture1); try { if (destinationOverride == null) { blpreader.bmp.Save(Path.Combine(outdir, Path.GetDirectoryName(file), materials[i].filename + ".png")); } else { blpreader.bmp.Save(Path.Combine(outdir, destinationOverride, materials[i].filename.ToLower() + ".png")); } } catch (Exception e) { Console.WriteLine(e.Message); } } textureID++; } else { for (var ti = 0; ti < wmo.textures.Count(); ti++) { if (wmo.textures[ti].startOffset == wmo.materials[i].texture1) { materials[i].textureID = textureID + i; materials[i].filename = Path.GetFileNameWithoutExtension(wmo.textures[ti].filename); if (wmo.materials[i].blendMode == 0) { materials[i].transparent = false; } else { materials[i].transparent = true; } if (!File.Exists(Path.Combine(outdir, Path.GetDirectoryName(file), materials[i].filename + ".png"))) { var blpreader = new BLPReader(); blpreader.LoadBLP(wmo.textures[ti].filename); try { if (destinationOverride == null) { blpreader.bmp.Save(Path.Combine(outdir, Path.GetDirectoryName(file), materials[i].filename + ".png")); } else { blpreader.bmp.Save(Path.Combine(outdir, destinationOverride, materials[i].filename.ToLower() + ".png")); } } catch (Exception e) { Console.WriteLine(e.Message); } } textureID++; } } } } //No idea how MTL files really work yet. Needs more investigation. foreach (var material in materials) { mtlsb.Append("newmtl " + material.filename + "\n"); mtlsb.Append("Ns 96.078431\n"); mtlsb.Append("Ka 1.000000 1.000000 1.000000\n"); mtlsb.Append("Kd 0.640000 0.640000 0.640000\n"); mtlsb.Append("Ks 0.000000 0.000000 0.000000\n"); mtlsb.Append("Ke 0.000000 0.000000 0.000000\n"); mtlsb.Append("Ni 1.000000\n"); mtlsb.Append("d 1.000000\n"); mtlsb.Append("illum 2\n"); mtlsb.Append("map_Kd " + material.filename + ".png\n"); if (material.transparent) { mtlsb.Append("map_d " + material.filename + ".png\n"); } } if (destinationOverride == null) { File.WriteAllText(Path.Combine(outdir, file.Replace(".wmo", ".mtl")), mtlsb.ToString()); } else { File.WriteAllText(Path.Combine(outdir, destinationOverride, Path.GetFileName(file.ToLower()).Replace(".wmo", ".mtl")), mtlsb.ToString()); } exportworker.ReportProgress(75, "Exporting model.."); var numRenderbatches = 0; //Get total amount of render batches for (var i = 0; i < wmo.group.Count(); i++) { if (wmo.group[i].mogp.renderBatches == null) { continue; } numRenderbatches = numRenderbatches + wmo.group[i].mogp.renderBatches.Count(); } var rb = 0; for (var g = 0; g < wmo.group.Count(); g++) { groups[g].renderBatches = new Structs.RenderBatch[numRenderbatches]; var group = wmo.group[g]; if (group.mogp.renderBatches == null) { continue; } for (var i = 0; i < group.mogp.renderBatches.Count(); i++) { var batch = group.mogp.renderBatches[i]; groups[g].renderBatches[rb].firstFace = batch.firstFace; groups[g].renderBatches[rb].numFaces = batch.numFaces; if (batch.flags == 2) { groups[g].renderBatches[rb].materialID = (uint)batch.possibleBox2_3; } else { groups[g].renderBatches[rb].materialID = batch.materialID; } groups[g].renderBatches[rb].blendType = wmo.materials[batch.materialID].blendMode; groups[g].renderBatches[rb].groupID = (uint)g; rb++; } } exportworker.ReportProgress(95, "Writing files.."); StreamWriter objsw; if (destinationOverride == null) { objsw = new StreamWriter(Path.Combine(outdir, file.Replace(".wmo", ".obj"))); } else { objsw = new StreamWriter(Path.Combine(outdir, destinationOverride, Path.GetFileName(file.ToLower()).Replace(".wmo", ".obj"))); } objsw.WriteLine("# Written by Marlamin's WoW OBJExporter. Original file: " + file); objsw.WriteLine("mtllib " + Path.GetFileNameWithoutExtension(file) + ".mtl"); foreach (var group in groups) { if (group.vertices == null) { continue; } Console.WriteLine("Writing " + group.name); objsw.WriteLine("g " + group.name); foreach (var vertex in group.vertices) { objsw.WriteLine("v " + vertex.Position.X + " " + vertex.Position.Y + " " + vertex.Position.Z); objsw.WriteLine("vt " + vertex.TexCoord.X + " " + -vertex.TexCoord.Y); objsw.WriteLine("vn " + vertex.Normal.X.ToString("F12") + " " + vertex.Normal.Y.ToString("F12") + " " + vertex.Normal.Z.ToString("F12")); } var indices = group.indices; foreach (var renderbatch in group.renderBatches) { var i = renderbatch.firstFace; if (renderbatch.numFaces > 0) { objsw.WriteLine("usemtl " + materials[renderbatch.materialID].filename); objsw.WriteLine("s 1"); while (i < (renderbatch.firstFace + renderbatch.numFaces)) { objsw.WriteLine("f " + (indices[i] + group.verticeOffset + 1) + "/" + (indices[i] + group.verticeOffset + 1) + "/" + (indices[i] + group.verticeOffset + 1) + " " + (indices[i + 1] + group.verticeOffset + 1) + "/" + (indices[i + 1] + group.verticeOffset + 1) + "/" + (indices[i + 1] + group.verticeOffset + 1) + " " + (indices[i + 2] + group.verticeOffset + 1) + "/" + (indices[i + 2] + group.verticeOffset + 1) + "/" + (indices[i + 2] + group.verticeOffset + 1)); i = i + 3; } } } } objsw.Close(); Console.WriteLine("Done loading WMO file!"); }
public static void ExportWMO(uint fileDataID, BackgroundWorker exportworker = null, string destinationOverride = null, short doodadSetExportID = short.MaxValue, string fileName = "", bool[] enabledGroups = null, bool[] enabledSets = null) { if (exportworker == null) { exportworker = new BackgroundWorker(); exportworker.WorkerReportsProgress = true; } if (string.IsNullOrEmpty(fileName)) { if (!Listfile.TryGetFilename(fileDataID, out fileName)) { CASCLib.Logger.WriteLine("Warning! Could not find filename for " + fileDataID + "!"); } } fileName = fileName.ToLower(); Console.WriteLine("Loading WMO file.."); exportworker.ReportProgress(5, "Reading WMO.."); var outDir = ConfigurationManager.AppSettings["outdir"]; var wmo = new WMOReader().LoadWMO(fileDataID, 0, fileName); var customCulture = (System.Globalization.CultureInfo)System.Threading.Thread.CurrentThread.CurrentCulture.Clone(); customCulture.NumberFormat.NumberDecimalSeparator = "."; System.Threading.Thread.CurrentThread.CurrentCulture = customCulture; exportworker.ReportProgress(30, "Reading WMO.."); uint totalVertices = 0; var groups = new Structs.WMOGroup[wmo.group.Count()]; for (var g = 0; g < wmo.group.Count(); g++) { if (enabledGroups != null && !enabledGroups[g]) { Console.WriteLine("Skipping group " + g + " due to WMO control"); continue; } Console.WriteLine("Loading group #" + g); if (wmo.group[g].mogp.vertices == null) { Console.WriteLine("Group has no vertices!"); continue; } for (var i = 0; i < wmo.groupNames.Count(); i++) { if (wmo.group[g].mogp.nameOffset == wmo.groupNames[i].offset) { groups[g].name = wmo.groupNames[i].name.Replace(" ", "_"); } } if (groups[g].name == "antiportal") { Console.WriteLine("Group is antiportal"); continue; } groups[g].verticeOffset = totalVertices; groups[g].vertices = new Structs.Vertex[wmo.group[g].mogp.vertices.Count()]; for (var i = 0; i < wmo.group[g].mogp.vertices.Count(); i++) { groups[g].vertices[i].Position = new Structs.Vector3D() { X = wmo.group[g].mogp.vertices[i].vector.X * -1, Y = wmo.group[g].mogp.vertices[i].vector.Z, Z = wmo.group[g].mogp.vertices[i].vector.Y }; groups[g].vertices[i].Normal = new Structs.Vector3D() { X = wmo.group[g].mogp.normals[i].normal.X, Y = wmo.group[g].mogp.normals[i].normal.Z, Z = wmo.group[g].mogp.normals[i].normal.Y }; groups[g].vertices[i].TexCoord = new Structs.Vector2D() { X = wmo.group[g].mogp.textureCoords[0][i].X, Y = wmo.group[g].mogp.textureCoords[0][i].Y }; totalVertices++; } var indicelist = new List <uint>(); for (var i = 0; i < wmo.group[g].mogp.indices.Count(); i++) { indicelist.Add(wmo.group[g].mogp.indices[i].indice); } groups[g].indices = indicelist.ToArray(); } // Create output directory. if (destinationOverride == null) { Directory.CreateDirectory(fileName == null ? outDir : Path.Combine(outDir, Path.GetDirectoryName(fileName))); } exportworker.ReportProgress(55, "Exporting WMO doodads.."); List <string> doodadList = new List <string>(); doodadList.Add("ModelFile;PositionX;PositionY;PositionZ;RotationW;RotationX;RotationY;RotationZ;ScaleFactor;DoodadSet"); if (doodadSetExportID > -1) { for (var i = 0; i < wmo.doodadSets.Count(); i++) { if (enabledSets != null && !enabledSets[i]) { Console.WriteLine("Skipping doodadSet " + i + " due to WMO control"); continue; } var doodadSet = wmo.doodadSets[i]; var currentDoodadSetName = doodadSet.setName.Replace("Set_", "").Replace("SET_", "").Replace("$DefaultGlobal", "Default"); if (doodadSetExportID != short.MaxValue) { if (i != 0 && i != doodadSetExportID) { Console.WriteLine("Skipping doodadset with ID " + i + " (" + currentDoodadSetName + ") because export filter is set to " + doodadSetExportID); continue; } } for (var j = doodadSet.firstInstanceIndex; j < (doodadSet.firstInstanceIndex + doodadSet.numDoodads); j++) { var doodadDefinition = wmo.doodadDefinitions[j]; var doodadFileName = ""; uint doodadFileDataID = 0; var doodadNotFound = false; if (wmo.doodadIds != null) { doodadFileDataID = wmo.doodadIds[doodadDefinition.offset]; if (!Listfile.TryGetFilename(doodadFileDataID, out doodadFileName)) { CASCLib.Logger.WriteLine("Could not find filename for " + doodadFileDataID + ", setting filename to filedataid.."); doodadFileName = doodadFileDataID.ToString(); } } else { CASCLib.Logger.WriteLine("Warning!! File " + fileName + " ID: " + fileDataID + " still has filenames!"); foreach (var doodadNameEntry in wmo.doodadNames) { if (doodadNameEntry.startOffset == doodadDefinition.offset) { doodadFileName = doodadNameEntry.filename.Replace(".MDX", ".M2").Replace(".MDL", ".M2").ToLower(); if (!Listfile.TryGetFileDataID(doodadFileName, out doodadFileDataID)) { CASCLib.Logger.WriteLine("Error! Could not find filedataid for " + doodadFileName + "!"); doodadNotFound = true; continue; } } } } if (!doodadNotFound) { string objFileName = Path.GetFileNameWithoutExtension(doodadFileName ?? doodadFileDataID.ToString()) + ".obj"; string objPath = Path.Combine(destinationOverride ?? outDir, destinationOverride != null ? "" : Path.GetDirectoryName(fileName)); string objName = Path.Combine(objPath, objFileName); if (!File.Exists(objName)) { M2Exporter.ExportM2(doodadFileDataID, null, objPath, doodadFileName); } if (File.Exists(objName)) { doodadList.Add(objFileName + ";" + doodadDefinition.position.X.ToString("F09") + ";" + doodadDefinition.position.Y.ToString("F09") + ";" + doodadDefinition.position.Z.ToString("F09") + ";" + doodadDefinition.rotation.W.ToString("F15") + ";" + doodadDefinition.rotation.X.ToString("F15") + ";" + doodadDefinition.rotation.Y.ToString("F15") + ";" + doodadDefinition.rotation.Z.ToString("F15") + ";" + doodadDefinition.scale + ";" + currentDoodadSetName); } } } } } if (doodadList.Count > 1) { string mpiFile = (fileName == null ? fileDataID.ToString() : Path.GetFileNameWithoutExtension(fileName.Replace(" ", ""))) + "_ModelPlacementInformation.csv"; File.WriteAllText(Path.Combine(outDir, destinationOverride ?? Path.GetDirectoryName(fileName), mpiFile), string.Join("\n", doodadList.ToArray())); } exportworker.ReportProgress(65, "Exporting WMO textures.."); var mtlsb = new StringBuilder(); var textureID = 0; if (wmo.materials == null) { CASCLib.Logger.WriteLine("Unable to find materials for WMO " + fileDataID + ", not exporting!"); return; } var materials = new Structs.Material[wmo.materials.Count()]; var extraMaterials = new List <Structs.Material>(); for (var i = 0; i < wmo.materials.Count(); i++) { var blpReader = new BLPReader(); if (wmo.textures == null) { if (Listfile.TryGetFilename(wmo.materials[i].texture1, out var textureFilename)) { materials[i].filename = Path.GetFileNameWithoutExtension(textureFilename).Replace(" ", "").ToLower(); } else { materials[i].filename = wmo.materials[i].texture1.ToString(); } blpReader.LoadBLP(wmo.materials[i].texture1); } else { for (var ti = 0; ti < wmo.textures.Count(); ti++) { if (wmo.textures[ti].startOffset == wmo.materials[i].texture1) { materials[i].filename = Path.GetFileNameWithoutExtension(wmo.textures[ti].filename).Replace(" ", "").ToLower(); blpReader.LoadBLP(wmo.textures[ti].filename); } } } materials[i].textureID = textureID + i; materials[i].transparent = wmo.materials[i].blendMode != 0; materials[i].blendMode = wmo.materials[i].blendMode; materials[i].shaderID = wmo.materials[i].shader; materials[i].terrainType = wmo.materials[i].groundType; string saveLocation = Path.Combine(outDir, destinationOverride ?? Path.GetDirectoryName(fileName), materials[i].filename + ".png"); if (!File.Exists(saveLocation)) { try { if (materials[i].transparent) { blpReader.bmp.Save(saveLocation); } else { blpReader.bmp.Clone(new Rectangle(0, 0, blpReader.bmp.Width, blpReader.bmp.Height), PixelFormat.Format32bppRgb).Save(saveLocation); } } catch (Exception e) { CASCLib.Logger.WriteLine("Exception while saving BLP " + materials[i].filename + ": " + e.Message); } } textureID++; string extraPath = Path.Combine(outDir, destinationOverride ?? Path.GetDirectoryName(fileName)); ExportExtraMaterials(wmo.materials[i].texture2, wmo, extraMaterials, i, extraPath); ExportExtraMaterials(wmo.materials[i].texture3, wmo, extraMaterials, i, extraPath); } var numRenderbatches = 0; //Get total amount of render batches for (var i = 0; i < wmo.group.Count(); i++) { if (wmo.group[i].mogp.renderBatches == null) { continue; } numRenderbatches = numRenderbatches + wmo.group[i].mogp.renderBatches.Count(); } exportworker.ReportProgress(75, "Exporting WMO model.."); bool exportMetadata = ConfigurationManager.AppSettings["textureMetadata"] == "True"; //No idea how MTL files really work yet. Needs more investigation. foreach (var material in materials) { mtlsb.Append("newmtl " + material.filename + "\n"); mtlsb.Append("Ns 96.078431\n"); mtlsb.Append("Ka 1.000000 1.000000 1.000000\n"); mtlsb.Append("Kd 0.640000 0.640000 0.640000\n"); mtlsb.Append("Ks 0.000000 0.000000 0.000000\n"); mtlsb.Append("Ke 0.000000 0.000000 0.000000\n"); mtlsb.Append("Ni 1.000000\n"); mtlsb.Append("d 1.000000\n"); mtlsb.Append("illum 1\n"); mtlsb.Append("map_Kd " + material.filename + ".png\n"); if (material.transparent) { mtlsb.Append("map_d " + material.filename + ".png\n"); } if (exportMetadata) { for (var g = 0; g < wmo.group.Count(); g++) { groups[g].renderBatches = new Structs.RenderBatch[numRenderbatches]; var group = wmo.group[g]; if (group.mogp.renderBatches == null) { continue; } for (var i = 0; i < group.mogp.renderBatches.Count(); i++) { var batch = group.mogp.renderBatches[i]; if (materials[batch.materialID].filename == material.filename) { mtlsb.Append("blend " + material.blendMode + "\n"); } } } } } foreach (var material in extraMaterials) { mtlsb.Append("newmtl " + material.filename + "\n"); mtlsb.Append("Ns 96.078431\n"); mtlsb.Append("Ka 1.000000 1.000000 1.000000\n"); mtlsb.Append("Kd 0.640000 0.640000 0.640000\n"); mtlsb.Append("Ks 0.000000 0.000000 0.000000\n"); mtlsb.Append("Ke 0.000000 0.000000 0.000000\n"); mtlsb.Append("Ni 1.000000\n"); mtlsb.Append("d 1.000000\n"); mtlsb.Append("illum 1\n"); mtlsb.Append("map_Kd " + material.filename + ".png\n"); if (material.transparent) { mtlsb.Append("map_d " + material.filename + ".png\n"); } } string mtlFile = fileName != null?fileName.Replace(".wmo", ".mtl") : fileDataID + ".mtl"; if (destinationOverride != null) { mtlFile = Path.GetFileName(mtlFile); } File.WriteAllText(Path.Combine(destinationOverride ?? outDir, mtlFile), mtlsb.ToString()); var rb = 0; for (var g = 0; g < wmo.group.Count(); g++) { groups[g].renderBatches = new Structs.RenderBatch[numRenderbatches]; var group = wmo.group[g]; if (group.mogp.renderBatches == null) { continue; } for (var i = 0; i < group.mogp.renderBatches.Count(); i++) { var batch = group.mogp.renderBatches[i]; groups[g].renderBatches[rb].firstFace = batch.firstFace; groups[g].renderBatches[rb].numFaces = batch.numFaces; groups[g].renderBatches[rb].materialID = batch.flags == 2 ? (uint)batch.possibleBox2_3 : batch.materialID; groups[g].renderBatches[rb].blendType = wmo.materials[batch.materialID].blendMode; groups[g].renderBatches[rb].groupID = (uint)g; rb++; } } exportworker.ReportProgress(95, "Writing WMO files.."); string objFile = fileName != null?fileName.Replace(".wmo", ".obj") : fileDataID + ".obj"; if (destinationOverride != null) { objFile = Path.GetFileName(objFile); } string fileID = fileName ?? fileDataID.ToString(); StreamWriter objWriter = new StreamWriter(Path.Combine(destinationOverride ?? outDir, objFile)); objWriter.WriteLine("# Written by Marlamin's WoW Export Tools. Original file: " + fileID); objWriter.WriteLine("mtllib " + Path.GetFileNameWithoutExtension(fileID) + ".mtl"); objWriter.WriteLine("o " + Path.GetFileName(fileID)); foreach (var group in groups) { if (group.vertices == null) { continue; } Console.WriteLine("Writing " + group.name); //Adjusted according to the WaveFront Object (.obj) File Format spec //https://people.cs.clemson.edu/~dhouse/courses/405/docs/brief-obj-file-format.html foreach (var vertex in group.vertices) { objWriter.WriteLine("v " + vertex.Position.X + " " + vertex.Position.Y + " " + vertex.Position.Z); } foreach (var vertex in group.vertices) { objWriter.WriteLine("vt " + vertex.TexCoord.X + " " + (vertex.TexCoord.Y - 1) * -1 + " 0.0000"); } foreach (var vertex in group.vertices) { objWriter.WriteLine("vn " + (-vertex.Normal.X).ToString("F12") + " " + vertex.Normal.Y.ToString("F12") + " " + vertex.Normal.Z.ToString("F12")); } var indices = group.indices; for (int rbi = 0; rbi < group.renderBatches.Count(); rbi++) { var renderbatch = group.renderBatches[rbi]; var i = renderbatch.firstFace; if (renderbatch.numFaces > 0) { objWriter.WriteLine("o " + group.name + rbi); objWriter.WriteLine("g " + group.name + rbi); //3DS Max's OBJ importer fails with invalid normal index without groups being defined objWriter.WriteLine("usemtl " + materials[renderbatch.materialID].filename); objWriter.WriteLine("s 1"); while (i < (renderbatch.firstFace + renderbatch.numFaces)) { objWriter.WriteLine("f " + (indices[i] + group.verticeOffset + 1) + "/" + (indices[i] + group.verticeOffset + 1) + "/" + (indices[i] + group.verticeOffset + 1) + " " + (indices[i + 1] + group.verticeOffset + 1) + "/" + (indices[i + 1] + group.verticeOffset + 1) + "/" + (indices[i + 1] + group.verticeOffset + 1) + " " + (indices[i + 2] + group.verticeOffset + 1) + "/" + (indices[i + 2] + group.verticeOffset + 1) + "/" + (indices[i + 2] + group.verticeOffset + 1)); i = i + 3; } } } } objWriter.Close(); }
public static void ExportWMO(string filename, string outdir, BackgroundWorker exportworker = null, string destinationOverride = null, ushort doodadSetExportID = ushort.MaxValue) { filename = filename.ToLower(); if (exportworker == null) { exportworker = new BackgroundWorker(); exportworker.WorkerReportsProgress = true; } exportworker.ReportProgress(5, "Reading WMO.."); var wmo = new WMOReader(); wmo.LoadWMO(filename); var customCulture = (System.Globalization.CultureInfo)System.Threading.Thread.CurrentThread.CurrentCulture.Clone(); customCulture.NumberFormat.NumberDecimalSeparator = "."; System.Threading.Thread.CurrentThread.CurrentCulture = customCulture; exportworker.ReportProgress(30, "Reading WMO.."); uint totalVertices = 0; var groups = new Structs.WMOGroup[wmo.wmofile.group.Count()]; for (var g = 0; g < wmo.wmofile.group.Count(); g++) { if (wmo.wmofile.group[g].mogp.vertices == null) { continue; } for (var i = 0; i < wmo.wmofile.groupNames.Count(); i++) { if (wmo.wmofile.group[g].mogp.nameOffset == wmo.wmofile.groupNames[i].offset) { groups[g].name = wmo.wmofile.groupNames[i].name.Replace(" ", "_"); } } if (groups[g].name == "antiportal") { //Console.WriteLine("Group is antiportal"); continue; } groups[g].verticeOffset = totalVertices; groups[g].vertices = new Structs.Vertex[wmo.wmofile.group[g].mogp.vertices.Count()]; for (var i = 0; i < wmo.wmofile.group[g].mogp.vertices.Count(); i++) { groups[g].vertices[i].Position = new Structs.Vector3D() { X = wmo.wmofile.group[g].mogp.vertices[i].vector.X * -1, Y = wmo.wmofile.group[g].mogp.vertices[i].vector.Z, Z = wmo.wmofile.group[g].mogp.vertices[i].vector.Y }; groups[g].vertices[i].Normal = new Structs.Vector3D() { X = wmo.wmofile.group[g].mogp.normals[i].normal.X, Y = wmo.wmofile.group[g].mogp.normals[i].normal.Z, Z = wmo.wmofile.group[g].mogp.normals[i].normal.Y }; groups[g].vertices[i].TexCoord = new Structs.Vector2D() { X = wmo.wmofile.group[g].mogp.textureCoords[0][i].X, Y = wmo.wmofile.group[g].mogp.textureCoords[0][i].Y }; totalVertices++; } var indicelist = new List <uint>(); for (var i = 0; i < wmo.wmofile.group[g].mogp.indices.Count(); i++) { indicelist.Add(wmo.wmofile.group[g].mogp.indices[i].indice); } groups[g].indices = indicelist.ToArray(); } if (destinationOverride == null) { // Create output directory if (!string.IsNullOrEmpty(filename)) { if (!Directory.Exists(Path.Combine(outdir, Path.GetDirectoryName(filename)))) { Directory.CreateDirectory(Path.Combine(outdir, Path.GetDirectoryName(filename))); } } else { if (!Directory.Exists(outdir)) { Directory.CreateDirectory(outdir); } } } #region M2Export bool exportM2 = Managers.ConfigurationManager.WMOExportM2; if (exportM2) { StreamWriter doodadSW; if (destinationOverride == null) { if (!string.IsNullOrEmpty(filename)) { doodadSW = new StreamWriter(Path.Combine(outdir, Path.GetDirectoryName(filename), Path.GetFileNameWithoutExtension(filename.Replace(" ", "")) + "_ModelPlacementInformation.csv")); } else { doodadSW = new StreamWriter(Path.Combine(outdir, Path.GetDirectoryName(filename), filename + "_ModelPlacementInformation.csv")); } } else { if (!string.IsNullOrEmpty(filename)) { doodadSW = new StreamWriter(Path.Combine(outdir, destinationOverride, Path.GetFileNameWithoutExtension(filename).Replace(" ", "") + "_ModelPlacementInformation.csv")); } else { doodadSW = new StreamWriter(Path.Combine(outdir, destinationOverride, filename + "_ModelPlacementInformation.csv")); } } exportworker.ReportProgress(55, "Exporting doodads.."); doodadSW.WriteLine("ModelFile;PositionX;PositionY;PositionZ;RotationW;RotationX;RotationY;RotationZ;ScaleFactor;DoodadSet"); for (var i = 0; i < wmo.wmofile.doodadSets.Count(); i++) { var doodadSet = wmo.wmofile.doodadSets[i]; var currentDoodadSetName = doodadSet.setName.Replace("Set_", "").Replace("SET_", "").Replace("$DefaultGlobal", "Default"); if (doodadSetExportID != ushort.MaxValue) { if (i != 0 && i != doodadSetExportID) { //Console.WriteLine("Skipping doodadset with ID " + i + " (" + currentDoodadSetName + ") because export filter is set to " + doodadSetExportID); continue; } } //Console.WriteLine("At doodadset " + i + " (" + currentDoodadSetName + ")"); for (var j = doodadSet.firstInstanceIndex; j < (doodadSet.firstInstanceIndex + doodadSet.numDoodads); j++) { foreach (var doodadNameEntry in wmo.wmofile.doodadNames) { var doodadDefinition = wmo.wmofile.doodadDefinitions[j]; if (doodadNameEntry.startOffset == doodadDefinition.offset) { var doodadFileName = doodadNameEntry.filename.Replace(".MDX", ".M2").Replace(".MDL", ".M2"); if (destinationOverride == null) { if (Managers.ConfigurationManager.WMODoodadsGlobalPath) { if (!File.Exists(Path.Combine(outdir, Path.GetFileName(doodadFileName.ToLower()).Replace(".m2", ".obj")))) { M2Exporter.ExportM2(doodadFileName, outdir, exportworker); } } else { if (!File.Exists(Path.Combine(outdir, Path.GetDirectoryName(filename), Path.GetFileName(doodadFileName.ToLower()).Replace(".m2", ".obj")))) { M2Exporter.ExportM2(doodadFileName, Path.Combine(outdir, Path.GetDirectoryName(filename)), exportworker); } } } else { if (!File.Exists(Path.Combine(destinationOverride, Path.GetFileName(doodadFileName.ToLower()).Replace(".m2", ".obj")))) { M2Exporter.ExportM2(doodadNameEntry.filename.Replace(".MDX", ".M2").Replace(".MDL", ".M2"), destinationOverride, exportworker); } } if (Managers.ConfigurationManager.WMODoodadsPlacementGlobalPath) { doodadSW.WriteLine(doodadNameEntry.filename.ToLower().Replace(".mdx", ".m2").Replace(".mdl", ".m2").Replace(".m2", ".obj;") + doodadDefinition.position.X.ToString("F09") + ";" + doodadDefinition.position.Y.ToString("F09") + ";" + doodadDefinition.position.Z.ToString("F09") + ";" + doodadDefinition.rotation.W.ToString("F15") + ";" + doodadDefinition.rotation.X.ToString("F15") + ";" + doodadDefinition.rotation.Y.ToString("F15") + ";" + doodadDefinition.rotation.Z.ToString("F15") + ";" + doodadDefinition.scale + ";" + currentDoodadSetName); } else { doodadSW.WriteLine(Path.GetFileNameWithoutExtension(doodadNameEntry.filename).ToLower() + ".obj;" + doodadDefinition.position.X.ToString("F09") + ";" + doodadDefinition.position.Y.ToString("F09") + ";" + doodadDefinition.position.Z.ToString("F09") + ";" + doodadDefinition.rotation.W.ToString("F15") + ";" + doodadDefinition.rotation.X.ToString("F15") + ";" + doodadDefinition.rotation.Y.ToString("F15") + ";" + doodadDefinition.rotation.Z.ToString("F15") + ";" + doodadDefinition.scale + ";" + currentDoodadSetName); } break; } } } } doodadSW.Close(); } #endregion var mtlsb = new StringBuilder(); var textureID = 0; if (wmo.wmofile.materials == null) { return; } var materials = new Structs.Material[wmo.wmofile.materials.Count()]; for (var i = 0; i < wmo.wmofile.materials.Count(); i++) { for (var ti = 0; ti < wmo.wmofile.textures.Count(); ti++) { if (wmo.wmofile.textures[ti].startOffset == wmo.wmofile.materials[i].texture1) { materials[i].textureID = textureID + i; materials[i].filename = Path.GetFileNameWithoutExtension(wmo.wmofile.textures[ti].filename); if (wmo.wmofile.materials[i].blendMode == 0) { materials[i].transparent = false; } else { materials[i].transparent = true; } if (!File.Exists(Path.Combine(outdir, Path.GetDirectoryName(filename), materials[i].filename + ".png"))) { var blpreader = new BLPReader(); blpreader.LoadBLP(Managers.ArchiveManager.ReadThisFile(wmo.wmofile.textures[ti].filename)); try { if (destinationOverride == null) { blpreader.bmp.Save(Path.Combine(outdir, Path.GetDirectoryName(filename), materials[i].filename + ".png")); } else { blpreader.bmp.Save(Path.Combine(outdir, destinationOverride, materials[i].filename.ToLower() + ".png")); } } catch { //Error on file save } } textureID++; } } } //No idea how MTL files really work yet. Needs more investigation. foreach (var material in materials) { mtlsb.Append("newmtl " + material.filename + "\n"); mtlsb.Append("Ns 96.078431\n"); mtlsb.Append("Ka 1.000000 1.000000 1.000000\n"); mtlsb.Append("Kd 0.640000 0.640000 0.640000\n"); mtlsb.Append("Ks 0.000000 0.000000 0.000000\n"); mtlsb.Append("Ke 0.000000 0.000000 0.000000\n"); mtlsb.Append("Ni 1.000000\n"); mtlsb.Append("d 1.000000\n"); mtlsb.Append("illum 1\n"); mtlsb.Append("map_Kd " + material.filename + ".png\n"); if (material.transparent) { mtlsb.Append("map_d " + material.filename + ".png\n"); } /* //temporary removed * if (ConfigurationManager.AppSettings["textureMetadata"] == "True") * { * mtlsb.Append("blend " + material.blendMode + "\n"); * mtlsb.Append("shader " + material.shaderID + "\n"); * mtlsb.Append("terrain " + material.terrainType + "\n"); * } */ } if (!string.IsNullOrEmpty(filename)) { if (destinationOverride == null) { File.WriteAllText(Path.Combine(outdir, filename.Replace(".wmo", ".mtl")), mtlsb.ToString()); } else { File.WriteAllText(Path.Combine(outdir, destinationOverride, Path.GetFileName(filename.ToLower()).Replace(".wmo", ".mtl")), mtlsb.ToString()); } } else { if (destinationOverride == null) { File.WriteAllText(Path.Combine(outdir, filename + ".mtl"), mtlsb.ToString()); } else { File.WriteAllText(Path.Combine(outdir, destinationOverride, filename + ".mtl"), mtlsb.ToString()); } } exportworker.ReportProgress(75, "Exporting model.."); var numRenderbatches = 0; //Get total amount of render batches for (var i = 0; i < wmo.wmofile.group.Count(); i++) { if (wmo.wmofile.group[i].mogp.renderBatches == null) { continue; } numRenderbatches = numRenderbatches + wmo.wmofile.group[i].mogp.renderBatches.Count(); } var rb = 0; for (var g = 0; g < wmo.wmofile.group.Count(); g++) { groups[g].renderBatches = new Structs.RenderBatch[numRenderbatches]; var group = wmo.wmofile.group[g]; if (group.mogp.renderBatches == null) { continue; } for (var i = 0; i < group.mogp.renderBatches.Count(); i++) { var batch = group.mogp.renderBatches[i]; groups[g].renderBatches[rb].firstFace = batch.firstFace; groups[g].renderBatches[rb].numFaces = batch.numFaces; if (batch.flags == 2) { groups[g].renderBatches[rb].materialID = (uint)batch.possibleBox2_3; } else { groups[g].renderBatches[rb].materialID = batch.materialID; } groups[g].renderBatches[rb].blendType = wmo.wmofile.materials[batch.materialID].blendMode; groups[g].renderBatches[rb].groupID = (uint)g; rb++; } } exportworker.ReportProgress(95, "Writing files.."); StreamWriter objsw; if (!string.IsNullOrEmpty(filename)) { if (destinationOverride == null) { objsw = new StreamWriter(Path.Combine(outdir, filename.Replace(".wmo", ".obj"))); } else { objsw = new StreamWriter(Path.Combine(outdir, destinationOverride, Path.GetFileName(filename.ToLower()).Replace(".wmo", ".obj"))); } objsw.WriteLine("# Written by Marlamin's WoW Export Tools. Original file: " + filename); objsw.WriteLine("mtllib " + Path.GetFileNameWithoutExtension(filename) + ".mtl"); } else { if (destinationOverride == null) { objsw = new StreamWriter(Path.Combine(outdir, filename + ".obj")); } else { objsw = new StreamWriter(Path.Combine(outdir, destinationOverride, filename + ".obj")); } objsw.WriteLine("# Written by Marlamin's WoW Export Tools. Original file id: " + filename); objsw.WriteLine("mtllib " + filename + ".mtl"); } foreach (var group in groups) { if (group.vertices == null) { continue; } //Console.WriteLine("Writing " + group.name); objsw.WriteLine("o " + group.name); //Added thunderysteak's adjustment (original commit: ed067c7c6e8321c33ef0f3679d33c9c472dcefc3) foreach (var vertex in group.vertices) { objsw.WriteLine("v " + vertex.Position.X + " " + vertex.Position.Y + " " + vertex.Position.Z); } foreach (var vertex in group.vertices) { objsw.WriteLine("vt " + vertex.TexCoord.X + " " + (vertex.TexCoord.Y - 1) * -1); } foreach (var vertex in group.vertices) { objsw.WriteLine("vn " + (-vertex.Normal.X).ToString("F12") + " " + vertex.Normal.Y.ToString("F12") + " " + vertex.Normal.Z.ToString("F12")); } var indices = group.indices; foreach (var renderbatch in group.renderBatches) { var i = renderbatch.firstFace; if (renderbatch.numFaces > 0) { //thunderysteak's adjustment //objsw.WriteLine("o " + group.name); //? objsw.WriteLine("g " + group.name);//3DS Max's OBJ importer fails with invalid normal index without groups being defined //-------------------------- objsw.WriteLine("usemtl " + materials[renderbatch.materialID].filename); objsw.WriteLine("s 1"); while (i < (renderbatch.firstFace + renderbatch.numFaces)) { objsw.WriteLine("f " + (indices[i] + group.verticeOffset + 1) + "/" + (indices[i] + group.verticeOffset + 1) + "/" + (indices[i] + group.verticeOffset + 1) + " " + (indices[i + 1] + group.verticeOffset + 1) + "/" + (indices[i + 1] + group.verticeOffset + 1) + "/" + (indices[i + 1] + group.verticeOffset + 1) + " " + (indices[i + 2] + group.verticeOffset + 1) + "/" + (indices[i + 2] + group.verticeOffset + 1) + "/" + (indices[i + 2] + group.verticeOffset + 1)); i += 3; } } } } objsw.Close(); //Console.WriteLine("Done loading WMO file!"); }
public static void ExportWMO(string file, string destinationOverride = null, string outdir = "", uint filedataid = 0) { Console.WriteLine("WMO glTF Exporter: Loading file {0}...", file); Console.WriteLine(filedataid); var wmo = new WoWFormatLib.Structs.WMO.WMO(); if (filedataid != 0) { wmo = new WMOReader().LoadWMO(CASC.OpenFile(filedataid)); } else { throw new Exception("Unsupported WMO for exporting! Use FileDataID!"); //wmo = new WMOReader().LoadWMO(file); } file = file.Replace("\\", "/"); 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))); } } 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[wmo.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 (var g = 0; g < wmo.group.Count(); g++) { if (wmo.group[g].mogp.vertices == null) { Console.WriteLine("Group has no vertices!"); continue; } if (wmo.group[g].mogp.renderBatches == null) { Console.WriteLine("Group has no renderbatches!"); continue; } for (var i = 0; i < wmo.groupNames.Count(); i++) { if (wmo.group[g].mogp.nameOffset == wmo.groupNames[i].offset) { groups[g].name = wmo.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 (var i = 0; i < wmo.group[g].mogp.vertices.Count(); i++) { writer.Write(wmo.group[g].mogp.vertices[i].vector.X * -1); writer.Write(wmo.group[g].mogp.vertices[i].vector.Z); writer.Write(wmo.group[g].mogp.vertices[i].vector.Y); if (wmo.group[g].mogp.vertices[i].vector.X * -1 < minPosX) { minPosX = wmo.group[g].mogp.vertices[i].vector.X * -1; } if (wmo.group[g].mogp.vertices[i].vector.Z < minPosY) { minPosY = wmo.group[g].mogp.vertices[i].vector.Z; } if (wmo.group[g].mogp.vertices[i].vector.Y < minPosZ) { minPosZ = wmo.group[g].mogp.vertices[i].vector.Y; } if (wmo.group[g].mogp.vertices[i].vector.X * -1 > maxPosX) { maxPosX = wmo.group[g].mogp.vertices[i].vector.X * -1; } if (wmo.group[g].mogp.vertices[i].vector.Z > maxPosY) { maxPosY = wmo.group[g].mogp.vertices[i].vector.Z; } if (wmo.group[g].mogp.vertices[i].vector.Y > maxPosZ) { maxPosZ = wmo.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)wmo.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 (var i = 0; i < wmo.group[g].mogp.vertices.Count(); i++) { writer.Write(wmo.group[g].mogp.normals[i].normal.X); writer.Write(wmo.group[g].mogp.normals[i].normal.Z); writer.Write(wmo.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)wmo.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 (var i = 0; i < wmo.group[g].mogp.vertices.Count(); i++) { writer.Write(wmo.group[g].mogp.textureCoords[0][i].X); writer.Write(wmo.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)wmo.group[g].mogp.vertices.Count(), type = "VEC2" }); bufferViews.Add(texCoordBuffer); var indexBufferPos = bufferViews.Count(); for (var i = 0; i < wmo.group[g].mogp.renderBatches.Count(); i++) { var batch = wmo.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 (var i = 0; i < wmo.group[g].mogp.indices.Count(); i++) { writer.Write(wmo.group[g].mogp.indices[i].indice); } indiceBuffer.byteLength = (uint)writer.BaseStream.Position - indiceBuffer.byteOffset; bufferViews.Add(indiceBuffer); if ((indiceBuffer.byteOffset + indiceBuffer.byteLength) % 4 != 0) { writer.Write((short)0); } } 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(); if (wmo.materials == null) { Console.WriteLine("WMO glTF exporter: Materials empty"); return; } var materialCount = wmo.materials.Count(); glTF.images = new Image[materialCount]; glTF.textures = new Texture[materialCount]; glTF.materials = new Material[materialCount]; for (var i = 0; i < materialCount; i++) { // Check if texture is a filedataid if (wmo.textures == null && CASC.FileExists(wmo.materials[i].texture1)) { var saveLocation = ""; if (destinationOverride == null) { saveLocation = Path.Combine(outdir, wmo.materials[i].texture1.ToString() + ".blp"); } else { saveLocation = Path.Combine(outdir, destinationOverride, wmo.materials[i].texture1.ToString() + ".blp"); } if (!File.Exists(Path.GetFileNameWithoutExtension(saveLocation) + "png")) // Check if already exported & converted version exists { using (var cascFile = CASC.OpenFile(wmo.materials[i].texture1)) using (var cascStream = new MemoryStream()) { cascFile.CopyTo(cascStream); File.WriteAllBytes(saveLocation, cascStream.ToArray()); } } glTF.images[i].uri = wmo.materials[i].texture1.ToString() + ".png"; glTF.textures[i].sampler = 0; glTF.textures[i].source = i; glTF.materials[i].name = wmo.materials[i].texture1.ToString(); 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].doubleSided = true; switch (wmo.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; } } else { if (wmo.textures == null) { throw new Exception("WMO textures do not exist or are invalid filedataid!"); } for (var ti = 0; ti < wmo.textures.Count(); ti++) { if (wmo.textures[ti].startOffset == wmo.materials[i].texture1) { var saveLocation = ""; var textureFilename = Path.GetFileNameWithoutExtension(wmo.textures[ti].filename.Replace("\\", "/")).ToLower(); if (destinationOverride == null) { saveLocation = Path.Combine(outdir, textureFilename + ".blp"); } else { saveLocation = Path.Combine(outdir, destinationOverride, textureFilename + ".blp"); } if (!File.Exists(Path.ChangeExtension(saveLocation, ".blp")) && !File.Exists(Path.ChangeExtension(saveLocation, ".png"))) // Check if already exported & converted version exists { using (var cascFile = CASC.OpenFile(wmo.textures[ti].filename)) using (var cascStream = new MemoryStream()) { cascFile.CopyTo(cascStream); File.WriteAllBytes(saveLocation, cascStream.ToArray()); } } Console.WriteLine(textureFilename); 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; glTF.materials[i].doubleSided = true; switch (wmo.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; } } } } } 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; var currentDoodadSetName = ""; for (var i = 0; i < wmo.doodadDefinitions.Count(); i++) { var doodadDefinition = wmo.doodadDefinitions[i]; foreach (var doodadSet in wmo.doodadSets) { if (doodadSet.firstInstanceIndex == i) { Console.WriteLine("At set: " + doodadSet.setName); currentDoodadSetName = doodadSet.setName.Replace("Set_", "").Replace("SET_", "").Replace("$DefaultGlobal", "Default"); } } if (wmo.doodadIds != null) { var doodadFileDataID = wmo.doodadIds[doodadDefinition.offset]; if (!File.Exists(doodadFileDataID + ".gltf")) { if (destinationOverride == null) { //M2Exporter.ExportM2(doodadFileDataID, null, Path.Combine(outdir, Path.GetDirectoryName(file))); } else { //M2Exporter.ExportM2(doodadFileDataID, null, destinationOverride); } } } else { if (wmo.doodadNames != null) { foreach (var doodadNameEntry in wmo.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); } } } } } } } if (destinationOverride == null) { File.WriteAllText(Path.Combine(outdir, file.Replace(".wmo", ".gltf")), JsonConvert.SerializeObject(glTF, Formatting.None, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore })); } else { File.WriteAllText(Path.Combine(destinationOverride, Path.GetFileName(file.ToLower()).Replace(".wmo", ".gltf")), JsonConvert.SerializeObject(glTF, Formatting.None, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore })); } Console.WriteLine("Done exporting WMO file!"); }