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
Binary file not shown.
@@ -245,6 +245,11 @@ export default {
a: ['Миграционният път на птиците, минаващ покрай Бургаските езера', 'Рядък вид птица', 'Местност в гр. Бургас'],
h: 'Грешен отговор. Via Pontica наричаме миграционния път на птиците'
},
{
q: 'What stands for "Via Pontica"?',
a: ['The migration route of birds passing by the Burgas lakes', 'A rare species of bird', 'Location in Burgas'],
h: 'Wrong answer. Via Pontica is the name given to the migratory route of birds.'
},
{
q: 'Къдроглавият пеликан...',
a: ['...има огромен жълт клюн', '...има малък розов клюн', '...не се среща в България'],
@@ -1,6 +1,6 @@
import { Group, Vector3, Matrix4, Mesh, Quaternion, PlaneGeometry, MeshStandardMaterial, DoubleSide } from 'three';
import { Group, Vector3, Matrix4, Mesh, Quaternion, PlaneGeometry, MeshStandardMaterial, DoubleSide, CanvasTexture, SRGBColorSpace } from 'three';
import { InteractiveObject } from '../InteractiveObject';
import { ActiveEvents } from '@dimforge/rapier3d';
import Utils from '@/lib/Utils';
class MazeObject {
constructor(engine, def, params = {}){
@@ -22,13 +22,17 @@ class MazeQuizGame {
this.mazeObject = new MazeObject(engine, def)
engine.addEventListener('collision', async e=>{
let ud = engine.phy.world.getCollider(e.handle2)?.parent()?.userData;
if (ud?.finish && engine.hero?.animationsMap?.win){
if (ud?.finish){
if (e.started){
engine.dashboard.updateProgress(1)
engine.hero.animationsMap._idle = engine.hero.animationsMap.idle
engine.hero.animationsMap.idle = engine.hero.animationsMap.win
engine.hero.characterControls.cameraDelta = Math.PI;
if ( engine.hero.animationsMap?.win){
engine.hero.animationsMap.idle = engine.hero.animationsMap.win
}
await Utils.wait(1000);
engine.hero.characterControls.cameraDelta = Math.PI;
engine.hero.characterControls.direction += Math.PI;
//engine.hero.model.rotation.y += Math.PI;
await Utils.wait(10000);
this.onfinish?.()
}else{
@@ -36,6 +40,12 @@ class MazeQuizGame {
engine.hero.characterControls.cameraDelta = 0
}
}
if (ud.qid !== undefined && e.started){
engine.dashboard.update({
hint: ud.question.q
})
engine.dashboard.updateProgress(ud.qid / questions.length)
}
//console.log(e, ud, engine.hero?.animationsMap);
})
}
+73 -55
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 _camera = new OrthographicCamera(width / -2, width / 2, height / 2, height / -2, 0, 1);
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 = {}
let _scene = new Scene();
img.addEventListener('load', function () {
ctx.drawImage(img, 0, 0, engine.w, engine.h);
URL.revokeObjectURL(url);
texture.needsUpdate = true;
updating = false;
})
//let _params = { minFilter: LinearFilter, magFilter: LinearFilter, format: RGBAFormat, stencilBuffer: true };
this.points = 0;
const dash = new Group();
let _texture = new TextureLoader().load('./assets/maze/x.png');
const dashGeometry = new PlaneGeometry(engine.aspect, 1);
const dashMesh = new Mesh(dashGeometry, new MeshStandardMaterial({
roughness: 0, metalness:0.1, transparent: true,
map: texture
}))
let _material = new MeshBasicMaterial({
map: _texture,
alphaTest: .5
});
dash.add(dashMesh);
engine.scene.add(dash)
// _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();
engine.addEventListener('beforeRender', ()=>{
dash.quaternion.copy(engine.camera.quaternion)
dash.position.copy(engine.camera.position)
dash.translateZ(-1.2 * engine.camera.zoom);
})
this.render = function (scene, camera) {
renderer.render(_scene, _camera);
};
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;
}
this.setSize = function (width, height) {
_camera.left = width / -2;
_camera.right = width / 2;
_camera.top = height / 2;
_camera.bottom = height / -2;
_camera.updateProjectionMatrix();
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 );
_text.position.set(width * .48, height * .47, 0);
};
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.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)