kopia lustrzana https://github.com/Tldraw/Tldraw
93 wiersze
2.6 KiB
TypeScript
93 wiersze
2.6 KiB
TypeScript
import { Vec } from '../Vec'
|
|
import { intersectLineSegmentCircle } from '../intersect'
|
|
import { getArcMeasure, getPointInArcT, getPointOnCircle } from '../utils'
|
|
import { Geometry2d, Geometry2dOptions } from './Geometry2d'
|
|
import { getVerticesCountForLength } from './geometry-constants'
|
|
|
|
/** @public */
|
|
export class Arc2d extends Geometry2d {
|
|
_center: Vec
|
|
radius: number
|
|
start: Vec
|
|
end: Vec
|
|
|
|
measure: number
|
|
length: number
|
|
angleStart: number
|
|
angleEnd: number
|
|
|
|
constructor(
|
|
config: Omit<Geometry2dOptions, 'isFilled' | 'isClosed'> & {
|
|
center: Vec
|
|
radius: number
|
|
start: Vec
|
|
end: Vec
|
|
sweepFlag: number
|
|
largeArcFlag: number
|
|
}
|
|
) {
|
|
super({ ...config, isFilled: false, isClosed: false })
|
|
const { center, radius, sweepFlag, largeArcFlag, start, end } = config
|
|
if (start.equals(end)) throw Error(`Arc must have different start and end points.`)
|
|
|
|
// ensure that the start and end are clockwise
|
|
this.angleStart = Vec.Angle(center, start)
|
|
this.angleEnd = Vec.Angle(center, end)
|
|
this.measure = getArcMeasure(this.angleStart, this.angleEnd, sweepFlag, largeArcFlag)
|
|
this.length = this.measure * radius
|
|
|
|
this.start = start
|
|
this.end = end
|
|
|
|
this._center = center
|
|
this.radius = radius
|
|
}
|
|
|
|
nearestPoint(point: Vec): Vec {
|
|
const { _center, measure, radius, angleEnd, angleStart, start: A, end: B } = this
|
|
const t = getPointInArcT(measure, angleStart, angleEnd, _center.angle(point))
|
|
if (t <= 0) return A
|
|
if (t >= 1) return B
|
|
|
|
// 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 nearest: Vec | undefined
|
|
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
|
|
}
|
|
|
|
hitTestLineSegment(A: Vec, B: Vec): boolean {
|
|
const { _center, radius, measure, angleStart, angleEnd } = this
|
|
const intersection = intersectLineSegmentCircle(A, B, _center, radius)
|
|
if (intersection === null) return false
|
|
|
|
return intersection.some((p) => {
|
|
const result = getPointInArcT(measure, angleStart, angleEnd, _center.angle(p))
|
|
return result >= 0 && result <= 1
|
|
})
|
|
}
|
|
|
|
getVertices(): Vec[] {
|
|
const { _center, measure, length, radius, angleStart } = this
|
|
const vertices: Vec[] = []
|
|
|
|
for (let i = 0, n = getVerticesCountForLength(Math.abs(length)); i < n + 1; i++) {
|
|
const t = (i / n) * measure
|
|
const angle = angleStart + t
|
|
vertices.push(getPointOnCircle(_center, radius, angle))
|
|
}
|
|
|
|
return vertices
|
|
}
|
|
}
|