Newer
Older
function hypotenuseSquared(a, b) {
return sqr(a) + sqr(b)
}
function distanceSquared([x0, y0], [x1, y1]) {
return hypotenuseSquared(x0 - x1, y0 - y1)
function distance(point0, point1) {
return Math.sqrt(distanceSquared(point0, point1))
}
function cap01(x) {
return Math.max(0, Math.min(1, x))
}
function distToSegmentSquared(lineStart, lineEnd, point) {
const l2 = distanceSquared(lineStart, lineEnd)
if (l2 === 0) return distanceSquared(point, lineStart)
((point[0] - lineStart[0]) * (lineEnd[0] - lineStart[0]) +
(point[1] - lineStart[1]) * (lineEnd[1] - lineStart[1])) /
l2
return distanceSquared(point, [
lineStart[0] + t * (lineEnd[0] - lineStart[0]),
lineStart[1] + t * (lineEnd[1] - lineStart[1]),
])
function interpolate([x0, y0], [x1, y1], t) {
return [x0 + (x1 - x0) * t, y0 + (y1 - y0) * t]
}
function project([x1, y1], [x2, y2], [x3, y3]) {
const x21 = x2 - x1,
y21 = y2 - y1
const x31 = x3 - x1,
y31 = y3 - y1
return (x31 * x21 + y31 * y21) / (x21 * x21 + y21 * y21)
}
function erasureInterval(lineStart, lineEnd, erasureCenter, erasureRadius) {
Moritz Langenstein
committed
if (!(lineStart && erasureCenter)) return undefined
if (!lineEnd) {
const dist2ToSingularity = distanceSquared(erasureCenter, lineStart)
return dist2ToSingularity <= erasureRadius ** 2 ? [0, 0] : undefined
}
const distToSegment2 = distToSegmentSquared(lineStart, lineEnd, erasureCenter)
Moritz Langenstein
committed
if (erasureRadius ** 2 < distToSegment2) return undefined
const lineLength = distance(lineStart, lineEnd)
if (lineLength === 0) {
Moritz Langenstein
committed
return distToSegment2 <= erasureRadius ** 2 ? [0, 1] : undefined
const projT = project(lineStart, lineEnd, erasureCenter)
const projectionPoint = interpolate(lineStart, lineEnd, projT)
const d2 = distance(erasureCenter, projectionPoint)
const halfLength = Math.sqrt(Math.abs(sqr(erasureRadius) - sqr(d2)))
if (halfLength === 0) return undefined
let touchFromStartDist = distance(lineStart, projectionPoint)
if (projT < 0) touchFromStartDist = -touchFromStartDist
const touchBeginFromStarDist = touchFromStartDist - halfLength
const touchEndFromStarDist = touchFromStartDist + halfLength
cap01(touchBeginFromStarDist / lineLength),
cap01(touchEndFromStarDist / lineLength),
export function computeErasureIntervals(points, erasureCenter, erasureRadius) {
return points
.map((point, i) => ({ point, i }))
.reduce((acc, { point, i }) => {
const interval = erasureInterval(
point,
points[i + 1],
erasureCenter,
erasureRadius,
)
if (!interval) return acc
return {
function overlaps([s1, e1], [, e2]) {
return s1 <= e2 && s1 <= e1
}
function mergeIntervals(...intervals) {
if (!intervals.length) return []
const sorted = intervals.sort(([a], [b]) => a - b)
sorted.forEach((x) => {
const top = stack[stack.length - 1]
if (overlaps(x, top)) {
if (x[1] > top[1]) top[1] = x[1]
} else {
stack.push(x)
}
})
return stack
}
export function combineErasureIntervals(i1, i2) {
const _i1 = { ...i1 }
Object.keys(i1).forEach((key) => {
if (i2[key]) {
_i1[key] = mergeIntervals(...i1[key], ...i2[key])
}
})
return { ...i2, ..._i1 }
}
Moritz Langenstein
committed
export function spreadErasureIntervals(intervals) {
const spread = {}
Moritz Langenstein
committed
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
intervals.forEach(([l, u]) => {
if (u > l) {
for (let li = Math.floor(l); li < u; li++) {
const list = spread[li] || []
const [il, iu] = [Math.max(li, l), Math.min(li + 1, u)]
list.push([il % 1, iu == li + 1 ? 1 : iu % 1])
spread[li] = list
}
} else {
spread[Math.floor(l)] = [[l % 1, u % 1]]
}
})
return spread
}
export function flattenErasureIntervals(intervals) {
const flatten = []
for (const idx in intervals) {
if (intervals[idx])
flatten.push(
intervals[idx].map(([l, u]) => [parseInt(idx) + l, parseInt(idx) + u]),
)
}
return flatten.flat()
}