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:
parent
947f7b1d76
commit
fb2d3b4372
14 changed files with 57 additions and 47 deletions
|
@ -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: {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
) {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue