# Loading models

> Show a .glb model, prepare it with the apricot-models CLI, name its parts once with a model kit, then repaint parts, open doors and lids, play Blender animations and shape keys, and react to taps on parts.

`<Model>` shows a `.glb` file and lets you change it without re-exporting: paint any part with a material preset, turn or slide moving parts, play its animations, and know which part was tapped.

It lives in a separate entry point, because three's glTF loader adds about 146 KB that apps without models should not carry:

```tsx title="FirstModel.tsx"
import { CameraRig, Scene } from "@apricot/react";
import { Model } from "@apricot/react/model";

export function FirstModel() {
  return (
    <Scene style={{ flex: 1 }} look="none" environment="studio" background="#0e0f12">
      <Model src={require("./assets/models/chair.glb")} ground>
        <CameraRig view="three-quarter" orbit />
      </Model>
    </Scene>
  );
}
```

- `src` is a bundled asset (`require("./x.glb")`) or a URL string.
- `ground` stands the model on the floor (its lowest point at y = 0), wherever its origin was put in Blender.
- The model loads asynchronously. Until it has loaded, `<Model>` renders nothing, **including its children**: a `<CameraRig>` inside it starts once the model appears. Use `onLoad` and `onError` to react.
- A model used by several components loads once and is shared, then freed when its last user unmounts.

## 1. Prepare the file

Ship `.glb` files (not `.gltf` with separate files), optimised with the `apricot-models` CLI from `@apricot/metro`:

```sh
npx apricot-models ./models -o ./assets/models
```

It removes duplicates and unused data, welds and **quantizes** meshes (smaller files with no runtime decoder), and decodes Draco and meshopt input on your computer. Hermes on the phone has no WebAssembly, so Draco, meshopt and KTX2/Basis cannot be decoded there; KTX2 textures are rejected with a clear message. It warns when a model is heavy for a phone: more than 150,000 triangles, more than 8 MB of textures, or textures larger than 2048 px.

Then list what the model contains:

```sh
npx apricot-models inspect ./assets/models/car.glb
```

`inspect` prints the part (node) names and material names: the names the selectors below match. Add `--json` for machine-readable output. In the app, `<Model inspect>` does the same on device: tap a part to highlight it and see its name and material.

Make the model face **+z** (its front towards the default camera) with y up, in metres.

## 2. Select parts

Models arrive named by whoever made them (`ruota_ant_sx`, `Object_123`, `car_paint.003`). Selectors pick parts by those names:

| Selector | Picks |
| --- | --- |
| `"wheel_*"` | Parts **or** materials whose name matches. `*` matches any characters, `?` one character. Case-insensitive, any language. |
| `"node:Lid"` | Parts (nodes) with that name, and everything under them |
| `"material:car_paint*"` | Only the material slots with that name (the name in the file, even after a preset replaced it) |
| `"tag:door"` | Parts tagged in Blender with the custom property `apricot` = `door` |
| `["glass", "window_*"]` | Any of several selectors |

## 3. Name parts once with a model kit

A **kit** gives parts names in your words, once. App code then uses those names, and swapping the model means changing only the kit. Kit names autocomplete in `<Model kit materials joints>`.

```tsx title="sedan.ts"
import { defineModelKit } from "@apricot/react/model";

export const sedan = defineModelKit(require("./assets/models/sedan.glb"), {
  paint: "material:vernice",
  glass: "material:vetro",
  rims: "material:cerchio",
  tyres: "material:gomma",
  lights: "material:luce",
  doorFrontLeft: { select: "porta_ant_sx", hinge: "front", axis: "y", range: [0, -65] },
  doorFrontRight: { select: "porta_ant_dx", hinge: "front", axis: "y", range: [0, 65] },
  hood: { select: "cofano", hinge: "back", axis: "x", range: [0, -40] },
  trunk: { select: "bagagliaio", hinge: "front", axis: "x", range: [0, 50] },
});
```

