# Ground and water

> Build outdoor scenes. Shape ground of any height with Terrain or any outline with Slab, add a moving sea with Water, shallows and foam with useShore, and let people place things on the sand, on a deck or afloat in the sea with Placement areas, no-go zones and snap points.

Outdoor pieces for islands, beaches, gardens, docks and dioramas:

| Piece | Use |
| --- | --- |
| `<Slab>` | Flat ground in any shape: an island, a lawn, a deck round a pool (with holes) |
| `<Terrain>` | A tile of ground of any height, cut square at its edges like a slice of the earth: a beach running into the sea, a hill |
| `<Water>` | A water surface moved by waves: the sea, a pool, a pond |
| `useWaves` | Waves shared by the water and what floats on it |
| `useShore` | How far each point of the water is from land: pale shallows, deep sea, foam along the shore |
| `smoothOutline` | Rounds a few corner points into a smooth shore |
| `<Placement areas zones snaps>` | Where things may stand, float or snap into place |

Shapes on the ground are **outlines**: arrays of `[x, z]` corners in metres, in order round the shape (`Outline`).

## An island in the sea

```tsx title="Island.tsx"
import {
  Box,
  CameraRig,
  type LayoutItem,
  Placeable,
  Placement,
  type PlacementArea,
  Scene,
  Slab,
  smoothOutline,
  Sphere,
  useLayout,
  useShore,
  useWaves,
  Water,
} from "@apricot/react";
import { useMemo } from "react";

const SIZE = [12, 10] as const;
const EVERYWHERE = [
  [-6, -5],
  [6, -5],
  [6, 5],
  [-6, 5],
] as const;
// A few points, rounded into a shore.
const ISLAND = smoothOutline([
  [-2.4, -1],
  [0, -1.8],
  [2.2, -0.9],
  [2, 1.3],
  [-0.4, 1.8],
  [-2.6, 0.7],
]);
const SAND = 0.2; // the island's top, above the sea at 0

interface Thing extends LayoutItem {
  kind: "lounger" | "duck";
}
const START: Thing[] = [
  { id: "lounger", kind: "lounger", position: [0, SAND, 0], turn: 0 },
  { id: "duck", kind: "duck", position: [3.5, 0, 2], turn: 30 },
];

export function IslandScreen() {
  const sea = useWaves({ height: 0.03, length: 1.6, speed: 0.6 });
  const shore = useShore([ISLAND], [-6, -5, 6, 5], { reach: 2.5 });
  const layout = useLayout(START, { save: "island" });
  // Later areas lie on earlier ones: the island on the sea.
  const areas = useMemo<PlacementArea[]>(
    () => [
      { id: "sea", outline: EVERYWHERE, water: sea },
      { id: "island", outline: ISLAND, level: SAND },
    ],
    [sea],
  );

  return (
    <Scene style={{ flex: 1 }} look="soft" background="#d5ecf3" shadows>
      <Water size={SIZE} waves={sea} shore={shore} clarity={0.3} />
      <Slab outline={ISLAND} height={0.5} position={[0, SAND - 0.5, 0]} color="#f3d9a4" />
      <Placement areas={areas} collide="warn" onChange={layout.place}>
        {layout.loaded &&
          layout.items.map((t) => (
            <Placeable key={t.id} id={t.id} position={t.position} turn={t.turn} on={t.kind === "duck" ? "water" : "floor"}>
              {t.kind === "duck" ? (
                <Sphere radius={0.15} position={[0, 0.1, 0]} color="#ffd23f" />
              ) : (
                <Box size={[0.6, 0.15, 1.6]} position={[0, 0.075, 0]} color="#f58fa8" />
              )}
            </Placeable>
          ))}
      </Placement>
      <CameraRig subject={null} view={{ azimuth: 30, elevation: 40, distance: 16 }} orbit={{ turn: 60, elevation: [12, 70] }} />
    </Scene>
  );
}
```

