Files
aperisolve/aperisolve/static/js/background.js
T
2025-12-27 14:55:57 +01:00

188 lines
5.1 KiB
JavaScript

import * as THREE from "three";
import Stats from "three/addons/stats.module.js";
let container, stats;
let camera, scene, renderer, mesh;
const instances = 5000;
let lastTime = 0;
const moveQ = new THREE.Quaternion(0.5, 0.5, 0.5, 0.0).normalize();
const tmpQ = new THREE.Quaternion();
const tmpM = new THREE.Matrix4();
const currentM = new THREE.Matrix4();
function isSoftwareRenderer() {
const canvas = document.createElement("canvas");
const gl =
canvas.getContext("webgl") ||
canvas.getContext("experimental-webgl");
if (!gl) return true;
const ext = gl.getExtension("WEBGL_debug_renderer_info");
if (!ext) return false;
const renderer = gl.getParameter(ext.UNMASKED_RENDERER_WEBGL);
return /swiftshader|software/i.test(renderer);
}
if (isSoftwareRenderer()) {
console.warn("Software rendering detected — disabling Three.js");
} else {
init();
}
function init() {
container = document.getElementById("three-bg");
camera = new THREE.PerspectiveCamera(
50,
window.innerWidth / window.innerHeight,
1,
1000
);
scene = new THREE.Scene();
// Get the computed background color of the body
const bodyBg = getComputedStyle(document.body).backgroundColor;
scene.background = new THREE.Color(bodyBg);
// geometry
const geometry = new THREE.InstancedBufferGeometry();
// per mesh data x,y,z,w,u,v,s,t for 4-element alignment
// only use x,y,z and u,v; but x, y, z, nx, ny, nz, u, v would be a good layout
const vertexBuffer = new THREE.InterleavedBuffer(
new Float32Array([
// Front
-1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, -1, -1, 1, 0, 0, 1, 0, 0,
1, -1, 1, 0, 1, 1, 0, 0,
// Back
1, 1, -1, 0, 1, 0, 0, 0, -1, 1, -1, 0, 0, 0, 0, 0, 1, -1, -1, 0, 1, 1, 0,
0, -1, -1, -1, 0, 0, 1, 0, 0,
// Left
-1, 1, -1, 0, 1, 1, 0, 0, -1, 1, 1, 0, 1, 0, 0, 0, -1, -1, -1, 0, 0, 1, 0,
0, -1, -1, 1, 0, 0, 0, 0, 0,
// Right
1, 1, 1, 0, 1, 0, 0, 0, 1, 1, -1, 0, 1, 1, 0, 0, 1, -1, 1, 0, 0, 0, 0, 0,
1, -1, -1, 0, 0, 1, 0, 0,
// Top
-1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, -1, 1, -1, 0, 0, 1, 0, 0,
1, 1, -1, 0, 1, 1, 0, 0,
// Bottom
1, -1, 1, 0, 1, 0, 0, 0, -1, -1, 1, 0, 0, 0, 0, 0, 1, -1, -1, 0, 1, 1, 0,
0, -1, -1, -1, 0, 0, 1, 0, 0,
]),
8
);
// Use vertexBuffer, starting at offset 0, 3 items in position attribute
const positions = new THREE.InterleavedBufferAttribute(vertexBuffer, 3, 0);
geometry.setAttribute("position", positions);
// Use vertexBuffer, starting at offset 4, 2 items in uv attribute
const uvs = new THREE.InterleavedBufferAttribute(vertexBuffer, 2, 4);
geometry.setAttribute("uv", uvs);
const indices = new Uint16Array([
0, 2, 1, 2, 3, 1, 4, 6, 5, 6, 7, 5, 8, 10, 9, 10, 11, 9, 12, 14, 13, 14, 15,
13, 16, 17, 18, 18, 17, 19, 20, 21, 22, 22, 21, 23,
]);
geometry.setIndex(new THREE.BufferAttribute(indices, 1));
// material
const material = new THREE.MeshBasicMaterial();
material.map = new THREE.TextureLoader().load("static/img/octo2.png");
material.map.colorSpace = THREE.SRGBColorSpace;
material.map.flipY = false;
material.transparent = true;
material.opacity = 0.3;
// per instance data
const matrix = new THREE.Matrix4();
const offset = new THREE.Vector3();
const orientation = new THREE.Quaternion();
const scale = new THREE.Vector3(1, 1, 1);
let x, y, z, w;
mesh = new THREE.InstancedMesh(geometry, material, instances);
for (let i = 0; i < instances; i++) {
// offsets
x = Math.random() * 100 - 50;
y = Math.random() * 100 - 50;
z = Math.random() * 100 - 50;
offset.set(x, y, z).normalize();
offset.multiplyScalar(5); // move out at least 5 units from center in current direction
offset.set(x + offset.x, y + offset.y, z + offset.z);
// orientations
x = Math.random() * 2 - 1;
y = Math.random() * 2 - 1;
z = Math.random() * 2 - 1;
w = Math.random() * 2 - 1;
orientation.set(x, y, z, w).normalize();
matrix.compose(offset, orientation, scale);
mesh.setMatrixAt(i, matrix);
}
scene.add(mesh);
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setAnimationLoop(animate);
container.appendChild(renderer.domElement);
stats = new Stats();
container.appendChild(stats.dom);
window.addEventListener("resize", onWindowResize);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
//
function animate() {
const time = performance.now();
mesh.rotation.y = time * 0.00005;
const delta = (time - lastTime) / 5000;
tmpQ.set(moveQ.x * delta, moveQ.y * delta, moveQ.z * delta, 1).normalize();
tmpM.makeRotationFromQuaternion(tmpQ);
for (let i = 0, il = instances; i < il; i++) {
mesh.getMatrixAt(i, currentM);
currentM.multiply(tmpM);
mesh.setMatrixAt(i, currentM);
}
mesh.instanceMatrix.needsUpdate = true;
mesh.computeBoundingSphere();
lastTime = time;
renderer.render(scene, camera);
//stats.update();
}