mirror of
https://git.do.srb2.org/STJr/UltimateZoneBuilder.git
synced 2024-11-23 20:32:34 +00:00
406 lines
12 KiB
C#
Executable file
406 lines
12 KiB
C#
Executable file
#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 = "/";
|
|
internal const int VERTEX_HEIGHT_THING_TYPE = 1504;
|
|
|
|
#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()
|
|
{
|
|
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<Vector3D> verts = new List<Vector3D>(12);
|
|
List<Face> faces = new List<Face>(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, maxZ + (maxZ - minZ)/2))
|
|
{
|
|
// Fial!
|
|
Cursor.Current = Cursors.Default;
|
|
|
|
// Return to base mode
|
|
General.Editing.ChangeMode(General.Editing.PreviousStableMode.Name);
|
|
return;
|
|
}
|
|
|
|
// Update caches
|
|
General.Map.Map.SnapAllToAccuracy();
|
|
General.Map.Map.UpdateConfiguration();
|
|
General.Map.Map.Update();
|
|
General.Map.IsChanged = true;
|
|
|
|
// Update things filter so that it includes added things
|
|
if(form.UseVertexHeights && !General.Map.UDMF) General.Map.ThingsFilter.Update();
|
|
|
|
// 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 bool CreateGeometry(List<Vector3D> verts, List<Face> faces, int maxZ)
|
|
{
|
|
if(verts.Count < 3 || faces.Count == 0)
|
|
{
|
|
MessageBox.Show("Cannot import the model: failed to find any suitable polygons!",
|
|
"Terrain Importer", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
return false;
|
|
}
|
|
|
|
MapSet map = General.Map.Map;
|
|
|
|
// Capacity checks
|
|
int totalverts = map.Vertices.Count + verts.Count;
|
|
int totalsides = map.Sidedefs.Count + faces.Count * 6;
|
|
int totallines = map.Linedefs.Count + faces.Count * 3;
|
|
int totalsectors = map.Sectors.Count + faces.Count;
|
|
int totalthings = 0;
|
|
if(form.UseVertexHeights && !General.Map.UDMF)
|
|
{
|
|
// We'll use vertex height things in non-udmf maps, if such things are defined in Game Configuration
|
|
totalthings = map.Things.Count + verts.Count;
|
|
}
|
|
|
|
if(totalverts > General.Map.FormatInterface.MaxVertices)
|
|
{
|
|
MessageBox.Show("Cannot import the model: resulting vertex count (" + totalverts
|
|
+ ") is larger than map format's maximum (" + General.Map.FormatInterface.MaxVertices + ")!",
|
|
"Terrain Importer", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
return false;
|
|
}
|
|
if(totalsides > General.Map.FormatInterface.MaxSidedefs)
|
|
{
|
|
MessageBox.Show("Cannot import the model: resulting sidedefs count (" + totalsides
|
|
+ ") is larger than map format's maximum (" + General.Map.FormatInterface.MaxSidedefs + ")!",
|
|
"Terrain Importer", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
return false;
|
|
}
|
|
if(totallines > General.Map.FormatInterface.MaxLinedefs)
|
|
{
|
|
MessageBox.Show("Cannot import the model: resulting sidedefs count (" + totallines
|
|
+ ") is larger than map format's maximum (" + General.Map.FormatInterface.MaxLinedefs + ")!",
|
|
"Terrain Importer", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
return false;
|
|
}
|
|
if(totalsectors > General.Map.FormatInterface.MaxSectors)
|
|
{
|
|
MessageBox.Show("Cannot import the model: resulting sidedefs count (" + totalsectors
|
|
+ ") is larger than map format's maximum (" + General.Map.FormatInterface.MaxSectors + ")!",
|
|
"Terrain Importer", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
return false;
|
|
}
|
|
if(totalthings > General.Map.FormatInterface.MaxThings)
|
|
{
|
|
MessageBox.Show("Cannot import the model: resulting things count (" + totalthings
|
|
+ ") is larger than map format's maximum (" + General.Map.FormatInterface.MaxThings + ")!",
|
|
"Terrain Importer", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
return false;
|
|
}
|
|
|
|
//make undo
|
|
General.Map.UndoRedo.CreateUndo("Import Terrain");
|
|
|
|
//prepare mapset
|
|
List<Linedef> newlines = new List<Linedef>();
|
|
map.BeginAddRemove();
|
|
map.SetCapacity(totalverts, totallines, totalsides, totalsectors, totalthings);
|
|
|
|
//terrain has many faces... let's create them
|
|
Dictionary<Vector3D, Vertex> newverts = new Dictionary<Vector3D, Vertex>();
|
|
foreach(Face face in faces)
|
|
{
|
|
// Create sector
|
|
Sector s = map.CreateSector();
|
|
s.Selected = true;
|
|
s.FloorHeight = (int)Math.Round((face.V1.z + face.V2.z + face.V3.z) / 3);
|
|
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
|
|
|
|
// And linedefs
|
|
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();
|
|
}
|
|
|
|
// Add slope things
|
|
if(form.UseVertexHeights && !General.Map.UDMF)
|
|
{
|
|
foreach(Vector3D pos in newverts.Keys)
|
|
{
|
|
Thing t = map.CreateThing();
|
|
General.Settings.ApplyDefaultThingSettings(t);
|
|
t.Type = VERTEX_HEIGHT_THING_TYPE;
|
|
t.Move(pos);
|
|
t.Selected = true;
|
|
}
|
|
}
|
|
|
|
//update new lines
|
|
foreach(Linedef l in newlines) l.ApplySidedFlags();
|
|
|
|
map.EndAddRemove();
|
|
return true;
|
|
}
|
|
|
|
private Linedef GetLine(Dictionary<Vector3D, Vertex> 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?
|
|
Sidedef side;
|
|
if(line == null)
|
|
{
|
|
line = General.Map.Map.CreateLinedef(start, end);
|
|
|
|
//create front sidedef and attach sector to it
|
|
side = General.Map.Map.CreateSidedef(line, true, sector);
|
|
}
|
|
else
|
|
{
|
|
//create back sidedef and attach sector to it
|
|
side = General.Map.Map.CreateSidedef(line, false, sector);
|
|
}
|
|
|
|
if(!form.UseVertexHeights) side.SetTextureLow(General.Map.Options.DefaultWallTexture);
|
|
line.Selected = true;
|
|
return line;
|
|
}
|
|
|
|
private Vertex GetVertex(Dictionary<Vector3D, Vertex> verts, Vector3D pos)
|
|
{
|
|
//already there?
|
|
if(verts.ContainsKey(pos)) return verts[pos];
|
|
|
|
//make a new one
|
|
Vertex v = General.Map.Map.CreateVertex(pos);
|
|
if(form.UseVertexHeights) 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<Vector3D> verts, List<Face> faces, ref int minZ, ref int maxZ)
|
|
{
|
|
using(StreamReader reader = File.OpenText(path))
|
|
{
|
|
string line;
|
|
int counter = 0;
|
|
float picoarse = (float)Math.Round(Angle2D.PI, 3);
|
|
|
|
while((line = reader.ReadLine()) != null)
|
|
{
|
|
counter++;
|
|
|
|
if(line.StartsWith("v "))
|
|
{
|
|
string[] parts = line.Split(space, StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
float x, y, z;
|
|
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
|
|
int px, py, pz;
|
|
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, StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
if(parts.Length != 4)
|
|
{
|
|
MessageBox.Show("Failed to parse face definition at line " + counter + ": only triangular 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;
|
|
|
|
// Skip face if vertex indexes match
|
|
if(verts[v1] == verts[v2] || verts[v1] == verts[v3] || verts[v2] == verts[v3]) continue;
|
|
|
|
// Skip face if it's vertical
|
|
Plane p = new Plane(verts[v1], verts[v2], verts[v3], true);
|
|
if((float)Math.Round(p.Normal.GetAngleZ(), 3) == picoarse) 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
|
|
}
|
|
}
|