| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | #include <algorithm> |
| | #include <cmath> |
| | #include <limits> |
| | #include <vector> |
| |
|
| | #include <boost/math/quadrature/gauss_kronrod.hpp> |
| |
|
| | #include "lc_hyperbola.h" |
| | #include "lc_quadratic.h" |
| | #include "rs_debug.h" |
| | #include "rs_line.h" |
| | #include "rs_math.h" |
| | #include "rs_painter.h" |
| |
|
| | |
| | |
| | |
| |
|
| | LC_HyperbolaData::LC_HyperbolaData(const RS_Vector &c, const RS_Vector &m, |
| | double r, double a1, double a2, bool rev) |
| | : center(c), majorP(m), ratio(r), angle1(a1), angle2(a2), reversed(rev) {} |
| | |
| |
|
| | LC_HyperbolaData::LC_HyperbolaData(const RS_Vector &f0, const RS_Vector &f1, |
| | const RS_Vector &p) |
| | : center((f0 + f1) * 0.5) { |
| | if (!p.valid || !f0.valid || !f1.valid) { |
| | majorP = RS_Vector(0, 0); |
| | return; |
| | } |
| |
|
| | double d0 = f0.distanceTo(p); |
| | double d1 = f1.distanceTo(p); |
| |
|
| | double dc = f0.distanceTo(f1); |
| | double diff = std::abs(d0 - d1); |
| |
|
| | if (dc < RS_TOLERANCE || diff < RS_TOLERANCE) { |
| | majorP = RS_Vector(0, 0); |
| | return; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | RS_Vector closerFocus = (d0 < d1) ? f0 : f1; |
| |
|
| | |
| | |
| | |
| | |
| | majorP = closerFocus - center; |
| |
|
| | |
| | |
| | double a = diff * 0.5; |
| | double c = dc * 0.5; |
| | double b = std::sqrt(c * c - a * a); |
| |
|
| | if (b < RS_TOLERANCE) { |
| | majorP = {}; |
| | return; |
| | } |
| |
|
| | ratio = b / a; |
| | majorP = majorP.normalized() * a; |
| | } |
| |
|
| | bool LC_HyperbolaData::isValid() const { |
| | LC_Hyperbola tempHb{nullptr, *this}; |
| | return tempHb.isValid(); |
| | } |
| |
|
| | RS_Vector LC_HyperbolaData::getFocus1() const { |
| | RS_Vector df = majorP * std::sqrt(1. + ratio * ratio); |
| | return center + df; |
| | } |
| | RS_Vector LC_HyperbolaData::getFocus2() const { |
| | RS_Vector df = majorP * std::sqrt(1. + ratio * ratio); |
| | return center - df; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | std::ostream &operator<<(std::ostream &os, const LC_HyperbolaData &d) { |
| | os << "HyperbolaData{" |
| | << "center=" << d.center << ", majorP=" << d.majorP |
| | << ", ratio=" << d.ratio << ", angle1=" << d.angle1 |
| | << ", angle2=" << d.angle2 |
| | << ", reversed=" << (d.reversed ? "true" : "false") << "}"; |
| | return os; |
| | } |
| |
|
| | LC_Hyperbola::LC_Hyperbola(RS_EntityContainer *parent, |
| | const LC_HyperbolaData &d) |
| | : LC_CachedLengthEntity(parent), data(d), |
| | m_bValid(d.majorP.squared() >= RS_TOLERANCE2) { |
| | LC_Hyperbola::calculateBorders(); |
| | } |
| |
|
| | LC_Hyperbola::LC_Hyperbola(const RS_Vector &f0, const RS_Vector &f1, |
| | const RS_Vector &p) |
| | : LC_Hyperbola(nullptr, LC_HyperbolaData(f0, f1, p)) {} |
| |
|
| | LC_Hyperbola::LC_Hyperbola(RS_EntityContainer *parent, |
| | const std::vector<double> &coeffs) |
| | : LC_CachedLengthEntity(parent), m_bValid(false) { |
| | createFromQuadratic(coeffs); |
| | } |
| |
|
| | LC_Hyperbola::LC_Hyperbola(RS_EntityContainer *parent, const LC_Quadratic &q) |
| | : LC_CachedLengthEntity(parent), m_bValid(false) { |
| | createFromQuadratic(q); |
| | } |
| |
|
| | |
| | |
| | |
| | bool LC_Hyperbola::createFromQuadratic(const LC_Quadratic &q) { |
| | std::vector<double> ce = q.getCoefficients(); |
| | if (ce.size() < 6) |
| | return false; |
| |
|
| | double A = ce[0], B = ce[1], C = ce[2]; |
| | double D = ce[3], E = ce[4], F = ce[5]; |
| |
|
| | |
| | double disc = B * B - 4.0 * A * C; |
| | if (disc <= 0.0) |
| | return false; |
| |
|
| | |
| | double det = A * (C * F - E * E / 4.0) - |
| | B / 2.0 * (B / 2.0 * F - D * E / 2.0) + |
| | D / 2.0 * (B / 2.0 * E - D * C / 2.0); |
| |
|
| | if (std::abs(det) < RS_TOLERANCE) |
| | return false; |
| |
|
| | |
| | double theta = 0.0; |
| | if (std::abs(B) > RS_TOLERANCE) { |
| | theta = 0.5 * std::atan2(B, A - C); |
| | } |
| |
|
| | double ct = std::cos(theta); |
| | double st = std::sin(theta); |
| |
|
| | |
| | double Ap = A * ct * ct + B * ct * st + C * st * st; |
| | double Cp = A * st * st - B * ct * st + C * ct * ct; |
| | |
| | |
| |
|
| | |
| | double Dp = D * ct + E * st; |
| | double Ep = -D * st + E * ct; |
| |
|
| | |
| | |
| | |
| | RS_Vector center{0., 0.}; |
| | if (std::abs(Ap) > RS_TOLERANCE) { |
| | center.x = -Dp / (2.0 * Ap); |
| | } else if (std::abs(Dp) > RS_TOLERANCE) { |
| | return false; |
| | } |
| |
|
| | if (std::abs(Cp) > RS_TOLERANCE) { |
| | center.y = -Ep / (2.0 * Cp); |
| | } else if (std::abs(Ep) > RS_TOLERANCE) { |
| | return false; |
| | } |
| |
|
| | |
| | double Fp = LC_Quadratic{{A, B, C, D, E, F}}.evaluateAt(center); |
| |
|
| | |
| | |
| | double denom = -Fp; |
| | if (std::abs(denom) < RS_TOLERANCE) |
| | return false; |
| |
|
| | double coeff_x = Ap / denom; |
| | double coeff_y = Cp / denom; |
| |
|
| | double a2 = 0., b2 = 0.; |
| | bool transverse_x = (coeff_x > 0.0); |
| |
|
| | if (transverse_x) { |
| | if (coeff_y >= 0.0) |
| | return false; |
| | a2 = 1.0 / coeff_x; |
| | b2 = -1.0 / coeff_y; |
| | } else { |
| | if (coeff_x >= 0.0) |
| | return false; |
| | a2 = 1.0 / coeff_y; |
| | b2 = -1.0 / coeff_x; |
| | } |
| |
|
| | if (a2 <= RS_TOLERANCE || b2 <= RS_TOLERANCE) |
| | return false; |
| |
|
| | double a = std::sqrt(a2); |
| | double ratio = std::sqrt(b2 / a2); |
| |
|
| | |
| | |
| | RS_Vector major_dir = transverse_x ? RS_Vector(ct, st) : RS_Vector(-st, ct); |
| |
|
| | |
| | RS_Vector vertex = center + major_dir * a; |
| | double sign_at_vertex = q.evaluateAt(vertex); |
| | bool reversed = (sign_at_vertex < 0.0); |
| |
|
| | |
| | if (reversed) { |
| | major_dir = -major_dir; |
| | } |
| |
|
| | |
| | data.center = center; |
| | data.majorP = major_dir * a; |
| | data.ratio = ratio; |
| | data.reversed = reversed; |
| | data.angle1 = 0.0; |
| | data.angle2 = 0.0; |
| |
|
| | m_bValid = true; |
| | LC_Hyperbola::calculateBorders(); |
| | LC_Hyperbola::updateLength(); |
| |
|
| | return true; |
| | } |
| |
|
| | |
| | bool LC_Hyperbola::createFromQuadratic(const std::vector<double> &coeffs) { |
| | if (coeffs.size() < 6) |
| | return false; |
| | LC_Quadratic q(coeffs); |
| | return createFromQuadratic(q); |
| | } |
| |
|
| | |
| | |
| | |
| | RS_Entity *LC_Hyperbola::clone() const { |
| | return new LC_Hyperbola(*this); |
| | } |
| |
|
| | RS_VectorSolutions LC_Hyperbola::getFoci() const { |
| | double e = std::sqrt(1.0 + data.ratio * data.ratio); |
| | RS_Vector vp = data.majorP * e; |
| | RS_VectorSolutions sol; |
| | sol.push_back(data.center + vp); |
| | sol.push_back(data.center - vp); |
| | return sol; |
| | } |
| |
|
| | RS_VectorSolutions LC_Hyperbola::getRefPoints() const { |
| | RS_VectorSolutions sol; |
| |
|
| | if (!m_bValid) { |
| | return sol; |
| | } |
| |
|
| | |
| | sol.push_back(data.center); |
| |
|
| | |
| | RS_Vector primaryVertex = getPrimaryVertex(); |
| | if (primaryVertex.valid) { |
| | sol.push_back(primaryVertex); |
| | } |
| |
|
| | |
| | RS_Vector f1 = data.getFocus1(); |
| | RS_Vector f2 = data.getFocus2(); |
| | if (f1.valid) |
| | sol.push_back(f1); |
| | if (f2.valid) |
| | sol.push_back(f2); |
| |
|
| | |
| | if (std::abs(data.angle1) >= RS_TOLERANCE || |
| | std::abs(data.angle2) >= RS_TOLERANCE) { |
| | RS_Vector start = getStartpoint(); |
| | RS_Vector end = getEndpoint(); |
| |
|
| | if (start.valid) |
| | sol.push_back(start); |
| | if (end.valid) |
| | sol.push_back(end); |
| | } |
| |
|
| | return sol; |
| | } |
| |
|
| | |
| | RS_Vector LC_Hyperbola::getStartpoint() const { |
| | if (data.angle1 == 0.0 && data.angle2 == 0.0) |
| | return RS_Vector(false); |
| | return getPoint(data.angle1, data.reversed); |
| | } |
| |
|
| | |
| | RS_Vector LC_Hyperbola::getEndpoint() const { |
| | if (data.angle1 == 0.0 && data.angle2 == 0.0) |
| | return RS_Vector(false); |
| | return getPoint(data.angle2, data.reversed); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | RS_Vector LC_Hyperbola::getMiddlePoint() const |
| | { |
| | if (!m_bValid) { |
| | return RS_Vector(false); |
| | } |
| |
|
| | |
| | if (std::abs(data.angle1) < RS_TOLERANCE && std::abs(data.angle2) < RS_TOLERANCE) { |
| | return RS_Vector(false); |
| | } |
| |
|
| | double totalLength = getLength(); |
| | if (std::isinf(totalLength) || totalLength <= 0.0) { |
| | return RS_Vector(false); |
| | } |
| |
|
| | |
| | |
| | return getNearestDist(totalLength * 0.5, data.center); |
| | } |
| |
|
| | |
| | |
| | |
| | double LC_Hyperbola::getDirection1() const { |
| | RS_Vector p = getStartpoint(); |
| | if (!p.valid) |
| | return 0.0; |
| | return getTangentDirection(p).angle(); |
| | } |
| |
|
| | double LC_Hyperbola::getDirection2() const { |
| | RS_Vector p = getEndpoint(); |
| | if (!p.valid) |
| | return 0.0; |
| | return getTangentDirection(p).angle(); |
| | } |
| |
|
| | |
| | RS_Vector LC_Hyperbola::getTangentDirectionParam(double parameter) const { |
| | double a = getMajorRadius(); |
| | double b = getMinorRadius(); |
| |
|
| | double dx = a * std::sinh(parameter); |
| | double dy = b * std::cosh(parameter); |
| | if (data.reversed) |
| | dx = -dx; |
| |
|
| | RS_Vector tangent{dx, dy}; |
| | tangent.rotate(data.majorP.angle()); |
| | return tangent.normalized(); |
| | } |
| |
|
| | RS_Vector LC_Hyperbola::getTangentDirection(const RS_Vector &point) const { |
| | double phi = getParamFromPoint(point, data.reversed); |
| | return getTangentDirectionParam(phi); |
| | } |
| |
|
| | |
| | RS_VectorSolutions LC_Hyperbola::getTangentPoint(const RS_Vector &point) const { |
| | if (!m_bValid || !point.valid) |
| | return RS_VectorSolutions(); |
| |
|
| | LC_Quadratic hyper = getQuadratic(); |
| | if (!hyper.isValid()) |
| | return RS_VectorSolutions(); |
| |
|
| | std::vector<double> coef = hyper.getCoefficients(); |
| | double A = coef[0], B = coef[1], C = coef[2]; |
| | double D = coef[3], E = coef[4], F = coef[5]; |
| |
|
| | double px = point.x, py = point.y; |
| |
|
| | double polarA = A * px + (B / 2.0) * py + D / 2.0; |
| | double polarB = (B / 2.0) * px + C * py + E / 2.0; |
| | double polarK = D / 2.0 * px + E / 2.0 * py + F; |
| |
|
| | if (std::abs(polarA) < RS_TOLERANCE && std::abs(polarB) < RS_TOLERANCE) { |
| | return RS_VectorSolutions(); |
| | } |
| |
|
| | RS_Vector p1, p2; |
| | if (std::abs(polarA) >= std::abs(polarB)) { |
| | p1 = RS_Vector(0.0, -polarK / polarB); |
| | p2 = RS_Vector(1.0, (-polarK - polarA) / polarB); |
| | } else { |
| | p1 = RS_Vector(-polarK / polarA, 0.0); |
| | p2 = RS_Vector((-polarK - polarB) / polarA, 1.0); |
| | } |
| |
|
| | RS_Line polar(nullptr, RS_LineData(p1, p2)); |
| |
|
| | RS_VectorSolutions sol = |
| | LC_Quadratic::getIntersection(hyper, polar.getQuadratic()); |
| |
|
| | RS_VectorSolutions tangents; |
| | for (size_t i = 0; i < sol.getNumber(); ++i) { |
| | RS_Vector tp = sol.get(i); |
| | if (!tp.valid) |
| | continue; |
| |
|
| | RS_Vector radius = tp - point; |
| | RS_Vector tangentDir = getTangentDirection(tp); |
| | if (tangentDir.valid && |
| | std::abs(RS_Vector::dotP(radius, tangentDir)) < RS_TOLERANCE * 10.0) { |
| | tangents.push_back(tp); |
| | } |
| | } |
| |
|
| | return tangents; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | RS_Vector LC_Hyperbola::getPoint(double phi, bool useReversed) const { |
| | const double a = getMajorRadius(); |
| | const double b = getMinorRadius(); |
| | if (a < RS_TOLERANCE || b < RS_TOLERANCE) |
| | return RS_Vector(false); |
| |
|
| | double ch = std::cosh(phi); |
| | double sh = std::sinh(phi); |
| |
|
| | RS_Vector local(useReversed ? -a * ch : a * ch, b * sh); |
| |
|
| | return localToWorld(local); |
| | } |
| |
|
| | |
| | RS_Vector LC_Hyperbola::worldToLocal(const RS_Vector& world) const |
| | { |
| | RS_Vector local = (world - getCenter()).rotated(- getAngle()); |
| | return local; |
| | } |
| |
|
| | |
| | RS_Vector LC_Hyperbola::localToWorld(const RS_Vector& local) const |
| | { |
| | return local.rotated(getAngle()) + getCenter(); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | double LC_Hyperbola::getParamFromPoint(const RS_Vector& p, |
| | bool ) const |
| | { |
| | if (!m_bValid || !p.valid) { |
| | return std::numeric_limits<double>::quiet_NaN(); |
| | } |
| |
|
| | const double a = getMajorRadius(); |
| | const double b = getMinorRadius(); |
| |
|
| | if (a < RS_TOLERANCE || b < RS_TOLERANCE) { |
| | return std::numeric_limits<double>::quiet_NaN(); |
| | } |
| |
|
| | |
| | |
| | |
| | RS_Vector local = p - data.center; |
| | local.rotate(-data.majorP.angle()); |
| |
|
| | |
| | double y_local = local.y; |
| |
|
| | |
| | double sinh_phi = y_local / b; |
| | double phi = std::asinh(sinh_phi); |
| |
|
| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| |
|
| | |
| | |
| |
|
| | |
| | |
| |
|
| | |
| | |
| | |
| | |
| |
|
| | |
| | |
| | return phi; |
| | } |
| |
|
| | |
| | bool LC_Hyperbola::isInClipRect(const RS_Vector &p, const LC_Rect& rect) const { |
| | return p.valid && rect.inArea(p); |
| | } |
| |
|
| | |
| | |
| | |
| |
|
| | void LC_Hyperbola::draw(RS_Painter *painter) { |
| | if (!painter || !isValid()) |
| | return; |
| |
|
| | const LC_Rect &clip = painter->getWcsBoundingRect(); |
| | if (clip.isEmpty(RS_TOLERANCE)) |
| | return; |
| |
|
| | double xmin = clip.minP().x, xmax = clip.maxP().x; |
| | double ymin = clip.minP().y, ymax = clip.maxP().y; |
| |
|
| | double a = getMajorRadius(), b = getMinorRadius(); |
| | if (a < RS_TOLERANCE || b < RS_TOLERANCE) |
| | return; |
| |
|
| | painter->save(); |
| | std::shared_ptr<double> painterRestore{&a, [painter](void*) { |
| | painter->restore(); }}; |
| |
|
| | double guiPixel = std::min(painter->toGuiDX(1.0), painter->toGuiDY(1.0)); |
| | double maxWorldError = 1.0 / guiPixel; |
| |
|
| | std::vector<RS_Vector> pts; |
| | pts.reserve(300); |
| |
|
| | bool isFull = (data.angle1 == 0.0 && data.angle2 == 0.0); |
| |
|
| | auto processBranch = [&, painter](bool rev) { |
| | std::vector<double> params; |
| |
|
| | RS_Line borders[4] = { |
| | RS_Line(nullptr, |
| | RS_LineData(RS_Vector(xmin, ymin), RS_Vector(xmax, ymin))), |
| | RS_Line(nullptr, |
| | RS_LineData(RS_Vector(xmax, ymin), RS_Vector(xmax, ymax))), |
| | RS_Line(nullptr, |
| | RS_LineData(RS_Vector(xmax, ymax), RS_Vector(xmin, ymax))), |
| | RS_Line(nullptr, |
| | RS_LineData(RS_Vector(xmin, ymax), RS_Vector(xmin, ymin)))}; |
| |
|
| | for (const auto &line : borders) { |
| | RS_VectorSolutions sol = |
| | LC_Quadratic::getIntersection(getQuadratic(), line.getQuadratic()); |
| | for (const RS_Vector &intersection : sol) { |
| | double phiCur = getParamFromPoint(intersection); |
| | if (std::isnan(phiCur) || phiCur < data.angle1 || phiCur > data.angle2) |
| | continue; |
| | if (isInClipRect(intersection, clip)) { |
| | params.push_back(phiCur); |
| | } |
| | } |
| | } |
| |
|
| | if (params.empty()) { |
| | RS_Vector test = getPoint((data.angle1 + data.angle2) * 0.5, rev); |
| | if (test.valid && isInClipRect(test, clip)) { |
| | params = {data.angle1, data.angle2}; |
| | } else { |
| | return; |
| | } |
| | } else { |
| | params.push_back(data.angle1); |
| | params.push_back(data.angle2); |
| | std::sort(params.begin(), params.end()); |
| | auto last = |
| | std::unique(params.begin(), params.end(), [](double a, double b) { |
| | return std::abs(a - b) < RS_TOLERANCE_ANGLE; |
| | }); |
| | params.erase(last, params.end()); |
| | } |
| |
|
| | for (size_t i = 0; i + 1 < params.size(); ++i) { |
| | double start = params[i]; |
| | double end = params[i + 1]; |
| | RS_Vector middle = getPoint((start + end) * 0.5, rev); |
| | pts.clear(); |
| | if (isInClipRect(middle, clip)) { |
| | adaptiveSample(pts, start, end, rev, maxWorldError); |
| | painter->drawSplinePointsWCS(pts, false); |
| | } |
| | } |
| | }; |
| |
|
| | if (isFull) { |
| | processBranch(false); |
| | processBranch(true); |
| | } else { |
| | processBranch(data.reversed); |
| | } |
| | } |
| |
|
| | |
| | void LC_Hyperbola::adaptiveSample(std::vector<RS_Vector> &out, double phiStart, |
| | double phiEnd, bool rev, |
| | double maxError) const { |
| | if (phiStart > phiEnd) |
| | std::swap(phiStart, phiEnd); |
| |
|
| | std::vector<std::pair<double, RS_Vector>> points; |
| | points.reserve(256); |
| |
|
| | std::function<void(double, double)> subdiv = [&](double pa, double pb) { |
| | RS_Vector A = getPoint(pa, rev); |
| | RS_Vector B = getPoint(pb, rev); |
| | if (!A.valid || !B.valid) |
| | return; |
| |
|
| | double pm = (pa + pb) * 0.5; |
| | RS_Vector M = getPoint(pm, rev); |
| | if (!M.valid) |
| | return; |
| |
|
| | double sagitta = (M - (A + B) * 0.5).magnitude(); |
| | double estimatedMaxError = sagitta * 1.15; |
| |
|
| | if (estimatedMaxError < maxError || (pb - pa) < 0.05) { |
| | points.emplace_back(pa, A); |
| | points.emplace_back(pb, B); |
| | return; |
| | } |
| |
|
| | subdiv(pa, pm); |
| | subdiv(pm, pb); |
| | }; |
| |
|
| | RS_Vector first = getPoint(phiStart, rev); |
| | if (first.valid) |
| | points.emplace_back(phiStart, first); |
| |
|
| | subdiv(phiStart, phiEnd); |
| |
|
| | std::sort(points.begin(), points.end(), |
| | [](const auto &a, const auto &b) { return a.first < b.first; }); |
| |
|
| | out.reserve(out.size() + points.size()); |
| | for (const auto &kv : points) { |
| | if (out.empty() || out.back().distanceTo(kv.second) > RS_TOLERANCE) { |
| | out.push_back(kv.second); |
| | } |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | RS_Vector LC_Hyperbola::getNearestMiddle(const RS_Vector& coord, |
| | double* dist, |
| | int middlePoints) const |
| | { |
| | Q_UNUSED(middlePoints); |
| |
|
| | if (!m_bValid) { |
| | if (dist) *dist = RS_MAXDOUBLE; |
| | return RS_Vector(false); |
| | } |
| |
|
| | |
| | if (std::abs(data.angle1) < RS_TOLERANCE && std::abs(data.angle2) < RS_TOLERANCE) { |
| | if (dist) *dist = RS_MAXDOUBLE; |
| | return RS_Vector(false); |
| | } |
| |
|
| | double totalLength = getLength(); |
| | if (std::isinf(totalLength) || totalLength <= 0.0) { |
| | if (dist) *dist = RS_MAXDOUBLE; |
| | return RS_Vector(false); |
| | } |
| |
|
| | |
| | double middleStart = totalLength * 0.25; |
| | double middleEnd = totalLength * 0.75; |
| |
|
| | |
| | RS_Vector nearest = getNearestPointOnEntity(coord, true); |
| | if (!nearest.valid) { |
| | if (dist) *dist = RS_MAXDOUBLE; |
| | return RS_Vector(false); |
| | } |
| |
|
| | |
| | double phi_nearest = getParamFromPoint(nearest, data.reversed); |
| | if (std::isnan(phi_nearest)) { |
| | if (dist) *dist = RS_MAXDOUBLE; |
| | return RS_Vector(false); |
| | } |
| |
|
| | double arcToNearest = std::abs(getArcLength(data.angle1, phi_nearest)); |
| |
|
| | double targetArcFromStart; |
| | if (arcToNearest >= middleStart && arcToNearest <= middleEnd) { |
| | |
| | targetArcFromStart = arcToNearest; |
| | } else if (arcToNearest < middleStart) { |
| | |
| | targetArcFromStart = middleStart; |
| | } else { |
| | |
| | targetArcFromStart = middleEnd; |
| | } |
| |
|
| | |
| | |
| | RS_Vector middlePoint = getNearestDist(targetArcFromStart, data.center); |
| |
|
| | if (dist) { |
| | *dist = coord.distanceTo(middlePoint); |
| | } |
| |
|
| | return middlePoint; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | RS_Vector LC_Hyperbola::getNearestOrthTan(const RS_Vector& , |
| | const RS_Line& normal, |
| | bool onEntity) const |
| | { |
| | if (!m_bValid) |
| | return RS_Vector(false); |
| |
|
| | RS_Vector n{normal.getDirection1()}; |
| |
|
| | n = n.normalized(); |
| |
|
| | double cos_th = std::cos(data.majorP.angle()); |
| | double sin_th = std::sin(data.majorP.angle()); |
| | double a = getMajorRadius(); |
| | double b = getMinorRadius(); |
| | int sign_x = data.reversed ? -1 : 1; |
| |
|
| | double K = sign_x * a * (cos_th * n.x + sin_th * n.y); |
| | double M = b * (-sin_th * n.x + cos_th * n.y); |
| |
|
| | if (std::abs(K) < RS_TOLERANCE) return RS_Vector(false); |
| |
|
| | double tanh_phi = - M / K; |
| |
|
| | if (std::abs(tanh_phi) >= 1.0 - RS_TOLERANCE) return RS_Vector(false); |
| |
|
| | double phi = std::atanh(tanh_phi); |
| |
|
| | if (onEntity && !isInfinite()) { |
| | double phi_min = std::min(data.angle1, data.angle2); |
| | double phi_max = std::max(data.angle1, data.angle2); |
| | if (phi < phi_min - RS_TOLERANCE || phi > phi_max + RS_TOLERANCE) return RS_Vector(false); |
| | } |
| |
|
| | return getPoint(phi, data.reversed); |
| | } |
| |
|
| | bool LC_Hyperbola::isInfinite() const |
| | { |
| | return RS_Math::equal(data.angle1, 0.) && |
| | RS_Math::equal(data.angle2, 0.); |
| | } |
| |
|
| |
|
| | |
| | double LC_Hyperbola::getArcLength(double phi1, double phi2) const { |
| | if (!m_bValid) |
| | return 0.0; |
| |
|
| | if (isInfinite()) |
| | return RS_MAXDOUBLE; |
| |
|
| | bool forward = phi2 > phi1; |
| | double p_min = std::min(phi1, phi2); |
| | double p_max = std::max(phi1, phi2); |
| |
|
| | double a = getMajorRadius(); |
| | double ecc = getEccentricity(); |
| | double ecc2 = ecc * ecc; |
| |
|
| | auto integrand = [a, ecc2](double phi) -> double { |
| | double ch = std::cosh(phi); |
| | double inner = std::max(0., ecc2 * ch * ch - 1.0); |
| | return a * std::sqrt(inner); |
| | }; |
| |
|
| | double result = 0.0; |
| | double abs_error = 0.0; |
| |
|
| | |
| | if (p_min < 0.0 && p_max > 0.0) { |
| | double part1 = |
| | boost::math::quadrature::gauss_kronrod<double, 61>::integrate( |
| | integrand, p_min, 0.0, 0, 1e-12, &abs_error); |
| | double part2 = |
| | boost::math::quadrature::gauss_kronrod<double, 61>::integrate( |
| | integrand, 0.0, p_max, 0, 1e-12, &abs_error); |
| | result = part1 + part2; |
| | } else { |
| | result = boost::math::quadrature::gauss_kronrod<double, 61>::integrate( |
| | integrand, p_min, p_max, 0, 1e-12, &abs_error); |
| | } |
| |
|
| | return forward ? result : -result; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | RS_Vector LC_Hyperbola::getNearestDist(double distance, |
| | const RS_Vector& coord, |
| | double* dist) const |
| | { |
| | if (!m_bValid || isInfinite()) return RS_Vector(false); |
| |
|
| | double totalLength = getLength(); |
| | if (totalLength <= std::abs(distance)) |
| | return RS_Vector(false); |
| |
|
| | double phi0 = getParamFromPoint(coord, data.reversed); |
| | const bool fromStart = std::abs(phi0 - data.angle1) <= std::abs(phi0 - data.angle2); |
| |
|
| | double targetArcFromStart = fromStart ? distance : totalLength - distance; |
| |
|
| | if (distance < 0.0 || targetArcFromStart < 0.0 || targetArcFromStart > totalLength + RS_TOLERANCE) |
| | return RS_Vector(false); |
| |
|
| | if (dist) |
| | *dist = targetArcFromStart; |
| |
|
| | double a = getMajorRadius(); |
| | double ecc2 = getEccentricity() * getEccentricity(); |
| |
|
| | |
| |
|
| | using std::asinh, std::cosh, std::sinh; |
| | double phi = asinh(sinh(data.angle1) + targetArcFromStart / totalLength * (sinh(data.angle2) - sinh(data.angle1))); |
| | if (std::isnan(phi)) |
| | phi = data.angle1; |
| |
|
| | constexpr int maxIter = 30; |
| | constexpr double tol = 1e-12; |
| |
|
| | bool converged = false; |
| | for (int i = 0; i < maxIter; ++i) { |
| | double s = getArcLength(data.angle1, phi); |
| | double ds_dphi_current = a * std::sqrt(ecc2 * cosh(phi) * cosh(phi) - 1.0); |
| | if (ds_dphi_current < RS_TOLERANCE) break; |
| |
|
| | double residual = targetArcFromStart - s; |
| | double delta = residual / ds_dphi_current; |
| | phi += delta; |
| |
|
| | LC_LOG<<__func__<<"(): "<<i<<": phi="<<phi<<", "<<s<<"("<<targetArcFromStart<<"): "<<residual; |
| | if (std::abs(delta) < tol) { |
| | converged = true; |
| | break; |
| | } |
| | } |
| |
|
| | |
| | if (!converged) { |
| | double phiLow = std::min(data.angle1, data.angle2) - 30.0; |
| | double phiHigh = std::max(data.angle1, data.angle2) + 30.0; |
| |
|
| | double sLow = getArcLength(data.angle1, phiLow); |
| | double sHigh = getArcLength(data.angle1, phiHigh); |
| |
|
| | while (sLow > targetArcFromStart) { phiLow -= 30.0; sLow = getArcLength(data.angle1, phiLow); } |
| | while (sHigh < targetArcFromStart) { phiHigh += 30.0; sHigh = getArcLength(data.angle1, phiHigh); } |
| |
|
| | for (int i = 0; i < 80; ++i) { |
| | phi = 0.5 * (phiLow + phiHigh); |
| | double s = getArcLength(data.angle1, phi); |
| | if (std::abs(s - targetArcFromStart) < 1e-9) break; |
| |
|
| | if (s < targetArcFromStart) phiLow = phi; |
| | else phiHigh = phi; |
| | } |
| | } |
| |
|
| | return getPoint(phi, data.reversed); |
| | } |
| |
|
| | |
| | |
| | |
| |
|
| | void LC_Hyperbola::move(const RS_Vector &offset) { |
| | data.center += offset; |
| | } |
| |
|
| | void LC_Hyperbola::rotate(const RS_Vector ¢er, double angle) { |
| | rotate(center, RS_Vector{angle}); |
| | } |
| |
|
| | void LC_Hyperbola::rotate(const RS_Vector ¢er, |
| | const RS_Vector &angleVector) { |
| | data.center.rotate(center, angleVector); |
| | data.majorP.rotate(angleVector); |
| | } |
| |
|
| | void LC_Hyperbola::scale(const RS_Vector ¢er, const RS_Vector &factor) { |
| | data.center.scale(center, factor); |
| | RS_VectorSolutions foci = getFoci(); |
| | RS_Vector vpStart = getStartpoint(); |
| | RS_Vector vpEnd = getEndpoint(); |
| | foci.scale(center, factor); |
| | vpStart.scale(center, factor); |
| | vpEnd.scale(center, factor); |
| | *this = LC_Hyperbola{foci[0], foci[1], vpStart}; |
| | data.angle1 = getParamFromPoint(vpStart); |
| | data.angle2 = getParamFromPoint(vpEnd); |
| | if (data.angle1 > data.angle2) |
| | std::swap(data.angle1, data.angle2); |
| | } |
| |
|
| | void LC_Hyperbola::mirror(const RS_Vector &axisPoint1, |
| | const RS_Vector &axisPoint2) { |
| | if (axisPoint1 == axisPoint2) |
| | return; |
| | RS_Vector vpStart = getStartpoint(); |
| | RS_Vector vpEnd = getEndpoint(); |
| | auto mirrorFunc = [&axisPoint1, &axisPoint2](RS_Vector& vp) { |
| | return vp.mirror(axisPoint1, axisPoint2); |
| | }; |
| | mirrorFunc(data.center); |
| | data.majorP.mirror(RS_Vector(0, 0), axisPoint2 - axisPoint1); |
| | |
| | data.angle2 = getParamFromPoint(mirrorFunc(vpStart)); |
| | data.angle1 = getParamFromPoint(mirrorFunc(vpEnd)); |
| | if (data.angle1 > data.angle2) |
| | std::swap(data.angle1, data.angle2); |
| |
|
| | LC_Hyperbola::calculateBorders(); |
| | } |
| |
|
| | |
| | |
| | |
| | RS_Vector LC_Hyperbola::getNearestEndpoint(const RS_Vector &coord, |
| | double *dist) const { |
| | if (dist) |
| | *dist = RS_MAXDOUBLE; |
| | if (!m_bValid || !coord.valid) { |
| | return RS_Vector(false); |
| | } |
| |
|
| | |
| | if (!std::isnormal(data.angle1) && !std::isnormal(data.angle2)) { |
| | return RS_Vector(false); |
| | } |
| |
|
| | double distance = RS_MAXDOUBLE; |
| | RS_Vector ret{false}; |
| | for (const RS_Vector &vp : {getStartpoint(), getEndpoint()}) { |
| | if (vp.valid) { |
| | double dvp = vp.distanceTo(coord); |
| | if (dvp <= distance - RS_TOLERANCE) { |
| | distance = dvp; |
| | ret = vp; |
| | } |
| | } |
| | } |
| | if (dist != nullptr) |
| | *dist = distance; |
| | return ret; |
| | } |
| |
|
| | |
| | RS_Vector LC_Hyperbola::getNearestPointOnEntity(const RS_Vector &coord, |
| | bool onEntity, double *dist, |
| | RS_Entity **entity) const { |
| | if (!m_bValid || !coord.valid) { |
| | if (dist) |
| | *dist = RS_MAXDOUBLE; |
| | return RS_Vector(false); |
| | } |
| |
|
| | if (entity) |
| | *entity = const_cast<LC_Hyperbola *>(this); |
| |
|
| | |
| | if (std::abs(data.angle1) < RS_TOLERANCE && |
| | std::abs(data.angle2) < RS_TOLERANCE) { |
| | |
| | |
| | RS_Vector vertex = data.center + data.majorP; |
| | double dVertex = coord.distanceTo(vertex); |
| |
|
| | |
| | |
| | RS_Vector dir = (coord - data.center).normalized(); |
| | double dot = dir.angleTo(data.majorP.normalized()); |
| |
|
| | if (std::abs(dot) < RS_TOLERANCE_ANGLE || |
| | std::abs(dot - M_PI) < RS_TOLERANCE_ANGLE) { |
| | |
| | if (dist) |
| | *dist = dVertex; |
| | return vertex; |
| | } else { |
| | |
| | if (dist) |
| | *dist = dVertex; |
| | return vertex; |
| | } |
| | } |
| |
|
| | |
| |
|
| | |
| | double phiGuess = getParamFromPoint(coord, data.reversed); |
| | if (std::isnan(phiGuess)) { |
| | phiGuess = (data.angle1 + data.angle2) * 0.5; |
| | } |
| |
|
| | |
| | double phiMin = std::min(data.angle1, data.angle2); |
| | double phiMax = std::max(data.angle1, data.angle2); |
| | phiGuess = std::max(phiMin, std::min(phiMax, phiGuess)); |
| |
|
| | |
| | RS_Vector pStart = getPoint(data.angle1, data.reversed); |
| | RS_Vector pEnd = getPoint(data.angle2, data.reversed); |
| | RS_Vector pGuess = getPoint(phiGuess, data.reversed); |
| |
|
| | double d2Start = coord.squaredTo(pStart); |
| | double d2End = coord.squaredTo(pEnd); |
| | double d2Guess = coord.squaredTo(pGuess); |
| |
|
| | double minD2 = std::min({d2Start, d2End, d2Guess}); |
| | RS_Vector nearest = |
| | (minD2 == d2Start) ? pStart : (minD2 == d2End ? pEnd : pGuess); |
| |
|
| | |
| | |
| | |
| |
|
| | double px = coord.x, py = coord.y; |
| | double cx = data.center.x, cy = data.center.y; |
| | double aa = data.majorP.magnitude(); |
| | double bb = aa * data.ratio; |
| | double ct = std::cos(data.majorP.angle()); |
| | double st = std::sin(data.majorP.angle()); |
| |
|
| | double A = aa * ct; |
| | double B = -bb * st; |
| | double C = aa * st; |
| | double D = bb * ct; |
| |
|
| | |
| | double dx = cx + A - px; |
| | double dy = cy + C - py; |
| |
|
| | double p = 4.0 * (A * dx + C * dy) / (B * dx + D * dy); |
| | double q = (dx * dx + dy * dy - aa * aa + bb * bb) / (B * dx + D * dy) * 2.0 - |
| | p * p / 2.0 - 3.0; |
| | double r = -p * (q + 5.0); |
| | double s = -(dx * dx + dy * dy - aa * aa - bb * bb) / (B * dx + D * dy) - q; |
| |
|
| | std::vector<double> ce = {s, r, q, p, |
| | 1.0}; |
| |
|
| | std::vector<double> roots = RS_Math::quarticSolverFull(ce); |
| |
|
| | |
| | for (double t : roots) { |
| | if (std::abs(B * dx + D * dy) < RS_TOLERANCE) |
| | continue; |
| |
|
| | double phi = std::atanh(t); |
| | if (std::isnan(phi) || std::isinf(phi)) |
| | continue; |
| |
|
| | |
| | bool inRange = (phi >= phiMin - RS_TOLERANCE_ANGLE && |
| | phi <= phiMax + RS_TOLERANCE_ANGLE); |
| |
|
| | if (onEntity && !inRange) |
| | continue; |
| |
|
| | RS_Vector cand = getPoint(phi, data.reversed); |
| | if (!cand.valid) |
| | continue; |
| |
|
| | double d2Cand = coord.squaredTo(cand); |
| |
|
| | if (onEntity) { |
| | |
| | if (!inRange) { |
| | double d2StartNew = coord.squaredTo(pStart); |
| | double d2EndNew = coord.squaredTo(pEnd); |
| | if (d2StartNew < minD2) { |
| | minD2 = d2StartNew; |
| | nearest = pStart; |
| | } |
| | if (d2EndNew < minD2) { |
| | minD2 = d2EndNew; |
| | nearest = pEnd; |
| | } |
| | continue; |
| | } |
| | } |
| |
|
| | if (d2Cand < minD2 - RS_TOLERANCE) { |
| | minD2 = d2Cand; |
| | nearest = cand; |
| | } |
| | } |
| |
|
| | |
| | if (onEntity) { |
| | if (coord.squaredTo(pStart) < minD2) { |
| | minD2 = coord.squaredTo(pStart); |
| | nearest = pStart; |
| | } |
| | if (coord.squaredTo(pEnd) < minD2) { |
| | minD2 = coord.squaredTo(pEnd); |
| | nearest = pEnd; |
| | } |
| | } |
| |
|
| | if (dist) |
| | *dist = std::sqrt(minD2); |
| | return nearest; |
| | } |
| |
|
| | |
| | double LC_Hyperbola::getDistanceToPoint(const RS_Vector &coord, |
| | RS_Entity **entity, |
| | RS2::ResolveLevel , |
| | double ) const { |
| | if (entity) |
| | *entity = nullptr; |
| |
|
| | if (!m_bValid || !coord.valid) { |
| | return RS_MAXDOUBLE; |
| | } |
| |
|
| | double dist = RS_MAXDOUBLE; |
| | getNearestPointOnEntity(coord, true, &dist, entity); |
| |
|
| | if (entity && *entity == nullptr && dist < RS_MAXDOUBLE) { |
| | *entity = const_cast<LC_Hyperbola *>(this); |
| | } |
| |
|
| | return dist; |
| | } |
| |
|
| | |
| | bool LC_Hyperbola::isPointOnEntity(const RS_Vector &coord, |
| | double tolerance) const { |
| | if (!m_bValid || !coord.valid) |
| | return false; |
| |
|
| | double dist = RS_MAXDOUBLE; |
| | getNearestPointOnEntity(coord, true, &dist); |
| | return dist <= tolerance; |
| | } |
| |
|
| | |
| | LC_Quadratic LC_Hyperbola::getQuadratic() const { |
| | std::vector<double> ce(6, 0.); |
| | ce[0] = data.majorP.squared(); |
| | ce[2] = -data.ratio * data.ratio * ce[0]; |
| | if (ce[0] < RS_TOLERANCE2 && std::abs(ce[2]) < RS_TOLERANCE2) { |
| | return LC_Quadratic(); |
| | } |
| | ce[0] = 1. / ce[0]; |
| | ce[2] = 1. / ce[2]; |
| | ce[5] = -1.; |
| | LC_Quadratic ret(ce); |
| | ret.rotate(getAngle()); |
| | ret.move(data.center); |
| | return ret; |
| | } |
| |
|
| | |
| | void LC_Hyperbola::calculateBorders() { |
| | minV = RS_Vector(RS_MAXDOUBLE, RS_MAXDOUBLE); |
| | maxV = RS_Vector(RS_MINDOUBLE, RS_MINDOUBLE); |
| |
|
| | if (!m_bValid) |
| | return; |
| |
|
| | |
| | if (data.angle1 == 0.0 && data.angle2 == 0.0) { |
| | minV = RS_Vector(-RS_MAXDOUBLE, -RS_MAXDOUBLE); |
| | maxV = RS_Vector(RS_MAXDOUBLE, RS_MAXDOUBLE); |
| | return; |
| | } |
| |
|
| | |
| | double phiStart = data.angle1; |
| | double phiEnd = data.angle2; |
| |
|
| | |
| | |
| | if (phiStart > phiEnd) |
| | std::swap(phiStart, phiEnd); |
| |
|
| | |
| |
|
| | |
| | double rot = getAngle(); |
| | RS_Vector dirX(cos(rot), sin(rot)); |
| | RS_Vector dirY(-sin(rot), cos(rot)); |
| |
|
| | auto addExtrema = [&](const RS_Vector &dir) { |
| | double dx = dir.x, dy = dir.y; |
| | if (std::abs(dx) < RS_TOLERANCE && std::abs(dy) < RS_TOLERANCE) |
| | return; |
| |
|
| | double tanh_phi = -(getMinorRadius() * dy) / (getMajorRadius() * dx); |
| | if (std::abs(tanh_phi) >= 1.0) |
| | return; |
| |
|
| | double phi = std::atanh(tanh_phi); |
| | |
| | |
| | for (int sign = 0; sign < 2; ++sign) { |
| | double phi_cand = phi + sign * M_PI; |
| | if (phi_cand >= phiStart - RS_TOLERANCE && |
| | phi_cand <= phiEnd + RS_TOLERANCE) { |
| | RS_Vector p = getPoint(phi_cand, data.reversed); |
| | if (p.valid) { |
| | minV = RS_Vector::minimum(minV, p); |
| | maxV = RS_Vector::maximum(maxV, p); |
| | } |
| | } |
| | } |
| | }; |
| |
|
| | addExtrema(RS_Vector(1.0, 0.0)); |
| | addExtrema(RS_Vector(0.0, 1.0)); |
| |
|
| | |
| | RS_Vector start = getPoint(phiStart, data.reversed); |
| | RS_Vector end = getPoint(phiEnd, data.reversed); |
| | if (start.valid) { |
| | minV = RS_Vector::minimum(minV, start); |
| | maxV = RS_Vector::maximum(maxV, start); |
| | } |
| | if (end.valid) { |
| | minV = RS_Vector::minimum(minV, end); |
| | maxV = RS_Vector::maximum(maxV, end); |
| | } |
| |
|
| | |
| | double expand = RS_TOLERANCE * 100.0; |
| | minV -= RS_Vector(expand, expand); |
| | maxV += RS_Vector(expand, expand); |
| | } |
| |
|
| | |
| | double LC_Hyperbola::getLength() const { |
| | if (!m_bValid) |
| | return 0.0; |
| |
|
| | return getArcLength(data.angle1, data.angle2); |
| | } |
| |
|
| | void LC_Hyperbola::updateLength() { |
| | cachedLength = LC_Hyperbola::getLength(); |
| | } |
| |
|
| | |
| | void LC_Hyperbola::setFocus1(const RS_Vector &f1) { |
| | if (!f1.valid || !m_bValid) |
| | return; |
| |
|
| | RS_Vector f2 = data.getFocus2(); |
| | |
| | RS_Vector currentPoint = getPoint(0.0, data.reversed); |
| | if (!currentPoint.valid) { |
| | currentPoint = getPoint(0.0, !data.reversed); |
| | } |
| | if (!currentPoint.valid) |
| | return; |
| |
|
| | LC_HyperbolaData newData(f1, f2, currentPoint); |
| | if (newData.isValid()) { |
| | data = newData; |
| | m_bValid = true; |
| | calculateBorders(); |
| | updateLength(); |
| | } |
| | } |
| |
|
| | void LC_Hyperbola::setFocus2(const RS_Vector &f2) { |
| | if (!f2.valid || !m_bValid) |
| | return; |
| |
|
| | RS_Vector f1 = data.getFocus1(); |
| | RS_Vector currentPoint = getPoint(0.0, data.reversed); |
| | if (!currentPoint.valid) { |
| | currentPoint = getPoint(0.0, !data.reversed); |
| | } |
| | if (!currentPoint.valid) |
| | return; |
| |
|
| | LC_HyperbolaData newData(f1, f2, currentPoint); |
| | if (newData.isValid()) { |
| | data = newData; |
| | m_bValid = true; |
| | calculateBorders(); |
| | updateLength(); |
| | } |
| | } |
| |
|
| | void LC_Hyperbola::setPointOnCurve(const RS_Vector &p) { |
| | if (!p.valid || !m_bValid) |
| | return; |
| |
|
| | RS_Vector f1 = data.getFocus1(); |
| | RS_Vector f2 = data.getFocus2(); |
| |
|
| | LC_HyperbolaData newData(f1, f2, p); |
| | if (newData.isValid()) { |
| | data = newData; |
| | m_bValid = true; |
| | calculateBorders(); |
| | updateLength(); |
| | } |
| | } |
| |
|
| | |
| | void LC_Hyperbola::setRatio(double r) { |
| | if (r <= 0.0 || !m_bValid) |
| | return; |
| | data.ratio = r; |
| | calculateBorders(); |
| | updateLength(); |
| | } |
| |
|
| | void LC_Hyperbola::setMinorRadius(double b) { |
| | if (b <= 0.0 || !m_bValid) |
| | return; |
| | double a = getMajorRadius(); |
| | if (a >= RS_TOLERANCE) { |
| | data.ratio = b / a; |
| | calculateBorders(); |
| | updateLength(); |
| | } |
| | } |
| |
|
| | |
| | void LC_Hyperbola::setPrimaryVertex(const RS_Vector &v) { |
| | if (!v.valid || !m_bValid) |
| | return; |
| |
|
| | RS_Vector dir = data.majorP; |
| | if (dir.squared() < RS_TOLERANCE2) |
| | return; |
| | dir.normalize(); |
| |
|
| | RS_Vector expectedVertex = data.reversed |
| | ? data.center - dir * getMajorRadius() |
| | : data.center + dir * getMajorRadius(); |
| |
|
| | RS_Vector offset = v - expectedVertex; |
| | double distanceAlongAxis = offset.dotP(dir); |
| |
|
| | double newA = std::abs(getMajorRadius() + distanceAlongAxis); |
| | if (newA < RS_TOLERANCE) |
| | return; |
| |
|
| | |
| | data.majorP = dir * newA; |
| | if (data.reversed) |
| | data.majorP = -data.majorP; |
| |
|
| | calculateBorders(); |
| | updateLength(); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | void LC_Hyperbola::moveRef(const RS_Vector& ref, const RS_Vector& offset) |
| | { |
| | |
| | RS_Vector originalStart = getStartpoint(); |
| | RS_Vector originalEnd = getEndpoint(); |
| | bool hadBounds = originalStart.valid && originalEnd.valid; |
| |
|
| | RS_Vector newRef = ref + offset; |
| |
|
| | if (ref.distanceTo(data.center) < RS_TOLERANCE) { |
| | data.center = newRef; |
| | } |
| | else if (ref.distanceTo(getPrimaryVertex()) < RS_TOLERANCE) { |
| | RS_Vector dir = newRef - data.center; |
| | if (dir.magnitude() > RS_TOLERANCE) { |
| | data.majorP = dir.normalized() * getMajorRadius(); |
| | } |
| | } |
| | else if (!isInfinite()) { |
| | |
| | if (ref.distanceTo(originalStart) < RS_TOLERANCE) { |
| | double phi = getParamFromPoint(newRef, data.reversed); |
| | if (!std::isnan(phi)) { |
| | data.angle1 = phi; |
| | } |
| | } |
| | else if (ref.distanceTo(originalEnd) < RS_TOLERANCE) { |
| | double phi = getParamFromPoint(newRef, data.reversed); |
| | if (!std::isnan(phi)) { |
| | data.angle2 = phi; |
| | } |
| | } |
| | else { |
| | |
| | RS_Vector f1 = getFocus1(); |
| | RS_Vector f2 = getFocus2(); |
| |
|
| | RS_Vector fixedPoint = originalStart.valid ? originalStart : getMiddlePoint(); |
| | if (!fixedPoint.valid) fixedPoint = getPrimaryVertex(); |
| |
|
| | if (ref.distanceTo(f1) < RS_TOLERANCE) { |
| | LC_HyperbolaData newData(newRef, f2, fixedPoint); |
| | if (newData.majorP.squared() >= RS_TOLERANCE2) { |
| | data = newData; |
| | } |
| | } |
| | else if (ref.distanceTo(f2) < RS_TOLERANCE) { |
| | LC_HyperbolaData newData(f1, newRef, fixedPoint); |
| | if (newData.majorP.squared() >= RS_TOLERANCE2) { |
| | data = newData; |
| | } |
| | } |
| | else { |
| | return; |
| | } |
| |
|
| | |
| | if (hadBounds) { |
| | double phiStart = getParamFromPoint(originalStart, data.reversed); |
| | double phiEnd = getParamFromPoint(originalEnd, data.reversed); |
| |
|
| | if (!std::isnan(phiStart)) data.angle1 = phiStart; |
| | if (!std::isnan(phiEnd)) data.angle2 = phiEnd; |
| |
|
| | if (data.angle1 > data.angle2) std::swap(data.angle1, data.angle2); |
| | } |
| | } |
| | } |
| |
|
| | calculateBorders(); |
| | updateLength(); |
| | } |
| |
|
| | |
| | RS_Vector LC_Hyperbola::getPrimaryVertex() const { |
| | if (!m_bValid) { |
| | return RS_Vector(false); |
| | } |
| |
|
| | double a = getMajorRadius(); |
| | if (a < RS_TOLERANCE) { |
| | return RS_Vector(false); |
| | } |
| |
|
| | |
| | |
| | RS_Vector vertex = data.center + data.majorP; |
| |
|
| | if (data.reversed) { |
| | |
| | vertex = data.center - data.majorP; |
| | } |
| |
|
| | return vertex; |
| | } |
| |
|
| | |
| | |
| | |
| | void LC_Hyperbola::moveStartpoint(const RS_Vector &pos) { |
| | if (!m_bValid || !pos.valid) |
| | return; |
| |
|
| | |
| | if (std::abs(data.angle1) < RS_TOLERANCE && |
| | std::abs(data.angle2) < RS_TOLERANCE) { |
| | RS_DEBUG->print( |
| | RS_Debug::D_WARNING, |
| | "LC_Hyperbola::moveStartpoint: ignored on unbounded hyperbola"); |
| | return; |
| | } |
| |
|
| | RS_Vector newStart = getNearestPointOnEntity(pos, true); |
| | if (!newStart.valid) |
| | return; |
| |
|
| | double newPhi1 = getParamFromPoint(newStart, data.reversed); |
| | double delta = data.angle2 - data.angle1; |
| |
|
| | if (data.angle1 > data.angle2) { |
| | |
| | data.angle1 = newPhi1 + delta; |
| | data.angle2 = newPhi1; |
| | } else { |
| | data.angle1 = newPhi1; |
| | data.angle2 = newPhi1 + delta; |
| | } |
| |
|
| | calculateBorders(); |
| | updateLength(); |
| | } |
| |
|
| | |
| | void LC_Hyperbola::moveEndpoint(const RS_Vector &pos) { |
| | if (!m_bValid || !pos.valid) |
| | return; |
| |
|
| | if (std::abs(data.angle1) < RS_TOLERANCE && |
| | std::abs(data.angle2) < RS_TOLERANCE) { |
| | RS_DEBUG->print( |
| | RS_Debug::D_WARNING, |
| | "LC_Hyperbola::moveEndpoint: ignored on unbounded hyperbola"); |
| | return; |
| | } |
| |
|
| | RS_Vector newEnd = getNearestPointOnEntity(pos, true); |
| | if (!newEnd.valid) |
| | return; |
| |
|
| | double newPhi2 = getParamFromPoint(newEnd, data.reversed); |
| | double delta = data.angle2 - data.angle1; |
| |
|
| | if (data.angle1 > data.angle2) { |
| | data.angle1 = newPhi2; |
| | data.angle2 = newPhi2 - delta; |
| | } else { |
| | data.angle2 = newPhi2; |
| | } |
| |
|
| | calculateBorders(); |
| | updateLength(); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | double LC_Hyperbola::areaLineIntegral() const |
| | { |
| | if (!m_bValid || isInfinite()) return 0.0; |
| |
|
| | double phi1 = data.angle1; |
| | double phi2 = data.angle2; |
| |
|
| | double a = getMajorRadius(); |
| | double b = getMinorRadius(); |
| | double a2 = a*a; |
| | double b2 = b*b; |
| | double cx = data.center.x; |
| | |
| | double cos_th = std::cos(data.majorP.angle()); |
| | double sin_th = std::sin(data.majorP.angle()); |
| | double cos2_th = cos_th*cos_th - sin_th*sin_th; |
| | double sin2_th = 2. * cos_th*sin_th; |
| |
|
| | double R = a * sin_th; |
| | double S = b * cos_th; |
| |
|
| | double c1 = (a2 - b2)/8.; |
| | double c2 = a * b / 4.; |
| | double c3 = a * b /2.; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | auto primitive = [&](double phi) -> double { |
| | double c1Term = c1 * sin2_th * std::cosh(2. * phi); |
| | double c2Term = c2 * cos2_th * std::sinh(2. * phi); |
| | double cxTerm = cx * (R * std::cosh(phi) + S * std::sinh(phi)); |
| | return c1Term + c2Term + c3 * phi + cxTerm; |
| | }; |
| |
|
| | return primitive(phi2) - primitive(phi1); |
| |
|
| | } |
| |
|
| | |
| | RS_Vector LC_Hyperbola::dualLineTangentPoint(const RS_Vector &line) const { |
| | if (!m_bValid || !line.valid) { |
| | return RS_Vector(false); |
| | } |
| | |
| | |
| | |
| | |
| | |
| | |
| | RS_Vector uv = RS_Vector{line}.rotate(-data.majorP.angle()); |
| | |
| | |
| | |
| |
|
| | |
| | if (std::abs(uv.x) < RS_TOLERANCE_ANGLE) |
| | return RS_Vector{false}; |
| | double r = -getRatio() * uv.y / uv.x; |
| | if (std::abs(r) > 1. - RS_TOLERANCE) |
| | return RS_Vector{false}; |
| |
|
| | return getPoint(std::atanh(r), false); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | RS2::Ending LC_Hyperbola::getTrimPoint(const RS_Vector& trimCoord, |
| | const RS_Vector& trimPoint) |
| | { |
| | if (!m_bValid || !trimPoint.valid || !trimCoord.valid || isInfinite()) { |
| | return RS2::EndingNone; |
| | } |
| |
|
| | |
| | RS_Vector nearest = getNearestPointOnEntity(trimCoord, true); |
| | if (!nearest.valid) { |
| | nearest = trimCoord; |
| | } |
| |
|
| | double phi_click = getParamFromPoint(nearest, data.reversed); |
| | double phi_inter = getParamFromPoint(trimPoint, data.reversed); |
| |
|
| | if (std::isnan(phi_click) || std::isnan(phi_inter)) { |
| | |
| | RS_Vector start = getStartpoint(); |
| | RS_Vector end = getEndpoint(); |
| | if (!start.valid || !end.valid) return RS2::EndingNone; |
| |
|
| | return (nearest.distanceTo(start) < nearest.distanceTo(end)) |
| | ? RS2::EndingStart : RS2::EndingEnd; |
| | } |
| |
|
| | |
| | |
| | |
| | return (phi_inter < phi_click) ? RS2::EndingStart : RS2::EndingEnd; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | RS_Vector LC_Hyperbola::prepareTrim(const RS_Vector& trimCoord, |
| | const RS_VectorSolutions& trimSol) |
| | { |
| | if (!m_bValid || trimSol.empty() || isInfinite()) { |
| | return RS_Vector(false); |
| | } |
| |
|
| | |
| | RS_Vector nearest = getNearestPointOnEntity(trimCoord, false); |
| | if (!nearest.valid) { |
| | nearest = trimCoord; |
| | } |
| |
|
| | double phi_ref = getParamFromPoint(nearest, data.reversed); |
| | if (std::isnan(phi_ref)) { |
| | return RS_Vector(false); |
| | } |
| |
|
| | RS_Vector bestSol(false); |
| | double minDeltaPhi = RS_MAXDOUBLE; |
| |
|
| | |
| | for (const RS_Vector& intersect : trimSol) { |
| | if (!intersect.valid) |
| | continue; |
| |
|
| | |
| | |
| |
|
| | double phi = getParamFromPoint(intersect, data.reversed); |
| | if (std::isnan(phi)) |
| | continue; |
| |
|
| | double deltaPhi = std::abs(phi - phi_ref); |
| | if (deltaPhi < minDeltaPhi) { |
| | minDeltaPhi = deltaPhi; |
| | bestSol = intersect; |
| | } |
| | } |
| |
|
| | if (!bestSol.valid) |
| | return RS_Vector(false); |
| |
|
| | double newPhi = getParamFromPoint(bestSol, data.reversed); |
| |
|
| | |
| | RS2::Ending side = getTrimPoint(trimCoord, bestSol); |
| |
|
| | if (side == RS2::EndingStart) { |
| | data.angle1 = newPhi; |
| | } else if (side == RS2::EndingEnd) { |
| | data.angle2 = newPhi; |
| | } else { |
| | return RS_Vector(false); |
| | } |
| |
|
| | calculateBorders(); |
| | updateLength(); |
| |
|
| | return bestSol; |
| | } |
| |
|