grid-apps-cmms/src/kiri/render.js

307 lines
11 KiB
JavaScript
Raw Normal View History

/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
"use strict";
(function() {
2020-11-11 14:40:15 -05:00
const KIRI = self.kiri, BASE = self.base, POLY = BASE.polygons, newPolygon = BASE.newPolygon;
2020-11-06 22:33:56 -05:00
class Render {
constructor() {
this.layers = {};
this.profiles = {};
2020-11-07 19:57:11 -05:00
this.stats = {
contour: 0,
flat_line: 0,
flat_poly: 0,
line_poly: 0,
line: 0
};
}
setLayer(layer, colors, off) {
let layers = this.layers;
2020-11-06 22:33:56 -05:00
if (typeof(colors) === 'number') {
colors = {
2020-11-08 10:55:36 -05:00
line: colors,
face: colors,
opacity: 1
2020-11-06 22:33:56 -05:00
};
}
this.current = layers[layer] = layers[layer] || {
off: off === true,
lines: [],
polys: [],
faces: [],
2020-11-06 23:51:58 -05:00
paths: [],
2020-11-06 22:33:56 -05:00
color: colors || {
line: 0,
face: 0,
opacity: 1
},
};
return this;
}
addLine(p1, p2) {
this.current.lines.push(p1, p2);
return this;
}
addLines(lines, options) {
if (options) {
2020-11-07 18:30:17 -05:00
options.open = true;
const polys = [];
for (let i=0; i<lines.length-1; i += 2) {
2020-11-07 18:30:17 -05:00
polys.push(new BASE.Polygon()
.append(lines[i])
.append(lines[i+1])
2020-11-07 18:30:17 -05:00
.setOpen());
}
2020-11-07 18:30:17 -05:00
return this.addPolys(polys, options);
}
for (let i=0; i<lines.length-1; i += 2) {
this.addLine(lines[i], lines[i+1]);
2020-11-07 19:57:11 -05:00
this.stats.line++;
}
2020-11-08 10:55:36 -05:00
return this;
}
addPoly(poly, options) {
2020-11-07 18:30:17 -05:00
return this.addPolys([poly], options);
}
addPolys(polys, options) {
2020-11-07 18:30:17 -05:00
if (options && options.flat) {
return this.addFlats(polys, options);
}
2020-11-08 20:19:50 -05:00
if (options && !options.thin) {
return this.addPaths(polys, options);
}
2020-11-06 23:51:58 -05:00
polys = flat(polys);
if (options) {
polys.forEach(p => {
if (options.z !== undefined) p.setZ(options.z);
if (options.color) p.color = options.color;
});
2020-11-09 20:12:58 -05:00
// if (options.color) console.log({polys, options})
2020-11-08 20:19:50 -05:00
}
2020-11-07 18:30:17 -05:00
this.current.polys.appendAll(polys);
2020-11-07 19:57:11 -05:00
this.stats.line_poly += polys.length;
return this;
}
2020-11-06 22:33:56 -05:00
2020-11-09 19:22:28 -05:00
// 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) {
2020-11-08 18:39:21 -05:00
this.addPolys(polys.clone(true));
}
}
2020-11-09 19:22:28 -05:00
// misleading name. these are flat offset paths (old style rendering)
2020-11-06 23:51:58 -05:00
addFlats(polys, options) {
const opts = options || {};
const offset = opts.offset || 1;
polys = flat(polys);
2020-11-06 22:33:56 -05:00
if (!polys.length) {
return;
}
const z = polys[0].getZ(), faces = this.current.faces;
2020-11-07 18:30:17 -05:00
const open = opts.open || false;
const off_opt = {
z,
flat: true,
type: open ? ClipperLib.EndType.etOpenSquare : undefined,
};
2020-11-06 22:33:56 -05:00
polys.forEach(poly => {
2020-11-07 18:30:17 -05:00
let exp = off_opt.outs = [];
if (open) {
exp.appendAll(POLY.expand_lines(poly, offset * 0.9, z));
2020-11-07 19:57:11 -05:00
this.stats.flat_line = 0;
2020-11-09 19:22:28 -05:00
} else if (offset) {
2020-11-07 18:30:17 -05:00
POLY.offset([poly], offset * 0.9, off_opt);
POLY.offset([poly], -offset * 0.9, off_opt);
2020-11-07 19:57:11 -05:00
this.stats.flat_poly = 0;
2020-11-07 18:30:17 -05:00
}
2020-11-06 22:33:56 -05:00
if (opts.outline) {
this.addPolys(exp.clone());
}
POLY.nest(exp).forEach((poly,i) => {
poly.earcut().forEach(ep => {
ep.forEachPoint(p => { faces.push(p.x, p.y, p.z) });
});
});
});
2020-11-06 23:51:58 -05:00
return this;
2020-11-06 22:33:56 -05:00
}
2020-11-06 23:51:58 -05:00
addPaths(polys, options) {
const opts = options || {};
2020-11-07 20:31:18 -05:00
const height = opts.height || 1;
2020-11-06 23:51:58 -05:00
const offset = opts.offset || 1;
polys = flat(polys);
if (!polys.length) {
return;
}
const profiles = this.profiles;
2020-11-07 20:31:18 -05:00
const prokey = `${offset}x${height}`;
if (!profiles[prokey]) {
const profile = new THREE.Shape();
2020-11-07 20:31:18 -05:00
profile.moveTo(-offset, -height);
profile.lineTo(-offset, height);
profile.lineTo( offset, height);
profile.lineTo( offset, -height);
profiles[prokey] = profile;
}
2020-11-07 20:31:18 -05:00
const profile = profiles[prokey].clone();
2020-11-06 23:51:58 -05:00
polys.forEach(poly => {
const contour = [];
poly = poly.debur(0.05);
2020-11-11 17:13:54 -05:00
if (!poly) return;
poly = poly.miter();
// cleaning converts open to closed which is problematic
// poly = poly.clean().miter();
2020-11-11 14:40:15 -05:00
// if (poly.debug) {
// const save = this.current;
// this.setLayer('debug.acute', 0xff0000).addPoly(poly, {thin:true});
// this.setLayer('debug.start', 0x00ff00).addPoly(newPolygon().centerRectangle(poly.first(),1,1), {thin:true});
// this.setLayer('debug.end', 0x0000ff).addPoly(newPolygon().centerRectangle(poly.last(),0.8,0.8), {thin:true});
// this.current = save;
// }
2020-11-06 23:51:58 -05:00
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<index.length; i++) {
index[i] += add;
}
const feces = new Float32Array(one.faces.length + faces.length);
const indln = one.index.length;
feces.set(one.faces);
feces.set(faces, one.faces.length);
one.faces = feces;
one.index.appendAll(index);
// allow changing colors
if (opts.color) {
if (!cur.cpath) {
cur.cpath = [ Object.assign({ start: 0, count: indln - 1 }, cur.color) ];
2020-11-09 01:42:17 -05:00
}
// rewrite last color count if color or opacity have changed
const pc = cur.cpath[cur.cpath.length - 1];
if (pc.face !== opts.color.face || pc.opacity !== opts.color.opacity) {
pc.count = indln - 1;
cur.cpath.push(Object.assign({ start: indln, count: Infinity }, opts.color));
}
}
} else {
cur.paths.push({ index, faces, z: poly.getZ() });
if (opts.color) {
cur.cpath = [ Object.assign({ start: 0, count: Infinity }, opts.color) ];
}
}
2020-11-07 19:57:11 -05:00
this.stats.contour++;
2020-11-06 23:51:58 -05:00
});
return this;
}
}
function flat(polys) {
if (Array.isArray(polys)) {
return POLY.flatten(polys.clone(true), [], true);
2020-11-06 23:51:58 -05:00
} else {
return POLY.flatten([polys.clone(true)], [], true);
2020-11-06 23:51:58 -05:00
}
}
function ProfiledContourGeometry(profileShape, contour, contourClosed) {
contourClosed = contourClosed !== undefined ? contourClosed : true;
2020-11-12 20:02:06 -05:00
const profileGeometry = new THREE.ShapeBufferGeometry(profileShape);
2020-11-06 23:51:58 -05:00
profileGeometry.rotateX(Math.PI * .5);
2020-11-12 20:02:06 -05:00
const profile = profileGeometry.attributes.position;
const faces = new Float32Array(profile.count * contour.length * 3);
2020-11-06 23:51:58 -05:00
for (let i = 0; i < contour.length; i++) {
2020-11-12 20:02:06 -05:00
const v1 = new THREE.Vector2().subVectors(contour[i - 1 < 0 ? contour.length - 1 : i - 1], contour[i]);
const v2 = new THREE.Vector2().subVectors(contour[i + 1 == contour.length ? 0 : i + 1], contour[i]);
const angle = v2.angle() - v1.angle();
const halfAngle = angle * .5;
2020-11-06 23:51:58 -05:00
let hA = halfAngle;
let tA = v2.angle() + Math.PI * .5;
if (!contourClosed){
if (i == 0 || i == contour.length - 1) {hA = Math.PI * .5;}
if (i == contour.length - 1) {tA = v1.angle() - Math.PI * .5;}
}
2020-11-12 20:02:06 -05:00
const shift = Math.tan(hA - Math.PI * .5);
const shiftMatrix = new THREE.Matrix4().set(
2020-11-06 23:51:58 -05:00
1, 0, 0, 0,
-shift, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
);
2020-11-12 20:02:06 -05:00
const tempAngle = tA;
const rotationMatrix = new THREE.Matrix4().set(
2020-11-06 23:51:58 -05:00
Math.cos(tempAngle), -Math.sin(tempAngle), 0, 0,
Math.sin(tempAngle), Math.cos(tempAngle), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
);
2020-11-12 20:02:06 -05:00
const translationMatrix = new THREE.Matrix4().set(
2020-11-06 23:51:58 -05:00
1, 0, 0, contour[i].x,
0, 1, 0, contour[i].y,
0, 0, 1, 0,
0, 0, 0, 1,
);
2020-11-12 20:02:06 -05:00
const cloneProfile = profile.clone();
2020-11-06 23:51:58 -05:00
cloneProfile.applyMatrix4(shiftMatrix);
cloneProfile.applyMatrix4(rotationMatrix);
cloneProfile.applyMatrix4(translationMatrix);
faces.set(cloneProfile.array, cloneProfile.count * i * 3);
}
2020-11-12 20:02:06 -05:00
const index = [];
const lastCorner = contourClosed == false ? contour.length - 1: contour.length;
2020-11-06 23:51:58 -05:00
for (let i = 0; i < lastCorner; i++) {
for (let j = 0; j < profile.count; j++) {
2020-11-12 20:02:06 -05:00
const currCorner = i;
const nextCorner = i + 1 == contour.length ? 0 : i + 1;
const currPoint = j;
const nextPoint = j + 1 == profile.count ? 0 : j + 1;
2020-11-06 23:51:58 -05:00
2020-11-12 20:02:06 -05:00
const a = nextPoint + profile.count * currCorner;
const b = currPoint + profile.count * currCorner;
const c = currPoint + profile.count * nextCorner;
const d = nextPoint + profile.count * nextCorner;
2020-11-06 23:51:58 -05:00
index.push(a, b, d);
index.push(b, c, d);
}
}
return {index, faces};
}
self.kiri.Render = Render;
})();