- The **water** lies level at its position's height (0 here) and covers `size` (width along x, depth along z), centred on its position.
- The **slab** stands up from its position by `height`, so its top is at `position.y + height`: `SAND` here.
- The **lounger** stands on the island; it can't go into the sea. The **duck** has `on="water"`: it floats in the sea, bobbing and tilting on the same waves, and can't go ashore.
- With `collide="warn"`, a thing dragged over the wrong ground passes over it with a red marker and goes back if let go there. With `"block"`, it slides along the shore.

## Water

| Prop | Default | Meaning |
| --- | --- | --- |
| `size` | required | `[width, depth]` in metres |
| `waves` | still water | Waves from `useWaves`, shared with what floats |
| `shore` | none | Distance to land from `useShore`: shallows, deep water and foam by the shore |
| `shallow`, `deep`, `beach` | turquoise, azure, pale aqua | Colours over the shallows, out at sea, and where it laps the sand |
| `foam` | white | Foam colour along the shore, or `false` |
| `caustics` | `0.6` | Light dancing on the bottom of the shallows, 0–1 (with a shore) |
| `glow` | `0.35` | How much it glows with its own colour, so it stays vivid in any light |
| `clarity` | `0` | How much you see through it, 0 (opaque) to 1 |
| `sparkle` | `0.5` | A pale glint on the crests, 0–1 |
| `spacing` | `0.25` | Metres between its points: finer carries shorter waves |
| `position` | `[0, 0, 0]` | Where its middle is; it is moved, never turned |

Without `shore`, the colour goes from `shallow` looking down into it to `deep` at a slant. While the water has waves, the scene keeps rendering.

### `useWaves(options)`

| Option | Default | Meaning |
| --- | --- | --- |
| `height` | `0.06` | Crest height above the middle, metres. `0` is still water |
| `length` | `2.5` | Metres from one crest to the next |
| `speed` | `0.8` | Metres per second |
| `direction` | `0` | Degrees round the vertical: `0` travels towards +x |

The waves are made once and change live when the options change (no shader rebuild). Pass the same waves to `<Water waves>` and to a water area of `<Placement>`, so floating things ride the waves you see.

### `useShore(land, bounds, options)`

Measures, once, how far each point of `bounds` (`[minX, minZ, maxX, maxZ]`) is from land.

- `land` is an array of outlines (`[ISLAND]`), or a test `(x, z) => boolean` (true on land). Keep a test function stable (define it outside the component): a new function measures again.
- `reach` (default 4) is how far out, in metres, the water deepens from the shore to the open sea. `size` (default 128) is the field's resolution.

## Slab

| Prop | Default | Meaning |
| --- | --- | --- |
| `outline` | required | The shape on the ground |
| `height` | `0.2` | How thick, standing up from its position |
| `holes` | none | Outlines cut through it (a pool in a deck) |
| `bevel` | `0.03` | Rounding of its edges, metres |

Plus everything a shape takes: `material`, `color`, transforms, `onPress`, shadows.

`smoothOutline(points, segments = 96)` turns a few corners into a smooth closed curve of `segments` points: the easiest way to draw a natural shore.

## Terrain

`<Terrain>` is ground whose height you give as a function. It is cut square at its edges, with sides that can show layers and, where it dips under water, the sea's depth seen through the cut: a diorama tile.

```tsx title="Beach.tsx"
import { Scene, Terrain, useShore, useWaves, Water } from "@apricot/react";

// Sand that slopes from x = -4 down into the sea past x = 1. Keep these functions outside the component.
const groundAt = (x: number, z: number) => 0.25 - 0.12 * (x + 1) + 0.04 * Math.sin(z * 1.5);
const sandAt = (_x: number, _z: number, y: number) => (y > 0.04 ? "#f3d9a4" : "#d9b77e");
const layerAt = (y: number) => (y > -0.15 ? "#e2c48e" : "#a9825a");
const isLand = (x: number, z: number) => groundAt(x, z) > 0;

export function Beach() {
  const sea = useWaves({ height: 0.012, length: 1.1, speed: 0.5, direction: 20 });
  const shore = useShore(isLand, [-4, -3, 4, 3], { reach: 2.2 });
  return (
    <Scene style={{ flex: 1 }} look="soft" background="#d5ecf3" shadows camera={{ position: [0, 6, 11], fov: 35 }}>
      <Terrain size={[8, 6]} height={groundAt} color={sandAt} sides={layerAt} water={{ level: 0 }} />
      <Water size={[8, 6]} waves={sea} shore={shore} clarity={0.3} />
    </Scene>
  );
}
```

