Skip to content

Commit f0d8df6

Browse files
committed
more robust triangulation
1 parent 110de57 commit f0d8df6

File tree

1 file changed

+63
-36
lines changed

1 file changed

+63
-36
lines changed

src/ofbx.cpp

Lines changed: 63 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -782,7 +782,7 @@ static OptionalError<Element*> readElement(Cursor* cursor, u32 version, Allocato
782782
static bool isEndLine(const Cursor& cursor)
783783
{
784784
return (*cursor.current == '\n')
785-
|| (*cursor.current == '\r' && cursor.current + 1 < cursor.end && *(cursor.current + 1) != '\n');
785+
|| (*cursor.current == '\r' && cursor.current + 1 < cursor.end && *(cursor.current + 1) != '\n');
786786
}
787787

788788

@@ -4233,12 +4233,12 @@ const char* getError()
42334233
}
42344234

42354235
static bool isConvex(const GeometryData& geom, const GeometryPartition::Polygon& polygon) {
4236-
if (polygon.vertex_count < 3) return false;
4237-
if (polygon.vertex_count == 3) return true;
4238-
4239-
const Vec3Attributes positions = geom.getPositions();
4240-
if (!positions.values) return false;
4241-
4236+
if (polygon.vertex_count < 3) return false;
4237+
if (polygon.vertex_count == 3) return true;
4238+
4239+
const Vec3Attributes positions = geom.getPositions();
4240+
if (!positions.values) return false;
4241+
42424242
Vec3 normal = {0, 0, 0};
42434243
// Compute polygon normal using Newell's method for robustness
42444244
// https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal
@@ -4287,8 +4287,8 @@ static bool isConvex(const GeometryData& geom, const GeometryPartition::Polygon&
42874287

42884288
if (sign_positive && sign_negative) return false;
42894289
}
4290-
4291-
return true;
4290+
4291+
return true;
42924292
}
42934293

