On Tue, 16 Nov 2021 05:22:13 GMT, Jeremy <[email protected]> wrote:
>> src/java.desktop/share/classes/java/awt/geom/Path2D.java line 2124:
>>
>>> 2122: // a box that is slightly too small. But the contract of this
>>> method
>>> 2123: // says we should err on the side of being too large.
>>> 2124: // So to address this: we take the difference between the
>>> control
>>
>> This is my alternative proposal to use the polynomial error as base error
>> (cubic case is more tricky as solveQuadratic is problematic too for huge
>> curves):
>>
>> // So to address this: we take using the upper limit of numerical
>> error
>> // caused by the polynomial evaluation (horner scheme).
>>
>> for (; !pi.isDone(); pi.next()) {
>> final int type = pi.currentSegment(coords);
>> switch (type) {
>> case PathIterator.SEG_MOVETO:
>> if (!started) {
>> started = true;
>> leftX = rightX = coords[0];
>> topY = bottomY = coords[1];
>> } else {
>> if (coords[0] < leftX) {
>> leftX = coords[0];
>> }
>> if (coords[0] > rightX) {
>> rightX = coords[0];
>> }
>> if (coords[1] < topY) {
>> topY = coords[1];
>> }
>> if (coords[1] > bottomY) {
>> bottomY = coords[1];
>> }
>> }
>> lastX = coords[0];
>> lastY = coords[1];
>> break;
>> case PathIterator.SEG_LINETO:
>> if (coords[0] < leftX) {
>> leftX = coords[0];
>> }
>> if (coords[0] > rightX) {
>> rightX = coords[0];
>> }
>> if (coords[1] < topY) {
>> topY = coords[1];
>> }
>> if (coords[1] > bottomY) {
>> bottomY = coords[1];
>> }
>> lastX = coords[0];
>> lastY = coords[1];
>> break;
>> case PathIterator.SEG_QUADTO:
>> if (coords[2] < leftX) {
>> leftX = coords[2];
>> }
>> if (coords[2] > rightX) {
>> rightX = coords[2];
>> }
>> if (coords[3] < topY) {
>> topY = coords[3];
>> }
>> if (coords[3] > bottomY) {
>> bottomY = coords[3];
>> }
>>
>> if (coords[0] < leftX || coords[0] > rightX) {
>> final double dx21 = (coords[0] - lastX);
>> coeff[2] = (coords[2] - coords[0]) - dx21; // A =
>> P3 - P0 - 2 P2
>> coeff[1] = 2.0 * dx21; // B = 2
>> (P2 - P1)
>> coeff[0] = lastX; // C = P1
>>
>> deriv_coeff[0] = coeff[1];
>> deriv_coeff[1] = 2.0 * coeff[2];
>>
>> double t = -deriv_coeff[0] / deriv_coeff[1];
>> if (t > 0.0 && t < 1.0) {
>> double x = coeff[0] + t * (coeff[1] + t *
>> coeff[2]);
>>
>> // error condition = sum ( abs (coeff) ):
>> final double margin = Math.ulp(
>> Math.abs(coeff[0])
>> + Math.abs(coeff[1]) +
>> Math.abs(coeff[2]));
>>
>> if (x - margin < leftX) {
>> leftX = x - margin;
>> }
>> if (x + margin > rightX) {
>> rightX = x + margin;
>> }
>> }
>> }
>> if (coords[1] < topY || coords[1] > bottomY) {
>> final double dy21 = (coords[1] - lastY);
>> coeff[2] = (coords[3] - coords[1]) - dy21;
>> coeff[1] = 2.0 * dy21;
>> coeff[0] = lastY;
>>
>> deriv_coeff[0] = coeff[1];
>> deriv_coeff[1] = 2.0 * coeff[2];
>>
>> double t = -deriv_coeff[0] / deriv_coeff[1];
>> if (t > 0.0 && t < 1.0) {
>> double y = coeff[0] + t * (coeff[1] + t *
>> coeff[2]);
>>
>> // error condition = sum ( abs (coeff) ):
>> final double margin = Math.ulp(
>> Math.abs(coeff[0])
>> + Math.abs(coeff[1]) +
>> Math.abs(coeff[2]));
>>
>> if (y - margin < topY) {
>> topY = y - margin;
>> }
>> if (y + margin > bottomY) {
>> bottomY = y + margin;
>> }
>> }
>> }
>> lastX = coords[2];
>> lastY = coords[3];
>> break;
>> case PathIterator.SEG_CUBICTO:
>> if (coords[4] < leftX) {
>> leftX = coords[4];
>> }
>> if (coords[4] > rightX) {
>> rightX = coords[4];
>> }
>> if (coords[5] < topY) {
>> topY = coords[5];
>> }
>> if (coords[5] > bottomY) {
>> bottomY = coords[5];
>> }
>>
>> if (coords[0] < leftX || coords[0] > rightX || coords[2]
>> < leftX || coords[2] > rightX) {
>> final double dx32 = 3.0 * (coords[2] - coords[0]);
>> final double dx21 = 3.0 * (coords[0] - lastX);
>> coeff[3] = (coords[4] - lastX) - dx32; // A = P3 -
>> P0 - 3 (P2 - P1) = (P3 - P0) + 3 (P1 - P2)
>> coeff[2] = (dx32 - dx21); // B = 3 (P2
>> - P1) - 3(P1 - P0) = 3 (P2 + P0) - 6 P1
>> coeff[1] = dx21; // C = 3 (P1
>> - P0)
>> coeff[0] = lastX; // D = P0
>>
>> deriv_coeff[0] = coeff[1];
>> deriv_coeff[1] = 2.0 * coeff[2];
>> deriv_coeff[2] = 3.0 * coeff[3];
>>
>> // solveQuadratic should be improved to get correct
>> t extrema (1 ulp):
>> final int tExtremaCount =
>> QuadCurve2D.solveQuadratic(deriv_coeff, tExtrema);
>> if (tExtremaCount > 0) {
>> // error condition = sum ( abs (coeff) ):
>> final double margin = Math.ulp(Math.abs(coeff[0])
>> + Math.abs(coeff[1]) + Math.abs(coeff[2])
>> + Math.abs(coeff[3]));
>>
>> for (int i = 0; i < tExtremaCount; i++) {
>> final double t = tExtrema[i];
>> if (t > 0.0 && t < 1.0) {
>> double x = coeff[0] + t * (coeff[1] + t
>> * (coeff[2] + t * coeff[3]));
>> if (x - margin < leftX) {
>> leftX = x - margin;
>> }
>> if (x + margin > rightX) {
>> rightX = x + margin;
>> }
>> }
>> }
>> }
>> }
>> if (coords[1] < topY || coords[1] > bottomY || coords[3]
>> < topY || coords[3] > bottomY) {
>> final double dy32 = 3.0 * (coords[3] - coords[1]);
>> final double dy21 = 3.0 * (coords[1] - lastY);
>> coeff[3] = (coords[5] - lastY) - dy32;
>> coeff[2] = (dy32 - dy21);
>> coeff[1] = dy21;
>> coeff[0] = lastY;
>>
>> deriv_coeff[0] = coeff[1];
>> deriv_coeff[1] = 2.0 * coeff[2];
>> deriv_coeff[2] = 3.0 * coeff[3];
>>
>> int tExtremaCount =
>> QuadCurve2D.solveQuadratic(deriv_coeff, tExtrema);
>> if (tExtremaCount > 0) {
>> // error condition = sum ( abs (coeff) ):
>> final double margin = Math.ulp(Math.abs(coeff[0])
>> + Math.abs(coeff[1]) + Math.abs(coeff[2])
>> + Math.abs(coeff[3]));
>>
>> for (int i = 0; i < tExtremaCount; i++) {
>> double t = tExtrema[i];
>> if (t > 0.0 && t < 1.0) {
>> double y = coeff[0] + t * (coeff[1] + t
>> * (coeff[2] + t * coeff[3]));
>> if (y - margin < topY) {
>> topY = y - margin;
>> }
>> if (y + margin > bottomY) {
>> bottomY = y + margin;
>> }
>> }
>> }
>> }
>> }
>> lastX = coords[4];
>> lastY = coords[5];
>> break;
>> case PathIterator.SEG_CLOSE:
>> default:
>> }
>> }
>
> Looks good, and it passes all the unit tests. I pushed an update with this
> code. (Although immediately after that I pushed a refactor for readability
> that you previously requested -- so the logic/margin is in the branch now but
> it looks a little different already...)
Here is the test result in marlin-math proving the margin condition is
satisfied from small to huge cubic curves:
https://github.com/bourgesl/marlin-math/blob/main/results/findExtrema/jdk17-2021-11-14-edge-cubic.log
Inaccuracies are within margin ~ 0.5 proved.
-------------
PR: https://git.openjdk.java.net/jdk/pull/6227