@hello-terrain/three 0.0.0-alpha.7 → 0.0.0-alpha.8

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
@@ -411,25 +411,25 @@ function resolveType(format) {
411
411
  function resolveFilter(mode) {
412
412
  return mode === "linear" ? three.LinearFilter : three.NearestFilter;
413
413
  }
414
- function configureStorageTexture(texture, format, filter) {
415
- texture.format = three.RGBAFormat;
416
- texture.type = resolveType(format);
417
- texture.magFilter = resolveFilter(filter);
418
- texture.minFilter = resolveFilter(filter);
419
- texture.wrapS = three.ClampToEdgeWrapping;
420
- texture.wrapT = three.ClampToEdgeWrapping;
421
- texture.generateMipmaps = false;
422
- texture.needsUpdate = true;
414
+ function configureStorageTexture(texture2, format, filter) {
415
+ texture2.format = three.RGBAFormat;
416
+ texture2.type = resolveType(format);
417
+ texture2.magFilter = resolveFilter(filter);
418
+ texture2.minFilter = resolveFilter(filter);
419
+ texture2.wrapS = three.ClampToEdgeWrapping;
420
+ texture2.wrapT = three.ClampToEdgeWrapping;
421
+ texture2.generateMipmaps = false;
422
+ texture2.needsUpdate = true;
423
423
  }
424
424
  function ArrayTextureBackend(edgeVertexCount, tileCount, options) {
425
425
  let currentEdgeVertexCount = edgeVertexCount;
426
426
  let currentTileCount = tileCount;
427
- const texture = new webgpu.StorageArrayTexture(
427
+ const tex = new webgpu.StorageArrayTexture(
428
428
  edgeVertexCount,
429
429
  edgeVertexCount,
430
430
  tileCount
431
431
  );
432
- configureStorageTexture(texture, options.format, options.filter);
432
+ configureStorageTexture(tex, options.format, options.filter);
433
433
  return {
434
434
  backendType: "array-texture",
435
435
  get edgeVertexCount() {
@@ -438,18 +438,21 @@ function ArrayTextureBackend(edgeVertexCount, tileCount, options) {
438
438
  get tileCount() {
439
439
  return currentTileCount;
440
440
  },
441
- texture,
441
+ texture: tex,
442
442
  uv(ix, iy, _tileIndex) {
443
443
  return tsl.vec2(ix.toFloat(), iy.toFloat());
444
444
  },
445
445
  texel(ix, iy, tileIndex) {
446
446
  return tsl.ivec3(ix, iy, tileIndex);
447
447
  },
448
+ sample(u, v, tileIndex) {
449
+ return tsl.texture(tex, tsl.vec2(u, v)).depth(tsl.int(tileIndex));
450
+ },
448
451
  resize(width, height, nextTileCount) {
449
452
  currentEdgeVertexCount = width;
450
453
  currentTileCount = nextTileCount;
451
- texture.setSize(width, height, nextTileCount);
452
- texture.needsUpdate = true;
454
+ tex.setSize(width, height, nextTileCount);
455
+ tex.needsUpdate = true;
453
456
  }
454
457
  };
455
458
  }
@@ -468,8 +471,8 @@ function AtlasBackend(edgeVertexCount, tileCount, options) {
468
471
  let currentTileCount = tileCount;
469
472
  let tilesPerRow = Math.max(1, Math.ceil(Math.sqrt(tileCount)));
470
473
  const atlasSize = tilesPerRow * edgeVertexCount;
471
- const texture = new webgpu.StorageTexture(atlasSize, atlasSize);
472
- configureStorageTexture(texture, options.format, options.filter);
474
+ const tex = new webgpu.StorageTexture(atlasSize, atlasSize);
475
+ configureStorageTexture(tex, options.format, options.filter);
473
476
  return {
474
477
  backendType: "atlas",
475
478
  get edgeVertexCount() {
@@ -478,7 +481,7 @@ function AtlasBackend(edgeVertexCount, tileCount, options) {
478
481
  get tileCount() {
479
482
  return currentTileCount;
480
483
  },
481
- texture,
484
+ texture: tex,
482
485
  uv(ix, iy, tileIndex) {
483
486
  const { atlasX, atlasY } = atlasCoord(
484
487
  tilesPerRow,
@@ -503,27 +506,37 @@ function AtlasBackend(edgeVertexCount, tileCount, options) {
503
506
  );
504
507
  return tsl.ivec2(atlasX, atlasY);
505
508
  },
509
+ sample(u, v, tileIndex) {
510
+ const tile = tsl.int(tileIndex);
511
+ const tilesPerRowNode = tsl.int(tilesPerRow);
512
+ const col = tile.mod(tilesPerRowNode);
513
+ const row = tile.div(tilesPerRowNode);
514
+ const invTilesPerRow = tsl.float(1 / tilesPerRow);
515
+ const atlasU = col.toFloat().add(u).mul(invTilesPerRow);
516
+ const atlasV = row.toFloat().add(v).mul(invTilesPerRow);
517
+ return tsl.texture(tex, tsl.vec2(atlasU, atlasV));
518
+ },
506
519
  resize(width, height, nextTileCount) {
507
520
  currentEdgeVertexCount = width;
508
521
  currentTileCount = nextTileCount;
509
522
  tilesPerRow = Math.max(1, Math.ceil(Math.sqrt(nextTileCount)));
510
523
  const nextAtlasSize = tilesPerRow * width;
511
- const image = texture.image;
524
+ const image = tex.image;
512
525
  image.width = nextAtlasSize;
513
526
  image.height = nextAtlasSize;
514
- texture.needsUpdate = true;
527
+ tex.needsUpdate = true;
515
528
  }
516
529
  };
517
530
  }
518
531
  function Texture3DBackend(edgeVertexCount, tileCount, options) {
519
532
  let currentEdgeVertexCount = edgeVertexCount;
520
533
  let currentTileCount = tileCount;
521
- const texture = new webgpu.StorageArrayTexture(
534
+ const tex = new webgpu.StorageArrayTexture(
522
535
  edgeVertexCount,
523
536
  edgeVertexCount,
524
537
  tileCount
525
538
  );
526
- configureStorageTexture(texture, options.format, options.filter);
539
+ configureStorageTexture(tex, options.format, options.filter);
527
540
  return {
528
541
  backendType: "texture-3d",
529
542
  get edgeVertexCount() {
@@ -532,18 +545,21 @@ function Texture3DBackend(edgeVertexCount, tileCount, options) {
532
545
  get tileCount() {
533
546
  return currentTileCount;
534
547
  },
535
- texture,
548
+ texture: tex,
536
549
  uv(ix, iy, _tileIndex) {
537
550
  return tsl.vec2(ix.toFloat(), iy.toFloat());
538
551
  },
539
552
  texel(ix, iy, tileIndex) {
540
553
  return tsl.ivec3(ix, iy, tileIndex);
541
554
  },
555
+ sample(u, v, tileIndex) {
556
+ return tsl.texture(tex, tsl.vec2(u, v)).depth(tsl.int(tileIndex));
557
+ },
542
558
  resize(width, height, nextTileCount) {
543
559
  currentEdgeVertexCount = width;
544
560
  currentTileCount = nextTileCount;
545
- texture.setSize(width, height, nextTileCount);
546
- texture.needsUpdate = true;
561
+ tex.setSize(width, height, nextTileCount);
562
+ tex.needsUpdate = true;
547
563
  }
548
564
  };
549
565
  }
@@ -552,7 +568,7 @@ function tryGetDeviceLimits(renderer) {
552
568
  return backend.backend?.device?.limits ?? {};
553
569
  }
554
570
  function createTerrainFieldStorage(edgeVertexCount, tileCount, renderer, options = {}) {
555
- const filter = options.filter ?? "nearest";
571
+ const filter = options.filter ?? "linear";
556
572
  const format = options.format ?? "rgba16float";
557
573
  const forcedBackend = options.backend;
558
574
  if (forcedBackend === "atlas") {
@@ -593,8 +609,18 @@ function loadTerrainFieldElevation(storage, ix, iy, tileIndex) {
593
609
  return loadTerrainField(storage, ix, iy, tileIndex).r;
594
610
  }
595
611
  function loadTerrainFieldNormal(storage, ix, iy, tileIndex) {
596
- const sample = loadTerrainField(storage, ix, iy, tileIndex);
597
- return tsl.vec2(sample.g, sample.b);
612
+ const raw = loadTerrainField(storage, ix, iy, tileIndex);
613
+ return tsl.vec2(raw.g, raw.b);
614
+ }
615
+ function sampleTerrainField(storage, u, v, tileIndex) {
616
+ return storage.sample(u, v, tileIndex);
617
+ }
618
+ function sampleTerrainFieldElevation(storage, u, v, tileIndex) {
619
+ return sampleTerrainField(storage, u, v, tileIndex).r;
620
+ }
621
+ function sampleTerrainFieldNormal(storage, u, v, tileIndex) {
622
+ const raw = sampleTerrainField(storage, u, v, tileIndex);
623
+ return tsl.vec2(raw.g, raw.b);
598
624
  }
599
625
  function packTerrainFieldSample(height, normalXZ, extra = tsl.float(0)) {
600
626
  return tsl.vec4(height, normalXZ.x, normalXZ.y, extra);
@@ -724,6 +750,7 @@ const quadtreeUpdate = work.param({
724
750
  distanceFactor: 1.5
725
751
  }).displayName("quadtreeUpdate");
726
752
  const surface = work.param(null).displayName("surface");
753
+ const terrainFieldFilter = work.param("linear").displayName("terrainFieldFilter");
727
754
  const elevationFn = work.param(() => tsl.float(0));
728
755
 
729
756
  function createLeafStorage(maxNodes) {
@@ -1535,11 +1562,13 @@ const createTerrainFieldTextureTask = work.task(
1535
1562
  (get, work, { resources }) => {
1536
1563
  const edgeVertexCount = get(innerTileSegments) + 3;
1537
1564
  const maxNodesValue = get(maxNodes);
1565
+ const filter = get(terrainFieldFilter);
1538
1566
  return work(
1539
1567
  () => createTerrainFieldStorage(
1540
1568
  edgeVertexCount,
1541
1569
  maxNodesValue,
1542
- resources?.renderer
1570
+ resources?.renderer,
1571
+ { filter }
1543
1572
  )
1544
1573
  );
1545
1574
  }
@@ -1865,10 +1894,14 @@ exports.quadtreeUpdateTask = quadtreeUpdateTask;
1865
1894
  exports.resetLeafSet = resetLeafSet;
1866
1895
  exports.resetSeamTable = resetSeamTable;
1867
1896
  exports.rootSize = rootSize;
1897
+ exports.sampleTerrainField = sampleTerrainField;
1898
+ exports.sampleTerrainFieldElevation = sampleTerrainFieldElevation;
1899
+ exports.sampleTerrainFieldNormal = sampleTerrainFieldNormal;
1868
1900
  exports.skirtScale = skirtScale;
1869
1901
  exports.storeTerrainField = storeTerrainField;
1870
1902
  exports.surface = surface;
1871
1903
  exports.surfaceTask = surfaceTask;
1904
+ exports.terrainFieldFilter = terrainFieldFilter;
1872
1905
  exports.terrainFieldStageTask = terrainFieldStageTask;
1873
1906
  exports.terrainGraph = terrainGraph;
1874
1907
  exports.terrainTasks = terrainTasks;
package/dist/index.d.cts CHANGED
@@ -404,6 +404,8 @@ interface TerrainFieldStorage {
404
404
  readonly texture: StorageArrayTexture | StorageTexture;
405
405
  uv(ix: Node, iy: Node, tileIndex: Node): Node;
406
406
  texel(ix: Node, iy: Node, tileIndex: Node): Node;
407
+ /** UV-based filtered sample. `u, v` in [0, 1] tile-local space. */
408
+ sample(u: Node, v: Node, tileIndex: Node): Node;
407
409
  resize(width: number, height: number, tileCount: number): void;
408
410
  }
409
411
  declare function ArrayTextureBackend(edgeVertexCount: number, tileCount: number, options: Required<Pick<TerrainFieldStorageOptions, "format" | "filter">>): TerrainFieldStorage;
@@ -418,6 +420,13 @@ declare function storeTerrainField(storage: TerrainFieldStorage, ix: Node, iy: N
418
420
  declare function loadTerrainField(storage: TerrainFieldStorage, ix: Node, iy: Node, tileIndex: Node): Node;
419
421
  declare function loadTerrainFieldElevation(storage: TerrainFieldStorage, ix: Node, iy: Node, tileIndex: Node): Node;
420
422
  declare function loadTerrainFieldNormal(storage: TerrainFieldStorage, ix: Node, iy: Node, tileIndex: Node): Node;
423
+ /**
424
+ * UV-based filtered sample. `u, v` are in [0, 1] tile-local space.
425
+ * Respects the filter mode (nearest / linear) set on the storage.
426
+ */
427
+ declare function sampleTerrainField(storage: TerrainFieldStorage, u: Node, v: Node, tileIndex: Node): Node;
428
+ declare function sampleTerrainFieldElevation(storage: TerrainFieldStorage, u: Node, v: Node, tileIndex: Node): Node;
429
+ declare function sampleTerrainFieldNormal(storage: TerrainFieldStorage, u: Node, v: Node, tileIndex: Node): Node;
421
430
  declare function packTerrainFieldSample(height: Node, normalXZ: Node, extra?: Node): Node;
422
431
 
423
432
  interface TerrainUniformsParams {
@@ -557,6 +566,8 @@ declare const maxLevel: _hello_terrain_work.ParamRef<number>;
557
566
  declare const quadtreeUpdate: _hello_terrain_work.ParamRef<UpdateParams>;
558
567
  /** Optional custom terrain surface; defaults to bounded flat surface when null. */
559
568
  declare const surface: _hello_terrain_work.ParamRef<Surface | null>;
569
+ /** Terrain field texture filter mode. */
570
+ declare const terrainFieldFilter: _hello_terrain_work.ParamRef<"nearest" | "linear">;
560
571
  /** Terrain elevation control function (per vertex, in gpu compute) */
561
572
  declare const elevationFn: _hello_terrain_work.ParamRef<ElevationCallback>;
562
573
 
@@ -740,5 +751,5 @@ declare const voronoiCells: three_src_nodes_TSL_js.ShaderNodeFn<[three_tsl.Proxi
740
751
  uv: Node;
741
752
  }>]>;
742
753
 
743
- export { ArrayTextureBackend, AtlasBackend, Dir, TerrainGeometry, TerrainMesh, Texture3DBackend, U32_EMPTY, allocLeafSet, allocSeamTable, beginUpdate, blendAngleCorrectedNormals, buildLeafIndex, buildSeams2to1, compileComputeTask, createComputePipelineTasks, createCubeSphereSurface, createElevationFieldContextTask, createFlatSurface, createInfiniteFlatSurface, createSpatialIndex, createState, createTerrainFieldStorage, createTerrainFieldTextureTask, createTerrainUniforms, createUniformsTask, deriveNormalZ, elevationFieldStageTask, elevationFn, elevationScale, executeComputeTask, getDeviceComputeLimits, innerTileSegments, instanceIdTask, isSkirtUV, isSkirtVertex, leafGpuBufferTask, leafStorageTask, loadTerrainField, loadTerrainFieldElevation, loadTerrainFieldNormal, maxLevel, maxNodes, origin, packTerrainFieldSample, positionNodeTask, quadtreeConfigTask, quadtreeUpdate, quadtreeUpdateTask, resetLeafSet, resetSeamTable, rootSize, skirtScale, storeTerrainField, surface, surfaceTask, terrainFieldStageTask, terrainGraph, terrainTasks, textureSpaceToVectorSpace, tileNodesTask, update, updateUniformsTask, vElevation, vGlobalVertexIndex, vectorSpaceToTextureSpace, voronoiCells };
754
+ export { ArrayTextureBackend, AtlasBackend, Dir, TerrainGeometry, TerrainMesh, Texture3DBackend, U32_EMPTY, allocLeafSet, allocSeamTable, beginUpdate, blendAngleCorrectedNormals, buildLeafIndex, buildSeams2to1, compileComputeTask, createComputePipelineTasks, createCubeSphereSurface, createElevationFieldContextTask, createFlatSurface, createInfiniteFlatSurface, createSpatialIndex, createState, createTerrainFieldStorage, createTerrainFieldTextureTask, createTerrainUniforms, createUniformsTask, deriveNormalZ, elevationFieldStageTask, elevationFn, elevationScale, executeComputeTask, getDeviceComputeLimits, innerTileSegments, instanceIdTask, isSkirtUV, isSkirtVertex, leafGpuBufferTask, leafStorageTask, loadTerrainField, loadTerrainFieldElevation, loadTerrainFieldNormal, maxLevel, maxNodes, origin, packTerrainFieldSample, positionNodeTask, quadtreeConfigTask, quadtreeUpdate, quadtreeUpdateTask, resetLeafSet, resetSeamTable, rootSize, sampleTerrainField, sampleTerrainFieldElevation, sampleTerrainFieldNormal, skirtScale, storeTerrainField, surface, surfaceTask, terrainFieldFilter, terrainFieldStageTask, terrainGraph, terrainTasks, textureSpaceToVectorSpace, tileNodesTask, update, updateUniformsTask, vElevation, vGlobalVertexIndex, vectorSpaceToTextureSpace, voronoiCells };
744
755
  export type { ComputePipeline, ComputeStageCallback, CubeSphereSurfaceConfig, ElevationCallback, ElevationFieldContext, ElevationParams, FlatSurfaceConfig, InfiniteFlatSurfaceConfig, IntNodeInput, LeafGpuBufferState, LeafSet, LeafStorageState, LodMode, QuadtreeConfig, QuadtreeConfigState, QuadtreeState, SeamTable, SpatialIndex, Surface, TerrainFieldStorage, TerrainFieldStorageBackendType, TerrainFieldStorageFormat, TerrainFieldStorageOptions, TerrainGraph, TerrainTasks, TerrainUniformsContext, TerrainUniformsParams, TileBounds, TileId, UpdateParams };
package/dist/index.d.mts CHANGED
@@ -404,6 +404,8 @@ interface TerrainFieldStorage {
404
404
  readonly texture: StorageArrayTexture | StorageTexture;
405
405
  uv(ix: Node, iy: Node, tileIndex: Node): Node;
406
406
  texel(ix: Node, iy: Node, tileIndex: Node): Node;
407
+ /** UV-based filtered sample. `u, v` in [0, 1] tile-local space. */
408
+ sample(u: Node, v: Node, tileIndex: Node): Node;
407
409
  resize(width: number, height: number, tileCount: number): void;
408
410
  }
409
411
  declare function ArrayTextureBackend(edgeVertexCount: number, tileCount: number, options: Required<Pick<TerrainFieldStorageOptions, "format" | "filter">>): TerrainFieldStorage;
@@ -418,6 +420,13 @@ declare function storeTerrainField(storage: TerrainFieldStorage, ix: Node, iy: N
418
420
  declare function loadTerrainField(storage: TerrainFieldStorage, ix: Node, iy: Node, tileIndex: Node): Node;
419
421
  declare function loadTerrainFieldElevation(storage: TerrainFieldStorage, ix: Node, iy: Node, tileIndex: Node): Node;
420
422
  declare function loadTerrainFieldNormal(storage: TerrainFieldStorage, ix: Node, iy: Node, tileIndex: Node): Node;
423
+ /**
424
+ * UV-based filtered sample. `u, v` are in [0, 1] tile-local space.
425
+ * Respects the filter mode (nearest / linear) set on the storage.
426
+ */
427
+ declare function sampleTerrainField(storage: TerrainFieldStorage, u: Node, v: Node, tileIndex: Node): Node;
428
+ declare function sampleTerrainFieldElevation(storage: TerrainFieldStorage, u: Node, v: Node, tileIndex: Node): Node;
429
+ declare function sampleTerrainFieldNormal(storage: TerrainFieldStorage, u: Node, v: Node, tileIndex: Node): Node;
421
430
  declare function packTerrainFieldSample(height: Node, normalXZ: Node, extra?: Node): Node;
422
431
 
423
432
  interface TerrainUniformsParams {
@@ -557,6 +566,8 @@ declare const maxLevel: _hello_terrain_work.ParamRef<number>;
557
566
  declare const quadtreeUpdate: _hello_terrain_work.ParamRef<UpdateParams>;
558
567
  /** Optional custom terrain surface; defaults to bounded flat surface when null. */
559
568
  declare const surface: _hello_terrain_work.ParamRef<Surface | null>;
569
+ /** Terrain field texture filter mode. */
570
+ declare const terrainFieldFilter: _hello_terrain_work.ParamRef<"nearest" | "linear">;
560
571
  /** Terrain elevation control function (per vertex, in gpu compute) */
561
572
  declare const elevationFn: _hello_terrain_work.ParamRef<ElevationCallback>;
562
573
 
@@ -740,5 +751,5 @@ declare const voronoiCells: three_src_nodes_TSL_js.ShaderNodeFn<[three_tsl.Proxi
740
751
  uv: Node;
741
752
  }>]>;
742
753
 
743
- export { ArrayTextureBackend, AtlasBackend, Dir, TerrainGeometry, TerrainMesh, Texture3DBackend, U32_EMPTY, allocLeafSet, allocSeamTable, beginUpdate, blendAngleCorrectedNormals, buildLeafIndex, buildSeams2to1, compileComputeTask, createComputePipelineTasks, createCubeSphereSurface, createElevationFieldContextTask, createFlatSurface, createInfiniteFlatSurface, createSpatialIndex, createState, createTerrainFieldStorage, createTerrainFieldTextureTask, createTerrainUniforms, createUniformsTask, deriveNormalZ, elevationFieldStageTask, elevationFn, elevationScale, executeComputeTask, getDeviceComputeLimits, innerTileSegments, instanceIdTask, isSkirtUV, isSkirtVertex, leafGpuBufferTask, leafStorageTask, loadTerrainField, loadTerrainFieldElevation, loadTerrainFieldNormal, maxLevel, maxNodes, origin, packTerrainFieldSample, positionNodeTask, quadtreeConfigTask, quadtreeUpdate, quadtreeUpdateTask, resetLeafSet, resetSeamTable, rootSize, skirtScale, storeTerrainField, surface, surfaceTask, terrainFieldStageTask, terrainGraph, terrainTasks, textureSpaceToVectorSpace, tileNodesTask, update, updateUniformsTask, vElevation, vGlobalVertexIndex, vectorSpaceToTextureSpace, voronoiCells };
754
+ export { ArrayTextureBackend, AtlasBackend, Dir, TerrainGeometry, TerrainMesh, Texture3DBackend, U32_EMPTY, allocLeafSet, allocSeamTable, beginUpdate, blendAngleCorrectedNormals, buildLeafIndex, buildSeams2to1, compileComputeTask, createComputePipelineTasks, createCubeSphereSurface, createElevationFieldContextTask, createFlatSurface, createInfiniteFlatSurface, createSpatialIndex, createState, createTerrainFieldStorage, createTerrainFieldTextureTask, createTerrainUniforms, createUniformsTask, deriveNormalZ, elevationFieldStageTask, elevationFn, elevationScale, executeComputeTask, getDeviceComputeLimits, innerTileSegments, instanceIdTask, isSkirtUV, isSkirtVertex, leafGpuBufferTask, leafStorageTask, loadTerrainField, loadTerrainFieldElevation, loadTerrainFieldNormal, maxLevel, maxNodes, origin, packTerrainFieldSample, positionNodeTask, quadtreeConfigTask, quadtreeUpdate, quadtreeUpdateTask, resetLeafSet, resetSeamTable, rootSize, sampleTerrainField, sampleTerrainFieldElevation, sampleTerrainFieldNormal, skirtScale, storeTerrainField, surface, surfaceTask, terrainFieldFilter, terrainFieldStageTask, terrainGraph, terrainTasks, textureSpaceToVectorSpace, tileNodesTask, update, updateUniformsTask, vElevation, vGlobalVertexIndex, vectorSpaceToTextureSpace, voronoiCells };
744
755
  export type { ComputePipeline, ComputeStageCallback, CubeSphereSurfaceConfig, ElevationCallback, ElevationFieldContext, ElevationParams, FlatSurfaceConfig, InfiniteFlatSurfaceConfig, IntNodeInput, LeafGpuBufferState, LeafSet, LeafStorageState, LodMode, QuadtreeConfig, QuadtreeConfigState, QuadtreeState, SeamTable, SpatialIndex, Surface, TerrainFieldStorage, TerrainFieldStorageBackendType, TerrainFieldStorageFormat, TerrainFieldStorageOptions, TerrainGraph, TerrainTasks, TerrainUniformsContext, TerrainUniformsParams, TileBounds, TileId, UpdateParams };
package/dist/index.d.ts CHANGED
@@ -404,6 +404,8 @@ interface TerrainFieldStorage {
404
404
  readonly texture: StorageArrayTexture | StorageTexture;
405
405
  uv(ix: Node, iy: Node, tileIndex: Node): Node;
406
406
  texel(ix: Node, iy: Node, tileIndex: Node): Node;
407
+ /** UV-based filtered sample. `u, v` in [0, 1] tile-local space. */
408
+ sample(u: Node, v: Node, tileIndex: Node): Node;
407
409
  resize(width: number, height: number, tileCount: number): void;
408
410
  }
409
411
  declare function ArrayTextureBackend(edgeVertexCount: number, tileCount: number, options: Required<Pick<TerrainFieldStorageOptions, "format" | "filter">>): TerrainFieldStorage;
@@ -418,6 +420,13 @@ declare function storeTerrainField(storage: TerrainFieldStorage, ix: Node, iy: N
418
420
  declare function loadTerrainField(storage: TerrainFieldStorage, ix: Node, iy: Node, tileIndex: Node): Node;
419
421
  declare function loadTerrainFieldElevation(storage: TerrainFieldStorage, ix: Node, iy: Node, tileIndex: Node): Node;
420
422
  declare function loadTerrainFieldNormal(storage: TerrainFieldStorage, ix: Node, iy: Node, tileIndex: Node): Node;
423
+ /**
424
+ * UV-based filtered sample. `u, v` are in [0, 1] tile-local space.
425
+ * Respects the filter mode (nearest / linear) set on the storage.
426
+ */
427
+ declare function sampleTerrainField(storage: TerrainFieldStorage, u: Node, v: Node, tileIndex: Node): Node;
428
+ declare function sampleTerrainFieldElevation(storage: TerrainFieldStorage, u: Node, v: Node, tileIndex: Node): Node;
429
+ declare function sampleTerrainFieldNormal(storage: TerrainFieldStorage, u: Node, v: Node, tileIndex: Node): Node;
421
430
  declare function packTerrainFieldSample(height: Node, normalXZ: Node, extra?: Node): Node;
422
431
 
423
432
  interface TerrainUniformsParams {
@@ -557,6 +566,8 @@ declare const maxLevel: _hello_terrain_work.ParamRef<number>;
557
566
  declare const quadtreeUpdate: _hello_terrain_work.ParamRef<UpdateParams>;
558
567
  /** Optional custom terrain surface; defaults to bounded flat surface when null. */
559
568
  declare const surface: _hello_terrain_work.ParamRef<Surface | null>;
569
+ /** Terrain field texture filter mode. */
570
+ declare const terrainFieldFilter: _hello_terrain_work.ParamRef<"nearest" | "linear">;
560
571
  /** Terrain elevation control function (per vertex, in gpu compute) */
561
572
  declare const elevationFn: _hello_terrain_work.ParamRef<ElevationCallback>;
562
573
 
@@ -740,5 +751,5 @@ declare const voronoiCells: three_src_nodes_TSL_js.ShaderNodeFn<[three_tsl.Proxi
740
751
  uv: Node;
741
752
  }>]>;
742
753
 
743
- export { ArrayTextureBackend, AtlasBackend, Dir, TerrainGeometry, TerrainMesh, Texture3DBackend, U32_EMPTY, allocLeafSet, allocSeamTable, beginUpdate, blendAngleCorrectedNormals, buildLeafIndex, buildSeams2to1, compileComputeTask, createComputePipelineTasks, createCubeSphereSurface, createElevationFieldContextTask, createFlatSurface, createInfiniteFlatSurface, createSpatialIndex, createState, createTerrainFieldStorage, createTerrainFieldTextureTask, createTerrainUniforms, createUniformsTask, deriveNormalZ, elevationFieldStageTask, elevationFn, elevationScale, executeComputeTask, getDeviceComputeLimits, innerTileSegments, instanceIdTask, isSkirtUV, isSkirtVertex, leafGpuBufferTask, leafStorageTask, loadTerrainField, loadTerrainFieldElevation, loadTerrainFieldNormal, maxLevel, maxNodes, origin, packTerrainFieldSample, positionNodeTask, quadtreeConfigTask, quadtreeUpdate, quadtreeUpdateTask, resetLeafSet, resetSeamTable, rootSize, skirtScale, storeTerrainField, surface, surfaceTask, terrainFieldStageTask, terrainGraph, terrainTasks, textureSpaceToVectorSpace, tileNodesTask, update, updateUniformsTask, vElevation, vGlobalVertexIndex, vectorSpaceToTextureSpace, voronoiCells };
754
+ export { ArrayTextureBackend, AtlasBackend, Dir, TerrainGeometry, TerrainMesh, Texture3DBackend, U32_EMPTY, allocLeafSet, allocSeamTable, beginUpdate, blendAngleCorrectedNormals, buildLeafIndex, buildSeams2to1, compileComputeTask, createComputePipelineTasks, createCubeSphereSurface, createElevationFieldContextTask, createFlatSurface, createInfiniteFlatSurface, createSpatialIndex, createState, createTerrainFieldStorage, createTerrainFieldTextureTask, createTerrainUniforms, createUniformsTask, deriveNormalZ, elevationFieldStageTask, elevationFn, elevationScale, executeComputeTask, getDeviceComputeLimits, innerTileSegments, instanceIdTask, isSkirtUV, isSkirtVertex, leafGpuBufferTask, leafStorageTask, loadTerrainField, loadTerrainFieldElevation, loadTerrainFieldNormal, maxLevel, maxNodes, origin, packTerrainFieldSample, positionNodeTask, quadtreeConfigTask, quadtreeUpdate, quadtreeUpdateTask, resetLeafSet, resetSeamTable, rootSize, sampleTerrainField, sampleTerrainFieldElevation, sampleTerrainFieldNormal, skirtScale, storeTerrainField, surface, surfaceTask, terrainFieldFilter, terrainFieldStageTask, terrainGraph, terrainTasks, textureSpaceToVectorSpace, tileNodesTask, update, updateUniformsTask, vElevation, vGlobalVertexIndex, vectorSpaceToTextureSpace, voronoiCells };
744
755
  export type { ComputePipeline, ComputeStageCallback, CubeSphereSurfaceConfig, ElevationCallback, ElevationFieldContext, ElevationParams, FlatSurfaceConfig, InfiniteFlatSurfaceConfig, IntNodeInput, LeafGpuBufferState, LeafSet, LeafStorageState, LodMode, QuadtreeConfig, QuadtreeConfigState, QuadtreeState, SeamTable, SpatialIndex, Surface, TerrainFieldStorage, TerrainFieldStorageBackendType, TerrainFieldStorageFormat, TerrainFieldStorageOptions, TerrainGraph, TerrainTasks, TerrainUniformsContext, TerrainUniformsParams, TileBounds, TileId, UpdateParams };
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { BufferGeometry, BufferAttribute, RGBAFormat, ClampToEdgeWrapping, HalfFloatType, FloatType, LinearFilter, NearestFilter, Vector3 as Vector3$1 } from 'three';
2
2
  import { MeshStandardNodeMaterial, InstancedMesh, InstancedBufferAttribute, StorageTexture, StorageArrayTexture, StorageBufferAttribute, Vector3 } from 'three/webgpu';
3
3
  import { param, task, graph } from '@hello-terrain/work';
4
- import { uniform, Fn, float, globalId, int, vec2, uint, If, workgroupBarrier, textureStore, uvec3, vec4, ivec2, ivec3, textureLoad, instanceIndex, min, max, pow, vec3, storage, vertexIndex, uv, select, positionLocal, normalLocal, remap, dot, varyingProperty, mx_noise_float, Loop, mix } from 'three/tsl';
4
+ import { uniform, Fn, float, globalId, int, vec2, uint, If, workgroupBarrier, textureStore, uvec3, vec4, texture, ivec2, ivec3, textureLoad, instanceIndex, min, max, pow, vec3, storage, vertexIndex, uv, select, positionLocal, normalLocal, remap, dot, varyingProperty, mx_noise_float, Loop, mix } from 'three/tsl';
5
5
  import { Fn as Fn$1 } from 'three/src/nodes/TSL.js';
6
6
 
7
7
  class TerrainGeometry extends BufferGeometry {
@@ -409,25 +409,25 @@ function resolveType(format) {
409
409
  function resolveFilter(mode) {
410
410
  return mode === "linear" ? LinearFilter : NearestFilter;
411
411
  }
412
- function configureStorageTexture(texture, format, filter) {
413
- texture.format = RGBAFormat;
414
- texture.type = resolveType(format);
415
- texture.magFilter = resolveFilter(filter);
416
- texture.minFilter = resolveFilter(filter);
417
- texture.wrapS = ClampToEdgeWrapping;
418
- texture.wrapT = ClampToEdgeWrapping;
419
- texture.generateMipmaps = false;
420
- texture.needsUpdate = true;
412
+ function configureStorageTexture(texture2, format, filter) {
413
+ texture2.format = RGBAFormat;
414
+ texture2.type = resolveType(format);
415
+ texture2.magFilter = resolveFilter(filter);
416
+ texture2.minFilter = resolveFilter(filter);
417
+ texture2.wrapS = ClampToEdgeWrapping;
418
+ texture2.wrapT = ClampToEdgeWrapping;
419
+ texture2.generateMipmaps = false;
420
+ texture2.needsUpdate = true;
421
421
  }
422
422
  function ArrayTextureBackend(edgeVertexCount, tileCount, options) {
423
423
  let currentEdgeVertexCount = edgeVertexCount;
424
424
  let currentTileCount = tileCount;
425
- const texture = new StorageArrayTexture(
425
+ const tex = new StorageArrayTexture(
426
426
  edgeVertexCount,
427
427
  edgeVertexCount,
428
428
  tileCount
429
429
  );
430
- configureStorageTexture(texture, options.format, options.filter);
430
+ configureStorageTexture(tex, options.format, options.filter);
431
431
  return {
432
432
  backendType: "array-texture",
433
433
  get edgeVertexCount() {
@@ -436,18 +436,21 @@ function ArrayTextureBackend(edgeVertexCount, tileCount, options) {
436
436
  get tileCount() {
437
437
  return currentTileCount;
438
438
  },
439
- texture,
439
+ texture: tex,
440
440
  uv(ix, iy, _tileIndex) {
441
441
  return vec2(ix.toFloat(), iy.toFloat());
442
442
  },
443
443
  texel(ix, iy, tileIndex) {
444
444
  return ivec3(ix, iy, tileIndex);
445
445
  },
446
+ sample(u, v, tileIndex) {
447
+ return texture(tex, vec2(u, v)).depth(int(tileIndex));
448
+ },
446
449
  resize(width, height, nextTileCount) {
447
450
  currentEdgeVertexCount = width;
448
451
  currentTileCount = nextTileCount;
449
- texture.setSize(width, height, nextTileCount);
450
- texture.needsUpdate = true;
452
+ tex.setSize(width, height, nextTileCount);
453
+ tex.needsUpdate = true;
451
454
  }
452
455
  };
453
456
  }
@@ -466,8 +469,8 @@ function AtlasBackend(edgeVertexCount, tileCount, options) {
466
469
  let currentTileCount = tileCount;
467
470
  let tilesPerRow = Math.max(1, Math.ceil(Math.sqrt(tileCount)));
468
471
  const atlasSize = tilesPerRow * edgeVertexCount;
469
- const texture = new StorageTexture(atlasSize, atlasSize);
470
- configureStorageTexture(texture, options.format, options.filter);
472
+ const tex = new StorageTexture(atlasSize, atlasSize);
473
+ configureStorageTexture(tex, options.format, options.filter);
471
474
  return {
472
475
  backendType: "atlas",
473
476
  get edgeVertexCount() {
@@ -476,7 +479,7 @@ function AtlasBackend(edgeVertexCount, tileCount, options) {
476
479
  get tileCount() {
477
480
  return currentTileCount;
478
481
  },
479
- texture,
482
+ texture: tex,
480
483
  uv(ix, iy, tileIndex) {
481
484
  const { atlasX, atlasY } = atlasCoord(
482
485
  tilesPerRow,
@@ -501,27 +504,37 @@ function AtlasBackend(edgeVertexCount, tileCount, options) {
501
504
  );
502
505
  return ivec2(atlasX, atlasY);
503
506
  },
507
+ sample(u, v, tileIndex) {
508
+ const tile = int(tileIndex);
509
+ const tilesPerRowNode = int(tilesPerRow);
510
+ const col = tile.mod(tilesPerRowNode);
511
+ const row = tile.div(tilesPerRowNode);
512
+ const invTilesPerRow = float(1 / tilesPerRow);
513
+ const atlasU = col.toFloat().add(u).mul(invTilesPerRow);
514
+ const atlasV = row.toFloat().add(v).mul(invTilesPerRow);
515
+ return texture(tex, vec2(atlasU, atlasV));
516
+ },
504
517
  resize(width, height, nextTileCount) {
505
518
  currentEdgeVertexCount = width;
506
519
  currentTileCount = nextTileCount;
507
520
  tilesPerRow = Math.max(1, Math.ceil(Math.sqrt(nextTileCount)));
508
521
  const nextAtlasSize = tilesPerRow * width;
509
- const image = texture.image;
522
+ const image = tex.image;
510
523
  image.width = nextAtlasSize;
511
524
  image.height = nextAtlasSize;
512
- texture.needsUpdate = true;
525
+ tex.needsUpdate = true;
513
526
  }
514
527
  };
515
528
  }
516
529
  function Texture3DBackend(edgeVertexCount, tileCount, options) {
517
530
  let currentEdgeVertexCount = edgeVertexCount;
518
531
  let currentTileCount = tileCount;
519
- const texture = new StorageArrayTexture(
532
+ const tex = new StorageArrayTexture(
520
533
  edgeVertexCount,
521
534
  edgeVertexCount,
522
535
  tileCount
523
536
  );
524
- configureStorageTexture(texture, options.format, options.filter);
537
+ configureStorageTexture(tex, options.format, options.filter);
525
538
  return {
526
539
  backendType: "texture-3d",
527
540
  get edgeVertexCount() {
@@ -530,18 +543,21 @@ function Texture3DBackend(edgeVertexCount, tileCount, options) {
530
543
  get tileCount() {
531
544
  return currentTileCount;
532
545
  },
533
- texture,
546
+ texture: tex,
534
547
  uv(ix, iy, _tileIndex) {
535
548
  return vec2(ix.toFloat(), iy.toFloat());
536
549
  },
537
550
  texel(ix, iy, tileIndex) {
538
551
  return ivec3(ix, iy, tileIndex);
539
552
  },
553
+ sample(u, v, tileIndex) {
554
+ return texture(tex, vec2(u, v)).depth(int(tileIndex));
555
+ },
540
556
  resize(width, height, nextTileCount) {
541
557
  currentEdgeVertexCount = width;
542
558
  currentTileCount = nextTileCount;
543
- texture.setSize(width, height, nextTileCount);
544
- texture.needsUpdate = true;
559
+ tex.setSize(width, height, nextTileCount);
560
+ tex.needsUpdate = true;
545
561
  }
546
562
  };
547
563
  }
@@ -550,7 +566,7 @@ function tryGetDeviceLimits(renderer) {
550
566
  return backend.backend?.device?.limits ?? {};
551
567
  }
552
568
  function createTerrainFieldStorage(edgeVertexCount, tileCount, renderer, options = {}) {
553
- const filter = options.filter ?? "nearest";
569
+ const filter = options.filter ?? "linear";
554
570
  const format = options.format ?? "rgba16float";
555
571
  const forcedBackend = options.backend;
556
572
  if (forcedBackend === "atlas") {
@@ -591,8 +607,18 @@ function loadTerrainFieldElevation(storage, ix, iy, tileIndex) {
591
607
  return loadTerrainField(storage, ix, iy, tileIndex).r;
592
608
  }
593
609
  function loadTerrainFieldNormal(storage, ix, iy, tileIndex) {
594
- const sample = loadTerrainField(storage, ix, iy, tileIndex);
595
- return vec2(sample.g, sample.b);
610
+ const raw = loadTerrainField(storage, ix, iy, tileIndex);
611
+ return vec2(raw.g, raw.b);
612
+ }
613
+ function sampleTerrainField(storage, u, v, tileIndex) {
614
+ return storage.sample(u, v, tileIndex);
615
+ }
616
+ function sampleTerrainFieldElevation(storage, u, v, tileIndex) {
617
+ return sampleTerrainField(storage, u, v, tileIndex).r;
618
+ }
619
+ function sampleTerrainFieldNormal(storage, u, v, tileIndex) {
620
+ const raw = sampleTerrainField(storage, u, v, tileIndex);
621
+ return vec2(raw.g, raw.b);
596
622
  }
597
623
  function packTerrainFieldSample(height, normalXZ, extra = float(0)) {
598
624
  return vec4(height, normalXZ.x, normalXZ.y, extra);
@@ -722,6 +748,7 @@ const quadtreeUpdate = param({
722
748
  distanceFactor: 1.5
723
749
  }).displayName("quadtreeUpdate");
724
750
  const surface = param(null).displayName("surface");
751
+ const terrainFieldFilter = param("linear").displayName("terrainFieldFilter");
725
752
  const elevationFn = param(() => float(0));
726
753
 
727
754
  function createLeafStorage(maxNodes) {
@@ -1533,11 +1560,13 @@ const createTerrainFieldTextureTask = task(
1533
1560
  (get, work, { resources }) => {
1534
1561
  const edgeVertexCount = get(innerTileSegments) + 3;
1535
1562
  const maxNodesValue = get(maxNodes);
1563
+ const filter = get(terrainFieldFilter);
1536
1564
  return work(
1537
1565
  () => createTerrainFieldStorage(
1538
1566
  edgeVertexCount,
1539
1567
  maxNodesValue,
1540
- resources?.renderer
1568
+ resources?.renderer,
1569
+ { filter }
1541
1570
  )
1542
1571
  );
1543
1572
  }
@@ -1812,4 +1841,4 @@ const voronoiCells = Fn((params) => {
1812
1841
  return k;
1813
1842
  });
1814
1843
 
1815
- export { ArrayTextureBackend, AtlasBackend, Dir, TerrainGeometry, TerrainMesh, Texture3DBackend, U32_EMPTY, allocLeafSet, allocSeamTable, beginUpdate, blendAngleCorrectedNormals, buildLeafIndex, buildSeams2to1, compileComputeTask, createComputePipelineTasks, createCubeSphereSurface, createElevationFieldContextTask, createFlatSurface, createInfiniteFlatSurface, createSpatialIndex, createState, createTerrainFieldStorage, createTerrainFieldTextureTask, createTerrainUniforms, createUniformsTask, deriveNormalZ, elevationFieldStageTask, elevationFn, elevationScale, executeComputeTask, getDeviceComputeLimits, innerTileSegments, instanceIdTask, isSkirtUV, isSkirtVertex, leafGpuBufferTask, leafStorageTask, loadTerrainField, loadTerrainFieldElevation, loadTerrainFieldNormal, maxLevel, maxNodes, origin, packTerrainFieldSample, positionNodeTask, quadtreeConfigTask, quadtreeUpdate, quadtreeUpdateTask, resetLeafSet, resetSeamTable, rootSize, skirtScale, storeTerrainField, surface, surfaceTask, terrainFieldStageTask, terrainGraph, terrainTasks, textureSpaceToVectorSpace, tileNodesTask, update, updateUniformsTask, vElevation, vGlobalVertexIndex, vectorSpaceToTextureSpace, voronoiCells };
1844
+ export { ArrayTextureBackend, AtlasBackend, Dir, TerrainGeometry, TerrainMesh, Texture3DBackend, U32_EMPTY, allocLeafSet, allocSeamTable, beginUpdate, blendAngleCorrectedNormals, buildLeafIndex, buildSeams2to1, compileComputeTask, createComputePipelineTasks, createCubeSphereSurface, createElevationFieldContextTask, createFlatSurface, createInfiniteFlatSurface, createSpatialIndex, createState, createTerrainFieldStorage, createTerrainFieldTextureTask, createTerrainUniforms, createUniformsTask, deriveNormalZ, elevationFieldStageTask, elevationFn, elevationScale, executeComputeTask, getDeviceComputeLimits, innerTileSegments, instanceIdTask, isSkirtUV, isSkirtVertex, leafGpuBufferTask, leafStorageTask, loadTerrainField, loadTerrainFieldElevation, loadTerrainFieldNormal, maxLevel, maxNodes, origin, packTerrainFieldSample, positionNodeTask, quadtreeConfigTask, quadtreeUpdate, quadtreeUpdateTask, resetLeafSet, resetSeamTable, rootSize, sampleTerrainField, sampleTerrainFieldElevation, sampleTerrainFieldNormal, skirtScale, storeTerrainField, surface, surfaceTask, terrainFieldFilter, terrainFieldStageTask, terrainGraph, terrainTasks, textureSpaceToVectorSpace, tileNodesTask, update, updateUniformsTask, vElevation, vGlobalVertexIndex, vectorSpaceToTextureSpace, voronoiCells };
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.7",
9
+ "version": "0.0.0-alpha.8",
10
10
  "type": "module",
11
11
  "main": "./dist/index.mjs",
12
12
  "module": "./dist/index.mjs",