#region ================== Namespaces using System; using System.Collections.Generic; using CodeImp.DoomBuilder.Editing; using System.Windows.Forms; using CodeImp.DoomBuilder.Geometry; using System.IO; using System.Globalization; using CodeImp.DoomBuilder.Windows; using CodeImp.DoomBuilder.Map; #endregion namespace CodeImp.DoomBuilder.BuilderEffects { [EditMode(DisplayName = "Terrain Importer", SwitchAction = "importobjasterrain", Volatile = true, UseByDefault = true, AllowCopyPaste = false)] public class ImportObjAsTerrainMode : ClassicMode { #region ================== Constants private readonly static char[] space = { ' ' }; private const string slash = "/"; #endregion #region ================== Variables private struct Face { public readonly Vector3D V1; public readonly Vector3D V2; public readonly Vector3D V3; public Face(Vector3D v1, Vector3D v2, Vector3D v3) { V1 = v1; V2 = v2; V3 = v3; } } private readonly ObjImportSettingsForm form; #endregion #region ================== Properties internal enum UpAxis { Y, Z, X } #endregion #region ================== Constructor public ImportObjAsTerrainMode() { form = new ObjImportSettingsForm(); } #endregion #region ================== Methods public override void OnEngage() { if(!General.Map.UDMF) { General.Interface.DisplayStatus(StatusType.Warning, "Terrain importer works only in UDMF map format!"); OnCancel(); } base.OnEngage(); General.Map.Map.ClearAllSelected(); //show interface if(form.ShowDialog() == DialogResult.OK && File.Exists(form.FilePath)) { OnAccept(); } else { OnCancel(); } } public override void OnAccept() { Cursor.Current = Cursors.AppStarting; General.Interface.DisplayStatus(StatusType.Busy, "Creating geometry..."); // Collections! Everyone loves them! List verts = new List(12); List faces = new List(4); int minZ = int.MaxValue; int maxZ = int.MinValue; // Read .obj, create and select sectors if(!ReadGeometry(form.FilePath, form.ObjScale, form.UpAxis, verts, faces, ref minZ, ref maxZ) || !CreateGeometry(verts, faces, minZ, maxZ + (maxZ - minZ)/2)) { // Fial! Cursor.Current = Cursors.Default; // Return to base mode General.Editing.ChangeMode(General.Editing.PreviousStableMode.Name); } // Update caches General.Map.Map.Update(); General.Map.IsChanged = true; // Done Cursor.Current = Cursors.Default; // Switch to Edit Selection mode General.Editing.ChangeMode("EditSelectionMode", true); } public override void OnCancel() { // Cancel base class base.OnCancel(); // Return to base mode General.Editing.ChangeMode(General.Editing.PreviousStableMode.Name); } #endregion #region ================== Geometry creation private static bool CreateGeometry(List verts, List faces, int minZ, int maxZ) { //make undo General.Map.UndoRedo.CreateUndo("Import Terrain"); //prepare mapset List newlines = new List(); MapSet map = General.Map.Map; map.BeginAddRemove(); map.SetCapacity(map.Vertices.Count + verts.Count, map.Linedefs.Count + faces.Count * 3, map.Sidedefs.Count + faces.Count * 3, map.Sectors.Count + faces.Count, 0); //terrain has many faces... let's create them Dictionary newverts = new Dictionary(); foreach(Face face in faces) { Sector s = map.CreateSector(); s.Selected = true; s.FloorHeight = minZ; s.CeilHeight = maxZ; s.Brightness = General.Settings.DefaultBrightness; //todo: allow user to change this s.SetCeilTexture(General.Map.Config.SkyFlatName); s.SetFloorTexture(General.Map.Options.DefaultFloorTexture); //todo: allow user to change this Linedef newline = GetLine(newverts, s, face.V1, face.V2); if(newline != null) newlines.Add(newline); newline = GetLine(newverts, s, face.V2, face.V3); if(newline != null) newlines.Add(newline); newline = GetLine(newverts, s, face.V3, face.V1); if(newline != null) newlines.Add(newline); s.UpdateCache(); } //update new lines foreach(Linedef l in newlines) l.ApplySidedFlags(); map.EndAddRemove(); return true; } private static Linedef GetLine(Dictionary verts, Sector sector, Vector3D v1, Vector3D v2) { Linedef line = null; //get start and end verts Vertex start = GetVertex(verts, v1); Vertex end = GetVertex(verts, v2); //check if the line is already created foreach(Linedef l in start.Linedefs) { if(l.End == end || l.Start == end) { line = l; break; } } //create a new line? if(line == null) { line = General.Map.Map.CreateLinedef(start, end); //create front sidedef and attach sector to it General.Map.Map.CreateSidedef(line, true, sector); } else { //create back sidedef and attach sector to it General.Map.Map.CreateSidedef(line, false, sector); } line.Selected = true; return line; } private static Vertex GetVertex(Dictionary verts, Vector3D pos) { //already there? if(verts.ContainsKey(pos)) return verts[pos]; //make a new one Vertex v = General.Map.Map.CreateVertex(pos); v.ZFloor = pos.z; verts.Add(pos, v); return v; } #endregion #region ================== .obj import private static bool ReadGeometry(string path, float scale, UpAxis axis, List verts, List faces, ref int minZ, ref int maxZ) { using(StreamReader reader = File.OpenText(path)) { string line; float x, y, z; int px, py, pz; int counter = 0; while((line = reader.ReadLine()) != null) { counter++; if(line.StartsWith("v ")) { string[] parts = line.Split(space); if(parts.Length != 4 || !float.TryParse(parts[1], NumberStyles.Float, CultureInfo.InvariantCulture, out x) || !float.TryParse(parts[2], NumberStyles.Float, CultureInfo.InvariantCulture, out y) || !float.TryParse(parts[3], NumberStyles.Float, CultureInfo.InvariantCulture, out z)) { MessageBox.Show("Failed to parse vertex definition at line " + counter + "!", "Terrain Importer", MessageBoxButtons.OK, MessageBoxIcon.Error); return false; } //apply up axis switch (axis) { case UpAxis.Y: px = (int)Math.Round(x * scale); py = (int)Math.Round(-z * scale); pz = (int)Math.Round(y * scale); break; case UpAxis.Z: px = (int)Math.Round(-x * scale); py = (int)Math.Round(-y * scale); pz = (int)Math.Round(z * scale); break; case UpAxis.X: px = (int)Math.Round(-y * scale); py = (int)Math.Round(-z * scale); pz = (int)Math.Round(x * scale); break; default: //same as UpAxis.Y px = (int)Math.Round(x * scale); py = (int)Math.Round(-z * scale); pz = (int)Math.Round(y * scale); break; } if(maxZ < pz) maxZ = pz; if(minZ > pz) minZ = pz; verts.Add(new Vector3D(px, py, pz)); } else if(line.StartsWith("f ")) { string[] parts = line.Split(space); if(parts.Length != 4) { MessageBox.Show("Failed to parse face definition at line " + counter + ": only triangle faces are supported!", "Terrain Importer", MessageBoxButtons.OK, MessageBoxIcon.Error); return false; } //.obj vertex indices are 1-based int v1 = ReadVertexIndex(parts[1]) - 1; int v2 = ReadVertexIndex(parts[2]) - 1; int v3 = ReadVertexIndex(parts[3]) - 1; if(verts[v1] == verts[v2] || verts[v1] == verts[v3] || verts[v2] == verts[v3]) continue; faces.Add(new Face(verts[v3], verts[v2], verts[v1])); } } } return true; } private static int ReadVertexIndex(string def) { int slashpos = def.IndexOf(slash); if(slashpos != -1) def = def.Substring(0, slashpos); return int.Parse(def); } #endregion } }