Files
P42_UI/app/libs/HelperBot/HelperBot.module.js
T
2025-12-11 20:57:20 +00:00

198 lines
6.7 KiB
JavaScript

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)