188 lines
5.1 KiB
JavaScript
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();
|
|
}
|