Perf: (slightly) faster min dist checks (#3401)

This PR improves a bunch of places where we do "minimum distance
checks". Previously, we were using `Vec.Dist`, which uses `Math.hypot`
to find the actual distance, but we can just as well use the squared
distance. So this PR makes a small improvement to `Vec.Dist2` and then
switches to that method when checking minimum distances.

### Change Type

- [x] `sdk` — Changes the tldraw SDK
- [x] `improvement` — Improving existing features


### Test Plan

- [x] Unit Tests

### Release Notes

- Performance: small improvements to hit testing.
This commit is contained in:
Steve Ruiz 2024-04-08 14:31:05 +01:00 committed by GitHub
parent 947f7b1d76
commit fb2d3b4372
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 57 additions and 47 deletions

View file

@ -514,7 +514,7 @@ export function degreesToRadians(d: number): number;
export const DOUBLE_CLICK_DURATION = 450;
// @internal (undocumented)
export const DRAG_DISTANCE = 4;
export const DRAG_DISTANCE = 16;
// @public (undocumented)
export const EASINGS: {

View file

@ -34,10 +34,10 @@ export const DOUBLE_CLICK_DURATION = 450
export const MULTI_CLICK_DURATION = 200
/** @internal */
export const COARSE_DRAG_DISTANCE = 6
export const COARSE_DRAG_DISTANCE = 36 // 6 squared
/** @internal */
export const DRAG_DISTANCE = 4
export const DRAG_DISTANCE = 16 // 4 squared
/** @internal */
export const SVG_PADDING = 32

View file

@ -8594,7 +8594,7 @@ export class Editor extends EventEmitter<TLEventMap> {
if (
!inputs.isDragging &&
inputs.isPointing &&
originPagePoint.dist(currentPagePoint) >
Vec.Dist2(originPagePoint, currentPagePoint) >
(this.getInstanceState().isCoarsePointer ? COARSE_DRAG_DISTANCE : DRAG_DISTANCE) /
this.getZoomLevel()
) {
@ -8684,7 +8684,7 @@ export class Editor extends EventEmitter<TLEventMap> {
if (
!inputs.isDragging &&
inputs.isPointing &&
originPagePoint.dist(currentPagePoint) >
Vec.Dist2(originPagePoint, currentPagePoint) >
(this.getInstanceState().isCoarsePointer ? COARSE_DRAG_DISTANCE : DRAG_DISTANCE) /
this.getZoomLevel()
) {

View file

@ -227,7 +227,7 @@ export class ClickManager {
if (
this._clickState !== 'idle' &&
this._clickScreenPoint &&
this._clickScreenPoint.dist(this.editor.inputs.currentScreenPoint) >
Vec.Dist2(this._clickScreenPoint, this.editor.inputs.currentScreenPoint) >
(this.editor.getInstanceState().isCoarsePointer ? COARSE_DRAG_DISTANCE : DRAG_DISTANCE)
) {
this.cancelDoubleClickTimeout()

View file

@ -308,19 +308,20 @@ export class Vec {
static Per(A: VecLike): Vec {
return new Vec(A.y, -A.x)
}
static Dist2(A: VecLike, B: VecLike): number {
return Vec.Sub(A, B).len2()
}
static Abs(A: VecLike): Vec {
return new Vec(Math.abs(A.x), Math.abs(A.y))
}
// Get the distance between two points.
static Dist(A: VecLike, B: VecLike): number {
return Math.hypot(A.y - B.y, A.x - B.x)
}
// Get the squared distance between two points. This is faster to calculate (no square root) so useful for "minimum distance" checks where the actual measurement does not matter.
static Dist2(A: VecLike, B: VecLike): number {
return (A.x - B.x) * (A.x - B.x) + (A.y - B.y) * (A.y - B.y)
}
/**
* Dot product of two vectors which is used to calculate the angle between them.
*/

View file

@ -52,15 +52,16 @@ export class Arc2d extends Geometry2d {
// Get the point (P) on the arc, then pick the nearest of A, B, and P
const P = _center.clone().add(point.clone().sub(_center).uni().mul(radius))
let distance = Infinity
let nearest: Vec | undefined
for (const pt of [A, B, P]) {
if (point.dist(pt) < distance) {
nearest = pt
distance = point.dist(pt)
let dist = Infinity
let d: number
for (const p of [A, B, P]) {
d = Vec.Dist2(point, p)
if (d < dist) {
nearest = p
dist = d
}
}
if (!nearest) throw Error('nearest point not found')
return nearest
}

View file

@ -55,9 +55,11 @@ export class CubicBezier2d extends Polyline2d {
nearestPoint(A: Vec): Vec {
let nearest: Vec | undefined
let dist = Infinity
let d: number
let p: Vec
for (const edge of this.segments) {
const p = edge.nearestPoint(A)
const d = p.dist(A)
p = edge.nearestPoint(A)
d = Vec.Dist2(p, A)
if (d < dist) {
nearest = p
dist = d

View file

@ -67,15 +67,16 @@ export class CubicSpline2d extends Geometry2d {
nearestPoint(A: Vec): Vec {
let nearest: Vec | undefined
let dist = Infinity
let d: number
let p: Vec
for (const segment of this.segments) {
const p = segment.nearestPoint(A)
const d = p.dist(A)
p = segment.nearestPoint(A)
d = Vec.Dist2(p, A)
if (d < dist) {
nearest = p
dist = d
}
}
if (!nearest) throw Error('nearest point not found')
return nearest
}

View file

@ -76,15 +76,16 @@ export class Ellipse2d extends Geometry2d {
nearestPoint(A: Vec): Vec {
let nearest: Vec | undefined
let dist = Infinity
let d: number
let p: Vec
for (const edge of this.edges) {
const p = edge.nearestPoint(A)
const d = p.dist(A)
p = edge.nearestPoint(A)
d = Vec.Dist2(p, A)
if (d < dist) {
nearest = p
dist = d
}
}
if (!nearest) throw Error('nearest point not found')
return nearest
}

View file

@ -55,14 +55,17 @@ export abstract class Geometry2d {
}
nearestPointOnLineSegment(A: Vec, B: Vec): Vec {
let distance = Infinity
const { vertices } = this
let nearest: Vec | undefined
for (let i = 0; i < this.vertices.length; i++) {
const point = this.vertices[i]
const d = Vec.DistanceToLineSegment(A, B, point)
if (d < distance) {
distance = d
nearest = point
let dist = Infinity
let d: number
let p: Vec
for (let i = 0; i < vertices.length; i++) {
p = vertices[i]
d = Vec.DistanceToLineSegment(A, B, p)
if (d < dist) {
dist = d
nearest = p
}
}
if (!nearest) throw Error('nearest point not found')

View file

@ -30,8 +30,8 @@ export class Group2d extends Geometry2d {
}
override nearestPoint(point: Vec): Vec {
let d = Infinity
let p: Vec | undefined
let dist = Infinity
let nearest: Vec | undefined
const { children } = this
@ -39,16 +39,18 @@ export class Group2d extends Geometry2d {
throw Error('no children')
}
let p: Vec
let d: number
for (const child of children) {
const nearest = child.nearestPoint(point)
const dist = nearest.dist(point)
if (dist < d) {
d = dist
p = nearest
p = child.nearestPoint(point)
d = Vec.Dist2(p, point)
if (d < dist) {
dist = d
nearest = p
}
}
if (!p) throw Error('nearest point not found')
return p
if (!nearest) throw Error('nearest point not found')
return nearest
}
override distanceToPoint(point: Vec, hitInside = false) {

View file

@ -51,18 +51,17 @@ export class Polyline2d extends Geometry2d {
const { segments } = this
let nearest = this.points[0]
let dist = Infinity
let p: Vec // current point on segment
let d: number // distance from A to p
for (let i = 0; i < segments.length; i++) {
p = segments[i].nearestPoint(A)
d = p.dist(A)
d = Vec.Dist2(p, A)
if (d < dist) {
nearest = p
dist = d
}
}
if (!nearest) throw Error('nearest point not found')
return nearest
}

View file

@ -4231,7 +4231,7 @@
{
"kind": "Property",
"canonicalReference": "@tldraw/store!Store#onBeforeChange:member",
"docComment": "/**\n * A callback before after each record's change.\n *\n * @param prev - The previous value, if any.\n *\n * @param next - The next value.\n */\n",
"docComment": "/**\n * A callback fired before each record's change.\n *\n * @param prev - The previous value, if any.\n *\n * @param next - The next value.\n */\n",
"excerptTokens": [
{
"kind": "Content",

View file

@ -309,7 +309,7 @@ export class Drawing extends StateNode {
}
const hasMovedFarEnough =
Vec.Dist(pagePointWhereNextSegmentChanged, inputs.currentPagePoint) > DRAG_DISTANCE
Vec.Dist2(pagePointWhereNextSegmentChanged, inputs.currentPagePoint) > DRAG_DISTANCE
// Find the distance from where the pointer was when shift was released and
// where it is now; if it's far enough away, then update the page point where
@ -382,7 +382,7 @@ export class Drawing extends StateNode {
}
const hasMovedFarEnough =
Vec.Dist(pagePointWhereNextSegmentChanged, inputs.currentPagePoint) > DRAG_DISTANCE
Vec.Dist2(pagePointWhereNextSegmentChanged, inputs.currentPagePoint) > DRAG_DISTANCE
// Find the distance from where the pointer was when shift was released and
// where it is now; if it's far enough away, then update the page point where