super-three 0.184.0 → 0.185.0
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/build/three.cjs +1260 -927
- package/build/three.core.js +1104 -829
- package/build/three.core.min.js +1 -1
- package/build/three.module.js +156 -98
- package/build/three.module.min.js +1 -1
- package/build/three.tsl.js +11 -4
- package/build/three.tsl.min.js +1 -1
- package/build/three.webgpu.js +7538 -3367
- package/build/three.webgpu.min.js +1 -1
- package/build/three.webgpu.nodes.js +7538 -3366
- package/build/three.webgpu.nodes.min.js +1 -1
- package/examples/jsm/Addons.js +3 -0
- package/examples/jsm/controls/ArcballControls.js +6 -2
- package/examples/jsm/controls/FirstPersonControls.js +79 -42
- package/examples/jsm/controls/TransformControls.js +68 -15
- package/examples/jsm/csm/CSMFrustum.js +31 -6
- package/examples/jsm/csm/CSMShadowNode.js +4 -1
- package/examples/jsm/effects/AsciiEffect.js +14 -1
- package/examples/jsm/exporters/DRACOExporter.js +27 -6
- package/examples/jsm/exporters/GLTFExporter.js +114 -12
- package/examples/jsm/exporters/PLYExporter.js +286 -73
- package/examples/jsm/exporters/USDZExporter.js +347 -112
- package/examples/jsm/generators/CityGenerator.js +346 -0
- package/examples/jsm/generators/ForestGenerator.js +347 -0
- package/examples/jsm/generators/TerrainGenerator.js +504 -0
- package/examples/jsm/generators/TreeGenerator.js +377 -0
- package/examples/jsm/generators/city/SidewalkGenerator.js +253 -0
- package/examples/jsm/generators/city/SkyscraperGenerator.js +1357 -0
- package/examples/jsm/geometries/LoftGeometry.js +355 -0
- package/examples/jsm/helpers/LightProbeGridHelper.js +1 -1
- package/examples/jsm/helpers/LightProbeHelper.js +5 -4
- package/examples/jsm/helpers/ViewHelper.js +16 -6
- package/examples/jsm/inspector/Inspector.js +53 -15
- package/examples/jsm/inspector/RendererInspector.js +152 -36
- package/examples/jsm/inspector/extensions/tsl-graph/TSLGraphEditor.js +1 -1
- package/examples/jsm/inspector/extensions/tsl-graph/TSLGraphLoader.js +1 -1
- package/examples/jsm/inspector/tabs/Console.js +200 -18
- package/examples/jsm/inspector/tabs/Memory.js +8 -0
- package/examples/jsm/inspector/tabs/Parameters.js +64 -4
- package/examples/jsm/inspector/tabs/Performance.js +21 -6
- package/examples/jsm/inspector/tabs/Settings.js +51 -9
- package/examples/jsm/inspector/tabs/Timeline.js +178 -93
- package/examples/jsm/inspector/tabs/Viewer.js +628 -16
- package/examples/jsm/inspector/ui/Graph.js +162 -25
- package/examples/jsm/inspector/ui/Item.js +92 -13
- package/examples/jsm/inspector/ui/List.js +1 -1
- package/examples/jsm/inspector/ui/Profiler.js +90 -45
- package/examples/jsm/inspector/ui/Style.js +1788 -1401
- package/examples/jsm/inspector/ui/Tab.js +5 -3
- package/examples/jsm/inspector/ui/Values.js +71 -6
- package/examples/jsm/inspector/ui/utils.js +130 -10
- package/examples/jsm/libs/basis/README.md +0 -1
- package/examples/jsm/libs/demuxer_mp4.js +3 -3
- package/examples/jsm/libs/draco/README.md +0 -1
- package/examples/jsm/libs/meshopt_clusterizer.module.js +421 -0
- package/examples/jsm/libs/meshopt_simplifier.module.js +627 -0
- package/examples/jsm/libs/mikktspace.module.js +34 -9
- package/examples/jsm/lighting/ClusteredLighting.js +55 -0
- package/examples/jsm/lighting/LightProbeGrid.js +106 -76
- package/examples/jsm/lines/LineMaterial.js +38 -15
- package/examples/jsm/lines/LineSegments2.js +8 -0
- package/examples/jsm/lines/webgpu/LineSegments2.js +8 -0
- package/examples/jsm/loaders/3DMLoader.js +1 -1
- package/examples/jsm/loaders/DRACOLoader.js +46 -13
- package/examples/jsm/loaders/EXRLoader.js +27 -55
- package/examples/jsm/loaders/HDRLoader.js +6 -28
- package/examples/jsm/loaders/KTX2Loader.js +31 -12
- package/examples/jsm/loaders/LDrawLoader.js +8 -8
- package/examples/jsm/loaders/LUT3dlLoader.js +2 -2
- package/examples/jsm/loaders/LUTCubeLoader.js +2 -2
- package/examples/jsm/loaders/LWOLoader.js +4 -0
- package/examples/jsm/loaders/LottieLoader.js +1 -1
- package/examples/jsm/loaders/MaterialXLoader.js +31 -9
- package/examples/jsm/loaders/NRRDLoader.js +3 -3
- package/examples/jsm/loaders/PCDLoader.js +7 -7
- package/examples/jsm/loaders/PLYLoader.js +218 -55
- package/examples/jsm/loaders/SVGLoader.js +547 -495
- package/examples/jsm/loaders/TTFLoader.js +1 -1
- package/examples/jsm/loaders/USDLoader.js +33 -9
- package/examples/jsm/loaders/VRMLLoader.js +1 -1
- package/examples/jsm/loaders/usd/USDAParser.js +93 -20
- package/examples/jsm/loaders/usd/USDCParser.js +1 -1
- package/examples/jsm/loaders/usd/USDComposer.js +52 -19
- package/examples/jsm/misc/TileCreasedNormalsPlugin.js +270 -0
- package/examples/jsm/misc/Volume.js +2 -2
- package/examples/jsm/objects/GroundedSkybox.js +1 -1
- package/examples/jsm/objects/Reflector.js +2 -3
- package/examples/jsm/objects/Water2Mesh.js +1 -1
- package/examples/jsm/physics/AmmoPhysics.js +15 -8
- package/examples/jsm/physics/RapierPhysics.js +27 -2
- package/examples/jsm/shaders/VolumeShader.js +31 -44
- package/examples/jsm/transpiler/GLSLDecoder.js +6 -6
- package/examples/jsm/transpiler/Linker.js +1 -1
- package/examples/jsm/tsl/display/BloomNode.js +59 -19
- package/examples/jsm/tsl/display/DepthOfFieldNode.js +2 -2
- package/examples/jsm/tsl/display/FXAANode.js +11 -12
- package/examples/jsm/tsl/display/GTAONode.js +34 -13
- package/examples/jsm/tsl/display/GodraysNode.js +1 -1
- package/examples/jsm/tsl/display/ImportanceSampledEnvironment.js +560 -0
- package/examples/jsm/tsl/display/PixelationPassNode.js +2 -2
- package/examples/jsm/tsl/display/RecurrentDenoiseNode.js +912 -0
- package/examples/jsm/tsl/display/SSAAPassNode.js +13 -25
- package/examples/jsm/tsl/display/SSGINode.js +89 -39
- package/examples/jsm/tsl/display/SSRNode.js +829 -132
- package/examples/jsm/tsl/display/SSSNode.js +2 -2
- package/examples/jsm/tsl/display/TemporalReprojectNode.js +1023 -0
- package/examples/jsm/tsl/lighting/ClusteredLightsNode.js +622 -0
- package/examples/jsm/tsl/lighting/DynamicLightsNode.js +1 -1
- package/examples/jsm/tsl/math/curlNoise.js +107 -0
- package/examples/jsm/tsl/shadows/TileShadowNode.js +1 -1
- package/examples/jsm/tsl/shadows/TileShadowNodeHelper.js +3 -3
- package/examples/jsm/tsl/utils/GroundedSkybox.js +62 -0
- package/examples/jsm/tsl/utils/RNoise.js +51 -0
- package/examples/jsm/tsl/utils/SpecularHelpers.js +325 -0
- package/examples/jsm/utils/BufferGeometryUtils.js +126 -59
- package/examples/jsm/utils/GeometryCompressionUtils.js +1 -1
- package/examples/jsm/utils/SceneOptimizer.js +1 -1
- package/examples/jsm/webxr/ARButton.js +1 -1
- package/examples/jsm/webxr/WebGLXRFallback.js +89 -0
- package/examples/jsm/webxr/XRControllerModelFactory.js +2 -2
- package/package.json +7 -7
- package/src/Three.TSL.js +10 -3
- package/src/Three.WebGPU.js +4 -0
- package/src/animation/AnimationAction.js +15 -11
- package/src/animation/AnimationClip.js +0 -139
- package/src/animation/KeyframeTrack.js +2 -2
- package/src/animation/PropertyBinding.js +2 -2
- package/src/cameras/Camera.js +2 -2
- package/src/cameras/StereoCamera.js +5 -2
- package/src/constants.js +1 -1
- package/src/core/BufferGeometry.js +29 -7
- package/src/core/Object3D.js +17 -7
- package/src/core/Raycaster.js +1 -1
- package/src/core/RenderTarget.js +15 -2
- package/src/extras/PMREMGenerator.js +6 -4
- package/src/extras/TextureUtils.js +1 -1
- package/src/extras/core/ShapePath.js +149 -160
- package/src/geometries/SphereGeometry.js +8 -3
- package/src/helpers/DirectionalLightHelper.js +2 -0
- package/src/helpers/HemisphereLightHelper.js +2 -0
- package/src/helpers/PointLightHelper.js +2 -0
- package/src/helpers/SpotLightHelper.js +1 -1
- package/src/loaders/DataTextureLoader.js +69 -38
- package/src/loaders/LoadingManager.js +5 -0
- package/src/loaders/MaterialLoader.js +36 -266
- package/src/loaders/ObjectLoader.js +3 -1
- package/src/materials/LineBasicMaterial.js +4 -0
- package/src/materials/Material.js +196 -1
- package/src/materials/MeshBasicMaterial.js +24 -0
- package/src/materials/MeshDepthMaterial.js +10 -0
- package/src/materials/MeshDistanceMaterial.js +10 -0
- package/src/materials/MeshLambertMaterial.js +40 -1
- package/src/materials/MeshMatcapMaterial.js +25 -1
- package/src/materials/MeshNormalMaterial.js +9 -1
- package/src/materials/MeshPhongMaterial.js +40 -1
- package/src/materials/MeshPhysicalMaterial.js +38 -0
- package/src/materials/MeshStandardMaterial.js +42 -1
- package/src/materials/MeshToonMaterial.js +34 -1
- package/src/materials/PointsMaterial.js +7 -0
- package/src/materials/ShaderMaterial.js +86 -0
- package/src/materials/SpriteMaterial.js +7 -0
- package/src/materials/nodes/Line2NodeMaterial.js +378 -322
- package/src/materials/nodes/MeshBasicNodeMaterial.js +2 -2
- package/src/materials/nodes/MeshNormalNodeMaterial.js +2 -2
- package/src/materials/nodes/MeshPhongNodeMaterial.js +0 -9
- package/src/materials/nodes/MeshPhysicalNodeMaterial.js +0 -30
- package/src/materials/nodes/MeshSSSNodeMaterial.js +0 -13
- package/src/materials/nodes/MeshStandardNodeMaterial.js +0 -11
- package/src/materials/nodes/NodeMaterial.js +115 -71
- package/src/materials/nodes/SpriteNodeMaterial.js +0 -10
- package/src/materials/nodes/manager/NodeMaterialObserver.js +49 -20
- package/src/math/Box3.js +5 -0
- package/src/math/FrustumArray.js +103 -133
- package/src/math/Interpolant.js +1 -1
- package/src/math/MathUtils.js +2 -2
- package/src/math/Matrix3.js +11 -0
- package/src/math/Matrix4.js +28 -3
- package/src/math/Vector2.js +2 -2
- package/src/math/Vector3.js +2 -2
- package/src/math/Vector4.js +2 -2
- package/src/math/interpolants/BezierInterpolant.js +4 -6
- package/src/nodes/Nodes.js +2 -5
- package/src/nodes/TSL.js +8 -6
- package/src/nodes/accessors/Arrays.js +4 -4
- package/src/nodes/accessors/Batch.js +108 -0
- package/src/nodes/accessors/Bitangent.js +2 -2
- package/src/nodes/accessors/BufferAttributeNode.js +13 -2
- package/src/nodes/accessors/Camera.js +7 -7
- package/src/nodes/accessors/ClippingNode.js +1 -1
- package/src/nodes/accessors/Instance.js +264 -0
- package/src/nodes/accessors/MaterialNode.js +18 -2
- package/src/nodes/accessors/MaterialProperties.js +4 -3
- package/src/nodes/accessors/{MorphNode.js → Morph.js} +99 -112
- package/src/nodes/accessors/Normal.js +13 -13
- package/src/nodes/accessors/Position.js +5 -1
- package/src/nodes/accessors/ReferenceNode.js +1 -0
- package/src/nodes/accessors/ReflectVector.js +3 -3
- package/src/nodes/accessors/SceneProperties.js +1 -1
- package/src/nodes/accessors/Skinning.js +263 -0
- package/src/nodes/accessors/StorageBufferNode.js +2 -2
- package/src/nodes/accessors/StorageTexture3DNode.js +100 -0
- package/src/nodes/accessors/StorageTextureNode.js +39 -7
- package/src/nodes/accessors/Tangent.js +4 -4
- package/src/nodes/accessors/TextureNode.js +60 -5
- package/src/nodes/code/FunctionNode.js +2 -16
- package/src/nodes/core/MRTNode.js +7 -2
- package/src/nodes/core/Node.js +39 -2
- package/src/nodes/core/NodeBuilder.js +201 -41
- package/src/nodes/core/NodeUtils.js +13 -13
- package/src/nodes/core/OutputStructNode.js +1 -1
- package/src/nodes/core/OverrideContextNode.js +153 -0
- package/src/nodes/core/PropertyNode.js +51 -5
- package/src/nodes/core/StackNode.js +56 -34
- package/src/nodes/core/StructNode.js +4 -6
- package/src/nodes/core/StructTypeNode.js +6 -6
- package/src/nodes/core/VarNode.js +8 -0
- package/src/nodes/core/VaryingNode.js +12 -2
- package/src/nodes/display/BlendModes.js +1 -41
- package/src/nodes/display/BumpMapNode.js +6 -1
- package/src/nodes/display/ColorAdjustment.js +10 -9
- package/src/nodes/display/FrontFacingNode.js +38 -9
- package/src/nodes/display/NormalMapNode.js +2 -2
- package/src/nodes/display/PassNode.js +35 -43
- package/src/nodes/display/PremultiplyAlphaFunctions.js +39 -0
- package/src/nodes/display/RenderOutputNode.js +12 -3
- package/src/nodes/functions/VolumetricLightingModel.js +40 -7
- package/src/nodes/gpgpu/WorkgroupInfoNode.js +1 -1
- package/src/nodes/lighting/EnvironmentNode.js +2 -2
- package/src/nodes/lighting/IESSpotLightNode.js +40 -2
- package/src/nodes/lighting/LightingContextNode.js +10 -2
- package/src/nodes/lighting/LightsNode.js +90 -37
- package/src/nodes/lighting/PointShadowNode.js +6 -1
- package/src/nodes/lighting/ShadowFilterNode.js +21 -52
- package/src/nodes/lighting/ShadowNode.js +34 -14
- package/src/nodes/materialx/lib/mx_noise.js +2 -2
- package/src/nodes/math/ConditionalNode.js +3 -3
- package/src/nodes/math/MathNode.js +39 -11
- package/src/nodes/math/OperatorNode.js +23 -23
- package/src/nodes/parsers/GLSLNodeFunction.js +1 -1
- package/src/nodes/pmrem/PMREMNode.js +33 -2
- package/src/nodes/tsl/TSLCore.js +63 -18
- package/src/nodes/utils/EquirectUV.js +30 -5
- package/src/nodes/utils/FunctionOverloadingNode.js +1 -1
- package/src/nodes/utils/Packing.js +35 -4
- package/src/nodes/utils/RTTNode.js +40 -24
- package/src/nodes/utils/ReflectorNode.js +1 -0
- package/src/nodes/utils/StorageArrayElementNode.js +1 -1
- package/src/objects/BatchedMesh.js +22 -14
- package/src/objects/InstancedMesh.js +0 -11
- package/src/objects/Skeleton.js +0 -9
- package/src/renderers/WebGLRenderer.js +19 -15
- package/src/renderers/common/Backend.js +49 -5
- package/src/renderers/common/Bindings.js +114 -45
- package/src/renderers/common/Buffer.js +9 -0
- package/src/renderers/common/ClippingContext.js +10 -2
- package/src/renderers/common/Info.js +63 -22
- package/src/renderers/common/Lighting.js +53 -5
- package/src/renderers/common/RenderList.js +41 -4
- package/src/renderers/common/RenderObject.js +73 -13
- package/src/renderers/common/Renderer.js +281 -108
- package/src/renderers/common/Sampler.js +16 -48
- package/src/renderers/common/Textures.js +32 -5
- package/src/renderers/common/TimestampQueryPool.js +1 -1
- package/src/renderers/common/UniformsGroup.js +31 -9
- package/src/renderers/common/XRManager.js +340 -22
- package/src/renderers/common/extras/PMREMGenerator.js +15 -38
- package/src/renderers/common/nodes/NodeBuilderState.js +9 -1
- package/src/renderers/common/nodes/NodeLibrary.js +4 -4
- package/src/renderers/common/nodes/NodeManager.js +63 -14
- package/src/renderers/common/nodes/NodeUniformBuffer.js +13 -0
- package/src/renderers/shaders/ShaderChunk/common.glsl.js +13 -4
- package/src/renderers/shaders/ShaderChunk/defaultnormal_vertex.glsl.js +0 -6
- package/src/renderers/shaders/ShaderChunk/envmap_fragment.glsl.js +1 -1
- package/src/renderers/shaders/ShaderChunk/envmap_physical_pars_fragment.glsl.js +2 -2
- package/src/renderers/shaders/ShaderChunk/envmap_vertex.glsl.js +1 -1
- package/src/renderers/shaders/ShaderChunk/lights_fragment_begin.glsl.js +1 -1
- package/src/renderers/shaders/ShaderChunk/lights_pars_begin.glsl.js +1 -1
- package/src/renderers/shaders/ShaderChunk/normal_fragment_begin.glsl.js +2 -2
- package/src/renderers/shaders/ShaderChunk/normal_vertex.glsl.js +6 -0
- package/src/renderers/shaders/ShaderChunk/shadowmap_vertex.glsl.js +1 -1
- package/src/renderers/shaders/ShaderChunk/transmission_fragment.glsl.js +1 -1
- package/src/renderers/shaders/ShaderLib/distance.glsl.js +1 -1
- package/src/renderers/webgl/WebGLOutput.js +2 -1
- package/src/renderers/webgl/WebGLProgram.js +5 -4
- package/src/renderers/webgl/WebGLPrograms.js +12 -5
- package/src/renderers/webgl/WebGLRenderLists.js +9 -1
- package/src/renderers/webgl/WebGLShaderCache.js +5 -11
- package/src/renderers/webgl/WebGLTextures.js +20 -7
- package/src/renderers/webgl/WebGLUniformsGroups.js +66 -38
- package/src/renderers/webgl-fallback/WebGLBackend.js +84 -25
- package/src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js +138 -8
- package/src/renderers/webgl-fallback/utils/WebGLAttributeUtils.js +1 -1
- package/src/renderers/webgl-fallback/utils/WebGLTextureUtils.js +52 -22
- package/src/renderers/webgl-fallback/utils/WebGLUtils.js +4 -2
- package/src/renderers/webgpu/WebGPUBackend.js +480 -169
- package/src/renderers/webgpu/descriptors/GPUBindGroupDescriptor.js +48 -0
- package/src/renderers/webgpu/descriptors/GPUBufferDescriptor.js +57 -0
- package/src/renderers/webgpu/descriptors/GPUCommandEncoderDescriptor.js +30 -0
- package/src/renderers/webgpu/descriptors/GPUComputePassDescriptor.js +38 -0
- package/src/renderers/webgpu/descriptors/GPUComputePipelineDescriptor.js +48 -0
- package/src/renderers/webgpu/descriptors/GPUCopyExternalImageDestInfo.js +47 -0
- package/src/renderers/webgpu/descriptors/GPUCopyExternalImageSourceInfo.js +50 -0
- package/src/renderers/webgpu/descriptors/GPUExtent3D.js +51 -0
- package/src/renderers/webgpu/descriptors/GPUPipelineLayoutDescriptor.js +39 -0
- package/src/renderers/webgpu/descriptors/GPUQuerySetDescriptor.js +47 -0
- package/src/renderers/webgpu/descriptors/GPURenderBundleEncoderDescriptor.js +74 -0
- package/src/renderers/webgpu/descriptors/GPURenderPassColorAttachment.js +72 -0
- package/src/renderers/webgpu/descriptors/GPURenderPassDepthStencilAttachment.js +99 -0
- package/src/renderers/webgpu/descriptors/GPURenderPassDescriptor.js +72 -0
- package/src/renderers/webgpu/descriptors/GPURenderPassTimestampWrites.js +49 -0
- package/src/renderers/webgpu/descriptors/GPURenderPipelineDescriptor.js +130 -0
- package/src/renderers/webgpu/descriptors/GPUSamplerDescriptor.js +119 -0
- package/src/renderers/webgpu/descriptors/GPUShaderModuleDescriptor.js +46 -0
- package/src/renderers/webgpu/descriptors/GPUTexelCopyBufferInfo.js +57 -0
- package/src/renderers/webgpu/descriptors/GPUTexelCopyBufferLayout.js +48 -0
- package/src/renderers/webgpu/descriptors/GPUTexelCopyTextureInfo.js +61 -0
- package/src/renderers/webgpu/descriptors/GPUTextureDescriptor.js +99 -0
- package/src/renderers/webgpu/descriptors/GPUTextureViewDescriptor.js +108 -0
- package/src/renderers/webgpu/nodes/StandardNodeLibrary.js +0 -1
- package/src/renderers/webgpu/nodes/WGSLNodeBuilder.js +164 -7
- package/src/renderers/webgpu/nodes/WGSLNodeFunction.js +1 -1
- package/src/renderers/webgpu/utils/WebGPUAttributeUtils.js +98 -38
- package/src/renderers/webgpu/utils/WebGPUBindingUtils.js +72 -68
- package/src/renderers/webgpu/utils/WebGPUPipelineUtils.js +164 -69
- package/src/renderers/webgpu/utils/WebGPUTexturePassUtils.js +158 -95
- package/src/renderers/webgpu/utils/WebGPUTextureUtils.js +297 -141
- package/src/renderers/webgpu/utils/WebGPUTimestampQueryPool.js +33 -18
- package/src/renderers/webgpu/utils/WebGPUUtils.js +38 -3
- package/src/textures/DepthTexture.js +1 -1
- package/src/textures/ExternalTexture.js +0 -3
- package/examples/fonts/LICENSE +0 -13
- package/examples/fonts/MPLUSRounded1c/MPLUSRounded1c-Regular.typeface.json.zip +0 -0
- package/examples/fonts/MPLUSRounded1c/OFL.txt +0 -91
- package/examples/fonts/README.md +0 -11
- package/examples/fonts/droid/NOTICE +0 -190
- package/examples/fonts/droid/README.txt +0 -18
- package/examples/fonts/droid/droid_sans_bold.typeface.json +0 -1
- package/examples/fonts/droid/droid_sans_mono_regular.typeface.json +0 -1
- package/examples/fonts/droid/droid_sans_regular.typeface.json +0 -1
- package/examples/fonts/droid/droid_serif_bold.typeface.json +0 -1
- package/examples/fonts/droid/droid_serif_regular.typeface.json +0 -1
- package/examples/fonts/gentilis_bold.typeface.json +0 -1
- package/examples/fonts/gentilis_regular.typeface.json +0 -1
- package/examples/fonts/helvetiker_bold.typeface.json +0 -1
- package/examples/fonts/helvetiker_regular.typeface.json +0 -1
- package/examples/fonts/optimer_bold.typeface.json +0 -1
- package/examples/fonts/optimer_regular.typeface.json +0 -1
- package/examples/fonts/ttf/README.md +0 -9
- package/examples/fonts/ttf/kenpixel.ttf +0 -0
- package/examples/jsm/inspector/extensions/extensions.json +0 -6
- package/examples/jsm/libs/ammo.wasm.js +0 -822
- package/examples/jsm/libs/ammo.wasm.wasm +0 -0
- package/examples/jsm/libs/draco/draco_encoder.js +0 -33
- package/examples/jsm/libs/draco/gltf/draco_encoder.js +0 -33
- package/examples/jsm/libs/lottie_canvas.module.js +0 -14849
- package/examples/jsm/libs/opentype.module.js +0 -14506
- package/examples/jsm/libs/rhino3dm/rhino3dm.js +0 -21
- package/examples/jsm/libs/rhino3dm/rhino3dm.module.js +0 -16
- package/examples/jsm/libs/rhino3dm/rhino3dm.wasm +0 -0
- package/examples/jsm/lighting/TiledLighting.js +0 -42
- package/examples/jsm/tsl/display/AnamorphicNode.js +0 -293
- package/examples/jsm/tsl/lighting/TiledLightsNode.js +0 -442
- package/src/nodes/accessors/BatchNode.js +0 -163
- package/src/nodes/accessors/InstanceNode.js +0 -349
- package/src/nodes/accessors/InstancedMeshNode.js +0 -50
- package/src/nodes/accessors/SkinningNode.js +0 -328
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Group,
|
|
3
|
+
Matrix4
|
|
4
|
+
} from 'three';
|
|
5
|
+
|
|
6
|
+
import { MeshStandardNodeMaterial } from 'three/webgpu';
|
|
7
|
+
import { cameraPosition, color, float, floor, Fn, fract, fwidth, hash, If, mix, mod, mx_fractal_noise_float, mx_noise_float, normalView, positionView, positionWorld, smoothstep, step, uint, varying, vec4 } from 'three/tsl';
|
|
8
|
+
|
|
9
|
+
import { SkyscraperGenerator, createSkyscraperMaterial, buildingPalette } from './city/SkyscraperGenerator.js';
|
|
10
|
+
import { SidewalkGenerator } from './city/SidewalkGenerator.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Lays out a grid of city blocks and fills each lot with a {@link SkyscraperGenerator}
|
|
14
|
+
* tower of its own seed, height and footprint, optionally on raised sidewalk
|
|
15
|
+
* slabs (curbs). Returns a `THREE.Group` ready to add to a scene.
|
|
16
|
+
*
|
|
17
|
+
* Pass a building material to dress the towers; the sidewalks dress themselves
|
|
18
|
+
* via {@link SidewalkGenerator}. The layout is exposed as
|
|
19
|
+
* {@link CityGenerator#layout} so the surrounding scene (road markings, etc.)
|
|
20
|
+
* can align to the same grid.
|
|
21
|
+
*
|
|
22
|
+
* ```js
|
|
23
|
+
* const city = new CityGenerator( { seed: 1 } );
|
|
24
|
+
* scene.add( city.build( materials ) );
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
class CityGenerator {
|
|
28
|
+
|
|
29
|
+
constructor( parameters = {} ) {
|
|
30
|
+
|
|
31
|
+
this.parameters = Object.assign( {}, CityGenerator.defaults, parameters );
|
|
32
|
+
this.layout = cityLayout( this.parameters );
|
|
33
|
+
|
|
34
|
+
this.generators = [];
|
|
35
|
+
this.sidewalk = new SidewalkGenerator( {
|
|
36
|
+
width: this.layout.blockW,
|
|
37
|
+
depth: this.layout.blockD,
|
|
38
|
+
height: this.parameters.curbHeight,
|
|
39
|
+
radius: this.parameters.curbRadius
|
|
40
|
+
} );
|
|
41
|
+
this.group = null;
|
|
42
|
+
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
build( materials = {} ) {
|
|
46
|
+
|
|
47
|
+
this.dispose();
|
|
48
|
+
|
|
49
|
+
const group = new Group();
|
|
50
|
+
group.name = 'City';
|
|
51
|
+
|
|
52
|
+
const L = this.layout;
|
|
53
|
+
const random = createRandom( this.parameters.seed );
|
|
54
|
+
|
|
55
|
+
// raise the lots onto rounded sidewalk slabs ( curbs ) when curbHeight > 0
|
|
56
|
+
|
|
57
|
+
const curb = this.parameters.curbHeight;
|
|
58
|
+
const slabs = [];
|
|
59
|
+
|
|
60
|
+
for ( let bx = 0; bx < L.blocksX; bx ++ ) {
|
|
61
|
+
|
|
62
|
+
for ( let bz = 0; bz < L.blocksZ; bz ++ ) {
|
|
63
|
+
|
|
64
|
+
const blockX = - L.cityW / 2 + bx * ( L.blockW + L.street );
|
|
65
|
+
const blockZ = - L.cityD / 2 + bz * ( L.blockD + L.street );
|
|
66
|
+
|
|
67
|
+
if ( curb > 0 ) {
|
|
68
|
+
|
|
69
|
+
slabs.push( new Matrix4().makeTranslation( blockX + L.blockW / 2, 0, blockZ + L.blockD / 2 ) );
|
|
70
|
+
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
for ( let lx = 0; lx < L.lotsX; lx ++ ) {
|
|
74
|
+
|
|
75
|
+
for ( let lz = 0; lz < L.lotsZ; lz ++ ) {
|
|
76
|
+
|
|
77
|
+
// a chamfered corner only reads as architecture when it faces the
|
|
78
|
+
// block's corner ( the street intersection ), so only the four corner
|
|
79
|
+
// lots are cut, each toward its own outward corner; the rest stay square
|
|
80
|
+
const cornerX = lx === 0 ? - 1 : ( lx === L.lotsX - 1 ? 1 : 0 );
|
|
81
|
+
const cornerZ = lz === 0 ? - 1 : ( lz === L.lotsZ - 1 ? 1 : 0 );
|
|
82
|
+
const onCorner = cornerX !== 0 && cornerZ !== 0;
|
|
83
|
+
|
|
84
|
+
const tall = random();
|
|
85
|
+
|
|
86
|
+
const generator = new SkyscraperGenerator( {
|
|
87
|
+
seed: Math.floor( random() * 100000 ),
|
|
88
|
+
totalHeight: 38 + tall * tall * 114, // a few tall towers, mostly mid-rise
|
|
89
|
+
footprint: { width: L.lot - 1 - random() * 4, depth: L.lot - 1 - random() * 4 }, // nearly fill the lot so neighbours sit close; width and depth vary independently
|
|
90
|
+
floorHeight: 3.4 + random() * 1.8,
|
|
91
|
+
bayWidth: 1.9 + random() * 2.1,
|
|
92
|
+
pierWidth: 0.4 + random() * 0.5,
|
|
93
|
+
pierDepth: 0.3 + random() * 0.4,
|
|
94
|
+
chamferWidth: onCorner ? 3 + random() * 4 : 0,
|
|
95
|
+
chamferCornerX: cornerX,
|
|
96
|
+
chamferCornerZ: cornerZ,
|
|
97
|
+
setbackDepth: random() < 0.4 ? 0.8 + random() * 2 : 0, // only some towers step back at the crown; the rest rise flat
|
|
98
|
+
stringCourseEvery: random() < 0.85 ? 3 + Math.floor( random() * 6 ) : 0
|
|
99
|
+
}, materials.building );
|
|
100
|
+
|
|
101
|
+
const building = generator.build();
|
|
102
|
+
building.position.set( blockX + ( lx + 0.5 ) * L.lot, curb, blockZ + ( lz + 0.5 ) * L.lot );
|
|
103
|
+
building.castShadow = building.receiveShadow = true;
|
|
104
|
+
|
|
105
|
+
group.add( building );
|
|
106
|
+
this.generators.push( generator );
|
|
107
|
+
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if ( slabs.length > 0 ) group.add( this.sidewalk.build( slabs ) );
|
|
117
|
+
|
|
118
|
+
this.group = group;
|
|
119
|
+
|
|
120
|
+
return group;
|
|
121
|
+
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
dispose() {
|
|
125
|
+
|
|
126
|
+
for ( const generator of this.generators ) generator.dispose();
|
|
127
|
+
this.generators.length = 0;
|
|
128
|
+
|
|
129
|
+
this.sidewalk.dispose();
|
|
130
|
+
|
|
131
|
+
this.group = null;
|
|
132
|
+
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
CityGenerator.defaults = {
|
|
138
|
+
seed: 1,
|
|
139
|
+
street: 22,
|
|
140
|
+
lot: 30,
|
|
141
|
+
lotsX: 3,
|
|
142
|
+
lotsZ: 2,
|
|
143
|
+
blocksX: 2,
|
|
144
|
+
blocksZ: 2,
|
|
145
|
+
curbHeight: 0.15, // ~6 in standard curb reveal / sidewalk height above the road
|
|
146
|
+
curbRadius: 5
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// derives the block / street dimensions from the parameters
|
|
150
|
+
function cityLayout( parameters ) {
|
|
151
|
+
|
|
152
|
+
const { street, lot, lotsX, lotsZ, blocksX, blocksZ } = parameters;
|
|
153
|
+
|
|
154
|
+
const blockW = lotsX * lot;
|
|
155
|
+
const blockD = lotsZ * lot;
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
street, lot, lotsX, lotsZ, blocksX, blocksZ, blockW, blockD,
|
|
159
|
+
cityW: blocksX * blockW + ( blocksX - 1 ) * street,
|
|
160
|
+
cityD: blocksZ * blockD + ( blocksZ - 1 ) * street
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// deterministic PRNG (mulberry32) so a seed always lays out the same city
|
|
166
|
+
function createRandom( seed ) {
|
|
167
|
+
|
|
168
|
+
let s = ( seed >>> 0 ) || 1;
|
|
169
|
+
|
|
170
|
+
return function () {
|
|
171
|
+
|
|
172
|
+
s = ( s + 0x6D2B79F5 ) | 0;
|
|
173
|
+
let t = Math.imul( s ^ ( s >>> 15 ), 1 | s );
|
|
174
|
+
t = ( t + Math.imul( t ^ ( t >>> 7 ), 61 | t ) ) ^ t;
|
|
175
|
+
return ( ( t ^ ( t >>> 14 ) ) >>> 0 ) / 4294967296;
|
|
176
|
+
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// --- road material -------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
// derivative-based bump for a procedural, world-space height field. the built-in bumpMap
|
|
184
|
+
// offsets the UV to read its height, so it returns a zero gradient for a height keyed off
|
|
185
|
+
// world position; this feeds the hardware screen-space derivatives of the height into
|
|
186
|
+
// Mikkelsen's surface-gradient method so the relief actually perturbs the normal.
|
|
187
|
+
function bumpNormal( height ) {
|
|
188
|
+
|
|
189
|
+
const dpdx = positionView.dFdx();
|
|
190
|
+
const dpdy = positionView.dFdy();
|
|
191
|
+
const r1 = dpdy.cross( normalView );
|
|
192
|
+
const r2 = normalView.cross( dpdx );
|
|
193
|
+
const det = dpdx.dot( r1 );
|
|
194
|
+
const grad = det.sign().mul( height.dFdx().mul( r1 ).add( height.dFdy().mul( r2 ) ) );
|
|
195
|
+
|
|
196
|
+
return det.abs().mul( normalView ).sub( grad ).normalize();
|
|
197
|
+
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// antialiased filled band: 1 where |coord| < halfWidth, edge sized to the
|
|
201
|
+
// pixel footprint ( fwidth ) so thin road paint stays crisp and doesn't shimmer
|
|
202
|
+
function lineAA( coord, halfWidth ) {
|
|
203
|
+
|
|
204
|
+
const aa = fwidth( coord ).max( 0.0001 );
|
|
205
|
+
return smoothstep( float( halfWidth ).add( aa ), float( halfWidth ).sub( aa ), coord.abs() );
|
|
206
|
+
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// the same, repeated at every multiple of `period` ( stripes, joints )
|
|
210
|
+
function gridLine( coord, period, halfWidth ) {
|
|
211
|
+
|
|
212
|
+
const g = coord.div( period );
|
|
213
|
+
const d = float( 0.5 ).sub( fract( g ).sub( 0.5 ).abs() ); // distance to nearest line, in periods
|
|
214
|
+
const aa = fwidth( g ).max( 0.0001 );
|
|
215
|
+
const hw = halfWidth / period;
|
|
216
|
+
return smoothstep( float( hw ).add( aa ), float( hw ).sub( aa ), d );
|
|
217
|
+
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* The shared material every tower in a {@link CityGenerator} is dressed with: one flat
|
|
222
|
+
* masonry colour per lot, picked from a palette by hashing the lot's grid cell.
|
|
223
|
+
*/
|
|
224
|
+
function createBuildingMaterial( layout, seed = 0 ) {
|
|
225
|
+
|
|
226
|
+
// every tower takes one flat colour, picked by hashing its lot — one shared material
|
|
227
|
+
// dresses the whole skyline; common tones repeat so the equal-probability pick feels real
|
|
228
|
+
const palette = buildingPalette.map( hex => color( hex ) );
|
|
229
|
+
|
|
230
|
+
const periodX = layout.blockW + layout.street;
|
|
231
|
+
const periodZ = layout.blockD + layout.street;
|
|
232
|
+
const gx = positionWorld.x.add( layout.cityW / 2 );
|
|
233
|
+
const gz = positionWorld.z.add( layout.cityD / 2 );
|
|
234
|
+
const blockIX = floor( gx.div( periodX ) );
|
|
235
|
+
const blockIZ = floor( gz.div( periodZ ) );
|
|
236
|
+
const cellX = blockIX.mul( layout.lotsX ).add( floor( gx.sub( blockIX.mul( periodX ) ).div( layout.lot ) ) );
|
|
237
|
+
const cellZ = blockIZ.mul( layout.lotsZ ).add( floor( gz.sub( blockIZ.mul( periodZ ) ).div( layout.lot ) ) );
|
|
238
|
+
const cellKey = uint( cellX.add( 4096 ) ).mul( uint( 73856093 ) ).bitXor( uint( cellZ.add( 4096 ) ).mul( uint( 19349663 ) ) ).bitXor( uint( ( seed * 2654435761 ) >>> 0 ) ).toVar();
|
|
239
|
+
const cellHash = ( a, b ) => hash( cellKey.add( uint( Math.round( ( a + b * 7 ) * 100 ) ) ) );
|
|
240
|
+
|
|
241
|
+
const pick = cellHash( 127.1, 311.7 );
|
|
242
|
+
let buildingBase = palette[ 0 ];
|
|
243
|
+
for ( let i = 1; i < palette.length; i ++ ) buildingBase = mix( buildingBase, palette[ i ], step( i / palette.length, pick ) );
|
|
244
|
+
buildingBase = buildingBase.mul( cellHash( 269.5, 183.3 ).mul( 0.12 ).add( 0.94 ) ); // subtle per-building brightness
|
|
245
|
+
|
|
246
|
+
// the pick is constant across a tower, so resolve it once per vertex ( varying )
|
|
247
|
+
return createSkyscraperMaterial( varying( buildingBase ) );
|
|
248
|
+
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* The road surface: wet asphalt with lane lines and crosswalks aligned to a
|
|
253
|
+
* {@link CityGenerator} layout. Apply it to a ground plane sized to the city.
|
|
254
|
+
*/
|
|
255
|
+
function createRoadMaterial( layout ) {
|
|
256
|
+
|
|
257
|
+
// wet asphalt: a warm-grey base in patchwork pours, two-scale aggregate
|
|
258
|
+
// grit, oily wear stains, hairline cracks and low-frequency wet patches
|
|
259
|
+
// that turn glossy and mirror the sky. detail fades in as the camera nears.
|
|
260
|
+
|
|
261
|
+
const p = positionWorld;
|
|
262
|
+
const detail = smoothstep( 240, 25, p.distance( cameraPosition ) );
|
|
263
|
+
|
|
264
|
+
const blotch = mx_fractal_noise_float( p.mul( 0.2 ), 3 ).mul( 0.5 ).add( 0.5 );
|
|
265
|
+
|
|
266
|
+
// close-range detail — aggregate grit, oily wear pools, hairline cracks and worn
|
|
267
|
+
// paint — only resolves near the camera, so its noise is sampled ( inside the branch )
|
|
268
|
+
// only where detail is non-zero and skipped across the far majority of the road
|
|
269
|
+
const near = Fn( () => {
|
|
270
|
+
|
|
271
|
+
const grit = float( 0 ).toVar(); // two scales of aggregate, -1..1
|
|
272
|
+
const stain = float( 0 ).toVar(); // oily wear pools
|
|
273
|
+
const crack = float( 0 ).toVar();
|
|
274
|
+
const worn = float( 1 ).toVar(); // paint rubbed thin and patchy, more so where tyres cross it
|
|
275
|
+
|
|
276
|
+
If( detail.greaterThan( 0 ), () => {
|
|
277
|
+
|
|
278
|
+
grit.assign( mx_noise_float( p.mul( 7 ) ).add( mx_noise_float( p.mul( 23 ) ) ).mul( 0.5 ) );
|
|
279
|
+
stain.assign( smoothstep( 0.5, 0.85, mx_fractal_noise_float( p.mul( 0.45 ), 3 ).mul( 0.5 ).add( 0.5 ) ) );
|
|
280
|
+
crack.assign( smoothstep( 0.88, 1, mx_fractal_noise_float( p.mul( 1.1 ), 4 ).abs().oneMinus() ).mul( detail ) );
|
|
281
|
+
worn.assign( smoothstep( 0.25, 0.7, mx_fractal_noise_float( p.mul( 0.7 ), 3 ).mul( 0.5 ).add( 0.5 ) ).mul( 0.55 ).add( 0.35 ) );
|
|
282
|
+
|
|
283
|
+
} );
|
|
284
|
+
|
|
285
|
+
return vec4( grit, stain, crack, worn );
|
|
286
|
+
|
|
287
|
+
} )();
|
|
288
|
+
|
|
289
|
+
const grit = near.x;
|
|
290
|
+
const stain = near.y;
|
|
291
|
+
const crack = near.z;
|
|
292
|
+
const worn = near.w;
|
|
293
|
+
|
|
294
|
+
const base = mix( color( 0x24262b ), color( 0x3b3f46 ), blotch );
|
|
295
|
+
const gritty = base.mul( grit.mul( 0.22 ).mul( detail ).add( 1 ) );
|
|
296
|
+
const asphalt = mix( gritty, gritty.mul( 0.5 ), stain.mul( 0.5 ).mul( detail ) );
|
|
297
|
+
|
|
298
|
+
const wet = smoothstep( 0.6, 0.85, mx_fractal_noise_float( p.mul( 0.14 ), 2 ).mul( 0.5 ).add( 0.5 ) );
|
|
299
|
+
|
|
300
|
+
// markings, aligned to the block / street grid. fx, fz are the position
|
|
301
|
+
// within one block+street period; the street is the [ blockW, period ) part.
|
|
302
|
+
|
|
303
|
+
const periodX = layout.blockW + layout.street;
|
|
304
|
+
const periodZ = layout.blockD + layout.street;
|
|
305
|
+
const fx = mod( p.x.add( layout.cityW / 2 ), periodX );
|
|
306
|
+
const fz = mod( p.z.add( layout.cityD / 2 ), periodZ );
|
|
307
|
+
const inStreetX = step( layout.blockW, fx ); // in a vertical street ( gap in X )
|
|
308
|
+
const inStreetZ = step( layout.blockD, fz ); // in a horizontal street ( gap in Z )
|
|
309
|
+
const su = fx.sub( layout.blockW ); // across the vertical street
|
|
310
|
+
const sv = fz.sub( layout.blockD ); // across the horizontal street
|
|
311
|
+
|
|
312
|
+
// lane markings down each street ( not through intersections ): a solid
|
|
313
|
+
// centre line splitting the two directions, with a dashed divider in each
|
|
314
|
+
// half, so every street carries four lanes
|
|
315
|
+
const dashV = step( fract( p.z.div( 7 ) ), 0.5 );
|
|
316
|
+
const dashH = step( fract( p.x.div( 7 ) ), 0.5 );
|
|
317
|
+
|
|
318
|
+
const centreV = lineAA( su.sub( layout.street / 2 ), 0.12 );
|
|
319
|
+
const dividerV = lineAA( su.sub( layout.street / 4 ), 0.1 ).max( lineAA( su.sub( layout.street * 3 / 4 ), 0.1 ) ).mul( dashV );
|
|
320
|
+
const laneV = centreV.max( dividerV ).mul( inStreetX ).mul( inStreetZ.oneMinus() );
|
|
321
|
+
|
|
322
|
+
const centreH = lineAA( sv.sub( layout.street / 2 ), 0.12 );
|
|
323
|
+
const dividerH = lineAA( sv.sub( layout.street / 4 ), 0.1 ).max( lineAA( sv.sub( layout.street * 3 / 4 ), 0.1 ) ).mul( dashH );
|
|
324
|
+
const laneH = centreH.max( dividerH ).mul( inStreetZ ).mul( inStreetX.oneMinus() );
|
|
325
|
+
|
|
326
|
+
// continental crosswalk bars ( long in the travel direction ) in each
|
|
327
|
+
// street arm, near the block edges it meets
|
|
328
|
+
const cw = 5;
|
|
329
|
+
const nearZ = step( fz, cw ).max( step( layout.blockD - cw, fz ) );
|
|
330
|
+
const nearX = step( fx, cw ).max( step( layout.blockW - cw, fx ) );
|
|
331
|
+
const crossV = gridLine( su, 1.2, 0.38 ).mul( inStreetX ).mul( inStreetZ.oneMinus() ).mul( nearZ );
|
|
332
|
+
const crossH = gridLine( sv, 1.2, 0.38 ).mul( inStreetZ ).mul( inStreetX.oneMinus() ).mul( nearX );
|
|
333
|
+
|
|
334
|
+
const paint = laneV.max( laneH ).max( crossV ).max( crossH ).mul( detail ).mul( worn );
|
|
335
|
+
|
|
336
|
+
const material = new MeshStandardNodeMaterial();
|
|
337
|
+
const surface = mix( asphalt, asphalt.mul( 0.6 ), wet ).mul( crack.mul( 0.5 ).oneMinus() );
|
|
338
|
+
material.colorNode = mix( surface, color( 0xd0ccc0 ), paint ); // worn white paint
|
|
339
|
+
material.roughnessNode = mix( float( 0.95 ).sub( paint.mul( 0.2 ) ), float( 0.32 ), wet );
|
|
340
|
+
material.normalNode = bumpNormal( grit.mul( 0.003 ).sub( crack.mul( 0.01 ) ).mul( detail ) ); // world units: ~3 mm aggregate, ~10 mm cracks
|
|
341
|
+
|
|
342
|
+
return material;
|
|
343
|
+
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export { CityGenerator, createBuildingMaterial, createRoadMaterial };
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BufferAttribute,
|
|
3
|
+
Group,
|
|
4
|
+
IcosahedronGeometry,
|
|
5
|
+
InstancedBufferAttribute,
|
|
6
|
+
InstancedMesh,
|
|
7
|
+
Object3D,
|
|
8
|
+
Vector3
|
|
9
|
+
} from 'three';
|
|
10
|
+
|
|
11
|
+
import { MeshStandardNodeMaterial } from 'three/webgpu';
|
|
12
|
+
import { attribute, color, float, Fn, If, mix, mx_noise_float, normalView, positionLocal, positionView, positionWorld, smoothstep, step, uniform } from 'three/tsl';
|
|
13
|
+
|
|
14
|
+
import { ImprovedNoise } from '../math/ImprovedNoise.js';
|
|
15
|
+
import { mergeVertices } from '../utils/BufferGeometryUtils.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Carpets a {@link TerrainGenerator} ( or anything exposing `sampleHeight`,
|
|
19
|
+
* `sampleSlope`, `minY`, `maxY` and `parameters.size` ) with a forest of hundreds
|
|
20
|
+
* of thousands of trees in a single draw call.
|
|
21
|
+
*
|
|
22
|
+
* Each tree is the cheapest thing that still reads as a tree: a ~20-face icosphere
|
|
23
|
+
* squashed into a tapered teardrop and lumped with a little noise, carrying a baked
|
|
24
|
+
* dark-base / bright-top gradient. Tens of triangles each, so a single
|
|
25
|
+
* {@link THREE.InstancedMesh} of half a million of them costs one draw call. Trees
|
|
26
|
+
* are placed by rejection sampling against ecological rules — a min/max altitude
|
|
27
|
+
* band ( above the mist floor, below the snowline ), a slope limit ( none on
|
|
28
|
+
* cliffs ) and a low-frequency density mask that opens clearings — then jittered in
|
|
29
|
+
* yaw, lean and ( squared-biased ) scale so the stand never reads as copies.
|
|
30
|
+
*
|
|
31
|
+
* ```js
|
|
32
|
+
* const forest = new ForestGenerator( { count: 500000 } );
|
|
33
|
+
* scene.add( forest.build( terrain ) );
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
class ForestGenerator {
|
|
37
|
+
|
|
38
|
+
constructor( parameters = {} ) {
|
|
39
|
+
|
|
40
|
+
this.parameters = Object.assign( {}, ForestGenerator.defaults, parameters );
|
|
41
|
+
|
|
42
|
+
// stochastic distance cull ( THREE.Fog-style near / far ): drawn within `from`, gone
|
|
43
|
+
// past `to`, the band between thinned by a baked random. live-tunable uniforms.
|
|
44
|
+
this.from = uniform( this.parameters.from );
|
|
45
|
+
this.to = uniform( this.parameters.to );
|
|
46
|
+
|
|
47
|
+
// main-camera position ( set via setCameraPosition ). NOT the TSL cameraPosition node:
|
|
48
|
+
// in the shadow pass that resolves to the light, which would cull the wrong trees.
|
|
49
|
+
this._cameraPosition = uniform( new Vector3() );
|
|
50
|
+
|
|
51
|
+
this.material = createForestMaterial( this.from, this.to, this._cameraPosition );
|
|
52
|
+
this.mesh = null;
|
|
53
|
+
this.group = null;
|
|
54
|
+
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
build( terrain ) {
|
|
58
|
+
|
|
59
|
+
this.dispose();
|
|
60
|
+
|
|
61
|
+
const p = this.parameters;
|
|
62
|
+
const geometry = blobGeometry( p );
|
|
63
|
+
|
|
64
|
+
const size = terrain.parameters.size;
|
|
65
|
+
const minY = terrain.minY;
|
|
66
|
+
const span = terrain.maxY - terrain.minY;
|
|
67
|
+
|
|
68
|
+
const random = createRandom( p.seed );
|
|
69
|
+
|
|
70
|
+
// a low-frequency field that breaks the forest into patches and clearings
|
|
71
|
+
const perlin = new ImprovedNoise();
|
|
72
|
+
const dOffX = random() * 256, dOffZ = random() * 256, dSlice = random() * 256;
|
|
73
|
+
const densityAt = ( x, z ) => smoothBlend( - 0.12, 0.22, perlin.noise( x * p.densityFrequency + dOffX, z * p.densityFrequency + dOffZ, dSlice ) );
|
|
74
|
+
|
|
75
|
+
const mesh = new InstancedMesh( geometry, this.material, p.count );
|
|
76
|
+
mesh.castShadow = mesh.receiveShadow = p.castShadow; // honoured on every rebuild
|
|
77
|
+
|
|
78
|
+
// per-instance cull data: xyz = tree position ( for its distance to the camera ),
|
|
79
|
+
// w = a threshold jitter from a separate PRNG, so it doesn't disturb placement
|
|
80
|
+
const cullData = new Float32Array( p.count * 4 );
|
|
81
|
+
const cullRandom = createRandom( ( p.seed ^ 0x9e3779b9 ) >>> 0 );
|
|
82
|
+
|
|
83
|
+
// per-instance regional colour drift, baked here so the vertex-bound shader taps no
|
|
84
|
+
// noise. offsets come from the cull PRNG, so placement is untouched.
|
|
85
|
+
const regionData = new Float32Array( p.count );
|
|
86
|
+
const rOffX = cullRandom() * 256, rOffZ = cullRandom() * 256, rSlice = cullRandom() * 256;
|
|
87
|
+
|
|
88
|
+
const dummy = new Object3D();
|
|
89
|
+
let placed = 0;
|
|
90
|
+
let attempts = 0;
|
|
91
|
+
const maxAttempts = p.count * 14; // give up rather than hang if the band is too small
|
|
92
|
+
|
|
93
|
+
while ( placed < p.count && attempts < maxAttempts ) {
|
|
94
|
+
|
|
95
|
+
attempts ++;
|
|
96
|
+
|
|
97
|
+
const x = ( random() - 0.5 ) * size;
|
|
98
|
+
const z = ( random() - 0.5 ) * size;
|
|
99
|
+
|
|
100
|
+
const y = terrain.sampleHeight( x, z );
|
|
101
|
+
const altitude = ( y - minY ) / span;
|
|
102
|
+
if ( altitude < p.altitudeMin || altitude > p.altitudeMax ) continue;
|
|
103
|
+
|
|
104
|
+
if ( terrain.sampleSlope( x, z ) < p.minSlope ) continue;
|
|
105
|
+
|
|
106
|
+
// density mask, feathered out at the top so the treeline scatters, not a clean line
|
|
107
|
+
let density = densityAt( x, z );
|
|
108
|
+
density *= smoothBlend( p.altitudeMax, p.altitudeMax - 0.14, altitude );
|
|
109
|
+
if ( random() >= density ) continue;
|
|
110
|
+
|
|
111
|
+
dummy.position.set( x, y - p.sink, z ); // sink the base point into the ground
|
|
112
|
+
dummy.rotation.set( ( random() - 0.5 ) * 0.12, random() * Math.PI * 2, ( random() - 0.5 ) * 0.12 ); // small lean + free yaw, trunk ~vertical
|
|
113
|
+
|
|
114
|
+
const s = p.minScale + random() * random() * ( p.maxScale - p.minScale ); // squared bias: mostly small, rare giants
|
|
115
|
+
dummy.scale.set( s * ( 0.85 + random() * 0.3 ), s, s * ( 0.85 + random() * 0.3 ) );
|
|
116
|
+
|
|
117
|
+
dummy.updateMatrix();
|
|
118
|
+
mesh.setMatrixAt( placed, dummy.matrix );
|
|
119
|
+
|
|
120
|
+
const c = placed * 4;
|
|
121
|
+
cullData[ c ] = x;
|
|
122
|
+
cullData[ c + 1 ] = dummy.position.y; // the sunk y, matching the drawn position
|
|
123
|
+
cullData[ c + 2 ] = z;
|
|
124
|
+
cullData[ c + 3 ] = cullRandom();
|
|
125
|
+
|
|
126
|
+
regionData[ placed ] = Math.min( 1, Math.max( 0, perlin.noise( x * 0.02 + rOffX, z * 0.02 + rOffZ, rSlice ) * 0.6 + 0.5 ) );
|
|
127
|
+
|
|
128
|
+
placed ++;
|
|
129
|
+
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
mesh.count = placed; // only what got planted
|
|
133
|
+
mesh.instanceMatrix.needsUpdate = true;
|
|
134
|
+
geometry.setAttribute( 'cull', new InstancedBufferAttribute( cullData, 4 ) );
|
|
135
|
+
geometry.setAttribute( 'region', new InstancedBufferAttribute( regionData, 1 ) );
|
|
136
|
+
|
|
137
|
+
const group = new Group();
|
|
138
|
+
group.name = 'Forest';
|
|
139
|
+
group.add( mesh );
|
|
140
|
+
|
|
141
|
+
this.mesh = mesh;
|
|
142
|
+
this.group = group;
|
|
143
|
+
|
|
144
|
+
return group;
|
|
145
|
+
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// call each frame so the distance cull tracks the camera
|
|
149
|
+
setCameraPosition( position ) {
|
|
150
|
+
|
|
151
|
+
this._cameraPosition.value.copy( position );
|
|
152
|
+
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
dispose() {
|
|
156
|
+
|
|
157
|
+
if ( this.mesh ) this.mesh.geometry.dispose();
|
|
158
|
+
this.mesh = null;
|
|
159
|
+
this.group = null;
|
|
160
|
+
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
ForestGenerator.defaults = {
|
|
166
|
+
seed: 1,
|
|
167
|
+
count: 500000, // number of trees to plant ( a single instanced draw call )
|
|
168
|
+
detail: 0, // icosphere subdivision ( 0 = 20 faces, welds to 12 verts )
|
|
169
|
+
radius: 1.3, // base half-width of a tree blob, in world units
|
|
170
|
+
height: 4, // base height of a tree blob
|
|
171
|
+
distortion: 0.5, // lumpiness of the blob hull ( a rough conifer, not a smooth egg )
|
|
172
|
+
sink: 0.4, // how far the base point is pushed under the surface, to hide it
|
|
173
|
+
altitudeMin: 0.12, // normalised altitude band the forest occupies: above the mist floor...
|
|
174
|
+
altitudeMax: 0.46, // ...and safely below the snowline
|
|
175
|
+
minSlope: 0.55, // minimum surface flatness ( normal.y ); steeper ground stays bare rock
|
|
176
|
+
densityFrequency: 0.012, // patch / clearing scale ( world units )
|
|
177
|
+
minScale: 0.7,
|
|
178
|
+
maxScale: 1.8,
|
|
179
|
+
from: 300, // distance ( like THREE.Fog ) within which every tree is drawn...
|
|
180
|
+
to: 620, // ...past which none are; the band between thins out stochastically
|
|
181
|
+
castShadow: false // whether the canopy casts + receives shadows ( 500k casters is a real cost — opt in )
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// deterministic PRNG ( mulberry32 ), matching the other generators
|
|
185
|
+
function createRandom( seed ) {
|
|
186
|
+
|
|
187
|
+
let s = ( seed >>> 0 ) || 1;
|
|
188
|
+
|
|
189
|
+
return function () {
|
|
190
|
+
|
|
191
|
+
s = ( s + 0x6D2B79F5 ) | 0;
|
|
192
|
+
let t = Math.imul( s ^ ( s >>> 15 ), 1 | s );
|
|
193
|
+
t = ( t + Math.imul( t ^ ( t >>> 7 ), 61 | t ) ) ^ t;
|
|
194
|
+
return ( ( t ^ ( t >>> 14 ) ) >>> 0 ) / 4294967296;
|
|
195
|
+
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function smoothBlend( edge0, edge1, x ) {
|
|
201
|
+
|
|
202
|
+
const t = Math.max( 0, Math.min( 1, ( x - edge0 ) / ( edge1 - edge0 ) ) );
|
|
203
|
+
return t * t * ( 3 - 2 * t );
|
|
204
|
+
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// smooth low-frequency lump over the unit sphere, so the blob hull is bumpy not spiky
|
|
208
|
+
function blobNoise( x, y, z ) {
|
|
209
|
+
|
|
210
|
+
return Math.sin( x * 3.1 ) * Math.sin( y * 2.7 + 1.3 ) * Math.sin( z * 3.5 + 2.1 );
|
|
211
|
+
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// one tree blob: an icosphere squashed into a lumpy, tapered teardrop, base at y = 0.
|
|
215
|
+
// normals are re-pointed up-and-out so it shades as a soft canopy volume; a baked `ao`
|
|
216
|
+
// ( 0 base → 1 crown ) drives the dark-underside / bright-crown gradient.
|
|
217
|
+
function blobGeometry( p ) {
|
|
218
|
+
|
|
219
|
+
// IcosahedronGeometry is non-indexed ( 60 verts ); deleting uv + normal lets mergeVertices
|
|
220
|
+
// weld by position to 12 verts — ~5× fewer vertex-shader runs. normals are rebuilt below.
|
|
221
|
+
let geometry = new IcosahedronGeometry( 1, p.detail );
|
|
222
|
+
geometry.deleteAttribute( 'uv' );
|
|
223
|
+
geometry.deleteAttribute( 'normal' );
|
|
224
|
+
geometry = mergeVertices( geometry );
|
|
225
|
+
|
|
226
|
+
const position = geometry.attributes.position;
|
|
227
|
+
const count = position.count;
|
|
228
|
+
|
|
229
|
+
const normals = new Float32Array( count * 3 );
|
|
230
|
+
const ao = new Float32Array( count );
|
|
231
|
+
|
|
232
|
+
for ( let i = 0; i < count; i ++ ) {
|
|
233
|
+
|
|
234
|
+
const ux = position.getX( i );
|
|
235
|
+
const uy = position.getY( i );
|
|
236
|
+
const uz = position.getZ( i ); // a point on the unit sphere
|
|
237
|
+
|
|
238
|
+
const h = ( uy + 1 ) / 2; // 0 at the base, 1 at the top
|
|
239
|
+
const taper = 1 - 0.62 * h; // narrower toward a pointier crown
|
|
240
|
+
const lump = 1 + p.distortion * blobNoise( ux, uy, uz );
|
|
241
|
+
const r = taper * lump;
|
|
242
|
+
|
|
243
|
+
position.setXYZ( i, ux * r * p.radius, h * p.height, uz * r * p.radius );
|
|
244
|
+
|
|
245
|
+
// up-and-outward normal: a soft, dome-lit canopy rather than faceted rock
|
|
246
|
+
const inv = 1 / Math.hypot( ux, 0.55, uz );
|
|
247
|
+
normals[ i * 3 ] = ux * inv;
|
|
248
|
+
normals[ i * 3 + 1 ] = 0.55 * inv;
|
|
249
|
+
normals[ i * 3 + 2 ] = uz * inv;
|
|
250
|
+
|
|
251
|
+
ao[ i ] = h;
|
|
252
|
+
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
position.needsUpdate = true;
|
|
256
|
+
geometry.setAttribute( 'normal', new BufferAttribute( normals, 3 ) );
|
|
257
|
+
geometry.setAttribute( 'ao', new BufferAttribute( ao, 1 ) );
|
|
258
|
+
geometry.computeBoundingSphere();
|
|
259
|
+
|
|
260
|
+
return geometry;
|
|
261
|
+
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// derivative-based bump ( surface-gradient method ): perturbs the view normal from a
|
|
265
|
+
// procedural height field, so the canopy reads as clustered foliage, not a smooth shell
|
|
266
|
+
function bumpNormal( height ) {
|
|
267
|
+
|
|
268
|
+
const dpdx = positionView.dFdx();
|
|
269
|
+
const dpdy = positionView.dFdy();
|
|
270
|
+
const r1 = dpdy.cross( normalView );
|
|
271
|
+
const r2 = normalView.cross( dpdx );
|
|
272
|
+
const det = dpdx.dot( r1 );
|
|
273
|
+
const grad = det.sign().mul( height.dFdx().mul( r1 ).add( height.dFdy().mul( r2 ) ) );
|
|
274
|
+
|
|
275
|
+
return det.abs().mul( normalView ).sub( grad ).normalize();
|
|
276
|
+
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* The single material shared by every tree in a {@link ForestGenerator}. A plain
|
|
281
|
+
* MeshStandardNodeMaterial lit by the scene — only the surface is authored: deep
|
|
282
|
+
* shadowed green in the recesses rising to a bright, yellow-green sunlit crown,
|
|
283
|
+
* mottled into needle clumps by 3D noise, with a matching bump so the clumps catch
|
|
284
|
+
* the light. Half a million instanced blobs makes this mesh vertex-bound, so the
|
|
285
|
+
* regional colour drift is baked to a per-instance attribute ( no shader noise for it ),
|
|
286
|
+
* and the costly clump noise + bump are **gated by distance** — full detail on the near
|
|
287
|
+
* trees ( where it reads ), skipped on the far canopy ( where it is sub-pixel ).
|
|
288
|
+
*
|
|
289
|
+
* @param {Node} from - distance within which every tree is drawn.
|
|
290
|
+
* @param {Node} to - distance past which no tree is drawn.
|
|
291
|
+
* @return {MeshStandardNodeMaterial}
|
|
292
|
+
*/
|
|
293
|
+
function createForestMaterial( from, to, camPos ) {
|
|
294
|
+
|
|
295
|
+
const material = new MeshStandardNodeMaterial();
|
|
296
|
+
material.metalness = 0;
|
|
297
|
+
material.roughness = 0.88;
|
|
298
|
+
|
|
299
|
+
const cull = attribute( 'cull', 'vec4' ); // xyz = tree position, w = random 0..1
|
|
300
|
+
const d = cull.xyz.distance( camPos ); // per-tree distance to the ( main ) camera
|
|
301
|
+
|
|
302
|
+
// stochastic distance cull: past its jittered `from`→`to` threshold a tree collapses to a
|
|
303
|
+
// point, dropping the far canopy. `positionLocal` is already WORLD space here ( the instance
|
|
304
|
+
// transform runs before positionNode ), so the ×0 lands the whole blob on the origin.
|
|
305
|
+
const t = d.sub( from ).div( to.sub( from ) );
|
|
306
|
+
material.positionNode = positionLocal.mul( step( t, cull.w ) ); // keep where random ≥ t
|
|
307
|
+
|
|
308
|
+
const ao = attribute( 'ao', 'float' ); // 0 at the blob base, 1 at the crown
|
|
309
|
+
|
|
310
|
+
// regional drift, baked per tree ( see build ) so no stage taps a noise; a blob is small
|
|
311
|
+
// enough that one value per tree reads as a smooth field across the canopy
|
|
312
|
+
const region = attribute( 'region', 'float' );
|
|
313
|
+
const deep = mix( color( 0x1d3318 ), color( 0x2e4420 ), region ); // shadowed interior
|
|
314
|
+
const bright = mix( color( 0x4c6a2e ), color( 0x6e8a40 ), region ); // sunlit tips ( muted green, not neon )
|
|
315
|
+
|
|
316
|
+
// one 3D noise field ( coarse + fine ), shared by the colour and bump, near canopy only
|
|
317
|
+
const detailFade = smoothstep( 280, 25, positionWorld.distance( camPos ) );
|
|
318
|
+
|
|
319
|
+
// gated by an If ( which must sit inside an Fn ) so the far canopy skips the noise
|
|
320
|
+
const clump = Fn( () => {
|
|
321
|
+
|
|
322
|
+
const c = float( 0 ).toVar();
|
|
323
|
+
|
|
324
|
+
If( detailFade.greaterThan( 0.01 ), () => {
|
|
325
|
+
|
|
326
|
+
c.assign( mx_noise_float( positionWorld.mul( 0.9 ) )
|
|
327
|
+
.add( mx_noise_float( positionWorld.mul( 3.1 ) ).mul( 0.5 ) )
|
|
328
|
+
.mul( detailFade ) );
|
|
329
|
+
|
|
330
|
+
} );
|
|
331
|
+
|
|
332
|
+
return c;
|
|
333
|
+
|
|
334
|
+
} )();
|
|
335
|
+
|
|
336
|
+
// deep recesses → bright clumps / crown
|
|
337
|
+
const lit = ao.mul( 0.5 ).add( 0.32 ).add( clump.mul( 0.18 ) ).clamp();
|
|
338
|
+
material.colorNode = mix( deep, bright, lit );
|
|
339
|
+
|
|
340
|
+
// clumps catch the light ( clump is 0 far away, so the bump flattens there )
|
|
341
|
+
material.normalNode = bumpNormal( clump.mul( 0.22 ) );
|
|
342
|
+
|
|
343
|
+
return material;
|
|
344
|
+
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export { ForestGenerator, createForestMaterial };
|