# Shapes and materials

> Draw boxes, spheres, cylinders and other shapes, dress them in one of 16 physical material presets, animate colours, add images and engraving, and define presets of your own.

## Shapes

All shapes are centred on their `position` and accept `material`, `color`, `onPress`, `visible`, `name`, `castShadow`, `receiveShadow`, `ref` and children.

```tsx title="Shapes.tsx"
import { Box, Capsule, Cone, Cylinder, Plane, Scene, Sphere, Torus } from "@apricot/react";

export function Shapes() {
  return (
    <Scene style={{ flex: 1 }} camera={{ position: [0, 2, 9] }}>
      <Box size={[1, 1, 1]} radius={0.12} position={[-3, 0, 0]} />
      <Sphere radius={0.6} position={[-1.5, 0, 0]} />
      <Cylinder radius={0.5} height={1.2} position={[0, 0, 0]} />
      <Cone radius={0.6} height={1.2} position={[1.5, 0, 0]} />
      <Torus radius={0.5} tube={0.15} position={[3, 0, 0]} />
      <Capsule radius={0.3} length={0.6} position={[0, 0, -2]} />
      <Plane size={12} rotation={[-Math.PI / 2, 0, 0]} position={[0, -0.8, 0]} material="matte" />
    </Scene>
  );
}
```

| Shape | Size props (defaults) | Orientation |
| --- | --- | --- |
| `<Box>` | `size` (1, or `[w, h, d]`), `radius` (0: rounded edges when > 0) | Centred |
| `<Sphere>` | `radius` (0.5), `segments` (32) | Centred |
| `<Cylinder>` | `radius` (0.5), `radiusTop` (= radius), `height` (1), `segments` (32) | Along y |
| `<Cone>` | `radius` (0.5), `height` (1), `segments` (32) | Points up (+y) |
| `<Plane>` | `size` (1, or `[w, h]`) | Faces +z. Rotate `[-Math.PI / 2, 0, 0]` for a floor |
| `<Torus>` | `radius` (0.5), `tube` (0.15), `segments` (48) | Ring in the x–y plane |
| `<Capsule>` | `radius` (0.3), `length` (0.6, the straight middle), `segments` (16) | Along y |
| `<Mesh>` | `geometry`: any three.js `BufferGeometry` you made | As built |

Changing a size prop rebuilds the shape's geometry; it is cheap but not free, so do not animate sizes through props every frame. Animate `scale` instead (it accepts a shared value).

## Materials

`material` takes one of three forms:

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

export function MaterialForms() {
  return (
    <Scene style={{ flex: 1 }} look="none" environment="studio">
      {/* 1. A preset name */}
      <Sphere material="chrome" position={[-1.2, 0, 0]} />
      {/* 2. A preset with options */}
      <Sphere material={{ preset: "gold", roughness: 0.35 }} position={[0, 0, 0]} />
      {/* 3. The color shortcut, on top of any preset */}
      <Box material="car-paint" color="#1f4fd1" position={[1.2, 0, 0]} />
    </Scene>
  );
}
```

The third form, a three.js `Material` object you created, is also accepted (and is yours to dispose). Without `material`, shapes wear `"standard"`, a light, slightly glossy grey.

### Presets

| Preset | Looks like | Colour | Roughness | Metalness | Notes |
| --- | --- | --- | --- | --- | --- |
| `standard` | Light grey plastic (the default) | `#cfd3d8` | 0.5 | 0 | |
| `matte` | Flat dark paint | `#2b2d32` | 0.7 | 0 | |
| `clay` | Unglazed clay, a sculpting look | `#d9d4cc` | 0.9 | 0 | |
| `plastic` | Dark plastic | `#2a2c30` | 0.5 | 0 | |
| `rubber` | Tyres, grips | `#1c1d1f` | 0.9 | 0 | |
| `paint` | Glossy lacquered paint | `#1d3f8f` | 0.45 | 0 | Clear coat |
| `gloss-black` | Piano black | `#0b0c0e` | 0.1 | 0 | Clear coat |
| `car-paint` | Metallic car paint | `#a8102a` | 0.32 | 0.55 | Clear coat and flakes |
| `chrome` | Mirror metal | `#eceef1` | 0.04 | 1 | |
| `aluminium` | Bright metal | `#d5d8dc` | 0.2 | 1 | |
| `brushed-metal` | Brushed steel | `#c9cdd3` | 0.3 | 1 | Streaks along the shape's U direction |
| `gold` | Gold | `#e9bd63` | 0.18 | 1 | |
| `rose-gold` | Rose gold | `#eab09a` | 0.2 | 1 | |
| `glass` | Clear glass | `#f4f8fb` | 0.04 | 0 | Transmission |
| `tinted-glass` | Dark car glass | `#ffffff` | 0.04 | 0 | Transmission with a dark tint |
| `light` | Glows by itself (lamps, screens, LEDs) | `#ffffff` | 0.3 | 0 | Emissive in its own colour |

