diff --git a/GPU/Common/SplineCommon.cpp b/GPU/Common/SplineCommon.cpp index cbd13f0dc9..f66125bbdc 100644 --- a/GPU/Common/SplineCommon.cpp +++ b/GPU/Common/SplineCommon.cpp @@ -393,13 +393,16 @@ public: const Vec3f derivV = tess_pos.SampleV(wv.deriv); Vec3f nrm = Cross(derivU, derivV); - if (nrm.Length2() < 1e-20f) { + const float lenU2 = derivU.Length2(); + const float lenV2 = derivV.Length2(); + if (std::min(lenU2, lenV2) <= 1e-8f * std::max(lenU2, lenV2)) { // A pole: a patch edge whose control points all meet at one point, like the top - // of a dome. One derivative vanishes there, so the cross product is zero and the - // normal would be NaN (dark spots on Pac-Man Arrangement's ghosts, #12354). Use - // the limit instead: next to an edge where dP/dv = 0, dP/dv ~ (u - u_edge) * d2P/dudv. + // of a dome. One derivative vanishes there, so the cross product is zero, or with + // animated control points just rounding noise, and the normal would be NaN or + // random (dark patches on Pac-Man Arrangement's ghosts, #12354). Use the limit + // instead: next to an edge where dP/dv = 0, dP/dv ~ (u - u_edge) * d2P/dudv. const Vec3f derivUV = tess_nrm.SampleV(wv.deriv); - if (derivV.Length2() <= derivU.Length2()) { + if (lenV2 <= lenU2) { nrm = Cross(derivU, derivUV); if (tile_u * 2 > surface.tess_u) { nrm = -nrm; diff --git a/unittest/TestSplineTessellation.cpp b/unittest/TestSplineTessellation.cpp index 6917d29583..91a6210d9f 100644 --- a/unittest/TestSplineTessellation.cpp +++ b/unittest/TestSplineTessellation.cpp @@ -193,6 +193,7 @@ struct TestCase { u32 vertType = GE_VTYPE_POS_FLOAT | GE_VTYPE_NRM_FLOAT | GE_VTYPE_TC_FLOAT | GE_VTYPE_COL_8888; bool patchFacing = false; int poleEdge = -1; // Collapse this edge to one point: 0 = first row (v = 0), 1 = first column (u = 0). + float poleNoise = 0.0f; // ...but only nearly, the way animated control points come out. }; // A bumpy, uneven grid of control points with varying UVs and colors, so that a mixed-up weight or @@ -214,11 +215,11 @@ std::vector MakeControlPoints(const TestCase &tc) { } if (tc.poleEdge == 0) { for (int i = 1; i < tc.pointsU; i++) { - points[i].pos = points[0].pos; + points[i].pos = Vec3Packedf(points[0].pos.x + i * tc.poleNoise, points[0].pos.y - i * tc.poleNoise, points[0].pos.z); } } else if (tc.poleEdge == 1) { for (int j = 1; j < tc.pointsV; j++) { - points[j * tc.pointsU].pos = points[0].pos; + points[j * tc.pointsU].pos = Vec3Packedf(points[0].pos.x, points[0].pos.y + j * tc.poleNoise, points[0].pos.z - j * tc.poleNoise); } } return points; @@ -439,6 +440,8 @@ bool TestSplineTessellation() { { "bezier, pole at v = 0", true, 4, 4, 8, 8, 0, 0, posNrm, false, 0 }, { "bezier, pole at u = 0", true, 4, 4, 8, 8, 0, 0, posNrm, false, 1 }, { "spline, pole at v = 0, open", false, 5, 6, 4, 4, 3, 3, posNrm, false, 0 }, + { "bezier, pole at u = 0 with rounding noise", true, 4, 4, 8, 8, 0, 0, posNrm, false, 1, 1e-7f }, + { "spline, pole at v = 0 with rounding noise", false, 5, 6, 4, 4, 3, 3, posNrm, false, 0, 1e-7f }, }; // Each edge type on each axis, one axis with three patches and the other with two. cases.push_back({ "spline, closed/open", false, 6, 5, 4, 6, 0, 3 });