Sloped belts and filters drawn as one belt turned to 45° (t40)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-25 23:50:45 -04:00
co-authored by Claude Opus 5.5
parent e19cb784d2
commit 0cf615eb3d
11 changed files with 383 additions and 79 deletions
+122 -23
View File
@@ -1,28 +1,127 @@
// A slope's band uses world coordinates, so its stripes never restart at block joints.
function slopeBeltAt(i, x, y) {
const b = (y >> 2) * BW + (x >> 2);
if (isSlopeBelt(blockP[b])) return b;
if ((y & 3) || ((x & 3) !== 0 && (x & 3) !== 3)) return -1;
const owner = slopeSealOwner(i);
return owner >= 0 && isSlopeBelt(blockP[owner]) ? owner : -1;
// Drawing only: the face stays on its simulation cells; the band hangs below it.
let slopeBeltJoins = new Uint8Array(0), slopeBeltHalo = new Uint8Array(0), beltCaps = new Uint8Array(0);
let slopeAcross = 0, slopeAlong = 0, slopeDistance = 0;
function slopeBeltConnections(bx, by, code, planned = null) {
const grade = beltGrade(code);
let joins = 0;
for (let end = 0; end < 2; end++) {
const side = end ? 1 : -1, nx = bx + side, ny = by + side * grade;
const nb = ny * BW + nx, next = planned && planned.get(nb);
if (nx >= 0 && nx < BW && ny >= 0 && ny < BH && beltGrade(next ? next[0] : blockP[nb]) === grade) {
joins |= 4 << end; continue;
}
const fy = by + (end ? grade > 0 : grade < 0), fb = fy * BW + nx;
if (nx < 0 || nx >= BW || fy < 0 || fy >= BH) continue;
const flat = planned && planned.get(fb), c = flat ? flat[0] : blockP[fb], v = flat ? flat[1] : blockV[fb];
if (c === BP_BELT || c === BP_FILTER || (c === BP_TUBE && (v === 1 || v === 3))) joins |= 1 << end;
}
return joins;
}
function flatBeltCaps(b) {
const bx = b % BW, by = (b / BW) | 0;
let caps = 0;
for (let end = 0; end < 2; end++) {
const x = bx + (end ? 1 : -1);
let joined = false;
if (x >= 0 && x < BW) for (let dy = -1; dy <= 0; dy++) {
if (by + dy < 0) continue;
const n = (by + dy) * BW + x, c = blockP[n], g = beltGrade(c);
if ((!dy && (c === BP_BELT || c === BP_FILTER || (c === BP_TUBE && (blockV[n] === 1 || blockV[n] === 3 || blockV[n] >= 4)))) ||
(g && dy === -(end ? g < 0 : g > 0))) joined = true;
}
if (!joined) caps |= 1 << end;
}
return caps;
}
function prepareSlopeBelts(x0, y0, x1, y1) {
if (slopeBeltJoins.length !== NB) { slopeBeltJoins = new Uint8Array(NB); slopeBeltHalo = new Uint8Array(NB); beltCaps = new Uint8Array(NB); }
slopeBeltHalo.fill(0);
for (let by = Math.max(0, (y0 >> 2) - 2); by <= Math.min(BH - 1, (y1 >> 2) + 2); by++) {
for (let bx = Math.max(0, (x0 >> 2) - 2); bx <= Math.min(BW - 1, (x1 >> 2) + 2); bx++) {
const b = by * BW + bx, c = blockP[b];
if (c === BP_BELT || c === BP_FILTER) beltCaps[b] = flatBeltCaps(b);
if (!isSlopeBelt(c)) continue;
const face = beltFaceY(c, bx, by, bx * BS) * W + bx * BS;
slopeBeltJoins[b] = slopeBeltConnections(bx, by, c) | (netSat(face) > 0 ? 16 : 0) | (filterCfg.get(b)?.inv ? 32 : 0);
for (let yy = Math.max(0, by - 1); yy <= Math.min(BH - 1, by + 1); yy++)
for (let xx = Math.max(0, bx - 1); xx <= Math.min(BW - 1, bx + 1); xx++) slopeBeltHalo[yy * BW + xx] = 1;
}
}
}
// Rounded stroke, with coordinates along/across the winning segment for rollers and mesh.
function slopeBeltSegment(x, y, ax, ay, bx, by, origin) {
const dx = bx - ax, dy = by - ay, len = Math.hypot(dx, dy), ux = dx / len, uy = dy / len;
const along = (x - ax) * ux + (y - ay) * uy, across = -(x - ax) * uy + (y - ay) * ux;
const qx = Math.abs(along - len / 2) - len / 2 + 0.75, qy = Math.abs(across) - 1.1;
const sd = Math.hypot(Math.max(0, qx), Math.max(0, qy)) + Math.min(Math.max(qx, qy), 0) - 0.75;
if (sd < slopeDistance) { slopeDistance = sd; slopeAcross = across + 2; slopeAlong = origin + along; }
}
function slopeBeltCorner(x, y, ax, ay, origin) {
const dx = x - ax, dy = y - ay, radius = Math.hypot(dx, dy), sd = radius - 1.85;
if (sd < slopeDistance) { slopeDistance = sd; slopeAcross = 2 + dy; slopeAlong = origin + dx; }
}
function slopeBeltShape(x, y, b, joins = slopeBeltJoins[b], code = blockP[b]) {
const grade = beltGrade(code), ox = (b % BW) * BS, oy = ((b / BW) | 0) * BS;
x -= ox; y -= oy;
const offset = 1.85 * Math.SQRT2, sy = grade < 0 ? 4 : 0;
const normal = 1.85 * Math.SQRT1_2;
let ax = -grade * normal, ay = sy + normal, bx = 4 - grade * normal, by = 4 - sy + normal;
if (joins & 1) { ay = sy + 2; ax = (ay - sy - offset) / grade; }
else if (joins & 4) { ax -= 4; ay -= grade * 4; }
if (joins & 2) { by = 6 - sy; bx = (by - sy - offset) / grade; }
else if (joins & 8) { bx += 4; by += grade * 4; }
slopeDistance = Infinity;
const origin = (ox + grade * oy) * Math.SQRT1_2;
slopeBeltSegment(x, y, ax, ay, bx, by, origin + (ax + grade * ay) * Math.SQRT1_2);
if (joins & 1) {
slopeBeltSegment(x, y, -2, ay, ax, ay, ox - 2);
slopeBeltCorner(x, y, ax, ay, ox + ax);
}
if (joins & 2) {
slopeBeltSegment(x, y, bx, by, 6, by, ox + bx);
slopeBeltCorner(x, y, bx, by, ox + bx);
}
return slopeDistance;
}
function slopeBeltAt(i, x, y, smooth = false) {
const own = (y >> 2) * BW + (x >> 2), code = blockP[own];
if (!slopeBeltHalo[own]) return -1;
const slope = isSlopeBelt(code);
if (!slope && (mach[i] || occ[i] || (mat[i] !== EMPTY && mat[i] !== BELTL && mat[i] !== BELTR))) return -1;
let seal = !slope && (mat[i] === BELTL || mat[i] === BELTR || part[i] === P_FILTER) ? slopeSealOwner(i) : -1;
if (seal >= 0 && !isSlopeBelt(blockP[seal])) seal = -1;
if (!slope && seal < 0 && (mat[i] !== EMPTY || part[i] || mach[i] || occ[i] || code)) return -1;
let best = smooth ? 0.8 : 0, found = slope ? own : seal;
for (let yy = Math.max(0, (y >> 2) - 1); yy <= Math.min(BH - 1, (y >> 2) + 1); yy++) {
for (let xx = Math.max(0, (x >> 2) - 1); xx <= Math.min(BW - 1, (x >> 2) + 1); xx++) {
const b = yy * BW + xx;
if (!isSlopeBelt(blockP[b])) continue;
const sd = slopeBeltShape(x + 0.5, y + 0.5, b);
if (sd < best) { best = sd; found = b; }
}
}
return found;
}
function slopeBeltColor(b, x, y, anim, br, bg, bb) {
const c = blockP[b], grade = beltGrade(c), d = y - beltFaceY(c, b % BW, (b / BW) | 0, x);
let r = br, g = bg, blue = bb;
if (d >= 0) {
const dir = isFilter(c) ? (blockV[b] & 128 ? 1 : -1) : (blockV[b] ? 1 : -1);
const axis = (x + grade * y) * 0.5, stripe = (Math.floor((axis - dir * (anim >> 1)) / 2) & 1);
if (d === 0) {
r = stripe ? 92 : 56; g = stripe ? 96 : 58; blue = stripe ? 104 : 66;
if (isFilter(c) && !stripe) {
const f = filterCfg.get(b), m = f && f.m[0];
if (m) { r = COLR[m] * 0.7; g = COLG[m] * 0.7; blue = COLB[m] * 0.7; }
}
} else if (isFilter(c)) {
if (d === 1 && ((x + (grade < 0 ? 0 : 1)) & 1)) { r = 64; g = 68; blue = 76; }
} else if (d === 1) {
r = stripe ? 132 : 104; g = stripe ? 120 : 94; blue = stripe ? 108 : 84;
} else { r = 46; g = 48; blue = 54; }
const sd = slopeBeltShape(x + 0.5, y + 0.5, b);
if (sd > 1e-8) return (br | 0) | ((bg | 0) << 8) | ((bb | 0) << 16);
const c = blockP[b], joins = slopeBeltJoins[b], mesh = isFilter(c);
const dir = mesh ? (blockV[b] & 128 ? 1 : -1) : (blockV[b] ? 1 : -1);
const a = slopeAlong, d = slopeAcross, time = joins & 16 ? anim : 0;
let r = 38, g = 41, blue = 46;
if (-sd < 0.8) {
const stripe = (Math.floor((a - dir * (d < 2 ? 1 : -1) * (time >> 1)) / 2) & 1), k = d < 2 ? 1 : 0.72;
r = (stripe ? 92 : 56) * k; g = (stripe ? 96 : 58) * k; blue = (stripe ? 104 : 66) * k;
} else if (mesh) {
const f = filterCfg.get(b), m = f && f.m.length ? f.m[(Math.floor(a / 2) & 65535) % f.m.length] : 0;
if ((Math.floor(a) + Math.floor(d)) & 1) { r = br * 0.8; g = bg * 0.8; blue = bb * 0.8; }
else { r = m ? COLR[m] * 0.75 : 64; g = m ? COLG[m] * 0.75 : 68; blue = m ? COLB[m] * 0.75 : 76; }
} else {
const dx = ((a % 2 + 2) % 2) - 1, dy = d - 2;
if (dx * dx + dy * dy < 0.95 * 0.95) {
const spin = Math.cos((Math.atan2(dy, dx) - dir * time / 1.9) * 3) > 0.6;
r = spin ? 104 : 132; g = spin ? 94 : 120; blue = spin ? 84 : 108;
}
}
return (r | 0) | ((g | 0) << 8) | ((blue | 0) << 16);
}