# Models in a 2D scene

> Models sitting exactly where sprites are: the 3D camera measures in the game’s pixels, and models and sprites are sorted in one list.

Example `models-on-2d`, running at https://nacatamalon.com/en/examples/models-on-2d. The whole catalogue: https://nacatamalon.com/llms/examples.md.

`useCamera3d()` is orthographic by default and measures in the game's pixels, so a model can sit exactly where a sprite is. Its origin is the bottom-left corner and y grows up, so a model goes where a sprite is with `y3d = H - y2d`; and it sees as far behind the camera as in front, so models around `z = 0` are drawn.

A gem floats over a path written in 2D coordinates, with a 2D shadow under it; a crate sits on the floor at a 2D point; and a sprite pillar is drawn in front of the gem, because `zIndex` sorts models and sprites in one list.

## Code

`index.ts`:

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

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

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

`components/crate.ts`:

```ts
import { createMesh, useCubeGeometry } from 'nacatamalon';
import { FLOOR, CRATE } from '../settings';

export const Crate = () => {
    createMesh({
        geometry: useCubeGeometry(),
        tint: '#c08040',
        transform: {
            x: CRATE.x,
            y: FLOOR - CRATE.size / 2,
            scaleX: CRATE.size,
            scaleY: CRATE.size,
            scaleZ: CRATE.size,
            rotationY: 0.5
        },
        zIndex: 0
    });
};
```

`components/floating_gem.ts`:

```ts
import { createMesh, createSprite, useIcoSphereGeometry, useUpdate } from 'nacatamalon';
import { FLOOR, GEM } from '../settings';

export const FloatingGem = () => {
    const shadow = createSprite({
        width: 30,
        height: 6,
        tint: '#0a0d18',
        transform: {
            y: FLOOR + 4
        },
        zIndex: -4
    });

    const gem = createMesh({
        geometry: useIcoSphereGeometry({
            subdivisions: 1
        }),
        tint: '#5bd6a0',
        transform: {
            scaleX: GEM.size,
            scaleY: GEM.size,
            scaleZ: GEM.size
        },
        zIndex: 0
    });

    // The scene clock drives the path; no separate elapsed-time counter is needed.
    useUpdate((delta, time) => {
        const x = GEM.centreX + Math.sin(time * GEM.pathSpeed) * GEM.range;

        gem.transform.x = x;
        gem.transform.y = FLOOR - GEM.hoverHeight - Math.sin(time * GEM.bobSpeed) * GEM.bobHeight;
        gem.transform.rotationY += delta * GEM.turnSpeed;
        gem.transform.rotationX += delta * GEM.tiltSpeed;
        shadow.transform.x = x;
    });
};
```

`components/hud.ts`:

```ts
import { createText, useScreenSpace } from 'nacatamalon';

export const Hud = () => {
    useScreenSpace();

    createText({
        text: 'follow2d: SAME X AND Y',
        transform: {
            x: 8,
            y: 8
        }
    });
    createText({
        text: 'PILLAR: 2D, IN FRONT BY zIndex',
        transform: {
            x: 8,
            y: 20
        },
        tint: '#6b7390'
    });
};
```

`components/scenery.ts`:

```ts
import { createSprite } from 'nacatamalon';
import { WIDTH, HEIGHT, FLOOR, TILE } from '../settings';

export const Scenery = () => {
    createSprite({
        width: WIDTH,
        height: FLOOR,
        tint: '#1b2138',
        anchor: {
            x: 0,
            y: 0
        },
        zIndex: -10
    });

    for (let x = 0; x < WIDTH; x += TILE) {
        createSprite({
            width: TILE,
            height: HEIGHT - FLOOR,
            tint: x % (TILE * 2) === 0 ? '#2e365a' : '#283050',
            anchor: {
                x: 0,
                y: 0
            },
            transform: {
                x,
                y: FLOOR
            },
            zIndex: -5
        });
    }

    // Shared zIndex ordering puts a flat pillar in front of the moving 3D gem.
    createSprite({
        width: 22,
        height: 110,
        tint: '#4a5580',
        anchor: {
            x: 0.5,
            y: 1
        },
        transform: {
            x: 200,
            y: FLOOR
        },
        zIndex: 10
    });
};
```

`scenes/room.ts`:

```ts
import { createScene, useCamera2d, useCamera3d, useDirectionalLight, useSpawn } from 'nacatamalon';
import { Crate } from '../components/crate';
import { FloatingGem } from '../components/floating_gem';
import { Hud } from '../components/hud';
import { Scenery } from '../components/scenery';

export const Room = () => {
    useCamera2d();
    // Models keep their shape and lighting; their positions now use the sprites' Y-down world.
    useCamera3d({
        projection: 'orthographic',
        follow2d: true,
        transform: {
            z: 200
        }
    });
    useDirectionalLight({
        intensity: 1.1,
        ambient: 0.35
    });

    useSpawn(Scenery)();
    useSpawn(FloatingGem)();
    useSpawn(Crate)();
    useSpawn(Hud)();

    return createScene();
};
```

`settings.ts`:

```ts
export const WIDTH = 320;
export const HEIGHT = 180;
export const FLOOR = 140;
export const TILE = 16;

export const GEM = {
    size: 16,
    centreX: 160,
    range: 130,
    pathSpeed: 0.7,
    hoverHeight: 40,
    bobHeight: 6,
    bobSpeed: 3,
    turnSpeed: 1.5,
    tiltSpeed: 0.7
};

export const CRATE = {
    x: 90,
    size: 28
};
```
