Skip to content

Commit

Permalink
Terrain and Resource system docs (#76)
Browse files Browse the repository at this point in the history
* Terrain data model docs

* Fix doc links

* Some preliminary docs on the Biomes resource system

* Doc updates

* Created example_usage.md

* Update overview
  • Loading branch information
DevinLeamy authored Aug 28, 2023
1 parent f567dc7 commit 78f15b7
Show file tree
Hide file tree
Showing 5 changed files with 237 additions and 0 deletions.
8 changes: 8 additions & 0 deletions docs/docs/resources/_category_.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"label": "Resource System",
"position": 4,
"link": {
"type": "generated-index",
"description": "Creating resources, resources types, manipulating resources, dependency injection."
}
}
121 changes: 121 additions & 0 deletions docs/docs/resources/example_usage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
---
sidebar_position: 2
---

# Example Usage

### Defining resource paths

```typescript
interface Health {
health: number;
maxHealth: number;
}

interface PlayerResource {
health: Health;
position: Vec3;
}

interface ExampleResourcePaths {
// Players are looked up by their BiomesId.
"/player": PathDef<[BiomesId], PlayerResource>;
"/player/health": PathDef<[BiomesId], Health>;
"/player/position": PathDef<[BiomesId], { position: Vec3 }>;
// The clock has no parameters.
"/clock": PathDef<[], { time: number }>;
}
```

### Defining components

```ts
type ExampleResourcesBuilder = BiomesResourcesBuilder<ExampleResourcePaths>;
type ExampleResourceDeps = TypedResourceDeps<ExampleResourcePaths>;
type ExampleResources = TypedResources<ExampleResourcePaths>;
type ExampleReactResources = ReactResources<ExampleResourcePaths>;
```

### Building resources

```ts
function genPlayerResource(deps: ExampleResourceDeps, id: BiomesId) {
// Calling deps.get() here creates a dependency between
// "/player" and "/player/health" + "/player/position".
// Whenever the dependencies update, this generator function will rerun.
const health = deps.get("/player/health", id);
const { position } = deps.get("/player/position", id);

return {
health,
position,
};
}

function addExampleResources(builder: ExampleResourcesBuilder) {
// Define a global resource.
builder.addGlobal("/clock", { time: secondsSinceEpoch() });
builder.add("/player", genPlayerResource);
builder.add("/player/health", { health: 100, maxHealth: 100 });
builder.add("/player/position", { position: [0, 0, 0] });
}
```

### Accessing resources

> _Note: The same can be done using `ExampleReactResources`_.
Resources are accessed using the `get()` method.

```ts
function healthBarColor(resources: ExampleResources, id: BiomesId): string {
const { health, maxHealth } = resources.get("/player/health", id);
const healthPercentage = Math.round((health / maxHealth) * 100);
if (healthPercentage >= 80) {
return "GREEN";
} else if (healthPercentage >= 50) {
return "YELLOW";
} else if (healthPercentage > 0) {
return "RED";
} else {
return "BLACK";
}
}
```

### Updating resources

> _Note: The same can be done using `ExampleReactResources`_.
Resources are updated using the `set()` method.

```ts
const JUMP_POWER = 10;

function jump(resources: ExampleResources, id: BiomesId) {
const { position } = resources.get("/player/position", id);
// Perform a realistic jump.
resources.set("/player/position", id, {
position: [position[0], position[1] + JUMP_POWER, position[2]],
});
}
```

### Using resources within React

If you want a resource update to trigger a react component to re-render, use the `use()` method on
`ReactResources`. `ReactResources` can be accessed from within all game components, through the `ClientContext`.

```tsx
const PlayerHealth: React.FC<{ playerId: BiomesId }> = ({ playerId }) => {
const { reactResources, userId } = useClientContext();
// Updates to this player's "/player/health" will cause a re-render.
const { health, maxHealth } = reactResources.use("/player/health", playerId);

return (
<div>
<h1>{`${health}/${maxHealth}`}</h1>
</div>
);
};
```
31 changes: 31 additions & 0 deletions docs/docs/resources/overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
sidebar_position: 1
---

# Overview

## Problem

