# Shading with ramps

> Flat pictures given a lit side and a shadowed one with shadeWithRamp, a light that moves round, and a floor of fillDither.

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

The same two pictures flat and shaded. `shadeWithRamp` moves each pixel on the edge facing the light one step up its ramp, and each one on the far edge one step down, each colour along its own.

The floor is `fillDither`: two colours in the ordered pattern, a shadow thickening in steps.

Click the preview first so it hears the keyboard. Space moves the light round.

## Code

`index.ts`:

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

const game = createGame('#app', {
    width: Settings.WIDTH,
    height: Settings.HEIGHT,
    background: '#3b4a5a',
    scaling: 'contain'
});

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

`art/figures.ts`:

```ts
import { mirrorPixels, pixelsFromRows } from 'nacatamalon/pixels';
import * as Settings from '../settings';

export const createArt = () => {
    const palette = {
        s: Settings.SKIN[1],
        u: Settings.SUIT[1],
        c: Settings.CRATE[1],
        k: '#222034'
    };

    // Half a figure: the mirror draws the rest.
    const figure = pixelsFromRows(['................', '......ss........', '.....sss........', '.....sks........', '.....sss........', '......ss........', '...uuuuu........', '..uuuuuu........', '..uuuuuu........', '..ss.uuu........', '..ss.uuu........', '.....uuu........', '.....uu.........', '.....uu.........', '....kkk.........', '................',], palette);

    mirrorPixels(figure, 'x');

    const crate = pixelsFromRows(Array.from({
        length: 16
    }, (_, y) => (y === 0 || y === 15 ? '................' : '.cccccccccccccc.')), palette);

    return {
        figure,
        crate
    };
};
```

`components/comparison.ts`:

```ts
import { createSprite, createText, getColor, useKeyboard, useUpdate } from 'nacatamalon';
import { clonePixels, createPixels, createPixelTexture, fillDither, outlinePixels, shadeWithRamp, updatePixelTexture } from 'nacatamalon/pixels';
import * as Settings from '../settings';
import { createArt } from '../art/figures';

export const Comparison = () => {
    const { figure, crate } = createArt();

    const pictures = [figure, crate].map((art, at) => {
        const flat = clonePixels(art);

        outlinePixels(flat, '#222034');
        createSprite({
            texture: createPixelTexture(flat),
            anchor: {
                x: 0,
                y: 0
            },
            transform: {
                x: 8 + at * 20,
                y: 12
            }
        });

        const shown = clonePixels(art);
        const texture = createPixelTexture(shown);

        createSprite({
            texture,
            anchor: {
                x: 0,
                y: 0
            },
            transform: {
                x: 8 + at * 20,
                y: 40
            }
        });

        return {
            art,
            shown,
            texture
        };
    });

    createText({
        text: 'FLAT',
        transform: {
            x: 52,
            y: 18
        },
        tint: '#a0a8c0'
    });

    const label = createText({
        text: '',
        transform: {
            x: 52,
            y: 46
        },
        tint: '#ffffff'
    });

    // A floor: the shadow thickens in steps of the same two colours.
    const dark = getColor('#2c3440');
    const light = getColor('#4a5666');
    const floor = createPixels(168, 16, light);

    [0.125, 0.375, 0.625, 0.875].forEach((amount, row) => fillDither(floor, 0, row * 4, 168, 4, light, dark, amount));
    createSprite({
        texture: createPixelTexture(floor),
        anchor: {
            x: 0,
            y: 0
        },
        transform: {
            x: 0,
            y: 60
        }
    });

    let at = 0;

    const shine = () => {
        const { name, x, y } = Settings.LIGHTS[at];

        label.text = name;

        for (const { art, shown, texture } of pictures) {
            shown.data.set(art.data);
            shadeWithRamp(shown, [Settings.SKIN, Settings.SUIT, Settings.CRATE], {
                light: {
                    x,
                    y
                }
            });
            outlinePixels(shown, '#222034');
            updatePixelTexture(texture);
        }
    };

    shine();

    const keys = useKeyboard();

    useUpdate(() => {
        if (keys.justPressed(' ')) {
            at = (at + 1) % Settings.LIGHTS.length;
            shine();
        }
    });
};
```

`scenes/demo.ts`:

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

import { Comparison } from '../components/comparison';

export const Shaded = () => {
    useSpawn(Comparison)();

    return createScene();
};
```

`settings.ts`:

```ts
export const WIDTH = 168;
export const HEIGHT = 76;
export const SKIN = ['#7a4a35', '#c98a63', '#f2c79a'];
export const SUIT = ['#1f3a5f', '#3a6ea5', '#7fb2d9'];
export const CRATE = ['#5b3a1e', '#8f5e2e', '#c2894a'];
export const LIGHTS = [
    {
        name: 'TOP LEFT',
        x: -1,
        y: -1
    },
    {
        name: 'TOP RIGHT',
        x: 1,
        y: -1
    },
    {
        name: 'BOTTOM RIGHT',
        x: 1,
        y: 1
    },
    {
        name: 'BOTTOM LEFT',
        x: -1,
        y: 1
    },
];
```
