问题
I'd like to display the point of intersection of two line segments. The Segments are animated, so they start and stop to intersect, based on progress.
Therefore I have this code:
class LineSegment {
constructor(x1,y1,x2,y2) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
}
contains (x,y) {
const
x1 = Math.min(this.x1, this.x2),
y1 = Math.min(this.y1, this.y2),
x2 = Math.max(this.x1, this.x2),
y2 = Math.max(this.y1, this.y2),
dot = ((x - x1) * (y2 - y1)) - ((y - y1) * (x2 - x1))
;
return dot <= Number.EPSILON &&
x >= x1 && x <= x2 &&
y >= y1 && y <= y2;
}
}
Somewhere in the code I use that like this:
const
seg1 = new LineSegment(…),
seg2 = new LineSegment(…),
i = Intersect(seg1, seg2), //working code that calculates x and y values
//for the »unbounded« intersection
contains = i !== null &&
seg1.contains(i.x, i.y) &&
seg2.contains(i.x, i.y)
;
if (contains) {
//show a circle around x and y
} else {
//remove that one
}
In fact, those intersections »flicker«, means they sometimes work and sometimes not. What am I missing here, I guess I am running into numerical issues here?
Due to the comment of @Gilles-Philippe Paillé here to code used to calculate the intersection. I lives in another Helper class and looks like this:
intersect ({ a: a2, b: b2, c: c2 }) {
const
{
a:a1,
b:b1,
c:c1
} = this,
denom = det(a1, a2, b1, b2)
;
//only chuck norris can devide by zero!
return denom == 0 ?
null :
[ -1 * det(b1, c1, b2, c2) / denom,
det(a1, c1, a2, c2) / denom ];
}
回答1:
The dot
variable is in reality the determinant (or the 2D cross product). The problem is that the determinant can be negative. Thus you need to test the absolute value of the determinant. Moreover, Number.EPSILON
is the smallest non-zero number, which is not useful is case of numerical inaccuracy. You should instead use a more reasonable value:
Math.abs(dot) <= 1e-8
Moreover, the determinant should be calculated using the segment point, not the bounding box min/max:
dot = ((x - this.x1) * (this.y2 - this.y1)) - ((y - this.y1) * (this.x2 - this.x1))
回答2:
A simpler solution is to check if one segment ends are on different half-planes in respect to the other segment and vice-versa. This requires no divisions:
function side(a, b, p) {
return (p.x - a.x)*(b.y - a.y) + (p.y - a.y)*(a.x - b.x);
}
function xsect(a0, b0, a1, b1) {
return (side(a0, b0, a1) * side(a0, b0, b1) < 0 &&
side(a1, b1, a0) * side(a1, b1, b0) < 0)
}
Things are more annoying if you need to include boundary points and/or collinear segments intersection (note also that the intersection point of two segments even with integer coordinates may be impossible to represent exactly with floating point numbers without approximation - e.g. (0, 0)-(1, 10)
with (0, 1)-(10, 1)
).
回答3:
Line intercepts
The following functions finds the intercept, if any, as a point {x, y}
of two lines defined by two points {p1:{x,y}, p2{x,y}}
on each line.
The first two function are for line segments (each has a fixed length) and the last two functions are infinitely long lines.
Line segment intercept
As 4 points p1 = {x,y}
, p2 = {x,y}
, p3 = {x,y}
, p4 = {x,y}
defining end points of two line segments. Returns point only if there is an intercept, else return undefined
.
function interceptSegs(p1, p2, p3, p4, p = {}) {
const x1 = p2.x - p1.x, y1 = p2.y - p1.y;
const x2 = p4.x - p3.x, y2 = p4.y - p3.y;
const c = x1 * y2 - y1 * x2;
if (c) {
const x3 = p1.x - p3.x, y3 = p1.y - p3.y;
const u = (x1 * y3 - y1 * x3) / c;
if (u >= 0 && u <= 1) {
const u = (x2 * y3 - y2 * x3) / c;
if (u >= 0 && u <= 1) {
p.x = p1.x + x1 * u;
p.y = p1.y + y1 * u;
return p;
}
}
}
}
OR As two lines l1 = {p1:{x,y}, p2{x,y}}
, l2 = {p1:{x,y}, p2{x,y}}
returns point only if there is an intercept.
function interceptSegs(l1, l2) {
const a = {x: l1.p2.x - l1.p1.x, y: l1.p2.y - l1.p1.y};
const b = {x: l2.p2.x - l2.p1.x, y: l2.p2.y - l2.p1.y};
const c = a.x * b.y - a.y * b.x;
if (c) {
const e = {x: l1.p1.x - l2.p1.x, y: l1.p1.y - l2.p1.y};
const u = (a.x * e.y - a.y * e.x) / c;
if (u >= 0 && u <= 1) {
const u = (b.x * e.y - b.y * e.x) / c;
if (u >= 0 && u <= 1) {
return {x: l1.p1.x + a.x * u, y: l1.p1.y + a.y * u};
}
}
}
}
Intercept lines
Similar to above functions but assumes lines are infinitely long. Will return a point or undefined
if lines are parallel.
function intercept(p1, p2, p3, p4, p = {}) {
const x1 = p2.x - p1.x, y1 = p2.y - p1.y;
const x2 = p4.x - p3.x, y2 = p4.y - p3.y;
const c = x1 * y2 - y1 * x2;
if (c) {
let u = (x1 * (p1.y - p3.y) - y1 * (p1.x - p3.x)) / c;
p.x = p3.x + x2 * u;
p.y = p3.y + y2 * u;
return p;
}
}
OR As lines l1
, l2
returns point only if there is an intercept. Returns undefined
if line are parallel.
function interceptLines(l1, l2) {
const a = {x: l1.p2.x - l1.p1.x, y: l1.p2.y - l1.p1.y};
const b = {x: l2.p2.x - l2.p1.x, y: l2.p2.y - l2.p1.y};
const c = a.x * b.y - a.y * b.x;
if (c) {
const u = (a.x * (l1.p1.y - l2.p1.y) - a.y * (l1.p1.x - l2.p1.x)) / c;
return {x: l2.p1.x + b.x * u, y: l2.p1.y + b.y * u};
}
}
来源:https://stackoverflow.com/questions/58694842/line-segement-intersection-works-sometimes-sometimes-not