# TV glow

> A television screen as a panel light whose colour and brightness change with every cut, lighting the room around it.

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

A living room at night with only the television on. The screen is a `useRectLight` facing the sofa, and every cut of the film changes its `color` and `intensity`, so the whole room follows.

Press space to switch it off and leave only the lamp in the corner, which is a small panel light of its own.

## Code

`index.ts`:

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

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

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

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

`components/interior.ts`:

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

export const Interior = () => {
    const floor = usePlaneGeometry({
        width: 8,
        depth: 8,
        widthSegments: 40,
        depthSegments: 40
    });

    const wall = usePlaneGeometry({
        width: 8,
        depth: 4,
        widthSegments: 40,
        depthSegments: 20
    });

    createMesh({
        geometry: floor,
        tint: '#6b5848'
    });
    createMesh({
        geometry: wall,
        tint: '#8a8478',
        transform: {
            y: 2,
            z: -4,
            rotationX: Math.PI / 2
        }
    });
    createMesh({
        geometry: wall,
        tint: '#8a8478',
        transform: {
            x: -4,
            y: 2,
            rotationX: Math.PI / 2,
            rotationY: Math.PI / 2
        }
    });

    const cube = useCubeGeometry();

    const box = (hex: string, x: number, y: number, z: number, sx: number, sy: number, sz: number) =>
        createMesh({
            geometry: cube,
            tint: getColor(hex),
            transform: {
                x,
                y,
                z,
                scaleX: sx,
                scaleY: sy,
                scaleZ: sz
            }
        });

    // The television on its stand, against the back wall.
    box('#3a3430', 0, 0.3, -3.4, 2.4, 0.6, 0.7);
    box('#151515', 0, 1.25, -3.55, 1.9, 1.15, 0.12);

    const screen = box('#ffffff', 0, 1.25, -3.48, 1.7, 0.95, 0.02);
    // Turned round to face the room: a light shines along its own -Z, which here is the wall.

    // The sofa facing it, and a low table between.
    box('#7a4a4a', 0, 0.35, 1.4, 2.8, 0.7, 1);
    box('#7a4a4a', 0, 0.85, 1.85, 2.8, 0.9, 0.3);
    box('#7a4a4a', -1.55, 0.6, 1.4, 0.3, 0.6, 1);
    box('#7a4a4a', 1.55, 0.6, 1.4, 0.3, 0.6, 1);
    box('#9a8068', 0, 0.22, -0.6, 1.4, 0.44, 0.8);

    return screen;
};
```

`components/lamp.ts`:

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

export const Lamp = () => {
    useRectLight({
        width: 0.3,
        height: 0.3,
        range: 3,
        intensity: 0.5,
        ambient: 0.04,
        color: '#ffb870',
        transform: {
            x: -3.4,
            y: 1.6,
            z: -3.4
        }
    });
    createMesh({
        geometry: useCubeGeometry(),
        tint: '#ffd9a0',
        emissive: '#ffb870',
        transform: {
            x: -3.4,
            y: 1.65,
            z: -3.4,
            scaleX: 0.3,
            scaleY: 0.3,
            scaleZ: 0.3
        }
    });
};
```

`scenes/demo.ts`:

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

import { Interior } from '../components/interior';
import { Lamp } from '../components/lamp';

export const Room = () => {
    // From the side, to see the screen and the front of the sofa it lights.
    useCamera3d(Settings.CAMERA);

    const screen = useSpawn(Interior)().api;

    const glow = useRectLight({
        width: 1.7,
        height: 0.95,
        range: 8,
        intensity: 3.2,
        ambient: 0,
        transform: {
            x: 0,
            y: 1.25,
            z: -3.45,
            rotationX: 0,
            rotationY: Math.PI
        }
    });

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

    // A dim lamp in the corner, so the room is not black with the screen off. Its own object: one
    // light each.
    useSpawn(Lamp)();

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

    createText({
        text: 'SPACE  TV ON/OFF',
        tint: '#7a7468',
        transform: {
            x: 8,
            y: 20
        }
    });

    const keys = useKeyboard();
    let on = true;
    let shot = Settings.SHOTS[0]!;
    let next = 0;
    let strength = 1;

    useUpdate((delta, time) => {
        if (keys.justPressed(' ')) {
            on = !on;
        }

        next -= delta;

        if (next <= 0) {
            shot = Settings.SHOTS[Math.floor(rand() * Settings.SHOTS.length)]!;
            strength = 0.6 + rand() * 0.6;
            next = 0.4 + rand() * 1.6;
        }

        // A screen is never quite still: a small flutter on top of each shot.
        const flutter = strength * (0.92 + Math.sin(time * 23) * 0.04 + Math.sin(time * 7) * 0.04);

        glow.color = shot;
        glow.intensity = on ? 3.2 * flutter : 0;
        screen.material.emissive = on ? shot : getColor('#101418');
        label.text = on ? 'TV ON' : 'TV OFF';
    });

    return createScene();
};
```

`settings.ts`:

```ts
export const WIDTH = 480;
export const HEIGHT = 320;
import type { TCamera3dOptions } from 'nacatamalon';
export const CAMERA = {
    projection: 'perspective',
    fov: 66,
    transform: {
        x: 3.6,
        y: 2.4,
        z: 1.2,
        rotationX: -0.35,
        rotationY: 0.98
    }
} satisfies TCamera3dOptions;
import { getColor } from 'nacatamalon';

export const SEED = 5;
export const SHOTS = ['#5a7dff', '#ff7a3a', '#3fc8c0', '#ffe9a8', '#ffffff', '#b05aff'].map((hex) => getColor(hex));
```
