1182 lines
32 KiB
C#
1182 lines
32 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using FlaxEngine;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Threading;
|
|
using FlaxEngine.Assertions;
|
|
using FlaxEngine.Utilities;
|
|
using Console = Cabrito.Console;
|
|
|
|
// TODO: remove coplanar/degenerate faces from the final mesh
|
|
// It seems the original algorithm is working but removing degenerate faces does not work
|
|
|
|
namespace Game
|
|
{
|
|
public class BrushGeometry
|
|
{
|
|
public MapBrush brush;
|
|
public Vector3[] vertices;
|
|
public uint[] indices;
|
|
public Vector2[] uvs;
|
|
public Vector3[] normals;
|
|
public Model model = null;
|
|
public MaterialBase brushMaterial = null; // FIXME: brush can have multiple textures
|
|
}
|
|
|
|
|
|
public class Q3MapImporter : Script
|
|
{
|
|
//private string mapPath = @"C:\dev\GoakeFlax\Assets\Maps\cube_q1.map";
|
|
private string mapPath = @"C:\dev\GoakeFlax\Assets\Maps\cube_q3.map";
|
|
//private string mapPath = @"C:\dev\GoakeFlax\Assets\Maps\cube_valve.map";
|
|
//private string mapPath = @"C:\dev\GoakeFlax\Assets\Maps\dm4.map";
|
|
|
|
//private string mapPath = @"C:\dev\Goake\maps\aerowalk\aerowalk.map";
|
|
//private string mapPath = @"C:\dev\GoakeFlax\Assets\Maps\problematic.map";
|
|
|
|
Model model;
|
|
public MaterialBase material;
|
|
|
|
const float epsilon = 0.01f;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void QuickHull(Vector3[] points, out Vector3[] outVertices)
|
|
{
|
|
var verts = new List<Vector3>();
|
|
var tris = new List<int>();
|
|
var normals = new List<Vector3>();
|
|
|
|
var calc = new ConvexHullCalculator();
|
|
calc.GenerateHull(points.ToList(), true, ref verts, ref tris, ref normals);
|
|
|
|
var finalPoints = new List<Vector3>();
|
|
|
|
foreach (var tri in tris)
|
|
{
|
|
finalPoints.Add(verts[tri]);
|
|
}
|
|
|
|
outVertices = finalPoints.ToArray();
|
|
}
|
|
|
|
private Color[] planeColors = new Color[]
|
|
{
|
|
Color.Red,
|
|
Color.Orange,
|
|
Color.Yellow,
|
|
Color.Green,
|
|
Color.Cyan,
|
|
Color.Blue,
|
|
Color.Purple,
|
|
Color.Magenta,
|
|
};
|
|
|
|
private int skipPlanes = 0;
|
|
private int takePlanes = 5;
|
|
|
|
private List<Vector3> debugPoints = new List<Vector3>();
|
|
|
|
private List<Tuple<Vector3, Vector3>> debugNormals = new List<Tuple<Vector3, Vector3>>();
|
|
|
|
public override void OnDebugDraw()
|
|
{
|
|
foreach (var cn in debugNormals)
|
|
{
|
|
DebugDraw.DrawLine(cn.Item1, cn.Item1 + cn.Item2 * 50f, Color.Red, 0f, false);
|
|
}
|
|
return;
|
|
if (root == null)
|
|
return;
|
|
|
|
//foreach (var p in debugPoints)
|
|
// DebugDraw.DrawSphere(new BoundingSphere(p, 30f), Color.LightBlue, 0f, false);
|
|
|
|
foreach (var brush in root.entities[0].brushes.Skip(1).Take(1))
|
|
{
|
|
int planeI = skipPlanes;
|
|
foreach (var plane in brush.planes.Take(takePlanes))
|
|
//foreach (var plane in brush.planes)
|
|
{
|
|
Plane p = new Plane(plane.v1, plane.v2, plane.v3);
|
|
Vector3 planeNormal = -p.Normal;
|
|
|
|
const float w = 300f;
|
|
|
|
Vector3 p1 = new Vector3(-w, -w, 0f);
|
|
Vector3 p2 = new Vector3(w, -w, 0f);
|
|
Vector3 p3 = new Vector3(-w, w, 0f);
|
|
Vector3 p4 = new Vector3(w, w, 0f);
|
|
|
|
Vector3 uu = Vector3.Up;
|
|
if (Mathf.Abs(Vector3.Dot(planeNormal, uu)) > 0.9999f)
|
|
uu = Vector3.Forward;
|
|
|
|
var q = Quaternion.LookAt(Vector3.Zero, planeNormal, -uu);
|
|
|
|
p1 = p1 * q;
|
|
p2 = p2 * q;
|
|
p3 = p3 * q;
|
|
p4 = p4 * q;
|
|
|
|
p1 += p.D * planeNormal;
|
|
p2 += p.D * planeNormal;
|
|
p3 += p.D * planeNormal;
|
|
p4 += p.D * planeNormal;
|
|
|
|
var color = planeColors[planeI%planeColors.Length] * 0.5f;
|
|
DebugDraw.DrawTriangle(p1, p2, p3, color);
|
|
DebugDraw.DrawTriangle(p2, p3, p4, color);
|
|
|
|
|
|
|
|
planeI++;
|
|
}
|
|
}
|
|
}
|
|
|
|
private MapEntity root;
|
|
|
|
private static IEnumerable<IEnumerable<T>> DifferentCombinations<T>(IEnumerable<T> elements, int k)
|
|
{
|
|
return k == 0 ? new[] { new T[0] } :
|
|
elements.SelectMany((e, i) =>
|
|
DifferentCombinations(elements.Skip(i + 1), (k - 1)).Select(c => (new[] {e}).Concat(c)));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Triangulates the brush by calculating intersection points between triplets of planes.
|
|
/// Does not work well with off-axis aligned planes.
|
|
/// </summary>
|
|
public static void TriangulateBrush(MapBrush brush, out Vector3[] vertices)
|
|
{
|
|
HashSet<Vector3> planePoints = new HashSet<Vector3>();
|
|
|
|
List<Plane> planes = new List<Plane>();
|
|
float maxDist = 0f;
|
|
foreach (var brushPlane in brush.planes)
|
|
{
|
|
if (Mathf.Abs(brushPlane.plane.D) > maxDist)
|
|
maxDist = Mathf.Abs(brushPlane.plane.D);
|
|
planes.Add(brushPlane.plane);
|
|
}
|
|
maxDist *= Mathf.Sqrt(3);
|
|
|
|
var combinations = DifferentCombinations(planes, 3).ToList();
|
|
|
|
// pass 1: get all intersection points
|
|
foreach (var comb in combinations)
|
|
{
|
|
var p1 = comb.Skip(0).First();
|
|
var p2 = comb.Skip(1).First();
|
|
var p3 = comb.Skip(2).First();
|
|
|
|
//var maxDist = Math.Abs(p1.D * p2.D * p3.D);//Math.Max(p1.D, Math.Max(p2.D, p3.D));
|
|
|
|
// intersection of three planes
|
|
double denom = Vector3.Dot(p1.Normal, Vector3.Cross(p2.Normal, p3.Normal));
|
|
//if (denom < 0.0000000001)
|
|
// continue;
|
|
|
|
|
|
var intersection = (Vector3.Cross(p2.Normal, p3.Normal) * -p1.D +
|
|
Vector3.Cross(p3.Normal, p1.Normal) * -p2.D +
|
|
Vector3.Cross(p1.Normal, p2.Normal) * -p3.D) / (float)denom;
|
|
|
|
if (Mathf.Abs(intersection.X) > maxDist * 1f || Mathf.Abs(intersection.Y) > maxDist * 1f ||
|
|
Mathf.Abs(intersection.Z) > maxDist * 1f)
|
|
{
|
|
denom = denom;
|
|
continue;
|
|
}
|
|
|
|
if (Math.Abs(denom) < 0.0000000001)
|
|
{
|
|
denom = denom;
|
|
continue;
|
|
}
|
|
//if (intersection.Length > maxDist*2f)
|
|
// continue;
|
|
|
|
// Flip Y and Z
|
|
/*var temp = intersection.Y;
|
|
intersection.Y = intersection.Z;
|
|
intersection.Z = temp;*/
|
|
|
|
//if (intersection.Length >= maxDist)
|
|
// temp = temp;
|
|
|
|
planePoints.Add(intersection);
|
|
}
|
|
|
|
// pass 2: cull points behind clipping planes
|
|
var planePoints2 = planePoints;
|
|
planePoints = new HashSet<Vector3>();
|
|
|
|
foreach (var p in planePoints2)
|
|
{
|
|
bool front = true;
|
|
foreach (var brushPlane in brush.planes)
|
|
{
|
|
var dot = -Plane.DotCoordinate(brushPlane.plane, p);
|
|
|
|
if (dot < -0.01f)
|
|
{
|
|
front = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (front)
|
|
planePoints.Add(p);
|
|
}
|
|
|
|
if (planePoints.Count > 0)
|
|
{
|
|
QuickHull(planePoints.ToArray(), out vertices);
|
|
return;
|
|
}
|
|
|
|
vertices = new Vector3[0];
|
|
}
|
|
|
|
static public void TriangulateBrush2(MapBrush brush, out Vector3[] vertices)
|
|
{
|
|
const float cs = 3000f;
|
|
|
|
Vector3[] cubePoints = new[]
|
|
{
|
|
new Vector3(-cs, -cs, -cs),
|
|
new Vector3(cs, -cs, -cs),
|
|
new Vector3(-cs, cs, -cs),
|
|
new Vector3(cs, cs, -cs),
|
|
new Vector3(-cs, -cs, cs),
|
|
new Vector3(cs, -cs, cs),
|
|
new Vector3(-cs, cs, cs),
|
|
new Vector3(cs, cs, cs),
|
|
};
|
|
Vector3[] cubeVerts;
|
|
QuickHull(cubePoints, out cubeVerts);
|
|
List<Vector3> brushVertices = new List<Vector3>(cubeVerts);
|
|
|
|
foreach (var brushPlane in brush.planes)
|
|
{
|
|
Plane plane = brushPlane.plane;
|
|
List<Vector3> faceVertices = new List<Vector3>();
|
|
List<Vector3> clippedVertices = new List<Vector3>();
|
|
|
|
Func<float, bool> isFront = (f) => f > epsilon;
|
|
Func<float, bool> isBack = (f) => f < epsilon;
|
|
|
|
for (int i = 0; i < brushVertices.Count; i++)
|
|
{
|
|
int i2 = ((i + 1) % 3 == 0) ? (i - 2) : (i + 1);
|
|
Vector3 start = brushVertices[i];
|
|
Vector3 end = brushVertices[i2];
|
|
|
|
var d1 = Plane.DotCoordinate(plane, start);
|
|
var d2 = Plane.DotCoordinate(plane, end);
|
|
|
|
if (isBack(d1))
|
|
{
|
|
// include the point behind the clipping plane
|
|
faceVertices.Add(start);
|
|
}
|
|
|
|
if (isBack(d1) && isFront(d2) || isFront(d1) && isBack(d2))
|
|
{
|
|
// the cutting plane clips the edge
|
|
//if (isFront(d2))
|
|
{
|
|
Ray ray = new Ray(start, (end - start).Normalized);
|
|
Ray ray2 = new Ray(end, (start - end).Normalized);
|
|
if (plane.Intersects(ref ray, out Vector3 point) || plane.Intersects(ref ray2, out point))
|
|
{
|
|
|
|
//faceVertices.Add(point);
|
|
//clippedVertices.Add(point);
|
|
|
|
/*
|
|
intersect
|
|
start __._ (end)
|
|
| |/
|
|
| /
|
|
|/
|
|
next
|
|
*/
|
|
if (isBack(d1) && isFront(d2))
|
|
{
|
|
// finish the current triangle and start the next one
|
|
// [start, intersect, next], [intersect, ...]
|
|
|
|
faceVertices.Add(point);
|
|
|
|
if ((faceVertices.Count % 3) == 2)
|
|
{
|
|
int i3 = ((i2 + 1) % 3 == 0) ? (i2 - 2) : (i2 + 1);
|
|
Vector3 next = brushVertices[i3];
|
|
faceVertices.Add(next);
|
|
}
|
|
else
|
|
ray = ray;
|
|
|
|
faceVertices.Add(point);
|
|
}
|
|
|
|
/*
|
|
|
|
____ (start)
|
|
| |/
|
|
| * intersect2
|
|
|/
|
|
end
|
|
*/
|
|
else if (isFront(d1) && isBack(d2))
|
|
{
|
|
// continue where we left off
|
|
// [intersect, intersect2, ...]
|
|
|
|
faceVertices.Add(point);
|
|
|
|
if ((i % 3) == 2)
|
|
{
|
|
int i3 = ((i2 + 1) % 3 == 0) ? (i2 - 2) : (i2 + 1);
|
|
Vector3 next = brushVertices[i3];
|
|
faceVertices.Add(next);
|
|
}
|
|
else
|
|
ray = ray;
|
|
}
|
|
else
|
|
ray = ray;
|
|
}
|
|
else
|
|
d1 = d1;
|
|
}
|
|
}
|
|
}
|
|
|
|
brushVertices.Clear();
|
|
brushVertices.AddRange(faceVertices);
|
|
|
|
Assert.IsTrue(faceVertices.Count % 3 == 0);
|
|
|
|
/*var newMeshPoints = new List<Vector3>();
|
|
int duplis = 0;
|
|
foreach (var v in faceVertices)
|
|
{
|
|
bool found = false;
|
|
foreach (var vo in newMeshPoints)
|
|
{
|
|
if ((v - vo).Length < epsilon)
|
|
{
|
|
found = true;
|
|
duplis++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//if (!newMeshPoints.Contains(v))
|
|
if (!found)
|
|
newMeshPoints.Add(v);
|
|
}
|
|
if (duplis > 0)
|
|
Console.Print("duplicates: " + duplis);
|
|
|
|
if (newMeshPoints.Count > 0)
|
|
{
|
|
var tempPoints = newMeshPoints;
|
|
newMeshPoints = new List<Vector3>(tempPoints.Count);
|
|
foreach (var tp in tempPoints)
|
|
{
|
|
// Flip Y and Z
|
|
newMeshPoints.Add(new Vector3(tp.X, tp.Z, tp.Y));
|
|
}
|
|
|
|
var hullPoints = QuickHull(newMeshPoints.ToArray());
|
|
var ms = new MeshSimplifier();
|
|
var optimizedVerts = ms.Simplify(hullPoints);
|
|
brushVertices.Clear();
|
|
brushVertices.AddRange(hullPoints);
|
|
}
|
|
else
|
|
brushVertices.Clear();
|
|
*/
|
|
|
|
}
|
|
|
|
vertices = brushVertices.ToArray();
|
|
}
|
|
|
|
static public void TriangulateBrush3(MapBrush brush, out Vector3[] vertices)
|
|
{
|
|
float cs = 4000f;
|
|
|
|
float maxD = 0f;
|
|
float minD = 0f;
|
|
foreach (var brushPlane in brush.planes)
|
|
{
|
|
var p = brushPlane.plane;
|
|
minD = Mathf.Min(p.D);
|
|
maxD = Mathf.Max(p.D);
|
|
}
|
|
|
|
//cs = maxD*2;
|
|
|
|
|
|
Vector3[] cubePoints = new[]
|
|
{
|
|
new Vector3(-1, -1, -1) * cs,
|
|
new Vector3(1, -1, -1) * cs,
|
|
new Vector3(-1, 1, -1) * cs,
|
|
new Vector3(1, 1, -1) * cs,
|
|
new Vector3(-1, -1, 1) * cs,
|
|
new Vector3(1, -1, 1) * cs,
|
|
new Vector3(-1, 1, 1) * cs,
|
|
new Vector3(1, 1, 1) * cs,
|
|
};
|
|
Vector3[] cubeVerts;
|
|
QuickHull(cubePoints, out cubeVerts);
|
|
List<Vector3> brushVertices = new List<Vector3>(cubeVerts);
|
|
|
|
//foreach (var brushPlane in brush.planes.Skip(skipPlanes).Take(takePlanes))
|
|
foreach (var brushPlane in brush.planes)
|
|
{
|
|
Plane plane = brushPlane.plane;
|
|
|
|
List<Vector3> faceVertices = new List<Vector3>();
|
|
List<Vector3> clippedVertices = new List<Vector3>();
|
|
|
|
Func<float, bool> isFront = (f) => f > epsilon;
|
|
Func<float, bool> isBack = (f) => f < epsilon;
|
|
|
|
List<Tuple<Vector3, Vector3>> planeEdges = new List<Tuple<Vector3, Vector3>>();
|
|
var faceEdges = new List<Tuple<Vector3, Vector3>>();
|
|
|
|
void ProcessEdges()
|
|
{
|
|
if (planeEdges.Count > 0)
|
|
{
|
|
// heal discontinued edges
|
|
for (int j = 0; j < planeEdges.Count; j++)
|
|
{
|
|
var edgePrev = planeEdges[j];
|
|
var edgeNext = planeEdges[(j + 1) % planeEdges.Count];
|
|
|
|
//if (edgePrev.Item2 != edgeNext.Item1)
|
|
if ((edgePrev.Item2 - edgeNext.Item1).Length > 0.0001f)
|
|
{
|
|
var newEdge = new Tuple<Vector3, Vector3>(edgePrev.Item2, edgeNext.Item1);
|
|
planeEdges.Insert(j + 1, newEdge);
|
|
j--;
|
|
}
|
|
}
|
|
|
|
// triangulate edges
|
|
/*for (int j = 0; j < edges.Count - 1; j++)
|
|
{
|
|
var edgePrev = edges[j];
|
|
var edgeNext = edges[(j + 1) % edges.Count];
|
|
|
|
Vector3 v0 = edges[0].Item1;
|
|
Vector3 v1 = edgePrev.Item2;
|
|
Vector3 v2 = edgeNext.Item2;
|
|
|
|
faceVertices.Add(v0);
|
|
faceVertices.Add(v1);
|
|
faceVertices.Add(v2);
|
|
}*/
|
|
|
|
// triangulate clipped face
|
|
/*for (int j = 0; j < clippedVertices.Count-1; j++)
|
|
{
|
|
Vector3 v0 = clippedVertices[0];
|
|
Vector3 v1 = edgePrev.Item2;
|
|
Vector3 v2 = edgeNext.Item2;
|
|
}*/
|
|
// TODO: maybe optimize the triangles here instead of using QuickHull
|
|
}
|
|
else
|
|
plane = plane;
|
|
|
|
|
|
faceEdges.AddRange(planeEdges);
|
|
planeEdges = new List<Tuple<Vector3, Vector3>>();
|
|
//edges.Clear();
|
|
}
|
|
|
|
for (int i = 0; i < brushVertices.Count; i++)
|
|
{
|
|
if (i > 0 && i % 3 == 0)
|
|
ProcessEdges();
|
|
|
|
int i2 = ((i + 1) % 3 == 0) ? (i - 2) : (i + 1);
|
|
Vector3 start = brushVertices[i];
|
|
Vector3 end = brushVertices[i2];
|
|
|
|
var d1 = Plane.DotCoordinate(plane, start);
|
|
var d2 = Plane.DotCoordinate(plane, end);
|
|
|
|
Vector3 edgeStart = start;
|
|
Vector3 edgeEnd = end;
|
|
|
|
|
|
if (isBack(d1))
|
|
{
|
|
edgeStart = start;
|
|
}
|
|
|
|
if (isBack(d1) && isFront(d2) || isFront(d1) && isBack(d2))
|
|
{
|
|
Ray ray = new Ray(start, (end - start).Normalized);
|
|
Ray ray2 = new Ray(end, (start - end).Normalized);
|
|
if (plane.Intersects(ref ray, out Vector3 point) || plane.Intersects(ref ray2, out point))
|
|
{
|
|
edgeEnd = point;
|
|
clippedVertices.Add(point);
|
|
|
|
if (isFront(d1))
|
|
{
|
|
edgeStart = edgeEnd;
|
|
edgeEnd = end;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isFront(d1) && isFront(d2))
|
|
continue;
|
|
|
|
var abs = Mathf.Abs((edgeEnd-edgeStart).Length);
|
|
if (abs < 0.000001f)
|
|
{
|
|
abs = abs;
|
|
//continue;
|
|
}
|
|
Tuple<Vector3, Vector3> edge = new Tuple<Vector3, Vector3>(edgeStart, edgeEnd);
|
|
planeEdges.Add(edge);
|
|
}
|
|
ProcessEdges();
|
|
|
|
if (true)
|
|
{
|
|
// triangulate edges
|
|
|
|
faceVertices.Clear();
|
|
//foreach (var edges in faceEdges)
|
|
{
|
|
// merge same points in edges
|
|
for (int j = 0; j < faceEdges.Count; j++)
|
|
{
|
|
Vector3 v0 = faceEdges[j].Item1;
|
|
Vector3 v1 = faceEdges[j].Item2;
|
|
|
|
var edgeNext = faceEdges[(j + 1) % faceEdges.Count];
|
|
Vector3 v2 = edgeNext.Item1;
|
|
Vector3 v3 = edgeNext.Item2;
|
|
|
|
var dot = Vector3.Dot(v1.Normalized, v2.Normalized);
|
|
if (v1 != v2 && dot > 0.9999999f)
|
|
{
|
|
v1 = v1;
|
|
//faceEdges[(j + 1) % faceEdges.Count] = new Tuple<Vector3, Vector3>(v1, v3);
|
|
}
|
|
}
|
|
|
|
List<Tuple<Vector3, Vector3>> newEdges = new List<Tuple<Vector3, Vector3>>();
|
|
for (int j = 0; j < faceEdges.Count; j++)
|
|
{
|
|
Vector3 v0 = faceEdges[j].Item1;
|
|
Vector3 v1 = faceEdges[j].Item2;
|
|
|
|
var abs = Mathf.Abs((v1-v0).Length);
|
|
if (abs < 0.000001f)
|
|
{
|
|
v1 = v1;
|
|
//continue;
|
|
}
|
|
|
|
Tuple<Vector3, Vector3> newEdge = new Tuple<Vector3, Vector3>(v0, v1);
|
|
while (true)
|
|
{
|
|
var edgeNext = faceEdges[(j + 1) % faceEdges.Count];
|
|
Vector3 v2 = edgeNext.Item1;
|
|
Vector3 v3 = edgeNext.Item2;
|
|
|
|
//var dot = Vector3.Dot((v3 - v0).Normalized, (v1 - v0).Normalized);
|
|
/*var dot = Vector3.Dot((v3 - v2).Normalized, (v1 - v0).Normalized);
|
|
if (dot > 0.9f)
|
|
{
|
|
newEdge = new Tuple<Vector3, Vector3>(v0, v3);
|
|
j++;
|
|
}
|
|
else*/
|
|
break;
|
|
|
|
/*var dot = Vector3.Dot((v3 - v2).Normalized, (v1 - v0).Normalized);
|
|
if (dot > 0.9f)
|
|
{
|
|
newEdge = new Tuple<Vector3, Vector3>(v0, v3);
|
|
j++;
|
|
}
|
|
else
|
|
break;*/
|
|
}
|
|
|
|
newEdges.Add(newEdge);
|
|
}
|
|
|
|
for (int j = 0; j < newEdges.Count - 1; j++)
|
|
{
|
|
var edgePrev = newEdges[j];
|
|
var edgeNext = newEdges[(j + 1) % newEdges.Count];
|
|
|
|
Vector3 v0 = newEdges[0].Item1;
|
|
Vector3 v1 = edgePrev.Item2;
|
|
Vector3 v2 = edgeNext.Item2;
|
|
|
|
faceVertices.Add(v0);
|
|
faceVertices.Add(v1);
|
|
faceVertices.Add(v2);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
List<Vector3> uniqPoints = new List<Vector3>();
|
|
foreach (var v in faceVertices)
|
|
{
|
|
bool found = false;
|
|
foreach (var v2 in uniqPoints)
|
|
{
|
|
if ((v - v2).Length < 0.01f)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
uniqPoints.Add(v);
|
|
//uniqPoints.Add(new Vector3((float)Math.Round(v.X, 3), (float)Math.Round(v.Y, 3), (float)Math.Round(v.Z, 3)));
|
|
}
|
|
|
|
//debugPoints = new List<Vector3>(uniqPoints);
|
|
|
|
|
|
Vector3[] hullPoints;
|
|
QuickHull(uniqPoints.ToArray(), out hullPoints);
|
|
var hullVerts = new MeshSimplifier().Simplify(hullPoints);
|
|
|
|
if (false)
|
|
{
|
|
// create edges from clipped points
|
|
var clippedEdges = new List<Tuple<Vector3, Vector3>>();
|
|
|
|
//foreach (var e in edges)
|
|
// newEdges.Add(new Tuple<Vector3, Vector3>(e.Item1, e.Item2));
|
|
|
|
for (int i = 0; i < clippedVertices.Count; i++)
|
|
{
|
|
int i2 = (i + 1) % clippedVertices.Count;
|
|
Vector3 start = clippedVertices[i];
|
|
Vector3 end = clippedVertices[i2];
|
|
|
|
while (i < clippedVertices.Count)
|
|
{
|
|
int i3 = (i + 2) % clippedVertices.Count;
|
|
Vector3 end2 = clippedVertices[i3];
|
|
|
|
var edgeDirection = (end - start).Normalized;
|
|
var edgeDirection2 = (end2 - start).Normalized;
|
|
|
|
if ((edgeDirection2 - edgeDirection).Length < 0.0001f)
|
|
{
|
|
end = end2;
|
|
i++;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
clippedEdges.Add(new Tuple<Vector3, Vector3>(start, end));
|
|
}
|
|
|
|
// triangulate edges
|
|
for (int j = 0; j < clippedEdges.Count; j++)
|
|
{
|
|
var edgePrev = clippedEdges[j];
|
|
var edgeNext = clippedEdges[(j + 1) % clippedEdges.Count];
|
|
|
|
Vector3 v0 = clippedEdges[0].Item1;
|
|
Vector3 v1 = edgePrev.Item2;
|
|
Vector3 v2 = edgeNext.Item2;
|
|
|
|
faceVertices.Add(v0);
|
|
faceVertices.Add(v1);
|
|
faceVertices.Add(v2);
|
|
}
|
|
}
|
|
|
|
if (true)
|
|
{
|
|
|
|
uniqPoints = uniqPoints;
|
|
hullVerts = hullVerts;
|
|
hullPoints = hullPoints;
|
|
faceVertices = faceVertices;
|
|
|
|
var optimizedVerts = hullVerts;//new MeshSimplifier().Simplify(faceVertices.ToArray());
|
|
|
|
brushVertices.Clear();
|
|
brushVertices.AddRange(optimizedVerts);
|
|
}
|
|
else
|
|
{
|
|
//debugPoints = new List<Vector3>(faceVertices);
|
|
|
|
//var hullPoints = faceVertices;
|
|
|
|
var ms = new MeshSimplifier();
|
|
var optimizedVerts = hullPoints; //ms.Simplify(hullPoints);
|
|
|
|
brushVertices.Clear();
|
|
brushVertices.AddRange(optimizedVerts);
|
|
}
|
|
}
|
|
|
|
vertices = brushVertices.ToArray();
|
|
}
|
|
|
|
public override void OnStart()
|
|
{
|
|
byte[] mapChars = File.ReadAllBytes(mapPath);
|
|
|
|
Stopwatch sw = Stopwatch.StartNew();
|
|
root = MapParser.Parse(mapChars);
|
|
sw.Stop();
|
|
|
|
Console.Print("Map parsing time: " + sw.Elapsed.TotalMilliseconds + "ms");
|
|
|
|
bool oneMesh = false;
|
|
bool convexMesh = true;
|
|
|
|
|
|
if (!oneMesh)
|
|
{
|
|
Dictionary<string, MaterialBase> materials = null;
|
|
var mapRootActor = Actor.AddChild<Actor>();
|
|
mapRootActor.Name = "MapRootActor";
|
|
|
|
List<BrushGeometry> brushGeometries = new List<BrushGeometry>(root.entities[0].brushes.Count);
|
|
|
|
|
|
// pass 1: triangulation
|
|
sw.Restart();
|
|
int brushIndex = 0;
|
|
int totalverts = 0;
|
|
foreach (var brush in root.entities[0].brushes)
|
|
{
|
|
try
|
|
{
|
|
BrushGeometry geom = new BrushGeometry();
|
|
|
|
TriangulateBrush(brush, out geom.vertices);
|
|
geom.brush = brush;
|
|
|
|
brushGeometries.Add(geom);
|
|
totalverts += geom.vertices.Length;
|
|
|
|
Assert.IsTrue(geom.vertices.Length > 0);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Console.Print("Failed to triangulate brush " + brushIndex.ToString() + ": " + e.Message);
|
|
//FlaxEngine.Engine.RequestExit();
|
|
}
|
|
brushIndex++;
|
|
}
|
|
sw.Stop();
|
|
Console.Print("Pass 1: triangulation: " + sw.Elapsed.TotalMilliseconds + "ms, total verts: " + totalverts + ", should be 1002");
|
|
|
|
// pass 2: texturing
|
|
sw.Restart();
|
|
foreach (var geom in brushGeometries)
|
|
{
|
|
var brushVertices = geom.vertices;
|
|
Vector2[] brushUvs = new Vector2[brushVertices.Length];
|
|
Vector3[] brushNormals = new Vector3[brushVertices.Length];
|
|
|
|
var textureName = geom.brush.planes[0].texture;
|
|
|
|
if (materials == null)
|
|
{
|
|
materials = new Dictionary<string, MaterialBase>();
|
|
|
|
BrushMaterialList brushMaterialList = Engine.GetCustomSettings("BrushMaterials")
|
|
?.CreateInstance<BrushMaterialList>();
|
|
if (brushMaterialList != null)
|
|
{
|
|
foreach (var m in brushMaterialList.materialAssets)
|
|
materials.Add(m.name, m.asset);
|
|
Console.Print("materials dictionary with " + materials.Count + " entries");
|
|
}
|
|
else
|
|
{
|
|
Console.Print("no materials dictionary found");
|
|
}
|
|
|
|
BrushMaterialList brushMaterialList2 = Engine.GetCustomSettings("BrushMaterialsLegacy")
|
|
?.CreateInstance<BrushMaterialList>();
|
|
if (brushMaterialList2 != null)
|
|
{
|
|
foreach (var m in brushMaterialList2.materialAssets)
|
|
materials.Add(m.name, m.asset);
|
|
}
|
|
}
|
|
|
|
// FIXME: brush can have multiple textures
|
|
if (!materials.TryGetValue(textureName, out geom.brushMaterial))
|
|
{
|
|
Console.Print("Material '" + textureName + "' not found for brush");
|
|
materials.Add(textureName, material);
|
|
geom.brushMaterial = material;
|
|
}
|
|
|
|
for (int i = 0; i < brushVertices.Length; i += 3)
|
|
{
|
|
Vector3 v1 = brushVertices[i + 0];
|
|
Vector3 v2 = brushVertices[i + 1];
|
|
Vector3 v3 = brushVertices[i + 2];
|
|
|
|
Vector3 normal = -Vector3.Cross(v3 - v1, v2 - v1).Normalized;
|
|
|
|
// fetch the texture parameters from the plane with matching normal
|
|
Vector2 uvScale = new Vector2(0f);
|
|
float uvRotation = 0f;
|
|
Vector2 uvOffset = new Vector2(0f);
|
|
bool found = false;
|
|
foreach (var brushPlane in geom.brush.planes)
|
|
{
|
|
if ((brushPlane.plane.Normal - normal).Length < 0.01f)
|
|
{
|
|
normal = brushPlane.plane.Normal; // for consistency
|
|
uvScale = 1f / brushPlane.scale;
|
|
uvRotation = brushPlane.rotation;
|
|
uvOffset = brushPlane.offset * brushPlane.scale;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
Console.Print("no matching plane found, bad geometry?");
|
|
|
|
Vector2 uv1, uv2, uv3;
|
|
// if quake format
|
|
{
|
|
// The texture is projected to the surface from three angles, the axis with least
|
|
// distortion is chosen here.
|
|
|
|
// Attempt to workaround most rounding errors at 45-degree angles which causes bias towards one axis.
|
|
// This behaviour is seemingly random in different engines and editors, so let's not bother.
|
|
|
|
Vector3 textureNormal = new Vector3((float)Math.Round(normal.X, 4),
|
|
(float)Math.Round(normal.Y, 4), (float)Math.Round(normal.Z, 4));
|
|
|
|
var dotX = Math.Abs(Vector3.Dot(textureNormal, Vector3.Right));
|
|
var dotY = Math.Abs(Vector3.Dot(textureNormal, Vector3.Up));
|
|
var dotZ = Math.Abs(Vector3.Dot(textureNormal, Vector3.Forward));
|
|
|
|
Vector3 axis;
|
|
if (dotY >= dotX && dotY >= dotZ)
|
|
axis = -Vector3.Up;
|
|
else if (dotX >= dotY && dotX >= dotZ)
|
|
axis = Vector3.Right;
|
|
else if (dotZ >= dotX && dotZ >= dotY)
|
|
axis = -Vector3.Forward;
|
|
else
|
|
axis = Vector3.Right;
|
|
|
|
var axisForward = Mathf.Abs(Vector3.Dot(axis, Vector3.Up)) > 0.01f
|
|
? -Vector3.Forward
|
|
: Vector3.Up;
|
|
var axisForward2 = Mathf.Abs(Vector3.Dot(axis, Vector3.Up)) > 0.01f
|
|
? Vector3.Up
|
|
: -Vector3.Forward;
|
|
|
|
Quaternion rot = Quaternion.Identity;
|
|
rot = rot * Quaternion.LookRotation(axis, axisForward);
|
|
rot = rot * Quaternion.RotationAxis(-Vector3.Forward,
|
|
180f * Mathf.DegreesToRadians);
|
|
rot = rot * Quaternion.RotationAxis(
|
|
Mathf.Abs(Vector3.Dot(axis, Vector3.Right)) > 0.01f
|
|
? Vector3.Right
|
|
: axisForward2,
|
|
uvRotation * Mathf.DegreesToRadians);
|
|
|
|
uv1 = ((Vector2)(v1 * rot) + uvOffset) * uvScale;
|
|
uv2 = ((Vector2)(v2 * rot) + uvOffset) * uvScale;
|
|
uv3 = ((Vector2)(v3 * rot) + uvOffset) * uvScale;
|
|
}
|
|
|
|
brushUvs[i + 0] = uv1;
|
|
brushUvs[i + 1] = uv2;
|
|
brushUvs[i + 2] = uv3;
|
|
|
|
brushNormals[i + 0] = normal;
|
|
brushNormals[i + 1] = normal;
|
|
brushNormals[i + 2] = normal;
|
|
}
|
|
|
|
geom.vertices = brushVertices;
|
|
geom.uvs = brushUvs;
|
|
geom.normals = brushNormals;
|
|
geom.indices = new uint[geom.vertices.Length];
|
|
for (uint i = 0; i < geom.vertices.Length; i++)
|
|
geom.indices[i] = i;
|
|
|
|
if (geom.vertices.Length > 0)
|
|
{
|
|
geom.model = Content.CreateVirtualAsset<Model>();
|
|
geom.model.SetupLODs(new int[] { 1 });
|
|
geom.model.LODs[0].Meshes[0].UpdateMesh(geom.vertices, geom.indices, geom.normals,
|
|
null, geom.uvs);
|
|
|
|
/*
|
|
StaticModel childModel = Actor.AddChild<StaticModel>();
|
|
childModel.Name = "Brush_" + brushIndex;
|
|
childModel.Model = geom.model;
|
|
childModel.SetMaterial(0, geom.brushMaterial);
|
|
childModel.Parent = mapRootActor;
|
|
|
|
CollisionData collisionData = Content.CreateVirtualAsset<CollisionData>();
|
|
if (collisionData.CookCollision(convexMesh ? CollisionDataType.ConvexMesh : CollisionDataType.TriangleMesh, geom.vertices.ToArray(),
|
|
geom.indices.ToArray()))
|
|
{
|
|
bool failed = true;
|
|
if (convexMesh)
|
|
{
|
|
// fallback to triangle mesh
|
|
failed = collisionData.CookCollision(CollisionDataType.TriangleMesh,
|
|
geom.vertices.ToArray(),
|
|
geom.indices.ToArray());
|
|
if (!failed)
|
|
Console.PrintWarning("Hull brush " + brushIndex.ToString() + " is not convex");
|
|
}
|
|
if (failed)
|
|
throw new Exception("failed to cook final collision");
|
|
}
|
|
|
|
var meshCollider = childModel.AddChild<MeshCollider>();
|
|
meshCollider.CollisionData = collisionData;
|
|
*/
|
|
}
|
|
|
|
//brushGeometries.Add(geom);
|
|
|
|
|
|
}
|
|
sw.Stop();
|
|
Console.Print("Pass 2: texturing: " + sw.Elapsed.TotalMilliseconds + "ms");
|
|
|
|
// pass 3: collision
|
|
sw.Restart();
|
|
brushIndex = 0;
|
|
foreach (var geom in brushGeometries)
|
|
{
|
|
StaticModel childModel = Actor.AddChild<StaticModel>();
|
|
childModel.Name = "Brush_" + brushIndex;
|
|
childModel.Model = geom.model;
|
|
childModel.SetMaterial(0, geom.brushMaterial);
|
|
childModel.Parent = mapRootActor;
|
|
|
|
CollisionData collisionData = Content.CreateVirtualAsset<CollisionData>();
|
|
if (collisionData.CookCollision(convexMesh ? CollisionDataType.ConvexMesh : CollisionDataType.TriangleMesh, geom.vertices,
|
|
geom.indices))
|
|
{
|
|
bool failed = true;
|
|
if (convexMesh)
|
|
{
|
|
// fallback to triangle mesh
|
|
failed = collisionData.CookCollision(CollisionDataType.TriangleMesh,
|
|
geom.vertices,
|
|
geom.indices);
|
|
if (!failed)
|
|
Console.PrintWarning("Hull brush " + brushIndex.ToString() + " is not convex");
|
|
}
|
|
if (failed)
|
|
throw new Exception("failed to cook final collision");
|
|
}
|
|
|
|
var meshCollider = childModel.AddChild<MeshCollider>();
|
|
meshCollider.CollisionData = collisionData;
|
|
brushIndex++;
|
|
}
|
|
sw.Stop();
|
|
Console.Print("Pass 3: collision: " + sw.Elapsed.TotalMilliseconds + "ms");
|
|
}
|
|
else
|
|
{
|
|
List<Vector3> vertices = new List<Vector3>();
|
|
List<Vector2> uvs = new List<Vector2>();
|
|
List<Vector3> normals = new List<Vector3>();
|
|
|
|
int brushIndex = 0;
|
|
foreach (var brush in root.entities[0].brushes)
|
|
{
|
|
try
|
|
{
|
|
TriangulateBrush3(brush, out Vector3[] brushVertices);
|
|
Vector2[] brushUvs = new Vector2[brushVertices.Length];
|
|
Vector3[] brushNormals = new Vector3[brushVertices.Length];
|
|
|
|
for (int i = 0; i < brushVertices.Length; i += 3)
|
|
{
|
|
Vector3 v1 = brushVertices[i + 0];
|
|
Vector3 v2 = brushVertices[i + 1];
|
|
Vector3 v3 = brushVertices[i + 2];
|
|
|
|
Vector3 normal = -Vector3.Cross(v3 - v1, v2 - v1).Normalized;
|
|
|
|
// fetch the texture parameters from the plane with matching normal
|
|
Vector2 uvScale = new Vector2(0f);
|
|
float uvRotation = 0f;
|
|
Vector2 uvOffset = new Vector2(0f);
|
|
bool found = false;
|
|
foreach (var brushPlane in brush.planes)
|
|
{
|
|
if ((brushPlane.plane.Normal - normal).Length < 0.01f)
|
|
{
|
|
normal = brushPlane.plane.Normal; // for consistency
|
|
uvScale = 1f / brushPlane.scale;
|
|
uvRotation = brushPlane.rotation;
|
|
uvOffset = brushPlane.offset * brushPlane.scale;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
Console.Print("no matching plane found, bad geometry?");
|
|
|
|
Vector2 uv1, uv2, uv3;
|
|
// if quake format
|
|
{
|
|
// The texture is projected to the surface from three angles, the axis with least
|
|
// distortion is chosen here.
|
|
|
|
// Attempt to workaround most rounding errors at 45-degree angles which causes bias towards one axis.
|
|
// This behaviour is seemingly random in different engines and editors, so let's not bother.
|
|
Vector3 textureNormal = new Vector3((float)Math.Round(normal.X, 4),
|
|
(float)Math.Round(normal.Y, 4), (float)Math.Round(normal.Z, 4));
|
|
|
|
var dotX = Math.Abs(Vector3.Dot(textureNormal, Vector3.Right));
|
|
var dotY = Math.Abs(Vector3.Dot(textureNormal, Vector3.Up));
|
|
var dotZ = Math.Abs(Vector3.Dot(textureNormal, Vector3.Forward));
|
|
|
|
Vector3 axis;
|
|
if (dotY >= dotX && dotY >= dotZ)
|
|
axis = -Vector3.Up;
|
|
else if (dotX >= dotY && dotX >= dotZ)
|
|
axis = Vector3.Right;
|
|
else if (dotZ >= dotX && dotZ >= dotY)
|
|
axis = -Vector3.Forward;
|
|
else
|
|
axis = Vector3.Right;
|
|
|
|
var axisForward = Mathf.Abs(Vector3.Dot(axis, Vector3.Up)) > 0.01f
|
|
? -Vector3.Forward
|
|
: Vector3.Up;
|
|
var axisForward2 = Mathf.Abs(Vector3.Dot(axis, Vector3.Up)) > 0.01f
|
|
? Vector3.Up
|
|
: -Vector3.Forward;
|
|
|
|
Quaternion rot = Quaternion.Identity;
|
|
rot = rot * Quaternion.LookRotation(axis, axisForward);
|
|
rot = rot * Quaternion.RotationAxis(-Vector3.Forward,
|
|
180f * Mathf.DegreesToRadians);
|
|
rot = rot * Quaternion.RotationAxis(
|
|
Mathf.Abs(Vector3.Dot(axis, Vector3.Right)) > 0.01f
|
|
? Vector3.Right
|
|
: axisForward2,
|
|
uvRotation * Mathf.DegreesToRadians);
|
|
|
|
uv1 = ((Vector2)(v1 * rot) + uvOffset) * uvScale;
|
|
uv2 = ((Vector2)(v2 * rot) + uvOffset) * uvScale;
|
|
uv3 = ((Vector2)(v3 * rot) + uvOffset) * uvScale;
|
|
}
|
|
|
|
brushUvs[i + 0] = uv1;
|
|
brushUvs[i + 1] = uv2;
|
|
brushUvs[i + 2] = uv3;
|
|
|
|
brushNormals[i + 0] = normal;
|
|
brushNormals[i + 1] = normal;
|
|
brushNormals[i + 2] = normal;
|
|
}
|
|
|
|
vertices.AddRange(brushVertices);
|
|
uvs.AddRange(brushUvs);
|
|
normals.AddRange(brushNormals);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Console.Print("Failed to hull brush " + brushIndex.ToString() + ": " + e.Message);
|
|
}
|
|
|
|
brushIndex++;
|
|
}
|
|
|
|
if (vertices.Count > 0)
|
|
{
|
|
uint[] triangles = new uint[vertices.Count];
|
|
for (uint i = 0; i < vertices.Count; i++)
|
|
triangles[i] = i;
|
|
|
|
model = Content.CreateVirtualAsset<Model>();
|
|
model.SetupLODs(new int[] { 1 });
|
|
model.LODs[0].Meshes[0].UpdateMesh(vertices.ToArray(), (int[])(object)triangles, normals.ToArray(),
|
|
null, uvs.ToArray());
|
|
|
|
StaticModel childModel = Actor.AddChild<StaticModel>();
|
|
childModel.Name = "MapModel";
|
|
childModel.Model = model;
|
|
childModel.SetMaterial(0, material);
|
|
|
|
CollisionData collisionData = Content.CreateVirtualAsset<CollisionData>();
|
|
if (collisionData.CookCollision(CollisionDataType.TriangleMesh, vertices.ToArray(),
|
|
triangles.ToArray()))
|
|
throw new Exception("failed to cook final collision");
|
|
var meshCollider = childModel.AddChild<MeshCollider>();
|
|
meshCollider.CollisionData = collisionData;
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void OnEnable()
|
|
{
|
|
// Here you can add code that needs to be called when script is enabled (eg. register for events)
|
|
}
|
|
|
|
public override void OnDisable()
|
|
{
|
|
// Here you can add code that needs to be called when script is disabled (eg. unregister from events)
|
|
}
|
|
|
|
public override void OnUpdate()
|
|
{
|
|
}
|
|
|
|
public override void OnDestroy()
|
|
{
|
|
Destroy(ref model);
|
|
base.OnDestroy();
|
|
}
|
|
}
|
|
} |