235 lines
8.3 KiB
JavaScript
235 lines
8.3 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'
|
|
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<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.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
|
|
|