elliptical-arc.cpp revision 763176bacabff6cd0f23bf6be4c6c721c48c526e
5255N/A/*
5255N/A * SVG Elliptical Arc Class
5255N/A *
5255N/A * Authors:
5255N/A * Marco Cecchetti <mrcekets at gmail.com>
5255N/A * Krzysztof KosiƄski <tweenk.pl@gmail.com>
5255N/A * Copyright 2008-2009 Authors
5255N/A *
5255N/A * This library is free software; you can redistribute it and/or
5255N/A * modify it either under the terms of the GNU Lesser General Public
5255N/A * License version 2.1 as published by the Free Software Foundation
5255N/A * (the "LGPL") or, at your option, under the terms of the Mozilla
5255N/A * Public License Version 1.1 (the "MPL"). If you do not alter this
5255N/A * notice, a recipient may use your version of this file under either
5255N/A * the MPL or the LGPL.
5255N/A *
5255N/A * You should have received a copy of the LGPL along with this library
5255N/A * in the file COPYING-LGPL-2.1; if not, write to the Free Software
5255N/A * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
5255N/A * You should have received a copy of the MPL along with this library
5255N/A * in the file COPYING-MPL-1.1
5255N/A *
5255N/A * The contents of this file are subject to the Mozilla Public License
5255N/A * Version 1.1 (the "License"); you may not use this file except in
5255N/A * compliance with the License. You may obtain a copy of the License at
5255N/A * http://www.mozilla.org/MPL/
5255N/A *
5255N/A * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
5255N/A * OF ANY KIND, either express or implied. See the LGPL or the MPL for
5255N/A * the specific language governing rights and limitations.
5255N/A */
5255N/A
5255N/A#include <cfloat>
5255N/A#include <limits>
5255N/A#include <memory>
5255N/A
5255N/A#include <2geom/elliptical-arc.h>
5255N/A#include <2geom/ellipse.h>
5255N/A#include <2geom/sbasis-geometric.h>
5255N/A#include <2geom/bezier-curve.h>
5255N/A#include <2geom/poly.h>
5255N/A#include <2geom/transforms.h>
5255N/A#include <2geom/utils.h>
5255N/A
5255N/A#include <2geom/numeric/vector.h>
5255N/A#include <2geom/numeric/fitting-tool.h>
5255N/A#include <2geom/numeric/fitting-model.h>
5255N/A
5255N/A
5255N/Anamespace Geom
5255N/A{
5255N/A
5255N/A/**
5255N/A * @class EllipticalArc
5255N/A * @brief Elliptical arc curve
5255N/A *
5255N/A * Elliptical arc is a curve taking the shape of a section of an ellipse.
5255N/A *
5255N/A * The arc function has two forms: the regular one, mapping the unit interval to points
5255N/A * on 2D plane (the linear domain), and a second form that maps some interval
5255N/A * \f$A \subseteq [0,2\pi)\f$ to the same points (the angular domain). The interval \f$A\f$
5255N/A * determines which part of the ellipse forms the arc. The arc is said to contain an angle
5255N/A * if its angular domain includes that angle (and therefore it is defined for that angle).
5255N/A *
5255N/A * The angular domain considers each ellipse to be
5255N/A * a rotated, scaled and translated unit circle: 0 corresponds to \f$(1,0)\f$ on the unit circle,
5255N/A * \f$\pi/2\f$ corresponds to \f$(0,1)\f$, \f$\pi\f$ to \f$(-1,0)\f$ and \f$3\pi/2\f$
5255N/A * to \f$(0,-1)\f$. After the angle is mapped to a point from a unit circle, the point is
5255N/A * transformed using a matrix of this form
5255N/A * \f[ M = \left[ \begin{array}{ccc}
5255N/A r_X \cos(\theta) & -r_Y \sin(\theta) & 0 \\
5255N/A r_X \sin(\theta) & r_Y \cos(\theta) & 0 \\
5255N/A c_X & c_Y & 1 \end{array} \right] \f]
5255N/A * where \f$r_X, r_Y\f$ are the X and Y rays of the ellipse, \f$\theta\f$ is its angle of rotation,
5255N/A * and \f$c_X, c_Y\f$ the coordinates of the ellipse's center - thus mapping the angle
5255N/A * to some point on the ellipse. Note that for example the point at angluar coordinate 0,
5255N/A * the center and the point at angular coordinate \f$\pi/4\f$ do not necessarily
5255N/A * create an angle of \f$\pi/4\f$ radians; it is only the case if both axes of the ellipse
5255N/A * are of the same length (i.e. it is a circle).
5255N/A *
5255N/A * @image html ellipse-angular-coordinates.png "An illustration of the angular domain"
5255N/A *
5255N/A * Each arc is defined by five variables: The initial and final point, the ellipse's rays,
5255N/A * and the ellipse's rotation. Each set of those parameters corresponds to four different arcs,
5255N/A * with two of them larger than half an ellipse and two of them turning clockwise while traveling
5255N/A * from initial to final point. The two flags disambiguate between them: "large arc flag" selects
5255N/A * the bigger arc, while the "sweep flag" selects the clockwise arc.
5255N/A *
5255N/A * @image html elliptical-arc-flags.png "The four possible arcs and the meaning of flags"
5255N/A *
5255N/A * @ingroup Curves
5255N/A */
5255N/A
5255N/ARect EllipticalArc::boundsExact() const
5255N/A{
5255N/A double extremes[4];
5255N/A double sinrot, cosrot;
5255N/A sincos(_rot_angle, sinrot, cosrot);
5255N/A
5255N/A extremes[0] = std::atan2( -ray(Y) * sinrot, ray(X) * cosrot );
5255N/A extremes[1] = extremes[0] + M_PI;
5255N/A if ( extremes[0] < 0 ) extremes[0] += 2*M_PI;
5255N/A extremes[2] = std::atan2( ray(Y) * cosrot, ray(X) * sinrot );
5255N/A extremes[3] = extremes[2] + M_PI;
5255N/A if ( extremes[2] < 0 ) extremes[2] += 2*M_PI;
5255N/A
5255N/A
5255N/A double arc_extremes[4];
5255N/A arc_extremes[0] = initialPoint()[X];
5255N/A arc_extremes[1] = finalPoint()[X];
5255N/A if ( arc_extremes[0] < arc_extremes[1] )
5255N/A std::swap(arc_extremes[0], arc_extremes[1]);
5255N/A arc_extremes[2] = initialPoint()[Y];
5255N/A arc_extremes[3] = finalPoint()[Y];
5255N/A if ( arc_extremes[2] < arc_extremes[3] )
5255N/A std::swap(arc_extremes[2], arc_extremes[3]);
5255N/A
5255N/A for (unsigned i = 0; i < 4; ++i) {
5255N/A if (containsAngle(extremes[i])) {
5255N/A arc_extremes[i] = valueAtAngle(extremes[i], (i >> 1) ? Y : X);
5255N/A }
5255N/A }
5255N/A
5255N/A return Rect( Point(arc_extremes[1], arc_extremes[3]) ,
5255N/A Point(arc_extremes[0], arc_extremes[2]) );
5255N/A}
5255N/A
5255N/A
5255N/APoint EllipticalArc::pointAtAngle(Coord t) const
5255N/A{
5255N/A Point ret = Point::polar(t) * unitCircleTransform();
5255N/A return ret;
5255N/A}
5255N/A
5255N/ACoord EllipticalArc::valueAtAngle(Coord t, Dim2 d) const
5255N/A{
5255N/A Coord sinrot, cosrot, cost, sint;
5255N/A sincos(_rot_angle, sinrot, cosrot);
5255N/A sincos(t, sint, cost);
5255N/A
5255N/A if ( d == X ) {
5255N/A return ray(X) * cosrot * cost
5255N/A - ray(Y) * sinrot * sint
5255N/A + center(X);
5255N/A } else {
5255N/A return ray(X) * sinrot * cost
5255N/A + ray(Y) * cosrot * sint
5255N/A + center(Y);
5255N/A }
5255N/A}
5255N/A
5255N/AAffine EllipticalArc::unitCircleTransform() const
5255N/A{
5255N/A Affine ret = Scale(ray(X), ray(Y)) * Rotate(_rot_angle);
5255N/A ret.setTranslation(center());
5255N/A return ret;
5255N/A}
5255N/A
5255N/Astd::vector<Coord> EllipticalArc::roots(Coord v, Dim2 d) const
5255N/A{
5255N/A std::vector<Coord> sol;
5255N/A
5255N/A if ( are_near(ray(X), 0) && are_near(ray(Y), 0) ) {
5255N/A if ( center(d) == v )
5255N/A sol.push_back(0);
5255N/A return sol;
5255N/A }
5255N/A
5255N/A static const char* msg[2][2] =
5255N/A {
5255N/A { "d == X; ray(X) == 0; "
5255N/A "s = (v - center(X)) / ( -ray(Y) * std::sin(_rot_angle) ); "
5255N/A "s should be contained in [-1,1]",
5255N/A "d == X; ray(Y) == 0; "
5255N/A "s = (v - center(X)) / ( ray(X) * std::cos(_rot_angle) ); "
5255N/A "s should be contained in [-1,1]"
5255N/A },
5255N/A { "d == Y; ray(X) == 0; "
5255N/A "s = (v - center(X)) / ( ray(Y) * std::cos(_rot_angle) ); "
5255N/A "s should be contained in [-1,1]",
5255N/A "d == Y; ray(Y) == 0; "
5255N/A "s = (v - center(X)) / ( ray(X) * std::sin(_rot_angle) ); "
5255N/A "s should be contained in [-1,1]"
5255N/A },
5255N/A };
5255N/A
5255N/A for ( unsigned int dim = 0; dim < 2; ++dim )
5255N/A {
5255N/A if ( are_near(ray((Dim2) dim), 0) )
5255N/A {
5255N/A if ( initialPoint()[d] == v && finalPoint()[d] == v )
5255N/A {
5255N/A THROW_INFINITESOLUTIONS(0);
5255N/A }
5255N/A if ( (initialPoint()[d] < finalPoint()[d])
5255N/A && (initialPoint()[d] > v || finalPoint()[d] < v) )
5255N/A {
5255N/A return sol;
5255N/A }
5255N/A if ( (initialPoint()[d] > finalPoint()[d])
5255N/A && (finalPoint()[d] > v || initialPoint()[d] < v) )
5255N/A {
5255N/A return sol;
5255N/A }
5255N/A double ray_prj = 0.0;
5255N/A switch(d)
5255N/A {
5255N/A case X:
5255N/A switch(dim)
5255N/A {
5255N/A case X: ray_prj = -ray(Y) * std::sin(_rot_angle);
5255N/A break;
5255N/A case Y: ray_prj = ray(X) * std::cos(_rot_angle);
5255N/A break;
5255N/A }
5255N/A break;
5255N/A case Y:
5255N/A switch(dim)
5255N/A {
5255N/A case X: ray_prj = ray(Y) * std::cos(_rot_angle);
5255N/A break;
5255N/A case Y: ray_prj = ray(X) * std::sin(_rot_angle);
5255N/A break;
5255N/A }
5255N/A break;
5255N/A }
5255N/A
5255N/A double s = (v - center(d)) / ray_prj;
5255N/A if ( s < -1 || s > 1 )
5255N/A {
5255N/A THROW_LOGICALERROR(msg[d][dim]);
5255N/A }
5255N/A switch(dim)
5255N/A {
5255N/A case X:
5255N/A s = std::asin(s); // return a value in [-PI/2,PI/2]
5255N/A if ( logical_xor( _sweep, are_near(initialAngle(), M_PI/2) ) )
5255N/A {
5255N/A if ( s < 0 ) s += 2*M_PI;
5255N/A }
5255N/A else
5255N/A {
5255N/A s = M_PI - s;
5255N/A if (!(s < 2*M_PI) ) s -= 2*M_PI;
5255N/A }
5255N/A break;
5255N/A case Y:
5255N/A s = std::acos(s); // return a value in [0,PI]
5255N/A if ( logical_xor( _sweep, are_near(initialAngle(), 0) ) )
5255N/A {
5255N/A s = 2*M_PI - s;
5255N/A if ( !(s < 2*M_PI) ) s -= 2*M_PI;
5255N/A }
5255N/A break;
5255N/A }
5255N/A
5255N/A //std::cerr << "s = " << rad_to_deg(s);
5255N/A s = map_to_01(s);
5255N/A //std::cerr << " -> t: " << s << std::endl;
5255N/A if ( !(s < 0 || s > 1) )
5255N/A sol.push_back(s);
5255N/A return sol;
5255N/A }
5255N/A }
5255N/A
5255N/A double rotx, roty;
5255N/A sincos(_rot_angle, roty, rotx);
5255N/A if (d == X) roty = -roty;
5255N/A
5255N/A double rxrotx = ray(X) * rotx;
5255N/A double c_v = center(d) - v;
5255N/A
5255N/A double a = -rxrotx + c_v;
5255N/A double b = ray(Y) * roty;
5255N/A double c = rxrotx + c_v;
5255N/A //std::cerr << "a = " << a << std::endl;
5255N/A //std::cerr << "b = " << b << std::endl;
5255N/A //std::cerr << "c = " << c << std::endl;
5255N/A
5255N/A if ( are_near(a,0) )
5255N/A {
5255N/A sol.push_back(M_PI);
5255N/A if ( !are_near(b,0) )
5255N/A {
5255N/A double s = 2 * std::atan(-c/(2*b));
5255N/A if ( s < 0 ) s += 2*M_PI;
5255N/A sol.push_back(s);
5255N/A }
5255N/A }
5255N/A else
5255N/A {
5255N/A double delta = b * b - a * c;
5255N/A //std::cerr << "delta = " << delta << std::endl;
5255N/A if ( are_near(delta, 0) )
5255N/A {
5255N/A double s = 2 * std::atan(-b/a);
5255N/A if ( s < 0 ) s += 2*M_PI;
5255N/A sol.push_back(s);
5255N/A }
5255N/A else if ( delta > 0 )
5255N/A {
5255N/A double sq = std::sqrt(delta);
5255N/A double s = 2 * std::atan( (-b - sq) / a );
5255N/A if ( s < 0 ) s += 2*M_PI;
5255N/A sol.push_back(s);
5255N/A s = 2 * std::atan( (-b + sq) / a );
5255N/A if ( s < 0 ) s += 2*M_PI;
5255N/A sol.push_back(s);
5255N/A }
5255N/A }
5255N/A
5255N/A std::vector<double> arc_sol;
5255N/A for (unsigned int i = 0; i < sol.size(); ++i )
5255N/A {
5255N/A //std::cerr << "s = " << rad_to_deg(sol[i]);
5255N/A sol[i] = map_to_01(sol[i]);
5255N/A //std::cerr << " -> t: " << sol[i] << std::endl;
5255N/A if ( !(sol[i] < 0 || sol[i] > 1) )
5255N/A arc_sol.push_back(sol[i]);
5255N/A }
5255N/A return arc_sol;
5255N/A}
5255N/A
5255N/A
5255N/A// D(E(t,C),t) = E(t+PI/2,O), where C is the ellipse center
5255N/A// the derivative doesn't rotate the ellipse but there is a translation
5255N/A// of the parameter t by an angle of PI/2 so the ellipse points are shifted
5255N/A// of such an angle in the cw direction
5255N/ACurve *EllipticalArc::derivative() const
5255N/A{
5255N/A EllipticalArc *result = static_cast<EllipticalArc*>(duplicate());
5255N/A result->_center[X] = result->_center[Y] = 0;
5255N/A result->_start_angle += M_PI/2;
5255N/A if( !( result->_start_angle < 2*M_PI ) )
5255N/A {
5255N/A result->_start_angle -= 2*M_PI;
5255N/A }
5255N/A result->_end_angle += M_PI/2;
5255N/A if( !( result->_end_angle < 2*M_PI ) )
5255N/A {
5255N/A result->_end_angle -= 2*M_PI;
5255N/A }
5255N/A result->_initial_point = result->pointAtAngle( result->initialAngle() );
5255N/A result->_final_point = result->pointAtAngle( result->finalAngle() );
5255N/A return result;
5255N/A}
5255N/A
5255N/A
5255N/Astd::vector<Point>
5255N/AEllipticalArc::pointAndDerivatives(Coord t, unsigned int n) const
5255N/A{
5255N/A unsigned int nn = n+1; // nn represents the size of the result vector.
5255N/A std::vector<Point> result;
5255N/A result.reserve(nn);
5255N/A double angle = map_unit_interval_on_circular_arc(t, initialAngle(),
5255N/A finalAngle(), _sweep);
5255N/A std::auto_ptr<EllipticalArc> ea( static_cast<EllipticalArc*>(duplicate()) );
5255N/A ea->_center = Point(0,0);
5255N/A unsigned int m = std::min(nn, 4u);
5255N/A for ( unsigned int i = 0; i < m; ++i )
5255N/A {
5255N/A result.push_back( ea->pointAtAngle(angle) );
5255N/A angle += M_PI/2;
5255N/A if ( !(angle < 2*M_PI) ) angle -= 2*M_PI;
5255N/A }
5255N/A m = nn / 4;
5255N/A for ( unsigned int i = 1; i < m; ++i )
5255N/A {
5255N/A for ( unsigned int j = 0; j < 4; ++j )
5255N/A result.push_back( result[j] );
5255N/A }
5255N/A m = nn - 4 * m;
5255N/A for ( unsigned int i = 0; i < m; ++i )
5255N/A {
5255N/A result.push_back( result[i] );
5255N/A }
5255N/A if ( !result.empty() ) // nn != 0
5255N/A result[0] = pointAtAngle(angle);
5255N/A return result;
5255N/A}
5255N/A
5255N/Abool EllipticalArc::containsAngle(Coord angle) const
5255N/A{
5255N/A return AngleInterval::contains(angle);
5255N/A}
5255N/A
5255N/ACurve* EllipticalArc::portion(double f, double t) const
5255N/A{
5255N/A // fix input arguments
5255N/A if (f < 0) f = 0;
5255N/A if (f > 1) f = 1;
5255N/A if (t < 0) t = 0;
5255N/A if (t > 1) t = 1;
5255N/A
5255N/A if ( are_near(f, t) )
5255N/A {
5255N/A EllipticalArc *arc = static_cast<EllipticalArc*>(duplicate());
5255N/A arc->_center = arc->_initial_point = arc->_final_point = pointAt(f);
5255N/A arc->_start_angle = arc->_end_angle = _start_angle;
5255N/A arc->_rot_angle = _rot_angle;
5255N/A arc->_sweep = _sweep;
5255N/A arc->_large_arc = _large_arc;
5255N/A return arc;
5255N/A }
5255N/A
5255N/A EllipticalArc *arc = static_cast<EllipticalArc*>(duplicate());
5255N/A arc->_initial_point = pointAt(f);
5255N/A arc->_final_point = pointAt(t);
5255N/A if ( f > t ) arc->_sweep = !_sweep;
5255N/A if ( _large_arc && fabs(sweepAngle() * (t-f)) < M_PI)
5255N/A arc->_large_arc = false;
5255N/A arc->_updateCenterAndAngles(arc->isSVGCompliant()); //TODO: be more clever
5255N/A return arc;
5255N/A}
5255N/A
5255N/A// the arc is the same but traversed in the opposite direction
5255N/ACurve *EllipticalArc::reverse() const {
5255N/A EllipticalArc *rarc = static_cast<EllipticalArc*>(duplicate());
5255N/A rarc->_sweep = !_sweep;
5255N/A rarc->_initial_point = _final_point;
5255N/A rarc->_final_point = _initial_point;
5255N/A rarc->_start_angle = _end_angle;
5255N/A rarc->_end_angle = _start_angle;
5255N/A rarc->_updateCenterAndAngles(rarc->isSVGCompliant());
5255N/A return rarc;
5255N/A}
5255N/A
5255N/A#ifdef HAVE_GSL // GSL is required for function "solve_reals"
5255N/Astd::vector<double> EllipticalArc::allNearestPoints( Point const& p, double from, double to ) const
5255N/A{
5255N/A std::vector<double> result;
5255N/A
5255N/A if ( from > to ) std::swap(from, to);
5255N/A if ( from < 0 || to > 1 )
5255N/A {
5255N/A THROW_RANGEERROR("[from,to] interval out of range");
5255N/A }
5255N/A
5255N/A if ( ( are_near(ray(X), 0) && are_near(ray(Y), 0) ) || are_near(from, to) )
5255N/A {
5255N/A result.push_back(from);
5255N/A return result;
5255N/A }
5255N/A else if ( are_near(ray(X), 0) || are_near(ray(Y), 0) )
5255N/A {
5255N/A LineSegment seg(pointAt(from), pointAt(to));
5255N/A Point np = seg.pointAt( seg.nearestPoint(p) );
5255N/A if ( are_near(ray(Y), 0) )
5255N/A {
5255N/A if ( are_near(_rot_angle, M_PI/2)
5255N/A || are_near(_rot_angle, 3*M_PI/2) )
5255N/A {
5255N/A result = roots(np[Y], Y);
5255N/A }
5255N/A else
5255N/A {
5255N/A result = roots(np[X], X);
5255N/A }
5255N/A }
5255N/A else
5255N/A {
5255N/A if ( are_near(_rot_angle, M_PI/2)
5255N/A || are_near(_rot_angle, 3*M_PI/2) )
5255N/A {
5255N/A result = roots(np[X], X);
5255N/A }
5255N/A else
5255N/A {
5255N/A result = roots(np[Y], Y);
5255N/A }
5255N/A }
5255N/A return result;
5255N/A }
5255N/A else if ( are_near(ray(X), ray(Y)) )
5255N/A {
5255N/A Point r = p - center();
5255N/A if ( are_near(r, Point(0,0)) )
5255N/A {
5255N/A THROW_INFINITESOLUTIONS(0);
5255N/A }
5255N/A // TODO: implement case r != 0
5255N/A// Point np = ray(X) * unit_vector(r);
5255N/A// std::vector<double> solX = roots(np[X],X);
5255N/A// std::vector<double> solY = roots(np[Y],Y);
5255N/A// double t;
5255N/A// if ( are_near(solX[0], solY[0]) || are_near(solX[0], solY[1]))
5255N/A// {
5255N/A// t = solX[0];
5255N/A// }
5255N/A// else
5255N/A// {
5255N/A// t = solX[1];
5255N/A// }
5255N/A// if ( !(t < from || t > to) )
5255N/A// {
5255N/A// result.push_back(t);
5255N/A// }
5255N/A// else
5255N/A// {
5255N/A//
5255N/A// }
5255N/A }
5255N/A
5255N/A // solve the equation <D(E(t),t)|E(t)-p> == 0
5255N/A // that provides min and max distance points
5255N/A // on the ellipse E wrt the point p
5255N/A // after the substitutions:
5255N/A // cos(t) = (1 - s^2) / (1 + s^2)
5255N/A // sin(t) = 2t / (1 + s^2)
5255N/A // where s = tan(t/2)
5255N/A // we get a 4th degree equation in s
5255N/A /*
5255N/A * ry s^4 ((-cy + py) Cos[Phi] + (cx - px) Sin[Phi]) +
5255N/A * ry ((cy - py) Cos[Phi] + (-cx + px) Sin[Phi]) +
5255N/A * 2 s^3 (rx^2 - ry^2 + (-cx + px) rx Cos[Phi] + (-cy + py) rx Sin[Phi]) +
5255N/A * 2 s (-rx^2 + ry^2 + (-cx + px) rx Cos[Phi] + (-cy + py) rx Sin[Phi])
5255N/A */
Point p_c = p - center();
double rx2_ry2 = (ray(X) - ray(Y)) * (ray(X) + ray(Y));
double sinrot, cosrot;
sincos(_rot_angle, sinrot, cosrot);
double expr1 = ray(X) * (p_c[X] * cosrot + p_c[Y] * sinrot);
Poly coeff;
coeff.resize(5);
coeff[4] = ray(Y) * ( p_c[Y] * cosrot - p_c[X] * sinrot );
coeff[3] = 2 * ( rx2_ry2 + expr1 );
coeff[2] = 0;
coeff[1] = 2 * ( -rx2_ry2 + expr1 );
coeff[0] = -coeff[4];
// for ( unsigned int i = 0; i < 5; ++i )
// std::cerr << "c[" << i << "] = " << coeff[i] << std::endl;
std::vector<double> real_sol;
// gsl_poly_complex_solve raises an error
// if the leading coefficient is zero
if ( are_near(coeff[4], 0) )
{
real_sol.push_back(0);
if ( !are_near(coeff[3], 0) )
{
double sq = -coeff[1] / coeff[3];
if ( sq > 0 )
{
double s = std::sqrt(sq);
real_sol.push_back(s);
real_sol.push_back(-s);
}
}
}
else
{
real_sol = solve_reals(coeff);
}
for ( unsigned int i = 0; i < real_sol.size(); ++i )
{
real_sol[i] = 2 * std::atan(real_sol[i]);
if ( real_sol[i] < 0 ) real_sol[i] += 2*M_PI;
}
// when s -> Infinity then <D(E)|E-p> -> 0 iff coeff[4] == 0
// so we add M_PI to the solutions being lim arctan(s) = PI when s->Infinity
if ( (real_sol.size() % 2) != 0 )
{
real_sol.push_back(M_PI);
}
double mindistsq1 = std::numeric_limits<double>::max();
double mindistsq2 = std::numeric_limits<double>::max();
double dsq;
unsigned int mi1, mi2;
for ( unsigned int i = 0; i < real_sol.size(); ++i )
{
dsq = distanceSq(p, pointAtAngle(real_sol[i]));
if ( mindistsq1 > dsq )
{
mindistsq2 = mindistsq1;
mi2 = mi1;
mindistsq1 = dsq;
mi1 = i;
}
else if ( mindistsq2 > dsq )
{
mindistsq2 = dsq;
mi2 = i;
}
}
double t = map_to_01( real_sol[mi1] );
if ( !(t < from || t > to) )
{
result.push_back(t);
}
bool second_sol = false;
t = map_to_01( real_sol[mi2] );
if ( real_sol.size() == 4 && !(t < from || t > to) )
{
if ( result.empty() || are_near(mindistsq1, mindistsq2) )
{
result.push_back(t);
second_sol = true;
}
}
// we need to test extreme points too
double dsq1 = distanceSq(p, pointAt(from));
double dsq2 = distanceSq(p, pointAt(to));
if ( second_sol )
{
if ( mindistsq2 > dsq1 )
{
result.clear();
result.push_back(from);
mindistsq2 = dsq1;
}
else if ( are_near(mindistsq2, dsq) )
{
result.push_back(from);
}
if ( mindistsq2 > dsq2 )
{
result.clear();
result.push_back(to);
}
else if ( are_near(mindistsq2, dsq2) )
{
result.push_back(to);
}
}
else
{
if ( result.empty() )
{
if ( are_near(dsq1, dsq2) )
{
result.push_back(from);
result.push_back(to);
}
else if ( dsq2 > dsq1 )
{
result.push_back(from);
}
else
{
result.push_back(to);
}
}
}
return result;
}
#endif
/*
* NOTE: this implementation follows Standard SVG 1.1 implementation guidelines
* for elliptical arc curves. See Appendix F.6.
*/
void EllipticalArc::_updateCenterAndAngles(bool svg)
{
Point d = initialPoint() - finalPoint();
// TODO move this to SVGElipticalArc?
if (svg)
{
if ( initialPoint() == finalPoint() )
{
_rot_angle = _start_angle = _end_angle = 0;
_center = initialPoint();
_rays = Geom::Point(0,0);
_large_arc = _sweep = false;
return;
}
_rays[X] = std::fabs(_rays[X]);
_rays[Y] = std::fabs(_rays[Y]);
if ( are_near(ray(X), 0) || are_near(ray(Y), 0) )
{
_rays[X] = L2(d) / 2;
_rays[Y] = 0;
_rot_angle = std::atan2(d[Y], d[X]);
_start_angle = 0;
_end_angle = M_PI;
_center = middle_point(initialPoint(), finalPoint());
_large_arc = false;
_sweep = false;
return;
}
}
else
{
if ( are_near(initialPoint(), finalPoint()) )
{
if ( are_near(ray(X), 0) && are_near(ray(Y), 0) )
{
_start_angle = _end_angle = 0;
_center = initialPoint();
return;
}
else
{
THROW_RANGEERROR("initial and final point are the same");
}
}
if ( are_near(ray(X), 0) && are_near(ray(Y), 0) )
{ // but initialPoint != finalPoint
THROW_RANGEERROR(
"there is no ellipse that satisfies the given constraints: "
"ray(X) == 0 && ray(Y) == 0 but initialPoint != finalPoint"
);
}
if ( are_near(ray(Y), 0) )
{
Point v = initialPoint() - finalPoint();
if ( are_near(L2sq(v), 4*ray(X)*ray(X)) )
{
Angle angle(v);
if ( are_near( angle, _rot_angle ) )
{
_start_angle = 0;
_end_angle = M_PI;
_center = v/2 + finalPoint();
return;
}
angle -= M_PI;
if ( are_near( angle, _rot_angle ) )
{
_start_angle = M_PI;
_end_angle = 0;
_center = v/2 + finalPoint();
return;
}
THROW_RANGEERROR(
"there is no ellipse that satisfies the given constraints: "
"ray(Y) == 0 "
"and slope(initialPoint - finalPoint) != rotation_angle "
"and != rotation_angle + PI"
);
}
if ( L2sq(v) > 4*ray(X)*ray(X) )
{
THROW_RANGEERROR(
"there is no ellipse that satisfies the given constraints: "
"ray(Y) == 0 and distance(initialPoint, finalPoint) > 2*ray(X)"
);
}
else
{
THROW_RANGEERROR(
"there is infinite ellipses that satisfy the given constraints: "
"ray(Y) == 0 and distance(initialPoint, finalPoint) < 2*ray(X)"
);
}
}
if ( are_near(ray(X), 0) )
{
Point v = initialPoint() - finalPoint();
if ( are_near(L2sq(v), 4*ray(Y)*ray(Y)) )
{
double angle = std::atan2(v[Y], v[X]);
if (angle < 0) angle += 2*M_PI;
double rot_angle = _rot_angle.radians() + M_PI/2;
if ( !(rot_angle < 2*M_PI) ) rot_angle -= 2*M_PI;
if ( are_near( angle, rot_angle ) )
{
_start_angle = M_PI/2;
_end_angle = 3*M_PI/2;
_center = v/2 + finalPoint();
return;
}
angle -= M_PI;
if ( angle < 0 ) angle += 2*M_PI;
if ( are_near( angle, rot_angle ) )
{
_start_angle = 3*M_PI/2;
_end_angle = M_PI/2;
_center = v/2 + finalPoint();
return;
}
THROW_RANGEERROR(
"there is no ellipse that satisfies the given constraints: "
"ray(X) == 0 "
"and slope(initialPoint - finalPoint) != rotation_angle + PI/2 "
"and != rotation_angle + (3/2)*PI"
);
}
if ( L2sq(v) > 4*ray(Y)*ray(Y) )
{
THROW_RANGEERROR(
"there is no ellipse that satisfies the given constraints: "
"ray(X) == 0 and distance(initialPoint, finalPoint) > 2*ray(Y)"
);
}
else
{
THROW_RANGEERROR(
"there is infinite ellipses that satisfy the given constraints: "
"ray(X) == 0 and distance(initialPoint, finalPoint) < 2*ray(Y)"
);
}
}
}
Rotate rm(_rot_angle);
Affine m(rm);
m[1] = -m[1];
m[2] = -m[2];
Point p = (d / 2) * m;
double rx2 = _rays[X] * _rays[X];
double ry2 = _rays[Y] * _rays[Y];
double rxpy = _rays[X] * p[Y];
double rypx = _rays[Y] * p[X];
double rx2py2 = rxpy * rxpy;
double ry2px2 = rypx * rypx;
double num = rx2 * ry2;
double den = rx2py2 + ry2px2;
assert(den != 0);
double rad = num / den;
Point c(0,0);
if (rad > 1)
{
rad -= 1;
rad = std::sqrt(rad);
if (_large_arc == _sweep) rad = -rad;
c = rad * Point(rxpy / ray(Y), -rypx / ray(X));
_center = c * rm + middle_point(initialPoint(), finalPoint());
}
else if (rad == 1 || svg)
{
double lamda = std::sqrt(1 / rad);
_rays[X] *= lamda;
_rays[Y] *= lamda;
_center = middle_point(initialPoint(), finalPoint());
}
else
{
THROW_RANGEERROR(
"there is no ellipse that satisfies the given constraints"
);
}
Point sp((p[X] - c[X]) / ray(X), (p[Y] - c[Y]) / ray(Y));
Point ep((-p[X] - c[X]) / ray(X), (-p[Y] - c[Y]) / ray(Y));
Point v(1, 0);
_start_angle = angle_between(v, sp);
double sweep_angle = angle_between(sp, ep);
if (!_sweep && sweep_angle > 0) sweep_angle -= 2*M_PI;
if (_sweep && sweep_angle < 0) sweep_angle += 2*M_PI;
_end_angle = _start_angle;
_end_angle += sweep_angle;
}
D2<SBasis> EllipticalArc::toSBasis() const
{
D2<SBasis> arc;
// the interval of parametrization has to be [0,1]
Coord et = initialAngle().radians() + ( _sweep ? sweepAngle() : -sweepAngle() );
Linear param(initialAngle(), et);
Coord cos_rot_angle, sin_rot_angle;
sincos(_rot_angle, sin_rot_angle, cos_rot_angle);
// order = 4 seems to be enough to get a perfect looking elliptical arc
SBasis arc_x = ray(X) * cos(param,4);
SBasis arc_y = ray(Y) * sin(param,4);
arc[0] = arc_x * cos_rot_angle - arc_y * sin_rot_angle + Linear(center(X),center(X));
arc[1] = arc_x * sin_rot_angle + arc_y * cos_rot_angle + Linear(center(Y),center(Y));
// ensure that endpoints remain exact
for ( int d = 0 ; d < 2 ; d++ ) {
arc[d][0][0] = initialPoint()[d];
arc[d][0][1] = finalPoint()[d];
}
return arc;
}
Curve *EllipticalArc::transformed(Affine const& m) const
{
Ellipse e(center(X), center(Y), ray(X), ray(Y), _rot_angle);
Ellipse et = e.transformed(m);
Point inner_point = pointAt(0.5);
return et.arc( initialPoint() * m,
inner_point * m,
finalPoint() * m,
isSVGCompliant() );
}
Coord EllipticalArc::map_to_01(Coord angle) const
{
return map_circular_arc_on_unit_interval(angle, initialAngle(),
finalAngle(), _sweep);
}
} // end namespace Geom
/*
Local Variables:
mode:c++
c-file-style:"stroustrup"
c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
indent-tabs-mode:nil
fill-column:99
End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :