pre-give up quickhull

This commit is contained in:
GoaLitiuM
2021-07-11 16:04:46 +03:00
parent b7fcc6e5ab
commit 8b008e5004
8 changed files with 475 additions and 117 deletions

View File

@@ -105,6 +105,25 @@ namespace Game
return plane.Normal.X * center.X + plane.Normal.Y * center.Y + plane.Normal.Z * center.Z - plane.D;
}
public float GetArea()
{
Q3MapImporter.HalfEdge areaEdgeStart = halfEdges[0];
Q3MapImporter.HalfEdge areaEdge = areaEdgeStart.previous;
Vector3 areaNorm = Vector3.Zero;
int iters = 0;
do
{
if (iters++ > 1000)
throw new Exception("merge infinite loop");
areaNorm += Vector3.Cross(areaEdge.edge.v1 - areaEdgeStart.edge.v1,
areaEdge.next.edge.v1 - areaEdgeStart.edge.v1);
areaEdge = areaEdge.previous;
} while (areaEdge != areaEdgeStart);
return areaNorm.Length;
}
}
public struct Tetrahedron
@@ -347,6 +366,19 @@ namespace Game
this.face = face;
face.halfEdges.Add(this);
}
public Vector3 tail
{
get
{
return edge.v2;
}
set
{
edge.v2 = value;
opposite.edge.v1 = value;
}
}
}
//http://algolist.ru/maths/geom/convhull/qhull3d.php
@@ -562,7 +594,7 @@ namespace Game
foreach (var halfEdge in face.halfEdges)
{
Assert.IsTrue(halfEdge.opposite.opposite == halfEdge,
"2 halfEdge.opposite.opposite == halfEdge");
"2 halfEdge.opposite.opposite == halfEdge (degenerate face?)");
Assert.IsTrue(hullFaces.Contains(halfEdge.opposite.face),
"2 hullFaces.Contains(halfEdge.opposite.face)");
}
@@ -583,6 +615,23 @@ namespace Game
Assert.Fail("outsideSet overflow");
}
// merge faces with similar normals
List<Face> discardedFaces = new List<Face>();
for (int i = 0; i < hullFaces.Count; i++)
{
Face firstFace = hullFaces[i];
// if visible?
{
while (PostAdjacentMerge(firstFace, discardedFaces, hullFaces))
{
//
}
}
}
foreach (var f in discardedFaces)
hullFaces.Remove(f);
List<Vector3> hullPoints = new List<Vector3>(hullFaces.Count * 3);
foreach (var face in hullFaces)
{
@@ -629,6 +678,240 @@ namespace Game
(1.0f / m2) * n2);
}
private bool PostAdjacentMerge(Face face, List<Face> discardedFaces, List<Face> hullFaces)
{
float maxdot_minang = Mathf.Cos(Mathf.DegreesToRadians * 3f);
HalfEdge edge = face.halfEdges[0];
do
{
Face oppFace = edge.opposite.face;
bool merge = false;
Vector3 ni = new Plane(face.v1, face.v2, face.v3).Normal;
Vector3 nj = new Plane(oppFace.v1, oppFace.v2, oppFace.v3).Normal;
float dotP = Vector3.Dot(ni, nj);
if (dotP > maxdot_minang)
{
if (face.GetArea() >= oppFace.GetArea())
{
// check if we can merge the 2 faces
merge = canMergeFaces(edge, hullFaces);
}
}
if (merge)
{
// mergeAdjacentFace
if (!MergeAdjacentFaces(edge, face, face, discardedFaces))
{
throw new Exception("merge failure");
}
return true;
}
edge = edge.next;
} while (edge != face.halfEdges[0]);
return false;
}
private static int asdf = 0;
bool canMergeFaces(HalfEdge he, List<Face> hullFaces)
{
asdf++;
if (asdf == 22)
asdf = asdf;
Face face1 = he.face;
Face face2 = he.opposite.face;
// construct the merged face
List<HalfEdge> edges = new List<HalfEdge>();
Face mergedFace = new Face(new Vector3(float.NaN), new Vector3(float.NaN), new Vector3(float.NaN));
// copy the first face edges
HalfEdge heTwin = null;
HalfEdge heCopy = null;
HalfEdge startEdge = (face1.halfEdges[0] != he) ? face1.halfEdges[0] : face1.halfEdges[1];
HalfEdge copyHe = startEdge;
HalfEdge prevEdge = null;
HalfEdge firstEdge = null;
do
{
HalfEdge newEdge = new HalfEdge(copyHe.edge, mergedFace);
newEdge.opposite = copyHe.opposite;
newEdge.face = mergedFace;
newEdge.tail = copyHe.tail;
if(copyHe == he)
{
heTwin = copyHe.opposite;
heCopy = newEdge;
}
if (firstEdge == null)
firstEdge = newEdge;
if (prevEdge != null)
{
prevEdge.next = newEdge;
newEdge.previous = prevEdge;
}
copyHe = copyHe.next;
prevEdge = newEdge;
} while (copyHe != startEdge);
if (prevEdge != null)
{
prevEdge.next = firstEdge;
firstEdge.previous = prevEdge;
}
if (heCopy == null)
heCopy = firstEdge;
// copy the second face edges
prevEdge = null;
firstEdge = null;
copyHe = face2.halfEdges[0];
do
{
HalfEdge newEdge = new HalfEdge(copyHe.edge, mergedFace);
newEdge.opposite = copyHe.opposite;
newEdge.face = mergedFace;
newEdge.tail = copyHe.tail;
if (firstEdge == null)
firstEdge = newEdge;
if (heTwin == copyHe)
heTwin = newEdge;
if (prevEdge != null)
{
prevEdge.next = newEdge;
newEdge.previous = prevEdge;
}
copyHe = copyHe.next;
prevEdge = newEdge;
} while (copyHe != face2.halfEdges[0]);
if (prevEdge != null)
{
prevEdge.next = firstEdge;
firstEdge.previous = prevEdge;
}
if (heTwin == null)
heTwin = firstEdge;
mergedFace.v1 = mergedFace.halfEdges[0].edge.v1;
mergedFace.v2 = mergedFace.halfEdges[1].edge.v1;
mergedFace.v3 = mergedFace.halfEdges[2].edge.v1;
if (heCopy == null)
heTwin = heTwin;
Assert.IsTrue(heTwin != null, "heTwin != null");
HalfEdge hedgeAdjPrev = heCopy.previous;
HalfEdge hedgeAdjNext = heCopy.next;
HalfEdge hedgeOppPrev = heTwin.previous;
HalfEdge hedgeOppNext = heTwin.next;
hedgeOppPrev.next = hedgeAdjNext;
hedgeAdjNext.previous = hedgeOppPrev;
hedgeAdjPrev.next = hedgeOppNext;
hedgeOppNext.previous = hedgeAdjPrev;
// compute normal and centroid
//mergedFace.computeNormalAndCentroid();
// test the vertex distance
float mTolarenace = epsilon;//-1;
float mPlaneTolerance = epsilon;//-1f;
float maxDist = mPlaneTolerance;
List<Vector3> uniqPoints = new List<Vector3>();
foreach (var hullFace in hullFaces)
{
AddUnique(uniqPoints, hullFace.v1);
AddUnique(uniqPoints, hullFace.v2);
AddUnique(uniqPoints, hullFace.v3);
}
foreach (var point in uniqPoints)
{
float dist = mergedFace.DistanceToPoint(point);
if (dist > maxDist)
{
return false;
}
}
// check the convexity
HalfEdge qhe = mergedFace.halfEdges[0];
Assert.IsTrue(mergedFace.halfEdges.Count == 3, "mergedFace.halfEdges.Count == 3");
do
{
Vector3 vertex = qhe.tail;
Vector3 nextVertex = qhe.next.tail;
Vector3 edgeVector = (nextVertex - vertex).Normalized;
Vector3 outVector = Vector3.Cross(-(new Plane(mergedFace.v1, mergedFace.v2, mergedFace.v3).Normal), edgeVector);
HalfEdge testHe = qhe.next;
do
{
Vector3 testVertex = testHe.tail;
float dist = Vector3.Dot(testVertex - vertex, outVector);
if (dist > mTolarenace)
return false;
testHe = testHe.next;
} while (testHe != qhe.next);
qhe = qhe.next;
} while (qhe != mergedFace.halfEdges[0]);
Face oppFace = he.opposite.face;
HalfEdge hedgeOpp = he.opposite;
hedgeAdjPrev = he.previous;
hedgeAdjNext = he.next;
hedgeOppPrev = hedgeOpp.previous;
hedgeOppNext = hedgeOpp.next;
// check if we are lining up with the face in adjPrev dir
while (hedgeAdjPrev.opposite.face == oppFace)
{
hedgeAdjPrev = hedgeAdjPrev.previous;
hedgeOppNext = hedgeOppNext.next;
}
// check if we are lining up with the face in adjNext dir
while (hedgeAdjNext.opposite.face == oppFace)
{
hedgeOppPrev = hedgeOppPrev.previous;
hedgeAdjNext = hedgeAdjNext.next;
}
// no redundant merges, just clean merge of 2 neighbour faces
if (hedgeOppPrev.opposite.face == hedgeAdjNext.opposite.face)
{
return false;
}
if (hedgeAdjPrev.opposite.face == hedgeOppNext.opposite.face)
{
return false;
}
return true;
}
private void AddPointToHull(Vector3 point, Face face, List<Vector3> unclaimedPoints,
List<Tuple<Face, Vector3>> outsideSet,
List<HalfEdge> horizonEdges, List<Face> hullFaces)
@@ -715,128 +998,32 @@ namespace Game
}
// merge NONCONVEX_WRT_LARGER_FACE
const float tolerance = -1f;
//List<Face> discardedFaces = new List<Face>();
if (false)
foreach (var newFace in newFaces)
{
HalfEdge edge = newFace.halfEdges[0];
bool convex = true;
do
foreach (var newFace in newFaces)
{
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;
// if face visible?
while (AdjacentMerge(point, newFace, unclaimedPoints, outsideSet, true))
{
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;
// merge until failure
}
if (faceArea > oppositeArea)
hullFaces.Add(newFace);
}
foreach (var newFace in newFaces)
{
// if face non-convex?
// mark face as visible?
while (AdjacentMerge(point, newFace, unclaimedPoints, outsideSet, false))
{
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;
// merge until failure
}
if (merge)
{
List<Face> discardedFaces = new List<Face>();
{
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<outsideSet.Count; i++)
{
if (outsideSet[i].Item1 == dface)
{
float distance = face.DistanceToPoint(point);
if (distance > 0)
outsideSet[i] = new Tuple<Face, Vector3>(newFace, outsideSet[i].Item2);
else
unclaimedPoints.Add(outsideSet[i].Item2);
}
}
}
}
} while (edge != newFace.halfEdges[0]);
hullFaces.Add(newFace);
hullFaces.Add(newFace);
}
}
else
hullFaces.AddRange(newFaces);
@@ -885,6 +1072,128 @@ namespace Game
}
}
private bool AdjacentMerge(Vector3 point, Face face, List<Vector3> unclaimedPoints, List<Tuple<Face, Vector3>> outsideSet, bool mergeWrtLargerFace)
{
const float tolerance = -1f;
HalfEdge edge = face.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);
if (mergeWrtLargerFace)
{
float faceArea = edge.face.GetArea();
float oppositeArea = edge.opposite.face.GetArea();
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;
}
}
else
{
if (edge.face.DistanceToPlane(edge.opposite.face) > -tolerance ||
edge.opposite.face.DistanceToPlane(edge.face) > -tolerance)
{
merge = true;
}
}
if (merge)
{
List<Face> discardedFaces = new List<Face>();
// mergeAdjacentFace
if (!MergeAdjacentFaces(edge, face, face, discardedFaces))
{
throw new Exception("merge failure");
}
foreach (var dface in discardedFaces)
{
for (int i=0; i<outsideSet.Count; i++)
{
if (outsideSet[i].Item1 == dface)
{
float distance = face.DistanceToPoint(point);
if (distance > 0)
outsideSet[i] = new Tuple<Face, Vector3>(face, outsideSet[i].Item2);
else
unclaimedPoints.Add(outsideSet[i].Item2);
}
}
}
}
} while (edge != face.halfEdges[0]);
return false; // no merge
}
private bool MergeAdjacentFaces(HalfEdge edge, Face newFace, Face oldFace, List<Face> discardedFaces)
{
Face oppositeFace = edge.opposite.face;
discardedFaces.Add(oppositeFace);
HalfEdge prev = edge.previous;
HalfEdge next = edge.next;
HalfEdge oppositePrev = edge.opposite.previous;
HalfEdge oppositeNext = edge.opposite.next;
HalfEdge breakEdge = prev;
while (prev.opposite.face == oppositeFace)
{
prev = prev.previous;
oppositeNext = oppositeNext.next;
if (prev == breakEdge)
return false;
}
breakEdge = next;
while (next.opposite.face == oppositeFace)
{
oppositePrev = oppositePrev.previous;
next = next.next;
if (next == breakEdge)
return false;
}
for (HalfEdge e = oppositeNext; e != oppositePrev.next; e = e.next)
e.face = newFace;
if (edge == oldFace.halfEdges[0])
oldFace.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);
return true;
}
// merges adjacent faces
private Face ConnectHalfEdges(Face face, HalfEdge prev, HalfEdge edge)
{
Face discardedFace = null;
@@ -899,7 +1208,33 @@ namespace Game
face.halfEdges[0] = edge;
}
/*if (oppFace.numVertices() == 3)*/
bool isDegenerate = false;
{
// is this correct?
HalfEdge s = oppFace.halfEdges[0];
if (s.next.next.next != s)
isDegenerate = true;
else if (s.previous.previous.previous != s)
isDegenerate = true;
else if (s.next.next == s)
isDegenerate = true;
else if (s.previous.previous == s)
isDegenerate = true;
HalfEdge ee = s;
int numVerts = 0;
do
{
numVerts++;
ee = ee.next;
} while (ee != s);
if (numVerts <= 2)
isDegenerate = true;
}
//if (oppFace.numVertices() == 3)
if (!isDegenerate)
{
// then we can get rid of the
// opposite face altogether
@@ -907,15 +1242,17 @@ namespace Game
//oppFace.mark = DELETED;
discardedFace = oppFace;
} /*else {
}
else
{
hedgeOpp = edge.opposite.next;
if (oppFace.halfEdges[0] == hedgeOpp.previous) {
oppFace.halfEdges[0] = hedgeOpp;
}
hedgeOpp.previous = hedgeOpp.previous.prev;
hedgeOpp.previous = hedgeOpp.previous.previous;
hedgeOpp.previous.next = hedgeOpp;
}*/
}
edge.previous = prev.previous;
edge.previous.next = edge;