319 lines
8.8 KiB
JavaScript
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, 'Myriad Pro'" font-size="150%">${p.hint || ''}</text>
|
|
<text x="90%" text-anchor="middle" y="8%" font-family="MyriadPro-Regular, 'Myriad Pro'" 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 }; |