From 8276a771720ff1ae53ec05495145c550349c5dd0 Mon Sep 17 00:00:00 2001 From: STEINNI Date: Wed, 22 Oct 2025 19:45:32 +0000 Subject: [PATCH] agentIDs and raycasting like mad! --- app/assets/styles/app.css | 2 - app/helpers/activeAttributes.js | 3 +- app/helpers/helpers3D.module.js | 6 ++ app/views/editors/KeyframeView.html | 11 +++- app/views/editors/KeyframeView.js | 32 +++++++-- app/views/editors/modules/kfArena.module.js | 73 +++++++++++++++------ 6 files changed, 97 insertions(+), 30 deletions(-) diff --git a/app/assets/styles/app.css b/app/assets/styles/app.css index 1ed8d11..18537f2 100755 --- a/app/assets/styles/app.css +++ b/app/assets/styles/app.css @@ -103,8 +103,6 @@ menu[eicmenu] [menuitem] > a > button, menu[eicmenu] [menuitem] > .nolink button overflow: hidden; z-index: 2; grid-template-rows: min-content 1fr; - max-height: 90vh; - max-width: 90vw; display: flex; flex-direction: column; border-radius: .3rem; diff --git a/app/helpers/activeAttributes.js b/app/helpers/activeAttributes.js index ad5cbc3..4c35b7f 100644 --- a/app/helpers/activeAttributes.js +++ b/app/helpers/activeAttributes.js @@ -39,7 +39,7 @@ app.helpers.activeAttributes = { * setupRefs is re-entrant: it can be called again after refreshing part of the view * @param {eicui-components []} components : the view's components (usually result of ui.eicfy(this.el) ) */ - setupRefs(components){ + setupRefs(components = []){ if(!this.components) this.components = {} for(let component of components.filter(component => component.el.hasAttribute('data-ref'))) { this.components[component.el.dataset.ref] = component @@ -61,6 +61,7 @@ app.helpers.activeAttributes = { } }, + /** * output (singular) : this.output('mydiv', '

Some markup

') places markup in a data-output node * @param {*} name diff --git a/app/helpers/helpers3D.module.js b/app/helpers/helpers3D.module.js index 1d2b7a6..5414ace 100644 --- a/app/helpers/helpers3D.module.js +++ b/app/helpers/helpers3D.module.js @@ -110,4 +110,10 @@ app.helpers.helpers3D = { return pivot }, + getNamedParent(obj) { + while (obj && !obj.name) { + obj = obj.parent + } + return obj + }, } \ No newline at end of file diff --git a/app/views/editors/KeyframeView.html b/app/views/editors/KeyframeView.html index 74a193a..0eab4c1 100644 --- a/app/views/editors/KeyframeView.html +++ b/app/views/editors/KeyframeView.html @@ -57,7 +57,16 @@ .kf-editor button[data-trigger="onResetKF"] { background-color: #A00; } .kf-editor section[data-output="agentProperties"] label{ font-size: 0.9em; } .kf-editor section[data-output="agentProperties"] div.cols-2 { grid-template-columns: 4fr 3fr; } - + .kf-editor div[data-output="agentId"] { + border: 1px solid #574; + border-radius: 5px; + background-color: #231; + box-shadow: 0px 0px 7px #0B69; + height: 2em; + padding: .2em 5px .2em 5px; + margin-top: 1em; + font-size: .8em; + }
diff --git a/app/views/editors/KeyframeView.js b/app/views/editors/KeyframeView.js index 9ee24cb..9bb9bfe 100644 --- a/app/views/editors/KeyframeView.js +++ b/app/views/editors/KeyframeView.js @@ -42,6 +42,7 @@ class KeyframeView extends WindozDomContent { this.agentPreview.animation = true this.kfArena = new app.LoadedModules.kfArena(this.outputs.kfArenaCanvas, this.agentSprites) + this.kfArena.onclickAgent = this.onclickAgent.bind(this) this.kfArena.startRendering() this.outputs.btnAddAgent.disabled = true @@ -53,20 +54,37 @@ class KeyframeView extends WindozDomContent { if(this.outputs.agentsSelector.value) this.agentPreview.setAgent(this.outputs.agentsSelector.value) if(!this.outputs.agentsSelector.value) return const agent = await this.models.agents.getProperties(this.outputs.agentsSelector.value) - this.fillAgentProperties(agent.atp_props) + this.fillAgentProperties('', agent.atp_props) + } + + onclickAgent(aid){ + console.log('Agent clicked:', aid) + this.updateKfButtons() } onAddAgent(event){ + //TODO prevent collisions ! + const aid = crypto.randomUUIDv7() + this.output('agentId', `ID: ${aid}`) this.kfArena.addAgent(this.outputs.agentsSelector.value, aid, { - x: document.querySelector('[name="position.x"]').value, - y: document.querySelector('[name="position.y"]').value, - z: document.querySelector('[name="position.z"]').value, + position: { + x: document.querySelector('[name="position.x"]').value, + y: document.querySelector('[name="position.y"]').value, + z: document.querySelector('[name="position.z"]').value, + } }) + this.updateKfButtons() } - fillAgentProperties(agentProps){ - this.outputs.agentProperties.innerHTML='' + updateKfButtons(){ + if(this.kfArena.agents.length > 0) this.outputs.btnSaveKF.disabled = false + } + + fillAgentProperties(aid, agentProps){ + this.outputs.agentProperties.innerHTML = ` +
ID: ${aid}
+ ` this.outputs.agentProperties.append(...this.fieldsFromJSON(agentProps, 'Internal properties')) this.outputs.agentProperties.append(...this.fieldsFromJSON({ "position.x": { @@ -103,8 +121,10 @@ class KeyframeView extends WindozDomContent { }, }, 'Speed vector')) this.outputs.btnAddAgent.disabled = false + this.setupRefs() } + } app.registerClass('KeyframeView', KeyframeView) diff --git a/app/views/editors/modules/kfArena.module.js b/app/views/editors/modules/kfArena.module.js index b65e457..da4b32d 100644 --- a/app/views/editors/modules/kfArena.module.js +++ b/app/views/editors/modules/kfArena.module.js @@ -3,29 +3,32 @@ import { OrbitControls } from '/app/thirdparty/Three/OrbitControls.module.js' import * as TWEEN from '/app/thirdparty/Three/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.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) @@ -36,7 +39,7 @@ export class kfArena{ 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({ @@ -49,19 +52,20 @@ export class kfArena{ 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 }) + this.canvasEl.addEventListener('click', this.onSceneClick.bind(this)) } - + startRendering(){ this.addControls() this.render() } - + render() { TWEEN.update() if(this.resizeRendererToDisplaySize()) { @@ -71,8 +75,8 @@ export class kfArena{ this.renderer.render(this.scene, this.camera) requestAnimationFrame(this.render.bind(this)) } - - + + resizeRendererToDisplaySize() { const width = this.canvasEl.clientWidth const height = this.canvasEl.clientHeight @@ -82,7 +86,7 @@ export class kfArena{ } return false } - + addControls(){ this.controls = new OrbitControls(this.camera, this.canvasEl) if(this.mode=='2D'){ @@ -97,22 +101,51 @@ export class kfArena{ RIGHT: THREE.MOUSE.DOLLY // zoom with right-click } } + - addAgent(typeId, aid, position){ + 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) { + const hit = this.getNamedParent(intersects[0].object) + if(hit) this.onclickAgent(hit.name) + } + } + + + addAgent(typeId, aid, properties){ const agentSprite = this.agentSprites.find(item => item.atp_id==typeId) if(!agentSprite) return const agentObj = this.agentFromJSON(aid, agentSprite.asp_3d) - - agentObj.position.set(position.x, position.z, position.y ) + + agentObj.position.set(properties.position.x, properties.position.z, properties.position.y ) + //TODO Speed vector this.scene.add(agentObj) + + this.agents.push({ + aid: aid, + props: properties, + }) } - - removeAgent(id){ - //find obj by id - //this.scene.remove(this.currentAgentObj) + + removeAgent(aid){ + const obj3d = scene.getObjectByName(aid) + this.scene.remove(obj3d) + this.agents = this.agents.filter(a => a.aid !== aid) } + + // 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