A kit part is either a **selector** (a name for something to paint) or a **joint** (a moving part, described below).

## 4. Paint parts

`materials` maps a kit name or a selector to a material: a preset name, a preset with options, or a three.js material. Colour changes animate.

```tsx title="Showroom.tsx"
import { StudioFloor } from "@apricot/assets";
import { CameraRig, Scene } from "@apricot/react";
import { Model } from "@apricot/react/model";
import { sedan } from "./sedan";

export function Showroom({ color }: { color: string }) {
  return (
    <Scene style={{ flex: 1 }} look="none" environment="studio" background="#0e0f12" camera={{ fov: 32 }}>
      <StudioFloor radius={8} />
      <Model
        kit={sedan}
        ground
        materials={{
          paint: { preset: "car-paint", color, transition: 0.6 },
          glass: "tinted-glass",
          rims: "chrome",
          tyres: "rubber",
          lights: "light",
        }}
      >
        <CameraRig view="three-quarter" orbit={{ elevation: [2, 70], zoom: [0.6, 1.8] }} />
      </Model>
    </Scene>
  );
}
```

Without a kit, use selectors directly: `materials={{ "material:car_paint*": "car-paint", "glass*": "tinted-glass" }}`. A key that matches nothing logs a warning. Unmounting the model (or removing a key) puts the file's original materials back.

## 5. Moving parts: joints

A joint turns a part around a hinge, or slides it along a line, driven by a value from **0 (rest, closed) to 1 (open)**. The hinge is found from the part's bounds at rest, so you describe it in words.

| Joint field | Meaning (default) |
| --- | --- |
| `select` | The part(s) that move together (in a kit) |
| `hinge` | Where it turns: an edge or corner of the part's bounds, by words: `front`, `back`, `top`, `bottom`, `left`, `right`, `center`, combined as `"bottom-back"`. Or an exact point `[x, y, z]` in the part's parent space (`"center"`) |
| `axis` | `"x"`, `"y"`, `"z"` or a vector, in the parent's space (`"y"`) |
| `range` | Degrees at 0 and at 1 (`[0, 90]`) |
| `slide` | Slide instead of turn: distance along the axis at 0 and at 1 |
| `transition` | Seconds a plain value change takes (0.6) |

The model faces +z: `front` is +z, `top` is +y, `left` is +x. A door hinged at its front edge turns around y; a hood hinged at its back edge turns around x.

Drive joints with `joints`, by kit name:

```tsx title="Doors.tsx"
import { Scene } from "@apricot/react";
import { Model } from "@apricot/react/model";
import { useState } from "react";
import { sedan } from "./sedan";

export function Doors() {
  const [open, setOpen] = useState<Record<string, boolean>>({});
  return (
    <Scene style={{ flex: 1 }} look="studio">
      <Model
        kit={sedan}
        ground
        joints={{
          doorFrontLeft: open.doorFrontLeft ? 1 : 0,
          doorFrontRight: open.doorFrontRight ? 1 : 0,
          hood: open.hood ? 1 : 0,
        }}
        onPartPress={({ part }) => part && setOpen((o) => ({ ...o, [part]: !o[part] }))}
      />
    </Scene>
  );
}
```

- A **plain number** animates to the new value over `transition`.
- A **shared value** is followed exactly, frame by frame: drive it with a gesture or `withSpring`.
- Without a kit, describe the joint inline: `joints={{ "node:Lid": { value: lid, hinge: "bottom-back", axis: "x", range: [0, -110] } }}`.

A lid that follows the finger and springs shut:

