grid-apps-cmms/src.old/geo/gyroid.js

220 lines
5.7 KiB
JavaScript

/** Copyright Stewart Allen -- All Rights Reserved */
"use strict";
// dep: geo.base
gapp.register("geo.gyroid", [], (root, exports) => {
const { base } = root;
const PI2 = Math.PI * 2;
let cache = {};
let lastVal;
let lastRes = 0;
let lastSlice = 0;
/**
* @param off {number} z offset value from 0-1
* @param res {number} resolution (pixels/slices per side)
*/
function slice(off, res, val) {
// auto clear cach if it hasn't been hit in the last 20 seconds
// or the requested resolution or tip values have changed
let now = Date.now();
if (res !== lastRes || val !== lastVal || now - lastSlice > 20000) {
cache = {};
}
lastVal = val;
lastRes = res;
lastSlice = now;
let rez = parseInt(res || 200);
let inc = PI2 / rez;
let z = PI2 * off;
let key = (z % PI2).round(3);
let hit = cache[key];
if (hit) {
return hit;
}
let tip = val || 0;
let edge = [];
let vals = [];
let points = 0;
let points_lr = 0;
let points_td = 0;
for (let x=0; x<PI2; x += inc) {
let vrow = []; // raw values row
let erow = []; // edge values row
edge.push(erow);
vals.push(vrow);
for (let y=0; y<PI2; y += inc) {
erow.push(0);
vrow.push(
Math.sin(x) * Math.cos(y) +
Math.sin(y) * Math.cos(z) +
Math.sin(z) * Math.cos(x)
);
}
}
// left-right threshold search (red)
vals.forEach((vrow, y) => {
let erow = edge[y];
let lval = vrow[vrow.length - 1];
vrow.forEach((val, x) => {
if (
(lval <= tip && val >= tip) || (lval >= tip && val <= tip) ||
(lval <= -tip && val >= -tip) || (lval >= -tip && val <= -tip)
) {
erow[x] = 1;
points++;
points_lr++;
}
lval = val;
})
});
// top-down threshold search (green)
for (let x=0; x<rez; x++) {
let lval = vals[vals.length-1][x];
for (let y=0; y<rez; y++) {
let val = vals[y][x];
if (
(lval <= tip && val >= tip) || (lval >= tip && val <= tip) ||
(lval <= -tip && val >= -tip) || (lval >= -tip && val <= -tip)
) {
if (edge[y][x]) {
edge[y][x] = 3;
} else {
edge[y][x] = 2;
points++
}
points_td++;
}
lval = val;
}
}
// deterime prevailing direction for chaining
let dir = points_td > points_lr ? 'lr' : 'td';
// create sparse representation
let sparse = [];
let center = rez / 2;
edge.forEach((row,y) => {
row.forEach((val,x) => {
if (val) {
let dx = Math.abs(x - center);
let dy = Math.abs(y - center);
sparse.push({x: x/rez, y: y/rez, val, dist: Math.max(dx,dy)});
}
});
});
sparse.sort((a,b) => {
return b.dist - a.dist;
});
// join sparse points array by closest distance
let polys = [];
let chain;
let added;
let cleared = 0;
let maxdist = 0.05;
do {
chain = null;
for (let i=0; i<sparse.length; i++) {
if (sparse[i]) {
chain = [ sparse[i] ];
polys.push(chain);
sparse[i] = null;
cleared++;
break;
}
}
do {
added = false;
let target = chain[chain.length - 1];
let cl_elm = null;
let cl_idx = null;
let cl_dst = Infinity;
for (let i=0; i<sparse.length; i++) {
let test_el = sparse[i];
if (test_el) {
let dst = distTo(target, test_el, dir);
if (cl_idx === null || dst < cl_dst) {
cl_idx = i;
cl_elm = test_el;
cl_dst = dst;
}
}
}
if (cl_elm) {
if (cl_dst > maxdist) {
break;
}
sparse[cl_idx] = null;
cleared++;
chain.push(cl_elm);
added = true;
}
} while (added);
} while (cleared < sparse.length);
let psimple = polys
.map(poly => filter(poly, 0))
.map(poly => filter(poly, inc));
let slice = {edge, points, dir, polys: psimple};
cache[key] = slice;
return slice;
}
// merge co-linear and distance threshold
function filter(poly, inc) {
if (poly.length <= 2) {
return poly;
}
let nupoly = [ poly[0] ];
let e1 = poly[1];
let e2 = null;
let last = poly.length - 2;
for (let i=1; i<poly.length; i++) {
let el = poly[i];
let drop = inc ?
(distTo(e1, el) <= inc) :
(e1.x === el.x || e1.y === el.y);
if (drop) {
e2 = el;
if (i < last) {
continue;
}
}
if (e2) {
nupoly.push({x:(e1.x + e2.x)/2, y:(e1.y + e2.y)/2});
e2 = null;
} else {
nupoly.push(e1);
if (i === last) {
nupoly.push(el);
}
}
e1 = el;
}
nupoly.push(poly[poly.length-1]);
return nupoly;
}
function distTo(a, b, dir) {
let dx = a.x - b.x;
let dy = a.y - b.y;
// bias distance by prevailing direction of discovery to join stragglers
if (dir === 'lr') dx = dx / 2;
if (dir === 'td') dy = dy / 2;
return Math.sqrt(dx * dx + dy * dy);
}
gapp.overlay(base, {
gyroid: { slice }
})
});