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