# Filters

> useFilter runs effects over what one object draws: an outline, a glow, a drop shadow, a crt on a screen, and a hit flash written by hand.

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

A filter runs effects over what one object, and everything under it, draws: one contour around a character made of pieces, a glow behind a coin, a shadow under a row of enemies. `useFilter` makes it and `add` says what it filters, like a mask.

It takes `outline`, `dropShadow` and `glow`, made for it, and every effect of the screen: `rect` keeps a `crt` to a television's screen. Each object filtered costs a picture of its own, so many enemies go under one object with one filter.

One of your own is written as a screen effect is, `fn effect(color, uv)` in WGSL and GLSL with its knobs: the slime's hit flash. On an object, `uv` and `resolution` are its part of the screen, the colour comes multiplied by its alpha, and `reach` or `padding` give it room to draw around the object.

## Code

`index.ts`:

```ts
import { createGame } from 'nacatamalon';
import { Filters } from './scenes/demo';
import { WIDTH, HEIGHT } from './settings';

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

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

`components/coin.ts`:

```ts
import { createSprite, useSelf, useTransform, useUpdate } from 'nacatamalon';
import { createPixelTexture, createPixels, fillCircle } from 'nacatamalon/pixels';
import { glow, useFilter } from 'nacatamalon/effects';

export const Coin = () => {
    useTransform({
        x: 190,
        y: 96
    });

    const disc = createPixels(24, 24, '#00000000');

    fillCircle(disc, 12, 12, 11, '#ffd23f');
    fillCircle(disc, 12, 12, 7, '#ff9f1c');
    createSprite({
        texture: createPixelTexture(disc)
    });

    const shine = useFilter({
        effects: [
            glow({
                color: '#ffd23f',
                radius: 8
            })
        ]
    });

    shine.add(useSelf());

    let time = 0;

    useUpdate((delta) => {
        time += delta;
        shine.effects[0].uniforms.intensity = 1.4 + Math.sin(time * 4) * 0.8;
    });
};

// 3. A television: the crt() is kept to its screen with rect, so the curve and the lines are the
//    screen's and not the game's.
```

`components/enemies.ts`:

```ts
import { useSelf, useSpawn, useTransform } from 'nacatamalon';

import { dropShadow, outline, useFilter } from 'nacatamalon/effects';

import { Enemy } from './enemy';

export const Enemies = () => {
    useTransform({
        x: 200,
        y: 210
    });

    for (let i = 0; i < 6; i++) {
        useSpawn(Enemy(i * 26))();
    }

    useFilter({
        effects: [
            outline({
                color: '#ffd23f'
            }),
            dropShadow({
                offset: {
                    x: 0,
                    y: 4
                },
                radius: 2
            })
        ]
    }).add(useSelf());
};
```

`components/enemy.ts`:

```ts
import { createSprite, useTransform, useUpdate } from 'nacatamalon';

export const Enemy = (x: number) => () => {
    const place = useTransform({
        x,
        y: 0
    });

    createSprite({
        width: 14,
        height: 14,
        tint: '#e15554'
    });
    createSprite({
        width: 4,
        height: 4,
        tint: '#ffffff',
        transform: {
            x: -3,
            y: -2
        }
    });
    createSprite({
        width: 4,
        height: 4,
        tint: '#ffffff',
        transform: {
            x: 3,
            y: -2
        }
    });

    let time = x;

    useUpdate((delta) => {
        time += delta;
        place.y = Math.round(Math.sin(time * 4) * 4);
    });
};
```

`components/glass.ts`:

```ts
import { createSprite, useMask, useSelf, useSpawn, useTransform, useUpdate } from 'nacatamalon';

import { blur, useFilter } from 'nacatamalon/effects';
import { COLORS } from '../settings';

export const Glass = () => {
    useTransform({
        x: 70,
        y: 210
    });
    useMask({
        rect: {
            x: -50,
            y: -30,
            width: 100,
            height: 60
        }
    }).add(useSelf());

    // The stripes' own place, kept to slide them along.
    let stripes = {
        x: 0
    };

    useSpawn(() => {
        stripes = useTransform({
            x: 0,
            y: 0
        });

        for (let i = 0; i < 16; i++) {
            createSprite({
                width: 8,
                height: 60,
                tint: COLORS[i % 8],
                transform: {
                    x: -60 + i * 8,
                    y: 0
                }
            });
        }
    })();
    useFilter({
        effects: [
            blur({
                radius: 4
            })
        ]
    }).add(useSelf());

    let time = 0;

    useUpdate((delta) => {
        time += delta;
        stripes.x = -((time * 12) % 64);
    });
};

// 5. A row of enemies under one filter, hung from one object: one picture for all of them, which is
//    what to do when there are many.
```

`components/hero.ts`:

```ts
import { createSprite, useTransform, useUpdate } from 'nacatamalon';

export const Hero = () => {
    const place = useTransform({
        x: 70,
        y: 96
    });

    createSprite({
        width: 18,
        height: 22,
        tint: '#4d9de0'
    });
    createSprite({
        width: 14,
        height: 12,
        tint: '#f4a7bb',
        transform: {
            x: 0,
            y: -16
        }
    });
    createSprite({
        width: 4,
        height: 26,
        tint: '#d8d8e8',
        transform: {
            x: 14,
            y: -6,
            rotation: 0.5
        }
    });
    createSprite({
        width: 6,
        height: 8,
        tint: '#7768ae',
        transform: {
            x: -5,
            y: 15
        }
    });
    createSprite({
        width: 6,
        height: 8,
        tint: '#7768ae',
        transform: {
            x: 5,
            y: 15
        }
    });

    let time = 0;

    useUpdate((delta) => {
        time += delta;
        place.y = 96 + Math.round(Math.sin(time * 3) * 3);
        place.rotation = Math.sin(time * 1.3) * 0.15;
    });
};

