BlueMap/BlueMapCommon/webapp/src/js/PopupMarker.js

244 lines
9.4 KiB
JavaScript

/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import {Marker} from "./markers/Marker";
import {CSS2DObject} from "./util/CSS2DRenderer";
import {animate, htmlToElement} from "./util/Utils";
import {BoxGeometry, MeshBasicMaterial, Mesh, Vector2} from "three";
import {i18n} from "../i18n";
export class PopupMarker extends Marker {
constructor(id, appState, events) {
super(id);
this.data.type = "popup";
this.data.label = "Last Map Interaction";
this.data.listed = false;
this.appState = appState;
this.events = events;
this.visible = false;
this.elementObject = new CSS2DObject(htmlToElement(`<div id="bm-marker-${this.data.id}" class="bm-marker-${this.data.type}">Test</div>`));
this.elementObject.position.set(0.5, 1, 0.5);
this.elementObject.disableDepthTest = true;
this.addEventListener( 'removed', () => {
if (this.element.parentNode) this.element.parentNode.removeChild(this.element);
});
let cubeGeo = new BoxGeometry(1.01, 1.01, 1.01).translate(0.5, 0.5, 0.5);
let cubeMaterial = new MeshBasicMaterial( {color: 0xffffff, opacity: 0.5, transparent: true} );
this.cube = new Mesh(cubeGeo, cubeMaterial);
this.cube.onClick = evt => this.onClick(evt);
this.add(this.elementObject);
this.add(this.cube);
this.animation = null;
this.events.addEventListener('bluemapMapInteraction', this.onMapInteraction);
window.addEventListener("mousedown", this.removeHandler);
window.addEventListener("touchstart", this.removeHandler, { passive: true });
window.addEventListener("keydown", this.removeHandler);
window.addEventListener("mousewheel", this.removeHandler);
}
onClick(event) {
return true;
}
onMapInteraction = evt => {
let isHires = true;
let int = evt.detail.hiresHit;
if (evt.detail.lowresHits) {
for (let i = 0; i < evt.detail.lowresHits.length; i++) {
if (!int) {
isHires = false;
int = evt.detail.lowresHits[i];
} else break;
}
}
if (!int) return;
this.position
.copy(int.pointOnLine || int.point)
.add(evt.detail.ray.direction.clone().multiplyScalar(0.05))
.floor();
//this.elementObject.position
//.copy(evt.detail.intersection.pointOnLine || evt.detail.intersection.point)
//.sub(this.position);
if (isHires) {
this.element.innerHTML = `
<div class="group">
<div class="label">${i18n.t("blockTooltip.block")}:</div>
<div class="content">
<div class="entry"><span class="label">x: </span><span class="value">${this.position.x}</span></div>
<div class="entry"><span class="label">y: </span><span class="value">${this.position.y}</span></div>
<div class="entry"><span class="label">z: </span><span class="value">${this.position.z}</span></div>
</div>
</div>
`;
} else {
this.element.innerHTML = `
<div class="group">
<div class="label">${i18n.t("blockTooltip.position")}:</div>
<div class="content">
<div class="entry"><span class="label">x: </span><span class="value">${this.position.x}</span></div>
<div class="entry"><span class="label">z: </span><span class="value">${this.position.z}</span></div>
</div>
</div>
`;
}
if (this.appState.debug) {
let chunkCoords = this.position.clone().divideScalar(16).floor();
let regionCoords = new Vector2(this.position.x, this.position.z).divideScalar(512).floor();
let regionFile = `r.${regionCoords.x}.${regionCoords.y}.mca`;
this.element.innerHTML += `
<hr>
<div class="group">
<div class="label">${i18n.t("blockTooltip.chunk")}:</div>
<div class="content">
<div class="entry"><span class="label">x: </span><span class="value">${chunkCoords.x}</span></div>
<div class="entry"><span class="label">y: </span><span class="value">${chunkCoords.y}</span></div>
<div class="entry"><span class="label">z: </span><span class="value">${chunkCoords.z}</span></div>
</div>
</div>
<hr>
<div class="group">
<div class="label">${i18n.t("blockTooltip.region.region")}:</div>
<div class="content">
<div class="entry"><span class="label">x: </span><span class="value">${regionCoords.x}</span></div>
<div class="entry"><span class="label">z: </span><span class="value">${regionCoords.y}</span></div>
</div>
<div class="content">
<div class="entry"><span class="label">${i18n.t("blockTooltip.region.file")}: </span><span class="value">${regionFile}</span></div>
</div>
</div>
`;
}
if (this.appState.debug) {
let faceIndex = int.faceIndex;
let attributes = int.object.geometry.attributes;
if (attributes.sunlight && attributes.blocklight) {
let sunlight = attributes.sunlight.array[faceIndex * 3];
let blocklight = attributes.blocklight.array[faceIndex * 3];
this.element.innerHTML += `
<hr>
<div class="group">
<div class="label">${i18n.t("blockTooltip.light.light")}:</div>
<div class="content">
<div class="entry"><span class="label">${i18n.t("blockTooltip.light.sun")}: </span><span class="value">${sunlight}</span></div>
<div class="entry"><span class="label">${i18n.t("blockTooltip.light.block")}: </span><span class="value">${blocklight}</span></div>
</div>
</div>
`;
}
}
if (this.appState.debug) {
let info = "";
if (isHires) {
let hrPath = evt.detail.hiresHit?.object?.userData?.tileUrl;
info += `<div>${hrPath}</div>`;
}
if (evt.detail.lowresHits) {
for (let i = 0; i < evt.detail.lowresHits.length; i++) {
let lrPath = evt.detail.lowresHits[i]?.object?.userData?.tileUrl;
if (lrPath) info += `<div>${lrPath}</div>`;
}
}
this.element.innerHTML += `
<hr>
<div class="files">
${info}
</div>
`;
}
if (this.appState.debug){
console.debug("Clicked Position Data:", evt.detail);
}
this.open();
};
open() {
if (this.animation) this.animation.cancel();
this.visible = true;
this.cube.visible = true;
let targetOpacity = 1;
this.element.style.opacity = "0";
this.animation = animate(progress => {
this.element.style.opacity = (progress * targetOpacity).toString();
}, 300);
}
removeHandler = evt => {
if (evt.composedPath().includes(this.element)) return;
this.close();
}
close() {
if (this.animation) this.animation.cancel();
this.cube.visible = false;
let startOpacity = parseFloat(this.element.style.opacity);
this.animation = animate(progress => {
this.element.style.opacity = (startOpacity - progress * startOpacity).toString();
}, 300, finished => {
if (finished) this.visible = false;
});
}
/**
* @returns {Element}
*/
get element() {
return this.elementObject.element.getElementsByTagName("div")[0];
}
dispose() {
super.dispose();
if (this.element.parentNode) this.element.parentNode.removeChild(this.element);
}
}