# 2D joints

> The joints of the 2D world: a pendulum and a chain on hinges, a lamp on a rope, a weight on a spring, a lift on a slider and a windmill welded and turned by a motor.

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

`useJoint2d` ties two bodies together, or a body to a point of the world: `'hinge'` for the pendulum and the chain, `'rope'` for the lamp, `'spring'` for the weight, `'slider'` for the lift that keeps upright with a spring under it, and `'weld'` for the windmill's blades, turned by a hinge's motor.

Every point is a point of the world, where the nail goes. Click to drop crates on them.

## Code

`index.ts`:

```ts
import { createGame } from 'nacatamalon';
import { installPhysics2d } from 'nacatamalon/physics2d';

import * as Settings from './settings';
import { Lab } from './scenes/demo';

installPhysics2d();

const game = createGame('#app', {
    width: Settings.WIDTH,
    height: Settings.HEIGHT,
    background: '#1d2030',
    step: 60
});

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

`components/bob.ts`:

```ts
import { createSprite, usePhysicsBody2d } from 'nacatamalon';

import type { TSpriteAtlas } from 'nacatamalon';

export const Bob = (tiles: TSpriteAtlas, x: number, y: number) => {
    createSprite({
        atlas: tiles,
        frame: 15,
        transform: {
            x,
            y
        }
    });
    usePhysicsBody2d({
        body: 'dynamic',
        collider: {
            shape: 'circle',
            radius: 7
        },
        density: 3
    });
};
```

`components/box.ts`:

```ts
import { createSprite, usePhysicsBody2d } from 'nacatamalon';

import { tint } from '../helpers/tint';

export const Box = (x: number, y: number, width: number, height: number, color: string, density = 1) => {
    createSprite({
        width,
        height,
        tint: tint(color),
        transform: {
            x,
            y
        }
    });
    usePhysicsBody2d({
        body: 'dynamic',
        collider: {
            shape: 'rect',
            width,
            height
        },
        density,
        friction: 0.7
    });
};
```

`components/crate.ts`:

```ts
import { createSprite, usePhysicsBody2d } from 'nacatamalon';

import type { TSpriteAtlas } from 'nacatamalon';

export const Crate = (tiles: TSpriteAtlas, x: number, y: number) => {
    createSprite({
        atlas: tiles,
        frame: 11,
        transform: {
            x,
            y
        }
    });
    usePhysicsBody2d({
        body: 'dynamic',
        collider: {
            shape: 'rect',
            width: 16,
            height: 16
        },
        density: 0.5,
        friction: 0.8
    });
};
```

`components/nail.ts`:

```ts
import { createSprite } from 'nacatamalon';

import { tint } from '../helpers/tint';

export const Nail = (x: number, y: number) => {
    createSprite({
        width: 4,
        height: 4,
        tint: tint('#ffd166'),
        transform: {
            x,
            y
        },
        zIndex: 2
    });
};
```

`helpers/label.ts`:

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

import { tint } from '../helpers/tint';

export const Label = (x: number, text: string) => {
    createText({
        text,
        anchor: {
            x: 0.5,
            y: 0
        },
        transform: {
            x,
            y: 10
        },
        tint: tint('#9aa4c0')
    });
};
```

`helpers/tint.ts`:

```ts
import { getColor } from 'nacatamalon';

export const tint = (hex: string) => getColor(hex);
```

`scenes/demo.ts`:

