# Placing things by hand

> Let people set things out themselves (furnish a room, arrange a desk, set up a beach) with Placement and Placeable. Tap to pick up, drag, twist to turn, snap to a grid and to walls, stand things on tables, hang them on walls, add your own rules, and keep the layout as plain data with undo, redo and saving.

`<Placement>` turns its `<Placeable>` children into things people move by hand:

- **tap** a thing to pick it up (a marker appears under it);
- **drag** it along the floor;
- **twist two fingers** to turn it;
- **tap empty space** to put it down.

Drags that start elsewhere still turn the camera (with `<CameraRig orbit>`). A drag that starts on the marker moves the picked-up thing, so thin things (a towel, a frame) are easy to grab.

The layout is **plain data**: each thing has an `id`, a `position` and a `turn`, passed in as props and reported back through `onChange`. Keep it with `useLayout`, which adds undo, redo and saving between runs.

## A room to furnish

A complete screen: a sofa, a coffee table with a lamp on it, a rug, and a frame on the wall. Undo and redo sit under the room.

```tsx title="FurnishRoom.tsx"
import {
  Box,
  CameraRig,
  Cone,
  Cylinder,
  Lamp,
  type LayoutItem,
  Merged,
  type PlaceOn,
  Placeable,
  Placement,
  Scene,
  useLayout,
} from "@apricot/react";
import type { ReactNode } from "react";
import { Button, StyleSheet, View } from "react-native";

type Kind = "sofa" | "table" | "lamp" | "rug" | "frame";

interface Piece extends LayoutItem {
  kind: Kind;
}

// Each piece stands on its origin and faces +z. Hung things have their back at z = 0.
const KINDS: Record<Kind, { on?: PlaceOn; surface?: boolean; solid?: boolean; make: () => ReactNode }> = {
  sofa: {
    make: () => (
      <Merged>
        <Box size={[1.6, 0.4, 0.8]} position={[0, 0.2, 0]} color="#ee9c9b" />
        <Box size={[1.6, 0.5, 0.2]} position={[0, 0.65, -0.3]} color="#e48a8a" />
      </Merged>
    ),
  },
  table: {
    surface: true, // things with on="surface" can stand on it
    make: () => (
      <Merged>
        <Box size={[0.9, 0.05, 0.5]} position={[0, 0.425, 0]} color="#e5c595" />
        <Cylinder radius={0.03} height={0.4} position={[-0.38, 0.2, -0.18]} color="#8d5c3d" />
        <Cylinder radius={0.03} height={0.4} position={[0.38, 0.2, -0.18]} color="#8d5c3d" />
        <Cylinder radius={0.03} height={0.4} position={[-0.38, 0.2, 0.18]} color="#8d5c3d" />
        <Cylinder radius={0.03} height={0.4} position={[0.38, 0.2, 0.18]} color="#8d5c3d" />
      </Merged>
    ),
  },
  lamp: {
    on: "surface", // on a table, or on the floor
    make: () => (
      <>
        <Merged>
          <Cylinder radius={0.05} height={0.25} position={[0, 0.125, 0]} color="#f2c8a1" />
          <Cone radius={0.12} height={0.14} position={[0, 0.3, 0]} color="#fff3e0" />
        </Merged>
        <Lamp position={[0, 0.28, 0]} glow={0.08} />
      </>
    ),
  },
  rug: {
    solid: false, // things can stand over it
    make: () => <Box size={[1.8, 0.01, 1.2]} position={[0, 0.005, 0]} color="#cfc3cb" />,
  },
  frame: {
    on: "wall",
    make: () => (
      <Merged>
        <Box size={[0.5, 0.4, 0.03]} position={[0, 0, 0.015]} color="#fbf7f1" />
        <Box size={[0.4, 0.3, 0.01]} position={[0, 0, 0.035]} color="#a2bbcc" />
      </Merged>
    ),
  },
};

const START: Piece[] = [
  { id: "rug", kind: "rug", position: [0, 0, 0.2], turn: 0 },
  { id: "sofa", kind: "sofa", position: [0, 0, -1.0], turn: 0 },
  { id: "table", kind: "table", position: [0, 0, 0.2], turn: 0 },
  { id: "lamp", kind: "lamp", position: [0.25, 0.45, 0.2], turn: 0 }, // y = the table's top
  { id: "frame", kind: "frame", position: [0, 1.5, -1.5], turn: 0 }, // on the back wall
];

function Room() {
  return (
    <>
      <Box size={[4, 0.1, 3]} position={[0, -0.05, 0]} color="#ebc79c" />
      <Box size={[4, 2.6, 0.1]} position={[0, 1.3, -1.55]} color="#faf1e8" />
      <Box size={[0.1, 2.6, 3]} position={[-2.05, 1.3, 0]} color="#faf1e8" />
    </>
  );
}

export function FurnishRoom() {
  const layout = useLayout(START, { save: "my-room" });
  return (
    <View style={{ flex: 1 }}>
      <Scene style={StyleSheet.absoluteFill} look="soft" shadows lamps={1}>
        <Room />
        <Placement
          bounds={[-2, -1.5, 2, 1.5]} // the walls: [minX, minZ, maxX, maxZ]
          wallHeight={2.6}
          snap={{ grid: 0.05, turn: 15, wall: 0.1 }}
          onChange={layout.place}
        >
          {layout.loaded &&
            layout.items.map((p) => {
              const kind = KINDS[p.kind];
              return (
                <Placeable
                  key={p.id}
                  id={p.id}
                  position={p.position}
                  turn={p.turn}
                  on={kind.on}
                  surface={kind.surface}
                  solid={kind.solid}
                >
                  {kind.make()}
                </Placeable>
              );
            })}
        </Placement>
        <CameraRig
          subject={null}
          view={{ azimuth: 35, elevation: 35, distance: 8, target: [0, 0.6, 0] }}
          orbit={{ turn: 40, elevation: [12, 70], zoom: [0.5, 1.4] }}
          inset={{ bottom: 100 }}
        />
      </Scene>
      <View style={{ position: "absolute", bottom: 40, left: 0, right: 0, flexDirection: "row", justifyContent: "center" }}>
        <Button title="Undo" disabled={!layout.canUndo} onPress={layout.undo} />
        <Button title="Redo" disabled={!layout.canRedo} onPress={layout.redo} />
      </View>
    </View>
  );
}
```

