# Shapes

> createShapes draws a list of shapes each frame: a HUD with a rounded bar, a cooldown pie and a ring, joins and caps, a star, and a ship in lines with a glow.

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

Shapes drawn by the graphics card as they are each frame: `createShapes` takes a list of rectangles, rounded or not, circles, ellipses and slices of them, outlines of any number of corners and lines as wide as asked for, each filled, outlined or both.

They are plain data: the bar's `width`, the pie's `end`, the trail's `points` are changed every frame and drawn as they are. A line turns as its `join` says and ends as its `cap` says; `align` puts an outline inside or outside what it goes round. All of one object are drawn in one go, and a filter takes them as it takes a sprite.

## Code

`index.ts`:

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

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

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

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

`components/hollow.ts`:

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

import type { TShapeEllipse } from 'nacatamalon';

export const Hollow = () => {
    useTransform({
        x: 24,
        y: 196
    });

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

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

    const c = [
        {
            x: 64,
            y: 2
        },
        {
            x: 104,
            y: 2
        },
        {
            x: 104,
            y: 14
        },
        {
            x: 78,
            y: 14
        },
        {
            x: 78,
            y: 34
        },
        {
            x: 104,
            y: 34
        },
        {
            x: 104,
            y: 46
        },
        {
            x: 64,
            y: 46
        },
    ];

    const egg: TShapeEllipse = {
        type: 'ellipse',
        x: 150,
        y: 24,
        radiusX: 22,
        radiusY: 10,
        rotation: 0,
        fill: '#7768ae',
        stroke: {
            width: 2,
            color: '#ffffff'
        }
    };

    createShapes({
        shapes: [
            {
                type: 'polygon',
                points: star,
                fill: '#ffd23f',
                stroke: {
                    width: 2,
                    color: '#ff6b1a',
                    join: 'round'
                }
            },
            {
                type: 'polygon',
                points: c,
                fill: '#0ead69',
                stroke: {
                    width: 2,
                    color: '#ffffff',
                    join: 'round',
                    align: 'outside'
                }
            },
            egg,
        ],
    });
    useUpdate((_delta, time) => {
        egg.rotation = time;
    });
};
```

`components/hud.ts`:

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

import type { TShapeCircle, TShapeRect } from 'nacatamalon';

export const Hud = () => {
    useTransform({
        x: 16,
        y: 40
    });

    const life: TShapeRect = {
        type: 'rect',
        x: 1,
        y: 1,
        width: 98,
        height: 10,
        radius: 3,
        fill: '#e15554'
    };

    const pie: TShapeCircle = {
        type: 'circle',
        x: 128,
        y: 6,
        radius: 9,
        start: -Math.PI / 2,
        end: -Math.PI / 2,
        fill: '#ffd23f'
    };

    const ring: TShapeCircle = {
        type: 'circle',
        x: 160,
        y: 6,
        radius: 8,
        start: -Math.PI / 2,
        end: -Math.PI / 2,
        stroke: {
            width: 3,
            color: '#3bceac',
            cap: 'round'
        }
    };

    createShapes({
        shapes: [
            {
                type: 'rect',
                x: 0,
                y: 0,
                width: 100,
                height: 12,
                radius: 4,
                fill: '#2a2a3a'
            },
            life,
            {
                type: 'rect',
                x: 0,
                y: 0,
                width: 100,
                height: 12,
                radius: 4,
                stroke: {
                    width: 1,
                    color: '#ffffff',
                    align: 'inside'
                }
            },
            {
                type: 'circle',
                x: 128,
                y: 6,
                radius: 9,
                fill: '#2a2a3a'
            },
            pie,
            {
                type: 'circle',
                x: 160,
                y: 6,
                radius: 8,
                stroke: {
                    width: 3,
                    color: '#2a2a3a'
                }
            },
            ring,
        ],
    });
    useUpdate((_delta, time) => {
        life.width = 98 * (0.55 + Math.sin(time * 1.5) * 0.4);
        pie.end = -Math.PI / 2 + ((time / 2) % 1) * Math.PI * 2;
        ring.end = -Math.PI / 2 + ((time / 3) % 1) * Math.PI * 2;
    });
};
```

`components/lines.ts`:

