# Falling objects

> Drop apples, coins or any mesh from above the screen with Pile, let them bounce and heap up at the bottom, mix kinds, tune the physics, replay the drop and stop rendering when everything rests.

`<Pile>` drops `count` copies of its children from above the visible area over `duration` seconds. They collide with each other and the screen edges, roll and heap up at the bottom, and never leave the screen. Each kind of object is drawn in one call, however many fall. Once everything rests the scene stops rendering and `onSettled` fires.

```tsx title="AppleDrop.tsx"
import { Apple } from "@apricot/assets";
import { Pile, Scene } from "@apricot/react";

export function AppleDrop() {
  return (
    <Scene style={{ flex: 1 }} look="soft" camera={{ position: [0, 0, 20], fov: 30 }}>
      <Pile count={45} duration={5} scale={[0.58, 0.68]}>
        <Apple />
      </Pile>
    </Scene>
  );
}
```

## How it works

- **Area.** By default the pile fills what the camera sees, `depth` (0.5) units deep on each side of z = 0. It follows resizes, so after a rotation the pile re-forms for the new screen. Pass `bounds={{ min: [x, y, z], max: [x, y, z] }}` to use a fixed box instead; its floor is `min[1]`.
- **Children are templates.** Each child is one kind of object, not drawn itself. Their own `position`, `rotation` and `scale` are **ignored**: every copy gets a random place, tumble and size. Only meshes work (shapes, `<Mesh>`, `<Apple>`, `<Coin>`, `<Peach>`); a `<Group>` or `<Model>` is skipped with a warning.
- **Collisions** treat every object as a sphere. Its radius is the mesh's `userData.radius` if set (the fruit assets set it), or else its geometry's bounding sphere.
- **Size.** `scale` is a random range relative to each child's own size (default `[0.9, 1.1]`). With the camera at `[0, 0, 20]` and fov 30°, the screen is about 10 units tall, so `[0.58, 0.68]` makes a 1-unit apple about a sixteenth of the screen height.

> [!TIP]
> Keep the camera narrow (`fov` 30 or less) and far back. A wide lens stretches objects near the screen edges.

## Mixing kinds

Give several children; each copy picks one of them at random.

```tsx title="Fruit.tsx"
import { Apple, Peach } from "@apricot/assets";
import { Pile } from "@apricot/react";

export const Fruit = () => (
  <Pile count={60} duration={4}>
    <Apple />
    <Apple color="#7bc043" />
    <Peach />
  </Pile>
);
```

## Over a screen

Make the scene transparent and let touches through, so the screen below stays usable while objects fall on it. The [falling-pile recipe](https://matthiasbrandolin.dev/apricot/recipes/falling-pile) wraps this as a component.

```tsx title="Celebrate.tsx"
import { Coin } from "@apricot/assets";
import { Pile, Scene } from "@apricot/react";
import type { ReactNode } from "react";
import { StyleSheet, View } from "react-native";

export function Celebrate({ children }: { children: ReactNode }) {
  return (
    <View style={{ flex: 1 }}>
      {children}
      <Scene transparent pointerEvents="none" style={StyleSheet.absoluteFill} camera={{ position: [0, 0, 20], fov: 30 }}>
        <Pile count={40} duration={3} scale={[0.6, 0.7]}>
          <Coin />
        </Pile>
      </Scene>
    </View>
  );
}
```

## Replaying and removing

The drop starts when the pile mounts and restarts when an option changes (options are compared by value, so inline arrays do not restart it). To replay it, change its `key`; to remove it, unmount it.

```tsx title="Replay.tsx"
import { Apple } from "@apricot/assets";
import { Pile, Scene } from "@apricot/react";
import { useState } from "react";
import { Button, StyleSheet, View } from "react-native";

export function Replay() {
  const [round, setRound] = useState(0);
  const [done, setDone] = useState(false);
  return (
    <View style={{ flex: 1 }}>
      <Scene style={StyleSheet.absoluteFill} camera={{ position: [0, 0, 20], fov: 30 }}>
        <Pile key={round} count={30} duration={3} onSettled={() => setDone(true)}>
          <Apple />
        </Pile>
      </Scene>
      {done && (
        <Button
          title="Again"
          onPress={() => {
            setDone(false);
            setRound((r) => r + 1);
          }}
        />
      )}
    </View>
  );
}
```

## Options

| Prop | Default | Meaning |
| --- | --- | --- |
| `count` | required | How many objects to drop in total |
| `duration` | `5` | Seconds over which they drop |
| `bounds` | the visible area | `{ min, max }` box to fill. Changing it moves the walls without restarting |
| `depth` | `0.5` | Half the thickness of the default area |
| `scale` | `[0.9, 1.1]` | Random size range, relative to each child |
| `speed` | `2` | Downward speed when objects appear, units per second |
| `spin` | `4` | Largest initial tumble, radians per second |
| `seed` | `1` | Random seed: the same seed gives the same drop. Change it for a different one |
| `physics` | see below | Solver settings |
| `onSettled` | | Called once everything has dropped and rests |

`physics` tunes the solver:

| Field | Default | Effect |
| --- | --- | --- |
| `gravity` | `[0, -15, 0]` | Units per second² |
| `friction` | `0.6` | Friction with the floor (walls have none) |
| `bodyFriction` | `0.15` | Friction between objects. High values stack them in towers instead of heaps |
| `damping` | `0.05` | Speed lost per second |
| `contactDamping` | `4` | Extra damping while touching something, so objects stop rolling |
| `restSpeed` | `0.12` | Below this speed an object counts as resting |
| `settleTime` | `0.4` | Seconds everything must rest before the pile sleeps |
| `maxSpeed` | `40` | Speed limit |
| `iterations` | `2` | Solver passes per step; more keeps deep piles tighter |
| `maxSubstep` | `1/120` | Longest simulation step, seconds |

## Cost

The solver is plain JavaScript on the JS thread. A few dozen objects are cheap; a few hundred keep the JS thread busy until they settle. Prefer fewer, larger objects.

## Related

- [falling-pile recipe](https://matthiasbrandolin.dev/apricot/recipes/falling-pile).
- [Motion reference](https://matthiasbrandolin.dev/apricot/reference/motion#pile).
- [Assets](https://matthiasbrandolin.dev/apricot/reference/assets): `Apple`, `Peach`, `Coin`.
