From 0b4a442f5ef498eb17770bedd7349205f631e36f Mon Sep 17 00:00:00 2001 From: Michael Peters Date: Sat, 27 Jan 2024 16:47:04 -0800 Subject: [PATCH] better intersection detection --- archive/line-segment-intersection-try.ts | 62 +++++++++++++ src/components/grid/grid.tsx | 107 ++++++++++------------- 2 files changed, 107 insertions(+), 62 deletions(-) create mode 100644 archive/line-segment-intersection-try.ts diff --git a/archive/line-segment-intersection-try.ts b/archive/line-segment-intersection-try.ts new file mode 100644 index 0000000..6e46a90 --- /dev/null +++ b/archive/line-segment-intersection-try.ts @@ -0,0 +1,62 @@ +import * as mathjs from 'mathjs'; + +interface Range { + x0: number; + y0: number; + x1: number; + y1: number; +} + +// my first attempt at this +function lineSegmentIntersection(l0: Range, l1: Range): boolean { + // convert to y = mx + b + const m0 = mathjs.fraction(l0.y1 - l0.y0, l0.x1 - l0.x0); + const m1 = mathjs.fraction(l1.y1 - l1.y0, l1.x1 - l1.x0); + + const b0 = mathjs.subtract(l0.y0, mathjs.multiply(m0, l0.x0)); + const b1 = mathjs.subtract(l1.y1, mathjs.multiply(m1, l1.x1)); + + if (mathjs.equal(m0, m1)) { + if (mathjs.equal(b0, b1)) { + // parallel lines with equal intersect + return ( + (l1.x0 >= l0.x0 && l1.x0 <= l0.x1) || + (l1.x1 >= l1.x0 && l1.x1 <= l1.x1) + ); + } else { + // parallel lines with different y-intersect will never intersect + return false; + } + } + + // solve for l0 = l1 + const M = mathjs.subtract(m0, m1); + const B = mathjs.subtract(b0, b1); + + // MX + B = 0 -> X = -B/M + const X = mathjs.divide(mathjs.unaryMinus(B), M); + const X_num = mathjs.number(X as mathjs.Fraction); + + const intersects = + X_num >= Math.min(l0.x0, l0.x1) && + X_num <= Math.max(l0.x0, l0.x1) && + X_num >= Math.min(l1.x0, l1.x1) && + X_num <= Math.max(l1.x0, l1.x1); + + // const Y = mathjs.add(mathjs.multiply(m0, X), b0); + // const Y_num = mathjs.number(Y as mathjs.Fraction); + // if (intersects) { + // console.log('intersects at', X_num, Y_num); + // } else { + // console.log('would have intersected at', X_num, Y_num, l0, l1); + // } + + // to test + // useEffect(() => { + // const l0 = { x0: 2, y0: 8, x1: 6, y1: 5, stroke: '' }; + // const l1 = { x0: 3, y0: 9, x1: 7, y1: 8, stroke: '' }; + // console.log('intersection:', l0, l1, lineSegmentIntersection(l0, l1)); + // }, []); + + return intersects; +} diff --git a/src/components/grid/grid.tsx b/src/components/grid/grid.tsx index 76efad1..e0fd982 100644 --- a/src/components/grid/grid.tsx +++ b/src/components/grid/grid.tsx @@ -11,8 +11,6 @@ import { import './grid.scss'; -import * as mathjs from 'mathjs'; - import * as lodash from 'lodash'; import { useRecoilValue } from 'recoil'; import { drawModeState, lineColorState } from '../../atoms'; @@ -37,57 +35,24 @@ interface Line { stroke: string; } +// https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection +// https://stackoverflow.com/questions/9043805/test-if-two-lines-intersect-javascript-function function lineSegmentIntersection(l0: Range, l1: Range): boolean { - // convert to y = mx + b - const m0 = mathjs.fraction(l0.y1 - l0.y0, l0.x1 - l0.x0); - const m1 = mathjs.fraction(l1.y1 - l1.y0, l1.x1 - l1.x0); - - const b0 = mathjs.subtract(l0.y0, mathjs.multiply(m0, l0.x0)); - const b1 = mathjs.subtract(l1.y1, mathjs.multiply(m1, l1.x1)); - - if (mathjs.equal(m0, m1)) { - if (mathjs.equal(b0, b1)) { - // parallel lines with equal intersect - return ( - (l1.x0 >= l0.x0 && l1.x0 <= l0.x1) || - (l1.x1 >= l1.x0 && l1.x1 <= l1.x1) - ); - } else { - // parallel lines with different y-intersect will never intersect - return false; - } + const determinant = + (l0.x1 - l0.x0) * (l1.y1 - l1.y0) - (l1.x1 - l1.x0) * (l0.y1 - l0.y0); + if (determinant === 0) { + // full lines have no intersection (they must be parallel) + return false; } - - // solve for l0 = l1 - const M = mathjs.subtract(m0, m1); - const B = mathjs.subtract(b0, b1); - - // MX + B = 0 -> X = -B/M - const X = mathjs.divide(mathjs.unaryMinus(B), M); - const X_num = mathjs.number(X as mathjs.Fraction); - - const intersects = - X_num >= Math.min(l0.x0, l0.x1) && - X_num <= Math.max(l0.x0, l0.x1) && - X_num >= Math.min(l1.x0, l1.x1) && - X_num <= Math.max(l1.x0, l1.x1); - - // const Y = mathjs.add(mathjs.multiply(m0, X), b0); - // const Y_num = mathjs.number(Y as mathjs.Fraction); - // if (intersects) { - // console.log('intersects at', X_num, Y_num); - // } else { - // console.log('would have intersected at', X_num, Y_num, l0, l1); - // } - - // to test - // useEffect(() => { - // const l0 = { x0: 2, y0: 8, x1: 6, y1: 5, stroke: '' }; - // const l1 = { x0: 3, y0: 9, x1: 7, y1: 8, stroke: '' }; - // console.log('intersection:', l0, l1, lineSegmentIntersection(l0, l1)); - // }, []); - - return intersects; + const lambda = + ((l1.y1 - l1.y0) * (l1.x1 - l0.x0) + + (l1.x0 - l1.x1) * (l1.y1 - l0.y0)) / + determinant; + const gamma = + ((l0.y0 - l0.y1) * (l1.x1 - l0.x0) + + (l0.x1 - l0.x0) * (l1.y1 - l0.y0)) / + determinant; + return 0 < lambda && lambda < 1 && 0 < gamma && gamma < 1; } function pointsToRange(p0: Point, p1: Point): Range { @@ -198,7 +163,10 @@ const Grid: FC = () => { setUserRedoLines([]); - if (lodash.isEqual(userStartPoint, point)) return; + if (lodash.isEqual(userStartPoint, point)) { + setUserStartPoint(null); + return; + } if (lodash.isNull(userStartPoint)) { setUserStartPoint(point); return; @@ -295,16 +263,31 @@ const Grid: FC = () => { const userLineIndicatorElement = useMemo(() => { if (lodash.isNull(userStartPoint)) return null; const point = snapToGrid(mousePoint, GAP); - return ( - - ); + if (userDrawMode === 'line') { + return ( + + ); + } else { + console.assert(userDrawMode === 'trash'); + return ( + + ); + } }, [userStartPoint, mousePoint]); const userPointIndicatorElement = useMemo(() => { const point = snapToGrid(mousePoint, GAP);