better plane clipping algo, also faster

This commit is contained in:
GoaLitiuM
2021-08-31 20:03:23 +03:00
parent 61b244b850
commit f0faac7322
11 changed files with 6761 additions and 117 deletions

View File

@@ -15,7 +15,6 @@ using Console = Cabrito.Console;
namespace Game
{
public class BrushGeometry
{
public MapBrush brush;
@@ -33,6 +32,7 @@ namespace Game
//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";
@@ -56,9 +56,6 @@ namespace Game
var calc = new ConvexHullCalculator();
calc.GenerateHull(points.ToList(), true, ref verts, ref tris, ref normals);
//var qh = new QuickHull();
//verts = qh.QuickHull2(points);
var finalPoints = new List<Vector3>();
foreach (var tri in tris)
@@ -146,7 +143,7 @@ namespace Game
private MapEntity root;
private IEnumerable<IEnumerable<T>> DifferentCombinations<T>(IEnumerable<T> elements, int k)
private static IEnumerable<IEnumerable<T>> DifferentCombinations<T>(IEnumerable<T> elements, int k)
{
return k == 0 ? new[] { new T[0] } :
elements.SelectMany((e, i) =>
@@ -157,35 +154,56 @@ namespace Game
/// Triangulates the brush by calculating intersection points between triplets of planes.
/// Does not work well with off-axis aligned planes.
/// </summary>
void TriangulateBrush(MapBrush brush, out Vector3[] vertices)
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)
planes.Add(new Plane(brushPlane.v1, brushPlane.v2, brushPlane.v3));
{
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();
foreach (var comb in Enumerable.Reverse(combinations))
// 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));
//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 (Math.Abs(denom) < 0.000001f)
continue; // multiple or no intersections
//if (denom < 0.0000000001)
// continue;
if (Math.Abs(denom) < 0.000001f)
denom = denom;
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;
@@ -197,6 +215,28 @@ namespace Game
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);
@@ -206,7 +246,7 @@ namespace Game
vertices = new Vector3[0];
}
Vector3[] TriangulateBrush2(MapBrush brush)
static public void TriangulateBrush2(MapBrush brush, out Vector3[] vertices)
{
const float cs = 3000f;
@@ -225,14 +265,14 @@ namespace Game
QuickHull(cubePoints, out cubeVerts);
List<Vector3> brushVertices = new List<Vector3>(cubeVerts);
foreach (var brushPlane in brush.planes.Take(1))
foreach (var brushPlane in brush.planes)
{
Plane plane = new Plane(brushPlane.v1, brushPlane.v2, brushPlane.v3);
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;
Func<float, bool> isBack = (f) => f < epsilon;
for (int i = 0; i < brushVertices.Count; i++)
{
@@ -255,7 +295,8 @@ namespace Game
//if (isFront(d2))
{
Ray ray = new Ray(start, (end - start).Normalized);
if (plane.Intersects(ref ray, out Vector3 point))
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);
@@ -324,6 +365,8 @@ namespace Game
brushVertices.Clear();
brushVertices.AddRange(faceVertices);
Assert.IsTrue(faceVertices.Count % 3 == 0);
/*var newMeshPoints = new List<Vector3>();
int duplis = 0;
foreach (var v in faceVertices)
@@ -368,12 +411,12 @@ namespace Game
}
return brushVertices.ToArray();
vertices = brushVertices.ToArray();
}
static public void TriangulateBrush3(MapBrush brush, out Vector3[] vertices)
{
float cs = 3000f;
float cs = 4000f;
float maxD = 0f;
float minD = 0f;
@@ -402,14 +445,10 @@ namespace Game
QuickHull(cubePoints, out cubeVerts);
List<Vector3> brushVertices = new List<Vector3>(cubeVerts);
int asdf = 0;
//foreach (var brushPlane in brush.planes.Skip(skipPlanes).Take(takePlanes))
foreach (var brushPlane in brush.planes)
{
Plane plane = brushPlane.plane;
//if (asdf % 2 == 0)
//plane = new Plane(-plane.Normal, -plane.D);
List<Vector3> faceVertices = new List<Vector3>();
List<Vector3> clippedVertices = new List<Vector3>();
@@ -417,30 +456,30 @@ namespace Game
Func<float, bool> isFront = (f) => f > epsilon;
Func<float, bool> isBack = (f) => f < epsilon;
List<Tuple<Vector3, Vector3>> edges = new List<Tuple<Vector3, Vector3>>();
List<Tuple<Vector3, Vector3>> faceEdges = new List<Tuple<Vector3, Vector3>>();
List<Tuple<Vector3, Vector3>> planeEdges = new List<Tuple<Vector3, Vector3>>();
var faceEdges = new List<Tuple<Vector3, Vector3>>();
void TriangulateEdges()
void ProcessEdges()
{
if (edges.Count > 0)
if (planeEdges.Count > 0)
{
// heal discontinued edges
for (int j = 0; j < edges.Count; j++)
for (int j = 0; j < planeEdges.Count; j++)
{
var edgePrev = edges[j];
var edgeNext = edges[(j + 1) % edges.Count];
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);
edges.Insert(j + 1, newEdge);
planeEdges.Insert(j + 1, newEdge);
j--;
}
}
// triangulate edges
for (int j = 0; j < edges.Count - 1; j++)
/*for (int j = 0; j < edges.Count - 1; j++)
{
var edgePrev = edges[j];
var edgeNext = edges[(j + 1) % edges.Count];
@@ -452,7 +491,7 @@ namespace Game
faceVertices.Add(v0);
faceVertices.Add(v1);
faceVertices.Add(v2);
}
}*/
// triangulate clipped face
/*for (int j = 0; j < clippedVertices.Count-1; j++)
@@ -466,13 +505,16 @@ namespace Game
else
plane = plane;
edges.Clear();
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)
TriangulateEdges();
ProcessEdges();
int i2 = ((i + 1) % 3 == 0) ? (i - 2) : (i + 1);
Vector3 start = brushVertices[i];
@@ -510,11 +552,127 @@ namespace Game
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);
edges.Add(edge);
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);
}
}
}
TriangulateEdges();
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
@@ -567,32 +725,13 @@ namespace Game
if (true)
{
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)));
}
uniqPoints = uniqPoints;
hullVerts = hullVerts;
hullPoints = hullPoints;
faceVertices = faceVertices;
//debugPoints = new List<Vector3>(uniqPoints);
Vector3[] hullPoints;
QuickHull(uniqPoints.ToArray(), out hullPoints);
var ms = new MeshSimplifier(1f, 7f);
var optimizedVerts = ms.Simplify(hullPoints);
var optimizedVerts = hullVerts;//new MeshSimplifier().Simplify(faceVertices.ToArray());
brushVertices.Clear();
brushVertices.AddRange(optimizedVerts);
@@ -601,7 +740,7 @@ namespace Game
{
//debugPoints = new List<Vector3>(faceVertices);
var hullPoints = faceVertices;
//var hullPoints = faceVertices;
var ms = new MeshSimplifier();
var optimizedVerts = hullPoints; //ms.Simplify(hullPoints);
@@ -609,39 +748,11 @@ namespace Game
brushVertices.Clear();
brushVertices.AddRange(optimizedVerts);
}
asdf++;
}
vertices = brushVertices.ToArray();
}
/*
Development (game)
cube3: 8.1ms + 123ms
aerowalk: 78ms + 1372ms
Development (editor)
cube3: 4.6ms + 77.3ms
aerowalk: 74ms + 1328ms
Release
cube3: 4.4ms + 61.4ms
aerowalk: 17ms + 511ms
UnitTest release:
aerowalk: 8ms + 267ms
aero unit:
.net6: 667 + 229
.net5: 704 + 237
.net5win7: 697 + 237
.net48 809 + 242
.net472 810 + 246
.net462 810 + 243
.net452 808 + 244
*/
public override void OnStart()
{
byte[] mapChars = File.ReadAllBytes(mapPath);
@@ -668,25 +779,30 @@ namespace Game
// pass 1: triangulation
sw.Restart();
int brushIndex = 0;
int totalverts = 0;
foreach (var brush in root.entities[0].brushes)
{
try
{
BrushGeometry geom = new BrushGeometry();
TriangulateBrush3(brush, out geom.vertices);
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");
Console.Print("Pass 1: triangulation: " + sw.Elapsed.TotalMilliseconds + "ms, total verts: " + totalverts + ", should be 1002");
// pass 2: texturing
sw.Restart();
@@ -700,18 +816,28 @@ namespace Game
if (materials == null)
{
var customSettings = Engine.GetCustomSettings("BrushMaterials");
BrushMaterialList brushMaterialList = customSettings?.CreateInstance<BrushMaterialList>();
materials = new Dictionary<string, MaterialBase>();
BrushMaterialList brushMaterialList = Engine.GetCustomSettings("BrushMaterials")
?.CreateInstance<BrushMaterialList>();
if (brushMaterialList != null)
{
materials = brushMaterialList.materialAssets.ToDictionary(x => x.name, y => y.asset);
foreach (var m in brushMaterialList.materialAssets)
materials.Add(m.name, m.asset);
Console.Print("materials dictionary with " + materials.Count + " entries");
}
else
{
materials = new Dictionary<string, MaterialBase>();
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
@@ -719,6 +845,7 @@ namespace Game
{
Console.Print("Material '" + textureName + "' not found for brush");
materials.Add(textureName, material);
geom.brushMaterial = material;
}
for (int i = 0; i < brushVertices.Length; i += 3)
@@ -860,6 +987,7 @@ namespace Game
// pass 3: collision
sw.Restart();
brushIndex = 0;
foreach (var geom in brushGeometries)
{
StaticModel childModel = Actor.AddChild<StaticModel>();
@@ -888,6 +1016,7 @@ namespace Game
var meshCollider = childModel.AddChild<MeshCollider>();
meshCollider.CollisionData = collisionData;
brushIndex++;
}
sw.Stop();
Console.Print("Pass 3: collision: " + sw.Elapsed.TotalMilliseconds + "ms");