2020-11-06 21:43:28 -05:00
|
|
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
|
|
|
|
|
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
|
|
(function() {
|
2020-11-06 22:33:56 -05:00
|
|
|
const KIRI = self.kiri, BASE = self.base, POLY = BASE.polygons;
|
|
|
|
|
|
2020-11-06 21:43:28 -05:00
|
|
|
class Render {
|
|
|
|
|
constructor() {
|
|
|
|
|
this.layers = {};
|
2020-11-07 14:55:15 -05:00
|
|
|
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
|
|
|
|
|
};
|
2020-11-06 21:43:28 -05:00
|
|
|
}
|
|
|
|
|
|
2020-11-06 22:33:56 -05:00
|
|
|
setLayer(layer, colors) {
|
2020-11-06 21:43:28 -05:00
|
|
|
let layers = this.layers;
|
2020-11-06 22:33:56 -05:00
|
|
|
if (typeof(colors) === 'number') {
|
|
|
|
|
colors = {
|
|
|
|
|
line: color,
|
2020-11-07 16:27:04 -05:00
|
|
|
face: color,
|
|
|
|
|
opacity: 1
|
2020-11-06 22:33:56 -05:00
|
|
|
};
|
|
|
|
|
}
|
2020-11-06 21:43:28 -05:00
|
|
|
this.current = layers[layer] = layers[layer] || {
|
|
|
|
|
lines: [],
|
|
|
|
|
polys: [],
|
|
|
|
|
faces: [],
|
2020-11-06 23:51:58 -05:00
|
|
|
paths: [],
|
2020-11-06 22:33:56 -05:00
|
|
|
color: colors || {
|
|
|
|
|
line: 0,
|
2020-11-07 16:27:04 -05:00
|
|
|
face: 0,
|
|
|
|
|
opacity: 1
|
|
|
|
|
},
|
2020-11-06 21:43:28 -05:00
|
|
|
};
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addLine(p1, p2) {
|
|
|
|
|
this.current.lines.push(p1, p2);
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-07 14:55:15 -05:00
|
|
|
addLines(lines, options) {
|
|
|
|
|
if (options) {
|
2020-11-07 18:30:17 -05:00
|
|
|
options.open = true;
|
|
|
|
|
const polys = [];
|
2020-11-07 14:55:15 -05:00
|
|
|
for (let i=0; i<lines.length-1; i += 2) {
|
2020-11-07 18:30:17 -05:00
|
|
|
polys.push(new BASE.Polygon()
|
2020-11-07 14:55:15 -05:00
|
|
|
.append(lines[i])
|
|
|
|
|
.append(lines[i+1])
|
2020-11-07 18:30:17 -05:00
|
|
|
.setOpen());
|
2020-11-07 14:55:15 -05:00
|
|
|
}
|
2020-11-07 18:30:17 -05:00
|
|
|
return this.addPolys(polys, options);
|
2020-11-07 14:55:15 -05:00
|
|
|
}
|
|
|
|
|
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-07 14:55:15 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addPoly(poly, options) {
|
2020-11-07 18:30:17 -05:00
|
|
|
return this.addPolys([poly], options);
|
2020-11-06 21:43:28 -05:00
|
|
|
}
|
|
|
|
|
|
2020-11-07 14:55:15 -05:00
|
|
|
addPolys(polys, options) {
|
2020-11-07 18:30:17 -05:00
|
|
|
if (options && options.flat) {
|
|
|
|
|
return this.addFlats(polys, options);
|
|
|
|
|
}
|
2020-11-07 14:55:15 -05:00
|
|
|
if (options) {
|
|
|
|
|
return this.addPaths(polys, options);
|
|
|
|
|
}
|
2020-11-06 23:51:58 -05:00
|
|
|
polys = flat(polys);
|
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;
|
2020-11-06 21:43:28 -05:00
|
|
|
return this;
|
|
|
|
|
}
|
2020-11-06 22:33:56 -05:00
|
|
|
|
2020-11-07 14:55:15 -05:00
|
|
|
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(poly.clone());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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-07 18:30:17 -05:00
|
|
|
} else {
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-07 14:55:15 -05:00
|
|
|
const profiles = this.profiles;
|
2020-11-07 20:31:18 -05:00
|
|
|
const prokey = `${offset}x${height}`;
|
|
|
|
|
if (!profiles[prokey]) {
|
2020-11-07 14:55:15 -05:00
|
|
|
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 14:55:15 -05:00
|
|
|
}
|
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.points.forEach(p => {
|
|
|
|
|
contour.push(new THREE.Vector2(p.x, p.y));
|
|
|
|
|
});
|
2020-11-07 14:55:15 -05:00
|
|
|
const {index, faces} = ProfiledContourGeometry(profile, contour, poly.isClosed());
|
2020-11-07 22:28:31 -05:00
|
|
|
const one = this.current.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);
|
|
|
|
|
feces.set(one.faces);
|
|
|
|
|
feces.set(faces, one.faces.length);
|
|
|
|
|
one.faces = feces;
|
|
|
|
|
one.index.appendAll(index);
|
|
|
|
|
} else {
|
|
|
|
|
this.current.paths.push({ index, faces, z: poly.getZ() });
|
|
|
|
|
}
|
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)) {
|
2020-11-07 14:55:15 -05:00
|
|
|
return POLY.flatten(polys.clone(true), [], true);
|
2020-11-06 23:51:58 -05:00
|
|
|
} else {
|
2020-11-07 14:55:15 -05:00
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
let profileGeometry = new THREE.ShapeBufferGeometry(profileShape);
|
|
|
|
|
profileGeometry.rotateX(Math.PI * .5);
|
|
|
|
|
|
|
|
|
|
let profile = profileGeometry.attributes.position;
|
|
|
|
|
let faces = new Float32Array(profile.count * contour.length * 3);
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < contour.length; i++) {
|
|
|
|
|
let v1 = new THREE.Vector2().subVectors(contour[i - 1 < 0 ? contour.length - 1 : i - 1], contour[i]);
|
|
|
|
|
let v2 = new THREE.Vector2().subVectors(contour[i + 1 == contour.length ? 0 : i + 1], contour[i]);
|
|
|
|
|
let angle = v2.angle() - v1.angle();
|
|
|
|
|
let halfAngle = angle * .5;
|
|
|
|
|
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;}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let shift = Math.tan(hA - Math.PI * .5);
|
|
|
|
|
let shiftMatrix = new THREE.Matrix4().set(
|
|
|
|
|
1, 0, 0, 0,
|
|
|
|
|
-shift, 1, 0, 0,
|
|
|
|
|
0, 0, 1, 0,
|
|
|
|
|
0, 0, 0, 1
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let tempAngle = tA;
|
|
|
|
|
let rotationMatrix = new THREE.Matrix4().set(
|
|
|
|
|
Math.cos(tempAngle), -Math.sin(tempAngle), 0, 0,
|
|
|
|
|
Math.sin(tempAngle), Math.cos(tempAngle), 0, 0,
|
|
|
|
|
0, 0, 1, 0,
|
|
|
|
|
0, 0, 0, 1
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let translationMatrix = new THREE.Matrix4().set(
|
|
|
|
|
1, 0, 0, contour[i].x,
|
|
|
|
|
0, 1, 0, contour[i].y,
|
|
|
|
|
0, 0, 1, 0,
|
|
|
|
|
0, 0, 0, 1,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let cloneProfile = profile.clone();
|
|
|
|
|
cloneProfile.applyMatrix4(shiftMatrix);
|
|
|
|
|
cloneProfile.applyMatrix4(rotationMatrix);
|
|
|
|
|
cloneProfile.applyMatrix4(translationMatrix);
|
|
|
|
|
|
|
|
|
|
faces.set(cloneProfile.array, cloneProfile.count * i * 3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let index = [];
|
|
|
|
|
let lastCorner = contourClosed == false ? contour.length - 1: contour.length;
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < lastCorner; i++) {
|
|
|
|
|
for (let j = 0; j < profile.count; j++) {
|
|
|
|
|
let currCorner = i;
|
|
|
|
|
let nextCorner = i + 1 == contour.length ? 0 : i + 1;
|
|
|
|
|
let currPoint = j;
|
|
|
|
|
let nextPoint = j + 1 == profile.count ? 0 : j + 1;
|
|
|
|
|
|
|
|
|
|
let a = nextPoint + profile.count * currCorner;
|
|
|
|
|
let b = currPoint + profile.count * currCorner;
|
|
|
|
|
let c = currPoint + profile.count * nextCorner;
|
|
|
|
|
let d = nextPoint + profile.count * nextCorner;
|
|
|
|
|
|
|
|
|
|
index.push(a, b, d);
|
|
|
|
|
index.push(b, c, d);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {index, faces};
|
2020-11-06 21:43:28 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.kiri.Render = Render;
|
|
|
|
|
})();
|