mirror of
https://github.com/belsabbagh/dotfiles.git
synced 2026-04-11 17:47:09 +00:00
192 lines
7.6 KiB
JavaScript
192 lines
7.6 KiB
JavaScript
.pragma library
|
|
|
|
.import "cubic.js" as Cubic
|
|
.import "point.js" as Point
|
|
.import "feature-mapping.js" as FeatureMapping
|
|
.import "utils.js" as Utils
|
|
.import "feature.js" as Feature
|
|
|
|
class MeasuredPolygon {
|
|
constructor(measurer, features, cubics, outlineProgress) {
|
|
this.measurer = measurer
|
|
this.features = features
|
|
this.outlineProgress = outlineProgress
|
|
this.cubics = []
|
|
|
|
const measuredCubics = []
|
|
let startOutlineProgress = 0
|
|
for(let i = 0; i < cubics.length; i++) {
|
|
if ((outlineProgress[i + 1] - outlineProgress[i]) > Utils.DistanceEpsilon) {
|
|
measuredCubics.push(
|
|
new MeasuredCubic(this, cubics[i], startOutlineProgress, outlineProgress[i + 1])
|
|
)
|
|
// The next measured cubic will start exactly where this one ends.
|
|
startOutlineProgress = outlineProgress[i + 1]
|
|
}
|
|
}
|
|
|
|
measuredCubics[measuredCubics.length - 1].updateProgressRange(measuredCubics[measuredCubics.length - 1].startOutlineProgress, 1)
|
|
this.cubics = measuredCubics
|
|
}
|
|
|
|
cutAndShift(cuttingPoint) {
|
|
if (cuttingPoint < Utils.DistanceEpsilon) return this
|
|
|
|
// Find the index of cubic we want to cut
|
|
const targetIndex = this.cubics.findIndex(it => cuttingPoint >= it.startOutlineProgress && cuttingPoint <= it.endOutlineProgress)
|
|
const target = this.cubics[targetIndex]
|
|
// Cut the target cubic.
|
|
// b1, b2 are two resulting cubics after cut
|
|
const { a: b1, b: b2 } = target.cutAtProgress(cuttingPoint)
|
|
|
|
// Construct the list of the cubics we need:
|
|
// * The second part of the target cubic (after the cut)
|
|
// * All cubics after the target, until the end + All cubics from the start, before the
|
|
// target cubic
|
|
// * The first part of the target cubic (before the cut)
|
|
const retCubics = [b2.cubic]
|
|
for(let i = 1; i < this.cubics.length; i++) {
|
|
retCubics.push(this.cubics[(i + targetIndex) % this.cubics.length].cubic)
|
|
}
|
|
retCubics.push(b1.cubic)
|
|
|
|
// Construct the array of outline progress.
|
|
// For example, if we have 3 cubics with outline progress [0 .. 0.3], [0.3 .. 0.8] &
|
|
// [0.8 .. 1.0], and we cut + shift at 0.6:
|
|
// 0. 0123456789
|
|
// |--|--/-|-|
|
|
// The outline progresses will start at 0 (the cutting point, that shifs to 0.0),
|
|
// then 0.8 - 0.6 = 0.2, then 1 - 0.6 = 0.4, then 0.3 - 0.6 + 1 = 0.7,
|
|
// then 1 (the cutting point again),
|
|
// all together: (0.0, 0.2, 0.4, 0.7, 1.0)
|
|
const retOutlineProgress = []
|
|
for (let i = 0; i < this.cubics.length + 2; i++) {
|
|
if (i === 0) {
|
|
retOutlineProgress.push(0)
|
|
} else if(i === this.cubics.length + 1) {
|
|
retOutlineProgress.push(1)
|
|
} else {
|
|
const cubicIndex = (targetIndex + i - 1) % this.cubics.length
|
|
retOutlineProgress.push(Utils.positiveModulo(this.cubics[cubicIndex].endOutlineProgress - cuttingPoint, 1))
|
|
}
|
|
}
|
|
|
|
// Shift the feature's outline progress too.
|
|
const newFeatures = []
|
|
for(let i = 0; i < this.features.length; i++) {
|
|
newFeatures.push(new FeatureMapping.ProgressableFeature(Utils.positiveModulo(this.features[i].progress - cuttingPoint, 1), this.features[i].feature))
|
|
}
|
|
|
|
// Filter out all empty cubics (i.e. start and end anchor are (almost) the same point.)
|
|
return new MeasuredPolygon(this.measurer, newFeatures, retCubics, retOutlineProgress)
|
|
}
|
|
|
|
static measurePolygon(measurer, polygon) {
|
|
const cubics = []
|
|
const featureToCubic = []
|
|
|
|
for (let featureIndex = 0; featureIndex < polygon.features.length; featureIndex++) {
|
|
const feature = polygon.features[featureIndex]
|
|
for (let cubicIndex = 0; cubicIndex < feature.cubics.length; cubicIndex++) {
|
|
if (feature instanceof Feature.Corner && cubicIndex == feature.cubics.length / 2) {
|
|
featureToCubic.push({ a: feature, b: cubics.length })
|
|
}
|
|
cubics.push(feature.cubics[cubicIndex])
|
|
}
|
|
}
|
|
|
|
const measures = [0] // Initialize with 0 like in Kotlin's scan
|
|
for (const cubic of cubics) {
|
|
const measurement = measurer.measureCubic(cubic)
|
|
if (measurement < 0) {
|
|
throw new Error("Measured cubic is expected to be greater or equal to zero")
|
|
}
|
|
const lastMeasure = measures[measures.length - 1]
|
|
measures.push(lastMeasure + measurement)
|
|
}
|
|
const totalMeasure = measures[measures.length - 1]
|
|
|
|
const outlineProgress = []
|
|
for (let i = 0; i < measures.length; i++) {
|
|
outlineProgress.push(measures[i] / totalMeasure)
|
|
}
|
|
|
|
const features = []
|
|
for (let i = 0; i < featureToCubic.length; i++) {
|
|
const ix = featureToCubic[i].b
|
|
features.push(
|
|
new FeatureMapping.ProgressableFeature(Utils.positiveModulo((outlineProgress[ix] + outlineProgress[ix + 1]) / 2, 1), featureToCubic[i].a))
|
|
}
|
|
|
|
return new MeasuredPolygon(measurer, features, cubics, outlineProgress)
|
|
}
|
|
}
|
|
|
|
class MeasuredCubic {
|
|
constructor(polygon, cubic, startOutlineProgress, endOutlineProgress) {
|
|
this.polygon = polygon
|
|
this.cubic = cubic
|
|
this.startOutlineProgress = startOutlineProgress
|
|
this.endOutlineProgress = endOutlineProgress
|
|
this.measuredSize = this.polygon.measurer.measureCubic(cubic)
|
|
}
|
|
|
|
updateProgressRange(
|
|
startOutlineProgress = this.startOutlineProgress,
|
|
endOutlineProgress = this.endOutlineProgress,
|
|
) {
|
|
this.startOutlineProgress = startOutlineProgress
|
|
this.endOutlineProgress = endOutlineProgress
|
|
}
|
|
|
|
cutAtProgress(cutOutlineProgress) {
|
|
const boundedCutOutlineProgress = Utils.coerceIn(cutOutlineProgress, this.startOutlineProgress, this.endOutlineProgress)
|
|
const outlineProgressSize = this.endOutlineProgress - this.startOutlineProgress
|
|
const progressFromStart = boundedCutOutlineProgress - this.startOutlineProgress
|
|
|
|
const relativeProgress = progressFromStart / outlineProgressSize
|
|
const t = this.polygon.measurer.findCubicCutPoint(this.cubic, relativeProgress * this.measuredSize)
|
|
|
|
const {a: c1, b: c2} = this.cubic.split(t)
|
|
return {
|
|
a: new MeasuredCubic(this.polygon, c1, this.startOutlineProgress, boundedCutOutlineProgress),
|
|
b: new MeasuredCubic(this.polygon, c2, boundedCutOutlineProgress, this.endOutlineProgress)
|
|
}
|
|
}
|
|
}
|
|
|
|
class LengthMeasurer {
|
|
constructor() {
|
|
this.segments = 3
|
|
}
|
|
|
|
measureCubic(c) {
|
|
return this.closestProgressTo(c, Number.POSITIVE_INFINITY).y
|
|
}
|
|
|
|
findCubicCutPoint(c, m) {
|
|
return this.closestProgressTo(c, m).x
|
|
}
|
|
|
|
closestProgressTo(cubic, threshold) {
|
|
let total = 0
|
|
let remainder = threshold
|
|
let prev = new Point.Point(cubic.anchor0X, cubic.anchor0Y)
|
|
|
|
for (let i = 1; i < this.segments; i++) {
|
|
const progress = i / this.segments
|
|
const point = cubic.pointOnCurve(progress)
|
|
const segment = point.minus(prev).getDistance()
|
|
|
|
if (segment >= remainder) {
|
|
return new Point.Point(progress - (1.0 - remainder / segment) / this.segments, threshold)
|
|
}
|
|
|
|
remainder -= segment
|
|
total += segment
|
|
prev = point
|
|
}
|
|
|
|
return new Point.Point(1.0, total)
|
|
}
|
|
} |