170 lines
8.5 KiB
JavaScript
170 lines
8.5 KiB
JavaScript
// 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) {
|
|
let joins = 0;
|
|
for (let end = 0; end < 2; end++) {
|
|
const join = endJoin(bx, by, end, planned);
|
|
if (join < 0) continue;
|
|
const nb = join >> 1, next = planned && planned.get(nb);
|
|
const slope = isSlopeBelt(next ? next[0] : blockP[nb]) &&
|
|
((nb / BW) | 0) - by === beltGrade(code) * (nb % BW - bx);
|
|
joins |= (slope ? 4 : 1) << end;
|
|
if (!slope && beltGrade(code) * (end ? 1 : -1) > 0 &&
|
|
((nb / BW) | 0) === by) joins |= 128; // low-end feed drops from the same-row flat
|
|
}
|
|
return joins;
|
|
}
|
|
function flatBeltCaps(b, planned = null) {
|
|
return 3 ^ pieceJoins(b % BW, (b / BW) | 0, planned);
|
|
}
|
|
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) | (isFilter(c) && netSat(face) > 0 && screenLoaded(b) && (tick & 2) ? 64 : 0);
|
|
for (let yy = Math.max(0, by - 2); 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;
|
|
if (isFilter(code) && (joins & 64)) y -= 0.45;
|
|
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) {
|
|
if ((joins & 128) && grade < 0) {
|
|
slopeBeltSegment(x, y, -2, 2, 0, 2, ox - 2);
|
|
slopeBeltSegment(x, y, 0, 2, 0, ay, ox);
|
|
slopeBeltSegment(x, y, 0, ay, ax, ay, ox);
|
|
} else slopeBeltSegment(x, y, -2, ay, ax, ay, ox - 2);
|
|
slopeBeltCorner(x, y, ax, ay, ox + ax);
|
|
}
|
|
if (joins & 2) {
|
|
if ((joins & 128) && grade > 0) {
|
|
slopeBeltSegment(x, y, bx, by, 4, by, ox + bx);
|
|
slopeBeltSegment(x, y, 4, by, 4, 2, ox + 4);
|
|
slopeBeltSegment(x, y, 4, 2, 6, 2, ox + 4);
|
|
} else 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 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 = !mesh && joins & 16 ? anim : 0;
|
|
let r = 38, g = 41, blue = 46;
|
|
if (-sd < (mesh ? 1.1 : 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);
|
|
}
|
|
|
|
function screenLoaded(b) {
|
|
const bx = b % BW, by = (b / BW) | 0, c = blockP[b];
|
|
for (let x = bx * BS; x < bx * BS + BS; x++) {
|
|
const i = (beltFaceY(c, bx, by, x) - 1) * W + x;
|
|
if (i >= 0 && (PH[mat[i]] === PH_POWDER || occ[i])) return true;
|
|
}
|
|
return false;
|
|
}
|
|
// Filled far wall, in belt coordinates. Project onto the face so ends stop at the slope ends.
|
|
let slopeWallOwner = -1;
|
|
function slopeWallShape(x, y, b, code = blockP[b]) {
|
|
const g = beltGrade(code), ox = b % BW * BS, oy = ((b / BW) | 0) * BS;
|
|
const sy = g < 0 ? BS : 0, lx = x - ox, ly = y - oy - sy;
|
|
const foot = (lx + g * ly) / 2, depth = (g * lx - ly) * Math.SQRT1_2;
|
|
return Math.max(-foot * Math.SQRT2, (foot - BS) * Math.SQRT2, -depth, depth - CLEAT_HEIGHT);
|
|
}
|
|
function slopeWallAt(x, y, smooth = false) {
|
|
slopeWallOwner = -1;
|
|
let best = smooth ? 0.8 : 0;
|
|
for (let by = Math.max(0, (y >> 2) - 1); by <= Math.min(BH - 1, (y >> 2) + 3); by++)
|
|
for (let bx = Math.max(0, (x >> 2) - 2); bx <= Math.min(BW - 1, (x >> 2) + 2); bx++) {
|
|
const b = by * BW + bx;
|
|
if (!isBelt(blockP[b]) || !isSlopeBelt(blockP[b]) || !slopeUphill(blockP[b], blockV[b])) continue; // downhill: a plain belt
|
|
const sd = slopeWallShape(x + 0.5, y + 0.5, b);
|
|
if (sd <= best) { best = sd; slopeWallOwner = b; }
|
|
}
|
|
return slopeWallOwner;
|
|
}
|
|
// The load occludes the far wall; ribs use exactly the simulation barrier raster.
|
|
function slopeDetailAt(i, x, y, smooth = false) {
|
|
slopeWallOwner = -1;
|
|
if (!slopeBeltHalo[(y >> 2) * BW + (x >> 2)] && !cleatMask[i]) return 0;
|
|
if (occ[i] || mach[i] || part[i]) return 0;
|
|
if (cleatMask[i] && PH[mat[i]] !== PH_SOLID) { slopeWallAt(x, y, smooth); return 1; }
|
|
if (mat[i] !== EMPTY && PH[mat[i]] !== PH_GAS) return 0;
|
|
return slopeWallAt(x, y, smooth) >= 0 ? 2 : 0;
|
|
}
|
|
|
|
function slopePreviewCleat(x, y, b, code) {
|
|
const bx = b % BW, by = (b / BW) | 0, g = beltGrade(code);
|
|
for (let h = 0; h <= CLEAT_REACH; h++) {
|
|
const foot = x - g * h;
|
|
if (foot >= bx * BS && foot < (bx + 1) * BS && foot % CLEAT_SPACING === 0 &&
|
|
y + h + 1 === beltFaceY(code, bx, by, foot)) return true;
|
|
}
|
|
return false;
|
|
}
|