package com.xenoage.zong.util.math; import static com.xenoage.utils.math.geom.Point2f.p; import com.xenoage.utils.math.QuadraticCurve; import com.xenoage.utils.math.VSide; import com.xenoage.utils.math.geom.Point2f; /** * Computes a cubic bezier curve from a given quadratic curve * and a start and end point. * * @author Andreas Wenger */ public class BezierCurveTools { /** * Computes a curbic bezier curve from the given quadratic curve. * @param curve the quadratic curve * @param startX the horizontal start coordinate * @param endX the horizontal end coordinate */ public static CubicBezierCurve computeBezierFrom(QuadraticCurve curve, float startX, float endX) { //compute p1 and p2 Point2f p1 = p(startX, curve.getY(startX)); Point2f p2 = p(endX, curve.getY(endX)); //compute a helper point about in the middle of the curve float hx = (startX + endX) / 2; Point2f h = p(hx, curve.getY(hx)); //t ist simply 0.5 :-) float t = 0.5f; //using the quadratic bezier formula, compute c (the control point of a quadratic bezier curve): //c = (h - (1-t)²p1 - t²p2) / (2t(1-t)) Point2f c = h.sub(p1.scale((1 - t) * (1 - t))).sub(p2.scale(t * t)) .scale(1f / (2 * t * (1 - t))); //since we want to have a cubic bezier curve, compute c1 and c2 out of p1, p2, and c. //this is easy, because the direction of the control points does not change and //the length of the distance is multiplied by 2/3 Point2f c1 = p1.scale(1f / 3).add(c.scale(2f / 3)); Point2f c2 = p2.scale(1f / 3).add(c.scale(2f / 3)); return new CubicBezierCurve(p1, c1, c2, p2); } /** * Corrects the given bezier curve, if it is too plain, * so it looks more like a part of a circle (which looks good for * a slur). */ public static CubicBezierCurve correctBezier(CubicBezierCurve curve, VSide side) { //compute left angle Point2f p1c1 = curve.getC1().sub(curve.getP1()); Point2f p1p2 = curve.getP2().sub(curve.getP1()); float angleLeft = (float) Math.acos(p1c1.dotProduct(p1p2) / (p1c1.length() * p1p2.length())); if (angleLeft < 0.3 || Float.isNaN(angleLeft)) { //correct curve float slurDown = -1 * side.getDir() * 1; Point2f p1 = curve.getP1().add(0, slurDown); Point2f c1 = curve.getC1().add(0, slurDown).rotate(p1, 1 * side.getDir() * 0.5f); Point2f p2 = curve.getP2().add(0, slurDown); Point2f c2 = curve.getC2().add(0, slurDown).rotate(p2, -1 * side.getDir() * 0.5f); return new CubicBezierCurve(p1, c1, c2, p2); } else { //curve is ok return curve; } } }