PageSourceSearch

https://robclouth.com/js/particles.js

js robclouth.com collected 2026-10-08 18:55:21 UTC 12,781 bytes, 459 lines download raw bytes

1const shader = {
2  vertex: `
3		//
4		// Description : Array and textureless GLSL 2D simplex noise function.
5		//      Author : Ian McEwan, Ashima Arts.
6		//  Maintainer : ijm
7		//     Lastmod : 20110822 (ijm)
8		//     License : Copyright (C) 2011 Ashima Arts. All rights reserved.
9		//               Distributed under the MIT License. See LICENSE file.
10		//               https://github.com/ashima/webgl-noise
11		//
12
13		vec3 mod289(vec3 x) {
14		return x - floor(x * (1.0 / 289.0)) * 289.0;
15		}
16
17		vec2 mod289(vec2 x) {
18		return x - floor(x * (1.0 / 289.0)) * 289.0;
19		}
20
21		vec3 permute(vec3 x) {
22		return mod289(((x*34.0)+1.0)*x);
23		}
24
25		float snoise(vec2 v)
26		{
27		const vec4 C = vec4(0.211324865405187,  // (3.0-sqrt(3.0))/6.0
28		0.366025403784439,  // 0.5*(sqrt(3.0)-1.0)
29		-0.577350269189626,  // -1.0 + 2.0 * C.x
30		0.024390243902439); // 1.0 / 41.0
31		// First corner
32		vec2 i  = floor(v + dot(v, C.yy) );
33		vec2 x0 = v -   i + dot(i, C.xx);
34
35		// Other corners
36		vec2 i1;
37		//i1.x = step( x0.y, x0.x ); // x0.x > x0.y ? 1.0 : 0.0
38		//i1.y = 1.0 - i1.x;
39		i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
40		// x0 = x0 - 0.0 + 0.0 * C.xx ;
41		// x1 = x0 - i1 + 1.0 * C.xx ;
42		// x2 = x0 - 1.0 + 2.0 * C.xx ;
43		vec4 x12 = x0.xyxy + C.xxzz;
44		x12.xy -= i1;
45
46		// Permutations
47		i = mod289(i); // Avoid truncation effects in permutation
48		vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 ))
49		+ i.x + vec3(0.0, i1.x, 1.0 ));
50
51		vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0);
52		m = m*m ;
53		m = m*m ;
54
55		// Gradients: 41 points uniformly over a line, mapped onto a diamond.
56		// The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287)
57
58		vec3 x = 2.0 * fract(p * C.www) - 1.0;
59		vec3 h = abs(x) - 0.5;
60		vec3 ox = floor(x + 0.5);
61		vec3 a0 = x - ox;
62
63		// Normalise gradients implicitly by scaling m
64		// Approximation of: m *= inversesqrt( a0*a0 + h*h );
65		m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h );
66
67		// Compute final noise value at P
68		vec3 g;
69		g.x  = a0.x  * x0.x  + h.x  * x0.y;
70		g.yz = a0.yz * x12.xz + h.yz * x12.yw;
71		return 130.0 * dot(m, g);
72		}
73
74		uniform float amplitude;
75		uniform float time;
76		attribute float id;
77		attribute float texId;
78		attribute float size;
79		attribute vec3 colour;
80
81		varying vec4 vColor;
82		varying float texIndex;
83
84		void main() {
85			if(size > 0.8)
86			{
87				texIndex = texId;
88
89				float a = (80.0-size)*0.01;
90				a = a > 1.0? 1.0: a;
91				a = a < 0.0? 0.0: a;
92				a *= (snoise(vec2(id, time*0.5))*0.5+0.5);
93				vColor = vec4(colour, a);
94
95				vec3 noise = vec3(snoise(vec2(id, time))*2.0, snoise(vec2(id, time + 10000.0))*2.0, 0.0);
96
97				vec4 mvPosition = modelViewMatrix * vec4( position + noise, 1.0 );
98
99				gl_PointSize = size * 1.0 * ( 300.0 / length( mvPosition.xyz ) ) + 2.0;
100
101				gl_Position = projectionMatrix * mvPosition;
102
103			}
104			else
105			gl_PointSize = 0.0;
106		}
107    `,
108  fragment: `
109		uniform vec3 color;
110		uniform sampler2D tex;
111
112		varying vec4 vColor;
113
114		void main() {
115			gl_FragColor = texture2D( tex, gl_PointCoord );
116		}
117    `
118};
119
120class FPS {
121  startTime = Date.now();
122  prevTime = this.startTime;
123
124  ms = 0;
125  msMin = Infinity;
126  msMax = 0;
127  fps = 0;
128  fpsMin = Infinity;
129  fpsMax = 0;
130  frames = 0;
131
132  begin = () => {
133    this.startTime = Date.now();
134  };
135
136  end = () => {
137    const time = Date.now();
138
139    this.ms = time - this.startTime;
140    this.msMin = Math.min(this.msMin, this.ms);
141    this.msMax = Math.max(this.msMax, this.ms);
142
143    this.frames++;
144
145    if (time > this.prevTime + 1000) {
146      this.fps = Math.round((this.frames * 1000) / (time - this.prevTime));
147      this.fpsMin = Math.min(this.fpsMin, this.fps);
148      this.fpsMax = Math.max(this.fpsMax, this.fps);
149
150      this.prevTime = time;
151      this.frames = 0;
152    }
153
154    return time;
155  };
156
157  update = () => {
158    this.startTime = this.end();
159  };
160}
161
162class Particles {
163  frameId = null;
164  active = false;
165  fps = new FPS();
166  currTime = 0;
167  mouseX = -10000;
168  mouseY = -10000;
169  windowHalfX = 0;
170  windowHalfY = 0;
171  renderer = null;
172  camera = null;
173  scene = null;
174  particles = null;
175  springStrength = 0.001;
176  mouseRadius = 5000;
177  mouseStrength = 0.2;
178  damping = 0.002;
179  imageParticlePositions = [];
180  shaderMaterial = null;
181  sourceImageW = 0;
182  sourceImageH = 0;
183  particleDensity = 1;
184  numToDraw = 0;
185  particleFadeTime = 0.01;
186
187  init = (canvas, particleData, sourceImageW, sourceImageH, options) => {
188    this.scale = (options && options.scale) || 0.5;
189    this.particleScale = (options && options.particleScale) || 1;
190
191    this.sourceImageW = sourceImageW;
192    this.sourceImageH = sourceImageH;
193
194    this.imageParticlePositions = particleData.trim().split(" ");
195
196    this.windowHalfX = window.innerWidth / 2;
197    this.windowHalfY = window.innerHeight / 2;
198
199    this.camera = new THREE.OrthographicCamera(
200      -this.windowHalfX,
201      this.windowHalfX,
202      -this.windowHalfY,
203      this.windowHalfY,
204      -500,
205      1000
206    );
207    this.camera.position.z = 1000;
208    this.scene = new THREE.Scene();
209    //scene.fog = new THREE.FogExp2(0x000000, 0.0008);
210
211    const uniforms = {
212      amplitude: {
213        type: "f",
214        value: 1.0
215      },
216      time: {
217        type: "f",
218        value: 0
219      },
220      color: {
221        type: "c",
222        value: new THREE.Color((options && options.particleColor) || "#fff")
223      },
224      tex: {
225        type: "t",
226        value: new THREE.TextureLoader().load("/images/particle_2.jpg")
227      }
228    };
229
230    this.shaderMaterial = new THREE.ShaderMaterial({
231      uniforms: uniforms,
232      vertexShader: shader.vertex,
233      fragmentShader: shader.fragment,
234
235      blending: (options && options.blendMode) || THREE.SubtractiveBlending,
236      depthTest: false,
237      transparent: true
238    });
239
240    this.setParticleDensity(1);
241
242    this.renderer = new THREE.WebGLRenderer({ canvas });
243    this.renderer.setClearColor((options && options.backgroundColor) || "#fff");
244
245    this.renderer.setPixelRatio(window.devicePixelRatio);
246    this.renderer.setSize(window.innerWidth, window.innerHeight);
247
248    document.addEventListener("mousemove", this.onDocumentMouseMove, false);
249    document.addEventListener("touchstart", this.onDocumentTouchStart, false);
250    document.addEventListener("touchend", this.onDocumentTouchEnd, false);
251    window.addEventListener("resize", this.onWindowResize, false);
252
253    this.start();
254  };
255
256  setParticleDensity = factor => {
257    const nParticles = Math.floor(this.imageParticlePositions.length * factor);
258    const geometry = new THREE.BufferGeometry();
259    // geometry.dynamic = true;
260
261    const positions = new Float32Array(nParticles * 3);
262    const colours = new Float32Array(nParticles * 3);
263    const sizes = new Float32Array(nParticles);
264    const ids = new Float32Array(nParticles);
265
266    geometry.addAttribute("position", new THREE.BufferAttribute(positions, 3));
267    geometry.addAttribute("colour", new THREE.BufferAttribute(colours, 3));
268    geometry.addAttribute("size", new THREE.BufferAttribute(sizes, 1));
269    geometry.addAttribute("id", new THREE.BufferAttribute(ids, 1));
270
271    if (this.particles) this.scene.remove(this.particles);
272    this.particles = new THREE.Points(geometry, this.shaderMaterial);
273
274    const initPositions = [];
275    const initSizes = [];
276    const particleVelocities = [];
277    const particleActive = [];
278
279    for (let v = 0; v < nParticles; v++) {
280      const particleInfo = this.imageParticlePositions[v].split(",");
281
282      const vertex = new THREE.Vector3();
283      vertex.x =
284        (parseInt(particleInfo[0]) - this.sourceImageW / 2) * this.scale;
285      vertex.y =
286        (parseInt(particleInfo[1]) - this.sourceImageH / 2) * this.scale;
287      //vertex.x =  Math.random()*window.innerWidth-this.windowHalfX;
288      //vertex.y =  Math.random()*window.innerHeight-this.windowHalfY;
289      vertex.z = 0;
290
291      initPositions.push(vertex.clone());
292      particleVelocities.push(new THREE.Vector3());
293      particleActive.push(0);
294
295      const size = (parseInt(particleInfo[2]) / 100) * 10 + 3;
296      initSizes.push(size);
297
298      positions[v * 3 + 0] = vertex.x;
299      positions[v * 3 + 1] = vertex.y;
300      positions[v * 3 + 2] = 0;
301
302      colours[v * 3 + 0] = 1;
303      colours[v * 3 + 1] = 1;
304      colours[v * 3 + 2] = 1;
305
306      sizes[v] = 0;
307
308      ids[v] = v;
309    }
310
311    this.particles.initPositions = initPositions;
312    this.particles.activeness = particleActive;
313    this.particles.initSizes = initSizes;
314    this.particles.positions = positions;
315    this.particles.velocities = particleVelocities;
316    this.particles.sizes = sizes;
317
318    this.scene.add(this.particles);
319  };
320
321  onWindowResize = () => {
322    //this.windowHalfX = window.innerWidth / 2;
323    //this.windowHalfY = window.innerHeight / 2;
324
325    this.camera.aspect = window.innerWidth / window.innerHeight;
326    this.camera.updateProjectionMatrix();
327
328    this.renderer.setSize(window.innerWidth, window.innerHeight);
329  };
330
331  onDocumentMouseMove = event => {
332    this.mouseX = event.clientX - this.windowHalfX;
333    this.mouseY = event.clientY - this.windowHalfY;
334  };
335
336  onDocumentTouchStart = event => {
337    this.mouseX = event.changedTouches[0].clientX - this.windowHalfX;
338    this.mouseY = event.changedTouches[0].clientY - this.windowHalfY;
339    console.log(event);
340  };
341
342  onDocumentTouchEnd = event => {
343    this.mouseX = -9999;
344    this.mouseY = -9999;
345  };
346
347  animate = () => {
348    this.frameId = requestAnimationFrame(this.animate);
349    this.render();
350  };
351
352  start = () => {
353    if (this.active) return;
354    this.active = true;
355    this.frameId = requestAnimationFrame(this.animate);
356  };
357
358  stop = () => {
359    this.active = false;
360    cancelAnimationFrame(this.frameId);
361  };
362
363  render = () => {
364    this.fps.update();
365
366    if (this.particles === null) return;
367
368    if (this.fps.ms < 25) this.numToDraw += 1000;
369    else if (this.fps.ms > 50) this.numToDraw -= 1000;
370
371    this.numToDraw = Math.min(
372      Math.max(this.numToDraw, 0),
373      this.particles.initPositions.length
374    );
375
376    //console.log(this.fps.ms);
377    //	if(this.fps.fps < 20 && this.particleDensity !== 0.5){
378    //		this.particleDensity = 0.5;
379    //		this.setParticleDensity(this.particleDensity);
380    //
381    //	}
382
383    for (let i = 0; i < this.particles.initPositions.length; i++) {
384      if (i < this.numToDraw) {
385        if (this.particles.activeness[i] < 1)
386          this.particles.activeness[i] += this.particleFadeTime;
387        else this.particles.activeness[i] = 1;
388      } else {
389        if (this.particles.activeness[i] > 0)
390          this.particles.activeness[i] -= this.particleFadeTime;
391        else this.particles.activeness[i] = 0;
392      }
393
394      if (this.particles.activeness[i] > 0) {
395        const pos = new THREE.Vector3(
396          this.particles.positions[i * 3],
397          this.particles.positions[i * 3 + 1],
398          this.particles.positions[i * 3 + 2]
399        );
400
401        //force toward starting point
402        const posToAnchor = new THREE.Vector3();
403        posToAnchor.subVectors(pos, this.particles.initPositions[i]);
404        const force = posToAnchor.clone();
405        force.multiplyScalar(-this.springStrength);
406
407        //damping force
408        const damp = new THREE.Vector3();
409        damp.set(
410          this.particles.velocities[i].x,
411          this.particles.velocities[i].y,
412          this.particles.velocities[i].z
413        );
414        damp.multiplyScalar(-this.damping);
415        // force.add(damp);
416
417        //force away from mouse
418        const d = new THREE.Vector3();
419        d.subVectors(new THREE.Vector3(this.mouseX, this.mouseY, 0), pos);
420        const dMag = (d.x * d.x + d.y * d.y) * 0.05;
421        let strength;
422        if (dMag < this.mouseRadius) {
423          strength = Math.min(-this.mouseStrength / dMag, 0.05);
424        } else strength = 0;
425
426        d.multiplyScalar(strength);
427        force.add(d);
428
429        this.particles.velocities[i].add(force);
430        pos.add(this.particles.velocities[i]);
431
432        this.particles.positions[i * 3] = pos.x;
433        this.particles.positions[i * 3 + 1] = pos.y;
434        this.particles.positions[i * 3 + 2] = pos.z;
435
436        const distToAnchor =
437          posToAnchor.x * posToAnchor.x + posToAnchor.y * posToAnchor.y;
438        const size = Math.min(
439          Math.sin(distToAnchor / 500) * distToAnchor * 0.004 +
440            this.particles.initSizes[i],
441          100
442        );
443        this.particles.sizes[i] =
444          size * this.particles.activeness[i] * this.particleScale;
445      }
446    }
447
448    this.currTime += 0.005;
449    this.particles.material.uniforms.time.value = this.currTime;
450    //Date.now() * 0.005;
451
452    this.particles.geometry.attributes.position.needsUpdate = true;
453    this.particles.geometry.attributes.size.needsUpdate = true;
454
455    //camera.position.z = mouseY;
456
457    this.renderer.render(this.scene, this.camera);
458  };
459}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.