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