From 48c7ea2e2abe88d84c5f8a4a0e2b48800b671841 Mon Sep 17 00:00:00 2001 From: goynov Date: Fri, 7 Nov 2025 12:48:36 +0200 Subject: [PATCH] hud observation for generic game objects --- .../InteractiveObjects/GenenricObject.js | 29 +++++- .../InteractiveObjects/VideoPlayer.js | 27 +----- src/lib/Dashboard.js | 93 ++++++++++++++++++- src/lib/GameEngine.js | 6 -- src/lib/MeshUtils.js | 27 +++++- src/lib/MotionEngine.js | 10 +- src/mixins/GameEnvironmentMixin.js | 3 +- 7 files changed, 152 insertions(+), 43 deletions(-) diff --git a/src/components/InteractiveObjects/GenenricObject.js b/src/components/InteractiveObjects/GenenricObject.js index 9ed900b..9907463 100644 --- a/src/components/InteractiveObjects/GenenricObject.js +++ b/src/components/InteractiveObjects/GenenricObject.js @@ -1,3 +1,5 @@ +import { getBoundingBox, getBoundingBoxCenterPoint, getBoundingBoxMaxLength } from "@/lib/MeshUtils"; + class GenericObject{ constructor(engine, data){ return new Promise(async(resolve, reject)=>{ @@ -6,8 +8,31 @@ class GenericObject{ if (!data.exclude){ engine.clickable.add(this.object, e=>{ - if (engine.dashboard && data.description){ - engine.dashboard.update({ hint: data.description }) + if (engine.dashboard){ + if (data.description){ + engine.dashboard.update({ hint: data.description }) + } + if (data.hud){ + if (this.object._hud ){ + engine.dashboard.detach(this.object); + }else{ + let bb = getBoundingBox(this.object); + let scale = 0.5/getBoundingBoxMaxLength(bb); + let position = getBoundingBoxCenterPoint(bb, this.object.position).multiplyScalar(scale).negate() + + let placement = { + quaternion: { x:0, y:0, z:0, w:0 }, + position, + scale: { + x: scale*this.object.scale.x, + y: scale*this.object.scale.y, + z: scale*this.object.scale.z + } + } + + engine.dashboard.attach(this.object, placement, true); + } + } } }); } diff --git a/src/components/InteractiveObjects/VideoPlayer.js b/src/components/InteractiveObjects/VideoPlayer.js index 5d23b80..302929e 100644 --- a/src/components/InteractiveObjects/VideoPlayer.js +++ b/src/components/InteractiveObjects/VideoPlayer.js @@ -34,34 +34,11 @@ class VideoPlayer { }) vi.addEventListener('play', ()=>{ if (engine.dashboard?.active){ - data = { - parent: plane.parent, - location: { - position: plane.position.clone(), - quaternion: plane.quaternion.clone(), - scale: plane.scale.clone() - } - } - engine.dashboard.group.attach(plane); - - engine.motionQueue.add({ - o: plane, - a: { - rotation: { x:0, y:0, z:0 }, - position: { x:0, y:0, z:-0.25 }, - scale: { x: 1, y:1, z:1 } - }, - t: 1 - }) + engine.dashboard.attach(plane); } }) vi.addEventListener('pause', ()=>{ - data.parent.attach(plane); - engine.motionQueue.add({ - o: plane, - a: data.location, - t: 1 - }) + engine.dashboard?.detach(plane); }) }) } diff --git a/src/lib/Dashboard.js b/src/lib/Dashboard.js index 1070b09..dc9c1e9 100644 --- a/src/lib/Dashboard.js +++ b/src/lib/Dashboard.js @@ -1,4 +1,7 @@ -import { PlaneGeometry, CylinderGeometry, CanvasTexture, Group, Mesh, MeshStandardMaterial, DoubleSide } from "three"; +import { + PlaneGeometry, CylinderGeometry, CanvasTexture, Group, + Mesh, MeshStandardMaterial, MeshBasicMaterial, DoubleSide +} from "three"; import Utils from "./Utils"; class DashBoard { constructor(engine) { @@ -20,6 +23,7 @@ class DashBoard { let texture = new CanvasTexture(canvas) let updating = false; let params = {} + let occupied = false; img.addEventListener('load', function () { ctx.drawImage(img, 0, 0, engine.w, engine.h); @@ -28,18 +32,36 @@ class DashBoard { updating = false; }) - const dash = new Group(); + const dash = new Group(), hud = new Group(), hudTarget = new Group(); + hud.visible = false; + let hudAnimation, hudPlane; this.group = dash; + dash.add(hud); + hud.add(hudTarget) dash.visible = false; const dashGeometry = new PlaneGeometry(engine.aspect, 1); - const dashMesh = new Mesh(dashGeometry, new MeshStandardMaterial({ - roughness: 0, metalness:0.1, transparent: true, + const dashMesh = new Mesh(dashGeometry, new MeshBasicMaterial({ + transparent: true, map: texture })) dash.add(dashMesh); - engine.scene.add(dash) + engine.scene.add(dash); + + (async()=>{ + hudPlane = new Mesh( + new PlaneGeometry(engine.aspect, 1), + new MeshBasicMaterial({ + map: await engine.loadTexture('/static/textures/hud.png', ''), + opacity: 0.37, + transparent:true + }) + ); + hudPlane.position.z = -0.25; + + hud.add(hudPlane) + })() engine.addEventListener('beforeRender', ()=>{ dash.quaternion.copy(engine.camera.quaternion) @@ -98,6 +120,67 @@ class DashBoard { }); } + this.attach = (object, dashPlacement, rotate)=>{ + hud.visible = true; + hudPlane.scale.set(0, .1, 1); + hudPlane.material.opacity = 0.5; + engine.motionQueue.add([{ + o:hudPlane, a:{material:{opacity:0.73}}, t:.4, d:.8 + },{ + o:hudPlane, a:{scale:{x:1}}, t:.4 + },{ + o:hudPlane, a:{scale:{y:1}}, t:.4, d:.4, + }]) + if (occupied) return false; + object._hud = { + parent: object.parent, + placement: { + position: object.position.clone(), + quaternion: object.quaternion.clone(), + scale: object.scale.clone() + } + } + hudTarget.attach(object); + occupied = true; + + engine.motionQueue.add({ + o: object, + a: dashPlacement || { + quaternion: { x:0, y:0, z:0, w:0 }, + position: { x:0, y:0, z:0 }, + scale: { x: 1, y:1, z:1 } + }, + t: 1 + }) + + if (rotate){ + engine.motionQueue.add(hudAnimation = { + o: hudTarget, + a: { + rotation: { y: 2*Math.PI } + }, + t: 4, + r: true, + d: 1 + }) + } + } + + this.detach = object=>{ + engine.motionQueue.remove(hudAnimation); + object._hud.parent.attach(object); + hud.rotation.y = 0; + hud.visible = false; + engine.motionQueue.add({ + o: object, + a: object._hud.placement, + t: 1 + }); + delete object._hud; + occupied = false; + hudAnimation = null; + } + this.createProgressBar(); this.update(); } diff --git a/src/lib/GameEngine.js b/src/lib/GameEngine.js index 159784b..a909ffc 100644 --- a/src/lib/GameEngine.js +++ b/src/lib/GameEngine.js @@ -485,12 +485,6 @@ class GameEngine extends THREE.EventDispatcher{ this.hero?.characterControls?.idleReset(); } - autoScale(object, mk = 1) { - let bb = new THREE.Box3().setFromObject(object); - let k = Math.max(bb.max.x - bb.min.x, bb.max.y - bb.min.y, bb.max.z - bb.min.z); - object.scale.multiplyScalar(mk / k); - } - setCamera(camera) { //camera.updateProjectionMatrix(); this.camera = camera; diff --git a/src/lib/MeshUtils.js b/src/lib/MeshUtils.js index 02be76a..9f0a0e1 100644 --- a/src/lib/MeshUtils.js +++ b/src/lib/MeshUtils.js @@ -1,4 +1,4 @@ -import { TextureLoader } from "three"; +import { TextureLoader, Box3, Vector3 } from "three"; function assignParams(mesh, params){ ['scale', 'rotation', 'position'].forEach(p=>params[p] && mesh[p].fromArray(params[p])); @@ -26,4 +26,27 @@ function assignMaterial(mesh, params){ } } -export { assignParams, assignMaterial } \ No newline at end of file +function getBoundingBox(object){ + return new Box3().setFromObject(object); +} + +function getBoundingBoxMaxLength(bb){ + return Math.max(bb.max.x - bb.min.x, bb.max.y - bb.min.y, bb.max.z - bb.min.z) +} + +function getBoundingBoxCenterPoint(bb, relativeTo){ + relativeTo = relativeTo || new Vector3(0,0,0) + return new Vector3( + bb.min.x + (bb.max.x - bb.min.x)/2 - relativeTo.x, + bb.min.y + (bb.max.y - bb.min.y)/2 - relativeTo.y, + bb.min.z + (bb.max.z - bb.min.z)/2 - relativeTo.z + ) +} + +function autoScale(object, mk = 1) { + let bb = getBoundingBox(object); + let k = getBoundingBoxMaxLength(bb); + object.scale.multiplyScalar(mk / k); +} + +export { assignParams, assignMaterial, autoScale, getBoundingBox, getBoundingBoxMaxLength, getBoundingBoxCenterPoint } \ No newline at end of file diff --git a/src/lib/MotionEngine.js b/src/lib/MotionEngine.js index 8a7d876..36e15a4 100644 --- a/src/lib/MotionEngine.js +++ b/src/lib/MotionEngine.js @@ -26,6 +26,8 @@ class MotionEngine { return target; } + // a = {o-object, a-action, t-time, f-finish event, d-delay, m-mode, r-repeat, + // rd-repeat the delay, rf-reset on finish} this.add = function (a) { a = Array.isArray(a) ? a : [a]; a.forEach(e => { @@ -35,7 +37,7 @@ class MotionEngine { this.clear = function (object) { for (var i = aq.length - 1; i >= 0; i--) { - if (object && aq[i].o == object || !object && aq[i].ct == aq[i].t) { + if (object && aq[i].o == object || !object && (aq[i].ct == aq[i].t || aq[i].rr)) { aq.splice(i, 1); } } @@ -75,7 +77,11 @@ class MotionEngine { e.ct = e.ct + t; if (e.ct > e.t) { e.ct = e.t; - e.f && e.f(); + e.f?.(); + if (e.rf){ + e.ct = t = 0; + e.rr = true; + } } calcValues(e.o, e.a, e.iv, e.ct / e.t, e.m || 'value'); if (e.ct == e.t && e.r) { diff --git a/src/mixins/GameEnvironmentMixin.js b/src/mixins/GameEnvironmentMixin.js index fb91f99..dd9da36 100644 --- a/src/mixins/GameEnvironmentMixin.js +++ b/src/mixins/GameEnvironmentMixin.js @@ -1,6 +1,7 @@ import { InteractiveObject } from '@/components/InteractiveObjects/InteractiveObject'; import { GameEngine } from '@/lib/GameEngine'; import { Hero } from '@/lib/Hero'; +import { autoScale } from '@/lib/MeshUtils'; let gameEngine = null; export default { @@ -185,7 +186,7 @@ export default { object[p].copy(l[data.id][p]) }) }else if (!data.type || data.type == 'GenericObject'){ - gameEngine.autoScale(object, autoScaleFactor); + autoScale(object, autoScaleFactor); } l[data.id] = l[data.id] || {}; ['position', 'scale', 'rotation', 'visible'].forEach(p=>{