First part on implementing markers

This commit is contained in:
Blue (Lukas Rieger) 2020-04-16 12:33:24 +02:00
parent e30b94efe8
commit 9a09f39b5a
14 changed files with 680 additions and 140 deletions

3
.gitignore vendored
View File

@ -29,3 +29,6 @@ package-lock.json
# exclude generated resource
BlueMapCore/src/main/resources/webroot.zip
BlueMapCore/src/main/resources/resourceExtensions.zip
#exclude-test-data
data/test-render

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="50px" height="50px" viewBox="0 0 50 50" enable-background="new 0 0 50 50" xml:space="preserve">
<g>
<path fill="#FFFFFF" d="M37.468,17.123C37.468,29.596,25,45.346,25,45.346s-12.468-14.93-12.468-28.223
c0-6.885,5.581-12.468,12.468-12.468C31.885,4.655,37.468,10.237,37.468,17.123z"/>
</g>
<g>
<path fill="#333333" d="M26.901,12.559c0.034,1.046-0.732,1.885-1.954,1.885c-1.083,0-1.85-0.838-1.85-1.885
c0-1.082,0.804-1.92,1.918-1.92C26.169,10.639,26.901,11.478,26.901,12.559z M23.464,29.063l0.017-11.757h3.072l-0.018,11.757
H23.464z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 919 B

View File

