Let a shader move the corners of the picture it wears (#79)

A shader could only say what colour a pixel has. Where the picture
is drawn was fixed to the four corners of its rectangle, so nothing
could wave, bend or wobble.

- The new node Vertex output takes x and y, in pixels of the
  picture, and moves every corner of the wearer's grid by them. The
  UVs stay with the corners, so the picture bends along.
- Subdivisions on that node says how fine the grid is: cells per
  axis, 1 to 64, 8 by default. 1 is the plain quad. A shader without
  the node keeps the quad every sprite shares.
- A graph holds one Vertex output at most. UV, Time, Property,
  Number and the math nodes may feed it, and one node may feed both
  outputs.
- Texture color, Texture alpha, Sample texture, Texture size and
  Color cannot feed it. The node says "Not available per corner"
  and the shader does not compile.
- Sprites, texts and the preview in the shader tab wear the grid.
  A nine-slice sprite and particles draw as if the node were not
  there.
- Picking, the selection frame, the size and colliders stay with
  the rectangle. A wearer with a Vertex output is never culled, as
  with Padding, and with Padding the grid covers the room around
  the picture too.
- tools/perf-shaders.mjs takes GM_PERF_SUBDIVISIONS to measure a
  grid against the plain quad.

Measured on stein2 (Intel HD 630), frames per second, one run per
row: at 800 sprites 58 with a shader on the quad, 46 with 8 cells,
41 with 32. Up to 400 sprites a grid hardly shows. Every sprite
holds its own grid.

Tests:
- Gate on melektaus-atx: 220 files, 3416 tests, CHECK EXIT 0.
- 15 new unit tests for the compiler and the grid.
- Four new e2e checks in modules 28 and 30: a fixed offset moves
  the picture, 8 cells bend only the lower half where 1 cell shears
  it all, Texture alpha is refused at the node, a text moves.
- e2e 28,30 on melektaus-atx: 110 checks, all pass.
- The e2e total of 1699 is counted (1695 + 4), the full suite did
  not run.
- No counter-probe, no screenshot. Not looked at: the preview, Time
  in the browser, flipped sprites, Padding together with the grid.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-10-03 00:14:00 +02:00
1 parent 963f8a152a
commit 4716726b6b
14 files changed
+586 -75

No files matched your search

+29
View File
@@ -33,6 +33,35 @@ export function paddedQuad(pad: QuadPadding): {
};
}
/**
* The same rectangle cut into `cells` by `cells` (#79), so a shader's Vertex output has corners to
* move inside it. Positions and UVs stay one and the same, as on the quad.
*/
export function gridQuad(
pad: QuadPadding | null,
cells: number,
): { positions: Float32Array; uvs: Float32Array; indices: Uint32Array } {
const n = Math.max(1, Math.round(cells));
const [u, v] = [pad?.u ?? 0, pad?.v ?? 0];
const corners = new Float32Array((n + 1) * (n + 1) * 2);
for (let row = 0; row <= n; row++) {
for (let column = 0; column <= n; column++) {
const at = (row * (n + 1) + column) * 2;
corners[at] = -u + ((1 + 2 * u) * column) / n;
corners[at + 1] = -v + ((1 + 2 * v) * row) / n;
}
}
const indices = new Uint32Array(n * n * 6);
for (let row = 0; row < n; row++) {
for (let column = 0; column < n; column++) {
const corner = row * (n + 1) + column;
const below = corner + n + 1;
indices.set([corner, corner + 1, below + 1, corner, below + 1, below], (row * n + column) * 6);
}
}
return { positions: corners, uvs: new Float32Array(corners), indices };
}
export function paddingKey(pad: QuadPadding | null): string {
return pad ? `${pad.u},${pad.v}` : '';
}