public static int LoadTexture(string filename, CacheStorage cache) { if (Listfile.TryGetFileDataID(filename, out var filedataid)) { return(LoadTexture(filedataid, cache)); } else { throw new Exception("Couldn't find filedataid for file " + filename + " in listfile!"); } }
public Stream StreamForTableName(string tableName, string build) { if (Listfile.TryGetFileDataID("dbfilesclient/" + tableName + ".db2", out var fileDataID)) { return(CASC.OpenFile(fileDataID)); } else { throw new FileNotFoundException("DBC " + tableName + " not found in listfile, could not look up filedataid!"); } }
public static void ExportM2(string file, BackgroundWorker exportworker = null, string destinationOverride = null) { if (!Listfile.TryGetFileDataID(file, out var filedataid)) { CASCLib.Logger.WriteLine("Error! Could not find filedataid for " + file + ", skipping export!"); return; } else { ExportM2(filedataid, exportworker, destinationOverride, file); } }
public static void ExportWMO(string file, BackgroundWorker exportworker = null, string destinationOverride = null, short doodadSetExportID = short.MaxValue, bool[] enabledGroups = null, bool[] enabledSets = null) { if (!Listfile.TryGetFileDataID(file, out var filedataid)) { CASCLib.Logger.WriteLine("Error! Could not find filedataid for " + file + ", skipping export!"); return; } else { ExportWMO(filedataid, exportworker, destinationOverride, doodadSetExportID, file, enabledGroups, enabledSets); } }
public static void LoadM2(string filename, CacheStorage cache, int shaderProgram) { filename = filename.ToLower().Replace(".mdx", ".m2"); filename = filename.ToLower().Replace(".mdl", ".m2"); if (cache.doodadBatches.ContainsKey(filename)) { return; } var model = new WoWFormatLib.Structs.M2.M2Model(); if (cache.models.ContainsKey(filename)) { model = cache.models[filename]; } else { if (Listfile.TryGetFileDataID(filename, out var filedataid)) { if (WoWFormatLib.Utils.CASC.FileExists(filedataid)) { var modelreader = new M2Reader(); modelreader.LoadM2(filedataid); cache.models.Add(filename, modelreader.model); model = modelreader.model; } else { throw new Exception("Model " + filename + " does not exist!"); } } else { throw new Exception("Filename " + filename + " does not exist in listfile!"); } } if (model.boundingbox == null) { CASCLib.Logger.WriteLine("Error during loading file: {0}, bounding box is not defined", filename); return; } var ddBatch = new Renderer.Structs.DoodadBatch() { boundingBox = new Renderer.Structs.BoundingBox() { min = new Vector3(model.boundingbox[0].X, model.boundingbox[0].Y, model.boundingbox[0].Z), max = new Vector3(model.boundingbox[1].X, model.boundingbox[1].Y, model.boundingbox[1].Z) } }; if (model.textures == null) { CASCLib.Logger.WriteLine("Error during loading file: {0}, model has no textures", filename); return; } if (model.skins == null) { CASCLib.Logger.WriteLine("Error during loading file: {0}, model has no skins", filename); return; } // Textures ddBatch.mats = new Renderer.Structs.Material[model.textures.Count()]; for (var i = 0; i < model.textures.Count(); i++) { uint textureFileDataID = 528732; ddBatch.mats[i].flags = model.textures[i].flags; switch (model.textures[i].type) { case 0: if (model.textureFileDataIDs != null && model.textureFileDataIDs.Length > 0 && model.textureFileDataIDs[i] != 0) { textureFileDataID = model.textureFileDataIDs[i]; } else { textureFileDataID = WoWFormatLib.Utils.CASC.getFileDataIdByName(model.textures[i].filename); } break; case 1: case 2: case 11: default: textureFileDataID = 528732; break; } // Not set in TXID if (textureFileDataID == 0) { textureFileDataID = 528732; } ddBatch.mats[i].textureID = BLPLoader.LoadTexture(textureFileDataID, cache); ddBatch.mats[i].filename = textureFileDataID.ToString(); } // Submeshes ddBatch.submeshes = new Renderer.Structs.Submesh[model.skins[0].submeshes.Count()]; for (var i = 0; i < model.skins[0].submeshes.Count(); i++) { if (filename.StartsWith("character")) { if (model.skins[0].submeshes[i].submeshID != 0) { if (!model.skins[0].submeshes[i].submeshID.ToString().EndsWith("01")) { continue; } } } ddBatch.submeshes[i].firstFace = model.skins[0].submeshes[i].startTriangle; ddBatch.submeshes[i].numFaces = model.skins[0].submeshes[i].nTriangles; for (var tu = 0; tu < model.skins[0].textureunit.Count(); tu++) { if (model.skins[0].textureunit[tu].submeshIndex == i) { ddBatch.submeshes[i].blendType = model.renderflags[model.skins[0].textureunit[tu].renderFlags].blendingMode; uint textureFileDataID = 528732; if (model.textureFileDataIDs != null && model.textureFileDataIDs.Length > 0 && model.textureFileDataIDs[model.texlookup[model.skins[0].textureunit[tu].texture].textureID] != 0) { textureFileDataID = model.textureFileDataIDs[model.texlookup[model.skins[0].textureunit[tu].texture].textureID]; } else { if (Listfile.FilenameToFDID.TryGetValue(model.textures[model.texlookup[model.skins[0].textureunit[tu].texture].textureID].filename.Replace('\\', '/').ToLower(), out var filedataid)) { textureFileDataID = filedataid; } else { textureFileDataID = 528732; } } if (!cache.materials.ContainsKey(textureFileDataID)) { throw new Exception("MaterialCache does not have texture " + textureFileDataID); } ddBatch.submeshes[i].material = (uint)cache.materials[textureFileDataID]; } } } ddBatch.vao = GL.GenVertexArray(); GL.BindVertexArray(ddBatch.vao); // Vertices & indices ddBatch.vertexBuffer = GL.GenBuffer(); ddBatch.indiceBuffer = GL.GenBuffer(); GL.BindBuffer(BufferTarget.ArrayBuffer, ddBatch.vertexBuffer); GL.BindBuffer(BufferTarget.ElementArrayBuffer, ddBatch.indiceBuffer); var modelindicelist = new List <uint>(); for (var i = 0; i < model.skins[0].triangles.Count(); i++) { modelindicelist.Add(model.skins[0].triangles[i].pt1); modelindicelist.Add(model.skins[0].triangles[i].pt2); modelindicelist.Add(model.skins[0].triangles[i].pt3); } var modelindices = modelindicelist.ToArray(); ddBatch.indices = modelindices; GL.BindBuffer(BufferTarget.ElementArrayBuffer, ddBatch.indiceBuffer); GL.BufferData(BufferTarget.ElementArrayBuffer, (IntPtr)(ddBatch.indices.Length * sizeof(uint)), ddBatch.indices, BufferUsageHint.StaticDraw); var modelvertices = new Renderer.Structs.M2Vertex[model.vertices.Count()]; for (var i = 0; i < model.vertices.Count(); i++) { modelvertices[i].Position = new Vector3(model.vertices[i].position.X, model.vertices[i].position.Y, model.vertices[i].position.Z); modelvertices[i].Normal = new Vector3(model.vertices[i].normal.X, model.vertices[i].normal.Y, model.vertices[i].normal.Z); modelvertices[i].TexCoord = new Vector2(model.vertices[i].textureCoordX, model.vertices[i].textureCoordY); } GL.BindBuffer(BufferTarget.ArrayBuffer, ddBatch.vertexBuffer); GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(modelvertices.Length * 8 * sizeof(float)), modelvertices, BufferUsageHint.StaticDraw); //Set pointers in buffer //var normalAttrib = GL.GetAttribLocation(shaderProgram, "normal"); //GL.EnableVertexAttribArray(normalAttrib); //GL.VertexAttribPointer(normalAttrib, 3, VertexAttribPointerType.Float, false, sizeof(float) * 8, sizeof(float) * 0); var texCoordAttrib = GL.GetAttribLocation(shaderProgram, "texCoord"); GL.EnableVertexAttribArray(texCoordAttrib); GL.VertexAttribPointer(texCoordAttrib, 2, VertexAttribPointerType.Float, false, sizeof(float) * 8, sizeof(float) * 3); var posAttrib = GL.GetAttribLocation(shaderProgram, "position"); GL.EnableVertexAttribArray(posAttrib); GL.VertexAttribPointer(posAttrib, 3, VertexAttribPointerType.Float, false, sizeof(float) * 8, sizeof(float) * 5); cache.doodadBatches.Add(filename, ddBatch); }
public static void ExportM2(uint fileDataID, BackgroundWorker exportworker = null, string destinationOverride = null, string filename = "") { if (exportworker == null) { exportworker = new BackgroundWorker { WorkerReportsProgress = true }; } var customCulture = (System.Globalization.CultureInfo)System.Threading.Thread.CurrentThread.CurrentCulture.Clone(); customCulture.NumberFormat.NumberDecimalSeparator = "."; System.Threading.Thread.CurrentThread.CurrentCulture = customCulture; var outdir = ConfigurationManager.AppSettings["outdir"]; var reader = new M2Reader(); exportworker.ReportProgress(15, "Reading M2.."); if (!CASC.FileExists(fileDataID)) { throw new Exception("404 M2 not found!"); } reader.LoadM2(fileDataID); // Don't export models without vertices if (reader.model.vertices.Count() == 0) { return; } var vertices = new Structs.Vertex[reader.model.vertices.Count()]; for (var i = 0; i < reader.model.vertices.Count(); i++) { vertices[i].Position = new Structs.Vector3D() { X = reader.model.vertices[i].position.X, Y = reader.model.vertices[i].position.Z, Z = reader.model.vertices[i].position.Y * -1 }; vertices[i].Normal = new Structs.Vector3D() { X = reader.model.vertices[i].normal.X, Y = reader.model.vertices[i].normal.Z, Z = reader.model.vertices[i].normal.Y }; vertices[i].TexCoord = new Structs.Vector2D() { X = reader.model.vertices[i].textureCoordX, Y = reader.model.vertices[i].textureCoordY }; } StreamWriter objsw; if (destinationOverride == null) { if (!string.IsNullOrEmpty(filename)) { if (!Directory.Exists(Path.Combine(outdir, Path.GetDirectoryName(filename)))) { Directory.CreateDirectory(Path.Combine(outdir, Path.GetDirectoryName(filename))); } objsw = new StreamWriter(Path.Combine(outdir, filename.Replace(".m2", ".obj"))); } else { if (!Directory.Exists(outdir)) { Directory.CreateDirectory(outdir); } objsw = new StreamWriter(Path.Combine(outdir, fileDataID + ".obj")); } } else { if (!string.IsNullOrEmpty(filename)) { objsw = new StreamWriter(Path.Combine(outdir, destinationOverride, Path.GetFileName(filename.ToLower()).Replace(".m2", ".obj"))); } else { objsw = new StreamWriter(Path.Combine(outdir, destinationOverride, fileDataID + ".obj")); } } if (!string.IsNullOrEmpty(filename)) { objsw.WriteLine("# Written by Marlamin's WoW Export Tools. Original file: " + filename); objsw.WriteLine("mtllib " + Path.GetFileNameWithoutExtension(filename) + ".mtl"); } else { objsw.WriteLine("# Written by Marlamin's WoW Export Tools. Original fileDataID: " + fileDataID); objsw.WriteLine("mtllib " + fileDataID + ".mtl"); } foreach (var vertex in 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 indicelist = new List <uint>(); for (var i = 0; i < reader.model.skins[0].triangles.Count(); i++) { var t = reader.model.skins[0].triangles[i]; indicelist.Add(t.pt1); indicelist.Add(t.pt2); indicelist.Add(t.pt3); } var indices = indicelist.ToArray(); exportworker.ReportProgress(35, "Writing files.."); var renderbatches = new Structs.RenderBatch[reader.model.skins[0].submeshes.Count()]; for (var i = 0; i < reader.model.skins[0].submeshes.Count(); i++) { renderbatches[i].firstFace = reader.model.skins[0].submeshes[i].startTriangle; renderbatches[i].numFaces = reader.model.skins[0].submeshes[i].nTriangles; renderbatches[i].groupID = (uint)i; for (var tu = 0; tu < reader.model.skins[0].textureunit.Count(); tu++) { if (reader.model.skins[0].textureunit[tu].submeshIndex == i) { renderbatches[i].blendType = reader.model.renderflags[reader.model.skins[0].textureunit[tu].renderFlags].blendingMode; renderbatches[i].materialID = reader.model.texlookup[reader.model.skins[0].textureunit[tu].texture].textureID; } } } exportworker.ReportProgress(65, "Exporting textures.."); StreamWriter mtlsb; if (destinationOverride == null) { if (!string.IsNullOrEmpty(filename)) { mtlsb = new StreamWriter(Path.Combine(outdir, filename.Replace(".m2", ".mtl"))); } else { mtlsb = new StreamWriter(Path.Combine(outdir, fileDataID + ".mtl")); } } else { if (!string.IsNullOrEmpty(filename)) { mtlsb = new StreamWriter(Path.Combine(outdir, destinationOverride, Path.GetFileName(filename.ToLower()).Replace(".m2", ".mtl"))); } else { mtlsb = new StreamWriter(Path.Combine(outdir, destinationOverride, fileDataID + ".mtl")); } } 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: if (reader.model.textureFileDataIDs != null && reader.model.textureFileDataIDs.Length > 0 && reader.model.textureFileDataIDs[i] != 0) { textureFileDataID = reader.model.textureFileDataIDs[i]; } else { Listfile.TryGetFileDataID(reader.model.textures[i].filename, out textureFileDataID); } break; case 1: case 2: case 11: default: Console.WriteLine("Texture type " + reader.model.textures[i].type + " not supported, falling back to placeholder texture"); break; } materials[i].textureID = textureID + i; if (!Listfile.TryGetFilename(textureFileDataID, out var textureFilename)) { textureFilename = textureFileDataID.ToString(); } materials[i].filename = Path.GetFileNameWithoutExtension(textureFilename); string textureSaveLocation; if (destinationOverride == null) { if (!string.IsNullOrEmpty(filename)) { textureSaveLocation = Path.Combine(outdir, Path.GetDirectoryName(filename), materials[i].filename + ".png"); } else { textureSaveLocation = Path.Combine(outdir, materials[i].filename + ".png"); } } else { textureSaveLocation = Path.Combine(outdir, destinationOverride, materials[i].filename + ".png"); } try { var blpreader = new BLPReader(); blpreader.LoadBLP(textureFileDataID); blpreader.bmp.Save(textureSaveLocation); } catch (Exception e) { CASCLib.Logger.WriteLine("Exception while saving BLP " + materials[i].filename + ": " + e.Message); } } exportworker.ReportProgress(85, "Writing files.."); foreach (var material in materials) { mtlsb.WriteLine("newmtl " + material.filename); mtlsb.WriteLine("illum 1"); //mtlsb.WriteLine("map_Ka " + material.filename + ".png"); mtlsb.WriteLine("map_Kd " + material.filename + ".png"); } mtlsb.Close(); if (!string.IsNullOrEmpty(filename)) { objsw.WriteLine("g " + Path.GetFileNameWithoutExtension(filename)); } else { objsw.WriteLine("g " + fileDataID); } foreach (var renderbatch in renderbatches) { var i = renderbatch.firstFace; if (!string.IsNullOrEmpty(filename)) { objsw.WriteLine("o " + Path.GetFileNameWithoutExtension(filename) + renderbatch.groupID); } else { objsw.WriteLine("g " + fileDataID.ToString() + renderbatch.groupID.ToString()); } objsw.WriteLine("usemtl " + 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.."); if (!string.IsNullOrEmpty(filename)) { objsw = new StreamWriter(Path.Combine(outdir, Path.GetFileName(filename.ToLower()).Replace(".m2", ".phys.obj"))); } else { objsw = new StreamWriter(Path.Combine(outdir, fileDataID + ".phys.obj")); } objsw.WriteLine("# Written by Marlamin's WoW Export Tools. Original file id: " + fileDataID); for (var 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 (var 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 }
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 ExportADT(uint wdtFileDataID, byte tileX, byte tileY, BackgroundWorker exportworker = null) { if (exportworker == null) { exportworker = new BackgroundWorker(); exportworker.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 MaxSize = 51200 / 3.0; var TileSize = MaxSize / 32.0; var ChunkSize = TileSize / 16.0; var UnitSize = ChunkSize / 8.0; var UnitSizeHalf = UnitSize / 2.0; if (!Listfile.TryGetFilename(wdtFileDataID, out string wdtFilename)) { Logger.WriteLine("ADT OBJ Exporter: WDT {0} has no known filename, skipping export!", wdtFileDataID); return; } var mapName = Path.GetFileNameWithoutExtension(wdtFilename); var file = "world/maps/" + mapName + "/" + mapName + "_" + tileX.ToString() + "_" + tileY.ToString() + ".adt"; var reader = new ADTReader(); reader.LoadADT(wdtFileDataID, tileX, tileY, true, wdtFilename); var adt = reader.adtfile; if (adt.chunks == null) { Logger.WriteLine("ADT OBJ Exporter: File {0} has no chunks, skipping export!", file); return; } Logger.WriteLine("ADT OBJ Exporter: Starting export of {0}..", file); Directory.CreateDirectory(Path.Combine(outdir, Path.GetDirectoryName(file))); exportworker.ReportProgress(0, "Loading ADT " + file); var renderBatches = new List <Structs.RenderBatch>(); var verticelist = new List <Structs.Vertex>(); var indicelist = new List <int>(); var materials = new Dictionary <int, string>(); ConfigurationManager.RefreshSection("appSettings"); var bakeQuality = ConfigurationManager.AppSettings["bakeQuality"]; // Calculate ADT offset in world coordinates var adtStartX = ((adt.x - 32) * TileSize) * -1; var adtStartY = ((adt.y - 32) * TileSize) * -1; // Calculate first chunk offset in world coordinates var initialChunkX = adtStartY + (adt.chunks[0].header.indexX * ChunkSize) * -1; var initialChunkY = adtStartX + (adt.chunks[0].header.indexY * ChunkSize) * -1; uint ci = 0; for (var x = 0; x < 16; x++) { double xOfs = x / 16d; for (var y = 0; y < 16; y++) { double yOfs = y / 16d; var genx = (initialChunkX + (ChunkSize * x) * -1); var geny = (initialChunkY + (ChunkSize * y) * -1); var chunk = adt.chunks[ci]; var off = verticelist.Count(); var batch = new Structs.RenderBatch(); for (int i = 0, idx = 0; i < 17; i++) { bool isSmallRow = (i % 2) != 0; int rowLength = isSmallRow ? 8 : 9; for (var j = 0; j < rowLength; j++) { var v = new Structs.Vertex(); v.Normal = new Structs.Vector3D { X = (double)chunk.normals.normal_0[idx] / 127, Y = (double)chunk.normals.normal_2[idx] / 127, Z = (double)chunk.normals.normal_1[idx] / 127 }; var px = geny - (j * UnitSize); var py = chunk.vertices.vertices[idx++] + chunk.header.position.Z; var pz = genx - (i * UnitSizeHalf); v.Position = new Structs.Vector3D { X = px, Y = py, Z = pz }; if ((i % 2) != 0) { v.Position.X = (px - UnitSizeHalf); } double ofs = j; if (isSmallRow) { ofs += 0.5; } if (bakeQuality == "none") { v.TexCoord = new Structs.Vector2D { X = (j + (((i % 2) != 0) ? 0.5f : 0f)) / 8f, Y = (i * 0.5f) / 8f }; } else if (bakeQuality == "high") { double tx = ofs / 8d; double ty = 1 - (i / 16d); v.TexCoord = new Structs.Vector2D { X = tx, Y = ty }; } else { double tx = -(v.Position.X - initialChunkY) / TileSize; double ty = (v.Position.Z - initialChunkX) / TileSize; v.TexCoord = new Structs.Vector2D { X = tx, Y = ty }; } verticelist.Add(v); } } batch.firstFace = (uint)indicelist.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; for (int j = 9, xx = 0, yy = 0; j < 145; j++, xx++) { if (xx >= 8) { xx = 0; ++yy; } var isHole = true; // Check if chunk is using low-res holes if ((chunk.header.flags & 0x10000) == 0) { // Calculate current hole number var currentHole = (int)Math.Pow(2, Math.Floor(xx / 2f) * 1f + Math.Floor(yy / 2f) * 4f); // Check if current hole number should be a hole if ((chunk.header.holesLowRes & currentHole) == 0) { isHole = false; } } else { // Check if current section is a hole if (((holesHighRes[yy] >> xx) & 1) == 0) { isHole = false; } } if (!isHole) { indicelist.AddRange(new int[] { off + j + 8, off + j - 9, off + j }); indicelist.AddRange(new int[] { off + j - 9, off + j - 8, off + j }); indicelist.AddRange(new int[] { off + j - 8, off + j + 9, off + j }); indicelist.AddRange(new int[] { off + j + 9, off + j + 8, off + j }); // Generates quads instead of 4x triangles //indicelist.AddRange(new int[] { off + j + 8, off + j - 9, off + j - 8 }); //indicelist.AddRange(new int[] { off + j - 8, off + j + 9, off + j + 8 }); } if ((j + 1) % (9 + 8) == 0) { j += 9; } } if (bakeQuality == "high") { materials.Add((int)ci + 1, Path.GetFileNameWithoutExtension(file).Replace(" ", "") + "_" + ci); batch.materialID = ci + 1; } else { if (!materials.ContainsKey(1)) { materials.Add(1, Path.GetFileNameWithoutExtension(file).Replace(" ", "")); } batch.materialID = (uint)materials.Count(); } batch.numFaces = (uint)(indicelist.Count()) - batch.firstFace; if (bakeQuality == "none") { // Build alpha textures, export raw and height textures var rawMaterials = new List <Renderer.Structs.Material>(); if (adt.textures.filenames == null) { for (var ti = 0; ti < adt.diffuseTextureFileDataIDs.Count(); ti++) { var material = new Renderer.Structs.Material(); material.filename = adt.diffuseTextureFileDataIDs[ti].ToString(); material.textureID = (int)adt.diffuseTextureFileDataIDs[ti]; if (adt.texParams != null && adt.texParams.Count() >= ti) { material.scale = (float)Math.Pow(2, (adt.texParams[ti].flags & 0xF0) >> 4); if (adt.texParams[ti].height != 0.0 || adt.texParams[ti].offset != 1.0) { material.heightScale = adt.texParams[ti].height; material.heightOffset = adt.texParams[ti].offset; if (!WoWFormatLib.Utils.CASC.FileExists(adt.heightTextureFileDataIDs[ti])) { Console.WriteLine("Height texture: " + adt.heightTextureFileDataIDs[ti] + " does not exist! Falling back to original texture (hack).."); material.heightTexture = (int)adt.diffuseTextureFileDataIDs[ti]; } else { material.heightTexture = (int)adt.heightTextureFileDataIDs[ti]; } } else { material.heightScale = 0.0f; material.heightOffset = 1.0f; } } else { material.heightScale = 0.0f; material.heightOffset = 1.0f; material.scale = 1.0f; } rawMaterials.Add(material); } } else { for (var ti = 0; ti < adt.textures.filenames.Count(); ti++) { var material = new Renderer.Structs.Material(); material.filename = adt.textures.filenames[ti]; material.textureID = (int)WoWFormatLib.Utils.CASC.getFileDataIdByName(adt.textures.filenames[ti]); if (adt.texParams != null && adt.texParams.Count() >= ti) { material.scale = (float)Math.Pow(2, (adt.texParams[ti].flags & 0xF0) >> 4); if (adt.texParams[ti].height != 0.0 || adt.texParams[ti].offset != 1.0) { material.heightScale = adt.texParams[ti].height; material.heightOffset = adt.texParams[ti].offset; var heightName = adt.textures.filenames[ti].Replace(".blp", "_h.blp"); if (!WoWFormatLib.Utils.CASC.FileExists(heightName)) { Console.WriteLine("Height texture: " + heightName + " does not exist! Falling back to original texture (hack).."); material.heightTexture = (int)WoWFormatLib.Utils.CASC.getFileDataIdByName(adt.textures.filenames[ti]); } else { material.heightTexture = (int)WoWFormatLib.Utils.CASC.getFileDataIdByName(heightName); } } else { material.heightScale = 0.0f; material.heightOffset = 1.0f; } } else { material.heightScale = 0.0f; material.heightOffset = 1.0f; material.scale = 1.0f; } rawMaterials.Add(material); } } var layerMaterials = new List <uint>(); var alphalayermats = new List <int>(); var layerscales = new List <float>(); var layerheights = new List <int>(); //batch.heightScales = new Vector4(); //batch.heightOffsets = new Vector4(); exportworker.ReportProgress(10, "Exporting alpha layers " + file); var baseTextureName = Path.Combine(outdir, Path.GetDirectoryName(file), "terraindiffuse", Path.GetFileNameWithoutExtension(file).Replace(" ", "")); var baseAlphaTextureName = Path.Combine(outdir, Path.GetDirectoryName(file), "terrainalpha", Path.GetFileNameWithoutExtension(file).Replace(" ", "")); Directory.CreateDirectory(Path.GetDirectoryName(baseAlphaTextureName)); var pixels = new byte[4, 4096]; for (var li = 0; li < adt.texChunks[ci].layers.Count(); li++) { if (adt.texChunks[ci].alphaLayer != null) { var values = adt.texChunks[ci].alphaLayer[li].layer; for (var tx = 0; tx < 64; tx++) { for (var ty = 0; ty < 64; ty++) { pixels[li, tx * 64 + ty] = values[tx * 64 + ty]; } } } Renderer.Structs.Material curMat; if (adt.diffuseTextureFileDataIDs == null) { if (adt.textures.filenames == null) { throw new Exception("ADT has no textures?"); } var texFileDataID = WoWFormatLib.Utils.CASC.getFileDataIdByName(adt.textures.filenames[adt.texChunks[ci].layers[li].textureId]); //layerMaterials.Add((uint)BLPLoader.LoadTexture(texFileDataID)); curMat = rawMaterials.Where(material => material.filename == adt.textures.filenames[adt.texChunks[ci].layers[li].textureId]).Single(); } else { //layerMaterials.Add((uint)BLPLoader.LoadTexture(adt.diffuseTextureFileDataIDs[adt.texChunks[ci].layers[li].textureId])); curMat = rawMaterials.Where(material => material.filename == adt.diffuseTextureFileDataIDs[adt.texChunks[ci].layers[li].textureId].ToString()).Single(); //Console.WriteLine(ci + " " + li + " " + curMat.filename); } //layerscales.Add(curMat.scale); //layerheights.Add(curMat.heightTexture); //batch.heightScales[li] = curMat.heightScale; //batch.heightOffsets[li] = curMat.heightOffset; } //batch.materialID = layerMaterials.ToArray(); //batch.alphaMaterialID = alphalayermats.ToArray(); //batch.scales = layerscales.ToArray(); //batch.heightMaterialIDs = layerheights.ToArray(); using (var bmp = new System.Drawing.Bitmap(64, 64)) { for (var tx = 0; tx < 64; tx++) { for (var ty = 0; ty < 64; ty++) { var color = System.Drawing.Color.FromArgb(pixels[0, tx * 64 + ty], pixels[1, tx * 64 + ty], pixels[2, tx * 64 + ty], pixels[3, tx * 64 + ty]); bmp.SetPixel(ty, tx, color); } } bmp.Save(baseAlphaTextureName + "_" + ci + ".png", System.Drawing.Imaging.ImageFormat.Png); } } renderBatches.Add(batch); ci++; } } ConfigurationManager.RefreshSection("appSettings"); bool exportWMO = ConfigurationManager.AppSettings["exportWMO"] == "True"; bool exportM2 = ConfigurationManager.AppSettings["exportM2"] == "True"; bool exportFoliage = ConfigurationManager.AppSettings["exportFoliage"] == "True"; if (exportFoliage) { exportworker.ReportProgress(65, "Exporting ADT foliage"); try { var build = WoWFormatLib.Utils.CASC.BuildName; var dbcd = new DBCD.DBCD(new DBC.CASCDBCProvider(), new GithubDBDProvider()); var groundEffectTextureDB = dbcd.Load("GroundEffectTexture"); var groundEffectDoodadDB = dbcd.Load("GroundEffectDoodad"); for (var c = 0; c < reader.adtfile.texChunks.Length; c++) { for (var l = 0; l < reader.adtfile.texChunks[c].layers.Length; l++) { var effectID = reader.adtfile.texChunks[c].layers[l].effectId; if (effectID == 0) { continue; } if (!groundEffectTextureDB.ContainsKey(effectID)) { continue; } dynamic textureEntry = groundEffectTextureDB[effectID]; foreach (int doodad in textureEntry.DoodadID) { if (!groundEffectDoodadDB.ContainsKey(doodad)) { continue; } dynamic doodadEntry = groundEffectDoodadDB[doodad]; var filedataid = (uint)doodadEntry.ModelFileID; if (!Listfile.TryGetFilename(filedataid, out var filename)) { Logger.WriteLine("Could not find filename for " + filedataid + ", setting filename to filedataid.."); filename = filedataid.ToString(); } if (!File.Exists(Path.Combine(outdir, Path.GetDirectoryName(file), "foliage"))) { Directory.CreateDirectory(Path.Combine(outdir, Path.GetDirectoryName(file), "foliage")); } if (!File.Exists(Path.Combine(outdir, Path.GetDirectoryName(file), "foliage", Path.GetFileNameWithoutExtension(filename).ToLower() + ".obj"))) { M2Exporter.ExportM2(filedataid, null, Path.Combine(outdir, Path.GetDirectoryName(file), "foliage"), filename); } } } } } catch (Exception e) { Logger.WriteLine("Error exporting GroundEffects: " + e.Message); } } if (exportWMO || exportM2) { var doodadSW = new StreamWriter(Path.Combine(outdir, Path.GetDirectoryName(file), Path.GetFileNameWithoutExtension(file).Replace(" ", "") + "_ModelPlacementInformation.csv")); doodadSW.WriteLine("ModelFile;PositionX;PositionY;PositionZ;RotationX;RotationY;RotationZ;ScaleFactor;ModelId;Type"); if (exportWMO) { exportworker.ReportProgress(25, "Exporting ADT worldmodels"); for (var mi = 0; mi < reader.adtfile.objects.worldModels.entries.Count(); mi++) { var wmo = reader.adtfile.objects.worldModels.entries[mi]; var filename = ""; uint filedataid = 0; if (reader.adtfile.objects.wmoNames.filenames == null) { filedataid = wmo.mwidEntry; if (!Listfile.TryGetFilename(filedataid, out filename)) { Logger.WriteLine("Warning! Could not find filename for " + filedataid + ", setting filename to filedataid.."); filename = filedataid.ToString() + ".wmo"; } } else { Logger.WriteLine("Warning!! File " + filename + " ID: " + filedataid + " still has filenames!"); filename = reader.adtfile.objects.wmoNames.filenames[wmo.mwidEntry]; if (!Listfile.TryGetFileDataID(filename, out filedataid)) { Logger.WriteLine("Error! Could not find filedataid for " + filename + "!"); continue; } } short doodadSet = -1; if (ConfigurationManager.AppSettings["exportWMODoodads"] == "True") { doodadSet = (short)wmo.doodadSet; } if (string.IsNullOrEmpty(filename)) { string wmoFile = Path.Combine(outdir, Path.GetDirectoryName(file), filedataid.ToString() + ".obj"); if (!File.Exists(wmoFile)) { WMOExporter.ExportWMO(filedataid, exportworker, Path.Combine(outdir, Path.GetDirectoryName(file)), doodadSet); } if (File.Exists(wmoFile)) { doodadSW.WriteLine(filedataid + ".obj;" + wmo.position.X + ";" + wmo.position.Y + ";" + wmo.position.Z + ";" + wmo.rotation.X + ";" + wmo.rotation.Y + ";" + wmo.rotation.Z + ";" + wmo.scale / 1024f + ";" + wmo.uniqueId + ";wmo"); } } else { string wmoFile = Path.Combine(outdir, Path.GetDirectoryName(file), Path.GetFileNameWithoutExtension(filename).ToLower() + ".obj"); if (!File.Exists(wmoFile)) { WMOExporter.ExportWMO(filedataid, exportworker, Path.Combine(outdir, Path.GetDirectoryName(file)), doodadSet, filename); } if (File.Exists(Path.Combine(outdir, Path.GetDirectoryName(file), Path.GetFileNameWithoutExtension(filename).ToLower() + ".obj"))) { doodadSW.WriteLine(Path.GetFileNameWithoutExtension(filename).ToLower() + ".obj;" + wmo.position.X + ";" + wmo.position.Y + ";" + wmo.position.Z + ";" + wmo.rotation.X + ";" + wmo.rotation.Y + ";" + wmo.rotation.Z + ";" + wmo.scale / 1024f + ";" + wmo.uniqueId + ";wmo"); } } } } if (exportM2) { exportworker.ReportProgress(50, "Exporting ADT doodads"); for (var mi = 0; mi < reader.adtfile.objects.models.entries.Count(); mi++) { var doodad = reader.adtfile.objects.models.entries[mi]; string filename; uint filedataid; if (reader.adtfile.objects.m2Names.filenames == null) { filedataid = doodad.mmidEntry; if (!Listfile.TryGetFilename(filedataid, out filename)) { Logger.WriteLine("Could not find filename for " + filedataid + ", setting filename to filedataid.."); filename = filedataid.ToString(); } } else { filename = reader.adtfile.objects.m2Names.filenames[doodad.mmidEntry].ToLower(); if (!Listfile.TryGetFileDataID(filename, out filedataid)) { Logger.WriteLine("Error! Could not find filedataid for " + filename + "!"); continue; } } if (!File.Exists(Path.Combine(outdir, Path.GetDirectoryName(file), Path.GetFileNameWithoutExtension(filename) + ".obj"))) { M2Exporter.ExportM2(filedataid, null, Path.Combine(outdir, Path.GetDirectoryName(file)), filename); } if (File.Exists(Path.Combine(outdir, Path.GetDirectoryName(file), Path.GetFileNameWithoutExtension(filename) + ".obj"))) { doodadSW.WriteLine(Path.GetFileNameWithoutExtension(filename) + ".obj;" + doodad.position.X + ";" + doodad.position.Y + ";" + doodad.position.Z + ";" + doodad.rotation.X + ";" + doodad.rotation.Y + ";" + doodad.rotation.Z + ";" + doodad.scale / 1024f + ";" + doodad.uniqueId + ";m2"); } } } doodadSW.Close(); } exportworker.ReportProgress(75, "Exporting terrain textures.."); if (bakeQuality != "none") { var mtlsw = new StreamWriter(Path.Combine(outdir, Path.GetDirectoryName(file), Path.GetFileNameWithoutExtension(file).Replace(" ", "") + ".mtl")); //No idea how MTL files really work yet. Needs more investigation. foreach (var material in materials) { mtlsw.WriteLine("newmtl " + material.Value.Replace(" ", "")); mtlsw.WriteLine("Ka 1.000000 1.000000 1.000000"); mtlsw.WriteLine("Kd 0.640000 0.640000 0.640000"); mtlsw.WriteLine("map_Ka " + material.Value.Replace(" ", "") + ".png"); mtlsw.WriteLine("map_Kd " + material.Value.Replace(" ", "") + ".png"); } mtlsw.Close(); } exportworker.ReportProgress(85, "Exporting terrain geometry.."); var indices = indicelist.ToArray(); var adtname = Path.GetFileNameWithoutExtension(file); var objsw = new StreamWriter(Path.Combine(outdir, Path.GetDirectoryName(file), Path.GetFileNameWithoutExtension(file).Replace(" ", "") + ".obj")); objsw.WriteLine("# Written by Marlamin's WoW OBJExporter. Original file: " + file); if (bakeQuality != "none") { objsw.WriteLine("mtllib " + Path.GetFileNameWithoutExtension(file).Replace(" ", "") + ".mtl"); } var verticeCounter = 1; var chunkCounter = 1; foreach (var vertex in verticelist) { //objsw.WriteLine("# C" + chunkCounter + ".V" + verticeCounter); objsw.WriteLine("v " + vertex.Position.X.ToString("R") + " " + vertex.Position.Y.ToString("R") + " " + vertex.Position.Z.ToString("R")); objsw.WriteLine("vt " + vertex.TexCoord.X + " " + vertex.TexCoord.Y); objsw.WriteLine("vn " + vertex.Normal.X.ToString("R") + " " + vertex.Normal.Y.ToString("R") + " " + vertex.Normal.Z.ToString("R")); verticeCounter++; if (verticeCounter == 146) { chunkCounter++; verticeCounter = 1; } } if (bakeQuality == "minimap" || bakeQuality == "low" || bakeQuality == "medium") { objsw.WriteLine("g " + adtname.Replace(" ", "")); objsw.WriteLine("usemtl " + materials[1]); objsw.WriteLine("s 1"); } for (int rbi = 0; rbi < renderBatches.Count(); rbi++) { var renderBatch = renderBatches[rbi]; var i = renderBatch.firstFace; if (bakeQuality == "high" || bakeQuality == "none") { objsw.WriteLine("g " + adtname.Replace(" ", "") + "_" + rbi); } if (bakeQuality == "high" && materials.ContainsKey((int)renderBatch.materialID)) { objsw.WriteLine("usemtl " + materials[(int)renderBatch.materialID]); } while (i < (renderBatch.firstFace + renderBatch.numFaces)) { objsw.WriteLine("f " + (indices[i + 2] + 1) + "/" + (indices[i + 2] + 1) + "/" + (indices[i + 2] + 1) + " " + (indices[i + 1] + 1) + "/" + (indices[i + 1] + 1) + "/" + (indices[i + 1] + 1) + " " + (indices[i] + 1) + "/" + (indices[i] + 1) + "/" + (indices[i] + 1)); i = i + 3; } } objsw.Close(); Logger.WriteLine("ADT OBJ Exporter: Finished with export of {0}..", file); }
public static void ExportM2(M2Reader reader, string fileName, BackgroundWorker exportworker = null, string destinationOverride = null, bool externalOverride = false, bool[] enabledGeosets = null) { if (exportworker == null) { exportworker = new BackgroundWorker { WorkerReportsProgress = true }; } var customCulture = (System.Globalization.CultureInfo)System.Threading.Thread.CurrentThread.CurrentCulture.Clone(); customCulture.NumberFormat.NumberDecimalSeparator = "."; System.Threading.Thread.CurrentThread.CurrentCulture = customCulture; var exportDir = ConfigurationManager.AppSettings["outdir"]; exportworker.ReportProgress(15, "Reading M2.."); // Don't export models without vertices if (reader.model.vertices.Count() == 0) { return; } var vertices = new Structs.Vertex[reader.model.vertices.Count()]; for (var i = 0; i < reader.model.vertices.Count(); i++) { vertices[i].Position = new Structs.Vector3D() { X = reader.model.vertices[i].position.X, Y = reader.model.vertices[i].position.Z, Z = reader.model.vertices[i].position.Y * -1 }; vertices[i].Normal = new Structs.Vector3D() { X = reader.model.vertices[i].normal.X, Y = reader.model.vertices[i].normal.Z, Z = reader.model.vertices[i].normal.Y }; vertices[i].TexCoord = new Structs.Vector2D() { X = reader.model.vertices[i].textureCoordX, Y = reader.model.vertices[i].textureCoordY }; } string outDir = exportDir; if (destinationOverride != null) { if (externalOverride) { outDir = destinationOverride; } else { outDir = Path.Combine(outDir, destinationOverride); } } else { outDir = Path.Combine(outDir, Path.GetDirectoryName(fileName)); } Directory.CreateDirectory(outDir); string filePath = Path.Combine(outDir, Path.GetFileName(fileName).Replace(".m2", "")); string objFilePath = filePath + ".obj"; string mtlFilePath = filePath + ".mtl"; StreamWriter objWriter = new StreamWriter(objFilePath); StreamWriter mtlWriter = new StreamWriter(mtlFilePath); // Write OBJ header. objWriter.WriteLine("# Written by Marlamin's WoW Export Tools. Source file: " + fileName); objWriter.WriteLine("mtllib " + Path.GetFileName(mtlFilePath)); foreach (var vertex in vertices) { objWriter.WriteLine("v " + vertex.Position.X + " " + vertex.Position.Y + " " + vertex.Position.Z); objWriter.WriteLine("vt " + vertex.TexCoord.X + " " + (vertex.TexCoord.Y - 1) * -1); objWriter.WriteLine("vn " + (-vertex.Normal.X).ToString("F12") + " " + vertex.Normal.Y.ToString("F12") + " " + vertex.Normal.Z.ToString("F12")); } var indicelist = new List <uint>(); for (var i = 0; i < reader.model.skins[0].triangles.Count(); i++) { var t = reader.model.skins[0].triangles[i]; indicelist.Add(t.pt1); indicelist.Add(t.pt2); indicelist.Add(t.pt3); } var indices = indicelist.ToArray(); exportworker.ReportProgress(35, "Writing files.."); var renderbatches = new Structs.RenderBatch[reader.model.skins[0].submeshes.Count()]; for (var i = 0; i < reader.model.skins[0].submeshes.Count(); i++) { if (enabledGeosets != null && !enabledGeosets[i]) { continue; } renderbatches[i].firstFace = reader.model.skins[0].submeshes[i].startTriangle; renderbatches[i].numFaces = reader.model.skins[0].submeshes[i].nTriangles; renderbatches[i].groupID = (uint)i; for (var tu = 0; tu < reader.model.skins[0].textureunit.Count(); tu++) { if (reader.model.skins[0].textureunit[tu].submeshIndex == i) { renderbatches[i].blendType = reader.model.renderflags[reader.model.skins[0].textureunit[tu].renderFlags].blendingMode; renderbatches[i].materialID = reader.model.texlookup[reader.model.skins[0].textureunit[tu].texture].textureID; } } } exportworker.ReportProgress(65, "Exporting textures.."); uint defaultTexID = DEFAULT_TEXTURE; if (!CASC.FileExists(defaultTexID)) { defaultTexID = DEFAULT_TEXTURE_SUB; } var textureID = 0; var materials = new Structs.Material[reader.model.textures.Count()]; for (var i = 0; i < reader.model.textures.Count(); i++) { uint textureFileDataID = defaultTexID; materials[i].flags = reader.model.textures[i].flags; if (reader.model.textures[i].type == 0) { if (reader.model.textureFileDataIDs != null && reader.model.textureFileDataIDs.Length > 0 && reader.model.textureFileDataIDs[i] != 0) { textureFileDataID = reader.model.textureFileDataIDs[i]; } else { Listfile.TryGetFileDataID(reader.model.textures[i].filename, out textureFileDataID); } } else { Console.WriteLine("Texture type " + reader.model.textures[i].type + " not supported, falling back to placeholder texture"); } materials[i].textureID = textureID + i; if (!Listfile.TryGetFilename(textureFileDataID, out var textureFilename)) { textureFilename = textureFileDataID.ToString(); } materials[i].filename = Path.GetFileNameWithoutExtension(textureFilename).Replace(" ", ""); try { var blpreader = new BLPReader(); blpreader.LoadBLP(textureFileDataID); blpreader.bmp.Save(Path.Combine(outDir, materials[i].filename + ".png")); } catch (Exception e) { CASCLib.Logger.WriteLine("Exception while saving BLP " + materials[i].filename + ": " + e.Message); } } exportworker.ReportProgress(85, "Writing files.."); foreach (var material in materials) { mtlWriter.WriteLine("newmtl " + material.filename); mtlWriter.WriteLine("illum 1"); mtlWriter.WriteLine("map_Kd " + material.filename + ".png"); if (ConfigurationManager.AppSettings["textureMetadata"] == "True") { foreach (var renderbatch in renderbatches) { if (materials[renderbatch.materialID].filename == material.filename) { mtlWriter.WriteLine("blend " + material.blendMode); } } } } mtlWriter.Close(); objWriter.WriteLine("o " + Path.GetFileName(fileName)); foreach (var renderbatch in renderbatches) { var i = renderbatch.firstFace; objWriter.WriteLine("g " + renderbatch.groupID); objWriter.WriteLine("usemtl " + materials[renderbatch.materialID].filename); objWriter.WriteLine("s 1"); while (i < (renderbatch.firstFace + renderbatch.numFaces)) { objWriter.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; } } objWriter.Close(); // Only export phys when exporting a single M2, causes issues for some users when combined with WMO/ADT if (destinationOverride == null && ConfigurationManager.AppSettings["exportCollision"] == "True") { exportworker.ReportProgress(90, "Exporting collision.."); objWriter = new StreamWriter(filePath + ".phys.obj"); objWriter.WriteLine("# Written by Marlamin's WoW Export Tools. Source file: " + fileName); for (var i = 0; i < reader.model.boundingvertices.Count(); i++) { objWriter.WriteLine("v " + reader.model.boundingvertices[i].vertex.X + " " + reader.model.boundingvertices[i].vertex.Z + " " + -reader.model.boundingvertices[i].vertex.Y); } for (var i = 0; i < reader.model.boundingtriangles.Count(); i++) { var t = reader.model.boundingtriangles[i]; objWriter.WriteLine("f " + (t.index_0 + 1) + " " + (t.index_1 + 1) + " " + (t.index_2 + 1)); } objWriter.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 }
private static uint MISSING_TEXTURE_ID = 186184; // textures/shanecube.blp public static Renderer.Structs.DoodadBatch LoadM2(string fileName, int shaderProgram) { fileName = fileName.ToLower().Replace(".mdx", ".m2"); fileName = fileName.ToLower().Replace(".mdl", ".m2"); M2Model model = new M2Model(); if (Listfile.TryGetFileDataID(fileName, out var fileDataID)) { if (WoWFormatLib.Utils.CASC.FileExists(fileDataID)) { var modelReader = new M2Reader(); modelReader.LoadM2(fileDataID); model = modelReader.model; } else { throw new Exception("Model " + fileName + " does not exist!"); } } else { throw new Exception("Filename " + fileName + " does not exist in listfile!"); } if (model.boundingbox == null) { throw new Exception("Model does not contain bounding box: " + fileName); } var doodadBatch = new Renderer.Structs.DoodadBatch() { boundingBox = new Renderer.Structs.BoundingBox() { min = new Vector3(model.boundingbox[0].X, model.boundingbox[0].Y, model.boundingbox[0].Z), max = new Vector3(model.boundingbox[1].X, model.boundingbox[1].Y, model.boundingbox[1].Z) } }; if (model.textures == null) { throw new Exception("Model does not contain textures: " + fileName); } if (model.skins == null) { throw new Exception("Model does not contain skins: " + fileName); } // Textures doodadBatch.mats = new Renderer.Structs.Material[model.textures.Count()]; for (var i = 0; i < model.textures.Count(); i++) { uint textureFileDataID = DEFAULT_TEXTURE_ID; doodadBatch.mats[i].flags = model.textures[i].flags; switch (model.textures[i].type) { case 0: // NONE if (model.textureFileDataIDs != null && model.textureFileDataIDs.Length > 0 && model.textureFileDataIDs[i] != 0) { textureFileDataID = model.textureFileDataIDs[i]; } else { textureFileDataID = WoWFormatLib.Utils.CASC.getFileDataIdByName(model.textures[i].filename); } break; case 1: // TEX_COMPONENT_SKIN case 2: // TEX_COMPONENT_OBJECT_SKIN case 11: // TEX_COMPONENT_MONSTER_1 break; } // Not set in TXID if (textureFileDataID == 0) { textureFileDataID = DEFAULT_TEXTURE_ID; } if (!WoWFormatLib.Utils.CASC.FileExists(textureFileDataID)) { textureFileDataID = MISSING_TEXTURE_ID; } doodadBatch.mats[i].textureID = BLPLoader.LoadTexture(textureFileDataID); doodadBatch.mats[i].filename = textureFileDataID.ToString(); } // Submeshes doodadBatch.submeshes = new Renderer.Structs.Submesh[model.skins[0].submeshes.Count()]; for (var i = 0; i < model.skins[0].submeshes.Count(); i++) { doodadBatch.submeshes[i].firstFace = model.skins[0].submeshes[i].startTriangle; doodadBatch.submeshes[i].numFaces = model.skins[0].submeshes[i].nTriangles; for (var tu = 0; tu < model.skins[0].textureunit.Count(); tu++) { if (model.skins[0].textureunit[tu].submeshIndex == i) { doodadBatch.submeshes[i].blendType = model.renderflags[model.skins[0].textureunit[tu].renderFlags].blendingMode; uint textureFileDataID = DEFAULT_TEXTURE_ID; if (!WoWFormatLib.Utils.CASC.FileExists(textureFileDataID)) { textureFileDataID = MISSING_TEXTURE_ID; } if (model.textureFileDataIDs != null && model.textureFileDataIDs.Length > 0 && model.textureFileDataIDs[model.texlookup[model.skins[0].textureunit[tu].texture].textureID] != 0) { textureFileDataID = model.textureFileDataIDs[model.texlookup[model.skins[0].textureunit[tu].texture].textureID]; } else { if (Listfile.FilenameToFDID.TryGetValue(model.textures[model.texlookup[model.skins[0].textureunit[tu].texture].textureID].filename.Replace('\\', '/').ToLower(), out var filedataid)) { textureFileDataID = filedataid; } else { textureFileDataID = DEFAULT_TEXTURE_ID; if (!WoWFormatLib.Utils.CASC.FileExists(textureFileDataID)) { textureFileDataID = MISSING_TEXTURE_ID; } } } if (!WoWFormatLib.Utils.CASC.FileExists(textureFileDataID)) { textureFileDataID = MISSING_TEXTURE_ID; } doodadBatch.submeshes[i].material = (uint)BLPLoader.LoadTexture(textureFileDataID); } } } doodadBatch.vao = GL.GenVertexArray(); GL.BindVertexArray(doodadBatch.vao); // Vertices & indices doodadBatch.vertexBuffer = GL.GenBuffer(); doodadBatch.indiceBuffer = GL.GenBuffer(); GL.BindBuffer(BufferTarget.ArrayBuffer, doodadBatch.vertexBuffer); GL.BindBuffer(BufferTarget.ElementArrayBuffer, doodadBatch.indiceBuffer); var modelindicelist = new List <uint>(); for (var i = 0; i < model.skins[0].triangles.Count(); i++) { modelindicelist.Add(model.skins[0].triangles[i].pt1); modelindicelist.Add(model.skins[0].triangles[i].pt2); modelindicelist.Add(model.skins[0].triangles[i].pt3); } var modelindices = modelindicelist.ToArray(); doodadBatch.indices = modelindices; GL.BindBuffer(BufferTarget.ElementArrayBuffer, doodadBatch.indiceBuffer); GL.BufferData(BufferTarget.ElementArrayBuffer, (IntPtr)(doodadBatch.indices.Length * sizeof(uint)), doodadBatch.indices, BufferUsageHint.StaticDraw); var modelvertices = new Renderer.Structs.M2Vertex[model.vertices.Count()]; for (var i = 0; i < model.vertices.Count(); i++) { modelvertices[i].Position = new Vector3(model.vertices[i].position.X, model.vertices[i].position.Y, model.vertices[i].position.Z); modelvertices[i].Normal = new Vector3(model.vertices[i].normal.X, model.vertices[i].normal.Y, model.vertices[i].normal.Z); modelvertices[i].TexCoord = new Vector2(model.vertices[i].textureCoordX, model.vertices[i].textureCoordY); } GL.BindBuffer(BufferTarget.ArrayBuffer, doodadBatch.vertexBuffer); GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(modelvertices.Length * 8 * sizeof(float)), modelvertices, BufferUsageHint.StaticDraw); //Set pointers in buffer //var normalAttrib = GL.GetAttribLocation(shaderProgram, "normal"); //GL.EnableVertexAttribArray(normalAttrib); //GL.VertexAttribPointer(normalAttrib, 3, VertexAttribPointerType.Float, false, sizeof(float) * 8, sizeof(float) * 0); var texCoordAttrib = GL.GetAttribLocation(shaderProgram, "texCoord"); GL.EnableVertexAttribArray(texCoordAttrib); GL.VertexAttribPointer(texCoordAttrib, 2, VertexAttribPointerType.Float, false, sizeof(float) * 8, sizeof(float) * 3); var posAttrib = GL.GetAttribLocation(shaderProgram, "position"); GL.EnableVertexAttribArray(posAttrib); GL.VertexAttribPointer(posAttrib, 3, VertexAttribPointerType.Float, false, sizeof(float) * 8, sizeof(float) * 5); return(doodadBatch); }
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 Export Tools " + 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: Listfile.TryGetFileDataID(reader.model.textures[i].filename, out textureFileDataID); 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 */ }
public static Renderer.Structs.WorldModel LoadWMO(string filename, CacheStorage cache, int shaderProgram) { if (cache.worldModelBatches.ContainsKey(filename)) { return(cache.worldModelBatches[filename]); } var wmo = new WoWFormatLib.Structs.WMO.WMO(); if (cache.worldModels.ContainsKey(filename)) { wmo = cache.worldModels[filename]; } else { if (!Listfile.TryGetFileDataID(filename, out uint fileDataID)) { CASCLib.Logger.WriteLine("Could not get filedataid for " + filename); } //Load WMO from file if (WoWFormatLib.Utils.CASC.FileExists(fileDataID)) { var wmofile = new WMOReader().LoadWMO(fileDataID); cache.worldModels.Add(filename, wmofile); wmo = cache.worldModels[filename]; } else { throw new Exception("WMO " + filename + " does not exist!"); } } if (wmo.group.Count() == 0) { CASCLib.Logger.WriteLine("WMO has no groups: ", filename); throw new Exception("Broken WMO! Report to developer (mail [email protected]) with this filename: " + filename); } var wmobatch = new Renderer.Structs.WorldModel() { groupBatches = new Renderer.Structs.WorldModelGroupBatches[wmo.group.Count()] }; var groupNames = new string[wmo.group.Count()]; for (var g = 0; g < wmo.group.Count(); g++) { if (wmo.group[g].mogp.vertices == null) { continue; } wmobatch.groupBatches[g].vao = GL.GenVertexArray(); wmobatch.groupBatches[g].vertexBuffer = GL.GenBuffer(); wmobatch.groupBatches[g].indiceBuffer = GL.GenBuffer(); GL.BindVertexArray(wmobatch.groupBatches[g].vao); GL.BindBuffer(BufferTarget.ArrayBuffer, wmobatch.groupBatches[g].vertexBuffer); var wmovertices = new Renderer.Structs.M2Vertex[wmo.group[g].mogp.vertices.Count()]; for (var i = 0; i < wmo.groupNames.Count(); i++) { if (wmo.group[g].mogp.nameOffset == wmo.groupNames[i].offset) { groupNames[g] = wmo.groupNames[i].name.Replace(" ", "_"); } } if (groupNames[g] == "antiportal") { continue; } for (var i = 0; i < wmo.group[g].mogp.vertices.Count(); i++) { wmovertices[i].Position = new Vector3(wmo.group[g].mogp.vertices[i].vector.X, wmo.group[g].mogp.vertices[i].vector.Y, wmo.group[g].mogp.vertices[i].vector.Z); wmovertices[i].Normal = new Vector3(wmo.group[g].mogp.normals[i].normal.X, wmo.group[g].mogp.normals[i].normal.Y, wmo.group[g].mogp.normals[i].normal.Z); if (wmo.group[g].mogp.textureCoords[0] == null) { wmovertices[i].TexCoord = new Vector2(0.0f, 0.0f); } else { wmovertices[i].TexCoord = new Vector2(wmo.group[g].mogp.textureCoords[0][i].X, wmo.group[g].mogp.textureCoords[0][i].Y); } } //Push to buffer GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(wmovertices.Length * 8 * sizeof(float)), wmovertices, BufferUsageHint.StaticDraw); //Set pointers in buffer //var normalAttrib = GL.GetAttribLocation(shaderProgram, "normal"); //GL.EnableVertexAttribArray(normalAttrib); //GL.VertexAttribPointer(normalAttrib, 3, VertexAttribPointerType.Float, false, sizeof(float) * 8, sizeof(float) * 0); var texCoordAttrib = GL.GetAttribLocation(shaderProgram, "texCoord"); GL.EnableVertexAttribArray(texCoordAttrib); GL.VertexAttribPointer(texCoordAttrib, 2, VertexAttribPointerType.Float, false, sizeof(float) * 8, sizeof(float) * 3); var posAttrib = GL.GetAttribLocation(shaderProgram, "position"); GL.EnableVertexAttribArray(posAttrib); GL.VertexAttribPointer(posAttrib, 3, VertexAttribPointerType.Float, false, sizeof(float) * 8, sizeof(float) * 5); //Switch to Index buffer GL.BindBuffer(BufferTarget.ElementArrayBuffer, wmobatch.groupBatches[g].indiceBuffer); var wmoindicelist = new List <uint>(); for (var i = 0; i < wmo.group[g].mogp.indices.Count(); i++) { wmoindicelist.Add(wmo.group[g].mogp.indices[i].indice); } wmobatch.groupBatches[g].indices = wmoindicelist.ToArray(); GL.BufferData(BufferTarget.ElementArrayBuffer, (IntPtr)(wmobatch.groupBatches[g].indices.Length * sizeof(uint)), wmobatch.groupBatches[g].indices, BufferUsageHint.StaticDraw); } GL.Enable(EnableCap.Texture2D); wmobatch.mats = new Renderer.Structs.Material[wmo.materials.Count()]; for (var i = 0; i < wmo.materials.Count(); i++) { wmobatch.mats[i].texture1 = wmo.materials[i].texture1; wmobatch.mats[i].texture2 = wmo.materials[i].texture2; wmobatch.mats[i].texture3 = wmo.materials[i].texture3; if (wmo.textures == null) { if (WoWFormatLib.Utils.CASC.FileExists(wmo.materials[i].texture1)) { wmobatch.mats[i].textureID1 = BLPLoader.LoadTexture(wmo.materials[i].texture1, cache); } if (WoWFormatLib.Utils.CASC.FileExists(wmo.materials[i].texture2)) { wmobatch.mats[i].textureID2 = BLPLoader.LoadTexture(wmo.materials[i].texture2, cache); } if (WoWFormatLib.Utils.CASC.FileExists(wmo.materials[i].texture3)) { wmobatch.mats[i].textureID3 = BLPLoader.LoadTexture(wmo.materials[i].texture3, cache); } } else { for (var ti = 0; ti < wmo.textures.Count(); ti++) { if (wmo.textures[ti].startOffset == wmo.materials[i].texture1) { wmobatch.mats[i].textureID1 = BLPLoader.LoadTexture(wmo.textures[ti].filename, cache); } if (wmo.textures[ti].startOffset == wmo.materials[i].texture2) { wmobatch.mats[i].textureID2 = BLPLoader.LoadTexture(wmo.textures[ti].filename, cache); } if (wmo.textures[ti].startOffset == wmo.materials[i].texture3) { wmobatch.mats[i].textureID3 = BLPLoader.LoadTexture(wmo.textures[ti].filename, cache); } } } } wmobatch.doodads = new Renderer.Structs.WMODoodad[wmo.doodadDefinitions.Count()]; for (var i = 0; i < wmo.doodadDefinitions.Count(); i++) { if (wmo.doodadNames != null) { for (var j = 0; j < wmo.doodadNames.Count(); j++) { if (wmo.doodadDefinitions[i].offset == wmo.doodadNames[j].startOffset) { wmobatch.doodads[i].filename = wmo.doodadNames[j].filename; } } } else { wmobatch.doodads[i].filedataid = wmo.doodadDefinitions[i].offset; } wmobatch.doodads[i].flags = wmo.doodadDefinitions[i].flags; wmobatch.doodads[i].position = new Vector3(wmo.doodadDefinitions[i].position.X, wmo.doodadDefinitions[i].position.Y, wmo.doodadDefinitions[i].position.Z); wmobatch.doodads[i].rotation = new Quaternion(wmo.doodadDefinitions[i].rotation.X, wmo.doodadDefinitions[i].rotation.Y, wmo.doodadDefinitions[i].rotation.Z, wmo.doodadDefinitions[i].rotation.W); wmobatch.doodads[i].scale = wmo.doodadDefinitions[i].scale; wmobatch.doodads[i].color = new Vector4(wmo.doodadDefinitions[i].color[0], wmo.doodadDefinitions[i].color[1], wmo.doodadDefinitions[i].color[2], wmo.doodadDefinitions[i].color[3]); } 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(); } wmobatch.wmoRenderBatch = new Renderer.Structs.RenderBatch[numRenderbatches]; var rb = 0; for (var g = 0; g < wmo.group.Count(); g++) { var group = wmo.group[g]; if (group.mogp.renderBatches == null) { continue; } for (var i = 0; i < group.mogp.renderBatches.Count(); i++) { wmobatch.wmoRenderBatch[rb].firstFace = group.mogp.renderBatches[i].firstFace; wmobatch.wmoRenderBatch[rb].numFaces = group.mogp.renderBatches[i].numFaces; uint matID = 0; if (group.mogp.renderBatches[i].flags == 2) { matID = (uint)group.mogp.renderBatches[i].possibleBox2_3; } else { matID = group.mogp.renderBatches[i].materialID; } wmobatch.wmoRenderBatch[rb].materialID = new uint[3]; for (var ti = 0; ti < wmobatch.mats.Count(); ti++) { if (wmo.materials[matID].texture1 == wmobatch.mats[ti].texture1) { wmobatch.wmoRenderBatch[rb].materialID[0] = (uint)wmobatch.mats[ti].textureID1; } if (wmo.materials[matID].texture2 == wmobatch.mats[ti].texture2) { wmobatch.wmoRenderBatch[rb].materialID[1] = (uint)wmobatch.mats[ti].textureID2; } if (wmo.materials[matID].texture3 == wmobatch.mats[ti].texture3) { wmobatch.wmoRenderBatch[rb].materialID[2] = (uint)wmobatch.mats[ti].textureID3; } } wmobatch.wmoRenderBatch[rb].blendType = wmo.materials[matID].blendMode; wmobatch.wmoRenderBatch[rb].groupID = (uint)g; rb++; } } cache.worldModelBatches.Add(filename, wmobatch); return(wmobatch); }
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(); }