dashboard and progressbar

This commit is contained in:
2025-10-30 18:54:28 +02:00
parent fb9c5c66e9
commit 6253fc32d7
7 changed files with 123 additions and 71 deletions
+75 -57
View File
@@ -1,70 +1,88 @@
import { MeshBasicMaterial, TextureLoader, LinearFilter,
Mesh,
OrthographicCamera,
PlaneGeometry,
RGBAFormat,
Scene } from 'three';
import { Text } from 'troika-three-text';
import { PlaneGeometry, CylinderGeometry, CanvasTexture, Group, Mesh, MeshStandardMaterial, DoubleSide } from "three";
import Utils from "./Utils";
class DashBoard {
constructor(renderer, width, height) {
constructor(engine) {
let svg = p=>`<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<g>
<rect x="0" y="85%" width="100%" height="15%" opacity="0.73" fill="#98d696"/>
<rect x="80%" y="0" width="20%" height="15%" opacity="0.73" fill="#98d696"/>
<rect x="0" y="0" width="20%" height="15%" opacity="0.73" fill="#98d696" visibility="hidden"/>
<circle r="10%" cx="11%" cy="85%" opacity="0.73" fill="#98d696" visibility="hidden"/>
</g>
<text id="hint" text-anchor="middle" x="50%" y="92%" font-family="MyriadPro-Regular, &apos;Myriad Pro&apos;" font-size="150%">${p.hint || ''}</text>
<text x="90%" text-anchor="middle" y="8%" font-family="MyriadPro-Regular, &apos;Myriad Pro&apos;" font-size="150%">Points</text>
</svg>`;
let img = new Image(), url, progressCylinder;
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
let texture = new CanvasTexture(canvas)
let updating = false;
let params = {}
img.addEventListener('load', function () {
ctx.drawImage(img, 0, 0, engine.w, engine.h);
URL.revokeObjectURL(url);
texture.needsUpdate = true;
updating = false;
})
let _camera = new OrthographicCamera(width / -2, width / 2, height / 2, height / -2, 0, 1);
const dash = new Group();
let _scene = new Scene();
const dashGeometry = new PlaneGeometry(engine.aspect, 1);
const dashMesh = new Mesh(dashGeometry, new MeshStandardMaterial({
roughness: 0, metalness:0.1, transparent: true,
map: texture
}))
//let _params = { minFilter: LinearFilter, magFilter: LinearFilter, format: RGBAFormat, stencilBuffer: true };
this.points = 0;
dash.add(dashMesh);
engine.scene.add(dash)
let _texture = new TextureLoader().load('./assets/maze/x.png');
engine.addEventListener('beforeRender', ()=>{
dash.quaternion.copy(engine.camera.quaternion)
dash.position.copy(engine.camera.position)
dash.translateZ(-1.2 * engine.camera.zoom);
})
let _material = new MeshBasicMaterial({
map: _texture,
alphaTest: .5
});
this.update = function(p = {}){
Object.assign(params, p);
if (updating) return false;
updating = true;
canvas.width = engine.w;
canvas.height = engine.h;
url = URL.createObjectURL(new Blob([svg(params)],{ type:"image/svg+xml;charset=utf-8" }));
img.src = url;
}
// _mesh = new Mesh( new PlaneGeometry( width * 0.015, width * 0.015 ), _material );
let _text = new Text();
_text.font = '/static/fonts/Montserrat-Regular.ttf';
_text.text = 'Точки: 0';
_text.anchorX = 'right';
_text.anchorY = 'top';
_text.fontSize = width * 0.015;
_text.position.set(width * .48, height * .47, 0);
_text.color = 0xffffff;
_text.outlineColor = 0x222222;
_text.outlineWidth = '5%';
_text.outlineBlur = '5%';
//_scene.add( _mesh );
_scene.add(_text);
_text.sync();
this.createProgressBar = function(){
const padLeft = engine.aspect/30;
const progressGeometry = new CylinderGeometry( 0.5, 0.5, 1, 3, 1, false, 0, Math.PI );
const staticCylinder = new Mesh( progressGeometry, new MeshStandardMaterial({
roughness: 0, metalness:0.1, transparent: true, opacity:0.52, color: 0x55ff00, side: DoubleSide
}) );
staticCylinder.rotation.set(-Math.PI/2, 0, Math.PI/2,)
staticCylinder.scale.set(0.02, engine.aspect/3, 0.02)
staticCylinder.position.set(padLeft - engine.aspect/3, 0.45, 0);
dash.add( staticCylinder );
this.render = function (scene, camera) {
renderer.render(_scene, _camera);
};
progressCylinder = new Mesh( progressGeometry, new MeshStandardMaterial({
roughness: 0, metalness:0.1, transparent: true, opacity:0.77, color: 0x11ff00
}) );
progressCylinder.rotation.set(Math.PI/2, 0, Math.PI/2,)
progressCylinder.scale.set(0.017, 0, 0.017)
progressCylinder.position.set(0, 0.45, 0);
dash.add( progressCylinder );
}
this.setSize = function (width, height) {
_camera.left = width / -2;
_camera.right = width / 2;
_camera.top = height / 2;
_camera.bottom = height / -2;
_camera.updateProjectionMatrix();
_text.position.set(width * .48, height * .47, 0);
};
this.addPoints = function (points) {
this.onpoints && this.onpoints(this.points + points, this.points);
this.points += points;
_text.text = 'точки: ' + this.points;
};
this.dispose = function () {
if (_mesh) _mesh.geometry.dispose();
if (_material) _material.dispose();
};
this.updateProgress = function(value){
const padLeft = engine.aspect/30;
progressCylinder.scale.y = engine.aspect/3 * value;
progressCylinder.position.x = padLeft - engine.aspect/2 + progressCylinder.scale.y/2
}
this.createProgressBar();
this.update();
}
}
+9 -9
View File
@@ -1,11 +1,8 @@
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/Addons.js';
import { DRACOLoader } from 'three/examples/jsm/Addons.js';
import { OrbitControls } from 'three/examples/jsm/Addons.js';
import { GLTFLoader, DRACOLoader, OrbitControls } from 'three/examples/jsm/Addons.js';
//import { Controller as OrbitControls } from './3rd-party/phy/3TH/Controller.js';
import { ViewportGizmo } from "three-viewport-gizmo";
import Stats from 'three/examples/jsm/libs/stats.module';
//import { AnaglyphEffect } from './three/AnaglyphEffect';
import { AnaglyphEffect } from 'three/addons/effects/AnaglyphEffect.js';
import { StereoEffect } from 'three/addons/effects/StereoEffect.js';
import { MapControls } from 'three/addons/controls/MapControls.js';
@@ -25,7 +22,7 @@ class GameEngine extends THREE.EventDispatcher{
this.opts = opts;
const gameEngine = this;
this.perspectiveCamera = new THREE.PerspectiveCamera(45, this.aspect, 0.01, 250);
this.perspectiveCamera = new THREE.PerspectiveCamera(45, this.aspect, 0.01, 200);
this.raycaster = new THREE.Raycaster();
this.perspectiveCamera.position.set(0, 0, 10);
@@ -99,7 +96,7 @@ class GameEngine extends THREE.EventDispatcher{
this.stereo = new StereoEffect(renderer);
this.stereo.setSize(this.w, this.h);
const dashboard = new DashBoard(renderer, this.w, this.h);
const dashboard = new DashBoard(this);
this.dashboard = dashboard;
this.activeObjects = new THREE.Group();
@@ -132,13 +129,16 @@ class GameEngine extends THREE.EventDispatcher{
gameEngine.hero?.update();
gameEngine.mixers.forEach(m => m.update(delta));
gameEngine.handleXrAction(gameEngine, delta)
gameEngine.dispatchEvent({type: 'beforeRender'})
gameEngine.render(scene, gameEngine.camera);
if (!renderer.xr.isPresenting) {
gameEngine.gizmo?.render();
}
renderer.autoClear = false;
dashboard.render();
renderer.autoClear = true;
// renderer.autoClear = false;
// dashboard.render();
// renderer.autoClear = true;
}
renderer.setAnimationLoop(animate);
+19
View File
@@ -60,6 +60,25 @@ export default {
.sort((a, b) => a.sort - b.sort).map(({ value }) => value)
},
drawOnCanvas(svg, width, height){
return new Promise((resolve, reject)=>{
let url = URL.createObjectURL(new Blob([svg],{ type:"image/svg+xml;charset=utf-8" }));
let img = new Image();
let canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
let ctx = canvas.getContext('2d');
img.addEventListener('load', function () {
ctx.drawImage(this, 0, 0, canvas.width, canvas.height);
URL.revokeObjectURL(url);
resolve(canvas);
}, { once: true })
img.src = url;
})
},
async wait(ms){
await new Promise((resolve, reject)=>{
setTimeout(resolve, ms)