Adds ellipse binding
This commit is contained in:
parent
94f0273ca4
commit
b737a42ca9
5 changed files with 281 additions and 443 deletions
|
@ -4,10 +4,7 @@ import { Utils } from './utils'
|
|||
|
||||
/* ----------------- Start Copy Here ---------------- */
|
||||
|
||||
function getIntersection(
|
||||
message: string,
|
||||
...points: number[][]
|
||||
): TLIntersection {
|
||||
function getIntersection(message: string, ...points: number[][]): TLIntersection {
|
||||
const didIntersect = points.length > 0
|
||||
return { didIntersect, message, points }
|
||||
}
|
||||
|
@ -15,12 +12,7 @@ function getIntersection(
|
|||
export class Intersect {
|
||||
static ray = {
|
||||
// Intersect a ray with a ray.
|
||||
ray(
|
||||
p0: number[],
|
||||
n0: number[],
|
||||
p1: number[],
|
||||
n1: number[]
|
||||
): TLIntersection {
|
||||
ray(p0: number[], n0: number[], p1: number[], n1: number[]): TLIntersection {
|
||||
const dx = p1[0] - p0[0]
|
||||
const dy = p1[1] - p0[1]
|
||||
const det = n1[0] * n0[1] - n1[1] * n0[0]
|
||||
|
@ -41,12 +33,7 @@ export class Intersect {
|
|||
},
|
||||
|
||||
// Interseg a ray with a line segment.
|
||||
lineSegment(
|
||||
origin: number[],
|
||||
direction: number[],
|
||||
a1: number[],
|
||||
a2: number[]
|
||||
): TLIntersection {
|
||||
lineSegment(origin: number[], direction: number[], a1: number[], a2: number[]): TLIntersection {
|
||||
const [x, y] = origin
|
||||
const [dx, dy] = direction
|
||||
const [x1, y1] = a1
|
||||
|
@ -70,9 +57,10 @@ export class Intersect {
|
|||
origin: number[],
|
||||
direction: number[],
|
||||
point: number[],
|
||||
size: number[]
|
||||
size: number[],
|
||||
rotation = 0
|
||||
): TLIntersection[] {
|
||||
return Intersect.rectangle.ray(point, size, origin, direction)
|
||||
return Intersect.rectangle.ray(point, size, rotation, origin, direction)
|
||||
},
|
||||
|
||||
// Intersect a ray with an ellipse.
|
||||
|
@ -93,36 +81,22 @@ export class Intersect {
|
|||
bounds(
|
||||
origin: number[],
|
||||
direction: number[],
|
||||
bounds: TLBounds
|
||||
bounds: TLBounds,
|
||||
rotation = 0
|
||||
): TLIntersection[] {
|
||||
const { minX, minY, width, height } = bounds
|
||||
return Intersect.ray.rectangle(
|
||||
origin,
|
||||
direction,
|
||||
[minX, minY],
|
||||
[width, height]
|
||||
)
|
||||
return Intersect.ray.rectangle(origin, direction, [minX, minY], [width, height], rotation)
|
||||
},
|
||||
}
|
||||
|
||||
static lineSegment = {
|
||||
// Intersect a line segment with a ray.
|
||||
ray(
|
||||
a1: number[],
|
||||
a2: number[],
|
||||
origin: number[],
|
||||
direction: number[]
|
||||
): TLIntersection {
|
||||
ray(a1: number[], a2: number[], origin: number[], direction: number[]): TLIntersection {
|
||||
return Intersect.ray.lineSegment(origin, direction, a1, a2)
|
||||
},
|
||||
|
||||
// Intersect a line segment with a line segment.
|
||||
lineSegment(
|
||||
a1: number[],
|
||||
a2: number[],
|
||||
b1: number[],
|
||||
b2: number[]
|
||||
): TLIntersection {
|
||||
lineSegment(a1: number[], a2: number[], b1: number[], b2: number[]): TLIntersection {
|
||||
const AB = Vec.sub(a1, b1)
|
||||
const BV = Vec.sub(b2, b1)
|
||||
const AV = Vec.sub(a2, a1)
|
||||
|
@ -151,12 +125,7 @@ export class Intersect {
|
|||
},
|
||||
|
||||
// Intersect a line segment with a rectangle
|
||||
rectangle(
|
||||
a1: number[],
|
||||
a2: number[],
|
||||
point: number[],
|
||||
size: number[]
|
||||
): TLIntersection[] {
|
||||
rectangle(a1: number[], a2: number[], point: number[], size: number[]): TLIntersection[] {
|
||||
return Intersect.rectangle.lineSegment(point, size, a1, a2)
|
||||
},
|
||||
|
||||
|
@ -171,14 +140,7 @@ export class Intersect {
|
|||
): TLIntersection {
|
||||
const sa = Vec.angle(center, start)
|
||||
const ea = Vec.angle(center, end)
|
||||
const ellipseTest = Intersect.ellipse.lineSegment(
|
||||
center,
|
||||
radius,
|
||||
radius,
|
||||
0,
|
||||
a1,
|
||||
a2
|
||||
)
|
||||
const ellipseTest = Intersect.ellipse.lineSegment(center, radius, radius, 0, a1, a2)
|
||||
|
||||
if (!ellipseTest.didIntersect) return getIntersection('No intersection')
|
||||
|
||||
|
@ -195,11 +157,8 @@ export class Intersect {
|
|||
|
||||
// Intersect a line segment with a circle.
|
||||
circle(a1: number[], a2: number[], c: number[], r: number): TLIntersection {
|
||||
const a =
|
||||
(a2[0] - a1[0]) * (a2[0] - a1[0]) + (a2[1] - a1[1]) * (a2[1] - a1[1])
|
||||
const b =
|
||||
2 *
|
||||
((a2[0] - a1[0]) * (a1[0] - c[0]) + (a2[1] - a1[1]) * (a1[1] - c[1]))
|
||||
const a = (a2[0] - a1[0]) * (a2[0] - a1[0]) + (a2[1] - a1[1]) * (a2[1] - a1[1])
|
||||
const b = 2 * ((a2[0] - a1[0]) * (a1[0] - c[0]) + (a2[1] - a1[1]) * (a1[1] - c[1]))
|
||||
const cc =
|
||||
c[0] * c[0] +
|
||||
c[1] * c[1] +
|
||||
|
@ -262,8 +221,7 @@ export class Intersect {
|
|||
const diff = Vec.sub(a2, a1)
|
||||
|
||||
const A = (diff[0] * diff[0]) / rx / rx + (diff[1] * diff[1]) / ry / ry
|
||||
const B =
|
||||
(2 * a1[0] * diff[0]) / rx / rx + (2 * a1[1] * diff[1]) / ry / ry
|
||||
const B = (2 * a1[0] * diff[0]) / rx / rx + (2 * a1[1] * diff[1]) / ry / ry
|
||||
const C = (a1[0] * a1[0]) / rx / rx + (a1[1] * a1[1]) / ry / ry - 1
|
||||
|
||||
// Make a list of t values (normalized points on the line where intersections occur).
|
||||
|
@ -323,18 +281,14 @@ export class Intersect {
|
|||
ray(
|
||||
point: number[],
|
||||
size: number[],
|
||||
rotation: number,
|
||||
origin: number[],
|
||||
direction: number[]
|
||||
): TLIntersection[] {
|
||||
const sideIntersections = Utils.getRectangleSides(point, size).reduce<
|
||||
const sideIntersections = Utils.getRectangleSides(point, size, rotation).reduce<
|
||||
TLIntersection[]
|
||||
>((acc, [message, [a1, a2]]) => {
|
||||
const intersection = Intersect.ray.lineSegment(
|
||||
origin,
|
||||
direction,
|
||||
a1,
|
||||
a2
|
||||
)
|
||||
const intersection = Intersect.ray.lineSegment(origin, direction, a1, a2)
|
||||
|
||||
if (intersection) {
|
||||
acc.push(getIntersection(message, ...intersection.points))
|
||||
|
@ -347,23 +301,19 @@ export class Intersect {
|
|||
},
|
||||
|
||||
// Intersect a rectangle with a line segment.
|
||||
lineSegment(
|
||||
point: number[],
|
||||
size: number[],
|
||||
a1: number[],
|
||||
a2: number[]
|
||||
): TLIntersection[] {
|
||||
const sideIntersections = Utils.getRectangleSides(point, size).reduce<
|
||||
TLIntersection[]
|
||||
>((acc, [message, [b1, b2]]) => {
|
||||
const intersection = Intersect.lineSegment.lineSegment(a1, a2, b1, b2)
|
||||
lineSegment(point: number[], size: number[], a1: number[], a2: number[]): TLIntersection[] {
|
||||
const sideIntersections = Utils.getRectangleSides(point, size).reduce<TLIntersection[]>(
|
||||
(acc, [message, [b1, b2]]) => {
|
||||
const intersection = Intersect.lineSegment.lineSegment(a1, a2, b1, b2)
|
||||
|
||||
if (intersection) {
|
||||
acc.push(getIntersection(message, ...intersection.points))
|
||||
}
|
||||
if (intersection) {
|
||||
acc.push(getIntersection(message, ...intersection.points))
|
||||
}
|
||||
|
||||
return acc
|
||||
}, [])
|
||||
return acc
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
return sideIntersections.filter((int) => int.didIntersect)
|
||||
},
|
||||
|
@ -375,24 +325,20 @@ export class Intersect {
|
|||
point2: number[],
|
||||
size2: number[]
|
||||
): TLIntersection[] {
|
||||
const sideIntersections = Utils.getRectangleSides(point1, size1).reduce<
|
||||
TLIntersection[]
|
||||
>((acc, [message, [a1, a2]]) => {
|
||||
const intersections = Intersect.rectangle.lineSegment(
|
||||
point2,
|
||||
size2,
|
||||
a1,
|
||||
a2
|
||||
)
|
||||
const sideIntersections = Utils.getRectangleSides(point1, size1).reduce<TLIntersection[]>(
|
||||
(acc, [message, [a1, a2]]) => {
|
||||
const intersections = Intersect.rectangle.lineSegment(point2, size2, a1, a2)
|
||||
|
||||
acc.push(
|
||||
...intersections.map((int) =>
|
||||
getIntersection(`${message} ${int.message}`, ...int.points)
|
||||
acc.push(
|
||||
...intersections.map((int) =>
|
||||
getIntersection(`${message} ${int.message}`, ...int.points)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
return acc
|
||||
}, [])
|
||||
return acc
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
return sideIntersections.filter((int) => int.didIntersect)
|
||||
},
|
||||
|
@ -406,46 +352,36 @@ export class Intersect {
|
|||
start: number[],
|
||||
end: number[]
|
||||
): TLIntersection[] {
|
||||
const sideIntersections = Utils.getRectangleSides(point, size).reduce<
|
||||
TLIntersection[]
|
||||
>((acc, [message, [a1, a2]]) => {
|
||||
const intersection = Intersect.arc.lineSegment(
|
||||
center,
|
||||
radius,
|
||||
start,
|
||||
end,
|
||||
a1,
|
||||
a2
|
||||
)
|
||||
const sideIntersections = Utils.getRectangleSides(point, size).reduce<TLIntersection[]>(
|
||||
(acc, [message, [a1, a2]]) => {
|
||||
const intersection = Intersect.arc.lineSegment(center, radius, start, end, a1, a2)
|
||||
|
||||
if (intersection) {
|
||||
acc.push({ ...intersection, message })
|
||||
}
|
||||
if (intersection) {
|
||||
acc.push({ ...intersection, message })
|
||||
}
|
||||
|
||||
return acc
|
||||
}, [])
|
||||
return acc
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
return sideIntersections.filter((int) => int.didIntersect)
|
||||
},
|
||||
|
||||
// Intersect a rectangle with a circle.
|
||||
circle(
|
||||
point: number[],
|
||||
size: number[],
|
||||
c: number[],
|
||||
r: number
|
||||
): TLIntersection[] {
|
||||
const sideIntersections = Utils.getRectangleSides(point, size).reduce<
|
||||
TLIntersection[]
|
||||
>((acc, [message, [a1, a2]]) => {
|
||||
const intersection = Intersect.lineSegment.circle(a1, a2, c, r)
|
||||
circle(point: number[], size: number[], c: number[], r: number): TLIntersection[] {
|
||||
const sideIntersections = Utils.getRectangleSides(point, size).reduce<TLIntersection[]>(
|
||||
(acc, [message, [a1, a2]]) => {
|
||||
const intersection = Intersect.lineSegment.circle(a1, a2, c, r)
|
||||
|
||||
if (intersection) {
|
||||
acc.push({ ...intersection, message })
|
||||
}
|
||||
if (intersection) {
|
||||
acc.push({ ...intersection, message })
|
||||
}
|
||||
|
||||
return acc
|
||||
}, [])
|
||||
return acc
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
return sideIntersections.filter((int) => int.didIntersect)
|
||||
},
|
||||
|
@ -459,62 +395,42 @@ export class Intersect {
|
|||
ry: number,
|
||||
rotation = 0
|
||||
): TLIntersection[] {
|
||||
const sideIntersections = Utils.getRectangleSides(point, size).reduce<
|
||||
TLIntersection[]
|
||||
>((acc, [message, [a1, a2]]) => {
|
||||
const intersection = Intersect.lineSegment.ellipse(
|
||||
a1,
|
||||
a2,
|
||||
c,
|
||||
rx,
|
||||
ry,
|
||||
rotation
|
||||
)
|
||||
const sideIntersections = Utils.getRectangleSides(point, size).reduce<TLIntersection[]>(
|
||||
(acc, [message, [a1, a2]]) => {
|
||||
const intersection = Intersect.lineSegment.ellipse(a1, a2, c, rx, ry, rotation)
|
||||
|
||||
if (intersection) {
|
||||
acc.push({ ...intersection, message })
|
||||
}
|
||||
if (intersection) {
|
||||
acc.push({ ...intersection, message })
|
||||
}
|
||||
|
||||
return acc
|
||||
}, [])
|
||||
return acc
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
return sideIntersections.filter((int) => int.didIntersect)
|
||||
},
|
||||
|
||||
// Intersect a rectangle with a bounding box.
|
||||
bounds(
|
||||
point: number[],
|
||||
size: number[],
|
||||
bounds: TLBounds
|
||||
): TLIntersection[] {
|
||||
bounds(point: number[], size: number[], bounds: TLBounds): TLIntersection[] {
|
||||
const { minX, minY, width, height } = bounds
|
||||
return Intersect.rectangle.rectangle(
|
||||
point,
|
||||
size,
|
||||
[minX, minY],
|
||||
[width, height]
|
||||
)
|
||||
return Intersect.rectangle.rectangle(point, size, [minX, minY], [width, height])
|
||||
},
|
||||
|
||||
// Intersect a rectangle with a polyline
|
||||
polyline(
|
||||
point: number[],
|
||||
size: number[],
|
||||
points: number[][]
|
||||
): TLIntersection[] {
|
||||
const sideIntersections = Utils.getRectangleSides(point, size).reduce<
|
||||
TLIntersection[]
|
||||
>((acc, [message, [a1, a2]]) => {
|
||||
const intersections = Intersect.lineSegment.polyline(a1, a2, points)
|
||||
polyline(point: number[], size: number[], points: number[][]): TLIntersection[] {
|
||||
const sideIntersections = Utils.getRectangleSides(point, size).reduce<TLIntersection[]>(
|
||||
(acc, [message, [a1, a2]]) => {
|
||||
const intersections = Intersect.lineSegment.polyline(a1, a2, points)
|
||||
|
||||
if (intersections.length > 0) {
|
||||
acc.push(
|
||||
getIntersection(message, ...intersections.flatMap((i) => i.points))
|
||||
)
|
||||
}
|
||||
if (intersections.length > 0) {
|
||||
acc.push(getIntersection(message, ...intersections.flatMap((i) => i.points)))
|
||||
}
|
||||
|
||||
return acc
|
||||
}, [])
|
||||
return acc
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
return sideIntersections.filter((int) => int.didIntersect)
|
||||
},
|
||||
|
@ -554,25 +470,13 @@ export class Intersect {
|
|||
bounds: TLBounds
|
||||
): TLIntersection[] {
|
||||
const { minX, minY, width, height } = bounds
|
||||
return Intersect.arc.rectangle(
|
||||
center,
|
||||
radius,
|
||||
start,
|
||||
end,
|
||||
[minX, minY],
|
||||
[width, height]
|
||||
)
|
||||
return Intersect.arc.rectangle(center, radius, start, end, [minX, minY], [width, height])
|
||||
},
|
||||
}
|
||||
|
||||
static circle = {
|
||||
// Intersect a circle with a line segment.
|
||||
lineSegment(
|
||||
c: number[],
|
||||
r: number,
|
||||
a1: number[],
|
||||
a2: number[]
|
||||
): TLIntersection {
|
||||
lineSegment(c: number[], r: number, a1: number[], a2: number[]): TLIntersection {
|
||||
return Intersect.lineSegment.circle(a1, a2, c, r)
|
||||
},
|
||||
|
||||
|
@ -596,12 +500,7 @@ export class Intersect {
|
|||
},
|
||||
|
||||
// Intersect a circle with a rectangle.
|
||||
rectangle(
|
||||
c: number[],
|
||||
r: number,
|
||||
point: number[],
|
||||
size: number[]
|
||||
): TLIntersection[] {
|
||||
rectangle(c: number[], r: number, point: number[], size: number[]): TLIntersection[] {
|
||||
return Intersect.rectangle.circle(point, size, c, r)
|
||||
},
|
||||
|
||||
|
@ -693,58 +592,24 @@ export class Intersect {
|
|||
bounds: TLBounds
|
||||
): TLIntersection[] {
|
||||
const { minX, minY, width, height } = bounds
|
||||
return Intersect.ellipse.rectangle(
|
||||
c,
|
||||
rx,
|
||||
ry,
|
||||
rotation,
|
||||
[minX, minY],
|
||||
[width, height]
|
||||
)
|
||||
return Intersect.ellipse.rectangle(c, rx, ry, rotation, [minX, minY], [width, height])
|
||||
},
|
||||
}
|
||||
|
||||
static bounds = {
|
||||
ray(
|
||||
bounds: TLBounds,
|
||||
origin: number[],
|
||||
direction: number[]
|
||||
): TLIntersection[] {
|
||||
ray(bounds: TLBounds, origin: number[], direction: number[]): TLIntersection[] {
|
||||
const { minX, minY, width, height } = bounds
|
||||
return Intersect.ray.rectangle(
|
||||
origin,
|
||||
direction,
|
||||
[minX, minY],
|
||||
[width, height]
|
||||
)
|
||||
return Intersect.ray.rectangle(origin, direction, [minX, minY], [width, height])
|
||||
},
|
||||
|
||||
lineSegment(
|
||||
bounds: TLBounds,
|
||||
a1: number[],
|
||||
a2: number[]
|
||||
): TLIntersection[] {
|
||||
lineSegment(bounds: TLBounds, a1: number[], a2: number[]): TLIntersection[] {
|
||||
const { minX, minY, width, height } = bounds
|
||||
return Intersect.lineSegment.rectangle(
|
||||
a1,
|
||||
a2,
|
||||
[minX, minY],
|
||||
[width, height]
|
||||
)
|
||||
return Intersect.lineSegment.rectangle(a1, a2, [minX, minY], [width, height])
|
||||
},
|
||||
|
||||
rectangle(
|
||||
bounds: TLBounds,
|
||||
point: number[],
|
||||
size: number[]
|
||||
): TLIntersection[] {
|
||||
rectangle(bounds: TLBounds, point: number[], size: number[]): TLIntersection[] {
|
||||
const { minX, minY, width, height } = bounds
|
||||
return Intersect.rectangle.rectangle(
|
||||
point,
|
||||
size,
|
||||
[minX, minY],
|
||||
[width, height]
|
||||
)
|
||||
return Intersect.rectangle.rectangle(point, size, [minX, minY], [width, height])
|
||||
},
|
||||
|
||||
bounds(bounds1: TLBounds, bounds2: TLBounds): TLIntersection[] {
|
||||
|
@ -764,14 +629,7 @@ export class Intersect {
|
|||
end: number[]
|
||||
): TLIntersection[] {
|
||||
const { minX, minY, width, height } = bounds
|
||||
return Intersect.arc.rectangle(
|
||||
center,
|
||||
radius,
|
||||
start,
|
||||
end,
|
||||
[minX, minY],
|
||||
[width, height]
|
||||
)
|
||||
return Intersect.arc.rectangle(center, radius, start, end, [minX, minY], [width, height])
|
||||
},
|
||||
|
||||
circle(bounds: TLBounds, c: number[], r: number): TLIntersection[] {
|
||||
|
@ -779,22 +637,9 @@ export class Intersect {
|
|||
return Intersect.circle.rectangle(c, r, [minX, minY], [width, height])
|
||||
},
|
||||
|
||||
ellipse(
|
||||
bounds: TLBounds,
|
||||
c: number[],
|
||||
rx: number,
|
||||
ry: number,
|
||||
rotation = 0
|
||||
): TLIntersection[] {
|
||||
ellipse(bounds: TLBounds, c: number[], rx: number, ry: number, rotation = 0): TLIntersection[] {
|
||||
const { minX, minY, width, height } = bounds
|
||||
return Intersect.ellipse.rectangle(
|
||||
c,
|
||||
rx,
|
||||
ry,
|
||||
rotation,
|
||||
[minX, minY],
|
||||
[width, height]
|
||||
)
|
||||
return Intersect.ellipse.rectangle(c, rx, ry, rotation, [minX, minY], [width, height])
|
||||
},
|
||||
|
||||
polyline(bounds: TLBounds, points: number[][]): TLIntersection[] {
|
||||
|
@ -804,20 +649,12 @@ export class Intersect {
|
|||
|
||||
static polyline = {
|
||||
// Intersect a polyline with a line segment.
|
||||
lineSegment(
|
||||
points: number[][],
|
||||
a1: number[],
|
||||
a2: number[]
|
||||
): TLIntersection[] {
|
||||
lineSegment(points: number[][], a1: number[], a2: number[]): TLIntersection[] {
|
||||
return Intersect.lineSegment.polyline(a1, a2, points)
|
||||
},
|
||||
|
||||
// Interesct a polyline with a rectangle.
|
||||
rectangle(
|
||||
points: number[][],
|
||||
point: number[],
|
||||
size: number[]
|
||||
): TLIntersection[] {
|
||||
rectangle(points: number[][], point: number[], size: number[]): TLIntersection[] {
|
||||
return Intersect.rectangle.polyline(point, size, points)
|
||||
},
|
||||
|
||||
|
|
|
@ -4,12 +4,7 @@
|
|||
import type React from 'react'
|
||||
import deepmerge from 'deepmerge'
|
||||
import isMobilePkg from 'ismobilejs'
|
||||
import {
|
||||
TLBezierCurveSegment,
|
||||
TLBounds,
|
||||
TLBoundsCorner,
|
||||
TLBoundsEdge,
|
||||
} from '../types'
|
||||
import { TLBezierCurveSegment, TLBounds, TLBoundsCorner, TLBoundsEdge } from '../types'
|
||||
import vec from './vec'
|
||||
import './polyfills'
|
||||
|
||||
|
@ -22,9 +17,7 @@ export class Utils {
|
|||
obj: T,
|
||||
fn: (entry: Entry<T>, i?: number, arr?: Entry<T>[]) => boolean
|
||||
) {
|
||||
return Object.fromEntries(
|
||||
(Object.entries(obj) as Entry<T>[]).filter(fn)
|
||||
) as Partial<T>
|
||||
return Object.fromEntries((Object.entries(obj) as Entry<T>[]).filter(fn)) as Partial<T>
|
||||
}
|
||||
|
||||
static deepMerge<T>(a: T, b: DeepPartial<T>): T {
|
||||
|
@ -52,29 +45,16 @@ export class Utils {
|
|||
*```
|
||||
*/
|
||||
|
||||
static lerpColor(
|
||||
color1: string,
|
||||
color2: string,
|
||||
factor = 0.5
|
||||
): string | undefined {
|
||||
static lerpColor(color1: string, color2: string, factor = 0.5): string | undefined {
|
||||
function h2r(hex: string) {
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
||||
return result
|
||||
? [
|
||||
parseInt(result[1], 16),
|
||||
parseInt(result[2], 16),
|
||||
parseInt(result[3], 16),
|
||||
]
|
||||
? [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)]
|
||||
: null
|
||||
}
|
||||
|
||||
function r2h(rgb: number[]) {
|
||||
return (
|
||||
'#' +
|
||||
((1 << 24) + (rgb[0] << 16) + (rgb[1] << 8) + rgb[2])
|
||||
.toString(16)
|
||||
.slice(1)
|
||||
)
|
||||
return '#' + ((1 << 24) + (rgb[0] << 16) + (rgb[1] << 8) + rgb[2]).toString(16).slice(1)
|
||||
}
|
||||
|
||||
const c1 = h2r(color1) || [0, 0, 0]
|
||||
|
@ -96,12 +76,7 @@ export class Utils {
|
|||
* @param rangeB to [low, high]
|
||||
* @param clamp
|
||||
*/
|
||||
static modulate(
|
||||
value: number,
|
||||
rangeA: number[],
|
||||
rangeB: number[],
|
||||
clamp = false
|
||||
): number {
|
||||
static modulate(value: number, rangeA: number[], rangeB: number[], clamp = false): number {
|
||||
const [fromLow, fromHigh] = rangeA
|
||||
const [v0, v1] = rangeB
|
||||
const result = v0 + ((value - fromLow) / (fromHigh - fromLow)) * (v1 - v0)
|
||||
|
@ -193,14 +168,12 @@ export class Utils {
|
|||
|
||||
/* ---------------------- Boxes --------------------- */
|
||||
|
||||
static getRectangleSides(
|
||||
point: number[],
|
||||
size: number[]
|
||||
): [string, number[][]][] {
|
||||
const tl = point
|
||||
const tr = vec.add(point, [size[0], 0])
|
||||
const br = vec.add(point, size)
|
||||
const bl = vec.add(point, [0, size[1]])
|
||||
static getRectangleSides(point: number[], size: number[], rotation = 0): [string, number[][]][] {
|
||||
const center = [point[0] + size[0] / 2, point[1] + size[1] / 2]
|
||||
const tl = vec.rotWith(point, center, rotation)
|
||||
const tr = vec.rotWith(vec.add(point, [size[0], 0]), center, rotation)
|
||||
const br = vec.rotWith(vec.add(point, size), center, rotation)
|
||||
const bl = vec.rotWith(vec.add(point, [0, size[1]]), center, rotation)
|
||||
|
||||
return [
|
||||
['top', [tl, tr]],
|
||||
|
@ -211,16 +184,10 @@ export class Utils {
|
|||
}
|
||||
|
||||
static getBoundsSides(bounds: TLBounds): [string, number[][]][] {
|
||||
return this.getRectangleSides(
|
||||
[bounds.minX, bounds.minY],
|
||||
[bounds.width, bounds.height]
|
||||
)
|
||||
return this.getRectangleSides([bounds.minX, bounds.minY], [bounds.width, bounds.height])
|
||||
}
|
||||
|
||||
static shallowEqual<T extends Record<string, unknown>>(
|
||||
objA: T,
|
||||
objB: T
|
||||
): boolean {
|
||||
static shallowEqual<T extends Record<string, unknown>>(objA: T, objB: T): boolean {
|
||||
if (objA === objB) return true
|
||||
|
||||
if (!objA || !objB) return false
|
||||
|
@ -234,10 +201,7 @@ export class Utils {
|
|||
for (let i = 0; i < len; i++) {
|
||||
const key = aKeys[i]
|
||||
|
||||
if (
|
||||
objA[key] !== objB[key] ||
|
||||
!Object.prototype.hasOwnProperty.call(objB, key)
|
||||
) {
|
||||
if (objA[key] !== objB[key] || !Object.prototype.hasOwnProperty.call(objB, key)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -320,11 +284,7 @@ export class Utils {
|
|||
* @param r The circle's radius.
|
||||
* @param P The point.
|
||||
*/
|
||||
static getClosestPointOnCircle(
|
||||
C: number[],
|
||||
r: number,
|
||||
P: number[]
|
||||
): number[] {
|
||||
static getClosestPointOnCircle(C: number[], r: number, P: number[]): number[] {
|
||||
const v = vec.sub(C, P)
|
||||
return vec.sub(C, vec.mul(vec.div(v, vec.len(v)), r))
|
||||
}
|
||||
|
@ -336,11 +296,7 @@ export class Utils {
|
|||
* @param C
|
||||
* @returns [x, y, r]
|
||||
*/
|
||||
static circleFromThreePoints(
|
||||
A: number[],
|
||||
B: number[],
|
||||
C: number[]
|
||||
): number[] {
|
||||
static circleFromThreePoints(A: number[], B: number[], C: number[]): number[] {
|
||||
const [x1, y1] = A
|
||||
const [x2, y2] = B
|
||||
const [x3, y3] = C
|
||||
|
@ -500,12 +456,7 @@ export class Utils {
|
|||
* @param A
|
||||
* @param B
|
||||
*/
|
||||
static getArcLength(
|
||||
C: number[],
|
||||
r: number,
|
||||
A: number[],
|
||||
B: number[]
|
||||
): number {
|
||||
static getArcLength(C: number[], r: number, A: number[], B: number[]): number {
|
||||
const sweep = Utils.getSweep(C, A, B)
|
||||
return r * (2 * Math.PI) * (sweep / (2 * Math.PI))
|
||||
}
|
||||
|
@ -518,13 +469,7 @@ export class Utils {
|
|||
* @param B
|
||||
* @param step
|
||||
*/
|
||||
static getArcDashOffset(
|
||||
C: number[],
|
||||
r: number,
|
||||
A: number[],
|
||||
B: number[],
|
||||
step: number
|
||||
): number {
|
||||
static getArcDashOffset(C: number[], r: number, A: number[], B: number[], step: number): number {
|
||||
const del0 = Utils.getSweep(C, A, B)
|
||||
const len0 = Utils.getArcLength(C, r, A, B)
|
||||
const off0 = del0 < 0 ? len0 : 2 * Math.PI * C[2] - len0
|
||||
|
@ -548,10 +493,7 @@ export class Utils {
|
|||
* @param points
|
||||
* @param tension
|
||||
*/
|
||||
static getTLBezierCurveSegments(
|
||||
points: number[][],
|
||||
tension = 0.4
|
||||
): TLBezierCurveSegment[] {
|
||||
static getTLBezierCurveSegments(points: number[][], tension = 0.4): TLBezierCurveSegment[] {
|
||||
const len = points.length
|
||||
const cpoints: number[][] = [...points]
|
||||
|
||||
|
@ -681,13 +623,7 @@ export class Utils {
|
|||
* @param x2
|
||||
* @param y2
|
||||
*/
|
||||
static cubicBezier(
|
||||
tx: number,
|
||||
x1: number,
|
||||
y1: number,
|
||||
x2: number,
|
||||
y2: number
|
||||
): number {
|
||||
static cubicBezier(tx: number, x1: number, y1: number, x2: number, y2: number): number {
|
||||
// Inspired by Don Lancaster's two articles
|
||||
// http://www.tinaja.com/glib/cubemath.pdf
|
||||
// http://www.tinaja.com/text/bezmath.html
|
||||
|
@ -876,8 +812,7 @@ export class Utils {
|
|||
|
||||
for (let i = 1; i < len - 1; i++) {
|
||||
const [x0, y0] = points[i]
|
||||
const d =
|
||||
Math.abs((y2 - y1) * x0 - (x2 - x1) * y0 + x2 * y1 - y2 * x1) / max
|
||||
const d = Math.abs((y2 - y1) * x0 - (x2 - x1) * y0 + x2 * y1 - y2 * x1) / max
|
||||
|
||||
if (distance > d) continue
|
||||
|
||||
|
@ -914,13 +849,7 @@ export class Utils {
|
|||
* @param rotation
|
||||
* @returns
|
||||
*/
|
||||
static pointInEllipse(
|
||||
A: number[],
|
||||
C: number[],
|
||||
rx: number,
|
||||
ry: number,
|
||||
rotation = 0
|
||||
): boolean {
|
||||
static pointInEllipse(A: number[], C: number[], rx: number, ry: number, rotation = 0): boolean {
|
||||
rotation = rotation || 0
|
||||
const cos = Math.cos(rotation)
|
||||
const sin = Math.sin(rotation)
|
||||
|
@ -984,12 +913,7 @@ export class Utils {
|
|||
* @returns
|
||||
*/
|
||||
static boundsCollide(a: TLBounds, b: TLBounds): boolean {
|
||||
return !(
|
||||
a.maxX < b.minX ||
|
||||
a.minX > b.maxX ||
|
||||
a.maxY < b.minY ||
|
||||
a.minY > b.maxY
|
||||
)
|
||||
return !(a.maxX < b.minX || a.minX > b.maxX || a.maxY < b.minY || a.minY > b.maxY)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -999,9 +923,7 @@ export class Utils {
|
|||
* @returns
|
||||
*/
|
||||
static boundsContain(a: TLBounds, b: TLBounds): boolean {
|
||||
return (
|
||||
a.minX < b.minX && a.minY < b.minY && a.maxY > b.maxY && a.maxX > b.maxX
|
||||
)
|
||||
return a.minX < b.minX && a.minY < b.minY && a.maxY > b.maxY && a.maxX > b.maxX
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1021,12 +943,7 @@ export class Utils {
|
|||
* @returns
|
||||
*/
|
||||
static boundsAreEqual(a: TLBounds, b: TLBounds): boolean {
|
||||
return !(
|
||||
b.maxX !== a.maxX ||
|
||||
b.minX !== a.minX ||
|
||||
b.maxY !== a.maxY ||
|
||||
b.minY !== a.minY
|
||||
)
|
||||
return !(b.maxX !== a.maxX || b.minX !== a.minX || b.maxY !== a.maxY || b.minY !== a.minY)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1056,9 +973,7 @@ export class Utils {
|
|||
|
||||
if (rotation !== 0) {
|
||||
return Utils.getBoundsFromPoints(
|
||||
points.map((pt) =>
|
||||
vec.rotWith(pt, [(minX + maxX) / 2, (minY + maxY) / 2], rotation)
|
||||
)
|
||||
points.map((pt) => vec.rotWith(pt, [(minX + maxX) / 2, (minY + maxY) / 2], rotation))
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1107,21 +1022,9 @@ export class Utils {
|
|||
* @param center
|
||||
* @param rotation
|
||||
*/
|
||||
static rotateBounds(
|
||||
bounds: TLBounds,
|
||||
center: number[],
|
||||
rotation: number
|
||||
): TLBounds {
|
||||
const [minX, minY] = vec.rotWith(
|
||||
[bounds.minX, bounds.minY],
|
||||
center,
|
||||
rotation
|
||||
)
|
||||
const [maxX, maxY] = vec.rotWith(
|
||||
[bounds.maxX, bounds.maxY],
|
||||
center,
|
||||
rotation
|
||||
)
|
||||
static rotateBounds(bounds: TLBounds, center: number[], rotation: number): TLBounds {
|
||||
const [minX, minY] = vec.rotWith([bounds.minX, bounds.minY], center, rotation)
|
||||
const [maxX, maxY] = vec.rotWith([bounds.maxX, bounds.maxY], center, rotation)
|
||||
|
||||
return {
|
||||
minX,
|
||||
|
@ -1359,31 +1262,19 @@ so that the two anchor points (initial and result) will be equal.
|
|||
|
||||
switch (handle) {
|
||||
case TLBoundsCorner.TopLeft: {
|
||||
cv = vec.sub(
|
||||
vec.rotWith([bx1, by1], c1, rotation),
|
||||
vec.rotWith([ax1, ay1], c0, rotation)
|
||||
)
|
||||
cv = vec.sub(vec.rotWith([bx1, by1], c1, rotation), vec.rotWith([ax1, ay1], c0, rotation))
|
||||
break
|
||||
}
|
||||
case TLBoundsCorner.TopRight: {
|
||||
cv = vec.sub(
|
||||
vec.rotWith([bx0, by1], c1, rotation),
|
||||
vec.rotWith([ax0, ay1], c0, rotation)
|
||||
)
|
||||
cv = vec.sub(vec.rotWith([bx0, by1], c1, rotation), vec.rotWith([ax0, ay1], c0, rotation))
|
||||
break
|
||||
}
|
||||
case TLBoundsCorner.BottomRight: {
|
||||
cv = vec.sub(
|
||||
vec.rotWith([bx0, by0], c1, rotation),
|
||||
vec.rotWith([ax0, ay0], c0, rotation)
|
||||
)
|
||||
cv = vec.sub(vec.rotWith([bx0, by0], c1, rotation), vec.rotWith([ax0, ay0], c0, rotation))
|
||||
break
|
||||
}
|
||||
case TLBoundsCorner.BottomLeft: {
|
||||
cv = vec.sub(
|
||||
vec.rotWith([bx1, by0], c1, rotation),
|
||||
vec.rotWith([ax1, ay0], c0, rotation)
|
||||
)
|
||||
cv = vec.sub(vec.rotWith([bx1, by0], c1, rotation), vec.rotWith([ax1, ay0], c0, rotation))
|
||||
break
|
||||
}
|
||||
case TLBoundsEdge.Top: {
|
||||
|
@ -1613,11 +1504,7 @@ left past the initial left edge) then swap points on that axis.
|
|||
*```
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
static getFromCache<V, I extends object>(
|
||||
cache: WeakMap<I, V>,
|
||||
item: I,
|
||||
getNext: () => V
|
||||
): V {
|
||||
static getFromCache<V, I extends object>(cache: WeakMap<I, V>, item: I, getNext: () => V): V {
|
||||
let value = cache.get(item)
|
||||
|
||||
if (value === undefined) {
|
||||
|
@ -1678,11 +1565,7 @@ left past the initial left edge) then swap points on that axis.
|
|||
*/
|
||||
static arrsIntersect<T, K>(a: T[], b: K[], fn?: (item: K) => T): boolean
|
||||
static arrsIntersect<T>(a: T[], b: T[]): boolean
|
||||
static arrsIntersect<T>(
|
||||
a: T[],
|
||||
b: unknown[],
|
||||
fn?: (item: unknown) => T
|
||||
): boolean {
|
||||
static arrsIntersect<T>(a: T[], b: unknown[], fn?: (item: unknown) => T): boolean {
|
||||
return a.some((item) => b.includes(fn ? fn(item) : item))
|
||||
}
|
||||
|
||||
|
@ -1732,9 +1615,7 @@ left past the initial left edge) then swap points on that axis.
|
|||
|
||||
d.push(' Z')
|
||||
|
||||
return d
|
||||
.join('')
|
||||
.replaceAll(/(\s?[A-Z]?,?-?[0-9]*\.[0-9]{0,2})(([0-9]|e|-)*)/g, '$1')
|
||||
return d.join('').replaceAll(/(\s?[A-Z]?,?-?[0-9]*\.[0-9]{0,2})(([0-9]|e|-)*)/g, '$1')
|
||||
}
|
||||
|
||||
/* -------------------------------------------------- */
|
||||
|
@ -1774,9 +1655,7 @@ left past the initial left edge) then swap points on that axis.
|
|||
*/
|
||||
static isTouchDisplay(): boolean {
|
||||
return (
|
||||
'ontouchstart' in window ||
|
||||
navigator.maxTouchPoints > 0 ||
|
||||
navigator.msMaxTouchPoints > 0
|
||||
'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -433,7 +433,10 @@ export class Arrow extends TLDrawShapeUtil<ArrowShape> {
|
|||
const anchor = Vec.sub(
|
||||
Vec.add(
|
||||
[expandedBounds.minX, expandedBounds.minY],
|
||||
Vec.mulV([expandedBounds.width, expandedBounds.height], binding.point)
|
||||
Vec.mulV(
|
||||
[expandedBounds.width, expandedBounds.height],
|
||||
Vec.rotWith(binding.point, [0.5, 0.5], target.rotation || 0)
|
||||
)
|
||||
),
|
||||
shape.point
|
||||
)
|
||||
|
@ -455,7 +458,7 @@ export class Arrow extends TLDrawShapeUtil<ArrowShape> {
|
|||
|
||||
if ([TLDrawShapeType.Rectangle, TLDrawShapeType.Text].includes(target.type)) {
|
||||
let hits = Intersect.ray
|
||||
.bounds(origin, direction, intersectBounds)
|
||||
.bounds(origin, direction, intersectBounds, target.rotation)
|
||||
.filter((int) => int.didIntersect)
|
||||
.map((int) => int.points[0])
|
||||
.sort((a, b) => Vec.dist(a, origin) - Vec.dist(b, origin))
|
||||
|
@ -475,25 +478,22 @@ export class Arrow extends TLDrawShapeUtil<ArrowShape> {
|
|||
|
||||
handlePoint = Vec.sub(hits[0], shape.point)
|
||||
} else if (target.type === TLDrawShapeType.Ellipse) {
|
||||
// const center = getShapeUtils(target).getCenter(target)
|
||||
const hits = Intersect.ray
|
||||
.ellipse(
|
||||
origin,
|
||||
direction,
|
||||
center,
|
||||
target.radius[0] + binding.distance,
|
||||
target.radius[1] + binding.distance,
|
||||
target.rotation || 0
|
||||
)
|
||||
.points.sort((a, b) => Vec.dist(a, origin) - Vec.dist(b, origin))
|
||||
|
||||
handlePoint = Vec.nudge(
|
||||
Vec.sub(
|
||||
Intersect.ray
|
||||
.ellipse(
|
||||
origin,
|
||||
direction,
|
||||
center,
|
||||
target.radius[0],
|
||||
target.radius[1],
|
||||
target.rotation || 0
|
||||
)
|
||||
.points.sort((a, b) => Vec.dist(a, origin) - Vec.dist(b, origin))[0],
|
||||
shape.point
|
||||
),
|
||||
origin,
|
||||
binding.distance
|
||||
)
|
||||
if (!hits[0]) {
|
||||
console.warn('No intersections')
|
||||
}
|
||||
|
||||
handlePoint = Vec.sub(hits[0], shape.point)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -155,7 +155,7 @@ export class Ellipse extends TLDrawShapeUtil<EllipseShape> {
|
|||
}
|
||||
|
||||
getCenter(shape: EllipseShape): number[] {
|
||||
return Utils.getBoundsCenter(this.getBounds(shape))
|
||||
return [shape.point[0] + shape.radius[0], shape.point[1] + shape.radius[1]]
|
||||
}
|
||||
|
||||
hitTest(shape: EllipseShape, point: number[]) {
|
||||
|
@ -171,6 +171,98 @@ export class Ellipse extends TLDrawShapeUtil<EllipseShape> {
|
|||
)
|
||||
}
|
||||
|
||||
getBindingPoint(
|
||||
shape: EllipseShape,
|
||||
point: number[],
|
||||
origin: number[],
|
||||
direction: number[],
|
||||
padding: number,
|
||||
anywhere: boolean
|
||||
) {
|
||||
{
|
||||
const bounds = this.getBounds(shape)
|
||||
|
||||
const expandedBounds = Utils.expandBounds(bounds, padding)
|
||||
|
||||
const center = this.getCenter(shape)
|
||||
|
||||
let bindingPoint: number[]
|
||||
let distance: number
|
||||
|
||||
if (!Utils.pointInEllipse(point, center, shape.radius[0] + 32, shape.radius[1] + 32)) return
|
||||
|
||||
if (anywhere) {
|
||||
if (Vec.dist(point, this.getCenter(shape)) < 12) {
|
||||
bindingPoint = [0.5, 0.5]
|
||||
} else {
|
||||
bindingPoint = Vec.divV(Vec.sub(point, [expandedBounds.minX, expandedBounds.minY]), [
|
||||
expandedBounds.width,
|
||||
expandedBounds.height,
|
||||
])
|
||||
}
|
||||
|
||||
distance = 0
|
||||
} else {
|
||||
// Find furthest intersection between ray from
|
||||
// origin through point and expanded bounds.
|
||||
// const intersection = Intersect.ray
|
||||
// .bounds(origin, direction, expandedBounds)
|
||||
// .filter((int) => int.didIntersect)
|
||||
// .map((int) => int.points[0])
|
||||
// .sort((a, b) => Vec.dist(b, origin) - Vec.dist(a, origin))[0]
|
||||
|
||||
const intersection = Intersect.ray
|
||||
.ellipse(origin, direction, center, shape.radius[0], shape.radius[1], shape.rotation || 0)
|
||||
.points.sort((a, b) => Vec.dist(a, origin) - Vec.dist(b, origin))[0]
|
||||
|
||||
if (!intersection) {
|
||||
console.log('could not find an intersection')
|
||||
return undefined
|
||||
}
|
||||
|
||||
// The anchor is a point between the handle and the intersection
|
||||
const anchor = Vec.med(point, intersection)
|
||||
|
||||
if (Vec.distanceToLineSegment(point, anchor, this.getCenter(shape)) < 12) {
|
||||
// If we're close to the center, snap to the center
|
||||
bindingPoint = [0.5, 0.5]
|
||||
} else {
|
||||
// Or else calculate a normalized point
|
||||
bindingPoint = Vec.divV(Vec.sub(anchor, [expandedBounds.minX, expandedBounds.minY]), [
|
||||
expandedBounds.width,
|
||||
expandedBounds.height,
|
||||
])
|
||||
}
|
||||
|
||||
if (Utils.pointInBounds(point, bounds)) {
|
||||
distance = 16
|
||||
} else {
|
||||
// Find the distance between the point and the ellipse
|
||||
const innerIntersection = Intersect.lineSegment.ellipse(
|
||||
point,
|
||||
center,
|
||||
center,
|
||||
shape.radius[0],
|
||||
shape.radius[1],
|
||||
shape.rotation || 0
|
||||
).points[0]
|
||||
|
||||
if (!innerIntersection) {
|
||||
console.log('could not find an intersection')
|
||||
return undefined
|
||||
}
|
||||
|
||||
distance = Math.max(16, Vec.dist(point, innerIntersection))
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
point: bindingPoint,
|
||||
distance,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
transform(
|
||||
_shape: EllipseShape,
|
||||
bounds: TLBounds,
|
||||
|
|
|
@ -1261,6 +1261,21 @@ export class TLDrawState implements TLCallbacks {
|
|||
}
|
||||
break
|
||||
}
|
||||
case 'translatingHandle': {
|
||||
if (key === 'Escape') {
|
||||
this.cancelSession(this.getPagePoint(info.point))
|
||||
}
|
||||
|
||||
if (key === 'Meta' || key === 'Control') {
|
||||
this.updateHandleSession(
|
||||
this.getPagePoint(info.point),
|
||||
info.shiftKey,
|
||||
info.altKey,
|
||||
info.metaKey
|
||||
)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1284,6 +1299,21 @@ export class TLDrawState implements TLCallbacks {
|
|||
}
|
||||
break
|
||||
}
|
||||
case 'translatingHandle': {
|
||||
if (key === 'Escape') {
|
||||
this.cancelSession(this.getPagePoint(info.point))
|
||||
}
|
||||
|
||||
if (key === 'Meta' || key === 'Control') {
|
||||
this.updateHandleSession(
|
||||
this.getPagePoint(info.point),
|
||||
info.shiftKey,
|
||||
info.altKey,
|
||||
info.metaKey
|
||||
)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue