private static StringBuilder CreateObjGeometry(List <Dictionary <string, List <WorldVertex[]> > > geometryByTexture, ref WavefrontExportSettings data) { StringBuilder obj = new StringBuilder(); Vector2D offset; const string vertexFormatter = "{0} {2} {1}\n"; Dictionary <Vector3D, int> uniqueVerts = new Dictionary <Vector3D, int>(); Dictionary <Vector3D, int> uniqueNormals = new Dictionary <Vector3D, int>(); Dictionary <PointF, int> uniqueUVs = new Dictionary <PointF, int>(); var vertexDataByTexture = new Dictionary <string, Dictionary <WorldVertex, VertexIndices> >(StringComparer.Ordinal); int pc = 0; int nc = 0; int uvc = 0; Vector3D tl = new Vector3D(double.MaxValue, double.MinValue, double.MinValue); Vector3D br = new Vector3D(double.MinValue, double.MaxValue, double.MaxValue); //optimize geometry foreach (Dictionary <string, List <WorldVertex[]> > dictionary in geometryByTexture) { foreach (KeyValuePair <string, List <WorldVertex[]> > group in dictionary) { Dictionary <WorldVertex, VertexIndices> vertsData = new Dictionary <WorldVertex, VertexIndices>(); foreach (WorldVertex[] verts in group.Value) { //vertex normals. biwa not sure why I need to invert the normal, but it seems to be necessary Vector3D n = new Vector3D(verts[0].nx, verts[0].ny, verts[0].nz).GetNormal() * -1; int ni; if (uniqueNormals.ContainsKey(n)) { ni = uniqueNormals[n]; } else { uniqueNormals.Add(n, ++nc); ni = nc; } foreach (WorldVertex v in verts) { if (vertsData.ContainsKey(v)) { continue; } VertexIndices indices = new VertexIndices(); indices.NormalIndex = ni; //vertex coords Vector3D vc = new Vector3D(v.x, v.y, v.z); if (uniqueVerts.ContainsKey(vc)) { indices.PositionIndex = uniqueVerts[vc]; } else { uniqueVerts.Add(vc, ++pc); indices.PositionIndex = pc; } //uv PointF uv = new PointF(v.u, v.v); if (uniqueUVs.ContainsKey(uv)) { indices.UVIndex = uniqueUVs[uv]; } else { uniqueUVs.Add(uv, ++uvc); indices.UVIndex = uvc; } vertsData.Add(v, indices); } } if (vertsData.Count > 0) { if (vertexDataByTexture.ContainsKey(group.Key)) { foreach (KeyValuePair <WorldVertex, VertexIndices> g in vertsData) { vertexDataByTexture[group.Key].Add(g.Key, g.Value); } } else { vertexDataByTexture.Add(group.Key, vertsData); } } } } // Get the dimensions of the model foreach (Dictionary <WorldVertex, VertexIndices> vdata in vertexDataByTexture.Values) { foreach (WorldVertex wv in vdata.Keys) { if (wv.x < tl.x) { tl.x = wv.x; } if (wv.x > br.x) { br.x = wv.x; } if (wv.y > tl.y) { tl.y = wv.y; } if (wv.y < br.y) { br.y = wv.y; } if (wv.z > tl.z) { tl.z = wv.z; } if (wv.z < br.z) { br.z = wv.z; } } } data.Radius = br.x - tl.x > tl.y - br.y ? (int)(tl.y - br.y) / 2 : (int)(br.x - tl.x) / 2; data.Height = (int)(tl.z - br.z); if (data.CenterModel) { offset = new Vector2D(tl.x + (br.x - tl.x) / 2.0, tl.y + (br.y - tl.y) / 2.0); } else { offset = new Vector2D(0.0, 0.0); } //write geometry //write vertices if (data.ExportForGZDoom) { foreach (KeyValuePair <Vector3D, int> group in uniqueVerts) { double z = (group.Key.z - (data.NormalizeLowestVertex ? br.z : 0)) * data.Scale * 1.2f; obj.Append(string.Format(CultureInfo.InvariantCulture, "v " + vertexFormatter, (group.Key.x - offset.x) * data.Scale, -(group.Key.y - offset.y) * data.Scale, z)); } } else { // biwa. Not sure why the x-axis is flipped here, since it will result in wrong normals when using the model directly in GZDoom. For this reason // I disabled the flipping above foreach (KeyValuePair <Vector3D, int> group in uniqueVerts) { double z = (group.Key.z - (data.NormalizeLowestVertex ? br.z : 0)) * data.Scale; obj.Append(string.Format(CultureInfo.InvariantCulture, "v " + vertexFormatter, -(group.Key.x - offset.x) * data.Scale, (group.Key.y - offset.y) * data.Scale, z)); } } //write normals foreach (KeyValuePair <Vector3D, int> group in uniqueNormals) { obj.Append(string.Format(CultureInfo.InvariantCulture, "vn " + vertexFormatter, group.Key.x, group.Key.y, group.Key.z)); } //write UV coords foreach (KeyValuePair <PointF, int> group in uniqueUVs) { obj.Append(string.Format(CultureInfo.InvariantCulture, "vt {0} {1}\n", group.Key.X, -group.Key.Y)); } // GZDoom ignores the material lib, so don't add it if the model is for GZDoom if (!data.ExportForGZDoom) { obj.Append("mtllib ").Append(data.ObjName + ".mtl").Append("\n"); } //write materials and surface indices foreach (Dictionary <string, List <WorldVertex[]> > dictionary in geometryByTexture) { foreach (KeyValuePair <string, List <WorldVertex[]> > group in dictionary) { //material obj.Append("usemtl ").Append(group.Key).Append("\n"); foreach (WorldVertex[] verts in group.Value) { //surface indices obj.Append("f"); foreach (WorldVertex v in verts) { VertexIndices vi = vertexDataByTexture[group.Key][v]; obj.Append(" " + vi.PositionIndex + "/" + vi.UVIndex + "/" + vi.NormalIndex); } obj.Append("\n"); } } } return(obj); }
private static void CreateObjFromSelection(ICollection <Sector> sectors, ref WavefrontExportSettings data) { BaseVisualMode mode = new BaseVisualMode(); bool renderingEffectsDisabled = false; if (!General.Settings.EnhancedRenderingEffects) { renderingEffectsDisabled = true; mode.ToggleEnhancedRendering(); } mode.RebuildElementData(); List <BaseVisualSector> visualSectors = new List <BaseVisualSector>(); //create visual geometry foreach (Sector s in sectors) { bool addvs = true; // Check if the sector has, or shares a line with a 3D floor control sector, and ignore it if necessary if (data.ExportForGZDoom && data.IgnoreControlSectors) { foreach (Sidedef sd in s.Sidedefs) { if (sd.Line.Action == 160) { addvs = false; break; } } } if (addvs) { BaseVisualSector bvs = mode.CreateBaseVisualSector(s); if (bvs != null) { visualSectors.Add(bvs); } } } if (visualSectors.Count == 0) { General.ErrorLogger.Add(ErrorType.Error, "OBJ Exporter: no visual sectors to export!"); return; } //sort geometry List <Dictionary <string, List <WorldVertex[]> > > geometryByTexture = SortGeometry(visualSectors, data.SkipTextures, !data.ExportForGZDoom); //restore vm settings if (renderingEffectsDisabled) { mode.ToggleEnhancedRendering(); } mode.Dispose(); //create obj StringBuilder obj = CreateObjGeometry(geometryByTexture, ref data); if (obj.Length == 0) { General.ErrorLogger.Add(ErrorType.Error, "OBJ Exporter: failed to create geometry!"); return; } //add header obj.Insert(0, "o " + General.Map.Options.LevelName + Environment.NewLine); //name obj.Insert(0, "# Created by Ultimate Doom Builder " + Application.ProductVersion + Environment.NewLine + Environment.NewLine); obj.Insert(0, "# " + General.Map.FileTitle + ", map " + General.Map.Options.LevelName + Environment.NewLine); data.Obj = obj.ToString(); string[] textures = new string[geometryByTexture[0].Keys.Count]; geometryByTexture[0].Keys.CopyTo(textures, 0); Array.Sort(textures); data.Textures = textures; string[] flats = new string[geometryByTexture[1].Keys.Count]; geometryByTexture[1].Keys.CopyTo(flats, 0); Array.Sort(flats); data.Flats = flats; data.Valid = true; }
public void Export(ICollection <Sector> sectors, WavefrontExportSettings settings) { CreateObjFromSelection(sectors, ref settings); if (!settings.Valid) { General.Interface.DisplayStatus(StatusType.Warning, "OBJ creation failed. Check 'Errors and Warnings' window for details."); return; } // Export Textures, but only of it's not exporting for GZDoom if (settings.ExportTextures && !settings.ExportForGZDoom) { //save all used textures if (settings.Textures != null) { foreach (string s in settings.Textures) { if (s == DEFAULT) { continue; } if (General.Map.Data.GetTextureExists(s)) { ImageData id = General.Map.Data.GetTextureImage(s); if (id.Width == 0 || id.Height == 0) { General.ErrorLogger.Add(ErrorType.Warning, "OBJ Exporter: texture \"" + s + "\" has invalid size (" + id.Width + "x" + id.Height + ")!"); continue; } Bitmap bmp = id.ExportBitmap(); lock (bmp) { string filepath = Path.Combine(settings.ObjPath, Path.GetDirectoryName(s), Path.GetFileNameWithoutExtension(s) + ".png"); // Make sure the directory is there Directory.CreateDirectory(Path.GetDirectoryName(filepath)); bmp.Save(filepath, ImageFormat.Png); } } else { General.ErrorLogger.Add(ErrorType.Warning, "OBJ Exporter: texture \"" + s + "\" does not exist!"); } } } if (settings.Flats != null) { foreach (string s in settings.Flats) { if (s == DEFAULT) { continue; } if (General.Map.Data.GetFlatExists(s)) { ImageData id = General.Map.Data.GetFlatImage(s); if (id.Width == 0 || id.Height == 0) { General.ErrorLogger.Add(ErrorType.Warning, "OBJ Exporter: flat \"" + s + "\" has invalid size (" + id.Width + "x" + id.Height + ")!"); continue; } Bitmap bmp = id.ExportBitmap(); // Handle duplicate names string flatname = s; if (settings.Textures != null && Array.IndexOf(settings.Textures, s) != -1) { flatname += "_FLAT"; } lock (bmp) { bmp.Save(Path.Combine(settings.ObjPath, Path.GetFileNameWithoutExtension(flatname) + ".PNG"), ImageFormat.Png); } } else { General.ErrorLogger.Add(ErrorType.Warning, "OBJ Exporter: flat \"" + s + "\" does not exist!"); } } } } //write obj string savePath; if (settings.ExportForGZDoom) { savePath = Path.Combine(settings.ModelPath, settings.ActorName + ".obj"); } else { savePath = Path.Combine(settings.ObjPath, settings.ObjName + ".obj"); } // Make sure the directory is there Directory.CreateDirectory(Path.GetDirectoryName(savePath)); using (StreamWriter sw = new StreamWriter(savePath, false)) sw.Write(settings.Obj); //create mtl StringBuilder mtl = new StringBuilder(); mtl.Append("# MTL for " + General.Map.FileTitle + ", map " + General.Map.Options.LevelName + Environment.NewLine); mtl.Append("# Created by Ultimate Doom Builder " + Application.ProductVersion + Environment.NewLine + Environment.NewLine); if (settings.Textures != null) { foreach (string s in settings.Textures) { if (s == DEFAULT) { continue; } string filepath = Path.Combine(settings.ObjPath, Path.GetDirectoryName(s), Path.GetFileNameWithoutExtension(s) + ".png"); mtl.Append("newmtl " + s + Environment.NewLine); mtl.Append("Kd 1.0 1.0 1.0" + Environment.NewLine); if (settings.ExportTextures) { mtl.Append("map_Kd " + filepath + Environment.NewLine); } mtl.Append(Environment.NewLine); } } if (settings.Flats != null) { foreach (string s in settings.Flats) { if (s == DEFAULT) { continue; } mtl.Append("newmtl " + s + Environment.NewLine); mtl.Append("Kd 1.0 1.0 1.0" + Environment.NewLine); if (settings.ExportTextures) { // Handle duplicate names string flatsuffix = string.Empty; if (settings.Textures != null && Array.IndexOf(settings.Textures, s) != -1) { flatsuffix = "_FLAT"; } string filepath = Path.Combine(settings.ObjPath, Path.GetDirectoryName(s), Path.GetFileNameWithoutExtension(s) + flatsuffix + ".png"); mtl.Append("map_Kd " + Path.Combine(settings.ObjPath, filepath) + Environment.NewLine); } mtl.Append(Environment.NewLine); } } if (!settings.ExportForGZDoom) { // Make sure the directory is there Directory.CreateDirectory(Path.GetDirectoryName(savePath)); string mtlPath = Path.Combine(Path.GetDirectoryName(savePath), Path.GetFileNameWithoutExtension(savePath) + ".mtl"); // Write mtl (only if not exporting for GZDoom, since it will be ignored anyway using (StreamWriter sw = new StreamWriter(mtlPath, false)) sw.Write(mtl.ToString()); } else { // Create ZScript or DECORATE Stream stream; string path = Path.Combine(settings.ActorPath, settings.ActorName); if (settings.ZScript) { stream = BuilderPlug.Me.GetResourceStream("ObjExportZScriptTemplate.txt"); path += ".zs"; } else { stream = BuilderPlug.Me.GetResourceStream("ObjExportDecorateTemplate.txt"); path += ".txt"; } using (StreamReader reader = new StreamReader(stream, Encoding.ASCII)) { string template = reader.ReadToEnd(); template = template.Replace("{ActorName}", settings.ActorName); template = template.Replace("{Sprite}", settings.Sprite); template = template.Replace("{FlagNoGravity}", settings.NoGravity ? "+NOGRAVITY" : ""); template = template.Replace("{FlagSpawnOnCeiling}", settings.SpawnOnCeiling ? "+SPAWNCEILING" : ""); template = template.Replace("{FlagSolid}", settings.Solid ? "+SOLID" : ""); template = template.Replace("{FlagInvulnerable}", settings.Solid ? "+INVULNERABLE" : ""); template = template.Replace("{FlagNoDamage}", settings.Solid ? "+NODAMAGE" : ""); template = template.Replace("{FlagShootable}", settings.Solid ? "+SHOOTABLE" : ""); template = template.Replace("{FlagNotAutoAimed}", settings.Solid ? "+NOTAUTOAIMED" : ""); template = template.Replace("{FlagNeverTarget}", settings.Solid ? "+NEVERTARGET" : ""); template = template.Replace("{FlagDontThrust}", settings.Solid ? "+DONTTHRUST" : ""); template = template.Replace("{PropRadius}", settings.Radius.ToString()); template = template.Replace("{PropHeight}", settings.Height.ToString()); // Make sure the directory is there Directory.CreateDirectory(Path.GetDirectoryName(path)); using (StreamWriter sw = new StreamWriter(path, false)) sw.Write(template); } // Create MODELDEF stream = BuilderPlug.Me.GetResourceStream("ObjExportModeldefTemplate.txt"); using (StreamReader reader = new StreamReader(stream, Encoding.ASCII)) { path = Path.Combine(settings.BasePath, "modeldef." + settings.ActorName + ".txt"); string template = reader.ReadToEnd(); // The path to the model is relative to the base path, so generate the base path string basepath = settings.BasePath.Trim(); string modelpath = settings.ModelPath.Trim(); // Make sue there's a directory separator at the end of the paths, otherwise it'll not work correctly if (!basepath.EndsWith(Path.DirectorySeparatorChar.ToString())) { basepath += Path.DirectorySeparatorChar; } if (!modelpath.EndsWith(Path.DirectorySeparatorChar.ToString())) { modelpath += Path.DirectorySeparatorChar; } Uri baseUri = new Uri(basepath); Uri modelUri = new Uri(modelpath); Uri relativeUri = baseUri.MakeRelativeUri(modelUri); string relativepath = Uri.UnescapeDataString(relativeUri.OriginalString); template = template.Replace("{ActorName}", settings.ActorName); template = template.Replace("{ModelPath}", relativepath); template = template.Replace("{Sprite}", settings.Sprite); // Make sure the directory is there Directory.CreateDirectory(Path.GetDirectoryName(path)); using (StreamWriter sw = new StreamWriter(path, false)) sw.Write(template); } } //done General.Interface.DisplayStatus(StatusType.Warning, "Geometry exported to \"" + savePath); }
public void Export(ICollection <Sector> sectors, WavefrontExportSettings settings) { CreateObjFromSelection(sectors, ref settings); if (!settings.Valid) { General.Interface.DisplayStatus(StatusType.Warning, "OBJ creation failed. Check 'Errors and Warnings' window for details."); return; } if (settings.ExportTextures) { //save all used textures if (settings.Textures != null) { foreach (string s in settings.Textures) { if (s == DEFAULT) { continue; } if (General.Map.Data.GetTextureExists(s)) { ImageData id = General.Map.Data.GetTextureImage(s); if (id.Width == 0 || id.Height == 0) { General.ErrorLogger.Add(ErrorType.Warning, "OBJ Exporter: texture \"" + s + "\" has invalid size (" + id.Width + "x" + id.Height + ")!"); continue; } Bitmap bmp = id.ExportBitmap(); lock (bmp) { bmp.Save(Path.Combine(settings.ObjPath, Path.GetFileNameWithoutExtension(s) + ".PNG"), ImageFormat.Png); } } else { General.ErrorLogger.Add(ErrorType.Warning, "OBJ Exporter: texture \"" + s + "\" does not exist!"); } } } if (settings.Flats != null) { foreach (string s in settings.Flats) { if (s == DEFAULT) { continue; } if (General.Map.Data.GetFlatExists(s)) { ImageData id = General.Map.Data.GetFlatImage(s); if (id.Width == 0 || id.Height == 0) { General.ErrorLogger.Add(ErrorType.Warning, "OBJ Exporter: flat \"" + s + "\" has invalid size (" + id.Width + "x" + id.Height + ")!"); continue; } Bitmap bmp = id.ExportBitmap(); // Handle duplicate names string flatname = s; if (settings.Textures != null && Array.IndexOf(settings.Textures, s) != -1) { flatname += "_FLAT"; } lock (bmp) { bmp.Save(Path.Combine(settings.ObjPath, Path.GetFileNameWithoutExtension(flatname) + ".PNG"), ImageFormat.Png); } } else { General.ErrorLogger.Add(ErrorType.Warning, "OBJ Exporter: flat \"" + s + "\" does not exist!"); } } } } //write obj string savePath = Path.Combine(settings.ObjPath, settings.ObjName); using (StreamWriter sw = new StreamWriter(savePath + ".obj", false)) sw.Write(settings.Obj); //create mtl StringBuilder mtl = new StringBuilder(); mtl.Append("# MTL for " + General.Map.FileTitle + ", map " + General.Map.Options.LevelName + Environment.NewLine); mtl.Append("# Created by Ultimate Doom Builder " + Application.ProductVersion + Environment.NewLine + Environment.NewLine); if (settings.Textures != null) { foreach (string s in settings.Textures) { if (s == DEFAULT) { continue; } mtl.Append("newmtl " + s.ToUpperInvariant() + Environment.NewLine); mtl.Append("Kd 1.0 1.0 1.0" + Environment.NewLine); if (settings.ExportTextures) { mtl.Append("map_Kd " + Path.Combine(settings.ObjPath, s.ToUpperInvariant() + ".PNG") + Environment.NewLine); } mtl.Append(Environment.NewLine); } } if (settings.Flats != null) { foreach (string s in settings.Flats) { if (s == DEFAULT) { continue; } mtl.Append("newmtl " + s.ToUpperInvariant() + Environment.NewLine); mtl.Append("Kd 1.0 1.0 1.0" + Environment.NewLine); if (settings.ExportTextures) { // Handle duplicate names string flatname = s; if (settings.Textures != null && Array.IndexOf(settings.Textures, s) != -1) { flatname += "_FLAT"; } mtl.Append("map_Kd " + Path.Combine(settings.ObjPath, flatname.ToUpperInvariant() + ".PNG") + Environment.NewLine); } mtl.Append(Environment.NewLine); } } //write mtl using (StreamWriter sw = new StreamWriter(savePath + ".mtl", false)) sw.Write(mtl.ToString()); //done General.Interface.DisplayStatus(StatusType.Warning, "Geometry exported to \"" + savePath + ".obj\""); }
private static StringBuilder CreateObjGeometry(List <Dictionary <string, List <WorldVertex[]> > > geometryByTexture, WavefrontExportSettings data) { StringBuilder obj = new StringBuilder(); const string vertexFormatter = "{0} {2} {1}\n"; Dictionary <Vector3D, int> uniqueVerts = new Dictionary <Vector3D, int>(); Dictionary <Vector3D, int> uniqueNormals = new Dictionary <Vector3D, int>(); Dictionary <PointF, int> uniqueUVs = new Dictionary <PointF, int>(); var vertexDataByTexture = new Dictionary <string, Dictionary <WorldVertex, VertexIndices> >(StringComparer.Ordinal); int pc = 0; int nc = 0; int uvc = 0; //optimize geometry foreach (Dictionary <string, List <WorldVertex[]> > dictionary in geometryByTexture) { foreach (KeyValuePair <string, List <WorldVertex[]> > group in dictionary) { Dictionary <WorldVertex, VertexIndices> vertsData = new Dictionary <WorldVertex, VertexIndices>(); foreach (WorldVertex[] verts in group.Value) { //vertex normals Vector3D n = new Vector3D(verts[0].nx, verts[0].ny, verts[0].nz).GetNormal(); int ni; if (uniqueNormals.ContainsKey(n)) { ni = uniqueNormals[n]; } else { uniqueNormals.Add(n, ++nc); ni = nc; } foreach (WorldVertex v in verts) { if (vertsData.ContainsKey(v)) { continue; } VertexIndices indices = new VertexIndices(); indices.NormalIndex = ni; //vertex coords Vector3D vc = new Vector3D(v.x, v.y, v.z); if (uniqueVerts.ContainsKey(vc)) { indices.PositionIndex = uniqueVerts[vc]; } else { uniqueVerts.Add(vc, ++pc); indices.PositionIndex = pc; } //uv PointF uv = new PointF(v.u, v.v); if (uniqueUVs.ContainsKey(uv)) { indices.UVIndex = uniqueUVs[uv]; } else { uniqueUVs.Add(uv, ++uvc); indices.UVIndex = uvc; } vertsData.Add(v, indices); } } if (vertsData.Count > 0) { if (vertexDataByTexture.ContainsKey(group.Key)) { foreach (KeyValuePair <WorldVertex, VertexIndices> g in vertsData) { vertexDataByTexture[group.Key].Add(g.Key, g.Value); } } else { vertexDataByTexture.Add(group.Key, vertsData); } } } } //write geometry //write vertices if (data.FixScale) { foreach (KeyValuePair <Vector3D, int> group in uniqueVerts) { obj.Append(string.Format(CultureInfo.InvariantCulture, "v " + vertexFormatter, -group.Key.x * data.Scale, group.Key.y * data.Scale, group.Key.z * data.Scale * 1.2f)); } } else { foreach (KeyValuePair <Vector3D, int> group in uniqueVerts) { obj.Append(string.Format(CultureInfo.InvariantCulture, "v " + vertexFormatter, -group.Key.x * data.Scale, group.Key.y * data.Scale, group.Key.z * data.Scale)); } } //write normals foreach (KeyValuePair <Vector3D, int> group in uniqueNormals) { obj.Append(string.Format(CultureInfo.InvariantCulture, "vn " + vertexFormatter, group.Key.x, group.Key.y, group.Key.z)); } //write UV coords foreach (KeyValuePair <PointF, int> group in uniqueUVs) { obj.Append(string.Format(CultureInfo.InvariantCulture, "vt {0} {1}\n", group.Key.X, -group.Key.Y)); } //write material library obj.Append("mtllib ").Append(data.ObjName + ".mtl").Append("\n"); //write materials and surface indices foreach (Dictionary <string, List <WorldVertex[]> > dictionary in geometryByTexture) { foreach (KeyValuePair <string, List <WorldVertex[]> > group in dictionary) { //material obj.Append("usemtl ").Append(group.Key).Append("\n"); foreach (WorldVertex[] verts in group.Value) { //surface indices obj.Append("f"); foreach (WorldVertex v in verts) { VertexIndices vi = vertexDataByTexture[group.Key][v]; obj.Append(" " + vi.PositionIndex + "/" + vi.UVIndex + "/" + vi.NormalIndex); } obj.Append("\n"); } } } return(obj); }
private static void CreateObjFromSelection(ICollection <Sector> sectors, ref WavefrontExportSettings data) { BaseVisualMode mode = new BaseVisualMode(); bool renderingEffectsDisabled = false; if (!General.Settings.GZDoomRenderingEffects) { renderingEffectsDisabled = true; mode.ToggleGZDoomRenderingEffects(); } mode.RebuildElementData(); List <BaseVisualSector> visualSectors = new List <BaseVisualSector>(); //create visual geometry foreach (Sector s in sectors) { BaseVisualSector bvs = mode.CreateBaseVisualSector(s); if (bvs != null) { visualSectors.Add(bvs); } } if (visualSectors.Count == 0) { General.ErrorLogger.Add(ErrorType.Error, "OBJ Exporter: no visual sectors to export!"); return; } //sort geometry List <Dictionary <string, List <WorldVertex[]> > > geometryByTexture = SortGeometry(visualSectors); //restore vm settings if (renderingEffectsDisabled) { mode.ToggleGZDoomRenderingEffects(); } mode.Dispose(); //create obj StringBuilder obj = CreateObjGeometry(geometryByTexture, data); if (obj.Length == 0) { General.ErrorLogger.Add(ErrorType.Error, "OBJ Exporter: failed to create geometry!"); return; } //add header obj.Insert(0, "o " + General.Map.Options.LevelName + Environment.NewLine); //name obj.Insert(0, "# Created by 3DGE Builder " + Application.ProductVersion + Environment.NewLine + Environment.NewLine); obj.Insert(0, "# " + General.Map.FileTitle + ", map " + General.Map.Options.LevelName + Environment.NewLine); data.Obj = obj.ToString(); string[] textures = new string[geometryByTexture[0].Keys.Count]; geometryByTexture[0].Keys.CopyTo(textures, 0); Array.Sort(textures); data.Textures = textures; string[] flats = new string[geometryByTexture[1].Keys.Count]; geometryByTexture[1].Keys.CopyTo(flats, 0); Array.Sort(flats); data.Flats = flats; data.Valid = true; }