# Masks

> useMask keeps where something is drawn to a rectangle or to the shape of another object, hard, inverted or soft.

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

A mask keeps the objects it is added to, and everything under them, to a rectangle or to the shape of another object. `useMask` makes it and it masks nothing until you `add` to it, so one mask can keep a map and its enemies and leave the HUD alone.

Top left, a list scrolling inside a window: a rectangle square to the screen costs nothing. Top right, a round torch over a wall of tiles: only what it lights is drawn, and the pointer only touches what is drawn. Move the mouse over the tiles. Bottom left, `invert: true` cuts a star out of a wall, and bottom right, `soft: true` with `channel: 'luminance'` lets the stripes through as much as a black and white spot painted in code is white. Inside the spot, a bar with a turned mask of its own keeps to both.

A mask's shape is an object like any other: it moves, turns and is animated like the rest, and it is not drawn unless the mask says `showShape: true`.

## Code

`index.ts`:

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

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

const game = createGame('#app', {
    width: Settings.WIDTH,
    height: Settings.HEIGHT,
    background: '#14141f',
    scaling: 'contain'
});

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

`components/list.ts`:

```ts
import { createSprite, createText, useMask, useSelf, useSpawn, useTransform, useUpdate } from 'nacatamalon';

import * as Settings from '../settings';

export const List = () => {
    useTransform({
        x: 20,
        y: 36
    });
    useMask({
        rect: {
            x: 0,
            y: 0,
            width: 200,
            height: 92
        }
    }).add(useSelf());

    const rows = useSpawn(() => {
        useTransform({
            x: 0,
            y: 0
        });

        for (let i = 0;i < 20;i++) {
            const y = i * 18;

            createSprite({
                width: 200,
                height: 15,
                anchor: {
                    x: 0,
                    y: 0
                },
                tint: Settings.COLORS[i % 8],
                transform: {
                    x: 0,
                    y
                }
            });
            createText({
                text: 'ITEM ' + String((i % 10) + 1).padStart(2, '0'),
                transform: {
                    x: 6,
                    y: y + 4
                },
                tint: '#10101a'
            });
        }
    })();

    let time = 0;

    useUpdate((delta) => {
        time += delta;
        // Ten rows down and round again: the second ten are the first ten once more.
        rows.transform!.y = -Math.floor((time * 24) % 180);
    });
};
```

`components/spot.ts`:

```ts
import { createSprite, useTransform, useUpdate } from 'nacatamalon';
import { createPixelTexture, createPixels, fillCircle } from 'nacatamalon/pixels';

export const Spot = () => {
    const place = useTransform({
        x: 360,
        y: 205
    });

    const spot = createPixels(96, 96, '#000000');

    for (let r = 46;r > 0;r--) {
        const light = Math.min(1, (1 - r / 46) * 1.6);

        fillCircle(spot, 48, 48, r, {
            r: light,
            g: light,
            b: light,
            a: 1
        });
    }

    createSprite({
        texture: createPixelTexture(spot)
    });

    let time = 0;

    useUpdate((delta) => {
        time += delta;
        place.x = 360 + Math.sin(time * 0.7) * 50;
    });
};
```

`components/star.ts`:

```ts
import { createPolygon, useTransform, useUpdate } from 'nacatamalon';

export const Star = () => {
    const place = useTransform({
        x: 120,
        y: 205,
        rotation: 0
    });

    const star = Array.from({
        length: 10
    }, (_, i) => {
        const r = i % 2 === 0 ? 40 : 17;
        const a = i / 10 * Math.PI * 2 - Math.PI / 2;

        return {
            x: Math.cos(a) * r,
            y: Math.sin(a) * r
        };
    });

    createPolygon({
        points: star,
        tint: '#ffffff'
    });
    useUpdate((delta) => {
        place.rotation += delta * 0.8;
    });
};
```

`components/stripes.ts`:

