# Sides of a model

> cull on a material picks which side of its triangles is thrown away: 'back' seen from outside, 'none' both sides, 'front' seen from inside.

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

`cull` on a model's material decides which side of its triangles is thrown away. `'back'`, the default, is a closed shape seen from outside: the first sheet keeps only its top and vanishes each time it turns over. `'none'` draws both sides, each lit as the side it is and each casting its shadow: the second sheet, the flag and the top of the liquid. `'front'` is a shape seen from inside, lit from inside: the sky round it all.

A shader tells the two sides apart with `ctx.frontFacing` (`composerFrontFacing()` in the composer): the flag is red on one side and white on the other, and the liquid is pale from above and deep from below, with no second sheet turned round. The flag waves by a vertex hook, and its shadow waves with it.

A glTF material marked `doubleSided` is drawn with `'none'` without being told.

## 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/sides.ts`:

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

import { FLAG, FLAG_GLSL, FLAG_VERTEX, FLAG_VERTEX_GLSL, LIQUID, LIQUID_GLSL } from '../shaders/sides';

export const Sides = () => {
    const sheet = usePlaneGeometry({
        width: 1.8,
        depth: 1.8,
        widthSegments: 4,
        depthSegments: 4
    });

    const onlyTop = createMesh({
        geometry: sheet,
        tint: '#ff9e3d',
        transform: {
            x: -4.6,
            y: 1.4
        }
    });

    const bothSides = createMesh({
        geometry: sheet,
        tint: '#ff9e3d',
        cull: 'none',
        transform: {
            x: -2.2,
            y: 1.4
        }
    });

    // A flag on its pole, standing up: a sheet lies flat facing up, and a quarter turn about x
    // stands it facing the camera. It turns about the pole, its pivot, to show both sides.
    createMesh({
        geometry: useCubeGeometry(),
        tint: '#aaaaaa',
        transform: {
            x: -0.2,
            y: 1.2,
            scaleX: 0.08,
            scaleY: 3.6,
            scaleZ: 0.08
        }
    });

    const flag = createMaterial({
        shader: 'mesh3d',
        cull: 'none',
        fragment: FLAG,
        fragmentGlsl: FLAG_GLSL,
        vertex: FLAG_VERTEX,
        vertexGlsl: FLAG_VERTEX_GLSL
    });

    const cloth = createMesh({
        geometry: usePlaneGeometry({
            width: 2.4,
            depth: 1.5,
            widthSegments: 16,
            depthSegments: 8
        }),
        material: flag,
        transform: {
            x: 1.0,
            y: 2.2,
            rotationX: Math.PI / 2,
            pivot: {
                x: -1.2,
                y: 0,
                z: 0
            }
        },
    });

    // The top of a liquid, tipping as the bottle would: seen from above and from below.
    const liquid = createMaterial({
        shader: 'mesh3d',
        cull: 'none',
        tint: '#7fd4ff',
        fragment: LIQUID,
        fragmentGlsl: LIQUID_GLSL
    });

    const surface = createMesh({
        geometry: useCircleGeometry({
            radius: 1.1,
            segments: 32
        }),
        material: liquid,
        castShadow: false,
        transform: {
            x: 4.4,
            y: 1.4
        }
    });

    useUpdate((delta, t) => {
        onlyTop.transform.rotationX += delta * 1.1;
        bothSides.transform.rotationX += delta * 1.1;
        surface.transform.rotationX = Math.sin(t * 0.7) * 1.5;
        cloth.transform.rotationY = t * 0.6;
    });
};
```

`scenes/demo.ts`:

```ts
import { cameraLookAt, createMesh, createScene, createText, useCamera3d, useDirectionalLight, usePlaneGeometry, usePointLight, useSphereGeometry, useSpawn } from 'nacatamalon';

import { Sides } from '../components/sides';

