Further cleanup and optimize spiro expansion.
This commit is contained in:
parent
e86890e16a
commit
e1e8144f1c
12 changed files with 234 additions and 224 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -44,8 +44,8 @@ snapshot/iosevka*
|
|||
snapshot/index.css
|
||||
|
||||
# Generated scripts
|
||||
support/*.js
|
||||
gen/build-glyphs.js
|
||||
gen/kits/*.js
|
||||
meta/*.js
|
||||
otl/*.js
|
||||
glyphs/*.js
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import '../support/glyph' as Glyph
|
||||
import '../support/point' as Point
|
||||
import '../support/spiro-kit' as spirokit
|
||||
import '../support/boole-kit' as BooleKit
|
||||
import './kits/spiro-kit' as spirokit
|
||||
import './kits/boole-kit' as BooleKit
|
||||
import '../support/anchor' as Anchor
|
||||
import '../support/monotonic-interpolate' as smoothreg
|
||||
|
||||
|
|
|
@ -125,8 +125,8 @@ class FairizedShapeSink {
|
|||
this.lastContour.pop();
|
||||
}
|
||||
this.contours.push(this.lastContour);
|
||||
this.lastContour = [];
|
||||
}
|
||||
this.lastContour = [];
|
||||
}
|
||||
moveTo(x, y) {
|
||||
this.endShape();
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import 'typo-geom' as TypoGeom
|
||||
import './curve-util' as CurveUtil
|
||||
import './spiroexpand' as [object SpiroContourContext]
|
||||
import '../../support/curve-util' as CurveUtil
|
||||
|
||||
export : define [SetupBuilders args] : begin
|
||||
define [object Glyph globalTransform] args
|
||||
|
@ -17,7 +16,7 @@ export : define [SetupBuilders args] : begin
|
|||
g1.include item
|
||||
set g1.contours : g1.contours.map CurveUtil.convertContourToCubic
|
||||
local c1 : TypoGeom.Boolean.combine operator [CurveUtil.convertShapeToArcs g.contours] [CurveUtil.convertShapeToArcs g1.contours] TypoGeom.Boolean.PolyFillType.pftNonZero TypoGeom.Boolean.PolyFillType.pftNonZero 16384
|
||||
local ctx : new SpiroContourContext
|
||||
local ctx : new CurveUtil.ArcFlattener
|
||||
TypoGeom.transferBezArcShape c1 ctx
|
||||
set g.contours ctx.contours
|
||||
this.includeGlyph g
|
|
@ -1,13 +1,8 @@
|
|||
import 'spiro' as SpiroJs
|
||||
import './spiroexpand' as [object SpiroExpansionContext SpiroContourContext]
|
||||
import './curve-util' as CurveUtil
|
||||
|
||||
import './transform' as Transform
|
||||
|
||||
define [fallback] : for [local j 0] (j < arguments.length) [inc j] : if (arguments.(j) !== nothing) : return arguments.(j)
|
||||
define [mix a b p] : a + (b - a) * p
|
||||
define [bez2 a b c t] : (1 - t) * (1 - t) * a + 2 * (1 - t) * t * b + t * t * c
|
||||
define [bez3 a b c d t] : (1 - t) * (1 - t) * (1 - t) * a + 3 * (1 - t) * (1 - t) * t * b + 3 * t * t * (1 - t) * c + t * t * t * d
|
||||
import '../../support/spiro-expand' as SpiroExpansionContext
|
||||
import '../../support/curve-util' as CurveUtil
|
||||
import '../../support/transform' as Transform
|
||||
import '../../support/utils' as [object fallback mix bez2 bez3]
|
||||
|
||||
export : define [SetupBuilders args] : begin
|
||||
define [object para Glyph Contrast globalTransform Stroke Superness] args
|
||||
|
@ -166,12 +161,12 @@ export : define [SetupBuilders args] : begin
|
|||
set knot.af : lambda [] : begin
|
||||
this.setType ty
|
||||
if af : af.apply this args
|
||||
SpiroJs.spiroToBezierOnContext knots closed s
|
||||
SpiroJs.spiroToArcsOnContext knots closed s
|
||||
foreach af [items-of lastafs] : if af : af.call s
|
||||
|
||||
local {.lhs lhs .rhs rhs} : s.expand [fallback s.contrast Contrast]
|
||||
if closed : then
|
||||
local g : new SpiroContourContext
|
||||
local g : new CurveUtil.ArcFlattener
|
||||
SpiroJs.spiroToBezierOnContext [lhs.slice 0 (-1)] true g QUAD PRECISION
|
||||
local lhsContour g.contours.0
|
||||
set g.contours {}
|
||||
|
@ -179,7 +174,7 @@ export : define [SetupBuilders args] : begin
|
|||
local rhsContour g.contours.0
|
||||
set g.contours {[lhsContour.concat rhsContour]}
|
||||
: else : begin
|
||||
local g : new SpiroContourContext
|
||||
local g : new CurveUtil.ArcFlattener
|
||||
lhs.0.type = rhs.0.type = lhs.(lhs.length - 1).type = rhs.(rhs.length - 1).type = 'corner'
|
||||
SpiroJs.spiroToBezierOnContext [lhs.concat : rhs.reverse] true g QUAD PRECISION
|
||||
|
||||
|
@ -190,7 +185,7 @@ export : define [SetupBuilders args] : begin
|
|||
return g
|
||||
|
||||
define [spiro-outline] : let [k : {}.slice.call arguments 0] : lambda [dontinc] : begin
|
||||
local g : new SpiroContourContext (this.gizmo || globalTransform)
|
||||
local g : new CurveUtil.ArcFlattener (this.gizmo || globalTransform)
|
||||
local {.knots knots .closed closed .lastafs lastafs} : prepareSpiroKnots k g
|
||||
SpiroJs.spiroToBezierOnContext knots closed g QUAD PRECISION
|
||||
foreach af [items-of lastafs] : if af : af.call g
|
|
@ -14,7 +14,7 @@
|
|||
"otfcc-ttcize": "^0.10.1",
|
||||
"patel": "^0.33.1",
|
||||
"semver": "^7.1.3",
|
||||
"spiro": "^1.1.0",
|
||||
"spiro": "^2.0.0",
|
||||
"stylus": "^0.54.7",
|
||||
"toml": "^3.0.0",
|
||||
"topsort": "^0.0.2",
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
const TypoGeom = require("typo-geom");
|
||||
const Point = require("./point");
|
||||
const { mix } = require("./utils");
|
||||
const Transform = require("./transform");
|
||||
|
||||
exports.OffsetCurve = class OffsetCurve {
|
||||
constructor(bone, offset, contrast) {
|
||||
|
@ -195,3 +196,34 @@ exports.convertContourToCubicRev = convertContourToCubicRev;
|
|||
exports.autoCubify = autoCubify;
|
||||
exports.fixedCubify = fixedCubify;
|
||||
exports.convertShapeToArcs = convertShapeToArcs;
|
||||
|
||||
exports.ArcFlattener = class ArcFlattener {
|
||||
constructor(gizmo) {
|
||||
this.gizmo = gizmo || Transform.Id();
|
||||
this.contours = [];
|
||||
this.lastContour = [];
|
||||
}
|
||||
beginShape() {}
|
||||
endShape() {
|
||||
if (this.lastContour.length) {
|
||||
this.contours.push(this.lastContour);
|
||||
}
|
||||
this.lastContour = [];
|
||||
}
|
||||
moveTo(x, y) {
|
||||
this.endShape();
|
||||
this.lastContour.push(Point.transformedXY(this.gizmo, x, y, true));
|
||||
}
|
||||
lineTo(x, y) {
|
||||
this.lastContour.push(Point.transformedXY(this.gizmo, x, y, true));
|
||||
}
|
||||
curveTo(xc, yc, x, y) {
|
||||
this.lastContour.push(Point.transformedXY(this.gizmo, xc, yc, false, false));
|
||||
this.lastContour.push(Point.transformedXY(this.gizmo, x, y, true));
|
||||
}
|
||||
cubicTo(x1, y1, x2, y2, x, y) {
|
||||
this.lastContour.push(Point.transformedXY(this.gizmo, x1, y1, false, true));
|
||||
this.lastContour.push(Point.transformedXY(this.gizmo, x2, y2, false, true));
|
||||
this.lastContour.push(Point.transformedXY(this.gizmo, x, y, true));
|
||||
}
|
||||
};
|
||||
|
|
132
support/spiro-expand.js
Normal file
132
support/spiro-expand.js
Normal file
|
@ -0,0 +1,132 @@
|
|||
const Transform = require("./transform");
|
||||
const { linreg } = require("./utils");
|
||||
|
||||
module.exports = class SpiroExpansionContext {
|
||||
constructor() {
|
||||
this.gizmo = Transform.Id();
|
||||
this.controlKnots = [];
|
||||
this.defaultD1 = 0;
|
||||
this.defaultD2 = 0;
|
||||
}
|
||||
beginShape() {}
|
||||
endShape() {}
|
||||
moveTo(x, y, unimportant) {
|
||||
if (unimportant) return;
|
||||
this.controlKnots.push({
|
||||
type: "g4",
|
||||
d1: this.defaultD1,
|
||||
d2: this.defaultD2,
|
||||
...this.gizmo.apply({ x, y })
|
||||
});
|
||||
}
|
||||
arcTo(arc, x, y) {
|
||||
const k0 = this.controlKnots[this.controlKnots.length - 1];
|
||||
if (!k0) throw new Error("Unreachable: lineTo called before moveTo");
|
||||
if (k0.normalAngle == null) {
|
||||
const tfDerive0 = this.gizmo.applyOffset({ x: arc.deriveX0, y: arc.deriveY0 });
|
||||
k0.normalAngle = Math.PI / 2 + Math.atan2(tfDerive0.y, tfDerive0.x);
|
||||
}
|
||||
{
|
||||
const tfDerive1 = this.gizmo.applyOffset({ x: arc.deriveX1, y: arc.deriveY1 });
|
||||
this.controlKnots.push({
|
||||
type: "g4",
|
||||
d1: k0.d1,
|
||||
d2: k0.d2,
|
||||
...this.gizmo.apply({ x, y }),
|
||||
normalAngle: Math.PI / 2 + Math.atan2(tfDerive1.y, tfDerive1.x)
|
||||
});
|
||||
}
|
||||
}
|
||||
setWidth(l, r) {
|
||||
const k0 = this.controlKnots[this.controlKnots.length - 1];
|
||||
if (k0) {
|
||||
(k0.d1 = l), (k0.d2 = r);
|
||||
} else {
|
||||
(this.defaultD1 = l), (this.defaultD2 = r);
|
||||
}
|
||||
}
|
||||
headsTo(direction) {
|
||||
const k0 = this.controlKnots[this.controlKnots.length - 1];
|
||||
if (k0) k0.proposedNormal = direction;
|
||||
}
|
||||
setType(type) {
|
||||
const k0 = this.controlKnots[this.controlKnots.length - 1];
|
||||
if (k0) k0.type = type;
|
||||
}
|
||||
expand(contrast) {
|
||||
if (contrast == null) contrast = 1 / 0.9;
|
||||
const lhs = [],
|
||||
rhs = [];
|
||||
|
||||
// Create important knots
|
||||
for (let j = 0; j < this.controlKnots.length; j++) {
|
||||
const knot = this.controlKnots[j];
|
||||
if (knot.unimportant) continue;
|
||||
|
||||
let dx = 0,
|
||||
dy = 0;
|
||||
if (knot.proposedNormal) {
|
||||
dx = knot.proposedNormal.x - normalX(knot.normalAngle, contrast);
|
||||
dy = knot.proposedNormal.y - normalY(knot.normalAngle, contrast);
|
||||
}
|
||||
lhs[j] = {
|
||||
type: knot.type,
|
||||
x: knot.x + knot.d1 * (dx + normalX(knot.normalAngle, contrast)),
|
||||
y: knot.y + knot.d1 * (dy + normalY(knot.normalAngle, contrast))
|
||||
};
|
||||
rhs[j] = {
|
||||
type: reverseKnotType(knot.type),
|
||||
x: knot.x - knot.d2 * (dx + normalX(knot.normalAngle, contrast)),
|
||||
y: knot.y - knot.d2 * (dy + normalY(knot.normalAngle, contrast))
|
||||
};
|
||||
}
|
||||
this.interpolateUnimportantKnots(lhs, rhs);
|
||||
return { lhs, rhs };
|
||||
}
|
||||
interpolateUnimportantKnots(lhs, rhs) {
|
||||
for (let j = 0; j < this.controlKnots.length; j++) {
|
||||
const knot = this.controlKnots[j];
|
||||
if (!knot.unimportant) continue;
|
||||
let jBefore, jAfter;
|
||||
for (jBefore = j - 1; this.controlKnots[jBefore].unimportant; jBefore--);
|
||||
for (jAfter = j + 1; this.controlKnots[jAfter].unimportant; jAfter++);
|
||||
|
||||
const knotBefore = this.gizmo.unapply(this.controlKnots[jBefore]),
|
||||
knotAfter = this.gizmo.unapply(this.controlKnots[jAfter]),
|
||||
ref = this.gizmo.unapply(knot),
|
||||
lhsBefore = this.gizmo.unapply(lhs[jBefore]),
|
||||
lhsAfter = this.gizmo.unapply(lhs[jAfter]),
|
||||
rhsBefore = this.gizmo.unapply(rhs[jBefore]),
|
||||
rhsAfter = this.gizmo.unapply(rhs[jAfter]);
|
||||
|
||||
lhs[j] = {
|
||||
type: knot.type,
|
||||
...this.gizmo.apply({
|
||||
x: linreg(knotBefore.x, lhsBefore.x, knotAfter.x, lhsAfter.x, ref.x),
|
||||
y: linreg(knotBefore.y, lhsBefore.y, knotAfter.y, lhsAfter.y, ref.y)
|
||||
})
|
||||
};
|
||||
rhs[j] = {
|
||||
type: reverseKnotType(knot.type),
|
||||
...this.gizmo.apply({
|
||||
x: linreg(knotBefore.x, rhsBefore.x, knotAfter.x, rhsAfter.x, ref.x),
|
||||
y: linreg(knotBefore.y, rhsBefore.y, knotAfter.y, rhsAfter.y, ref.y)
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
function zeroes(n) {
|
||||
let a = new Array(n);
|
||||
for (let i = 0; i < n; ++i) a[i] = 0;
|
||||
return a;
|
||||
}
|
||||
function normalX(angle, contrast) {
|
||||
return Math.cos(angle) * contrast;
|
||||
}
|
||||
function normalY(angle) {
|
||||
return Math.sin(angle);
|
||||
}
|
||||
function reverseKnotType(ty) {
|
||||
return ty === "left" ? "right" : ty === "right" ? "left" : ty;
|
||||
}
|
|
@ -1,191 +0,0 @@
|
|||
import './monotonic-interpolate' as smooth
|
||||
import './transform' as Transform
|
||||
import './point' as Point
|
||||
|
||||
define [fallback] : for [local j 0] (j < arguments.length) [inc j] : if (arguments.(j) !== nothing) : return arguments.(j)
|
||||
define [linreg x0 y0 x1 y1 x] : y0 + (x - x0) * (y1 - y0) / (x1 - x0)
|
||||
|
||||
define-macro xytransform : syntax-rules
|
||||
`[xytransform @tfm @x @y] : begin
|
||||
set [env.declarations.get [formOf x]].isParameter 0
|
||||
set [env.declarations.get [formOf y]].isParameter 0
|
||||
let [t : env.newt] `[begin \\
|
||||
set @t @x
|
||||
set @x : @x * @tfm.xx + @y * @tfm.yx + @tfm.x
|
||||
set @y : @t * @tfm.xy + @y * @tfm.yy + @tfm.y
|
||||
]
|
||||
|
||||
define [normalY angle] : Math.sin angle
|
||||
define [normalX angle vex] : [Math.cos angle] * vex
|
||||
|
||||
class SpiroExpansionContext
|
||||
public [new] : begin
|
||||
set this.gizmo [Transform.Id]
|
||||
set this.controlKnots {}
|
||||
set this.defaultd1 0
|
||||
set this.defaultd2 0
|
||||
|
||||
public [beginShape] : begin
|
||||
public [endShape] : begin
|
||||
|
||||
public [moveTo x y unimportant] : begin
|
||||
if unimportant : return nothing
|
||||
# Transform incoming knots using gizmo
|
||||
xytransform this.gizmo x y
|
||||
this.controlKnots.push {.x x .y y .type 'g4' .d1 this.defaultd1 .d2 this.defaultd2}
|
||||
|
||||
public [lineTo x y unimportant] : begin
|
||||
local lastKnot this.controlKnots.(this.controlKnots.length - 1)
|
||||
xytransform this.gizmo x y
|
||||
local thisKnot {.x x .y y .type 'g4' .d1 lastKnot.d1 .d2 lastKnot.d2}
|
||||
if lastKnot : begin
|
||||
local normalAngle : Math.PI / 2 + [Math.atan2 (y - lastKnot.y) (x - lastKnot.x)]
|
||||
set thisKnot.normalAngle normalAngle
|
||||
if (lastKnot.normalAngle === nothing) : set lastKnot.normalAngle normalAngle
|
||||
if [not unimportant] : this.controlKnots.push thisKnot
|
||||
|
||||
public [cubicTo x1 y1 x2 y2 x y unimportant] : begin
|
||||
local lastKnot this.controlKnots.(this.controlKnots.length - 1)
|
||||
xytransform this.gizmo x1 y1
|
||||
xytransform this.gizmo x2 y2
|
||||
xytransform this.gizmo x y
|
||||
local thisKnot {.x x .y y .type 'g4' .d1 lastKnot.d1 .d2 lastKnot.d2}
|
||||
if (lastKnot && lastKnot.normalAngle === nothing) : begin
|
||||
local normalAngle : Math.PI / 2 + [Math.atan2 (y1 - lastKnot.y) (x1 - lastKnot.x)]
|
||||
if (lastKnot.normalAngle === nothing) : set lastKnot.normalAngle normalAngle
|
||||
if [not unimportant] : begin
|
||||
local normalAngle : Math.PI / 2 + [Math.atan2 (y - y2) (x - x2)]
|
||||
set thisKnot.normalAngle normalAngle
|
||||
this.controlKnots.push thisKnot
|
||||
|
||||
public [setWidth l r] : begin
|
||||
local lastKnot this.controlKnots.(this.controlKnots.length - 1)
|
||||
if lastKnot : then
|
||||
lastKnot.d1 = l; lastKnot.d2 = r
|
||||
: else
|
||||
this.defaultd1 = l; this.defaultd2 = r
|
||||
|
||||
public [headsTo direction] : begin
|
||||
local lastKnot this.controlKnots.(this.controlKnots.length - 1)
|
||||
if lastKnot : begin
|
||||
lastKnot.proposedNormal = direction
|
||||
|
||||
public [setType type] : begin
|
||||
local lastKnot this.controlKnots.(this.controlKnots.length - 1)
|
||||
if lastKnot : begin
|
||||
lastKnot.type = type
|
||||
|
||||
public [expand contrast] : begin
|
||||
local lhs {}
|
||||
local rhs {}
|
||||
|
||||
local contrast : fallback contrast (1 / 0.9)
|
||||
|
||||
local d1s {}
|
||||
local d2s {}
|
||||
local dxs {}
|
||||
local dys {}
|
||||
local js {}
|
||||
foreach j [range 0 this.controlKnots.length] : if [not this.controlKnots.(j).unimportant] : begin
|
||||
local knot this.controlKnots.(j)
|
||||
js.push j
|
||||
d1s.push knot.d1
|
||||
d2s.push knot.d2
|
||||
if knot.proposedNormal : then
|
||||
dxs.push : knot.proposedNormal.x - [normalX knot.normalAngle contrast]
|
||||
dys.push : knot.proposedNormal.y - [normalY knot.normalAngle contrast]
|
||||
: else
|
||||
dxs.push 0; dys.push 0
|
||||
local fd1 : smooth js d1s
|
||||
local fd2 : smooth js d2s
|
||||
local fdx : smooth js dxs
|
||||
local fdy : smooth js dys
|
||||
|
||||
# interpolate important knots
|
||||
foreach j [range 0 this.controlKnots.length] : begin
|
||||
local knot this.controlKnots.(j)
|
||||
if [not knot.unimportant] : begin
|
||||
set lhs.(j) : object
|
||||
x : knot.x + ([fdx j] + [normalX knot.normalAngle contrast]) * [fd1 j]
|
||||
y : knot.y + ([fdy j] + [normalY knot.normalAngle contrast]) * [fd1 j]
|
||||
type knot.type
|
||||
set rhs.(j) : object
|
||||
x : knot.x - ([fdx j] + [normalX knot.normalAngle contrast]) * [fd2 j]
|
||||
y : knot.y - ([fdy j] + [normalY knot.normalAngle contrast]) * [fd2 j]
|
||||
type : match knot.type
|
||||
"left" "right"
|
||||
"right" "left"
|
||||
type type
|
||||
# interpolate unimportant knots referencing their original position relationship
|
||||
foreach j [range 0 this.controlKnots.length] : begin
|
||||
local knot this.controlKnots.(j)
|
||||
if knot.unimportant : begin
|
||||
local jBefore (j - 1)
|
||||
while this.controlKnots.(jBefore).unimportant : dec jBefore
|
||||
local jAfter (j + 1)
|
||||
while this.controlKnots.(jAfter).unimportant : inc jAfter
|
||||
|
||||
local knotBefore : this.gizmo.unapply this.controlKnots.(jBefore)
|
||||
local knotAfter : this.gizmo.unapply this.controlKnots.(jAfter)
|
||||
local ref : this.gizmo.unapply knot
|
||||
local lhsBefore : this.gizmo.unapply lhs.(jBefore)
|
||||
local lhsAfter : this.gizmo.unapply lhs.(jAfter)
|
||||
local rhsBefore : this.gizmo.unapply rhs.(jBefore)
|
||||
local rhsAfter : this.gizmo.unapply rhs.(jAfter)
|
||||
|
||||
local kLHS : this.gizmo.apply : object
|
||||
x : linreg knotBefore.x lhsBefore.x knotAfter.x lhsAfter.x ref.x
|
||||
y : linreg knotBefore.y lhsBefore.y knotAfter.y lhsAfter.y ref.y
|
||||
local kRHS : this.gizmo.apply : object
|
||||
x : linreg knotBefore.x rhsBefore.x knotAfter.x rhsAfter.x ref.x
|
||||
y : linreg knotBefore.y rhsBefore.y knotAfter.y rhsAfter.y ref.y
|
||||
|
||||
set lhs.(j) : object
|
||||
x kLHS.x
|
||||
y kLHS.y
|
||||
type knot.type
|
||||
set rhs.(j) : object
|
||||
x kRHS.x
|
||||
y kRHS.y
|
||||
type : match knot.type
|
||||
"left" "right"
|
||||
"right" "left"
|
||||
type type
|
||||
return {.lhs lhs .rhs rhs}
|
||||
|
||||
class SpiroContourContext
|
||||
public [new gizmo] : begin
|
||||
set this.gizmo (gizmo || [Transform.Id])
|
||||
set this.contours { }
|
||||
set this.defaultTag null
|
||||
|
||||
public [beginShape] : begin
|
||||
|
||||
public [endShape] : begin
|
||||
|
||||
public [moveTo x y] : begin
|
||||
local contour {[Point.transformedXY this.gizmo x y true]}
|
||||
set contour.tag this.defaultTag
|
||||
this.contours.push contour
|
||||
return this
|
||||
|
||||
public [lineTo x y] : begin
|
||||
this.contours.((this.contours.length - 1)).push
|
||||
Point.transformedXY this.gizmo x y true
|
||||
return this
|
||||
|
||||
public [curveTo xc yc x y] : begin
|
||||
this.contours.((this.contours.length - 1)).push
|
||||
Point.transformedXY this.gizmo xc yc false
|
||||
Point.transformedXY this.gizmo x y true
|
||||
return this
|
||||
|
||||
public [cubicTo x1 y1 x2 y2 x y] : begin
|
||||
this.contours.((this.contours.length - 1)).push
|
||||
Point.transformedXY this.gizmo x1 y1 false true
|
||||
Point.transformedXY this.gizmo x2 y2 false true
|
||||
Point.transformedXY this.gizmo x y true
|
||||
return this
|
||||
|
||||
export SpiroExpansionContext
|
||||
export SpiroContourContext
|
|
@ -20,6 +20,12 @@ module.exports = class Transform {
|
|||
y: pt.x * this.xy + pt.y * this.yy + this.y
|
||||
};
|
||||
}
|
||||
applyOffset(delta) {
|
||||
return {
|
||||
x: delta.x * this.xx + delta.y * this.yx,
|
||||
y: delta.x * this.xy + delta.y * this.yy
|
||||
};
|
||||
}
|
||||
unapply(pt) {
|
||||
const xx = pt.x - this.x;
|
||||
const yy = pt.y - this.y;
|
||||
|
|
49
support/utils.js
Normal file
49
support/utils.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
"use strict";
|
||||
|
||||
function mix(a, b, p) {
|
||||
return a + (b - a) * p;
|
||||
}
|
||||
function ratio(l, r, m) {
|
||||
return l === r ? 0 : (m - l) / (r - l);
|
||||
}
|
||||
function barmixL(l, r, b, p) {
|
||||
return l > r ? barmixL(r, l, b, p) : l + b + p * (r - l - b * 3);
|
||||
}
|
||||
function barmixM(l, r, b, p) {
|
||||
return barmixL(l, r, b, p) + b / 2;
|
||||
}
|
||||
function barMixR(l, r, b, p) {
|
||||
return barMixR(l, r, b, p) + b;
|
||||
}
|
||||
function linreg(x0, y0, x1, y1, x) {
|
||||
return y0 + ((x - x0) * (y1 - y0)) / (x1 - x0);
|
||||
}
|
||||
function clamp(l, h, x) {
|
||||
return x < l ? l : x > h ? h : x;
|
||||
}
|
||||
function fallback(...args) {
|
||||
for (const item of args) if (item !== void 0) return item;
|
||||
return void 0;
|
||||
}
|
||||
function bez2(a, b, c, t) {
|
||||
return (1 - t) * (1 - t) * a + 2 * (1 - t) * t * b + t * t * c;
|
||||
}
|
||||
function bez3(a, b, c, d, t) {
|
||||
return (
|
||||
(1 - t) * (1 - t) * (1 - t) * a +
|
||||
3 * (1 - t) * (1 - t) * t * b +
|
||||
3 * t * t * (1 - t) * c +
|
||||
t * t * t * d
|
||||
);
|
||||
}
|
||||
|
||||
exports.mix = mix;
|
||||
exports.ratio = ratio;
|
||||
exports.barmixL = barmixL;
|
||||
exports.barmixM = barmixM;
|
||||
exports.barmixR = barMixR;
|
||||
exports.linreg = linreg;
|
||||
exports.clamp = clamp;
|
||||
exports.fallback = fallback;
|
||||
exports.bez2 = bez2;
|
||||
exports.bez3 = bez3;
|
|
@ -1,12 +0,0 @@
|
|||
import './anchor' as Anchor
|
||||
|
||||
export : define [mix a b p] : a + (b - a) * p
|
||||
export : define [ratio l r m] : if [l === r] 0 ((m - l) / (r - l))
|
||||
export : define [barmixL l r b p] : if (l > r) [barmixL r l b p] (l + b + p * (r - l - b * 3))
|
||||
export : define [barmixM l r b p] : [barmixL l r b p] + b / 2
|
||||
export : define [barmixR l r b p] : [barmixR l r b p] + b
|
||||
export : define [linreg x0 y0 x1 y1 x] : y0 + (x - x0) * (y1 - y0) / (x1 - x0)
|
||||
export : define [clamp l h x] : if (x < l) l : if (x > h) h x
|
||||
export : define [fallback] : begin
|
||||
for [local j 0] (j < arguments.length) [inc j] : if (arguments.(j) !== nothing) : return arguments.(j)
|
||||
return nothing
|
Loading…
Add table
Add a link
Reference in a new issue