| Prop | Default | Meaning |
| --- | --- | --- |
| `size` | required | `[width, depth]` in metres, centred on its position |
| `height` | required | `(x, z) => y`: the ground's height at each point, metres |
| `color` | white | `(x, z, y) => colour`: the top's colour point by point (sand to grass by height) |
| `sides` | the top's edge colour | `(y) => colour`: layers in the cut |
| `spacing` | `0.1` | Metres between its points |
| `base` | 0.2 below its lowest point | How far down its sides reach |
| `roughness` | `0.9` | 0 polished to 1 matte |
| `water` | none | `{ level, top, deep, fade, clarity }`: water in it up to `level`, its depth showing in the cut. Pair it with a `<Water>` of the same size at that level |

> [!IMPORTANT]
> Keep `height`, `color` and `sides` the same functions between renders (define them at module level, or with `useCallback`). A new function rebuilds the terrain.

## Where things may go

`<Placement>` takes three outdoor options (see [Placing things by hand](https://matthiasbrandolin.dev/apricot/guides/placement) for the rest):

| Option | Each item | Meaning |
| --- | --- | --- |
| `areas` | `{ id, outline, level, water }` | Ground (or water) things stand in. A thing stays wholly inside one area, at its `level`, sliding along its edge. **Later areas lie on earlier ones**: list the sea first, then the island, then a deck on the island. Areas with `water` (waves, or `true`) take only `on="water"` things; the others take the rest |
| `zones` | `{ id, outline }` | Places nothing may stand: rocks, a flower bed, round a palm. A thing touching one doesn't fit |
| `snaps` | `{ at: [x, z], turn, radius, accepts }` | Spots that pull a thing within `radius` (default 0.3 m) into place and turn it to `turn` degrees. `accepts(id)` says which things it takes |

```tsx title="Beach rules"
<Placement
  areas={[
    { id: "sea", outline: everywhere, water: sea },
    { id: "sand", outline: drySand, level: 0.16 },
    { id: "deck", outline: deck, level: 0.26 },
  ]}
  zones={[{ id: "rocks", outline: rocks }]}
  snaps={[{ at: [2, 1.75], turn: 0, accepts: (id) => id.startsWith("lounger") }]}
  // The boat only in the sea; canPlace is told the area and the zones touched.
  canPlace={(id, place, { area }) => !id.startsWith("boat") || area === "sea"}
  collide="warn"
>
  …
</Placement>
```

Floating things (`on="water"`) bob and tilt with the waves at rest and cast no shadow while they move, so the shadow map isn't redrawn every frame.

## Performance

- Waves keep the scene rendering. Keep the water's `spacing` as coarse as the waves allow, and put still things (the ground, the scenery, the `<Placement>`) in a `<Bundle>`.
- `useShore` is measured once (an exact distance transform: a 160 × 160 field takes about 16 ms on a desktop). Change its inputs rarely.
- Instance repeated scenery (palms, rocks, fence posts) with `<Instances>`.

## Related

- [Ground and water reference](https://matthiasbrandolin.dev/apricot/reference/ground-and-water).
- [Placing things by hand](https://matthiasbrandolin.dev/apricot/guides/placement).
- [Lighting](https://matthiasbrandolin.dev/apricot/guides/lighting#daylight-a-sun-that-follows-the-clock): `<Daylight>` for the sun over the beach.
