Add theme_3d_systems/static/src/js/three/hero_network_scene.js
This commit is contained in:
parent
a2b1877b2a
commit
da03446aa6
1 changed files with 158 additions and 0 deletions
158
theme_3d_systems/static/src/js/three/hero_network_scene.js
Normal file
158
theme_3d_systems/static/src/js/three/hero_network_scene.js
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { loadThree } from './three_core_loader';
|
||||
|
||||
/**
|
||||
* High-performance 3D Network Scene (Three.js WebGL)
|
||||
* Features interconnected nodes, floating geometry, and mouse parallax.
|
||||
*/
|
||||
export class HeroNetworkScene {
|
||||
constructor(containerEl, options = {}) {
|
||||
this.container = containerEl;
|
||||
this.options = Object.assign({
|
||||
particleCount: 65,
|
||||
lineDistance: 130,
|
||||
primaryColor: 0x00f2fe,
|
||||
secondaryColor: 0x7928ca,
|
||||
}, options);
|
||||
|
||||
this.scene = null;
|
||||
this.camera = null;
|
||||
this.renderer = null;
|
||||
this.particles = [];
|
||||
this.linesMesh = null;
|
||||
this.animationFrameId = null;
|
||||
this.mouseX = 0;
|
||||
this.mouseY = 0;
|
||||
this.targetX = 0;
|
||||
this.targetY = 0;
|
||||
this.isDestroyed = false;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
async init() {
|
||||
const THREE = await loadThree();
|
||||
if (this.isDestroyed || !this.container) return;
|
||||
|
||||
const width = this.container.clientWidth || window.innerWidth;
|
||||
const height = this.container.clientHeight || window.innerHeight;
|
||||
|
||||
// Scene & Camera
|
||||
this.scene = new THREE.Scene();
|
||||
this.camera = new THREE.PerspectiveCamera(60, width / height, 1, 1000);
|
||||
this.camera.position.z = 320;
|
||||
|
||||
// Renderer
|
||||
this.renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true, powerPreference: 'high-performance' });
|
||||
this.renderer.setSize(width, height);
|
||||
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
||||
this.container.appendChild(this.renderer.domElement);
|
||||
|
||||
// Nodes & Geometry
|
||||
const particleGeo = new THREE.SphereGeometry(1.8, 12, 12);
|
||||
const particleMat = new THREE.MeshBasicMaterial({ color: this.options.primaryColor });
|
||||
|
||||
for (let i = 0; i < this.options.particleCount; i++) {
|
||||
const mesh = new THREE.Mesh(particleGeo, particleMat);
|
||||
mesh.position.x = (Math.random() - 0.5) * 450;
|
||||
mesh.position.y = (Math.random() - 0.5) * 350;
|
||||
mesh.position.z = (Math.random() - 0.5) * 250;
|
||||
|
||||
mesh.velocity = {
|
||||
x: (Math.random() - 0.5) * 0.4,
|
||||
y: (Math.random() - 0.5) * 0.4,
|
||||
z: (Math.random() - 0.5) * 0.4,
|
||||
};
|
||||
|
||||
this.scene.add(mesh);
|
||||
this.particles.push(mesh);
|
||||
}
|
||||
|
||||
// Lines setup
|
||||
const lineMat = new THREE.LineBasicMaterial({
|
||||
color: this.options.primaryColor,
|
||||
transparent: true,
|
||||
opacity: 0.25,
|
||||
});
|
||||
const lineGeo = new THREE.BufferGeometry();
|
||||
this.linesMesh = new THREE.LineSegments(lineGeo, lineMat);
|
||||
this.scene.add(this.linesMesh);
|
||||
|
||||
// Mouse move listener for parallax
|
||||
this.onMouseMove = (e) => {
|
||||
this.mouseX = (e.clientX - window.innerWidth / 2) * 0.05;
|
||||
this.mouseY = (e.clientY - window.innerHeight / 2) * 0.05;
|
||||
};
|
||||
window.addEventListener('mousemove', this.onMouseMove, { passive: true });
|
||||
|
||||
// Resize handler
|
||||
this.onResize = () => {
|
||||
if (!this.container || !this.renderer || !this.camera) return;
|
||||
const w = this.container.clientWidth;
|
||||
const h = this.container.clientHeight;
|
||||
this.camera.aspect = w / h;
|
||||
this.camera.updateProjectionMatrix();
|
||||
this.renderer.setSize(w, h);
|
||||
};
|
||||
window.addEventListener('resize', this.onResize, { passive: true });
|
||||
|
||||
this.animate(THREE);
|
||||
}
|
||||
|
||||
animate(THREE) {
|
||||
if (this.isDestroyed) return;
|
||||
|
||||
this.animationFrameId = requestAnimationFrame(() => this.animate(THREE));
|
||||
|
||||
this.targetX += (this.mouseX - this.targetX) * 0.05;
|
||||
this.targetY += (this.mouseY - this.targetY) * 0.05;
|
||||
this.camera.position.x = this.targetX;
|
||||
this.camera.position.y = -this.targetY;
|
||||
this.camera.lookAt(this.scene.position);
|
||||
|
||||
const positions = [];
|
||||
|
||||
for (let i = 0; i < this.particles.length; i++) {
|
||||
const p = this.particles[i];
|
||||
p.position.x += p.velocity.x;
|
||||
p.position.y += p.velocity.y;
|
||||
p.position.z += p.velocity.z;
|
||||
|
||||
// Boundary wrap
|
||||
if (Math.abs(p.position.x) > 240) p.velocity.x *= -1;
|
||||
if (Math.abs(p.position.y) > 180) p.velocity.y *= -1;
|
||||
if (Math.abs(p.position.z) > 140) p.velocity.z *= -1;
|
||||
|
||||
// Connect lines
|
||||
for (let j = i + 1; j < this.particles.length; j++) {
|
||||
const p2 = this.particles[j];
|
||||
const dist = p.position.distanceTo(p2.position);
|
||||
if (dist < this.options.lineDistance) {
|
||||
positions.push(p.position.x, p.position.y, p.position.z);
|
||||
positions.push(p2.position.x, p2.position.y, p2.position.z);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.linesMesh) {
|
||||
this.linesMesh.geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
|
||||
}
|
||||
|
||||
this.renderer.render(this.scene, this.camera);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.isDestroyed = true;
|
||||
if (this.animationFrameId) {
|
||||
cancelAnimationFrame(this.animationFrameId);
|
||||
}
|
||||
window.removeEventListener('mousemove', this.onMouseMove);
|
||||
window.removeEventListener('resize', this.onResize);
|
||||
|
||||
if (this.renderer && this.renderer.domElement && this.renderer.domElement.parentNode) {
|
||||
this.renderer.domElement.parentNode.removeChild(this.renderer.domElement);
|
||||
this.renderer.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue