grid-apps-cmms/src2/kiri-mode/fdm/fill.js
2025-07-05 00:19:41 -04:00

320 lines
10 KiB
JavaScript

/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
import { base, util } from '../../geo/base.js';
import { slice as gyroid_slice } from '../../geo/gyroid.js';
export const fill_fixed = {
hex: fillHexFull,
grid: fillGrid,
triangle: fillTriangle
};
export const fill = {
hex: fillHexFull,
grid: fillGrid,
gyroid: fillGyroid,
triangle: fillTriangle,
linear: fillLinear,
cubic: fillCubic
};
const DEG2RAD = Math.PI / 180;
function fillHexFull(target) {
fillHex(target, true);
}
/**
* emitter creates a hex infill pattern and sends to target
*
* @param {Object} target
* @param {boolean} full continuous walls
*/
function fillHex(target, full) {
// compute segment lengths (vert/horiz and 45)
let spacing = target.offset();
// let vhlen = (1 / target.density()) * (target.lineWidth() + spacing);
let vhlen = (1 / target.density()) * target.lineWidth() * 0.5;
let anxlen = (Math.cos(30 * DEG2RAD) * vhlen).round(7);
let anylen = (Math.sin(30 * DEG2RAD) * vhlen).round(7);
let bounds = target.bounds();
let even = true;
let evenZ = target.zIndex() % 2 === 0;
let maxy = bounds.max.y + (vhlen + anylen * 2);
let x, y;
if (full || evenZ) {
x = bounds.min.x;
for (;;) {
if (even && x > bounds.max.x) break;
if (!even && x > bounds.max.x + anxlen + spacing) break;
y = bounds.min.y;
target.newline();
while (y <= maxy) {
target.emit(x,y);
y += vhlen;
target.emit(x,y);
if (even) x += anxlen; else x -= anxlen;
y += anylen;
target.emit(x,y);
y += vhlen;
target.emit(x,y);
if (even) x -= anxlen; else x += anxlen;
y += anylen;
}
x += spacing;
if (even) x += (anxlen * 2);
even = !even;
target.newline();
}
} else {
y = bounds.min.y + vhlen;
for (;;) {
if (even && y > bounds.max.y) break;
if (!even && y > bounds.max.y + anylen) break;
x = bounds.min.x;
target.newline();
while (x < bounds.max.x) {
target.emit(x,y);
if (even) y += anylen; else y -= anylen;
x += anxlen;
target.emit(x,y);
x += spacing;
target.emit(x,y);
if (even) y -= anylen; else y += anylen;
x += anxlen;
target.emit(x,y);
x += spacing;
}
y += vhlen;
if (even) y += (anylen * 2);
even = !even;
target.newline();
}
}
}
function fillGyroid(target) {
let bounds = target.bounds();
let height = target.zHeight();
let span_x = bounds.max.x - bounds.min.x;
let span_y = bounds.max.y - bounds.min.y;
let density = target.density();
let tile = 1 + (1 - density) * 15;
let tile_x = span_x / tile;
let tile_y = span_y / tile;
let tile_z = 1 / tile;
let gen = gyroid_slice(target.zValue() * tile_z, (1 - density) * 500);
let polys = [];
if (gen.dir == 'lr') {
for (let ty=0; ty<=tile_y; ty++) {
for (let tx=0; tx<=tile_x; tx++) {
for (let poly of gen.polys) {
target.newline();
let points = poly.map(el => {
return {
x: el.x * tile + tx * tile + bounds.min.x,
y: el.y * tile + ty * tile + bounds.min.y,
z: 0
}
});
polys.push(base.newPolygon().setOpen(true).addObj(points));
}
}
polys = connectOpenPolys(polys);
}
} else {
for (let tx=0; tx<=tile_x; tx++) {
for (let ty=0; ty<=tile_y; ty++) {
for (let poly of gen.polys) {
target.newline();
let points = poly.map(el => {
return {
x: el.x * tile + tx * tile + bounds.min.x,
y: el.y * tile + ty * tile + bounds.min.y,
z: 0
}
});
polys.push(base.newPolygon().setOpen(true).addObj(points));
}
}
polys = connectOpenPolys(polys);
}
}
for (let poly of polys.filter(p => p.perimeter() > 2)) {
target.newline();
for (let point of poly.points) {
target.emit(point.x, point.y);
}
}
}
function connectOpenPolys(noff, dist = 0.1) {
if (noff.length <= 1) {
return noff;
}
let heal = 0;
// heal/rejoin open segments that have close endpoints
outer: for(;; heal++) {
let ntmp = noff, tlen = ntmp.length;
for (let i=0; i<tlen; i++) {
let s1 = ntmp[i];
if (!s1 || !s1.open) continue;
for (let j=i+1; j<tlen; j++) {
let s2 = ntmp[j];
if (!s2 || !s2.open) continue;
if (s1.last().distTo2D(s2.first()) <= dist) {
s1.addPoints(s2.points);
ntmp[j] = null;
continue outer;
}
if (s1.first().distTo2D(s2.last()) <= dist) {
s2.addPoints(s1.points);
ntmp[i] = null;
continue outer;
}
if (s1.first().distTo2D(s2.first()) <= dist) {
s1.reverse();
s1.addPoints(s2.points);
ntmp[j] = null;
continue outer;
}
if (s1.last().distTo2D(s2.last()) <= dist) {
s2.reverse();
s1.addPoints(s2.points);
ntmp[j] = null;
continue outer;
}
}
}
break;
}
if (heal > 0) {
// cull nulls
noff = noff.filter(o => o);
}
return noff;
}
function fillGrid(target) {
let bounds = target.bounds();
let height = target.zHeight();
let span_x = bounds.max.x - bounds.min.x;
let span_y = bounds.max.y - bounds.min.y;
let offset = target.offset() / 2;
let tile = (1 / target.density()) * target.lineWidth();
let tile_x = tile + offset;
let tile_xc = span_x / tile_x;
let tile_yc = span_y / tile;
for (let tx=0; tx<=tile_xc; tx++) {
target.newline();
for (let ty=0; ty<=tile_yc; ty++) {
let bx = tx * tile_x + bounds.min.x;
let by = ty * tile + bounds.min.y;
if ((tx + ty) % 2) {
target.emit(bx, by);
target.emit(bx + tile_x - offset, by + tile);
} else {
target.emit(bx + tile_x - offset, by);
target.emit(bx, by + tile);
}
}
}
}
function fillCubic(target) {
let bounds = target.bounds();
let span = Math.max(
bounds.max.x - bounds.min.x,
bounds.max.y - bounds.min.y
);
let steps = Math.floor((span / target.lineWidth()) * target.density());
let step = span / steps;
let ztype = Math.floor(target.zIndex() / target.repeat()) % 3;
if (ztype === 1) {
for (let tx=bounds.min.x; tx<=bounds.max.x; tx += step) {
target.newline();
target.emit(tx, bounds.min.y);
target.emit(tx, bounds.max.y);
}
} else if (ztype === 0) {
for (let ty=bounds.min.y; ty<=bounds.max.y; ty += step) {
target.newline();
target.emit(bounds.min.x, ty);
target.emit(bounds.max.x, ty);
}
} else {
step *- Math.sqrt(2);
for (let tx=bounds.min.x; tx<=bounds.max.x; tx += step) {
target.newline();
target.emit(tx, bounds.min.y);
target.emit(tx + 1000, bounds.max.y + 1000);
}
for (let ty=bounds.min.y; ty<=bounds.max.y; ty += step) {
target.newline();
target.emit(bounds.min.x, ty);
target.emit(bounds.min.x + 1000, ty + 1000);
}
}
}
function fillLinear(target) {
let bounds = target.bounds();
let span = Math.max(
bounds.max.x - bounds.min.x,
bounds.max.y - bounds.min.y
);
let steps = Math.floor((span / target.lineWidth()) * target.density());
let step = span / steps;
let ztype = Math.floor(target.zIndex() / target.repeat()) % 2;
if (ztype === 1) {
for (let tx=bounds.min.x; tx<=bounds.max.x; tx += step) {
target.newline();
target.emit(tx, bounds.min.y);
target.emit(tx, bounds.max.y);
}
} else if (ztype === 0) {
for (let ty=bounds.min.y; ty<=bounds.max.y; ty += step) {
target.newline();
target.emit(bounds.min.x, ty);
target.emit(bounds.max.x, ty);
}
}
}
function fillTriangle(target) {
let bounds = target.bounds();
let span_x = bounds.max.x - bounds.min.x;
let span_y = bounds.max.y - bounds.min.y;
let offset = target.offset();
let line_w = target.lineWidth() / 2;
let tile = (1 / target.density()) * (target.lineWidth() * 1.25);
let tile_x = tile + offset*2 + line_w;
let tile_xc = span_x / tile_x;
let tile_yc = span_y / tile;
for (let tx=0; tx<=tile_xc; tx++) {
target.newline();
for (let ty=0; ty<=tile_yc; ty++) {
let bx = tx * tile_x + bounds.min.x;
let by = ty * tile + bounds.min.y;
if ((tx + ty) % 2) {
target.emit(bx, by);
target.emit(bx + tile_x - offset - line_w, by + tile);
} else {
target.emit(bx + tile_x - offset - line_w, by);
target.emit(bx, by + tile);
}
}
}
for (let tx=0; tx<=tile_xc; tx++) {
let bx = tx * tile_x + bounds.min.x;
let xp = bx + tile_x - line_w/2 - offset/2;
target.newline();
target.emit(xp, bounds.min.y);
target.emit(xp, bounds.max.y);
}
}