What this shows:

- **Where things go** is set per thing with `on`: `"floor"` (default), `"surface"` (stands on things marked `surface`, or on the floor), `"wall"` (hangs on the walls round `bounds`), `"water"` (floats, see [Ground and water](https://matthiasbrandolin.dev/apricot/guides/ground-and-water)).
- **Things on a surface move with it.** Drag the table and the lamp comes along; both new places are reported.
- **Solid things don't overlap.** A dragged sofa stops against the table and slides along it. `solid={false}` (a rug) lets things over it.
- **`snap.wall`** pulls furniture flush against a wall when it comes this close (metres).
- **`<Merged>`** draws each piece's plain shapes as one mesh: one draw call per piece. See [Performance](https://matthiasbrandolin.dev/apricot/concepts/performance#draw-calls).
- **`layout.loaded`**: with `save`, wait until the kept layout has been read before drawing the pieces, so they don't jump.

## Building pieces

Each piece is whatever you put inside its `<Placeable>`: shapes, a `<Merged>` group of shapes, or a `<Model>`.

- Build it **standing on its origin** (its lowest point at y = 0, centred on x and z) and **facing +z**.
- Build things for walls with **their back at z = 0**, facing +z (into the room).
- A piece is measured from what it draws: its footprint is its bounding box, turned with it.
- A `<Model>` works the same; use `ground` to stand it on its lowest point. It is measured once it has loaded. Until then it has no size, so nothing is checked and it is in no one's way.

```tsx title="Armchair.tsx"
import { Placeable } from "@apricot/react";
import { Model } from "@apricot/react/model";

export const Armchair = () => (
  <Placeable id="armchair" position={[0.75, 0, -0.35]} turn={330}>
    <Model src={require("./assets/armchair.glb")} ground />
  </Placeable>
);
```

## Positions and turns

| Field | Meaning |
| --- | --- |
| `position` | `[x, y, z]` in metres. For floor things, `y` is the floor's level (0). For things on a surface, `y` is the surface's top. For hung things, `[x or z along the wall, height, the wall's x or z]` |
| `turn` | Degrees round the vertical axis. On a wall, set by the wall: `0` on the back wall (min z), `90` on the min-x wall, `180` on the front wall (max z), `270` on the max-x wall |

The walls are the four sides of `bounds`, `wallHeight` tall. A thing can be dragged onto any wall the camera sees from inside the room.

## Snapping

`snap={{ grid, turn, wall }}`: positions snap to a `grid` (metres), turns to steps of `turn` (degrees), and furniture moves flush to a wall within `wall` metres. All default to 0 (free).

## When things meet: `collide`

| Mode | A moved thing that meets another |
| --- | --- |
| `"block"` (default) | Stops against it and slides along it |
| `"warn"` | Passes through it, with the marker in its `blocked` colour. Let go there, it goes back to where it last fitted |
| `"off"` | Ignores it |

`"warn"` feels freer on a small phone screen; `"block"` never lets a layout go wrong.

## Your own rules: `canPlace`

`canPlace(id, place, check)` is called while a thing moves or turns. Return `false` where it may not go: with `"block"` it stays where it last fitted; otherwise the marker turns red and, let go there, it goes back.

```tsx title="NoDoorway.tsx"
import { Placement, type Place, type PlaceCheck } from "@apricot/react";
import type { ReactNode } from "react";

// Keep the doorway (x between -0.5 and 0.5, near the front wall) clear.
const inDoorway = ({ position: [x, , z] }: Place) => Math.abs(x) < 0.5 && z > 1.0;

export const RoomRules = ({ children }: { children: ReactNode }) => (
  <Placement
    bounds={[-2, -1.5, 2, 1.5]}
    collide="warn"
    canPlace={(id, place: Place, { standingOn }: PlaceCheck) => standingOn !== null || !inDoorway(place)}
  >
    {children}
  </Placement>
);
```

`check` (`PlaceCheck`) tells you what the thing would stand on (`standingOn`, an id or `null`), what it would overlap (`overlapping`), where it goes (`on`), the `area` it would stand in and the no-go `zones` it touches (see [Ground and water](https://matthiasbrandolin.dev/apricot/guides/ground-and-water)).

## A panel for the picked-up thing

Control the selection to show your own UI for it: `selected` and `onSelect`. `onFitChange` says whether it fits where it is, to show a hint. The `ref` (`PlacementHandle`) turns things by code and lists what stands on them.

```tsx title="PiecePanel.tsx"
import { Box, CameraRig, type LayoutItem, Placeable, Placement, type PlacementHandle, Scene, useLayout } from "@apricot/react";
import { useRef, useState } from "react";
import { Button, StyleSheet, Text, View } from "react-native";

const START: LayoutItem[] = [
  { id: "crate-1", position: [-0.6, 0, 0], turn: 0 },
  { id: "crate-2", position: [0.6, 0, 0], turn: 0 },
];

export function PiecePanel() {
  const layout = useLayout(START);
  const placement = useRef<PlacementHandle>(null);
  const [selected, setSelected] = useState<string | null>(null);
  const [fits, setFits] = useState(true);
  // Undone away: nothing picked up.
  if (selected !== null && !layout.items.some((p) => p.id === selected)) setSelected(null);

  return (
    <View style={{ flex: 1 }}>
      <Scene style={StyleSheet.absoluteFill} look="soft" shadows>
        <Box size={[4, 0.1, 3]} position={[0, -0.05, 0]} color="#ebc79c" />
        <Placement
          ref={placement}
          bounds={[-2, -1.5, 2, 1.5]}
          collide="warn"
          selected={selected}
          onSelect={(id) => {
            setSelected(id);
            setFits(true);
          }}
          onChange={layout.place}
          onFitChange={(_, now) => setFits(now)}
          marker={{ color: "#3ccf6e", blocked: "#ff5a4f", radius: 0.08 }}
        >
          {layout.items.map((p) => (
            <Placeable key={p.id} id={p.id} position={p.position} turn={p.turn}>
              <Box size={[0.6, 0.5, 0.4]} position={[0, 0.25, 0]} color="#c98b5a" />
            </Placeable>
          ))}
        </Placement>
        <CameraRig subject={null} view={{ azimuth: 30, elevation: 40, distance: 7 }} orbit={{ turn: 45 }} />
      </Scene>
      {selected ? (
        <View style={{ position: "absolute", bottom: 40, left: 16, right: 16, gap: 8 }}>
          <Text>{fits ? "Drag to move, twist to turn" : "Doesn't fit here: let go and it goes back"}</Text>
          <View style={{ flexDirection: "row", flexWrap: "wrap" }}>
            <Button title="Turn left" onPress={() => placement.current?.turnBy(selected, 45)} />
            <Button title="Turn right" onPress={() => placement.current?.turnBy(selected, -45)} />
            <Button
              title="Remove"
              onPress={() => {
                // What stands on it goes too, in one undo step.
                layout.remove([selected, ...(placement.current?.standingOn(selected) ?? [])]);
                setSelected(null);
              }}
            />
            <Button title="Undo" disabled={!layout.canUndo} onPress={layout.undo} />
            <Button title="Done" onPress={() => setSelected(null)} />
          </View>
        </View>
      ) : null}
    </View>
  );
}
```

`turnBy(id, degrees)` turns with the same checks as a twist (snapped to `snap.turn`) and returns `false` if it could not. Things hung on walls don't turn.

## The marker

`marker` styles the patch under the picked-up thing (behind it, on a wall): a colour, `false` for none, or `{ color, blocked, opacity, margin, radius, outline }`. A large `radius` makes a pill or a disc; `outline` draws only a ring of that width.

## Keeping the layout: `useLayout`

`useLayout(initial, options)` holds a list of `LayoutItem`s (`{ id, position, turn }` plus your own fields) with undo and redo.

```tsx
const layout = useLayout(START, { save: "living-room", version: 1 });
<Placement onChange={layout.place}>…</Placement>
```

| Returns | Use |
| --- | --- |
| `items` | The layout now. Draw a `<Placeable>` per item |
| `place` | Pass to `<Placement onChange>`. The moves of one gesture (a desk with what stands on it) are one undo step |
| `add(item)`, `update(id, change)`, `remove(ids)`, `set(items)` | Change the layout; each is one step. `remove` takes one id or several |
| `undo()`, `redo()`, `canUndo`, `canRedo` | History, up to `history` steps (default 100) |
| `newId(prefix)` | A free id: `chair-1`, `chair-2`… |
| `loaded` | With `save`: true once the kept layout has been read. Draw items from then on |

With `save`, the layout is written a moment after each change and read back at the next launch:

- on a phone, as a JSON file in the app's documents (needs `expo-file-system`);
- on the web, in local storage;
- anywhere else you like, with `storage: { read(name), write(name, text) }`.

Raise `version` when your items change shape: a layout kept by another version is ignored and the screen starts from `initial`.

Adding a thing and picking it up at once:

```tsx
const add = (kind: Kind) => {
  const id = layout.newId(kind);
  layout.add({ id, kind, position: [0.3, 0, 0.5], turn: 0 });
  setSelected(id);
};
```

## Performance

- Wrap the room and its `<Placement>` in a `<Bundle>`: still things are recorded once and replayed, which halves the CPU time per frame on Android. Dragged things inside it still move.
- Use `<Merged>` for pieces made of many shapes.
- While a thing moves it casts no shadow, so the shadow map is redrawn when it is picked up and put down, not every frame.
- Draw each piece in a `memo` component, so moving one does not re-render the others.

See [Performance](https://matthiasbrandolin.dev/apricot/concepts/performance) and the [room builder recipe](https://matthiasbrandolin.dev/apricot/recipes/room-builder).

## Related

- [Placement reference](https://matthiasbrandolin.dev/apricot/reference/placement).
- [Ground and water](https://matthiasbrandolin.dev/apricot/guides/ground-and-water): areas, no-go zones, snap points and floating things.
- [Light a room](https://matthiasbrandolin.dev/apricot/guides/rooms): daylight through a window, lamps that come on at dusk.
