# SpatialDirection

Which way a positional channel points, and how much quieter it is off-axis.

Source: https://socketsvc.vercel.app/api/client/SpatialDirection
<Callout title="Generated from source">
  Record `com.socketsvc.client.api.SpatialDirection` — generated from [SpatialDirection.java](https://github.com/HippoDev03/SocketSVC/blob/main/client/common/src/main/java/com/socketsvc/client/api/SpatialDirection.java). Edit the Javadoc in the Java source, then run `pnpm gen:api`.
</Callout>

```java
package com.socketsvc.client.api;

public record SpatialDirection(double x, double y, double z, float innerAngle, float outerAngle, float outerGain) { ... }
```

Which way a positional channel points, and how much quieter it is off-axis.

The standard cone: full volume inside `innerAngle`, falling to `outerGain` at
`outerAngle`, and staying there beyond it. A speaker stack facing the seats is louder in
front of it than behind, which is the difference between audio placed in a room and audio glued
to the listener's head.

A direction is a **gain calculation, not a second audio path**: it multiplies into the
distance gain the channel already computes. That is what makes it cheap enough to be worth
having, and it is why the same model belongs on the server's own speaker API - a cone is a
property of a loudspeaker, not of who is listening.

## Components

| Component | Description |
| --- | --- |
| `double x` | facing, in world axes. Need not be normalised; a zero vector is omnidirectional |
| `double y` | — |
| `double z` | — |
| `float innerAngle` | the **full width** of the cone within which nothing is attenuated, in degrees (0..360) - so 60 means 30 degrees either side of the axis, which is the convention OpenAL and the Web Audio API both use |
| `float outerAngle` | the full width at which `outerGain` is reached; never less than `innerAngle` |
| `float outerGain` | the floor, 0..1. Deliberately allowed to be above zero: a real speaker is quieter behind, not silent, and a hard cut-off is heard as a fault |

## Methods

### cone

```java
public static SpatialDirection cone(double x, double y, double z, float inner, float outer, float outerGain)
```

A cone pointing along `(x, y, z)`, full volume within the `inner`-degree-wide
cone and falling to `outerGain` by the `outer`-degree-wide one. Both are full
widths, not half-angles.

### directional

```java
public boolean directional()
```

Whether this direction actually attenuates anything, so a channel that does not point
anywhere skips the work entirely.

### gain

```java
public double gain(double dx, double dy, double dz)
```

How loudly this channel is heard by somebody `dx, dy, dz` away from it.

1 inside the inner cone, `outerGain` outside the outer one, and a smooth ride
between - cosine-interpolated rather than linear in the angle, because the ear follows
loudness rather than degrees and a linear ramp is heard as a step near the edges.

A listener standing exactly on the speaker gets 1: there is no direction from a point to
itself, and going silent there would be the one place a cone must not be heard.