# Crossing scenes

> Transitions with both scenes on screen at once: crossfade, push, and a wipe, an iris and a clock mask with the new scene behind them.

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

Transitions that go from one scene to the other with nothing in between: `crossfade`, `push`, `crossWipe`, `crossIris` and `crossDissolve`, here with a clock face that sweeps like a hand. Both rooms are on screen at once: the one going out as its last picture, still, and the one coming in already playing.

A transition of your own does the same with `cross: true`, reading the old room with `sampleOutgoing(uv)`.

Click the preview first so it hears the keyboard. 1 to 5 choose the transition, E changes the curve, X goes. M plays a `.motion` file that changes room the same way, written as data.

## Code

`index.ts`:

```ts
import { createGame } from 'nacatamalon';
import { WIDTH, HEIGHT } from './settings';
import { makeRoom, type TSelection } from './scenes/room';

const state: TSelection = {
    chosen: 0,
    curve: 0
};

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

// Declare Scenes
game({
    Day: makeRoom(state, 'DAY', 'Night', '#3a6fb0', '#ffd34d', 'left', '/example-assets/motions/cross-to-night.motion'),
    Night: makeRoom(state, 'NIGHT', 'Day', '#141a33', '#9b7bff', 'right', '/example-assets/motions/cross-to-day.motion'),
}, 'Day');
```

`scenes/room.ts`:

```ts
import { createScene, createSprite, createText, getColor, useKeyboard, useLoadMotion, useLoadTexture, useMotion, useScene, useUpdate } from 'nacatamalon';

import { crossDissolve, crossfade, crossIris, crossWipe, push } from 'nacatamalon/transitions';
import type { TTransition, TWipeDirection } from 'nacatamalon/transitions';
import { CURVES, WIDTH, KINDS, HEIGHT } from '../settings';

export type TSelection = { chosen: number; curve: number; };
export const makeRoom = (state: TSelection, title: string, next: string, bg: string, block: string, way: TWipeDirection, motion: string) => () => {
    const scene = useScene();
    const keys = useKeyboard();

    const clock = useLoadTexture({
        src: '/example-assets/assets/masks/clock.png'
    });

    const tour = useMotion(useLoadMotion({
        src: motion
    }));

    createSprite({
        anchor: {
            x: 0,
            y: 0
        },
        width: WIDTH,
        height: HEIGHT,
        tint: getColor(bg)
    });

    const blocks = Array.from({
        length: 9
    }, (_, i) => createSprite({
        width: 28,
        height: 28,
        tint: getColor(block),
        transform: {
            x: 40 + i * 50,
            y: 120 + Math.sin(i) * 30
        },
    }));

    createText({
        text: title,
        style: {
            fontSize: 16
        },
        tint: '#f2f6fa',
        transform: {
            x: 16,
            y: 14
        }
    });

    const said = createText({
        text: 'M PLAYS ' + motion.split('/').slice(-2).join('/').toUpperCase(),
        style: {
            fontSize: 8
        },
        tint: '#ffd34d',
        transform: {
            x: 16,
            y: 36
        }
    });

    const help = createText({
        text: '',
        style: {
            fontSize: 8
        },
        tint: '#f2f6fa',
        transform: {
            x: 16,
            y: 252
        }
    });

    tour.on('countdown', () => {
        said.text = 'THE MOTION CHANGES ROOM IN 1.5 S';
    });

    // Each room goes the other way, so a push brings the next room in from the side it is on.
    const transitionOf = (): TTransition => {
        switch (state.chosen) {
            case 1: return push(0.6, way);
            case 2: return crossWipe(0.8, way);
            case 3: return crossIris(0.8, {
                x: 0.8,
                y: 0.45
            });
            case 4: return crossDissolve(1.2, clock, {
                softness: 0.04
            });
            default: return crossfade(0.8);
        }
    };

    useUpdate((delta) => {
        // Spinning, so the frozen picture of the room going out can be told from the live one.
        blocks.forEach((sprite, i) => {
            sprite.transform.rotation += delta * (1 + i * 0.2);
        });

        for (let i = 0; i < KINDS.length; i++) {
            if (keys.justPressed(String(i + 1))) {
                state.chosen = i;
            }
        }

        if (keys.justPressed('e')) {
            state.curve = (state.curve + 1) % CURVES.length;
        }

        help.text = `1-5 ${KINDS[state.chosen]}  E CURVE: ${CURVES[state.curve].label}  X GO  M MOTION`;

        if (keys.justPressed('m') && !tour.playing) {
            tour.play();
        }

        if (keys.justPressed('x')) {
            const { ease } = CURVES[state.curve];
            const transition = transitionOf();

            scene.change(next, {
                transition: ease ? {
                    ...transition,
                    ease
                } : transition
            });
        }
    });

    return createScene();
};
```

`settings.ts`:

```ts
import { easeInOutSine, easeOutBounce } from 'nacatamalon/math';

// Crossing scenes: transitions that go from one scene to the other with no cover in between.
// The ones that cover (fade, wipe, iris, dissolve) hide the screen, swap behind it and uncover
// it again. These show both scenes at once instead:
//
// - crossfade(0.8): halfway through you see half of each.
// - push(0.6, 'left'): the new room slides in and pushes the old one off the other side.
// - crossWipe(0.8, 'right'): a straight edge with the new room behind it, the wipe of the films.
// - crossIris(0.8, center): a circle opening on the new room.
// - crossDissolve(1.2, clock): the new room shows through in the order of a picture's greys,
//   here a clock face, so it sweeps from one room to the other like a hand.
//
// The old room is shown as its last picture, still: the blocks stop spinning in it while the
// new room's blocks already spin. The engine waits for the new room to load with the old one
// still playing, then swaps at once and spends the whole duration crossing.
//
// - Any transition moves along a curve with ease: { ...crossWipe(0.8), ease: easeOutBounce }.
// - A transition of your own crosses with cross: true. Its hook gets the new scene as color and
//   reads the old one's picture with sampleOutgoing(uv):
//     { duration: 0.8, cross: true, fragment: `fn effect(color: vec4<f32>, uv: vec2<f32>) -> vec4<f32> {
//         return mix(sampleOutgoing(uv), color, mu.progress); }` }
// - M plays a .motion file instead: public/motions/cross-to-night.motion (and cross-to-day). Its
//   last event changes room the same way, written as data:
//     { "t": 1.5, "kind": "scene", "scene": "Night", "transition": { "type": "crossDissolve",
//       "duration": 1.4, "mask": "../assets/masks/clock.png", "softness": 0.04, "ease": "easeInOutSine" } }
//
// 1-5 choose the transition, E changes the curve, X goes to the other room, M plays the motion.

export const WIDTH = 480;
export const HEIGHT = 270;
export const KINDS = ['CROSSFADE', 'PUSH', 'WIPE', 'IRIS', 'CLOCK'];
export const CURVES = [
    {
        label: 'STRAIGHT',
        ease: undefined
    },
    {
        label: 'SINE',
        ease: easeInOutSine
    },
    {
        label: 'BOUNCE',
        ease: easeOutBounce
    },
];
```
