# Angles in the composer

> composerAtan2 turns a place into how far round a centre it is: rays that turn, a ring that fills like a timer, and a sphere pushed into petals.

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

`composerAtan2(y, x)` is the building block of anything radial. It is spelled `atan2` in WGSL and `atan` in GLSL; the composer writes both, so it runs on either card, in either stage.

Rays that turn with time, a ring that fills like a timer from `material.uniforms.progress`, and a sphere pushed into six petals in the vertex stage by the angle around its axis.

## Code

`index.ts`:

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

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

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

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

`components/radial.ts`:

```ts
import { createMaterial, createMesh, createSprite, useIcoSphereGeometry, useUpdate } from 'nacatamalon';
import { compileShader, composerAdd, composerAtan2, composerFract, composerLength, composerMix, composerMul, composerSin, composerSmoothstep, composerStep, composerSub, composerTime, composerUniform, composerUv, composerVec3, composerVec4, composerVertexNormal, composerVertexPosition, composerWorldNormal, composerX, composerY, composerZ } from 'nacatamalon/shader';
import * as Settings from '../settings';

export const Radial = () => {
    // From the middle of the sprite: uv - 0.5, and the angle of that point.
    const centred = composerSub(composerUv(), 0.5);
    const angle = composerAtan2(composerY(centred), composerX(centred));
    const turn = composerAdd(composerMul(angle, 1 / Settings.TAU), 0.5); // 0..1 all the way round
    const radius = composerLength(centred);

    // Twelve rays that turn: the angle, counted in twelfths, with time added to spin it.
    const rays = composerStep(0.5, composerFract(composerAdd(composerMul(turn, 12), composerMul(composerTime(), 0.3))));
    const sunColor = composerMix(composerVec3(0.9, 0.35, 0.1), composerVec3(1, 0.85, 0.3), rays);
    const disc = composerSub(1, composerSmoothstep(0.46, 0.5, radius));

    createSprite({
        width: 100,
        height: 100,
        transform: {
            x: 70,
            y: 80
        },
        material: createMaterial(compileShader({
            shader: 'sprite2d',
            color: composerVec4(sunColor, disc)
        })),
    });

    // A ring lit up to `progress` of the way round.
    const filled = composerStep(turn, composerUniform('progress', 0));
    const ring = composerMul(composerSmoothstep(0.3, 0.32, radius), composerSub(1, composerSmoothstep(0.45, 0.47, radius)));
    const ringColor = composerMix(composerVec3(0.15, 0.18, 0.3), composerVec3(0.35, 0.95, 0.65), filled);

    const timer = createMaterial(compileShader({
        shader: 'sprite2d',
        color: composerVec4(ringColor, ring)
    }));

    createSprite({
        width: 100,
        height: 100,
        transform: {
            x: 180,
            y: 80
        },
        material: timer
    });

    // Petals: the corner pushed out along its normal by a wave of the angle around the Y axis. Time
    // added to the angle turns the petals round, in the shader, with the model standing still.
    const p = composerVertexPosition();
    const around = composerAtan2(composerZ(p), composerX(p));
    const petals = composerMul(composerSin(composerAdd(composerMul(around, 6), composerTime())), 0.3);

    createMesh({
        geometry: useIcoSphereGeometry({
            subdivisions: 4
        }),
        transform: {
            x: 290,
            y: Settings.HEIGHT - 80,
            scaleX: 38,
            scaleY: 38,
            scaleZ: 38,
            rotationX: Math.PI / 2
        },
        material: createMaterial(compileShader({
            shader: 'mesh3d',
            // Lighter where the surface faces the camera, darker where it turns away, so the petals read.
            color: composerVec4(composerMix(composerVec3(0.55, 0.15, 0.4), composerVec3(1, 0.7, 0.85), composerZ(composerWorldNormal())), 1),
            position: composerAdd(p, composerMul(composerVertexNormal(), petals)),
        })),
    });

    useUpdate((_delta, t) => {
        timer.uniforms.progress = (t / 4) % 1;
    });
};
```

`scenes/demo.ts`:

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

import { Radial } from '../components/radial';

export const Level = () => {
    useCamera3d();
    useDirectionalLight({
        intensity: 1
    });

    useSpawn(Radial)();
    createText({
        text: 'RAYS',
        anchor: {
            x: 0.5,
            y: 0
        },
        transform: {
            x: 70,
            y: 140
        },
        tint: '#6b7390'
    });
    createText({
        text: 'TIMER',
        anchor: {
            x: 0.5,
            y: 0
        },
        transform: {
            x: 180,
            y: 140
        },
        tint: '#6b7390'
    });
    createText({
        text: 'PETALS',
        anchor: {
            x: 0.5,
            y: 0
        },
        transform: {
            x: 290,
            y: 140
        },
        tint: '#6b7390'
    });

    return createScene();
};
```

`settings.ts`:

```ts
export const WIDTH = 360;
export const HEIGHT = 160;
export const TAU = Math.PI * 2;
```
