Move boolean composition out of kits.
This commit is contained in:
parent
4cf5faf93c
commit
3ef9aaa2e2
7 changed files with 103 additions and 42 deletions
|
@ -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]
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue