Diagonal tubes as thick as straight ones, with centred elbows where they join (t35)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+7
-6
@@ -356,10 +356,10 @@ Drawing (src/render/): browser only. This file: the renderer state R, colour tab
|
|||||||
Propagated coloured light from glowing and burning cells.
|
Propagated coloured light from glowing and burning cells.
|
||||||
- functions: builtForLight, looseForLight, computeLight
|
- functions: builtForLight, looseForLight, computeLight
|
||||||
|
|
||||||
### src/render/world.js (361 lines)
|
### src/render/world.js (410 lines)
|
||||||
Scale colour without changing the smooth-look class.
|
Scale colour without changing the smooth-look class.
|
||||||
- functions: fogColor, diagonalTubeBlock, drawWorld
|
- functions: fogColor, diagonalTubeJoins, diagonalTubeShape, diagonalTubeBlock, drawWorld
|
||||||
- variables: TUBE_CLASS
|
- variables: TUBE_CLASS, diagonalJoins, diagonalAcross, diagonalAlong, diagonalCap
|
||||||
|
|
||||||
### src/render/machines.js (242 lines)
|
### src/render/machines.js (242 lines)
|
||||||
Machine overlays: fans, engines, silo and the rest, drawn on top of the world.
|
Machine overlays: fans, engines, silo and the rest, drawn on top of the world.
|
||||||
@@ -374,7 +374,7 @@ Smooth look: WebGL setup, texture uploads, drawing and adaptive resolution.
|
|||||||
- functions: smCompile, smoothAvailable, smUpload, smoothDraw, smoothPace
|
- functions: smCompile, smoothAvailable, smUpload, smoothDraw, smoothPace
|
||||||
- variables: SM, smBytes
|
- variables: SM, smBytes
|
||||||
|
|
||||||
### src/render/smooth-shaders.js (348 lines)
|
### src/render/smooth-shaders.js (366 lines)
|
||||||
Smooth shaders: vertex and fragment programs for the rounded world look.
|
Smooth shaders: vertex and fragment programs for the rounded world look.
|
||||||
- variables: SM_VS, SM_FS
|
- variables: SM_VS, SM_FS
|
||||||
|
|
||||||
@@ -467,7 +467,7 @@ Gamepad input.
|
|||||||
- functions: padPressed, pollPad
|
- functions: padPressed, pollPad
|
||||||
- variables: PAD
|
- variables: PAD
|
||||||
|
|
||||||
### src/ui/previews.js (199 lines)
|
### src/ui/previews.js (216 lines)
|
||||||
Cursor previews: shared grid helpers, air streaks and tool preview dispatch.
|
Cursor previews: shared grid helpers, air streaks and tool preview dispatch.
|
||||||
- functions: blockRectPx, drawGrid, previewColor, drawAirStreaks, drawReachCircle, drawPreviews, previewBlock
|
- functions: blockRectPx, drawGrid, previewColor, drawAirStreaks, drawReachCircle, drawPreviews, previewBlock
|
||||||
|
|
||||||
@@ -476,7 +476,7 @@ Drill previews: shaken rock cracks, pulse wavefronts and aiming fan. Rock the dr
|
|||||||
- functions: crackSeg, drawCracks, pulseRGB, fanPoints, drawDrillPulses, drawDrillAim
|
- functions: crackSeg, drawCracks, pulseRGB, fanPoints, drawDrillPulses, drawDrillAim
|
||||||
- variables: CRACK_N, CRACK_LV
|
- variables: CRACK_N, CRACK_LV
|
||||||
|
|
||||||
### src/ui/preview-selection.js (80 lines)
|
### src/ui/preview-selection.js (81 lines)
|
||||||
Selection previews: labelled tags, copy and cut boxes, and paste ghosts. a small labelled tag drawn on the world (screen pixels)
|
Selection previews: labelled tags, copy and cut boxes, and paste ghosts. a small labelled tag drawn on the world (screen pixels)
|
||||||
- functions: drawTag, drawSelection, drawPastePreview
|
- functions: drawTag, drawSelection, drawPastePreview
|
||||||
- variables: SEL_COL
|
- variables: SEL_COL
|
||||||
@@ -547,4 +547,5 @@ The frame loop and boot.
|
|||||||
- `tools/scenes/stone-uses.js`: The stone and sand machines: a half-full silo with its gate open over a belt, a stone cutter fed with rubble, and cut stone and glass brick walls. Run clear-sky.js first, e.g. python3 tools/shot.py /tmp/stone.png --setup tools/scenes/clear-sky.js --js "$(cat tools/scenes/stone-uses.js)" --ticks 240 --zoom 7
|
- `tools/scenes/stone-uses.js`: The stone and sand machines: a half-full silo with its gate open over a belt, a stone cutter fed with rubble, and cut stone and glass brick walls. Run clear-sky.js first, e.g. python3 tools/shot.py /tmp/stone.png --setup tools/scenes/clear-sky.js --js "$(cat tools/scenes/stone-uses.js)" --ticks 240 --zoom 7
|
||||||
- `tools/scenes/t33-ground.js`: Paused dug pocket: copper ore, rubble and dirt against stone, granite and dirt. Use --js "..." to set daylight or R.lens after this setup; CAM frames the pocket.
|
- `tools/scenes/t33-ground.js`: Paused dug pocket: copper ore, rubble and dirt against stone, granite and dirt. Use --js "..." to set daylight or R.lens after this setup; CAM frames the pocket.
|
||||||
- `tools/scenes/t35-diagonal.js`: Run after clear-sky.js: a belt takes sand from a pile, up a diagonal screw to an upper belt.
|
- `tools/scenes/t35-diagonal.js`: Run after clear-sky.js: a belt takes sand from a pile, up a diagonal screw to an upper belt.
|
||||||
|
- `tools/scenes/t35-joins.js`: Run after clear-sky.js. Paused, empty pipes make casing, flights and mouths easy to inspect.
|
||||||
- `tools/scenes/t36-funnel.js`: A twelve-block funnel, one block open at the bottom, draining 400 grains of sand.
|
- `tools/scenes/t36-funnel.js`: A twelve-block funnel, one block open at the bottom, draining 400 grains of sand.
|
||||||
|
|||||||
@@ -4,6 +4,14 @@ What agents have done, newest first. Add an entry for every piece of work: the d
|
|||||||
note or request it was for, what changed, the files touched, the tests run, and anything left
|
note or request it was for, what changed, the files touched, the tests run, and anything left
|
||||||
open or not verified. Keep each entry short; the git history has the details.
|
open or not verified. Keep each entry short; the git history has the details.
|
||||||
|
|
||||||
|
## 2026-10-25: t35 follow-up: diagonal tube elbows (Claude Code, Codex)
|
||||||
|
|
||||||
|
- Diagonal tubes are as thick as straight ones, and an end block joining a straight tube, belt or
|
||||||
|
filter draws an elbow at its centre, so the pipes meet centred and edge to edge; free ends get a
|
||||||
|
collar; ghosts match (`src/render/world.js`, `smooth-shaders.js`, `src/ui/previews.js`,
|
||||||
|
`preview-selection.js`). Scene `tools/scenes/t35-joins.js`.
|
||||||
|
- `test/t_diag_tubes.js`; `run_all.py --browser` ALL PASSED.
|
||||||
|
|
||||||
## 2026-10-25: t35/t36 follow-ups: wall drags make slopes, diagonal tube art (Claude Code, Codex)
|
## 2026-10-25: t35/t36 follow-ups: wall drags make slopes, diagonal tube art (Claude Code, Codex)
|
||||||
|
|
||||||
- Wall piece: a diagonal drag lays slopes (`pieceCode`/`turnSlope` in `src/ui/selection.js`), T flips
|
- Wall piece: a diagonal drag lays slopes (`pieceCode`/`turnSlope` in `src/ui/selection.js`), T flips
|
||||||
|
|||||||
@@ -51,7 +51,8 @@ float beltSD(vec2 lb, bool endL, bool endR) {
|
|||||||
// the line only rounds off where the whole run ends.
|
// the line only rounds off where the whole run ends.
|
||||||
float beltLineSD(ivec2 c, vec2 lb) {
|
float beltLineSD(ivec2 c, vec2 lb) {
|
||||||
int nr = cls(cellAt(tCol, c + ivec2(4, 0))), nl = cls(cellAt(tCol, c - ivec2(4, 0)));
|
int nr = cls(cellAt(tCol, c + ivec2(4, 0))), nl = cls(cellAt(tCol, c - ivec2(4, 0)));
|
||||||
return beltSD(lb, isBelt(nl) + isF(nl) < 0.5, isBelt(nr) + isF(nr) < 0.5);
|
return beltSD(lb, isBelt(nl) + isF(nl) < 0.5 && !(nl >= 19 && nl <= 22),
|
||||||
|
isBelt(nr) + isF(nr) < 0.5 && !(nr >= 19 && nr <= 22));
|
||||||
}
|
}
|
||||||
// The rubber band of a belt or filter: 1 on a ridge. Ridges travel along the top run one way and
|
// The rubber band of a belt or filter: 1 on a ridge. Ridges travel along the top run one way and
|
||||||
// back along the bottom.
|
// back along the bottom.
|
||||||
@@ -250,18 +251,35 @@ void main() {
|
|||||||
bool vert = nc == 9 || nc == 11;
|
bool vert = nc == 9 || nc == 11;
|
||||||
float sp2 = nc == 9 ? -1.0 : nc == 10 ? 1.0 : nc == 11 ? 1.0 : -1.0;
|
float sp2 = nc == 9 ? -1.0 : nc == 10 ? 1.0 : nc == 11 ? 1.0 : -1.0;
|
||||||
float a = (vert ? fract(w.x * 0.25) : fract(w.y * 0.25)) * 4.0, along = vert ? w.y : w.x;
|
float a = (vert ? fract(w.x * 0.25) : fract(w.y * 0.25)) * 4.0, along = vert ? w.y : w.x;
|
||||||
|
float tubeSD = -1.0, capRing = 0.0;
|
||||||
if (nc >= 19) {
|
if (nc >= 19) {
|
||||||
vec2 d = nc == 19 ? vec2(1.0, -1.0) : nc == 20 ? vec2(1.0, 1.0) : nc == 21 ? vec2(-1.0, 1.0) : vec2(-1.0, -1.0);
|
vec2 d = vec2(1.0, nc == 19 || nc == 21 ? -1.0 : 1.0);
|
||||||
float offset = cellAt(tTree, c).r * 255.0 - 128.0;
|
vec4 tag = cellAt(tTree, c);
|
||||||
bool rising = nc == 19 || nc == 21;
|
vec2 local = floor(tag.gb * 255.0 + 0.5) - 8.0 + fract(w) - 2.0;
|
||||||
a = 2.0 + (offset + dot(fract(w) - 0.5, vec2(1.0, rising ? 1.0 : -1.0))) * 0.70710678;
|
int joins = int(tag.a * 255.0 + 0.5);
|
||||||
along = dot(w, d) * 0.70710678; sp2 = 1.0;
|
tubeSD = 100.0;
|
||||||
|
for (int end = 0; end < 2; end++) {
|
||||||
|
int join = (joins >> (end * 3)) & 7;
|
||||||
|
float side = end == 0 ? -1.0 : 1.0;
|
||||||
|
vec2 ray = join == 0 ? vec2(side, side * (nc == 19 || nc == 21 ? -1.0 : 1.0)) * 0.70710678 :
|
||||||
|
join == 1 ? vec2(0, -1) : join == 2 ? vec2(1, 0) : join == 3 ? vec2(0, 1) : vec2(-1, 0);
|
||||||
|
float t = dot(local, ray), cross2 = dot(local, vec2(-ray.y, ray.x));
|
||||||
|
float radius = length(vec2(cross2, min(0.0, t))), cap = t - (join == 0 ? 2.82842712 : 2.0);
|
||||||
|
float sd = max(radius - 2.0, join != 0 || (joins & (64 << end)) != 0 ? cap : -10.0);
|
||||||
|
if (sd < tubeSD) {
|
||||||
|
tubeSD = sd;
|
||||||
|
a = 2.0 + (side * cross2 < 0.0 ? -radius : radius);
|
||||||
|
along = dot(w - local, d) * 0.70710678 + side * t;
|
||||||
|
capRing = (joins & (64 << end)) != 0 ? smoothstep(-0.55, -0.15, cap) : 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sp2 = nc == 19 || nc == 20 ? 1.0 : -1.0;
|
||||||
}
|
}
|
||||||
float wall = 0.62;
|
float wall = 0.62;
|
||||||
vec3 tc;
|
vec3 tc;
|
||||||
if (a < wall || a > 4.0 - wall) {
|
if (a < wall || a > 4.0 - wall || capRing > 0.5) {
|
||||||
float u2 = a < wall ? a / wall : (4.0 - a) / wall; // 0 at the outside, 1 at the bore
|
float u2 = a < wall ? a / wall : (4.0 - a) / wall; // 0 at the outside, 1 at the bore
|
||||||
float cyl = sin(u2 * 1.5708);
|
float cyl = sin(clamp(u2, 0.0, 1.0) * 1.5708);
|
||||||
tc = vec3(0.62, 0.41, 0.27) * (0.6 + 0.55 * cyl);
|
tc = vec3(0.62, 0.41, 0.27) * (0.6 + 0.55 * cyl);
|
||||||
if (a < wall) tc *= 1.08;
|
if (a < wall) tc *= 1.08;
|
||||||
float ring = 1.0 - smoothstep(0.0, 0.3, min(fract(along * 0.25), 1.0 - fract(along * 0.25)) * 4.0);
|
float ring = 1.0 - smoothstep(0.0, 0.3, min(fract(along * 0.25), 1.0 - fract(along * 0.25)) * 4.0);
|
||||||
@@ -279,7 +297,7 @@ void main() {
|
|||||||
if (nc >= 19) {
|
if (nc >= 19) {
|
||||||
bool full = own.r + own.g + own.b > 0.0;
|
bool full = own.r + own.g + own.b > 0.0;
|
||||||
if (full) C = mix(C, own.rgb, 1.0 - smoothstep(0.36, 0.5, length(fract(w) - 0.5)));
|
if (full) C = mix(C, own.rgb, 1.0 - smoothstep(0.36, 0.5, length(fract(w) - 0.5)));
|
||||||
C = mix(C, tc, 1.0 - smoothstep(2.0 - px, 2.0 + px, abs(a - 2.0)));
|
C = mix(C, tc, 1.0 - smoothstep(2.0 - px, 2.0 + px, tubeSD + 2.0));
|
||||||
} else C = tc;
|
} else C = tc;
|
||||||
}
|
}
|
||||||
else if (nc == 6 || (nc >= 15 && nc <= 18)) {
|
else if (nc == 6 || (nc >= 15 && nc <= 18)) {
|
||||||
|
|||||||
+67
-18
@@ -6,25 +6,66 @@ function fogColor(c, k) { return (c & 0xff000000) | (((c >>> 16 & 255) * k) << 1
|
|||||||
const TUBE_CLASS = new Uint8Array(15); TUBE_CLASS[1] = 9; TUBE_CLASS[4] = 10; TUBE_CLASS[2] = 11; TUBE_CLASS[3] = 12;
|
const TUBE_CLASS = new Uint8Array(15); TUBE_CLASS[1] = 9; TUBE_CLASS[4] = 10; TUBE_CLASS[2] = 11; TUBE_CLASS[3] = 12;
|
||||||
for (let p = P_TUR; p <= P_TUL; p++) TUBE_CLASS[p] = 19 + p - P_TUR;
|
for (let p = P_TUR; p <= P_TUL; p++) TUBE_CLASS[p] = 19 + p - P_TUR;
|
||||||
// Drawing only: the four transport lanes remain in the square block.
|
// Drawing only: the four transport lanes remain in the square block.
|
||||||
|
let diagonalJoins = new Uint8Array(0);
|
||||||
|
function diagonalTubeJoins(bx, by, v, planned = null) {
|
||||||
|
// Low/high three bits select the two edge exits; bits 6/7 mark free-end collars.
|
||||||
|
const dy = v === 4 || v === 6 ? -1 : 1;
|
||||||
|
let joins = 0;
|
||||||
|
for (let end = 0; end < 2; end++) {
|
||||||
|
const side = end ? 1 : -1, nx = bx + side, ny = by + side * dy;
|
||||||
|
const nb = ny * BW + nx, next = planned && planned.get(nb);
|
||||||
|
const nc = next ? next[0] : blockP[nb], nv = next ? next[1] : blockV[nb];
|
||||||
|
if (nx >= 0 && ny >= 0 && nx < BW && ny < BH &&
|
||||||
|
nc === BP_TUBE && nv >= 4 && (nv === 4 || nv === 6) === (dy < 0)) continue;
|
||||||
|
let join = 0;
|
||||||
|
for (const dir of [side > 0 ? 1 : 3, side * dy > 0 ? 2 : 0]) {
|
||||||
|
const x = bx + TUBE_DIRS[dir][0], y = by + TUBE_DIRS[dir][1];
|
||||||
|
if (x < 0 || y < 0 || x >= BW || y >= BH) continue;
|
||||||
|
const n = y * BW + x, next = planned && planned.get(n);
|
||||||
|
const code = next ? next[0] : blockP[n], nv = next ? next[1] : blockV[n];
|
||||||
|
if ((code === BP_TUBE && nv < 4 && (nv & 1) === (dir & 1)) ||
|
||||||
|
((code === BP_BELT || code === BP_FILTER) && (dir & 1))) { join = dir + 1; break; }
|
||||||
|
}
|
||||||
|
joins |= join << (end * 3);
|
||||||
|
if (!join) joins |= 64 << end;
|
||||||
|
}
|
||||||
|
return joins;
|
||||||
|
}
|
||||||
|
// Scratch coordinates shared by the pixel shading; no allocations in the grain loop.
|
||||||
|
let diagonalAcross = 0, diagonalAlong = 0, diagonalCap = 0;
|
||||||
|
function diagonalTubeShape(x, y, b, v, joins) {
|
||||||
|
const lx = x - (b % BW) * BS - 2, ly = y - ((b / BW) | 0) * BS - 2;
|
||||||
|
let best = Infinity;
|
||||||
|
for (let end = 0; end < 2; end++) {
|
||||||
|
const join = (joins >> (end * 3)) & 7, side = end ? 1 : -1;
|
||||||
|
const dx = join ? TUBE_DIRS[join - 1][0] : side * Math.SQRT1_2;
|
||||||
|
const dy = join ? TUBE_DIRS[join - 1][1] : side * (v === 4 || v === 6 ? -1 : 1) * Math.SQRT1_2;
|
||||||
|
const along = lx * dx + ly * dy, across = -lx * dy + ly * dx;
|
||||||
|
const radius = Math.hypot(across, Math.min(0, along)), cap = along - (join ? 2 : 2 * Math.SQRT2);
|
||||||
|
const sd = Math.max(radius - 2, join || (joins & (64 << end)) ? cap : -10);
|
||||||
|
if (sd < best) {
|
||||||
|
best = sd; diagonalAcross = side * across < 0 ? -radius : radius;
|
||||||
|
diagonalAlong = ((b % BW) * BS + 2 + (((b / BW) | 0) * BS + 2) * (v === 4 || v === 6 ? -1 : 1)) * Math.SQRT1_2 + side * along; diagonalCap = joins & (64 << end) ? cap : -10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
function diagonalTubeBlock(x, y, smooth) {
|
function diagonalTubeBlock(x, y, smooth) {
|
||||||
const i = y * W + x, own = (y >> 2) * BW + (x >> 2);
|
const i = y * W + x, own = (y >> 2) * BW + (x >> 2);
|
||||||
if (part[i] >= P_TUR && part[i] <= P_TUL) return own;
|
if (part[i] >= P_TUR && part[i] <= P_TUL) return own;
|
||||||
if (mat[i] !== EMPTY || part[i] || mach[i] || occ[i] || blockP[own]) return -1;
|
if (mat[i] !== EMPTY || part[i] || mach[i] || occ[i] || blockP[own]) return -1;
|
||||||
// Look in the four adjoining blocks for a pipe crossing this empty corner.
|
let best = smooth ? 0.75 : 0, found = -1;
|
||||||
for (let k = 0; k < 4; k++) {
|
for (let k = 0; k < 4; k++) {
|
||||||
const bx = (x >> 2) + (k === 0 ? -1 : k === 1 ? 1 : 0);
|
const bx = (x >> 2) + (k === 0 ? -1 : k === 1 ? 1 : 0);
|
||||||
const by = (y >> 2) + (k === 2 ? -1 : k === 3 ? 1 : 0);
|
const by = (y >> 2) + (k === 2 ? -1 : k === 3 ? 1 : 0);
|
||||||
if (bx < 0 || by < 0 || bx >= BW || by >= BH) continue;
|
if (bx < 0 || by < 0 || bx >= BW || by >= BH) continue;
|
||||||
const b = by * BW + bx, v = blockV[b];
|
const b = by * BW + bx, v = blockV[b];
|
||||||
if (blockP[b] !== BP_TUBE || v < 4) continue;
|
if (blockP[b] !== BP_TUBE || v < 4) continue;
|
||||||
const rising = v === 4 || v === 6, lx = x - bx * BS, ly = rising ? 3 - (y - by * BS) : y - by * BS;
|
const joins = diagonalJoins[b] === undefined ? diagonalTubeJoins(bx, by, v) : diagonalJoins[b];
|
||||||
if (Math.abs(lx - ly) > (smooth ? 3 : 1)) continue;
|
const sd = diagonalTubeShape(x + 0.5, y + 0.5, b, v, joins);
|
||||||
const side = lx + ly < 3 ? -1 : 1, nx = bx + side, ny = by + (rising ? -side : side);
|
if (sd <= best) { best = sd; found = b; }
|
||||||
if (nx < 0 || ny < 0 || nx >= BW || ny >= BH) continue;
|
|
||||||
const nb = ny * BW + nx;
|
|
||||||
if (blockP[nb] === BP_BELT || (blockP[nb] === BP_TUBE && (blockV[nb] < 4 || ((blockV[nb] === 4 || blockV[nb] === 6) === rising)))) return b;
|
|
||||||
}
|
}
|
||||||
return -1;
|
return found;
|
||||||
}
|
}
|
||||||
function drawWorld() {
|
function drawWorld() {
|
||||||
const bw = R.bw, bh = R.bh, px = R.px;
|
const bw = R.bw, bh = R.bh, px = R.px;
|
||||||
@@ -44,6 +85,13 @@ function drawWorld() {
|
|||||||
for (const i of moldCellsAt(mo.kind, mo.x, mo.y).walls) { const sx = (i % W) - vx0, sy = ((i / W) | 0) - vy0; if (sx >= 0 && sy >= 0 && sx < bw && sy < bh) mmask[sy * bw + sx] = 1; }
|
for (const i of moldCellsAt(mo.kind, mo.x, mo.y).walls) { const sx = (i % W) - vx0, sy = ((i / W) | 0) - vy0; if (sx >= 0 && sy >= 0 && sx < bw && sy < bh) mmask[sy * bw + sx] = 1; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (diagonalJoins.length !== NB) diagonalJoins = new Uint8Array(NB);
|
||||||
|
for (let by = Math.max(0, (vy0 >> 2) - 1); by <= Math.min(BH - 1, ((vy0 + bh) >> 2) + 1); by++) {
|
||||||
|
for (let bx = Math.max(0, (vx0 >> 2) - 1); bx <= Math.min(BW - 1, ((vx0 + bw) >> 2) + 1); bx++) {
|
||||||
|
const b = by * BW + bx;
|
||||||
|
if (blockP[b] === BP_TUBE && blockV[b] >= 4) diagonalJoins[b] = diagonalTubeJoins(bx, by, blockV[b]);
|
||||||
|
}
|
||||||
|
}
|
||||||
const useFog = SET.fog;
|
const useFog = SET.fog;
|
||||||
const OUT = sm ? 0x04000000 : 0xff000000; // unexplored and off the map: black
|
const OUT = sm ? 0x04000000 : 0xff000000; // unexplored and off the map: black
|
||||||
const lighting = SET.lighting !== 'off' && !R.lens;
|
const lighting = SET.lighting !== 'off' && !R.lens;
|
||||||
@@ -178,22 +226,23 @@ function drawWorld() {
|
|||||||
// field parts drawn over contents
|
// field parts drawn over contents
|
||||||
let p = part[i];
|
let p = part[i];
|
||||||
const db = diagonalTubeBlock(x, y, sm);
|
const db = diagonalTubeBlock(x, y, sm);
|
||||||
let diagonalAcross = 0;
|
let diagonal = false;
|
||||||
if (db >= 0) {
|
if (db >= 0) {
|
||||||
const v = blockV[db], rising = v === 4 || v === 6;
|
const v = blockV[db], joins = diagonalJoins[db];
|
||||||
diagonalAcross = x - (db % BW) * BS - (rising ? 3 - (y - ((db / BW) | 0) * BS) : y - ((db / BW) | 0) * BS);
|
const sd = diagonalTubeShape(x + 0.5, y + 0.5, db, v, joins);
|
||||||
p = sm || Math.abs(diagonalAcross) <= 1 ? TUBE_PARTS[v] : 0;
|
p = sm || sd <= 0 ? TUBE_PARTS[v] : 0;
|
||||||
if (sm) tpx[row + bx] = 128 + diagonalAcross;
|
diagonal = !!p;
|
||||||
|
if (sm) tpx[row + bx] = (joins << 24) | ((y - ((db / BW) | 0) * BS + 8) << 16) | ((x - (db % BW) * BS + 8) << 8);
|
||||||
}
|
}
|
||||||
if (p && sm && (IS_TUBE[p] || p === P_FILTER)) { if (m === EMPTY || PH[m] === PH_GAS) { r = 0; g = 0; b = 0; } else { r = Math.max(1, r); } }
|
if (p && sm && (IS_TUBE[p] || p === P_FILTER)) { if (m === EMPTY || PH[m] === PH_GAS) { r = 0; g = 0; b = 0; } else { r = Math.max(1, r); } }
|
||||||
else if (p) {
|
else if (p) {
|
||||||
if (IS_TUBE[p]) {
|
if (IS_TUBE[p]) {
|
||||||
const on = netSat(i) > 0;
|
const on = netSat(i) > 0;
|
||||||
const vert = p === P_TU || p === P_TD, diagonal = TDX[p] && TDY[p];
|
const vert = p === P_TU || p === P_TD;
|
||||||
const axis = diagonal ? (x * TDX[p] + y * TDY[p]) >> 1 : vert ? y : x;
|
const axis = diagonal ? Math.floor(diagonalAlong) : vert ? y : x;
|
||||||
const across = diagonal ? diagonalAcross + 1 : vert ? x & 3 : y & 3;
|
const across = diagonal ? diagonalAcross + 2 : vert ? x & 3 : y & 3;
|
||||||
const dir = diagonal ? -1 : (p === P_TU || p === P_TL) ? 1 : -1;
|
const dir = diagonal ? (blockV[db] >= 6 ? 1 : -1) : (p === P_TU || p === P_TL) ? 1 : -1;
|
||||||
if (diagonal ? across !== 1 : across === 0 || across === 3) { // copper casing on the two long sides; its load shows through
|
if (diagonal ? Math.abs(diagonalAcross) > 1.38 || diagonalCap > -0.55 : across === 0 || across === 3) { // copper casing on the two long sides; its load shows through
|
||||||
if (m === EMPTY) { r = 150; g = 96; b = 64; } else { r = r * 0.72 + 44; g = g * 0.72 + 26; b = b * 0.72 + 14; }
|
if (m === EMPTY) { r = 150; g = 96; b = 64; } else { r = r * 0.72 + 44; g = g * 0.72 + 26; b = b * 0.72 + 14; }
|
||||||
if (!diagonal && (axis & 3) === 0) { r -= 24; g -= 18; b -= 12; }
|
if (!diagonal && (axis & 3) === 0) { r -= 24; g -= 18; b -= 12; }
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -72,7 +72,8 @@ function drawPastePreview() {
|
|||||||
const [x, y, w, h] = blockRectPx(ox, oy, bp.bw, bp.bh);
|
const [x, y, w, h] = blockRectPx(ox, oy, bp.bw, bp.bh);
|
||||||
ctx.strokeStyle = 'rgba(92,186,166,0.85)'; ctx.setLineDash([zp, zp]); ctx.lineWidth = Math.max(1, zp * 0.3);
|
ctx.strokeStyle = 'rgba(92,186,166,0.85)'; ctx.setLineDash([zp, zp]); ctx.lineWidth = Math.max(1, zp * 0.3);
|
||||||
ctx.strokeRect(x, y, w, h); ctx.setLineDash([]);
|
ctx.strokeRect(x, y, w, h); ctx.setLineDash([]);
|
||||||
for (const [dx, dy, code, v] of bp.blocks) { const [bx2, by2, bw2, bh2] = blockRectPx(ox + dx, oy + dy, 1, 1); ctx.fillStyle = `rgba(${previewColor(code, v)},0.5)`; previewBlock(code, bx2, by2, bw2, bh2); }
|
const planned = new Map(bp.blocks.map(([dx, dy, code, v]) => [(oy + dy) * BW + ox + dx, [code, v]]));
|
||||||
|
for (const [dx, dy, code, v] of bp.blocks) { const [bx2, by2, bw2, bh2] = blockRectPx(ox + dx, oy + dy, 1, 1); ctx.fillStyle = `rgba(${previewColor(code, v)},0.5)`; previewBlock(code, bx2, by2, bw2, bh2, false, code === BP_TUBE ? v : -1, ox + dx, oy + dy, planned); }
|
||||||
for (const [type, dx, dy] of bp.machines) { const P = PIECE_BY_ID[type]; const [mx, my, mw, mh] = blockRectPx(ox + dx, oy + dy, P.bw, P.bh); ctx.strokeStyle = 'rgba(255,210,122,0.8)'; ctx.strokeRect(mx, my, mw, mh); }
|
for (const [type, dx, dy] of bp.machines) { const P = PIECE_BY_ID[type]; const [mx, my, mw, mh] = blockRectPx(ox + dx, oy + dy, P.bw, P.bh); ctx.strokeStyle = 'rgba(255,210,122,0.8)'; ctx.strokeRect(mx, my, mw, mh); }
|
||||||
for (const [k, dx, dy] of bp.molds) { const md = MOLDS[k]; const [mx, my, mw, mh] = blockRectPx(ox + dx, oy + dy, md.bw, md.bh); ctx.strokeStyle = 'rgba(255,190,90,0.8)'; ctx.strokeRect(mx, my, mw, mh); }
|
for (const [k, dx, dy] of bp.molds) { const md = MOLDS[k]; const [mx, my, mw, mh] = blockRectPx(ox + dx, oy + dy, md.bw, md.bh); ctx.strokeStyle = 'rgba(255,190,90,0.8)'; ctx.strokeRect(mx, my, mw, mh); }
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
|
|||||||
+25
-8
@@ -112,6 +112,7 @@ function drawPreviews() {
|
|||||||
if (dd !== GAME.dragDir) { GAME.dragDir = dd; hudDirty = true; }
|
if (dd !== GAME.dragDir) { GAME.dragDir = dd; hudDirty = true; }
|
||||||
const dIdx = dd >= 0 ? dd : (GAME.pieceDir[P.id] || 0);
|
const dIdx = dd >= 0 ? dd : (GAME.pieceDir[P.id] || 0);
|
||||||
const afford = canAfford(pieceCostNow(P), cells.length);
|
const afford = canAfford(pieceCostNow(P), cells.length);
|
||||||
|
const planned = code === BP_TUBE && dIdx >= 4 ? new Map(cells.map(([x, y]) => [y * BW + x, [BP_TUBE, dIdx]])) : null;
|
||||||
for (const [bx, by] of cells) {
|
for (const [bx, by] of cells) {
|
||||||
const err = canPlaceBlock(bx, by, code), rb = by * BW + bx;
|
const err = canPlaceBlock(bx, by, code), rb = by * BW + bx;
|
||||||
const swap = GAME.replace && !!blockP[rb] && /already built/.test(err) && !blockIs(rb, code, v, pieceExtra(P));
|
const swap = GAME.replace && !!blockP[rb] && /already built/.test(err) && !blockIs(rb, code, v, pieceExtra(P));
|
||||||
@@ -119,10 +120,10 @@ function drawPreviews() {
|
|||||||
const ok = (!err || swap) && !ghost && afford;
|
const ok = (!err || swap) && !ghost && afford;
|
||||||
const [x, y, w, h] = blockRectPx(bx, by, 1, 1);
|
const [x, y, w, h] = blockRectPx(bx, by, 1, 1);
|
||||||
ctx.fillStyle = ghost ? 'rgba(90,190,230,0.22)' : ok ? (swap ? 'rgba(255,190,90,0.5)' : `rgba(${col},0.55)`) : 'rgba(239,106,85,0.35)';
|
ctx.fillStyle = ghost ? 'rgba(90,190,230,0.22)' : ok ? (swap ? 'rgba(255,190,90,0.5)' : `rgba(${col},0.55)`) : 'rgba(239,106,85,0.35)';
|
||||||
previewBlock(code, x, y, w, h, false, P.id === 'tube' ? dIdx : -1);
|
previewBlock(code, x, y, w, h, false, P.id === 'tube' ? dIdx : -1, bx, by, planned);
|
||||||
ctx.strokeStyle = ghost ? 'rgba(90,190,230,0.8)' : ok ? 'rgba(236,226,210,0.8)' : 'rgba(239,106,85,0.9)'; ctx.lineWidth = Math.max(1, zp * 0.25);
|
ctx.strokeStyle = ghost ? 'rgba(90,190,230,0.8)' : ok ? 'rgba(236,226,210,0.8)' : 'rgba(239,106,85,0.9)'; ctx.lineWidth = Math.max(1, zp * 0.25);
|
||||||
if (ghost) ctx.setLineDash([zp, zp * 0.7]);
|
if (ghost) ctx.setLineDash([zp, zp * 0.7]);
|
||||||
previewBlock(code, x, y, w, h, true, P.id === 'tube' ? dIdx : -1); ctx.setLineDash([]);
|
previewBlock(code, x, y, w, h, true, P.id === 'tube' ? dIdx : -1, bx, by, planned); ctx.setLineDash([]);
|
||||||
if (P.dirs && (P.id === 'belt' || P.id === 'tube' || P.filter || P.id === 'pump' || P.id === 'outlet')) {
|
if (P.dirs && (P.id === 'belt' || P.id === 'tube' || P.filter || P.id === 'pump' || P.id === 'outlet')) {
|
||||||
// a chevron on every block, pointing the way it will carry (or pump, or pour)
|
// a chevron on every block, pointing the way it will carry (or pump, or pour)
|
||||||
const [dx, dy] = P.id === 'tube' ? TUBE_DIRS[dIdx] : P.id === 'pump' ? PUMP_V[dIdx] : P.id === 'outlet' ? OUTLET_V[dIdx] : [[-1, 0], [1, 0]][dIdx];
|
const [dx, dy] = P.id === 'tube' ? TUBE_DIRS[dIdx] : P.id === 'pump' ? PUMP_V[dIdx] : P.id === 'outlet' ? OUTLET_V[dIdx] : [[-1, 0], [1, 0]][dIdx];
|
||||||
@@ -179,14 +180,30 @@ function drawPreviews() {
|
|||||||
drawCursorLabel(p.x, p.y, t === 'vacuum' ? GAME.brush[GAME.tool] : t === 'ignite' || t === 'drill' ? 1 : 2);
|
drawCursorLabel(p.x, p.y, t === 'vacuum' ? GAME.brush[GAME.tool] : t === 'ignite' || t === 'drill' ? 1 : 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
function previewBlock(code, x, y, w, h, stroke = false, direction = -1) {
|
function previewBlock(code, x, y, w, h, stroke = false, direction = -1, bx = -1, by = -1, planned = null) {
|
||||||
const ctx = R.ctx;
|
const ctx = R.ctx;
|
||||||
if (code === BP_TUBE && direction >= 4) {
|
if (code === BP_TUBE && direction >= 4) {
|
||||||
const rising = direction === 4 || direction === 6;
|
const joins = bx >= 0 ? diagonalTubeJoins(bx, by, direction, planned) : 0;
|
||||||
ctx.save(); ctx.translate(x + w / 2, y + h / 2); ctx.rotate(rising ? -Math.PI / 4 : Math.PI / 4);
|
const points = [];
|
||||||
const length = w * Math.SQRT2, width = w * 0.75;
|
for (let end = 0; end < 2; end++) {
|
||||||
if (stroke) ctx.strokeRect(-length / 2, -width / 2, length, width);
|
const join = (joins >> (end * 3)) & 7, side = end ? 1 : -1;
|
||||||
else ctx.fillRect(-length / 2, -width / 2, length, width);
|
const d = join ? TUBE_DIRS[join - 1] : [side, side * (direction === 4 || direction === 6 ? -1 : 1)];
|
||||||
|
points.push([d[0] * w / 2, d[1] * h / 2]);
|
||||||
|
}
|
||||||
|
const r = w / 2, a = points[0], b = points[1];
|
||||||
|
const al = Math.hypot(...a), bl = Math.hypot(...b);
|
||||||
|
const n0 = [a[1] / al, -a[0] / al], n1 = [-b[1] / bl, b[0] / bl];
|
||||||
|
const turn = n0[0] * n1[1] - n0[1] * n1[0], dot = n0[0] * n1[0] + n0[1] * n1[1];
|
||||||
|
ctx.save(); ctx.translate(x + w / 2, y + h / 2); ctx.beginPath();
|
||||||
|
ctx.moveTo(a[0] + n0[0] * r, a[1] + n0[1] * r);
|
||||||
|
if (turn >= 0) ctx.lineTo((n0[0] + n1[0]) * r / (1 + dot), (n0[1] + n1[1]) * r / (1 + dot));
|
||||||
|
else ctx.arc(0, 0, r, Math.atan2(n0[1], n0[0]), Math.atan2(n1[1], n1[0]), true);
|
||||||
|
ctx.lineTo(b[0] + n1[0] * r, b[1] + n1[1] * r);
|
||||||
|
ctx.lineTo(b[0] - n1[0] * r, b[1] - n1[1] * r);
|
||||||
|
if (turn <= 0) ctx.lineTo(-(n0[0] + n1[0]) * r / (1 + dot), -(n0[1] + n1[1]) * r / (1 + dot));
|
||||||
|
else ctx.arc(0, 0, r, Math.atan2(-n1[1], -n1[0]), Math.atan2(-n0[1], -n0[0]), true);
|
||||||
|
ctx.lineTo(a[0] - n0[0] * r, a[1] - n0[1] * r); ctx.closePath();
|
||||||
|
if (stroke) ctx.stroke(); else ctx.fill();
|
||||||
ctx.restore(); return;
|
ctx.restore(); return;
|
||||||
}
|
}
|
||||||
if (!isSlope(code)) { if (stroke) ctx.strokeRect(x, y, w, h); else ctx.fillRect(x, y, w, h); return; }
|
if (!isSlope(code)) { if (stroke) ctx.strokeRect(x, y, w, h); else ctx.fillRect(x, y, w, h); return; }
|
||||||
|
|||||||
+14
-2
@@ -168,8 +168,19 @@ for(const [dx,dy,v] of [[5,-4,4],[4,5,5],[-5,4,6],[-4,-5,7]]) {
|
|||||||
check(`a diagonal drag picks direction ${v}`,run(`dragDirFor(PIECE_BY_ID.tube,{bx:20,by:20},{bx:${20+dx},by:${20+dy}})`)===v);
|
check(`a diagonal drag picks direction ${v}`,run(`dragDirFor(PIECE_BY_ID.tube,{bx:20,by:20},{bx:${20+dx},by:${20+dy}})`)===v);
|
||||||
check(`direction ${v} lays a corner-touching diagonal run`,run(`blockLine(20,20,${20+dx},${20+dy},true).every(([x,y],k)=>x===20+k*${Math.sign(dx)} && y===20+k*${Math.sign(dy)})`));
|
check(`direction ${v} lays a corner-touching diagonal run`,run(`blockLine(20,20,${20+dx},${20+dy},true).every(([x,y],k)=>x===20+k*${Math.sign(dx)} && y===20+k*${Math.sign(dy)})`));
|
||||||
}
|
}
|
||||||
// The drawing can bridge a joint only in unoccupied air; transport parts stay square.
|
// End elbows follow the adjoining straight piece's axis, regardless of flow direction.
|
||||||
run(fs.readFileSync('src/render/world.js', 'utf8'));
|
run(fs.readFileSync('src/render/world.js', 'utf8'));
|
||||||
|
for (const v of [4,5,6,7]) for (let dir=0;dir<4;dir++) for (const code of ['BP_TUBE','BP_BELT','BP_FILTER']) {
|
||||||
|
if (code !== 'BP_TUBE' && !(dir & 1)) continue;
|
||||||
|
fresh();
|
||||||
|
const [dx,dy]=run(`TUBE_DIRS[${dir}]`);
|
||||||
|
run(`placeBlock(BP_TUBE,${v},20,20,true);placeBlock(${code},${dir},${20+dx},${20+dy},true);`);
|
||||||
|
const end = dx ? dx>0 : (v===4 || v===6 ? dy<0 : dy>0);
|
||||||
|
check(`diagonal ${v} bends toward ${code} direction ${dir}`,run(`(diagonalTubeJoins(20,20,${v}) >> ${end?3:0} & 7)`)===dir+1);
|
||||||
|
check(`planned ${code} direction ${dir} bends the diagonal ${v} ghost too`,run(`(()=>{removeBlock(${20+dx},${20+dy});const planned=new Map([[${20+dy}*BW+${20+dx},[${code},${dir}]]]);const j=diagonalTubeJoins(20,20,${v},planned);placeBlock(${code},${dir},${20+dx},${20+dy},true);return (j >> ${end?3:0} & 7)===${dir+1};})()`));
|
||||||
|
check(`diagonal ${v} meets ${code} direction ${dir} across its centred edge`,run(`(()=>{const b=20*BW+20,j=diagonalTubeJoins(20,20,${v});return [0.5,1.5,2.5,3.5].every(t=>diagonalTubeShape(${dx ? 82+dx*1.99 : '80+t'},${dy ? 82+dy*1.99 : '80+t'},b,${v},j)<=0);})()`));
|
||||||
|
}
|
||||||
|
// The drawing can bridge a joint only in unoccupied air; transport parts stay square.
|
||||||
for (const v of [4,5,6,7]) {
|
for (const v of [4,5,6,7]) {
|
||||||
flatWorld(run,240,192,176);
|
flatWorld(run,240,192,176);
|
||||||
const rising = v===4 || v===6;
|
const rising = v===4 || v===6;
|
||||||
@@ -179,7 +190,8 @@ for (const v of [4,5,6,7]) {
|
|||||||
for(const obstacle of ['mat[j]=STONE','mat[j]=SAND','part[j]=P_PIPE','mach[j]=1','occ[j]=1','blockP[(j/W>>2)*BW+(j%W>>2)]=BP_WALL']) {
|
for(const obstacle of ['mat[j]=STONE','mat[j]=SAND','part[j]=P_PIPE','mach[j]=1','occ[j]=1','blockP[(j/W>>2)*BW+(j%W>>2)]=BP_WALL']) {
|
||||||
check(`diagonal ${v} leaves ${obstacle} visible`,run(`(()=>{const j=${y}*W+${x};${obstacle};const hidden=diagonalTubeBlock(${x},${y},true);mat[j]=EMPTY;part[j]=0;mach[j]=0;occ[j]=0;blockP[(${y}>>2)*BW+(${x}>>2)]=0;return hidden<0;})()`));
|
check(`diagonal ${v} leaves ${obstacle} visible`,run(`(()=>{const j=${y}*W+${x};${obstacle};const hidden=diagonalTubeBlock(${x},${y},true);mat[j]=EMPTY;part[j]=0;mach[j]=0;occ[j]=0;blockP[(${y}>>2)*BW+(${x}>>2)]=0;return hidden<0;})()`));
|
||||||
}
|
}
|
||||||
|
check(`diagonal ${v} keeps its continuation uncapped`,run(`(diagonalTubeJoins(20,20,${v}) & 128)===0 && (diagonalTubeJoins(21,${rising ? 19 : 21},${v}) & 64)===0`));
|
||||||
run(`removeBlock(21,${rising ? 19 : 21});`);
|
run(`removeBlock(21,${rising ? 19 : 21});`);
|
||||||
check(`diagonal ${v} has no stray corner after its end`,run(`diagonalTubeBlock(${x},${y},false)===-1`));
|
check(`diagonal ${v} has no stray corner after its end`,run(`diagonalTubeBlock(${x+1},${rising ? y-1 : y+1},false)===-1`));
|
||||||
}
|
}
|
||||||
done();
|
done();
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// Run after clear-sky.js. Paused, empty pipes make casing, flights and mouths easy to inspect.
|
||||||
|
const jx = BX + 5, jy = BY - 3;
|
||||||
|
for (let k = 1; k <= 4; k++) placeBlock(BP_TUBE, 0, jx, jy + k, true);
|
||||||
|
for (let k = 0; k < 6; k++) placeBlock(BP_TUBE, 4, jx + k, jy - k, true);
|
||||||
|
for (let k = 1; k <= 4; k++) placeBlock(BP_TUBE, 0, jx + 5, jy - 5 - k, true);
|
||||||
|
const hx = BX + 21, hy = BY - 3;
|
||||||
|
for (let k = 1; k <= 4; k++) placeBlock(BP_BELT, 1, hx - k, hy, true);
|
||||||
|
for (let k = 0; k < 6; k++) placeBlock(BP_TUBE, 4, hx + k, hy - k, true);
|
||||||
|
for (let k = 1; k <= 4; k++) placeBlock(BP_BELT, 1, hx + 5 + k, hy - 5, true);
|
||||||
|
for (let k = 0; k < 4; k++) placeBlock(BP_TUBE, 5, BX + 32 + k, BY - 10 + k, true);
|
||||||
|
window.JOINTS = {
|
||||||
|
verticalIn: [jx * 4 + 2, jy * 4 + 4],
|
||||||
|
verticalOut: [(jx + 5) * 4 + 2, (jy - 5) * 4],
|
||||||
|
beltIn: [hx * 4, hy * 4 + 2],
|
||||||
|
beltOut: [(hx + 6) * 4, (hy - 5) * 4 + 2],
|
||||||
|
free: [(BX + 35) * 4 + 4, (BY - 7) * 4 + 4]
|
||||||
|
};
|
||||||
|
window.CAM = [(BX + 20) * 4, (BY - 5) * 4];
|
||||||
|
|
||||||
|
// Keep the camera-following bot out of the joint close-ups.
|
||||||
|
drawBot = () => {};
|
||||||
Reference in New Issue
Block a user