|
38 | 38 | <script type="module">
|
39 | 39 |
|
40 | 40 | import * as THREE from 'three';
|
41 |
| - import { Fn, uniform, texture, instancedArray, instanceIndex, float, hash, vec3, If } from 'three/tsl'; |
| 41 | + import { Fn, If, uniform, float, uv, vec2, vec3, hash, |
| 42 | + instancedArray, instanceIndex } from 'three/tsl'; |
42 | 43 |
|
43 | 44 | import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
44 | 45 | import Stats from 'three/addons/libs/stats.module.js';
|
45 | 46 |
|
46 | 47 | import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
|
47 | 48 |
|
48 |
| - const particleCount = 500000; |
| 49 | + const particleCount = 200_000; |
49 | 50 |
|
50 |
| - const gravity = uniform( - .0098 ); |
| 51 | + const gravity = uniform( - .00098 ); |
51 | 52 | const bounce = uniform( .8 );
|
52 | 53 | const friction = uniform( .99 );
|
53 | 54 | const size = uniform( .12 );
|
|
58 | 59 | let controls, stats;
|
59 | 60 | let computeParticles;
|
60 | 61 |
|
| 62 | + let isOrbitControlsActive; |
| 63 | + |
61 | 64 | const timestamps = document.getElementById( 'timestamps' );
|
62 | 65 |
|
63 | 66 | init();
|
|
67 | 70 | const { innerWidth, innerHeight } = window;
|
68 | 71 |
|
69 | 72 | camera = new THREE.PerspectiveCamera( 50, innerWidth / innerHeight, .1, 1000 );
|
70 |
| - camera.position.set( 15, 30, 15 ); |
| 73 | + camera.position.set( 0, 5, 20 ); |
71 | 74 |
|
72 | 75 | scene = new THREE.Scene();
|
73 | 76 |
|
74 |
| - // textures |
75 |
| - |
76 |
| - const textureLoader = new THREE.TextureLoader(); |
77 |
| - const map = textureLoader.load( 'textures/sprite1.png' ); |
78 |
| - |
79 | 77 | //
|
80 | 78 |
|
81 |
| - const positionBuffer = instancedArray( particleCount, 'vec3' ); |
82 |
| - const velocityBuffer = instancedArray( particleCount, 'vec3' ); |
83 |
| - const colorBuffer = instancedArray( particleCount, 'vec3' ); |
| 79 | + const positions = instancedArray( particleCount, 'vec3' ); |
| 80 | + const velocities = instancedArray( particleCount, 'vec3' ); |
| 81 | + const colors = instancedArray( particleCount, 'vec3' ); |
84 | 82 |
|
85 | 83 | // compute
|
| 84 | + |
| 85 | + const separation = 0.2; |
| 86 | + const amount = Math.sqrt( particleCount ); |
| 87 | + const offset = float( amount / 2 ); |
86 | 88 |
|
87 | 89 | const computeInit = Fn( () => {
|
88 | 90 |
|
89 |
| - const position = positionBuffer.element( instanceIndex ); |
90 |
| - const color = colorBuffer.element( instanceIndex ); |
91 |
| - |
92 |
| - const randX = hash( instanceIndex ); |
93 |
| - const randY = hash( instanceIndex.add( 2 ) ); |
94 |
| - const randZ = hash( instanceIndex.add( 3 ) ); |
| 91 | + const position = positions.element( instanceIndex ); |
| 92 | + const color = colors.element( instanceIndex ); |
| 93 | + |
| 94 | + const x = instanceIndex.mod( amount ); |
| 95 | + const z = instanceIndex.div( amount ); |
| 96 | + |
| 97 | + position.x = offset.sub( x ).mul( separation ); |
| 98 | + position.z = offset.sub( z ).mul( separation ); |
95 | 99 |
|
96 |
| - position.x = randX.mul( 100 ).add( - 50 ); |
97 |
| - position.y = 0; // randY.mul( 10 ); |
98 |
| - position.z = randZ.mul( 100 ).add( - 50 ); |
99 |
| - |
100 |
| - color.assign( vec3( randX, randY, randZ ) ); |
| 100 | + color.x = hash( instanceIndex ); |
| 101 | + color.y = hash( instanceIndex.add( 2 ) ); |
101 | 102 |
|
102 | 103 | } )().compute( particleCount );
|
103 | 104 |
|
104 | 105 | //
|
105 | 106 |
|
106 | 107 | const computeUpdate = Fn( () => {
|
107 | 108 |
|
108 |
| - const position = positionBuffer.element( instanceIndex ); |
109 |
| - const velocity = velocityBuffer.element( instanceIndex ); |
| 109 | + const position = positions.element( instanceIndex ); |
| 110 | + const velocity = velocities.element( instanceIndex ); |
110 | 111 |
|
111 | 112 | velocity.addAssign( vec3( 0.00, gravity, 0.00 ) );
|
112 | 113 | position.addAssign( velocity );
|
|
131 | 132 |
|
132 | 133 | computeParticles = computeUpdate().compute( particleCount );
|
133 | 134 |
|
134 |
| - // create nodes |
135 |
| - |
136 |
| - const textureNode = texture( map ); |
137 |
| - |
138 | 135 | // create particles
|
139 |
| - |
140 |
| - const particleMaterial = new THREE.SpriteNodeMaterial(); |
141 |
| - particleMaterial.colorNode = textureNode.mul( colorBuffer.element( instanceIndex ) ); |
142 |
| - particleMaterial.positionNode = positionBuffer.toAttribute(); |
143 |
| - particleMaterial.scaleNode = size; |
144 |
| - particleMaterial.depthWrite = false; |
145 |
| - particleMaterial.depthTest = true; |
146 |
| - particleMaterial.transparent = true; |
147 |
| - |
148 |
| - const particles = new THREE.Mesh( new THREE.PlaneGeometry( 1, 1 ), particleMaterial ); |
| 136 | + |
| 137 | + const material = new THREE.SpriteNodeMaterial(); |
| 138 | + material.colorNode = uv().mul( colors.element( instanceIndex ) ); |
| 139 | + material.positionNode = positions.toAttribute(); |
| 140 | + material.scaleNode = size; |
| 141 | + material.alphaTestNode = uv().mul( 2 ).distance( vec2( 1 ) ); |
| 142 | + material.transparent = false; |
| 143 | + |
| 144 | + const particles = new THREE.Sprite( material ); |
149 | 145 | particles.count = particleCount;
|
150 | 146 | particles.frustumCulled = false;
|
151 | 147 | scene.add( particles );
|
152 | 148 |
|
153 | 149 | //
|
154 | 150 |
|
155 |
| - const helper = new THREE.GridHelper( 60, 40, 0x303030, 0x303030 ); |
| 151 | + const helper = new THREE.GridHelper( 90, 45, 0x303030, 0x303030 ); |
156 | 152 | scene.add( helper );
|
157 | 153 |
|
158 |
| - const geometry = new THREE.PlaneGeometry( 1000, 1000 ); |
| 154 | + const geometry = new THREE.PlaneGeometry( 200, 200 ); |
159 | 155 | geometry.rotateX( - Math.PI / 2 );
|
160 | 156 |
|
161 | 157 | const plane = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial( { visible: false } ) );
|
|
179 | 175 |
|
180 | 176 | renderer.computeAsync( computeInit );
|
181 | 177 |
|
182 |
| - // click event |
| 178 | + // Hit |
183 | 179 |
|
184 | 180 | const computeHit = Fn( () => {
|
185 | 181 |
|
186 |
| - const position = positionBuffer.element( instanceIndex ); |
187 |
| - const velocity = velocityBuffer.element( instanceIndex ); |
| 182 | + const position = positions.element( instanceIndex ); |
| 183 | + const velocity = velocities.element( instanceIndex ); |
188 | 184 |
|
189 | 185 | const dist = position.distance( clickPosition );
|
190 | 186 | const direction = position.sub( clickPosition ).normalize();
|
191 |
| - const distArea = float( 6 ).sub( dist ).max( 0 ); |
| 187 | + const distArea = float( 3 ).sub( dist ).max( 0 ); |
192 | 188 |
|
193 | 189 | const power = distArea.mul( .01 );
|
194 |
| - const relativePower = power.mul( hash( instanceIndex ).mul( .5 ).add( .5 ) ); |
| 190 | + const relativePower = power.mul( hash( instanceIndex ).mul( 1.5 ).add( .5 ) ); |
195 | 191 |
|
196 | 192 | velocity.assign( velocity.add( direction.mul( relativePower ) ) );
|
197 | 193 |
|
|
200 | 196 | //
|
201 | 197 |
|
202 | 198 | function onMove( event ) {
|
| 199 | + |
| 200 | + if ( isOrbitControlsActive ) return; |
203 | 201 |
|
204 | 202 | pointer.set( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1 );
|
205 | 203 |
|
206 | 204 | raycaster.setFromCamera( pointer, camera );
|
207 | 205 |
|
208 |
| - const intersects = raycaster.intersectObjects( [ plane ], false ); |
| 206 | + const intersects = raycaster.intersectObject( plane, false ); |
209 | 207 |
|
210 | 208 | if ( intersects.length > 0 ) {
|
211 | 209 |
|
|
224 | 222 |
|
225 | 223 | }
|
226 | 224 |
|
227 |
| - // events |
228 |
| - |
229 | 225 | renderer.domElement.addEventListener( 'pointermove', onMove );
|
230 | 226 |
|
231 |
| - // |
| 227 | + // controls |
232 | 228 |
|
233 | 229 | controls = new OrbitControls( camera, renderer.domElement );
|
| 230 | + controls.enableDamping = true; |
234 | 231 | controls.minDistance = 5;
|
235 | 232 | controls.maxDistance = 200;
|
236 |
| - controls.target.set( 0, 0, 0 ); |
| 233 | + controls.target.set( 0, -8, 0 ); |
237 | 234 | controls.update();
|
| 235 | + |
| 236 | + controls.addEventListener( 'start', () => { isOrbitControlsActive = true; } ); |
| 237 | + controls.addEventListener( 'end', () => { isOrbitControlsActive = false; } ); |
| 238 | + |
| 239 | + controls.touches = { |
| 240 | + ONE: null, |
| 241 | + TWO: THREE.TOUCH.DOLLY_PAN |
| 242 | + }; |
238 | 243 |
|
239 | 244 | //
|
240 | 245 |
|
|
266 | 271 |
|
267 | 272 | stats.update();
|
268 | 273 |
|
| 274 | + controls.update(); |
| 275 | + |
269 | 276 | await renderer.computeAsync( computeParticles );
|
270 | 277 | renderer.resolveTimestampsAsync( THREE.TimestampQuery.COMPUTE );
|
271 | 278 |
|
|
0 commit comments