Files
pronature-platform/src/lib/Dashboard.js
T
2025-12-08 10:12:05 +02:00

319 lines
8.8 KiB
JavaScript

import {
PlaneGeometry, CylinderGeometry, CanvasTexture, Group,
Mesh, MeshStandardMaterial, MeshBasicMaterial, DoubleSide
} from "three";
import { Text } from "troika-three-text";
class DashBoard {
#points = 0;
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, levelProgress;
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
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);
URL.revokeObjectURL(url);
texture.needsUpdate = true;
updating = false;
})
const dash = new Group(), hud = new Group(), hudTarget = new Group();
hud.visible = false;
let hudAnimation, hudPlane, textPlane;
this.group = dash;
dash.add(hud);
hud.add(hudTarget)
hudTarget.position.set(0,0,0.52);
dash.visible = false;
const k = 1.55;
const dashWidth = engine.aspect * k, dashHeight = k;
const dashGeometry = new PlaneGeometry(dashWidth, dashHeight);
const dashMesh = new Mesh(dashGeometry, new MeshBasicMaterial({
transparent: true,
map: texture
}))
dash.add(dashMesh);
engine.scene.add(dash);
const loadingPlane = new Mesh(
new PlaneGeometry(dashWidth, dashHeight),
new MeshStandardMaterial({
color:0xffffff,
opacity:0, transparent:true,
roughness:0, metalness:0.1
})
);
const loadingProgress = new ProgressBar(engine);
loadingProgress.object.scale.set(dashWidth*0.8, 0.05*dashHeight, 0.05*dashHeight)
loadingProgress.object.position.set(-dashWidth/2 + dashWidth*0.1, 0, 0)
loadingPlane.add(loadingProgress.object);
dash.add(loadingPlane);
(async()=>{
let map = await engine.loadTexture('/static/textures/hud.png', '');
hudPlane = new Mesh(
new PlaneGeometry(dashWidth * 0.96, dashHeight * 0.9),
new MeshBasicMaterial({
map,
opacity: 0.37,
transparent:true
})
);
hudPlane.position.z = -0.07 * dashHeight;
hudPlane.position.y = -0.011 * dashHeight
hud.add(hudPlane)
textPlane = new Mesh(
new PlaneGeometry(dashWidth * 0.86, 0.15 * dashHeight),
new MeshBasicMaterial({
map,
opacity: 0.52,
transparent:true
})
);
textPlane.position.z = -0.002;
textPlane.position.y = -0.33 * dashHeight
textPlane.visible = false;
dash.add(textPlane)
// fix #44
textPlane.material.depthTest = false;
//hudPlane.material.depthTest = false;
})()
const text = new Text()
Object.assign(text, {
text:``,
fontSize: 0.033 * dashHeight, lineHeight: 1 * dashHeight, maxWidth: dashWidth * 0.8,
textAlign: 'center', font: '/static/fonts/Montserrat-Regular.ttf',
anchorX: 'center', anchorY: 0.03 * dashHeight, depthOffset: 0.1, color: 0x000000,
clipRect: [-dashWidth * 0.4, -0.12 * dashHeight, dashWidth * 0.4, 0]
})
text.sync();
text.position.set(0, -0.27 * dashHeight, 0.001);
text.material.depthTest = false;
dash.add(text);
const pointsText = new Text()
Object.assign(pointsText, {
text:``, fontSize: 0.044, font: '/static/fonts/Montserrat-Bold.ttf',
outlineColor: 0x113377, outlineWidth: '3%', anchorX: 'center',
})
pointsText.position.set(0.86 * dashWidth/2, 0.47 * dashHeight, -0.001);
dash.add(pointsText);
engine.addEventListener('beforeRender', ()=>{
dash.quaternion.copy(engine.camera.quaternion)
dash.position.copy(engine.camera.position)
//dash.translateZ(-1.2 * engine.camera.zoom);
dash.translateZ(-0.75/Math.tan(engine.camera.fov/2 * Math.PI/180) * engine.camera.zoom);
})
this.updateText = function(t, textScrolledCallback){
textPlane.visible = !!t;
engine.motionQueue.clear(text);
text.text = t;
text.anchorY = 0.03 * dashHeight;
text.sync(()=>{
let dMax = text.textRenderInfo.blockBounds[3] - text.textRenderInfo.blockBounds[1];
let dh = dMax + text.clipRect[1];
if (dh > 0){
let updateFn = ()=>{
//text.sync();
if (text.textRenderInfo.blockBounds[1]> text.clipRect[1] * 1.33 ){
textScrolledCallback?.(true);
textScrolledCallback = null;
}
}
engine.motionQueue.add({
o: text,
a: { anchorY: - dMax },
t: dMax*99 / dashHeight,
u: updateFn
})
}else{
textScrolledCallback?.(false)
}
})
}
levelProgress = new ProgressBar(engine)
dash.add(levelProgress.object);
levelProgress.object.position.set(-dashWidth/2 + dashWidth/30, 0.45 * dashHeight, -0.001)
levelProgress.object.scale.set(dashWidth/3, 0.02 * dashHeight, 0.02 * dashHeight)
this.levelProgress = levelProgress;
this.addPoints = function(p){
this.#points += p;
engine.motionQueue.add({
o: pointsText,
a: {rotation:{y: Math.PI}, scale:{x:1.5, y:1.5, z:1.5}},
t: 0.25,
f:()=>{
pointsText.text = this.#points;
pointsText.sync();
engine.motionQueue.add({
o: pointsText,
a: {rotation:{y: Math.PI*2}, scale:{x:1, y:1, z:1}},
t: 0.5,
f: ()=>{pointsText.rotation.y = 0;}
})
}
})
}
this.enable = ()=>{
dash.visible = true;
}
this.disable = ()=>{
dash.visible = false;
}
this.reset = ()=>{
this.levelProgress.update(0);
this.updateText('');
}
//dashPlacement, rotate = false, plane = false
this.attach = (object, opts = {})=>{
hud.visible = true;
hudPlane.visible = !!opts.plane;
if (opts.plane){
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;
let result = new Promise((resolve, reject)=>{
engine.motionQueue.add({
o: object,
a: opts.placement || {
quaternion: { x:0, y:0, z:0, w:0 },
position: { x:0, y:0, z:0 },
scale: { x: 1, y: 1, z: 1 }
},
t: opts.skipTransition ? 0 : 1,
f: resolve
})
})
if (opts.rotate){
engine.motionQueue.add(hudAnimation = {
o: hudTarget,
a: {
rotation: { y: 2*Math.PI }
},
t: 4,
r: true,
d: 1
})
}
return result;
}
this.detach = (object, opts = {})=>{
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: opts.skipTransition ? 0 : 1
});
delete object._hud;
occupied = false;
hudAnimation = null;
}
this.loading = function(progress, tt){
loadingPlane.visible = progress > 0 && progress < 1;
loadingProgress.update(progress, tt)
}
this.loading(0,0);
}
get active(){
return this.group.visible;
}
set active(v){
this.group.visible = v;
}
get points(){
return this.#points;
}
}
class ProgressBar{
constructor(engine, params = {}){
this.object = new Group();
const geometry = new CylinderGeometry( 0.5, 0.5, 1, 3, 1, false, 0, Math.PI );
const staticCylinder = new Mesh( geometry, 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.position.x = 0.5;
this.object.add( staticCylinder );
const progressCylinder = new Mesh( geometry, new MeshStandardMaterial({
roughness: 0, metalness:0.1, transparent: true, opacity:0.77, color: 0x11ff00
}) );
progressCylinder.rotation.set(Math.PI/2, 0, Math.PI/2,)
this.object.add( progressCylinder );
this.update = function(value, transitionTime = 0.5){
progressCylinder.visible = !!value;
engine.motionQueue.clear(progressCylinder);
engine.motionQueue.add({
o: progressCylinder,
a: {
scale: {y: value},
position: {x: value / 2}
},
t: transitionTime
})
}
this.update(0)
}
}
export { DashBoard };