From b38ef166c7ed9efa1cac71b4fd16d0e168079ad7 Mon Sep 17 00:00:00 2001 From: aardgoose Date: Tue, 21 May 2024 16:57:39 +0100 Subject: [PATCH] WebGPURenderer: support using 3d textures in shaders with texture3d() and add VolumeNodeMaterial() and examples. (#28418) * support 3d textures * remove unused import * whitespace fixes * export texture3D * use new tsl * replacing to updateValue() --------- Co-authored-by: aardgoose --- examples/files.json | 4 +- examples/jsm/nodes/Nodes.js | 1 + examples/jsm/nodes/accessors/ReferenceNode.js | 6 + examples/jsm/nodes/accessors/Texture3DNode.js | 100 +++++++++ examples/jsm/nodes/core/NodeBuilder.js | 4 +- examples/jsm/nodes/materials/Materials.js | 1 + .../jsm/nodes/materials/VolumeNodeMaterial.js | 106 +++++++++ .../common/nodes/NodeSampledTexture.js | 14 +- .../renderers/webgl/nodes/GLSLNodeBuilder.js | 12 +- .../webgl/utils/WebGLTextureUtils.js | 14 ++ .../renderers/webgpu/nodes/WGSLNodeBuilder.js | 18 +- .../webgpu/utils/WebGPUBindingUtils.js | 8 + .../webgpu/utils/WebGPUTextureUtils.js | 4 +- examples/screenshots/webgpu_volume_cloud.jpg | Bin 0 -> 10871 bytes examples/screenshots/webgpu_volume_perlin.jpg | Bin 0 -> 26297 bytes examples/webgpu_volume_cloud.html | 203 ++++++++++++++++++ examples/webgpu_volume_perlin.html | 159 ++++++++++++++ 17 files changed, 642 insertions(+), 12 deletions(-) create mode 100644 examples/jsm/nodes/accessors/Texture3DNode.js create mode 100644 examples/jsm/nodes/materials/VolumeNodeMaterial.js create mode 100644 examples/screenshots/webgpu_volume_cloud.jpg create mode 100644 examples/screenshots/webgpu_volume_perlin.jpg create mode 100644 examples/webgpu_volume_cloud.html create mode 100644 examples/webgpu_volume_perlin.html diff --git a/examples/files.json b/examples/files.json index 43f9d04e09837e..4509551e4d2365 100644 --- a/examples/files.json +++ b/examples/files.json @@ -384,7 +384,9 @@ "webgpu_storage_buffer", "webgpu_mesh_batch", "webgpu_instancing_morph", - "webgpu_texturegrad" + "webgpu_texturegrad", + "webgpu_volume_cloud", + "webgpu_volume_perlin" ], "webaudio": [ "webaudio_orientation", diff --git a/examples/jsm/nodes/Nodes.js b/examples/jsm/nodes/Nodes.js index cf6c2ce42ee5e7..a37b01cec4d739 100644 --- a/examples/jsm/nodes/Nodes.js +++ b/examples/jsm/nodes/Nodes.js @@ -104,6 +104,7 @@ export { default as StorageBufferNode, storage, storageObject } from './accessor export * from './accessors/TangentNode.js'; export { default as TextureNode, texture, textureLoad, /*textureLevel,*/ sampler } from './accessors/TextureNode.js'; export { default as TextureStoreNode, textureStore } from './accessors/TextureStoreNode.js'; +export { default as Texture3DNode } from './accessors/Texture3DNode.js'; export { default as UVNode, uv } from './accessors/UVNode.js'; export { default as UserDataNode, userData } from './accessors/UserDataNode.js'; diff --git a/examples/jsm/nodes/accessors/ReferenceNode.js b/examples/jsm/nodes/accessors/ReferenceNode.js index 5f95110c44d979..0069581b52732b 100644 --- a/examples/jsm/nodes/accessors/ReferenceNode.js +++ b/examples/jsm/nodes/accessors/ReferenceNode.js @@ -90,6 +90,12 @@ class ReferenceNode extends Node { getNodeType( builder ) { + if ( this.node === null ) { + + this.updateValue(); + + } + return this.node.getNodeType( builder ); } diff --git a/examples/jsm/nodes/accessors/Texture3DNode.js b/examples/jsm/nodes/accessors/Texture3DNode.js new file mode 100644 index 00000000000000..9662279ecf45af --- /dev/null +++ b/examples/jsm/nodes/accessors/Texture3DNode.js @@ -0,0 +1,100 @@ +import TextureNode from './TextureNode.js'; +import { addNodeClass } from '../core/Node.js'; +import { nodeProxy, vec3, tslFn, If } from '../shadernode/ShaderNode.js'; + +const normal = tslFn( ( { texture, uv } ) => { + + const epsilon = 0.0001; + + const ret = vec3().temp(); + + If( uv.x.lessThan( epsilon ), () => { + + ret.assign( vec3( 1, 0, 0 ) ); + + } ).elseif( uv.y.lessThan( epsilon ), () => { + + ret.assign( vec3( 0, 1, 0 ) ); + + } ).elseif( uv.z.lessThan( epsilon ), () => { + + ret.assign( vec3( 0, 0, 1 ) ); + + } ).elseif( uv.x.greaterThan( 1 - epsilon ), () => { + + ret.assign( vec3( - 1, 0, 0 ) ); + + } ).elseif( uv.y.greaterThan( 1 - epsilon ), () => { + + ret.assign( vec3( 0, - 1, 0 ) ); + + } ).elseif( uv.z.greaterThan( 1 - epsilon ), () => { + + ret.assign( vec3( 0, 0, - 1 ) ); + + } ).else( () => { + + const step = 0.01; + + const x = texture.uv( uv.add( vec3( - step, 0.0, 0.0 ) ) ).r.sub( texture.uv( uv.add( vec3( step, 0.0, 0.0 ) ) ).r ); + const y = texture.uv( uv.add( vec3( 0.0, - step, 0.0 ) ) ).r.sub( texture.uv( uv.add( vec3( 0.0, step, 0.0 ) ) ).r ); + const z = texture.uv( uv.add( vec3( 0.0, 0.0, - step ) ) ).r.sub( texture.uv( uv.add( vec3( 0.0, 0.0, step ) ) ).r ); + + ret.assign( vec3( x, y, z ) ); + + } ); + + return ret.normalize(); + +} ); + + +class Texture3DNode extends TextureNode { + + constructor( value, uvNode = null, levelNode = null ) { + + super( value, uvNode, levelNode ); + + this.isTexture3DNode = true; + + } + + getInputType( /*builder*/ ) { + + return 'texture3D'; + + } + + getDefaultUV() { + + return vec3( 0.5, 0.5, 0.5 ); + + } + + setUpdateMatrix( /*updateMatrix*/ ) { } // Ignore .updateMatrix for 3d TextureNode + + setupUV( builder, uvNode ) { + + return uvNode; + + } + + generateUV( builder, uvNode ) { + + return uvNode.build( builder, 'vec3' ); + + } + + normal( uvNode ) { + + return normal( { texture: this, uv: uvNode } ); + + } + +} + +export default Texture3DNode; + +export const texture3D = nodeProxy( Texture3DNode ); + +addNodeClass( 'Texture3DNode', Texture3DNode ); diff --git a/examples/jsm/nodes/core/NodeBuilder.js b/examples/jsm/nodes/core/NodeBuilder.js index 0a7929ebe7bc27..a496c6d6152c0c 100644 --- a/examples/jsm/nodes/core/NodeBuilder.js +++ b/examples/jsm/nodes/core/NodeBuilder.js @@ -473,7 +473,7 @@ class NodeBuilder { isReference( type ) { - return type === 'void' || type === 'property' || type === 'sampler' || type === 'texture' || type === 'cubeTexture' || type === 'storageTexture'; + return type === 'void' || type === 'property' || type === 'sampler' || type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'texture3D'; } @@ -529,7 +529,7 @@ class NodeBuilder { getVectorType( type ) { if ( type === 'color' ) return 'vec3'; - if ( type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' ) return 'vec4'; + if ( type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'texture3D' ) return 'vec4'; return type; diff --git a/examples/jsm/nodes/materials/Materials.js b/examples/jsm/nodes/materials/Materials.js index 7c1073c3bc627e..ce02b53641aed1 100644 --- a/examples/jsm/nodes/materials/Materials.js +++ b/examples/jsm/nodes/materials/Materials.js @@ -17,3 +17,4 @@ export { default as MeshMatcapNodeMaterial } from './MeshMatcapNodeMaterial.js'; export { default as PointsNodeMaterial } from './PointsNodeMaterial.js'; export { default as SpriteNodeMaterial } from './SpriteNodeMaterial.js'; export { default as ShadowNodeMaterial } from './ShadowNodeMaterial.js'; +export { default as VolumeNodeMaterial } from './VolumeNodeMaterial.js'; diff --git a/examples/jsm/nodes/materials/VolumeNodeMaterial.js b/examples/jsm/nodes/materials/VolumeNodeMaterial.js new file mode 100644 index 00000000000000..9c53f35f899462 --- /dev/null +++ b/examples/jsm/nodes/materials/VolumeNodeMaterial.js @@ -0,0 +1,106 @@ +import NodeMaterial, { addNodeMaterial } from './NodeMaterial.js'; +import { varying } from '../core/VaryingNode.js'; +import { property } from '../core/PropertyNode.js'; +import { materialReference } from '../accessors/MaterialReferenceNode.js'; +import { modelWorldMatrixInverse } from '../accessors/ModelNode.js'; +import { cameraPosition } from '../accessors/CameraNode.js'; +import { positionGeometry } from '../accessors/PositionNode.js'; +import { tslFn, vec2, vec3, vec4 } from '../shadernode/ShaderNode.js'; +import { min, max } from '../math/MathNode.js'; +import { loop, Break } from '../utils/LoopNode.js'; +import { texture3D } from '../accessors/Texture3DNode.js'; + +class VolumeNodeMaterial extends NodeMaterial { + + constructor( params = {} ) { + + super(); + + this.normals = false; + this.lights = false; + this.isVolumeNodeMaterial = true; + this.testNode = null; + + this.setValues( params ); + + } + + setup( builder ) { + + const map = texture3D( this.map, null, 0 ); + + const hitBox = tslFn( ( { orig, dir } ) => { + + const box_min = vec3( - 0.5 ); + const box_max = vec3( 0.5 ); + + const inv_dir = dir.reciprocal(); + + const tmin_tmp = box_min.sub( orig ).mul( inv_dir ); + const tmax_tmp = box_max.sub( orig ).mul( inv_dir ); + + const tmin = min( tmin_tmp, tmax_tmp ); + const tmax = max( tmin_tmp, tmax_tmp ); + + const t0 = max( tmin.x, max( tmin.y, tmin.z ) ); + const t1 = min( tmax.x, min( tmax.y, tmax.z ) ); + + return vec2( t0, t1 ); + + } ); + + this.fragmentNode = tslFn( () => { + + const vOrigin = varying( vec3( modelWorldMatrixInverse.mul( vec4( cameraPosition, 1.0 ) ) ) ); + const vDirection = varying( positionGeometry.sub( vOrigin ) ); + + const rayDir = vDirection.normalize(); + const bounds = property( 'vec2', 'bounds' ).assign( hitBox( { orig: vOrigin, dir: rayDir } ) ); + + bounds.x.greaterThan( bounds.y ).discard(); + + bounds.assign( vec2( max( bounds.x, 0.0 ), bounds.y ) ); + + const p = property( 'vec3', 'p' ).assign( vOrigin.add( bounds.x.mul( rayDir ) ) ); + const inc = property( 'vec3', 'inc' ).assign( vec3( rayDir.abs().reciprocal() ) ); + const delta = property( 'float', 'delta' ).assign( min( inc.x, min( inc.y, inc.z ) ) ); + + delta.divAssign( materialReference( 'steps', 'float' ) ); + + const ac = property( 'vec4', 'ac' ).assign( vec4( materialReference( 'base', 'color' ), 0.0 ) ); + + loop( { type: 'float', start: bounds.x, end: bounds.y, update: '+= delta' }, () => { + + const d = property( 'float', 'd' ).assign( map.uv( p.add( 0.5 ) ).r ); + + if ( this.testNode !== null ) { + + this.testNode( { map: map, mapValue: d, probe: p, finalColor: ac } ).append(); + + } else { + + // default to show surface of mesh + ac.a.assign( 1 ); + Break(); + + } + + p.addAssign( rayDir.mul( delta ) ); + + } ); + + ac.a.equal( 0 ).discard(); + + return vec4( ac ); + + } )(); + + super.setup( builder ); + + } + +} + +export default VolumeNodeMaterial; + +addNodeMaterial( 'VolumeNodeMaterial', VolumeNodeMaterial ); diff --git a/examples/jsm/renderers/common/nodes/NodeSampledTexture.js b/examples/jsm/renderers/common/nodes/NodeSampledTexture.js index 6d830d27e975b8..9f85016adabd8a 100644 --- a/examples/jsm/renderers/common/nodes/NodeSampledTexture.js +++ b/examples/jsm/renderers/common/nodes/NodeSampledTexture.js @@ -46,4 +46,16 @@ class NodeSampledCubeTexture extends NodeSampledTexture { } -export { NodeSampledTexture, NodeSampledCubeTexture }; +class NodeSampledTexture3D extends NodeSampledTexture { + + constructor( name, textureNode ) { + + super( name, textureNode ); + + this.isSampledTexture3D = true; + + } + +} + +export { NodeSampledTexture, NodeSampledCubeTexture, NodeSampledTexture3D }; diff --git a/examples/jsm/renderers/webgl/nodes/GLSLNodeBuilder.js b/examples/jsm/renderers/webgl/nodes/GLSLNodeBuilder.js index deffeaef783078..08ed6b223d9c83 100644 --- a/examples/jsm/renderers/webgl/nodes/GLSLNodeBuilder.js +++ b/examples/jsm/renderers/webgl/nodes/GLSLNodeBuilder.js @@ -3,7 +3,7 @@ import { MathNode, GLSLNodeParser, NodeBuilder, UniformNode, vectorComponents } import NodeUniformBuffer from '../../common/nodes/NodeUniformBuffer.js'; import NodeUniformsGroup from '../../common/nodes/NodeUniformsGroup.js'; -import { NodeSampledTexture, NodeSampledCubeTexture } from '../../common/nodes/NodeSampledTexture.js'; +import { NodeSampledTexture, NodeSampledCubeTexture, NodeSampledTexture3D } from '../../common/nodes/NodeSampledTexture.js'; import { RedFormat, RGFormat, IntType, DataTexture, RGBFormat, RGBAFormat, FloatType } from 'three'; @@ -27,6 +27,7 @@ const supports = { const defaultPrecisions = ` precision highp float; precision highp int; +precision highp sampler3D; precision mediump sampler2DArray; precision lowp sampler2DShadow; `; @@ -322,6 +323,10 @@ ${ flowData.code } snippet = `samplerCube ${ uniform.name };`; + } else if ( uniform.type === 'texture3D' ) { + + snippet = `sampler3D ${ uniform.name };`; + } else if ( uniform.type === 'buffer' ) { const bufferNode = uniform.node; @@ -759,6 +764,11 @@ void main() { this.bindings[ shaderStage ].push( uniformGPU ); + } else if ( type === 'texture3D' ) { + + uniformGPU = new NodeSampledTexture3D( uniformNode.name, uniformNode.node ); + this.bindings[ shaderStage ].push( uniformGPU ); + } else if ( type === 'buffer' ) { node.name = `NodeBuffer_${ node.id }`; diff --git a/examples/jsm/renderers/webgl/utils/WebGLTextureUtils.js b/examples/jsm/renderers/webgl/utils/WebGLTextureUtils.js index 64a8d44cab3a4f..9d9fcd97dce0a0 100644 --- a/examples/jsm/renderers/webgl/utils/WebGLTextureUtils.js +++ b/examples/jsm/renderers/webgl/utils/WebGLTextureUtils.js @@ -83,6 +83,10 @@ class WebGLTextureUtils { glTextureType = gl.TEXTURE_2D_ARRAY; + } else if ( texture.isData3DTexture === true ) { + + glTextureType = gl.TEXTURE_3D; + } else { glTextureType = gl.TEXTURE_2D; @@ -286,6 +290,10 @@ class WebGLTextureUtils { gl.texStorage3D( gl.TEXTURE_2D_ARRAY, levels, glInternalFormat, width, height, depth ); + } else if ( texture.isData3DTexture ) { + + gl.texStorage3D( gl.TEXTURE_3D, levels, glInternalFormat, width, height, depth ); + } else if ( ! texture.isVideoTexture ) { gl.texStorage2D( glTextureType, levels, glInternalFormat, width, height ); @@ -429,6 +437,12 @@ class WebGLTextureUtils { gl.texSubImage3D( gl.TEXTURE_2D_ARRAY, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image.data ); + } else if ( texture.isData3DTexture ) { + + const image = options.image; + + gl.texSubImage3D( gl.TEXTURE_3D, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image.data ); + } else if ( texture.isVideoTexture ) { texture.update(); diff --git a/examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js b/examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js index 8413e2fadeaf21..5988065e948668 100644 --- a/examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js +++ b/examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js @@ -3,7 +3,7 @@ import { NoColorSpace, FloatType } from 'three'; import NodeUniformsGroup from '../../common/nodes/NodeUniformsGroup.js'; import NodeSampler from '../../common/nodes/NodeSampler.js'; -import { NodeSampledTexture, NodeSampledCubeTexture } from '../../common/nodes/NodeSampledTexture.js'; +import { NodeSampledTexture, NodeSampledCubeTexture, NodeSampledTexture3D } from '../../common/nodes/NodeSampledTexture.js'; import NodeUniformBuffer from '../../common/nodes/NodeUniformBuffer.js'; import NodeStorageBuffer from '../../common/nodes/NodeStorageBuffer.js'; @@ -316,7 +316,7 @@ class WGSLNodeBuilder extends NodeBuilder { const name = node.name; const type = node.type; - if ( type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' ) { + if ( type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'texture3D' ) { return name; @@ -369,7 +369,7 @@ class WGSLNodeBuilder extends NodeBuilder { const bindings = this.bindings[ shaderStage ]; - if ( type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' ) { + if ( type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'texture3D' ) { let texture = null; @@ -381,6 +381,10 @@ class WGSLNodeBuilder extends NodeBuilder { texture = new NodeSampledCubeTexture( uniformNode.name, uniformNode.node ); + } else if ( type === 'texture3D' ) { + + texture = new NodeSampledTexture3D( uniformNode.name, uniformNode.node ); + } texture.store = node.isStoreTextureNode === true; @@ -455,7 +459,7 @@ class WGSLNodeBuilder extends NodeBuilder { isReference( type ) { - return super.isReference( type ) || type === 'texture_2d' || type === 'texture_cube' || type === 'texture_depth_2d' || type === 'texture_storage_2d'; + return super.isReference( type ) || type === 'texture_2d' || type === 'texture_cube' || type === 'texture_depth_2d' || type === 'texture_storage_2d' || type === 'texture_3d'; } @@ -734,7 +738,7 @@ ${ flowData.code } for ( const uniform of uniforms ) { - if ( uniform.type === 'texture' || uniform.type === 'cubeTexture' || uniform.type === 'storageTexture' ) { + if ( uniform.type === 'texture' || uniform.type === 'cubeTexture' || uniform.type === 'storageTexture' || uniform.type === 'texture3D' ) { const texture = uniform.node.value; @@ -770,6 +774,10 @@ ${ flowData.code } textureType = 'texture_external'; + } else if ( texture.isData3DTexture === true ) { + + textureType = 'texture_3d'; + } else if ( uniform.node.isStoreTextureNode === true ) { const format = getFormat( texture ); diff --git a/examples/jsm/renderers/webgpu/utils/WebGPUBindingUtils.js b/examples/jsm/renderers/webgpu/utils/WebGPUBindingUtils.js index cc59d7074c60c0..c9ea8e251089bb 100644 --- a/examples/jsm/renderers/webgpu/utils/WebGPUBindingUtils.js +++ b/examples/jsm/renderers/webgpu/utils/WebGPUBindingUtils.js @@ -103,6 +103,10 @@ class WebGPUBindingUtils { texture.viewDimension = GPUTextureViewDimension.TwoDArray; + } else if ( binding.isSampledTexture3D ) { + + texture.viewDimension = GPUTextureViewDimension.ThreeD; + } bindingGPU.texture = texture; @@ -214,6 +218,10 @@ class WebGPUBindingUtils { dimensionViewGPU = GPUTextureViewDimension.Cube; + } else if ( binding.isSampledTexture3D ) { + + dimensionViewGPU = GPUTextureViewDimension.ThreeD; + } else if ( binding.texture.isDataArrayTexture ) { dimensionViewGPU = GPUTextureViewDimension.TwoDArray; diff --git a/examples/jsm/renderers/webgpu/utils/WebGPUTextureUtils.js b/examples/jsm/renderers/webgpu/utils/WebGPUTextureUtils.js index 614a62a4a928bc..4edf9df0d8cb39 100644 --- a/examples/jsm/renderers/webgpu/utils/WebGPUTextureUtils.js +++ b/examples/jsm/renderers/webgpu/utils/WebGPUTextureUtils.js @@ -327,11 +327,11 @@ class WebGPUTextureUtils { // transfer texture data - if ( texture.isDataTexture || texture.isData3DTexture ) { + if ( texture.isDataTexture ) { this._copyBufferToTexture( options.image, textureData.texture, textureDescriptorGPU, 0, texture.flipY ); - } else if ( texture.isDataArrayTexture ) { + } else if ( texture.isDataArrayTexture || texture.isData3DTexture ) { for ( let i = 0; i < options.image.depth; i ++ ) { diff --git a/examples/screenshots/webgpu_volume_cloud.jpg b/examples/screenshots/webgpu_volume_cloud.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b1abafb33263e67017b61fe4e95bd459ca782bd6 GIT binary patch literal 10871 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

k(;RdftD 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 Xzx3Mx{N)-_4M0zLErAwED z7J7$JLVyHL-tXLT{<&k^aev?6XJzcQld+SGIrpAxt~sCi%&Wgwi-6nO8d@3vGBN;w zjPwFrp#dKO*U0`I|IXL`9pu;loo|qnUnjqL(@!b-XKXz`eq2}8i4%H4Tk%YPi`_A*it<3Vv>57 zkb8^wX=OXJ;phRMwB4KclvFINZ0sET0)j%qA~Ld%<>VC<)t;$qXg=4{HZnFb1(}&! z*gH5nIlDkyy?uQB`~w1mK70%dkN6ZBm6(*AlA8AAYkFS(_ktgVMa3mm)it$s^$m?p z9i3g>J-xsC`p3p6Ca0$V&Y&<$%PXsE>l>R}*u$ga6Wl5O?3@g6{eP=Nn*U$=kQ67o z_U}EX_*WmYYyPC;`W^Bc_a$#KJTaiK^k* z?7u4gN2C9L3cdfYH2OaZ{U3d+%+X2_Jv)3}!HTNe>e|*lEgcEVnLu{8OXwwLQKd;jPjG^#P@9X5%E#E$#2g_Kk~P zKt_j_y9uD7Ba4zOD(&6Y1($B$^Xk=&X$;582m`` zoT-5J3(JKtqxVuH+SIN9H}kIm?N6OAayZ&(=qr$=@WZM`8|SAgJeW)81f3pnj?!9= z1w0M#%=QpSVA8py?8Zi1Qs!cn)bN_v&fYe0D8c+UT^ijfze-G95SC3kd%76Q&iG%1cYBAqi_c)ZXjcbh^l2M{TMco`w@7 zm%!}MM|kfJZtk{8U(VPC=bz(l+<^tCf(+Mt5C6W@`n7m1*hE7rqsC;0S!6t)8`8%= z*X)r;P{#tplnoZM+&yUUBc1-|8U^l5vyk(G4D<-gDqVB_=bKigqBL<&D007#M^_8W zOpZSN+(Gg+MB9TA0{i|I;06=6FyghcHHHTT#)V8Amz(yB4oMsS#5z?zWc54z7O)yS ztu3{dzWdQF^Eq!@e%1`5EOAL%%3hIbmOiYf`iq}QRMk= zbE-Ni>A2eO#RDmad$4&mO?#8<*Vz;q|K_yIV& zR)P|L1yFAruWePuJ(sn-t1Y1O$=US&l)Af`v%(N8W?5dVM|H4EzhFyz= z?4y}RAM?heqb+3J#N1-~lYR4GZt#s6hC7-Jhz- zqJCLhTU(Q;;5qdszn!oBCz<$N>W5MNOw9J@g>;^JI_axSl_?_qhHi5LiMqhBjfJI( ztWkpCvj3QV*8;u#G;`vspEAN@Q8vTAyoTP1$?hfUC7;78j;*Q;lC2#5(!sA>zz1$4 z1V;Vmt)rn5?WM3M={jz%>^$wKsFSjDld%r6fE8Nk`j^>~RBk`L!+bz2QmH%eCN+Qg zfO@N+?t*^PT8s2?%+lVyMKAPxYu#BQ-%w~R7s(DZe8OPrF$5{O(;3aAQ7d>h^K2ZhT*o98`ipU9T&DEE> zZJ|B<=6AMgF6Jo)MF)#tTqrY(-NBPw7zKzxj~_3&oXln<=G9yQK3+a(IJleBc^9bB zicM*Ym)=Z8y`4BwP#qBKFOE~t)@3BOqo!cH+4`bNxxs(y3cv@~4gd>jJ}9Q%-sRgR zJ*{7a48${{5OI-Y@p?dePHP?vIPwbcnx4t*dHuY^1hSH#B)SKHOd&4T^#BiaGIJG_ zQEw%WocHDZKK?Yf!sp0$p-v6e*OLTLdXj6IDu*qTrZ9r@Bkt z*5mpe{aw=1A)g|~0^P3w;kY(>Vo4}qBUCo(cQ1czH(D1O;_m(vUn{p| z0tSVXkJ=1YxtTw_)|FUZX^?<>@9=shY}3Zq!>-U3RR47g6nkrU0v!rp2>`G!nhFTQ z4>E9u#G*Ep9gfA7htb@HW3oJ+Vd&x|5 zidwf)O7csk=FCM09~F|PCsUwG8KlZh`{Z~Mt+U_S>fyeAy8kv*)R|!W`Dq&CFpn8Z z_F?gb=1_m$^7oK@H_HTm@dVRm`~~g-@l%^*=p3Bw8{YDuuJ?D%2ZwoH?A^g6a{NH&(eNeZo@Sp`HL%UcIOP zK^Mev7R1it_N9A-IwEtbsYk3_c^7)-j`J7m7nNoOSezz?gqV?9#`8vq|lU)3ZN9}+uTzPf$yTTkX`ie zLV1YgNIT@Gg=&`CqV5~WE*@%E3uAu-H`ZCKyE2ONEwan*GvBwMZ-}RXH$}r@cD-AE(i{Fa7JGjcF z`0?w3$1W!+N^B*qA;Bwv3p_3n=F^DxSfZ!0va#0jE)DQ#Pam6cLB$Mza}V~q;{v!> zhlbp{@gl|3%F8U;_;&pn(O;&f zQ+*r)& z=Yjd{7kuWdyKLRpI_*x#t>O`$fW z+rpNkN)nCcJS{5$%pV7Y#&lc|Z75_$Gf)w^xD=0n_=F@};N@`6LGC4ut9LKfW63GO zizTwiLjNb;LvQv$h5^5s;&6MmQb~K=U7zE&{Yj7D zpubJ=v|9Wvbyoo3_AxL`k;r!N{=Z6ZukX>%NL*5zW1ScK{wOnJK^-iL(-AUgC2Lh3 zv|y?d)`R2IV1@OJbCL^19>bIMbfRHIBo9i#m#bQ#v&VQbpKUU1(rm`^>s+mz+w$HK zw8Tx8fLx*&&4|FKZOhMAv=rNkXnPx^i53nC4)vGiVL0pBCew83$G{N__4FM%v2D!J zUC0|JpL|^$pB?cXO+Oy)=X6LYei%o_u8GnJ0&QjrW7;S_x%6^!`G~yS)i?KnRX5oT zn+E;J07nw6Gw{|qo;@m<>Mx4Aa*odXe7gaC8%w+2^9Q5#mw=4Wq_Z5CR7k;VA&Tjr zcf&%_@A{?6a;S=I7jti20YDypg!x=Gv2O(&Ov@_?$EQ2Fi)NUEjP~VL&m=F!s?ELT zeBog=_wCH%&FLwm=(3e7A{t7sD?jt&VwOlRjiJ%tjrbWLKfQ|c4XMdPeuYt8R9Mzc zn~rao0IT-f7iJ8Xt^l3z>V?iBxz2QnPabPi?sns!FVFY<1)QAh%rFfm!xOrna!vl> z$vhaoP8;5(Eg6<|1yBKeEcSRZ*W&iNn8_3+Jkys*KWIMuSK4Hpw14`TPZ15kkWjF7q2kX(_%zgB!(zt#4trR9!7)k?`(%yXQabWOp zCl79=;~GAKg&FtQuDkf^n+R^6m+MK~#ZQOfw__h=3Mc*vd#x^v^!Q|{A#7osH`$2t zd*i;vpe?dHBr%FW(J!Xf9th}|6Xc3V5Rk-wj+*ruGBO^O-82&-OCyJEQOwyreM?Ea z{*GY!3(E~rzG=^_Q9Bk5dYQGK=K+CK2hEw+?S#U+RT;3X5r>622WukV?UIr+`IZ%7 z<*vK#F8*WvBVXTaT}aF<<6^NRo!e-A>VT1s^kjj(M>vT8YivtV5HsD)#Sl%4^F4md zPPJ72Lk*5HpV65@XS8m7S|9BBk~>`~$Ja2Clq}HDh@hZi=Ezb@ZHC*vzxNhpi(`qa zh3lN|kMA!)_nvoMrkp+KFY0Kvnmz^>6Zz=`hN@pPIqonyOr!iSrU{Je^lW&kE5Hk^ zEUtvl8QR<@&d=xTZ#NilDu6WoWe?G(pwco|=WAuCJZj{&wB$1~xx1>M?Y0}Jr)LO% zJRhys>v#~~lb#y=CV|}y5CZKQ{>fC@fdhguBI7-FOm}ubJS=;8;~#p3f7_g3CHr1& zZN+cQmVV6r^u0hPa3oe$7T>>T^-E7&`i()|2z)b3GwzZvKob-3_9Xt(JP@m!8kxIc;ZxsKS6(#x@TG-i zkDPMcZbuQ<%JdPEX7!5|TE0%~qFABTEm$`HVvi8NQHG>{q8qIHAg%xRrN$Ltu@qPz zT1`sOS%`HlpbPc>X)hPr2b-akN%%k^rV zHhWX4pMNh)v=kLI?GX*mi+ZU4~74Yoq2x3Z;-@_gjepr zm1)gk=ACTuFH(Nun0!o+GiYN!#SX_~t=}5-b+{mgnFZ5xR)jRrntI2Y5|YK@)m#N)(r1 z2=F)6;%hTBsI$2LWOd|@7-uA(&}a$;!<{4?j6T`^Q14#h7srz`$W|dmh!L3HsV-=n zC26Xs1$_K5jWNI(M3&_^i?0_d@rBG78R~lLXKw z%&Z~cC*WGGS$y$4udc+xTpW zxIjM!E9vmHux;Us91L^;w2{+9G*ph-#zFD9ShX&SE*?r{lYHwLVkP<1sl^Ah)i1Wh z6CM$3LbIj6i-L9-eO3mDBSYddJm@40|r{%7p zy9)fWtz%G274hjGO&fK`+gTFB2g;Ys*oQ@Kd9mdZK9PUEIE#30Kj7wEBe#uky$o7< z@mQxTFy*1TtE3MGV$`Io4&fI%)_*o`X>Mu1BmblRQ=7o@vXOv^)T{u9c$kQY;fE@} zqP1^hhDoPZhN}iqaZUXuEqAal@oJ`!mK8s56xo-=#vgCtkl} zbXdM%S)}85GkEuS-SlOARvXZyVw0VCFJLQ&$)vzX9p2i*>u%z4y=5=_ zNgfU<-Sm{7CWqO$=N07YcU%Ez^-;=`R^GE94GCGRI@R?~3xo{pdAT~5GS#6C8y-Ky>Faxc&KR z?PWh6jDb@WnU&8tT!a?T<$Ejm^R5&7QDpV^+>}>-^0+0g)#hUe2ZY^{YCTo0xqj3J z;%;;Ok9yVMr~`S}Ko^&)K<4Y>aktIpnm~^qVXb7#ZCNW{Z*8 zvK3_8_t=EtfC*a{Ny|_Ft7sJPjecX`LWfvFM9lCk`&X$@FBV3Psqk9-Dg11kueD@7 zcV@bb)jx!8%DYQ!i}L`Fh;*BJlD{Q=R%c|7MlU1mG3Ad344YZTMD{#ixGl)y0!pe2 z=p112G)oM3Oh36a~`m&1TX-)VV8{Ed$$%_Rvpgv1piKy zEG_3JuBI%roCRhYCOZad30(n#gZ%=U-?vY6+LkU!CNDO+aTsP5`U?gY!9`82Ms#}S zsDS`d<|9-?D`8MqS=&mg4@gtfq{g$`e0XR^o?h3UO^Qh? z^>Y8ce}It2gi>R6Oyv~1pGvNc-ElR_j_Q}xtqEGNYNMrfF|iL#qb+txYY6Zk@O4XSIok{%#Y* zB6q9ld+2Y#EGJ~#z?@|-_CJ%17q0Y2L`()~`v|g=3ki` z<>spsonH}4I;}GK+bU{6()-X|5BVosh4O8Pc5}>KKDBWq6RK0XD)7qj)8dy zH!e&F&;HavN7(Q;4`k!&e?U)J>ZPCNrpr6Aye@weDZp;1fs#|q{2mvNm!QXk(>>RC zSW__Djjt-TN|L{)iOt7(wVpXm#kTjH>nz1stPgVAM(0!7g2c>f#v!tf<6zaz6Jdf3 zF&plz|IS7U8y}}iPEZPAIPJCg4!RU-%Q0(i$uMk)q|)T&{QCWH@@0zIGAc@*8eFyb z$mo}2@=vRwj`S*|di}`cTzzo$d^@I+vGk#yh}DU+yR$=^7GEMbB4NI>DBhyc(79P7 zlHh$gJ{@|E1or8F^1lL5tp~y+fN{PMWjQ_sqx4_SKHWwEiMd2O;eHr$ZkZXEoSY(5 z?BVErWTh!MCD>Ox*D8_QX&Qe2e}-NDKVT;r@rG-*&{&NUrK~R0xW2mf({-`#i2*K1 z(z{eUz{)%DpD$K7MLBI`9v#oFi{&$+LnB422~oLk5m6<@1|O2M`vlO(I))Or2<`_f ziVaDf=lyfMxmrK!6+R&-_I=#{QDz)N>R{rok9gM3i{4cqix;g*O>}Fkoa0AyrRjhz zeU|XSlHn|#rL1(0P?h=Ot$K@!x~blBf+8OCO&9T05UGARQ7^Gjc!Ge+{FapLRMPbF z1YWbN=6>*_(I*a>I{{N8RuOJsKxWS_9df%4SGacnC4=8b;p$L^8@OQ#>K}TYq{-k zJdO*<9@C zKKjbW5cp#Kk6qiyTfEWTPT7){HBZANtCsqD5o^;rf23*Mbnamwvj`X1M4$f2_(zi* z^uzm6B6I~0WK>(^Ux2~{lQD0oWaiX)oSBFGQp#$Pjz89emvk+9mV44vACF@aMThjJm>TMN zsj7PWs7nk1O*mz{zlJVx-E=CR^WF7hH@Ncb$fg_9rTVJ zayb&C^<-T&%Piav#uSOgf?_@%Hb)qNoaK;vmfN3Y_O?^Sdl9vvvHA+7X;ZC&4)NnK z`2&P7&L_*oTX&BzvwBLZBp1e?5Z(7LZ@U?e=-1ZNLB8&NAapr=eEBxp3vZ9qmap!m ziGEk*Vpq)}T`sJ`%aT+zS9f6@R6p*05g~ez6keDx77J<64pQgK9sreExV9bp=2V3z zVEr+Axh3sOU>3_+l+5icDJWtKIY2}l{u_(n+)a;A`b9y01p+GubRu(+_-Zn_;d5>S}QuSbzFNY zZg}ssa@RYc*?bbYB3NdnC;ho<(2V|@;5FnUgyKogKHowq&~)HU3>~e6t+wQ`R;W@H z)a`}W5r}+~#~|VxxNf2eHaR4sS^G4udpm>_S#%i3+k{=)Z7b-VuLv|$ah0KugD*A! z4-7&ma00}*S|n{iu#GYH3J`ycj^*s|OFbnMtTX@ItD4(+Q$75#T?HApSEN48^OM8D zrud)PQgx%SpWq>t{mmOr7xm25j!q3x3hpL~RiT8|zvAVstDDRSe5UD8&d?1Xo|Lfv zqD1naybx&t54&V0sJZN@3gLP6r^a<#A`Hhg^69_S)f=!-io|7vU!HM)n?9+hUMybn zs)Npdh<#pzg0ydp+Z3Kjf*y?=1~fW;i8RDTcEBozU#4Q)HkF`IpB?WU#@8|-+=5B! z@9Y4u${wX9($i23pBx!#>tgm5!%!mbK^V_gUXrTtFJ$InB`V z8~d1Zn$Ip9YHoQZ*du;z@$b4!^}`C!=x+7CKTYc&+R8P*bLyw;`keVZzlgxSCgvrX zAEd|c-Wp8M?k8>AR+(3T2cpM&!k7p(HsTzI<_uoFYz0S;AvpF5AP{#2=s~i) zmKD1*oOkbYH|Og>0Kb$%`$Mu%4Z&ZcIvl)qBe$qUqW^s#_u@^P7t3A=e0F-{T6n zUx2SomboFcJSLurVEz-Yjx8BipG1r)ahCJ5mk%nR#frYYIQrF*5EH5r-|NLnwj_W0 zFbMUiscpa66kUJDL2qb@qEt5O_eDMJsKCG+e@1r8Ea6Z zPINX&u4JANJ};`j0=#oO0`r#Q@8%O+v9I}CEDQQ?YMgQExqP(GUcY_?n6%5dDGAmRckt*tyM0$Ge4eEcs>S#A;SSNoQ^$dQsbkih9k!@YM5E`xwzi_N4auihoX--F2Y3f< zov)cwWBG#nF~tuyF1ZOW@CNXv=VR{=G1$LNdQ{q@y5=<~e`wyQ#bwZI_}7Lwmcin2 zB2kbvBppJSR;xH*{0lRhveb%__t23yzZaa(x_914^X)S_bYoc%kV4^k8jx5gdT)SO z#+)_Ph5$j8W~|W_y)&L|YF}0f!1zJWgxY}K)Ql=OtrJky%>H-*PSp|{vD10Mx1q{A zp;3-S_e3sPYo9ESuhu5c(ReG_Lwk7V1P3)d`+5sPdVnzv1FR_}1N54I4dZ2!3?V%t z=c-WA@|v{4^V#?q*z;Z;dQJMYKgPEG1-?dZ6$L{s&HjeUM~lvH;qISmtXaFV>G0n0 zz(VNMZ7>8hJ?%vf4ScD^nRcXVRZK2?)_9%$9_TlMr-foj3Suu9i*pw~GQxS|BFj0v z3?SPD<6hgrt|6Mt&Ngf#>^5<7tZb9{!cSdAK!(w)uCq-uo4$pXGgd@eynjAHYZB?) zxtJ4&+RKpmBrdbbi*^)FRI&?onGOb+r~K5DAO1&hpJzGqRHgq&pY>;^bHZYQUJ3F;d%vlDxVa_0!be08I6#! zXo6Z6mOya8a)Ls;alS9xMT%kKuJ0xEP^EMUzU$`Zhym4)`!f0ldU2zJE&XN>D>nc< z7WfyXA2PbVC#`; zuf}rVWWm`Z;5TgLm(u6_+Zyb8^1QtFK!eMLonLocr+#C-y2gL(3TtL)mRx8lDJf~$ zNYyvZTzY%0-?Eh=_y);8d?wnH?1#>K8C!+A3;7DFiLxqVxWx@FU+>FfSQ?==whJznJcv!XDtbvh0mzMDw z$5UCJmJ12a0I9YRHOlOwMiEb(^yQXe@)B-qosY=s9f_B`c#Y5RRJ~g}zTS{%ci)Od zb-os-FhaJ7P04yz7P`4uAFjjM>EmodOhM*OZ2=FA(H__5)LA3%E`gL^F`g)c;^}YA zD`uo{N}m?v`Qy4U)>eET_OqAd@b;&n+%SV0t>gXMPXvO(lD)uLSo-jQ*(zPVoW)S! zSI-`=B>Q=uJ6hcp8#pviY#ZmqH(FfSE6vmGMdwbFqJigs=Tmi~N*(Q4mspSo(_JVz z)JZwV6r@Z|2jcf9Je5w>L#&G3pX|eXDR~7h5Q|6hx~xQIv)4M^W27!*of~V1MGistmQ72^4#1~49~Y0Qg29n7I(K$ zP&{bwx(8K*f-*uIcL=2PlHVBZw%^vs5Fc3JjYGNS6c8;O#?xS5?>EZ^qG zbZN`j^7}*U4|nS4<^uOZAHmz5fNmH1=wMv#Pb^(n0N+@PD5h(4@%B&~YXjrZ!!Zp{ zDLVW(@4d5o?=@wyv+(GC=z3JDwQ7*@QhuK?SOHWeP3VdhF-y2=_Qa;-$klcLs)_%+`Pl`W& z?tOo;qp^sw*9UhkafDmqB)_|v4yEV~d%&}Qnje`acZkQOe z-#4c5`a`dM`Rw4L{l#Mnr7WxA=ETgs3d(TkzYkkHq75{vATO7OxsV8l9=hepxk-tRio$Mi~h4m6nz&)KibUrzv!HW26m z2V1dR4jAf<#&x#2GeM7LqjT-}4=og_82g8MXm4$8VdQqfAULv(4!_U&1l!w-q?e@? z+TL-m5YL_{p&YRNI?bOe9{Q^4Rizy$in!hmWWn=By(#Sc!WE)0-7!za0MRy1txQpA z{eCa~gBMalWUY1_Zk>W|Gv-5GkK%vqS7Byw3P%AMh(@|IWkRHHaQnv4gi^b zb_KgFkx(65NHWvh}3+qdOmVft}mqJjArYMpuyJJ!yWH;t2R z4Goe#LNZK&nn@VVXk{=1K76EOvlt(w^UjPIP*?R6FONW`qXCY?P7xM~w8ZKA@R;ewj=&1~2gCO*S{?OtYdKHc zrN-v!tSiRm^7pG+ngx1RCx3p;h$=AXk@AM{v!F9Gg87+<`&Fok6OVTbRsK% zWYcp6czel5;J{mAgDvo^SRKspr_(pDX>T_E&=hW~PGC;?^P3Jo0`z~|{|l4+ZY15| zY%1{c*x-T5dLN=)pwS^iSJkv!!us{Ng-r0_P>c3pLsgsVj|b(-V#54SA0_AC;5OM; zHAN`MiPo3sr#af!PXiY-zyLxBMh@%oi-&n0uV((SbTjSTP}t0v&aN@$l~s?P=Q@ML zK-%lu3yF&j%drg-vN*FDWgFT}5ns#Xv`i7Deu)9I))4ee&bP6pmMQr!Eghna>I8!# zbYjS|oIg*uds&Th7(Ra4V^`;b51%S=4F)@iN7?(5$V-06g6Bidq2J!v`xzY3NfkoH zpNNo=U(ThxY7mY0^k7b&T**NdDhc;a3LNiDj_(Jfaqeu3^DN8Mru|!Q+I}XU=5M7O z?>2>;svvQ}_{yb`$fGMjhxIa1RhP9B=pf-#T^Ss!t?`?Tm&Z@CPl|!;p3txsxS#M3 z;a!1`$TwZPM%ABu-{+Qyd6li7nLp3)t$}ulEdFELv{E^k!e~ zY|kU_4mPC8_5KO)H`Go^UtNR9oHpq#d_BA5q3o8=y!%2`TB>wsot0H1bJnG()|r*Q z{>Nb|BF}<9|4AL5D!qa?HGKSeNXL5sK>iHTdI&+V!_B><6W|N__4C z64NEEiP@(mJexXhy{QyASS6OyO8E1OeMG!N#;V?X2o`D^ex9vb!E?qhL>$UL$_!;+ z#7=td?`RubWN2nh-~avJmALN$JIY;YC2SI>_Et2R2l=k`Qnzr$WY4-2w$JM~qI=os zn6Pwh6p~#c#*)caMo9({wLc~>Ljq$q>$NeK^M(c%zYmTGGTRuYZxtj)3k7l`Wc@;5 z#vE^td8SyV{XWFTzadp!;o;llCpj-@;!k7YPw>szMoZ$NPC_S`SeslJ-W&OIypPucZWeBXQ$x(~@ZvxU0$=(=__|cKDIiF`v7jyZ};mX@o{< zSZK&TwhJnL=yurrrov>c$qjC_9&p6d!+dyd+GSk&G&L%Fi$#B48*L5^t~ohAC0@s9 zhRe(u4O$x+`K78GTuhL$#=J`wL=+z*pyA3=zZ(p_yZ%^BT2v?Lxa}m)s7O@QX|90L zW-V^oK83f6>XNGb{e$;CewAOCY$7gH%{GH}cWZ}>F^JnX0_jOAKa+kpq)~G*-r$O) zyl?4Z7;5zXAE5VVTjBMDQoW3qm3sj^&7rGs{6#E=oScP%(+75MM;+OhE&2zWA6r~`bi|3pkm5?H}e zKT8n2S$dX@?&!Mmw)(xGGdlXn>K7>HM{&L(`Br`Ny7dL;&8Pbl$07Q-bYcq3rqlE% z_M|S!OWHm8soJv;y}{VRjel^6{>n=_=fez~Px@JC#3?I{XCaTRrz8&VY+$LgDS@;k zOcF*+NZlhDINj=o8Z68ErW=>F5b9p~PZe(8f+sXDM$6Ftd0aW4x!we=Bpt~ksHB_fYtpbJM~{xQYF(m>VLnJIiUZWb?y(V#HGnq(8OT!tR-i`77c zCgm@7oR~$HphsiK&P5yhfk%{NOIt6!sDEwR{IPQVa&H{m*#lkIGw{o_DH_toSLK(~ zsmiP4q*Ye9{Z>!;H~P9y;0i#4?Iw>_l5Id?XHa5~(1o_tSh?uDbo}xFKhDwZvA=F1 z@kvU6dZxoy@>09RQ6C6;Qal{EQ+r{LFI)tDqHm{YScWv)t(yeT#5IMnvHEMS^VPTlMolEDS%j0FB$E~%|7wzc2W z*0P57`9qtbE~h)eaxq93dN#s8;g+;EIJTWa!^9cOrzMHP$`SGdmwfT=&@)r7VvfvX z;#6H->e*iC*G|Iy4}Hnb6v7j+IWphTy1v@D_>>gFAQXNlP8B}Ya%QVvpZ{LGeMz!+ z5cOn<`gXt!|4AeLOwPGl==`Dm@D@QDSzBH1g z${88^;l$zkcwJCJ6p=nmS$0WP070opN}5}>L?P2DDh?MuZM!^zIbPu=Nd}2ADcY^P ztRe6goDS0F_XYUbR1K2eWh(t4S5#rURjEX0|ndqbtw7bz$w8)EhW+Hh z-@p-qWD?EEd@j{EvBB;uDtJKBJ#)r zcy45RmX(Y*k}j?lJgOp~9^#E~F$c$r#Panpn6+44zK3F;v$EZDYd#7QlTTc0)q@22 zrI>0;5qz064&pi38&V+q2F&tZ-LQUwu-BgnW+p9P|>HfJ4KilvxTHKRvn-R$axLk>jr{Dt|+M!M!K z&vhV#)Tgmf3vWlGRn+t>Im2Qsx6U1^D9LGNB!9>J)1TO!{P~uHvL_|pqR~lOe2~{L zyIctY6g&g+o)3H=l{C8oZ&=lRw=6WJW6<&WUIQI+&+!TM3Y|hy+4Ha!ruU0R|K8E* z;-fNu_U`v81K26aep4ndCmzNk>e z3Pup5F^V&FvPXv&j|y)#Zs~z(c>TA9RTB(TvdT!54{MTDcjBd&C8#??3JCB?u@m!q zPx*4RBX+X-@VSZCE=OS7)8>^6TK8@ifqmZu{Jz~VVMc=+-A zp`(EDo+YV`p3K@6BUqnGnsjW2HCUvz4m3W5bcN55p#PmLM~Y7b9ecV`Oyl~YJ(fF; z*o@V+PFT3_dW_bV*Z5ub{cZta zlou!{4c7{lzGIXG{W8XeiZw|MB4|BFR=ym{#rRLB=>mligv&IiQ|(vBu=7JY=pNM3 zxKcjQsI209*`@Oqlk}EC9G$UTOiIq+dD}Fuj+hbpD8O#v1u?gc4YlQsMxPmscozmH zbrd2}l;JHg&N(S**+J7QvJHZfZyvtbTEQ3UiOm&=s$T)3q)r{t#uu+c)xO~UyAaGG zU#<2-{7uigH!jzFb7+?iK)VKfiijO&EcKI?(bsN;^^-l-MYH{Xz~6KDI49Y{P@cns zJCEa2BKfWWO6z8Uw%FPS#T_NcUTv?HwlOVrZ8^PDt!{yxL{pt3lyYF_8H>2Nsh4)a z3+;5PV}5PpGqKW9l##wB(^68Cn+m-M{`5k98(39WUhIOr3u8h(OE-?jMHft{q5Bvc zp0CF}jc$6(_iEAp^FR0B9&U~g*eySF^RTCsX-sz$n5(TTwQ2SZa)tk+8739c0p8}P z@!`J1KfKn+#R;*{KGZ7Y%WgFS=}P5%guCg}286qaCq4aIv;9#sd~fQ6PdrAtf#y}* zn_Gepkk$hPo(?B@`6Z#{WmQMtfxCBcu2#dRHkzUIAM>L-J1V$a90!y1cWQ8uAA9Lr z0TcvgrAsPJ><64DcHx69@e9GyktMA>;Eh%-r!`0?Zs}R0PPjGt6S+^rBLUT&vrFL|F?sohbQawzSruOoCy)mg3+Rk44%I@; zUDNGO#Z1Ndua7fr;qQ&>BLqmy{zaZC0KcV&O5RW7*s` z!>-@0-14$Tofq(H22cq%bLOJ}fr)#^M{quEAV5PhCJChemo!Hdh1}0~Q`2sHzLeavTMy&B#Um`D{R-B2Z{-!KK@BUx;idQFU2(dzL9vCfb^l#i zQbi&~(c6xi0MWw}|fk2$+w>2(q_B}N4X?LDWb7KR_&ATw8hAgQ`k6|U)zLIyo@ zr1nYB976B3YNNby4@?Dw?gnGR!%A#6Po+Cf>qM`qQ3DR!76*>tR54tcryKhElX&oZ zh||nKOxZHCe`tC|RtWuE!*#hnm_n!VkmMiSP-kiuaIFXhX^Xm}Z8cxgS3L)}9YGm& zsLF88-#FP0h7vh;tmgmru*#VhJcnKH6Y{I0Fri&wK5Dy5;x?UN zsukTj@@gCr&7zx!K?qA8#!Lrh`3N$0C#F?-^}6gPuwP*PPU}T*z~RW#`ndCUfy765 zVvLx%y#B1Ul}5u921@MsFrtEURwS_5ovh#cVMVgbNLreXY=~(@Bg}$WcX=PCfxod7 zf2|iIq1bCO>RjC%Xgv5J?wRP??VGm|3p7E@j0d!t27c@we zOkoMiG;CY&EYl59HCWQ8cKYn&Vz)VwX+oS4R`MUM)&q271S8Glmo`nH?iQbu>eF?r zKag_a2v`7CKb&{$@>T%HTCm3Cj_Hwcb4^03ImSPcub$k;RH!?`v?DI;1N;$wcPU(& z5p=;yCP(GBR?LTbV^IMXI!Oy*nMMM~IHQkjxbr_^Vk#SS7tHFmoFUIDZe>2JFr|^uk9a| zhKka6wi_WZ58&>5pw%$C^&iSYMg9g@c1iC>KWmxH2ihTpmW-gD8#JG!np09N**p@e zdoy|(#UUB1$E#`MYdGD;f@WQQPM>jWLLKnCYuWZEf_#tXrMV+VMdmt^(3}W?RJn;e zJD1tPG%-AQe)`>v3kn!7mbDwsMsPili&R#?Y->))(o=89oq9j7gB;CTweuZ3yR)o2 z#)hq(wET>Qb{wF)#03{;hh?C)TOkI>P^tRnimKR*+Ipz-B-rFIl*g&X5>w!;&uU=Y zUhb^7KWo|V=_V>$njFe5x+^M8YCe145ZcX6P5cIQ=82e>#{(A!=;;EcpC&3f)lDF# z#;lEW(ufWtT!RSSZtD#t(97jcg5k#bmbV89PMan267!Z&yH3~uq5PAtMQ52Bw1On0( z2-Se}9y%Fnq(%rep_hBH9s_A}kHXcA&{n0pU|Ycn^XF0p<_HlT_mM>6tx&Zu zW}(klP}qJ$KkZ?jvzv^O;J5z-x?Y|DoCkqBGIN-Y111=h(`?mAzmqocSDeMM?*NT#@8*R#9G#J zg=Vt!L9i(jKZx^`U*pY5zW|)r&@oZ%uY|hjuKE+~D4?!P@9pDUnQ2QS-#CYVLfqiw z)P>3xp^nX)#m2c@S|DE)li1p0OjU@;wpW~mQ?C|ZpN0*3C{u{3EA4mxSM5#*%E!2o zApV$W4)XVZF8J0;>c=uTVv|x!3zA}n7f=%QO@S9!^1AT{!QbVyWY(b?>bNQ7L?`Dh ziuAMLkW;IpTo&-Sf&?MuYcR09l~Hvbq=VRDmEPyl{MFt`(X8-jc;v@7DYp9 z4w~1!3}rp(5t<65sr|Uq9kse`F9o~YNvxObFqK{s>cMLY+~HM5{Bxe2`KHm50ukOF&dDgST`WKGFEx zamMU&U4KIssJtAi$e3H0Tr_!Aj5nFQJ471!mi=U1oMC4F@-EqOinTc;CtuO^&1 zC69MlEv}iM+>n;p3&)Nb*aVDS@~H~YUL~AuZHt_UsALq4N~G9`Q!0g@48=0iEXV2+ z`y41If|5VsgCT5;!C)HS&9dNi+j>Kte?O8JT1BzgZV;cL5M+dG?~7_U72BLmCrJOXw8CM>)wVUz5tmzq*A@%HmRv-&8Or zkakU@%hcwmvISvNZ)9tY@x^6v`td0Q*487yITprElj}b5zcuGZi<201R3JR!%LB-Z z`&^=470>m!u>MSju|+DbEeKgiKWD@?eOoy(k_H717gQZ5z zzpRR+phcFxq^6~$*r(Y-io>+e(P!b*)h8{6>=z`c(V9pIJU>(o#cLkJbf~Ce9oYAOGXxg>WX?ras?|xvEDq zDp7s2^$Syf2u;Pe9zibZYrpQi#I`yFlY~4gofaDK4#JAZ6RCcncD68`jdCe zk5J3-XMJ-gUj8To-sg&FWS@`n_wNo9PwX^7zA6DH} zbZZSc??08)u`zIJinjdk`F9ekY_rOZn1%r-y%q1&Jp6vTaNB`(xQ)VSB}S$zE6vav zqG{{AGNGkBvsyc?3M)<1xfBO;w%xq7$^4HgcGlF?76P&Ma?iO%PKv(1*jLa*P5Zb& zV@0xZy{H0ZXkF^AjCc+%S9qqn`4eHa)L5ucctQX5fJ`pkxkzQDIY6`eP@B$4-6Vk{2KC~ok3LvjBHv-eibtvMBc?_9WW*C%K^UR(i3b-kjdY`nJ51_FWo zR>>Qk1ZIR~VCIUPiZE@{s|pHNrNiHemtTdiinm%_f(3Qu@H;1WEWd!C zwk(SW>l3Q5sqvoU!XqkW(E8(*fGkq=j9-rx5J-Gh2ABd6n``HTMg(wF6_Qpyb#A?u z_mPu^-`~GaZv320e9T{^cDA{`X25nZtWWuonsCSnkreae8kjYLJE{4G)3tITY^@V$ z-u;y-c!^+M4j&y%YKsLi?VqcdH^+5`#Lk3Q8)Mu2z2BBh({`a^FOY5J50N) z&OLq}efm^V*7~W#P=IY9LuE3-|BFd4YyJ^+{>}`|<=>y%eoPcC82n2kp4dKMi#l zVpx@MwUY|>D37h2Sf1ugec}h`-5WTemAL;76R+Fb~;Fs@(Uon0!E=sL_8~l$q9X z2wod23oWx3Yg#*m>^F~2=h}Ag@#@L88z>}r21o{snzT)YpJmMY>%FFe&FBSfEMWnq zHq&-bi|p|}5U{Yb0Tb3t{ngF4rE2_rtHR1B6yT?&hIlwT##T+N;B1zXd{I}gLxn5h z%t?Z`!0+Gt6mO^w5#79xUUL$&0o$QUl${`hj_KPrM)BNSpFc5O+xesj`s(B9V|6*r zvK!}Lv)gQNpj`$N6#5#~^(5mI;IxFGOBN-_)e7Er1zp>Au9cq$-YJrl(!t3(zf;iT zz2>5+h^p8#PAcwV>HZo2w^YAR#%hY~O4xKA?+#g|H8PmnBx&_)ziI=U+@Mey5N2)Fh_m)0$?>8`0$q{f&5?!_7A@u{=Y2 zTyYqw35X?%Q%%j?NJ_!duvt>g9s?Dr^%NgTYtk+IIv4)_&-y`YQBP zgZ{*eH_U3x|0M=N@dx-c8~A%XPorEQj?H+G_n~=L8XNXK7;=_a^OW8FmRSTG?&quvhb)S?LLhx zw&c_HON6gOh+;cMJ}c=>N3vUTXn+B}_Ea$4JDPR1X16K5&Y8_+qq)ZbXzP#z&V;B> zR(_d{JLfQOsLl>CgenTNb@QCWj!QTS^;t?8R9k^!wnz_thFNHXZJ15AM%`(E8Ml+3 zNEG1^j@AIVeZ&PU_v>ZyhA(@)2;W?#SH+e5gGb2);z#VE#Itg*n_4`Wm!Z$Z`zhsh z?k>%I2XO7itt(6+f*;z*Q`)l?S-oM;&#R*lvx{rtiqz_!kh!u2P1!LALitK86LkAt z#`5Q2SMRUpSxouN^6gB_hYD^lU;?lX@gPj?eB-%&dyOxB*&HzO!9sT_xoQQKbG}bp zVrVnYAoO?C%tfnhaSW)gGH7*1ntj7^@<&>NxV)^f-J9}JJx4s3k`(jtT+Tpd3jd-d%*yj6=cKe-Y|N{go3c~T;$vbA8>5_rt>X$=vXuL0eDhvNrqH4Joy<+JIt4>*2xCWIbgj}A`pww-+C zSa7Ug{zqiRb=R4Uz;&0px@H?(6D%4xUzyhZSw@If(W$9U^Pu$1*0vnV-jDV6<+~@O zz-FYn%5Ml4&42;Vixs>4>?Y$EE(};EOd!thO=QYzPI;WcHRgZ3^ zLTz9+WYbAmj)0-TUG~vu_Eq!pj>Z}kt=e-0iu&|snnZpoa8C*Xs!pCXbuaU6CfKL; z2|DSCRT#5=XL#Ct61VG)SLT&g0h^LW(7yVhKz~pTK0#@okB)fWie;Wgqo)!jAM?iF z{AO+Jd-;m+n4eQgx!sr~I<-TenE>7rVEg94@v!?fW83M=Xe>#Vh+wAI`_H$Uh5!As z0ccOf?L&oWCvEY@G`|1l+fb!?GQ9glYjwPtL; zawHNhMKARyw`71qD>E6;1uK40?luVKxbYMsUC6Be$@g37n(cUv*US2i`ecY9BJh!}3CNEQDHMM`XW(3)XdvG1Jl)GH%oY(^ zI@?B(o_JLot-bMseLfxRqP29`Uh#t-cilL$0iJk=r+*r|n>=wMo1?eoaEVL{17|x{ z9?X`E)(8nMN3>uF*7`=KLG;YpJfwr@X#gD0gXqH8$i?RHG&Y9Hc)frfTq`ev*Z>M| zE|fidS$4%QN+g0+^&Zq@Hq=*|xI4Qs{%sCZGq(PwA=}!w>@@GAR-n3kpP~QeGR+VuMbPzPXQ|Nq+qMFT_B7Xo;EEP`}m?f-sOfc)Y?*dLV;?ZC)T&ceU{J zN7O?Z*-=3A1M68G#Uzqy#eU|a!CC8ufj0Hxl0XU6JeXGpWIR$M3=@>AN9Td#jwViACHE((3ze(($Gq%q%X`*nA9W z7gOfWCF}h2Tw9*T7Om=Zm-=(?>4qMxLrocz0SzrFbH6j5=JbIkcy`;^ns9K1+Sac9 zVa$zdv|(3P*%>T*Rpz6R+QD&r)+OX4wr<6wg230Aju+_8M*mWCFFg~rhp7xGr*LQQ zFhj;>dx0Dl*5kM-EqHU8g4W$f88j0SvmNs*t9cXUGfCY^)7po}RINBDNs#lgY_zio zk^O>&ed-5RRoAincFq?*j`#5kgw>g+q@+YOi)(oPFb&&!mCDG>t(kn$^;&!ezD0*d z6z#xq$HjGxt<pKw_@-6T`lb$2HhkR}e-IIEI zIjISV_d+8Op`dlgT{IDcWeck;Y)nQHwUY(#`|@r^JktqY1888-QJF!4gw# z!7@4n8@bi_-`1Jn#q(KAQXEFMLY7@3ZE#j3kyF>{b)VM3Y0$`kN`I9gcU4QEv%_r( z9pau*L5dLX;^tEaQwX51p!@(TGqtj^zhN;@bQe$$z z^CEFzU34MKXW+(pB`cVKtPmqi!twt=Cw2H#IX@edX=hMG8r=w6XWT(TF=*y*> z@s`t~&Z-yp+LK;97O1^2p&fk;uK1n4`k7qSKQo@SUi!(1nvo#ogM>Wt>nTj0V>g5j z=5E48%(_-IOkyBK1?=wm<_j6e$}Y`iEZ?#9lh2ecyKmKN80dSk~<1 z?Ki23zG0z-JM^cs{EwI;+A08#fGY+9yAfMQY3f zHCqajivU2t`e4N$7u<@B?3wT}Ooeo}3Di|O{wAj~{0<)PP$zu;E$kNUv^TXrM;^gf zUcVTsi`@4A*BXlL_){ce1k8>yOE?B^wsW4zlcS^I;%1R{aB(g^Bj)6kM#pi@EYGbO zC9`E4KL@kRJd-o1~+?ca0W@r4(mW}!i`$O0MVW=AT-bbk4 z0x>%BUqvOFmEd=Rjs$CVded8Yuvemc*=?t;g_Q_QiUQHko!p=OvE@{L4If6>XlzTE z6J1SL@*T8P+m+aD6iHdZc)=llk1(@4ay7GfOg@g!AgW)x+SO{Q$X&}kC1ZMqI6a%u zW)=}E3I6o-rUwPbT&e&(v08rdVFRekyuGj2*Q=0)jBVDeUTE9$qUCpQLrL|M(UC&h zN>*$l5jzkEZj%SP#&=!J;K%LIci6Yi0!|)7e7XS%X&G-Z8sr-^ zVKahk@>Dt76sHO&!2Gm|;(H{F5&(VuSrw%=veps**^m5N;q;)cvy8?|%U~zWU-|Lfh@S(ny_s^|A z{)YAAO6XXCs5fL(ez2q%J`LY42)|H~AK2|@ty8Fhba<|c-o`=;!otZFbO|L8OBA~TD zY_D<$wceG9!p$OAR+_L!6CMaLmt3G#XO1}e0W!rW-ORsHRNo}!YlzbEteW_MGJa_2 z&IJH1T1k6~&=Bl={ht`M|Fwl#6Rm}OL`)UFul0LB^@{3o^bSCIXv~P!`9PHez%I2! zzd8QP2F;YD)TLO`%xJji=}- z-?HH5K02(Wo+GAPtq{Xlt_d;j7!7DX4BKTKZ5*|sCO!D>!$`5^O|GvuL!kRRpj>(n z$w-HEzhgj-foH{)1eiVmmh5_v)!Z5%*4yuE#pSTwU=DXWLf8bV*_{2|&6uvQdrS^d z@(rH^JH7qB_a^EdrEKGNRYZ6dKwm$(7-cygQd7RYlxcN3g67n_8c|o{XGg>fH!)YeBskSG2a#b+IV?N*vew-o#Ae;EsE*D z-ICf%63#TA$eXb>(JyKay1f%5QnOMWbe{AcmezT3`&*^r*N+u8lPf8Z;_}0Y5-6f& zDWpZ4`l9W9B|{Z^EjPwnQwI8riGC3N;mmchv2K=!upiA}CbS*%_hT0NfpTS5W2X#> z@-f6sjxK@kC_bSZFfL~L4XwYnj4^moQE#eq{aJTaxznGSWPY}S1KceV%jEI6^nn}$GrXBqc z>T$<~6pz9GbUT9cjyOdqE8KNDg$};Il0Cn&+e=~6`IlRKMM}d6K*Pr>TK6WJnJljL zN~*Y?0+pM+9PC4$Zcc1EAA5b&25JHDJ~_B(DTC{gK$*PKzBOXO8t zb}c5zmL`@jR!Nd^+_<2)$S6;2cNqvHeSt0_$;c1_>E|~WIevRW_jt<6QA%bq95Y*e zjOR_lxx7qwln-z=EvWU)$|{)8gw|P<`+fRvpH~01vHJhd{ioUR|KUGwu>G3)Kc^rO AH2?qr literal 0 HcmV?d00001 diff --git a/examples/webgpu_volume_cloud.html b/examples/webgpu_volume_cloud.html new file mode 100644 index 00000000000000..9206bd35253c17 --- /dev/null +++ b/examples/webgpu_volume_cloud.html @@ -0,0 +1,203 @@ + + + + three.js webgpu - volume - cloud + + + + + + +

+ three.js webgpu - volume - cloud +
+ + + + + + + diff --git a/examples/webgpu_volume_perlin.html b/examples/webgpu_volume_perlin.html new file mode 100644 index 00000000000000..489c7bf950ab90 --- /dev/null +++ b/examples/webgpu_volume_perlin.html @@ -0,0 +1,159 @@ + + + + three.js webgpu - volume - perlin + + + + + + +
+ three.js webgpu - volume - perlin +
+ + + + + + +