diff --git a/theme_3d_systems/static/src/js/three/system_flow_scene.js b/theme_3d_systems/static/src/js/three/system_flow_scene.js new file mode 100644 index 0000000..90b3c1a --- /dev/null +++ b/theme_3d_systems/static/src/js/three/system_flow_scene.js @@ -0,0 +1,59 @@ +/** @odoo-module **/ + +import { loadThree } from './three_core_loader'; + +/** + * 3D System Flow Visualization (Customer -> Website -> Odoo ERP -> CRM/Sales/Accounting) + */ +export class SystemFlowScene { + constructor(containerEl) { + this.container = containerEl; + this.scene = null; + this.camera = null; + this.renderer = null; + this.isDestroyed = false; + this.animationFrameId = null; + this.init(); + } + + async init() { + const THREE = await loadThree(); + if (this.isDestroyed || !this.container) return; + + const w = this.container.clientWidth || 600; + const h = this.container.clientHeight || 300; + + this.scene = new THREE.Scene(); + this.camera = new THREE.PerspectiveCamera(50, w / h, 1, 1000); + this.camera.position.z = 200; + + this.renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true }); + this.renderer.setSize(w, h); + this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); + this.container.appendChild(this.renderer.domElement); + + // Ambient Light + const light = new THREE.PointLight(0x00f2fe, 2, 300); + light.position.set(0, 50, 100); + this.scene.add(light); + + this.animate(THREE); + } + + animate(THREE) { + if (this.isDestroyed) return; + this.animationFrameId = requestAnimationFrame(() => this.animate(THREE)); + if (this.renderer && this.scene && this.camera) { + this.renderer.render(this.scene, this.camera); + } + } + + destroy() { + this.isDestroyed = true; + if (this.animationFrameId) cancelAnimationFrame(this.animationFrameId); + if (this.renderer && this.renderer.domElement && this.renderer.domElement.parentNode) { + this.renderer.domElement.parentNode.removeChild(this.renderer.domElement); + this.renderer.dispose(); + } + } +}