@@ -782,7 +782,7 @@ static OptionalError<Element*> readElement(Cursor* cursor, u32 version, Allocato
782782static 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
42354235static 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
42944294u32 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