Files
P42_UI/app/views/editors/modules/kfArena.module.js
T
2025-11-24 19:23:54 +00:00

226 lines
8.1 KiB
JavaScript

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'
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 light = new THREE.DirectionalLight(0xffffff, 1)
light.position.set(5, 5, 5)
this.scene.add(light)
this.scene.add(new THREE.AmbientLight(0xffffff, .4))
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.renderer.physicallyCorrectLights = true
// this.renderer.outputColorSpace = THREE.SRGBColorSpace
// this.renderer.toneMapping = THREE.ACESFilmicToneMapping
// this.renderer.toneMappingExposure = 1
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<intersects.length; i++){
hit = this.getNamedParent(intersects[i].object)
if((!hit) || (!hit.name)) continue
if(Object.keys(this.agents).includes(hit.name)) break
}
if(hit && Object.keys(this.agents).includes(hit.name)) this.onclickAgent(hit)
}
}
addAgent(typeId, aid, properties, values){
const agentSprite = this.agentSprites.find(item => 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.scene.remove(obj3d)
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