/** Copyright Stewart Allen -- All Rights Reserved */ "use strict"; // dep: geo.base // dep: geo.polygon // dep: geo.polygons gapp.register("kiri.layers", [], (root, exports) => { const { base, kiri } = root; const { polygons, newPolygon } = base; const POLY = base.polygons; class Layers { constructor() { this.init(); } init() { this.layers = {}; this.profiles = {}; this.stats = { contour: 0, flat_line: 0, flat_poly: 0, line_poly: 0, line: 0 }; } getLayer(layer) { return this.layers[layer]; } setLayer(layer, colors, off) { let layers = this.layers; if (typeof(colors) === 'number') { colors = { line: colors, face: colors, opacity: 1 }; } this.current = layers[layer] = layers[layer] || { off: off === true, lines: [], polys: [], // colors are an attribute on polygons faces: [], cface: undefined, // face color indices paths: [], cpath: undefined, // path colors indices color: colors || { check: 0, line: 0, face: 0, opacity: 1 }, }; return this; } addLine(p1, p2) { this.current.lines.push(p1, p2); return this; } addLines(lines, options) { if (options) { options.open = true; const polys = []; for (let i=0; i p.clean(true)); } if (options && options.flat) { return this.addFlats(polys, options); } if (options && !options.thin) { return this.addPaths(polys, options); } polys = flat(polys); if (options) { polys.forEach(p => { if (options.z !== undefined) { p.setZ(options.z); } switch (typeof(options.color)) { case 'number': p.color = options.color; break; case 'object': p.color = options.color.line; break; } }); } this.current.polys.appendAll(polys); this.stats.line_poly += polys.length; return this; } // z planar closed polygonal areas addAreas(polys, options) { const faces = this.current.faces; polys = Array.isArray(polys) ? polys : [ polys ]; polys.forEach(poly => { poly.earcut().forEach(ep => { ep.forEachPoint(p => { faces.push(p.x, p.y, p.z) }); }); }); if (options && options.outline) { this.addPolys(polys.clone(true)); } } // misleading name. these are flat offset paths (old style rendering) addFlats(polys, options) { const opts = options || {}; const offset = opts.offset || 1; polys = flat(polys); if (!polys.length) { return; } const z = polys[0].getZ(), faces = this.current.faces; const off_opt = { z, flat: true }; polys.forEach(poly => { let exp = off_opt.outs = []; if (poly.isOpen()) { // shorten line by offset const pp = poly.points, pl = pp.length; pp[0] = pp[0].offsetPointTo(pp[1], offset); pp[pl-1] = pp[pl-1].offsetPointTo(pp[pl-2], offset); exp.appendAll(POLY.expand_lines(poly, offset * 0.9, z)); // force re-nest if offsets cause ends to join exp = POLY.flatten(exp,[],true); this.stats.flat_line = 0; } else if (offset) { POLY.offset([poly], offset * 0.9, off_opt); POLY.offset([poly], -offset * 0.9, off_opt); this.stats.flat_poly = 0; } if (opts.outline) { this.addPolys(exp.clone()); } const faceidx = faces.length / 3; POLY.nest(exp).forEach((poly,i) => { poly.earcut().forEach(ep => { ep.forEachPoint(p => { faces.push(p.x, p.y, p.z) }); }); }); const cur = this.current; const color = opts.color ? (typeof(opts.color) === 'number' ? { line: opts.color, face: opts.color } : opts.color) : cur.color; if (!cur.cface) { cur.cface = [ Object.assign({ start: faceidx, count: Infinity }, color) ]; } else { // rewrite last color count if color or opacity have changed const pc = cur.cface[cur.cface.length - 1]; if (pc.face !== color.face || pc.opacity !== color.opacity) { pc.count = faceidx; cur.cface.push(Object.assign({ start: faceidx, count: Infinity }, color)); } } }); return this; } addPaths(polys, options) { const opts = options || {}; const height = opts.height || 1; const offset = opts.offset || 1; polys = flat(polys); if (!polys.length) { return; } const profiles = this.profiles; const prokey = `${offset}x${height}`; if (!profiles[prokey]) { const profile = new THREE.Shape(); profile.moveTo(-offset, -height); profile.lineTo(-offset, height); profile.lineTo( offset, height); profile.lineTo( offset, -height); profiles[prokey] = profile; } const profile = profiles[prokey].clone(); polys.forEach(poly => { const contour = []; poly = poly.debur(0.05); if (!poly) return; poly = poly.miter(); poly.points.forEach(p => { contour.push(new THREE.Vector2(p.x, p.y)); }); const {index, faces} = ProfiledContourGeometry(profile, contour, poly.isClosed()); const cur = this.current; const one = cur.paths[0]; if (one) { // merge all contour geometry for massive speed gain const add = one.faces.length / 3; for (let i=0; i