diff options
Diffstat (limited to 'zinclib.d/src/ZincPath.cpp')
-rw-r--r-- | zinclib.d/src/ZincPath.cpp | 415 |
1 files changed, 415 insertions, 0 deletions
diff --git a/zinclib.d/src/ZincPath.cpp b/zinclib.d/src/ZincPath.cpp new file mode 100644 index 0000000..a120014 --- /dev/null +++ b/zinclib.d/src/ZincPath.cpp @@ -0,0 +1,415 @@ +/** Path.cpp + * zinclib + * + * This software is the property of IntuiLab SA, France. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Here we defines The ZincPath object + * + * 08/03/05 + * + * Contributors: + * Benoit Peccatte <peccatte@intuilab.com> + * + */ +#include "Zinc.hpp" +#include "ZincInternal.hpp" + +#include <math.h> + +// convert degree to radians +const double convertRatio = atan2 (1., 1.) * 4. / 180.; + +/** + * Calculate d % m for doubles + * this is because the C % works only for integers + */ +inline double modulo (double d, double m) +{ + return d - (floor (d / m) * m); +// return d; +} + +/** + * Append the point to the real path + * + * @param x,y the point coordinate + * @param c true if the point is a control point + */ +inline void ZincPath::addPoint (double x, double y, bool c) +{ + // update last control point + lastX = x; + lastY = y; + + // we can't use a flat list since zinc accepts flat list only for simple + // lines + Tcl_Obj* point[3]; + int i = 2; + //create a point object + //an object for x + point[0] = Tcl_NewDoubleObj (x); + // an object for y + point[1] = Tcl_NewDoubleObj (y); + // an object for 'c' only if needed + if (c) + { + point[2] = Tcl_NewStringObj ("c", -1); + i = 3; + } + + // the point (this increments refcount) + Tcl_Obj* tmp = Tcl_NewListObj (i, point); + // append the point to the list + Zinc::z_tcl_call ( Tcl_ListObjAppendElement (Zinc::interp, path, tmp), + "addpoint Error:"); +} + +/** + * Convert ellipse from SVG form to centered form (used only by arcTo) + * + * @param x0,y0 origin of the arc + * @param rx x-radius of ellipse in degree (can be modified) + * @param ry y-radius of ellipse in degree (can be modified) + * @param phi rotation of ellipse in degree (can be modified) + * @param largeArc true if the large part of the ellipse + * @param sweep true for a positive angle direction for the drawing + * @param x,y destination point + * @param cx,cy center coordinate + * @param theta begining of arc in degree + * @param delta extent of arc in degree + */ +void ZincPath::convertFromSvg (double x0, double y0, double &rx, double &ry, + double &phi, bool largeArc, bool sweep, + double x, double y, double &cx, double &cy, + double &theta, double &delta) +{ + /* all this strictly follow the script given in "SVG essentials" + * p85 : convert an elliptical arc fo SVG to an elliptical arc + * based around a central point + */ + + // temporary variables + double dx2, dy2, phiR, x1, y1; + double rxSq, rySq, x1Sq, y1Sq; + double sign, sq, coef, radiusCheck; + double cx1, cy1, sx2, sy2; + double p, n, ux, uy, vx, vy; + + // compute 1/2 distance between current and final point + dx2 = (x0 - x) / 2.; + dy2 = (y0 - y) / 2.; + + //convert from degree to radians + phi = modulo (phi, 360.); + phiR = phi * convertRatio; + + //compute (x1, y1) + x1 = cos (phiR) * dx2 + sin (phiR) * dy2; + y1 = -sin (phiR) * dx2 + cos (phiR) * dy2; + + // make sure radii are large enough + rx = fabs (rx); ry = fabs (ry); + rxSq = rx * rx; + rySq = ry * ry; + x1Sq = x1 * x1; + y1Sq = y1 * y1; + + radiusCheck = (x1Sq / rxSq) + (y1Sq / rySq); + if (radiusCheck > 1.) + { + rx *= sqrt (radiusCheck); + ry *= sqrt (radiusCheck); + rxSq = rx * rx; + rySq = ry * ry; + } + + //step 2 compute (cx1, cy1) + sign = (largeArc == sweep) ? -1. : 1.; + sq = ((rxSq * rySq) - (rxSq * y1Sq) - (rySq * x1Sq)) / + ((rxSq * y1Sq) + (rySq * x1Sq)); + sq = (sq < 0.) ? 0. : sq; + coef = (sign * sqrt (sq)); + cx1 = coef * ((rx * y1) / ry); + cy1 = coef * -((ry * x1) / rx); + + //step 3 : compute (cx, cy) from (cx1, cy1) + sx2 = (x0 + x) / 2; + sy2 = (y0 + y) / 2; + + cx = sx2 + (cos (phiR) * cx1 - sin (phiR) * cy1); + cy = sy2 + (sin (phiR) * cx1 + cos (phiR) * cy1); + + //step 4 : compute angle start angle extent + ux = (x1 - cx1) / rx; + uy = (y1 - cy1) / ry; + vx = (-x1 - cx1) / rx; + vy = (-y1 - cy1) / ry; + n = sqrt ((ux *ux) + (uy * uy)); + p = ux; // 1 * ux + 0 * uy + sign = (uy < 0.) ? -1. : 1.; + + theta = sign * acos (p /n); + theta = theta / convertRatio; + + n = sqrt ((ux * ux + uy * uy) * (vx * vx + vy * vy)); + p = ux * vx + uy * vy; + sign = ((ux * vy - uy * vx) < 0.) ? -1. : 1.; + delta = sign * acos (p / n); + delta = delta / convertRatio;; + + if (!sweep && delta > 0.) + { + delta -= 360.; + } + else if (sweep && delta < 0.) + { + delta += 360.; + } + +// delta = modulo (delta, 360.); +// theta = modulo (theta, 360.); +} + + +/** + * The public constructor + * + * @param x,y the initial point + */ +ZincPath::ZincPath (double x, double y) + : firstX (x), firstY (y) +{ + // create a default path + path = Tcl_NewListObj (0, NULL); + // the path must not be deleted by tcl + Tcl_IncrRefCount (path); + // add the first point + addPoint (x, y, false); +} + +/** + * The public destructor + * + * @warning Do not destroy a ZincPath if Zinc is not loaded + */ +ZincPath::~ZincPath () +{ + //decrement reference count on all objs in list -> free + Tcl_SetIntObj (path, 1); + //decrement reference count on the list -> free + Tcl_DecrRefCount (path); +} + +/** + * Close current path + */ +void ZincPath::close () +{ + addPoint (firstX, firstY, false); +} + +/** + * Draw a line from current point to next point + * + * @param x,y next point + */ +void ZincPath::lineTo (double x, double y) +{ + addPoint (x, y, false); +} + +/** + * Draw a cubic bezier using specified control and destination points + * call cubicBezierTo + * + * @param cx1,cy1 first control point + * @param cx2,cy2 second control point + * @param x,y destination point + */ +void ZincPath::curveTo (double cx1, double cy1, double cx2, double cy2, + double x, double y) +{ + cubicBezierTo (cx1, cy1, cx2, cy2, x, y); +} + +/** + * Draw a cubic bezier using specified control and destination points + * + * @param cx1,cy1 first control point + * @param cx2,cy2 second control point + * @param x,y destination point + */ +void ZincPath::cubicBezierTo (double cx1, double cy1, + double cx2, double cy2, + double x, double y) +{ + addPoint (cx1, cy1, true); + addPoint (cx2, cy2, true); + addPoint (x, y, false); +} + +/** + * Draw a quadratic bezier using specified control and destination point + * + * @param cx1,cy1 first control point + * @param cx2,cy2 second control point + * @param x,y destination point + */ +void ZincPath::quadraticBezierTo (double cx, double cy, double x, double y) +{ + // convert from a quadratic bezier to a cubic bezier + // since that's what is supported by zinc + /* [[x1, y1], [qx, qy, 'q'], [x2,y2]] + cx1 = x1 + (qx - x1) * 2/3 + cy1 = y1 + (qy - y1) * 2/3 + cx2 = qx + (x2 - qx)/3 + cy2 = qy + (y2 - qy)/3 + */ + double cx1 = lastX + (cx - lastX) * 2/3; + double cy1 = lastY + (cy - lastY) * 2/3; + double cx2 = cx + (x - cx) / 3; + double cy2 = cy + (y - cy) / 3; + addPoint (cx1, cy1, true); + addPoint (cx2, cy2, true); + addPoint (x, y, false); +} + +/** + * Draw an arc from current point to x,y + * + * @param rx x-radius of ellipse + * @param ry y-radius of ellipse + * @param xAxisRotation rotation of ellipse + * @param largeArc true if the large part of the ellipse + * @param sweepFlag true for a positive angle direction for the drawing + * @param x,y destination point + */ +void ZincPath::arcTo (double rx, double ry, double xAxisRotation, bool largeArc, + bool sweepFlag, double x, double y) +{ + double sx, sy, start, arc; + // convert to a centered representation + convertFromSvg (lastX, lastY, rx, ry, xAxisRotation, largeArc, sweepFlag, + x, y, sx, sy, start, arc); + + // this is all taken from first case study for Intuikit + + /* convert to a curve representation + * For a good approximation, we need 8 quadratic Bezier + * to make a circle : the maximal angle is 45° + */ + // local variables + int segs; + double segAngle, angle, angleMid; + double cosphi, sinphi, tx, ty; + double previousX, previousY; + double bx, by, qx, qy; + double cx1, cy1, cx2, cy2; + + //1) calculate segment counts + segs = int (ceil (fabs (arc) / 45.)); + + //let's create segments of the same angle + //2) calculate this angle + segAngle = arc / double(segs) * convertRatio; + + xAxisRotation = xAxisRotation * convertRatio; + start = start * convertRatio; + + //3) Our fake starting point (relative to (x,y)) + // true start point is (x,y) + sx = lastX - cos (start) * rx; + sy = lastY - sin (start) * ry; + + /* 4) calculate values that will be used for a rotation + * of centre (x,y) and angle phi + * the matrix is : + * cos(phi) -sin(phi) tx + * sin(phi) cos(phi) ty + * 0 0 1 + */ + cosphi = cos (xAxisRotation); + sinphi = sin (xAxisRotation); + tx = (1. - cosphi) * lastX + sinphi * lastY; + ty = (1. - cosphi) * lastY - sinphi * lastX; + + //5) save crrent values + previousX = lastX; + previousY = lastY; + angle = start; + + //6) we already got the first point + + //7) calculate segments + for (int i(0) ; i < segs ; i++) + { + //7.1) increment angle + angle += segAngle; + + //7.2) calculate intermediate angle value + angleMid = angle - segAngle / 2.; + + //7.3) calculate last point of the segment from center and rays + bx = sx + cos (angle) * rx; + by = sy + sin (angle) * ry; + + //7.4) calculate control point for the quadratic bezier curve + qx = sx + cos (angleMid) * (rx / cos (segAngle / 2.)); + qy = sy + sin (angleMid) * (ry / cos (segAngle / 2.)); + + //7.5) calculate control points for the equivalent bezier curve + cx1 = previousX + (qx - previousX) * 2. / 3.; + cy1 = previousY + (qy - previousY) * 2. / 3.; + cx2 = qx + (bx - qx) / 3.; + cy2 = qy + (by - qy) / 3.; + + //7.6) add points + addPoint (cosphi * cx1 - sinphi * cy1 + tx, + sinphi * cx1 + cosphi * cy1 + ty, true); + addPoint (cosphi * cx2 - sinphi * cy2 + tx, + sinphi * cx2 + cosphi * cy2 + ty, true); + addPoint (cosphi * bx - sinphi * by + tx, + sinphi * bx + cosphi * by + ty, false); + + //7.7) Save last point + previousX = bx; + previousY = by; + } + +} + +/** + * Return a table of Tcl_Obj* containing a liste of coords points + * It's up to the caller to delete the resulting table + * + * @return a Tcl_Obj* of type list + */ +Tcl_Obj* ZincPath::getTable () +{ + return path; +} + + |