# Objects, groups and refs

> How the scene tree works, what a SceneObject ref gives you, how groups combine transforms, and who owns and frees geometry and materials.

## The scene tree

Components inside `<Scene>` form a tree, like views. Each object is attached to its closest parent object, and its `position`, `rotation` and `scale` are **relative to that parent**.

```tsx title="Tree.tsx"
import { Box, Group, Scene, Sphere } from "@apricot/react";

export function Tree() {
  return (
    <Scene style={{ flex: 1 }}>
      {/* Moving or turning the group moves both children together. */}
      <Group position={[0, -0.5, 0]} rotation={[0, Math.PI / 6, 0]}>
        <Box size={[2, 0.2, 1]} />
        <Sphere radius={0.3} position={[0, 0.45, 0]} material="chrome" />
      </Group>
    </Scene>
  );
}
```

These components are objects and accept children: `Group`, `Mesh` and every shape, `Model`, `Primitive`, `Vehicle`, `FollowPath`, and behaviors such as `Spin` (which wrap their children in a group).

Ordinary React components can sit anywhere in the tree: an object inside them still attaches to the closest object above it.

## Refs: `SceneObject`

A `ref` on an object holds a `SceneObject`, the underlying three.js `Object3D`. Use it to read or change the object directly, or to hand it to a component that follows it.

```tsx title="Refs.tsx"
import { Box, FollowCamera, Group, Scene, type SceneObject, useFrame } from "@apricot/react";
import { useRef } from "react";

function Runner() {
  const runner = useRef<SceneObject>(null);
  useFrame(({ time }) => {
    if (!runner.current) return true;
    runner.current.position.z = -time * 2; // runs away from the camera
    return true;
  });
  return (
    <>
      <Group ref={runner}>
        <Box size={[0.5, 1, 0.5]} position={[0, 0.5, 0]} />
      </Group>
      {/* After the target, so it reads this frame's position. */}
      <FollowCamera target={runner} />
    </>
  );
}

export function Refs() {
  return (
    <Scene style={{ flex: 1 }}>
      <Runner />
    </Scene>
  );
}
```

What you can do with a `SceneObject`:

| Use | How |
| --- | --- |
| Move, turn, scale | `ref.current.position.set(x, y, z)`, `.rotation.y = a`, `.scale.setScalar(s)` |
| Hide | `ref.current.visible = false` |
| Read where it is in the world | `ref.current.getWorldPosition(vector)` |
| Follow it with the camera | `<FollowCamera target={ref}>`, `<CameraRig follow={ref}>` |
| Pin a label to it | `<Label attach={ref}>` |

> [!WARNING]
> Changing an object through its ref **does not wake the scene**. Do it inside `useFrame` (and return `true` while it moves), or call `useScene().invalidate()` afterwards. If a prop and your code both set the same value (for example `position`), the prop wins on the next frame, so set one or the other.

`<Instances>` refs are different: they hold an `InstancesHandle` with `group` and `set(index, item)`. `<CameraRig>` refs hold a `CameraRigHandle` with `flyTo` and `jumpTo`.

## Groups

`<Group>` is an empty object that transforms its children together. Use it to:

- place several shapes as one object;
- apply a second motion on top of another (a swipe `rotation` inside a `<Spin>`);
- set a pivot point: put the child off-centre inside a group, then rotate the group.

```tsx title="Pivot.tsx"
import { Box, Group, Scene, Spin } from "@apricot/react";

export function Pivot() {
  return (
    <Scene style={{ flex: 1 }}>
      <Spin speed={1} axis={[0, 0, 1]}>
        {/* The arm turns around its left end, not its centre. */}
        <Group>
          <Box size={[2, 0.2, 0.2]} position={[1, 0, 0]} />
        </Group>
      </Spin>
    </Scene>
  );
}
```

## Visibility, names and order

- `visible={false}` on a shape hides it without unmounting it, and keeps its material and shader.
- `name` sets the object's name: useful in model selectors and debugging.
- Unmounting a component removes its object and frees what it created.
- Draw order does not depend on the order in JSX: depth decides what is in front.

## Who frees what

| Thing | Created by | Freed when |
| --- | --- | --- |
| Shape geometry (`<Box size>`…) | Apricot | The shape unmounts or its size changes |
| Preset materials (`material="chrome"`) | Apricot, **shared** between identical uses | The last component using it unmounts |
| A `geometry` you pass to `<Mesh>` or `<Instances>` | You | Never by Apricot: dispose it yourself (`geometry.dispose()` in an effect cleanup) |
| A three.js `material` you pass | You | Never by Apricot |
| Models (`<Model src>`) and textures (`useTexture`) | Apricot, cached per app | The last user unmounts |
| Objects you create with `@apricot/assets` factories (`createPeach()`…) | You | Call `disposeObject(object)` from `@apricot/core` |

## Escape hatch: three.js objects

`<Primitive object={…}>` adds any existing three.js `Object3D` to the scene, with the usual `position`, `rotation`, `scale` and children. Use it for objects built by `@apricot/assets` factories or your own three.js code. Most apps never need it: see [Custom geometry and behaviors](https://matthiasbrandolin.dev/apricot/guides/custom).

## Related

- [Objects reference](https://matthiasbrandolin.dev/apricot/reference/objects): `Group`, `Primitive`, `Mesh` and shapes.
- [Coordinates and units](https://matthiasbrandolin.dev/apricot/concepts/coordinates).