```ts
import { createSprite, useMask, useSpawn, useTransform, useUpdate } from 'nacatamalon';

import * as Settings from '../settings';

export const Stripes = () => {
    const stripes = useSpawn(() => {
        useTransform({
            x: 0,
            y: 0
        });

        for (let i = 0;i < 30;i++) {
            createSprite({
                width: 8,
                height: 100,
                tint: Settings.COLORS[i % 8],
                transform: {
                    x: 250 + i * 8,
                    y: 205
                }
            });
        }
    })();

    const turning = useSpawn(() => {
        useTransform({
            x: 360,
            y: 205,
            rotation: 0
        });
        createSprite({
            width: 60,
            height: 6,
            tint: '#ffffff'
        });
    })();

    useMask({
        rect: {
            x: -20,
            y: -20,
            width: 40,
            height: 40
        }
    }).add(turning);

    let time = 0;

    useUpdate((delta) => {
        time += delta;
        stripes.transform!.x = -((time * 16) % 64);
        turning.transform!.rotation = time;
    });
};
```

`components/tiles.ts`:

```ts
import type { TSprite } from 'nacatamalon';
import { createSprite, getColor, usePointer, useUpdate } from 'nacatamalon';

import * as Settings from '../settings';

export const Tiles = () => {
    const tiles: { tint: string; sprite: TSprite }[] = [];

    for (let row = 0;row < 8;row++) {
        for (let column = 0;column < 18;column++) {
            const tint = Settings.COLORS[(row * 3 + column) % 8];

            tiles.push({
                tint,
                sprite: createSprite({
                    width: 11,
                    height: 11,
                    tint,
                    transform: {
                        x: 256 + column * 12,
                        y: 40 + row * 12
                    }
                })
            });
        }
    }

    const pointer = usePointer();

    useUpdate(() => {
        const at = pointer.position();
        const under = at === null ? [] : pointer.pick(at.screenX, at.screenY);

        for (const tile of tiles) {
            tile.sprite.tint = getColor(under.includes(tile.sprite) ? '#ffffff' : tile.tint);
        }
    });
};
```

`components/torch.ts`:

```ts
import { createPolygon, useTransform, useUpdate } from 'nacatamalon';

export const Torch = () => {
    const place = useTransform({
        x: 360,
        y: 80
    });

    const circle = Array.from({
        length: 24
    }, (_, i) => ({
        x: Math.cos(i / 24 * Math.PI * 2) * 30,
        y: Math.sin(i / 24 * Math.PI * 2) * 30
    }));

    createPolygon({
        points: circle,
        tint: '#ffffff'
    });

    let time = 0;

    useUpdate((delta) => {
        time += delta;
        place.x = 360 + Math.cos(time * 0.9) * 70;
        place.y = 82 + Math.sin(time * 1.7) * 30;
    });
};
```

`components/wall.ts`:

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

export const Wall = () => {
    for (let row = 0;row < 7;row++) {
        for (let column = 0;column < 9;column++) {
            const x = 28 + column * 24 + (row % 2) * 12;

            createSprite({
                width: 22,
                height: 10,
                tint: row % 2 === 0 ? '#b5523b' : '#9c4330',
                transform: {
                    x,
                    y: 160 + row * 12
                }
            });
        }
    }
};
```

`helpers/label.ts`:

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

export const label = (text: string, x: number, y: number) => createText({
    text,
    font: 'nacatamalon:thin',
    transform: {
        x,
        y
    }
});
```

`scenes/demo.ts`:

```ts
import { createScene, useMask, useSpawn } from 'nacatamalon';

import { label } from '../helpers/label';
import { List } from '../components/list';
import { Torch } from '../components/torch';
import { Tiles } from '../components/tiles';
import { Star } from '../components/star';
import { Wall } from '../components/wall';
import { Spot } from '../components/spot';
import { Stripes } from '../components/stripes';

export const Masks = () => {
    label('RECT: A SCISSOR', 20, 22);
    label('BY A SHAPE: POINT AT THE TILES', 256, 22);
    label('INVERTED', 20, 146);
    label('SOFT, BY LUMINANCE', 256, 146);

    useSpawn(List)();

    const torch = useSpawn(Torch)();

    useMask({
        by: torch
    }).add(useSpawn(Tiles)());

    const star = useSpawn(Star)();

    useMask({
        by: star,
        invert: true
    }).add(useSpawn(Wall)());

    const spot = useSpawn(Spot)();

    const soft = useMask({
        by: spot,
        soft: true,
        channel: 'luminance'
    });

    soft.add(useSpawn(Stripes)());

    return createScene();
};
```

`settings.ts`:

```ts
export const WIDTH = 480;
export const HEIGHT = 270;
export const COLORS = ['#ff6b1a', '#ffd23f', '#3bceac', '#0ead69', '#4d9de0', '#7768ae', '#e15554', '#f4a7bb'];
```
