import * as THREE from 'three' import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' import * as TWEEN from 'three/examples/jsm/libs/tween.module.js' import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; export class kfArena{ constructor(canvasEl, agentSprites){ Object.assign(this, app.helpers.helpers3D) this.agentSprites = app.Assets.Store.json.agentSprites this.canvasEl = canvasEl this.agentSprites = agentSprites this.renderer = null this.mode = '3D' this.sceneSize = app.Assets.Store.json.arenaConfig.arenaSize this.initScene() this.raycaster = new THREE.Raycaster() this.agents = {} this.onclickAgent = null } initScene(){ // Scene this.scene = new THREE.Scene() // Camera this.camera = new THREE.PerspectiveCamera(75, this.canvasEl.clientWidth / this.canvasEl.clientHeight, 0.1, 1000) this.camera.position.set(3, 3, 5) this.camera.lookAt(0, 0, 0) this.camera.layers.enable(1) this.camera.layers.enable(2) // 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.grid = new THREE.GridHelper(this.sceneSize.x, this.sceneSize.x, 0x8888AA, 0x8888AA) this.grid.layers.set(1) this.scene.add(this.grid) // Base plane const planeGeo = new THREE.PlaneGeometry(100, 100) const planeMat = new THREE.MeshBasicMaterial({ color: 0x000055, opacity: 0.3, transparent: true, // needed for opacity < 1 to take effect side: THREE.DoubleSide }) this.basePlane = new THREE.Mesh(planeGeo, planeMat) this.basePlane.rotation.x = -Math.PI / 2 // lay it flat (like the grid) this.basePlane.position.y=-0.01 // to avoid artefacts on objets bases this.scene.add(this.basePlane) this.axes = new THREE.AxesHelper(this.sceneSize.x/2) this.axes.layers.set(2) this.scene.add(this.axes) this.renderer = new THREE.WebGLRenderer({ antialias: true, canvas: this.canvasEl, stencil: true }) this.canvasEl.addEventListener('click', this.onSceneClick.bind(this)) } startRendering(){ this.addControls() this.render() } render() { TWEEN.update() this.animateHighlight3DObj() const resized = this.resizeRendererToDisplaySize() 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 } addControls(){ this.controls = new OrbitControls(this.camera, this.canvasEl) if(this.mode=='2D'){ this.controls.maxPolarAngle = 0 // Math.PI / 2 this.controls.minPolarAngle = 0 // Math.PI / 2 } else if(this.mode=='3D'){ } this.controls.mouseButtons = { LEFT: THREE.MOUSE.ROTATE, // keep orbit on left MIDDLE: THREE.MOUSE.PAN, // pan with middle-click RIGHT: THREE.MOUSE.DOLLY // zoom with right-click } } onSceneClick(event){ // ray from the mouse, through the camera lens, to find the first (most foreground) object if(typeof(this.onclickAgent) != 'function') return const normalizedPointer = new THREE.Vector2() const rect = this.canvasEl.getBoundingClientRect() normalizedPointer.x = ((event.clientX - rect.left) / rect.width) * 2 - 1 normalizedPointer.y = -((event.clientY - rect.top) / rect.height) * 2 + 1 this.raycaster.setFromCamera(normalizedPointer, this.camera) const intersects = this.raycaster.intersectObjects(this.scene.children, true) if (intersects.length > 0) { let hit = null for(let i=0; i item.atp_id==typeId) if(!agentSprite) { console.warn(`No sprite for type: ${typeId}`) return } const agentObj = this.agentFromJSON(aid, agentSprite.asp_3d) agentObj.position.set(values.position.x, values.position.z, values.position.y ) this.scene.add(agentObj) //Speed vector const objAboveHead = this.getObjectTopCenter(this.scene.getObjectByName(aid)) this.createArrow(objAboveHead, { x: values.speed.x, y: values.speed.z, z:values.speed.y }, { name: `_speed_${aid}`, color: 0xff0055, scale: 0.5, }) this.agents[aid] = { type: typeId, props: properties, values: values, } } removeAgent(aid){ const obj3d = this.scene.getObjectByName(aid) this.clearHighlight3DObj(obj3d, this.scene) this.scene.remove(obj3d) const arrow3d = this.scene.getObjectByName(`_speed_${aid}`) if(arrow3d) { this.clearHighlight3DObj(arrow3d, this.scene) this.scene.remove(arrow3d) } if(aid in this.agents) delete(this.agents[aid]) } moveAgent(aid, position){ const obj3d = this.scene.getObjectByName(aid) this.clearHighlight3DObj(obj3d, this.scene) const dx = Number(position.x) - obj3d.position.x const dy = Number(position.z) - obj3d.position.y const dz = Number(position.y) - obj3d.position.z if(this.moveAgentTween) this.moveAgentTween.stop() this.moveAgentTween = new TWEEN.Tween(obj3d.position) .to({ x: Number(position.x), y: Number(position.z), z: Number(position.y), }, 500) .easing(TWEEN.Easing.Quadratic.InOut) .onComplete(() => { this.highlight3DObj(obj3d, this.scene) } ) .start() const arrow3d = this.scene.getObjectByName(`_speed_${aid}`) if(arrow3d){ if(this.moveArrowTween) this.moveArrowTween.stop() this.moveArrowTween = new TWEEN.Tween(arrow3d.position) .to({ x: arrow3d.position.x + dx, y: arrow3d.position.y + dy, z: arrow3d.position.z + dz, }, 500) .easing(TWEEN.Easing.Quadratic.InOut) .start() } } changeAgentSpeed(aid, speed){ const objName = `_speed_${aid}` const obj3d = this.scene.getObjectByName(objName) if(obj3d) this.scene.remove(obj3d) const objAboveHead = this.getObjectTopCenter(this.scene.getObjectByName(aid)) this.createArrow(objAboveHead, { x: speed.x, y: speed.z, z: speed.y }, { name: objName, color: 0xff0055, scale: 0.5, }) } reloadAgents(agents){ for(const aid in this.agents){ this.removeAgent(aid) } for(const agent of agents){ this.addAgent(agent.ekfs_atp_id, agent.aid, agent.props, {...agent.gps_values, ...agent.store_values}) } } // getAllAgents(){ // const agents = [] // scene.traverse(o => o.name && names.push(o.name)) } // Make this module available to common JS if(!app.LoadedModules) app.LoadedModules = {} app.LoadedModules.kfArena = kfArena