```ts
import { createScene, createSprite, createSpriteAtlas, createText, useLoadTexture, usePhysicsBody2d, usePhysicsWorld2d, usePointer, useSpawn, useTransform } from 'nacatamalon';
import { useJoint2d } from 'nacatamalon/physics2d';
import type { TGameObject } from 'nacatamalon';
import * as Settings from '../settings';

import { tint } from '../helpers/tint';
import { Box } from '../components/box';
import { Bob } from '../components/bob';
import { Crate } from '../components/crate';
import { Nail } from '../components/nail';
import { Label } from '../helpers/label';

export const Lab = () => {
    usePhysicsWorld2d();

    const tiles = createSpriteAtlas({
        texture: useLoadTexture({
            src: '/example-assets/assets/platformer/tiles.png'
        }),
        columns: 8,
        rows: 3
    });

    const box = useSpawn(Box);
    const nail = useSpawn(Nail);

    // The floor and two walls, so nothing leaves.
    useSpawn(function Ground() {
        useTransform({
            x: Settings.WIDTH / 2,
            y: Settings.FLOOR + 10
        });
        createSprite({
            width: Settings.WIDTH,
            height: 20,
            tint: tint('#3a3f58'),
            transform: {
                x: 0,
                y: 0
            }
        });
        usePhysicsBody2d({
            body: 'static',
            collider: {
                shape: 'rect',
                width: Settings.WIDTH,
                height: 20
            }
        });
    })();

    // A pendulum: a weight on a hinge, let go from the side.
    Label(55, 'HINGE');

    const bob = useSpawn(Bob)(tiles, 100, 40);

    useJoint2d(bob, null, {
        type: 'hinge',
        at: {
            x: 55,
            y: 40
        }
    });
    nail(55, 40);

    // A chain: eight links, each hinged to the next, hanging from a nail.
    Label(130, 'CHAIN');

    let above: TGameObject | null = null;

    for (let i = 0; i < 8; i++) {
        const link = box(130, 44 + i * 12, 4, 12, i % 2 === 0 ? '#c0c4d8' : '#8a8ea8');

        useJoint2d(link, above, {
            type: 'hinge',
            at: {
                x: 130,
                y: 38 + i * 12
            }
        });
        above = link;
    }

    nail(130, 38);

    // A lamp on a rope: it swings, and can come up closer without the rope pushing it.
    Label(200, 'ROPE');

    const lamp = box(230, 90, 14, 10, '#ffd166', 2);

    useJoint2d(lamp, null, {
        type: 'rope',
        length: 80,
        atB: {
            x: 200,
            y: 30
        }
    });
    nail(200, 30);

    // A weight on a spring, bouncing one and a half times a second.
    Label(270, 'SPRING');

    const weight = box(270, 120, 20, 20, '#ff6b8b', 1);

    useJoint2d(weight, null, {
        type: 'spring',
        length: 60,
        frequency: 1.5,
        damping: 0.1,
        atB: {
            x: 270,
            y: 30
        }
    });
    nail(270, 30);

    // A lift: a slider keeps it upright on a vertical rail, and a spring under it makes it bounce.
    Label(340, 'SLIDER');
    createSprite({
        width: 2,
        height: 200,
        tint: tint('#3a3f58'),
        transform: {
            x: 340,
            y: 200
        }
    });

    const lift = box(340, 220, 40, 6, '#4cc9f0', 2);

    useJoint2d(lift, null, {
        type: 'slider',
        axis: {
            x: 0,
            y: 1
        },
        limits: [-100, 60]
    });
    useJoint2d(lift, null, {
        type: 'spring',
        length: 80,
        frequency: 2,
        damping: 0.3,
        atB: {
            x: 340,
            y: Settings.FLOOR
        }
    });

    // A windmill: two blades welded across a hinge with a motor.
    Label(420, 'WINDMILL');

    const blade = box(420, 200, 90, 6, '#c77dff');
    const across = box(420, 200, 6, 90, '#c77dff');

    useJoint2d(blade, across, {
        type: 'weld'
    });
    useJoint2d(blade, null, {
        type: 'hinge',
        at: {
            x: 420,
            y: 200
        },
        motor: {
            speed: 1.5,
            strength: 20
        }
    });
    nail(420, 200);

    // Click to drop a crate there.
    const spawnCrate = useSpawn(Crate);

    usePointer().onDown(({ worldX, worldY }) => {
        spawnCrate(tiles, worldX, worldY);
    });

    // Over the ground, which is a piece of its own and so is drawn after the scene's own things.
    createText({
        text: 'CLICK TO DROP A CRATE',
        anchor: {
            x: 0.5,
            y: 0
        },
        transform: {
            x: Settings.WIDTH / 2,
            y: Settings.FLOOR + 4
        },
        tint: tint('#9aa4c0'),
        zIndex: 1
    });

    return createScene();
};
```

`settings.ts`:

```ts
export const WIDTH = 480;
export const HEIGHT = 320;
export const FLOOR = 300;
```
