/* * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved. * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. * * * * * * * * * * * * * * * * * * * * */ package com.sun.javafx.geom; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * */ public class CubicApproximator { private float accuracy; private float maxCubicSize; public CubicApproximator(float accuracy, float maxCubicSize) { this.accuracy = accuracy; this.maxCubicSize = maxCubicSize; } public void setAccuracy(float accuracy) { this.accuracy = accuracy; } public float getAccuracy() { return accuracy; } public void setMaxCubicSize(float maxCCubicSize) { this.maxCubicSize = maxCCubicSize; } public float getMaxCubicSize() { return maxCubicSize; } public float approximate(List res, List tmp, CubicCurve2D curve) { float d = getApproxError(curve); if (d < accuracy) { tmp.add(curve); res.add(approximate(curve)); return d; } else { SplitCubic(tmp, new float[] {curve.x1, curve.y1, curve.ctrlx1, curve.ctrly1, curve.ctrlx2, curve.ctrly2, curve.x2, curve.y2}); return approximate(tmp, res); } } public float approximate(List res, CubicCurve2D curve) { List tmp = new ArrayList(); return approximate(res, tmp, curve); } private QuadCurve2D approximate(CubicCurve2D c) { // Using following approximation of the control point: (3P1-P0+3P2-P3)/4 return new QuadCurve2D(c.x1, c.y1, (3f*c.ctrlx1 - c.x1 + 3f*c.ctrlx2 - c.x2)/4f, (3f*c.ctrly1 - c.y1 + 3f*c.ctrly2 - c.y2)/4f, c.x2, c.y2); } private float approximate(List curves, List res) { QuadCurve2D approx = approximate(curves.get(0)); float dMax = CubicApproximator.compareCPs( curves.get(0), CubicApproximator.elevate(approx)); res.add(approx); for (int i = 1; i < curves.size(); i++) { approx = approximate(curves.get(i)); float d = CubicApproximator.compareCPs( curves.get(i), CubicApproximator.elevate(approx)); if (d > dMax) { dMax = d; } res.add(approx); } return dMax; } private static CubicCurve2D elevate(QuadCurve2D q) { return new CubicCurve2D(q.x1, q.y1, (q.x1 + 2f*q.ctrlx)/3f, (q.y1 + 2f*q.ctrly)/3f, (2f*q.ctrlx + q.x2)/3f, (2f*q.ctrly + q.y2)/3f, q.x2, q.y2); } private static float compare(CubicCurve2D c1, CubicCurve2D c2) { float res = Math.abs(c1.x1 - c2.x1); float d = Math.abs(c1.y1 - c2.y1); if (res < d) res = d; d = Math.abs(c1.ctrlx1 - c2.ctrlx1); if (res < d) res = d; d = Math.abs(c1.ctrly1 - c2.ctrly1); if (res < d) res = d; d = Math.abs(c1.ctrlx2 - c2.ctrlx2); if (res < d) res = d; d = Math.abs(c1.ctrly2 - c2.ctrly2); if (res < d) res = d; d = Math.abs(c1.x2 - c2.x2); if (res < d) res = d; d = Math.abs(c1.y2 - c2.y2); if (res < d) res = d; return res; } private static float getApproxError(float [] coords) { /* Calculating error for approximation using formula: * max(|(-3P1+P0+3P2-P3)/6|, |(3P1-P0-3P2+P3)/6|) */ float res = (-3f*coords[2] + coords[0] + 3f*coords[4] - coords[6])/6f; float d = (-3f*coords[3] + coords[1] + 3f*coords[5] - coords[7])/6f; if (res < d) res = d; d = (3f*coords[2] - coords[0] - 3f*coords[4] + coords[6])/6f; if (res < d) res = d; d = (3f*coords[3] - coords[1] - 3f*coords[5] + coords[7])/6f; if (res < d) res = d; return res; } public static float getApproxError(CubicCurve2D curve) { return getApproxError(new float[] {curve.x1, curve.y1, curve.ctrlx1, curve.ctrly1, curve.ctrlx2, curve.ctrly2, curve.x2, curve.y2}); } private static float compareCPs(CubicCurve2D c1, CubicCurve2D c2) { float res = Math.abs(c1.ctrlx1 - c2.ctrlx1); float d = Math.abs(c1.ctrly1 - c2.ctrly1); if (res < d) res = d; d = Math.abs(c1.ctrlx2 - c2.ctrlx2); if (res < d) res = d; d = Math.abs(c1.ctrly2 - c2.ctrly2); if (res < d) res = d; return res; } /* * Checking size of the cubic curves and split them if necessary. * Calling DrawMonotonicCubic for the curves of the appropriate size. * Note: coords array could be changed */ private void ProcessMonotonicCubic(List resVect, float[] coords) { float[] coords1 = new float[8]; float tx, ty; float xMin, xMax; float yMin, yMax; xMin = xMax = coords[0]; yMin = yMax = coords[1]; for (int i = 2; i < 8; i += 2) { xMin = (xMin > coords[i])? coords[i] : xMin; xMax = (xMax < coords[i])? coords[i] : xMax; yMin = (yMin > coords[i + 1])? coords[i + 1] : yMin; yMax = (yMax < coords[i + 1])? coords[i + 1] : yMax; } if (xMax - xMin > maxCubicSize || yMax - yMin > maxCubicSize || getApproxError(coords) > accuracy) { coords1[6] = coords[6]; coords1[7] = coords[7]; coords1[4] = (coords[4] + coords[6])/2f; coords1[5] = (coords[5] + coords[7])/2f; tx = (coords[2] + coords[4])/2f; ty = (coords[3] + coords[5])/2f; coords1[2] = (tx + coords1[4])/2f; coords1[3] = (ty + coords1[5])/2f; coords[2] = (coords[0] + coords[2])/2f; coords[3] = (coords[1] + coords[3])/2f; coords[4] = (coords[2] + tx)/2f; coords[5] = (coords[3] + ty)/2f; coords[6]=coords1[0]=(coords[4] + coords1[2])/2f; coords[7]=coords1[1]=(coords[5] + coords1[3])/2f; ProcessMonotonicCubic(resVect, coords); ProcessMonotonicCubic(resVect, coords1); } else { resVect.add(new CubicCurve2D( coords[0], coords[1], coords[2], coords[3], coords[4], coords[5], coords[6], coords[7])); } } /* * Split cubic curve into monotonic in X and Y parts. Calling * * Note: coords array could be changed */ public void SplitCubic(List resVect, float[] coords) { /* Temporary array for holding parameters corresponding to the extreme * in X and Y points */ float params[] = new float[4]; float eqn[] = new float[3]; float res[] = new float[2]; int cnt = 0; /* Simple check for monotonicity in X before searching for the extreme * points of the X(t) function. We first check if the curve is * monotonic in X by seeing if all of the X coordinates are strongly * ordered. */ if ((coords[0] > coords[2] || coords[2] > coords[4] || coords[4] > coords[6]) && (coords[0] < coords[2] || coords[2] < coords[4] || coords[4] < coords[6])) { /* Searching for extreme points of the X(t) function by solving * dX(t) * ---- = 0 equation * dt */ eqn[2] = -coords[0] + 3*coords[2] - 3*coords[4] + coords[6]; eqn[1] = 2*(coords[0] - 2*coords[2] + coords[4]); eqn[0] = -coords[0] + coords[2]; int nr = QuadCurve2D.solveQuadratic(eqn, res); /* Following code also correctly works in degenerate case of * the quadratic equation (nr = -1) because we do not need * splitting in this case. */ for (int i = 0; i < nr; i++) { if (res[i] > 0 && res[i] < 1) { params[cnt++] = res[i]; } } } /* Simple check for monotonicity in Y before searching for the extreme * points of the Y(t) function. We first check if the curve is * monotonic in Y by seeing if all of the Y coordinates are strongly * ordered. */ if ((coords[1] > coords[3] || coords[3] > coords[5] || coords[5] > coords[7]) && (coords[1] < coords[3] || coords[3] < coords[5] || coords[5] < coords[7])) { /* Searching for extreme points of the Y(t) function by solving * dY(t) * ----- = 0 equation * dt */ eqn[2] = -coords[1] + 3*coords[3] - 3*coords[5] + coords[7]; eqn[1] = 2*(coords[1] - 2*coords[3] + coords[5]); eqn[0] = -coords[1] + coords[3]; int nr = QuadCurve2D.solveQuadratic(eqn, res); /* Following code also correctly works in degenerate case of * the quadratic equation (nr = -1) because we do not need * splitting in this case. */ for (int i = 0; i < nr; i++) { if (res[i] > 0 && res[i] < 1) { params[cnt++] = res[i]; } } } if (cnt > 0) { /* Sorting parameter values corresponding to the extreme points * of the curve */ Arrays.sort(params, 0, cnt); /* Processing obtained monotonic parts */ ProcessFirstMonotonicPartOfCubic(resVect, coords, params[0]); for (int i = 1; i < cnt; i++) { float param = params[i] - params[i-1]; if (param > 0) { ProcessFirstMonotonicPartOfCubic(resVect, coords, /* Scale parameter to match with rest of the curve */ (float)(param/(1f - params[i - 1]))); } } } ProcessMonotonicCubic(resVect,coords); } /* * Bite the piece of the cubic curve from start point till the point * corresponding to the specified parameter then call ProcessCubic for the * bitten part. * Note: coords array will be changed */ private void ProcessFirstMonotonicPartOfCubic( List resVector, float[] coords, float t) { float[] coords1 = new float[8]; float tx, ty; coords1[0] = coords[0]; coords1[1] = coords[1]; tx = coords[2] + t*(coords[4] - coords[2]); ty = coords[3] + t*(coords[5] - coords[3]); coords1[2] = coords[0] + t*(coords[2] - coords[0]); coords1[3] = coords[1] + t*(coords[3] - coords[1]); coords1[4] = coords1[2] + t*(tx - coords1[2]); coords1[5] = coords1[3] + t*(ty - coords1[3]); coords[4] = coords[4] + t*(coords[6] - coords[4]); coords[5] = coords[5] + t*(coords[7] - coords[5]); coords[2] = tx + t*(coords[4] - tx); coords[3] = ty + t*(coords[5] - ty); coords[0]=coords1[6]=coords1[4] + t*(coords[2] - coords1[4]); coords[1]=coords1[7]=coords1[5] + t*(coords[3] - coords1[5]); ProcessMonotonicCubic(resVector, coords1); } }