Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Terrain and Resource system docs #76

Merged
merged 7 commits into from
Aug 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.