From b7fcc6e5ab9e189326d8bd0904f6de66ff706528 Mon Sep 17 00:00:00 2001 From: GoaLitiuM Date: Wed, 7 Jul 2021 01:41:29 +0300 Subject: [PATCH] betterer quickhull --- Assets/Maps/cube.map | 16 + Content/GameSettings.json | 2 +- Content/Settings/TimeSettings.json | 6 +- Source/Game/Q3MapImporter.cs | 934 +++++++++++++++-------------- 4 files changed, 516 insertions(+), 442 deletions(-) diff --git a/Assets/Maps/cube.map b/Assets/Maps/cube.map index 8da68d5..c68842f 100644 --- a/Assets/Maps/cube.map +++ b/Assets/Maps/cube.map @@ -12,4 +12,20 @@ ( 64 64 16 ) ( 65 64 16 ) ( 64 64 17 ) __TB_empty 0 0 0 0.0625 0.0625 0 0 0 ( 64 64 16 ) ( 64 64 17 ) ( 64 65 16 ) __TB_empty 0 0 0 0.0625 0.0625 0 0 0 } +// brush 1 +{ +( -32 -32 48 ) ( -32 32 48 ) ( 0 0 80 ) __TB_empty 0 0 0 0.0625 0.0625 0 0 0 +( 0 0 80 ) ( 32 -32 48 ) ( -32 -32 48 ) __TB_empty 0 0 0 0.0625 0.0625 0 0 0 +( 32 32 48 ) ( -32 32 48 ) ( -32 -32 48 ) __TB_empty 0 0 0 0.0625 0.0625 0 0 0 +( 0 0 80 ) ( -32 32 48 ) ( 32 32 48 ) __TB_empty 0 0 0 0.0625 0.0625 0 0 0 +( 0 0 80 ) ( 32 32 48 ) ( 32 -32 48 ) __TB_empty 0 0 0 0.0625 0.0625 0 0 0 +} +// brush 2 +{ +( 0 0 80 ) ( -32 32 112 ) ( -32 -32 112 ) __TB_empty 0 0 0 0.0625 0.0625 0 0 0 +( 0 0 80 ) ( -32 -32 112 ) ( 32 -32 112 ) __TB_empty 0 0 0 0.0625 0.0625 0 0 0 +( -32 32 112 ) ( 32 32 112 ) ( 32 -32 112 ) __TB_empty 0 0 0 0.0625 0.0625 0 0 0 +( 0 0 80 ) ( 32 32 112 ) ( -32 32 112 ) __TB_empty 0 0 0 0.0625 0.0625 0 0 0 +( 32 -32 112 ) ( 32 32 112 ) ( 0 0 80 ) __TB_empty 0 0 0 0.0625 0.0625 0 0 0 +} } diff --git a/Content/GameSettings.json b/Content/GameSettings.json index e00aac6..aa2854a 100644 --- a/Content/GameSettings.json +++ b/Content/GameSettings.json @@ -5,7 +5,7 @@ "Data": { "ProductName": "Goake", "CompanyName": "GoaLitiuM", - "FirstScene": "194e05f445ece24ec5448d886e1334df", + "FirstScene": "0733cc9b40d3d05366be64bbd9b59e21", "NoSplashScreen": true, "Time": "a55dc3c04da4ea3744b7f1994565beac", "Audio": "492a33824049ba25a8bdcdb148179e67", diff --git a/Content/Settings/TimeSettings.json b/Content/Settings/TimeSettings.json index d1ff977..62df3b4 100644 --- a/Content/Settings/TimeSettings.json +++ b/Content/Settings/TimeSettings.json @@ -1,11 +1,11 @@ { "ID": "a55dc3c04da4ea3744b7f1994565beac", "TypeName": "FlaxEditor.Content.Settings.TimeSettings", - "EngineBuild": 6220, + "EngineBuild": 6221, "Data": { - "UpdateFPS": 60.0, + "UpdateFPS": 120.0, "PhysicsFPS": 60.0, - "DrawFPS": 60.0, + "DrawFPS": 120.0, "TimeScale": 1.0, "MaxUpdateDeltaTime": 0.1 } diff --git a/Source/Game/Q3MapImporter.cs b/Source/Game/Q3MapImporter.cs index 8d22bc7..21c9caf 100644 --- a/Source/Game/Q3MapImporter.cs +++ b/Source/Game/Q3MapImporter.cs @@ -96,6 +96,15 @@ namespace Game (plane.Normal.X * plane.Normal.X) + (plane.Normal.Y * plane.Normal.Y) + (plane.Normal.Z * plane.Normal.Z)); } + + public float DistanceToPlane(Face face) + { + Plane plane = new Plane(v1, v2, v3); + + var center = (face.v1 + face.v2 + face.v3) / 3f; + + return plane.Normal.X * center.X + plane.Normal.Y * center.Y + plane.Normal.Z * center.Z - plane.D; + } } public struct Tetrahedron @@ -132,124 +141,6 @@ namespace Game const float epsilon = 0.00001f; - private List FindHull(List points, Edge edge, Vector3 planeNormal) - { - // picks the furthest point of the edge, filter out points from the formed triangle, - // and recursively repeat this until we have only the outermost points of the convex - // hull. - - if (points.Count == 0) - return new List(); - - // furthest point from edge - Vector3 furthest = Vector3.Zero; - float furthestDist = 0f; - foreach (var vert in points) - { - var edgeDir = (edge.v2 - edge.v1).Normalized; - var closest = edge.v1 + edgeDir * Vector3.Dot(vert - edge.v1, edgeDir); - - var dist = (vert - closest).Length; - if (dist > furthestDist) - { - furthest = vert; - furthestDist = dist; - } - } - - List set1 = new List(); - List set2 = new List(); - - var edgeNormal = Vector3.Cross(edge.v2 - edge.v1, planeNormal); - for (int i = 0; i < points.Count; i++) - { - var vert = points[i]; - if (vert == edge.v1 || vert == edge.v2 || vert == furthest) - continue; - - var normal1 = new Plane(vert, edge.v1, edge.v2).Normal; - var normal2 = new Plane(vert, edge.v2, furthest).Normal; - var normal3 = new Plane(vert, furthest, edge.v1).Normal; - - var d1 = Vector3.Dot(planeNormal, normal1); - var d2 = Vector3.Dot(planeNormal, normal2); - var d3 = Vector3.Dot(planeNormal, normal3); - - if ((d1 > 0 && d2 > 0 && d3 > 0) || - (d1 < 0 && d2 < 0 && d3 < 0)) - { - // all triangles formed by these points have similar normals, - } - else - { - // normals pointing different directions, eliminate - continue; - } - - var norm = (vert - edge.v1).Normalized; - var dd = Vector3.Dot(edgeNormal.Normalized, norm); - if (dd > epsilon) - { - set1.Add(vert); - } - else if (dd < -epsilon) - { - set2.Add(vert); - } - } - - List set = new List(new[] {furthest}); - set.AddRange(FindHull(set1, new Edge(edge.v1, furthest), planeNormal)); - set.AddRange(FindHull(set2, new Edge(furthest, edge.v2), planeNormal)); - return set; - } - - private List QuickHull(List points, Vector3 planeNormal) - { - // longest pair - Vector3 a1 = Vector3.Zero; - Vector3 a2 = Vector3.Zero; - foreach (var v1 in points) - { - foreach (var v2 in points) - { - if ((v2 - v1).LengthSquared > (a2 - a1).LengthSquared) - { - a1 = v1; - a2 = v2; - } - } - } - - var edgeNormal = Vector3.Cross(a2 - a1, planeNormal); - List set1 = new List(); - List set2 = new List(); - - foreach (var vert in points) - { - if (vert == a1 || vert == a2) - continue; - - var norm = (vert - a1) /*.Normalized*/; - var dd = Vector3.Dot(edgeNormal /*.Normalized*/, norm); - if (dd > epsilon) - { - set1.Add(vert); - } - else if (dd < -epsilon) - { - set2.Add(vert); - } - else - set1.Add(vert); - } - - List set = new List(new[] {a1, a2}); - set.AddRange(FindHull(set1, new Edge(a2, a1), planeNormal)); - set.AddRange(FindHull(set2, new Edge(a1, a2), planeNormal)); - return set; - } - private void SortPoints(List points, Vector3 planeNormal) { Vector3 center = Vector3.Zero; @@ -282,93 +173,171 @@ namespace Game private Face[] CreateInitialSimplex(Vector3[] points) { - // TODO: more optimal to find first set of points which are not coplanar? - - // find the longest edge - Vector3 v1 = Vector3.Zero; - Vector3 v2 = Vector3.Zero; - foreach (var p1 in points) + if (false) { - foreach (var p2 in points) + // TODO: more optimal to find first set of points which are not coplanar? + + // find the longest edge + Vector3 v1 = Vector3.Zero; + Vector3 v2 = Vector3.Zero; + foreach (var p1 in points) { - if ((p2 - p1).LengthSquared > (v2 - v1).LengthSquared) + foreach (var p2 in points) { - v1 = p1; - v2 = p2; + if ((p2 - p1).LengthSquared > (v2 - v1).LengthSquared) + { + v1 = p1; + v2 = p2; + } } } - } - Assert.IsTrue(v1 != v2, "a1 != a2"); + if (v1 == v2) + v1 = v2; - // find the furthest point from the edge to form a face - Vector3 v3 = Vector3.Zero; - float furthestDist = 0f; - foreach (var point in points) - { - //if (vert == a1 || vert == a2) - // continue; + Assert.IsTrue(v1 != v2, "a1 != a2"); - var edgeDir = (v2 - v1).Normalized; - var closest = v1 + edgeDir * Vector3.Dot(point - v1, edgeDir); - - var dist = (point - closest).Length; - if (dist > furthestDist) + // find the furthest point from the edge to form a face + Vector3 v3 = Vector3.Zero; + float furthestDist = 0f; + foreach (var point in points) { - v3 = point; - furthestDist = dist; + //if (vert == a1 || vert == a2) + // continue; + + var edgeDir = (v2 - v1).Normalized; + var closest = v1 + edgeDir * Vector3.Dot(point - v1, edgeDir); + + var dist = (point - closest).Length; + if (dist > furthestDist) + { + v3 = point; + furthestDist = dist; + } } - } - Assert.IsTrue(v3 != v1, "furthest != a1"); - Assert.IsTrue(v3 != v2, "furthest != a2"); + Assert.IsTrue(v3 != v1, "furthest != a1"); + Assert.IsTrue(v3 != v2, "furthest != a2"); - // find the furthest point from he face - Plane plane = new Plane(v1, v2, v3); - Vector3 v4 = Vector3.Zero; - float fourthDist = 0f; - foreach (var point in points) - { - if (point == v1 || point == v2 || point == v3) - continue; - - float distance = PointDistanceFromPlane(point, plane); - if (Math.Abs(distance) > fourthDist) + // find the furthest point from he face + Plane plane = new Plane(v1, v2, v3); + Vector3 v4 = Vector3.Zero; + float fourthDist = 0f; + foreach (var point in points) { - v4 = point; - fourthDist = distance; + if (point == v1 || point == v2 || point == v3) + continue; + + float distance = PointDistanceFromPlane(point, plane); + if (Math.Abs(distance) > fourthDist) + { + v4 = point; + fourthDist = distance; + } } - } - // make sure the tetrahedron is in counter-clockwise order - if (fourthDist > 0) - { - return new Face[] + // make sure the tetrahedron is in counter-clockwise order + if (fourthDist > 0) { - new Face(v1, v3, v2), - new Face(v1, v4, v3), - new Face(v1, v2, v4), - new Face(v2, v3, v4), - }; + return new Face[] + { + new Face(v1, v3, v2), + new Face(v1, v4, v3), + new Face(v1, v2, v4), + new Face(v2, v3, v4), + }; + } + else + { + return new Face[] + { + new Face(v1, v2, v3), + new Face(v1, v3, v4), + new Face(v1, v4, v2), + new Face(v2, v4, v3), + }; + } } else { - return new Face[] + Vector3 v1 = Vector3.Zero, v2 = Vector3.Zero, v3 = Vector3.Zero, v4 = Vector3.Zero; + bool found = false; + + foreach (var p1 in points) { - new Face(v1, v2, v3), - new Face(v1, v3, v4), - new Face(v1, v4, v2), - new Face(v2, v4, v3), - }; + foreach (var p2 in points) + { + if (p1 == p2) + continue; + + if (AreCoincident(p1, p2)) + continue; + + + foreach (var p3 in points) + { + if (p3 == p2 || p3 == p1) + continue; + + if (AreCollinear(p1, p2, p3)) + continue; + + foreach (var p4 in points) + { + if (p4 == p1 || p4 == p2 || p4 == p3) + continue; + + if (AreCoplanar(p1, p2, p3, p4)) + continue; + + found = true; + v1 = p1; + v2 = p2; + v3 = p3; + v4 = p4; + break; + } + } + } + } + + if (!found) + throw new Exception("CreateInitialSimplex failed"); + + Plane plane = new Plane(v1, v2, v3); + var fourthDist = PointDistanceFromPlane(v4, plane); + + if (fourthDist > 0) + { + return new Face[] + { + new Face(v1, v3, v2), + new Face(v1, v4, v3), + new Face(v1, v2, v4), + new Face(v2, v3, v4), + }; + } + else + { + return new Face[] + { + new Face(v1, v2, v3), + new Face(v1, v3, v4), + new Face(v1, v4, v2), + new Face(v2, v4, v3), + }; + } } } public class HalfEdge { public Face face; + //public Face oppositeFace; public HalfEdge opposite; public HalfEdge previous, next; + public Edge edge; //public bool horizonVisited; @@ -382,22 +351,20 @@ namespace Game //http://algolist.ru/maths/geom/convhull/qhull3d.php - private List QuickHull2(Vector3[] points) + private void PopulateOutsideSet(List> outsideSet, Face[] faces, Vector3[] points) { - var tetrahedron = CreateInitialSimplex(points); - - List> outsideSet = new List>(); foreach (var point in points) { - foreach (Face face in tetrahedron) + foreach (Face face in faces) { float distance = face.DistanceToPoint(point); - if (Math.Abs(distance) < epsilon) + /*if (Math.Abs(distance) < epsilon) { // point is in the plane, this gets merged distance = distance; } - else if (distance > 0) + else*/ + if (distance > 0) { //side.outsideSet.Add(point); outsideSet.Add(new Tuple(face, point)); @@ -405,6 +372,16 @@ namespace Game } } } + } + + private List QuickHull2(Vector3[] points) + { + Assert.IsTrue(points.Length >= 4, "points.Length >= 4"); + + var tetrahedron = CreateInitialSimplex(points); + + List> outsideSet = new List>(); + PopulateOutsideSet(outsideSet, tetrahedron, points); // all points not in side.outsideSet are inside in "inside" set @@ -494,7 +471,6 @@ namespace Game // grow hull - List> unclaimedPoints2 = new List>(); List horizonEdges = new List(); List hullFaces = new List(); @@ -539,6 +515,7 @@ namespace Game float distance = face.DistanceToPoint(point); if (Math.Abs(distance) > furthestDist) + //if (distance > furthestDist) { pointToAdd = fp; pointFace = face; @@ -546,16 +523,26 @@ namespace Game } } + Assert.IsTrue(furthestDist > 0, "furthestDist > 0"); Assert.IsTrue(pointToAdd != null, "pointToAdd != null"); outsideSet.Remove(pointToAdd); foreach (var face in hullFaces) + { face.visited = false; + foreach (var halfEdge in face.halfEdges) + { + Assert.IsTrue(halfEdge.opposite.opposite == halfEdge, "halfEdge.opposite.opposite == halfEdge"); + Assert.IsTrue(hullFaces.Contains(halfEdge.opposite.face), + "hullFaces.Contains(halfEdge.opposite.face)"); + } + } var hullFacesNew = new List(); + var unclaimedPoints = new List(); - AddPointToHull(pointToAdd.Item2, pointFace, unclaimedPoints2, outsideSet, horizonEdges, hullFacesNew); + AddPointToHull(pointToAdd.Item2, pointFace, unclaimedPoints, outsideSet, horizonEdges, hullFacesNew); // remove lit/seen/visited faces, their points were added to unclaimed points for (int i = 0; i < hullFaces.Count; i++) @@ -569,17 +556,31 @@ namespace Game hullFaces.AddRange(hullFacesNew); + foreach (var face in hullFaces) + { + face.visited = false; + foreach (var halfEdge in face.halfEdges) + { + Assert.IsTrue(halfEdge.opposite.opposite == halfEdge, + "2 halfEdge.opposite.opposite == halfEdge"); + Assert.IsTrue(hullFaces.Contains(halfEdge.opposite.face), + "2 hullFaces.Contains(halfEdge.opposite.face)"); + } + } - //outsideSet.AddRange(unclaimedPoints2); - //unclaimedPoints2.Clear(); - outsideSet = new List>(unclaimedPoints2); - unclaimedPoints2.Clear(); + foreach (var fb in outsideSet) + unclaimedPoints.Add(fb.Item2); - if (iterCount >= 3) - break; + outsideSet.Clear(); + PopulateOutsideSet(outsideSet, hullFaces.ToArray(), unclaimedPoints.ToArray()); + + //if (iterCount >= 3) + // break; if (hullFaces.Count > 1000 || iterCount > 1000) Assert.Fail("overflow"); + if (outsideSet.Count > 100000) + Assert.Fail("outsideSet overflow"); } List hullPoints = new List(hullFaces.Count * 3); @@ -593,23 +594,89 @@ namespace Game return hullPoints; } - private void AddPointToHull(Vector3 point, Face face, List> unclaimedPoints2, + private void AddUnique(List list, Vector3 point) + { + foreach (var p in list) + { + if ((point - p).Length < epsilon) + return; + } + list.Add(point); + } + + bool AreCoincident(Vector3 a, Vector3 b) + { + return (a - b).Length <= epsilon; + } + + bool AreCollinear(Vector3 a, Vector3 b, Vector3 c) + { + return Vector3.Cross(c - a, c - b).Length <= epsilon; + } + + bool AreCoplanar(Vector3 a, Vector3 b, Vector3 c, Vector3 d) + { + var n1 = Vector3.Cross(c - a, c - b); + var n2 = Vector3.Cross(d - a, d - b); + + var m1 = n1.Length; + var m2 = n2.Length; + + return m1 > epsilon + && m2 > epsilon + && AreCollinear(Vector3.Zero, + (1.0f / m1) * n1, + (1.0f / m2) * n2); + } + + private void AddPointToHull(Vector3 point, Face face, List unclaimedPoints, List> outsideSet, List horizonEdges, List hullFaces) { horizonEdges.Clear(); - CalculateHorizon(face, point, unclaimedPoints2, outsideSet, horizonEdges, face.halfEdges[0]); + CalculateHorizon(face, point, unclaimedPoints, outsideSet, horizonEdges, face.halfEdges[0]); // create new faces if (horizonEdges.Count > 0) { List newFaces = new List(); - HalfEdge first = horizonEdges.First(); - HalfEdge prev = null; + HalfEdge firstEdge = horizonEdges.First(); + HalfEdge prevEdge = null; foreach (var edge in horizonEdges) { var newFace = new Face(point, edge.edge.v1, edge.edge.v2); + var newPlane = new Plane(newFace.v1, newFace.v2, newFace.v3); + + var uniqPoints = new List(); + AddUnique(uniqPoints, newFace.v1); + AddUnique(uniqPoints, newFace.v2); + AddUnique(uniqPoints, newFace.v3); + + var fourtPoint = edge.opposite.next.edge.v2; + + AddUnique(uniqPoints, edge.opposite.next.edge.v1); + AddUnique(uniqPoints, edge.opposite.next.edge.v2); + AddUnique(uniqPoints, edge.opposite.previous.edge.v1); + AddUnique(uniqPoints, edge.opposite.previous.edge.v2); + + var distFromPlane = PointDistanceFromPlane(fourtPoint, newPlane); + if (Math.Abs(distFromPlane) < epsilon) + { + // both faces are coplanar, merge them together + + + distFromPlane = distFromPlane; + if (AreCoplanar(newFace.v1, newFace.v2, newFace.v3, fourtPoint)) + distFromPlane = distFromPlane; + } + else if (AreCoplanar(newFace.v1, newFace.v2, newFace.v3, fourtPoint)) + { + distFromPlane = distFromPlane; + } + + + var newEdges = new List(); foreach (var ne in newFace.GetEdges()) newEdges.Add(new HalfEdge(ne, newFace)); @@ -620,33 +687,159 @@ namespace Game newEdges[i].next = newEdges[(i + 1) % 3]; } - if (prev != null) + if (prevEdge != null) { - var prevEdge = newFaces.Last().halfEdges.Last(); - var lastEdge = newEdges.First(); - lastEdge.opposite = prevEdge; - prevEdge.opposite = lastEdge; + var prevAdjacentEdge = newFaces.Last().halfEdges.Last(); + var lastAdjacentEdge = newEdges.First(); + lastAdjacentEdge.opposite = prevAdjacentEdge; + prevAdjacentEdge.opposite = lastAdjacentEdge; } //edge.face = newFace; newEdges[1].opposite = edge.opposite; + edge.opposite.opposite = newEdges[1]; newFaces.Add(newFace); - prev = edge; + prevEdge = edge; } - if (prev != null) + if (prevEdge != null) { - var lastEdge = newFaces.Last().halfEdges.Last(); - var firstEdge = newFaces.First().halfEdges.First(); - lastEdge.opposite = firstEdge; - firstEdge.opposite = lastEdge; + var lastAdjacentEdge = newFaces.Last().halfEdges.Last(); + var firstAdjacentEdge = newFaces.First().halfEdges.First(); + lastAdjacentEdge.opposite = firstAdjacentEdge; + firstAdjacentEdge.opposite = lastAdjacentEdge; //first.previous.opposite = prev.next; //prev.next.opposite = first.previous; } - hullFaces.AddRange(newFaces); + // merge NONCONVEX_WRT_LARGER_FACE + const float tolerance = -1f; + //List discardedFaces = new List(); + if (false) + foreach (var newFace in newFaces) + { + HalfEdge edge = newFace.halfEdges[0]; + + bool convex = true; + do + { + Face oppositeFace = edge.opposite.face; + bool merge = false; + + + var p1 = new Plane(face.v1, face.v2, face.v3); + var p2 = new Plane(oppositeFace.v1, oppositeFace.v2, oppositeFace.v3); + + // ? + float faceArea, oppositeArea; + + { + HalfEdge areaEdgeStart = edge; + HalfEdge areaEdge = areaEdgeStart.previous; + Vector3 areaNorm = Vector3.Zero; + do + { + areaNorm += Vector3.Cross(areaEdge.edge.v1 - areaEdgeStart.edge.v1, + areaEdge.next.edge.v1 - areaEdgeStart.edge.v1); + areaEdge = areaEdge.previous; + + } while (areaEdge != areaEdgeStart); + + faceArea = areaNorm.Length; + } + { + HalfEdge areaEdgeStart = edge.opposite; + HalfEdge areaEdge = areaEdgeStart.previous; + Vector3 areaNorm = Vector3.Zero; + do + { + areaNorm += Vector3.Cross(areaEdge.edge.v1 - areaEdgeStart.edge.v1, + areaEdge.next.edge.v1 - areaEdgeStart.edge.v1); + areaEdge = areaEdge.previous; + + } while (areaEdge != areaEdgeStart); + + oppositeArea = areaNorm.Length; + } + + if (faceArea > oppositeArea) + { + if (edge.face.DistanceToPlane(edge.opposite.face) > -tolerance) + merge = true; + else if (edge.opposite.face.DistanceToPlane(edge.face) > -tolerance) + convex = false; + } + else + { + if (edge.opposite.face.DistanceToPlane(edge.face) > -tolerance) + merge = true; + else if (edge.face.DistanceToPlane(edge.opposite.face) > -tolerance) + convex = false; + } + + if (merge) + { + List discardedFaces = new List(); + { + discardedFaces.Add(oppositeFace); + + HalfEdge prev = edge.previous; + HalfEdge next = edge.next; + HalfEdge oppositePrev = edge.opposite.previous; + HalfEdge oppositeNext = edge.opposite.next; + + while (prev.opposite.face == oppositeFace) + { + prev = prev.previous; + oppositeNext = oppositeNext.next; + } + + while (next.opposite.face == oppositeFace) + { + oppositePrev = oppositePrev.previous; + next = next.next; + } + + for (HalfEdge e = oppositeNext; e != oppositePrev.next; e = e.next) + e.face = newFace; + + if (edge == face.halfEdges[0]) + face.halfEdges[0] = next; + + Face discardedFace = ConnectHalfEdges(newFace, oppositePrev, next); + Face discardedFace2 = ConnectHalfEdges(newFace, prev, oppositeNext); + + if (discardedFace != null) + discardedFaces.Add(discardedFace); + if (discardedFace2 != null) + discardedFaces.Add(discardedFace2); + } + + foreach (var dface in discardedFaces) + { + for (int i=0; i 0) + outsideSet[i] = new Tuple(newFace, outsideSet[i].Item2); + else + unclaimedPoints.Add(outsideSet[i].Item2); + } + } + } + + } + } while (edge != newFace.halfEdges[0]); + + + hullFaces.Add(newFace); + } + else + hullFaces.AddRange(newFaces); // verify foreach (var newFace in hullFaces) @@ -681,6 +874,8 @@ namespace Game Assert.IsTrue(halfEdge.previous != null, "AddPointToHull: halfEdge.previous != null"); Assert.IsTrue(halfEdge.next != null, "AddPointToHull: halfEdge.next != null"); + Assert.IsTrue(halfEdge.next.next.next == halfEdge, "AddPointToHull: halfEdge.next.next.next == halfEdge"); + Assert.IsTrue(halfEdge.previous.previous.previous == halfEdge, "AddPointToHull: halfEdge.previous.previous.previous == halfEdge"); Assert.IsTrue(halfEdge.opposite != null, "AddPointToHull: halfEdge.opposite != null"); //Assert.IsTrue(halfEdge.oppositeFace != null, "halfEdge.oppositeFace != null"); Assert.IsTrue(halfEdge.opposite.face != null, "AddPointToHull: halfEdge.opposite.face != null"); @@ -690,8 +885,57 @@ namespace Game } } + private Face ConnectHalfEdges(Face face, HalfEdge prev, HalfEdge edge) + { + Face discardedFace = null; + + if (prev.opposite.face == edge.opposite.face) + { + Face oppFace = edge.opposite.face; + HalfEdge hedgeOpp; + + if (prev == face.halfEdges[0]) + { + face.halfEdges[0] = edge; + } + + /*if (oppFace.numVertices() == 3)*/ + { + // then we can get rid of the + // opposite face altogether + hedgeOpp = edge.opposite.previous.opposite; + + //oppFace.mark = DELETED; + discardedFace = oppFace; + } /*else { + hedgeOpp = edge.opposite.next; + + if (oppFace.halfEdges[0] == hedgeOpp.previous) { + oppFace.halfEdges[0] = hedgeOpp; + } + hedgeOpp.previous = hedgeOpp.previous.prev; + hedgeOpp.previous.next = hedgeOpp; + }*/ + edge.previous = prev.previous; + edge.previous.next = edge; + + edge.opposite = hedgeOpp; + hedgeOpp.opposite = edge; + + // oppFace was modified, so need to recompute + //oppFace.computeNormalAndCentroid(); + } + else + { + prev.next = edge; + edge.previous = prev; + } + + return discardedFace; + } + // calculates the outermost edges of the geometry seen from the eyePoint - private void CalculateHorizon(Face face, Vector3 eyePoint, List> unclaimedPoints2, + private void CalculateHorizon(Face face, Vector3 eyePoint, List unclaimedPoints, List> outsideSet, List horizonEdges, HalfEdge currentEdge) { @@ -701,7 +945,7 @@ namespace Game foreach (var set in outsideSet) { if (set.Item1 == face) - unclaimedPoints2.Add(set); + unclaimedPoints.Add(set.Item2); } HalfEdge startingEdge = currentEdge; @@ -710,11 +954,17 @@ namespace Game Face oppositeFace = currentEdge.opposite.face; if (!oppositeFace.visited) { - if (oppositeFace.DistanceToPoint(eyePoint) > epsilon) + var dist = oppositeFace.DistanceToPoint(eyePoint); + if (dist > epsilon) { // positive distance means this is visible - CalculateHorizon(oppositeFace, eyePoint, unclaimedPoints2, outsideSet, horizonEdges, currentEdge.opposite); + CalculateHorizon(oppositeFace, eyePoint, unclaimedPoints, outsideSet, horizonEdges, + currentEdge.opposite); } + /*else if (Math.Abs(dist) <= epsilon) + { + dist = dist; + }*/ else { if (!horizonEdges.Contains(currentEdge)) @@ -731,7 +981,7 @@ namespace Game byte[] mapChars = File.ReadAllBytes(mapPath); var root = MapParser.Parse(mapChars); - const float cs = 300f; + const float cs = 3000f; Vector3[] cubePoints = new[] { @@ -744,107 +994,25 @@ namespace Game new Vector3(-cs, cs, cs), new Vector3(cs, cs, cs), }; - - Vector3[] cubeVerts = new[] - { - cubePoints[2], - cubePoints[3], - cubePoints[1], - cubePoints[2], - cubePoints[1], - cubePoints[0], - - cubePoints[0], - cubePoints[1], - cubePoints[4], - cubePoints[1], - cubePoints[5], - cubePoints[4], - - cubePoints[7], - cubePoints[5], - cubePoints[1], - cubePoints[7], - cubePoints[1], - cubePoints[3], - - cubePoints[6], - cubePoints[7], - cubePoints[3], - cubePoints[6], - cubePoints[3], - cubePoints[2], - - cubePoints[4], - cubePoints[6], - cubePoints[2], - cubePoints[4], - cubePoints[2], - cubePoints[0], - - cubePoints[4], - cubePoints[5], - cubePoints[6], - cubePoints[5], - cubePoints[7], - cubePoints[6], - }; - - Vector3[] cubeVerts2 = new[] - { - cubePoints[2], - cubePoints[3], - cubePoints[1], - cubePoints[0], - - cubePoints[0], - cubePoints[1], - cubePoints[4], - cubePoints[5], - - cubePoints[7], - cubePoints[5], - cubePoints[1], - cubePoints[3], - - cubePoints[6], - cubePoints[7], - cubePoints[3], - cubePoints[2], - - cubePoints[4], - cubePoints[6], - cubePoints[2], - cubePoints[0], - - cubePoints[4], - cubePoints[5], - cubePoints[6], - cubePoints[7], - }; + Vector3[] cubeVerts = QuickHull2(cubePoints).ToArray(); List vertices = new List(); - - // convert planes - vertices = QuickHull2(cubePoints); - //Assert.IsTrue(vertices.Count == 36, "vertices.Count == 36, count: " + vertices.Count); - if (false) - foreach (var brush in root.entities[0].brushes) + foreach (var brush in root.entities[0].brushes.Take(1)) { List brushVertices = new List(cubeVerts); //foreach (var plane in new [] { brush.planes.First() }) - foreach (var plane in brush.planes.Skip(0).Take(1)) + foreach (var plane in brush.planes.Reverse().Take(2)) //foreach (var plane in brush.planes) { Plane p = new Plane(plane.v1, plane.v2, plane.v3); - List clippedVerts = new List(); + Vector3 planeNormal = p.Normal; List newBrushVertices = new List(); List faceVertices = new List(); if (true) { - Func isFront = (f) => f > 0; - Func isBack = (f) => f < 0; + Func isFront = (f) => f > epsilon; + Func isBack = (f) => f < -epsilon; for (int i = 0; i < brushVertices.Count; i++) { @@ -852,8 +1020,9 @@ namespace Game Vector3 start = brushVertices[i]; Vector3 end = brushVertices[i2]; - var d1 = (start.X * p.Normal.X) + (start.Y * p.Normal.Y) + (start.Z * p.Normal.Z) + p.D; - var d2 = (end.X * p.Normal.X) + (end.Y * p.Normal.Y) + (end.Z * p.Normal.Z) + p.D; + var d1 = (start.X * planeNormal.X) + (start.Y * planeNormal.Y) + (start.Z * planeNormal.Z) + + p.D; + var d2 = (end.X * planeNormal.X) + (end.Y * planeNormal.Y) + (end.Z * planeNormal.Z) + p.D; if (isBack(d1)) { @@ -868,129 +1037,53 @@ namespace Game // include clip Ray ray2 = new Ray(start, (end - start).Normalized); if (p.Intersects(ref ray2, out Vector3 intersect2)) - { faceVertices.Add(intersect2); - clippedVerts.Add(intersect2); - } else d1 = d1; } } - - - if (i % 3 == 2) - { - if (faceVertices.Count == 4) - { - // quad, triangulize - /*newBrushVertices.Add(faceVertices[0]); - newBrushVertices.Add(faceVertices[1]); - newBrushVertices.Add(faceVertices[2]); - - newBrushVertices.Add(faceVertices[2]); - newBrushVertices.Add(faceVertices[3]); - newBrushVertices.Add(faceVertices[0]);*/ - - var hullPoints = QuickHull(faceVertices, -p.Normal); - - Assert.IsTrue(hullPoints.Count > 3, "QuickHull reduced quad to triangle!"); - - SortPoints(hullPoints, p.Normal); - - for (int j = 1; j <= hullPoints.Count - 2; j++) - { - Vector3 v1 = hullPoints[0]; - Vector3 v2 = hullPoints[j]; - Vector3 v3 = hullPoints[j + 1]; - - var normal = new Plane(v1, v2, v3).Normal; - if (Vector3.Dot(p.Normal, normal) < 0) - { - v2 = hullPoints[j + 1]; - v3 = hullPoints[j]; - } - - newBrushVertices.Add(v1); - newBrushVertices.Add(v2); - newBrushVertices.Add(v3); - } - } - else if (faceVertices.Count == 3) - { - // ok, either nothing was clipped or two got clipped - newBrushVertices.AddRange(faceVertices); - } - else if (faceVertices.Count == 0) - { - // ok, everything got clipped - d1 = d1; - } - else - d1 = d1; - - faceVertices.Clear(); - } } if (true) { - List clippedVerts2 = new List(); - foreach (var v in clippedVerts) + var newMeshPoints = new List(); + int duplis = 0; + foreach (var v in faceVertices) { - if (!clippedVerts2.Any(x => (v - x).Length < 0.01)) - clippedVerts2.Add(v); - } - - var hullPoints = QuickHull(clippedVerts2, -p.Normal); - SortPoints(hullPoints, p.Normal); - - for (int i = 1; i <= hullPoints.Count - 2; i++) - { - Vector3 v1 = hullPoints[0]; - Vector3 v2 = hullPoints[i]; - Vector3 v3 = hullPoints[i + 1]; - - var normal = new Plane(v1, v2, v3).Normal; - if (Vector3.Dot(p.Normal, normal) < 0) + bool found = false; + foreach (var vo in newMeshPoints) { - v2 = hullPoints[i + 1]; - v3 = hullPoints[i]; + if ((v - vo).Length < epsilon) + { + found = true; + duplis++; + break; + } } - newBrushVertices.Add(v1); - newBrushVertices.Add(v2); - newBrushVertices.Add(v3); + //if (!newMeshPoints.Contains(v)) + if (!found) + newMeshPoints.Add(v); } - /*var brushPoints = QuickHull(newBrushVertices); - newBrushVertices.Clear(); + if (duplis > 0) + Console.Print("duplicates: " + duplis); - for (int i = 1; i <= brushPoints.Count - 2; i++) + if (newMeshPoints.Count > 0) { - Vector3 v1 = hullPoints[0]; - Vector3 v2 = hullPoints[i]; - Vector3 v3 = hullPoints[i+1]; - - var normal = new Plane(v1, v2, v3).Normal; - if (Vector3.Dot(p.Normal, normal) < 0) - { - v2 = hullPoints[i + 1]; - v3 = hullPoints[i]; - } - - newBrushVertices.Add(v1); - newBrushVertices.Add(v2); - newBrushVertices.Add(v3); - - //flip = !flip; - }*/ + var hullPoints = QuickHull2(newMeshPoints.ToArray()); + newBrushVertices.Clear(); + newBrushVertices.AddRange(hullPoints); + } + else + newBrushVertices.Clear(); } } Assert.IsTrue(newBrushVertices.Count % 3 == 0, "invalid amount of vertices: " + newBrushVertices.Count); - Assert.IsTrue(newBrushVertices.Count > 0, - "brush was clipped completely, vertices: " + newBrushVertices.Count); + //Assert.IsTrue(newBrushVertices.Count > 0, + // "brush was clipped completely, vertices: " + newBrushVertices.Count); brushVertices.Clear(); brushVertices.AddRange(newBrushVertices); @@ -1000,41 +1093,6 @@ namespace Game vertices.AddRange(brushVertices); } - /*Actor levelActor = Actor.AddChild(); - levelActor.Name = "LevelGeometry"; - - int brushIndex = 1; - foreach (var brush in root.entities[0].brushes) - { - BoxBrush boxBrush = levelActor.AddChild(); - boxBrush.Size = new Vector3(1000f); - boxBrush.Mode = BrushMode.Additive; - boxBrush.Name = "Brush " + brushIndex; - - int planeIndex = 1; - foreach (var brushPlane in brush.planes) - { - Plane plane = new Plane(brushPlane.v1, brushPlane.v2, brushPlane.v3); - - BoxBrush planeBrush = levelActor.AddChild(); - - planeBrush.Mode = BrushMode.Subtractive; - planeBrush.Position = -plane.Normal * plane.D; - planeBrush.Orientation = Quaternion.LookRotation(plane.Normal); - planeBrush.Position = planeBrush.Position - (planeBrush.Transform.Forward * 1500f); - planeBrush.Size = new Vector3(3000f); - planeBrush.Name = "ClipPlane " + brushIndex + " - " + planeIndex; - planeIndex++; - } - - break; - - brushIndex++; - } - - levelActor.Orientation = Quaternion.Euler(90f, 0, 0); - - Scene.BuildCSG(200);*/ model = Content.CreateVirtualAsset(); model.SetupLODs(new int[] {1});