diff --git a/Sledge.BspEditor/Providers/ObjProvider.cs b/Sledge.BspEditor/Providers/ObjBspSourceProvider.cs
similarity index 50%
rename from Sledge.BspEditor/Providers/ObjProvider.cs
rename to Sledge.BspEditor/Providers/ObjBspSourceProvider.cs
index d1ec44019..fe03d9bb6 100644
--- a/Sledge.BspEditor/Providers/ObjProvider.cs
+++ b/Sledge.BspEditor/Providers/ObjBspSourceProvider.cs
@@ -1,71 +1,74 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel.Composition;
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Numerics;
+using System.Text;
+using System.Threading.Tasks;
+using Sledge.BspEditor.Environment;
+using Sledge.BspEditor.Primitives;
+using Sledge.BspEditor.Primitives.MapObjectData;
+using Sledge.BspEditor.Primitives.MapObjects;
using Sledge.Common;
+using Sledge.Common.Shell.Documents;
using Sledge.DataStructures.Geometric;
-using Sledge.DataStructures.MapObjects;
+using Plane = Sledge.DataStructures.Geometric.Plane;
-namespace Sledge.Providers.Map
+namespace Sledge.BspEditor.Providers
{
- // Parts of this code is borrowed from the Helix Toolkit: http://helixtoolkit.codeplex.com, license: MIT
- // http://www.martinreddy.net/gfx/3d/OBJ.spec
- ///
- /// OBJ is a model format and does not have the intersecting planes limitation.
- /// Detection of a convex solid is performed and on the off chance it is okay a solid is generated,
- /// but in most cases the loader must fall back to tetrehedrons.
- /// OBJ also embeds textures which doesn't work the the standard resource model of a Sledge map:-
- /// all texture data is ignored and untextured faces are generated.
- ///
- public class ObjProvider : MapProvider
+ [Export(typeof(IBspSourceProvider))]
+ public class ObjBspSourceProvider : IBspSourceProvider
{
- protected override IEnumerable GetFormatFeatures()
+ private static readonly IEnumerable SupportedTypes = new List
{
- return new[]
- {
- MapFeature.Solids
- };
- }
+ // Sledge only supports solids in the OBJ format
+ typeof(Solid),
+ };
+
+ public IEnumerable SupportedDataTypes => SupportedTypes;
- protected override bool IsValidForFileName(string filename)
+ public IEnumerable SupportedFileExtensions { get; } = new[]
{
- return filename.EndsWith(".obj");
- }
+ new FileExtensionInfo("Wavefront model format", ".obj")
+ };
- private string CleanLine(string line)
+ public Task Load(Stream stream, IEnvironment environment)
{
- if (line == null) return null;
- return line.StartsWith("#") ? "" : line.Trim();
+ return Task.Run(() =>
+ {
+ using (var reader = new StreamReader(stream, Encoding.ASCII, true, 1024, false))
+ {
+ var result = new BspFileLoadResult();
+
+ var map = new Map();
+
+ Read(map, reader);
+
+ result.Map = map;
+ return result;
+ }
+ });
}
- protected override DataStructures.MapObjects.Map GetFromStream(Stream stream)
+ public Task Save(Stream stream, Map map)
{
- using (var reader = new StreamReader(stream))
+ return Task.Run(() =>
{
- var map = new DataStructures.MapObjects.Map();
- Read(map, reader);
- /*
- var allentities = ReadAllEntities(reader, map.IDGenerator);
- var worldspawn = allentities.FirstOrDefault(x => x.EntityData.Name == "worldspawn")
- ?? new Entity(0) { EntityData = { Name = "worldspawn" } };
- allentities.Remove(worldspawn);
- map.WorldSpawn.EntityData = worldspawn.EntityData;
- allentities.ForEach(x => x.SetParent(map.WorldSpawn));
- foreach (var obj in worldspawn.Children.ToArray())
+ using (var writer = new StreamWriter(stream, Encoding.ASCII, 1024, true))
{
- obj.SetParent(map.WorldSpawn);
+ Write(map, writer);
}
- map.WorldSpawn.UpdateBoundingBox(false);
- */
- return map;
- }
+ });
}
+ #region Reading
+
private struct ObjFace
{
- public string Group { get; set; }
- public List Vertices { get; set; }
+ public string Group { get; }
+ public List Vertices { get; }
public ObjFace(string group, IEnumerable vertices) : this()
{
@@ -74,12 +77,20 @@ public ObjFace(string group, IEnumerable vertices) : this()
}
}
- private void Read(DataStructures.MapObjects.Map map, StreamReader reader)
+ private string CleanLine(string line)
+ {
+ if (line == null) return null;
+ return line.StartsWith("#") ? "" : line.Trim();
+ }
+
+ private void Read(Map map, StreamReader reader)
{
- var points = new List();
+ const NumberStyles ns = NumberStyles.Float;
+
+ var points = new List();
var faces = new List();
var currentGroup = "default";
- var scale = 100m;
+ var scale = 100f;
string line;
while ((line = reader.ReadLine()) != null)
@@ -87,16 +98,14 @@ private void Read(DataStructures.MapObjects.Map map, StreamReader reader)
if (line.StartsWith("# Scale: "))
{
var num = line.Substring(9);
- decimal s;
- if (decimal.TryParse(num, NumberStyles.Float, CultureInfo.InvariantCulture, out s))
+ if (float.TryParse(num, NumberStyles.Float, CultureInfo.InvariantCulture, out var s))
{
scale = s;
}
}
line = CleanLine(line);
- string keyword, values;
- SplitLine(line, out keyword, out values);
+ SplitLine(line, out var keyword, out var values);
if (String.IsNullOrWhiteSpace(keyword)) continue;
var vals = (values ?? "").Split(' ').Where(x => !String.IsNullOrWhiteSpace(x)).ToArray();
@@ -104,7 +113,8 @@ private void Read(DataStructures.MapObjects.Map map, StreamReader reader)
{
// Things I care about
case "v": // geometric vertices
- points.Add(Coordinate.Parse(vals[0], vals[1], vals[2]) * scale);
+ var vec = NumericsExtensions.Parse(vals[0], vals[1], vals[2], ns, CultureInfo.InvariantCulture);
+ points.Add(vec * scale);
break;
case "f": // face
faces.Add(new ObjFace(currentGroup, vals.Select(x => ParseFaceIndex(points, x))));
@@ -180,7 +190,7 @@ private void Read(DataStructures.MapObjects.Map map, StreamReader reader)
// not relevant
break;
- #endregion
+ #endregion
}
}
@@ -196,22 +206,23 @@ private void Read(DataStructures.MapObjects.Map map, StreamReader reader)
{
foreach (var face in solid.Faces)
{
- face.Colour = solid.Colour;
- face.AlignTextureToFace();
+ face.Texture.AlignToNormal(face.Plane.Normal);
}
- solid.SetParent(map.WorldSpawn);
+
+ solid.Hierarchy.Parent = map.Root;
}
+
+ map.Root.DescendantsChanged();
}
- private IEnumerable CreateSolids(DataStructures.MapObjects.Map map, List points, IEnumerable objFaces)
+ private IEnumerable CreateSolids(Map map, List points, IEnumerable objFaces)
{
var faces = objFaces.Select(x => CreateFace(map, points, x)).ToList();
// See if the solid is valid
- var solid = new Solid(map.IDGenerator.GetNextObjectID());
- solid.Colour = Colour.GetRandomBrushColour();
- solid.Faces.AddRange(faces);
- faces.ForEach(x => x.Parent = solid);
+ var solid = new Solid(map.NumberGenerator.Next("MapObject"));
+ solid.Data.Add(new ObjectColor(Colour.GetRandomBrushColour()));
+ solid.Data.AddRange(faces);
if (solid.IsValid())
{
// Do an additional check to ensure that all edges are shared
@@ -227,16 +238,17 @@ private IEnumerable CreateSolids(DataStructures.MapObjects.Map map, List<
// Not a valid solid, decompose into tetrahedrons/etc
foreach (var face in faces)
{
- var polygon = new Polygon(face.Vertices.Select(x => x.Location));
+ var polygon = face.ToPolygon();
if (!polygon.IsValid() || !polygon.IsConvex())
{
// tetrahedrons
- foreach (var triangle in face.GetTriangles())
+ foreach (var triangle in GetTriangles(face))
{
- var tf = new Face(map.IDGenerator.GetNextFaceID());
- tf.Plane = new Plane(triangle[0].Location, triangle[1].Location, triangle[2].Location);
- tf.Vertices.AddRange(triangle.Select(x => new Vertex(x.Location, tf)));
- tf.UpdateBoundingBox();
+ var tf = new Face(map.NumberGenerator.Next("Face"))
+ {
+ Plane = new Plane(triangle[0], triangle[1], triangle[2])
+ };
+ tf.Vertices.AddRange(triangle);
yield return SolidifyFace(map, tf);
}
}
@@ -248,44 +260,65 @@ private IEnumerable CreateSolids(DataStructures.MapObjects.Map map, List<
}
}
- private Solid SolidifyFace(DataStructures.MapObjects.Map map, Face face)
+ private IEnumerable GetTriangles(Face face)
{
- var solid = new Solid(map.IDGenerator.GetNextObjectID());
- solid.Colour = Colour.GetRandomBrushColour();
- solid.Faces.Add(face);
- face.Parent = solid;
- var center = face.Vertices.Aggregate(Coordinate.Zero, (sum, v) => sum + v.Location) / face.Vertices.Count;
+ for (var i = 1; i < face.Vertices.Count - 1; i++)
+ {
+ yield return new[]
+ {
+ face.Vertices[0],
+ face.Vertices[i],
+ face.Vertices[i + 1]
+ };
+ }
+ }
+
+ private Solid SolidifyFace(Map map, Face face)
+ {
+ var solid = new Solid(map.NumberGenerator.Next("MapObject"));
+ solid.Data.Add(new ObjectColor(Colour.GetRandomBrushColour()));
+ solid.Data.Add(face);
+
+ var center = face.Vertices.Aggregate(Vector3.Zero, (sum, v) => sum + v) / face.Vertices.Count;
var offset = center - face.Plane.Normal * 5;
for (var i = 0; i < face.Vertices.Count; i++)
{
var v1 = face.Vertices[i];
var v2 = face.Vertices[(i + 1) % face.Vertices.Count];
- var f = new Face(map.IDGenerator.GetNextFaceID());
- f.Parent = solid;
- f.Plane = new Plane(v1.Location, offset, v2.Location);
- f.Parent = solid;
- f.Vertices.Add(new Vertex(offset, f));
- f.Vertices.Add(new Vertex(v2.Location, f));
- f.Vertices.Add(new Vertex(v1.Location, f));
- f.UpdateBoundingBox();
-
- solid.Faces.Add(f);
+
+ var f = new Face(map.NumberGenerator.Next("Face"))
+ {
+ Plane = new Plane(v1, offset, v2)
+ };
+
+ f.Vertices.Add(offset);
+ f.Vertices.Add(v2);
+ f.Vertices.Add(v1);
+
+ solid.Data.Add(f);
}
+
+ solid.DescendantsChanged();
return solid;
}
- private Face CreateFace(DataStructures.MapObjects.Map map, List points, ObjFace objFace)
+ private Face CreateFace(Map map, List points, ObjFace objFace)
{
var verts = objFace.Vertices.Select(x => points[x]).ToList();
- var f = new Face(map.IDGenerator.GetNextFaceID());
- f.Plane = new Plane(verts[2], verts[1], verts[0]);
- f.Vertices.AddRange(verts.Select(x => new Vertex(x, f)).Reverse());
- f.UpdateBoundingBox();
+
+ var f = new Face(map.NumberGenerator.Next("Face"))
+ {
+ Plane = new Plane(verts[2], verts[1], verts[0])
+ };
+
+ verts.Reverse();
+ f.Vertices.AddRange(verts);
+
return f;
}
- private int ParseFaceIndex(List list, string index)
+ private int ParseFaceIndex(List list, string index)
{
if (index.Contains('/')) index = index.Substring(0, index.IndexOf('/'));
var idx = int.Parse(index);
@@ -316,86 +349,47 @@ private static void SplitLine(string line, out string keyword, out string argume
arguments = line.Substring(idx + 1);
}
- public static IEnumerable SplitValues(string values)
- {
- return values.Split(' ', '\t').Where(x => !String.IsNullOrWhiteSpace(x));
- }
+ #endregion
+
+ #region Writing
- protected override void SaveToStream(Stream stream, DataStructures.MapObjects.Map map)
+ private void Write(Map map, StreamWriter writer)
{
- /*
- // Csg version
-
- var csg = new CsgSolid();
- foreach (var mo in map.WorldSpawn.Find(x => x is Solid).OfType())
- {
- csg = csg.Union(new CsgSolid(mo.Faces.Select(x => new Polygon(x.Vertices.Select(v => v.Location)))));
- }
+ writer.WriteLine("# Sledge Object Export");
+ writer.WriteLine("# Scale: 1");
+ writer.WriteLine();
- using (var sw = new StreamWriter(stream))
+ foreach (var solid in map.Root.Find(x => x is Solid).OfType())
{
- foreach (var polygon in csg.Polygons)
+ writer.Write("g solid_");
+ writer.Write(solid.ID);
+ writer.WriteLine();
+
+ foreach (var face in solid.Faces)
{
- foreach (var v in polygon.Vertices)
+ foreach (var v in face.Vertices)
{
- sw.Write("v ");
- sw.Write(v.X.ToString("0.0000", CultureInfo.InvariantCulture));
- sw.Write(' ');
- sw.Write(v.Y.ToString("0.0000", CultureInfo.InvariantCulture));
- sw.Write(' ');
- sw.Write(v.Z.ToString("0.0000", CultureInfo.InvariantCulture));
- sw.WriteLine();
+ writer.Write("v ");
+ writer.Write(v.X.ToString("0.0000", CultureInfo.InvariantCulture));
+ writer.Write(' ');
+ writer.Write(v.Y.ToString("0.0000", CultureInfo.InvariantCulture));
+ writer.Write(' ');
+ writer.Write(v.Z.ToString("0.0000", CultureInfo.InvariantCulture));
+ writer.WriteLine();
}
-
- sw.Write("f ");
- for (int i = polygon.Vertices.Count; i > 0; i--)
- {
- sw.Write(-i);
- sw.Write(' ');
- }
- sw.WriteLine();
- sw.WriteLine();
- }
- }
- */
- // Semi-recoverable version
- using (var sw = new StreamWriter(stream))
- {
- sw.WriteLine("# Sledge Object Export");
- sw.WriteLine("# Scale: 1");
- sw.WriteLine();
-
- foreach (var solid in map.WorldSpawn.Find(x => x is Solid).OfType())
- {
- sw.Write("g solid_");
- sw.Write(solid.ID);
- sw.WriteLine();
-
- foreach (var face in solid.Faces)
+ writer.Write("f ");
+ for (var i = 1; i <= face.Vertices.Count; i++)
{
- foreach (var v in face.Vertices)
- {
- sw.Write("v ");
- sw.Write(v.Location.X.ToString("0.0000", CultureInfo.InvariantCulture));
- sw.Write(' ');
- sw.Write(v.Location.Y.ToString("0.0000", CultureInfo.InvariantCulture));
- sw.Write(' ');
- sw.Write(v.Location.Z.ToString("0.0000", CultureInfo.InvariantCulture));
- sw.WriteLine();
- }
-
- sw.Write("f ");
- for (var i = 1; i <= face.Vertices.Count; i++)
- {
- sw.Write(-i);
- sw.Write(' ');
- }
- sw.WriteLine();
- sw.WriteLine();
+ writer.Write(-i);
+ writer.Write(' ');
}
+ writer.WriteLine();
+ writer.WriteLine();
}
}
}
+
+ #endregion
}
}
diff --git a/Sledge.BspEditor/Sledge.BspEditor.csproj b/Sledge.BspEditor/Sledge.BspEditor.csproj
index f3bb90c2d..b9c9963cb 100644
--- a/Sledge.BspEditor/Sledge.BspEditor.csproj
+++ b/Sledge.BspEditor/Sledge.BspEditor.csproj
@@ -245,6 +245,7 @@
Resources.resx
+