# Cut-outs

> alphaCutoff cuts a picture out of its card: a fence, trees of crossed cards, grass, a paper figure and a torn banner, solid, unsorted and casting the shadow of their shapes.

Example `cut-outs`, running at https://nacatamalon.com/en/examples/cut-outs. The whole catalogue: https://nacatamalon.com/llms/examples.md.

Flat cards whose pictures are cut out of them with `alphaCutoff`, the way the consoles of the time drew fences, trees and grass: every pixel less than half solid is thrown away and the rest is drawn solid. So a card hides what is behind it, needs no sorting, and casts the shadow of its shape rather than of its square. `cull: 'none'` shows both sides. A torn banner waved by its corner hook is cut too, and so is its shadow, holes and all.

Press space to draw them without cutting: the empty parts of each card are unseen but still there, hiding the fence drawn after them and casting square shadows. Press S to read the pictures smooth, with the rounded edges of the N64, or in pixels, with the steps of the PlayStation.

## Code

`index.ts`:

```ts
import { createGame } from 'nacatamalon';

import * as Settings from './settings';
import { Scene } from './scenes/demo';

const game = createGame('#app', {
    width: Settings.WIDTH,
    height: Settings.HEIGHT,
    background: '#9cc8e8',
});

// Declare Scenes
game({
    Scene
});
```

`art/pictures.ts`:

```ts
import { getColor } from 'nacatamalon';
import { createPixels, fillCircle, fillEllipse, fillNoise, fillPolygon, fillRect, mapPixels } from 'nacatamalon/pixels';
import type { TPixels } from 'nacatamalon/pixels';

// The pictures, painted here. What is left unpainted has no alpha: that is what is cut away.

const c = (hex: string) => getColor(hex);

// Pickets with pointed tops on two rails.
export const paintFence = (): TPixels => {
    const fence = createPixels(48, 20);

    fillRect(fence, 0, 6, 48, 2, c('#d9c9a3'));
    fillRect(fence, 0, 14, 48, 2, c('#d9c9a3'));

    for (let x = 1; x < 48; x += 6) {
        fillRect(fence, x, 3, 3, 17, c('#efe3c4'));
        fillPolygon(fence, [
            {
                x,
                y: 3
            },
            {
                x: x + 1.5,
                y: 0
            },
            {
                x: x + 3,
                y: 3
            }
        ], c('#efe3c4'));
        fillRect(fence, x + 2, 3, 1, 17, c('#bba77e'));
    }

    return fence;
};

// A crown of leaves: round clumps, holed where the light comes through.
export const paintLeaves = (): TPixels => {
    const crown = createPixels(32, 32);

    for (const [x, y, r] of [[16, 12, 9], [9, 18, 7], [23, 18, 7], [16, 22, 7], [11, 9, 5], [21, 9, 5]]) {
        fillCircle(crown, x, y, r, c('#3f8f3a'));
    }

    const shade = fillNoise(createPixels(32, 32), {
        seed: 6,
        scale: 3,
        octaves: 2
    });

    return mapPixels(crown, (color, x, y) => {
        if (color.a === 0) {
            return color;
        }

        const n = shade.data[(y * 32 + x) * 4] / 255;

        if (n < 0.28) {
            return {
                r: 0,
                g: 0,
                b: 0,
                a: 0
            };
        }

        return n > 0.6 ? c('#69b84d') : n < 0.4 ? c('#2c6a2c') : color;
    });
};

// Blades of grass, from the ground up.
export const paintGrass = (): TPixels => {
    const tuft = createPixels(16, 10);

    for (let x = 0; x < 16; x += 2) {
        const tall = 4 + ((x * 7) % 6);

        fillRect(tuft, x, 10 - tall, 1, tall, x % 4 === 0 ? c('#7fc35a') : c('#5a9e3e'));
    }

    return tuft;
};

// A banner worn by the weather: a diamond on purple, torn at the far edge and holed.
export const paintFlag = (): TPixels => {
    const flag = createPixels(28, 16, c('#6a3fb0'));

    fillPolygon(flag, [
        {
            x: 11,
            y: 2
        },
        {
            x: 17,
            y: 8
        },
        {
            x: 11,
            y: 14
        },
        {
            x: 5,
            y: 8
        }
    ], c('#ffd23f'));

    const hole = {
        r: 0,
        g: 0,
        b: 0,
        a: 0
    };

    for (let y = 0; y < 16; y++) {
        const torn = 22 + ((y * 5) % 7);

        fillRect(flag, torn, y, 28 - torn, 1, hole);
    }

    fillCircle(flag, 15, 5, 1, hole);
    fillCircle(flag, 10, 11, 1, hole);

    return flag;
};

// A paper figure: a round head, a body, arms and legs, with a dark edge round the paper.
export const paintFigure = (): TPixels => {
    const figure = createPixels(20, 36);

    const paper = (fill: string) => {
        fillCircle(figure, 10, 6, 5, c(fill));
        fillRect(figure, 5, 11, 10, 12, c(fill));
        fillRect(figure, 1, 12, 4, 3, c(fill));
        fillRect(figure, 15, 12, 4, 3, c(fill));
        fillRect(figure, 6, 23, 3, 13, c(fill));
        fillRect(figure, 11, 23, 3, 13, c(fill));
    };

    paper('#1c1c28');
    // The same shapes a pixel in, painted: the dark left round them is the paper's edge.
    fillCircle(figure, 10, 6, 4, c('#ffd8a8'));
    fillRect(figure, 6, 12, 8, 10, c('#e84a4a'));
    fillRect(figure, 2, 13, 3, 1, c('#ffd8a8'));
    fillRect(figure, 15, 13, 3, 1, c('#ffd8a8'));
    fillRect(figure, 7, 22, 1, 13, c('#3d7bff'));
    fillRect(figure, 12, 22, 1, 13, c('#3d7bff'));
    fillEllipse(figure, 8, 5, 0.5, 1, c('#1c1c28'));
    fillEllipse(figure, 12, 5, 0.5, 1, c('#1c1c28'));
    fillRect(figure, 8, 8, 4, 1, c('#1c1c28'));

    return figure;
};
```

