136 lines
3.3 KiB
JavaScript
136 lines
3.3 KiB
JavaScript
![]() |
import { Bezier as UpstreamBezier } from 'bezier-js'
|
||
|
|
||
|
/*
|
||
|
* The BezierJS library has an issue where it does not find
|
||
|
* the intersection of two paths under some circumstances.
|
||
|
* See: https://github.com/Pomax/bezierjs/issues/203
|
||
|
*
|
||
|
* A PR was submitted to address this by Jonathan Haas
|
||
|
* See: https://github.com/Pomax/bezierjs/pull/219
|
||
|
*
|
||
|
* However, that PR does not get any attention, and in general
|
||
|
* the library seems to be rather unmaintained. The maintainer
|
||
|
* (Pomax) says as much in the README.
|
||
|
*
|
||
|
* That being said, BezierJS is a great library and this stuff
|
||
|
* is just hard because there is no closed-form integral solution
|
||
|
* for this, so a lot of this is trial an error.
|
||
|
*
|
||
|
* Rather than maintain a fork, we extend the Bezier class and
|
||
|
* implement our own reduce method.
|
||
|
*
|
||
|
* The changes to the reduce method where written by Jonathan <3
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* Extend the upstream Bezier class with a custom reduce method
|
||
|
*/
|
||
|
class Bezier extends UpstreamBezier {
|
||
|
reduce() {
|
||
|
const utils = this.getUtils()
|
||
|
const EPSILON = 0.001
|
||
|
|
||
|
function reduceStep(bezier) {
|
||
|
const splitTs = []
|
||
|
let t1 = 0
|
||
|
|
||
|
if (bezier._t2 - bezier._t1 < EPSILON || bezier.simple()) {
|
||
|
return [bezier]
|
||
|
}
|
||
|
|
||
|
while (t1 < 1) {
|
||
|
// Check if the rest of the curve is already simple
|
||
|
const remaining = bezier.split(t1, 1)
|
||
|
if (remaining.simple()) {
|
||
|
break
|
||
|
}
|
||
|
|
||
|
// Binary search to find the furthest simple segment
|
||
|
let low = t1 + EPSILON
|
||
|
let high = 1
|
||
|
let best = t1 + EPSILON
|
||
|
|
||
|
if (low > best) {
|
||
|
break
|
||
|
}
|
||
|
|
||
|
for (let i = 0; i < 100; i++) {
|
||
|
// limit to 20 iterations max
|
||
|
const mid = (low + high) / 2
|
||
|
const segment = bezier.split(t1, mid)
|
||
|
|
||
|
if (segment.simple()) {
|
||
|
best = mid
|
||
|
low = mid
|
||
|
} else {
|
||
|
high = mid
|
||
|
}
|
||
|
|
||
|
if (t1 !== best && i >= 5) {
|
||
|
// we have found a good split location, don't need to be super exact
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
|
||
|
splitTs.push(best)
|
||
|
|
||
|
t1 = best
|
||
|
}
|
||
|
|
||
|
// endpoint
|
||
|
splitTs.push(1)
|
||
|
|
||
|
// Split the curve using the collected t values
|
||
|
const parts = []
|
||
|
let prevT = 0
|
||
|
for (const t of splitTs) {
|
||
|
const segment = bezier.split(prevT, t)
|
||
|
segment._t1 = utils.map(prevT, 0, 1, bezier._t1, bezier._t2)
|
||
|
segment._t2 = utils.map(t, 0, 1, bezier._t1, bezier._t2)
|
||
|
parts.push(segment)
|
||
|
prevT = t
|
||
|
}
|
||
|
|
||
|
return parts
|
||
|
}
|
||
|
|
||
|
// Vars we'll use
|
||
|
let i,
|
||
|
t1,
|
||
|
t2 = 0,
|
||
|
segment,
|
||
|
pass1 = [],
|
||
|
pass2 = []
|
||
|
|
||
|
// First pass: split on extrema
|
||
|
let extrema = this.extrema().values
|
||
|
// remove extrema very close to 1 or 0
|
||
|
while (extrema[0] < EPSILON) {
|
||
|
extrema.shift()
|
||
|
}
|
||
|
while (extrema[extrema.length - 1] > 1 - EPSILON) {
|
||
|
extrema.shift()
|
||
|
}
|
||
|
// add 1 and 0
|
||
|
extrema.unshift(0)
|
||
|
extrema.push(1)
|
||
|
|
||
|
for (t1 = extrema[0], i = 1; i < extrema.length; i++) {
|
||
|
t2 = extrema[i]
|
||
|
segment = this.split(t1, t2)
|
||
|
segment._t1 = t1
|
||
|
segment._t2 = t2
|
||
|
pass1.push(segment)
|
||
|
t1 = t2
|
||
|
}
|
||
|
|
||
|
// second pass: further reduce these segments to simple segments
|
||
|
pass1.forEach(function (p1) {
|
||
|
pass2.push(...reduceStep(p1))
|
||
|
})
|
||
|
return pass2
|
||
|
}
|
||
|
}
|
||
|
|
||
|
export { Bezier }
|