import * as THREE from './three.module.js' import { OrbitControls } from './OrbitControls.module.js' import * as TWEEN from './tween.module.js' export class Threetobus{ constructor(){ this.cameras = {} this.renderers = [] } initScene(){ // Scene this.scene = new THREE.Scene() this.grid = new THREE.GridHelper(20, 20, 0x8888AA, 0x8888AA) this.scene.add(this.grid) // Cameras this.cameras.camPerspective = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000) this.cameras.camPerspective.position.set(3, 3, 5) const aspect = window.innerWidth / window.innerHeight const frustumSize = 10 this.cameras.cam2Dtop = new THREE.OrthographicCamera( -frustumSize * aspect / 2, frustumSize * aspect / 2, frustumSize / 2, -frustumSize / 2, 0.1, 1000 ) this.cameras.cam2Dtop.position.set(0, 100, 0) this.cameras.cam2Dtop.lookAt(0, 0, 0) // Lights const light = new THREE.DirectionalLight(0xffffff, 1) light.position.set(5, 5, 5) light.intensity = 2 this.scene.add(light) this.scene.add(new THREE.AmbientLight(0xffffff, 0.4)) } startRendering(canvasEl, mode){ let renderEngine if(mode=='2D'){ renderEngine = new RenderingEngine(canvasEl, this.scene, this.cameras.cam2Dtop) } else if(mode=='3D') { renderEngine = new RenderingEngine(canvasEl, this.scene, this.cameras.camPerspective) renderEngine.addControls() } else console.error('Unknown rendering mode !') renderEngine.render() this.renderers.push(renderEngine) } buildFromJSON(desc){ let obj if(desc.type === 'Mesh') { const geom = new THREE[desc.geometry.type](...(desc.geometry.args || [])) const mat = new THREE[desc.material.type]( desc.material.color ? { color: desc.material.color } : {} ) obj = new THREE.Mesh(geom, mat) } else if(desc.type === 'Group') { obj = new THREE.Group() } else { throw new Error("Unknown type: " + desc.type) } // Apply transforms if(desc.position) obj.position.set(...desc.position) if(desc.rotation) obj.rotation.set(...desc.rotation) if(desc.scale) obj.scale.set(...desc.scale) // Recursively add children if(desc.children) { desc.children.forEach(childDesc => { obj.add(this.buildFromJSON(childDesc)) }) } return obj } smoothRelMove(options){ // options: object, dX, dY, dZ, delay, easing, easingMode // easings: Linear, Quadratic, Cubic, Quartic, Quintic, Sinusoidal, Exponential, Circular, Elastic, Back, Bounce // easingMode: In → starts slow, accelerates towards the end. // Out → starts fast, decelerates smoothly. // InOut → slow at both ends, faster in the middle. options.easing = options.easing ? options.easing : 'Quadratic' options.easingMode = options.easingMode ? options.easingMode : 'InOut' new TWEEN.Tween(options.object.position) .to({ x: options.object.position.x + options.dX, y: options.object.position.y + options.dY, z: options.object.position.z + options.dZ, }, options.delay) .easing(TWEEN.Easing[options.easing][options.easingMode]) .start() } } class RenderingEngine{ constructor(canvasEl, scene, camera){ this.canvasEl = canvasEl this.scene = scene this.renderer = new THREE.WebGLRenderer({ antialias: true, canvas: this.canvasEl }) this.camera = camera } addControls(){ this.controls = new OrbitControls(this.camera, this.canvasEl) window.addEventListener('resize', () => { this.camera.aspect = window.innerWidth / window.innerHeight this.camera.updateProjectionMatrix() this.renderer.setSize(window.innerWidth, window.innerHeight) }) } render() { TWEEN.update() if(this.resizeRendererToDisplaySize()) { this.camera.aspect = this.renderer.domElement.clientWidth / this.canvasEl.clientHeight this.camera.updateProjectionMatrix() } this.renderer.render(this.scene, this.camera) requestAnimationFrame(this.render.bind(this)) } resizeRendererToDisplaySize() { const width = this.canvasEl.clientWidth const height = this.canvasEl.clientHeight if (this.canvasEl.width !== width || this.canvasEl.height !== height) { this.renderer.setSize(width, height, false) return true } return false } } // Make this module available to common JS if(!app.LoadedModules) app.LoadedModules = {} app.LoadedModules.Threetobus = Threetobus