diff --git a/public/static/meshes/award.glb b/public/static/meshes/award.glb new file mode 100644 index 0000000..ef3183a Binary files /dev/null and b/public/static/meshes/award.glb differ diff --git a/src/components/GamePlaying/GamePlaying.vue b/src/components/GamePlaying/GamePlaying.vue index 7ebb5f0..c2a624e 100644 --- a/src/components/GamePlaying/GamePlaying.vue +++ b/src/components/GamePlaying/GamePlaying.vue @@ -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: ['...има огромен жълт клюн', '...има малък розов клюн', '...не се среща в България'], diff --git a/src/components/InteractiveObjects/MazeQuizGame/MazeObject.js b/src/components/InteractiveObjects/MazeQuizGame/MazeObject.js index 4102778..dfaba9e 100644 --- a/src/components/InteractiveObjects/MazeQuizGame/MazeObject.js +++ b/src/components/InteractiveObjects/MazeQuizGame/MazeObject.js @@ -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 = {}){ diff --git a/src/components/InteractiveObjects/MazeQuizGame/MazeQuizGame.js b/src/components/InteractiveObjects/MazeQuizGame/MazeQuizGame.js index d03e54a..1d1eb04 100644 --- a/src/components/InteractiveObjects/MazeQuizGame/MazeQuizGame.js +++ b/src/components/InteractiveObjects/MazeQuizGame/MazeQuizGame.js @@ -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); }) } diff --git a/src/lib/Dashboard.js b/src/lib/Dashboard.js index 450352f..7c1d259 100644 --- a/src/lib/Dashboard.js +++ b/src/lib/Dashboard.js @@ -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=>` + + + + + + + +${p.hint || ''} +Points +`; + + 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(); } } diff --git a/src/lib/GameEngine.js b/src/lib/GameEngine.js index 3d5226e..8930e88 100644 --- a/src/lib/GameEngine.js +++ b/src/lib/GameEngine.js @@ -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); diff --git a/src/lib/Utils.js b/src/lib/Utils.js index e6e9752..f063aa4 100644 --- a/src/lib/Utils.js +++ b/src/lib/Utils.js @@ -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)