| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | #include "lc_parabola.h" |
| |
|
| | #include "rs_debug.h" |
| | #include "rs_information.h" |
| | #include "rs_line.h" |
| |
|
| | namespace { |
| |
|
| | |
| | LC_SplinePointsData convert2SplineData(const LC_ParabolaData& data) |
| | { |
| | LC_SplinePointsData splineData{}; |
| | splineData.controlPoints = {data.controlPoints.cbegin(), data.controlPoints.cend()}; |
| | |
| | RS_Vector sp1 = (data.controlPoints.front() + data.controlPoints.back())*0.25 + data.controlPoints.at(1)*0.5; |
| | splineData.splinePoints = {data.controlPoints.front(), sp1, data.controlPoints.back()}; |
| | splineData.useControlPoints = true; |
| | splineData.closed = false; |
| | return splineData; |
| | } |
| |
|
| | |
| | bool validateConvexPoints(std::vector<RS_Vector>& points) |
| | { |
| | if (points.size() != 4) |
| | return false; |
| | bool valid = std::all_of(points.cbegin(), points.cend(), [](const RS_Vector& point) { |
| | return point.valid; |
| | }); |
| | if (!valid) |
| | return false; |
| | |
| | std::sort(points.begin(), points.end(), [](const RS_Vector& v0, const RS_Vector& v1) { |
| | if (v0.y + RS_TOLERANCE < v1.y) |
| | return true; |
| | if (v0.y - RS_TOLERANCE > v1.y) |
| | return false; |
| | return v0.x + RS_TOLERANCE < v1.x; |
| | }); |
| | |
| | for(size_t i=1; i<points.size(); ++i) |
| | if (points[i].squaredTo(points[i-1]) <= RS_TOLERANCE2) |
| | return false; |
| | |
| | std::sort(points.begin() + 1, points.end(), [origin=points.front()] (const RS_Vector& pt0, |
| | const RS_Vector& pt1) { |
| | return (pt0 - origin).angle() < (pt1 - origin).angle(); |
| | }); |
| | RS_Line l0{nullptr, {points[0], points[2]}}; |
| | RS_Line l1{nullptr, {points[1], points[3]}}; |
| | RS_VectorSolutions sol = RS_Information::getIntersection(&l0, &l1, true); |
| | return ! sol.empty(); |
| | } |
| |
|
| | std::vector<RS_Vector> getAxisVectors(std::vector<RS_Vector> pts) |
| | { |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | if (!validateConvexPoints(pts)) { |
| | |
| | return {}; |
| | } |
| | |
| |
|
| | const auto& a = pts[0]; |
| | const auto& b = pts[2]; |
| | const auto& c = pts[1]; |
| | const auto& d = pts[3]; |
| | RS_Line ab{nullptr, {a, b}}; |
| | RS_Line cd{nullptr, {c, d}}; |
| | auto sol0 = RS_Information::getIntersection(&ab, &cd, true); |
| | if (sol0.empty()) |
| | return {}; |
| | auto po = sol0.at(0); |
| | |
| | auto findCircleIntersection=[&po, &pts](size_t index)->std::pair<RS_Vector, RS_Vector> { |
| | double ds = std::sqrt((pts[index] - po).magnitude()*(pts[index+2] - po).magnitude()); |
| | RS_Vector dv = (pts[index+2] - pts[index]).normalized(); |
| | return {po - dv * ds, po + dv *ds}; |
| | }; |
| | const auto [pm, pn] = findCircleIntersection(0); |
| | const auto [pq, pp] = findCircleIntersection(1); |
| | return { (pm - pq).normalized(), (pn - pq).normalized()}; |
| | } |
| |
|
| | LC_ParabolaData fromPointsAxis(const std::vector<RS_Vector>& points, const RS_Vector& axis) |
| | { |
| | |
| | const auto& rCenter = points.front(); |
| | std::array<RS_Vector, 4> rotated; |
| | std::transform(points.cbegin(), points.cend(), rotated.begin(), |
| | [&rCenter, da=RS_Vector{M_PI/2 - axis.angle()}](const RS_Vector& vp){ |
| | RS_Vector ret = vp; |
| | return ret.rotate(rCenter, da); |
| | }); |
| | std::sort(rotated.begin(), rotated.end(), [](const RS_Vector& p0, const RS_Vector& p1) { |
| | return p0.x < p1.x; |
| | }); |
| | |
| | |
| | double sxi2=0., sxi=0., sxyi=0., syi=0.; |
| | std::vector<double> xis,yis; |
| | for (size_t i = 1; i < rotated.size(); ++i) { |
| | double xi = rotated[i].x + rotated.front().x; |
| | |
| | if (std::abs(rotated[i].x - rotated.front().x) <= RS_TOLERANCE) |
| | continue; |
| | double yi = (rotated[i].y - rotated.front().y)/(rotated[i].x - rotated.front().x); |
| | sxi2 += xi * xi; |
| | sxi += xi; |
| | sxyi += xi * yi; |
| | syi += yi; |
| | xis.push_back(xi); |
| | yis.push_back(yi); |
| | } |
| | |
| | const double d = sxi2*xis.size() - sxi*sxi; |
| | if (std::abs(d) < RS_TOLERANCE) |
| | { |
| | |
| | return {}; |
| | } |
| | const double a = (sxyi*xis.size() - syi*sxi)/d; |
| | if (std::abs(a) < RS_TOLERANCE2) |
| | { |
| | return {}; |
| | } |
| | const double b = (sxi2*syi - sxi*sxyi)/d; |
| | |
| | for (size_t i=0; i< xis.size(); i++) |
| | { |
| | LC_LOG<<__func__<<"(): fitting: xi = "<<xis[i]<<": "<<yis[i] - (a*xis[i] + b); |
| | } |
| | double c = 0.; |
| | for (const auto& point: rotated) |
| | { |
| | c += point.y - point.x * (b + point.x * a); |
| | } |
| | c /= 4; |
| | double da = {axis.angle() - M_PI/2}; |
| | auto f0 = [&a, &b, &c, &rCenter, &da](const RS_Vector& pt) { |
| | double x = RS_Vector{pt}.rotate(rCenter, -da).x; |
| | double y = c + x*(b + a*x); |
| | return RS_Vector{x, y}.rotate(rCenter, da); |
| | }; |
| |
|
| | |
| | double ds2=0.; |
| | for (const auto& point: points) |
| | { |
| | double ds= (point - f0(point)).squared(); |
| | ds2 += ds; |
| | LC_LOG<<"oxi = ("<<point.x<<", "<< point.y<<"): ("<<f0(point).x<<", "<<f0(point).y<<"): dr=" |
| | << ds<<": "<<ds2; |
| | } |
| | if (ds2 >= RS_TOLERANCE2) |
| | return {}; |
| | |
| | |
| |
|
| | auto f1 = [&a, &b, &da](double x) { |
| | return RS_Vector{1., 2.*a*x +b}.rotate(da); |
| | }; |
| | std::array<RS_Vector, 2> tangents = {{f1(rotated.front().x), f1(rotated.back().x)}}; |
| |
|
| | auto ret = LC_ParabolaData::FromEndPointsTangents( |
| | {rotated.front().rotate(rCenter, da), rotated.back().rotate(rCenter, da)}, |
| | tangents |
| | ); |
| |
|
| | return ret; |
| | } |
| |
|
| | } |
| |
|
| | LC_ParabolaData LC_ParabolaData::FromEndPointsTangents( |
| | const std::array<RS_Vector, 2>& endPoints, |
| | const std::array<RS_Vector, 2>& endTangents) |
| | { |
| | RS_Line l0{nullptr, {endPoints.at(0), endPoints.at(0) + endTangents.at(0)}}; |
| | RS_Line l1{nullptr, {endPoints.at(1), endPoints.at(1) + endTangents.at(1)}}; |
| | auto sol = RS_Information::getIntersection(&l0, &l1); |
| | if (sol.empty()) |
| | return {}; |
| | return {{endPoints.at(0), sol.at(0), endPoints.at(1)}}; |
| | } |
| |
|
| | std::vector<LC_ParabolaData> LC_ParabolaData::From4Points(const std::vector<RS_Vector>& points) |
| | { |
| | std::vector<RS_Vector> axes = getAxisVectors(points); |
| |
|
| | std::vector<LC_ParabolaData> ret; |
| | std::transform(axes.cbegin(), axes.cend(), std::back_inserter(ret), [&points](const RS_Vector& axis) { |
| | return fromPointsAxis(points, axis); |
| | }); |
| | ret.erase(std::remove_if(ret.begin(), ret.end(), [](const LC_ParabolaData& d){return !d.valid;}), ret.end()); |
| | return ret; |
| | } |
| |
|
| | LC_ParabolaData::LC_ParabolaData(std::array<RS_Vector, 3> controlPoints): |
| | controlPoints{std::move(controlPoints)} |
| | , valid{true} |
| | { |
| | CalculatePrimitives(); |
| | } |
| |
|
| | void LC_ParabolaData::CalculatePrimitives() |
| | { |
| | |
| | |
| | |
| | |
| |
|
| | |
| | RS_Vector c1 = controlPoints.at(1) - controlPoints.at(0); |
| | RS_Vector c2 = controlPoints.at(2) - controlPoints.at(0); |
| | |
| | auto f0 = [&c1, &c2](double t) { |
| | return c1*(2. * t * (1. - t)) + c2 * ( t * t); |
| | }; |
| |
|
| | axis = c2 * 0.5 - c1; |
| | if(axis.squared() < RS_TOLERANCE2) { |
| | valid = false; |
| | return; |
| | } |
| | |
| | |
| | |
| | double t = -0.5*axis.dotP(c1)/axis.squared(); |
| | auto localVertex = f0(t); |
| | axis.normalize(); |
| | double dy = (c2 - localVertex).dotP(axis); |
| | double dx = (c2 - localVertex).dotP({- axis.y, axis.x}); |
| | |
| | const double h = dx*dx/(4.*dy); |
| | vertex = localVertex + controlPoints.front(); |
| | axis *= h; |
| | focus = vertex + axis; |
| | } |
| |
|
| | RS_LineData LC_ParabolaData::GetAxis() const |
| | { |
| | const auto vp = (controlPoints.front() + controlPoints.back())*0.5 - vertex; |
| | return {vertex, vertex + axis*(0.5*vp.dotP(axis)/axis.squared())}; |
| | } |
| |
|
| | double LC_ParabolaData::FindX(const RS_Vector& point) const |
| | { |
| | |
| | const auto vp = RS_Vector{point}.rotate(vertex, M_PI/2 - axis.angle()) - vertex; |
| | return vp.x; |
| | } |
| |
|
| | RS_Vector LC_ParabolaData::FromX(double x) const |
| | { |
| | |
| | auto vp = RS_Vector{x, x*x/(4.*axis.magnitude())}.rotate(axis.angle() - M_PI/2) + vertex; |
| | return vp; |
| | } |
| |
|
| | std::array<RS_Vector, 2> LC_ParabolaData::FromXWithTangent(double x) const |
| | { |
| | const double h = vertex.distanceTo(focus) * 0.5; |
| | return {RS_Vector{x, x*x/(4.*h)}.rotate(axis.angle() - M_PI/2) + vertex, |
| | RS_Vector{2.*h, x}.rotate(axis.angle() - M_PI/2)}; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | LC_Quadratic LC_ParabolaData::getQuadratic() const |
| | { |
| | if (!valid) |
| | return LC_Quadratic{}; |
| | std::vector<double> ce(6, 0.); |
| | ce[0] = 1.; |
| | ce[4] = -4. * axis.magnitude(); |
| | LC_Quadratic lq{ce}; |
| | lq.rotate(axis.angle() - M_PI/2); |
| | lq.move(vertex); |
| | return lq; |
| | } |
| |
|
| | LC_Parabola::LC_Parabola(RS_EntityContainer* parent, const LC_ParabolaData& d): |
| | LC_SplinePoints{parent, convert2SplineData(d)} |
| | , data{d} |
| | { |
| | } |
| |
|
| | RS_Entity* LC_Parabola::clone() const |
| | { |
| | auto* e = new LC_Parabola(*this); |
| | return e; |
| | } |
| |
|
| |
|
| | RS2::EntityType LC_Parabola::rtti() const |
| | { |
| | return RS2::EntityParabola; |
| | } |
| |
|
| | LC_Quadratic LC_Parabola::getQuadratic() const |
| | { |
| | return data.getQuadratic(); |
| | } |
| |
|
| | RS_Vector LC_Parabola::getTangentDirection(const RS_Vector& point)const |
| | { |
| | RS_VectorSolutions sol = getTangentPoint(point); |
| | if(sol.empty()) |
| | return {}; |
| | RS_Vector tangentPoint = getTangentPoint(point).at(0); |
| | RS_Vector p0 = rotateToQuadratic(tangentPoint) - data.vertex; |
| | return RS_Vector{2.*data.axis.magnitude(), p0.x}.rotate(data.axis.angle() - M_PI/2).normalized(); |
| | } |
| |
|
| | RS_VectorSolutions LC_Parabola::getTangentPoint(const RS_Vector& point) const |
| | { |
| | RS_Vector p0 = rotateToQuadratic(point) - data.vertex; |
| | |
| | |
| | |
| | |
| | |
| | const double h = data.axis.magnitude(); |
| | if (4.0*h*p0.y >= p0.x * p0.x + RS_TOLERANCE) |
| | return {}; |
| | auto pf = [this, &h](double x){ |
| | return RS_Vector{x, x*x/(4.*h)}.rotate(data.axis.angle() - M_PI/2.) + data.vertex; |
| | }; |
| | double dx = std::sqrt(std::abs(p0.x*p0.x - 4. * h * p0.y)); |
| | if (dx <= RS_TOLERANCE) |
| | return {point}; |
| | return {pf(p0.x + dx), pf(p0.x - dx)}; |
| | } |
| |
|
| | RS_Vector LC_Parabola::dualLineTangentPoint(const RS_Vector& line) const |
| | { |
| | |
| | auto uv = RS_Vector{line}.rotate(M_PI/2. - data.axis.angle()); |
| | |
| | |
| | if (std::abs(uv.y) < RS_TOLERANCE) |
| | return RS_Vector{false}; |
| | return data.FromX(-2.*data.axis.magnitude()*uv.x/uv.y); |
| | } |
| |
|
| |
|
| | RS2::Ending LC_Parabola::getTrimPoint(const RS_Vector& trimCoord, |
| | const RS_Vector& trimPoint) |
| | { |
| | double xi[] = {rotateToQuadratic(getStartpoint()).x, |
| | rotateToQuadratic(trimCoord).x, |
| | rotateToQuadratic(trimPoint).x}; |
| | return (std::signbit(xi[0] - xi[1]) != std::signbit(xi[2] - xi[1])) ? |
| | RS2::EndingEnd : RS2::EndingStart; |
| | } |
| |
|
| | RS_Vector LC_Parabola::prepareTrim(const RS_Vector& trimCoord, |
| | const RS_VectorSolutions& trimSol) |
| | { |
| | |
| | if ( ! trimSol.hasValid()) return(RS_Vector(false)); |
| | if ( trimSol.getNumber() == 1 ) return(trimSol.get(0)); |
| | auto vp0=trimSol.getClosest(trimCoord, nullptr, 0); |
| |
|
| | double dr2=trimCoord.squaredTo(vp0); |
| | |
| | if(dr2 < trimCoord.squaredTo(getStartpoint()) && dr2 < trimCoord.squaredTo(getEndpoint())) |
| | return vp0; |
| | |
| | RS_Vector vp1=(trimCoord.squaredTo(getStartpoint()) <= trimCoord.squaredTo(getEndpoint()))?getStartpoint():getEndpoint(); |
| |
|
| | |
| | auto dvp1=vp1 - trimCoord; |
| | RS_VectorSolutions sol1; |
| | for(size_t i=0; i<trimSol.size(); i++){ |
| | auto dvp2=trimSol.at(i) - trimCoord; |
| | if( RS_Vector::dotP(dvp1, dvp2) > RS_TOLERANCE) |
| | sol1.push_back(trimSol.at(i)); |
| | } |
| | |
| | if(sol1.size()) |
| | return sol1.getClosest(trimCoord, nullptr, 0); |
| |
|
| | |
| | return vp0; |
| | } |
| |
|
| | RS_Vector LC_Parabola::rotateToQuadratic(RS_Vector vp) const |
| | { |
| | return vp.rotate(data.vertex, M_PI/2 - data.axis.angle()); |
| | } |
| |
|
| | void LC_Parabola::LC_Parabola::moveStartpoint(const RS_Vector& pos) |
| | { |
| | RS_Vector p0=getNearestPointOnEntity(pos); |
| | RS_Vector t0=getTangentDirection(p0); |
| | auto t1 = RS_Vector{getDirection2()}; |
| | data = LC_ParabolaData::FromEndPointsTangents( |
| | { p0, data.controlPoints.back() }, |
| | { t0, t1} |
| | ); |
| | LC_SplinePoints::getData() = convert2SplineData(data); |
| | calculateBorders(); |
| | } |
| |
|
| | void LC_Parabola::LC_Parabola::moveEndpoint(const RS_Vector& pos) |
| | { |
| | auto t0 = RS_Vector{getDirection1()}; |
| | RS_Vector p2=getNearestPointOnEntity(pos); |
| | RS_Vector t2=RS_Vector{getTangentDirection(p2)}; |
| | data = LC_ParabolaData::FromEndPointsTangents( |
| | { data.controlPoints.front(), p2 }, |
| | { t0, t2} |
| | ); |
| | LC_SplinePoints::getData() = convert2SplineData(data); |
| | calculateBorders(); |
| | } |
| |
|
| | double LC_Parabola::getDirection1() const |
| | { |
| | return (data.controlPoints.at(1) - data.controlPoints.front()).angle(); |
| | } |
| |
|
| | double LC_Parabola::getDirection2() const |
| | { |
| | return (data.controlPoints.back() - data.controlPoints.at(1)).angle(); |
| | } |
| | RS_VectorSolutions LC_Parabola::getRefPoints() const |
| | { |
| | return {data.controlPoints.front(), data.controlPoints.at(1), data.controlPoints.back()}; |
| | } |
| |
|
| |
|
| | void LC_Parabola::move(const RS_Vector& offset) |
| | { |
| | for(auto& point: data.controlPoints) |
| | point.move(offset); |
| | update(); |
| | } |
| | void LC_Parabola::rotate(const RS_Vector& center, double angle) |
| | { |
| | for(auto& point: data.controlPoints) |
| | point.rotate(center, angle); |
| | update(); |
| | } |
| | void LC_Parabola::rotate(const RS_Vector& center, const RS_Vector& angleVector) |
| | { |
| | for(auto& point: data.controlPoints) |
| | point.rotate(center, angleVector); |
| | update(); |
| | } |
| | void LC_Parabola::scale(const RS_Vector& center, const RS_Vector& factor) |
| | { |
| | for(auto& point: data.controlPoints) |
| | point.scale(center, factor); |
| | update(); |
| | } |
| | void LC_Parabola::mirror(const RS_Vector& axisPoint1, const RS_Vector& axisPoint2) |
| | { |
| | for(auto& point: data.controlPoints) |
| | point.mirror(axisPoint1, axisPoint2); |
| | update(); |
| | } |
| |
|
| | RS_Entity& LC_Parabola::shear(double k) |
| | { |
| | for(auto& point: data.controlPoints) |
| | point.shear(k); |
| | update(); |
| | return *this; |
| | } |
| |
|
| | void LC_Parabola::moveRef(const RS_Vector& ref, const RS_Vector& offset) |
| | { |
| | for(auto& point: data.controlPoints) |
| | if (point.squaredTo(ref) < RS_TOLERANCE2) { |
| | point.move(offset); |
| | break; |
| | } |
| |
|
| | update(); |
| | } |
| |
|
| | void LC_Parabola::revertDirection() |
| | { |
| | std::swap(data.controlPoints.front(), data.controlPoints.back()); |
| | } |
| |
|
| | void LC_Parabola::LC_Parabola::update() |
| | { |
| | data.CalculatePrimitives(); |
| | LC_SplinePoints::getData() = convert2SplineData(data); |
| | calculateBorders(); |
| | } |
| |
|
| | RS_Vector LC_Parabola::getNearestOrthTan([[maybe_unused]] const RS_Vector& coord, |
| | const RS_Line& normal, |
| | bool onEntity ) const |
| | { |
| | |
| | auto line = RS_Vector{ normal.getDirection1() }.rotate(M_PI/2 - data.axis.angle()); |
| | |
| | if (onEntity) { |
| | const double x0 = data.FindX(getStartpoint()); |
| | const double x1 = data.FindX(getEndpoint()); |
| | if (std::signbit(line.x - x0) == std::signbit(line.x - x1)) |
| | return RS_Vector{false}; |
| | } |
| | if (std::abs(line.y) < RS_TOLERANCE) |
| | return RS_Vector{false}; |
| | const double x = -2.*data.axis.magnitude()*line.x/line.y; |
| | return RS_Vector{x, x*x/(4.*data.axis.magnitude())}.rotate(data.axis.angle() - M_PI/2) + data.vertex; |
| | } |
| |
|
| | std::unique_ptr<LC_Parabola> LC_Parabola::approximateOffset(double dist) const |
| | { |
| | auto controlPoints = data.controlPoints; |
| | RS_Vector displacement = data.axis.normalized() * (- dist); |
| | for(RS_Vector& controlPoint: controlPoints) |
| | controlPoint.move(displacement); |
| | LC_ParabolaData offsetData{controlPoints}; |
| | return std::make_unique<LC_Parabola>(nullptr, offsetData); |
| | } |
| |
|
| | double LC_Parabola::areaLineIntegral() const |
| | { |
| | if (!data.valid) { |
| | return 0.0; |
| | } |
| | const RS_Vector& p0 = data.controlPoints[0]; |
| | const RS_Vector& p1 = data.controlPoints[1]; |
| | const RS_Vector& p2 = data.controlPoints[2]; |
| | double x0 = p0.x, y0 = p0.y; |
| | double x1 = p1.x, y1 = p1.y; |
| | double x2 = p2.x, y2 = p2.y; |
| |
|
| | double a = -x0 / 2.0 - x1 / 3.0 - x2 / 6.0; |
| | double b = x0 / 3.0 - x2 / 3.0; |
| | double c = x0 / 6.0 + x1 / 3.0 + x2 / 2.0; |
| |
|
| | return a * y0 + b * y1 + c * y2; |
| | } |
| |
|
| | double LC_Parabola::getLength() const |
| | { |
| | if (!data.valid) |
| | return 0.0; |
| | double h = data.axis.magnitude(); |
| | if (h < RS_TOLERANCE) |
| | return 0.0; |
| |
|
| | double x1 = data.FindX(getStartpoint()); |
| | double x2 = data.FindX(getEndpoint()); |
| |
|
| | double xmin = std::min(x1, x2); |
| | double xmax = std::max(x1, x2); |
| |
|
| | auto F = [h](double x) -> double { |
| | double sqrt_term = std::sqrt(x * x + 4 * h * h); |
| | return (x / (4. * h)) * sqrt_term + h * std::log(x + sqrt_term); |
| | }; |
| |
|
| | return F(xmax) - F(xmin); |
| | } |
| |
|
| |
|
| |
|