diff --git a/src/MapViewer.js b/src/MapViewer.js index 5a042af..2d08521 100644 --- a/src/MapViewer.js +++ b/src/MapViewer.js @@ -27,7 +27,7 @@ import {Map} from "./map/Map"; import {SkyboxScene} from "./skybox/SkyboxScene"; import {ControlsManager} from "./controls/ControlsManager"; import Stats from "./util/Stats"; -import {alert, dispatchEvent, elementOffset, htmlToElement} from "./util/Utils"; +import {alert, dispatchEvent, elementOffset, generateCacheHash, htmlToElement} from "./util/Utils"; import {TileManager} from "./map/TileManager"; import {HIRES_VERTEX_SHADER} from "./map/hires/HiresVertexShader"; import {HIRES_FRAGMENT_SHADER} from "./map/hires/HiresFragmentShader"; @@ -73,6 +73,8 @@ export class MapViewer { loadedLowresViewDistance: 2000, } + this.tileCacheHash = generateCacheHash(); + this.stats = new Stats(); this.stats.hide(); @@ -87,7 +89,7 @@ export class MapViewer { this.renderer.uniforms = this.data.uniforms; // CSS2D renderer - this.css2dRenderer = new CSS2DRenderer(); + this.css2dRenderer = new CSS2DRenderer(this.events); this.skyboxScene = new SkyboxScene(this.data.uniforms); @@ -310,7 +312,7 @@ export class MapViewer { this.map = map; if (this.map && this.map.isMap) { - return map.load(HIRES_VERTEX_SHADER, HIRES_FRAGMENT_SHADER, LOWRES_VERTEX_SHADER, LOWRES_FRAGMENT_SHADER, this.data.uniforms) + return map.load(HIRES_VERTEX_SHADER, HIRES_FRAGMENT_SHADER, LOWRES_VERTEX_SHADER, LOWRES_FRAGMENT_SHADER, this.data.uniforms, this.tileCacheHash) .then(() => { for (let texture of this.map.loadedTextures){ this.renderer.initTexture(texture); @@ -356,6 +358,16 @@ export class MapViewer { this.map.loadMapArea(this.data.loadedCenter.x, this.data.loadedCenter.y, this.data.loadedHiresViewDistance, this.data.loadedLowresViewDistance); } + clearTileCache(newTileCacheHash) { + if (!newTileCacheHash) newTileCacheHash = generateCacheHash(); + + this.tileCacheHash = newTileCacheHash; + if (this.map) { + this.map.lowresTileManager.tileLoader.tileCacheHash = this.tileCacheHash; + this.map.hiresTileManager.tileLoader.tileCacheHash = this.tileCacheHash; + } + } + /** * @returns {number} */ diff --git a/src/controls/freeflight/FreeFlightControls.js b/src/controls/freeflight/FreeFlightControls.js index eadc9f6..3c3523e 100644 --- a/src/controls/freeflight/FreeFlightControls.js +++ b/src/controls/freeflight/FreeFlightControls.js @@ -41,14 +41,18 @@ export class FreeFlightControls { this.target = target; this.manager = null; + this.data = { + + }; + this.hammer = new Manager(this.target); this.initializeHammer(); this.keyMove = new KeyMoveControls(this.target, 0.5, 0.1); this.keyHeight = new KeyHeightControls(this.target, 0.5, 0.2); - this.mouseRotate = new MouseRotateControls(this.target, 0.002, -0.003, -0.002, 0.5); - this.mouseAngle = new MouseAngleControls(this.target, 0.002, -0.003, -0.002, 0.5); - this.touchPan = new TouchPanControls(this.hammer, 0.005, 0.15); + this.mouseRotate = new MouseRotateControls(this.target, 1.5, -2, -1.5, 0.5); + this.mouseAngle = new MouseAngleControls(this.target, 1.5, -2, -1.5, 0.5); + this.touchPan = new TouchPanControls(this.target, this.hammer, 5, 0.15); this.started = false; @@ -74,24 +78,6 @@ export class FreeFlightControls { this.target.addEventListener("mousedown", this.onMouseDown); this.target.addEventListener("mouseup", this.onMouseUp); window.addEventListener("wheel", this.onWheel, {passive: true}); - - let startOrtho = this.manager.ortho; - let startDistance = this.manager.distance; - let startAngle = this.manager.angle; - let startY = this.manager.position.y; - - let targetAngle = Math.PI / 2; - - animate(progress => { - let smoothProgress = EasingFunctions.easeInOutQuad(progress); - - this.manager.ortho = MathUtils.lerp(startOrtho, 0, progress); - this.manager.distance = MathUtils.lerp(startDistance, 0, smoothProgress); - this.manager.angle = MathUtils.lerp(startAngle, targetAngle, smoothProgress); - this.manager.position.y = MathUtils.lerp(startY, this.animationTargetHeight, smoothProgress); - }, 500, () => { - this.started = true; - }); } stop() { @@ -105,8 +91,6 @@ export class FreeFlightControls { this.target.removeEventListener("mousedown", this.onMouseDown); this.target.removeEventListener("mouseup", this.onMouseUp); window.removeEventListener("wheel", this.onWheel); - - this.started = false; } /** @@ -114,11 +98,6 @@ export class FreeFlightControls { * @param map {Map} */ update(delta, map) { - if (!this.started) { - this.animationTargetHeight = map.terrainHeightAt(this.manager.position.x, this.manager.position.z) + 10; - return; - } - this.keyMove.update(delta, map); this.keyHeight.update(delta, map); this.mouseRotate.update(delta, map); @@ -126,6 +105,8 @@ export class FreeFlightControls { this.touchPan.update(delta, map); this.manager.angle = MathUtils.clamp(this.manager.angle, 0, Math.PI); + this.manager.distance = 0; + this.manager.ortho = 0; } initializeHammer() { @@ -145,7 +126,10 @@ export class FreeFlightControls { if (Math.abs(this.clickStart.x - evt.x) > 5) return; if (Math.abs(this.clickStart.y - evt.y) > 5) return; - this.target.requestPointerLock(); + document.body.requestFullscreen() + .finally(() => { + this.target.requestPointerLock(); + }); } onWheel = evt => { diff --git a/src/controls/freeflight/mouse/MouseAngleControls.js b/src/controls/freeflight/mouse/MouseAngleControls.js index e38f507..082d9ff 100644 --- a/src/controls/freeflight/mouse/MouseAngleControls.js +++ b/src/controls/freeflight/mouse/MouseAngleControls.js @@ -46,6 +46,9 @@ export class MouseAngleControls { this.speedRight = speedRight; this.speedCapture = speedCapture; this.stiffness = stiffness; + + this.pixelToSpeedMultiplier = 0; + this.updatePixelToSpeedMultiplier(); } /** @@ -57,12 +60,16 @@ export class MouseAngleControls { this.target.addEventListener("mousedown", this.onMouseDown); window.addEventListener("mousemove", this.onMouseMove); window.addEventListener("mouseup", this.onMouseUp); + + window.addEventListener("resize", this.updatePixelToSpeedMultiplier); } stop() { this.target.removeEventListener("mousedown", this.onMouseDown); window.removeEventListener("mousemove", this.onMouseMove); window.removeEventListener("mouseup", this.onMouseUp); + + window.removeEventListener("resize", this.updatePixelToSpeedMultiplier); } /** @@ -103,14 +110,14 @@ export class MouseAngleControls { */ onMouseMove = evt => { if (document.pointerLockElement) { - this.deltaAngle += evt.movementY * this.speedCapture; + this.deltaAngle += evt.movementY * this.speedCapture * this.pixelToSpeedMultiplier; } else if(this.moving){ if (evt.buttons === 1) { - this.deltaAngle += (evt.y - this.lastY) * this.speedLeft; + this.deltaAngle += (evt.y - this.lastY) * this.speedLeft * this.pixelToSpeedMultiplier; } else { - this.deltaAngle += (evt.y - this.lastY) * this.speedRight; + this.deltaAngle += (evt.y - this.lastY) * this.speedRight * this.pixelToSpeedMultiplier; } } @@ -125,4 +132,8 @@ export class MouseAngleControls { this.moving = false; } + updatePixelToSpeedMultiplier = () => { + this.pixelToSpeedMultiplier = 1 / this.target.clientHeight; + } + } \ No newline at end of file diff --git a/src/controls/freeflight/mouse/MouseRotateControls.js b/src/controls/freeflight/mouse/MouseRotateControls.js index d171260..94d04e9 100644 --- a/src/controls/freeflight/mouse/MouseRotateControls.js +++ b/src/controls/freeflight/mouse/MouseRotateControls.js @@ -28,7 +28,7 @@ import {MathUtils} from "three"; export class MouseRotateControls { /** - * @param target {EventTarget} + * @param target {Element} * @param speedLeft {number} * @param speedRight {number} * @param speedCapture {number} @@ -46,6 +46,9 @@ export class MouseRotateControls { this.speedRight = speedRight; this.speedCapture = speedCapture; this.stiffness = stiffness; + + this.pixelToSpeedMultiplier = 0; + this.updatePixelToSpeedMultiplier(); } /** @@ -57,12 +60,16 @@ export class MouseRotateControls { this.target.addEventListener("mousedown", this.onMouseDown); window.addEventListener("mousemove", this.onMouseMove); window.addEventListener("mouseup", this.onMouseUp); + + window.addEventListener("resize", this.updatePixelToSpeedMultiplier); } stop() { this.target.removeEventListener("mousedown", this.onMouseDown); window.removeEventListener("mousemove", this.onMouseMove); window.removeEventListener("mouseup", this.onMouseUp); + + window.removeEventListener("resize", this.updatePixelToSpeedMultiplier); } /** @@ -103,14 +110,14 @@ export class MouseRotateControls { */ onMouseMove = evt => { if (document.pointerLockElement) { - this.deltaRotation -= evt.movementX * this.speedCapture; + this.deltaRotation -= evt.movementX * this.speedCapture * this.pixelToSpeedMultiplier; } else if(this.moving){ if (evt.buttons === 1) { - this.deltaRotation -= (evt.x - this.lastX) * this.speedLeft; + this.deltaRotation -= (evt.x - this.lastX) * this.speedLeft * this.pixelToSpeedMultiplier; } else { - this.deltaRotation -= (evt.x - this.lastX) * this.speedRight; + this.deltaRotation -= (evt.x - this.lastX) * this.speedRight * this.pixelToSpeedMultiplier; } } @@ -125,4 +132,8 @@ export class MouseRotateControls { this.moving = false; } + updatePixelToSpeedMultiplier = () => { + this.pixelToSpeedMultiplier = (1 / this.target.clientWidth) * (this.target.clientWidth / this.target.clientHeight); + } + } \ No newline at end of file diff --git a/src/controls/freeflight/touch/TouchPanControls.js b/src/controls/freeflight/touch/TouchPanControls.js index 2ba513e..7c9b5f9 100644 --- a/src/controls/freeflight/touch/TouchPanControls.js +++ b/src/controls/freeflight/touch/TouchPanControls.js @@ -30,11 +30,13 @@ export class TouchPanControls { static tempVec2_1 = new Vector2(); /** + * @param target {Element} * @param hammer {Manager} * @param speed {number} * @param stiffness {number} */ - constructor(hammer, speed, stiffness) { + constructor(target, hammer, speed, stiffness) { + this.target = target; this.hammer = hammer; this.manager = null; @@ -44,6 +46,10 @@ export class TouchPanControls { this.speed = speed; this.stiffness = stiffness; + + this.pixelToSpeedMultiplierX = 0; + this.pixelToSpeedMultiplierY = 0; + this.updatePixelToSpeedMultiplier(); } /** @@ -56,6 +62,8 @@ export class TouchPanControls { this.hammer.on("movemove", this.onTouchMove); this.hammer.on("moveend", this.onTouchUp); this.hammer.on("movecancel", this.onTouchUp); + + window.addEventListener("resize", this.updatePixelToSpeedMultiplier); } stop() { @@ -63,6 +71,8 @@ export class TouchPanControls { this.hammer.off("movemove", this.onTouchMove); this.hammer.off("moveend", this.onTouchUp); this.hammer.off("movecancel", this.onTouchUp); + + window.removeEventListener("resize", this.updatePixelToSpeedMultiplier); } /** @@ -75,8 +85,8 @@ export class TouchPanControls { let smoothing = this.stiffness / (16.666 / delta); smoothing = MathUtils.clamp(smoothing, 0, 1); - this.manager.rotation += this.deltaPosition.x * this.speed * this.stiffness; - this.manager.angle -= this.deltaPosition.y * this.speed * this.stiffness; + this.manager.rotation += this.deltaPosition.x * this.speed * this.pixelToSpeedMultiplierX * this.stiffness; + this.manager.angle -= this.deltaPosition.y * this.speed * this.pixelToSpeedMultiplierY * this.stiffness; this.deltaPosition.multiplyScalar(1 - smoothing); if (this.deltaPosition.lengthSq() < 0.0001) { @@ -126,4 +136,9 @@ export class TouchPanControls { this.moving = false; } + updatePixelToSpeedMultiplier = () => { + this.pixelToSpeedMultiplierX = (1 / this.target.clientWidth) * (this.target.clientWidth / this.target.clientHeight); + this.pixelToSpeedMultiplierY = 1 / this.target.clientHeight; + } + } \ No newline at end of file diff --git a/src/controls/map/MapControls.js b/src/controls/map/MapControls.js index 1b2ee24..4b0d23a 100644 --- a/src/controls/map/MapControls.js +++ b/src/controls/map/MapControls.js @@ -27,9 +27,9 @@ import {MouseMoveControls} from "./mouse/MouseMoveControls"; import {MouseZoomControls} from "./mouse/MouseZoomControls"; import {MouseRotateControls} from "./mouse/MouseRotateControls"; import {MouseAngleControls} from "./mouse/MouseAngleControls"; -import {MathUtils, Vector2} from "three"; +import {MathUtils, Vector2, Vector3} from "three"; import {Manager, Pan, Pinch, Rotate, Tap, DIRECTION_ALL, DIRECTION_VERTICAL} from "hammerjs"; -import {animate, EasingFunctions, softClamp} from "../../util/Utils"; +import {softClamp} from "../../util/Utils"; import {MapHeightControls} from "./MapHeightControls"; import {KeyMoveControls} from "./keyboard/KeyMoveControls"; import {KeyAngleControls} from "./keyboard/KeyAngleControls"; @@ -39,29 +39,34 @@ import {TouchMoveControls} from "./touch/TouchMoveControls"; import {TouchRotateControls} from "./touch/TouchRotateControls"; import {TouchAngleControls} from "./touch/TouchAngleControls"; import {TouchZoomControls} from "./touch/TouchZoomControls"; +import {PlayerMarker} from "../../markers/PlayerMarker"; const HALF_PI = Math.PI * 0.5; export class MapControls { + static _beforeMoveTemp = new Vector3(); + /** - * @param rootElement {EventTarget} + * @param rootElement {Element} */ constructor(rootElement) { this.rootElement = rootElement; + this.data = { + followingPlayer: null + }; + /** @type {ControlsManager} */ this.manager = null; - this.started = false; - this.hammer = new Manager(this.rootElement); this.initializeHammer(); //controls - this.mouseMove = new MouseMoveControls(this.rootElement, 0.002,0.3); - this.mouseRotate = new MouseRotateControls(this.rootElement, 0.004, 0.3); - this.mouseAngle = new MouseAngleControls(this.rootElement, 0.004, 0.3); + this.mouseMove = new MouseMoveControls(this.rootElement, 1.5,0.3); + this.mouseRotate = new MouseRotateControls(this.rootElement, 6, 0.3); + this.mouseAngle = new MouseAngleControls(this.rootElement, 3, 0.3); this.mouseZoom = new MouseZoomControls(this.rootElement, 1, 0.2); this.keyMove = new KeyMoveControls(this.rootElement, 0.025, 0.2); @@ -69,14 +74,15 @@ export class MapControls { this.keyAngle = new KeyAngleControls(this.rootElement, 0.04, 0.15); this.keyZoom = new KeyZoomControls(this.rootElement, 0.2, 0.15); - this.touchMove = new TouchMoveControls(this.hammer, 0.002,0.3); + this.touchMove = new TouchMoveControls(this.rootElement, this.hammer, 1.5,0.3); this.touchRotate = new TouchRotateControls(this.hammer, 0.0174533, 0.3); - this.touchAngle = new TouchAngleControls(this.hammer, 0.01, 0.3); + this.touchAngle = new TouchAngleControls(this.rootElement, this.hammer, 3, 0.3); this.touchZoom = new TouchZoomControls(this.hammer); this.mapHeight = new MapHeightControls(0.2, 0.1); - this.animationTargetHeight = 0; + this.lastTap = -1; + this.lastTapCenter = null; } /** @@ -104,27 +110,11 @@ export class MapControls { this.touchZoom.start(manager); this.mapHeight.start(manager); - - - let startOrtho = this.manager.ortho; - let startDistance = this.manager.distance; - let startAngle = this.manager.angle; - let startY = this.manager.position.y; - - let targetDistance = MathUtils.clamp(this.manager.distance, 100, 10000); - let targetAngle = Math.min(startAngle, this.getMaxPerspectiveAngleForDistance(targetDistance)); - - animate(progress => { - let smoothProgress = EasingFunctions.easeInOutQuad(progress); - - this.manager.ortho = MathUtils.lerp(startOrtho, 0, progress); - this.manager.distance = MathUtils.lerp(startDistance, targetDistance, smoothProgress); - this.manager.angle = MathUtils.lerp(startAngle, targetAngle, smoothProgress); - this.manager.position.y = MathUtils.lerp(startY, this.animationTargetHeight, smoothProgress); - }, 500, () => this.started = true); } stop() { + this.stopFollowingPlayerMarker(); + this.rootElement.removeEventListener("contextmenu", this.onContextMenu); this.hammer.off("tap", this.onTap); @@ -144,8 +134,6 @@ export class MapControls { this.touchZoom.stop(); this.mapHeight.stop(); - - this.started = false; } /** @@ -153,18 +141,27 @@ export class MapControls { * @param map {Map} */ update(delta, map) { - if (!this.started){ - this.mapHeight.updateHeights(delta, map); - this.animationTargetHeight = this.mapHeight.getSuggestedHeight(); - return; + this.manager.position.y = 0; // reset target y position + + // move + MapControls._beforeMoveTemp.copy(this.manager.position); + this.mouseMove.update(delta, map); + this.keyMove.update(delta, map); + this.touchMove.update(delta, map); + + // if moved, stop following the marker and give back control + if (this.data.followingPlayer && !MapControls._beforeMoveTemp.equals(this.manager.position)) { + this.stopFollowingPlayerMarker(); } - // move and zoom - this.mouseMove.update(delta, map); + // follow player marker + if (this.data.followingPlayer) { + this.manager.position.copy(this.data.followingPlayer.position); + } + + // zoom this.mouseZoom.update(delta, map); - this.keyMove.update(delta, map); this.keyZoom.update(delta, map); - this.touchMove.update(delta, map); this.touchZoom.update(delta, map); this.manager.distance = softClamp(this.manager.distance, 5, 10000, 0.8); @@ -189,7 +186,6 @@ export class MapControls { // target height if (this.manager.ortho === 0 || this.manager.angle === 0) { - this.manager.position.y = 0; this.mapHeight.maxAngle = maxAngleForZoom; this.mapHeight.update(delta, map); } @@ -211,38 +207,6 @@ export class MapControls { return MathUtils.clamp((1 - Math.pow(Math.max(distance - 5, 0.001) / 500, 0.5)) * HALF_PI,0, HALF_PI) } - setPerspectiveView() { - this.reset(); - - let startOrtho = this.manager.ortho; - let startAngle = this.manager.angle; - - let targetAngle = Math.min(startAngle, this.getMaxPerspectiveAngleForDistance(this.manager.distance)); - - animate(progress => { - let smoothProgress = EasingFunctions.easeInOutQuad(progress); - - this.manager.ortho = MathUtils.lerp(startOrtho, 0, progress); - this.manager.angle = MathUtils.lerp(startAngle, targetAngle, smoothProgress); - }, 500); - } - - setOrthographicView(targetRotation = 0, targetAngle = 0) { - this.reset(); - - let startOrtho = this.manager.ortho; - let startAngle = this.manager.angle; - let startRotation = this.manager.rotation; - - animate(progress => { - let smoothProgress = EasingFunctions.easeInOutQuad(progress); - - this.manager.ortho = MathUtils.lerp(startOrtho, 1, progress); - this.manager.angle = MathUtils.lerp(startAngle, targetAngle, smoothProgress); - this.manager.rotation = MathUtils.lerp(startRotation, targetRotation, smoothProgress); - }, 500); - } - initializeHammer() { let touchTap = new Tap({ event: 'tap', pointers: 1, taps: 1, threshold: 5 }); let touchMove = new Pan({ event: 'move', pointers: 1, direction: DIRECTION_ALL, threshold: 0 }); @@ -264,12 +228,36 @@ export class MapControls { this.hammer.add(touchZoom); } + /** + * @param marker {object} + */ + followPlayerMarker(marker) { + if (marker.isPlayerMarker) marker = marker.data; + this.data.followingPlayer = marker; + } + + stopFollowingPlayerMarker() { + this.data.followingPlayer = null; + } + onContextMenu = evt => { evt.preventDefault(); } onTap = evt => { - this.manager.handleMapInteraction(new Vector2(evt.center.x, evt.center.y)); + let doubleTap = false; + let center = new Vector2(evt.center.x, evt.center.y); + + let now = Date.now(); + if (this.lastTap > 0 && this.lastTapCenter && now - this.lastTap < 500 && this.lastTapCenter.distanceTo(center) < 5){ + doubleTap = true; + this.lastTap = -1; + } else { + this.lastTap = now; + this.lastTapCenter = center; + } + + this.manager.handleMapInteraction(new Vector2(evt.center.x, evt.center.y), {doubleTap: doubleTap}); } } \ No newline at end of file diff --git a/src/controls/map/mouse/MouseAngleControls.js b/src/controls/map/mouse/MouseAngleControls.js index ae9066f..467ed3e 100644 --- a/src/controls/map/mouse/MouseAngleControls.js +++ b/src/controls/map/mouse/MouseAngleControls.js @@ -42,6 +42,9 @@ export class MouseAngleControls { this.speed = speed; this.stiffness = stiffness; + + this.pixelToSpeedMultiplierY = 0; + this.updatePixelToSpeedMultiplier(); } /** @@ -53,12 +56,16 @@ export class MouseAngleControls { this.target.addEventListener("mousedown", this.onMouseDown); window.addEventListener("mousemove", this.onMouseMove); window.addEventListener("mouseup", this.onMouseUp); + + window.addEventListener("resize", this.updatePixelToSpeedMultiplier); } stop() { this.target.removeEventListener("mousedown", this.onMouseDown); window.removeEventListener("mousemove", this.onMouseMove); window.removeEventListener("mouseup", this.onMouseUp); + + window.removeEventListener("resize", this.updatePixelToSpeedMultiplier); } /** @@ -71,7 +78,7 @@ export class MouseAngleControls { let smoothing = this.stiffness / (16.666 / delta); smoothing = MathUtils.clamp(smoothing, 0, 1); - this.manager.angle += this.deltaAngle * smoothing * this.speed; + this.manager.angle += this.deltaAngle * smoothing * this.speed * this.pixelToSpeedMultiplierY; this.deltaAngle *= 1 - smoothing; if (Math.abs(this.deltaAngle) < 0.0001) { @@ -116,4 +123,8 @@ export class MouseAngleControls { this.moving = false; } + updatePixelToSpeedMultiplier = () => { + this.pixelToSpeedMultiplierY = 1 / this.target.clientHeight; + } + } \ No newline at end of file diff --git a/src/controls/map/mouse/MouseMoveControls.js b/src/controls/map/mouse/MouseMoveControls.js index f47de1b..25b02ae 100644 --- a/src/controls/map/mouse/MouseMoveControls.js +++ b/src/controls/map/mouse/MouseMoveControls.js @@ -31,7 +31,7 @@ export class MouseMoveControls { static tempVec2_1 = new Vector2(); /** - * @param target {EventTarget} + * @param target {Element} * @param speed {number} * @param stiffness {number} */ @@ -45,6 +45,10 @@ export class MouseMoveControls { this.speed = speed; this.stiffness = stiffness; + + this.pixelToSpeedMultiplierX = 0; + this.pixelToSpeedMultiplierY = 0; + this.updatePixelToSpeedMultiplier(); } /** @@ -56,12 +60,16 @@ export class MouseMoveControls { this.target.addEventListener("mousedown", this.onMouseDown); window.addEventListener("mousemove", this.onMouseMove); window.addEventListener("mouseup", this.onMouseUp); + + window.addEventListener("resize", this.updatePixelToSpeedMultiplier); } stop() { this.target.removeEventListener("mousedown", this.onMouseDown); window.removeEventListener("mousemove", this.onMouseMove); window.removeEventListener("mouseup", this.onMouseUp); + + window.removeEventListener("resize", this.updatePixelToSpeedMultiplier); } /** @@ -77,8 +85,8 @@ export class MouseMoveControls { let directionDelta = MouseMoveControls.tempVec2_1.copy(this.deltaPosition); directionDelta.rotateAround(VEC2_ZERO, this.manager.rotation); - this.manager.position.x += directionDelta.x * smoothing * this.manager.distance * this.speed; - this.manager.position.z += directionDelta.y * smoothing * this.manager.distance * this.speed; + this.manager.position.x += directionDelta.x * smoothing * this.manager.distance * this.speed * this.pixelToSpeedMultiplierX; + this.manager.position.z += directionDelta.y * smoothing * this.manager.distance * this.speed * this.pixelToSpeedMultiplierY; this.deltaPosition.multiplyScalar(1 - smoothing); if (this.deltaPosition.lengthSq() < 0.0001) { @@ -124,4 +132,9 @@ export class MouseMoveControls { if (evt.button === 0) this.moving = false; } + updatePixelToSpeedMultiplier = () => { + this.pixelToSpeedMultiplierX = (1 / this.target.clientWidth) * (this.target.clientWidth / this.target.clientHeight); + this.pixelToSpeedMultiplierY = 1 / this.target.clientHeight; + } + } \ No newline at end of file diff --git a/src/controls/map/mouse/MouseRotateControls.js b/src/controls/map/mouse/MouseRotateControls.js index 4ac89e8..b6e17d8 100644 --- a/src/controls/map/mouse/MouseRotateControls.js +++ b/src/controls/map/mouse/MouseRotateControls.js @@ -42,6 +42,9 @@ export class MouseRotateControls { this.speed = speed; this.stiffness = stiffness; + + this.pixelToSpeedMultiplierX = 0; + this.updatePixelToSpeedMultiplier(); } /** @@ -53,12 +56,16 @@ export class MouseRotateControls { this.target.addEventListener("mousedown", this.onMouseDown); window.addEventListener("mousemove", this.onMouseMove); window.addEventListener("mouseup", this.onMouseUp); + + window.addEventListener("resize", this.updatePixelToSpeedMultiplier); } stop() { this.target.removeEventListener("mousedown", this.onMouseDown); window.removeEventListener("mousemove", this.onMouseMove); window.removeEventListener("mouseup", this.onMouseUp); + + window.removeEventListener("resize", this.updatePixelToSpeedMultiplier); } /** @@ -71,7 +78,7 @@ export class MouseRotateControls { let smoothing = this.stiffness / (16.666 / delta); smoothing = MathUtils.clamp(smoothing, 0, 1); - this.manager.rotation += this.deltaRotation * smoothing * this.speed; + this.manager.rotation += this.deltaRotation * smoothing * this.speed * this.pixelToSpeedMultiplierX; this.deltaRotation *= 1 - smoothing; if (Math.abs(this.deltaRotation) < 0.0001) { @@ -116,4 +123,8 @@ export class MouseRotateControls { this.moving = false; } + updatePixelToSpeedMultiplier = () => { + this.pixelToSpeedMultiplierX = (1 / this.target.clientWidth); //* (this.target.clientWidth / this.target.clientHeight); + } + } \ No newline at end of file diff --git a/src/controls/map/touch/TouchAngleControls.js b/src/controls/map/touch/TouchAngleControls.js index c07cab4..8d37606 100644 --- a/src/controls/map/touch/TouchAngleControls.js +++ b/src/controls/map/touch/TouchAngleControls.js @@ -28,11 +28,13 @@ import {MathUtils} from "three"; export class TouchAngleControls { /** + * @param target {Element} * @param hammer {Manager} * @param speed {number} * @param stiffness {number} */ - constructor(hammer, speed, stiffness) { + constructor(target, hammer, speed, stiffness) { + this.target = target; this.hammer = hammer; this.manager = null; @@ -42,6 +44,9 @@ export class TouchAngleControls { this.speed = speed; this.stiffness = stiffness; + + this.pixelToSpeedMultiplierY = 0; + this.updatePixelToSpeedMultiplier(); } /** @@ -54,6 +59,8 @@ export class TouchAngleControls { this.hammer.on("tiltmove", this.onTouchMove); this.hammer.on("tiltend", this.onTouchUp); this.hammer.on("tiltcancel", this.onTouchUp); + + window.addEventListener("resize", this.updatePixelToSpeedMultiplier); } stop() { @@ -61,6 +68,8 @@ export class TouchAngleControls { this.hammer.off("tiltmove", this.onTouchMove); this.hammer.off("tiltend", this.onTouchUp); this.hammer.off("tiltcancel", this.onTouchUp); + + window.removeEventListener("resize", this.updatePixelToSpeedMultiplier); } /** @@ -73,7 +82,7 @@ export class TouchAngleControls { let smoothing = this.stiffness / (16.666 / delta); smoothing = MathUtils.clamp(smoothing, 0, 1); - this.manager.angle += this.deltaAngle * smoothing * this.speed; + this.manager.angle += this.deltaAngle * smoothing * this.speed * this.pixelToSpeedMultiplierY; this.deltaAngle *= 1 - smoothing; if (Math.abs(this.deltaAngle) < 0.0001) { @@ -115,4 +124,8 @@ export class TouchAngleControls { this.moving = false; } + updatePixelToSpeedMultiplier = () => { + this.pixelToSpeedMultiplierY = 1 / this.target.clientHeight; + } + } \ No newline at end of file diff --git a/src/controls/map/touch/TouchMoveControls.js b/src/controls/map/touch/TouchMoveControls.js index e4d730c..336e7d1 100644 --- a/src/controls/map/touch/TouchMoveControls.js +++ b/src/controls/map/touch/TouchMoveControls.js @@ -31,11 +31,13 @@ export class TouchMoveControls { static tempVec2_1 = new Vector2(); /** + * @param target {Element} * @param hammer {Manager} * @param speed {number} * @param stiffness {number} */ - constructor(hammer, speed, stiffness) { + constructor(target, hammer, speed, stiffness) { + this.target = target; this.hammer = hammer; this.manager = null; @@ -45,6 +47,10 @@ export class TouchMoveControls { this.speed = speed; this.stiffness = stiffness; + + this.pixelToSpeedMultiplierX = 0; + this.pixelToSpeedMultiplierY = 0; + this.updatePixelToSpeedMultiplier(); } /** @@ -57,6 +63,8 @@ export class TouchMoveControls { this.hammer.on("movemove", this.onTouchMove); this.hammer.on("moveend", this.onTouchUp); this.hammer.on("movecancel", this.onTouchUp); + + window.addEventListener("resize", this.updatePixelToSpeedMultiplier); } stop() { @@ -64,6 +72,8 @@ export class TouchMoveControls { this.hammer.off("movemove", this.onTouchMove); this.hammer.off("moveend", this.onTouchUp); this.hammer.off("movecancel", this.onTouchUp); + + window.removeEventListener("resize", this.updatePixelToSpeedMultiplier); } /** @@ -79,8 +89,8 @@ export class TouchMoveControls { let directionDelta = TouchMoveControls.tempVec2_1.copy(this.deltaPosition); directionDelta.rotateAround(VEC2_ZERO, this.manager.rotation); - this.manager.position.x += directionDelta.x * smoothing * this.manager.distance * this.speed; - this.manager.position.z += directionDelta.y * smoothing * this.manager.distance * this.speed; + this.manager.position.x += directionDelta.x * smoothing * this.manager.distance * this.speed * this.pixelToSpeedMultiplierX; + this.manager.position.z += directionDelta.y * smoothing * this.manager.distance * this.speed * this.pixelToSpeedMultiplierY; this.deltaPosition.multiplyScalar(1 - smoothing); if (this.deltaPosition.lengthSq() < 0.0001) { @@ -130,4 +140,9 @@ export class TouchMoveControls { this.moving = false; } + updatePixelToSpeedMultiplier = () => { + this.pixelToSpeedMultiplierX = (1 / this.target.clientWidth) * (this.target.clientWidth / this.target.clientHeight); + this.pixelToSpeedMultiplierY = 1 / this.target.clientHeight; + } + } \ No newline at end of file diff --git a/src/map/Map.js b/src/map/Map.js index 75ed9cd..b83cc51 100644 --- a/src/map/Map.js +++ b/src/map/Map.js @@ -27,7 +27,7 @@ import { FileLoader, FrontSide, NearestFilter, NearestMipMapLinearFilter, Raycaster, Scene, ShaderMaterial, Texture, Vector2, Vector3, VertexColors } from "three"; -import {alert, dispatchEvent, hashTile, stringToImage} from "../util/Utils"; +import {alert, dispatchEvent, generateCacheHash, hashTile, stringToImage} from "../util/Utils"; import {TileManager} from "./TileManager"; import {TileLoader} from "./TileLoader"; import {MarkerFileManager} from "../markers/MarkerFileManager"; @@ -42,7 +42,7 @@ export class Map { * @param texturesUrl {string} * @param events {EventTarget} */ - constructor(id, dataUrl, settingsUrl, texturesUrl, events = null) { + constructor(id, dataUrl, settingsUrl, texturesUrl, events = null) { Object.defineProperty( this, 'isMap', { value: true } ); this.events = events; @@ -91,9 +91,10 @@ export class Map { * @param lowresVertexShader {string} * @param lowresFragmentShader {string} * @param uniforms {object} + * @param tileCacheHash {number} * @returns {Promise} */ - load(hiresVertexShader, hiresFragmentShader, lowresVertexShader, lowresFragmentShader, uniforms) { + load(hiresVertexShader, hiresFragmentShader, lowresVertexShader, lowresFragmentShader, uniforms, tileCacheHash = 0) { this.unload() let settingsPromise = this.loadSettings(); @@ -108,8 +109,8 @@ export class Map { this.hiresMaterial = this.createHiresMaterial(hiresVertexShader, hiresFragmentShader, uniforms, textures); - this.hiresTileManager = new TileManager(new Scene(), new TileLoader(`${this.data.dataUrl}hires/`, this.hiresMaterial, this.data.hires), this.onTileLoad("hires"), this.onTileUnload("hires"), this.events); - this.lowresTileManager = new TileManager(new Scene(), new TileLoader(`${this.data.dataUrl}lowres/`, this.lowresMaterial, this.data.lowres), this.onTileLoad("lowres"), this.onTileUnload("lowres"), this.events); + this.hiresTileManager = new TileManager(new Scene(), new TileLoader(`${this.data.dataUrl}hires/`, this.hiresMaterial, this.data.hires, tileCacheHash), this.onTileLoad("hires"), this.onTileUnload("hires"), this.events); + this.lowresTileManager = new TileManager(new Scene(), new TileLoader(`${this.data.dataUrl}lowres/`, this.lowresMaterial, this.data.lowres, tileCacheHash), this.onTileLoad("lowres"), this.onTileUnload("lowres"), this.events); this.hiresTileManager.scene.autoUpdate = false; this.lowresTileManager.scene.autoUpdate = false; @@ -201,7 +202,7 @@ export class Map { let loader = new FileLoader(); loader.setResponseType("json"); - loader.load(this.data.settingsUrl, + loader.load(this.data.settingsUrl + "?" + generateCacheHash(), settings => { if (settings.maps && settings.maps[this.data.id]) { resolve(settings.maps[this.data.id]); @@ -225,7 +226,7 @@ export class Map { let loader = new FileLoader(); loader.setResponseType("json"); - loader.load(this.data.texturesUrl, + loader.load(this.data.texturesUrl + "?" + generateCacheHash(), resolve, () => {}, () => reject(`Failed to load the textures.json for map: ${this.data.id}`) diff --git a/src/map/TileLoader.js b/src/map/TileLoader.js index 69b39bd..ac71fc7 100644 --- a/src/map/TileLoader.js +++ b/src/map/TileLoader.js @@ -31,13 +31,14 @@ export class TileLoader { * @param tilePath {string} * @param material {THREE.Material | THREE.Material[]} * @param tileSettings {{ - * tileSize: {x: number, z: number}, - * scale: {x: number, z: number}, + * tileSize: {x: number, z: number}, + * scale: {x: number, z: number}, * translate: {x: number, z: number} * }} + * @param tileCacheHash {number} * @param layer {number} */ - constructor(tilePath, material, tileSettings, layer = 0) { + constructor(tilePath, material, tileSettings, tileCacheHash = 0, layer = 0) { Object.defineProperty( this, 'isTileLoader', { value: true } ); this.tilePath = tilePath; @@ -46,6 +47,8 @@ export class TileLoader { this.layer = layer; + this.tileCacheHash = tileCacheHash; + this.fileLoader = new FileLoader(); this.fileLoader.setResponseType('json'); @@ -53,8 +56,10 @@ export class TileLoader { } load = (tileX, tileZ) => { + let tileUrl = this.tilePath + pathFromCoords(tileX, tileZ) + '.json'; + return new Promise((resolve, reject) => { - this.fileLoader.load(this.tilePath + pathFromCoords(tileX, tileZ) + '.json', + this.fileLoader.load(tileUrl + '?' + this.tileCacheHash, geometryJson => { if (!geometryJson.type || geometryJson.type !== 'BufferGeometry') reject({status: "empty"}); @@ -69,7 +74,7 @@ export class TileLoader { object.position.set(tileX * tileSize.x + translate.x, 0, tileZ * tileSize.z + translate.z); object.scale.set(scale.x, 1, scale.z); - object.userData.tileUrl = this.tilePath + pathFromCoords(tileX, tileZ) + '.json'; + object.userData.tileUrl = tileUrl; object.updateMatrixWorld(true); diff --git a/src/markers/MarkerManager.js b/src/markers/MarkerManager.js index c2a1a2a..e717251 100644 --- a/src/markers/MarkerManager.js +++ b/src/markers/MarkerManager.js @@ -24,7 +24,7 @@ */ import {FileLoader, Scene} from "three"; import {MarkerSet} from "./MarkerSet"; -import {alert} from "../util/Utils"; +import {alert, generateCacheHash} from "../util/Utils"; /** * A manager for loading and updating markers from a file @@ -170,7 +170,7 @@ export class MarkerManager { return new Promise((resolve, reject) => { let loader = new FileLoader(); loader.setResponseType("json"); - loader.load(this.fileUrl, + loader.load(this.fileUrl + "?" + generateCacheHash(), markerFileData => { if (!markerFileData) reject(`Failed to parse '${this.fileUrl}'!`); else resolve(markerFileData); diff --git a/src/markers/ObjectMarker.js b/src/markers/ObjectMarker.js index dc28d69..4bf73b2 100644 --- a/src/markers/ObjectMarker.js +++ b/src/markers/ObjectMarker.js @@ -41,6 +41,8 @@ export class ObjectMarker extends Marker { this.data.detail = null; this.data.link = null; this.data.newTab = true; + + this.lastClick = -1; } onClick(event) { @@ -50,6 +52,8 @@ export class ObjectMarker extends Marker { pos.sub(this.position); } + if (event.data.doubleTap) return false; + if (this.data.detail || this.data.label) { let popup = new LabelPopup(this.data.detail || this.data.label); popup.position.copy(pos); diff --git a/src/markers/PlayerMarker.js b/src/markers/PlayerMarker.js index 0d85bb7..c682b89 100644 --- a/src/markers/PlayerMarker.js +++ b/src/markers/PlayerMarker.js @@ -40,6 +40,8 @@ export class PlayerMarker extends Marker { this.data.playerUuid = playerUuid; this.data.name = playerUuid; + this.data.world = "?"; + this.elementObject = new CSS2DObject(htmlToElement(`
playerhead @@ -135,6 +137,9 @@ export class PlayerMarker extends Marker { if (this.playerNameElement.innerHTML !== name) this.playerNameElement.innerHTML = name; + // update world + this.data.world = markerData.world || "?"; + } dispose() { diff --git a/src/markers/PoiMarker.js b/src/markers/PoiMarker.js index f2925ca..8837735 100644 --- a/src/markers/PoiMarker.js +++ b/src/markers/PoiMarker.js @@ -43,11 +43,13 @@ export class PoiMarker extends HtmlMarker { } onClick(event) { + if (event.data.doubleTap) return false; + if (this.highlight || !this.data.label) return true; this.highlight = true; let eventHandler = evt => { - if (evt.path.includes(this.element)) return; + if (evt.composedPath().includes(this.element)) return; this.highlight = false; diff --git a/src/util/CSS2DRenderer.js b/src/util/CSS2DRenderer.js index d394cbc..d264336 100644 --- a/src/util/CSS2DRenderer.js +++ b/src/util/CSS2DRenderer.js @@ -9,7 +9,7 @@ import { Object3D, Vector2, Vector3 } from "three"; -import {htmlToElement} from "./Utils"; +import {dispatchEvent, htmlToElement} from "./Utils"; var CSS2DObject = function ( element ) { @@ -24,6 +24,8 @@ var CSS2DObject = function ( element ) { this.anchor = new Vector2(); + this.events = null; + this.addEventListener( 'removed', function () { this.traverse( function ( object ) { @@ -38,18 +40,33 @@ var CSS2DObject = function ( element ) { } ); - this.element.addEventListener("click", event => { - if (this.onClick(event)) { + let lastClick = -1; + let handleClick = event => { + let doubleTap = false; + + let now = Date.now(); + if (now - lastClick < 500){ + doubleTap = true; + } + + lastClick = now; + + let data = {doubleTap: doubleTap}; + + if (this.onClick( {event: event, data: data} )) { event.preventDefault(); event.stopPropagation(); + } else { + // fire event + dispatchEvent(this.events, "bluemapMapInteraction", { + data: data, + object: this, + }); } - }); - this.element.addEventListener("touch", event => { - if (this.onClick(event)) { - event.preventDefault(); - event.stopPropagation(); - } - }); + } + + this.element.addEventListener("click", handleClick); + this.element.addEventListener("touch", handleClick); }; @@ -58,7 +75,7 @@ CSS2DObject.prototype.constructor = CSS2DObject; // -var CSS2DRenderer = function () { +var CSS2DRenderer = function (events = null) { var _this = this; @@ -78,6 +95,8 @@ var CSS2DRenderer = function () { this.domElement = domElement; + this.events = events; + this.getSize = function () { return { @@ -104,6 +123,8 @@ var CSS2DRenderer = function () { if ( object instanceof CSS2DObject ) { + object.events = _this.events; + object.onBeforeRender( _this, scene, camera ); vector.setFromMatrixPosition( object.matrixWorld ); diff --git a/src/util/Utils.js b/src/util/Utils.js index fb88950..129358c 100644 --- a/src/util/Utils.js +++ b/src/util/Utils.js @@ -89,6 +89,9 @@ const splitNumberToPath = num => { */ export const hashTile = (x, z) => `x${x}z${z}`; +export const generateCacheHash = () => { + return Math.round(Math.random() * 1000000); +} /** * Dispatches an event to the element of this map-viewer @@ -184,7 +187,7 @@ export const animate = function (animationFrame, durationMs = 1000, postAnimatio this.lastFrame = time; } - let progress = MathUtils.clamp((time - this.animationStart) / durationMs, 0, 1); + let progress = durationMs === 0 ? 1 : MathUtils.clamp((time - this.animationStart) / durationMs, 0, 1); let deltaTime = time - this.lastFrame; animationFrame(progress, deltaTime);