| /* |
| * Copyright (C) 2025 Mozilla. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
| * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| * POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| const NUM_PARTICLES = 8000; |
| |
| const scene = new THREE.Scene(); |
| scene.background = new THREE.Color(0x111111); |
| |
| const camera = new THREE.PerspectiveCamera(75, 1, 0.1, 1000); |
| const innerHeight = 1080; |
| const innerWidth = 1920; |
| camera.position.z = 500; |
| |
| const canvas = { |
| addEventListener() {}, |
| style: {}, |
| getContext(kind) { |
| return { |
| getExtension(extension) { |
| if (extension == 'EXT_blend_minmax') { |
| return {MIN_EXT: 32775, MAX_EXT: 32776} |
| } |
| if (extension == 'OES_vertex_array_object') { |
| return { |
| createVertexArrayOES() { return 1 }, |
| bindVertexArrayOES() {}, |
| deleteVertexArrayOES() {}, |
| } |
| } |
| }, |
| createTexture() {}, |
| bindTexture() {}, |
| texImage2D() {}, |
| texImage3D() {}, |
| texParameteri() {}, |
| uniform1i() {}, |
| uniform1f() {}, |
| uniform2f() {}, |
| uniform3f() {}, |
| uniform4f() {}, |
| clearColor() {}, |
| clear() {}, |
| clearDepth() {}, |
| clearStencil() {}, |
| enable() {}, |
| disable() {}, |
| depthFunc() {}, |
| depthMask() {}, |
| frontFace() {}, |
| cullFace() {}, |
| getContextAttributes() {}, |
| createBuffer() {}, |
| createFramebuffer() {}, |
| bindBuffer() {}, |
| bufferData() {}, |
| createProgram() {}, |
| attachShader() {}, |
| linkProgram() {}, |
| useProgram() {}, |
| getAttribLocation() {}, |
| getUniformLocation() {}, |
| createShader() {}, |
| shaderSource() {}, |
| compileShader() {}, |
| getShaderParameter() {}, |
| getProgramInfoLog() { return "" }, |
| getShaderInfoLog() { return "" }, |
| getProgramParameter() {}, |
| deleteShader() {}, |
| colorMask() {}, |
| stencilMask() {}, |
| createVertexArray() {}, |
| bindVertexArray() {}, |
| drawElements() {}, |
| lineWidth() {}, |
| drawArrays() {}, |
| viewport() {}, |
| getParameter(param) { |
| if (param == 34930) { return 16 } |
| if (param == 35660) { return 16 } |
| if (param == 3379) { return 8192 } |
| if (param == 36347) { return 1024 } |
| if (param == 36348) { return 32 } |
| if (param == 36349) { return 1024 } |
| if (param == 35661) { return 80 } |
| if (param == 7938) { return "WebGL 2.0" } |
| if (param == 3088) { return [0,0,1024,480] } |
| if (param == 2978) { return [0,0,1024,480] } |
| }, |
| MAX_TEXTURE_IMAGE_UNITS: 34930, |
| MAX_VERTEX_TEXTURE_IMAGE_UNITS: 35660, |
| MAX_TEXTURE_SIZE: 3379, |
| MAX_VERTEX_UNIFORM_VECTORS: 36347, |
| MAX_VARYING_VECTORS: 36348, |
| MAX_FRAGMENT_UNIFORM_VECTORS: 36349, |
| MAX_COMBINED_TEXTURE_IMAGE_UNITS: 35661, |
| VERSION: 7938, |
| SCISSOR_BOX: 3088, |
| VIEWPORT: 2978 |
| } |
| } |
| } |
| |
| const renderer = new THREE.WebGLRenderer({ |
| antialias: false, |
| canvas, |
| powerPreference: 'low-power', |
| precision: 'lowp' |
| }); |
| renderer.setSize(innerWidth, innerHeight); |
| |
| const createGeometryParticle = (size) => { |
| const visibleHeight = 2 * Math.tan(75 * Math.PI/360) * 500; |
| const radius = (size / innerHeight) * visibleHeight / 2; |
| |
| // Main circle |
| const geometry = new THREE.CircleGeometry(radius, 32); |
| const material = new THREE.MeshBasicMaterial({ |
| color: 0xffffff, |
| depthTest: false |
| }); |
| const circle = new THREE.Mesh(geometry, material); |
| |
| const posArray = geometry.attributes.position.array; |
| const outlineVertices = []; |
| for (let i = 3; i < posArray.length; i += 3) { |
| outlineVertices.push( |
| new THREE.Vector3( |
| posArray[i], |
| posArray[i + 1], |
| posArray[i + 2] |
| ) |
| ); |
| } |
| const outlineGeometry = new THREE.BufferGeometry().setFromPoints(outlineVertices); |
| const outline = new THREE.LineLoop( |
| outlineGeometry, |
| new THREE.LineBasicMaterial({ color: 0x000000, depthTest: false }) |
| ); |
| |
| const group = new THREE.Group(); |
| group.add(circle); |
| group.add(outline); |
| return group; |
| }; |
| |
| // Initialize particles |
| var initialized = false; |
| const particles = []; |
| |
| function initialize() { |
| for(let i = 0; i < NUM_PARTICLES; i++) { |
| const size = 10 + Math.random() * 80; |
| const particle = createGeometryParticle(size); |
| |
| // Random initial position |
| const visibleWidth = 2 * Math.tan(75 * Math.PI/360) * 500 * camera.aspect; |
| particle.position.set( |
| THREE.MathUtils.randFloatSpread(visibleWidth), |
| THREE.MathUtils.randFloatSpread(visibleWidth/camera.aspect), |
| 0 |
| ); |
| |
| // Velocity storage |
| particle.velocity = new THREE.Vector2( |
| (Math.random() - 0.5) * 8, |
| (Math.random() - 0.5) * 8 |
| ); |
| |
| scene.add(particle); |
| particles.push(particle); |
| } |
| initialized = true; |
| } |
| |
| // Metrics and animation |
| const visibleWidth = 2 * Math.tan(75 * Math.PI/360) * 500 * camera.aspect; |
| const visibleHeight = visibleWidth / camera.aspect; |
| |
| function animate() { |
| particles.forEach(particle => { |
| particle.position.x += particle.velocity.x; |
| particle.position.y += particle.velocity.y; |
| |
| // Boundary checks |
| if(Math.abs(particle.position.x) > visibleWidth/2) |
| particle.velocity.x *= -1; |
| if(Math.abs(particle.position.y) > visibleHeight/2) |
| particle.velocity.y *= -1; |
| }); |
| |
| renderer.render(scene, camera); |
| } |
| |
| class Benchmark { |
| runIteration() { |
| if (!initialized) { |
| initialize(); |
| } |
| animate(); |
| } |
| } |