Tldraw/packages/editor/src/lib/primitives/geometry/CubicSpline2d.ts

88 wiersze
2.1 KiB
TypeScript

import { Vec } from '../Vec'
import { CubicBezier2d } from './CubicBezier2d'
import { Geometry2d, Geometry2dOptions } from './Geometry2d'
/** @public */
export class CubicSpline2d extends Geometry2d {
points: Vec[]
constructor(config: Omit<Geometry2dOptions, 'isClosed' | 'isFilled'> & { points: Vec[] }) {
super({ ...config, isClosed: false, isFilled: false })
const { points } = config
this.points = points
}
_segments?: CubicBezier2d[]
// eslint-disable-next-line no-restricted-syntax
get segments() {
if (!this._segments) {
this._segments = []
const { points } = this
const len = points.length
const last = len - 2
const k = 1.25
for (let i = 0; i < len - 1; i++) {
const p0 = i === 0 ? points[0] : points[i - 1]
const p1 = points[i]
const p2 = points[i + 1]
const p3 = i === last ? p2 : points[i + 2]
const start = p1,
cp1 =
i === 0 ? p0 : new Vec(p1.x + ((p2.x - p0.x) / 6) * k, p1.y + ((p2.y - p0.y) / 6) * k),
cp2 =
i === last
? p2
: new Vec(p2.x - ((p3.x - p1.x) / 6) * k, p2.y - ((p3.y - p1.y) / 6) * k),
end = p2
this._segments.push(new CubicBezier2d({ start, cp1, cp2, end }))
}
}
return this._segments
}
_length?: number
// eslint-disable-next-line no-restricted-syntax
get length() {
if (!this._length) {
this._length = this.segments.reduce((acc, segment) => acc + segment.length, 0)
}
return this._length
}
getVertices() {
const vertices = this.segments.reduce((acc, segment) => {
return acc.concat(segment.vertices)
}, [] as Vec[])
vertices.push(this.points[this.points.length - 1])
return vertices
}
nearestPoint(A: Vec): Vec {
let nearest: Vec | undefined
let dist = Infinity
let d: number
let p: Vec
for (const segment of this.segments) {
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
}
hitTestLineSegment(A: Vec, B: Vec): boolean {
return this.segments.some((segment) => segment.hitTestLineSegment(A, B))
}
}