42944294
u32 triangulate(const GeometryData& geom, const GeometryPartition::Polygon& polygon, int* tri_indices, int* tmp) {
@@ -4310,10 +4310,14 @@ u32 triangulate(const GeometryData& geom, const GeometryPartition::Polygon& poly
43104310
}
43114311

43124312
// ear clipping - the simplest (and not the most efficient) implementation
4313-
int tmp_mem[128]; // temporary memory if use did not pass any in `tmp`
4313+
int tmp_mem[128]; // temporary memory if user did not pass any in `tmp`
43144314
int* vertices = tmp ? tmp : tmp_mem;
43154315

4316-
if (!tmp && polygon.vertex_count > 128) return 0;
4316+
if (!tmp && polygon.vertex_count > 128) {
4317+
// user should provide enough memory in `tmp`
4318+
assert(false);
4319+
return 0;
4320+
}
43174321

43184322
for (int i = 0; i < polygon.vertex_count; ++i) {
43194323
vertices[i] = polygon.from_vertex + i;
@@ -4338,6 +4342,17 @@ u32 triangulate(const GeometryData& geom, const GeometryPartition::Polygon& poly
43384342
edge1.x * edge2.y - edge1.y * edge2.x
43394343
};
43404344

4345+
float normal_len_sq = normal.x * normal.x + normal.y * normal.y + normal.z * normal.z;
4346+
if (normal_len_sq < 1e-12f) {
4347+
// Degenerate polygon, handle it like a convex polygon
4348+
for (int tri = 0; tri < polygon.vertex_count - 2; ++tri) {
4349+
tri_indices[tri * 3 + 0] = polygon.from_vertex;
4350+
tri_indices[tri * 3 + 1] = polygon.from_vertex + 1 + tri;
4351+
tri_indices[tri * 3 + 2] = polygon.from_vertex + 2 + tri;
4352+
}
4353+
return 3 * (polygon.vertex_count - 2);
4354+
}
4355+
43414356
while (num_vertices > 3) {
43424357
bool ear_found = false;
43434358

@@ -4361,7 +4376,7 @@ u32 triangulate(const GeometryData& geom, const GeometryPartition::Polygon& poly
43614376

43624377
const float dot = cross.x * normal.x + cross.y * normal.y + cross.z * normal.z;
43634378

4364-
// 3 vertices with wrong winding order can not make an ear
4379+
// Check if this forms a convex vertex (same orientation as polygon normal)
43654380
if (dot <= 0) continue;
43664381

43674382
// Check if any other vertex is inside this triangle
@@ -4371,22 +4386,32 @@ u32 triangulate(const GeometryData& geom, const GeometryPartition::Polygon& poly
43714386

43724387
Vec3 vt = positions.get(vertices[j]);
43734388

4374-
// Barycentric coordinate test
4375-
Vec3 v0v1 = {vc.x - vp.x, vc.y - vp.y, vc.z - vp.z};
4376-
Vec3 v0v2 = {vn.x - vp.x, vn.y - vp.y, vn.z - vp.z};
4377-
Vec3 v0vt = {vt.x - vp.x, vt.y - vp.y, vt.z - vp.z};
4378-
4379-
float dot00 = v0v2.x * v0v2.x + v0v2.y * v0v2.y + v0v2.z * v0v2.z;
4380-
float dot01 = v0v2.x * v0v1.x + v0v2.y * v0v1.y + v0v2.z * v0v1.z;
4381-
float dot02 = v0v2.x * v0vt.x + v0v2.y * v0vt.y + v0v2.z * v0vt.z;
4382-
float dot11 = v0v1.x * v0v1.x + v0v1.y * v0v1.y + v0v1.z * v0v1.z;
4383-
float dot12 = v0v1.x * v0vt.x + v0v1.y * v0vt.y + v0v1.z * v0vt.z;
4389+
// Use point-in-triangle test with proper edge function
4390+
// Check if point is on the same side of all three edges
4391+
Vec3 edge0 = {vc.x - vp.x, vc.y - vp.y, vc.z - vp.z};
4392+
Vec3 edge1 = {vn.x - vc.x, vn.y - vc.y, vn.z - vc.z};
4393+
Vec3 edge2 = {vp.x - vn.x, vp.y - vn.y, vp.z - vn.z};
4394+
4395+
Vec3 toPoint0 = {vt.x - vp.x, vt.y - vp.y, vt.z - vp.z};
4396+
Vec3 toPoint1 = {vt.x - vc.x, vt.y - vc.y, vt.z - vc.z};
4397+
Vec3 toPoint2 = {vt.x - vn.x, vt.y - vn.y, vt.z - vn.z};
43844398

4385-
float inv_denom = 1.0f / (dot00 * dot11 - dot01 * dot01);
4386-
float u = (dot11 * dot02 - dot01 * dot12) * inv_denom;
4387-
float v = (dot00 * dot12 - dot01 * dot02) * inv_denom;
4399+
Vec3 cross0 = {edge0.y * toPoint0.z - edge0.z * toPoint0.y,
4400+
edge0.z * toPoint0.x - edge0.x * toPoint0.z,
4401+
edge0.x * toPoint0.y - edge0.y * toPoint0.x};
4402+
Vec3 cross1 = {edge1.y * toPoint1.z - edge1.z * toPoint1.y,
4403+
edge1.z * toPoint1.x - edge1.x * toPoint1.z,
4404+
edge1.x * toPoint1.y - edge1.y * toPoint1.x};
4405+
Vec3 cross2 = {edge2.y * toPoint2.z - edge2.z * toPoint2.y,
4406+
edge2.z * toPoint2.x - edge2.x * toPoint2.z,
4407+
edge2.x * toPoint2.y - edge2.y * toPoint2.x};
43884408

4389-
if (u > 0 && v > 0 && u + v < 1) {
4409+
float dot0 = cross0.x * normal.x + cross0.y * normal.y + cross0.z * normal.z;
4410+
float dot1 = cross1.x * normal.x + cross1.y * normal.y + cross1.z * normal.z;
4411+
float dot2 = cross2.x * normal.x + cross2.y * normal.y + cross2.z * normal.z;
4412+
4413+
// Point is inside if all cross products have the same sign as the normal
4414+
if (dot0 >= 0 && dot1 >= 0 && dot2 >= 0) {
43904415
is_ear = false;
43914416
break;
43924417
}
@@ -4408,20 +4433,22 @@ u32 triangulate(const GeometryData& geom, const GeometryPartition::Polygon& poly
44084433
}
44094434
}
44104435

4411-
if (!ear_found) {
4412-
// Fallback: couldn't find ear, add remaining triangle
4413-
break;
4414-
}
4436+
if (!ear_found) break;
44154437
}
44164438

4417-
// Add final triangle
4418-
if (num_vertices == 3) {
4419-
tri_indices[tri_count * 3 + 0] = vertices[0];
4420-
tri_indices[tri_count * 3 + 1] = vertices[1];
4421-
tri_indices[tri_count * 3 + 2] = vertices[2];
4422-
tri_count++;
4439+
if (num_vertices >= 3) {
4440+
// Add remaining triangles
4441+
// There can be more than 1 triangle in some cases, e.g., degenerate triangles
4442+
// We output those triangles so the output size matches what the user would expect based on polygon.vertex_count
4443+
for (int i = 1; i < num_vertices - 1; ++i) {
4444+
tri_indices[tri_count * 3 + 0] = vertices[0];
4445+
tri_indices[tri_count * 3 + 1] = vertices[i];
4446+
tri_indices[tri_count * 3 + 2] = vertices[i + 1];
4447+
tri_count++;
4448+
}
44234449
}
44244450

4451+
assert(tri_count == polygon.vertex_count - 2);
44254452
return tri_count * 3;
44264453
}
44274454

0 commit comments

Comments
 (0)