> [!TIP]
> Metals and gloss reflect their surroundings. Under the default `look="soft"` they have little to reflect. For product scenes use `look="none"` with `environment="studio"` (or `"soft"`, `"sunset"`, `"night"`). See [Lighting](https://matthiasbrandolin.dev/apricot/guides/lighting).

> [!NOTE]
> Glass shows, tints and bends what is behind it. Over a plain background colour it looks like almost nothing. Put something behind it, such as `<Backdrop>` from `@apricot/assets` or other objects.

### Options

A preset with options: `{ preset, color, roughness, metalness, map, uv, engrave, side, vertexColors, physical, transition }`.

| Option | Type | What it does |
| --- | --- | --- |
| `preset` | string | The preset to start from (required). |
| `color` | colour | Base colour. **Changes animate.** |
| `roughness` | 0–1 | 0 is a mirror, 1 is fully diffuse. **Changes animate.** |
| `metalness` | 0–1 | 1 is metal. **Changes animate.** |
| `transition` | seconds | How long colour, roughness and metalness changes take (default 0.4). |
| `map` | texture | A colour image, multiplied by `color`. Load it with `useTexture`. |
| `engrave` | `{ mask, color?, roughness?, metalness? }` | Engraving or printing from a mask image (opaque = engraved). |
| `side` | `"front"`, `"back"`, `"double"` | Which faces show; `"double"` for thin things seen from both sides. |
| `vertexColors` | boolean | Multiply by the geometry's own per-vertex colours. |
| `physical` | object | Any other three.js `MeshPhysicalNodeMaterial` property, for full control. |

## Animating a colour

Changing `color`, `roughness` or `metalness` animates smoothly without rebuilding the shader:

```tsx title="Repaint.tsx"
import { Box, Scene } from "@apricot/react";
import { useState } from "react";
import { Button, View } from "react-native";

const COLORS = ["#a8102a", "#1c4f9c", "#e9eaec", "#111214"];

export function Repaint() {
  const [index, setIndex] = useState(0);
  return (
    <View style={{ flex: 1 }}>
      <Scene style={{ flex: 1 }} look="none" environment="studio">
        <Box size={1.5} radius={0.2} material={{ preset: "car-paint", color: COLORS[index], transition: 0.6 }} />
      </Scene>
      <Button title="Next colour" onPress={() => setIndex((i) => (i + 1) % COLORS.length)} />
    </View>
  );
}
```

Changing anything else (the preset, `map`, `engrave`, `side`, `physical`) builds a new material, which costs a shader compile.

## Images on a shape

Load a PNG or JPEG with `useTexture` (from `@apricot/react/model`) and pass it as `map`. It returns `null` while loading.

```tsx title="Poster.tsx"
import { Plane, Scene } from "@apricot/react";
import { useTexture } from "@apricot/react/model";

export function Poster() {
  const image = useTexture(require("./poster.jpg"));
  return (
    <Scene style={{ flex: 1 }} look="flat">
      <Plane size={[3, 4]} material={{ preset: "standard", color: "#ffffff", map: image }} />
    </Scene>
  );
}
```

See [Textures](https://matthiasbrandolin.dev/apricot/guides/textures) for options.

## Presets of your own

Register a preset once (at module level), then use its name anywhere a preset is accepted:

```tsx title="brand.ts"
import { definePreset } from "@apricot/react";

definePreset("brand-orange", {
  color: "#ff9f59",
  roughness: 0.35,
  metalness: 0,
  clearcoat: 1,
  clearcoatRoughness: 0.05,
});
```

```tsx title="UseBrand.tsx"
import { Sphere } from "@apricot/react";
import "./brand";

export const Ball = () => <Sphere material="brand-orange" />;
```

A preset spec has `color`, `roughness`, `metalness` (required), and optionally `clearcoat`, `clearcoatRoughness`, `brushed`, `flakes`, `transmission`, `ior`, `thickness`, `attenuationColor`, `attenuationDistance`, `emissive` (a colour, or `"color"` to glow in its own colour) and `emissiveIntensity`. An unknown preset name throws an error that lists the known ones.

## Materials for your own meshes

`useMaterial(preset, options)` returns a shared preset material for a `<Mesh>` or for three.js code. Components asking for the same preset and options share one material.

```tsx title="Custom.tsx"
import { Mesh, useMaterial } from "@apricot/react";
import { useEffect, useMemo } from "react";
import { TorusKnotGeometry } from "three/webgpu";

export function Knot() {
  const geometry = useMemo(() => new TorusKnotGeometry(0.6, 0.2, 128, 16), []);
  useEffect(() => () => geometry.dispose(), [geometry]);
  const material = useMaterial("rose-gold");
  return <Mesh geometry={geometry} material={material} />;
}
```

`createMaterial(preset, options)` makes a standalone material (not shared, not animated) for code outside React.

## Related

- [Materials reference](https://matthiasbrandolin.dev/apricot/reference/materials).
- [Lighting, environments and shadows](https://matthiasbrandolin.dev/apricot/guides/lighting).
- [Objects reference](https://matthiasbrandolin.dev/apricot/reference/objects).
