@@ -2249,6 +2249,97 @@ inline std::vector<Vec2> sampleCurveWireByU(const CurveWire& wire, int segmentsP
22492249 return samples;
22502250}
22512251
2252+ // / Ear-clipping of a simple parametric wire that clips the ear with the shortest 3D diagonal first, so a strip is not fanned.
2253+ inline std::vector<std::array<int , 3 >> triangulateWireByShortestChord (const SurfaceWire& wire,
2254+ const std::vector<Vec3>& points)
2255+ {
2256+ std::vector<int > remainingIndices;
2257+ remainingIndices.reserve (wire.vertices .size ());
2258+ if (wire.signedArea () >= 0 .) {
2259+ for (size_t vertexIndex = 0 ; vertexIndex < wire.vertices .size (); ++vertexIndex) {
2260+ remainingIndices.push_back (static_cast <int >(vertexIndex));
2261+ }
2262+ } else {
2263+ for (size_t reverseIndex = wire.vertices .size (); reverseIndex > 0 ; --reverseIndex) {
2264+ remainingIndices.push_back (static_cast <int >(reverseIndex - 1 ));
2265+ }
2266+ }
2267+
2268+ // the 3D diagonal of the ear at a position, or infinity when it is not an ear (same tests as triangulateSimpleWire)
2269+ const double notAnEar = std::numeric_limits<double >::infinity ();
2270+ auto earChord = [&](size_t indexPosition) {
2271+ const int previousIndex = remainingIndices[(indexPosition + remainingIndices.size () - 1 ) % remainingIndices.size ()];
2272+ const int currentIndex = remainingIndices[indexPosition];
2273+ const int nextIndex = remainingIndices[(indexPosition + 1 ) % remainingIndices.size ()];
2274+ const auto & previousVertex = wire.vertices [previousIndex];
2275+ const auto & currentVertex = wire.vertices [currentIndex];
2276+ const auto & nextVertex = wire.vertices [nextIndex];
2277+ if (cross2D (currentVertex - previousVertex, nextVertex - currentVertex) <= kTolerance ) {
2278+ return notAnEar;
2279+ }
2280+ for (int candidateIndex : remainingIndices) {
2281+ if (candidateIndex == previousIndex || candidateIndex == currentIndex || candidateIndex == nextIndex) {
2282+ continue ;
2283+ }
2284+ if (pointInTriangle (wire.vertices [candidateIndex], previousVertex, currentVertex, nextVertex)) {
2285+ return notAnEar;
2286+ }
2287+ }
2288+ return norm (points[nextIndex] - points[previousIndex]);
2289+ };
2290+
2291+ std::vector<double > chords (remainingIndices.size ());
2292+ for (size_t indexPosition = 0 ; indexPosition < remainingIndices.size (); ++indexPosition) {
2293+ chords[indexPosition] = earChord (indexPosition);
2294+ }
2295+
2296+ std::vector<std::array<int , 3 >> triangles;
2297+ while (remainingIndices.size () > 3 ) {
2298+ // the shortest diagonal wins; ties go to the lowest position
2299+ size_t bestPosition = remainingIndices.size ();
2300+ double bestChord = notAnEar;
2301+ for (size_t indexPosition = 0 ; indexPosition < remainingIndices.size (); ++indexPosition) {
2302+ if (chords[indexPosition] < bestChord) {
2303+ bestChord = chords[indexPosition];
2304+ bestPosition = indexPosition;
2305+ }
2306+ }
2307+ if (bestPosition == remainingIndices.size ()) {
2308+ return triangulateSimpleWire (wire); // near-collinear runs can block every ear in this order
2309+ }
2310+
2311+ const size_t count = remainingIndices.size ();
2312+ const int previousIndex = remainingIndices[(bestPosition + count - 1 ) % count];
2313+ const int clippedIndex = remainingIndices[bestPosition];
2314+ const int nextIndex = remainingIndices[(bestPosition + 1 ) % count];
2315+ triangles.push_back ({previousIndex, clippedIndex, nextIndex});
2316+ remainingIndices.erase (remainingIndices.begin () + bestPosition);
2317+ chords.erase (chords.begin () + bestPosition);
2318+
2319+ // the two neighbours change shape; any other non-ear can only change if the clipped vertex lay in its triangle
2320+ const size_t newCount = remainingIndices.size ();
2321+ const size_t beforePosition = (bestPosition + newCount - 1 ) % newCount;
2322+ const size_t afterPosition = bestPosition % newCount;
2323+ for (size_t indexPosition = 0 ; indexPosition < newCount; ++indexPosition) {
2324+ if (indexPosition == beforePosition || indexPosition == afterPosition) {
2325+ chords[indexPosition] = earChord (indexPosition);
2326+ } else if (chords[indexPosition] == notAnEar) {
2327+ const auto & previousVertex = wire.vertices [remainingIndices[(indexPosition + newCount - 1 ) % newCount]];
2328+ const auto & currentVertex = wire.vertices [remainingIndices[indexPosition]];
2329+ const auto & nextVertex = wire.vertices [remainingIndices[(indexPosition + 1 ) % newCount]];
2330+ if (pointInTriangle (wire.vertices [clippedIndex], previousVertex, currentVertex, nextVertex)) {
2331+ chords[indexPosition] = earChord (indexPosition);
2332+ }
2333+ }
2334+ }
2335+ }
2336+
2337+ if (remainingIndices.size () == 3 ) {
2338+ triangles.push_back ({remainingIndices[0 ], remainingIndices[1 ], remainingIndices[2 ]});
2339+ }
2340+ return triangles;
2341+ }
2342+
22522343// / Append the display triangulation of a wire-trimmed quadric patch: the sampled outer loop, ear-clipped; holes are omitted.
22532344template <typename MapUV>
22542345void appendCurveTrimMesh (const CurveWire& outerWire, const MapUV& mapUV, std::vector<Vec3>& vertices,
@@ -2263,7 +2354,8 @@ void appendCurveTrimMesh(const CurveWire& outerWire, const MapUV& mapUV, std::ve
22632354 for (const auto & sample : sampledWire.vertices ) {
22642355 vertices.push_back (mapUV (sample.uCoord , sample.vCoord ));
22652356 }
2266- for (const auto & triangle : triangulateSimpleWire (sampledWire)) {
2357+ const std::vector<Vec3> points (vertices.begin () + firstVertexIndex, vertices.end ());
2358+ for (const auto & triangle : triangulateWireByShortestChord (sampledWire, points)) {
22672359 triangles.push_back (
22682360 {firstVertexIndex + triangle[0 ], firstVertexIndex + triangle[1 ], firstVertexIndex + triangle[2 ]});
22692361 }
0 commit comments