# Instances

> Draw hundreds or thousands of copies of a shape or model in one draw call each, give every copy its own place, colour and visibility, tell which copy was tapped, and animate copies without React renders.

Separate meshes cost about 80 µs each per frame on a mid-range phone, so a few hundred of them use up a whole frame. `<Instances>` draws every copy of a shape **in one draw call**: seats in a cinema, trees in a forest, coins, posts, products on a shelf.

```tsx title="Shelf.tsx"
import { Box, Instances, Scene } from "@apricot/react";

const ITEMS = Array.from({ length: 60 }, (_, i) => ({
  position: [(i % 12) * 0.6 - 3.3, Math.floor(i / 12) * 0.8 - 1.6, 0] as const,
  color: i % 3 === 0 ? "#ff9f59" : "#cfd3d8",
}));

export function Shelf() {
  return (
    <Scene style={{ flex: 1 }} camera={{ position: [0, 0, 8] }}>
      <Instances items={ITEMS} material="plastic">
        <Box size={0.4} radius={0.05} />
      </Instances>
    </Scene>
  );
}
```

## What to copy

Give one of these:

| Prop | Copies | Example |
| --- | --- | --- |
| children | The shapes inside, together, as one copy. Their positions place them within each copy. | A seat made of a cushion and a back: two `<Box>` children |
| `geometry` | One three.js geometry | A geometry you built |
| `object` | Every mesh of an object | A loaded model: `useModel(src).object` |

Each part is one draw call for all copies: a two-part seat is two draw calls, however many seats.

## The copies: `items`

`items` is an array. Each item describes one copy:

| Field | Type | Default |
| --- | --- | --- |
| `position` | `[x, y, z]` | `[0, 0, 0]` |
| `rotation` | `[x, y, z]` radians | `[0, 0, 0]` |
| `scale` | number or `[x, y, z]` | `1` |
| `color` | colour: tints this copy (multiplies the material's colour) | none |
| `visible` | boolean | `true` |

Your own fields may ride along (an id, a state): they come back in `onPress`. Changing `items` updates the copies; it is fine for selections and state changes, not for per-frame animation (see below).

## Materials

`material` and `color` work as on shapes. With `object` or shape children, the copies keep their own materials unless you pass `material` or `color`. When copies have their own colours, the base material is white by default so the colours show true.

## Taps: which copy

```tsx title="Cinema.tsx"
import { Box, CameraRig, type InstanceItem, Instances, Plane, Scene } from "@apricot/react";
import { useMemo, useState } from "react";

interface Seat extends InstanceItem {
  id: string;
  taken: boolean;
}

const SEATS: Seat[] = Array.from({ length: 10 * 16 }, (_, i) => {
  const row = Math.floor(i / 16);
  const col = i % 16;
  return {
    id: `${String.fromCharCode(65 + row)}${col + 1}`,
    taken: (i * 7) % 5 === 0,
    position: [(col - 7.5) * 1.1, row * 0.38, row * 1.2],
  };
});

export function Cinema() {
  const [picked, setPicked] = useState<ReadonlySet<string>>(new Set());
  const items = useMemo(
    () => SEATS.map((s) => ({ ...s, color: s.taken ? "#5b6068" : picked.has(s.id) ? "#22c55e" : "#c2410c" })),
    [picked],
  );
  const toggle = (seat: Seat) =>
    setPicked((current) => {
      const next = new Set(current);
      if (next.has(seat.id)) next.delete(seat.id);
      else next.add(seat.id);
      return next;
    });

  return (
    <Scene style={{ flex: 1 }} background="#14161b" shadows>
      <Plane size={60} rotation={[-Math.PI / 2, 0, 0]} material="matte" color="#24272e" />
      <Instances items={items} material="plastic" onPress={({ item }) => !item.taken && toggle(item)}>
        <Box size={[0.9, 0.25, 0.85]} position={[0, 0.45, 0]} />
        <Box size={[0.9, 0.9, 0.18]} position={[0, 0.85, 0.42]} />
      </Instances>
      <CameraRig subject={null} view={{ azimuth: 0, elevation: 32, distance: 30, target: [0, 2, 6] }} orbit={{ turn: 55 }} />
    </Scene>
  );
}
```

`onPress` receives `{ index, item, hit }`: the copy's index in `items`, the item itself (with your fields) and the [`PressHit`](https://matthiasbrandolin.dev/apricot/guides/interaction#shapes).

## Animating copies every frame

For motion, do not rebuild `items` every frame. Use the ref, an `InstancesHandle`, and `set(index, item)` in `useFrame`: it changes one copy with no React render.

```tsx title="Snow.tsx"
import { Instances, type InstancesHandle, Scene, Sphere, useFrame } from "@apricot/react";
import { seededRandom } from "@apricot/core";
import { useMemo, useRef } from "react";

const COUNT = 300;

function Flakes() {
  const random = useMemo(() => seededRandom(3), []);
  const flakes = useMemo(
    () => Array.from({ length: COUNT }, () => ({ x: random() * 10 - 5, y: random() * 8 - 4, z: random() * -6, speed: 0.4 + random() * 0.6 })),
    [random],
  );
  const items = useMemo(() => flakes.map((f) => ({ position: [f.x, f.y, f.z] as const, scale: 0.05 })), [flakes]);
  const copies = useRef<InstancesHandle>(null);

  useFrame(({ delta }) => {
    const handle = copies.current;
    if (!handle) return true;
    flakes.forEach((f, i) => {
      f.y -= f.speed * delta;
      if (f.y < -4) f.y += 8;
      handle.set(i, { position: [f.x, f.y, f.z], scale: 0.05 });
    });
    return true;
  });

  return (
    <Instances ref={copies} items={items} color="#ffffff">
      <Sphere radius={1} segments={8} />
    </Instances>
  );
}

export function Snow() {
  return (
    <Scene style={{ flex: 1 }} look="flat" background="#1b2333">
      <Flakes />
    </Scene>
  );
}
```

`handle.group` is the group holding the copies, if you need to move them all.

## Shadows

With `<Scene shadows>`, instanced copies cast and receive like any mesh, and all copies are drawn in the shadow pass in one call too. That is why repeated casters (pillars, trees, posts) should always be instanced. Override with `castShadow` / `receiveShadow`.

## Related

- [Instances reference](https://matthiasbrandolin.dev/apricot/reference/instances).
- [Performance](https://matthiasbrandolin.dev/apricot/concepts/performance).
- [Loading models](https://matthiasbrandolin.dev/apricot/guides/models#many-copies-of-a-model).
