Matrix-Toolbox — Mat3-/Mat4-Speicherlayout, als Doku gezeichnet
Visuelle Referenz für die eingebaute `Matrix`-Toolbox. Zeichnet das spaltenweise (column-major) Speicherlayout von Mat3 und Mat4 auf eine SVG-Canvas: welcher Array-Index welche logische Zelle hält, wo die Translation sitzt ([6]/[7] bei Mat3, [12]/[13]/[14] bei Mat4), die Transformationsformel und einen Spickzettel für die Punktpaar-Fitter estimateSimilarity / estimateAffine / estimateHomography. Die ganze Grafik erzeugt das Skript selbst — Dokumentation als Code.
Matrix layout reference
JavaScript
// Visual reference for the built-in `Matrix` toolbox — draws the
// column-major storage layout of Mat3 / Mat4 onto an SVG canvas so the
// index ⇄ logical-cell mapping is impossible to misremember.
// demo_matrix_toolbox.js
//!NOIMAGE
//!OUTPUT: OUTPUT
//!PARAM: SCALE:number=2.0,min=1.0,max=3.0,step=0.5
// ── Layout constants ──────────────────────────────────────────────────────
const W = 1180, H = 910;
const COL = ["#e94560", "#4ade80", "#60a5fa", "#fbbf24"]; // per-column hue
const BG = "#12121e", PANEL = "#1c1c2e", INK = "#e8e8f0", DIM = "#8b8ba7";
const MONO = "JetBrains Mono", SANS = "Inter";
const svg = Engine.createSVGCanvas(W, H);
// Background
svg.fill(BG);
svg.drawPath(Engine.createPath().rect(0, 0, W, H));
// ── Tiny drawing helpers ──────────────────────────────────────────────────
function panel(x, y, w, h) {
svg.fill(PANEL);
svg.drawPath(Engine.createPath().rect(x, y, w, h));
}
function cell(x, y, s, colIdx, label, idx, labelSize) {
svg.fill(COL[colIdx]);
svg.drawPath(Engine.createPath().rect(x, y, s - 6, s - 6));
svg.drawText(label, x + (s - 6) / 2, y + (s - 6) / 2 + 7, {
font: MONO, size: labelSize || 24, color: "#12121e", anchor: "middle",
});
svg.drawText("[" + idx + "]", x + s - 12, y + 16, {
font: MONO, size: 11, color: "#12121e88", anchor: "end",
});
}
function text(t, x, y, size, color, anchor, font) {
svg.drawText(t, x, y, {
font: font || SANS, size: size, color: color || INK,
anchor: anchor || "start",
});
}
// Flat storage strip: one square per backing-array slot, coloured by the
// column it belongs to — visually proves "consecutive indices = a column".
function strip(x, y, n, perCol, labels) {
const s = 44;
for (let i = 0; i < n; i++) {
const colIdx = Math.floor(i / perCol);
svg.fill(COL[colIdx]);
svg.drawPath(Engine.createPath().rect(x + i * s, y, s - 4, s - 4));
svg.drawText(labels[i], x + i * s + (s - 4) / 2, y + 21, {
font: MONO, size: 16, color: "#12121e", anchor: "middle",
});
svg.drawText("" + i, x + i * s + (s - 4) / 2, y + 35, {
font: MONO, size: 10, color: "#12121e99", anchor: "middle",
});
}
}
// ── Header ────────────────────────────────────────────────────────────────
text("Matrix toolbox — storage layout", 40, 54, 30, INK, "start", SANS);
text("Mat3 / Mat4 are column-major (gl-matrix). The flat backing array stores COLUMNS, not rows.",
40, 82, 16, DIM);
// ══ Mat3 ══════════════════════════════════════════════════════════════════
panel(40, 104, W - 80, 366);
text("Mat3", 64, 142, 22, COL[2], "start", MONO);
text("3×3 — 2-D transforms (translate / rotate / scale / homography)", 138, 142, 15, DIM);
// Logical grid: row r, col c. Column-major index = c*3 + r.
const g3x = 64, g3y = 158, s3 = 62;
const M3 = [["a", "b", "c"], ["d", "e", "f"], ["g", "h", "i"]];
for (let r = 0; r < 3; r++) {
for (let c = 0; c < 3; c++) {
cell(g3x + c * s3, g3y + r * s3, s3, c, M3[r][c], c * 3 + r);
}
}
// The transform it performs.
const fx = g3x + 3 * s3 + 64, fy = g3y + 6;
text("transforms a point (x, y):", fx, fy, 15, INK);
text("x' = a·x + b·y + c", fx, fy + 38, 22, COL[0], "start", MONO);
text("y' = d·x + e·y + f", fx, fy + 72, 22, COL[1], "start", MONO);
text("w' = g·x + h·y + i", fx, fy + 106, 22, COL[2], "start", MONO);
text("→ translation (tx, ty) = (c, f) = indices [6] and [7]", fx, fy + 148, 15, COL[3]);
text(" not [2] and [5] — that catches everyone.", fx, fy + 170, 14, DIM);
// Flat storage strip.
text("backing array (column-major):", 64, g3y + 3 * s3 + 30, 14, DIM);
strip(64, g3y + 3 * s3 + 42, 9, 3, ["a", "d", "g", "b", "e", "h", "c", "f", "i"]);
text("└ column 0 ┘ └ column 1 ┘ └ column 2 ┘",
66, g3y + 3 * s3 + 104, 13, DIM, "start", MONO);
// ══ Mat4 ══════════════════════════════════════════════════════════════════
panel(40, 486, W - 80, 300);
text("Mat4", 64, 524, 22, COL[3], "start", MONO);
text("4×4 — 3-D transforms. Same rule: index = column·4 + row.", 138, 524, 15, DIM);
const g4x = 64, g4y = 540, s4 = 52;
for (let r = 0; r < 4; r++) {
for (let c = 0; c < 4; c++) {
cell(g4x + c * s4, g4y + r * s4, s4, c, "" + (c * 4 + r), c * 4 + r, 20);
}
}
text("logical grid — number shown is the storage index",
g4x, g4y + 4 * s4 + 18, 13, DIM);
const f4x = g4x + 4 * s4 + 64, f4y = g4y + 6;
text("Translation lives in the LAST column:", f4x, f4y, 15, INK);
text("tx = [12] ty = [13] tz = [14]", f4x, f4y + 36, 20, COL[3], "start", MONO);
text("The 3×3 rotation / scale block is", f4x, f4y + 78, 15, INK);
text("indices [0,1,2] [4,5,6] [8,9,10].", f4x, f4y + 100, 15, INK, "start", MONO);
text("Mat3 and Mat4 share the column-major convention — learn it once.",
f4x, f4y + 140, 14, DIM);
// ══ estimate* cheatsheet ══════════════════════════════════════════════════
panel(40, 802, W - 80, 92);
text("Fitting transforms to point pairs — returns a ready-to-use Mat3 (src→dst forward):",
64, 832, 15, INK);
text("Mat3.estimateSimilarity(src, dst)", 64, 866, 16, COL[0], "start", MONO);
text("scale + rotate + move · ≥ 2 pts", 64, 884, 12, DIM);
text("Mat3.estimateAffine(src, dst)", 440, 866, 16, COL[1], "start", MONO);
text("+ shear · ≥ 3 pts", 440, 884, 12, DIM);
text("Mat3.estimateHomography(src, dst)", 760, 866, 16, COL[2], "start", MONO);
text("perspective · ≥ 4 pts", 760, 884, 12, DIM);
// ── Rasterise for preview ─────────────────────────────────────────────────
const markup = svg.toString();
const img = Engine.loadSVG(markup, SCALE);
Engine.saveImage(img, "OUTPUT");
`Matrix toolbox reference — ${img.width}×${img.height}, ${markup.length} chars SVG`;
// © 2026 Michael Lechner · mlc OpticScript · https://mlcgo.eu · Elastic License 2.0