1534 lines
29 KiB
C++
1534 lines
29 KiB
C++
/*
|
|
* Copyright (c) 2016-2019 Irlan Robson https://irlanrobson.github.io
|
|
*
|
|
* This software is provided 'as-is', without any express or implied
|
|
* warranty. In no event will the authors be held liable for any damages
|
|
* arising from the use of this software.
|
|
* Permission is granted to anyone to use this software for any purpose,
|
|
* including commercial applications, and to alter it and redistribute it
|
|
* freely, subject to the following restrictions:
|
|
* 1. The origin of this software must not be misrepresented; you must not
|
|
* claim that you wrote the original software. If you use this software
|
|
* in a product, an acknowledgment in the product documentation would be
|
|
* appreciated but is not required.
|
|
* 2. Altered source versions must be plainly marked as such, and must not be
|
|
* misrepresented as being the original software.
|
|
* 3. This notice may not be removed or altered from any source distribution.
|
|
*/
|
|
|
|
#include <bounce/quickhull/qh_hull.h>
|
|
#include <bounce/common/draw.h>
|
|
|
|
static float32 qhFindAABB(u32 iMin[3], u32 iMax[3], const b3Vec3* vs, u32 count)
|
|
{
|
|
b3Vec3 min(B3_MAX_FLOAT, B3_MAX_FLOAT, B3_MAX_FLOAT);
|
|
iMin[0] = 0;
|
|
iMin[1] = 0;
|
|
iMin[2] = 0;
|
|
|
|
b3Vec3 max(-B3_MAX_FLOAT, -B3_MAX_FLOAT, -B3_MAX_FLOAT);
|
|
iMax[0] = 0;
|
|
iMax[1] = 0;
|
|
iMax[2] = 0;
|
|
|
|
for (u32 i = 0; i < count; ++i)
|
|
{
|
|
b3Vec3 v = vs[i];
|
|
|
|
for (u32 j = 0; j < 3; ++j)
|
|
{
|
|
if (v[j] < min[j])
|
|
{
|
|
min[j] = v[j];
|
|
iMin[j] = i;
|
|
}
|
|
|
|
if (v[j] > max[j])
|
|
{
|
|
max[j] = v[j];
|
|
iMax[j] = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 3.0f * (b3Abs(max.x) + b3Abs(max.y) + b3Abs(max.z)) * B3_EPSILON;
|
|
}
|
|
|
|
qhHull::qhHull()
|
|
{
|
|
m_vertexList.head = NULL;
|
|
m_vertexList.count = 0;
|
|
|
|
m_faceList.head = NULL;
|
|
m_faceList.count = 0;
|
|
|
|
m_buffer = NULL;
|
|
|
|
m_vertexCapacity = 0;
|
|
m_vertexCount = 0;
|
|
|
|
m_edgeCapacity = 0;
|
|
m_edgeCount = 0;
|
|
|
|
m_faceCapacity = 0;
|
|
m_faceCount = 0;
|
|
}
|
|
|
|
qhHull::~qhHull()
|
|
{
|
|
qhVertex* v = m_vertexList.head;
|
|
while (v)
|
|
{
|
|
qhVertex* v0 = v;
|
|
v = v->next;
|
|
|
|
FreeVertex(v0);
|
|
}
|
|
|
|
B3_ASSERT(m_vertexCount == 0);
|
|
|
|
qhFace* f = m_faceList.head;
|
|
while (f)
|
|
{
|
|
qhFace* f0 = f;
|
|
f = f->next;
|
|
|
|
qhHalfEdge* e = f0->edge;
|
|
do
|
|
{
|
|
qhHalfEdge* e0 = e;
|
|
e = e->next;
|
|
|
|
FreeEdge(e0);
|
|
} while (e != f0->edge);
|
|
|
|
FreeFace(f0);
|
|
}
|
|
|
|
B3_ASSERT(m_edgeCount == 0);
|
|
B3_ASSERT(m_faceCount == 0);
|
|
|
|
b3Free(m_buffer);
|
|
}
|
|
|
|
void qhHull::Construct(const b3Vec3* vs, u32 count)
|
|
{
|
|
B3_ASSERT(m_buffer == NULL);
|
|
B3_ASSERT(count > 0 && count >= 4);
|
|
|
|
// Compute memory buffer size for the worst case.
|
|
u32 size = 0;
|
|
|
|
// Hull using Euler's Formula
|
|
u32 V = count;
|
|
u32 E = 3 * V - 6;
|
|
u32 HE = 2 * E;
|
|
u32 F = 2 * V - 4;
|
|
|
|
size += V * sizeof(qhVertex);
|
|
size += HE * sizeof(qhHalfEdge);
|
|
size += F * sizeof(qhFace);
|
|
|
|
// Horizon
|
|
size += HE * sizeof(qhHalfEdge*);
|
|
|
|
// Saved horizon vertices
|
|
// One vertex per horizon edge
|
|
size += HE * sizeof(qhVertex*);
|
|
|
|
// Saved conflict vertices
|
|
size += V * sizeof(qhVertex*);
|
|
|
|
// New Faces
|
|
// One face per horizon edge
|
|
size += HE * sizeof(qhFace*);
|
|
|
|
// Allocate memory buffer
|
|
m_buffer = b3Alloc(size);
|
|
|
|
// Initialize free lists
|
|
m_vertexCapacity = V;
|
|
m_vertexCount = m_vertexCapacity;
|
|
m_freeVertices = NULL;
|
|
qhVertex* vertices = (qhVertex*)m_buffer;
|
|
for (u32 i = 0; i < V; ++i)
|
|
{
|
|
vertices[i].active = true;
|
|
FreeVertex(vertices + i);
|
|
}
|
|
B3_ASSERT(m_vertexCount == 0);
|
|
|
|
m_edgeCapacity = HE;
|
|
m_edgeCount = m_edgeCapacity;
|
|
m_freeEdges = NULL;
|
|
qhHalfEdge* edges = (qhHalfEdge*)((u8*)vertices + V * sizeof(qhVertex));
|
|
for (u32 i = 0; i < HE; ++i)
|
|
{
|
|
edges[i].active = true;
|
|
FreeEdge(edges + i);
|
|
}
|
|
B3_ASSERT(m_edgeCount == 0);
|
|
|
|
m_faceCapacity = F;
|
|
m_faceCount = m_faceCapacity;
|
|
m_freeFaces = NULL;
|
|
qhFace* faces = (qhFace*)((u8*)edges + HE * sizeof(qhHalfEdge));
|
|
for (u32 i = 0; i < F; ++i)
|
|
{
|
|
qhFace* f = faces + i;
|
|
f->conflictList.head = NULL;
|
|
f->conflictList.count = 0;
|
|
f->active = true;
|
|
FreeFace(f);
|
|
}
|
|
B3_ASSERT(m_faceCount == 0);
|
|
|
|
m_horizon = (qhHalfEdge**)((u8*)faces + F * sizeof(qhFace));
|
|
m_horizonCount = 0;
|
|
|
|
m_horizonVertices = (qhVertex**)((u8*)m_horizon + HE * sizeof(qhHalfEdge*));
|
|
|
|
m_conflictVertices = (qhVertex**)((u8*)m_horizonVertices + HE * sizeof(qhVertex*));
|
|
m_conflictCount = 0;
|
|
|
|
m_newFaces = (qhFace**)((u8*)m_conflictVertices + V * sizeof(qhVertex*));
|
|
m_newFaceCount = 0;
|
|
|
|
m_iterations = 0;
|
|
|
|
// Build initial tetrahedron
|
|
if (!BuildInitialHull(vs, count))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Run Quickhull
|
|
qhVertex* eye = FindEyeVertex();
|
|
while (eye)
|
|
{
|
|
Validate();
|
|
ValidateConvexity();
|
|
|
|
AddEyeVertex(eye);
|
|
|
|
eye = FindEyeVertex();
|
|
|
|
++m_iterations;
|
|
}
|
|
|
|
Validate();
|
|
ValidateConvexity();
|
|
|
|
#if 0
|
|
// Ensure the hull contains the original vertex set
|
|
for (u32 i = 0; i < count; ++i)
|
|
{
|
|
b3Vec3 v = vs[i];
|
|
|
|
for (qhFace* f = m_faceList.head; f; f = f->next)
|
|
{
|
|
float32 d = b3Distance(v, f->plane);
|
|
B3_ASSERT(d < m_tolerance);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool qhHull::BuildInitialHull(const b3Vec3* vertices, u32 vertexCount)
|
|
{
|
|
if (vertexCount < 4)
|
|
{
|
|
B3_ASSERT(false);
|
|
return false;
|
|
}
|
|
|
|
u32 i1 = 0, i2 = 0;
|
|
|
|
{
|
|
// Find the points that maximizes the distance along the
|
|
// canonical axes.
|
|
// Also store a tolerance for coplanarity checks.
|
|
u32 aabbMin[3], aabbMax[3];
|
|
m_tolerance = qhFindAABB(aabbMin, aabbMax, vertices, vertexCount);
|
|
|
|
// Find the longest segment.
|
|
float32 d0 = 0.0f;
|
|
|
|
for (u32 i = 0; i < 3; ++i)
|
|
{
|
|
b3Vec3 A = vertices[aabbMin[i]];
|
|
b3Vec3 B = vertices[aabbMax[i]];
|
|
|
|
float32 d = b3DistanceSquared(A, B);
|
|
|
|
if (d > d0)
|
|
{
|
|
d0 = d;
|
|
i1 = aabbMin[i];
|
|
i2 = aabbMax[i];
|
|
}
|
|
}
|
|
|
|
// Coincidence check
|
|
if (d0 <= B3_EPSILON * B3_EPSILON)
|
|
{
|
|
B3_ASSERT(false);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
B3_ASSERT(i1 != i2);
|
|
|
|
b3Vec3 A = vertices[i1];
|
|
b3Vec3 B = vertices[i2];
|
|
|
|
u32 i3 = 0;
|
|
|
|
{
|
|
// Find the triangle which has the largest area.
|
|
float32 a0 = 0.0f;
|
|
|
|
for (u32 i = 0; i < vertexCount; ++i)
|
|
{
|
|
if (i == i1 || i == i2)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
b3Vec3 C = vertices[i];
|
|
|
|
float32 a = b3AreaSquared(A, B, C);
|
|
|
|
if (a > a0)
|
|
{
|
|
a0 = a;
|
|
i3 = i;
|
|
}
|
|
}
|
|
|
|
// Colinear check.
|
|
if (a0 <= (2.0f * B3_EPSILON) * (2.0f * B3_EPSILON))
|
|
{
|
|
B3_ASSERT(false);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
B3_ASSERT(i3 != i1 && i3 != i2);
|
|
|
|
b3Vec3 C = vertices[i3];
|
|
|
|
b3Vec3 N = b3Cross(B - A, C - A);
|
|
N.Normalize();
|
|
|
|
b3Plane plane(N, A);
|
|
|
|
u32 i4 = 0;
|
|
|
|
{
|
|
// Find the furthest point from the triangle plane.
|
|
float32 d0 = 0.0f;
|
|
|
|
for (u32 i = 0; i < vertexCount; ++i)
|
|
{
|
|
if (i == i1 || i == i2 || i == i3)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
b3Vec3 D = vertices[i];
|
|
|
|
float32 d = b3Abs(b3Distance(D, plane));
|
|
|
|
if (d > d0)
|
|
{
|
|
d0 = d;
|
|
i4 = i;
|
|
}
|
|
}
|
|
|
|
// Coplanar check.
|
|
if (d0 <= m_tolerance)
|
|
{
|
|
B3_ASSERT(false);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
B3_ASSERT(i4 != i1 && i4 != i2 && i4 != i3);
|
|
|
|
// Add okay simplex to the hull.
|
|
b3Vec3 D = vertices[i4];
|
|
|
|
qhVertex* v1 = AddVertex(A);
|
|
qhVertex* v2 = AddVertex(B);
|
|
qhVertex* v3 = AddVertex(C);
|
|
qhVertex* v4 = AddVertex(D);
|
|
|
|
if (b3Distance(D, plane) < 0.0f)
|
|
{
|
|
AddFace(v1, v2, v3);
|
|
AddFace(v4, v2, v1);
|
|
AddFace(v4, v3, v2);
|
|
AddFace(v4, v1, v3);
|
|
}
|
|
else
|
|
{
|
|
// Ensure CCW order.
|
|
AddFace(v1, v3, v2);
|
|
AddFace(v4, v1, v2);
|
|
AddFace(v4, v2, v3);
|
|
AddFace(v4, v3, v1);
|
|
}
|
|
|
|
// Connectivity check.
|
|
Validate();
|
|
|
|
// Add remaining points to the conflict lists on each face.
|
|
for (u32 i = 0; i < vertexCount; ++i)
|
|
{
|
|
// Skip hull vertices.
|
|
if (i == i1 || i == i2 || i == i3 || i == i4)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
b3Vec3 p = vertices[i];
|
|
|
|
// Ignore internal points since they can't be in the hull.
|
|
float32 d0 = m_tolerance;
|
|
qhFace* f0 = NULL;
|
|
|
|
for (qhFace* f = m_faceList.head; f != NULL; f = f->next)
|
|
{
|
|
float32 d = b3Distance(p, f->plane);
|
|
if (d > d0)
|
|
{
|
|
d0 = d;
|
|
f0 = f;
|
|
}
|
|
}
|
|
|
|
if (f0)
|
|
{
|
|
qhVertex* v = AllocateVertex();
|
|
v->position = p;
|
|
v->conflictFace = f0;
|
|
f0->conflictList.PushFront(v);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
qhVertex* qhHull::FindEyeVertex() const
|
|
{
|
|
// Find the furthest conflict point.
|
|
float32 d0 = m_tolerance;
|
|
qhVertex* v0 = NULL;
|
|
|
|
for (qhFace* f = m_faceList.head; f != NULL; f = f->next)
|
|
{
|
|
for (qhVertex* v = f->conflictList.head; v != NULL; v = v->next)
|
|
{
|
|
float32 d = b3Distance(v->position, f->plane);
|
|
if (d > d0)
|
|
{
|
|
d0 = d;
|
|
v0 = v;
|
|
}
|
|
}
|
|
}
|
|
|
|
return v0;
|
|
}
|
|
|
|
void qhHull::AddEyeVertex(qhVertex* eye)
|
|
{
|
|
FindHorizon(eye);
|
|
AddNewFaces(eye);
|
|
MergeNewFaces();
|
|
ResolveOrphans();
|
|
}
|
|
|
|
void qhHull::FindHorizon(qhVertex* eye)
|
|
{
|
|
// Mark faces
|
|
for (qhFace* face = m_faceList.head; face != NULL; face = face->next)
|
|
{
|
|
float32 d = b3Distance(eye->position, face->plane);
|
|
if (d > m_tolerance)
|
|
{
|
|
face->mark = qhFaceMark::e_visible;
|
|
}
|
|
else
|
|
{
|
|
face->mark = qhFaceMark::e_invisible;
|
|
}
|
|
}
|
|
|
|
// Find the horizon
|
|
m_horizonCount = 0;
|
|
for (qhFace* face = m_faceList.head; face != NULL; face = face->next)
|
|
{
|
|
if (face->mark == qhFaceMark::e_invisible)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
qhHalfEdge* begin = face->edge;
|
|
qhHalfEdge* edge = begin;
|
|
do
|
|
{
|
|
qhHalfEdge* twin = edge->twin;
|
|
qhFace* other = twin->face;
|
|
|
|
if (other->mark == qhFaceMark::e_invisible)
|
|
{
|
|
m_horizon[m_horizonCount++] = edge;
|
|
}
|
|
|
|
edge = edge->next;
|
|
} while (edge != begin);
|
|
}
|
|
|
|
// Sort the horizon in CCW order
|
|
B3_ASSERT(m_horizonCount > 0);
|
|
for (u32 i = 0; i < m_horizonCount - 1; ++i)
|
|
{
|
|
qhHalfEdge* e1 = m_horizon[i]->twin;
|
|
qhVertex* v1 = e1->tail;
|
|
|
|
for (u32 j = i + 1; j < m_horizonCount; ++j)
|
|
{
|
|
// Ensure unique edges
|
|
B3_ASSERT(m_horizon[i] != m_horizon[j]);
|
|
|
|
qhHalfEdge* e2 = m_horizon[j];
|
|
qhVertex* v2 = e2->tail;
|
|
|
|
if (v1 == v2)
|
|
{
|
|
b3Swap(m_horizon[j], m_horizon[i + 1]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void qhHull::AddNewFaces(qhVertex* eye)
|
|
{
|
|
// Ensure CCW horizon order.
|
|
// Usually it can fail hit if face merging is disable.
|
|
B3_ASSERT(m_horizonCount > 0);
|
|
for (u32 i = 0; i < m_horizonCount; ++i)
|
|
{
|
|
qhHalfEdge* e1 = m_horizon[i]->twin;
|
|
|
|
u32 j = i + 1 < m_horizonCount ? i + 1 : 0;
|
|
qhHalfEdge* e2 = m_horizon[j];
|
|
|
|
B3_ASSERT(e1->tail == e2->tail);
|
|
}
|
|
|
|
// Save horizon vertices
|
|
for (u32 i = 0; i < m_horizonCount; ++i)
|
|
{
|
|
qhHalfEdge* edge = m_horizon[i];
|
|
|
|
m_horizonVertices[i] = edge->tail;
|
|
}
|
|
|
|
// Remove the eye vertex from the conflict list
|
|
b3Vec3 eyePosition = eye->position;
|
|
|
|
eye->conflictFace->conflictList.Remove(eye);
|
|
FreeVertex(eye);
|
|
|
|
// Add the eye point to the hull
|
|
qhVertex* v1 = AddVertex(eyePosition);
|
|
|
|
// Save conflict vertices
|
|
m_conflictCount = 0;
|
|
|
|
// Remove visible faces
|
|
qhFace* f = m_faceList.head;
|
|
while (f)
|
|
{
|
|
// Skip invisible faces.
|
|
if (f->mark == qhFaceMark::e_invisible)
|
|
{
|
|
f = f->next;
|
|
continue;
|
|
}
|
|
|
|
qhVertex* v = f->conflictList.head;
|
|
while (v)
|
|
{
|
|
// Save vertex
|
|
m_conflictVertices[m_conflictCount++] = v;
|
|
|
|
// Remove vertex from face
|
|
v->conflictFace = NULL;
|
|
v = f->conflictList.Remove(v);
|
|
}
|
|
|
|
// Remove face
|
|
f = RemoveFace(f);
|
|
}
|
|
|
|
// Add new faces to the hull
|
|
m_newFaceCount = 0;
|
|
for (u32 i = 0; i < m_horizonCount; ++i)
|
|
{
|
|
u32 j = i + 1 < m_horizonCount ? i + 1 : 0;
|
|
|
|
qhVertex* v2 = m_horizonVertices[i];
|
|
qhVertex* v3 = m_horizonVertices[j];
|
|
|
|
m_newFaces[m_newFaceCount++] = AddFace(v1, v2, v3);
|
|
}
|
|
}
|
|
|
|
void qhHull::ResolveOrphans()
|
|
{
|
|
// Move the orphaned conflict vertices into the new faces
|
|
// Remove internal conflict vertices
|
|
for (u32 i = 0; i < m_conflictCount; ++i)
|
|
{
|
|
qhVertex* v = m_conflictVertices[i];
|
|
|
|
b3Vec3 p = v->position;
|
|
|
|
float32 d0 = m_tolerance;
|
|
qhFace* f0 = NULL;
|
|
|
|
for (u32 j = 0; j < m_newFaceCount; ++j)
|
|
{
|
|
qhFace* nf = m_newFaces[j];
|
|
|
|
// Was the face deleted due to merging?
|
|
if (nf->active == false)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
float32 d = b3Distance(p, nf->plane);
|
|
if (d > d0)
|
|
{
|
|
d0 = d;
|
|
f0 = nf;
|
|
}
|
|
}
|
|
|
|
if (f0)
|
|
{
|
|
// Add conflict vertex to the new face
|
|
f0->conflictList.PushFront(v);
|
|
v->conflictFace = f0;
|
|
}
|
|
else
|
|
{
|
|
// Remove conflict vertex
|
|
FreeVertex(v);
|
|
}
|
|
}
|
|
}
|
|
|
|
qhVertex* qhHull::AddVertex(const b3Vec3& position)
|
|
{
|
|
qhVertex* v = AllocateVertex();
|
|
v->position = position;
|
|
v->conflictFace = NULL;
|
|
|
|
m_vertexList.PushFront(v);
|
|
|
|
return v;
|
|
}
|
|
|
|
qhHalfEdge* qhHull::FindHalfEdge(const qhVertex* v1, const qhVertex* v2) const
|
|
{
|
|
for (qhFace* face = m_faceList.head; face != NULL; face = face->next)
|
|
{
|
|
qhHalfEdge* e = face->edge;
|
|
do
|
|
{
|
|
B3_ASSERT(e->active == true);
|
|
B3_ASSERT(e->twin != NULL);
|
|
B3_ASSERT(e->twin->active == true);
|
|
|
|
if (e->tail == v1 && e->twin->tail == v2)
|
|
{
|
|
return e;
|
|
}
|
|
|
|
if (e->tail == v2 && e->twin->tail == v1)
|
|
{
|
|
return e->twin;
|
|
}
|
|
|
|
e = e->next;
|
|
} while (e != face->edge);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static B3_FORCE_INLINE b3Vec3 b3Newell(const b3Vec3& a, const b3Vec3& b)
|
|
{
|
|
return b3Vec3((a.y - b.y) * (a.z + b.z), (a.z - b.z) * (a.x + b.x), (a.x - b.x) * (a.y + b.y));
|
|
}
|
|
|
|
// Compute face centroid, normal, and area
|
|
static void b3ResetFaceData(qhFace* face)
|
|
{
|
|
// Compute polygon centroid
|
|
b3Vec3 c;
|
|
c.SetZero();
|
|
|
|
u32 count = 0;
|
|
qhHalfEdge* e = face->edge;
|
|
do
|
|
{
|
|
b3Vec3 v = e->tail->position;
|
|
c += v;
|
|
++count;
|
|
e = e->next;
|
|
} while (e != face->edge);
|
|
|
|
B3_ASSERT(count >= 3);
|
|
c /= float32(count);
|
|
|
|
// Compute normal
|
|
b3Vec3 n;
|
|
n.SetZero();
|
|
|
|
e = face->edge;
|
|
do
|
|
{
|
|
b3Vec3 v1 = e->tail->position;
|
|
b3Vec3 v2 = e->next->tail->position;
|
|
|
|
// Shift the polygon origin to the centroid
|
|
v1 -= c;
|
|
v2 -= c;
|
|
|
|
// Apply Newell's method
|
|
n += b3Newell(v1, v2);
|
|
|
|
e = e->next;
|
|
} while (e != face->edge);
|
|
|
|
// Centroid
|
|
face->center = c;
|
|
|
|
float32 len = b3Length(n);
|
|
B3_ASSERT(len > B3_EPSILON);
|
|
n /= len;
|
|
|
|
// Area
|
|
face->area = 0.5f * len;
|
|
|
|
// Normal
|
|
face->plane.normal = n;
|
|
face->plane.offset = b3Dot(n, c);
|
|
}
|
|
|
|
static u32 b3VertexCount(const qhFace* face)
|
|
{
|
|
u32 n = 0;
|
|
qhHalfEdge* e = face->edge;
|
|
do
|
|
{
|
|
++n;
|
|
e = e->next;
|
|
} while (e != face->edge);
|
|
return n;
|
|
}
|
|
|
|
qhHalfEdge* qhHull::FixMerge(qhFace* face1, qhHalfEdge* ein)
|
|
{
|
|
qhHalfEdge* eout = ein->next;
|
|
|
|
B3_ASSERT(ein->twin->face == eout->twin->face);
|
|
|
|
qhFace* face3 = ein->twin->face;
|
|
|
|
u32 count = b3VertexCount(face3);
|
|
|
|
// Is the face 3 a triangle?
|
|
if (count == 3)
|
|
{
|
|
qhHalfEdge* nextEdge = eout->next;
|
|
|
|
// Unlink incoming edge from face 1
|
|
B3_ASSERT(ein->prev->next == ein);
|
|
ein->prev->next = ein->twin->next;
|
|
|
|
// Unlink incoming edge twin from face 3
|
|
B3_ASSERT(ein->twin->next->prev == ein->twin);
|
|
ein->twin->next->prev = ein->prev;
|
|
|
|
B3_ASSERT(ein->face == face1);
|
|
if (face1->edge == ein)
|
|
{
|
|
face1->edge = ein->prev;
|
|
}
|
|
|
|
// Set incoming edge twin face reference the face 1
|
|
B3_ASSERT(ein->twin->next->face == face3);
|
|
ein->twin->next->face = face1;
|
|
|
|
// Unlink outgoing edge from face 1
|
|
B3_ASSERT(eout->next->prev == eout);
|
|
eout->next->prev = eout->twin->prev;
|
|
B3_ASSERT(eout->twin->prev->next == eout->twin);
|
|
eout->twin->prev->next = eout->next;
|
|
|
|
B3_ASSERT(eout->face == face1);
|
|
if (face1->edge == eout)
|
|
{
|
|
face1->edge = eout->next;
|
|
}
|
|
|
|
// Reset face 1 data
|
|
b3ResetFaceData(face1);
|
|
|
|
// Validate face 1
|
|
Validate(face1);
|
|
|
|
// Remove outgoing vertex
|
|
m_vertexList.Remove(eout->tail);
|
|
FreeVertex(eout->tail);
|
|
|
|
// Remove incoming edge
|
|
FreeEdge(ein->twin);
|
|
FreeEdge(ein);
|
|
|
|
// Remove outgoing edge
|
|
FreeEdge(eout->twin);
|
|
FreeEdge(eout);
|
|
|
|
// Move face 3 conflict vertices into face 1
|
|
qhVertex* v = face3->conflictList.head;
|
|
while (v)
|
|
{
|
|
qhVertex* v0 = v;
|
|
v = face3->conflictList.Remove(v);
|
|
|
|
face1->conflictList.PushFront(v0);
|
|
v0->conflictFace = face1;
|
|
}
|
|
|
|
// Remove face 3
|
|
m_faceList.Remove(face3);
|
|
FreeFace(face3);
|
|
|
|
// Return the next edge
|
|
return nextEdge;
|
|
}
|
|
else
|
|
{
|
|
qhHalfEdge* nextEdge = eout->next;
|
|
|
|
// Extend the incoming edge to the next vertex
|
|
B3_ASSERT(ein->twin->tail == eout->tail);
|
|
ein->twin->tail = eout->twin->tail;
|
|
|
|
// Remove outgoing vertex
|
|
m_vertexList.Remove(eout->tail);
|
|
FreeVertex(eout->tail);
|
|
|
|
// Unlink outgoing edge from face 1
|
|
B3_ASSERT(eout->prev->next == eout);
|
|
eout->prev->next = eout->next;
|
|
B3_ASSERT(eout->next->prev == eout);
|
|
eout->next->prev = eout->prev;
|
|
|
|
B3_ASSERT(eout->face == face1);
|
|
if (face1->edge == eout)
|
|
{
|
|
face1->edge = eout->next;
|
|
}
|
|
|
|
// Reset face 1 data
|
|
b3ResetFaceData(face1);
|
|
|
|
// Validate face 1
|
|
Validate(face1);
|
|
|
|
// Unlink outgoing edge twin from face 3
|
|
B3_ASSERT(eout->twin->prev->next == eout->twin);
|
|
eout->twin->prev->next = eout->twin->next;
|
|
B3_ASSERT(eout->twin->next->prev == eout->twin);
|
|
eout->twin->next->prev = eout->twin->prev;
|
|
|
|
B3_ASSERT(eout->twin->face == face3);
|
|
if (face3->edge == eout->twin)
|
|
{
|
|
face3->edge = eout->twin->next;
|
|
}
|
|
|
|
// Remove outgoing edge
|
|
FreeEdge(eout->twin);
|
|
FreeEdge(eout);
|
|
|
|
// Return the next edge
|
|
return nextEdge;
|
|
}
|
|
|
|
// Return the next edge to the given edge
|
|
return eout;
|
|
}
|
|
|
|
qhFace* qhHull::RemoveEdge(qhHalfEdge* edge)
|
|
{
|
|
B3_ASSERT(edge->active == true);
|
|
|
|
qhFace* face1 = edge->face;
|
|
B3_ASSERT(face1->active == true);
|
|
B3_ASSERT(edge->twin->active == true);
|
|
qhFace* face2 = edge->twin->face;
|
|
|
|
// Edge must be shared.
|
|
B3_ASSERT(face2 != NULL);
|
|
B3_ASSERT(face2->active == true);
|
|
B3_ASSERT(face2 != face1);
|
|
|
|
// Merge face 2 into face 1
|
|
|
|
// Set face 2 edges owner face to face 1,
|
|
// except the twin edge which will be deleted
|
|
for (qhHalfEdge* e2 = edge->twin->next; e2 != edge->twin; e2 = e2->next)
|
|
{
|
|
B3_ASSERT(e2->face == face2);
|
|
e2->face = face1;
|
|
}
|
|
|
|
// Set the face 1 to reference a non-deleted edge
|
|
B3_ASSERT(edge->face == face1);
|
|
if (face1->edge == edge)
|
|
{
|
|
face1->edge = edge->next;
|
|
}
|
|
|
|
// Unlink edge from face 1
|
|
B3_ASSERT(edge->prev->next == edge);
|
|
edge->prev->next = edge->twin->next;
|
|
B3_ASSERT(edge->next->prev == edge);
|
|
edge->next->prev = edge->twin->prev;
|
|
|
|
B3_ASSERT(edge->twin->prev->next == edge->twin);
|
|
edge->twin->prev->next = edge->next;
|
|
B3_ASSERT(edge->twin->next->prev == edge->twin);
|
|
edge->twin->next->prev = edge->prev;
|
|
|
|
// Reset face 1 data
|
|
b3ResetFaceData(face1);
|
|
|
|
// Validate face 1
|
|
Validate(face1);
|
|
|
|
// Move face 2 conflict vertices into face 1
|
|
qhVertex* v = face2->conflictList.head;
|
|
while (v)
|
|
{
|
|
qhVertex* v0 = v;
|
|
v = face2->conflictList.Remove(v);
|
|
|
|
face1->conflictList.PushFront(v0);
|
|
v0->conflictFace = face1;
|
|
}
|
|
|
|
// Remove face 2
|
|
m_faceList.Remove(face2);
|
|
FreeFace(face2);
|
|
|
|
// Remove edge
|
|
FreeEdge(edge->twin);
|
|
FreeEdge(edge);
|
|
|
|
// Repair topological errors in the face
|
|
while (FixFace(face1));
|
|
|
|
// Return face 1
|
|
return face1;
|
|
}
|
|
|
|
bool qhHull::FixFace(qhFace* face)
|
|
{
|
|
// Maintained invariants:
|
|
// - Each vertex must have at least three neighbor faces
|
|
// - Face 1 must be convex
|
|
|
|
// Search a incoming (and outgoing edge) in the face 1
|
|
// which have the same neighbour face.
|
|
qhHalfEdge* edge = NULL;
|
|
|
|
qhHalfEdge* ein = face->edge;
|
|
do
|
|
{
|
|
qhHalfEdge* eout = ein->next;
|
|
|
|
// Has the outgoing vertex become redundant?
|
|
if (ein->twin->face == eout->twin->face)
|
|
{
|
|
edge = ein;
|
|
break;
|
|
}
|
|
|
|
ein = eout;
|
|
} while (ein != face->edge);
|
|
|
|
if (edge)
|
|
{
|
|
// Remove the outgoing vertex.
|
|
FixMerge(face, edge);
|
|
return true;
|
|
}
|
|
|
|
// Topological error not found.
|
|
return false;
|
|
}
|
|
|
|
qhFace* qhHull::AddFace(qhVertex* v1, qhVertex* v2, qhVertex* v3)
|
|
{
|
|
// Each vertex must be free.
|
|
//B3_ASSERT(v1->edge == NULL);
|
|
//B3_ASSERT(v2->edge == NULL);
|
|
//B3_ASSERT(v3->edge == NULL);
|
|
qhFace* face = AllocateFace();
|
|
|
|
qhHalfEdge* e1 = FindHalfEdge(v1, v2);
|
|
if (e1 == NULL)
|
|
{
|
|
e1 = AllocateEdge();
|
|
e1->tail = v1;
|
|
e1->face = face;
|
|
e1->prev = NULL;
|
|
e1->next = NULL;
|
|
|
|
e1->twin = AllocateEdge();
|
|
e1->twin->tail = v2;
|
|
e1->twin->face = NULL;
|
|
e1->twin->prev = NULL;
|
|
e1->twin->next = NULL;
|
|
e1->twin->twin = e1;
|
|
}
|
|
else
|
|
{
|
|
// Edge must be free.
|
|
B3_ASSERT(e1->face == NULL);
|
|
e1->face = face;
|
|
|
|
B3_ASSERT(e1->tail == v1);
|
|
B3_ASSERT(e1->twin != NULL);
|
|
B3_ASSERT(e1->twin->active == true);
|
|
B3_ASSERT(e1->twin->tail == v2);
|
|
}
|
|
|
|
qhHalfEdge* e2 = FindHalfEdge(v2, v3);
|
|
if (e2 == NULL)
|
|
{
|
|
e2 = AllocateEdge();
|
|
e2->tail = v2;
|
|
e2->face = face;
|
|
e2->prev = NULL;
|
|
e2->next = NULL;
|
|
|
|
e2->twin = AllocateEdge();
|
|
e2->twin->tail = v3;
|
|
e2->twin->face = NULL;
|
|
e2->twin->prev = NULL;
|
|
e2->twin->next = NULL;
|
|
e2->twin->twin = e2;
|
|
}
|
|
else
|
|
{
|
|
// Edge must be free.
|
|
B3_ASSERT(e2->face == NULL);
|
|
e2->face = face;
|
|
|
|
B3_ASSERT(e2->tail == v2);
|
|
B3_ASSERT(e2->twin != NULL);
|
|
B3_ASSERT(e2->twin->active == true);
|
|
B3_ASSERT(e2->twin->tail == v3);
|
|
}
|
|
|
|
qhHalfEdge* e3 = FindHalfEdge(v3, v1);
|
|
if (e3 == NULL)
|
|
{
|
|
e3 = AllocateEdge();
|
|
e3->tail = v3;
|
|
e3->face = face;
|
|
e3->prev = NULL;
|
|
e3->next = NULL;
|
|
|
|
e3->twin = AllocateEdge();
|
|
e3->twin->tail = v1;
|
|
e3->twin->face = NULL;
|
|
e3->twin->prev = NULL;
|
|
e3->twin->next = NULL;
|
|
e3->twin->twin = e3;
|
|
}
|
|
else
|
|
{
|
|
// Edge must be free.
|
|
B3_ASSERT(e3->face == NULL);
|
|
e3->face = face;
|
|
|
|
B3_ASSERT(e3->tail == v3);
|
|
B3_ASSERT(e3->twin != NULL);
|
|
B3_ASSERT(e3->twin->active == true);
|
|
B3_ASSERT(e3->twin->tail == v1);
|
|
}
|
|
|
|
B3_ASSERT(e1->prev == NULL);
|
|
e1->prev = e3;
|
|
B3_ASSERT(e1->next == NULL);
|
|
e1->next = e2;
|
|
|
|
B3_ASSERT(e2->prev == NULL);
|
|
e2->prev = e1;
|
|
B3_ASSERT(e2->next == NULL);
|
|
e2->next = e3;
|
|
|
|
B3_ASSERT(e3->prev == NULL);
|
|
e3->prev = e2;
|
|
B3_ASSERT(e3->next == NULL);
|
|
e3->next = e1;
|
|
|
|
face->edge = e1;
|
|
|
|
b3ResetFaceData(face);
|
|
|
|
face->conflictList.head = NULL;
|
|
face->conflictList.count = 0;
|
|
|
|
Validate(face);
|
|
|
|
m_faceList.PushFront(face);
|
|
|
|
return face;
|
|
}
|
|
|
|
qhFace* qhHull::RemoveFace(qhFace* face)
|
|
{
|
|
// Conflict vertices must have been removed
|
|
B3_ASSERT(face->conflictList.count == 0);
|
|
|
|
// Remove half-edges
|
|
qhHalfEdge* e = face->edge;
|
|
do
|
|
{
|
|
qhHalfEdge* e0 = e;
|
|
e = e->next;
|
|
|
|
// Is the edge a boundary?
|
|
if (e0->twin->face == NULL)
|
|
{
|
|
// Edge is non-shared.
|
|
FreeEdge(e0->twin);
|
|
FreeEdge(e0);
|
|
}
|
|
else
|
|
{
|
|
// Edge is shared.
|
|
// Mark the twin edge as a boundary edge.
|
|
B3_ASSERT(e0->twin != NULL);
|
|
B3_ASSERT(e0->twin->twin == e0);
|
|
e0->face = NULL;
|
|
e0->prev = NULL;
|
|
e0->next = NULL;
|
|
}
|
|
|
|
} while (e != face->edge);
|
|
|
|
// Remove face
|
|
qhFace* nextFace = m_faceList.Remove(face);
|
|
FreeFace(face);
|
|
|
|
// Return the next face in the list of faces
|
|
return nextFace;
|
|
}
|
|
|
|
bool qhHull::MergeFace(qhFace* face1)
|
|
{
|
|
// Non-convex edge
|
|
qhHalfEdge* edge = NULL;
|
|
|
|
qhHalfEdge* e = face1->edge;
|
|
|
|
do
|
|
{
|
|
qhHalfEdge* twin = e->twin;
|
|
qhFace* face2 = twin->face;
|
|
|
|
B3_ASSERT(face2 != NULL);
|
|
B3_ASSERT(face2 != face1);
|
|
|
|
float32 d1 = b3Distance(face2->center, face1->plane);
|
|
float32 d2 = b3Distance(face1->center, face2->plane);
|
|
|
|
if (d1 < -m_tolerance && d2 < -m_tolerance)
|
|
{
|
|
// Edge is convex
|
|
e = e->next;
|
|
continue;
|
|
}
|
|
|
|
// Edge is concave or coplanar
|
|
edge = e;
|
|
break;
|
|
|
|
} while (e != face1->edge);
|
|
|
|
if (edge)
|
|
{
|
|
RemoveEdge(edge);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool qhHull::MergeLargeFace(qhFace* face1)
|
|
{
|
|
// Find a non-convex edge
|
|
qhHalfEdge* edge = NULL;
|
|
|
|
qhHalfEdge* e = face1->edge;
|
|
|
|
B3_ASSERT(e->face == face1);
|
|
|
|
do
|
|
{
|
|
qhHalfEdge* twin = e->twin;
|
|
qhFace* face2 = twin->face;
|
|
|
|
B3_ASSERT(face2 != NULL);
|
|
B3_ASSERT(face2 != face1);
|
|
|
|
if (face1->area > face2->area)
|
|
{
|
|
// Face 1 merge
|
|
float32 d = b3Distance(face2->center, face1->plane);
|
|
if (d < -m_tolerance)
|
|
{
|
|
// Edge is convex wrt to the face 1
|
|
e = e->next;
|
|
continue;
|
|
}
|
|
|
|
// Edge is concave or coplanar wrt to the face 1
|
|
edge = e;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// Face 2 merge
|
|
float32 d = b3Distance(face1->center, face2->plane);
|
|
if (d < -m_tolerance)
|
|
{
|
|
// Edge is convex wrt to the face 2
|
|
e = e->next;
|
|
continue;
|
|
}
|
|
|
|
// Edge is concave or coplanar wrt to the face 2
|
|
edge = e;
|
|
break;
|
|
}
|
|
|
|
e = e->next;
|
|
|
|
} while (e != face1->edge);
|
|
|
|
if (edge)
|
|
{
|
|
RemoveEdge(edge);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void qhHull::MergeNewFaces()
|
|
{
|
|
// Merge with respect to the largest face.
|
|
for (u32 i = 0; i < m_newFaceCount; ++i)
|
|
{
|
|
qhFace* face = m_newFaces[i];
|
|
|
|
// Was the face deleted due to merging?
|
|
if (face->active == false)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
while (MergeLargeFace(face));
|
|
}
|
|
|
|
// Merge with respect to the both faces.
|
|
for (u32 i = 0; i < m_newFaceCount; ++i)
|
|
{
|
|
qhFace* face = m_newFaces[i];
|
|
|
|
// Was the face deleted due to merging?
|
|
if (face->active == false)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
while (MergeFace(face));
|
|
}
|
|
}
|
|
|
|
void qhHull::Translate(const b3Vec3& translation)
|
|
{
|
|
// Shift vertices
|
|
for (qhVertex* v = m_vertexList.head; v != NULL; v = v->next)
|
|
{
|
|
v->position += translation;
|
|
}
|
|
|
|
// Reset face data
|
|
for (qhFace* f = m_faceList.head; f; f = f->next)
|
|
{
|
|
b3ResetFaceData(f);
|
|
}
|
|
}
|
|
|
|
void qhHull::ValidateConvexity() const
|
|
{
|
|
for (qhFace* face = m_faceList.head; face != NULL; face = face->next)
|
|
{
|
|
B3_ASSERT(face->active == true);
|
|
|
|
const qhHalfEdge* edge = face->edge;
|
|
do
|
|
{
|
|
B3_ASSERT(edge->active == true);
|
|
B3_ASSERT(edge->face == face);
|
|
|
|
B3_ASSERT(edge->twin != NULL);
|
|
B3_ASSERT(edge->twin->active == true);
|
|
qhFace* other = edge->twin->face;
|
|
|
|
// Ensure closed volume
|
|
B3_ASSERT(other != NULL);
|
|
B3_ASSERT(other->active == true);
|
|
|
|
// Ensure topological health
|
|
B3_ASSERT(face != other);
|
|
|
|
// Ensure edge convexity
|
|
float32 d1 = b3Distance(other->center, face->plane);
|
|
B3_ASSERT(d1 < -m_tolerance);
|
|
|
|
float32 d2 = b3Distance(face->center, other->plane);
|
|
B3_ASSERT(d2 < -m_tolerance);
|
|
|
|
// Ensure polygon convexity
|
|
b3Vec3 P = edge->tail->position;
|
|
b3Vec3 Q = edge->twin->tail->position;
|
|
|
|
b3Vec3 E = Q - P;
|
|
b3Vec3 D = b3Cross(E, face->plane.normal);
|
|
|
|
// Edge side plane
|
|
b3Plane plane;
|
|
plane.normal = b3Normalize(D);
|
|
plane.offset = b3Dot(plane.normal, P);
|
|
|
|
// All the other vertices must be behind the edge side plane
|
|
const qhHalfEdge* eother = edge->prev;
|
|
do
|
|
{
|
|
float32 d = b3Distance(eother->tail->position, plane);
|
|
B3_ASSERT(d <= 0.0f);
|
|
|
|
eother = eother->prev;
|
|
} while (eother != edge->next);
|
|
|
|
edge = edge->next;
|
|
} while (edge != edge);
|
|
}
|
|
}
|
|
|
|
void qhHull::Validate(const qhHalfEdge* edge) const
|
|
{
|
|
B3_ASSERT(edge->active == true);
|
|
|
|
const qhHalfEdge* twin = edge->twin;
|
|
B3_ASSERT(twin->active == true);
|
|
B3_ASSERT(twin->twin == edge);
|
|
|
|
B3_ASSERT(edge->tail->active == true);
|
|
b3Vec3 A = edge->tail->position;
|
|
|
|
B3_ASSERT(twin->tail->active == true);
|
|
b3Vec3 B = twin->tail->position;
|
|
|
|
B3_ASSERT(b3DistanceSquared(A, B) > B3_EPSILON * B3_EPSILON);
|
|
|
|
const qhHalfEdge* next = edge->next;
|
|
B3_ASSERT(next->active == true);
|
|
B3_ASSERT(twin->tail == next->tail);
|
|
|
|
{
|
|
// CCW
|
|
bool found = false;
|
|
const qhFace* face = edge->face;
|
|
const qhHalfEdge* e = face->edge;
|
|
do
|
|
{
|
|
if (e == edge)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
e = e->next;
|
|
} while (e != face->edge);
|
|
|
|
B3_ASSERT(found == true);
|
|
}
|
|
|
|
{
|
|
// CW
|
|
bool found = false;
|
|
const qhFace* face = edge->face;
|
|
const qhHalfEdge* e = face->edge;
|
|
do
|
|
{
|
|
if (e == edge)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
e = e->prev;
|
|
} while (e != face->edge);
|
|
|
|
B3_ASSERT(found == true);
|
|
}
|
|
}
|
|
|
|
void qhHull::Validate(const qhFace* face) const
|
|
{
|
|
B3_ASSERT(face->active == true);
|
|
|
|
// CCW
|
|
{
|
|
const qhHalfEdge* edge = face->edge;
|
|
do
|
|
{
|
|
B3_ASSERT(edge->active == true);
|
|
B3_ASSERT(edge->face == face);
|
|
|
|
B3_ASSERT(edge->twin != NULL);
|
|
B3_ASSERT(edge->twin->active == true);
|
|
|
|
if (edge->twin->face != NULL)
|
|
{
|
|
B3_ASSERT(edge->twin->face->active == true);
|
|
B3_ASSERT(edge->twin->face != face);
|
|
}
|
|
|
|
edge = edge->next;
|
|
} while (edge != face->edge);
|
|
}
|
|
|
|
// CW
|
|
{
|
|
const qhHalfEdge* edge = face->edge;
|
|
do
|
|
{
|
|
B3_ASSERT(edge->active == true);
|
|
B3_ASSERT(edge->face == face);
|
|
|
|
B3_ASSERT(edge->twin != NULL);
|
|
B3_ASSERT(edge->twin->active == true);
|
|
|
|
if (edge->twin->face != NULL)
|
|
{
|
|
B3_ASSERT(edge->twin->face->active == true);
|
|
B3_ASSERT(edge->twin->face != face);
|
|
}
|
|
|
|
edge = edge->prev;
|
|
} while (edge != face->edge);
|
|
}
|
|
|
|
{
|
|
const qhHalfEdge* edge = face->edge;
|
|
do
|
|
{
|
|
Validate(edge);
|
|
|
|
edge = edge->next;
|
|
} while (edge != face->edge);
|
|
}
|
|
}
|
|
|
|
void qhHull::Validate() const
|
|
{
|
|
for (qhVertex* vertex = m_vertexList.head; vertex != NULL; vertex = vertex->next)
|
|
{
|
|
B3_ASSERT(vertex->active == true);
|
|
}
|
|
|
|
for (qhFace* face = m_faceList.head; face != NULL; face = face->next)
|
|
{
|
|
B3_ASSERT(face->active == true);
|
|
|
|
for (qhVertex* vertex = face->conflictList.head; vertex != NULL; vertex = vertex->next)
|
|
{
|
|
B3_ASSERT(vertex->active == true);
|
|
}
|
|
|
|
// Ensure each vertex has at least three neighbor faces
|
|
qhHalfEdge* ein = face->edge;
|
|
do
|
|
{
|
|
qhHalfEdge* eout = ein->next;
|
|
|
|
B3_ASSERT(ein->twin->face != eout->twin->face);
|
|
|
|
ein = eout;
|
|
} while (ein != face->edge);
|
|
|
|
Validate(face);
|
|
}
|
|
}
|
|
|
|
void qhHull::Draw() const
|
|
{
|
|
for (qhFace* face = m_faceList.head; face != NULL; face = face->next)
|
|
{
|
|
b3Vec3 c = face->center;
|
|
b3Vec3 n = face->plane.normal;
|
|
|
|
b3Draw_draw->DrawSegment(c, c + n, b3Color(1.0f, 1.0f, 1.0f));
|
|
|
|
const qhHalfEdge* edge = face->edge;
|
|
do
|
|
{
|
|
qhVertex* v1 = face->edge->tail;
|
|
qhVertex* v2 = edge->tail;
|
|
const qhHalfEdge* next = edge->next;
|
|
qhVertex* v3 = next->tail;
|
|
|
|
b3Draw_draw->DrawSegment(v2->position, v3->position, b3Color(0.0f, 0.0f, 0.0f, 1.0f));
|
|
b3Draw_draw->DrawSolidTriangle(n, v1->position, v2->position, v3->position, b3Color(1.0f, 1.0f, 1.0f, 0.5f));
|
|
|
|
edge = next;
|
|
} while (edge->next != face->edge);
|
|
|
|
qhVertex* v = face->conflictList.head;
|
|
while (v)
|
|
{
|
|
b3Draw_draw->DrawPoint(v->position, 4.0f, b3Color(1.0f, 1.0f, 0.0f));
|
|
b3Draw_draw->DrawSegment(c, v->position, b3Color(1.0f, 1.0f, 0.0f));
|
|
v = v->next;
|
|
}
|
|
}
|
|
} |