// 2. A coin that shines: a glow behind it, its strength written every frame.
```

`components/slime.ts`:

```ts
import { createSprite, useSelf, useTransform, useUpdate } from 'nacatamalon';
import { createPixelTexture, createPixels, fillCircle } from 'nacatamalon/pixels';
import { useFilter } from 'nacatamalon/effects';

import { HIT_FLASH } from '../shaders/hit_flash';

export const Slime = () => {
    useTransform({
        x: 410,
        y: 214
    });

    const body = createPixels(28, 22, '#00000000');

    fillCircle(body, 14, 14, 13, '#0ead69');
    createSprite({
        texture: createPixelTexture(body)
    });
    createSprite({
        width: 4,
        height: 5,
        tint: '#10101a',
        transform: {
            x: -5,
            y: 1
        }
    });
    createSprite({
        width: 4,
        height: 5,
        tint: '#10101a',
        transform: {
            x: 5,
            y: 1
        }
    });

    const hit = useFilter({
        effects: [HIT_FLASH]
    });

    hit.add(useSelf());

    let time = 0;

    useUpdate((delta) => {
        time += delta;
        // Struck every second and a half: white at once, fading in a quarter of a second.
        hit.effects[0].uniforms.flash = Math.max(0, 1 - (time % 1.5) * 4);
    });
};
```

`components/tv.ts`:

```ts
import { createSprite, createText, useSpawn, useTransform, useUpdate } from 'nacatamalon';

import { crt, useFilter } from 'nacatamalon/effects';
import { COLORS } from '../settings';

export const Tv = () => {
    useTransform({
        x: 330,
        y: 96
    });
    createSprite({
        width: 120,
        height: 92,
        tint: '#3a3a4a'
    });

    const show = useSpawn(() => {
        useTransform({
            x: 0,
            y: 0
        });
        createSprite({
            width: 96,
            height: 72,
            tint: '#10101a'
        });

        for (let i = 0; i < 6; i++) {
            createSprite({
                width: 16,
                height: 72,
                tint: COLORS[i],
                transform: {
                    x: -40 + i * 16,
                    y: 0
                }
            });
        }

        const word = createText({
            text: 'ON AIR',
            transform: {
                x: -22,
                y: -4
            },
            tint: '#ffffff'
        });

        let time = 0;

        useUpdate((delta) => {
            time += delta;
            word.transform.y = -4 + Math.round(Math.sin(time * 2) * 20);
        });
    })();

    useFilter({
        effects: [
            crt({
                curvature: 0.2,
                scanlines: 0.6
            })
        ],
        rect: {
            x: -48,
            y: -36,
            width: 96,
            height: 72
        }
    }).add(show);
};

// 4. Frosted glass: a window over stripes that move, blurred, and kept to the window by a mask.
//    The mask is outside the filter, so the blur does not spill out of it.
```

`scenes/demo.ts`:

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

import { dropShadow, outline, useFilter } from 'nacatamalon/effects';
import { Hero } from '../components/hero';
import { Coin } from '../components/coin';
import { Tv } from '../components/tv';
import { Glass } from '../components/glass';
import { Enemies } from '../components/enemies';
import { Slime } from '../components/slime';

const label = (text: string, x: number, y: number) => createText({
    text,
    font: 'nacatamalon:thin',
    transform: {
        x,
        y
    }
});

export const Filters = () => {
    label('OUTLINE + DROP SHADOW', 14, 24);
    label('GLOW', 172, 24);
    label('CRT ON ITS SCREEN', 278, 24);
    label('BLUR INSIDE A MASK', 14, 160);
    label('ONE FILTER FOR MANY', 200, 160);
    label('YOUR OWN: A HIT', 372, 160);

    const hero = useSpawn(Hero)();

    useFilter({
        effects: [
            outline({
                color: '#ffffff'
            }),
            dropShadow({
                offset: {
                    x: 3,
                    y: 3
                },
                radius: 0,
                amount: 0.5
            })
        ]
    }).add(hero);
    useSpawn(Coin)();
    useSpawn(Tv)();
    useSpawn(Glass)();
    useSpawn(Enemies)();
    useSpawn(Slime)();

    return createScene();
};
```

`settings.ts`:

```ts
export const WIDTH = 480;
export const HEIGHT = 270;
// Filters: effects run over what one object, and everything under it, draws. useFilter makes one and
// add says what it filters; each object it is added to is filtered on its own.

export const COLORS = ['#ff6b1a', '#ffd23f', '#3bceac', '#0ead69', '#4d9de0', '#7768ae', '#e15554', '#f4a7bb'];
```

`shaders/hit_flash.ts`:

```ts
// 6. One of your own: a hit flash, written in place in both languages. What it is handed has its
//    colour multiplied by its alpha, so white there is vec3(alpha), and what is see-through stays so.
export const HIT_FLASH = {
    name: 'hitFlash',
    fragment: /* wgsl */ `
fn effect(color: vec4<f32>, uv: vec2<f32>) -> vec4<f32> {
    return vec4<f32>(mix(color.rgb, vec3<f32>(color.a), mu.flash), color.a);
}`,
    fragmentGlsl: /* glsl */ `
vec4 effect(vec4 color, vec2 uv) {
    return vec4(mix(color.rgb, vec3(color.a), mu.flash), color.a);
}`,
    uniforms: {
        flash: 0
    },
};
```
