Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion packages/editor/src/lib/primitives/Vec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,11 +430,14 @@ export class Vec {
* @param P - A point not on the line to test.
*/
static NearestPointOnLineThroughPoint(A: VecLike, u: VecLike, P: VecLike): Vec {
// Inlined: t = Vec.Sub(P, A).pry(u), return Vec.Mul(u, t).add(A)
const t = (P.x - A.x) * u.x + (P.y - A.y) * u.y
return new Vec(A.x + u.x * t, A.y + u.y * t)
}

static NearestPointOnLineSegment(A: VecLike, B: VecLike, P: VecLike, clamp = true): Vec {
// Parametric projection of P onto segment AB.
// Inlined: d = Vec.Sub(B, A); t = Vec.Sub(P, A).pry(d) / d.len(); return Vec.Lrp(A, B, t)
const dx = B.x - A.x
const dy = B.y - A.y
const d2 = dx * dx + dy * dy
Expand All @@ -452,13 +455,17 @@ export class Vec {
}

static DistanceToLineThroughPoint(A: VecLike, u: VecLike, P: VecLike): number {
// |cross(P-A, u)| = perpendicular distance to line through A with direction u
// Inlined: Vec.Dist(P, Vec.NearestPointOnLineThroughPoint(A, u, P))
// Uses |cross(P-A, u)| which equals the perpendicular distance when u is a unit vector.
const dx = P.x - A.x
const dy = P.y - A.y
return Math.abs(dx * u.y - dy * u.x)
}

static DistanceToLineSegment(A: VecLike, B: VecLike, P: VecLike, clamp = true): number {
// Inlined: Vec.Dist(P, Vec.NearestPointOnLineSegment(A, B, P, clamp))
// Computes the nearest point via parametric t-projection then returns the scalar distance,
// avoiding the intermediate Vec allocation that NearestPointOnLineSegment would create.
const dx = B.x - A.x
const dy = B.y - A.y
const d2 = dx * dx + dy * dy
Expand Down Expand Up @@ -511,6 +518,7 @@ export class Vec {
* two vectors, between -π and π. The sign indicates direction of angle.
*/
static AngleBetween(A: VecLike, B: VecLike): number {
// p = dot(A, B); n = |A| * |B| (uses x*x instead of Math.pow(x, 2))
const p = A.x * B.x + A.y * B.y
const n = Math.sqrt((A.x * A.x + A.y * A.y) * (B.x * B.x + B.y * B.y))
const sign = A.x * B.y - A.y * B.x < 0 ? -1 : 1
Expand All @@ -525,6 +533,7 @@ export class Vec {
* @returns The interpolated point.
*/
static Lrp(A: VecLike, B: VecLike, t: number): Vec {
// Inlined: Vec.Sub(B, A).mul(t).add(A) — note: only interpolates x/y, not z.
return new Vec(A.x + (B.x - A.x) * t, A.y + (B.y - A.y) * t)
}

Expand Down
5 changes: 4 additions & 1 deletion packages/editor/src/lib/primitives/geometry/Arc2d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ export class Arc2d extends Geometry2d {
if (t <= 0) return A
if (t >= 1) return B

// t is in (0,1), so the nearest point is the projection onto the arc
// Inlined: Vec.Sub(point, _center).uni().mul(radius).add(_center)
// When t is in (0,1), the nearest point is the radial projection of point onto the arc.
// Previously this also checked min-distance against A and B, but that's unnecessary when
// t is already in range — the radial projection is always closer.
const dx = point.x - _center.x
const dy = point.y - _center.y
const len = Math.sqrt(dx * dx + dy * dy)
Expand Down
7 changes: 7 additions & 0 deletions packages/editor/src/lib/primitives/geometry/Circle2d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ export class Circle2d extends Geometry2d {
}

nearestPoint(point: VecLike): Vec {
// Inlined: Vec.Sub(point, _center).uni().mul(radius).add(_center)
// Computes direction from center to point, normalizes, scales by radius, offsets by center.
const { _center, _radius: radius } = this
const dx = point.x - _center.x
const dy = point.y - _center.y
Expand All @@ -54,6 +56,9 @@ export class Circle2d extends Geometry2d {
}

override distanceToPoint(point: VecLike, hitInside = false): number {
// Inlined: Math.abs(Vec.Dist(point, _center) - radius)
// Computes distance from point to center, then subtracts radius for edge distance.
// Returns negative when inside a filled circle to indicate containment.
const { _center, _radius: radius } = this
const dx = point.x - _center.x
const dy = point.y - _center.y
Expand All @@ -66,6 +71,8 @@ export class Circle2d extends Geometry2d {
}

override hitTestPoint(point: VecLike, margin = 0, hitInside = false): boolean {
// Equivalent to: dist = Vec.Dist(point, _center); return dist within [radius - margin, radius + margin]
// Uses squared distances throughout to avoid any sqrt calls.
const { _center, _radius: radius } = this
const dx = point.x - _center.x
const dy = point.y - _center.y
Expand Down
7 changes: 6 additions & 1 deletion packages/editor/src/lib/primitives/geometry/Edge2d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export class Edge2d extends Geometry2d {
this._start = start
this._end = end

// Precomputed segment delta and squared length (replaces Vec.Sub(end, start) and Vec.Len2)
this._dx = end.x - start.x
this._dy = end.y - start.y
this._len2 = this._dx * this._dx + this._dy * this._dy
Expand All @@ -30,17 +31,21 @@ export class Edge2d extends Geometry2d {
}

override nearestPoint(point: VecLike): Vec {
// Inlined: Vec.NearestPointOnLineSegment(start, end, point)
// Uses precomputed dx/dy/len2 and parametric t-clamping instead of Vec allocations.
const { _start: start, _end: end, _dx: dx, _dy: dy, _len2: len2 } = this
if (len2 === 0) return start

// Parametric t = dot(P-A, B-A) / |B-A|^2, clamped to [0,1]
const t = ((point.x - start.x) * dx + (point.y - start.y) * dy) / len2
if (t <= 0) return start
if (t >= 1) return end
return new Vec(start.x + dx * t, start.y + dy * t)
}

override distanceToPoint(point: VecLike, _hitInside = false): number {
// Inlined: Vec.Dist(point, this.nearestPoint(point))
// Finds nearest point via parametric t-projection then returns scalar distance directly,
// avoiding the Vec allocation that nearestPoint would create.
const { _start: start, _end: end, _dx: dx, _dy: dy, _len2: len2 } = this
if (len2 === 0) return Vec.Dist(point, start)

Expand Down
3 changes: 3 additions & 0 deletions packages/editor/src/lib/primitives/geometry/Polyline2d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ export class Polyline2d extends Geometry2d {
}

nearestPoint(A: VecLike): Vec {
// Inlined: for each segment, Edge2d.nearestPoint(A) + Vec.Dist2(result, A), pick closest.
// Inlines the per-segment nearest-point math to avoid N Edge2d.nearestPoint Vec allocations;
// only allocates a single Vec at the end for the best result.
const { vertices } = this
let bestX = vertices[0].x
let bestY = vertices[0].y
Expand Down
7 changes: 7 additions & 0 deletions packages/editor/src/lib/primitives/intersect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,17 @@ export function intersectLineSegmentLineSegment(
const ub_t = AVx * ABy - AVy * ABx
const u_b = BVy * AVx - BVx * AVy

// These comparisons inline approximately(x, 0) and approximatelyLte(x, 0/1)
// to avoid 7+ function calls per invocation.
if (Math.abs(ua_t) <= precision || Math.abs(ub_t) <= precision) return null // coincident

if (Math.abs(u_b) <= precision) return null // parallel

const ua = ua_t / u_b
const ub = ub_t / u_b
// Inlined: approximately(ua, 0) && approximatelyLte(ua, 1) && same for ub
if (ua >= -precision && ua <= 1 + precision && ub >= -precision && ub <= 1 + precision) {
// Inlined: Vec.Lrp(a1, a2, ua) — i.e. a1 + ua * (a2 - a1)
return new Vec(a1.x + ua * AVx, a1.y + ua * AVy)
}

Expand All @@ -53,6 +57,8 @@ export function intersectLineSegmentLineSegment(
* @public
*/
export function intersectLineSegmentCircle(a1: VecLike, a2: VecLike, c: VecLike, r: number) {
// Precompute segment delta (dx, dy) and origin-to-center offset (ocx, ocy)
// to avoid repeated (a2.x - a1.x) and (a1.x - c.x) subexpressions.
const dx = a2.x - a1.x
const dy = a2.y - a1.y
const ocx = a1.x - c.x
Expand All @@ -75,6 +81,7 @@ export function intersectLineSegmentCircle(a1: VecLike, a2: VecLike, c: VecLike,

const result: VecLike[] = []

// Inlined: Vec.Lrp(a1, a2, u) — i.e. a1 + u * (a2 - a1)
if (u1 >= 0 && u1 <= 1) result.push(new Vec(a1.x + dx * u1, a1.y + dy * u1))
if (u2 >= 0 && u2 <= 1) result.push(new Vec(a1.x + dx * u2, a1.y + dy * u2))

Expand Down
5 changes: 4 additions & 1 deletion packages/editor/src/lib/primitives/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,13 +317,16 @@ function cross(x: VecLike, y: VecLike, z: VecLike): number {
* @public
*/
export function pointInPolygon(A: VecLike, points: VecLike[]): boolean {
// Uses winding number algorithm. Previously also had a per-edge check:
// if (Vec.Dist(A, a) + Vec.Dist(A, b) === Vec.Dist(a, b)) return true
// which tested if A lies exactly on edge (a, b). Removed because it cost 3 sqrts per vertex
// and exact float equality with sqrt results essentially never fires in practice.
let windingNumber = 0
let a: VecLike
let b: VecLike

for (let i = 0; i < points.length; i++) {
a = points[i]
// Point is the same as one of the corners of the polygon
if (a.x === A.x && a.y === A.y) return true

b = points[(i + 1) % points.length]
Expand Down
Loading