/** Turns a List<CubicCurve2D.Float> into a SVG Element representing a sketch of that spline. */ Element splineToSketch(SVGDocument document, List<CubicCurve2D.Float> spline) { String svgNS = SVGDOMImplementation.SVG_NAMESPACE_URI; // <g> is an SVG group // TODO: add a random(ish) rotation to the group Element group = document.createElementNS(svgNS, "g"); // For each curve in the path, draw along it using a "brush". In // our case the brush is a simple circle, but this could be changed // to something more advanced. for (CubicCurve2D.Float curve : spline) { // TODO: magic number & step in loop guard for (double i = 0.0; i <= 1.0; i += 0.01) { Point2D result = evalParametric(curve, i); // Add random jitter at some random positive or negative // distance along the unit normal to the tangent of the // curve Point2D n = vectorUnitNormal(evalParametricTangent(curve, i)); float dx = (float) ((Math.random() - 0.5) * n.getX()); float dy = (float) ((Math.random() - 0.5) * n.getY()); Element brush = document.createElementNS(svgNS, "circle"); brush.setAttribute("cx", Double.toString(result.getX() + dx)); brush.setAttribute("cy", Double.toString(result.getY() + dy)); // TODO: magic number for circle radius brush.setAttribute("r", Double.toString(1.0)); brush.setAttribute("fill", "green"); brush.setAttributeNS(null, "z-index", Integer.toString(zOrder.CONTOUR.ordinal())); group.appendChild(brush); } } return group; }
private static Point2D evalParametric(CubicCurve2D curve, double t) { if (null == curve) { return null; } // B(t) = (1-t)^3 P_0 + 3(1-t)^2t C_1 + 3(1 - t)t^2 C_2 + t^3 P_1 // do nothing fancy, just calculate it. double rx = ((Math.pow((1 - t), 3) * curve.getX1())) + (3 * Math.pow((1 - t), 2) * t * curve.getCtrlX1()) + (3 * (1 - t) * t * t * curve.getCtrlX2()) + (t * t * t * curve.getX2()); double ry = ((Math.pow((1 - t), 3) * curve.getY1())) + (3 * Math.pow((1 - t), 2) * t * curve.getCtrlY1()) + (3 * (1 - t) * t * t * curve.getCtrlY2()) + (t * t * t * curve.getY2()); return new Point2D.Float((float) rx, (float) ry); }
/** * Calculates the unit normal of a particular vector, where the vector is represented by the * direction and magnitude of the line segment from (0,0) to (p.x, p.y). * * @param p * @return */ private static Point2D vectorUnitNormal(Point2D p) { // if null object passed or if the passed vector has zero length if (null == p || ((0 == p.getX()) && (0 == p.getY()))) { return null; } // normalise the input "vector" // 1. get length of vector (Pythagoras) // 2. divide both x and y by this length // note: c cannot be 0 as we've already considered zero length input // vectors above. double c = Math.sqrt((p.getX() * p.getX()) + (p.getY() * p.getY())); double nvx = p.getX() / c; double nvy = p.getY() / c; // Now rotate (nvx, nvy) by 90 degrees to get the normal for the // input vector. // rx = nvx * cos (pi/2) - nvy * sin (pi/2) // ty = nvx * sin (pi/2) + nvy * cos (pi/2) // but cos (pi/2) = 0 and sin (pi/2) = 1, so this simplifies return new Point2D.Float((float) (-1 * nvy), (float) nvx); }