audio-code 0.2.0

A simple, high level game engine for audio games: 3D sound, speech, virtual menus, physics and timing. No graphics required.
Documentation
# Positions, directions and 3D

Everything in audio-code lives in one 3D world: physics bodies, positioned sounds and the
listener. This page explains how positions are written (`vec3`), what the numbers mean, and
how to make a game that uses all three dimensions: flying, swimming, climbing, or sounds
above and below the player.

## `vec3(x, y, z)`: what the three numbers mean

A position is a `Vec3`: three numbers, written `vec3(x, y, z)`. Each number is a distance
in **meters** from the origin, the point `vec3(0.0, 0.0, 0.0)` (also written `Vec3::ZERO`).

| Number | Positive | Negative | On a map |
|--------|----------|----------|----------|
| `x` (first)  | east  | west  | right / left |
| `y` (second) | north | south | up / down on the page (forward / back) |
| `z` (third)  | up    | down  | height above the ground |

Examples:

```rust
vec3(0.0, 0.0, 0.0)     // the origin
vec3(5.0, 0.0, 0.0)     // 5 m east of the origin
vec3(-5.0, 0.0, 0.0)    // 5 m west
vec3(0.0, 10.0, 0.0)    // 10 m north
vec3(3.0, -2.0, 0.0)    // 3 m east and 2 m south
vec3(0.0, 0.0, 4.0)     // 4 m straight above the origin
vec3(10.0, 15.0, 8.0)   // 10 m east, 15 m north, 8 m up: a bird in a tree
```

Write the numbers with a decimal point (`5.0`, not `5`), because they are `f32` values.

You can read the parts back with `.x`, `.y` and `.z`:

```rust
let pos = ctx.physics.position(player);
let height = pos.z;
ctx.speak(format!("{:.0} meters east, {:.0} meters north", pos.x, pos.y));
```

### The same type for directions and speeds

A `Vec3` can also describe a **direction** or a **velocity**. Then it does not say where
something is, but which way and how fast it goes:

```rust
ctx.physics.set_velocity(car, vec3(0.0, 14.0, 0.0));      // 14 m/s towards north
ctx.physics.apply_impulse(ball, vec3(0.0, 0.0, 6.0));     // a kick straight up
ctx.physics.move_by(player, vec3(1.0, 0.0, 0.0));         // 1 m east, right now
```

### Calculating with positions

```rust
let a = vec3(1.0, 2.0, 0.0);
let b = vec3(4.0, 6.0, 0.0);

a + b                  // vec3(5.0, 8.0, 0.0): move a by b
b - a                  // vec3(3.0, 4.0, 0.0): the way from a to b
(b - a).length()       // 5.0: how far that is, in meters
a.distance(b)          // 5.0: the same in one call
a * 2.0                // vec3(2.0, 4.0, 0.0): twice as far from the origin
(b - a).normalize()    // vec3(0.6, 0.8, 0.0): only the direction, 1 m long
a + vec3(0.0, 0.0, 2.0) // 2 m above a
```

`length` and `distance` count all three axes, so a sound 3 m east and 4 m up is 5 m away.

### Directions from angles

Instead of writing directions by hand, you usually get them from an angle:

```rust
math::forward(90.0)               // vec3(1.0, 0.0, 0.0): east, 1 m long
math::right(0.0)                  // facing north, right is east
math::forward_3d(0.0, 45.0)       // north and 45° up
pos + math::forward(yaw) * 3.0    // 3 m ahead of someone facing `yaw`
```

## Angles: facing (yaw) and pitch

Two angles describe where someone looks. Both are in degrees.

**Facing** (also called yaw) is the compass direction, measured clockwise from north:

```text
            0 north
              |
 270 west ----+---- 90 east
              |
           180 south
```

**Pitch** is how far someone looks up or down: 0 is level with the horizon, 90 is straight
up, -90 is straight down, 30 is a little up.

