You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

229 lines
6.6 KiB

import { Line } from './model/Line';
import type { IRectangle2, ILine, IRectangle } from './interfaces';
export enum EState {
POINT = 1,
PARALLEL = 2,
COINCIDENT = 3,
NONE = 4,
}
export class Intersection {
constructor(
public readonly state: EState,
public readonly x = 0,
public readonly y = 0
) {}
}
export function intersectLineLine(la: ILine, lb: ILine) {
const uaT = (lb.x2 - lb.x1) * (la.y1 - lb.y1) - (lb.y2 - lb.y1) * (la.x1 - lb.x1);
const ubT = (la.x2 - la.x1) * (la.y1 - lb.y1) - (la.y2 - la.y1) * (la.x1 - lb.x1);
const uB = (lb.y2 - lb.y1) * (la.x2 - la.x1) - (lb.x2 - lb.x1) * (la.y2 - la.y1);
if (uB) {
const ua = uaT / uB;
const ub = ubT / uB;
if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) {
return new Intersection(EState.POINT, la.x1 + ua * (la.x2 - la.x1), la.y1 + ua * (la.y2 - la.y1));
}
return new Intersection(EState.NONE);
}
return new Intersection(uaT === 0 || ubT === 0 ? EState.COINCIDENT : EState.PARALLEL);
}
export function fractionAlongLineA(la: ILine, lb: ILine) {
const uaT = (lb.x2 - lb.x1) * (la.y1 - lb.y1) - (lb.y2 - lb.y1) * (la.x1 - lb.x1);
const ubT = (la.x2 - la.x1) * (la.y1 - lb.y1) - (la.y2 - la.y1) * (la.x1 - lb.x1);
const uB = (lb.y2 - lb.y1) * (la.x2 - la.x1) - (lb.x2 - lb.x1) * (la.y2 - la.y1);
if (uB) {
const ua = uaT / uB;
const ub = ubT / uB;
if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) {
return ua;
}
}
return Number.POSITIVE_INFINITY;
}
export function hasFractionToLineCenter(bounds: IRectangle2, line: ILine) {
function testLine(xa: number, ya: number, xb: number, yb: number) {
let testDistance = fractionAlongLineA(line, new Line(xa, ya, xb, yb));
testDistance = Math.abs(testDistance - 0.5);
if (testDistance >= 0 && testDistance <= 1) {
return 1;
}
return 0;
}
// top
let countIntersections = testLine(bounds.x, bounds.y, bounds.x2, bounds.y);
// left
countIntersections += testLine(bounds.x, bounds.y, bounds.x, bounds.y2);
if (countIntersections > 1) {
return true;
}
// bottom
countIntersections += testLine(bounds.x, bounds.y2, bounds.x2, bounds.y2);
if (countIntersections > 1) {
return true;
}
// right
countIntersections += testLine(bounds.x2, bounds.y, bounds.x2, bounds.y2);
// if no intersection, return -1
return countIntersections > 0;
}
export enum OutCode {
LEFT,
TOP,
RIGHT,
BOTTOM,
}
export function outcode(bounds: IRectangle, px: number, py: number): Set<OutCode> {
// taken from JDK 8 java.awt.geom.Rectangle2D.Double#outcode(double, double)
const out = new Set<OutCode>();
if (bounds.width <= 0) {
out.add(OutCode.LEFT);
out.add(OutCode.RIGHT);
} else if (px < bounds.x) {
out.add(OutCode.LEFT);
} else if (px > bounds.x + bounds.width) {
out.add(OutCode.RIGHT);
}
if (bounds.height <= 0) {
out.add(OutCode.TOP);
out.add(OutCode.BOTTOM);
} else if (py < bounds.y) {
out.add(OutCode.TOP);
} else if (py > bounds.y + bounds.height) {
out.add(OutCode.BOTTOM);
}
return out;
}
export function intersectsLine(bounds: IRectangle, line: ILine) {
let x1 = line.x1;
let y1 = line.y1;
const x2 = line.x2;
const y2 = line.y2;
// taken from JDK 8 java.awt.geom.Rectangle2D.Double#intersectsLine(double, double, double, double)
const out2 = Array.from(outcode(bounds, x2, y2));
if (out2.length === 0) {
return true;
}
let out1 = outcode(bounds, x1, y1);
while (out1.size !== 0) {
for (const a of out2) {
if (out1.has(a)) {
return false;
}
}
if (out1.has(OutCode.RIGHT) || out1.has(OutCode.LEFT)) {
let x = bounds.x;
if (out1.has(OutCode.RIGHT)) {
x += bounds.width;
}
y1 = y1 + ((x - x1) * (y2 - y1)) / (x2 - x1);
x1 = x;
} else {
let y = bounds.y;
if (out1.has(OutCode.BOTTOM)) {
y += bounds.height;
}
x1 = x1 + ((y - y1) * (x2 - x1)) / (y2 - y1);
y1 = y;
}
out1 = outcode(bounds, x1, y1);
}
return true;
}
export function fractionToLineCenter(bounds: IRectangle2, line: ILine) {
let minDistance = Number.POSITIVE_INFINITY;
let countIntersections = 0;
function testLine(xa: number, ya: number, xb: number, yb: number) {
let testDistance = fractionAlongLineA(line, new Line(xa, ya, xb, yb));
testDistance = Math.abs(testDistance - 0.5);
if (testDistance >= 0 && testDistance <= 1) {
countIntersections++;
if (testDistance < minDistance) {
minDistance = testDistance;
}
}
}
// top
testLine(bounds.x, bounds.y, bounds.x2, bounds.y);
// left
testLine(bounds.x, bounds.y, bounds.x, bounds.y2);
if (countIntersections > 1) {
return minDistance;
}
// bottom
testLine(bounds.x, bounds.y2, bounds.x2, bounds.y2);
if (countIntersections > 1) {
return minDistance;
}
// right
testLine(bounds.x2, bounds.y, bounds.x2, bounds.y2);
// if no intersection, return -1
if (countIntersections === 0) {
return -1;
}
return minDistance;
}
export function fractionToLineEnd(bounds: IRectangle2, line: ILine) {
let minDistance = Number.POSITIVE_INFINITY;
let countIntersections = 0;
function testLine(xa: number, ya: number, xb: number, yb: number) {
const testDistance = fractionAlongLineA(line, new Line(xa, ya, xb, yb));
if (testDistance >= 0 && testDistance <= 1) {
countIntersections++;
if (testDistance < minDistance) {
minDistance = testDistance;
}
}
}
// top
testLine(bounds.x, bounds.y, bounds.x2, bounds.y);
// left
testLine(bounds.x, bounds.y, bounds.x, bounds.y2);
if (countIntersections > 1) {
return minDistance;
}
// bottom
testLine(bounds.x, bounds.y2, bounds.x2, bounds.y2);
if (countIntersections > 1) {
return minDistance;
}
// right
testLine(bounds.x2, bounds.y, bounds.x2, bounds.y2);
// if no intersection, return -1
if (countIntersections === 0) {
return -1;
}
return minDistance;
}
export function testIntersection(line: ILine, bounds: IRectangle2) {
let count = 0;
// top
const top = intersectLineLine(line, new Line(bounds.x, bounds.y, bounds.x2, bounds.y));
count += top.state === EState.POINT ? 1 : 0;
// left
const left = intersectLineLine(line, new Line(bounds.x, bounds.y, bounds.x, bounds.y2));
count += left.state === EState.POINT ? 1 : 0;
// bottom
const bottom = intersectLineLine(line, new Line(bounds.x, bounds.y2, bounds.x2, bounds.y2));
count += bottom.state === EState.POINT ? 1 : 0;
// right
const right = intersectLineLine(line, new Line(bounds.x2, bounds.y, bounds.x2, bounds.y2));
count += right.state === EState.POINT ? 1 : 0;
return { top, left, bottom, right, count };
}