kopia lustrzana https://github.com/Tldraw/Tldraw
303 wiersze
8.2 KiB
TypeScript
303 wiersze
8.2 KiB
TypeScript
import { Box2d } from './Box2d'
|
|
import { pointInPolygon } from './utils'
|
|
import { Vec2d, VecLike } from './Vec2d'
|
|
|
|
// need even more intersections? See https://gist.github.com/steveruizok/35c02d526c707003a5c79761bfb89a52
|
|
|
|
/**
|
|
* Find the intersection between a line segment and a line segment.
|
|
*
|
|
* @param a1 - The first segment's first point.
|
|
* @param a2 - The first segment's second point.
|
|
* @param b1 - The second segment's first point.
|
|
* @param b2 - The second segment's second point.
|
|
* @public
|
|
*/
|
|
export function intersectLineSegmentLineSegment(
|
|
a1: VecLike,
|
|
a2: VecLike,
|
|
b1: VecLike,
|
|
b2: VecLike
|
|
) {
|
|
const ABx = a1.x - b1.x
|
|
const ABy = a1.y - b1.y
|
|
const BVx = b2.x - b1.x
|
|
const BVy = b2.y - b1.y
|
|
const AVx = a2.x - a1.x
|
|
const AVy = a2.y - a1.y
|
|
const ua_t = BVx * ABy - BVy * ABx
|
|
const ub_t = AVx * ABy - AVy * ABx
|
|
const u_b = BVy * AVx - BVx * AVy
|
|
|
|
if (ua_t === 0 || ub_t === 0) return null // coincident
|
|
|
|
if (u_b === 0) return null // parallel
|
|
|
|
if (u_b !== 0) {
|
|
const ua = ua_t / u_b
|
|
const ub = ub_t / u_b
|
|
if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) {
|
|
return Vec2d.AddXY(a1, ua * AVx, ua * AVy)
|
|
}
|
|
}
|
|
|
|
return null // no intersection
|
|
}
|
|
|
|
/**
|
|
* Find the intersections between a line segment and a circle.
|
|
*
|
|
* @param a1 - The segment's first point.
|
|
* @param a2 - The segment's second point.
|
|
* @param c - The circle's center.
|
|
* @param r - The circle's radius.
|
|
* @public
|
|
*/
|
|
export function intersectLineSegmentCircle(a1: VecLike, a2: VecLike, c: VecLike, r: number) {
|
|
const a = (a2.x - a1.x) * (a2.x - a1.x) + (a2.y - a1.y) * (a2.y - a1.y)
|
|
const b = 2 * ((a2.x - a1.x) * (a1.x - c.x) + (a2.y - a1.y) * (a1.y - c.y))
|
|
const cc =
|
|
c.x * c.x + c.y * c.y + a1.x * a1.x + a1.y * a1.y - 2 * (c.x * a1.x + c.y * a1.y) - r * r
|
|
const deter = b * b - 4 * a * cc
|
|
|
|
if (deter < 0) return null // outside
|
|
if (deter === 0) return null // tangent
|
|
|
|
const e = Math.sqrt(deter)
|
|
const u1 = (-b + e) / (2 * a)
|
|
const u2 = (-b - e) / (2 * a)
|
|
|
|
if ((u1 < 0 || u1 > 1) && (u2 < 0 || u2 > 1)) {
|
|
return null // outside or inside
|
|
// if ((u1 < 0 && u2 < 0) || (u1 > 1 && u2 > 1)) {
|
|
// return null // outside
|
|
// } else return null // inside'
|
|
}
|
|
|
|
const result: VecLike[] = []
|
|
|
|
if (0 <= u1 && u1 <= 1) result.push(Vec2d.Lrp(a1, a2, u1))
|
|
if (0 <= u2 && u2 <= 1) result.push(Vec2d.Lrp(a1, a2, u2))
|
|
|
|
if (result.length === 0) return null // no intersection
|
|
|
|
return result
|
|
}
|
|
|
|
/**
|
|
* Find the intersections between a line segment and a polyline.
|
|
*
|
|
* @param a1 - The segment's first point.
|
|
* @param a2 - The segment's second point.
|
|
* @param points - The points in the polyline.
|
|
* @public
|
|
*/
|
|
export function intersectLineSegmentPolyline(a1: VecLike, a2: VecLike, points: VecLike[]) {
|
|
const result: VecLike[] = []
|
|
let segmentIntersection: VecLike | null
|
|
|
|
for (let i = 0, n = points.length - 1; i < n; i++) {
|
|
segmentIntersection = intersectLineSegmentLineSegment(a1, a2, points[i], points[i + 1])
|
|
if (segmentIntersection) result.push(segmentIntersection)
|
|
}
|
|
|
|
if (result.length === 0) return null // no intersection
|
|
|
|
return result
|
|
}
|
|
|
|
/**
|
|
* Find the intersections between a line segment and a closed polygon.
|
|
*
|
|
* @param a1 - The segment's first point.
|
|
* @param a2 - The segment's second point.
|
|
* @param points - The points in the polygon.
|
|
* @public
|
|
*/
|
|
export function intersectLineSegmentPolygon(a1: VecLike, a2: VecLike, points: VecLike[]) {
|
|
const result: VecLike[] = []
|
|
let segmentIntersection: VecLike | null
|
|
|
|
for (let i = 1, n = points.length; i < n + 1; i++) {
|
|
segmentIntersection = intersectLineSegmentLineSegment(
|
|
a1,
|
|
a2,
|
|
points[i - 1],
|
|
points[i % points.length]
|
|
)
|
|
if (segmentIntersection) result.push(segmentIntersection)
|
|
}
|
|
|
|
if (result.length === 0) return null // no intersection
|
|
|
|
return result
|
|
}
|
|
|
|
/**
|
|
* Find the intersections between a circle and a circle.
|
|
*
|
|
* @param c1 - The first circle's center.
|
|
* @param r1 - The first circle's radius.
|
|
* @param c2 - The second circle's center.
|
|
* @param r2 - The second circle's radius.
|
|
* @public
|
|
*/
|
|
export function intersectCircleCircle(c1: VecLike, r1: number, c2: VecLike, r2: number) {
|
|
let dx = c2.x - c1.x
|
|
let dy = c2.y - c1.y
|
|
const d = Math.sqrt(dx * dx + dy * dy),
|
|
x = (d * d - r2 * r2 + r1 * r1) / (2 * d),
|
|
y = Math.sqrt(r1 * r1 - x * x)
|
|
dx /= d
|
|
dy /= d
|
|
return [
|
|
new Vec2d(c1.x + dx * x - dy * y, c1.y + dy * x + dx * y),
|
|
new Vec2d(c1.x + dx * x + dy * y, c1.y + dy * x - dx * y),
|
|
]
|
|
}
|
|
|
|
/**
|
|
* Find the intersections between a circle and a bounding box.
|
|
*
|
|
* @param c - The circle's center.
|
|
* @param r - The circle's radius.
|
|
* @param points - The points in the polygon.
|
|
* @public
|
|
*/
|
|
export function intersectCirclePolygon(c: VecLike, r: number, points: VecLike[]) {
|
|
const result: VecLike[] = []
|
|
let a: VecLike, b: VecLike, int: VecLike[] | null
|
|
|
|
for (let i = 0, n = points.length; i < n; i++) {
|
|
a = points[i]
|
|
b = points[(i + 1) % points.length]
|
|
int = intersectLineSegmentCircle(a, b, c, r)
|
|
if (int) result.push(...int)
|
|
}
|
|
|
|
if (result.length === 0) return null // no intersection
|
|
|
|
return result
|
|
}
|
|
|
|
/**
|
|
* Find the intersections between a circle and a bounding box.
|
|
*
|
|
* @param c - The circle's center.
|
|
* @param r - The circle's radius.
|
|
* @param points - The points in the polyline.
|
|
* @public
|
|
*/
|
|
export function intersectCirclePolyline(c: VecLike, r: number, points: VecLike[]) {
|
|
const result: VecLike[] = []
|
|
let a: VecLike, b: VecLike, int: VecLike[] | null
|
|
|
|
for (let i = 1, n = points.length; i < n; i++) {
|
|
a = points[i - 1]
|
|
b = points[i]
|
|
int = intersectLineSegmentCircle(a, b, c, r)
|
|
if (int) result.push(...int)
|
|
}
|
|
|
|
if (result.length === 0) return null // no intersection
|
|
|
|
return result
|
|
}
|
|
|
|
/**
|
|
* Find the intersections between a polygon and a bounding box.
|
|
*
|
|
* @public
|
|
*/
|
|
export function intersectPolygonBounds(points: VecLike[], bounds: Box2d) {
|
|
const result: VecLike[] = []
|
|
let segmentIntersection: VecLike[] | null
|
|
|
|
for (const side of bounds.sides) {
|
|
segmentIntersection = intersectLineSegmentPolygon(side[0], side[1], points)
|
|
if (segmentIntersection) result.push(...segmentIntersection)
|
|
}
|
|
|
|
if (result.length === 0) return null // no intersection
|
|
|
|
return result
|
|
}
|
|
|
|
function ccw(A: VecLike, B: VecLike, C: VecLike) {
|
|
return (C.y - A.y) * (B.x - A.x) > (B.y - A.y) * (C.x - A.x)
|
|
}
|
|
|
|
/** @public */
|
|
export function linesIntersect(A: VecLike, B: VecLike, C: VecLike, D: VecLike) {
|
|
return ccw(A, C, D) !== ccw(B, C, D) && ccw(A, B, C) !== ccw(A, B, D)
|
|
}
|
|
|
|
/**
|
|
* Create a new convex polygon as the intersection of two convex polygons.
|
|
*
|
|
* @param polygonA - An array of points representing the first polygon.
|
|
* @param polygonB - An array of points representing the second polygon.
|
|
* @public
|
|
*/
|
|
export function intersectPolygonPolygon(polygonA: VecLike[], polygonB: VecLike[]) {
|
|
// Create an empty polygon as P
|
|
const result: VecLike[] = []
|
|
let a: VecLike, b: VecLike, c: VecLike, d: VecLike
|
|
|
|
// Add all corners of PolygonA that is inside PolygonB to P
|
|
for (let i = 0, n = polygonA.length; i < n; i++) {
|
|
a = polygonA[i]
|
|
if (pointInPolygon(a, polygonB)) {
|
|
result.push(a)
|
|
}
|
|
}
|
|
// Add all corners of PolygonB that is inside PolygonA to P
|
|
for (let i = 0, n = polygonB.length; i < n; i++) {
|
|
a = polygonB[i]
|
|
if (pointInPolygon(a, polygonA)) {
|
|
result.push(a)
|
|
}
|
|
}
|
|
|
|
// Add all intersection points to P
|
|
for (let i = 0, n = polygonA.length; i < n; i++) {
|
|
a = polygonA[i]
|
|
b = polygonA[(i + 1) % polygonA.length]
|
|
|
|
for (let j = 0, m = polygonB.length; j < m; j++) {
|
|
c = polygonB[j]
|
|
d = polygonB[(j + 1) % polygonB.length]
|
|
const intersection = intersectLineSegmentLineSegment(a, b, c, d)
|
|
|
|
if (intersection !== null) {
|
|
result.push(intersection)
|
|
}
|
|
}
|
|
}
|
|
|
|
if (result.length === 0) return null // no intersection
|
|
|
|
// Order all points in the P counter-clockwise.
|
|
return orderClockwise(result)
|
|
}
|
|
|
|
function orderClockwise(points: VecLike[]): VecLike[] {
|
|
const C = Vec2d.Average(points)
|
|
return points.sort((A, B) => Vec2d.Angle(C, A) - Vec2d.Angle(C, B))
|
|
}
|
|
|
|
/** @public */
|
|
export function polygonsIntersect(a: VecLike[], b: VecLike[]) {
|
|
let a0: VecLike, a1: VecLike, b0: VecLike, b1: VecLike
|
|
for (let i = 0, n = a.length; i < n; i++) {
|
|
a0 = a[i]
|
|
a1 = a[(i + 1) % n]
|
|
for (let j = 0, m = b.length; j < m; j++) {
|
|
b0 = b[j]
|
|
b1 = b[(j + 1) % m]
|
|
if (linesIntersect(a0, a1, b0, b1)) return true
|
|
}
|
|
}
|
|
return false
|
|
}
|