# Light through walls

> A house of single sheets: from inside, cull 'none' keeps its walls, and only a street lamp that casts a shadow stops its light at the wall.

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

A house built of single sheets, a lamp inside and a street lamp outside. A wall faces out, so from inside you see its other side, which the usual `cull: 'back'` throws away: walk in with the up arrow and the walls are gone. Press **1** for `cull: 'none'`: both sides are drawn, and each is lit as the side it is, the inside by the lamp inside.

The second thing to see is what stops light: only a shadow. Light is worked out from which way a surface faces and where the lamp is, and does not know what is in between, so the far wall inside is lit by the street lamp straight through the near one. Press **2** and the street lamp casts a shadow: then it knows, and the far wall goes dark.

The arrows go round the house and in and out of it.

## Code

`index.ts`:

```ts
import { createGame } from 'nacatamalon';
import * as Settings from './settings';
import { Scene } from './scenes/demo';

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

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

`components/house.ts`:

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

export const House = () => {
    // Four walls and a roof, each a single sheet facing out. A sheet lies flat facing up; a
    // quarter turn about x stands it up facing +z, and the turn about y says which way round.
    const wall = usePlaneGeometry({
        width: 6,
        depth: 3,
        widthSegments: 6,
        depthSegments: 3
    });

    const tint = getColor('#d8d2c4');

    const walls = [
        createMesh({
            geometry: wall,
            tint,
            transform: {
                z: 3,
                y: 1.5,
                rotationX: Math.PI / 2
            }
        }),
        createMesh({
            geometry: wall,
            tint,
            transform: {
                x: 3,
                y: 1.5,
                rotationX: Math.PI / 2,
                rotationY: Math.PI / 2
            }
        }),
        createMesh({
            geometry: wall,
            tint,
            transform: {
                z: -3,
                y: 1.5,
                rotationX: Math.PI / 2,
                rotationY: Math.PI
            }
        }),
        createMesh({
            geometry: wall,
            tint,
            transform: {
                x: -3,
                y: 1.5,
                rotationX: Math.PI / 2,
                rotationY: -Math.PI / 2
            }
        }),
        createMesh({
            geometry: usePlaneGeometry({
                width: 6,
                depth: 6,
                widthSegments: 6,
                depthSegments: 6
            }),
            tint: '#8a5a44',
            transform: {
                y: 3
            }
        }),
    ];

    return walls;
};
```

`scenes/demo.ts`:

```ts
import { cameraLookAt, createMesh, createScene, createText, useAmbientLight, useCamera3d, useCubeGeometry, useKeyboard, usePlaneGeometry, usePointLight, useSpotLight, useUpdate, useSphereGeometry, useSpawn } from 'nacatamalon';

import { House } from '../components/house';

export const Scene = () => {
    const camera = useCamera3d({
        projection: 'perspective',
        fov: 60,
        near: 0.05,
        far: 100
    });

    useAmbientLight({
        intensity: 0.1
    });

    // Inside: a warm bulb. A point light never casts, which is fine: it is the one in the room.
    usePointLight({
        color: '#ffb35c',
        intensity: 1.6,
        range: 4.5,
        transform: {
            y: 2.2
        }
    });

    // Outside: a cold street lamp at the height of the walls, aimed at the house from its right.
    // A torch can cast.
    const street = useSpotLight({
        color: '#bcd8ff',
        intensity: 2.4,
        range: 18,
        angle: 0.7,
        penumbra: 0.3,
        // Facing along (-1, -0.15, 0): turned a quarter round to face -x, and tipped a little down.
        transform: {
            x: 7,
            y: 2.6,
            rotationY: Math.PI / 2,
            rotationX: Math.atan2(-0.15, 1)
        },
    });

    // In squares: the light is worked out at the corners, and a floor of one square would only be
    // lit where its four corners are, twenty steps away.
    createMesh({
        geometry: usePlaneGeometry({
            width: 40,
            depth: 40,
            widthSegments: 40,
            depthSegments: 40
        }),
        tint: '#3a3a48',
        castShadow: false
    });
    // The street lamp itself: a post and its glowing head.
    createMesh({
        geometry: useCubeGeometry(),
        tint: '#555566',
        transform: {
            x: 7.3,
            y: 1.3,
            scaleX: 0.15,
            scaleY: 2.6,
            scaleZ: 0.15
        }
    });
    createMesh({
        geometry: useSphereGeometry({
            radius: 0.22
        }),
        tint: '#000000',
        emissive: '#bcd8ff',
        castShadow: false,
        transform: {
            x: 7,
            y: 2.6
        }
    });

    const walls = useSpawn(House)().api;

    const walls1 = createText({
        text: '',
        transform: {
            x: 8,
            y: 8
        }
    });

    const shadow2 = createText({
        text: '',
        transform: {
            x: 8,
            y: 20
        }
    });

    createText({
        text: 'ARROWS: ROUND, IN AND OUT',
        tint: '#8c82a6',
        transform: {
            x: 8,
            y: 340
        }
    });

    const keys = useKeyboard();
    // Where the camera is: round the house, and how far from its middle. Under 3 is inside.
    let around = 0.4;
    let far = 11;

    useUpdate((delta) => {
        if (keys.justPressed('1')) {
            // A material is read every frame, so this is the whole switch.
            for (const sheet of walls) {
                sheet.material.cull = sheet.material.cull === 'none' ? 'back' : 'none';
            }
        }

        if (keys.justPressed('2')) {
            street.castShadow = !street.castShadow;
        }

        if (keys.isDown('ArrowLeft')) {
            around -= delta * 1.2;
        }

        if (keys.isDown('ArrowRight')) {
            around += delta * 1.2;
        }

        if (keys.isDown('ArrowUp')) {
            far = Math.max(1.2, far - delta * 5);
        }

        if (keys.isDown('ArrowDown')) {
            far = Math.min(16, far + delta * 5);
        }

        camera.transform.x = Math.sin(around) * far;
        camera.transform.z = Math.cos(around) * far;
        camera.transform.y = far < 3 ? 1.6 : 1.6 + (far - 3) * 0.35;
        cameraLookAt(camera, {
            x: 0,
            y: 1.4,
            z: 0
        });

        walls1.text = `1  WALLS: CULL ${(walls[0].material.cull ?? 'back').toUpperCase()}`;
        shadow2.text = `2  STREET LAMP SHADOW: ${street.castShadow ? 'ON' : 'OFF'}`;
    });

    return createScene();
};
```

`settings.ts`:

```ts
export const WIDTH = 640;
export const HEIGHT = 360;
```
