diff --git a/examples-testing/changes.patch b/examples-testing/changes.patch index 644865c68..20f18f011 100644 --- a/examples-testing/changes.patch +++ b/examples-testing/changes.patch @@ -15563,20 +15563,39 @@ index 6fbdb55ae..fd5ec5705 100644 init(); diff --git a/examples-testing/examples/webgpu_custom_fog.ts b/examples-testing/examples/webgpu_custom_fog.ts -index bcfcb0fd3..be07fc038 100644 +index 8b1036a14..b3ec9805b 100644 --- a/examples-testing/examples/webgpu_custom_fog.ts +++ b/examples-testing/examples/webgpu_custom_fog.ts -@@ -4,8 +4,8 @@ import { color, fog, float, positionWorld, triNoise3D, positionView, normalWorld - import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; - import { Inspector } from 'three/addons/inspector/Inspector.js'; +@@ -7,9 +7,17 @@ import { SkyMesh } from 'three/addons/objects/SkyMesh.js'; + import { TerrainGenerator } from 'three/addons/generators/TerrainGenerator.js'; + import { ForestGenerator } from 'three/addons/generators/ForestGenerator.js'; --let camera, scene, renderer; --let controls; -+let camera: THREE.PerspectiveCamera, scene: THREE.Scene, renderer: THREE.WebGPURenderer; -+let controls: OrbitControls; +-let camera, scene, renderer, controls, timer; +-let terrain, forest, terrainGroup, forestGroup; +-let sky, sun, sunLight, pmremGenerator, envScene; ++let camera: THREE.PerspectiveCamera, ++ scene: THREE.Scene, ++ renderer: THREE.WebGPURenderer, ++ controls: FirstPersonControls, ++ timer: THREE.Timer; ++let terrain: TerrainGenerator, forest: ForestGenerator, terrainGroup: THREE.Group, forestGroup: THREE.Group; ++let sky: SkyMesh, ++ sun: THREE.Vector3, ++ sunLight: THREE.DirectionalLight, ++ pmremGenerator: THREE.PMREMGenerator, ++ envScene: THREE.Scene; - init(); + const parameters = { + elevation: 11, // sun height above the horizon, in degrees ( low = golden hour ) +@@ -126,7 +134,7 @@ async function init() { + + // gui + +- const gui = renderer.inspector.createParameters('Settings'); ++ const gui = (renderer.inspector as Inspector).createParameters('Settings'); + const skyFolder = gui.addFolder('Sun'); + skyFolder.add(parameters, 'elevation', 1, 40).step(0.5).name('elevation').onChange(updateSun); diff --git a/examples-testing/examples/webgpu_custom_fog_background.ts b/examples-testing/examples/webgpu_custom_fog_background.ts index a9b619345..e482a1e2e 100644 --- a/examples-testing/examples/webgpu_custom_fog_background.ts diff --git a/jsdoc-testing/changes.patch b/jsdoc-testing/changes.patch index c9fcb4cf8..0f40e8d0c 100644 --- a/jsdoc-testing/changes.patch +++ b/jsdoc-testing/changes.patch @@ -171,6 +171,215 @@ index baf210642..ddf63e336 100644 -import { Group } from 'three'; -import { MeshStandardNodeMaterial } from 'three/webgpu'; +export function createRoadMaterial(layout: CityGeneratorLayout): MeshStandardNodeMaterial; +diff --git a/jsdoc-testing/jsdoc/examples/jsm/generators/ForestGenerator.d.ts b/jsdoc-testing/jsdoc/examples/jsm/generators/ForestGenerator.d.ts +index 4b2772243..d481a3379 100644 +--- a/jsdoc-testing/jsdoc/examples/jsm/generators/ForestGenerator.d.ts ++++ b/jsdoc-testing/jsdoc/examples/jsm/generators/ForestGenerator.d.ts +@@ -1,3 +1,25 @@ ++import { Group, InstancedMesh, MeshStandardNodeMaterial, Node, UniformNode, Vector3 } from 'three/webgpu'; ++import { TerrainGenerator } from './TerrainGenerator.js'; ++ ++export interface ForestGeneratorParameters { ++ seed: number; ++ count: number; ++ detail: number; ++ radius: number; ++ height: number; ++ distortion: number; ++ sink: number; ++ altitudeMin: number; ++ altitudeMax: number; ++ minSlope: number; ++ densityFrequency: number; ++ minScale: number; ++ maxScale: number; ++ from: number; ++ to: number; ++ castShadow: boolean; ++} ++ + /** + * Carpets a {@link TerrainGenerator} ( or anything exposing `sampleHeight`, + * `sampleSlope`, `minY`, `maxY` and `parameters.size` ) with a forest of hundreds +@@ -18,55 +40,24 @@ + * ``` + */ + export class ForestGenerator { +- constructor(parameters?: {}); +- parameters: { +- seed: number; +- count: number; +- detail: number; +- radius: number; +- height: number; +- distortion: number; +- sink: number; +- altitudeMin: number; +- altitudeMax: number; +- minSlope: number; +- densityFrequency: number; +- minScale: number; +- maxScale: number; +- from: number; +- to: number; +- castShadow: boolean; +- }; +- from: import("three/webgpu").UniformNode; +- to: import("three/webgpu").UniformNode; +- _cameraPosition: import("three/webgpu").UniformNode; ++ constructor(parameters?: Partial); ++ ++ parameters: ForestGeneratorParameters; ++ ++ from: UniformNode<'float', number>; ++ to: UniformNode<'float', number>; ++ + material: MeshStandardNodeMaterial; + mesh: InstancedMesh | null; + group: Group | null; +- build(terrain: any): Group; +- setCameraPosition(position: any): void; ++ ++ build(terrain: TerrainGenerator): Group; ++ setCameraPosition(position: Vector3): void; + dispose(): void; ++ ++ static defaults: ForestGeneratorParameters + } +-export namespace ForestGenerator { +- namespace defaults { +- let seed: number; +- let count: number; +- let detail: number; +- let radius: number; +- let height: number; +- let distortion: number; +- let sink: number; +- let altitudeMin: number; +- let altitudeMax: number; +- let minSlope: number; +- let densityFrequency: number; +- let minScale: number; +- let maxScale: number; +- let from: number; +- let to: number; +- let castShadow: boolean; +- } +-} ++ + /** + * The single material shared by every tree in a {@link ForestGenerator}. A plain + * MeshStandardNodeMaterial lit by the scene — only the surface is authored: deep +@@ -81,7 +72,4 @@ export namespace ForestGenerator { + * @param {Node} to - distance past which no tree is drawn. + * @return {MeshStandardNodeMaterial} + */ +-export function createForestMaterial(from: Node, to: Node, camPos: any): MeshStandardNodeMaterial; +-import { MeshStandardNodeMaterial } from 'three/webgpu'; +-import { InstancedMesh } from 'three'; +-import { Group } from 'three'; ++export function createForestMaterial(from: Node<'float'>, to: Node<'float'>, camPos: Node<'vec3'>): MeshStandardNodeMaterial; +diff --git a/jsdoc-testing/jsdoc/examples/jsm/generators/TerrainGenerator.d.ts b/jsdoc-testing/jsdoc/examples/jsm/generators/TerrainGenerator.d.ts +index a5b279bf6..7ca0512e6 100644 +--- a/jsdoc-testing/jsdoc/examples/jsm/generators/TerrainGenerator.d.ts ++++ b/jsdoc-testing/jsdoc/examples/jsm/generators/TerrainGenerator.d.ts +@@ -1,3 +1,22 @@ ++import { BufferGeometry, Group, MeshStandardNodeMaterial, UniformNode } from 'three/webgpu'; ++ ++export interface TerrainGeneratorParameters { ++ seed: number; ++ size: number; ++ segments: number; ++ heightScale: number; ++ frequency: number; ++ octaves: number; ++ lacunarity: number; ++ gain: number; ++ erosion: number; ++ warp: number; ++ valleyBias: number; ++ seaLevel: number; ++ talus: number; ++ talusPasses: number; ++} ++ + /** + * Bakes a procedural mountain range into a single {@link THREE.BufferGeometry} and + * returns a `THREE.Group` ready to add to a scene. +@@ -22,55 +41,27 @@ + * ``` + */ + export class TerrainGenerator { +- constructor(parameters?: {}); +- parameters: { +- seed: number; +- size: number; +- segments: number; +- heightScale: number; +- frequency: number; +- octaves: number; +- lacunarity: number; +- gain: number; +- erosion: number; +- warp: number; +- valleyBias: number; +- seaLevel: number; +- talus: number; +- talusPasses: number; +- }; +- minHeight: import("three/webgpu").UniformNode; +- maxHeight: import("three/webgpu").UniformNode; ++ constructor(parameters?: Partial); ++ ++ parameters: TerrainGeneratorParameters; ++ ++ minHeight: UniformNode<'float', number>; ++ maxHeight: UniformNode<'float', number>; ++ + material: MeshStandardNodeMaterial; + geometry: BufferGeometry | null; + group: Group | null; ++ ++ heights?: Float32Array; ++ gridSize?: number; ++ minY?: number; ++ maxY?: number; ++ + build(): Group; +- heights: Float32Array | undefined; +- gridSize: number | undefined; +- minY: number | undefined; +- maxY: number | undefined; +- sampleHeight(x: any, z: any): number; +- sampleSlope(x: any, z: any): number; ++ ++ sampleHeight(x: number, z: number): number; ++ sampleSlope(x: number, z: number): number; + dispose(): void; ++ ++ static defaults: TerrainGeneratorParameters; + } +-export namespace TerrainGenerator { +- namespace defaults { +- let seed: number; +- let size: number; +- let segments: number; +- let heightScale: number; +- let frequency: number; +- let octaves: number; +- let lacunarity: number; +- let gain: number; +- let erosion: number; +- let warp: number; +- let valleyBias: number; +- let seaLevel: number; +- let talus: number; +- let talusPasses: number; +- } +-} +-import { MeshStandardNodeMaterial } from 'three/webgpu'; +-import { BufferGeometry } from 'three'; +-import { Group } from 'three'; diff --git a/jsdoc-testing/jsdoc/examples/jsm/generators/city/SidewalkGenerator.d.ts b/jsdoc-testing/jsdoc/examples/jsm/generators/city/SidewalkGenerator.d.ts index 81c1a0ec9..d560e432c 100644 --- a/jsdoc-testing/jsdoc/examples/jsm/generators/city/SidewalkGenerator.d.ts diff --git a/jsdoc-testing/declarations.js b/jsdoc-testing/declarations.js index 2e1d4a1d7..69d0f2793 100644 --- a/jsdoc-testing/declarations.js +++ b/jsdoc-testing/declarations.js @@ -7,6 +7,8 @@ const files = [ 'examples/jsm/generators/city/SidewalkGenerator', 'examples/jsm/generators/city/SkyscraperGenerator', 'examples/jsm/generators/CityGenerator', + 'examples/jsm/generators/ForestGenerator', + 'examples/jsm/generators/TerrainGenerator', 'examples/jsm/geometries/LoftGeometry', 'examples/jsm/lighting/ClusteredLighting', 'examples/jsm/lighting/LightProbeGrid', diff --git a/three.js b/three.js index a72e673b3..fc5a1a3ad 160000 --- a/three.js +++ b/three.js @@ -1 +1 @@ -Subproject commit a72e673b300a9aa1ca7723d06bd912468e78cecb +Subproject commit fc5a1a3adacaab36c26ced0bb9c214b8306fb8a5 diff --git a/types/three/examples/jsm/generators/ForestGenerator.d.ts b/types/three/examples/jsm/generators/ForestGenerator.d.ts new file mode 100644 index 000000000..ea278980c --- /dev/null +++ b/types/three/examples/jsm/generators/ForestGenerator.d.ts @@ -0,0 +1,79 @@ +import { Group, InstancedMesh, MeshStandardNodeMaterial, Node, UniformNode, Vector3 } from "three/webgpu"; +import { TerrainGenerator } from "./TerrainGenerator.js"; + +export interface ForestGeneratorParameters { + seed: number; + count: number; + detail: number; + radius: number; + height: number; + distortion: number; + sink: number; + altitudeMin: number; + altitudeMax: number; + minSlope: number; + densityFrequency: number; + minScale: number; + maxScale: number; + from: number; + to: number; + castShadow: boolean; +} + +/** + * Carpets a {@link TerrainGenerator} ( or anything exposing `sampleHeight`, + * `sampleSlope`, `minY`, `maxY` and `parameters.size` ) with a forest of hundreds + * of thousands of trees in a single draw call. + * + * Each tree is the cheapest thing that still reads as a tree: a ~20-face icosphere + * squashed into a tapered teardrop and lumped with a little noise, carrying a baked + * dark-base / bright-top gradient. Tens of triangles each, so a single + * {@link THREE.InstancedMesh} of half a million of them costs one draw call. Trees + * are placed by rejection sampling against ecological rules — a min/max altitude + * band ( above the mist floor, below the snowline ), a slope limit ( none on + * cliffs ) and a low-frequency density mask that opens clearings — then jittered in + * yaw, lean and ( squared-biased ) scale so the stand never reads as copies. + * + * ```js + * const forest = new ForestGenerator( { count: 500000 } ); + * scene.add( forest.build( terrain ) ); + * ``` + */ +export class ForestGenerator { + constructor(parameters?: Partial); + + parameters: ForestGeneratorParameters; + + from: UniformNode<"float", number>; + to: UniformNode<"float", number>; + + material: MeshStandardNodeMaterial; + mesh: InstancedMesh | null; + group: Group | null; + + build(terrain: TerrainGenerator): Group; + setCameraPosition(position: Vector3): void; + dispose(): void; + + static defaults: ForestGeneratorParameters; +} + +/** + * The single material shared by every tree in a {@link ForestGenerator}. A plain + * MeshStandardNodeMaterial lit by the scene — only the surface is authored: deep + * shadowed green in the recesses rising to a bright, yellow-green sunlit crown, + * mottled into needle clumps by 3D noise, with a matching bump so the clumps catch + * the light. Half a million instanced blobs makes this mesh vertex-bound, so the + * regional colour drift is baked to a per-instance attribute ( no shader noise for it ), + * and the costly clump noise + bump are **gated by distance** — full detail on the near + * trees ( where it reads ), skipped on the far canopy ( where it is sub-pixel ). + * + * @param {Node} from - distance within which every tree is drawn. + * @param {Node} to - distance past which no tree is drawn. + * @return {MeshStandardNodeMaterial} + */ +export function createForestMaterial( + from: Node<"float">, + to: Node<"float">, + camPos: Node<"vec3">, +): MeshStandardNodeMaterial; diff --git a/types/three/examples/jsm/generators/TerrainGenerator.d.ts b/types/three/examples/jsm/generators/TerrainGenerator.d.ts new file mode 100644 index 000000000..6e13320c4 --- /dev/null +++ b/types/three/examples/jsm/generators/TerrainGenerator.d.ts @@ -0,0 +1,67 @@ +import { BufferGeometry, Group, MeshStandardNodeMaterial, UniformNode } from "three/webgpu"; + +export interface TerrainGeneratorParameters { + seed: number; + size: number; + segments: number; + heightScale: number; + frequency: number; + octaves: number; + lacunarity: number; + gain: number; + erosion: number; + warp: number; + valleyBias: number; + seaLevel: number; + talus: number; + talusPasses: number; +} + +/** + * Bakes a procedural mountain range into a single {@link THREE.BufferGeometry} and + * returns a `THREE.Group` ready to add to a scene. + * + * The heightfield is a derivative-damped fractal sum ( Quilez's fake erosion ): each + * octave is suppressed where the running slope is already steep, concentrating detail + * into weathered ridgelines, and a low-frequency domain warp makes those ridges + * meander. A few passes of thermal ( talus ) erosion then relax any slope past the + * angle of repose, settling the fractal's needle-spikes into real crests. + * + * The grid is triangulated with alternating quad diagonals ( a diamond pattern ), so a + * coarse mesh holds its silhouette without a one-way grain. The surface shades itself + * from altitude and slope in TSL — grass, forest, rock, scree and snow, with detail + * normals and aerial perspective — so no material or textures are needed. + * + * The baked height grid is exposed through {@link TerrainGenerator#sampleHeight} so a + * scattered forest ( or anything else ) can sit exactly on the surface. + * + * ```js + * const terrain = new TerrainGenerator( { seed: 1 } ); + * scene.add( terrain.build() ); + * ``` + */ +export class TerrainGenerator { + constructor(parameters?: Partial); + + parameters: TerrainGeneratorParameters; + + minHeight: UniformNode<"float", number>; + maxHeight: UniformNode<"float", number>; + + material: MeshStandardNodeMaterial; + geometry: BufferGeometry | null; + group: Group | null; + + heights?: Float32Array; + gridSize?: number; + minY?: number; + maxY?: number; + + build(): Group; + + sampleHeight(x: number, z: number): number; + sampleSlope(x: number, z: number): number; + dispose(): void; + + static defaults: TerrainGeneratorParameters; +}