```tsx title="Laptop.tsx"
import { CameraRig, Scene } from "@apricot/react";
import { defineModelKit, Model } from "@apricot/react/model";
import { Gesture, GestureDetector } from "react-native-gesture-handler";
import { cancelAnimation, useSharedValue, withSpring } from "react-native-reanimated";

const laptop = defineModelKit(require("./assets/models/laptop.glb"), {
  lid: { select: "lid", hinge: "bottom-back", axis: "x", range: [0, -110] },
});

export function Laptop() {
  const open = useSharedValue(0);
  const start = useSharedValue(0);
  const drag = Gesture.Pan()
    .onBegin(() => {
      cancelAnimation(open);
      start.value = open.value;
    })
    .onUpdate((e) => {
      open.value = Math.min(1, Math.max(0, start.value - e.translationY / 260));
    })
    .onFinalize(() => {
      open.value = withSpring(0, { damping: 16, stiffness: 140, overshootClamping: true });
    });

  return (
    <GestureDetector gesture={drag}>
      <Scene style={{ flex: 1 }} look="none" environment="studio" background="#0e0f12">
        <Model kit={laptop} joints={{ lid: open }}>
          <CameraRig view={{ azimuth: 20, elevation: 22, distance: 7.5, target: [0, 0.7, 0] }} subject={null} />
        </Model>
      </Scene>
    </GestureDetector>
  );
}
```

## 6. Animations made in Blender

`animations` drives the file's animation clips by name:

| Value | Effect |
| --- | --- |
| A number 0–1 | Shows that point of the clip. Plain numbers animate there; shared values scrub exactly |
| `"once"` | Plays it once |
| `"loop"` | Plays it on repeat |

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

export const Opening = ({ open }: { open: boolean }) => (
  <Model src={require("./assets/models/box.glb")} animations={{ LidOpen: open ? 1 : 0 }} />
);
export const Idle = () => <Model src={require("./assets/models/robot.glb")} animations={{ Idle: "loop" }} />;
```

## 7. Shape keys

`morphs` sets shape keys (morph targets) by name, from 0 (off) to 1 (full): a smile, a squash, a lid closing. Plain numbers animate; shared values follow exactly.

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

export const Face = ({ happy }: { happy: boolean }) => (
  <Model src={require("./assets/models/face.glb")} morphs={{ Smile: happy ? 1 : 0 }} />
);
```

Copies of rigged (skinned) models get their own skeleton, so each copy animates on its own.

## Taps on parts

`onPartPress` receives a `PartPressEvent`:

| Field | Meaning |
| --- | --- |
| `part` | The kit name of the tapped part: a joint if one includes it, else the most specific kit name. `undefined` without a kit, or for parts the kit does not name |
| `parts` | Every kit name whose selector includes the tapped mesh |
| `name` | The tapped mesh's name in the file |
| `material` | The tapped material's name in the file (what `material:` selectors match) |
| `wearing` | The preset it wears now, if replaced |
| `mesh`, `point` | The three.js mesh and the world-space point hit |

## Placing, moving and following a model

- `position`, `rotation` and `scale` work as on any object, and accept shared values.
- `ref` gives a `SceneObject` for `<FollowCamera target>`, `<Label attach>` or `<CameraRig follow>`.
- Inside `<Vehicle>` a model drives like a toy car. Name its wheel nodes `wheel_fl`, `wheel_fr`, `wheel_rl`, `wheel_rr` (see [Vehicles](https://matthiasbrandolin.dev/apricot/guides/vehicles)).
- With `shadows` on, every mesh of the model casts and receives; override with `castShadow` / `receiveShadow`.

## Many copies of a model

`useModel(src)` loads a model and returns `{ object, animations, error }` (`object` is `null` while loading). Pass `object` to `<Instances>` to draw hundreds of copies in one call each:

```tsx title="Forest.tsx"
import { Instances, type InstanceItem } from "@apricot/react";
import { useModel } from "@apricot/react/model";

export function Forest({ trees }: { trees: InstanceItem[] }) {
  const { object } = useModel(require("./assets/models/pine.glb"));
  return <Instances object={object} items={trees} />;
}
```

## Related

- [Model reference](https://matthiasbrandolin.dev/apricot/reference/model).
- [Tooling](https://matthiasbrandolin.dev/apricot/reference/tooling): `apricot-models`.
- [Camera](https://matthiasbrandolin.dev/apricot/guides/camera).
