import * as THREE from 'three' import * as TWEEN from 'three/examples/jsm/libs/tween.module.js' import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; class HelperBot{ constructor(canvasSelector){ this.renderer = null this.canvasEl = document.querySelector(canvasSelector) this.initScene() window.bot=this } initScene(){ this.canvasEl .width = window.innerWidth this.canvasEl .height = window.innerHeight this.scene = new THREE.Scene() this.camera = new THREE.PerspectiveCamera(30, this.canvasEl.clientWidth / this.canvasEl.clientHeight, 0.1, 1000) this.camera.position.set(0, 1, 10) this.camera.lookAt(0, 0, 0) // Lights const dLight1 = new THREE.DirectionalLight(0xffffff, 1.5) dLight1.position.set(5, 5, 5) this.scene.add(dLight1) const dLight2 = new THREE.DirectionalLight(0xffffff, 1.5) dLight2.position.set(-5, -5, 5) this.scene.add(dLight2) this.scene.add(new THREE.AmbientLight(0xffffff, 1)) this.renderer = new THREE.WebGLRenderer({ canvas: this.canvasEl , alpha: true }) this.renderer.setSize(this.canvasEl .width, this.canvasEl .height) this.renderer.setPixelRatio(window.devicePixelRatio) this.renderer.setClearColor(0x000000, 0) this.threeClock = new THREE.Clock() const loader = new GLTFLoader().setPath('/app/assets/3dModels/') loader.load('mecha2.glb', async gltf => { this.mecha = new THREE.Group() this.scene.add(this.mecha) this.mechaModel = gltf.scene this.mecha.add(this.mechaModel) this.mechaModel.scale.set(5, 5, 5) this.mechaModel.rotation.set(0, 0, 0) this.mechaModel.position.set(0, -1.5, 0) this.mechaModel.updateMatrix() this.mechaModel.rotateY(-Math.PI) // 180deg so facing user // GPU compilation (optimization) await this.renderer.compileAsync(this.mechaModel, this.camera, this.scene) this.mechaModel.traverse(obj => { if (obj.isMesh && obj.material && obj.material.color) { const c = obj.material.color.clone() const boost = 0.1 obj.material.emissive = c obj.material.emissiveIntensity = boost } }) if (gltf.animations && gltf.animations.length > 0) { this.mixer = new THREE.AnimationMixer(this.mechaModel) const action = this.mixer.clipAction(gltf.animations[0]) action.play() } this.leftArm = this.mechaModel.getObjectByName('ArmL1_01') this.animate = true }) this.resizeWindow() window.addEventListener('resize', this.resizeWindow.bind(this)) this.render() } resizeWindow() { const w = window.innerWidth const h = window.innerHeight this.canvasEl .width = w this.canvasEl .height = h this.renderer.setSize(w, h) this.renderer.setPixelRatio(window.devicePixelRatio) this.camera.aspect = w / h this.camera.updateProjectionMatrix() } startRendering(){ this.addControls() this.render() } render() { TWEEN.update() const resized = this.resizeRendererToDisplaySize() const delta = this.threeClock.getDelta() if(this.mixer && this.animate) this.mixer.update(delta) 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 } translateBot(position, delay=500, endcb, updatecb){ if(this.movingBotTween) this.movingBotTween.end() this.movingBotTween = new TWEEN.Tween(this.mecha.position) .to({ x: Number(position.x), y: Number(position.z), z: Number(position.y), }, delay) .easing(TWEEN.Easing.Sinusoidal.InOut) if(typeof(endcb)=='function') this.movingBotTween.onComplete(endcb) if(typeof(updatecb)=='function') this.movingBotTween.onUpdate(updatecb) this.movingBotTween.start() } rotateBot(rotation, delay=500, endcb, updatecb){ //TODO : respect existing Z if(this.rotatingBotTween) this.rotatingBotTween.end() this.rotatingBotTween = new TWEEN.Tween(this.mecha.rotation) .to({ x: Number(rotation.x*Math.PI/180), y: Number(rotation.z*Math.PI/180), z: Number(rotation.y*Math.PI/180), }, delay) .easing(TWEEN.Easing.Sinusoidal.InOut) if(typeof(endcb)=='function') this.rotatingBotTween.onComplete(endcb) if(typeof(updatecb)=='function') this.rotatingBotTween.onUpdate(updatecb) this.rotatingBotTween.start() } propelMove(position){ //TODO : respect existing Z rotation const moveDelay = 1000 const dx = this.mecha.position.x - position.x const dy = this.mecha.position.z - position.y const dz = this.mecha.position.y - position.z const delay = Math.max(Math.abs(dx), Math.abs(dy), Math.abs(dz)) * moveDelay const ax = (dy>0.5) ? -10 : ((dy<-0.5) ? 10 : 0 ) const ay = (dx>0.5) ? 10 : ((dx<-0.5) ? -10 : 0 ) console.log( this.mecha.position.x, this.mecha.position.y) console.log(dx,dy,ax,ay) const rotationDelay = Math.min(delay/4, 500) this.rotateBot({x: ax, y: ay, z: 0}, rotationDelay) let chkpt1 = true, chkpt2 = true, chkpt3 = true const startTime = Date.now() this.translateBot(position, delay, null, () => { const t = (Date.now()-startTime)/delay if(chkpt1 && (t >= 0.2)){ //Time to stop accelerating this.rotateBot({x: 0, y: 0, z: 0}, rotationDelay) chkpt1 = false } if(chkpt2 && (t >= 0.7)){ //Time to brake this.rotateBot({x: -ax, y: -ay, z: 0}, rotationDelay) chkpt2 = false } if(chkpt3 && (t >= 0.9)){ //Time to restore this.rotateBot({x: 0, y: 0, z: 0}, rotationDelay) chkpt3 = false } }) } } app.registerClass('HelperBot', HelperBot)