`components/garden.ts`:

```ts
import { createMaterial, createMesh, createText, useCylinderGeometry, useKeyboard, usePlaneGeometry, useUpdate } from 'nacatamalon';
import { createPixelTexture } from 'nacatamalon/pixels';

import { card, strip } from '../geometry/card';
import { paintFence, paintFigure, paintFlag, paintGrass, paintLeaves } from '../art/pictures';

export const Garden = () => {
    // One material per picture, shared by every card wearing it, so a key changes them all.
    const cutOut = (picture: ReturnType<typeof paintFence>) =>
        createMaterial({
            shader: 'mesh3d',
            texture: createPixelTexture(picture),
            alphaCutoff: 0.5,
            cull: 'none'
        });

    const fence = cutOut(paintFence());
    const leaves = cutOut(paintLeaves());
    const grass = cutOut(paintGrass());
    const figure = cutOut(paintFigure());

    // The flag: its picture cut out, and moved by a corner hook that waves it more the further it is
    // from the pole. Its shadow is cut and waved the same way.
    const flag = createMaterial({
        shader: 'mesh3d',
        texture: createPixelTexture(paintFlag()),
        alphaCutoff: 0.5,
        cull: 'none',
        vertex: `fn vertex(pos: vec3<f32>, ctx: VertContext) -> vec3<f32> {
            return pos + vec3<f32>(0.0, 0.0, sin(pos.x * 3.0 - mu.time * 4.0) * 0.18 * pos.x);
        }`,
        vertexGlsl: `vec3 vertex(vec3 pos, VertContext ctx) {
            return pos + vec3(0.0, 0.0, sin(pos.x * 3.0 - mu.time * 4.0) * 0.18 * pos.x);
        }`,
    });

    const materials = [fence, leaves, grass, figure, flag];

    createMesh({
        geometry: usePlaneGeometry({
            width: 18,
            depth: 10
        }),
        tint: '#4f7a3a',
        castShadow: false
    });

    // Two trees: a trunk and a crown of two cards crossed, the cheapest tree there is.
    for (const x of [-3.6, 3.4]) {
        createMesh({
            geometry: useCylinderGeometry({
                radius: 0.12,
                height: 1.4
            }),
            tint: '#6b4a2b',
            transform: {
                x,
                y: 0.7,
                z: -0.4
            }
        });
        createMesh({
            geometry: card(2.4, 2.4),
            material: leaves,
            transform: {
                x,
                y: 1.1,
                z: -0.4
            }
        });
        createMesh({
            geometry: card(2.4, 2.4),
            material: leaves,
            transform: {
                x,
                y: 1.1,
                z: -0.4,
                rotationY: Math.PI / 2
            }
        });
    }

    // Tufts of grass, each two small cards crossed.
    const tufts = [[-1.8, 1.6], [-0.6, 2.4], [0.9, 1.9], [2.2, 2.6], [-2.8, 2.8], [1.6, 0.6]];

    for (const [x, z] of tufts) {
        createMesh({
            geometry: card(0.7, 0.45),
            material: grass,
            transform: {
                x,
                z,
                rotationY: 0.4
            }
        });
        createMesh({
            geometry: card(0.7, 0.45),
            material: grass,
            transform: {
                x,
                z,
                rotationY: 0.4 + Math.PI / 2
            }
        });
    }

    // A flag on a pole, beside the figure.
    createMesh({
        geometry: useCylinderGeometry({
            radius: 0.04,
            height: 2.6
        }),
        tint: '#8a8a96',
        transform: {
            x: 1.6,
            y: 1.3,
            z: 0.2
        }
    });
    createMesh({
        geometry: strip(1.4, 0.8, 16),
        material: flag,
        transform: {
            x: 1.6,
            y: 1.7,
            z: 0.2
        }
    });

    // A paper figure, turning: flat as a sheet, cut to its outline.
    const paper = createMesh({
        geometry: card(1, 1.8),
        material: figure,
        transform: {
            x: 0.2,
            z: 0.6
        }
    });

    // A fence along the back: three cards end to end, made last, so it is drawn after what stands in
    // front of it. Not cut, the empty parts of the cards in front are still there, unseen, and hide
    // the fence wherever they cover it.
    for (let i = 0; i < 3; i++) {
        createMesh({
            geometry: card(3, 1.2),
            material: fence,
            transform: {
                x: -4.5 + i * 3,
                z: -1.5
            }
        });
    }

    const label = createText({
        text: '',
        transform: {
            x: 8,
            y: 8
        }
    });

    createText({
        text: 'SPACE: CUT OR NOT   S: SMOOTH OR PIXELS',
        tint: '#c8d8b0',
        transform: {
            x: 8,
            y: 340
        }
    });

    const keys = useKeyboard();
    let cut = true;
    let smooth = false;

    useUpdate((delta) => {
        if (keys.justPressed(' ')) {
            cut = !cut;
        }

        if (keys.justPressed('s')) {
            smooth = !smooth;
        }

        for (const material of materials) {
            material.alphaCutoff = cut ? 0.5 : 0;
            material.smooth = smooth;
        }

        paper.transform.rotationY += delta * 0.8;
        label.text = `${cut ? 'CUT OUT (alphaCutoff 0.5)' : 'NOT CUT: HOLES HIDE, SHADOWS SQUARE'} · ${smooth ? 'SMOOTH, N64' : 'PIXELS, PS1'}`;
    });
};
```

