-
Notifications
You must be signed in to change notification settings - Fork 300
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Terrain and Resource system docs (#76)
* 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
1 parent
f567dc7
commit 78f15b7
Showing
5 changed files
with
237 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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." | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
}; | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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." | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |