@hello-terrain/react 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 ADDED
@@ -0,0 +1,85 @@
1
+ # @hello-terrain/react
2
+
3
+ React bindings for Hello Terrain, built for [react-three/fiber](https://r3f.docs.pmnd.rs/) and WebGPU.
4
+
5
+ `@hello-terrain/react` wraps the core terrain graph from `@hello-terrain/three` with a React-first API:
6
+
7
+ - `Terrain` renders the terrain mesh and connects node materials
8
+ - `useTerrain()` creates and owns a `TerrainHandle`
9
+ - `TerrainProvider` and `useTerrainContext()` share terrain state with sibling systems
10
+ - `terrain.ready` and `terrain.runtime` make it easier to coordinate rendering, queries, and raycasts
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ pnpm add @hello-terrain/react @react-three/fiber three react react-dom
16
+ ```
17
+
18
+ `@hello-terrain/react` depends on WebGPU and expects a `three/webgpu` renderer.
19
+
20
+ ## Basic Example
21
+
22
+ ```tsx
23
+ import { Terrain } from "@hello-terrain/react";
24
+ import { Canvas } from "@react-three/fiber";
25
+ import type { WebGPURendererParameters } from "three/src/renderers/webgpu/WebGPURenderer.js";
26
+ import { float } from "three/tsl";
27
+ import * as THREE from "three/webgpu";
28
+
29
+ const elevation = () => float(0);
30
+
31
+ export function App() {
32
+ return (
33
+ <Canvas
34
+ gl={async (props) => {
35
+ const renderer = new THREE.WebGPURenderer(
36
+ props as WebGPURendererParameters,
37
+ );
38
+ await renderer.init();
39
+ return renderer;
40
+ }}
41
+ camera={{ position: [0, 30, 60] }}
42
+ >
43
+ <ambientLight intensity={0.15} />
44
+ <directionalLight intensity={1} position={[1, 1, 1]} />
45
+
46
+ <Terrain rootSize={1024} maxLevel={6} elevation={elevation}>
47
+ {({ positionNode }) => (
48
+ <meshStandardNodeMaterial positionNode={positionNode} />
49
+ )}
50
+ </Terrain>
51
+ </Canvas>
52
+ );
53
+ }
54
+ ```
55
+
56
+ ## When To Use `useTerrain()`
57
+
58
+ Use `Terrain` by itself for the smallest setup. Reach for `useTerrain()` when you want to:
59
+
60
+ - reuse the same terrain handle across multiple components
61
+ - access `terrain.runtime.query` or `terrain.runtime.raycast`
62
+ - share terrain state with sibling systems like controllers or cameras
63
+ - inspect the underlying graph or provide custom graph tasks
64
+
65
+ ## Public API
66
+
67
+ - `Terrain`
68
+ - `useTerrain`
69
+ - `TerrainProvider`
70
+ - `useTerrainContext`
71
+ - `TerrainHandle`
72
+ - `TerrainOptions`
73
+ - `TerrainRuntime`
74
+ - `TerrainTask`
75
+
76
+ ## Docs
77
+
78
+ - [Introduction](http://hello-terrain.kenny.wtf/docs)
79
+ - [Installation](http://hello-terrain.kenny.wtf/docs/installation)
80
+ - [React Overview](http://hello-terrain.kenny.wtf/docs/react)
81
+ - [Examples](http://hello-terrain.kenny.wtf/examples)
82
+
83
+ ## Support
84
+
85
+ For support, join the [Discord server](https://discord.gg/HgTd2B828n).
package/dist/index.cjs ADDED
@@ -0,0 +1,603 @@
1
+ 'use strict';
2
+
3
+ const jsxRuntime = require('react/jsx-runtime');
4
+ const three = require('@hello-terrain/three');
5
+ const fiber = require('@react-three/fiber');
6
+ const react = require('react');
7
+ const work = require('@hello-terrain/work');
8
+ const three$1 = require('three');
9
+
10
+ const TerrainContext = react.createContext(null);
11
+ function TerrainProvider({ value, children }) {
12
+ return /* @__PURE__ */ jsxRuntime.jsx(TerrainContext.Provider, { value, children });
13
+ }
14
+ function useTerrainContext() {
15
+ const value = react.useContext(TerrainContext);
16
+ if (!value) {
17
+ throw new Error("useTerrainContext must be used within a TerrainProvider");
18
+ }
19
+ return value;
20
+ }
21
+
22
+ function createSyncTerrainRuntimeTask(runtime) {
23
+ return work.task((get, work) => {
24
+ const query = get(three.terrainTasks.terrainQuery).query;
25
+ const raycast = get(three.terrainTasks.terrainRaycast);
26
+ return work(() => {
27
+ runtime.query = query;
28
+ runtime.raycast = raycast;
29
+ return runtime;
30
+ });
31
+ }).displayName("syncTerrainRuntimeTask").cache("none");
32
+ }
33
+ function createSyncTerrainNodesTask(getTerrainNodes, setTerrainNodes, getReady, setReady) {
34
+ return work.task((get, work) => {
35
+ const positionNode = get(three.terrainTasks.positionNode);
36
+ return work(() => {
37
+ const nextReady = positionNode != null;
38
+ const terrainNodes = getTerrainNodes();
39
+ if (terrainNodes.positionNode !== positionNode) {
40
+ setTerrainNodes({
41
+ positionNode
42
+ });
43
+ }
44
+ if (getReady() !== nextReady) {
45
+ setReady(nextReady);
46
+ }
47
+ return {
48
+ positionNode,
49
+ ready: nextReady
50
+ };
51
+ });
52
+ }).displayName("syncTerrainNodesTask").cache("none");
53
+ }
54
+
55
+ function resetOrSet(graph, ownedParamIds, ref, value, setValue) {
56
+ if (value === void 0) {
57
+ if (ownedParamIds.has(ref.id)) {
58
+ graph.reset(ref);
59
+ ownedParamIds.delete(ref.id);
60
+ }
61
+ return;
62
+ }
63
+ graph.set(ref, setValue);
64
+ ownedParamIds.add(ref.id);
65
+ }
66
+ function useTerrainParams(graph, options) {
67
+ const ownedParamIdsRef = react.useRef(/* @__PURE__ */ new Set());
68
+ const {
69
+ rootSize: nextRootSize,
70
+ origin: nextOrigin,
71
+ maxLevel: nextMaxLevel,
72
+ maxNodes: nextMaxNodes,
73
+ innerTileSegments: nextInnerTileSegments,
74
+ skirtScale: nextSkirtScale,
75
+ elevationScale: nextElevationScale,
76
+ elevation,
77
+ surface: nextSurface,
78
+ terrainFieldFilter: nextTerrainFieldFilter
79
+ } = options;
80
+ react.useLayoutEffect(() => {
81
+ ownedParamIdsRef.current.clear();
82
+ }, [graph]);
83
+ react.useLayoutEffect(() => {
84
+ const ownedParamIds = ownedParamIdsRef.current;
85
+ resetOrSet(
86
+ graph,
87
+ ownedParamIds,
88
+ three.rootSize,
89
+ nextRootSize,
90
+ () => nextRootSize
91
+ );
92
+ resetOrSet(graph, ownedParamIds, three.origin, nextOrigin, () => ({
93
+ x: nextOrigin?.x ?? 0,
94
+ y: nextOrigin?.y ?? 0,
95
+ z: nextOrigin?.z ?? 0
96
+ }));
97
+ resetOrSet(
98
+ graph,
99
+ ownedParamIds,
100
+ three.maxLevel,
101
+ nextMaxLevel,
102
+ () => nextMaxLevel
103
+ );
104
+ resetOrSet(
105
+ graph,
106
+ ownedParamIds,
107
+ three.maxNodes,
108
+ nextMaxNodes,
109
+ () => nextMaxNodes
110
+ );
111
+ resetOrSet(
112
+ graph,
113
+ ownedParamIds,
114
+ three.innerTileSegments,
115
+ nextInnerTileSegments,
116
+ () => nextInnerTileSegments
117
+ );
118
+ resetOrSet(
119
+ graph,
120
+ ownedParamIds,
121
+ three.skirtScale,
122
+ nextSkirtScale,
123
+ () => nextSkirtScale
124
+ );
125
+ resetOrSet(
126
+ graph,
127
+ ownedParamIds,
128
+ three.elevationScale,
129
+ nextElevationScale,
130
+ () => nextElevationScale
131
+ );
132
+ resetOrSet(
133
+ graph,
134
+ ownedParamIds,
135
+ three.surface,
136
+ nextSurface,
137
+ () => nextSurface
138
+ );
139
+ resetOrSet(
140
+ graph,
141
+ ownedParamIds,
142
+ three.terrainFieldFilter,
143
+ nextTerrainFieldFilter,
144
+ () => nextTerrainFieldFilter
145
+ );
146
+ if (elevation === void 0) {
147
+ if (ownedParamIds.has(three.elevationFn.id)) {
148
+ graph.reset(three.elevationFn);
149
+ ownedParamIds.delete(three.elevationFn.id);
150
+ }
151
+ } else {
152
+ graph.set(three.elevationFn, () => elevation);
153
+ ownedParamIds.add(three.elevationFn.id);
154
+ }
155
+ }, [
156
+ graph,
157
+ nextRootSize,
158
+ nextOrigin,
159
+ nextMaxLevel,
160
+ nextMaxNodes,
161
+ nextInnerTileSegments,
162
+ nextSkirtScale,
163
+ nextElevationScale,
164
+ elevation,
165
+ nextSurface,
166
+ nextTerrainFieldFilter
167
+ ]);
168
+ }
169
+
170
+ const WEBGPU_RENDERER_ERROR = "@hello-terrain/react requires a WebGPURenderer on <Canvas gl={...}>.";
171
+ const GRAPH_RUN_ERROR = "@hello-terrain/react terrain graph run failed.";
172
+ function toVector3Like(state, getCameraOrigin) {
173
+ return getCameraOrigin?.(state) ?? state.camera.position;
174
+ }
175
+ function getTerrainRunnerErrorKey(error) {
176
+ if (error instanceof Error) {
177
+ return `${error.name}:${error.message}`;
178
+ }
179
+ return String(error);
180
+ }
181
+ function isWebGpuRenderer(renderer) {
182
+ return typeof renderer === "object" && renderer !== null && "backend" in renderer;
183
+ }
184
+ function useTerrainRunner({
185
+ graph,
186
+ targets,
187
+ getCameraOrigin,
188
+ cameraHysteresis = 0.05
189
+ }) {
190
+ const graphRef = react.useRef(graph);
191
+ const targetsRef = react.useRef(targets);
192
+ const getCameraOriginRef = react.useRef(getCameraOrigin);
193
+ const lastCameraOriginRef = react.useRef(null);
194
+ const runningRef = react.useRef(false);
195
+ const generationRef = react.useRef(0);
196
+ const runAbortControllerRef = react.useRef(null);
197
+ const runPromiseRef = react.useRef(null);
198
+ const lastErrorKeyRef = react.useRef(null);
199
+ const reportError = react.useCallback(
200
+ (error, errorKey) => {
201
+ const nextErrorKey = errorKey ?? getTerrainRunnerErrorKey(error);
202
+ if (lastErrorKeyRef.current === nextErrorKey) return;
203
+ lastErrorKeyRef.current = nextErrorKey;
204
+ console.error(error);
205
+ },
206
+ []
207
+ );
208
+ const clearError = react.useCallback(() => {
209
+ lastErrorKeyRef.current = null;
210
+ }, []);
211
+ const stopCurrentRun = react.useCallback(async () => {
212
+ const activeController = runAbortControllerRef.current;
213
+ if (activeController && !activeController.signal.aborted) {
214
+ activeController.abort(new Error("Terrain runner stopped"));
215
+ }
216
+ const activeRun = runPromiseRef.current;
217
+ if (activeRun) {
218
+ await activeRun.catch(() => {
219
+ });
220
+ }
221
+ }, []);
222
+ const updateCameraOrigin = react.useCallback(
223
+ (state, activeGraph) => {
224
+ const activeGetCameraOrigin = getCameraOriginRef.current;
225
+ const cameraOrigin = toVector3Like(state, activeGetCameraOrigin);
226
+ const nextOrigin = new three$1.Vector3(
227
+ cameraOrigin.x,
228
+ cameraOrigin.y,
229
+ cameraOrigin.z
230
+ );
231
+ const lastOrigin = lastCameraOriginRef.current;
232
+ const hysteresisSq = cameraHysteresis * cameraHysteresis;
233
+ if (!lastOrigin || lastOrigin.distanceToSquared(nextOrigin) >= hysteresisSq) {
234
+ activeGraph.set(three.quadtreeUpdate, (prev) => {
235
+ prev.cameraOrigin.x = nextOrigin.x;
236
+ prev.cameraOrigin.y = nextOrigin.y;
237
+ prev.cameraOrigin.z = nextOrigin.z;
238
+ return prev;
239
+ });
240
+ lastCameraOriginRef.current = nextOrigin;
241
+ }
242
+ },
243
+ [cameraHysteresis]
244
+ );
245
+ const finishRun = react.useCallback(
246
+ (activeRunController, activeGeneration) => {
247
+ if (runAbortControllerRef.current === activeRunController) {
248
+ runAbortControllerRef.current = null;
249
+ runPromiseRef.current = null;
250
+ }
251
+ if (generationRef.current === activeGeneration) {
252
+ runningRef.current = false;
253
+ }
254
+ },
255
+ []
256
+ );
257
+ react.useLayoutEffect(() => {
258
+ void stopCurrentRun();
259
+ graphRef.current = graph;
260
+ targetsRef.current = targets;
261
+ getCameraOriginRef.current = getCameraOrigin;
262
+ lastCameraOriginRef.current = null;
263
+ runningRef.current = false;
264
+ generationRef.current += 1;
265
+ clearError();
266
+ return () => {
267
+ generationRef.current += 1;
268
+ runningRef.current = false;
269
+ void stopCurrentRun();
270
+ };
271
+ }, [clearError, getCameraOrigin, graph, stopCurrentRun, targets]);
272
+ fiber.useFrame((state) => {
273
+ if (runningRef.current) return;
274
+ const renderer = state.gl;
275
+ if (!isWebGpuRenderer(renderer)) {
276
+ reportError(
277
+ new Error(WEBGPU_RENDERER_ERROR),
278
+ `renderer:${WEBGPU_RENDERER_ERROR}`
279
+ );
280
+ return;
281
+ }
282
+ const activeGeneration = generationRef.current;
283
+ const activeRunController = new AbortController();
284
+ runningRef.current = true;
285
+ runAbortControllerRef.current = activeRunController;
286
+ const runPromise = (async () => {
287
+ try {
288
+ const activeGraph = graphRef.current;
289
+ const activeTargets = targetsRef.current;
290
+ updateCameraOrigin(state, activeGraph);
291
+ const report = await activeGraph.run({
292
+ targets: activeTargets,
293
+ signal: activeRunController.signal,
294
+ resources: {
295
+ renderer
296
+ }
297
+ });
298
+ if (report.status === "error") {
299
+ reportError(new Error(GRAPH_RUN_ERROR), `graph:${GRAPH_RUN_ERROR}`);
300
+ return;
301
+ }
302
+ clearError();
303
+ } catch (error) {
304
+ if (activeRunController.signal.aborted) return;
305
+ reportError(error);
306
+ } finally {
307
+ finishRun(activeRunController, activeGeneration);
308
+ }
309
+ })();
310
+ runPromiseRef.current = runPromise;
311
+ });
312
+ return stopCurrentRun;
313
+ }
314
+
315
+ function useTerrain(options = {}) {
316
+ const runtime = react.useMemo(
317
+ () => ({
318
+ query: null,
319
+ raycast: null
320
+ }),
321
+ []
322
+ );
323
+ const isMountedRef = react.useRef(true);
324
+ const graphLifecycleRef = react.useRef(0);
325
+ const terrainNodesRef = react.useRef({
326
+ positionNode: null
327
+ });
328
+ const [terrainNodes, setTerrainNodes] = react.useState(terrainNodesRef.current);
329
+ const readyRef = react.useRef(false);
330
+ const [ready, setReady] = react.useState(false);
331
+ const emitTerrainNodes = react.useCallback(
332
+ (nextTerrainNodes) => {
333
+ if (!isMountedRef.current) return;
334
+ setTerrainNodes((prevTerrainNodes) => {
335
+ if (prevTerrainNodes.positionNode === nextTerrainNodes.positionNode) {
336
+ terrainNodesRef.current = prevTerrainNodes;
337
+ return prevTerrainNodes;
338
+ }
339
+ terrainNodesRef.current = nextTerrainNodes;
340
+ return nextTerrainNodes;
341
+ });
342
+ },
343
+ []
344
+ );
345
+ const getTerrainNodes = react.useCallback(
346
+ () => terrainNodesRef.current,
347
+ []
348
+ );
349
+ const emitReady = react.useCallback((nextReady) => {
350
+ if (!isMountedRef.current) return;
351
+ readyRef.current = nextReady;
352
+ setReady((prevReady) => prevReady === nextReady ? prevReady : nextReady);
353
+ }, []);
354
+ const getReady = react.useCallback(
355
+ () => readyRef.current,
356
+ []
357
+ );
358
+ const syncTerrainRuntimeTask = react.useMemo(
359
+ () => createSyncTerrainRuntimeTask(runtime),
360
+ [runtime]
361
+ );
362
+ const syncTerrainNodesTask = react.useMemo(
363
+ () => createSyncTerrainNodesTask(
364
+ getTerrainNodes,
365
+ emitTerrainNodes,
366
+ getReady,
367
+ emitReady
368
+ ),
369
+ [emitReady, emitTerrainNodes, getReady, getTerrainNodes]
370
+ );
371
+ const graph = react.useMemo(() => {
372
+ const nextGraph = three.terrainGraph();
373
+ for (const task of options.tasks ?? []) {
374
+ nextGraph.add(task);
375
+ }
376
+ nextGraph.add(syncTerrainRuntimeTask);
377
+ nextGraph.add(syncTerrainNodesTask);
378
+ return nextGraph;
379
+ }, [options.tasks, syncTerrainNodesTask, syncTerrainRuntimeTask]);
380
+ const runnerTargets = react.useMemo(
381
+ () => {
382
+ const userTasks = options.tasks ?? [];
383
+ return [
384
+ ...userTasks,
385
+ three.terrainTasks.executeCompute,
386
+ three.terrainTasks.terrainReadback,
387
+ syncTerrainRuntimeTask,
388
+ syncTerrainNodesTask
389
+ ];
390
+ },
391
+ [options.tasks, syncTerrainNodesTask, syncTerrainRuntimeTask]
392
+ );
393
+ const stopTerrainRunner = useTerrainRunner({
394
+ graph,
395
+ targets: runnerTargets,
396
+ getCameraOrigin: options.getCameraOrigin,
397
+ cameraHysteresis: options.cameraHysteresis
398
+ });
399
+ react.useEffect(() => {
400
+ graphLifecycleRef.current += 1;
401
+ const lifecycle = graphLifecycleRef.current;
402
+ isMountedRef.current = true;
403
+ return () => {
404
+ isMountedRef.current = false;
405
+ queueMicrotask(() => {
406
+ if (graphLifecycleRef.current !== lifecycle) return;
407
+ void stopTerrainRunner().finally(() => {
408
+ if (graphLifecycleRef.current !== lifecycle) return;
409
+ graph.dispose();
410
+ });
411
+ });
412
+ };
413
+ }, [graph, stopTerrainRunner]);
414
+ react.useLayoutEffect(() => {
415
+ runtime.query = null;
416
+ runtime.raycast = null;
417
+ const nextTerrainNodes = {
418
+ positionNode: null
419
+ };
420
+ terrainNodesRef.current = nextTerrainNodes;
421
+ readyRef.current = false;
422
+ setTerrainNodes(nextTerrainNodes);
423
+ setReady(false);
424
+ }, [graph, runtime]);
425
+ useTerrainParams(graph, options);
426
+ return react.useMemo(
427
+ () => ({
428
+ graph,
429
+ tasks: three.terrainTasks,
430
+ runtime,
431
+ ready,
432
+ ...terrainNodes
433
+ }),
434
+ [graph, ready, runtime, terrainNodes]
435
+ );
436
+ }
437
+
438
+ function useTerrainMesh(innerTileSegments, maxNodes) {
439
+ const [mesh] = react.useState(
440
+ () => new three.TerrainMesh({
441
+ innerTileSegments: innerTileSegments ?? 13,
442
+ maxNodes: maxNodes ?? 1024
443
+ })
444
+ );
445
+ react.useEffect(() => {
446
+ mesh.innerTileSegments = innerTileSegments ?? 13;
447
+ }, [mesh, innerTileSegments]);
448
+ react.useEffect(() => {
449
+ mesh.maxNodes = maxNodes ?? 1024;
450
+ }, [mesh, maxNodes]);
451
+ react.useEffect(() => {
452
+ return () => {
453
+ mesh.geometry.dispose();
454
+ };
455
+ }, [mesh]);
456
+ return mesh;
457
+ }
458
+ function syncTerrainMesh(mesh, terrain) {
459
+ const leaves = terrain.graph.peek(three.terrainTasks.quadtreeUpdate);
460
+ if (leaves && mesh.count !== leaves.count) {
461
+ mesh.count = leaves.count;
462
+ mesh.instanceMatrix.needsUpdate = true;
463
+ }
464
+ const raycast = terrain.runtime.raycast;
465
+ if (mesh.terrainRaycast !== raycast) {
466
+ mesh.terrainRaycast = raycast;
467
+ }
468
+ }
469
+ function attachTerrainMaterial(node, terrainNodes) {
470
+ if (!react.isValidElement(node)) return node;
471
+ const nextKey = `terrain-material-${terrainNodes.positionNode?.id ?? "null"}`;
472
+ if ("attach" in node.props && node.props.attach != null) {
473
+ return react.cloneElement(node, { key: nextKey });
474
+ }
475
+ return react.cloneElement(node, { attach: "material", key: nextKey });
476
+ }
477
+ function TerrainWithHandle({
478
+ terrain,
479
+ children,
480
+ innerTileSegments,
481
+ maxNodes,
482
+ ...primitiveProps
483
+ }) {
484
+ const mesh = useTerrainMesh(innerTileSegments, maxNodes);
485
+ const { visible: primitiveVisible = true, ...restPrimitiveProps } = primitiveProps;
486
+ fiber.useFrame(() => {
487
+ syncTerrainMesh(mesh, terrain);
488
+ });
489
+ return /* @__PURE__ */ jsxRuntime.jsx(TerrainProvider, { value: terrain, children: /* @__PURE__ */ jsxRuntime.jsx(
490
+ "primitive",
491
+ {
492
+ object: mesh,
493
+ visible: terrain.ready && primitiveVisible,
494
+ ...restPrimitiveProps,
495
+ children: terrain.ready ? attachTerrainMaterial(
496
+ children({
497
+ positionNode: terrain.positionNode
498
+ }),
499
+ terrain
500
+ ) : null
501
+ }
502
+ ) });
503
+ }
504
+ function InternalTerrain(props) {
505
+ const {
506
+ children,
507
+ rootSize,
508
+ origin,
509
+ maxLevel,
510
+ innerTileSegments,
511
+ skirtScale,
512
+ elevationScale,
513
+ elevation,
514
+ surface,
515
+ terrainFieldFilter,
516
+ getCameraOrigin,
517
+ cameraHysteresis,
518
+ tasks,
519
+ maxNodes,
520
+ ...primitiveProps
521
+ } = props;
522
+ const terrain = useTerrain({
523
+ rootSize,
524
+ origin,
525
+ maxLevel,
526
+ innerTileSegments,
527
+ skirtScale,
528
+ elevationScale,
529
+ elevation,
530
+ surface,
531
+ terrainFieldFilter,
532
+ getCameraOrigin,
533
+ cameraHysteresis,
534
+ tasks,
535
+ maxNodes
536
+ });
537
+ return /* @__PURE__ */ jsxRuntime.jsx(
538
+ TerrainWithHandle,
539
+ {
540
+ terrain,
541
+ innerTileSegments,
542
+ maxNodes,
543
+ ...primitiveProps,
544
+ children
545
+ }
546
+ );
547
+ }
548
+ function Terrain({
549
+ terrain: providedTerrain,
550
+ children,
551
+ rootSize,
552
+ origin,
553
+ maxLevel,
554
+ innerTileSegments,
555
+ skirtScale,
556
+ elevationScale,
557
+ elevation,
558
+ surface,
559
+ terrainFieldFilter,
560
+ getCameraOrigin,
561
+ cameraHysteresis,
562
+ tasks,
563
+ maxNodes,
564
+ ...primitiveProps
565
+ }) {
566
+ if (providedTerrain) {
567
+ return /* @__PURE__ */ jsxRuntime.jsx(
568
+ TerrainWithHandle,
569
+ {
570
+ terrain: providedTerrain,
571
+ innerTileSegments,
572
+ maxNodes,
573
+ ...primitiveProps,
574
+ children
575
+ }
576
+ );
577
+ }
578
+ return /* @__PURE__ */ jsxRuntime.jsx(
579
+ InternalTerrain,
580
+ {
581
+ rootSize,
582
+ origin,
583
+ maxLevel,
584
+ innerTileSegments,
585
+ skirtScale,
586
+ elevationScale,
587
+ elevation,
588
+ surface,
589
+ terrainFieldFilter,
590
+ getCameraOrigin,
591
+ cameraHysteresis,
592
+ tasks,
593
+ maxNodes,
594
+ ...primitiveProps,
595
+ children
596
+ }
597
+ );
598
+ }
599
+
600
+ exports.Terrain = Terrain;
601
+ exports.TerrainProvider = TerrainProvider;
602
+ exports.useTerrain = useTerrain;
603
+ exports.useTerrainContext = useTerrainContext;