From 9a09f39b5a4ba5e9a1b12b65f79a21dc99914edd Mon Sep 17 00:00:00 2001 From: "Blue (Lukas Rieger)" Date: Thu, 16 Apr 2020 12:33:24 +0200 Subject: [PATCH 1/3] First part on implementing markers --- .gitignore | 3 + BlueMapCore/src/main/webroot/assets/poi.svg | 15 ++ .../src/main/webroot/js/libs/BlueMap.js | 66 +++--- .../main/webroot/js/libs/hud/CSS2DRenderer.js | 190 ++++++++++++++++++ .../js/libs/{modules => hud}/HudInfo.js | 71 ++++--- .../src/main/webroot/js/libs/hud/Marker.js | 36 ++++ .../main/webroot/js/libs/hud/MarkerManager.js | 76 +++++++ .../src/main/webroot/js/libs/hud/MarkerSet.js | 36 ++++ .../src/main/webroot/js/libs/hud/POIMarker.js | 57 ++++++ .../main/webroot/js/libs/hud/ShapeMarker.js | 72 +++++++ BlueMapCore/src/main/webroot/js/libs/ui/UI.js | 15 +- .../main/webroot/style/modules/hudInfo.scss | 30 +-- BlueMapCore/src/main/webroot/style/style.scss | 15 +- BlueMapCore/webpack.config.js | 138 ++++++------- 14 files changed, 680 insertions(+), 140 deletions(-) create mode 100644 BlueMapCore/src/main/webroot/assets/poi.svg create mode 100644 BlueMapCore/src/main/webroot/js/libs/hud/CSS2DRenderer.js rename BlueMapCore/src/main/webroot/js/libs/{modules => hud}/HudInfo.js (78%) create mode 100644 BlueMapCore/src/main/webroot/js/libs/hud/Marker.js create mode 100644 BlueMapCore/src/main/webroot/js/libs/hud/MarkerManager.js create mode 100644 BlueMapCore/src/main/webroot/js/libs/hud/MarkerSet.js create mode 100644 BlueMapCore/src/main/webroot/js/libs/hud/POIMarker.js create mode 100644 BlueMapCore/src/main/webroot/js/libs/hud/ShapeMarker.js diff --git a/.gitignore b/.gitignore index 4cf4afab..d36fc987 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/BlueMapCore/src/main/webroot/assets/poi.svg b/BlueMapCore/src/main/webroot/assets/poi.svg new file mode 100644 index 00000000..e37b635e --- /dev/null +++ b/BlueMapCore/src/main/webroot/assets/poi.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/BlueMapCore/src/main/webroot/js/libs/BlueMap.js b/BlueMapCore/src/main/webroot/js/libs/BlueMap.js index b5c5ead9..df9b5097 100644 --- a/BlueMapCore/src/main/webroot/js/libs/BlueMap.js +++ b/BlueMapCore/src/main/webroot/js/libs/BlueMap.js @@ -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 = $('
').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 = $('
loading...
').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 = $('
loading...
').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 ); - this.lowresTileManager.update(); - this.hiresTileManager.update(); + 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,18 +188,23 @@ 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; - 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 (!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; - this.controls.position.copy(this.controls.targetPosition); + + if (!smooth) { + this.controls.position.copy(this.controls.targetPosition); + } } } } @@ -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); diff --git a/BlueMapCore/src/main/webroot/js/libs/hud/CSS2DRenderer.js b/BlueMapCore/src/main/webroot/js/libs/hud/CSS2DRenderer.js new file mode 100644 index 00000000..c16e52a4 --- /dev/null +++ b/BlueMapCore/src/main/webroot/js/libs/hud/CSS2DRenderer.js @@ -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 }; \ No newline at end of file diff --git a/BlueMapCore/src/main/webroot/js/libs/modules/HudInfo.js b/BlueMapCore/src/main/webroot/js/libs/hud/HudInfo.js similarity index 78% rename from BlueMapCore/src/main/webroot/js/libs/modules/HudInfo.js rename to BlueMapCore/src/main/webroot/js/libs/hud/HudInfo.js index 6c2d9517..e226ae33 100644 --- a/BlueMapCore/src/main/webroot/js/libs/modules/HudInfo.js +++ b/BlueMapCore/src/main/webroot/js/libs/hud/HudInfo.js @@ -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 = $(` - + `); + 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 = $("
"); 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; } diff --git a/BlueMapCore/src/main/webroot/js/libs/hud/Marker.js b/BlueMapCore/src/main/webroot/js/libs/hud/Marker.js new file mode 100644 index 00000000..a0486a17 --- /dev/null +++ b/BlueMapCore/src/main/webroot/js/libs/hud/Marker.js @@ -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); + } + } + +} \ No newline at end of file diff --git a/BlueMapCore/src/main/webroot/js/libs/hud/MarkerManager.js b/BlueMapCore/src/main/webroot/js/libs/hud/MarkerManager.js new file mode 100644 index 00000000..b04621a9 --- /dev/null +++ b/BlueMapCore/src/main/webroot/js/libs/hud/MarkerManager.js @@ -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(); + }; + +} \ No newline at end of file diff --git a/BlueMapCore/src/main/webroot/js/libs/hud/MarkerSet.js b/BlueMapCore/src/main/webroot/js/libs/hud/MarkerSet.js new file mode 100644 index 00000000..aec4becc --- /dev/null +++ b/BlueMapCore/src/main/webroot/js/libs/hud/MarkerSet.js @@ -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); + }); + } + +} \ No newline at end of file diff --git a/BlueMapCore/src/main/webroot/js/libs/hud/POIMarker.js b/BlueMapCore/src/main/webroot/js/libs/hud/POIMarker.js new file mode 100644 index 00000000..c9e7d2e2 --- /dev/null +++ b/BlueMapCore/src/main/webroot/js/libs/hud/POIMarker.js @@ -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 = $(`
`); + 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; + } + } + } + +} \ No newline at end of file diff --git a/BlueMapCore/src/main/webroot/js/libs/hud/ShapeMarker.js b/BlueMapCore/src/main/webroot/js/libs/hud/ShapeMarker.js new file mode 100644 index 00000000..603f83e1 --- /dev/null +++ b/BlueMapCore/src/main/webroot/js/libs/hud/ShapeMarker.js @@ -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; + } + } + } + +} \ No newline at end of file diff --git a/BlueMapCore/src/main/webroot/js/libs/ui/UI.js b/BlueMapCore/src/main/webroot/js/libs/ui/UI.js index 4a96b5c6..b8bb6ab8 100644 --- a/BlueMapCore/src/main/webroot/js/libs/ui/UI.js +++ b/BlueMapCore/src/main/webroot/js/libs/ui/UI.js @@ -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(); - } } \ No newline at end of file diff --git a/BlueMapCore/src/main/webroot/style/modules/hudInfo.scss b/BlueMapCore/src/main/webroot/style/modules/hudInfo.scss index c3cc84d1..997cd8ca 100644 --- a/BlueMapCore/src/main/webroot/style/modules/hudInfo.scss +++ b/BlueMapCore/src/main/webroot/style/modules/hudInfo.scss @@ -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); } } \ No newline at end of file diff --git a/BlueMapCore/src/main/webroot/style/style.scss b/BlueMapCore/src/main/webroot/style/style.scss index 4ef91e78..a7d2e851 100644 --- a/BlueMapCore/src/main/webroot/style/style.scss +++ b/BlueMapCore/src/main/webroot/style/style.scss @@ -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; diff --git a/BlueMapCore/webpack.config.js b/BlueMapCore/webpack.config.js index 63497397..9c696846 100644 --- a/BlueMapCore/webpack.config.js +++ b/BlueMapCore/webpack.config.js @@ -6,75 +6,75 @@ 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', - devtool: 'source-map', - entry: { - 'bluemap': path.resolve(WEBROOT_PATH, 'js/site.js'), - }, - output: { - path: BUILD_PATH, - filename: 'js/[name].js', - }, - devServer: { - contentBase: WORLD_DATA_PATH, - compress: true, - port: 8080, - hot: true, - host: '0.0.0.0' - }, - plugins: [ - new MiniCssExtractPlugin({ - filename: 'style/[name].css?[hash]', - }), - new HtmlWebpackPlugin({ - template: path.resolve(WEBROOT_PATH, 'index.html'), - hash: true, - }), - ], - resolve: { - extensions: ['.js', '.css', '.scss'], - }, - module: { - rules: [ - // Transpile JavaScript source files using TypeScript engine - { - test: /\.(js|ts)$/, - include: /src/, - use: 'ts-loader', - }, - // Just import normal css files - { - test: /\.css$/, - include: /src/, - use: [ - { loader: MiniCssExtractPlugin.loader }, - { loader: 'css-loader' }, - ], - }, - // Converts scss files into css to use within custom elements - { - test: /\.scss$/, - include: /src/, - use: [ - { loader: MiniCssExtractPlugin.loader }, - { loader: 'css-loader' }, - { loader: 'sass-loader' }, - ], - }, - // Load additional files - { - test: /\.(png|svg)(\?.*$|$)/, - include: /src/, - use: [ - { - loader: 'file-loader', - options: { name: 'assets/[name].[ext]?[hash]' }, - }, - ], - }, - ], - }, + mode: 'production', + devtool: 'source-map', + entry: { + 'bluemap': path.resolve(WEBROOT_PATH, 'js/site.js'), + }, + output: { + path: BUILD_PATH, + filename: 'js/[name].js', + }, + devServer: { + contentBase: WORLD_DATA_PATH, + compress: true, + port: 8080, + hot: true, + host: '0.0.0.0' + }, + plugins: [ + new MiniCssExtractPlugin({ + filename: 'style/[name].css?[hash]', + }), + new HtmlWebpackPlugin({ + template: path.resolve(WEBROOT_PATH, 'index.html'), + hash: true, + }), + ], + resolve: { + extensions: ['.js', '.css', '.scss'], + }, + module: { + rules: [ + // Transpile JavaScript source files using TypeScript engine + { + test: /\.(js|ts)$/, + include: /src/, + use: 'ts-loader', + }, + // Just import normal css files + { + test: /\.css$/, + include: /src/, + use: [ + { loader: MiniCssExtractPlugin.loader }, + { loader: 'css-loader' }, + ], + }, + // Converts scss files into css to use within custom elements + { + test: /\.scss$/, + include: /src/, + use: [ + { loader: MiniCssExtractPlugin.loader }, + { loader: 'css-loader' }, + { loader: 'sass-loader' }, + ], + }, + // Load additional files + { + test: /\.(png|svg)(\?.*$|$)/, + include: /src/, + use: [ + { + loader: 'file-loader', + options: { name: 'assets/[name].[ext]?[hash]' }, + }, + ], + }, + ], + }, } From 2cbff3ccdbb32bcaed0d457e4629b3a6ea1d75a3 Mon Sep 17 00:00:00 2001 From: "Blue (Lukas Rieger)" Date: Fri, 17 Apr 2020 14:56:30 +0200 Subject: [PATCH 2/3] Complete implementing markers on the web-client --- .../src/main/webroot/js/libs/BlueMap.js | 4 +- .../src/main/webroot/js/libs/hud/HudInfo.js | 14 +++- .../src/main/webroot/js/libs/hud/Marker.js | 7 +- .../src/main/webroot/js/libs/hud/POIMarker.js | 2 - .../main/webroot/js/libs/hud/ShapeMarker.js | 78 +++++++++++++------ 5 files changed, 76 insertions(+), 29 deletions(-) diff --git a/BlueMapCore/src/main/webroot/js/libs/BlueMap.js b/BlueMapCore/src/main/webroot/js/libs/BlueMap.js index df9b5097..dc82985f 100644 --- a/BlueMapCore/src/main/webroot/js/libs/BlueMap.js +++ b/BlueMapCore/src/main/webroot/js/libs/BlueMap.js @@ -281,8 +281,8 @@ export default class BlueMap { this.renderer.render(this.skyboxScene, this.skyboxCamera); this.renderer.clearDepth(); this.renderer.render(this.lowresScene, this.camera); + this.renderer.clearDepth(); if (this.camera.position.y < 400) { - this.renderer.clearDepth(); this.renderer.render(this.hiresScene, this.camera); } this.renderer.render(this.shapeScene, this.camera); @@ -336,7 +336,7 @@ export default class BlueMap { this.renderer = new WebGLRenderer({ alpha: true, antialias: true, - sortObjects: false, + sortObjects: true, preserveDrawingBuffer: true, logarithmicDepthBuffer: false, }); diff --git a/BlueMapCore/src/main/webroot/js/libs/hud/HudInfo.js b/BlueMapCore/src/main/webroot/js/libs/hud/HudInfo.js index e226ae33..daaed65a 100644 --- a/BlueMapCore/src/main/webroot/js/libs/hud/HudInfo.js +++ b/BlueMapCore/src/main/webroot/js/libs/hud/HudInfo.js @@ -63,8 +63,20 @@ export default class HudInfo { this.rayPosition.y = - ( event.pos.y / this.blueMap.element.offsetHeight ) * 2 + 1; this.raycaster.setFromCamera(this.rayPosition, this.blueMap.camera); + + //check markers first + let intersects = this.raycaster.intersectObjects( this.blueMap.shapeScene.children ); + console.log(intersects); + if (intersects.length !== 0){ + try { + intersects[0].object.userData.marker.onClick(intersects[0].point); + } catch (ignore) {} + return; + } + + //then show position info let hiresData = true; - let intersects = this.raycaster.intersectObjects( this.blueMap.hiresScene.children ); + intersects = this.raycaster.intersectObjects( this.blueMap.hiresScene.children ); if (intersects.length === 0){ hiresData = false; intersects = this.raycaster.intersectObjects( this.blueMap.lowresScene.children ); diff --git a/BlueMapCore/src/main/webroot/js/libs/hud/Marker.js b/BlueMapCore/src/main/webroot/js/libs/hud/Marker.js index a0486a17..9cda90f9 100644 --- a/BlueMapCore/src/main/webroot/js/libs/hud/Marker.js +++ b/BlueMapCore/src/main/webroot/js/libs/hud/Marker.js @@ -1,3 +1,5 @@ +import {Vector3} from "three"; + export default class Marker { constructor(blueMap, markerSet, markerData) { @@ -5,7 +7,8 @@ export default class Marker { this.markerSet = markerSet; this.type = markerData.type; this.map = markerData.map; - this.label = markerData.label; + this.position = new Vector3(markerData.position.x, markerData.position.y, markerData.position.z); + this.label = `
${markerData.label}
`; this.link = markerData.link; this.newTab = !!markerData.newTab; @@ -25,7 +28,7 @@ export default class Marker { updateRenderObject(object, scene, camera){ if (this.visible) { //update visiblity - let distanceSquared = object.position.distanceToSquared(camera.position); + let distanceSquared = this.position.distanceToSquared(camera.position); object.visible = distanceSquared <= this.maxDistanceSquared && distanceSquared >= this.minDistanceSquared; } else { object.visible = false; diff --git a/BlueMapCore/src/main/webroot/js/libs/hud/POIMarker.js b/BlueMapCore/src/main/webroot/js/libs/hud/POIMarker.js index c9e7d2e2..3a8cdfd4 100644 --- a/BlueMapCore/src/main/webroot/js/libs/hud/POIMarker.js +++ b/BlueMapCore/src/main/webroot/js/libs/hud/POIMarker.js @@ -15,8 +15,6 @@ export default class POIMarker extends Marker { x: markerData.iconAnchor.x, y: markerData.iconAnchor.y }; - - this.position = new Vector3(markerData.position.x, markerData.position.y, markerData.position.z); } setVisible(visible){ diff --git a/BlueMapCore/src/main/webroot/js/libs/hud/ShapeMarker.js b/BlueMapCore/src/main/webroot/js/libs/hud/ShapeMarker.js index 603f83e1..9249bd69 100644 --- a/BlueMapCore/src/main/webroot/js/libs/hud/ShapeMarker.js +++ b/BlueMapCore/src/main/webroot/js/libs/hud/ShapeMarker.js @@ -1,13 +1,16 @@ import { Vector2, Shape, - ExtrudeBufferGeometry, MeshBasicMaterial, Mesh, - Object3D, + Line, + LineBasicMaterial, + BufferGeometry, + ShapeBufferGeometry, DoubleSide } from 'three'; import Marker from "./Marker"; +import $ from "jquery"; export default class ShapeMarker extends Marker { @@ -20,44 +23,65 @@ export default class ShapeMarker extends Marker { points.push(new Vector2(point.x, point.z)); }); } - this.floor = markerData.floor ? markerData.floor : 0; - this.ceiling = markerData.ceiling ? markerData.ceiling : 128; + this.height = markerData.height ? markerData.height : 128; + this.fillColor = this.prepareColor(markerData.fillColor); + this.borderColor = this.prepareColor(markerData.borderColor); + + //fill 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, + let fillGeo = new ShapeBufferGeometry(shape, 1); + fillGeo.rotateX(Math.PI * 0.5); + fillGeo.translate(0, this.height + 0.0072, 0); + let fillMaterial = new MeshBasicMaterial({ + color: this.fillColor.rgb, + opacity: this.fillColor.a, transparent: true, - side: DoubleSide - } ); + side: DoubleSide, + }); + let fill = new Mesh( fillGeo, fillMaterial ); - let extrudeMesh = new Mesh( extrude, material ); + //border + points.push(points[0]); + let lineGeo = new BufferGeometry().setFromPoints(points); + lineGeo.rotateX(Math.PI * 0.5); + lineGeo.translate(0, this.height + 0.0072, 0); + let lineMaterial = new LineBasicMaterial({ + color: this.borderColor.rgb, + opacity: this.borderColor.a, + transparent: true, + depthTest: false, + }); + let line = new Line( lineGeo, lineMaterial ); + + this.renderObject = fill; + fill.add(line); + + this.renderObject.userData = { + marker: this, + }; - 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); + $(document).on('bluemap-update-frame', this.onRender); } else { this.blueMap.shapeScene.remove(this.renderObject); + $(document).off('bluemap-update-frame', this.onRender); } } - onClick = () => { + onRender = () => { + this.updateRenderObject(this.renderObject, this.blueMap.shapeScene, this.blueMap.camera); + }; + + onClick = (clickPos) => { if (this.label) { - //this.blueMap.ui.hudInfo.showInfoBubble(this.label, this.position.x, this.position.y, this.position.z); + this.blueMap.ui.hudInfo.showInfoBubble(this.label, clickPos.x, clickPos.y, clickPos.z); } if (this.link){ @@ -67,6 +91,16 @@ export default class ShapeMarker extends Marker { location.href = this.link; } } + }; + + prepareColor(color){ + if (color.r === undefined) color.r = 0; + if (color.g === undefined) color.g = 0; + if (color.b === undefined) color.b = 0; + if (color.a === undefined) color.a = 1; + + color.rgb = (color.r << 16) + (color.g << 8) + (color.b); + return color; } } \ No newline at end of file From d8e189528eee0e1c963fd954c850aa7e218ae37c Mon Sep 17 00:00:00 2001 From: "Blue (Lukas Rieger)" Date: Sun, 19 Apr 2020 20:12:37 +0200 Subject: [PATCH 3/3] Implement MarkerAPI --- BlueMapAPI | 2 +- .../bluemap/common/api/BlueMapAPIImpl.java | 43 +++- .../{RendererImpl.java => RenderAPIImpl.java} | 6 +- .../common/api/marker/MarkerAPIImpl.java | 163 ++++++++++++ .../api/marker/MarkerFileFormatException.java | 44 ++++ .../bluemap/common/api/marker/MarkerImpl.java | 210 ++++++++++++++++ .../common/api/marker/MarkerSetImpl.java | 232 ++++++++++++++++++ .../common/api/marker/POIMarkerImpl.java | 108 ++++++++ .../common/api/marker/ShapeMarkerImpl.java | 200 +++++++++++++++ .../bluemap/common/plugin/Plugin.java | 6 + .../src/main/webroot/js/libs/hud/MarkerSet.js | 6 +- 11 files changed, 1011 insertions(+), 9 deletions(-) rename BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/{RendererImpl.java => RenderAPIImpl.java} (93%) create mode 100644 BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/MarkerAPIImpl.java create mode 100644 BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/MarkerFileFormatException.java create mode 100644 BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/MarkerImpl.java create mode 100644 BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/MarkerSetImpl.java create mode 100644 BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/POIMarkerImpl.java create mode 100644 BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/ShapeMarkerImpl.java diff --git a/BlueMapAPI b/BlueMapAPI index 1d7495df..51ea1fe8 160000 --- a/BlueMapAPI +++ b/BlueMapAPI @@ -1 +1 @@ -Subproject commit 1d7495dffd6d7c72e22a99888a277eb17de55d31 +Subproject commit 51ea1fe8d1e48eeeeb5e71af9e3d12c371214d89 diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/BlueMapAPIImpl.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/BlueMapAPIImpl.java index abcb9ac8..9f5b4427 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/BlueMapAPIImpl.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/BlueMapAPIImpl.java @@ -24,6 +24,11 @@ */ package de.bluecolored.bluemap.common.api; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -31,19 +36,23 @@ import java.util.Optional; import java.util.UUID; +import javax.imageio.ImageIO; + import de.bluecolored.bluemap.api.BlueMapAPI; import de.bluecolored.bluemap.api.renderer.BlueMapMap; import de.bluecolored.bluemap.api.renderer.BlueMapWorld; -import de.bluecolored.bluemap.api.renderer.Renderer; import de.bluecolored.bluemap.common.MapType; +import de.bluecolored.bluemap.common.api.marker.MarkerAPIImpl; import de.bluecolored.bluemap.common.plugin.Plugin; import de.bluecolored.bluemap.core.BlueMap; import de.bluecolored.bluemap.core.world.World; public class BlueMapAPIImpl extends BlueMapAPI { + private static final String IMAGE_ROOT_PATH = "images"; + public Plugin blueMap; - public RendererImpl renderer; + public RenderAPIImpl renderer; public Map worlds; public Map maps; @@ -51,7 +60,7 @@ public class BlueMapAPIImpl extends BlueMapAPI { public BlueMapAPIImpl(Plugin blueMap) { this.blueMap = blueMap; - this.renderer = new RendererImpl(this, blueMap.getRenderManager()); + this.renderer = new RenderAPIImpl(this, blueMap.getRenderManager()); worlds = new HashMap<>(); for (World world : blueMap.getWorlds()) { @@ -67,10 +76,15 @@ public BlueMapAPIImpl(Plugin blueMap) { } @Override - public Renderer getRenderer() { + public RenderAPIImpl getRenderAPI() { return renderer; } + @Override + public MarkerAPIImpl getMarkerAPI() throws IOException { + return new MarkerAPIImpl(this, blueMap.getMainConfig().getWebDataPath().resolve("markers.json").toFile()); + } + @Override public Collection getMaps() { return Collections.unmodifiableCollection(maps.values()); @@ -81,6 +95,27 @@ public Collection getWorlds() { return Collections.unmodifiableCollection(worlds.values()); } + @Override + public String createImage(BufferedImage image, String path) throws IOException { + path = path.replaceAll("[^a-zA-Z_\\.\\-\\/]", "_"); + String separator = FileSystems.getDefault().getSeparator(); + + Path webRoot = blueMap.getMainConfig().getWebRoot().toAbsolutePath(); + Path webDataRoot = blueMap.getMainConfig().getWebDataPath().toAbsolutePath(); + + Path imagePath; + if (webDataRoot.startsWith(webRoot)) { + imagePath = webDataRoot.resolve(Paths.get(IMAGE_ROOT_PATH, path.replace("/", separator))).toAbsolutePath(); + } else { + imagePath = webRoot.resolve("assets").resolve(Paths.get(IMAGE_ROOT_PATH, path.replace("/", separator))).toAbsolutePath(); + } + + if (!ImageIO.write(image, "png", imagePath.toFile())) + throw new IOException("The format 'png' is not supported!"); + + return webRoot.relativize(imagePath).toString().replace(separator, "/"); + } + @Override public String getBlueMapVersion() { return BlueMap.VERSION; diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/RendererImpl.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/RenderAPIImpl.java similarity index 93% rename from BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/RendererImpl.java rename to BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/RenderAPIImpl.java index c227776d..adb543ec 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/RendererImpl.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/RenderAPIImpl.java @@ -30,15 +30,15 @@ import com.flowpowered.math.vector.Vector3i; import de.bluecolored.bluemap.api.renderer.BlueMapMap; -import de.bluecolored.bluemap.api.renderer.Renderer; +import de.bluecolored.bluemap.api.renderer.RenderAPI; import de.bluecolored.bluemap.common.RenderManager; -public class RendererImpl implements Renderer { +public class RenderAPIImpl implements RenderAPI { private BlueMapAPIImpl api; private RenderManager renderManager; - protected RendererImpl(BlueMapAPIImpl api, RenderManager renderManager) { + protected RenderAPIImpl(BlueMapAPIImpl api, RenderManager renderManager) { this.api = api; this.renderManager = renderManager; } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/MarkerAPIImpl.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/MarkerAPIImpl.java new file mode 100644 index 00000000..7175bcc1 --- /dev/null +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/MarkerAPIImpl.java @@ -0,0 +1,163 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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. + */ +package de.bluecolored.bluemap.common.api.marker; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import com.google.common.collect.Sets; + +import de.bluecolored.bluemap.api.marker.MarkerAPI; +import de.bluecolored.bluemap.api.marker.MarkerSet; +import de.bluecolored.bluemap.common.api.BlueMapAPIImpl; +import de.bluecolored.bluemap.core.logger.Logger; +import ninja.leaping.configurate.ConfigurationNode; +import ninja.leaping.configurate.gson.GsonConfigurationLoader; + +public class MarkerAPIImpl implements MarkerAPI { + + private BlueMapAPIImpl api; + private File markerFile; + private Map markerSets; + private Set removedMarkerSets; + + public MarkerAPIImpl(BlueMapAPIImpl api, File markerFile) throws IOException { + this.api = api; + this.markerFile = markerFile; + + this.markerSets = new ConcurrentHashMap<>(); + this.removedMarkerSets = Sets.newConcurrentHashSet(); + + load(); + } + + @Override + public Collection getMarkerSets() { + return Collections.unmodifiableCollection(this.markerSets.values()); + } + + @Override + public Optional getMarkerSet(String id) { + return Optional.ofNullable(this.markerSets.get(id)); + } + + @Override + public synchronized MarkerSet createMarkerSet(String id) { + MarkerSetImpl set = this.markerSets.get(id); + + if (set == null) { + set = new MarkerSetImpl(id); + this.markerSets.put(id, set); + } + + return set; + } + + @Override + public synchronized boolean removeMarkerSet(String id) { + if (this.markerSets.remove(id) != null) { + this.removedMarkerSets.add(id); + return true; + } + + return false; + } + + @Override + public synchronized void load() throws IOException { + this.removedMarkerSets.clear(); + + if (!markerFile.exists()) { + markerFile.getParentFile().mkdirs(); + markerFile.createNewFile(); + } + + GsonConfigurationLoader loader = GsonConfigurationLoader.builder().setFile(markerFile).build(); + ConfigurationNode node = loader.load(); + + Set externallyRemovedSets = new HashSet<>(markerSets.keySet()); + for (ConfigurationNode markerSetNode : node.getNode("markerSets").getChildrenList()) { + String setId = markerSetNode.getNode("id").getString(); + if (setId == null) continue; + + externallyRemovedSets.remove(setId); + MarkerSetImpl set = markerSets.get(setId); + + if (set == null) { + set = new MarkerSetImpl(setId); + } + + try { + set.load(api, markerSetNode); + markerSets.put(setId, set); + } catch (MarkerFileFormatException ex) { + Logger.global.logDebug("Marker-API: Failed to load marker-set '" + setId + ": " + ex); + } + } + + for (String setId : externallyRemovedSets) { + markerSets.remove(setId); + } + } + + @Override + public synchronized void save() throws IOException { + if (!markerFile.exists()) { + markerFile.getParentFile().mkdirs(); + markerFile.createNewFile(); + } + + GsonConfigurationLoader loader = GsonConfigurationLoader.builder().setFile(markerFile).build(); + ConfigurationNode node = loader.load(); + + List markerList = node.getNode("markerSets").getChildrenList(); + node.removeChild("markerSets"); + + Set newMarkers = new HashSet<>(markerSets.keySet()); + for (ConfigurationNode markerSetNode : markerList) { + String setId = markerSetNode.getNode("id").getString(); + if (setId == null) continue; + if (removedMarkerSets.contains(setId)) continue; + + newMarkers.remove(setId); + MarkerSetImpl set = markerSets.get(setId); + + if (set != null) set.save(markerSetNode, false); + + node.getNode("markerSets").getAppendedNode().mergeValuesFrom(markerSetNode); + } + + removedMarkerSets.clear(); + } + +} diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/MarkerFileFormatException.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/MarkerFileFormatException.java new file mode 100644 index 00000000..fb531687 --- /dev/null +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/MarkerFileFormatException.java @@ -0,0 +1,44 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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. + */ +package de.bluecolored.bluemap.common.api.marker; + +import java.io.IOException; + +public class MarkerFileFormatException extends IOException { + private static final long serialVersionUID = 1L; + + public MarkerFileFormatException() { + super(); + } + + public MarkerFileFormatException(String message) { + super(message); + } + + public MarkerFileFormatException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/MarkerImpl.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/MarkerImpl.java new file mode 100644 index 00000000..d3fc9b07 --- /dev/null +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/MarkerImpl.java @@ -0,0 +1,210 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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. + */ +package de.bluecolored.bluemap.common.api.marker; + +import java.util.Optional; + +import com.flowpowered.math.vector.Vector3d; +import com.google.common.base.Preconditions; + +import de.bluecolored.bluemap.api.BlueMapAPI; +import de.bluecolored.bluemap.api.marker.Marker; +import de.bluecolored.bluemap.api.renderer.BlueMapMap; +import ninja.leaping.configurate.ConfigurationNode; + +public abstract class MarkerImpl implements Marker { + + private final String id; + private BlueMapMap map; + private Vector3d postition; + private double minDistance, maxDistance; + private String label, link; + private boolean newTab; + + private boolean hasUnsavedChanges; + + public MarkerImpl(String id, BlueMapMap map, Vector3d position) { + Preconditions.checkNotNull(id); + Preconditions.checkNotNull(map); + Preconditions.checkNotNull(position); + + this.id = id; + this.map = map; + this.postition = position; + this.minDistance = 0; + this.maxDistance = 100000; + this.label = id; + this.link = null; + this.newTab = true; + + this.hasUnsavedChanges = true; + } + + @Override + public String getId() { + return this.id; + } + + public abstract String getType(); + + @Override + public BlueMapMap getMap() { + return this.map; + } + + @Override + public synchronized void setMap(BlueMapMap map) { + this.map = map; + this.hasUnsavedChanges = true; + } + + @Override + public Vector3d getPosition() { + return this.postition; + } + + @Override + public synchronized void setPosition(Vector3d position) { + this.postition = position; + this.hasUnsavedChanges = true; + } + + @Override + public double getMinDistance() { + return this.minDistance; + } + + @Override + public synchronized void setMinDistance(double minDistance) { + this.minDistance = minDistance; + this.hasUnsavedChanges = true; + } + + @Override + public double getMaxDistance() { + return this.maxDistance; + } + + @Override + public synchronized void setMaxDistance(double maxDistance) { + this.maxDistance = maxDistance; + this.hasUnsavedChanges = true; + } + + @Override + public String getLabel() { + return this.label; + } + + @Override + public synchronized void setLabel(String label) { + this.label = label; + this.hasUnsavedChanges = true; + } + + @Override + public Optional getLink() { + return Optional.ofNullable(this.link); + } + + @Override + public boolean isNewTab() { + return this.newTab; + } + + @Override + public synchronized void setLink(String link, boolean newTab) { + this.link = link; + this.newTab = newTab; + this.hasUnsavedChanges = true; + } + + @Override + public synchronized void removeLink() { + this.link = null; + this.hasUnsavedChanges = true; + } + + public synchronized void load(BlueMapAPI api, ConfigurationNode markerNode) throws MarkerFileFormatException { + this.hasUnsavedChanges = false; + + //map + String mapId = markerNode.getNode("map").getString(); + if (mapId == null) throw new MarkerFileFormatException("There is no map defined!"); + this.map = api.getMap(mapId).orElseThrow(() -> new MarkerFileFormatException("Could not resolve map with id: " + mapId)); + + //position + this.postition = readPos(markerNode.getNode("position")); + + //minmaxDistance + this.minDistance = markerNode.getNode("minDistance").getDouble(0); + this.maxDistance = markerNode.getNode("maxDistance").getDouble(100000); + + //label + this.label = markerNode.getNode("label").getString(this.id); + + //link + this.link = markerNode.getNode("link").getString(); + this.newTab = markerNode.getNode("newTab").getBoolean(true); + } + + public synchronized void save(ConfigurationNode markerNode, boolean force) { + if (!force && !hasUnsavedChanges) return; + + markerNode.getNode("id").setValue(this.id); + markerNode.getNode("type").setValue(this.getType()); + markerNode.getNode("map").setValue(this.map.getId()); + writePos(markerNode.getNode("position"), this.postition); + markerNode.getNode("minDistance").setValue(Math.round(this.minDistance * 1000d) / 1000d); + markerNode.getNode("maxDistance").setValue(Math.round(this.maxDistance * 1000d) / 1000d); + markerNode.getNode("label").setValue(this.label); + markerNode.getNode("link").setValue(this.link); + markerNode.getNode("newTab").setValue(this.newTab); + + hasUnsavedChanges = false; + } + + private static Vector3d readPos(ConfigurationNode node) throws MarkerFileFormatException { + ConfigurationNode nx, ny, nz; + nx = node.getNode("x"); + ny = node.getNode("y"); + nz = node.getNode("z"); + + if (nx.isVirtual() || ny.isVirtual() || nz.isVirtual()) throw new MarkerFileFormatException("Failed to read position: One of the nodes x,y or z is missing!"); + + return new Vector3d( + nx.getDouble(), + ny.getDouble(), + nz.getDouble() + ); + } + + private static void writePos(ConfigurationNode node, Vector3d pos) { + node.getNode("x").setValue(Math.round(pos.getX() * 1000d) / 1000d); + node.getNode("y").setValue(Math.round(pos.getY() * 1000d) / 1000d); + node.getNode("z").setValue(Math.round(pos.getZ() * 1000d) / 1000d); + } + +} diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/MarkerSetImpl.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/MarkerSetImpl.java new file mode 100644 index 00000000..4c8cac12 --- /dev/null +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/MarkerSetImpl.java @@ -0,0 +1,232 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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. + */ +package de.bluecolored.bluemap.common.api.marker; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import com.flowpowered.math.vector.Vector3d; +import com.google.common.collect.Sets; + +import de.bluecolored.bluemap.api.BlueMapAPI; +import de.bluecolored.bluemap.api.marker.Marker; +import de.bluecolored.bluemap.api.marker.MarkerSet; +import de.bluecolored.bluemap.api.marker.Shape; +import de.bluecolored.bluemap.api.renderer.BlueMapMap; +import de.bluecolored.bluemap.core.logger.Logger; +import ninja.leaping.configurate.ConfigurationNode; + +public class MarkerSetImpl implements MarkerSet { + + private final String id; + private String label; + private boolean toggleable; + private boolean isDefaultHidden; + private Map markers; + + private Set removedMarkers; + + private boolean hasUnsavedChanges; + + public MarkerSetImpl(String id) { + this.id = id; + this.label = id; + this.toggleable = true; + this.isDefaultHidden = false; + this.markers = new ConcurrentHashMap<>(); + + this.removedMarkers = Sets.newConcurrentHashSet(); + + this.hasUnsavedChanges = true; + } + + @Override + public String getId() { + return this.id; + } + + @Override + public String getLabel() { + return this.label; + } + + @Override + public synchronized void setLabel(String label) { + this.label = label; + this.hasUnsavedChanges = true; + } + + @Override + public boolean isToggleable() { + return this.toggleable; + } + + @Override + public synchronized void setToggleable(boolean toggleable) { + this.toggleable = toggleable; + this.hasUnsavedChanges = true; + } + + @Override + public boolean isDefautHidden() { + return this.isDefaultHidden; + } + + @Override + public synchronized void setDefaultHidden(boolean defaultHide) { + this.isDefaultHidden = defaultHide; + this.hasUnsavedChanges = true; + } + + @Override + public Collection getMarkers() { + return Collections.unmodifiableCollection(markers.values()); + } + + @Override + public Optional getMarker(String id) { + return Optional.ofNullable(markers.get(id)); + } + + @Override + public synchronized POIMarkerImpl createPOIMarker(String id, BlueMapMap map, Vector3d position) { + removeMarker(id); + + POIMarkerImpl marker = new POIMarkerImpl(id, map, position); + markers.put(id, marker); + + return marker; + } + + @Override + public synchronized ShapeMarkerImpl createShapeMarker(String id, BlueMapMap map, Vector3d position, Shape shape, float height) { + removeMarker(id); + + ShapeMarkerImpl marker = new ShapeMarkerImpl(id, map, position, shape, height); + markers.put(id, marker); + + return marker; + } + + @Override + public synchronized boolean removeMarker(String id) { + if (markers.remove(id) != null) { + removedMarkers.add(id); + return true; + } + return false; + } + + public synchronized void load(BlueMapAPI api, ConfigurationNode node) throws MarkerFileFormatException { + this.hasUnsavedChanges = false; + this.removedMarkers.clear(); + + this.label = node.getNode("label").getString(id); + this.toggleable = node.getNode("toggleable").getBoolean(true); + this.isDefaultHidden = node.getNode("defaultHide").getBoolean(false); + + BlueMapMap dummyMap = api.getMaps().iterator().next(); + Shape dummyShape = Shape.createRect(0d, 0d, 1d, 1d); + Set externallyRemovedMarkers = new HashSet<>(this.markers.keySet()); + + for (ConfigurationNode markerNode : node.getNode("marker").getChildrenList()) { + String id = markerNode.getNode("id").getString(); + String type = markerNode.getNode("type").getString(); + + if (id == null || type == null) { + Logger.global.logDebug("Marker-API: Failed to load a marker in the set '" + this.id + "': No id or type defined!"); + } + + MarkerImpl marker = markers.get(id); + externallyRemovedMarkers.remove(id); + + if (marker == null || !marker.getType().equals(type)) { + switch (type) { + case POIMarkerImpl.MARKER_TYPE: + marker = new POIMarkerImpl(id, dummyMap, Vector3d.ZERO); + break; + case ShapeMarkerImpl.MARKER_TYPE: + marker = new ShapeMarkerImpl(id, dummyMap, Vector3d.ZERO, dummyShape, 0f); + break; + } + } + + try { + marker.load(api, markerNode); + markers.put(id, marker); + } catch (MarkerFileFormatException ex) { + Logger.global.logDebug("Marker-API: Failed to load marker '" + id + "' in the set '" + this.id + "': " + ex); + } + } + + for (String id : externallyRemovedMarkers) { + markers.remove(id); + } + } + + public synchronized void save(ConfigurationNode node, boolean force) { + List markerList = node.getNode("marker").getChildrenList(); + node.removeChild("marker"); + + Set newMarkers = new HashSet<>(markers.keySet()); + for (ConfigurationNode markerNode : markerList) { + String id = markerNode.getNode("id").getString(); + if (id == null) continue; + if (removedMarkers.contains(id)) continue; + + newMarkers.remove(id); + MarkerImpl marker = markers.get(id); + + if (marker != null) marker.save(markerNode, false); + + node.getNode("marker").getAppendedNode().mergeValuesFrom(markerNode); + } + + for (String markerId : newMarkers) { + MarkerImpl marker = markers.get(markerId); + if (marker == null) continue; + + marker.save(node.getNode("marker").getAppendedNode(), true); + } + + removedMarkers.clear(); + + if (!force && !hasUnsavedChanges) return; + + node.getNode("id").setValue(this.id); + node.getNode("label").setValue(this.label); + node.getNode("toggleable").setValue(this.toggleable); + node.getNode("defaultHide").setValue(this.isDefaultHidden); + + this.hasUnsavedChanges = false; + } + +} diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/POIMarkerImpl.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/POIMarkerImpl.java new file mode 100644 index 00000000..8c5f99c6 --- /dev/null +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/POIMarkerImpl.java @@ -0,0 +1,108 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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. + */ +package de.bluecolored.bluemap.common.api.marker; + +import com.flowpowered.math.vector.Vector2i; +import com.flowpowered.math.vector.Vector3d; + +import de.bluecolored.bluemap.api.BlueMapAPI; +import de.bluecolored.bluemap.api.marker.POIMarker; +import de.bluecolored.bluemap.api.renderer.BlueMapMap; +import ninja.leaping.configurate.ConfigurationNode; + +public class POIMarkerImpl extends MarkerImpl implements POIMarker { + public static final String MARKER_TYPE = "poi"; + + private String iconAddress; + private Vector2i anchor; + + private boolean hasUnsavedChanges; + + public POIMarkerImpl(String id, BlueMapMap map, Vector3d position) { + super(id, map, position); + + this.iconAddress = "assets/poi.svg"; + this.anchor = new Vector2i(25, 45); + + this.hasUnsavedChanges = true; + } + + @Override + public String getType() { + return MARKER_TYPE; + } + + @Override + public String getIconAddress() { + return iconAddress; + } + + @Override + public Vector2i getIconAnchor() { + return anchor; + } + + @Override + public synchronized void setIcon(String iconAddress, Vector2i anchor) { + this.iconAddress = iconAddress; + this.anchor = anchor; + this.hasUnsavedChanges = true; + } + + @Override + public synchronized void load(BlueMapAPI api, ConfigurationNode markerNode) throws MarkerFileFormatException { + super.load(api, markerNode); + + this.hasUnsavedChanges = false; + + this.iconAddress = markerNode.getNode("icon").getString("assets/poi.svg"); + this.anchor = readAnchor(markerNode.getNode("iconAnchor")); + } + + @Override + public synchronized void save(ConfigurationNode markerNode, boolean force) { + super.save(markerNode, force); + + if (!force && !hasUnsavedChanges) return; + + markerNode.getNode("icon").setValue(this.iconAddress); + writeAnchor(markerNode.getNode("iconAnchor"), this.anchor); + + hasUnsavedChanges = false; + } + + private static Vector2i readAnchor(ConfigurationNode node) { + return new Vector2i( + node.getNode("x").getInt(0), + node.getNode("y").getInt(0) + ); + } + + private static void writeAnchor(ConfigurationNode node, Vector2i anchor) { + node.getNode("x").setValue(anchor.getX()); + node.getNode("y").setValue(anchor.getY()); + } + +} diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/ShapeMarkerImpl.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/ShapeMarkerImpl.java new file mode 100644 index 00000000..5235b2c2 --- /dev/null +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/marker/ShapeMarkerImpl.java @@ -0,0 +1,200 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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. + */ +package de.bluecolored.bluemap.common.api.marker; + +import java.awt.Color; +import java.util.List; + +import com.flowpowered.math.vector.Vector2d; +import com.flowpowered.math.vector.Vector3d; +import com.google.common.base.Preconditions; + +import de.bluecolored.bluemap.api.BlueMapAPI; +import de.bluecolored.bluemap.api.marker.Shape; +import de.bluecolored.bluemap.api.marker.ShapeMarker; +import de.bluecolored.bluemap.api.renderer.BlueMapMap; +import ninja.leaping.configurate.ConfigurationNode; + +public class ShapeMarkerImpl extends MarkerImpl implements ShapeMarker { + public static final String MARKER_TYPE = "shape"; + + private Shape shape; + private float height; + private Color borderColor, fillColor; + + private boolean hasUnsavedChanges; + + public ShapeMarkerImpl(String id, BlueMapMap map, Vector3d position, Shape shape, float height) { + super(id, map, position); + + Preconditions.checkNotNull(shape); + + this.shape = shape; + this.height = height; + + this.hasUnsavedChanges = true; + } + + @Override + public String getType() { + return MARKER_TYPE; + } + + @Override + public Shape getShape() { + return this.shape; + } + + @Override + public float getHeight() { + return this.height; + } + + @Override + public synchronized void setShape(Shape shape, float height) { + Preconditions.checkNotNull(shape); + + this.shape = shape; + this.height = height; + this.hasUnsavedChanges = true; + } + + @Override + public Color getBorderColor() { + return this.borderColor; + } + + @Override + public synchronized void setBorderColor(Color color) { + Preconditions.checkNotNull(color); + + this.borderColor = color; + this.hasUnsavedChanges = true; + } + + @Override + public Color getFillColor() { + return this.fillColor; + } + + @Override + public synchronized void setFillColor(Color color) { + Preconditions.checkNotNull(color); + + this.fillColor = color; + this.hasUnsavedChanges = true; + } + + @Override + public void load(BlueMapAPI api, ConfigurationNode markerNode) throws MarkerFileFormatException { + super.load(api, markerNode); + + this.shape = readShape(markerNode.getNode("icon")); + this.height = markerNode.getNode("height").getFloat(64); + this.borderColor = readColor(markerNode.getNode("borderColor")); + this.fillColor = readColor(markerNode.getNode("fillColor")); + } + + @Override + public void save(ConfigurationNode markerNode, boolean force) { + super.save(markerNode, force); + + if (!force && !hasUnsavedChanges) return; + + writeShape(markerNode.getNode("shape"), this.shape); + markerNode.getNode("height").setValue(Math.round(height * 1000f) / 1000f); + writeColor(markerNode.getNode("borderColor"), this.borderColor); + writeColor(markerNode.getNode("fillColor"), this.fillColor); + + hasUnsavedChanges = false; + } + + private Shape readShape(ConfigurationNode node) throws MarkerFileFormatException { + List posNodes = node.getChildrenList(); + + if (posNodes.size() < 3) throw new MarkerFileFormatException("Failed to read shape: point-list has fewer than 3 entries!"); + + Vector2d[] positions = new Vector2d[posNodes.size()]; + for (int i = 0; i < positions.length; i++) { + positions[i] = readShapePos(posNodes.get(i)); + } + + return new Shape(positions); + } + + private static Vector2d readShapePos(ConfigurationNode node) throws MarkerFileFormatException { + ConfigurationNode nx, nz; + nx = node.getNode("x"); + nz = node.getNode("z"); + + if (nx.isVirtual() || nz.isVirtual()) throw new MarkerFileFormatException("Failed to read shape position: Node x or z is not set!"); + + return new Vector2d( + nx.getDouble(), + nz.getDouble() + ); + } + + private static Color readColor(ConfigurationNode node) throws MarkerFileFormatException { + ConfigurationNode nr, ng, nb, na; + nr = node.getNode("r"); + ng = node.getNode("g"); + nb = node.getNode("b"); + na = node.getNode("a"); + + if (nr.isVirtual() || ng.isVirtual() || nb.isVirtual()) throw new MarkerFileFormatException("Failed to read color: Node r,g or b is not set!"); + + float alpha = na.getFloat(1); + if (alpha < 0 || alpha > 1) throw new MarkerFileFormatException("Failed to read color: alpha value out of range (0-1)!"); + + try { + return new Color(nr.getInt(), ng.getInt(), nb.getInt(), (int)(alpha * 255)); + } catch (IllegalArgumentException ex) { + throw new MarkerFileFormatException("Failed to read color: " + ex.getMessage(), ex); + } + } + + private static void writeShape(ConfigurationNode node, Shape shape) { + for (int i = 0; i < shape.getPointCount(); i++) { + ConfigurationNode pointNode = node.getAppendedNode(); + Vector2d point = shape.getPoint(i); + pointNode.getNode("x").setValue(Math.round(point.getX() * 1000d) / 1000d); + pointNode.getNode("z").setValue(Math.round(point.getY() * 1000d) / 1000d); + } + } + + private static void writeColor(ConfigurationNode node, Color color) { + int r = color.getRed(); + int g = color.getGreen(); + int b = color.getBlue(); + float a = color.getAlpha() / 255f; + + node.getNode("r").setValue(r); + node.getNode("g").setValue(g); + node.getNode("b").setValue(b); + node.getNode("a").setValue(a); + } + +} diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java index 9d77676a..2a5d9e23 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java @@ -199,6 +199,11 @@ public synchronized void load() throws IOException, ParseResourceException { MapType mapType = new MapType(id, name, world, tileRenderer); maps.put(id, mapType); } + if (maps.isEmpty()) { + Logger.global.logWarning("There are no valid maps configured, please check your config! Disabling BlueMap..."); + unload(); + return; + } //initialize render manager renderManager = new RenderManager(config.getRenderThreadCount()); @@ -293,6 +298,7 @@ public synchronized void unload() { //disable api if (api != null) api.unregister(); + api = null; //unregister listeners serverInterface.unregisterAllListeners(); diff --git a/BlueMapCore/src/main/webroot/js/libs/hud/MarkerSet.js b/BlueMapCore/src/main/webroot/js/libs/hud/MarkerSet.js index aec4becc..a3480234 100644 --- a/BlueMapCore/src/main/webroot/js/libs/hud/MarkerSet.js +++ b/BlueMapCore/src/main/webroot/js/libs/hud/MarkerSet.js @@ -6,7 +6,7 @@ export default class MarkerSet { constructor(blueMap, setData) { this.blueMap = blueMap; this.id = setData.id; - this.label = setData.label ? setData.label : this.id; + this.label = setData.label ? this.escapeHTML(setData.label) : this.id; this.toggleable = setData.toggleable !== undefined ? !!setData.toggleable : true; this.defaultHide = !!setData.defaultHide; this.marker = []; @@ -33,4 +33,8 @@ export default class MarkerSet { }); } + escapeHTML(text) { + return text.replace(/&/g,'&').replace(//g,'>'); + } + } \ No newline at end of file