# Neon signs

> Three neon signs as panel lights in an alley at night, one of them failing.

Example `rect-light-neon`, running at https://nacatamalon.com/en/examples/rect-light-neon. The whole catalogue: https://nacatamalon.com/llms/examples.md.

An alley at night lit only by three neon signs, each a `useRectLight` and the shape you see on one object. They face out of the wall and tilt down, so the pavement and the crates take each colour in a long band.

A light shines along its own -Z, so a sign on a wall is turned round with `rotationY: Math.PI` to light the street and not the bricks behind it. The amber one is failing: its intensity drops to zero for a moment now and then.

## Code

`index.ts`:

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

import { WIDTH, HEIGHT } from './settings';
import { Alley } from './scenes/demo';

const game = createGame('#app', {
    width: WIDTH,
    height: HEIGHT,
    background: '#07060c'
});

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

`components/alleywalls.ts`:

```ts
import { createMesh, useCubeGeometry, usePlaneGeometry } from 'nacatamalon';

export const AlleyWalls = () => {
    const ground = usePlaneGeometry({
        width: 12,
        depth: 8,
        widthSegments: 48,
        depthSegments: 32
    });

    const wall = usePlaneGeometry({
        width: 12,
        depth: 5,
        widthSegments: 48,
        depthSegments: 20
    });

    createMesh({
        geometry: ground,
        tint: '#3a3b48',
        specular: '#6a6a80',
        shininess: 24
    });
    createMesh({
        geometry: wall,
        tint: '#5a4a52',
        transform: {
            y: 2.5,
            z: -3,
            rotationX: Math.PI / 2
        }
    });

    // Crates and a bin by the wall, to catch the light on their tops and fronts.
    const cube = useCubeGeometry();

    for (const [x, z, w, h] of [[-2.6, -2.2, 0.9, 0.9], [-1.7, -2.4, 0.7, 0.6], [2.2, -2.3, 0.8, 1.2], [0.4, -1.4, 0.5, 0.5]] as const) {
        createMesh({
            geometry: cube,
            tint: '#b0a8a0',
            transform: {
                x,
                y: h / 2,
                z,
                scaleX: w,
                scaleY: h,
                scaleZ: w
            }
        });
    }
};
```

`components/sign.ts`:

```ts
import { createMesh, getColor, useCubeGeometry, useRectLight } from 'nacatamalon';

export const Sign = (x: number, y: number, width: number, height: number, hex: string) => {
    const color = getColor(hex);

    // Turned to face out of the wall (a light shines along its own -Z) and tilted down at the
    // pavement, where a sign's light lands.
    const light = useRectLight({
        width,
        height,
        range: 5,
        intensity: 2.6,
        ambient: 0.03,
        color,
        transform: {
            x,
            y,
            z: -2.85,
            rotationX: -0.45,
            rotationY: Math.PI
        }
    });

    const tube = createMesh({
        geometry: useCubeGeometry(),
        tint: '#ffffff',
        emissive: color,
        transform: {
            x,
            y,
            z: -2.95,
            scaleX: width,
            scaleY: height,
            scaleZ: 0.06
        }
    });

    return {
        light,
        tube,
        color
    };
};
```

`scenes/demo.ts`:

```ts
import { createScene, createText, getColor, useCamera3d, useSpawn, useUpdate } from 'nacatamalon';
import * as Settings from '../settings';

import { Sign } from '../components/sign';
import { AlleyWalls } from '../components/alleywalls';

export const Alley = () => {
    useCamera3d(Settings.CAMERA);

    useSpawn(AlleyWalls)();

    let seed = Settings.SEED;
    const rand = () => (seed = (seed * 16807) % 2147483647) / 2147483647;
    const spawn = useSpawn(Sign);

    spawn(-2.2, 2.2, 2.2, 0.35, '#ff3fa4');
    spawn(2.3, 2.6, 0.35, 1.6, '#3fe0ff');

    const failing = spawn(0.2, 1.4, 1.4, 0.25, '#ffb23f').api;

    createText({
        text: 'NEON ALLEY',
        tint: '#ff9fd0',
        transform: {
            x: 8,
            y: 8
        }
    });

    // Off for a moment now and then, and sometimes a few times in a row.
    let wait = 1;
    let dark = 0;

    useUpdate((delta) => {
        wait -= delta;
        dark -= delta;

        if (wait <= 0) {
            dark = 0.05 + rand() * 0.12;
            wait = rand() < 0.4 ? 0.15 : 0.6 + rand() * 2;
        }

        const on = dark <= 0;

        failing.light.intensity = on ? 2.6 : 0;
        failing.tube.material.emissive = on ? failing.color : getColor('#2a1d10');
    });

    return createScene();
};
```

`settings.ts`:

```ts
export const WIDTH = 480;
export const HEIGHT = 320;
import type { TCamera3dOptions } from 'nacatamalon';

export const CAMERA = {
    projection: 'perspective',
    fov: 58,
    transform: {
        x: 0.4,
        y: 1.4,
        z: 4.2,
        rotationX: -0.12
    }
} satisfies TCamera3dOptions;
export const SEED = 11;
```
