@hello-terrain/three 0.0.0-alpha.1 → 0.0.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +179 -23
- package/dist/index.cjs +2687 -8
- package/dist/index.d.cts +814 -10
- package/dist/index.d.mts +814 -10
- package/dist/index.d.ts +814 -10
- package/dist/index.mjs +2614 -11
- package/package.json +12 -2
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# @hello-terrain/three
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
Realtime web terrain engine for vast virtual worlds. Built for [three.js](https://threejs.org/) WebGPU.
|
|
4
|
+
|
|
3
5
|
## Features
|
|
4
6
|
|
|
5
7
|
- Performant variable LOD system for huge (earth-scale!) open worlds
|
|
@@ -7,36 +9,190 @@ Realtime web terrain engine, for vast virtual worlds. Built for [three.js](https
|
|
|
7
9
|
- TSL-based elevation and texture assignment nodes
|
|
8
10
|
- Composable compute stage plugins
|
|
9
11
|
|
|
10
|
-
##
|
|
12
|
+
## Quick Start
|
|
13
|
+
|
|
14
|
+
### React Three Fiber
|
|
15
|
+
|
|
16
|
+
```jsx
|
|
17
|
+
import { useRef, useMemo, useEffect } from "react";
|
|
18
|
+
import { Canvas, extend, useFrame, useThree } from "@react-three/fiber";
|
|
19
|
+
import { OrbitControls } from "@react-three/drei";
|
|
20
|
+
import * as THREE from "three/webgpu";
|
|
21
|
+
import { float, Fn, vec2 } from "three/tsl";
|
|
22
|
+
import {
|
|
23
|
+
terrainGraph,
|
|
24
|
+
TerrainGeometry,
|
|
25
|
+
TerrainMesh,
|
|
26
|
+
innerTileSegments,
|
|
27
|
+
elevationScale,
|
|
28
|
+
elevationFn,
|
|
29
|
+
quadtreeUpdate,
|
|
30
|
+
quadtreeUpdateTask,
|
|
31
|
+
positionNodeTask,
|
|
32
|
+
voronoiCells,
|
|
33
|
+
} from "@hello-terrain/three";
|
|
34
|
+
import { task } from "@hello-terrain/work";
|
|
35
|
+
|
|
36
|
+
extend(THREE);
|
|
37
|
+
extend({ TerrainGeometry, TerrainMesh });
|
|
38
|
+
|
|
39
|
+
function Terrain({ graph }) {
|
|
40
|
+
const meshRef = useRef(null);
|
|
41
|
+
const materialRef = useRef(null);
|
|
42
|
+
|
|
43
|
+
// Define the elevation function (runs on the GPU)
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
graph.set(elevationFn, () => ({ worldPosition }) => {
|
|
46
|
+
return voronoiCells({
|
|
47
|
+
scale: float(1),
|
|
48
|
+
facet: 0,
|
|
49
|
+
seed: 0,
|
|
50
|
+
uv: vec2(worldPosition.x, worldPosition.z).mul(0.5),
|
|
51
|
+
}).mul(0.5);
|
|
52
|
+
});
|
|
53
|
+
graph.set(elevationScale, () => 10);
|
|
54
|
+
}, [graph]);
|
|
55
|
+
|
|
56
|
+
// Apply position node to the material when graph produces it
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
graph.add(
|
|
59
|
+
task((get, work) => {
|
|
60
|
+
const positionNode = get(positionNodeTask);
|
|
61
|
+
const leafSet = get(quadtreeUpdateTask);
|
|
62
|
+
return work(() => {
|
|
63
|
+
const mesh = meshRef.current;
|
|
64
|
+
const material = materialRef.current;
|
|
65
|
+
if (mesh && leafSet?.count !== undefined) mesh.count = leafSet.count;
|
|
66
|
+
if (material && positionNode) {
|
|
67
|
+
material.positionNode = positionNode;
|
|
68
|
+
material.needsUpdate = true;
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}).displayName("applyPositionNodeTask"),
|
|
72
|
+
);
|
|
73
|
+
}, [graph]);
|
|
74
|
+
|
|
75
|
+
// Update camera and run the graph each frame
|
|
76
|
+
useFrame(async ({ camera, gl }) => {
|
|
77
|
+
graph.set(quadtreeUpdate, (prev) => {
|
|
78
|
+
prev.cameraOrigin.x = camera.position.x;
|
|
79
|
+
prev.cameraOrigin.y = camera.position.y;
|
|
80
|
+
prev.cameraOrigin.z = camera.position.z;
|
|
81
|
+
return prev;
|
|
82
|
+
});
|
|
83
|
+
await graph.run({ resources: { renderer: gl } });
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<terrainMesh ref={meshRef} innerTileSegments={innerTileSegments.get()} maxNodes={1024}>
|
|
88
|
+
<meshStandardNodeMaterial ref={materialRef} />
|
|
89
|
+
</terrainMesh>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export default function App() {
|
|
94
|
+
const graph = useMemo(() => terrainGraph(), []);
|
|
95
|
+
return (
|
|
96
|
+
<Canvas
|
|
97
|
+
gl={async (props) => {
|
|
98
|
+
const renderer = new THREE.WebGPURenderer({ ...props, antialias: true });
|
|
99
|
+
await renderer.init();
|
|
100
|
+
return renderer;
|
|
101
|
+
}}
|
|
102
|
+
camera={{ position: [0, 30, 60] }}
|
|
103
|
+
>
|
|
104
|
+
<ambientLight intensity={0.5} />
|
|
105
|
+
<directionalLight position={[1, 1, 1]} />
|
|
106
|
+
<Terrain graph={graph} />
|
|
107
|
+
<OrbitControls />
|
|
108
|
+
</Canvas>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
```
|
|
11
112
|
|
|
12
|
-
|
|
113
|
+
### Vanilla Three.js
|
|
13
114
|
|
|
14
|
-
|
|
115
|
+
```js
|
|
116
|
+
import * as THREE from "three/webgpu";
|
|
117
|
+
import { float, vec2 } from "three/tsl";
|
|
118
|
+
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
|
|
119
|
+
import {
|
|
120
|
+
terrainGraph,
|
|
121
|
+
TerrainMesh,
|
|
122
|
+
innerTileSegments,
|
|
123
|
+
elevationScale,
|
|
124
|
+
elevationFn,
|
|
125
|
+
quadtreeUpdate,
|
|
126
|
+
quadtreeUpdateTask,
|
|
127
|
+
positionNodeTask,
|
|
128
|
+
voronoiCells,
|
|
129
|
+
} from "@hello-terrain/three";
|
|
130
|
+
import { task } from "@hello-terrain/work";
|
|
15
131
|
|
|
16
|
-
|
|
132
|
+
// Renderer
|
|
133
|
+
const renderer = new THREE.WebGPURenderer({ antialias: true });
|
|
134
|
+
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
135
|
+
document.body.appendChild(renderer.domElement);
|
|
136
|
+
await renderer.init();
|
|
17
137
|
|
|
18
|
-
|
|
138
|
+
// Scene & camera
|
|
139
|
+
const scene = new THREE.Scene();
|
|
140
|
+
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100000);
|
|
141
|
+
camera.position.set(0, 30, 60);
|
|
142
|
+
const controls = new OrbitControls(camera, renderer.domElement);
|
|
19
143
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
144
|
+
// Terrain
|
|
145
|
+
const material = new THREE.MeshStandardNodeMaterial();
|
|
146
|
+
const mesh = new TerrainMesh({ innerTileSegments: innerTileSegments.get(), maxNodes: 1024, material });
|
|
147
|
+
scene.add(mesh);
|
|
148
|
+
scene.add(new THREE.AmbientLight(0xffffff, 0.5));
|
|
149
|
+
scene.add(new THREE.DirectionalLight(0xffffff, 1));
|
|
25
150
|
|
|
151
|
+
// Task graph
|
|
152
|
+
const graph = terrainGraph();
|
|
26
153
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
154
|
+
graph.set(elevationFn, () => ({ worldPosition }) => {
|
|
155
|
+
return voronoiCells({
|
|
156
|
+
scale: float(1),
|
|
157
|
+
facet: 0,
|
|
158
|
+
seed: 0,
|
|
159
|
+
uv: vec2(worldPosition.x, worldPosition.z).mul(0.5),
|
|
160
|
+
}).mul(0.5);
|
|
161
|
+
});
|
|
162
|
+
graph.set(elevationScale, () => 10);
|
|
30
163
|
|
|
164
|
+
// Apply graph outputs to the mesh
|
|
165
|
+
graph.add(
|
|
166
|
+
task((get, work) => {
|
|
167
|
+
const positionNode = get(positionNodeTask);
|
|
168
|
+
const leafSet = get(quadtreeUpdateTask);
|
|
169
|
+
return work(() => {
|
|
170
|
+
if (leafSet?.count !== undefined) mesh.count = leafSet.count;
|
|
171
|
+
if (positionNode) {
|
|
172
|
+
material.positionNode = positionNode;
|
|
173
|
+
material.needsUpdate = true;
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}).displayName("applyPositionNodeTask"),
|
|
177
|
+
);
|
|
31
178
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
179
|
+
// Render loop
|
|
180
|
+
renderer.setAnimationLoop(async () => {
|
|
181
|
+
controls.update();
|
|
182
|
+
graph.set(quadtreeUpdate, (prev) => {
|
|
183
|
+
prev.cameraOrigin.x = camera.position.x;
|
|
184
|
+
prev.cameraOrigin.y = camera.position.y;
|
|
185
|
+
prev.cameraOrigin.z = camera.position.z;
|
|
186
|
+
return prev;
|
|
187
|
+
});
|
|
188
|
+
await graph.run({ resources: { renderer } });
|
|
189
|
+
renderer.render(scene, camera);
|
|
190
|
+
});
|
|
191
|
+
```
|
|
36
192
|
|
|
193
|
+
## Documentation
|
|
37
194
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
- `pnpm run release` to build and publish to npm
|
|
195
|
+
1. Read the [Introduction](http://hello-terrain.kenny.wtf/docs) to understand the architecture.
|
|
196
|
+
2. Read the [Installation](http://hello-terrain.kenny.wtf/docs/installation) guide.
|
|
197
|
+
3. Browse the [Examples](http://hello-terrain.kenny.wtf/examples).
|
|
198
|
+
4. Join the [Discord](https://discord.gg/HgTd2B828n) for support.
|