@hello-terrain/react 0.0.0-alpha.10 → 0.0.0-alpha.12

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