3-D cube — different image per visible face (front, right, top)

A variation of the 3D cube demo that maps three different input images to the visible faces (front, right, and top). Includes Lambertian shading and a vector wireframe.

FRONT
FRONT — 3-D cube — different image per visible face (front, right, top)
RIGHT
RIGHT — 3-D cube — different image per visible face (front, right, top)
TOP
TOP — 3-D cube — different image per visible face (front, right, top)
OUTPUT
OUTPUT — 3-D cube — different image per visible face (front, right, top)
JavaScript
// 3-D perspective cube — different image per visible face
// demo_cube_3faces.js
//!INPUT: FRONT, RIGHT, TOP
//!OUTPUT: OUTPUT

// Variant of demo_cube that uses three independent textures — one
// each for the front, right and top faces. Same projection +
// Lambertian-shading + wireframe-overlay pipeline; only the per-face
// source image differs.

const W = 700, H = 700;

const EYE    = Matrix.Vec3.fromValues(3.2, 2.4, 3.2);
const CENTER = Matrix.Vec3.fromValues(0, 0, 0);
const view   = new Matrix.Mat4().lookAt(EYE, CENTER, Matrix.Vec3.fromValues(0, 1, 0));
const proj   = new Matrix.Mat4().perspectiveNO(Math.PI / 3.6, W / H, 0.1, 100);
const mvp    = Matrix.Mat4.clone(proj).multiply(view);

function project(v) {
    const p = Matrix.Vec3.fromValues(...v).transformMat4(mvp);
    return [(p[0] + 1) / 2 * W, (1 - (p[1] + 1) / 2) * H];
}

const V = [[-1,-1,-1],[1,-1,-1],[1,1,-1],[-1,1,-1],
           [-1,-1,1], [1,-1,1], [1,1,1], [-1,1,1]];

// Each face binds to a different source image.
const front = Engine.loadImage(FRONT);
const right = Engine.loadImage(RIGHT);
const top   = Engine.loadImage(TOP);

const FACES = [
    { vi: [7, 6, 5, 4], n: [0, 0, 1], img: front },
    { vi: [2, 6, 5, 1], n: [1, 0, 0], img: right },
    { vi: [3, 2, 6, 7], n: [0, 1, 0], img: top   },
];

const sun = Matrix.Vec3.fromValues(0.6, 0.8, 0.4).normalize();
const out = Engine.createImage(W, H).tint(new Pixel(0.05, 0.05, 0.08, 1), 1);

for (const face of FACES) {
    const pts   = face.vi.map(i => project(V[i]));
    const shade = Math.max(0.55, 0.55 + 0.45 *
        Matrix.Vec3.fromValues(...face.n).dot(sun));
    const tile  = face.img.clone().resize(300, 300).brightness(shade);
    out.stampAt(tile, pts.map(([x, y]) => px(x, y)));
    tile.free();
}

// Wireframe overlay
const canvas = Engine.createCanvas(W, H);
const edges  = [[0,1],[1,2],[2,3],[3,0],[4,5],[5,6],[6,7],[7,4],[0,4],[1,5],[2,6],[3,7]];
canvas.pen(new Pixel(0.7, 0.85, 1.0, 0.6), 1.2);
for (const [a, b] of edges) {
    const [ax, ay] = project(V[a]);
    const [bx, by] = project(V[b]);
    canvas.drawPath(Engine.createPath().moveTo(ax, ay).lineTo(bx, by), true);
}
out.blendAt(canvas.toImage(), px(0, 0), 1.0, Blend.Over).save(OUTPUT);

front.free(); right.free(); top.free();
out.free();

// © 2026 Michael Lechner · mlc OpticScript · https://mlcgo.eu · Elastic License 2.0