export const Scene = () => {
    const camera = useCamera3d({
        projection: 'perspective',
        fov: 50,
        near: 0.1,
        far: 200,
        transform: {
            y: 3,
            z: 10
        }
    });

    cameraLookAt(camera, {
        x: 0,
        y: 1.2,
        z: 0
    });
    useDirectionalLight({
        color: '#fff1d0',
        intensity: 0.9,
        ambient: 0.12,
        castShadow: true,
        shadowArea: 16,
        transform: {
            rotationX: -0.9,
            rotationY: 0.5
        }
    });
    // A blue lamp under everything, so an underside is lit by something the top is not.
    usePointLight({
        color: '#3d7bff',
        intensity: 1.4,
        range: 7,
        transform: {
            y: -0.2,
            z: 2
        }
    });

    // The sky, seen from inside. 'front' throws away the side facing in and keeps the inside, lit
    // as an inside: by the sun, from within.
    createMesh({
        geometry: useSphereGeometry({
            radius: 60,
            segments: 24,
            rings: 16
        }),
        tint: '#33407a',
        emissive: '#0d1230',
        cull: 'front',
        castShadow: false
    });
    createMesh({
        geometry: usePlaneGeometry({
            width: 16,
            depth: 8
        }),
        tint: '#5a5a6e',
        castShadow: false,
        transform: {
            y: -0.6
        }
    });

    useSpawn(Sides)();
    createText({
        text: 'BACK',
        transform: {
            x: 52,
            y: 300
        }
    });
    createText({
        text: 'NONE',
        transform: {
            x: 172,
            y: 300
        }
    });
    createText({
        text: 'NONE, TWO COLOURS',
        transform: {
            x: 300,
            y: 300
        }
    });
    createText({
        text: 'LIQUID',
        transform: {
            x: 520,
            y: 300
        }
    });
    createText({
        text: 'THE SKY: FRONT',
        tint: '#8c82a6',
        transform: {
            x: 8,
            y: 8
        }
    });

    return createScene();
};
```

`settings.ts`:

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

`shaders/sides.ts`:

```ts
// The flag and the liquid, written twice, once per language, so they run on both cards. Each
// asks ctx.frontFacing which side of the sheet it is drawing: only ever false with cull 'none'
// (or 'front'), where the other side is kept.

// The flag waves more towards its free edge: the sheet runs from -1.2 to 1.2 across, pinned at
// the pole end. It moves along its own facing, and the engine works the facing out again.
export const FLAG_VERTEX = `
fn vertex(pos: vec3<f32>, ctx: VertContext) -> vec3<f32> {
    let free = (pos.x + 1.2) / 2.4;
    return pos + ctx.normal * sin(pos.x * 2.6 - mu.time * 5.0) * 0.22 * free;
}
`;

export const FLAG_VERTEX_GLSL = `
vec3 vertex(vec3 pos, VertContext ctx) {
    float free = (pos.x + 1.2) / 2.4;
    return pos + ctx.normal * sin(pos.x * 2.6 - mu.time * 5.0) * 0.22 * free;
}
`;

export const FLAG = `
fn effect(surface: vec4<f32>, ctx: FragContext) -> vec4<f32> {
    let cloth = select(vec3<f32>(0.95, 0.95, 0.9), vec3<f32>(0.85, 0.12, 0.15), ctx.frontFacing);
    return vec4<f32>(cloth * ctx.light + ctx.shine, 1.0);
}
`;

export const FLAG_GLSL = `
vec4 effect(vec4 surface, FragContext ctx) {
    vec3 cloth = ctx.frontFacing ? vec3(0.85, 0.12, 0.15) : vec3(0.95, 0.95, 0.9);
    return vec4(cloth * ctx.light + ctx.shine, 1.0);
}
`;

// From above the surface is the drink catching the light; from below it is the drink seen through
// itself, deeper and darker, with a little glow so it never goes black.
export const LIQUID = `
fn effect(surface: vec4<f32>, ctx: FragContext) -> vec4<f32> {
    let below = surface.rgb * vec3<f32>(0.25, 0.45, 0.7) * (ctx.light + vec3<f32>(0.25));
    let above = surface.rgb * ctx.light + ctx.shine;
    return vec4<f32>(select(below, above, ctx.frontFacing), 1.0);
}
`;

export const LIQUID_GLSL = `
vec4 effect(vec4 surface, FragContext ctx) {
    vec3 below = surface.rgb * vec3(0.25, 0.45, 0.7) * (ctx.light + vec3(0.25));
    vec3 above = surface.rgb * ctx.light + ctx.shine;
    return vec4(ctx.frontFacing ? above : below, 1.0);
}
`;
```
