QCAD Bugtracker

  • Status Closed
  • Percent Complete
    100%
  • Task Type Bug Report
  • Category QCAD (main)
  • Assigned To
    Andrew
  • Operating System All
  • Severity Low
  • Priority Very Low
  • Reported Version 3.32.3
  • Due in Version Undecided
  • Due Date Undecided
  • Votes 1
    • CVH (11.09.2025)
  • Private
Attached to Project: QCAD Bugtracker
Opened by CVH - 23.07.2025
Last edited by Andrew - 30.07.2025

FS#2685 - Ellipse - Line intersection not found

Andrew,

Related to forum topic P48583 .

Probably affects ‘Windows All’, user Chuckk reported this for 3.32.3 on Windows 11pro
Or even ‘All Qt 5 Builds’.

In the attached example (the larger file) using QCAD 3.32.2:
There are no real snappable intersection points returned for a line crossing the ellipse.
It reverts to the ellipse center.
Pointing anywhere near them divides the ellipse at the minor points.

An attempt to use Intersection manual (SY) fails too.

Both methods work well in an earlier version.

At some point RSnapIntersection.snap relies on REntity.getIntersectionPoints(...)
What eventually will be handled on RShape level.

For the topic case:
BreakOut.breakOut calls ShapeAlgorithms.autoSplit(...)
ShapeAlgorithms.autoSplit calls ShapeAlgorithms.getIntersectionPoints(...)
ShapeAlgorithms.getIntersectionPoints relies on RShape.getIntersectionPoints(...)

getIntersectionPoints based on RShapes may fail with shape pointers.

Regards,
CVH

Closed by  Andrew
30.07.2025 09:48
Reason for closing:  Fixed
CVH commented on 24.07.2025 10:02

Andrew,

Bug traced back to REllipse::getTangentPoint(RLine) .
⇒ No line that (almost) crosses an ellipse center can be a tangent.

Applies for all recent releases since end of 2023 and all OS.
Also fails for any resource using RShape::getIntersectionPointsLE() in one way or another.

Please refer to the follow up in the topic: P48589

Regards,
CVH

CVH commented on 28.07.2025 01:21

Andrew,

The condition for an endless line with equation y = mx + c to be a tangent to a standard full ellipse x²/a² + y²/a² = 1 is given by c² == a²m² + b².
Excluding a vertical normalized line.

In REllipse::getTangentPoint(RLine) the value of a²m² + b² is already known as parameter A for the quadratic equation.

!! We cannot quantify "almost a tangent" by the degree to which the discriminant of the quadratic equation differs from zero !!

When using floating-point some minute tolerance is indeed required.
This test within +/- 0.001 may also fail when c is (nearly) zero or when the line passes (almost) through the ellipse center.

For "almost a tangent" there will be a nearby parallel that is actually a perfect tangent.
The y-intercept of this parallel is +/- sqrt(a²m² + b²) or +/- sqrt(A) depending the sign of c.

How much c is allowed to deviate depends on the parallel tolerance and the slope:
Allowed Δc is less or equal to sqrt( (1/m)² + tol² ) with a reasonably small tolerance relative to the radii.

Substituting A and eliminating the root: c² +/- ( (1/m)² + tol²) ) == A.

Plugged into the code of REllipse::getTangentPoint(RLine):

/**
 * \return Tangent point of the given line to this tangent or an invalid vector if the
 * line is not a tangent. (The given line is regarded as endless)
 */
RVector REllipse::getTangentPoint(const RLine& line) const {

    ...

    // check if the transformed line is tangent to the axis-aligned ellipse:
    // slope of line:
    double m = (lineNeutral.getEndPoint().y - lineNeutral.getStartPoint().y) / (lineNeutral.getEndPoint().x - lineNeutral.getStartPoint().x);

    // y-intersept:
    double c = lineNeutral.getStartPoint().y - m * lineNeutral.getStartPoint().x;

    double a = getMajorRadius();
    double b = getMinorRadius();

    double A = (b * b) + (a * a * m * m);
    double tolerance = ..... ;
    double tolAc2 = (1 / m / m) + (tolerance * tolerance);

    // for a tangent line, c squared equals A (within tolerance):
    if (RMath::fuzzyCompare(c*c, A, tolAc2)) {
        // solve for the nearby ideal tangent solution:  
        if (c >= 0) {
            c = sqrt(A);
        }
        else {
            c = -sqrt(A);
        }

        double B = a * a * m * c;    // Avoiding *2/2
        double x = -B / A;
        double y = m * x + c;

        RVector ret = RVector(x, y);

        // without verification ...?
        // in situ point of tangency on full ellipse and endless line:
        ret.rotate(getAngle());
        ret.move(getCenter());
        return ret;
    }

    return RVector::invalid;
}

Note 1: 'As is' the code does not validate the solution to be on a limited line segment or ellipse arc.

While only used in RShape::getIntersectionPointsLE(...) what validates the final solution(s) to be on a limited ellipse arc and on a limited line segment.
This part is never reached when a valid point of tangency is returned.

Note 2: Tolerances differ ...
- For a vertical line it compares for major points within RS::PointTolerance
- The allowed parallel tolerance is not yet defined in the double tolerance

I tend to propose a size relative tolerance based on the radii.
For example: double tolerance = (a + b) / 2000;
Using the same tolerance as for a vertical would be appropriate.
Perhaps added as parameter to the function ... Reverting to a default.

Regards,
CVH

Loading...

Available keyboard shortcuts

Tasklist

Task Details

Task Editing