Biomes is a [Next.js](https://nextjs.org/) app with a [Three.js](https://threejs.org/) renderer.
React uses react state to manage resources, and Three.js doesn't have an out-of-the-box solution for state management;
typically refreshing a Three.js app will reset the scene.

There are a few problems here:

1. How to persist Three.js game state.
2. How to update React state when the Three.js game state changes, triggering a re-render.
3. How to update the Three.js game state when the React state changes.

Moreover, there is the problem of defining dependencies between resources. For instance, we may want to change
the player's appearance if their health is below a certain point. How can we describe this dependency between the player's appearance
and their health?

## Resource System

The resource system was created to solve these problems and is composed of a few components, the main ones being:

1. `BiomesResourcesBuilder`: Used to define resources.
2. `TypedResourceDeps`: Used to define dependencies between resources, using [dependency injection](https://en.wikipedia.org/wiki/Dependency_injection).
3. `TypedResources`: Used to access resources.
4. `ReactResources`: Used to access resources from within React components.
5. `ResourcePaths`: Typed resource keys with paths that define arguments for lookups.
8 changes: 8 additions & 0 deletions docs/docs/terrain/_category_.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"label": "Terrain",
"position": 3,
"link": {
"type": "generated-index",
"description": "Terrain data model, generation, and manipulation."
}
}
69 changes: 69 additions & 0 deletions docs/docs/terrain/data_model.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
sidebar_position: 1
---

# Data Model

### Glossary

- `Shard`: A 32x32x32 [tensor](https://en.wikipedia.org/wiki/Tensor).
- `BlockId`: An integer value that corresponds to a given block type, e.g. `1: grass`, `2: dirt`. All block types are defined
in [terrain.json](https://github.com/ill-inc/biomes-game/blob/main/src/shared/asset_defs/terrain.ts).
- `Seed`: The initial state of the terrain.
- `Voxel`: A single block.
- `Subvoxel`: A single block that's 1/8th the size of a full block. Each full block contains `8x8x8` subvoxels.

## Shards

The entire voxel 3D world is split up into shards.
The shard data is stored as a buffer, and need to be decompressed to be read.

Each shard contains the following data.

> _Note: all positions, will the exception of `Box`, are defined relative to the
> lowermost coordinate of the shard._
>
> _E.g. `[0, 0, 0]` corresponds to the lowestmost coordinate, `[31, 31, 31]` corresponds
> to the uppermost coordinate, and `[10, 16, 19]` lies somewhere between the lowest and highest most coordinates._
### Box

`v0`: lowermost coordinate of the shard.

`v1`: uppermost coordinate of the shard.

### ShardSeed

The Biomes terrain has some initial state we refer to as the `seed`. Each voxel in the world has some initial block type, which
is stored in `ShardSeed`s. `ShardSeed(x, y, z)` stores the `BlockId` of the initial block type at that location.

The seed shards are generated by scripts defined in [galois](https://github.com/ill-inc/biomes-game/tree/main/src/galois/py/notebooks).

### ShardDiff

When a voxel's block type is modified and becomes different then the seed shard, we store this diff(erence) in the diff
shards. Because most blocks will not be updated, these are sparse tensors - they store a maximum of `32x32x32` entries.
In other words, we _only_ store the updates.

Like the `ShardSeed`, these shards define a mapping for position to `BlockId`.

### ShardShape

The terrain is mostly occupied by full blocks, perfect cubes. However, all voxels can be transformed into a different shape,
for example a stair, a fence, window, table, etcetera, using shaping tools. The shape shards store information about the current shape of each voxel.

The shape data is encoded as an integer and contains two things:

1. `ShapeId`: e.g. stair. Shapes are defined in [shapes.json](https://github.com/ill-inc/biomes-game/blob/main/src/shared/asset_defs/gen/shapes.json).
2. `IsomorphismId`: e.g. stair flipped vertically and facing north.

### ShardPlacer

Each user has a `BiomesId` which is their unique identifier. The placer shards correspond voxels with the user,
`BiomesId`, that last modified it.

### ShardOccupancy

Placeables, like a boombox, TV, or flower, are not voxels however they still take up space. The occupancy tensor
records the space occupied by non-voxel things so that other things are not placed in the space they occupy.
Each position corresponds to the `BiomesId` of the entity that occupied the position, if there is one.

0 comments on commit 78f15b7

Please sign in to comment.