Move boolean composition out of kits.

This commit is contained in:
be5invis 2021-03-02 18:37:15 -08:00
parent 4cf5faf93c
commit 3ef9aaa2e2
7 changed files with 103 additions and 42 deletions

View file

@ -1,38 +1,22 @@
import 'typo-geom' as TypoGeom import 'typo-geom' as TypoGeom
import '../support/curve-util' as CurveUtil import '../support/curve-util' as CurveUtil
import '../support/geometry' as [object BooleanGeometry]
export : define [SetupBuilders args] : begin export : define [SetupBuilders args] : begin
define [object Glyph GlobalTransform] args define [object Glyph GlobalTransform] args
define [Boole operator] : begin define [impl operator operands] : begin
define [BooleImplFn] : begin return : function [] : begin
define k : {}.slice.call arguments 0 local operandGeometries {}
return : lambda [] : begin foreach operand [items-of operands] : begin
local contourArcs : getArcsFromProc this k.0 local g1 : new Glyph
if (k.length === 1) : set contourArcs : TypoGeom.Boolean.removeOverlap set g1.gizmo : this.gizmo || GlobalTransform
begin contourArcs g1.include operand
begin TypoGeom.Boolean.PolyFillType.pftNonZero operandGeometries.push g1.geometry
begin CurveUtil.BOOLE_RESOLUTION
foreach [item : items-of : k.slice 1] : begin this.includeGeometry : new BooleanGeometry operator operandGeometries
set contourArcs : TypoGeom.Boolean.combine operator
begin contourArcs
getArcsFromProc this item
begin TypoGeom.Boolean.PolyFillType.pftNonZero
begin TypoGeom.Boolean.PolyFillType.pftNonZero
begin CurveUtil.BOOLE_RESOLUTION
local ctx : new CurveUtil.BezToContoursSink define [union] : impl TypoGeom.Boolean.ClipType.ctUnion [{}.slice.call arguments 0]
TypoGeom.ShapeConv.transferBezArcShape contourArcs ctx define [intersection] : impl TypoGeom.Boolean.ClipType.ctIntersection [{}.slice.call arguments 0]
this.includeContours ctx.contours 0 0 define [difference] : impl TypoGeom.Boolean.ClipType.ctDifference [{}.slice.call arguments 0]
define [getArcsFromProc g p] : begin
local g1 : new Glyph
set g1.gizmo : g.gizmo || GlobalTransform
g1.include p
return : CurveUtil.convertShapeToArcs : g1.geometry.asContours
define union : Boole TypoGeom.Boolean.ClipType.ctUnion
define intersection : Boole TypoGeom.Boolean.ClipType.ctIntersection
define difference : Boole TypoGeom.Boolean.ClipType.ctDifference
return [object union intersection difference] return [object union intersection difference]

View file

@ -63,9 +63,7 @@ export : define [SetupBuilders args] : begin
define [widths.center.heading w d] : lambda [] : begin define [widths.center.heading w d] : lambda [] : begin
if this.setWidth : this.setWidth ([fallback w Stroke] / 2) ([fallback w Stroke] / 2) if this.setWidth : this.setWidth ([fallback w Stroke] / 2) ([fallback w Stroke] / 2)
if this.headsTo : this.headsTo d if this.headsTo : this.headsTo d
define [unimportant] : begin define [unimportant] : if this.setUnimportant : this.setUnimportant
if (this.points && this.points.length && this.points.(this.points.length - 1)) : this.points.(this.points.length - 1).subdivided = true
if (this.controlKnots && this.controlKnots.length && this.controlKnots.(this.controlKnots.length - 1)) : this.controlKnots.(this.controlKnots.length - 1).unimportant = true
define [important] nothing define [important] nothing
# Interpolation pesudoknots # Interpolation pesudoknots
@ -175,8 +173,7 @@ export : define [SetupBuilders args] : begin
return { .knots knots .closed closed } return { .knots knots .closed closed }
define [convertSpiroToBezier knots closed ctx] : begin define [convertSpiroToBezier knots closed ctx] : begin
define CUBIC false return : SpiroJs.spiroToBezierOnContext knots closed ctx CurveUtil.SPIRO_PRECISION
return : SpiroJs.spiroToBezierOnContext knots closed ctx CUBIC CurveUtil.GEOMETRY_PRECISION
define [dispiro] : let [args : {}.slice.call arguments 0] : lambda [] : begin define [dispiro] : let [args : {}.slice.call arguments 0] : lambda [] : begin
define CLOSED true define CLOSED true
@ -220,4 +217,4 @@ export : define [SetupBuilders args] : begin
g4 g2 corner flat curl close end straight g4 g2 corner flat curl close end straight
widths disable-contrast heading unimportant important widths disable-contrast heading unimportant important
alsoThru alsoThruThem bezcontrols quadcontrols archv arcvh complexThru alsoThru alsoThruThem bezcontrols quadcontrols archv arcvh complexThru
dispiro spiro-outline] dispiro spiro-outline]