```rust
ctx.physics.turn(player, 30.0);    // facing 30° further right (negative = left)
ctx.physics.tilt(player, 30.0);    // look 30° further up (negative = down), stops at ±90
ctx.physics.set_facing(player, 180.0);
ctx.physics.set_pitch(player, 0.0);
ctx.physics.look_at(player, target_pos);   // face a point: sets facing and pitch
let (yaw, pitch) = math::look_at(from, to);
math::elevation(from, to);                  // how many degrees `to` is above `from`
```

Shapes never rotate. Facing and pitch only change where the body moves with
`move_forward`, where the ears point when the body is followed, and how `describe`
describes things.

## Three kinds of games

| Game | Axes you use | Gravity | Typical calls |
|------|--------------|---------|---------------|
| Top down (maze, map) | x and y, z stays 0 | off | `turn`, `move_forward`, `strafe` |
| Side scroller | x (left/right) and z (up/down), y stays 0, facing stays 0 | on | `set_velocity`, `jump` |
| Full 3D (flying, swimming, space, a building with floors) | all three | off for flying, on for walking | `turn`, `tilt`, `move_forward`, `move_up` |

In a side scroller the player always faces north, so east (`+x`) is on the right ear and
`z` is up.

## Moving in 3D

```rust
ctx.physics.move_forward(drone, 1.0);   // 1 m where it looks, including up or down
ctx.physics.move_up(drone, 1.0);        // 1 m straight up, -1.0 straight down
ctx.physics.strafe(drone, 1.0);         // 1 m to the right, always level
ctx.physics.move_by(drone, vec3(0.0, 0.0, -0.5));   // any direction
```

All of these stop at walls, floors and ceilings and return a `MoveResult`. With a pitch of
0 (the default), `move_forward` stays level, so walking games do not change. If a walking
player may look up and down but should still walk level, move it with
`move_by(id, math::forward(facing) * distance)`.

`ctx.input.movement_3d()` reads the keys for 3D movement: arrows or WASD for `x` (right)
and `y` (forward), Page Up or E for up and Page Down or Q for down (`z`).

```rust
let dir = ctx.input.movement_3d();
let facing = ctx.physics.facing(drone);
let delta = math::right(facing) * dir.x + math::forward(facing) * dir.y + vec3(0.0, 0.0, dir.z);
ctx.physics.move_by(drone, delta * 4.0 * ctx.time.delta());   // 4 m/s
```

## Sound in 3D

Positioned sounds always use the full 3D distance. The listener can also look up and down:

```rust
ctx.audio.set_listener(pos, yaw);              // looking level
ctx.audio.set_listener_3d(pos, yaw, pitch);    // looking up or down
ctx.follow(drone);                             // uses the body's facing and pitch
```

How a sound's direction is heard on headphones:

| Where the sound is | What you hear |
|--------------------|---------------|
| right / left       | louder in that ear |
| ahead              | centered, clear |
| behind             | centered, muffled and up to 30 % quieter |
| above              | centered, clear |
| below              | centered, a little dull and up to 15 % quieter |

Above, below, ahead and behind are measured from the head: when the player looks straight
up, a sound above is "ahead", and a sound at the horizon in front is "below". Stereo
headphones cannot really place a sound above or below, so tell the player the height as
well, for example with `describe`.

## Describing positions

`describe` speaks the height when it differs by a meter or more:

```rust
ctx.physics.describe(player, bird);
// "2 o'clock, 9 meters"                    same height
// "12 o'clock, 11 meters, 4 meters above"  higher
// "directly below, 5 meters"               nothing to the side
```

The clock direction uses the facing (12 o'clock is straight ahead), the distance is the
straight line in 3D.

## A complete example

`examples/flight.rs` is a small 3D game: you fly a drone through a hall and look for birds
that sit at different heights. It uses `tilt`, `move_forward` with pitch, `move_up`,
`describe` with heights and the listener's pitch. Run it with
`cargo run --example flight` and use headphones.