improve slice/preview visuals with custom normals

This commit is contained in:
Stewart Allen 2022-10-14 12:42:51 -04:00
commit ce856146e9
7 changed files with 143 additions and 41 deletions

View file

@ -196,6 +196,8 @@ function pointsToPath(points, offset, open, miter = 1.5) {
const left = [];
const right = [];
const faces = [];
const normals = [];
const zn = -1;
// calculate vertex normals from segments normals
// vertex info is associated with the origin point
let fl, fr;
@ -244,19 +246,33 @@ function pointsToPath(points, offset, open, miter = 1.5) {
let r1 = right.peek(1);
let l2 = left.peek(2);
let r2 = right.peek(2);
let ln = l1.vp.normal;
let rn = r1.vp.normal;
if (split_left && split_right) {
faces.push(l1, l2, r1);
faces.push(r2, r1, l2);
fl = fl || l2;
fr = fr || r2;
normals.push(ln.dx, ln.dy, zn);
normals.push(ln.dx, ln.dy, zn);
normals.push(-rn.dx, -rn.dy, zn);
normals.push(-rn.dx, -rn.dy, zn);
normals.push(-rn.dx, -rn.dy, zn);
normals.push(ln.dx, ln.dy, zn);
} else if (split_left) {
faces.push(l1, l2, r1);
fl = fl || l2;
fr = fr || r1;
normals.push(ln.dx, ln.dy, zn);
normals.push(ln.dx, ln.dy, zn);
normals.push(-rn.dx, -rn.dy, zn);
} else { // split right
faces.push(r2, r1, l1);
fl = fl || l1;
fr = fr || r2;
normals.push(-rn.dx, -rn.dy, zn);
normals.push(-rn.dx, -rn.dy, zn);
normals.push(ln.dx, ln.dy, zn);
}
}
} else {
@ -270,6 +286,14 @@ function pointsToPath(points, offset, open, miter = 1.5) {
let r1 = right.peek(split_right ? 2 : 1);
faces.push(l1, l0, r1);
faces.push(r0, r1, l0);
let ln = l0.vp.normal;
let rn = r0.vp.normal;
normals.push(ln.dx, ln.dy, zn);
normals.push(ln.dx, ln.dy, zn);
normals.push(-rn.dx, -rn.dy, zn);
normals.push(-rn.dx, -rn.dy, zn);
normals.push(-rn.dx, -rn.dy, zn);
normals.push(ln.dx, ln.dy, zn);
}
}
if (open) {
@ -292,15 +316,24 @@ function pointsToPath(points, offset, open, miter = 1.5) {
if (!open && faces) {
let l1 = left.peek(1);
let r1 = right.peek(1);
let ln = l1.vp.normal;
let rn = r1.vp.normal;
faces.push(fl, l1, fr);
faces.push(r1, fr, l1);
normals.push(ln.dx, ln.dy, zn);
normals.push(ln.dx, ln.dy, zn);
normals.push(-rn.dx, -rn.dy, zn);
normals.push(-rn.dx, -rn.dy, zn);
normals.push(-rn.dx, -rn.dy, zn);
normals.push(ln.dx, ln.dy, zn);
}
return { left, right, faces, open };
return { left, right, faces, normals, open };
}
function pathTo3D(path, height, z) {
const { faces, left, right, open } = path;
const { faces, normals, left, right, open } = path;
const out = [];
const nrm = [];
if (!(faces && left && right)) {
return [];
}
@ -315,6 +348,13 @@ function pathTo3D(path, height, z) {
for (let p of faces.slice().reverse()) {
out.push(p.x, p.y, p.z + height);
}
nrm.appendAll(normals);
// reverse normals to match faces, but underside so reverse Z as well
for (let i=normals.length-1; i>0; i-=3) {
nrm.push(normals[i-2]);
nrm.push(normals[i-1]);
nrm.push(-normals[i-0]);
}
for (let i=0, l=left.length, tl = open ? l-1 : l; i<tl; i++) {
let p0 = left[i];
let p1 = left[(i+1)%l];
@ -324,6 +364,13 @@ function pathTo3D(path, height, z) {
out.push(p1.x, p1.y, p1.z - height);
out.push(p1.x, p1.y, p1.z + height);
out.push(p0.x, p0.y, p0.z + height);
let ln = p0.vp.normal;
nrm.push(ln.dx, ln.dy, -1);
nrm.push(ln.dx, ln.dy, 1);
nrm.push(ln.dx, ln.dy, 1);
nrm.push(ln.dx, ln.dy, 1);
nrm.push(ln.dx, ln.dy, -1);
nrm.push(ln.dx, ln.dy, -1);
}
for (let i=0, l=right.length, tl = open ? l-1 : l; i<tl; i++) {
let p0 = right[i];
@ -334,8 +381,16 @@ function pathTo3D(path, height, z) {
out.push(p1.x, p1.y, p1.z - height);
out.push(p0.x, p0.y, p0.z + height);
out.push(p1.x, p1.y, p1.z + height);
let rn = p0.vp.normal;
nrm.push(-rn.dy, rn.dx, 1);
nrm.push(-rn.dy, rn.dx, -1);
nrm.push(-rn.dy, rn.dx, -1);
nrm.push(-rn.dy, rn.dx, -1);
nrm.push(-rn.dy, rn.dx, 1);
nrm.push(-rn.dy, rn.dx, 1);
}
if (open) {
// begin cap
let l0 = left[0];
let r0 = right[0];
out.push(l0.x, l0.y, l0.z + height);
@ -344,16 +399,31 @@ function pathTo3D(path, height, z) {
out.push(r0.x, r0.y, r0.z + height);
out.push(r0.x, r0.y, r0.z - height);
out.push(l0.x, l0.y, l0.z + height);
let ln = left.peek();
let rn = right.peek();
out.push(ln.x, ln.y, ln.z + height);
out.push(ln.x, ln.y, ln.z - height);
out.push(rn.x, rn.y, rn.z - height);
out.push(rn.x, rn.y, rn.z + height);
out.push(ln.x, ln.y, ln.z + height);
out.push(rn.x, rn.y, rn.z - height);
let ln = l0.vp.normal;
nrm.push(-ln.dy, ln.dx, 1);
nrm.push(-ln.dy, ln.dx, -1);
nrm.push(-ln.dy, ln.dx, -1);
nrm.push(-ln.dy, ln.dx, 1);
nrm.push(-ln.dy, ln.dx, -1);
nrm.push(-ln.dy, ln.dx, 1);
// end cap
let le = left.peek();
let re = right.peek();
out.push(le.x, le.y, le.z + height);
out.push(le.x, le.y, le.z - height);
out.push(re.x, re.y, re.z - height);
out.push(re.x, re.y, re.z + height);
out.push(le.x, le.y, le.z + height);
out.push(re.x, re.y, re.z - height);
ln = re.vp.normal;
nrm.push(-ln.dy, ln.dx, 1);
nrm.push(-ln.dy, ln.dx, -1);
nrm.push(-ln.dy, ln.dx, -1);
nrm.push(-ln.dy, ln.dx, 1);
nrm.push(-ln.dy, ln.dx, 1);
nrm.push(-ln.dy, ln.dx, -1);
}
return out;
return { faces: out, normals: nrm };
}
// produces indexed geometry which isn't ideal for rendering because

View file

@ -920,6 +920,11 @@ class Polygon {
return this;
}
setOpenValue(b) {
this.open = b;
return this;
}
isOpen() {
return this.open;
}

View file

@ -481,7 +481,7 @@ FDM.slice = function(settings, widget, onupdate, ondone) {
let fillSpace = fillSpacing * spaceMult * solidWidth;
doSolidLayerFill(slice, fillSpace, sliceFillAngle);
}
sliceFillAngle += 90.0;
sliceFillAngle = (sliceFillAngle + 90.0) % 360;
}, "solid layers");
// add lead in anchor when specified in belt mode (but not for synths)
@ -717,7 +717,7 @@ FDM.slice = function(settings, widget, onupdate, ondone) {
let fillSpace = fillSpacing * spaceMult * solidWidth;
let solidMinArea = params.sliceSolidMinArea;
doSolidsFill(slice, fillSpace, sliceFillAngle, solidMinArea, promises);
sliceFillAngle += 90.0;
sliceFillAngle = (sliceFillAngle + 90.0) % 360;
}, "fill solids");
// very last layer (top) is set to finish solid rate
slices.last().finishSolids = true

View file

@ -195,13 +195,15 @@ kiri.Layers.prototype.encode = function(state) {
polys: encode(layer.polys, state),
lines: encodePointArray(layer.lines, state),
faces: codec.allocFloat32Array(layer.faces),
norms: layer.norms ? codec.allocFloat32Array(layer.norms) : undefined,
cface: layer.cface || codec.undef,
color: layer.color,
paths: layer.paths.map(lp => {
const pe = {
z: lp.z,
index: lp.index,
faces: codec.allocFloat32Array(lp.faces)
faces: codec.allocFloat32Array(lp.faces),
norms: lp.norms ? codec.allocFloat32Array(lp.norms) : undefined
};
if (zeroOut && state.zeros && pe.faces.length) {
state.zeros.push(pe.faces.buffer);
@ -226,22 +228,25 @@ kiri.Layers.prototype.encode = function(state) {
registerDecoder('layers', function(v, state) {
let render = new kiri.Layers();
for (let i=0; i<v.layers.length; i++) {
const data = v.data[i];
const d = render.layers[v.layers[i]] = {
polys: decode(v.data[i].polys, state),
cpoly: v.data[i].cpoly,
lines: decodePointArray(v.data[i].lines),
faces: codec.allocFloat32Array(v.data[i].faces),
cface: v.data[i].cface,
color: v.data[i].color,
paths: v.data[i].paths.map(lp => {
polys: decode(data.polys, state),
cpoly: data.cpoly,
lines: decodePointArray(data.lines),
faces: codec.allocFloat32Array(data.faces),
norms: data.norms ? codec.allocFloat32Array(data.norms) : undefined,
cface: data.cface,
color: data.color,
paths: data.paths.map(lp => {
return {
z: lp.z,
index: lp.index,
faces: codec.allocFloat32Array(lp.faces)
faces: codec.allocFloat32Array(lp.faces),
norms: lp.norms ? codec.allocFloat32Array(lp.norms) : undefined
};
}),
cpath: v.data[i].cpath,
off: v.data[i].off
cpath: data.cpath,
off: data.off
};
// fixup null -> Infinity in material counts (JSON stringify sucks)
if (d.cface) {

View file

@ -55,6 +55,7 @@ class Layers {
lines: [],
polys: [], // colors are an attribute on polygons
faces: [],
norms: undefined, // vertex normals
cface: undefined, // face color indices
paths: [],
cpath: undefined, // path colors indices
@ -148,18 +149,25 @@ class Layers {
if (!polys.length) {
return;
}
const z = polys[0].getZ(), faces = this.current.faces;
const cur = this.current;
const z = polys[0].getZ(), faces = cur.faces;
const off_opt = { z, flat: true };
polys.forEach(poly => {
const faceidx = faces.length / 3;
if (newPath) {
const path = poly.toPath2D(offset * 0.95);
const { left, right, normals } = path;
for (let p of path.faces) {
faces.push(p.x, p.y, p.z);
}
if (opts.outline) {
this.addPolys(newPolygon().addPoints(path.left).setOpen());
this.addPolys(newPolygon().addPoints(path.right).setOpen());
this.addPolys(newPolygon().addPoints(left).setOpenValue(poly.open));
this.addPolys(newPolygon().addPoints(right).setOpenValue(poly.open));
}
if (cur.norms) {
cur.norms.appendAll(normals);
} else {
cur.norms = normals;
}
} else {
let exp = off_opt.outs = [];
@ -186,7 +194,6 @@ class Layers {
});
});
}
const cur = this.current;
const color = opts.color ?
(typeof(opts.color) === 'number' ? { line: opts.color, face: opts.color } : opts.color) :
cur.color;
@ -220,7 +227,7 @@ class Layers {
continue;
}
const z = opts.z || poly.getZ();
const faces = poly.toPath3D(offset, height, 0);
const { faces, normals } = poly.toPath3D(offset, height, 0);
const cur = this.current;
const one = cur.paths[0];
if (one) {
@ -244,8 +251,11 @@ class Layers {
cur.cpath.push(Object.assign({ start: indln, count: Infinity }, opts.color));
}
}
if (normals) {
one.norms.appendAll(normals);
}
} else {
cur.paths.push({ index: [], faces, z });
cur.paths.push({ index: [], faces, z, norms: normals });
if (opts.color) {
cur.cpath = [ Object.assign({ start: 0, count: Infinity }, opts.color) ];
}

View file

@ -101,7 +101,7 @@ class Stack {
}
}
const { polys, lines, faces, cface, paths, cpath, color, off } = layer;
const { polys, lines, faces, cface, paths, cpath, color, off, norms } = layer;
const meshes = [];
const defstate = !off;
const mats = [];
@ -168,15 +168,20 @@ class Stack {
} else {
geo.setAttribute('position', new THREE.BufferAttribute(faces, 3));
}
geo.computeVertexNormals();
if (norms) {
geo.setAttribute('normal', new THREE.BufferAttribute(norms, 3));
// geo.normalizeNormals();
} else {
geo.computeVertexNormals();
}
if (cface) {
cface.forEach((c, i) => geo.addGroup(c.start, c.count, i));
} else {
geo.addGroup(0, Infinity, 0);
}
const mesh = new THREE.Mesh(geo, mat);
mesh.castShadow = true;
mesh.receiveShadow = true;
// mesh.castShadow = true;
// mesh.receiveShadow = true;
meshes.push(mesh);
group.add(mesh);
mats.appendAll(mat);
@ -191,11 +196,18 @@ class Stack {
mat.forEach(m => m.visible = defstate);
// const mat = newMat(data);
paths.forEach((path, i) => {
const { index, faces, z, colors } = path;
const { index, faces, norms, z, colors } = path;
const geo = new THREE.BufferGeometry();
geo.setAttribute('position', new THREE.BufferAttribute(faces, 3));
if (index.length) geo.setIndex(index);
geo.computeVertexNormals();
if (index.length) {
geo.setIndex(index);
}
if (norms) {
geo.setAttribute('normal', new THREE.BufferAttribute(norms, 3));
// geo.normalizeNormals();
} else {
geo.computeVertexNormals();
}
if (cpath) {
cpath.forEach((c, i) => geo.addGroup(c.start, c.count, i));
} else {
@ -203,8 +215,8 @@ class Stack {
}
const mesh = new THREE.Mesh(geo, mat);
mesh.position.z = z;
mesh.castShadow = true;
mesh.receiveShadow = true;
// mesh.castShadow = true;
// mesh.receiveShadow = true;
meshes.push(mesh);
group.add(mesh);
});

View file

@ -307,8 +307,8 @@ class Widget {
mesh.renderOrder = 1;
geometry.computeVertexNormals();
geometry.addGroup(0, Infinity, 0);
mesh.castShadow = true;
mesh.receiveShadow = true;
// mesh.castShadow = true;
// mesh.receiveShadow = true;
mesh.widget = this;
this.mesh = mesh;
// invalidates points cache (like any scale/rotation)