double REllipse::angleToParam(double a) const { double p; if (fabs(a - 2*M_PI)<RS::AngleTolerance) { p = 2*M_PI; } else if (fabs(a)<RS::AngleTolerance) { p = 0.0; } else { REllipse normEllipse = *this; normEllipse.move(-center); normEllipse.rotate(-getAngle()); normEllipse.setStartParam(0.0); normEllipse.setEndParam(2*M_PI); RLine line(RVector(0,0), RVector::createPolar(getMajorRadius()*2, a)); QList<RVector> r = RShape::getIntersectionPoints(line, normEllipse, true); if (r.length()!=1) { return RNANDOUBLE; } p = acos(r[0].x / getMajorRadius()); } if (RMath::getNormalizedAngle(a) > M_PI) { p = 2*M_PI-p; } return p; }
/** * Exports an ellipse with the current attributes. * \todo switch from line based interpolation to arcs. */ void RExporter::exportEllipse(const REllipse& ellipse, double offset) { if (ellipse.getMajorRadius()<RS::PointTolerance || ellipse.getMinorRadius()<RS::PointTolerance) { return; } RPolyline polyline; RVector cp = ellipse.getCenter(); double radius1 = ellipse.getMajorRadius(); double radius2 = ellipse.getMinorRadius(); double angle = ellipse.getAngle(); double a1 = ellipse.getStartParam(); double a2 = ellipse.getEndParam(); bool reversed = ellipse.isReversed(); double aStep; // Angle Step (rad) double a; // Current Angle (rad) aStep=0.05; RVector vp; RVector vc(cp.x, cp.y); vp.set(cp.x+cos(a1)*radius1, cp.y+sin(a1)*radius2); vp.rotate(angle, vc); polyline.appendVertex(vp); if (!reversed) { // Arc Counterclockwise: if (a1>a2-RS::AngleTolerance) { a2+=2*M_PI; } for(a=a1+aStep; a<=a2; a+=aStep) { vp.set(cp.x+cos(a)*radius1, cp.y+sin(a)*radius2); vp.rotate(angle, vc); polyline.appendVertex(vp); } } else { // Arc Clockwise: if (a1<a2+RS::AngleTolerance) { a2-=2*M_PI; } for(a=a1-aStep; a>=a2; a-=aStep) { vp.set(cp.x+cos(a)*radius1, cp.y+sin(a)*radius2); vp.rotate(angle, vc); polyline.appendVertex(vp); } } vp.set(cp.x+cos(a2)*radius1, cp.y+sin(a2)*radius2); vp.rotate(angle, vc); polyline.appendVertex(vp); exportPolyline(polyline, offset); }
QList<RVector> RShape::getIntersectionPointsAE(const RArc& arc1, const REllipse& ellipse2, bool limited) { QList<RVector> candidates = RShape::getIntersectionPointsCE( RCircle(arc1.getCenter(), arc1.getRadius()), ellipse2); if (!limited) { return candidates; } QList<RVector> res; for (int i=0; i<candidates.count(); i++) { RVector c = candidates[i]; if (arc1.isOnShape(c)) { if (!ellipse2.isFullEllipse()) { double a1 = ellipse2.getCenter().getAngleTo(ellipse2.getStartPoint()); double a2 = ellipse2.getCenter().getAngleTo(ellipse2.getEndPoint()); double a = ellipse2.getCenter().getAngleTo(c); if (!RMath::isAngleBetween(a, a1, a2, ellipse2.isReversed())) { continue; } } res.append(c); } } return res; }
void REllipseEntity::setShape(const REllipse& e) { data.setCenter(e.getCenter()); data.setMajorPoint(e.getMajorPoint()); data.setRatio(e.getRatio()); data.setStartParam(e.getStartParam()); data.setEndParam(e.getEndParam()); data.setReversed(e.isReversed()); }
QList<RVector> RShape::getIntersectionPointsEE(const REllipse& ellipse1, const REllipse& ellipse2) { QList<RVector> ret; // two full ellipses: // if bounding boxes don't intersect, ellipses don't either: if (ellipse1.isFullEllipse() && ellipse2.isFullEllipse() && !ellipse1.getBoundingBox().intersects(ellipse2.getBoundingBox())) { return ret; } // normalize ellipse ratios: REllipse ellipse1Copy = ellipse1; if (ellipse1Copy.getMajorRadius() < ellipse1Copy.getMinorRadius()) { ellipse1Copy.switchMajorMinor(); } REllipse ellipse2Copy = ellipse2; if (ellipse2Copy.getMajorRadius() < ellipse2Copy.getMinorRadius()) { ellipse2Copy.switchMajorMinor(); } // for later comparison, make sure that major points are in // quadrant I or II (relative to the ellipse center): if (fabs(ellipse1Copy.getMajorPoint().y)<RS::PointTolerance) { if (ellipse1Copy.getMajorPoint().x<0.0) { ellipse1Copy.setMajorPoint(-ellipse1Copy.getMajorPoint()); } } else { if (ellipse1Copy.getMajorPoint().y<0.0) { ellipse1Copy.setMajorPoint(-ellipse1Copy.getMajorPoint()); } } if (fabs(ellipse2Copy.getMajorPoint().y)<RS::PointTolerance) { if (ellipse2Copy.getMajorPoint().x<0.0) { ellipse2Copy.setMajorPoint(-ellipse2Copy.getMajorPoint()); } } else { if (ellipse2Copy.getMajorPoint().y<0.0) { ellipse2Copy.setMajorPoint(-ellipse2Copy.getMajorPoint()); } } // for comparison: bool identicalCenter = (ellipse1Copy.getCenter() - ellipse2Copy.getCenter()).getMagnitude() < RS::PointTolerance; bool identicalShape = (ellipse1Copy.getMajorPoint() - ellipse2Copy.getMajorPoint()).getMagnitude() < RS::PointTolerance && fabs(ellipse1Copy.getMajorRadius() - ellipse2Copy.getMajorRadius()) < RS::PointTolerance && fabs(ellipse1Copy.getMinorRadius() - ellipse2Copy.getMinorRadius()) < RS::PointTolerance; // ellipses are identical (no intersection points): if (identicalCenter && identicalShape) { //qDebug() << "RShape::getIntersectionPointsEE: identical"; return ret; } // special case: ellipse shapes are identical (different positions): if (identicalShape) { double angle = -ellipse1Copy.getAngle(); double yScale = 1.0 / ellipse1Copy.getRatio(); RVector circleCenter1 = ellipse1Copy.getCenter(); circleCenter1.rotate(angle); circleCenter1.scale(RVector(1.0, yScale)); RVector circleCenter2 = ellipse2Copy.getCenter(); circleCenter2.rotate(angle); circleCenter2.scale(RVector(1.0, yScale)); RCircle circle1(circleCenter1, ellipse1Copy.getMajorRadius()); RCircle circle2(circleCenter2, ellipse2Copy.getMajorRadius()); ret = getIntersectionPointsCC(circle1, circle2); RVector::scaleList(ret, RVector(1.0, 1.0/yScale)); RVector::rotateList(ret, -angle); return ret; } // transform ellipse2 to coordinate system of ellipse1: RVector centerOffset = -ellipse1Copy.getCenter(); double angleOffset = -ellipse1Copy.getAngle(); double majorRadius1 = ellipse1Copy.getMajorRadius(); double majorRadius2 = ellipse2Copy.getMajorRadius(); // special case: treat first ellipse as a line: if (ellipse1Copy.getMinorRadius() < RS::PointTolerance || ellipse1Copy.getRatio() < RS::PointTolerance) { ellipse2Copy.move(centerOffset); ellipse2Copy.rotate(angleOffset); RLine line(RVector(-majorRadius1,0.0),RVector(majorRadius1,0.0)); ret = getIntersectionPointsLE(line, ellipse2Copy); RVector::rotateList(ret, -angleOffset); RVector::moveList(ret, -centerOffset); // qDebug() << "RShape::getIntersectionPointsEE: ellipse 1 as line"; return ret; } // special case: treat second ellipse as a line: if (ellipse2Copy.getMinorRadius() < RS::PointTolerance || ellipse2Copy.getRatio()< RS::PointTolerance) { ellipse2Copy.move(centerOffset); ellipse2Copy.rotate(angleOffset); RLine line(RVector(-majorRadius2,0.),RVector(majorRadius2,0.)); line.rotate(ellipse2Copy.getAngle(), RVector(0.,0.)); line.move(ellipse2Copy.getCenter()); ret = getIntersectionPointsLE(line, ellipse1Copy); RVector::rotateList(ret, -angleOffset); RVector::moveList(ret, -centerOffset); // qDebug() << "RShape::getIntersectionPointsEE: ellipse 2 as line"; return ret; } double phi_1 = ellipse1Copy.getAngle(); double a1 = ellipse1Copy.getMajorRadius(); double b1 = ellipse1Copy.getMinorRadius(); double h1 = ellipse1Copy.getCenter().x; double k1 = ellipse1Copy.getCenter().y; double phi_2 = ellipse2Copy.getAngle(); double a2 = ellipse2Copy.getMajorRadius(); double b2 = ellipse2Copy.getMinorRadius(); double h2 = ellipse2Copy.getCenter().x; double k2 = ellipse2Copy.getCenter().y; int i, j, k, nroots, nychk, nintpts; double AA, BB, CC, DD, EE, FF, H2_TR, K2_TR, A22, B22, PHI_2R; double cosphi, cosphi2, sinphi, sinphi2, cosphisinphi; double tmp0, tmp1, tmp2, tmp3; double cy[5] = {0.0}; double py[5] = {0.0}; double r[3][5] = { {0.0} }; double x1, x2; double ychk[5] = {0.0}; double xint[5]; double yint[5]; // each of the ellipse axis lengths must be positive if ( (!(a1 > 0.0) || !(b1 > 0.0)) || (!(a2 > 0.0) || !(b2 > 0.0)) ) { //(*rtnCode) = ERROR_ELLIPSE_PARAMETERS; // qDebug() << "negative axis"; return QList<RVector>(); } // the rotation angles should be between -2pi and 2pi (?) if (fabs(phi_1) > twopi) { phi_1 = fmod(phi_1, twopi); } if (fabs(phi_2) > twopi) { phi_2 = fmod(phi_2, twopi); } // determine the two ellipse equations from input parameters: // Finding the points of intersection between two general ellipses // requires solving a quartic equation. Before attempting to solve the // quartic, several quick tests can be used to eliminate some cases // where the ellipses do not intersect. Optionally, can whittle away // at the problem, by addressing the easiest cases first. // Working with the translated+rotated ellipses simplifies the // calculations. The ellipses are translated then rotated so that the // first ellipse is centered at the origin and oriented with the // coordinate axes. Then, the first ellipse will have the implicit // (polynomial) form of // x^2/A1^2 + y+2/B1^2 = 1 // For the second ellipse, the center is first translated by the amount // required to put the first ellipse at the origin, e.g., by (-H1, -K1) // Then, the center of the second ellipse is rotated by the amount // required to orient the first ellipse with the coordinate axes, e.g., // through the angle -PHI_1. // The translated and rotated center point coordinates for the second // ellipse are found with the rotation matrix, derivations are // described in the reference. cosphi = cos(phi_1); sinphi = sin(phi_1); H2_TR = (h2 - h1)*cosphi + (k2 - k1)*sinphi; K2_TR = (h1 - h2)*sinphi + (k2 - k1)*cosphi; PHI_2R = phi_2 - phi_1; if (fabs(PHI_2R) > twopi) { PHI_2R = fmod(PHI_2R, twopi); } // Calculate implicit (Polynomial) coefficients for the second ellipse // in its translated-by (-H1, -H2) and rotated-by -PHI_1 postion // AA*x^2 + BB*x*y + CC*y^2 + DD*x + EE*y + FF = 0 // Formulas derived in the reference // To speed things up, store multiply-used expressions first cosphi = cos(PHI_2R); cosphi2 = cosphi*cosphi; sinphi = sin(PHI_2R); sinphi2 = sinphi*sinphi; cosphisinphi = 2.0*cosphi*sinphi; A22 = a2*a2; B22 = b2*b2; tmp0 = (cosphi*H2_TR + sinphi*K2_TR)/A22; tmp1 = (sinphi*H2_TR - cosphi*K2_TR)/B22; tmp2 = cosphi*H2_TR + sinphi*K2_TR; tmp3 = sinphi*H2_TR - cosphi*K2_TR; // implicit polynomial coefficients for the second ellipse AA = cosphi2/A22 + sinphi2/B22; BB = cosphisinphi/A22 - cosphisinphi/B22; CC = sinphi2/A22 + cosphi2/B22; DD = -2.0*cosphi*tmp0 - 2.0*sinphi*tmp1; EE = -2.0*sinphi*tmp0 + 2.0*cosphi*tmp1; FF = tmp2*tmp2/A22 + tmp3*tmp3/B22 - 1.0; // qDebug() << "second ellipse:"; // qDebug() << "AA: " << AA; // qDebug() << "BB: " << BB; // qDebug() << "CC: " << CC; // qDebug() << "DD: " << DD; // qDebug() << "EE: " << EE; // qDebug() << "FF: " << FF; // create and solve the quartic equation to find intersection points: // If execution arrives here, the ellipses are at least 'close' to // intersecting. // Coefficients for the Quartic Polynomial in y are calculated from // the two implicit equations. // Formulas for these coefficients are derived in the reference. cy[4] = pow(a1, 4.0)*AA*AA + b1*b1*(a1*a1*(BB*BB - 2.0*AA*CC) + b1*b1*CC*CC); cy[3] = 2.0*b1*(b1*b1*CC*EE + a1*a1*(BB*DD - AA*EE)); cy[2] = a1*a1*((b1*b1*(2.0*AA*CC - BB*BB) + DD*DD - 2.0*AA*FF) - 2.0*a1*a1*AA*AA) + b1*b1*(2.0*CC*FF + EE*EE); cy[1] = 2.0*b1*(a1*a1*(AA*EE - BB*DD) + EE*FF); cy[0] = (a1*(a1*AA - DD) + FF)*(a1*(a1*AA + DD) + FF); // Once the coefficients for the Quartic Equation in y are known, the // roots of the quartic polynomial will represent y-values of the // intersection points of the two ellipse curves. // The quartic sometimes degenerates into a polynomial of lesser // degree, so handle all possible cases. if (fabs (cy[4]) > 0.0) { // quartic coefficient nonzero, use quartic formula: for (i = 0; i <= 3; i++) { py[4-i] = cy[i]/cy[4]; } py[0] = 1.0; RMath::getBiQuadRoots (py, r); nroots = 4; } else if (fabs (cy[3]) > 0.0) { // quartic degenerates to cubic, use cubic formula: for (i = 0; i <= 2; i++) { py[3-i] = cy[i]/cy[3]; } py[0] = 1.0; RMath::getCubicRoots (py, r); nroots = 3; } else if (fabs (cy[2]) > 0.0) { // quartic degenerates to quadratic, use quadratic formula: for (i = 0; i <= 1; i++) { py[2-i] = cy[i]/cy[2]; } py[0] = 1.0; RMath::getQuadRoots(py, r); nroots = 2; } else if (fabs (cy[1]) > 0.0) { // quartic degenerates to linear: solve directly: // cy[1]*Y + cy[0] = 0 r[1][1] = (-cy[0]/cy[1]); r[2][1] = 0.0; nroots = 1; } else { // completely degenerate quartic: ellipses identical? // a completely degenerate quartic, which would seem to // indicate that the ellipses are identical. However, some // configurations lead to a degenerate quartic with no // points of intersection. nroots = 0; } //qDebug() << "nroots" << nroots; // check roots of the quartic: are they points of intersection? // determine which roots are real, discard any complex roots nychk = 0; for (i = 1; i <= nroots; i++) { if (fabs (r[2][i]) < epsTolerance) { nychk++; ychk[nychk] = r[1][i]*b1; //qDebug() << "real root: ychk[nychk]: " << ychk[nychk]; } } // sort the real roots by straight insertion for (j = 2; j <= nychk; j++) { tmp0 = ychk[j]; for (k = j - 1; k >= 1; k--) { if (ychk[k] <= tmp0) { break; } ychk[k+1] = ychk[k]; } ychk[k+1] = tmp0; } // determine whether polynomial roots are points of intersection // for the two ellipses nintpts = 0; for (i = 1; i <= nychk; i++) { // check for multiple roots if ((i > 1) && (fabs(ychk[i] - ychk[i-1]) < (epsTolerance/2.0))) { continue; } // check intersection points for ychk[i] if (fabs(ychk[i]) > b1) { x1 = 0.0; } else { x1 = a1*sqrt (1.0 - (ychk[i]*ychk[i])/(b1*b1)); } x2 = -x1; // qDebug() << "fabs(ellipse2tr(x1, ychk[i], AA, BB, CC, DD, EE, FF)): " << fabs(ellipse2tr(x1, ychk[i], AA, BB, CC, DD, EE, FF)); if (fabs(ellipse2tr(x1, ychk[i], AA, BB, CC, DD, EE, FF)) < epsTolerance/2.0) { // qDebug() << "got intersection I..."; nintpts++; if (nintpts > 4) { //(*rtnCode) = ERROR_INTERSECTION_PTS; return QList<RVector>(); } xint[nintpts] = x1; yint[nintpts] = ychk[i]; // qDebug() << "intersection I: " << xint[nintpts] << "/" << yint[nintpts]; } // qDebug() << "fabs(ellipse2tr(x2, ychk[i], AA, BB, CC, DD, EE, FF))" << fabs(ellipse2tr(x2, ychk[i], AA, BB, CC, DD, EE, FF)); if ((fabs(ellipse2tr(x2, ychk[i], AA, BB, CC, DD, EE, FF)) < epsTolerance/2.0) && (fabs(x2 - x1) > epsTolerance/2.0)) { // qDebug() << "got intersection II..."; nintpts++; if (nintpts > 4) { //(*rtnCode) = ERROR_INTERSECTION_PTS; return QList<RVector>(); } xint[nintpts] = x2; yint[nintpts] = ychk[i]; // qDebug() << "intersection II: " << xint[nintpts] << "/" << yint[nintpts]; } } //qDebug() << "nintpts: " << nintpts; for (int i=1; i<=nintpts; i++) { // qDebug() << "intersection: x/y: " << xint[i] << "/" << yint[i]; RVector v(xint[i], yint[i]); v.rotate(-angleOffset); v.move(-centerOffset); ret.append(v); } return ret; }
QList<RVector> RShape::getIntersectionPointsEE(const REllipse& ellipse1, const REllipse& ellipse2, bool limited) { QList<RVector> candidates = getIntersectionPointsEE(ellipse1, ellipse2); if (!limited) { return candidates; } QList<RVector> ret; for (int i=0; i<candidates.length(); i++) { RVector c = candidates[i]; bool onShape = true; double a1 = ellipse1.getCenter().getAngleTo(ellipse1.getStartPoint()); double a2 = ellipse1.getCenter().getAngleTo(ellipse1.getEndPoint()); double a = ellipse1.getCenter().getAngleTo(c); if (!RMath::isAngleBetween(a, a1, a2, ellipse1.isReversed())) { // qDebug() << "1) angle NOT between: " // << " a: " << RMath::rad2deg(a) // << " a1: " << RMath::rad2deg(a1) // << " a2: " << RMath::rad2deg(a2) // << " r: " << ellipse1.isReversed(); //ret.append(c); onShape = false; } a1 = ellipse2.getCenter().getAngleTo(ellipse2.getStartPoint()); a2 = ellipse2.getCenter().getAngleTo(ellipse2.getEndPoint()); a = ellipse2.getCenter().getAngleTo(c); if (!RMath::isAngleBetween(a, a1, a2, ellipse2.isReversed())) { // qDebug() << "2) angle NOT between: " // << " a: " << RMath::rad2deg(a) // << " a1: " << RMath::rad2deg(a1) // << " a2: " << RMath::rad2deg(a2) // << " r: " << ellipse2.isReversed(); //ret.append(c); onShape = false; } if (onShape) { ret.append(c); } } return ret; }
QList<RVector> RShape::getIntersectionPointsLE(const RLine& line1, const REllipse& ellipse2, bool limited) { QList<RVector> res; // find out if line1 is (almost) a tangent: QList<RLine> tangents = ellipse2.getTangents(line1.getMiddlePoint()); for (int i=0; i<tangents.length(); i++) { double a = tangents[i].getAngle(); double ad1 = fabs(RMath::getAngleDifference180(a, line1.getDirection1())); double ad2 = fabs(RMath::getAngleDifference180(a, line1.getDirection2())); if (ad1 < 1.0e-2 || ad2 < 1.0e-2) { res.append(tangents[i].getEndPoint()); // no need to continue: max. one tangent possible: return res; } } // rotate into normal position: double ang = ellipse2.getAngle(); double rx = ellipse2.getMajorRadius(); double ry = ellipse2.getMinorRadius(); RVector center = ellipse2.getCenter(); RVector a1 = line1.getStartPoint(); a1.rotate(-ang, center); RVector a2 = line1.getEndPoint(); a2.rotate(-ang, center); RVector origin = a1; RVector dir = a2-a1; RVector diff = origin - center; RVector mDir = RVector(dir.x/(rx*rx), dir.y/(ry*ry)); RVector mDiff = RVector(diff.x/(rx*rx), diff.y/(ry*ry)); double a = RVector::getDotProduct(dir, mDir); double b = RVector::getDotProduct(dir, mDiff); double c = RVector::getDotProduct(diff, mDiff) - 1.0; double d = b*b - a*c; RVector res1 = RVector::invalid; RVector res2 = RVector::invalid; if (d < 0) { // no solution } else if ( d > 0 ) { double root = sqrt(d); double t_a = (-b - root) / a; double t_b = (-b + root) / a; res1 = a1.getLerp(a2, t_a).rotate(ang, center); res2 = a1.getLerp(a2, t_b).rotate(ang, center); } else { double t = -b/a; if ( 0 <= t && t <= 1 ) { // one solution: res1 = a1.getLerp(a2, t).rotate(ang, center); } else { // no solution } } if (res1.isValid()) { if (!limited || (line1.isOnShape(res1) && ellipse2.isOnShape(res1))) { res.append(res1); } } if (res2.isValid()) { if (!limited || (line1.isOnShape(res2) && ellipse2.isOnShape(res2))) { res.append(res2); } } return res; }