```ts
import { createShapes, useTransform } from 'nacatamalon';

import type { TShape } from 'nacatamalon';

export const Lines = () => {
    useTransform({
        x: 16,
        y: 96
    });

    const zigzag = (x: number) => [
        {
            x,
            y: 30
        },
        {
            x: x + 14,
            y: 0
        },
        {
            x: x + 28,
            y: 30
        },
        {
            x: x + 42,
            y: 4
        }
    ];

    createShapes({
        shapes: [
            {
                type: 'line',
                points: zigzag(0),
                stroke: {
                    width: 6,
                    color: '#4d9de0',
                    join: 'miter'
                }
            },
            {
                type: 'line',
                points: zigzag(56),
                stroke: {
                    width: 6,
                    color: '#4d9de0',
                    join: 'round'
                }
            },
            {
                type: 'line',
                points: zigzag(112),
                stroke: {
                    width: 6,
                    color: '#4d9de0',
                    join: 'bevel'
                }
            },
            // The thin white line is where each one really ends.
            ...(['butt', 'round', 'square'] as const).flatMap((cap, i): TShape[] => [
                {
                    type: 'line',
                    points: [
                        {
                            x: 10 + i * 56,
                            y: 52
                        },
                        {
                            x: 34 + i * 56,
                            y: 52
                        }
                    ],
                    stroke: {
                        width: 8,
                        color: '#f4a7bb',
                        cap
                    }
                },
                {
                    type: 'line',
                    points: [
                        {
                            x: 10 + i * 56,
                            y: 52
                        },
                        {
                            x: 34 + i * 56,
                            y: 52
                        }
                    ],
                    stroke: {
                        width: 1,
                        color: '#ffffff'
                    }
                },
            ]),
        ],
    });
};
```

`components/ship.ts`:

```ts
import { createShapes, useSelf, useTransform, useUpdate } from 'nacatamalon';
import { glow, useFilter } from 'nacatamalon/effects';
import type { TShapePolygon } from 'nacatamalon';
import * as Settings from '../settings';

export const Ship = () => {
    const place = useTransform({
        x: 360,
        y: 150,
        scaleX: 1.5,
        scaleY: 1.5
    });

    const flame: TShapePolygon = {
        type: 'polygon',
        points: [
            {
                x: -5,
                y: -3
            },
            {
                x: -12,
                y: 0
            },
            {
                x: -5,
                y: 3
            }
        ],
        fill: '#ff6b1a'
    };

    createShapes({
        shapes: [
            flame,
            {
                type: 'line',
                points: Settings.SHIP,
                closed: true,
                stroke: {
                    width: 2,
                    color: '#9be7ff',
                    join: 'miter'
                }
            },
        ],
    });
    useFilter({
        effects: [
            glow({
                color: '#4d9de0',
                radius: 6
            })
        ]
    }).add(useSelf());
    useUpdate((_delta, time) => {
        place.x = 360 + Math.cos(time * 0.8) * 80;
        place.y = 150 + Math.sin(time * 1.6) * 50;
        // Facing where it goes: the way the path turns at this moment.
        place.rotation = Math.atan2(Math.cos(time * 1.6) * 80, -Math.sin(time * 0.8) * 80);
        flame.points[1].x = -12 - Math.abs(Math.sin(time * 20)) * 4;
    });

    return place;
};
```

`components/trail.ts`:

```ts
import { createShapes, useUpdate } from 'nacatamalon';

import type { TShapeLine } from 'nacatamalon';

export const Trail = (ship: { x: number; y: number; }) => {
    const dots: Array<{ x: number; y: number; }> = [];

    const trail: TShapeLine = {
        type: 'line',
        points: [],
        stroke: {
            width: 2,
            color: '#4d9de080',
            join: 'round',
            cap: 'round'
        }
    };

    createShapes({
        shapes: [trail]
    });
    useUpdate(() => {
        dots.push({
            x: ship.x,
            y: ship.y
        });

        if (dots.length > 40) {
            dots.shift();
        }

        trail.points = dots.filter((_, i) => i % 3 === 0 || i === dots.length - 1);
    });
};
```

`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, useSpawn } from 'nacatamalon';

import { label } from '../helpers/label';
import { Hud } from '../components/hud';
import { Lines } from '../components/lines';
import { Hollow } from '../components/hollow';
import { Ship } from '../components/ship';
import { Trail } from '../components/trail';

export const Shapes = () => {
    label('A HUD', 16, 24);
    label('JOINS AND ENDS', 16, 80);
    label('HOLLOWED IN', 16, 180);
    label('A SHIP IN LINES, WITH A GLOW', 260, 24);

    useSpawn(Hud)();
    useSpawn(Lines)();
    useSpawn(Hollow)();

    const ship = useSpawn(Ship)();

    // What Ship returns, its place, is the trail's to follow.
    useSpawn(Trail)(ship.api);

    return createScene();
};
```

`settings.ts`:

```ts
export const WIDTH = 480;
export const HEIGHT = 270;
export const SHIP = [
    {
        x: 12,
        y: 0
    },
    {
        x: -8,
        y: -8
    },
    {
        x: -4,
        y: 0
    },
    {
        x: -8,
        y: 8
    }
];
```