@ -42,6 +42,8 @@ import {
Vector3,
} from 'three';
import { CSS2DRenderer } from './hud/CSS2DRenderer';
import UI from './ui/UI.js';
import Controls from './Controls.js';
@ -61,6 +63,7 @@ export default class BlueMap {
constructor(element, dataRoot) {
this.element = $('<div class="bluemap-container"></div>').appendTo(element)[0];
this.dataRoot = dataRoot;
this.locationHash = '';
this.hiresViewDistance = 160;
this.lowresViewDistance = 3200;
@ -79,18 +82,17 @@ export default class BlueMap {
};
this.debugInfo = false;
this.ui = new UI(this);
this.loadingNoticeElement = $('<div>loading...</div>').appendTo($(this.element));
window.onerror = this.onLoadError;
this.fileLoader = new FileLoader();
this.blobLoader = new FileLoader();
this.blobLoader.setResponseType('blob');
this.bufferGeometryLoader = new BufferGeometryLoader();
this.ui = new UI(this);
this.loadingNoticeElement = $('<div>loading...</div>').appendTo($(this.element));
window.onerror = this.onLoadError;
this.initStage();
this.locationHash = '';
this.controls = new Controls(this.camera, this.element, this.hiresScene);
this.loadSettings().then(async () => {
@ -100,16 +102,16 @@ export default class BlueMap {
this.loadUserSettings();
this.handleContainerResize();
this.changeMap(this.maps[0]);
this.changeMap(this.maps[0], false);
this.ui.load();
await this.ui.load();
this.start();
}).catch(error => {
this.onLoadError(error.toString());
});
}
changeMap(map) {
changeMap(map, loadTiles = true) {
if (this.debugInfo) console.debug("changing map: ", map);
if (this.map === map) return;
@ -156,13 +158,15 @@ export default class BlueMap {
startPos
);
if (loadTiles) {
this.lowresTileManager.update();
this.hiresTileManager.update();
}
document.dispatchEvent(new Event('bluemap-map-change'));
}
loadLocationHash() {
loadLocationHash(smooth = false) {
let hashVars = window.location.hash.substring(1).split(':');
if (hashVars.length >= 1){
if (this.settings.maps[hashVars[0]] !== undefined && this.map !== hashVars[0]){
@ -184,21 +188,26 @@ export default class BlueMap {
if (!isNaN(dir)) this.controls.targetDirection = dir;
if (!isNaN(dist)) this.controls.targetDistance = dist;
if (!isNaN(angle)) this.controls.targetAngle = angle;
if (!smooth) {
this.controls.direction = this.controls.targetDirection;
this.controls.distance = this.controls.targetDistance;
this.controls.angle = this.controls.targetAngle;
this.controls.targetPosition.y = this.controls.minHeight;
this.controls.position.copy(this.controls.targetPosition);
}
}
if (hashVars.length >= 7){
let height = parseInt(hashVars[6]);
if (!isNaN(height)){
this.controls.minHeight = height;
this.controls.targetPosition.y = height;
if (!smooth) {
this.controls.position.copy(this.controls.targetPosition);
}
}
}
}
start() {
this.loadingNoticeElement.remove();
@ -207,7 +216,7 @@ export default class BlueMap {
$(window).on('hashchange', () => {
if (this.locationHash === window.location.hash) return;
this.loadLocationHash();
this.loadLocationHash(true);
});
this.update();
@ -269,13 +278,16 @@ export default class BlueMap {
this.skyboxCamera.updateProjectionMatrix();
this.renderer.clear();
this.renderer.render(this.skyboxScene, this.skyboxCamera, this.renderer.getRenderTarget(), false);
this.renderer.render(this.skyboxScene, this.skyboxCamera);
this.renderer.clearDepth();
this.renderer.render(this.lowresScene, this.camera, this.renderer.getRenderTarget(), false);
this.renderer.render(this.lowresScene, this.camera);
if (this.camera.position.y < 400) {
this.renderer.clearDepth();
this.renderer.render(this.hiresScene, this.camera, this.renderer.getRenderTarget(), false);
this.renderer.render(this.hiresScene, this.camera);
}
this.renderer.render(this.shapeScene, this.camera);
this.hudRenderer.render(this.hudScene, this.camera);
};
handleContainerResize = () => {
@ -290,6 +302,8 @@ export default class BlueMap {
.css('width', this.element.clientWidth)
.css('height', this.element.clientHeight);
this.hudRenderer.setSize(this.element.clientWidth, this.element.clientHeight);
this.updateFrame = true;
};
@ -324,10 +338,12 @@ export default class BlueMap {
antialias: true,
sortObjects: false,
preserveDrawingBuffer: true,
logarithmicDepthBuffer: true,
logarithmicDepthBuffer: false,
});
this.renderer.autoClear = false;
this.hudRenderer = new CSS2DRenderer();
this.camera = new PerspectiveCamera(75, this.element.scrollWidth / this.element.scrollHeight, 0.1, 10000);
this.camera.updateProjectionMatrix();
@ -340,7 +356,11 @@ export default class BlueMap {
this.lowresScene = new Scene();
this.hiresScene = new Scene();
this.shapeScene = new Scene();
this.hudScene = new Scene();
$(this.renderer.domElement).addClass("map-canvas").appendTo(this.element);
$(this.hudRenderer.domElement).addClass("map-canvas-hud").appendTo(this.element);
this.handleContainerResize();
$(window).resize(this.handleContainerResize);

View File

@ -0,0 +1,190 @@
/**
* @author mrdoob / http://mrdoob.com/
*/
import {
Matrix4,
Object3D,
Vector3
} from "three";
var CSS2DObject = function ( element ) {
Object3D.call( this );
this.element = element;
this.element.style.position = 'absolute';
this.addEventListener( 'removed', function () {
this.traverse( function ( object ) {
if ( object.element instanceof Element && object.element.parentNode !== null ) {
object.element.parentNode.removeChild( object.element );
}
} );
} );
};
CSS2DObject.prototype = Object.create( Object3D.prototype );
CSS2DObject.prototype.constructor = CSS2DObject;
//
var CSS2DRenderer = function () {
var _this = this;
var _width, _height;
var _widthHalf, _heightHalf;
var vector = new Vector3();
var viewMatrix = new Matrix4();
var viewProjectionMatrix = new Matrix4();
var cache = {
objects: new WeakMap()
};
var domElement = document.createElement( 'div' );
domElement.style.overflow = 'hidden';
this.domElement = domElement;
this.getSize = function () {
return {
width: _width,
height: _height
};
};
this.setSize = function ( width, height ) {
_width = width;
_height = height;
_widthHalf = _width / 2;
_heightHalf = _height / 2;
domElement.style.width = width + 'px';
domElement.style.height = height + 'px';
};
var renderObject = function ( object, scene, camera ) {
if ( object instanceof CSS2DObject ) {
object.onBeforeRender( _this, scene, camera );
vector.setFromMatrixPosition( object.matrixWorld );
vector.applyMatrix4( viewProjectionMatrix );
var element = object.element;
var style = 'translate(-50%,-50%) translate(' + ( vector.x * _widthHalf + _widthHalf ) + 'px,' + ( - vector.y * _heightHalf + _heightHalf ) + 'px)';
element.style.WebkitTransform = style;
element.style.MozTransform = style;
element.style.oTransform = style;
element.style.transform = style;
element.style.display = ( object.visible && vector.z >= - 1 && vector.z <= 1 ) ? '' : 'none';
var objectData = {
distanceToCameraSquared: getDistanceToSquared( camera, object )
};
cache.objects.set( object, objectData );
if ( element.parentNode !== domElement ) {
domElement.appendChild( element );
}
object.onAfterRender( _this, scene, camera );
}
for ( var i = 0, l = object.children.length; i < l; i ++ ) {
renderObject( object.children[ i ], scene, camera );
}
};
var getDistanceToSquared = function () {
var a = new Vector3();
var b = new Vector3();
return function ( object1, object2 ) {
a.setFromMatrixPosition( object1.matrixWorld );
b.setFromMatrixPosition( object2.matrixWorld );
return a.distanceToSquared( b );
};
}();
var filterAndFlatten = function ( scene ) {
var result = [];
scene.traverse( function ( object ) {
if ( object instanceof CSS2DObject ) result.push( object );
} );
return result;
};
var zOrder = function ( scene ) {
var sorted = filterAndFlatten( scene ).sort( function ( a, b ) {
var distanceA = cache.objects.get( a ).distanceToCameraSquared;
var distanceB = cache.objects.get( b ).distanceToCameraSquared;
return distanceA - distanceB;
} );
var zMax = sorted.length;
for ( var i = 0, l = sorted.length; i < l; i ++ ) {
sorted[ i ].element.style.zIndex = zMax - i;
}
};
this.render = function ( scene, camera ) {
if ( scene.autoUpdate === true ) scene.updateMatrixWorld();
if ( camera.parent === null ) camera.updateMatrixWorld();
viewMatrix.copy( camera.matrixWorldInverse );
viewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, viewMatrix );
renderObject( scene, scene, camera );
zOrder( scene );
};
};
export { CSS2DObject, CSS2DRenderer };

View File

@ -6,35 +6,56 @@ import {
Mesh,
MeshBasicMaterial
} from 'three';
import {CSS2DObject} from './CSS2DRenderer';
import {pathFromCoords} from "../utils";
export default class HudInfo {
constructor(blueMap, container){
constructor(blueMap){
this.blueMap = blueMap;
this.container = container;
let blockMarkerGeo = new BoxBufferGeometry( 1, 1, 1 );
let blockMarkerGeo = new BoxBufferGeometry( 1.01, 1.01, 1.01 );
blockMarkerGeo.translate(0.5, 0.5, 0.5);
this.blockMarker = new Mesh(blockMarkerGeo, new MeshBasicMaterial( {
color: 0xffffff,
opacity: 0.3,
opacity: 0.5,
depthWrite: false,
depthTest: false,
transparent: true
transparent: true,
} ));
this.rayPosition = new Vector2();
this.raycaster = new Raycaster();
this.element = $(`
<div class="hud-info" style="display: none">
<div class="hud-info"><div class="bubble" style="display: none">
<div class="content"></div>
</div>
`).appendTo(this.container);
</div></div>
`);
this.bubble = this.element.find(".bubble");
this.hudElement = new CSS2DObject(this.element[0]);
$(document).on('bluemap-info-click', this.onShowInfo);
$(window).on('mousedown wheel', this.onHideInfo);
$(window).on('mousedown wheel touchstart', this.onHideInfo);
}
showInfoBubble(content, x, y, z, onClose) {
if (this.onClose){
this.onClose();
this.onClose = undefined;
}
this.bubble.hide();
this.bubble.find(".content").html(content);
this.hudElement.position.set(x, y, z);
this.bubble.stop();
this.blueMap.hudScene.add(this.hudElement);
this.bubble.fadeIn(200);
this.onClose = onClose;
this.blueMap.updateFrame = true;
}
onShowInfo = event => {
@ -50,9 +71,7 @@ export default class HudInfo {
}
if (intersects.length > 0) {
this.element.hide();
let content = this.element.find(".content");
content.html("");
let content = $("<div></div>");
if (this.blueMap.debugInfo){
console.debug("Tapped position data: ", intersects[0]);
@ -129,28 +148,28 @@ export default class HudInfo {
`).appendTo(content);
}
//display the element
this.element.css('left', `${event.pos.x}px`);
this.element.css('top', `${event.pos.y}px`);
if (event.pos.y < this.blueMap.element.offsetHeight / 3){
this.element.addClass("below");
} else {
this.element.removeClass("below");
}
this.element.fadeIn(200);
//add block marker
if (hiresData){
this.blockMarker.position.set(block.x, block.y, block.z);
this.blueMap.hiresScene.add(this.blockMarker);
this.blueMap.updateFrame = true;
this.blockMarker.needsUpdate = true;
}
this.showInfoBubble(content.html(), block.x + 0.5, block.y + 1, block.z + 0.5);
}
};
onHideInfo = event => {
if (!this.element.is(':animated')) {
this.element.fadeOut(200);
if (!this.bubble.is(':animated')) {
this.bubble.fadeOut(200, () => {
this.blueMap.hudScene.remove(this.hudElement);
if (this.onClose){
this.onClose();
this.onClose = undefined;
}
});
this.blueMap.hiresScene.remove(this.blockMarker);
this.blueMap.updateFrame = true;
}

View File

@ -0,0 +1,36 @@
export default class Marker {
constructor(blueMap, markerSet, markerData) {
this.blueMap = blueMap;
this.markerSet = markerSet;
this.type = markerData.type;
this.map = markerData.map;
this.label = markerData.label;
this.link = markerData.link;
this.newTab = !!markerData.newTab;
this.visible = false;
this.minDistance = parseFloat(markerData.minDistance ? markerData.minDistance : 0);
this.minDistanceSquared = this.minDistance * this.minDistance;
this.maxDistance = parseFloat(markerData.maxDistance ? markerData.maxDistance : 100000);
this.maxDistanceSquared = this.maxDistance * this.maxDistance;
}
setVisible(visible) {
this.visible = visible && this.blueMap.map === this.map;
this.blueMap.updateFrame = true;
}
updateRenderObject(object, scene, camera){
if (this.visible) {
//update visiblity
let distanceSquared = object.position.distanceToSquared(camera.position);
object.visible = distanceSquared <= this.maxDistanceSquared && distanceSquared >= this.minDistanceSquared;
} else {
object.visible = false;
scene.remove(object);
}
}
}

View File

@ -0,0 +1,76 @@
import MarkerSet from "./MarkerSet";
import $ from "jquery";
import ToggleButton from "../ui/ToggleButton";
import Label from "../ui/Label";
export default class MarkerManager {
constructor(blueMap, ui) {
this.blueMap = blueMap;
this.ui = ui;
this.markerSets = [];
this.readyPromise =
this.loadMarkerData()
.then(this.loadMarkers);
$(document).on('bluemap-map-change', this.onBlueMapMapChange);
}
loadMarkerData() {
return new Promise((resolve, reject) => {
this.blueMap.fileLoader.load(this.blueMap.dataRoot + 'markers.json',
markerData => {
this.markerData = JSON.parse(markerData);
resolve();
},
xhr => {},
error => {
reject();
}
);
});
}
loadMarkers = () => {
this.markerData.markerSets.forEach(setData => {
this.markerSets.push(new MarkerSet(this.blueMap, setData));
});
};
update(){
this.markerSets.forEach(markerSet => {
markerSet.update();
});
}
addMenuElements(menu){
let addedLabel = false;
this.markerSets.forEach(markerSet => {
if (markerSet.toggleable) {
if (!addedLabel){
menu.addElement(new Label("marker:"));
addedLabel = true;
}
let menuElement = new ToggleButton(markerSet.label, !markerSet.defaultHide, button => {
markerSet.visible = button.isSelected();
markerSet.update();
});
markerSet.visible = !markerSet.defaultHide;
markerSet.update();
menu.addElement(menuElement);
}
});
}
onBlueMapMapChange = async () => {
await this.readyPromise;
this.update();
};
}

View File

@ -0,0 +1,36 @@
import POIMarker from "./POIMarker";
import ShapeMarker from "./ShapeMarker";
export default class MarkerSet {
constructor(blueMap, setData) {
this.blueMap = blueMap;
this.id = setData.id;
this.label = setData.label ? setData.label : this.id;
this.toggleable = setData.toggleable !== undefined ? !!setData.toggleable : true;
this.defaultHide = !!setData.defaultHide;
this.marker = [];
this.visible = true;
if (Array.isArray(setData.marker)){
setData.marker.forEach(markerData => {
switch (markerData.type){
case 'poi':
this.marker.push(new POIMarker(this.blueMap, this, markerData));
break;
case 'shape':
this.marker.push(new ShapeMarker(this.blueMap, this, markerData));
break;
}
});
}
}
update() {
this.marker.forEach(marker => {
marker.setVisible(this.visible);
});
}
}

View File

@ -0,0 +1,57 @@
import $ from 'jquery';
import Marker from "./Marker";
import {CSS2DObject} from "./CSS2DRenderer";
import {Vector3} from "three";
import POI from "../../../assets/poi.svg";
export default class POIMarker extends Marker {
constructor(blueMap, markerSet, markerData) {
super(blueMap, markerSet, markerData);
this.icon = markerData.icon ? markerData.icon : POI;
this.iconAnchor = {
x: markerData.iconAnchor.x,
y: markerData.iconAnchor.y
};
this.position = new Vector3(markerData.position.x, markerData.position.y, markerData.position.z);
}
setVisible(visible){
super.setVisible(visible);
if (!this.renderObject){
let iconElement = $(`<div class="marker-poi"><img src="${this.icon}" style="transform: translate(50%, 50%) translate(${-this.iconAnchor.x}px, ${-this.iconAnchor.y}px)"></div>`);
iconElement.find("img").click(this.onClick);
this.renderObject = new CSS2DObject(iconElement[0]);
this.renderObject.position.copy(this.position);
this.renderObject.onBeforeRender = (renderer, scene, camera) => this.updateRenderObject(this.renderObject, scene, camera);
}
if (this.visible) {
this.blueMap.hudScene.add(this.renderObject);
} else {
this.blueMap.hudScene.remove(this.renderObject);
}
}
onClick = () => {
if (this.label) {
this.setVisible(false);
this.blueMap.ui.hudInfo.showInfoBubble(this.label, this.position.x, this.position.y, this.position.z, () => {
this.setVisible(this.markerSet.visible);
});
}
if (this.link){
if (this.newTab){
window.open(this.link, '_blank');
} else {
location.href = this.link;
}
}
}
}

View File

@ -0,0 +1,72 @@
import {
Vector2,
Shape,
ExtrudeBufferGeometry,
MeshBasicMaterial,
Mesh,
Object3D,
DoubleSide
} from 'three';
import Marker from "./Marker";
export default class ShapeMarker extends Marker {
constructor(blueMap, markerSet, markerData) {
super(blueMap, markerSet, markerData);
let points = [];
if (Array.isArray(markerData.shape)) {
markerData.shape.forEach(point => {
points.push(new Vector2(point.x, point.z));
});
}
this.floor = markerData.floor ? markerData.floor : 0;
this.ceiling = markerData.ceiling ? markerData.ceiling : 128;
let shape = new Shape(points);
let extrude = new ExtrudeBufferGeometry(shape, {
steps: 1,
depth: this.ceiling - this.floor,
bevelEnabled: false
});
extrude.rotateX(Math.PI * 0.5);
extrude.translate(0, this.ceiling, 0);
let material = new MeshBasicMaterial( {
color: 0xff0000,
opacity: 0.25,
transparent: true,
side: DoubleSide
} );
let extrudeMesh = new Mesh( extrude, material );
this.renderObject = new Object3D();
this.renderObject.add(extrudeMesh);
}
setVisible(visible){
super.setVisible(visible);
if (this.visible) {
console.log(this.renderObject);
this.blueMap.shapeScene.add(this.renderObject);
} else {
this.blueMap.shapeScene.remove(this.renderObject);
}
}
onClick = () => {
if (this.label) {
//this.blueMap.ui.hudInfo.showInfoBubble(this.label, this.position.x, this.position.y, this.position.z);
}
if (this.link){
if (this.newTab){
window.open(this.link, '_blank');
} else {
location.href = this.link;
}
}
}
}

View File

@ -38,7 +38,8 @@ import ToggleButton from "./ToggleButton";
import MapSelection from "./MapSeletion";
import NIGHT from '../../../assets/night.svg';
import HudInfo from "../modules/HudInfo";
import HudInfo from "../hud/HudInfo";
import MarkerManager from "../hud/MarkerManager";
export default class UI {
@ -55,10 +56,11 @@ export default class UI {
this.toolbar.element.appendTo(this.hud);
//modules
this.hudInfo = new HudInfo(this.blueMap, this.element);
this.hudInfo = new HudInfo(this.blueMap);
this.markers = new MarkerManager(this.blueMap, this);
}
load() {
async load() {
//elements
let menuButton = new MenuButton(this.menu);
let mapSelect = new MapSelection(this.blueMap);
@ -108,7 +110,11 @@ export default class UI {
//menu
this.menu.addElement(nightButton);
this.menu.addElement(mobSpawnOverlay);
//this.menu.addElement(mobSpawnOverlay);
await this.markers.readyPromise;
this.markers.addMenuElements(this.menu);
this.menu.addElement(new Separator());
this.menu.addElement(new Label('render quality:'));
this.menu.addElement(quality);
@ -119,7 +125,6 @@ export default class UI {
this.menu.addElement(new Separator());
this.menu.addElement(debugInfo);
this.menu.update();
}
}

View File

@ -1,18 +1,15 @@
.bluemap-container .ui .hud-info {
position: absolute;
.bluemap-container .hud-info {
pointer-events: none;
}
transform: translate(-50%, calc(-100% - 1rem));
.bluemap-container .hud-info .bubble {
transform: translate(0, calc(-50% - 0.5rem));
background-color: $normal_bg;
filter: drop-shadow(1px 1px 3px #0008);
pointer-events: none;
white-space: nowrap;
&.below {
transform: translate(-50%, 1rem);
}
.content {
position: relative;
@ -67,23 +64,26 @@
content: '';
position: absolute;
bottom: -1rem;
bottom: calc(-1rem + 1px);
left: calc(50% - 0.5rem);
width: 0;
height: 0;
z-index: 0;
border: solid 0.5rem;
border-color: $normal_bg transparent transparent transparent;
}
}
}
&.below .content::after {
top: -1rem;
.bluemap-container .marker-poi {
pointer-events: none;
border: solid 0.5rem;
border-color: transparent transparent $normal_bg transparent;
> * {
pointer-events: auto;
}
> img {
filter: drop-shadow(1px 1px 3px #0008);
}
}

View File

@ -31,19 +31,30 @@ html, body {
overflow: hidden;
> .map-canvas {
> .map-canvas, .map-canvas-hud {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
> .map-canvas {
background-color: #000;
z-index: 0;
}
> .map-canvas-hud {
pointer-events: none;
z-index: 10;
> * {
pointer-events: auto;
}
}
> .ui {
display: flex;
align-items: stretch;

View File

@ -6,7 +6,7 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const WEBROOT_PATH = path.resolve(__dirname, 'src/main/webroot')
const BUILD_PATH = path.resolve(__dirname, 'build/generated/webroot')
// folder with a generated world to render in the dev server
const WORLD_DATA_PATH = path.resolve(__dirname, 'build/generated/world')
const WORLD_DATA_PATH = path.resolve(__dirname, '../data/test-render')
module.exports = {
mode: 'production',