[READ-ONLY] Mirror of https://github.com/flo-bit/particle-surfer. flo-bit.dev/particle-surfer/
0

Configure Feed

Select the types of activity you want to include in your feed.

js

Florian (Aug 1, 2025, 7:56 PM +0200) a514b3b3 3a950c87

+270 -270
+1 -1
package.json
··· 5 5 "type": "module", 6 6 "scripts": { 7 7 "dev": "vite", 8 - "build": "tsc && vite build", 8 + "build": "vite build", 9 9 "preview": "vite preview" 10 10 }, 11 11 "devDependencies": {
+269
src/index.js
··· 1 + import * as THREE from 'https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.module.js'; 2 + 3 + //import { LPSphere, LPPlane } from 'https://1florki.github.io/low_poly_generator/lpshapes.js' 4 + 5 + import { Noise } from 'https://1florki.github.io/jsutils2/noise.js' 6 + import { Gradient } from 'https://1florki.github.io/threejsutils/gradient.js' 7 + 8 + 9 + class Particle { 10 + constructor(opt) { 11 + opt = opt || {}; 12 + 13 + this.pos = new THREE.Vector3(0, 0, 0); 14 + this.speed = new THREE.Vector3(0, 0, 0); 15 + this.acc = new THREE.Vector3(0, 0, 0); 16 + this.time = opt.time || Math.random() * 1.8 + 0.3; 17 + if(opt.size) this.reset(opt.size); 18 + } 19 + reset(size) { 20 + //if(Math.random() < 0.01) console.log(this.speed.length()); 21 + this.setPos((Math.random() - 0.5) * 2 * size.x, (Math.random() - 0.5) * 2 * size.y, 0); 22 + this.acc.set(0, 0, 0); 23 + this.speed.set(0, 0, 0); 24 + this.time = Math.random() * 1.8 + 0.3 25 + //console.log("reset"); 26 + } 27 + setPos(x, y, z) { 28 + this.pos.set(x, y, z); 29 + } 30 + update(dt, maxSpeed) { 31 + this.speed.add(this.acc); 32 + this.speed.clampLength(0, maxSpeed); 33 + this.pos.add(this.speed); 34 + this.time -= dt; 35 + } 36 + isDead(size) { 37 + return (Math.abs(this.pos.x) > size.x || Math.abs(this.pos.y) > size.y || this.time < 0) 38 + } 39 + } 40 + 41 + class Particles { 42 + constructor(opt) { 43 + opt = opt || {}; 44 + 45 + this.num = opt.num || 10000; 46 + 47 + this.particleSize = opt.particleSize || 1; 48 + this.size = opt.size || new THREE.Vector3(1, 1, 1); 49 + 50 + 51 + this.vertexColors = opt.vertexColors || true; 52 + this.color = opt.color || new THREE.Color(0xffffff); 53 + this.maxSpeed = opt.maxSpeed || 0.02; 54 + 55 + this.minTime = opt.minTime || 0.01; 56 + this.maxTime = opt.maxTime || 1; 57 + this.newNoise(opt.seed || 0); 58 + 59 + this.createParticles(); 60 + } 61 + newNoise(seed) { 62 + this.noise = new Noise({min: -0.01, max: 0.01, scale: 0.2, octaves: 2, persistence: 0.5, seed: seed}) 63 + } 64 + applyNoiseForce(p) { 65 + p.acc.x = this.noise.getValue(p.pos.x, p.pos.y + 23, p.pos.z) + 0.002; 66 + p.acc.y = this.noise.getValue(p.pos.x + 100, p.pos.y, p.pos.z); 67 + } 68 + 69 + createParticles() { 70 + this.geo = new THREE.BufferGeometry(); 71 + 72 + this.positionData = new Float32Array(this.num * 3); 73 + 74 + this.geo.setAttribute('position', new THREE.BufferAttribute(this.positionData, 3)); 75 + 76 + if(this.vertexColors) { 77 + this.colorData = new Float32Array(this.num * 3); 78 + this.geo.setAttribute('color', new THREE.BufferAttribute(this.colorData, 3)); 79 + } 80 + 81 + this.parts = []; 82 + for(let i = 0; i < this.num; i++) { 83 + this.parts.push(new Particle({size: this.size})); 84 + } 85 + 86 + var mat = new THREE.PointsMaterial({vertexColors: true, size: this.particleSize, sizeAttenuation: false, color: this.color}); 87 + this.mesh = new THREE.Points(this.geo, mat); 88 + } 89 + update(dt, num) { 90 + for(let i = 0; i < this.num; i++) { 91 + let p = this.parts[i]; 92 + 93 + this.applyNoiseForce(p); 94 + p.update(dt, this.maxSpeed); 95 + if(p.isDead(this.size)) p.reset(this.size) 96 + 97 + this.positionData[i * 3] = p.pos.x; 98 + this.positionData[i * 3 + 1] = p.pos.y; 99 + this.positionData[i * 3 + 2] = p.pos.z; 100 + 101 + this.colorData[i * 3] = num * ((p.acc.x + 0.01) * 80 + 0.2); 102 + this.colorData[i * 3 + 1] = (1 - num) * ((p.acc.x + 0.01) * 80 + 0.2); 103 + this.colorData[i * 3 + 2] = (p.acc.y + 0.01) * 50 + 0.2; 104 + 105 + } 106 + this.geo.attributes.position.needsUpdate = true; 107 + 108 + if(this.vertexColors) this.geo.attributes.color.needsUpdate = true; 109 + 110 + this.noise.shiftBy(0.0, 0.0, 0.00); 111 + } 112 + 113 + } 114 + 115 + 116 + var renderer, scene, light, camera, keys = {}, mesh, camNode, clock, particles = [], player, playerPart, active = 0, size, stop = false; 117 + 118 + const levels = [[89842, 74789], [41742, 37680], [78288, 60840], [15693, 83395], [54971, 5891], [29338, 42504], [83166, 59559], [14271, 26324], [87125, 3695], [43298, 94833], [12641, 84336], [81706, 92840], [81342, 18215], [68226, 9387], [50415, 61135], [40356, 68917], [21870, 34087], [77604, 4641], [35813, 32668]]; 119 + 120 + let level = 0; 121 + 122 + function setupScene() { 123 + renderer = new THREE.WebGLRenderer({antialising: true}); 124 + 125 + renderer.setSize(window.innerWidth, window.innerHeight); 126 + renderer.setPixelRatio(window.devicePixelRatio); 127 + 128 + document.body.appendChild(renderer.domElement); 129 + 130 + window.addEventListener('resize', onResize, false); 131 + 132 + scene = new THREE.Scene({background: new THREE.Color(0xff0000)}); 133 + 134 + let ambi = new THREE.AmbientLight(0xffffff, 0.5); // soft white light 135 + scene.add(ambi); 136 + 137 + let light = new THREE.DirectionalLight(0xffffff, 0.5); 138 + scene.add(light); 139 + let light2 = new THREE.DirectionalLight(0xffffff, 0.5); 140 + light2.position.set(0, - 1, 0.5) 141 + scene.add(light2); 142 + 143 + document.addEventListener("keydown", (event) => { 144 + keys[event.key] = true 145 + if(event.key == " ") { 146 + switchActive(); 147 + } 148 + if(event.key == "t") { 149 + stop = !stop; 150 + } 151 + if(event.key == "l") { 152 + nextLevel(); 153 + } 154 + if(event.key == "r") { 155 + let s1 = Math.floor(Math.random() * 100000); 156 + let s2 = Math.floor(Math.random() * 100000); 157 + particles[0].newNoise(s1); 158 + particles[1].newNoise(s2); 159 + console.log("random level [" + s1 + ", " + s2 + "]"); 160 + resetPlayer(); 161 + } 162 + }, false); 163 + 164 + document.addEventListener("mouseup", (event) => { 165 + switchActive(); 166 + }); 167 + 168 + let fogColor = new THREE.Color(0x000000); 169 + scene.background = fogColor; 170 + scene.fog = new THREE.Fog(fogColor, 4.5, 4.7); 171 + 172 + size = new THREE.Vector3(10, 2, .01); 173 + 174 + camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.2, 40); 175 + camera.position.z = 4.5 176 + 177 + clock = new THREE.Clock(); 178 + 179 + let part1 = new Particles({size: size, seed: levels[level][0]}); 180 + let part2 = new Particles({size: size, seed: levels[level][1]}); 181 + 182 + particles.push(part1); 183 + particles.push(part2); 184 + scene.add(part1.mesh); 185 + scene.add(part2.mesh); 186 + switchActive(); 187 + 188 + player = new THREE.Mesh(new THREE.SphereGeometry( 0.07, 8, 8 ), new THREE.MeshBasicMaterial( {color: 0xffffff} )) 189 + playerPart = new Particle(); 190 + playerPart.pos.set(-size.x * 5 / 6, 0, 0); 191 + playerPart.time = 1000000; 192 + scene.add(player); 193 + 194 + let borderColor = 0x444444//0xcf1020 195 + let goalColor = 0xffffff//0xcf1020 196 + let border = new THREE.Mesh(new THREE.BoxGeometry( size.x * 2, 0.1, 0.15 ), new THREE.MeshStandardMaterial( {color: borderColor} )); 197 + border.position.y = size.y; 198 + scene.add(border); 199 + let border2 = border.clone(); 200 + border2.position.y = -size.y; 201 + scene.add(border2) 202 + 203 + let border3 = new THREE.Mesh(new THREE.BoxGeometry( 0.1, size.y * 2 + 0.1, 0.15 ), new THREE.MeshStandardMaterial( {color: borderColor} )); 204 + border3.position.x = -size.x; 205 + scene.add(border3) 206 + 207 + let goal = new THREE.Mesh(new THREE.BoxGeometry( 0.1, size.y * 2 + 0.1, 0.15 ), new THREE.MeshStandardMaterial( {color: goalColor} )); 208 + goal.position.x = size.x; 209 + scene.add(goal) 210 + } 211 + function nextLevel() { 212 + level++; 213 + if(level >= levels.length) level = 0; 214 + 215 + particles[0].newNoise(levels[level][0]); 216 + particles[1].newNoise(levels[level][1]); 217 + console.log("level: " + level + " [" + levels[level][0] + ", " + levels[level][1] + "]"); 218 + } 219 + function switchActive() { 220 + active++; 221 + if(active >= particles.length) active = 0; 222 + for(let i = 0; i < particles.length; i++) { 223 + particles[i].mesh.material.size = 1.7; 224 + particles[i].mesh.position.z = -0.1; 225 + } 226 + particles[active].mesh.material.size = 1.7; 227 + particles[active].mesh.position.z = 0; 228 + } 229 + function resetPlayer() { 230 + playerPart.reset(size); 231 + playerPart.pos.set(-size.x * 5 / 6, 0, 0); 232 + playerPart.time = 1000000; 233 + } 234 + function animate(now) { 235 + requestAnimationFrame(animate); 236 + 237 + // animation loop here 238 + 239 + let dt = clock.getDelta(); 240 + //console.log(dt); 241 + for(let i = 0; i < particles.length; i++) { 242 + particles[i].update(dt, i); 243 + } 244 + if(!stop) { 245 + 246 + particles[active].applyNoiseForce(playerPart); 247 + playerPart.update(dt, particles[active].maxSpeed); 248 + player.position.copy(playerPart.pos); 249 + if(playerPart.isDead(size)) { 250 + if(playerPart.pos.x >= size.x) { 251 + nextLevel(); 252 + } 253 + resetPlayer(); 254 + } 255 + camera.position.x = (playerPart.pos.x + camera.position.x * 49) / 50; 256 + } 257 + //console.log(playerPart.pos); 258 + renderer.render(scene, camera); 259 + } 260 + 261 + function onResize() { 262 + camera.aspect = window.innerWidth / window.innerHeight; 263 + camera.updateProjectionMatrix(); 264 + 265 + renderer.setSize(window.innerWidth, window.innerHeight); 266 + } 267 + 268 + setupScene(); 269 + animate();
-269
src/index.ts
··· 1 - import * as THREE from 'https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.module.js'; 2 - 3 - //import { LPSphere, LPPlane } from 'https://1florki.github.io/low_poly_generator/lpshapes.js' 4 - 5 - import { Noise } from 'https://1florki.github.io/jsutils2/noise.js' 6 - import { Gradient } from 'https://1florki.github.io/threejsutils/gradient.js' 7 - 8 - 9 - class Particle { 10 - constructor(opt) { 11 - opt = opt || {}; 12 - 13 - this.pos = new THREE.Vector3(0, 0, 0); 14 - this.speed = new THREE.Vector3(0, 0, 0); 15 - this.acc = new THREE.Vector3(0, 0, 0); 16 - this.time = opt.time || Math.random() * 1.8 + 0.3; 17 - if(opt.size) this.reset(opt.size); 18 - } 19 - reset(size) { 20 - //if(Math.random() < 0.01) console.log(this.speed.length()); 21 - this.setPos((Math.random() - 0.5) * 2 * size.x, (Math.random() - 0.5) * 2 * size.y, 0); 22 - this.acc.set(0, 0, 0); 23 - this.speed.set(0, 0, 0); 24 - this.time = Math.random() * 1.8 + 0.3 25 - //console.log("reset"); 26 - } 27 - setPos(x, y, z) { 28 - this.pos.set(x, y, z); 29 - } 30 - update(dt, maxSpeed) { 31 - this.speed.add(this.acc); 32 - this.speed.clampLength(0, maxSpeed); 33 - this.pos.add(this.speed); 34 - this.time -= dt; 35 - } 36 - isDead(size) { 37 - return (Math.abs(this.pos.x) > size.x || Math.abs(this.pos.y) > size.y || this.time < 0) 38 - } 39 - } 40 - 41 - class Particles { 42 - constructor(opt) { 43 - opt = opt || {}; 44 - 45 - this.num = opt.num || 10000; 46 - 47 - this.particleSize = opt.particleSize || 1; 48 - this.size = opt.size || new THREE.Vector3(1, 1, 1); 49 - 50 - 51 - this.vertexColors = opt.vertexColors || true; 52 - this.color = opt.color || new THREE.Color(0xffffff); 53 - this.maxSpeed = opt.maxSpeed || 0.02; 54 - 55 - this.minTime = opt.minTime || 0.01; 56 - this.maxTime = opt.maxTime || 1; 57 - this.newNoise(opt.seed || 0); 58 - 59 - this.createParticles(); 60 - } 61 - newNoise(seed) { 62 - this.noise = new Noise({min: -0.01, max: 0.01, scale: 0.2, octaves: 2, persistence: 0.5, seed: seed}) 63 - } 64 - applyNoiseForce(p) { 65 - p.acc.x = this.noise.getValue(p.pos.x, p.pos.y + 23, p.pos.z) + 0.002; 66 - p.acc.y = this.noise.getValue(p.pos.x + 100, p.pos.y, p.pos.z); 67 - } 68 - 69 - createParticles() { 70 - this.geo = new THREE.BufferGeometry(); 71 - 72 - this.positionData = new Float32Array(this.num * 3); 73 - 74 - this.geo.setAttribute('position', new THREE.BufferAttribute(this.positionData, 3)); 75 - 76 - if(this.vertexColors) { 77 - this.colorData = new Float32Array(this.num * 3); 78 - this.geo.setAttribute('color', new THREE.BufferAttribute(this.colorData, 3)); 79 - } 80 - 81 - this.parts = []; 82 - for(let i = 0; i < this.num; i++) { 83 - this.parts.push(new Particle({size: this.size})); 84 - } 85 - 86 - var mat = new THREE.PointsMaterial({vertexColors: true, size: this.particleSize, sizeAttenuation: false, color: this.color}); 87 - this.mesh = new THREE.Points(this.geo, mat); 88 - } 89 - update(dt, num) { 90 - for(let i = 0; i < this.num; i++) { 91 - let p = this.parts[i]; 92 - 93 - this.applyNoiseForce(p); 94 - p.update(dt, this.maxSpeed); 95 - if(p.isDead(this.size)) p.reset(this.size) 96 - 97 - this.positionData[i * 3] = p.pos.x; 98 - this.positionData[i * 3 + 1] = p.pos.y; 99 - this.positionData[i * 3 + 2] = p.pos.z; 100 - 101 - this.colorData[i * 3] = num * ((p.acc.x + 0.01) * 80 + 0.2); 102 - this.colorData[i * 3 + 1] = (1 - num) * ((p.acc.x + 0.01) * 80 + 0.2); 103 - this.colorData[i * 3 + 2] = (p.acc.y + 0.01) * 50 + 0.2; 104 - 105 - } 106 - this.geo.attributes.position.needsUpdate = true; 107 - 108 - if(this.vertexColors) this.geo.attributes.color.needsUpdate = true; 109 - 110 - this.noise.shiftBy(0.0, 0.0, 0.00); 111 - } 112 - 113 - } 114 - 115 - 116 - var renderer, scene, light, camera, keys = {}, mesh, camNode, clock, particles = [], player, playerPart, active = 0, size, stop = false; 117 - 118 - const levels = [[89842, 74789], [41742, 37680], [78288, 60840], [15693, 83395], [54971, 5891], [29338, 42504], [83166, 59559], [14271, 26324], [87125, 3695], [43298, 94833], [12641, 84336], [81706, 92840], [81342, 18215], [68226, 9387], [50415, 61135], [40356, 68917], [21870, 34087], [77604, 4641], [35813, 32668]]; 119 - 120 - let level = 0; 121 - 122 - function setupScene() { 123 - renderer = new THREE.WebGLRenderer({antialising: true}); 124 - 125 - renderer.setSize(window.innerWidth, window.innerHeight); 126 - renderer.setPixelRatio(window.devicePixelRatio); 127 - 128 - document.body.appendChild(renderer.domElement); 129 - 130 - window.addEventListener('resize', onResize, false); 131 - 132 - scene = new THREE.Scene({background: new THREE.Color(0xff0000)}); 133 - 134 - let ambi = new THREE.AmbientLight(0xffffff, 0.5); // soft white light 135 - scene.add(ambi); 136 - 137 - let light = new THREE.DirectionalLight(0xffffff, 0.5); 138 - scene.add(light); 139 - let light2 = new THREE.DirectionalLight(0xffffff, 0.5); 140 - light2.position.set(0, - 1, 0.5) 141 - scene.add(light2); 142 - 143 - document.addEventListener("keydown", (event) => { 144 - keys[event.key] = true 145 - if(event.key == " ") { 146 - switchActive(); 147 - } 148 - if(event.key == "t") { 149 - stop = !stop; 150 - } 151 - if(event.key == "l") { 152 - nextLevel(); 153 - } 154 - if(event.key == "r") { 155 - let s1 = Math.floor(Math.random() * 100000); 156 - let s2 = Math.floor(Math.random() * 100000); 157 - particles[0].newNoise(s1); 158 - particles[1].newNoise(s2); 159 - console.log("random level [" + s1 + ", " + s2 + "]"); 160 - resetPlayer(); 161 - } 162 - }, false); 163 - 164 - document.addEventListener("mouseup", (event) => { 165 - switchActive(); 166 - }); 167 - 168 - let fogColor = new THREE.Color(0x000000); 169 - scene.background = fogColor; 170 - scene.fog = new THREE.Fog(fogColor, 4.5, 4.7); 171 - 172 - size = new THREE.Vector3(10, 2, .01); 173 - 174 - camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.2, 40); 175 - camera.position.z = 4.5 176 - 177 - clock = new THREE.Clock(); 178 - 179 - let part1 = new Particles({size: size, seed: levels[level][0]}); 180 - let part2 = new Particles({size: size, seed: levels[level][1]}); 181 - 182 - particles.push(part1); 183 - particles.push(part2); 184 - scene.add(part1.mesh); 185 - scene.add(part2.mesh); 186 - switchActive(); 187 - 188 - player = new THREE.Mesh(new THREE.SphereGeometry( 0.07, 8, 8 ), new THREE.MeshBasicMaterial( {color: 0xffffff} )) 189 - playerPart = new Particle(); 190 - playerPart.pos.set(-size.x * 5 / 6, 0, 0); 191 - playerPart.time = 1000000; 192 - scene.add(player); 193 - 194 - let borderColor = 0x444444//0xcf1020 195 - let goalColor = 0xffffff//0xcf1020 196 - let border = new THREE.Mesh(new THREE.BoxGeometry( size.x * 2, 0.1, 0.15 ), new THREE.MeshStandardMaterial( {color: borderColor} )); 197 - border.position.y = size.y; 198 - scene.add(border); 199 - let border2 = border.clone(); 200 - border2.position.y = -size.y; 201 - scene.add(border2) 202 - 203 - let border3 = new THREE.Mesh(new THREE.BoxGeometry( 0.1, size.y * 2 + 0.1, 0.15 ), new THREE.MeshStandardMaterial( {color: borderColor} )); 204 - border3.position.x = -size.x; 205 - scene.add(border3) 206 - 207 - let goal = new THREE.Mesh(new THREE.BoxGeometry( 0.1, size.y * 2 + 0.1, 0.15 ), new THREE.MeshStandardMaterial( {color: goalColor} )); 208 - goal.position.x = size.x; 209 - scene.add(goal) 210 - } 211 - function nextLevel() { 212 - level++; 213 - if(level >= levels.length) level = 0; 214 - 215 - particles[0].newNoise(levels[level][0]); 216 - particles[1].newNoise(levels[level][1]); 217 - console.log("level: " + level + " [" + levels[level][0] + ", " + levels[level][1] + "]"); 218 - } 219 - function switchActive() { 220 - active++; 221 - if(active >= particles.length) active = 0; 222 - for(let i = 0; i < particles.length; i++) { 223 - particles[i].mesh.material.size = 1.7; 224 - particles[i].mesh.position.z = -0.1; 225 - } 226 - particles[active].mesh.material.size = 1.7; 227 - particles[active].mesh.position.z = 0; 228 - } 229 - function resetPlayer() { 230 - playerPart.reset(size); 231 - playerPart.pos.set(-size.x * 5 / 6, 0, 0); 232 - playerPart.time = 1000000; 233 - } 234 - function animate(now) { 235 - requestAnimationFrame(animate); 236 - 237 - // animation loop here 238 - 239 - let dt = clock.getDelta(); 240 - //console.log(dt); 241 - for(let i = 0; i < particles.length; i++) { 242 - particles[i].update(dt, i); 243 - } 244 - if(!stop) { 245 - 246 - particles[active].applyNoiseForce(playerPart); 247 - playerPart.update(dt, particles[active].maxSpeed); 248 - player.position.copy(playerPart.pos); 249 - if(playerPart.isDead(size)) { 250 - if(playerPart.pos.x >= size.x) { 251 - nextLevel(); 252 - } 253 - resetPlayer(); 254 - } 255 - camera.position.x = (playerPart.pos.x + camera.position.x * 49) / 50; 256 - } 257 - //console.log(playerPart.pos); 258 - renderer.render(scene, camera); 259 - } 260 - 261 - function onResize() { 262 - camera.aspect = window.innerWidth / window.innerHeight; 263 - camera.updateProjectionMatrix(); 264 - 265 - renderer.setSize(window.innerWidth, window.innerHeight); 266 - } 267 - 268 - setupScene(); 269 - animate();