View file

@ -4,6 +4,7 @@ const TypoGeom = require("typo-geom");
const Point = require("./point"); const Point = require("./point");
const Transform = require("./transform"); const Transform = require("./transform");
exports.SPIRO_PRECISION = 1 / 2;
exports.GEOMETRY_PRECISION = 1 / 4; exports.GEOMETRY_PRECISION = 1 / 4;
exports.RECIP_GEOMETRY_PRECISION = 4; exports.RECIP_GEOMETRY_PRECISION = 4;
exports.BOOLE_RESOLUTION = 0x4000; exports.BOOLE_RESOLUTION = 0x4000;
@ -34,6 +35,18 @@ exports.OffsetCurve = class OffsetCurve {
} }
}; };
exports.ReverseCurve = class ReverseCurve {
constructor(original) {
this.m_original = original;
}
eval(t) {
return this.m_original.eval(1 - t);
}
derivative(t) {
return -this.m_original.derivative(1 - t);
}
};
exports.convertShapeToArcs = function convertShapeToArcs(shape) { exports.convertShapeToArcs = function convertShapeToArcs(shape) {
return shape.map(convertContourToArcs); return shape.map(convertContourToArcs);
}; };

View file

@ -2,6 +2,8 @@
const Point = require("./point"); const Point = require("./point");
const Transform = require("./transform"); const Transform = require("./transform");
const CurveUtil = require("./curve-util");
const TypoGeom = require("typo-geom");
class GeometryBase { class GeometryBase {
asContours() { asContours() {
@ -132,7 +134,9 @@ class TransformedGeometry extends GeometryBase {
return result; return result;
} }
filterTag(fn) { filterTag(fn) {
return new TransformedGeometry(this.m_geom.filterTag(fn), this.m_transform); const e = this.m_geom.filterTag(fn);
if (!e) return null;
return new TransformedGeometry(e, this.m_transform);
} }
isEmpty() { isEmpty() {
return this.m_geom.isEmpty(); return this.m_geom.isEmpty();
@ -192,6 +196,62 @@ class CombineGeometry extends GeometryBase {
} }
} }
class BooleanGeometry extends GeometryBase {
constructor(operator, operands) {
super();
this.m_operator = operator;
this.m_operands = operands;
this.m_resolved = null;
}
asContours() {
if (this.m_resolved) return this.m_resolved;
this.m_resolved = this.asContoursImpl();
return this.m_resolved;
}
asContoursImpl() {
let arcs = CurveUtil.convertShapeToArcs(this.m_operands[0].asContours());
if (this.m_operands.length < 2) {
arcs = TypoGeom.Boolean.removeOverlap(
arcs,
TypoGeom.Boolean.PolyFillType.pftNonZero,
CurveUtil.BOOLE_RESOLUTION
);
}
for (let j = 1; j < this.m_operands.length; j++) {
arcs = TypoGeom.Boolean.combine(
this.m_operator,
arcs,
CurveUtil.convertShapeToArcs(this.m_operands[j].asContours()),
TypoGeom.Boolean.PolyFillType.pftNonZero,
TypoGeom.Boolean.PolyFillType.pftNonZero,
CurveUtil.BOOLE_RESOLUTION
);
}
const ctx = new CurveUtil.BezToContoursSink();
TypoGeom.ShapeConv.transferBezArcShape(arcs, ctx);
return ctx.contours;
}
asReferences() {
return null;
}
filterTag(fn) {
let filtered = [];
for (const operand of this.m_operands) {
const fp = operand.filterTag(fn);
if (fp) filtered.push(fp);
}
return new BooleanGeometry(this.m_operator, filtered);
}
isEmpty() {
for (const operand of this.m_operands) if (!operand.isEmpty()) return false;
return true;
}
measureComplexity() {
let s = 0;
for (const operand of this.m_operands) s += operand.measureComplexity();
}
}
function combineWith(a, b) { function combineWith(a, b) {
if (a instanceof CombineGeometry) { if (a instanceof CombineGeometry) {
return a.with(b); return a.with(b);
@ -206,5 +266,6 @@ exports.ReferenceGeometry = ReferenceGeometry;
exports.TaggedGeometry = TaggedGeometry; exports.TaggedGeometry = TaggedGeometry;
exports.TransformedGeometry = TransformedGeometry; exports.TransformedGeometry = TransformedGeometry;
exports.CombineGeometry = CombineGeometry; exports.CombineGeometry = CombineGeometry;
exports.BooleanGeometry = BooleanGeometry;
exports.combineWith = combineWith; exports.combineWith = combineWith;

View file

@ -95,15 +95,15 @@ module.exports = class Glyph {
this.avoidBeingComposite = g.avoidBeingComposite; this.avoidBeingComposite = g.avoidBeingComposite;
} }
combineGeometryImpl(g) { includeGeometry(g) {
if (this.ctxTag) g = new Geom.TaggedGeometry(g, this.ctxTag); if (this.ctxTag) g = new Geom.TaggedGeometry(g, this.ctxTag);
this.geometry = Geom.combineWith(this.geometry, g); this.geometry = Geom.combineWith(this.geometry, g);
} }
includeGlyphImpl(g, shiftX, shiftY) { includeGlyphImpl(g, shiftX, shiftY) {
if (g._m_identifier) { if (g._m_identifier) {
this.combineGeometryImpl(new Geom.ReferenceGeometry(g, shiftX, shiftY)); this.includeGeometry(new Geom.ReferenceGeometry(g, shiftX, shiftY));
} else { } else {
this.combineGeometryImpl( this.includeGeometry(
new Geom.TransformedGeometry(g.geometry, Transform.Translate(shiftX, shiftY)) new Geom.TransformedGeometry(g.geometry, Transform.Translate(shiftX, shiftY))
); );
} }
@ -116,7 +116,7 @@ module.exports = class Glyph {
for (const z of contour) c.push(Point.translated(z, shiftX, shiftY)); for (const z of contour) c.push(Point.translated(z, shiftX, shiftY));
parts.push(new Geom.ContourGeometry(c)); parts.push(new Geom.ContourGeometry(c));
} }
this.combineGeometryImpl(new Geom.CombineGeometry(parts)); this.includeGeometry(new Geom.CombineGeometry(parts));
} }
applyTransform(tfm, alsoAnchors) { applyTransform(tfm, alsoAnchors) {

View file

@ -53,6 +53,10 @@ module.exports = class SpiroExpansionContext {
const k0 = this.controlKnots[this.controlKnots.length - 1]; const k0 = this.controlKnots[this.controlKnots.length - 1];
if (k0) k0.type = type; if (k0) k0.type = type;
} }
setUnimportant() {
const k0 = this.controlKnots[this.controlKnots.length - 1];
if (k0) k0.unimportant = true;
}
expand(contrast) { expand(contrast) {
if (contrast == null) contrast = 1 / 0.9; if (contrast == null) contrast = 1 / 0.9;
const lhs = [], const lhs = [],
@ -100,6 +104,7 @@ module.exports = class SpiroExpansionContext {
rhsAfter = this.gizmo.unapply(rhs[jAfter]); rhsAfter = this.gizmo.unapply(rhs[jAfter]);
lhs[j] = { lhs[j] = {
unimportant: true,
type: knot.type, type: knot.type,
...this.gizmo.apply({ ...this.gizmo.apply({
x: linreg(knotBefore.x, lhsBefore.x, knotAfter.x, lhsAfter.x, ref.x), x: linreg(knotBefore.x, lhsBefore.x, knotAfter.x, lhsAfter.x, ref.x),
@ -107,6 +112,7 @@ module.exports = class SpiroExpansionContext {
}) })
}; };
rhs[j] = { rhs[j] = {
unimportant: true,
type: reverseKnotType(knot.type), type: reverseKnotType(knot.type),
...this.gizmo.apply({ ...this.gizmo.apply({
x: linreg(knotBefore.x, rhsBefore.x, knotAfter.x, rhsAfter.x, ref.x), x: linreg(knotBefore.x, rhsBefore.x, knotAfter.x, rhsAfter.x, ref.x),

View file

@ -17,10 +17,10 @@
"otb-ttc-bundle": "^1.0.0", "otb-ttc-bundle": "^1.0.0",
"patel": "^0.34.0", "patel": "^0.34.0",
"semver": "^7.3.4", "semver": "^7.3.4",
"spiro": "^2.0.0", "spiro": "^3.0.0",
"stylus": "^0.54.8", "stylus": "^0.54.8",
"toposort": "^2.0.2", "toposort": "^2.0.2",
"typo-geom": "^0.11.0", "typo-geom": "^0.12.0",
"verda": "^1.2.1", "verda": "^1.2.1",
"wawoff2": "^1.0.2", "wawoff2": "^1.0.2",
"which": "^2.0.2" "which": "^2.0.2"