`geometry/card.ts`:

```ts
import { createGeometry } from 'nacatamalon';

// An upright card standing on its bottom edge, the whole picture on it the right way up: four
// corners, two triangles counter-clockwise from the front.
export const card = (width: number, height: number) => createGeometry({
    positions: [-width / 2, 0, 0, width / 2, 0, 0, width / 2, height, 0, -width / 2, height, 0],
    indices: [0, 1, 2, 0, 2, 3],
    uvs: [0, 1, 1, 1, 1, 0, 0, 0],
});

// The same, cut into columns from its left edge, for a corner hook to bend: a flag on its pole.
export const strip = (width: number, height: number, columns: number) => {
    const positions: number[] = [];
    const uvs: number[] = [];
    const indices: number[] = [];

    for (let i = 0; i <= columns; i++) {
        const u = i / columns;

        positions.push(u * width, 0, 0, u * width, height, 0);
        uvs.push(u, 1, u, 0);

        if (i < columns) {
            indices.push(i * 2, i * 2 + 2, i * 2 + 3, i * 2, i * 2 + 3, i * 2 + 1);
        }
    }

    return createGeometry({
        positions,
        indices,
        uvs
    });
};
```

`scenes/demo.ts`:

```ts
import { cameraLookAt, createScene, useCamera3d, useDirectionalLight, useSpawn } from 'nacatamalon';

import { Garden } from '../components/garden';

export const Scene = () => {
    const camera = useCamera3d({
        projection: 'perspective',
        fov: 50,
        transform: {
            y: 2.6,
            z: 8
        }
    });

    cameraLookAt(camera, {
        x: 0,
        y: 1,
        z: 0
    });
    useDirectionalLight({
        color: '#fff3d6',
        intensity: 1,
        ambient: 0.3,
        castShadow: true,
        shadowArea: 16,
        transform: {
            rotationX: -0.75,
            rotationY: 0.7
        }
    });

    useSpawn(Garden)();

    return createScene();
};
```

`settings.ts`:

```ts
export const WIDTH = 640;
export const HEIGHT = 360;
```
