# Map geometry

> Build maps and test positions with plain-data shapes from disko:geometry.

The physics world handles collisions for you, but games still need geometry
of their own: placing walls in a circle, putting posts at the corners of a
hexagon, checking whether a player is standing inside the center circle.
`disko:geometry` gives you the math for that.

## Shapes are just data

The shapes in `disko:geometry` are plain objects, not things in the world:

- a point is `{ x, y }`;
- a segment is `{ start, end }`;
- a circle is `{ center, radius }`;
- an arc is `{ start, end, curve }`.

Creating a shape doesn't add anything to the match. You use shapes to
_compute_ positions, then pass the results to `world.create…` functions. And
because these functions don't depend on the world, you can call them
anywhere, including top-level code, and they give the same answer on every
computer.

## Curved walls with arcs

A wall in the world can be straight or curved. A curved wall is described by
its two ends and a single number, `curve`, that says how much it bends:

- `curve: 0` bends the wall into a half circle;
- a positive `curve` is a gentler bend (less than half a circle);
- a negative `curve` bends past half a circle.

Working that number out by hand is fiddly. `arcFromCenter` does it for you:
give it a center, a radius, a start angle and how far around to sweep, and it
returns `start`, `end` and `curve`, exactly the values `world.createSegment`
takes. This builds a round arena out of eight arcs, and six posts in a
hexagon:

```ts title="src/arena.ts"
game.on("start", () => {
  // A circular arena wall out of 8 world arcs.
  for (let i = 0; i < 8; i++) {
    const arc = arcFromCenter({
      center: vec(0, 0),
      radius: 300,
      startAngle: (i * TAU) / 8,
      sweep: TAU / 8,
    });

    world.createSegment({
      start: world.createVertex(arc.start),
      end: world.createVertex(arc.end),
      curve: arc.curve,
    });
  }

  // Six static corner posts.
  for (const post of regularPolygon({
    center: vec(0, 0),
    radius: 120,
    sides: 6,
  })) {
    world.createVertex(post);
  }
});
```

## Asking questions about positions

The other half of the module answers questions: where do two lines cross,
how far is a point from a wall, is a point inside a shape. This checks every
tick whether a player is inside the center circle:

```ts title="src/rules.ts"
const centerCircle = circle(vec(0, 0), 60);

game.on("tick", () => {
  for (const player of game.players) {
    const position = player.controlledObject?.state().position;

    if (position !== undefined && circleContainsPoint(centerCircle, position)) {
      // The player is inside the center circle.
    }
  }
});
```

The module covers:

- **Intersections:** `segmentIntersection`, `lineCircleIntersections`,
  `rayArcIntersection` and more.
- **Distances and closest points:** to lines, segments, circles and arcs.
- **Bounds:** the box around a shape, and whether two boxes overlap.
- **Containment:** whether a point is inside a circle, a box or a polygon.

### How answers are returned

- A question with at most one answer returns it, or `null` if there's none.
- A question with up to two answers, such as where a line meets a circle,
  returns an array, ordered along the first shape.
- Points exactly on a boundary count as inside.
- Comparisons are exact. Add a last argument `{ epsilon }` to allow a small
  margin of error.
- A polygon is an array of three or more points.

See the [`disko:geometry` reference](/reference/geometry) for every function.
