@hello-terrain/react 0.0.0-alpha.10 → 0.0.0-alpha.11
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/dist/index.cjs +78 -77
- package/dist/index.d.cts +9 -4
- package/dist/index.d.mts +9 -4
- package/dist/index.d.ts +9 -4
- package/dist/index.mjs +79 -78
- package/package.json +6 -5
package/dist/index.cjs
CHANGED
|
@@ -21,10 +21,13 @@ function useTerrainContext() {
|
|
|
21
21
|
|
|
22
22
|
function createSyncTerrainRuntimeTask(runtime) {
|
|
23
23
|
return work.task((get, work) => {
|
|
24
|
-
const
|
|
24
|
+
const queryContext = get(three.terrainTasks.terrainQuery);
|
|
25
|
+
const query = queryContext.query;
|
|
26
|
+
const sphereQuery = queryContext.sphereQuery;
|
|
25
27
|
const raycast = get(three.terrainTasks.terrainRaycast);
|
|
26
28
|
return work(() => {
|
|
27
29
|
runtime.query = query;
|
|
30
|
+
runtime.sphereQuery = sphereQuery;
|
|
28
31
|
runtime.raycast = raycast;
|
|
29
32
|
return runtime;
|
|
30
33
|
});
|
|
@@ -73,8 +76,9 @@ function useTerrainParams(graph, options) {
|
|
|
73
76
|
innerTileSegments: nextInnerTileSegments,
|
|
74
77
|
skirtScale: nextSkirtScale,
|
|
75
78
|
elevationScale: nextElevationScale,
|
|
79
|
+
radius: nextRadius,
|
|
76
80
|
elevation,
|
|
77
|
-
|
|
81
|
+
topology: nextTopology,
|
|
78
82
|
terrainFieldFilter: nextTerrainFieldFilter
|
|
79
83
|
} = options;
|
|
80
84
|
react.useLayoutEffect(() => {
|
|
@@ -132,9 +136,16 @@ function useTerrainParams(graph, options) {
|
|
|
132
136
|
resetOrSet(
|
|
133
137
|
graph,
|
|
134
138
|
ownedParamIds,
|
|
135
|
-
three.
|
|
136
|
-
|
|
137
|
-
() =>
|
|
139
|
+
three.radius,
|
|
140
|
+
nextRadius,
|
|
141
|
+
() => nextRadius
|
|
142
|
+
);
|
|
143
|
+
resetOrSet(
|
|
144
|
+
graph,
|
|
145
|
+
ownedParamIds,
|
|
146
|
+
three.topology,
|
|
147
|
+
nextTopology,
|
|
148
|
+
() => nextTopology
|
|
138
149
|
);
|
|
139
150
|
resetOrSet(
|
|
140
151
|
graph,
|
|
@@ -161,8 +172,9 @@ function useTerrainParams(graph, options) {
|
|
|
161
172
|
nextInnerTileSegments,
|
|
162
173
|
nextSkirtScale,
|
|
163
174
|
nextElevationScale,
|
|
175
|
+
nextRadius,
|
|
164
176
|
elevation,
|
|
165
|
-
|
|
177
|
+
nextTopology,
|
|
166
178
|
nextTerrainFieldFilter
|
|
167
179
|
]);
|
|
168
180
|
}
|
|
@@ -316,6 +328,7 @@ function useTerrain(options = {}) {
|
|
|
316
328
|
const runtime = react.useMemo(
|
|
317
329
|
() => ({
|
|
318
330
|
query: null,
|
|
331
|
+
sphereQuery: null,
|
|
319
332
|
raycast: null
|
|
320
333
|
}),
|
|
321
334
|
[]
|
|
@@ -328,44 +341,27 @@ function useTerrain(options = {}) {
|
|
|
328
341
|
const [terrainNodes, setTerrainNodes] = react.useState(terrainNodesRef.current);
|
|
329
342
|
const readyRef = react.useRef(false);
|
|
330
343
|
const [ready, setReady] = react.useState(false);
|
|
331
|
-
const emitTerrainNodes = react.useCallback(
|
|
332
|
-
(
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
[]
|
|
344
|
-
);
|
|
345
|
-
const getTerrainNodes = react.useCallback(
|
|
346
|
-
() => terrainNodesRef.current,
|
|
347
|
-
[]
|
|
348
|
-
);
|
|
344
|
+
const emitTerrainNodes = react.useCallback((nextTerrainNodes) => {
|
|
345
|
+
if (!isMountedRef.current) return;
|
|
346
|
+
setTerrainNodes((prevTerrainNodes) => {
|
|
347
|
+
if (prevTerrainNodes.positionNode === nextTerrainNodes.positionNode) {
|
|
348
|
+
terrainNodesRef.current = prevTerrainNodes;
|
|
349
|
+
return prevTerrainNodes;
|
|
350
|
+
}
|
|
351
|
+
terrainNodesRef.current = nextTerrainNodes;
|
|
352
|
+
return nextTerrainNodes;
|
|
353
|
+
});
|
|
354
|
+
}, []);
|
|
355
|
+
const getTerrainNodes = react.useCallback(() => terrainNodesRef.current, []);
|
|
349
356
|
const emitReady = react.useCallback((nextReady) => {
|
|
350
357
|
if (!isMountedRef.current) return;
|
|
351
358
|
readyRef.current = nextReady;
|
|
352
359
|
setReady((prevReady) => prevReady === nextReady ? prevReady : nextReady);
|
|
353
360
|
}, []);
|
|
354
|
-
const getReady = react.useCallback(
|
|
355
|
-
|
|
356
|
-
[]
|
|
357
|
-
);
|
|
358
|
-
const syncTerrainRuntimeTask = react.useMemo(
|
|
359
|
-
() => createSyncTerrainRuntimeTask(runtime),
|
|
360
|
-
[runtime]
|
|
361
|
-
);
|
|
361
|
+
const getReady = react.useCallback(() => readyRef.current, []);
|
|
362
|
+
const syncTerrainRuntimeTask = react.useMemo(() => createSyncTerrainRuntimeTask(runtime), [runtime]);
|
|
362
363
|
const syncTerrainNodesTask = react.useMemo(
|
|
363
|
-
() => createSyncTerrainNodesTask(
|
|
364
|
-
getTerrainNodes,
|
|
365
|
-
emitTerrainNodes,
|
|
366
|
-
getReady,
|
|
367
|
-
emitReady
|
|
368
|
-
),
|
|
364
|
+
() => createSyncTerrainNodesTask(getTerrainNodes, emitTerrainNodes, getReady, emitReady),
|
|
369
365
|
[emitReady, emitTerrainNodes, getReady, getTerrainNodes]
|
|
370
366
|
);
|
|
371
367
|
const graph = react.useMemo(() => {
|
|
@@ -377,19 +373,17 @@ function useTerrain(options = {}) {
|
|
|
377
373
|
nextGraph.add(syncTerrainNodesTask);
|
|
378
374
|
return nextGraph;
|
|
379
375
|
}, [options.tasks, syncTerrainNodesTask, syncTerrainRuntimeTask]);
|
|
380
|
-
const runnerTargets = react.useMemo(
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
[options.tasks, syncTerrainNodesTask, syncTerrainRuntimeTask]
|
|
392
|
-
);
|
|
376
|
+
const runnerTargets = react.useMemo(() => {
|
|
377
|
+
const userTasks = options.tasks ?? [];
|
|
378
|
+
return [
|
|
379
|
+
...userTasks,
|
|
380
|
+
three.terrainTasks.executeCompute,
|
|
381
|
+
three.terrainTasks.terrainReadback,
|
|
382
|
+
three.terrainTasks.gpuSpatialIndexUpload,
|
|
383
|
+
syncTerrainRuntimeTask,
|
|
384
|
+
syncTerrainNodesTask
|
|
385
|
+
];
|
|
386
|
+
}, [options.tasks, syncTerrainNodesTask, syncTerrainRuntimeTask]);
|
|
393
387
|
const stopTerrainRunner = useTerrainRunner({
|
|
394
388
|
graph,
|
|
395
389
|
targets: runnerTargets,
|
|
@@ -423,31 +417,34 @@ function useTerrain(options = {}) {
|
|
|
423
417
|
setReady(false);
|
|
424
418
|
}, [graph, runtime]);
|
|
425
419
|
useTerrainParams(graph, options);
|
|
420
|
+
const topology = options.topology ?? null;
|
|
426
421
|
return react.useMemo(
|
|
427
422
|
() => ({
|
|
428
423
|
graph,
|
|
429
424
|
tasks: three.terrainTasks,
|
|
430
425
|
runtime,
|
|
431
426
|
ready,
|
|
427
|
+
topology,
|
|
432
428
|
...terrainNodes
|
|
433
429
|
}),
|
|
434
|
-
[graph, ready, runtime, terrainNodes]
|
|
430
|
+
[graph, ready, runtime, topology, terrainNodes]
|
|
435
431
|
);
|
|
436
432
|
}
|
|
437
433
|
|
|
438
|
-
function useTerrainMesh(innerTileSegments, maxNodes) {
|
|
434
|
+
function useTerrainMesh(innerTileSegments, maxNodes, flipWinding) {
|
|
439
435
|
const [mesh] = react.useState(
|
|
440
436
|
() => new three.TerrainMesh({
|
|
441
|
-
innerTileSegments
|
|
442
|
-
maxNodes: maxNodes ?? 1024
|
|
437
|
+
...innerTileSegments !== void 0 ? { innerTileSegments } : {},
|
|
438
|
+
maxNodes: maxNodes ?? 1024,
|
|
439
|
+
flipWinding
|
|
443
440
|
})
|
|
444
441
|
);
|
|
445
|
-
react.useEffect(() => {
|
|
446
|
-
mesh.innerTileSegments = innerTileSegments ?? 13;
|
|
447
|
-
}, [mesh, innerTileSegments]);
|
|
448
442
|
react.useEffect(() => {
|
|
449
443
|
mesh.maxNodes = maxNodes ?? 1024;
|
|
450
444
|
}, [mesh, maxNodes]);
|
|
445
|
+
react.useEffect(() => {
|
|
446
|
+
mesh.flipWinding = flipWinding;
|
|
447
|
+
}, [mesh, flipWinding]);
|
|
451
448
|
react.useEffect(() => {
|
|
452
449
|
return () => {
|
|
453
450
|
mesh.geometry.dispose();
|
|
@@ -461,6 +458,13 @@ function syncTerrainMesh(mesh, terrain) {
|
|
|
461
458
|
mesh.count = leaves.count;
|
|
462
459
|
mesh.instanceMatrix.needsUpdate = true;
|
|
463
460
|
}
|
|
461
|
+
const uniforms = terrain.graph.peek(three.terrainTasks.updateUniforms);
|
|
462
|
+
if (uniforms) {
|
|
463
|
+
const segments = uniforms.uInnerTileSegments.value;
|
|
464
|
+
if (typeof segments === "number") {
|
|
465
|
+
mesh.innerTileSegments = segments;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
464
468
|
const raycast = terrain.runtime.raycast;
|
|
465
469
|
if (mesh.terrainRaycast !== raycast) {
|
|
466
470
|
mesh.terrainRaycast = raycast;
|
|
@@ -481,25 +485,18 @@ function TerrainWithHandle({
|
|
|
481
485
|
maxNodes,
|
|
482
486
|
...primitiveProps
|
|
483
487
|
}) {
|
|
484
|
-
const
|
|
488
|
+
const flipWinding = (terrain.topology?.projection ?? "flat") === "cubeSphere";
|
|
489
|
+
const mesh = useTerrainMesh(innerTileSegments, maxNodes, flipWinding);
|
|
485
490
|
const { visible: primitiveVisible = true, ...restPrimitiveProps } = primitiveProps;
|
|
486
491
|
fiber.useFrame(() => {
|
|
487
492
|
syncTerrainMesh(mesh, terrain);
|
|
488
493
|
});
|
|
489
|
-
return /* @__PURE__ */ jsxRuntime.jsx(TerrainProvider, { value: terrain, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
children: terrain.ready ? attachTerrainMaterial(
|
|
496
|
-
children({
|
|
497
|
-
positionNode: terrain.positionNode
|
|
498
|
-
}),
|
|
499
|
-
terrain
|
|
500
|
-
) : null
|
|
501
|
-
}
|
|
502
|
-
) });
|
|
494
|
+
return /* @__PURE__ */ jsxRuntime.jsx(TerrainProvider, { value: terrain, children: /* @__PURE__ */ jsxRuntime.jsx("primitive", { object: mesh, visible: terrain.ready && primitiveVisible, ...restPrimitiveProps, children: terrain.ready ? attachTerrainMaterial(
|
|
495
|
+
children({
|
|
496
|
+
positionNode: terrain.positionNode
|
|
497
|
+
}),
|
|
498
|
+
terrain
|
|
499
|
+
) : null }) });
|
|
503
500
|
}
|
|
504
501
|
function InternalTerrain(props) {
|
|
505
502
|
const {
|
|
@@ -510,8 +507,9 @@ function InternalTerrain(props) {
|
|
|
510
507
|
innerTileSegments,
|
|
511
508
|
skirtScale,
|
|
512
509
|
elevationScale,
|
|
510
|
+
radius,
|
|
513
511
|
elevation,
|
|
514
|
-
|
|
512
|
+
topology,
|
|
515
513
|
terrainFieldFilter,
|
|
516
514
|
getCameraOrigin,
|
|
517
515
|
cameraHysteresis,
|
|
@@ -526,8 +524,9 @@ function InternalTerrain(props) {
|
|
|
526
524
|
innerTileSegments,
|
|
527
525
|
skirtScale,
|
|
528
526
|
elevationScale,
|
|
527
|
+
radius,
|
|
529
528
|
elevation,
|
|
530
|
-
|
|
529
|
+
topology,
|
|
531
530
|
terrainFieldFilter,
|
|
532
531
|
getCameraOrigin,
|
|
533
532
|
cameraHysteresis,
|
|
@@ -554,8 +553,9 @@ function Terrain({
|
|
|
554
553
|
innerTileSegments,
|
|
555
554
|
skirtScale,
|
|
556
555
|
elevationScale,
|
|
556
|
+
radius,
|
|
557
557
|
elevation,
|
|
558
|
-
|
|
558
|
+
topology,
|
|
559
559
|
terrainFieldFilter,
|
|
560
560
|
getCameraOrigin,
|
|
561
561
|
cameraHysteresis,
|
|
@@ -584,8 +584,9 @@ function Terrain({
|
|
|
584
584
|
innerTileSegments,
|
|
585
585
|
skirtScale,
|
|
586
586
|
elevationScale,
|
|
587
|
+
radius,
|
|
587
588
|
elevation,
|
|
588
|
-
|
|
589
|
+
topology,
|
|
589
590
|
terrainFieldFilter,
|
|
590
591
|
getCameraOrigin,
|
|
591
592
|
cameraHysteresis,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { ElevationCallback,
|
|
2
|
+
import { ElevationCallback, Topology, TerrainGraph, TerrainTasks, TerrainQuery, TerrainSphereQuery, TerrainRaycast } from '@hello-terrain/three';
|
|
3
|
+
export { TerrainQuery, TerrainRaycast, TerrainRaycastResult, TerrainSample, TerrainSphereQuery, TerrainSurfaceSample, TerrainSurfaceSampleBatch, TerrainTile } from '@hello-terrain/three';
|
|
3
4
|
import { Task } from '@hello-terrain/work';
|
|
4
5
|
import { ThreeElements, RootState } from '@react-three/fiber';
|
|
5
6
|
import { ReactNode } from 'react';
|
|
6
|
-
import { WebGPURenderer } from 'three/webgpu';
|
|
7
7
|
import { ShaderCallNodeInternal } from 'three/src/nodes/TSL.js';
|
|
8
|
+
import { WebGPURenderer } from 'three/webgpu';
|
|
8
9
|
|
|
9
10
|
type TerrainVector3Like = {
|
|
10
11
|
x: number;
|
|
@@ -19,6 +20,8 @@ interface TerrainNodes {
|
|
|
19
20
|
}
|
|
20
21
|
interface TerrainRuntime {
|
|
21
22
|
query: TerrainQuery | null;
|
|
23
|
+
/** Cube-sphere query; `null` unless the topology uses the cubeSphere projection. */
|
|
24
|
+
sphereQuery: TerrainSphereQuery | null;
|
|
22
25
|
raycast: TerrainRaycast | null;
|
|
23
26
|
}
|
|
24
27
|
interface TerrainHandle extends TerrainNodes {
|
|
@@ -26,6 +29,7 @@ interface TerrainHandle extends TerrainNodes {
|
|
|
26
29
|
tasks: TerrainTasks;
|
|
27
30
|
runtime: TerrainRuntime;
|
|
28
31
|
ready: boolean;
|
|
32
|
+
topology?: Topology | null;
|
|
29
33
|
}
|
|
30
34
|
interface TerrainOptions {
|
|
31
35
|
rootSize?: number;
|
|
@@ -35,8 +39,9 @@ interface TerrainOptions {
|
|
|
35
39
|
innerTileSegments?: number;
|
|
36
40
|
skirtScale?: number;
|
|
37
41
|
elevationScale?: number;
|
|
42
|
+
radius?: number;
|
|
38
43
|
elevation?: ElevationCallback;
|
|
39
|
-
|
|
44
|
+
topology?: Topology | null;
|
|
40
45
|
terrainFieldFilter?: "nearest" | "linear";
|
|
41
46
|
getCameraOrigin?: (state: RootState) => TerrainVector3Like;
|
|
42
47
|
cameraHysteresis?: number;
|
|
@@ -48,7 +53,7 @@ interface TerrainProps extends TerrainPrimitiveProps, TerrainOptions {
|
|
|
48
53
|
children: (nodes: TerrainNodes) => ReactNode;
|
|
49
54
|
}
|
|
50
55
|
|
|
51
|
-
declare function Terrain({ terrain: providedTerrain, children, rootSize, origin, maxLevel, innerTileSegments, skirtScale, elevationScale, elevation,
|
|
56
|
+
declare function Terrain({ terrain: providedTerrain, children, rootSize, origin, maxLevel, innerTileSegments, skirtScale, elevationScale, radius, elevation, topology, terrainFieldFilter, getCameraOrigin, cameraHysteresis, tasks, maxNodes, ...primitiveProps }: TerrainProps): react_jsx_runtime.JSX.Element;
|
|
52
57
|
|
|
53
58
|
interface TerrainProviderProps {
|
|
54
59
|
value: TerrainHandle;
|
package/dist/index.d.mts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { ElevationCallback,
|
|
2
|
+
import { ElevationCallback, Topology, TerrainGraph, TerrainTasks, TerrainQuery, TerrainSphereQuery, TerrainRaycast } from '@hello-terrain/three';
|
|
3
|
+
export { TerrainQuery, TerrainRaycast, TerrainRaycastResult, TerrainSample, TerrainSphereQuery, TerrainSurfaceSample, TerrainSurfaceSampleBatch, TerrainTile } from '@hello-terrain/three';
|
|
3
4
|
import { Task } from '@hello-terrain/work';
|
|
4
5
|
import { ThreeElements, RootState } from '@react-three/fiber';
|
|
5
6
|
import { ReactNode } from 'react';
|
|
6
|
-
import { WebGPURenderer } from 'three/webgpu';
|
|
7
7
|
import { ShaderCallNodeInternal } from 'three/src/nodes/TSL.js';
|
|
8
|
+
import { WebGPURenderer } from 'three/webgpu';
|
|
8
9
|
|
|
9
10
|
type TerrainVector3Like = {
|
|
10
11
|
x: number;
|
|
@@ -19,6 +20,8 @@ interface TerrainNodes {
|
|
|
19
20
|
}
|
|
20
21
|
interface TerrainRuntime {
|
|
21
22
|
query: TerrainQuery | null;
|
|
23
|
+
/** Cube-sphere query; `null` unless the topology uses the cubeSphere projection. */
|
|
24
|
+
sphereQuery: TerrainSphereQuery | null;
|
|
22
25
|
raycast: TerrainRaycast | null;
|
|
23
26
|
}
|
|
24
27
|
interface TerrainHandle extends TerrainNodes {
|
|
@@ -26,6 +29,7 @@ interface TerrainHandle extends TerrainNodes {
|
|
|
26
29
|
tasks: TerrainTasks;
|
|
27
30
|
runtime: TerrainRuntime;
|
|
28
31
|
ready: boolean;
|
|
32
|
+
topology?: Topology | null;
|
|
29
33
|
}
|
|
30
34
|
interface TerrainOptions {
|
|
31
35
|
rootSize?: number;
|
|
@@ -35,8 +39,9 @@ interface TerrainOptions {
|
|
|
35
39
|
innerTileSegments?: number;
|
|
36
40
|
skirtScale?: number;
|
|
37
41
|
elevationScale?: number;
|
|
42
|
+
radius?: number;
|
|
38
43
|
elevation?: ElevationCallback;
|
|
39
|
-
|
|
44
|
+
topology?: Topology | null;
|
|
40
45
|
terrainFieldFilter?: "nearest" | "linear";
|
|
41
46
|
getCameraOrigin?: (state: RootState) => TerrainVector3Like;
|
|
42
47
|
cameraHysteresis?: number;
|
|
@@ -48,7 +53,7 @@ interface TerrainProps extends TerrainPrimitiveProps, TerrainOptions {
|
|
|
48
53
|
children: (nodes: TerrainNodes) => ReactNode;
|
|
49
54
|
}
|
|
50
55
|
|
|
51
|
-
declare function Terrain({ terrain: providedTerrain, children, rootSize, origin, maxLevel, innerTileSegments, skirtScale, elevationScale, elevation,
|
|
56
|
+
declare function Terrain({ terrain: providedTerrain, children, rootSize, origin, maxLevel, innerTileSegments, skirtScale, elevationScale, radius, elevation, topology, terrainFieldFilter, getCameraOrigin, cameraHysteresis, tasks, maxNodes, ...primitiveProps }: TerrainProps): react_jsx_runtime.JSX.Element;
|
|
52
57
|
|
|
53
58
|
interface TerrainProviderProps {
|
|
54
59
|
value: TerrainHandle;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { ElevationCallback,
|
|
2
|
+
import { ElevationCallback, Topology, TerrainGraph, TerrainTasks, TerrainQuery, TerrainSphereQuery, TerrainRaycast } from '@hello-terrain/three';
|
|
3
|
+
export { TerrainQuery, TerrainRaycast, TerrainRaycastResult, TerrainSample, TerrainSphereQuery, TerrainSurfaceSample, TerrainSurfaceSampleBatch, TerrainTile } from '@hello-terrain/three';
|
|
3
4
|
import { Task } from '@hello-terrain/work';
|
|
4
5
|
import { ThreeElements, RootState } from '@react-three/fiber';
|
|
5
6
|
import { ReactNode } from 'react';
|
|
6
|
-
import { WebGPURenderer } from 'three/webgpu';
|
|
7
7
|
import { ShaderCallNodeInternal } from 'three/src/nodes/TSL.js';
|
|
8
|
+
import { WebGPURenderer } from 'three/webgpu';
|
|
8
9
|
|
|
9
10
|
type TerrainVector3Like = {
|
|
10
11
|
x: number;
|
|
@@ -19,6 +20,8 @@ interface TerrainNodes {
|
|
|
19
20
|
}
|
|
20
21
|
interface TerrainRuntime {
|
|
21
22
|
query: TerrainQuery | null;
|
|
23
|
+
/** Cube-sphere query; `null` unless the topology uses the cubeSphere projection. */
|
|
24
|
+
sphereQuery: TerrainSphereQuery | null;
|
|
22
25
|
raycast: TerrainRaycast | null;
|
|
23
26
|
}
|
|
24
27
|
interface TerrainHandle extends TerrainNodes {
|
|
@@ -26,6 +29,7 @@ interface TerrainHandle extends TerrainNodes {
|
|
|
26
29
|
tasks: TerrainTasks;
|
|
27
30
|
runtime: TerrainRuntime;
|
|
28
31
|
ready: boolean;
|
|
32
|
+
topology?: Topology | null;
|
|
29
33
|
}
|
|
30
34
|
interface TerrainOptions {
|
|
31
35
|
rootSize?: number;
|
|
@@ -35,8 +39,9 @@ interface TerrainOptions {
|
|
|
35
39
|
innerTileSegments?: number;
|
|
36
40
|
skirtScale?: number;
|
|
37
41
|
elevationScale?: number;
|
|
42
|
+
radius?: number;
|
|
38
43
|
elevation?: ElevationCallback;
|
|
39
|
-
|
|
44
|
+
topology?: Topology | null;
|
|
40
45
|
terrainFieldFilter?: "nearest" | "linear";
|
|
41
46
|
getCameraOrigin?: (state: RootState) => TerrainVector3Like;
|
|
42
47
|
cameraHysteresis?: number;
|
|
@@ -48,7 +53,7 @@ interface TerrainProps extends TerrainPrimitiveProps, TerrainOptions {
|
|
|
48
53
|
children: (nodes: TerrainNodes) => ReactNode;
|
|
49
54
|
}
|
|
50
55
|
|
|
51
|
-
declare function Terrain({ terrain: providedTerrain, children, rootSize, origin, maxLevel, innerTileSegments, skirtScale, elevationScale, elevation,
|
|
56
|
+
declare function Terrain({ terrain: providedTerrain, children, rootSize, origin, maxLevel, innerTileSegments, skirtScale, elevationScale, radius, elevation, topology, terrainFieldFilter, getCameraOrigin, cameraHysteresis, tasks, maxNodes, ...primitiveProps }: TerrainProps): react_jsx_runtime.JSX.Element;
|
|
52
57
|
|
|
53
58
|
interface TerrainProviderProps {
|
|
54
59
|
value: TerrainHandle;
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
|
-
import { terrainTasks, elevationFn, rootSize, origin, maxLevel, maxNodes, innerTileSegments, skirtScale, elevationScale,
|
|
2
|
+
import { terrainTasks, elevationFn, rootSize, origin, maxLevel, maxNodes, innerTileSegments, skirtScale, elevationScale, radius, topology, terrainFieldFilter, quadtreeUpdate, terrainGraph, TerrainMesh } from '@hello-terrain/three';
|
|
3
3
|
import { useFrame } from '@react-three/fiber';
|
|
4
4
|
import { createContext, useContext, useRef, useLayoutEffect, useCallback, useMemo, useState, useEffect, isValidElement, cloneElement } from 'react';
|
|
5
5
|
import { task } from '@hello-terrain/work';
|
|
@@ -19,10 +19,13 @@ function useTerrainContext() {
|
|
|
19
19
|
|
|
20
20
|
function createSyncTerrainRuntimeTask(runtime) {
|
|
21
21
|
return task((get, work) => {
|
|
22
|
-
const
|
|
22
|
+
const queryContext = get(terrainTasks.terrainQuery);
|
|
23
|
+
const query = queryContext.query;
|
|
24
|
+
const sphereQuery = queryContext.sphereQuery;
|
|
23
25
|
const raycast = get(terrainTasks.terrainRaycast);
|
|
24
26
|
return work(() => {
|
|
25
27
|
runtime.query = query;
|
|
28
|
+
runtime.sphereQuery = sphereQuery;
|
|
26
29
|
runtime.raycast = raycast;
|
|
27
30
|
return runtime;
|
|
28
31
|
});
|
|
@@ -71,8 +74,9 @@ function useTerrainParams(graph, options) {
|
|
|
71
74
|
innerTileSegments: nextInnerTileSegments,
|
|
72
75
|
skirtScale: nextSkirtScale,
|
|
73
76
|
elevationScale: nextElevationScale,
|
|
77
|
+
radius: nextRadius,
|
|
74
78
|
elevation,
|
|
75
|
-
|
|
79
|
+
topology: nextTopology,
|
|
76
80
|
terrainFieldFilter: nextTerrainFieldFilter
|
|
77
81
|
} = options;
|
|
78
82
|
useLayoutEffect(() => {
|
|
@@ -130,9 +134,16 @@ function useTerrainParams(graph, options) {
|
|
|
130
134
|
resetOrSet(
|
|
131
135
|
graph,
|
|
132
136
|
ownedParamIds,
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
() =>
|
|
137
|
+
radius,
|
|
138
|
+
nextRadius,
|
|
139
|
+
() => nextRadius
|
|
140
|
+
);
|
|
141
|
+
resetOrSet(
|
|
142
|
+
graph,
|
|
143
|
+
ownedParamIds,
|
|
144
|
+
topology,
|
|
145
|
+
nextTopology,
|
|
146
|
+
() => nextTopology
|
|
136
147
|
);
|
|
137
148
|
resetOrSet(
|
|
138
149
|
graph,
|
|
@@ -159,8 +170,9 @@ function useTerrainParams(graph, options) {
|
|
|
159
170
|
nextInnerTileSegments,
|
|
160
171
|
nextSkirtScale,
|
|
161
172
|
nextElevationScale,
|
|
173
|
+
nextRadius,
|
|
162
174
|
elevation,
|
|
163
|
-
|
|
175
|
+
nextTopology,
|
|
164
176
|
nextTerrainFieldFilter
|
|
165
177
|
]);
|
|
166
178
|
}
|
|
@@ -314,6 +326,7 @@ function useTerrain(options = {}) {
|
|
|
314
326
|
const runtime = useMemo(
|
|
315
327
|
() => ({
|
|
316
328
|
query: null,
|
|
329
|
+
sphereQuery: null,
|
|
317
330
|
raycast: null
|
|
318
331
|
}),
|
|
319
332
|
[]
|
|
@@ -326,44 +339,27 @@ function useTerrain(options = {}) {
|
|
|
326
339
|
const [terrainNodes, setTerrainNodes] = useState(terrainNodesRef.current);
|
|
327
340
|
const readyRef = useRef(false);
|
|
328
341
|
const [ready, setReady] = useState(false);
|
|
329
|
-
const emitTerrainNodes = useCallback(
|
|
330
|
-
(
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
[]
|
|
342
|
-
);
|
|
343
|
-
const getTerrainNodes = useCallback(
|
|
344
|
-
() => terrainNodesRef.current,
|
|
345
|
-
[]
|
|
346
|
-
);
|
|
342
|
+
const emitTerrainNodes = useCallback((nextTerrainNodes) => {
|
|
343
|
+
if (!isMountedRef.current) return;
|
|
344
|
+
setTerrainNodes((prevTerrainNodes) => {
|
|
345
|
+
if (prevTerrainNodes.positionNode === nextTerrainNodes.positionNode) {
|
|
346
|
+
terrainNodesRef.current = prevTerrainNodes;
|
|
347
|
+
return prevTerrainNodes;
|
|
348
|
+
}
|
|
349
|
+
terrainNodesRef.current = nextTerrainNodes;
|
|
350
|
+
return nextTerrainNodes;
|
|
351
|
+
});
|
|
352
|
+
}, []);
|
|
353
|
+
const getTerrainNodes = useCallback(() => terrainNodesRef.current, []);
|
|
347
354
|
const emitReady = useCallback((nextReady) => {
|
|
348
355
|
if (!isMountedRef.current) return;
|
|
349
356
|
readyRef.current = nextReady;
|
|
350
357
|
setReady((prevReady) => prevReady === nextReady ? prevReady : nextReady);
|
|
351
358
|
}, []);
|
|
352
|
-
const getReady = useCallback(
|
|
353
|
-
|
|
354
|
-
[]
|
|
355
|
-
);
|
|
356
|
-
const syncTerrainRuntimeTask = useMemo(
|
|
357
|
-
() => createSyncTerrainRuntimeTask(runtime),
|
|
358
|
-
[runtime]
|
|
359
|
-
);
|
|
359
|
+
const getReady = useCallback(() => readyRef.current, []);
|
|
360
|
+
const syncTerrainRuntimeTask = useMemo(() => createSyncTerrainRuntimeTask(runtime), [runtime]);
|
|
360
361
|
const syncTerrainNodesTask = useMemo(
|
|
361
|
-
() => createSyncTerrainNodesTask(
|
|
362
|
-
getTerrainNodes,
|
|
363
|
-
emitTerrainNodes,
|
|
364
|
-
getReady,
|
|
365
|
-
emitReady
|
|
366
|
-
),
|
|
362
|
+
() => createSyncTerrainNodesTask(getTerrainNodes, emitTerrainNodes, getReady, emitReady),
|
|
367
363
|
[emitReady, emitTerrainNodes, getReady, getTerrainNodes]
|
|
368
364
|
);
|
|
369
365
|
const graph = useMemo(() => {
|
|
@@ -375,19 +371,17 @@ function useTerrain(options = {}) {
|
|
|
375
371
|
nextGraph.add(syncTerrainNodesTask);
|
|
376
372
|
return nextGraph;
|
|
377
373
|
}, [options.tasks, syncTerrainNodesTask, syncTerrainRuntimeTask]);
|
|
378
|
-
const runnerTargets = useMemo(
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
[options.tasks, syncTerrainNodesTask, syncTerrainRuntimeTask]
|
|
390
|
-
);
|
|
374
|
+
const runnerTargets = useMemo(() => {
|
|
375
|
+
const userTasks = options.tasks ?? [];
|
|
376
|
+
return [
|
|
377
|
+
...userTasks,
|
|
378
|
+
terrainTasks.executeCompute,
|
|
379
|
+
terrainTasks.terrainReadback,
|
|
380
|
+
terrainTasks.gpuSpatialIndexUpload,
|
|
381
|
+
syncTerrainRuntimeTask,
|
|
382
|
+
syncTerrainNodesTask
|
|
383
|
+
];
|
|
384
|
+
}, [options.tasks, syncTerrainNodesTask, syncTerrainRuntimeTask]);
|
|
391
385
|
const stopTerrainRunner = useTerrainRunner({
|
|
392
386
|
graph,
|
|
393
387
|
targets: runnerTargets,
|
|
@@ -421,31 +415,34 @@ function useTerrain(options = {}) {
|
|
|
421
415
|
setReady(false);
|
|
422
416
|
}, [graph, runtime]);
|
|
423
417
|
useTerrainParams(graph, options);
|
|
418
|
+
const topology = options.topology ?? null;
|
|
424
419
|
return useMemo(
|
|
425
420
|
() => ({
|
|
426
421
|
graph,
|
|
427
422
|
tasks: terrainTasks,
|
|
428
423
|
runtime,
|
|
429
424
|
ready,
|
|
425
|
+
topology,
|
|
430
426
|
...terrainNodes
|
|
431
427
|
}),
|
|
432
|
-
[graph, ready, runtime, terrainNodes]
|
|
428
|
+
[graph, ready, runtime, topology, terrainNodes]
|
|
433
429
|
);
|
|
434
430
|
}
|
|
435
431
|
|
|
436
|
-
function useTerrainMesh(innerTileSegments, maxNodes) {
|
|
432
|
+
function useTerrainMesh(innerTileSegments, maxNodes, flipWinding) {
|
|
437
433
|
const [mesh] = useState(
|
|
438
434
|
() => new TerrainMesh({
|
|
439
|
-
innerTileSegments
|
|
440
|
-
maxNodes: maxNodes ?? 1024
|
|
435
|
+
...innerTileSegments !== void 0 ? { innerTileSegments } : {},
|
|
436
|
+
maxNodes: maxNodes ?? 1024,
|
|
437
|
+
flipWinding
|
|
441
438
|
})
|
|
442
439
|
);
|
|
443
|
-
useEffect(() => {
|
|
444
|
-
mesh.innerTileSegments = innerTileSegments ?? 13;
|
|
445
|
-
}, [mesh, innerTileSegments]);
|
|
446
440
|
useEffect(() => {
|
|
447
441
|
mesh.maxNodes = maxNodes ?? 1024;
|
|
448
442
|
}, [mesh, maxNodes]);
|
|
443
|
+
useEffect(() => {
|
|
444
|
+
mesh.flipWinding = flipWinding;
|
|
445
|
+
}, [mesh, flipWinding]);
|
|
449
446
|
useEffect(() => {
|
|
450
447
|
return () => {
|
|
451
448
|
mesh.geometry.dispose();
|
|
@@ -459,6 +456,13 @@ function syncTerrainMesh(mesh, terrain) {
|
|
|
459
456
|
mesh.count = leaves.count;
|
|
460
457
|
mesh.instanceMatrix.needsUpdate = true;
|
|
461
458
|
}
|
|
459
|
+
const uniforms = terrain.graph.peek(terrainTasks.updateUniforms);
|
|
460
|
+
if (uniforms) {
|
|
461
|
+
const segments = uniforms.uInnerTileSegments.value;
|
|
462
|
+
if (typeof segments === "number") {
|
|
463
|
+
mesh.innerTileSegments = segments;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
462
466
|
const raycast = terrain.runtime.raycast;
|
|
463
467
|
if (mesh.terrainRaycast !== raycast) {
|
|
464
468
|
mesh.terrainRaycast = raycast;
|
|
@@ -479,25 +483,18 @@ function TerrainWithHandle({
|
|
|
479
483
|
maxNodes,
|
|
480
484
|
...primitiveProps
|
|
481
485
|
}) {
|
|
482
|
-
const
|
|
486
|
+
const flipWinding = (terrain.topology?.projection ?? "flat") === "cubeSphere";
|
|
487
|
+
const mesh = useTerrainMesh(innerTileSegments, maxNodes, flipWinding);
|
|
483
488
|
const { visible: primitiveVisible = true, ...restPrimitiveProps } = primitiveProps;
|
|
484
489
|
useFrame(() => {
|
|
485
490
|
syncTerrainMesh(mesh, terrain);
|
|
486
491
|
});
|
|
487
|
-
return /* @__PURE__ */ jsx(TerrainProvider, { value: terrain, children: /* @__PURE__ */ jsx(
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
children: terrain.ready ? attachTerrainMaterial(
|
|
494
|
-
children({
|
|
495
|
-
positionNode: terrain.positionNode
|
|
496
|
-
}),
|
|
497
|
-
terrain
|
|
498
|
-
) : null
|
|
499
|
-
}
|
|
500
|
-
) });
|
|
492
|
+
return /* @__PURE__ */ jsx(TerrainProvider, { value: terrain, children: /* @__PURE__ */ jsx("primitive", { object: mesh, visible: terrain.ready && primitiveVisible, ...restPrimitiveProps, children: terrain.ready ? attachTerrainMaterial(
|
|
493
|
+
children({
|
|
494
|
+
positionNode: terrain.positionNode
|
|
495
|
+
}),
|
|
496
|
+
terrain
|
|
497
|
+
) : null }) });
|
|
501
498
|
}
|
|
502
499
|
function InternalTerrain(props) {
|
|
503
500
|
const {
|
|
@@ -508,8 +505,9 @@ function InternalTerrain(props) {
|
|
|
508
505
|
innerTileSegments,
|
|
509
506
|
skirtScale,
|
|
510
507
|
elevationScale,
|
|
508
|
+
radius,
|
|
511
509
|
elevation,
|
|
512
|
-
|
|
510
|
+
topology,
|
|
513
511
|
terrainFieldFilter,
|
|
514
512
|
getCameraOrigin,
|
|
515
513
|
cameraHysteresis,
|
|
@@ -524,8 +522,9 @@ function InternalTerrain(props) {
|
|
|
524
522
|
innerTileSegments,
|
|
525
523
|
skirtScale,
|
|
526
524
|
elevationScale,
|
|
525
|
+
radius,
|
|
527
526
|
elevation,
|
|
528
|
-
|
|
527
|
+
topology,
|
|
529
528
|
terrainFieldFilter,
|
|
530
529
|
getCameraOrigin,
|
|
531
530
|
cameraHysteresis,
|
|
@@ -552,8 +551,9 @@ function Terrain({
|
|
|
552
551
|
innerTileSegments,
|
|
553
552
|
skirtScale,
|
|
554
553
|
elevationScale,
|
|
554
|
+
radius,
|
|
555
555
|
elevation,
|
|
556
|
-
|
|
556
|
+
topology,
|
|
557
557
|
terrainFieldFilter,
|
|
558
558
|
getCameraOrigin,
|
|
559
559
|
cameraHysteresis,
|
|
@@ -582,8 +582,9 @@ function Terrain({
|
|
|
582
582
|
innerTileSegments,
|
|
583
583
|
skirtScale,
|
|
584
584
|
elevationScale,
|
|
585
|
+
radius,
|
|
585
586
|
elevation,
|
|
586
|
-
|
|
587
|
+
topology,
|
|
587
588
|
terrainFieldFilter,
|
|
588
589
|
getCameraOrigin,
|
|
589
590
|
cameraHysteresis,
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "https://github.com/kenjinp/hello-terrain.git"
|
|
8
8
|
},
|
|
9
|
-
"version": "0.0.0-alpha.
|
|
9
|
+
"version": "0.0.0-alpha.11",
|
|
10
10
|
"type": "module",
|
|
11
11
|
"main": "./dist/index.mjs",
|
|
12
12
|
"module": "./dist/index.mjs",
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"dist"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@hello-terrain/
|
|
26
|
-
"@hello-terrain/
|
|
25
|
+
"@hello-terrain/work": "0.3.0",
|
|
26
|
+
"@hello-terrain/three": "0.0.0-alpha.11"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@testing-library/dom": "^10.4.0",
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
"jsdom": "^26.0.0",
|
|
35
35
|
"unbuild": "^3.5.0",
|
|
36
36
|
"vitest": "^4.0.17",
|
|
37
|
+
"@config/oxlint": "0.1.0",
|
|
37
38
|
"@config/oxfmt": "0.1.0",
|
|
38
|
-
"@config/typescript": "0.1.0"
|
|
39
|
-
"@config/oxlint": "0.1.0"
|
|
39
|
+
"@config/typescript": "0.1.0"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"@react-three/fiber": "^8.0.0 || ^9.0.0",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"build": "unbuild",
|
|
52
52
|
"release": "pnpm run build && pnpm publish --access=public",
|
|
53
53
|
"test": "vitest",
|
|
54
|
+
"typecheck": "tsc -p tsconfig.json --noEmit --composite false",
|
|
54
55
|
"lint": "oxlint -c node_modules/@config/oxlint/react.json",
|
|
55
56
|
"format": "oxfmt -c node_modules/@config/oxfmt/base.json --write ."
|
|
56
57
|
}
|