mirror of
https://github.com/BlueMap-Minecraft/BlueMap.git
synced 2025-01-08 09:17:43 +01:00
Move BlueMapVue and BlueMapWeb into the main repo
This commit is contained in:
parent
6642c11742
commit
bd52fb6eb7
@ -1 +0,0 @@
|
||||
Subproject commit b5cd8989340198464e10af891afa8e4a13565dc5
|
25
BlueMapCommon/BlueMapVue/BlueMapWeb/.github/workflows/nodejs.yml
vendored
Normal file
25
BlueMapCommon/BlueMapVue/BlueMapWeb/.github/workflows/nodejs.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
name: Node.js CI
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: artifacts
|
||||
path: build/*
|
9
BlueMapCommon/BlueMapVue/BlueMapWeb/.gitignore
vendored
Normal file
9
BlueMapCommon/BlueMapVue/BlueMapWeb/.gitignore
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
.classpath
|
||||
.project
|
||||
.idea
|
||||
|
||||
build
|
||||
doc
|
||||
node_modules
|
||||
|
||||
public/js
|
13
BlueMapCommon/BlueMapVue/BlueMapWeb/.npmignore
Normal file
13
BlueMapCommon/BlueMapVue/BlueMapWeb/.npmignore
Normal file
@ -0,0 +1,13 @@
|
||||
.classpath
|
||||
.project
|
||||
.idea
|
||||
|
||||
build
|
||||
doc
|
||||
node_modules
|
||||
|
||||
public/js
|
||||
|
||||
###### Below is additionally to .gitignore
|
||||
.github
|
||||
public
|
22
BlueMapCommon/BlueMapVue/BlueMapWeb/LICENSE
Normal file
22
BlueMapCommon/BlueMapVue/BlueMapWeb/LICENSE
Normal file
@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) Blue <https://www.bluecolored.de>
|
||||
Copyright (c) contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
5777
BlueMapCommon/BlueMapVue/BlueMapWeb/package-lock.json
generated
Normal file
5777
BlueMapCommon/BlueMapVue/BlueMapWeb/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
43
BlueMapCommon/BlueMapVue/BlueMapWeb/package.json
Normal file
43
BlueMapCommon/BlueMapVue/BlueMapWeb/package.json
Normal file
@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "bluemap",
|
||||
"version": "1.1.0",
|
||||
"description": "A library to load and display Minecraft maps generated by BlueMap.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/BlueMap-Minecraft/BlueMapWeb.git"
|
||||
},
|
||||
"keywords": [
|
||||
"minecraft",
|
||||
"minecraft-mod",
|
||||
"minecraft-plugin",
|
||||
"threejs",
|
||||
"webgl",
|
||||
"bluemap"
|
||||
],
|
||||
"author": "Lukas Rieger <contact@bluecolored.de> (https://bluecolored.de/)",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/BlueMap-Minecraft/BlueMap/issues"
|
||||
},
|
||||
"homepage": "https://bluecolo.red/bluemap",
|
||||
"dependencies": {
|
||||
"hammerjs": "~2.0.8",
|
||||
"three": "~0.123.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "~7.11.6",
|
||||
"@babel/plugin-proposal-class-properties": "~7.10.4",
|
||||
"@babel/polyfill": "~7.11.5",
|
||||
"@babel/preset-env": "~7.11.5",
|
||||
"@rollup/plugin-babel": "~5.2.1",
|
||||
"@types/babel__core": "~7.1.9",
|
||||
"concurrently": "~5.3.0",
|
||||
"copyfiles": "~2.3.0",
|
||||
"http-server": "~0.12.3",
|
||||
"rollup": "~2.28.2",
|
||||
"rollup-plugin-terser": "~7.0.2"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "rollup -c"
|
||||
}
|
||||
}
|
44
BlueMapCommon/BlueMapVue/BlueMapWeb/rollup-test.config.js
Normal file
44
BlueMapCommon/BlueMapVue/BlueMapWeb/rollup-test.config.js
Normal file
@ -0,0 +1,44 @@
|
||||
import babel from "@rollup/plugin-babel";
|
||||
import { terser } from "rollup-plugin-terser";
|
||||
|
||||
const babelrc = {
|
||||
babelHelpers: 'bundled',
|
||||
presets: [
|
||||
['@babel/preset-env', {
|
||||
targets: "> 0.25%, not dead",
|
||||
bugfixes: true,
|
||||
loose: true
|
||||
}]
|
||||
],
|
||||
plugins: [
|
||||
['@babel/plugin-proposal-class-properties', {
|
||||
loose: true
|
||||
}]
|
||||
]
|
||||
};
|
||||
|
||||
export default [
|
||||
{
|
||||
input: 'src/BlueMap.js',
|
||||
external: [ 'three', 'hammerjs' ],
|
||||
plugins: [
|
||||
babel( {
|
||||
compact: false,
|
||||
babelrc: false,
|
||||
...babelrc
|
||||
} )
|
||||
],
|
||||
output: [
|
||||
{
|
||||
format: 'umd',
|
||||
name: 'BlueMap',
|
||||
file: 'build/bluemap.js',
|
||||
indent: '\t',
|
||||
globals: {
|
||||
three: 'THREE',
|
||||
hammerjs: 'Hammer',
|
||||
}
|
||||
}
|
||||
],
|
||||
}
|
||||
];
|
77
BlueMapCommon/BlueMapVue/BlueMapWeb/rollup.config.js
Normal file
77
BlueMapCommon/BlueMapVue/BlueMapWeb/rollup.config.js
Normal file
@ -0,0 +1,77 @@
|
||||
import babel from "@rollup/plugin-babel";
|
||||
import { terser } from "rollup-plugin-terser";
|
||||
|
||||
const babelrc = {
|
||||
babelHelpers: 'bundled',
|
||||
presets: [
|
||||
['@babel/preset-env', {
|
||||
targets: "> 0.25%, not dead",
|
||||
bugfixes: true,
|
||||
loose: true
|
||||
}]
|
||||
],
|
||||
plugins: [
|
||||
['@babel/plugin-proposal-class-properties', {
|
||||
loose: true
|
||||
}]
|
||||
]
|
||||
};
|
||||
|
||||
export default [
|
||||
{
|
||||
input: 'src/BlueMap.js',
|
||||
external: [ 'three', 'hammerjs' ],
|
||||
plugins: [
|
||||
babel( {
|
||||
compact: false,
|
||||
babelrc: false,
|
||||
...babelrc
|
||||
} )
|
||||
],
|
||||
output: [
|
||||
{
|
||||
format: 'umd',
|
||||
name: 'BlueMap',
|
||||
file: 'build/bluemap.js',
|
||||
indent: '\t',
|
||||
globals: {
|
||||
three: 'THREE',
|
||||
hammerjs: 'Hammer',
|
||||
}
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
input: 'src/BlueMap.js',
|
||||
external: [ 'three', 'hammerjs' ],
|
||||
plugins: [
|
||||
babel( {
|
||||
babelrc: false,
|
||||
...babelrc
|
||||
} ),
|
||||
terser(),
|
||||
],
|
||||
output: [
|
||||
{
|
||||
format: 'umd',
|
||||
name: 'BlueMap',
|
||||
file: 'build/bluemap.min.js',
|
||||
globals: {
|
||||
three: 'THREE',
|
||||
hammerjs: 'Hammer',
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
input: 'src/BlueMap.js',
|
||||
external: [ 'three', 'hammerjs' ],
|
||||
plugins: [],
|
||||
output: [
|
||||
{
|
||||
format: 'esm',
|
||||
file: 'build/bluemap.module.js'
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
67
BlueMapCommon/BlueMapVue/BlueMapWeb/src/BlueMap.js
Normal file
67
BlueMapCommon/BlueMapVue/BlueMapWeb/src/BlueMap.js
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
import {Object3D} from "three";
|
||||
|
||||
export * from "./MapViewer";
|
||||
|
||||
export * from "./map/Map";
|
||||
export * from "./map/Tile";
|
||||
export * from "./map/TileLoader";
|
||||
export * from "./map/TileManager";
|
||||
export * from "./map/TileMap";
|
||||
|
||||
export * from "./markers/ExtrudeMarker";
|
||||
export * from "./markers/HtmlMarker";
|
||||
export * from "./markers/LineMarker";
|
||||
export * from "./markers/Marker";
|
||||
export * from "./markers/MarkerManager";
|
||||
export * from "./markers/MarkerSet";
|
||||
export * from "./markers/PlayerMarkerSet";
|
||||
export * from "./markers/ObjectMarker";
|
||||
export * from "./markers/PlayerMarker";
|
||||
export * from "./markers/PoiMarker";
|
||||
export * from "./markers/ShapeMarker";
|
||||
|
||||
export * from "./controls/map/MapControls";
|
||||
export * from "./controls/freeflight/FreeFlightControls";
|
||||
|
||||
export * from "./util/CombinedCamera";
|
||||
export * from "./util/Utils";
|
||||
|
||||
/**
|
||||
* @param event {object}
|
||||
* @return {boolean} - whether the event has been consumed (true) or not (false)
|
||||
*/
|
||||
Object3D.prototype.onClick = function(event) {
|
||||
|
||||
if (this.parent){
|
||||
if (!Array.isArray(event.eventStack)) event.eventStack = [];
|
||||
event.eventStack.push(this);
|
||||
|
||||
return this.parent.onClick(event);asd
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
492
BlueMapCommon/BlueMapVue/BlueMapWeb/src/MapViewer.js
Normal file
492
BlueMapCommon/BlueMapVue/BlueMapWeb/src/MapViewer.js
Normal file
@ -0,0 +1,492 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
import {Color, PerspectiveCamera, Raycaster, Scene, Vector2, Vector3, WebGLRenderer} from "three";
|
||||
import {Map} from "./map/Map";
|
||||
import {SkyboxScene} from "./skybox/SkyboxScene";
|
||||
import {ControlsManager} from "./controls/ControlsManager";
|
||||
import Stats from "./util/Stats";
|
||||
import {alert, dispatchEvent, elementOffset, generateCacheHash, htmlToElement, softClamp} from "./util/Utils";
|
||||
import {TileManager} from "./map/TileManager";
|
||||
import {HIRES_VERTEX_SHADER} from "./map/hires/HiresVertexShader";
|
||||
import {HIRES_FRAGMENT_SHADER} from "./map/hires/HiresFragmentShader";
|
||||
import {LOWRES_VERTEX_SHADER} from "./map/lowres/LowresVertexShader";
|
||||
import {LOWRES_FRAGMENT_SHADER} from "./map/lowres/LowresFragmentShader";
|
||||
import {CombinedCamera} from "./util/CombinedCamera";
|
||||
import {CSS2DRenderer} from "./util/CSS2DRenderer";
|
||||
import {MarkerSet} from "./markers/MarkerSet";
|
||||
|
||||
export class MapViewer {
|
||||
|
||||
/**
|
||||
* @param element {Element}
|
||||
* @param events {EventTarget}
|
||||
*/
|
||||
constructor(element, events = element) {
|
||||
Object.defineProperty( this, 'isMapViewer', { value: true } );
|
||||
|
||||
this.rootElement = element;
|
||||
this.events = events;
|
||||
|
||||
this.data = {
|
||||
map: null,
|
||||
camera: null,
|
||||
controlsManager: null,
|
||||
uniforms: {
|
||||
sunlightStrength: { value: 1 },
|
||||
ambientLight: { value: 0 },
|
||||
skyColor: { value: new Color(0.5, 0.5, 1) },
|
||||
hiresTileMap: {
|
||||
value: {
|
||||
map: null,
|
||||
size: TileManager.tileMapSize,
|
||||
scale: new Vector2(1, 1),
|
||||
translate: new Vector2(),
|
||||
pos: new Vector2(),
|
||||
}
|
||||
}
|
||||
},
|
||||
superSampling: 1,
|
||||
loadedCenter: new Vector2(0, 0),
|
||||
loadedHiresViewDistance: 200,
|
||||
loadedLowresViewDistance: 2000,
|
||||
}
|
||||
|
||||
this.tileCacheHash = generateCacheHash();
|
||||
|
||||
this.stats = new Stats();
|
||||
this.stats.hide();
|
||||
|
||||
// renderer
|
||||
this.renderer = new WebGLRenderer({
|
||||
antialias: true,
|
||||
sortObjects: true,
|
||||
preserveDrawingBuffer: true,
|
||||
logarithmicDepthBuffer: true,
|
||||
});
|
||||
this.renderer.autoClear = false;
|
||||
this.renderer.uniforms = this.data.uniforms;
|
||||
|
||||
// CSS2D renderer
|
||||
this.css2dRenderer = new CSS2DRenderer(this.events);
|
||||
|
||||
this.skyboxScene = new SkyboxScene(this.data.uniforms);
|
||||
|
||||
this.camera = new CombinedCamera(75, 1, 0.1, 10000, 0);
|
||||
this.skyboxCamera = new PerspectiveCamera(75, 1, 0.1, 10000);
|
||||
this.skyboxCamera.updateProjectionMatrix();
|
||||
|
||||
this.controlsManager = new ControlsManager(this, this.camera);
|
||||
|
||||
this.raycaster = new Raycaster();
|
||||
this.raycaster.layers.enableAll();
|
||||
this.raycaster.params.Line2 = {threshold: 20}
|
||||
|
||||
/** @type {Map} */
|
||||
this.map = null;
|
||||
|
||||
this.markers = new MarkerSet("bm-root");
|
||||
|
||||
this.lastFrame = 0;
|
||||
|
||||
// initialize
|
||||
this.initializeRootElement();
|
||||
|
||||
// handle window resizes
|
||||
window.addEventListener("resize", this.handleContainerResize);
|
||||
|
||||
// start render-loop
|
||||
requestAnimationFrame(this.renderLoop);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the root-element
|
||||
*/
|
||||
initializeRootElement() {
|
||||
this.rootElement.innerHTML = "";
|
||||
|
||||
let outerDiv = htmlToElement(`<div style="position: relative; width: 100%; height: 100%; overflow: hidden;"></div>`);
|
||||
this.rootElement.appendChild(outerDiv);
|
||||
|
||||
// 3d-canvas
|
||||
outerDiv.appendChild(this.renderer.domElement);
|
||||
|
||||
// html-markers
|
||||
this.css2dRenderer.domElement.style.position = 'absolute';
|
||||
this.css2dRenderer.domElement.style.top = '0';
|
||||
this.css2dRenderer.domElement.style.left = '0';
|
||||
this.css2dRenderer.domElement.style.pointerEvents = 'none';
|
||||
outerDiv.appendChild(this.css2dRenderer.domElement);
|
||||
|
||||
// performance monitor
|
||||
outerDiv.appendChild(this.stats.dom);
|
||||
|
||||
this.handleContainerResize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the render-resolution and aspect ratio based on the size of the root-element
|
||||
*/
|
||||
handleContainerResize = () => {
|
||||
this.renderer.setSize(this.rootElement.clientWidth, this.rootElement.clientHeight);
|
||||
this.renderer.setPixelRatio(window.devicePixelRatio * this.superSampling);
|
||||
|
||||
this.css2dRenderer.setSize(this.rootElement.clientWidth, this.rootElement.clientHeight);
|
||||
|
||||
this.camera.aspect = this.rootElement.clientWidth / this.rootElement.clientHeight;
|
||||
this.camera.updateProjectionMatrix();
|
||||
};
|
||||
|
||||
/**
|
||||
* Triggers an interaction on the screen (map), e.g. a mouse-click.
|
||||
*
|
||||
* This will first attempt to invoke the onClick() method on the Object3D (e.g. Markers) that has been clicked.
|
||||
* And if none of those consumed the event, it will fire a <code>bluemapMapInteraction</code> event.
|
||||
*
|
||||
* @param screenPosition {Vector2} - Clicked position on the screen (usually event.x, event.y)
|
||||
* @param data {object} - Custom event data that will be added to the interaction-event
|
||||
*/
|
||||
handleMapInteraction(screenPosition, data = {}) {
|
||||
let rootOffset = elementOffset(this.rootElement);
|
||||
let normalizedScreenPos = new Vector2(
|
||||
((screenPosition.x - rootOffset.top) / this.rootElement.clientWidth) * 2 - 1,
|
||||
-((screenPosition.y - rootOffset.left) / this.rootElement.clientHeight) * 2 + 1
|
||||
);
|
||||
|
||||
if (this.map && this.map.isLoaded){
|
||||
this.raycaster.setFromCamera(normalizedScreenPos, this.camera);
|
||||
|
||||
// check Object3D interactions
|
||||
const intersectScenes = [this.map.hiresTileManager.scene, this.markers];
|
||||
for (let i = 0; i < this.map.lowresTileManager.length; i++) {
|
||||
intersectScenes.push(this.map.lowresTileManager[i].scene);
|
||||
}
|
||||
|
||||
let intersects = this.raycaster.intersectObjects(intersectScenes, true);
|
||||
let hit = null;
|
||||
let lowresHits = [];
|
||||
let hiresHit = null;
|
||||
let covered = false;
|
||||
|
||||
for (let i = 0; i < intersects.length; i++) {
|
||||
if (intersects[i].object){
|
||||
let object = intersects[i].object;
|
||||
|
||||
// check if deeply-visible
|
||||
let parent = object;
|
||||
let visible = parent.visible;
|
||||
while (visible && parent.parent){
|
||||
parent = parent.parent;
|
||||
visible = parent.visible;
|
||||
}
|
||||
|
||||
if (visible) {
|
||||
if (!hit) hit = intersects[i];
|
||||
|
||||
// find root-scene
|
||||
let parentRoot = object;
|
||||
while(parentRoot.parent) parentRoot = parentRoot.parent;
|
||||
|
||||
for (let l = 0; l < this.map.lowresTileManager.length; l++) {
|
||||
if (parentRoot === this.map.lowresTileManager[l].sceneParent) {
|
||||
if (!lowresHits[l]) lowresHits[l] = intersects[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (parentRoot === this.map.hiresTileManager.sceneParent) {
|
||||
if (!hiresHit) hiresHit = intersects[i];
|
||||
}
|
||||
|
||||
if (!covered || (object.material && !object.material.depthTest)) {
|
||||
if (object.onClick && object.onClick({
|
||||
data: data,
|
||||
intersection: intersects[i]
|
||||
})) return;
|
||||
}
|
||||
|
||||
if (parentRoot !== this.map.lowresTileManager[0].sceneParent) {
|
||||
covered = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fire event
|
||||
dispatchEvent(this.events, "bluemapMapInteraction", {
|
||||
data: data,
|
||||
hit: hit,
|
||||
hiresHit: hiresHit,
|
||||
lowresHits: lowresHits,
|
||||
intersections: intersects,
|
||||
ray: this.raycaster.ray
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* The render-loop to update and possibly render a new frame.
|
||||
* @param now {number} the current time in milliseconds
|
||||
*/
|
||||
renderLoop = (now) => {
|
||||
requestAnimationFrame(this.renderLoop);
|
||||
|
||||
// calculate delta time
|
||||
if (this.lastFrame <= 0) this.lastFrame = now;
|
||||
let delta = now - this.lastFrame;
|
||||
this.lastFrame = now;
|
||||
|
||||
// update stats
|
||||
this.stats.begin();
|
||||
|
||||
// update controls
|
||||
if (this.map != null) {
|
||||
this.controlsManager.update(delta, this.map);
|
||||
}
|
||||
|
||||
// render
|
||||
this.render(delta);
|
||||
|
||||
// update stats
|
||||
this.stats.update();
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Renders a frame
|
||||
* @param delta {number}
|
||||
*/
|
||||
render(delta) {
|
||||
dispatchEvent(this.events, "bluemapRenderFrame", {
|
||||
delta: delta,
|
||||
});
|
||||
|
||||
// render
|
||||
this.renderer.clear();
|
||||
|
||||
// prepare skybox camera
|
||||
this.skyboxCamera.rotation.copy(this.camera.rotation);
|
||||
|
||||
// render skybox
|
||||
this.renderer.render(this.skyboxScene, this.skyboxCamera);
|
||||
this.renderer.clearDepth();
|
||||
|
||||
if (this.map && this.map.isLoaded) {
|
||||
|
||||
// shift whole scene including camera towards 0,0 to tackle shader-precision issues
|
||||
const s = 10000;
|
||||
const sX = Math.round(this.camera.position.x / s) * s;
|
||||
const sZ = Math.round(this.camera.position.z / s) * s;
|
||||
this.camera.position.x -= sX;
|
||||
this.camera.position.z -= sZ;
|
||||
|
||||
// update uniforms
|
||||
this.data.uniforms.hiresTileMap.value.pos.copy(this.map.hiresTileManager.centerTile);
|
||||
this.data.uniforms.hiresTileMap.value.translate.set(
|
||||
this.map.data.hires.translate.x - sX,
|
||||
this.map.data.hires.translate.z - sZ
|
||||
);
|
||||
|
||||
// prepare camera for lowres
|
||||
const cameraFar = this.camera.far;
|
||||
if (this.controlsManager.distance < 1000) {
|
||||
this.camera.far = 1000000; // disable far clipping for lowres
|
||||
}
|
||||
this.camera.updateProjectionMatrix();
|
||||
|
||||
// render lowres
|
||||
const highestLod = this.map.lowresTileManager.length - 1;
|
||||
for (let i = this.map.lowresTileManager.length - 1; i >= 0; i--) {
|
||||
if (i === highestLod || this.controlsManager.distance < 1000 * Math.pow(this.map.data.lowres.lodFactor, i + 1)) {
|
||||
let scenePos = this.map.lowresTileManager[i].scene.position;
|
||||
scenePos.x = -sX;
|
||||
scenePos.z = -sZ;
|
||||
|
||||
if (i === 0) {
|
||||
this.camera.far = cameraFar; // reset far clipping for the highest lowres lod to make depth-tests possible
|
||||
this.camera.updateProjectionMatrix();
|
||||
}
|
||||
|
||||
this.renderer.render(this.map.lowresTileManager[i].sceneParent, this.camera);
|
||||
|
||||
if (i !== 0) this.renderer.clearDepth(); // clear depth-buffer for all lowres except the highest
|
||||
}
|
||||
}
|
||||
|
||||
this.camera.far = cameraFar; // reset far clipping
|
||||
|
||||
// render hires
|
||||
if (this.controlsManager.distance < 1000) {
|
||||
this.camera.updateProjectionMatrix();
|
||||
let scenePos = this.map.hiresTileManager.scene.position;
|
||||
scenePos.x = -sX;
|
||||
scenePos.z = -sZ;
|
||||
this.renderer.render(this.map.hiresTileManager.sceneParent, this.camera);
|
||||
}
|
||||
|
||||
// shift back
|
||||
this.camera.position.x += sX;
|
||||
this.camera.position.z += sZ;
|
||||
|
||||
}
|
||||
|
||||
// render markers
|
||||
this.renderer.render(this.markers, this.camera);
|
||||
this.css2dRenderer.render(this.markers, this.camera);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes / Sets the map that will be loaded and displayed
|
||||
* @param map {Map}
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
switchMap(map = null) {
|
||||
if (this.map && this.map.isMap) this.map.unload();
|
||||
|
||||
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, this.tileCacheHash)
|
||||
.then(() => {
|
||||
for (let texture of this.map.loadedTextures){
|
||||
this.renderer.initTexture(texture);
|
||||
}
|
||||
|
||||
this.data.uniforms.skyColor.value = map.data.skyColor;
|
||||
this.data.uniforms.ambientLight.value = map.data.ambientLight;
|
||||
this.data.uniforms.hiresTileMap.value.map = map.hiresTileManager.tileMap.texture;
|
||||
this.data.uniforms.hiresTileMap.value.scale.set(map.data.hires.tileSize.x, map.data.hires.tileSize.z);
|
||||
this.data.uniforms.hiresTileMap.value.translate.set(map.data.hires.translate.x, map.data.hires.translate.z);
|
||||
|
||||
setTimeout(this.updateLoadedMapArea);
|
||||
|
||||
dispatchEvent(this.events, "bluemapMapChanged", {
|
||||
map: map
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
alert(this.events, error, "error");
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given area on the map (and unloads everything outside that area)
|
||||
* @param centerX {number}
|
||||
* @param centerZ {number}
|
||||
* @param hiresViewDistance {number?}
|
||||
* @param lowresViewDistance {number?}
|
||||
*/
|
||||
loadMapArea(centerX, centerZ, hiresViewDistance = -1, lowresViewDistance = -1) {
|
||||
this.data.loadedCenter.set(centerX, centerZ);
|
||||
if (hiresViewDistance >= 0) this.data.loadedHiresViewDistance = hiresViewDistance;
|
||||
if (lowresViewDistance >= 0) this.data.loadedLowresViewDistance = lowresViewDistance;
|
||||
|
||||
this.updateLoadedMapArea();
|
||||
}
|
||||
|
||||
updateLoadedMapArea = () => {
|
||||
if (!this.map) return;
|
||||
if (this.controlsManager.distance < 1000) {
|
||||
this.map.loadMapArea(this.data.loadedCenter.x, this.data.loadedCenter.y, this.data.loadedHiresViewDistance, this.data.loadedLowresViewDistance);
|
||||
} else {
|
||||
this.map.loadMapArea(this.data.loadedCenter.x, this.data.loadedCenter.y, 0, this.data.loadedLowresViewDistance);
|
||||
}
|
||||
}
|
||||
|
||||
clearTileCache(newTileCacheHash) {
|
||||
if (!newTileCacheHash) newTileCacheHash = generateCacheHash();
|
||||
|
||||
this.tileCacheHash = newTileCacheHash;
|
||||
if (this.map) {
|
||||
for (let i = 0; i < this.map.lowresTileManager.length; i++) {
|
||||
this.map.lowresTileManager[i].tileLoader.tileCacheHash = this.tileCacheHash;
|
||||
}
|
||||
this.map.hiresTileManager.tileLoader.tileCacheHash = this.tileCacheHash;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
get superSampling() {
|
||||
return this.data.superSampling;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value {number}
|
||||
*/
|
||||
set superSampling(value) {
|
||||
this.data.superSampling = value;
|
||||
this.handleContainerResize();
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {CombinedCamera}
|
||||
*/
|
||||
get camera() {
|
||||
return this._camera;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value {CombinedCamera}
|
||||
*/
|
||||
set camera(value) {
|
||||
this._camera = value;
|
||||
this.data.camera = value.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {ControlsManager}
|
||||
*/
|
||||
get controlsManager() {
|
||||
return this._controlsManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value {ControlsManager}
|
||||
*/
|
||||
set controlsManager(value) {
|
||||
this._controlsManager = value;
|
||||
this.data.controlsManager = value.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Map}
|
||||
*/
|
||||
get map() {
|
||||
return this._map;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value {Map}
|
||||
*/
|
||||
set map(value) {
|
||||
this._map = value;
|
||||
if (value) this.data.map = value.data;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,333 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import {MathUtils, Vector3} from "three";
|
||||
import {dispatchEvent} from "../util/Utils";
|
||||
import {Map} from "../map/Map";
|
||||
|
||||
export class ControlsManager {
|
||||
|
||||
/**
|
||||
* @param mapViewer {MapViewer}
|
||||
* @param camera {CombinedCamera}
|
||||
*/
|
||||
constructor(mapViewer, camera) {
|
||||
Object.defineProperty( this, 'isControlsManager', { value: true } );
|
||||
|
||||
this.data = {
|
||||
mapViewer: null,
|
||||
camera: null,
|
||||
controls: null,
|
||||
position: new Vector3(0, 0, 0),
|
||||
rotation: 0,
|
||||
angle: 0,
|
||||
tilt: 0,
|
||||
};
|
||||
|
||||
this.mapViewer = mapViewer;
|
||||
this.camera = camera;
|
||||
|
||||
/** @type {Vector3} */
|
||||
this.lastPosition = this.position.clone();
|
||||
this.lastRotation = this.rotation;
|
||||
this.lastAngle = this.angle;
|
||||
this.lastDistance = this.distance;
|
||||
this.lastOrtho = this.ortho;
|
||||
this.lastTilt = this.tilt;
|
||||
|
||||
this.lastMapUpdatePosition = this.position.clone();
|
||||
this.lastMapUpdateDistance = this.distance;
|
||||
|
||||
this.averageDeltaTime = 16;
|
||||
|
||||
this._controls = null;
|
||||
|
||||
// start
|
||||
this.distance = 300;
|
||||
this.position.set(0, 0, 0);
|
||||
this.rotation = 0;
|
||||
this.angle = 0;
|
||||
this.tilt = 0;
|
||||
this.ortho = 0;
|
||||
|
||||
this.updateCamera();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param deltaTime {number}
|
||||
* @param map {Map}
|
||||
*/
|
||||
update(deltaTime, map) {
|
||||
if (deltaTime > 50) deltaTime = 50; // assume min 20 UPS
|
||||
this.averageDeltaTime = this.averageDeltaTime * 0.9 + deltaTime * 0.1; // average delta-time to avoid choppy controls on lag-spikes
|
||||
|
||||
if (this._controls) this._controls.update(this.averageDeltaTime, map);
|
||||
|
||||
this.updateCamera();
|
||||
}
|
||||
|
||||
updateCamera() {
|
||||
let valueChanged = this.isValueChanged();
|
||||
|
||||
if (valueChanged) {
|
||||
this.resetValueChanged();
|
||||
|
||||
// wrap rotation
|
||||
while (this.rotation >= Math.PI) this.rotation -= Math.PI * 2;
|
||||
while (this.rotation <= -Math.PI) this.rotation += Math.PI * 2;
|
||||
|
||||
// prevent problems with the rotation when the angle is 0 (top-down) or distance is 0 (first-person)
|
||||
let rotatableAngle = this.angle;
|
||||
if (Math.abs(rotatableAngle) <= 0.0001) rotatableAngle = 0.0001;
|
||||
else if (Math.abs(rotatableAngle) - Math.PI <= 0.0001) rotatableAngle = rotatableAngle - 0.0001;
|
||||
let rotatableDistance = this.distance;
|
||||
if (Math.abs(rotatableDistance) <= 0.0001) rotatableDistance = 0.0001;
|
||||
|
||||
// fix distance for orthogonal-camera
|
||||
if (this.ortho > 0) {
|
||||
rotatableDistance = MathUtils.lerp(rotatableDistance, Math.max(rotatableDistance, 300), Math.pow(this.ortho, 8));
|
||||
}
|
||||
|
||||
// calculate rotationVector
|
||||
let rotationVector = new Vector3(Math.sin(this.rotation), 0, -Math.cos(this.rotation)); // 0 is towards north
|
||||
let angleRotationAxis = new Vector3(0, 1, 0).cross(rotationVector);
|
||||
rotationVector.applyAxisAngle(angleRotationAxis, (Math.PI / 2) - rotatableAngle);
|
||||
rotationVector.multiplyScalar(rotatableDistance);
|
||||
|
||||
// position camera
|
||||
this.camera.rotation.set(0, 0, 0);
|
||||
this.camera.position.copy(this.position).sub(rotationVector);
|
||||
this.camera.lookAt(this.position);
|
||||
this.camera.rotateZ(this.tilt + rotatableAngle < 0 ? Math.PI : 0);
|
||||
|
||||
// optimize far/near planes
|
||||
if (this.ortho <= 0) {
|
||||
let near = MathUtils.clamp(rotatableDistance / 1000, 0.01, 1);
|
||||
let far = MathUtils.clamp(rotatableDistance * 2, Math.max(near + 1, 2000), rotatableDistance + 5000);
|
||||
if (far - near > 10000) near = far - 10000;
|
||||
this.camera.near = near;
|
||||
this.camera.far = far;
|
||||
} else if (this.angle === 0) {
|
||||
this.camera.near = 1;
|
||||
this.camera.far = rotatableDistance + 300;
|
||||
} else {
|
||||
this.camera.near = 1;
|
||||
this.camera.far = 100000;
|
||||
}
|
||||
|
||||
// event
|
||||
dispatchEvent(this.mapViewer.events, "bluemapCameraMoved", {
|
||||
controlsManager: this,
|
||||
camera: this.camera
|
||||
});
|
||||
}
|
||||
|
||||
// if the position changed, update map to show new position
|
||||
if (this.mapViewer.map) {
|
||||
let triggerDistance = 1;
|
||||
if (valueChanged) {
|
||||
if (this.distance > 300) {
|
||||
triggerDistance = this.mapViewer.data.loadedLowresViewDistance * 0.5;
|
||||
} else {
|
||||
triggerDistance = this.mapViewer.data.loadedHiresViewDistance * 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
Math.abs(this.lastMapUpdatePosition.x - this.position.x) >= triggerDistance ||
|
||||
Math.abs(this.lastMapUpdatePosition.z - this.position.z) >= triggerDistance ||
|
||||
(this.distance < 1000 && this.lastMapUpdateDistance > 1000)
|
||||
) {
|
||||
this.lastMapUpdatePosition = this.position.clone();
|
||||
this.lastMapUpdateDistance = this.distance;
|
||||
this.mapViewer.loadMapArea(this.position.x, this.position.z);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers an interaction on the screen (map), e.g. a mouse-click
|
||||
* @param screenPosition {THREE.Vector2} - Clicked position on the screen (usually event.x, event.y)
|
||||
* @param data {object} - Custom event data that will be added to the interaction-event
|
||||
*/
|
||||
handleMapInteraction(screenPosition, data = {}) {
|
||||
this.mapViewer.handleMapInteraction(screenPosition, data);
|
||||
}
|
||||
|
||||
isValueChanged() {
|
||||
return !(
|
||||
this.data.position.equals(this.lastPosition) &&
|
||||
this.data.rotation === this.lastRotation &&
|
||||
this.data.angle === this.lastAngle &&
|
||||
this.distance === this.lastDistance &&
|
||||
this.ortho === this.lastOrtho &&
|
||||
this.data.tilt === this.lastTilt
|
||||
);
|
||||
}
|
||||
|
||||
resetValueChanged() {
|
||||
this.lastPosition.copy(this.data.position);
|
||||
this.lastRotation = this.data.rotation;
|
||||
this.lastAngle = this.data.angle;
|
||||
this.lastDistance = this.distance;
|
||||
this.lastOrtho = this.ortho;
|
||||
this.lastTilt = this.data.tilt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
get ortho() {
|
||||
return this.camera.ortho;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ortho {number}
|
||||
*/
|
||||
set ortho(ortho) {
|
||||
this.camera.ortho = ortho;
|
||||
}
|
||||
|
||||
get distance() {
|
||||
return this.camera.distance;
|
||||
}
|
||||
|
||||
set distance(distance) {
|
||||
this.camera.distance = distance;
|
||||
}
|
||||
|
||||
/** @typedef ControlsLike {{
|
||||
* start: function(controls: ControlsManager),
|
||||
* stop: function(),
|
||||
* update: function(deltaTime: number, map: Map)
|
||||
* }}
|
||||
|
||||
/**
|
||||
* @param controls {ControlsLike}
|
||||
*/
|
||||
set controls(controls) {
|
||||
if (this._controls && this._controls.stop)
|
||||
this._controls.stop();
|
||||
|
||||
this._controls = controls;
|
||||
if (controls) this.data.controls = controls.data || null
|
||||
|
||||
if (this._controls && this._controls.start)
|
||||
this._controls.start(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {ControlsLike}
|
||||
*/
|
||||
get controls() {
|
||||
return this._controls;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {MapViewer}
|
||||
*/
|
||||
get mapViewer() {
|
||||
return this._mapViewer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value {MapViewer}
|
||||
*/
|
||||
set mapViewer(value) {
|
||||
this._mapViewer = value;
|
||||
this.data.mapViewer = value.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {CombinedCamera}
|
||||
*/
|
||||
get camera() {
|
||||
return this._camera;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value {CombinedCamera}
|
||||
*/
|
||||
set camera(value) {
|
||||
this._camera = value;
|
||||
this.data.camera = value.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Vector3}
|
||||
*/
|
||||
get position() {
|
||||
return this.data.position;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value {Vector3}
|
||||
*/
|
||||
set position(value) {
|
||||
this.data.position = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
get rotation() {
|
||||
return this.data.rotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value {number}
|
||||
*/
|
||||
set rotation(value) {
|
||||
this.data.rotation = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
get angle() {
|
||||
return this.data.angle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value {number}
|
||||
*/
|
||||
set angle(value) {
|
||||
this.data.angle = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
get tilt() {
|
||||
return this.data.tilt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value {number}
|
||||
*/
|
||||
set tilt(value) {
|
||||
this.data.tilt = value;
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
export class KeyCombination {
|
||||
|
||||
static CTRL = 0;
|
||||
static SHIFT = 1;
|
||||
static ALT = 2;
|
||||
|
||||
/**
|
||||
* @param code {string}
|
||||
* @param modifiers {...number}
|
||||
*/
|
||||
constructor(code, ...modifiers) {
|
||||
|
||||
this.code = code;
|
||||
this.ctrl = modifiers.includes(KeyCombination.CTRL) || this.code === "CtrlLeft" || this.code === "CtrlRight";
|
||||
this.shift = modifiers.includes(KeyCombination.SHIFT) || this.code === "ShiftLeft" || this.code === "ShiftRight";
|
||||
this.alt = modifiers.includes(KeyCombination.ALT) || this.code === "AltLeft" || this.code === "AltRight";
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param evt {KeyboardEvent}
|
||||
* @returns {boolean}
|
||||
*/
|
||||
testDown(evt) {
|
||||
return this.code === evt.code &&
|
||||
this.ctrl === evt.ctrlKey &&
|
||||
this.shift === evt.shiftKey &&
|
||||
this.alt === evt.altKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param evt {KeyboardEvent}
|
||||
* @returns {boolean}
|
||||
*/
|
||||
testUp(evt) {
|
||||
return this.code === evt.code;
|
||||
}
|
||||
|
||||
static oneDown(evt, ...combinations) {
|
||||
for (let combination of combinations){
|
||||
if (combination.testDown(evt)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static oneUp(evt, ...combinations) {
|
||||
for (let combination of combinations){
|
||||
if (combination.testUp(evt)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,147 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import {MathUtils, Vector2} from "three";
|
||||
import {Manager, Pan, DIRECTION_ALL} from "hammerjs";
|
||||
import {animate, EasingFunctions} from "../../util/Utils";
|
||||
import {KeyMoveControls} from "./keyboard/KeyMoveControls";
|
||||
import {MouseRotateControls} from "./mouse/MouseRotateControls";
|
||||
import {MouseAngleControls} from "./mouse/MouseAngleControls";
|
||||
import {KeyHeightControls} from "./keyboard/KeyHeightControls";
|
||||
import {TouchPanControls} from "./touch/TouchPanControls";
|
||||
|
||||
export class FreeFlightControls {
|
||||
|
||||
/**
|
||||
* @param target {Element}
|
||||
*/
|
||||
constructor(target) {
|
||||
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, 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;
|
||||
|
||||
this.clickStart = new Vector2();
|
||||
this.moveSpeed = 0.5;
|
||||
|
||||
this.animationTargetHeight = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param manager {ControlsManager}
|
||||
*/
|
||||
start(manager) {
|
||||
this.manager = manager;
|
||||
|
||||
this.keyMove.start(manager);
|
||||
this.keyHeight.start(manager);
|
||||
this.mouseRotate.start(manager);
|
||||
this.mouseAngle.start(manager);
|
||||
this.touchPan.start(manager);
|
||||
|
||||
this.target.addEventListener("contextmenu", this.onContextMenu);
|
||||
this.target.addEventListener("mousedown", this.onMouseDown);
|
||||
this.target.addEventListener("mouseup", this.onMouseUp);
|
||||
window.addEventListener("wheel", this.onWheel, {passive: true});
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.keyMove.stop();
|
||||
this.keyHeight.stop();
|
||||
this.mouseRotate.stop();
|
||||
this.mouseAngle.stop();
|
||||
this.touchPan.stop();
|
||||
|
||||
this.target.removeEventListener("contextmenu", this.onContextMenu);
|
||||
this.target.removeEventListener("mousedown", this.onMouseDown);
|
||||
this.target.removeEventListener("mouseup", this.onMouseUp);
|
||||
window.removeEventListener("wheel", this.onWheel);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param delta {number}
|
||||
* @param map {Map}
|
||||
*/
|
||||
update(delta, map) {
|
||||
this.keyMove.update(delta, map);
|
||||
this.keyHeight.update(delta, map);
|
||||
this.mouseRotate.update(delta, map);
|
||||
this.mouseAngle.update(delta, map);
|
||||
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() {
|
||||
let touchMove = new Pan({ event: 'move', pointers: 1, direction: DIRECTION_ALL, threshold: 0 });
|
||||
this.hammer.add(touchMove);
|
||||
}
|
||||
|
||||
onContextMenu = evt => {
|
||||
evt.preventDefault();
|
||||
}
|
||||
|
||||
onMouseDown = evt => {
|
||||
this.clickStart.set(evt.x, evt.y);
|
||||
}
|
||||
|
||||
onMouseUp = evt => {
|
||||
if (Math.abs(this.clickStart.x - evt.x) > 5) return;
|
||||
if (Math.abs(this.clickStart.y - evt.y) > 5) return;
|
||||
|
||||
document.body.requestFullscreen()
|
||||
.finally(() => {
|
||||
this.target.requestPointerLock();
|
||||
});
|
||||
}
|
||||
|
||||
onWheel = evt => {
|
||||
let delta = evt.deltaY;
|
||||
if (evt.deltaMode === WheelEvent.DOM_DELTA_PIXEL) delta *= 0.01;
|
||||
if (evt.deltaMode === WheelEvent.DOM_DELTA_LINE) delta *= 0.33;
|
||||
|
||||
this.moveSpeed *= Math.pow(1.5, -delta * 0.25);
|
||||
this.moveSpeed = MathUtils.clamp(this.moveSpeed, 0.05, 5);
|
||||
|
||||
this.keyMove.speed = this.moveSpeed;
|
||||
this.keyHeight.speed = this.moveSpeed;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import {MathUtils} from "three";
|
||||
import {KeyCombination} from "../../KeyCombination";
|
||||
|
||||
export class KeyHeightControls {
|
||||
|
||||
static KEYS = {
|
||||
UP: [
|
||||
new KeyCombination("Space"),
|
||||
new KeyCombination("PageUp")
|
||||
],
|
||||
DOWN: [
|
||||
new KeyCombination("ShiftLeft"),
|
||||
new KeyCombination("ShiftRight"),
|
||||
new KeyCombination("PageDown")
|
||||
],
|
||||
}
|
||||
|
||||
/**
|
||||
* @param target {EventTarget}
|
||||
* @param speed {number}
|
||||
* @param stiffness {number}
|
||||
*/
|
||||
constructor(target, speed, stiffness) {
|
||||
this.target = target;
|
||||
this.manager = null;
|
||||
|
||||
this.deltaY = 0;
|
||||
|
||||
this.up = false;
|
||||
this.down = false;
|
||||
|
||||
this.speed = speed;
|
||||
this.stiffness = stiffness;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param manager {ControlsManager}
|
||||
*/
|
||||
start(manager) {
|
||||
this.manager = manager;
|
||||
|
||||
window.addEventListener("keydown", this.onKeyDown);
|
||||
window.addEventListener("keyup", this.onKeyUp);
|
||||
}
|
||||
|
||||
stop() {
|
||||
window.removeEventListener("keydown", this.onKeyDown);
|
||||
window.removeEventListener("keyup", this.onKeyUp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param delta {number}
|
||||
* @param map {Map}
|
||||
*/
|
||||
update(delta, map) {
|
||||
if (this.up) this.deltaY += 1;
|
||||
if (this.down) this.deltaY -= 1;
|
||||
|
||||
if (this.deltaY === 0) return;
|
||||
|
||||
let smoothing = this.stiffness / (16.666 / delta);
|
||||
smoothing = MathUtils.clamp(smoothing, 0, 1);
|
||||
|
||||
this.manager.position.y += this.deltaY * smoothing * this.speed * delta * 0.06;
|
||||
|
||||
this.deltaY *= 1 - smoothing;
|
||||
if (Math.abs(this.deltaY) < 0.0001) {
|
||||
this.deltaY = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param evt {KeyboardEvent}
|
||||
*/
|
||||
onKeyDown = evt => {
|
||||
if (KeyCombination.oneUp(evt, ...KeyHeightControls.KEYS.UP)){
|
||||
this.up = true;
|
||||
evt.preventDefault();
|
||||
}
|
||||
else if (KeyCombination.oneUp(evt, ...KeyHeightControls.KEYS.DOWN)){
|
||||
this.down = true;
|
||||
evt.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param evt {KeyboardEvent}
|
||||
*/
|
||||
onKeyUp = evt => {
|
||||
if (KeyCombination.oneUp(evt, ...KeyHeightControls.KEYS.UP)){
|
||||
this.up = false;
|
||||
}
|
||||
if (KeyCombination.oneUp(evt, ...KeyHeightControls.KEYS.DOWN)){
|
||||
this.down = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,155 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import {MathUtils, Vector2} from "three";
|
||||
import {VEC2_ZERO} from "../../../util/Utils";
|
||||
import {KeyCombination} from "../../KeyCombination";
|
||||
|
||||
export class KeyMoveControls {
|
||||
|
||||
static KEYS = {
|
||||
LEFT: [
|
||||
new KeyCombination("ArrowLeft"),
|
||||
new KeyCombination("KeyA")
|
||||
],
|
||||
UP: [
|
||||
new KeyCombination("ArrowUp"),
|
||||
new KeyCombination("KeyW")
|
||||
],
|
||||
RIGHT: [
|
||||
new KeyCombination("ArrowRight"),
|
||||
new KeyCombination("KeyD")
|
||||
],
|
||||
DOWN: [
|
||||
new KeyCombination("ArrowDown"),
|
||||
new KeyCombination("KeyS")
|
||||
],
|
||||
}
|
||||
|
||||
static temp_v2 = new Vector2();
|
||||
|
||||
/**
|
||||
* @param target {EventTarget}
|
||||
* @param speed {number}
|
||||
* @param stiffness {number}
|
||||
*/
|
||||
constructor(target, speed, stiffness) {
|
||||
this.target = target;
|
||||
this.manager = null;
|
||||
|
||||
this.deltaPosition = new Vector2();
|
||||
|
||||
this.up = false;
|
||||
this.down = false;
|
||||
this.left = false;
|
||||
this.right = false;
|
||||
|
||||
this.speed = speed;
|
||||
this.stiffness = stiffness;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param manager {ControlsManager}
|
||||
*/
|
||||
start(manager) {
|
||||
this.manager = manager;
|
||||
|
||||
window.addEventListener("keydown", this.onKeyDown);
|
||||
window.addEventListener("keyup", this.onKeyUp);
|
||||
}
|
||||
|
||||
stop() {
|
||||
window.removeEventListener("keydown", this.onKeyDown);
|
||||
window.removeEventListener("keyup", this.onKeyUp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param delta {number}
|
||||
* @param map {Map}
|
||||
*/
|
||||
update(delta, map) {
|
||||
if (this.up) this.deltaPosition.y -= 1;
|
||||
if (this.down) this.deltaPosition.y += 1;
|
||||
if (this.left) this.deltaPosition.x -= 1;
|
||||
if (this.right) this.deltaPosition.x += 1;
|
||||
|
||||
if (this.deltaPosition.x === 0 && this.deltaPosition.y === 0) return;
|
||||
|
||||
let smoothing = this.stiffness / (16.666 / delta);
|
||||
smoothing = MathUtils.clamp(smoothing, 0, 1);
|
||||
|
||||
let rotatedDelta = KeyMoveControls.temp_v2.copy(this.deltaPosition);
|
||||
rotatedDelta.rotateAround(VEC2_ZERO, this.manager.rotation);
|
||||
|
||||
this.manager.position.x += rotatedDelta.x * smoothing * this.speed * delta * 0.06;
|
||||
this.manager.position.z += rotatedDelta.y * smoothing * this.speed * delta * 0.06;
|
||||
|
||||
this.deltaPosition.multiplyScalar(1 - smoothing);
|
||||
if (this.deltaPosition.lengthSq() < 0.0001) {
|
||||
this.deltaPosition.set(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param evt {KeyboardEvent}
|
||||
*/
|
||||
onKeyDown = evt => {
|
||||
if (KeyCombination.oneUp(evt, ...KeyMoveControls.KEYS.UP)){
|
||||
this.up = true;
|
||||
evt.preventDefault();
|
||||
}
|
||||
if (KeyCombination.oneUp(evt, ...KeyMoveControls.KEYS.DOWN)){
|
||||
this.down = true;
|
||||
evt.preventDefault();
|
||||
}
|
||||
if (KeyCombination.oneUp(evt, ...KeyMoveControls.KEYS.LEFT)){
|
||||
this.left = true;
|
||||
evt.preventDefault();
|
||||
}
|
||||
if (KeyCombination.oneUp(evt, ...KeyMoveControls.KEYS.RIGHT)){
|
||||
this.right = true;
|
||||
evt.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param evt {KeyboardEvent}
|
||||
*/
|
||||
onKeyUp = evt => {
|
||||
if (KeyCombination.oneUp(evt, ...KeyMoveControls.KEYS.UP)){
|
||||
this.up = false;
|
||||
}
|
||||
if (KeyCombination.oneUp(evt, ...KeyMoveControls.KEYS.DOWN)){
|
||||
this.down = false;
|
||||
}
|
||||
if (KeyCombination.oneUp(evt, ...KeyMoveControls.KEYS.LEFT)){
|
||||
this.left = false;
|
||||
}
|
||||
if (KeyCombination.oneUp(evt, ...KeyMoveControls.KEYS.RIGHT)){
|
||||
this.right = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,139 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import {MathUtils} from "three";
|
||||
|
||||
export class MouseAngleControls {
|
||||
|
||||
/**
|
||||
* @param target {EventTarget}
|
||||
* @param speedLeft {number}
|
||||
* @param speedRight {number}
|
||||
* @param speedCapture {number}
|
||||
* @param stiffness {number}
|
||||
*/
|
||||
constructor(target, speedLeft, speedRight, speedCapture, stiffness) {
|
||||
this.target = target;
|
||||
this.manager = null;
|
||||
|
||||
this.moving = false;
|
||||
this.lastY = 0;
|
||||
this.deltaAngle = 0;
|
||||
|
||||
this.speedLeft = speedLeft;
|
||||
this.speedRight = speedRight;
|
||||
this.speedCapture = speedCapture;
|
||||
this.stiffness = stiffness;
|
||||
|
||||
this.pixelToSpeedMultiplier = 0;
|
||||
this.updatePixelToSpeedMultiplier();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param manager {ControlsManager}
|
||||
*/
|
||||
start(manager) {
|
||||
this.manager = manager;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param delta {number}
|
||||
* @param map {Map}
|
||||
*/
|
||||
update(delta, map) {
|
||||
if (this.deltaAngle === 0) return;
|
||||
|
||||
let smoothing = this.stiffness / (16.666 / delta);
|
||||
smoothing = MathUtils.clamp(smoothing, 0, 1);
|
||||
|
||||
this.manager.angle += this.deltaAngle * smoothing;
|
||||
|
||||
this.deltaAngle *= 1 - smoothing;
|
||||
if (Math.abs(this.deltaAngle) < 0.0001) {
|
||||
this.deltaAngle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.deltaAngle = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param evt {MouseEvent}
|
||||
*/
|
||||
onMouseDown = evt => {
|
||||
this.moving = true;
|
||||
this.deltaAngle = 0;
|
||||
this.lastY = evt.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param evt {MouseEvent}
|
||||
*/
|
||||
onMouseMove = evt => {
|
||||
if (document.pointerLockElement) {
|
||||
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.pixelToSpeedMultiplier;
|
||||
} else {
|
||||
this.deltaAngle += (evt.y - this.lastY) * this.speedRight * this.pixelToSpeedMultiplier;
|
||||
}
|
||||
}
|
||||
|
||||
this.lastY = evt.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param evt {MouseEvent}
|
||||
*/
|
||||
onMouseUp = evt => {
|
||||
this.moving = false;
|
||||
}
|
||||
|
||||
updatePixelToSpeedMultiplier = () => {
|
||||
this.pixelToSpeedMultiplier = 1 / this.target.clientHeight;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,139 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import {MathUtils} from "three";
|
||||
|
||||
export class MouseRotateControls {
|
||||
|
||||
/**
|
||||
* @param target {Element}
|
||||
* @param speedLeft {number}
|
||||
* @param speedRight {number}
|
||||
* @param speedCapture {number}
|
||||
* @param stiffness {number}
|
||||
*/
|
||||
constructor(target, speedLeft, speedRight, speedCapture, stiffness) {
|
||||
this.target = target;
|
||||
this.manager = null;
|
||||
|
||||
this.moving = false;
|
||||
this.lastX = 0;
|
||||
this.deltaRotation = 0;
|
||||
|
||||
this.speedLeft = speedLeft;
|
||||
this.speedRight = speedRight;
|
||||
this.speedCapture = speedCapture;
|
||||
this.stiffness = stiffness;
|
||||
|
||||
this.pixelToSpeedMultiplier = 0;
|
||||
this.updatePixelToSpeedMultiplier();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param manager {ControlsManager}
|
||||
*/
|
||||
start(manager) {
|
||||
this.manager = manager;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param delta {number}
|
||||
* @param map {Map}
|
||||
*/
|
||||
update(delta, map) {
|
||||
if (this.deltaRotation === 0) return;
|
||||
|
||||
let smoothing = this.stiffness / (16.666 / delta);
|
||||
smoothing = MathUtils.clamp(smoothing, 0, 1);
|
||||
|
||||
this.manager.rotation += this.deltaRotation * smoothing;
|
||||
|
||||
this.deltaRotation *= 1 - smoothing;
|
||||
if (Math.abs(this.deltaRotation) < 0.0001) {
|
||||
this.deltaRotation = 0;
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.deltaRotation = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param evt {MouseEvent}
|
||||
*/
|
||||
onMouseDown = evt => {
|
||||
this.moving = true;
|
||||
this.deltaRotation = 0;
|
||||
this.lastX = evt.x;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param evt {MouseEvent}
|
||||
*/
|
||||
onMouseMove = evt => {
|
||||
if (document.pointerLockElement) {
|
||||
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.pixelToSpeedMultiplier;
|
||||
} else {
|
||||
this.deltaRotation -= (evt.x - this.lastX) * this.speedRight * this.pixelToSpeedMultiplier;
|
||||
}
|
||||
}
|
||||
|
||||
this.lastX = evt.x;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param evt {MouseEvent}
|
||||
*/
|
||||
onMouseUp = evt => {
|
||||
this.moving = false;
|
||||
}
|
||||
|
||||
updatePixelToSpeedMultiplier = () => {
|
||||
this.pixelToSpeedMultiplier = (1 / this.target.clientWidth) * (this.target.clientWidth / this.target.clientHeight);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,144 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import {MathUtils, Vector2} from "three";
|
||||
|
||||
export class TouchPanControls {
|
||||
|
||||
static tempVec2_1 = new Vector2();
|
||||
|
||||
/**
|
||||
* @param target {Element}
|
||||
* @param hammer {Manager}
|
||||
* @param speed {number}
|
||||
* @param stiffness {number}
|
||||
*/
|
||||
constructor(target, hammer, speed, stiffness) {
|
||||
this.target = target;
|
||||
this.hammer = hammer;
|
||||
this.manager = null;
|
||||
|
||||
this.moving = false;
|
||||
this.lastPosition = new Vector2();
|
||||
this.deltaPosition = new Vector2();
|
||||
|
||||
this.speed = speed;
|
||||
this.stiffness = stiffness;
|
||||
|
||||
this.pixelToSpeedMultiplierX = 0;
|
||||
this.pixelToSpeedMultiplierY = 0;
|
||||
this.updatePixelToSpeedMultiplier();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param manager {ControlsManager}
|
||||
*/
|
||||
start(manager) {
|
||||
this.manager = manager;
|
||||
|
||||
this.hammer.on("movestart", this.onTouchDown);
|
||||
this.hammer.on("movemove", this.onTouchMove);
|
||||
this.hammer.on("moveend", this.onTouchUp);
|
||||
this.hammer.on("movecancel", this.onTouchUp);
|
||||
|
||||
window.addEventListener("resize", this.updatePixelToSpeedMultiplier);
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.hammer.off("movestart", this.onTouchDown);
|
||||
this.hammer.off("movemove", this.onTouchMove);
|
||||
this.hammer.off("moveend", this.onTouchUp);
|
||||
this.hammer.off("movecancel", this.onTouchUp);
|
||||
|
||||
window.removeEventListener("resize", this.updatePixelToSpeedMultiplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param delta {number}
|
||||
* @param map {Map}
|
||||
*/
|
||||
update(delta, map) {
|
||||
if (this.deltaPosition.x === 0 && this.deltaPosition.y === 0) return;
|
||||
|
||||
let smoothing = this.stiffness / (16.666 / delta);
|
||||
smoothing = MathUtils.clamp(smoothing, 0, 1);
|
||||
|
||||
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) {
|
||||
this.deltaPosition.set(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.deltaPosition.set(0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param evt {object}
|
||||
*/
|
||||
onTouchDown = evt => {
|
||||
if (evt.pointerType === "mouse") return;
|
||||
|
||||
this.moving = true;
|
||||
this.deltaPosition.set(0, 0);
|
||||
this.lastPosition.set(evt.center.x, evt.center.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param evt {object}
|
||||
*/
|
||||
onTouchMove = evt => {
|
||||
if (evt.pointerType === "mouse") return;
|
||||
|
||||
let position = TouchPanControls.tempVec2_1.set(evt.center.x, evt.center.y);
|
||||
|
||||
if(this.moving){
|
||||
this.deltaPosition.sub(position).add(this.lastPosition);
|
||||
}
|
||||
|
||||
this.lastPosition.copy(position);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param evt {object}
|
||||
*/
|
||||
onTouchUp = evt => {
|
||||
if (evt.pointerType === "mouse") return;
|
||||
|
||||
this.moving = false;
|
||||
}
|
||||
|
||||
updatePixelToSpeedMultiplier = () => {
|
||||
this.pixelToSpeedMultiplierX = (1 / this.target.clientWidth) * (this.target.clientWidth / this.target.clientHeight);
|
||||
this.pixelToSpeedMultiplierY = 1 / this.target.clientHeight;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,272 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import {MouseMoveControls} from "./mouse/MouseMoveControls";
|
||||
import {MouseZoomControls} from "./mouse/MouseZoomControls";
|
||||
import {MouseRotateControls} from "./mouse/MouseRotateControls";
|
||||
import {MouseAngleControls} from "./mouse/MouseAngleControls";
|
||||
import {MathUtils, Vector2, Vector3} from "three";
|
||||
import {Manager, Pan, Pinch, Rotate, Tap, DIRECTION_ALL, DIRECTION_VERTICAL} from "hammerjs";
|
||||
import {softClamp} from "../../util/Utils";
|
||||
import {MapHeightControls} from "./MapHeightControls";
|
||||
import {KeyMoveControls} from "./keyboard/KeyMoveControls";
|
||||
import {KeyAngleControls} from "./keyboard/KeyAngleControls";
|
||||
import {KeyRotateControls} from "./keyboard/KeyRotateControls";
|
||||
import {KeyZoomControls} from "./keyboard/KeyZoomControls";
|
||||
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 {Element}
|
||||
*/
|
||||
constructor(rootElement) {
|
||||
this.rootElement = rootElement;
|
||||
|
||||
this.data = {
|
||||
followingPlayer: null
|
||||
};
|
||||
|
||||
/** @type {ControlsManager} */
|
||||
this.manager = null;
|
||||
|
||||
this.hammer = new Manager(this.rootElement);
|
||||
this.initializeHammer();
|
||||
|
||||
//controls
|
||||
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);
|
||||
this.keyRotate = new KeyRotateControls(this.rootElement, 0.06, 0.15);
|
||||
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.rootElement, this.hammer, 1.5,0.3);
|
||||
this.touchRotate = new TouchRotateControls(this.hammer, 0.0174533, 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.lastTap = -1;
|
||||
this.lastTapCenter = null;
|
||||
|
||||
this.minDistance = 5;
|
||||
this.maxDistance = 100000;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param manager {ControlsManager}
|
||||
*/
|
||||
start(manager) {
|
||||
this.manager = manager;
|
||||
|
||||
this.rootElement.addEventListener("contextmenu", this.onContextMenu);
|
||||
this.hammer.on("tap", this.onTap);
|
||||
|
||||
this.mouseMove.start(manager);
|
||||
this.mouseRotate.start(manager);
|
||||
this.mouseAngle.start(manager);
|
||||
this.mouseZoom.start(manager);
|
||||
|
||||
this.keyMove.start(manager);
|
||||
this.keyRotate.start(manager);
|
||||
this.keyAngle.start(manager);
|
||||
this.keyZoom.start(manager);
|
||||
|
||||
this.touchMove.start(manager);
|
||||
this.touchRotate.start(manager);
|
||||
this.touchAngle.start(manager);
|
||||
this.touchZoom.start(manager);
|
||||
|
||||
this.mapHeight.start(manager);
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.stopFollowingPlayerMarker();
|
||||
|
||||
this.rootElement.removeEventListener("contextmenu", this.onContextMenu);
|
||||
this.hammer.off("tap", this.onTap);
|
||||
|
||||
this.mouseMove.stop();
|
||||
this.mouseRotate.stop();
|
||||
this.mouseAngle.stop();
|
||||
this.mouseZoom.stop();
|
||||
|
||||
this.keyMove.stop();
|
||||
this.keyRotate.stop();
|
||||
this.keyAngle.stop();
|
||||
this.keyZoom.stop();
|
||||
|
||||
this.touchMove.stop();
|
||||
this.touchRotate.stop();
|
||||
this.touchAngle.stop();
|
||||
this.touchZoom.stop();
|
||||
|
||||
this.mapHeight.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param delta {number}
|
||||
* @param map {Map}
|
||||
*/
|
||||
update(delta, map) {
|
||||
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();
|
||||
}
|
||||
|
||||
// follow player marker
|
||||
if (this.data.followingPlayer) {
|
||||
this.manager.position.copy(this.data.followingPlayer.position);
|
||||
}
|
||||
|
||||
// zoom
|
||||
this.mouseZoom.update(delta, map);
|
||||
this.keyZoom.update(delta, map);
|
||||
this.touchZoom.update(delta, map);
|
||||
|
||||
this.manager.distance = softClamp(this.manager.distance, this.minDistance, this.maxDistance, 0.8);
|
||||
|
||||
// max angle for current distance
|
||||
let maxAngleForZoom = this.getMaxPerspectiveAngleForDistance(this.manager.distance);
|
||||
|
||||
// rotation
|
||||
this.mouseRotate.update(delta, map);
|
||||
this.keyRotate.update(delta, map);
|
||||
this.touchRotate.update(delta, map);
|
||||
|
||||
const rotating = this.mouseRotate.moving || this.touchRotate.moving ||
|
||||
this.keyRotate.left || this.keyRotate.right
|
||||
|
||||
// snap rotation to north on orthographic view
|
||||
if (this.manager.ortho !== 0 && Math.abs(this.manager.rotation) < (rotating ? 0.05 : 0.3)) {
|
||||
this.manager.rotation = softClamp(this.manager.rotation, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
// tilt
|
||||
if (this.manager.ortho === 0) {
|
||||
this.mouseAngle.update(delta, map);
|
||||
this.keyAngle.update(delta, map);
|
||||
this.touchAngle.update(delta, map);
|
||||
this.manager.angle = softClamp(this.manager.angle, 0, maxAngleForZoom, 0.8);
|
||||
}
|
||||
|
||||
// target height
|
||||
if (this.manager.ortho === 0 || this.manager.angle === 0) {
|
||||
this.mapHeight.maxAngle = maxAngleForZoom;
|
||||
this.mapHeight.update(delta, map);
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.mouseMove.reset();
|
||||
this.mouseRotate.reset();
|
||||
this.mouseAngle.reset();
|
||||
this.mouseZoom.reset();
|
||||
|
||||
this.touchMove.reset();
|
||||
this.touchRotate.reset();
|
||||
this.touchAngle.reset();
|
||||
this.touchZoom.reset();
|
||||
}
|
||||
|
||||
getMaxPerspectiveAngleForDistance(distance) {
|
||||
return MathUtils.clamp((1 - Math.pow(Math.max(distance - 5, 0.001) / 500, 0.5)) * HALF_PI,0, HALF_PI)
|
||||
}
|
||||
|
||||
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 });
|
||||
let touchTilt = new Pan({ event: 'tilt', pointers: 2, direction: DIRECTION_VERTICAL, threshold: 0 });
|
||||
let touchRotate = new Rotate({ event: 'rotate', pointers: 2, threshold: 0 });
|
||||
let touchZoom = new Pinch({ event: 'zoom', pointers: 2, threshold: 0 });
|
||||
|
||||
touchMove.recognizeWith(touchRotate);
|
||||
touchMove.recognizeWith(touchTilt);
|
||||
touchMove.recognizeWith(touchZoom);
|
||||
touchTilt.recognizeWith(touchRotate);
|
||||
touchTilt.recognizeWith(touchZoom);
|
||||
touchRotate.recognizeWith(touchZoom);
|
||||
|
||||
this.hammer.add(touchTap);
|
||||
this.hammer.add(touchTilt);
|
||||
this.hammer.add(touchMove);
|
||||
this.hammer.add(touchRotate);
|
||||
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 => {
|
||||
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});
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import {MathUtils, Vector2} from "three";
|
||||
|
||||
export class MapHeightControls {
|
||||
|
||||
/**
|
||||
* @param cameraHeightStiffness {number}
|
||||
* @param targetHeightStiffness {number}
|
||||
*/
|
||||
constructor(cameraHeightStiffness, targetHeightStiffness) {
|
||||
this.manager = null;
|
||||
|
||||
this.cameraHeightStiffness = cameraHeightStiffness;
|
||||
this.targetHeightStiffness = targetHeightStiffness;
|
||||
this.maxAngle = Math.PI / 2;
|
||||
|
||||
this.targetHeight = 0;
|
||||
this.cameraHeight = 0;
|
||||
|
||||
this.minCameraHeight = 0;
|
||||
this.distanceTagretHeight = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param manager {ControlsManager}
|
||||
*/
|
||||
start(manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
stop() {}
|
||||
|
||||
/**
|
||||
* @param delta {number}
|
||||
* @param map {Map}
|
||||
*/
|
||||
update(delta, map) {
|
||||
|
||||
// adjust target height
|
||||
this.updateHeights(delta, map);
|
||||
this.manager.position.y = Math.max(this.manager.position.y, this.getSuggestedHeight());
|
||||
}
|
||||
|
||||
updateHeights(delta, map) {
|
||||
//target height
|
||||
let targetSmoothing = this.targetHeightStiffness / (16.666 / delta);
|
||||
targetSmoothing = MathUtils.clamp(targetSmoothing, 0, 1);
|
||||
|
||||
let targetTerrainHeight = map.terrainHeightAt(this.manager.position.x, this.manager.position.z) + 3 || 0;
|
||||
|
||||
let targetDelta = targetTerrainHeight - this.targetHeight;
|
||||
this.targetHeight += targetDelta * targetSmoothing;
|
||||
if (Math.abs(targetDelta) < 0.001) this.targetHeight = targetTerrainHeight;
|
||||
|
||||
// camera height
|
||||
this.minCameraHeight = 0;
|
||||
if (this.maxAngle >= 0.1) {
|
||||
let cameraSmoothing = this.cameraHeightStiffness / (16.666 / delta);
|
||||
cameraSmoothing = MathUtils.clamp(cameraSmoothing, 0, 1);
|
||||
|
||||
let cameraTerrainHeight = map.terrainHeightAt(this.manager.camera.position.x, this.manager.camera.position.z) || 0;
|
||||
|
||||
let cameraDelta = cameraTerrainHeight - this.cameraHeight;
|
||||
this.cameraHeight += cameraDelta * cameraSmoothing;
|
||||
if (Math.abs(cameraDelta) < 0.001) this.cameraHeight = cameraTerrainHeight;
|
||||
|
||||
let maxAngleHeight = Math.cos(this.maxAngle) * this.manager.distance;
|
||||
this.minCameraHeight = this.cameraHeight - maxAngleHeight + 1;
|
||||
}
|
||||
|
||||
// adjust targetHeight by distance
|
||||
this.distanceTagretHeight = Math.max(MathUtils.lerp(this.targetHeight, 0, this.manager.distance / 500), 0);
|
||||
}
|
||||
|
||||
getSuggestedHeight() {
|
||||
return Math.max(this.distanceTagretHeight, this.minCameraHeight);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import {MathUtils} from "three";
|
||||
import {KeyCombination} from "../../KeyCombination";
|
||||
|
||||
export class KeyAngleControls {
|
||||
|
||||
static KEYS = {
|
||||
UP: [
|
||||
new KeyCombination("ArrowUp", KeyCombination.ALT),
|
||||
new KeyCombination("KeyW", KeyCombination.ALT),
|
||||
new KeyCombination("PageUp")
|
||||
],
|
||||
DOWN: [
|
||||
new KeyCombination("ArrowDown", KeyCombination.ALT),
|
||||
new KeyCombination("KeyS", KeyCombination.ALT),
|
||||
new KeyCombination("PageDown")
|
||||
],
|
||||
}
|
||||
|
||||
/**
|
||||
* @param target {EventTarget}
|
||||
* @param speed {number}
|
||||
* @param stiffness {number}
|
||||
*/
|
||||
constructor(target, speed, stiffness) {
|
||||
this.target = target;
|
||||
this.manager = null;
|
||||
|
||||
this.deltaAngle = 0;
|
||||
|
||||
this.up = false;
|
||||
this.down = false;
|
||||
|
||||
this.speed = speed;
|
||||
this.stiffness = stiffness;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param manager {ControlsManager}
|
||||
*/
|
||||
start(manager) {
|
||||
this.manager = manager;
|
||||
|
||||
window.addEventListener("keydown", this.onKeyDown);
|
||||
window.addEventListener("keyup", this.onKeyUp);
|
||||
}
|
||||
|
||||
stop() {
|
||||
window.removeEventListener("keydown", this.onKeyDown);
|
||||
window.removeEventListener("keyup", this.onKeyUp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param delta {number}
|
||||
* @param map {Map}
|
||||
*/
|
||||
update(delta, map) {
|
||||
if (this.up) this.deltaAngle -= 1;
|
||||
if (this.down) this.deltaAngle += 1;
|
||||
|
||||
if (this.deltaAngle === 0) return;
|
||||
|
||||
let smoothing = this.stiffness / (16.666 / delta);
|
||||
smoothing = MathUtils.clamp(smoothing, 0, 1);
|
||||
|
||||
this.manager.angle += this.deltaAngle * smoothing * this.speed * delta * 0.06;
|
||||
|
||||
this.deltaAngle *= 1 - smoothing;
|
||||
if (Math.abs(this.deltaAngle) < 0.0001) {
|
||||
this.deltaAngle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param evt {KeyboardEvent}
|
||||
*/
|
||||
onKeyDown = evt => {
|
||||
if (KeyCombination.oneDown(evt, ...KeyAngleControls.KEYS.UP)){
|
||||
this.up = true;
|
||||
evt.preventDefault();
|
||||
}
|
||||
if (KeyCombination.oneDown(evt, ...KeyAngleControls.KEYS.DOWN)){
|
||||
this.down = true;
|
||||
evt.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param evt {KeyboardEvent}
|
||||
*/
|
||||
onKeyUp = evt => {
|
||||
if (KeyCombination.oneUp(evt, ...KeyAngleControls.KEYS.UP)){
|
||||
this.up = false;
|
||||
}
|
||||
if (KeyCombination.oneUp(evt, ...KeyAngleControls.KEYS.DOWN)){
|
||||
this.down = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,155 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import {MathUtils, Vector2} from "three";
|
||||
import {VEC2_ZERO} from "../../../util/Utils";
|
||||
import {KeyCombination} from "../../KeyCombination";
|
||||
|
||||
export class KeyMoveControls {
|
||||
|
||||
static KEYS = {
|
||||
LEFT: [
|
||||
new KeyCombination("ArrowLeft"),
|
||||
new KeyCombination("KeyA")
|
||||
],
|
||||
UP: [
|
||||
new KeyCombination("ArrowUp"),
|
||||
new KeyCombination("KeyW")
|
||||
],
|
||||
RIGHT: [
|
||||
new KeyCombination("ArrowRight"),
|
||||
new KeyCombination("KeyD")
|
||||
],
|
||||
DOWN: [
|
||||
new KeyCombination("ArrowDown"),
|
||||
new KeyCombination("KeyS")
|
||||
],
|
||||
}
|
||||
|
||||
static temp_v2 = new Vector2();
|
||||
|
||||
/**
|
||||
* @param target {EventTarget}
|
||||
* @param speed {number}
|
||||
* @param stiffness {number}
|
||||
*/
|
||||
constructor(target, speed, stiffness) {
|
||||
this.target = target;
|
||||
this.manager = null;
|
||||
|
||||
this.deltaPosition = new Vector2();
|
||||
|
||||
this.up = false;
|
||||
this.down = false;
|
||||
this.left = false;
|
||||
this.right = false;
|
||||
|
||||
this.speed = speed;
|
||||
this.stiffness = stiffness;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param manager {ControlsManager}
|
||||
*/
|
||||
start(manager) {
|
||||
this.manager = manager;
|
||||
|
||||
window.addEventListener("keydown", this.onKeyDown);
|
||||
window.addEventListener("keyup", this.onKeyUp);
|
||||
}
|
||||
|
||||
stop() {
|
||||
window.removeEventListener("keydown", this.onKeyDown);
|
||||
window.removeEventListener("keyup", this.onKeyUp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param delta {number}
|
||||
* @param map {Map}
|
||||
*/
|
||||
update(delta, map) {
|
||||
if (this.up) this.deltaPosition.y -= 1;
|
||||
if (this.down) this.deltaPosition.y += 1;
|
||||
if (this.left) this.deltaPosition.x -= 1;
|
||||
if (this.right) this.deltaPosition.x += 1;
|
||||
|
||||
if (this.deltaPosition.x === 0 && this.deltaPosition.y === 0) return;
|
||||
|
||||
let smoothing = this.stiffness / (16.666 / delta);
|
||||
smoothing = MathUtils.clamp(smoothing, 0, 1);
|
||||
|
||||
let rotatedDelta = KeyMoveControls.temp_v2.copy(this.deltaPosition);
|
||||
rotatedDelta.rotateAround(VEC2_ZERO, this.manager.rotation);
|
||||
|
||||
this.manager.position.x += rotatedDelta.x * smoothing * this.manager.distance * this.speed * delta * 0.06;
|
||||
this.manager.position.z += rotatedDelta.y * smoothing * this.manager.distance * this.speed * delta * 0.06;
|
||||
|
||||
this.deltaPosition.multiplyScalar(1 - smoothing);
|
||||
if (this.deltaPosition.lengthSq() < 0.0001) {
|
||||
this.deltaPosition.set(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param evt {KeyboardEvent}
|
||||
*/
|
||||
onKeyDown = evt => {
|
||||
if (KeyCombination.oneDown(evt, ...KeyMoveControls.KEYS.UP)){
|
||||
this.up = true;
|
||||
evt.preventDefault();
|
||||
}
|
||||
if (KeyCombination.oneDown(evt, ...KeyMoveControls.KEYS.DOWN)){
|
||||
this.down = true;
|
||||
evt.preventDefault();
|
||||
}
|
||||
if (KeyCombination.oneDown(evt, ...KeyMoveControls.KEYS.LEFT)){
|
||||
this.left = true;
|
||||
evt.preventDefault();
|
||||
}
|
||||
if (KeyCombination.oneDown(evt, ...KeyMoveControls.KEYS.RIGHT)){
|
||||
this.right = true;
|
||||
evt.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param evt {KeyboardEvent}
|
||||
*/
|
||||
onKeyUp = evt => {
|
||||
if (KeyCombination.oneUp(evt, ...KeyMoveControls.KEYS.UP)){
|
||||
this.up = false;
|
||||
}
|
||||
if (KeyCombination.oneUp(evt, ...KeyMoveControls.KEYS.DOWN)){
|
||||
this.down = false;
|
||||
}
|
||||
if (KeyCombination.oneUp(evt, ...KeyMoveControls.KEYS.LEFT)){
|
||||
this.left = false;
|
||||
}
|
||||
if (KeyCombination.oneUp(evt, ...KeyMoveControls.KEYS.RIGHT)){
|
||||
this.right = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import {MathUtils} from "three";
|
||||
import {KeyCombination} from "../../KeyCombination";
|
||||
|
||||
export class KeyRotateControls {
|
||||
|
||||
static KEYS = {
|
||||
LEFT: [
|
||||
new KeyCombination("ArrowLeft", KeyCombination.ALT),
|
||||
new KeyCombination("KeyA", KeyCombination.ALT),
|
||||
new KeyCombination("Delete"),
|
||||
],
|
||||
RIGHT: [
|
||||
new KeyCombination("ArrowRight", KeyCombination.ALT),
|
||||
new KeyCombination("KeyD", KeyCombination.ALT),
|
||||
new KeyCombination("End"),
|
||||
],
|
||||
}
|
||||
|
||||
/**
|
||||
* @param target {EventTarget}
|
||||
* @param speed {number}
|
||||
* @param stiffness {number}
|
||||
*/
|
||||
constructor(target, speed, stiffness) {
|
||||
this.target = target;
|
||||
this.manager = null;
|
||||
|
||||
this.deltaRotation = 0;
|
||||
|
||||
this.left = false;
|
||||
this.right = false;
|
||||
|
||||
this.speed = speed;
|
||||
this.stiffness = stiffness;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param manager {ControlsManager}
|
||||
*/
|
||||
start(manager) {
|
||||
this.manager = manager;
|
||||
|
||||
window.addEventListener("keydown", this.onKeyDown);
|
||||
window.addEventListener("keyup", this.onKeyUp);
|
||||
}
|
||||
|
||||
stop() {
|
||||
window.removeEventListener("keydown", this.onKeyDown);
|
||||
window.removeEventListener("keyup", this.onKeyUp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param delta {number}
|
||||
* @param map {Map}
|
||||
*/
|
||||
update(delta, map) {
|
||||
if (this.left) this.deltaRotation += 1;
|
||||
if (this.right) this.deltaRotation -= 1;
|
||||
|
||||
if (this.deltaRotation === 0) return;
|
||||
|
||||
let smoothing = this.stiffness / (16.666 / delta);
|
||||
smoothing = MathUtils.clamp(smoothing, 0, 1);
|
||||
|
||||
this.manager.rotation += this.deltaRotation * smoothing * this.speed * delta * 0.06;
|
||||
|
||||
this.deltaRotation *= 1 - smoothing;
|
||||
if (Math.abs(this.deltaRotation) < 0.0001) {
|
||||
this.deltaRotation = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param evt {KeyboardEvent}
|
||||
*/
|
||||
onKeyDown = evt => {
|
||||
if (KeyCombination.oneDown(evt, ...KeyRotateControls.KEYS.LEFT)){
|
||||
this.left = true;
|
||||
evt.preventDefault();
|
||||
}
|
||||
if (KeyCombination.oneDown(evt, ...KeyRotateControls.KEYS.RIGHT)){
|
||||
this.right = true;
|
||||
evt.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param evt {KeyboardEvent}
|
||||
*/
|
||||
onKeyUp = evt => {
|
||||
if (KeyCombination.oneUp(evt, ...KeyRotateControls.KEYS.LEFT)){
|
||||
this.left = false;
|
||||
}
|
||||
if (KeyCombination.oneUp(evt, ...KeyRotateControls.KEYS.RIGHT)){
|
||||
this.right = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import {MathUtils} from "three";
|
||||
import {KeyCombination} from "../../KeyCombination";
|
||||
|
||||
export class KeyZoomControls {
|
||||
|
||||
static KEYS = {
|
||||
IN: [
|
||||
new KeyCombination("NumpadAdd"),
|
||||
new KeyCombination("Insert"),
|
||||
],
|
||||
OUT: [
|
||||
new KeyCombination("NumpadSubtract"),
|
||||
new KeyCombination("Home"),
|
||||
],
|
||||
}
|
||||
|
||||
/**
|
||||
* @param target {EventTarget}
|
||||
* @param speed {number}
|
||||
* @param stiffness {number}
|
||||
*/
|
||||
constructor(target, speed, stiffness) {
|
||||
this.target = target;
|
||||
this.manager = null;
|
||||
|
||||
this.deltaZoom = 0;
|
||||
|
||||
this.in = false;
|
||||
this.out = false;
|
||||
|
||||
this.speed = speed;
|
||||
this.stiffness = stiffness;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param manager {ControlsManager}
|
||||
*/
|
||||
start(manager) {
|
||||
this.manager = manager;
|
||||
|
||||
window.addEventListener("keydown", this.onKeyDown);
|
||||
window.addEventListener("keyup", this.onKeyUp);
|
||||
}
|
||||
|
||||
stop() {
|
||||
window.removeEventListener("keydown", this.onKeyDown);
|
||||
window.removeEventListener("keyup", this.onKeyUp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param delta {number}
|
||||
* @param map {Map}
|
||||
*/
|
||||
update(delta, map) {
|
||||
if (this.in) this.deltaZoom -= 1;
|
||||
if (this.out) this.deltaZoom += 1;
|
||||
|
||||
if (this.deltaZoom === 0) return;
|
||||
|
||||
let smoothing = this.stiffness / (16.666 / delta);
|
||||
smoothing = MathUtils.clamp(smoothing, 0, 1);
|
||||
|
||||
this.manager.distance *= Math.pow(1.5, this.deltaZoom * smoothing * this.speed * delta * 0.06);
|
||||
|
||||
this.deltaZoom *= 1 - smoothing;
|
||||
if (Math.abs(this.deltaZoom) < 0.0001) {
|
||||
this.deltaZoom = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param evt {KeyboardEvent}
|
||||
*/
|
||||
onKeyDown = evt => {
|
||||
if (KeyCombination.oneDown(evt, ...KeyZoomControls.KEYS.IN)){
|
||||
this.in = true;
|
||||
evt.preventDefault();
|
||||
}
|
||||
if (KeyCombination.oneDown(evt, ...KeyZoomControls.KEYS.OUT)){
|
||||
this.out = true;
|
||||
evt.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param evt {KeyboardEvent}
|
||||
*/
|
||||
onKeyUp = evt => {
|
||||
if (KeyCombination.oneUp(evt, ...KeyZoomControls.KEYS.IN)){
|
||||
this.in = false;
|
||||
}
|
||||
if (KeyCombination.oneUp(evt, ...KeyZoomControls.KEYS.OUT)){
|
||||
this.out = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import {MathUtils} from "three";
|
||||
|
||||
export class MouseAngleControls {
|
||||
|
||||
/**
|
||||
* @param target {EventTarget}
|
||||
* @param speed {number}
|
||||
* @param stiffness {number}
|
||||
*/
|
||||
constructor(target, speed, stiffness) {
|
||||
this.target = target;
|
||||
this.manager = null;
|
||||
|
||||
this.moving = false;
|
||||
this.lastY = 0;
|
||||
this.deltaAngle = 0;
|
||||
|
||||
this.speed = speed;
|
||||
this.stiffness = stiffness;
|
||||
|
||||
this.pixelToSpeedMultiplierY = 0;
|
||||
this.updatePixelToSpeedMultiplier();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param manager {ControlsManager}
|
||||
*/
|
||||
start(manager) {
|
||||
this.manager = manager;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param delta {number}
|
||||
* @param map {Map}
|
||||
*/
|
||||
update(delta, map) {
|
||||
if (this.deltaAngle === 0) return;
|
||||
|
||||
let smoothing = this.stiffness / (16.666 / delta);
|
||||
smoothing = MathUtils.clamp(smoothing, 0, 1);
|
||||
|
||||
this.manager.angle += this.deltaAngle * smoothing * this.speed * this.pixelToSpeedMultiplierY;
|
||||
|
||||
this.deltaAngle *= 1 - smoothing;
|
||||
if (Math.abs(this.deltaAngle) < 0.0001) {
|
||||
this.deltaAngle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.deltaAngle = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param evt {MouseEvent}
|
||||
*/
|
||||
onMouseDown = evt => {
|
||||
if ((evt.buttons !== undefined ? evt.buttons === 2 : evt.button === 2) ||
|
||||
((evt.altKey || evt.ctrlKey) && (evt.buttons !== undefined ? evt.buttons === 1 : evt.button === 0))) {
|
||||
this.moving = true;
|
||||
this.deltaAngle = 0;
|
||||
this.lastY = evt.y;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param evt {MouseEvent}
|
||||
*/
|
||||
onMouseMove = evt => {
|
||||
if(this.moving){
|
||||
this.deltaAngle -= evt.y - this.lastY;
|
||||
}
|
||||
|
||||
this.lastY = evt.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param evt {MouseEvent}
|
||||
*/
|
||||
onMouseUp = evt => {
|
||||
this.moving = false;
|
||||
}
|
||||
|
||||
updatePixelToSpeedMultiplier = () => {
|
||||
this.pixelToSpeedMultiplierY = 1 / this.target.clientHeight;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,140 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import {MathUtils, Vector2} from "three";
|
||||
import {VEC2_ZERO} from "../../../util/Utils";
|
||||
|
||||
export class MouseMoveControls {
|
||||
|
||||
static tempVec2_1 = new Vector2();
|
||||
|
||||
/**
|
||||
* @param target {Element}
|
||||
* @param speed {number}
|
||||
* @param stiffness {number}
|
||||
*/
|
||||
constructor(target, speed, stiffness) {
|
||||
this.target = target;
|
||||
this.manager = null;
|
||||
|
||||
this.moving = false;
|
||||
this.lastPosition = new Vector2();
|
||||
this.deltaPosition = new Vector2();
|
||||
|
||||
this.speed = speed;
|
||||
this.stiffness = stiffness;
|
||||
|
||||
this.pixelToSpeedMultiplierX = 0;
|
||||
this.pixelToSpeedMultiplierY = 0;
|
||||
this.updatePixelToSpeedMultiplier();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param manager {ControlsManager}
|
||||
*/
|
||||
start(manager) {
|
||||
this.manager = manager;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param delta {number}
|
||||
* @param map {Map}
|
||||
*/
|
||||
update(delta, map) {
|
||||
if (this.deltaPosition.x === 0 && this.deltaPosition.y === 0) return;
|
||||
|
||||
let smoothing = this.stiffness / (16.666 / delta);
|
||||
smoothing = MathUtils.clamp(smoothing, 0, 1);
|
||||
|
||||
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.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) {
|
||||
this.deltaPosition.set(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.deltaPosition.set(0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param evt {MouseEvent}
|
||||
*/
|
||||
onMouseDown = evt => {
|
||||
if ((evt.buttons !== undefined ? evt.buttons === 1 : evt.button === 0) && !evt.altKey) {
|
||||
this.moving = true;
|
||||
this.deltaPosition.set(0, 0);
|
||||
this.lastPosition.set(evt.x, evt.y);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param evt {MouseEvent}
|
||||
*/
|
||||
onMouseMove = evt => {
|
||||
let position = MouseMoveControls.tempVec2_1.set(evt.x, evt.y);
|
||||
|
||||
if(this.moving){
|
||||
this.deltaPosition.sub(position).add(this.lastPosition);
|
||||
}
|
||||
|
||||
this.lastPosition.copy(position);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param evt {MouseEvent}
|
||||
*/
|
||||
onMouseUp = evt => {
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import {MathUtils} from "three";
|
||||
|
||||
export class MouseRotateControls {
|
||||
|
||||
/**
|
||||
* @param target {EventTarget}
|
||||
* @param speed {number}
|
||||
* @param stiffness {number}
|
||||
*/
|
||||
constructor(target, speed, stiffness) {
|
||||
this.target = target;
|
||||
this.manager = null;
|
||||
|
||||
this.moving = false;
|
||||
this.lastX = 0;
|
||||
this.deltaRotation = 0;
|
||||
|
||||
this.speed = speed;
|
||||
this.stiffness = stiffness;
|
||||
|
||||
this.pixelToSpeedMultiplierX = 0;
|
||||
this.updatePixelToSpeedMultiplier();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param manager {ControlsManager}
|
||||
*/
|
||||
start(manager) {
|
||||
this.manager = manager;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param delta {number}
|
||||
* @param map {Map}
|
||||
*/
|
||||
update(delta, map) {
|
||||
if (this.deltaRotation === 0) return;
|
||||
|
||||
let smoothing = this.stiffness / (16.666 / delta);
|
||||
smoothing = MathUtils.clamp(smoothing, 0, 1);
|
||||
|
||||
this.manager.rotation += this.deltaRotation * smoothing * this.speed * this.pixelToSpeedMultiplierX;
|
||||
|
||||
this.deltaRotation *= 1 - smoothing;
|
||||
if (Math.abs(this.deltaRotation) < 0.0001) {
|
||||
this.deltaRotation = 0;
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.deltaRotation = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param evt {MouseEvent}
|
||||
*/
|
||||
onMouseDown = evt => {
|
||||
if ((evt.buttons !== undefined ? evt.buttons === 2 : evt.button === 2) ||
|
||||
((evt.altKey || evt.ctrlKey) && (evt.buttons !== undefined ? evt.buttons === 1 : evt.button === 0))) {
|
||||
this.moving = true;
|
||||
this.deltaRotation = 0;
|
||||
this.lastX = evt.x;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param evt {MouseEvent}
|
||||
*/
|
||||
onMouseMove = evt => {
|
||||
if(this.moving){
|
||||
this.deltaRotation += evt.x - this.lastX;
|
||||
}
|
||||
|
||||
this.lastX = evt.x;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param evt {MouseEvent}
|
||||
*/
|
||||
onMouseUp = evt => {
|
||||
this.moving = false;
|
||||
}
|
||||
|
||||
updatePixelToSpeedMultiplier = () => {
|
||||
this.pixelToSpeedMultiplierX = (1 / this.target.clientWidth); //* (this.target.clientWidth / this.target.clientHeight);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import {MathUtils} from "three";
|
||||
|
||||
export class MouseZoomControls {
|
||||
|
||||
/**
|
||||
* @param target {EventTarget}
|
||||
* @param speed {number}
|
||||
* @param stiffness {number}
|
||||
*/
|
||||
constructor(target, speed, stiffness) {
|
||||
this.target = target;
|
||||
this.manager = null;
|
||||
|
||||
this.stiffness = stiffness;
|
||||
this.speed = speed;
|
||||
|
||||
this.deltaZoom = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param manager {ControlsManager}
|
||||
*/
|
||||
start(manager) {
|
||||
this.manager = manager;
|
||||
|
||||
this.target.addEventListener("wheel", this.onMouseWheel, {passive: true});
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.target.removeEventListener("wheel", this.onMouseWheel);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param delta {number}
|
||||
* @param map {Map}
|
||||
*/
|
||||
update(delta, map) {
|
||||
if (this.deltaZoom === 0) return;
|
||||
|
||||
let smoothing = this.stiffness / (16.666 / delta);
|
||||
smoothing = MathUtils.clamp(smoothing, 0, 1);
|
||||
|
||||
this.manager.distance *= Math.pow(1.5, this.deltaZoom * smoothing * this.speed);
|
||||
|
||||
this.deltaZoom *= 1 - smoothing;
|
||||
if (Math.abs(this.deltaZoom) < 0.0001) {
|
||||
this.deltaZoom = 0;
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.deltaZoom = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param evt {WheelEvent}
|
||||
*/
|
||||
onMouseWheel = evt => {
|
||||
let delta = evt.deltaY;
|
||||
if (evt.deltaMode === WheelEvent.DOM_DELTA_PIXEL) delta *= 0.01;
|
||||
if (evt.deltaMode === WheelEvent.DOM_DELTA_LINE) delta *= 0.33;
|
||||
|
||||
this.deltaZoom += delta;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import {MathUtils} from "three";
|
||||
|
||||
export class TouchAngleControls {
|
||||
|
||||
/**
|
||||
* @param target {Element}
|
||||
* @param hammer {Manager}
|
||||
* @param speed {number}
|
||||
* @param stiffness {number}
|
||||
*/
|
||||
constructor(target, hammer, speed, stiffness) {
|
||||
this.target = target;
|
||||
this.hammer = hammer;
|
||||
this.manager = null;
|
||||
|
||||
this.moving = false;
|
||||
this.lastY = 0;
|
||||
this.deltaAngle = 0;
|
||||
|
||||
this.speed = speed;
|
||||
this.stiffness = stiffness;
|
||||
|
||||
this.pixelToSpeedMultiplierY = 0;
|
||||
this.updatePixelToSpeedMultiplier();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param manager {ControlsManager}
|
||||
*/
|
||||
start(manager) {
|
||||
this.manager = manager;
|
||||
|
||||
this.hammer.on("tiltstart", this.onTouchDown);
|
||||
this.hammer.on("tiltmove", this.onTouchMove);
|
||||
this.hammer.on("tiltend", this.onTouchUp);
|
||||
this.hammer.on("tiltcancel", this.onTouchUp);
|
||||
|
||||
window.addEventListener("resize", this.updatePixelToSpeedMultiplier);
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.hammer.off("tiltstart", this.onTouchDown);
|
||||
this.hammer.off("tiltmove", this.onTouchMove);
|
||||
this.hammer.off("tiltend", this.onTouchUp);
|
||||
this.hammer.off("tiltcancel", this.onTouchUp);
|
||||
|
||||
window.removeEventListener("resize", this.updatePixelToSpeedMultiplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param delta {number}
|
||||
* @param map {Map}
|
||||
*/
|
||||
update(delta, map) {
|
||||
if (this.deltaAngle === 0) return;
|
||||
|
||||
let smoothing = this.stiffness / (16.666 / delta);
|
||||
smoothing = MathUtils.clamp(smoothing, 0, 1);
|
||||
|
||||
this.manager.angle += this.deltaAngle * smoothing * this.speed * this.pixelToSpeedMultiplierY;
|
||||
|
||||
this.deltaAngle *= 1 - smoothing;
|
||||
if (Math.abs(this.deltaAngle) < 0.0001) {
|
||||
this.deltaAngle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.deltaAngle = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param evt {object}
|
||||
*/
|
||||
onTouchDown = evt => {
|
||||
this.moving = true;
|
||||
this.deltaAngle = 0;
|
||||
this.lastY = evt.center.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param evt {object}
|
||||
*/
|
||||
onTouchMove = evt => {
|
||||
if(this.moving){
|
||||
this.deltaAngle -= evt.center.y - this.lastY;
|
||||
}
|
||||
|
||||
this.lastY = evt.center.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param evt {object}
|
||||
*/
|
||||
onTouchUp = evt => {
|
||||
this.moving = false;
|
||||
}
|
||||
|
||||
updatePixelToSpeedMultiplier = () => {
|
||||
this.pixelToSpeedMultiplierY = 1 / this.target.clientHeight;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,148 @@
|
||||
import {MathUtils, Vector2} from "three";
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import {VEC2_ZERO} from "../../../util/Utils";
|
||||
|
||||
export class TouchMoveControls {
|
||||
|
||||
static tempVec2_1 = new Vector2();
|
||||
|
||||
/**
|
||||
* @param target {Element}
|
||||
* @param hammer {Manager}
|
||||
* @param speed {number}
|
||||
* @param stiffness {number}
|
||||
*/
|
||||
constructor(target, hammer, speed, stiffness) {
|
||||
this.target = target;
|
||||
this.hammer = hammer;
|
||||
this.manager = null;
|
||||
|
||||
this.moving = false;
|
||||
this.lastPosition = new Vector2();
|
||||
this.deltaPosition = new Vector2();
|
||||
|
||||
this.speed = speed;
|
||||
this.stiffness = stiffness;
|
||||
|
||||
this.pixelToSpeedMultiplierX = 0;
|
||||
this.pixelToSpeedMultiplierY = 0;
|
||||
this.updatePixelToSpeedMultiplier();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param manager {ControlsManager}
|
||||
*/
|
||||
start(manager) {
|
||||
this.manager = manager;
|
||||
|
||||
this.hammer.on("movestart", this.onTouchDown);
|
||||
this.hammer.on("movemove", this.onTouchMove);
|
||||
this.hammer.on("moveend", this.onTouchUp);
|
||||
this.hammer.on("movecancel", this.onTouchUp);
|
||||
|
||||
window.addEventListener("resize", this.updatePixelToSpeedMultiplier);
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.hammer.off("movestart", this.onTouchDown);
|
||||
this.hammer.off("movemove", this.onTouchMove);
|
||||
this.hammer.off("moveend", this.onTouchUp);
|
||||
this.hammer.off("movecancel", this.onTouchUp);
|
||||
|
||||
window.removeEventListener("resize", this.updatePixelToSpeedMultiplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param delta {number}
|
||||
* @param map {Map}
|
||||
*/
|
||||
update(delta, map) {
|
||||
if (this.deltaPosition.x === 0 && this.deltaPosition.y === 0) return;
|
||||
|
||||
let smoothing = this.stiffness / (16.666 / delta);
|
||||
smoothing = MathUtils.clamp(smoothing, 0, 1);
|
||||
|
||||
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.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) {
|
||||
this.deltaPosition.set(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.deltaPosition.set(0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param evt {object}
|
||||
*/
|
||||
onTouchDown = evt => {
|
||||
if (evt.pointerType === "mouse") return;
|
||||
|
||||
this.moving = true;
|
||||
this.deltaPosition.set(0, 0);
|
||||
this.lastPosition.set(evt.center.x, evt.center.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param evt {object}
|
||||
*/
|
||||
onTouchMove = evt => {
|
||||
if (evt.pointerType === "mouse") return;
|
||||
|
||||
let position = TouchMoveControls.tempVec2_1.set(evt.center.x, evt.center.y);
|
||||
|
||||
if(this.moving){
|
||||
this.deltaPosition.sub(position).add(this.lastPosition);
|
||||
}
|
||||
|
||||
this.lastPosition.copy(position);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param evt {object}
|
||||
*/
|
||||
onTouchUp = evt => {
|
||||
if (evt.pointerType === "mouse") return;
|
||||
|
||||
this.moving = false;
|
||||
}
|
||||
|
||||
updatePixelToSpeedMultiplier = () => {
|
||||
this.pixelToSpeedMultiplierX = (1 / this.target.clientWidth) * (this.target.clientWidth / this.target.clientHeight);
|
||||
this.pixelToSpeedMultiplierY = 1 / this.target.clientHeight;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import {MathUtils} from "three";
|
||||
|
||||
export class TouchRotateControls {
|
||||
|
||||
/**
|
||||
* @param hammer {Manager}
|
||||
* @param speed {number}
|
||||
* @param stiffness {number}
|
||||
*/
|
||||
constructor(hammer, speed, stiffness) {
|
||||
this.hammer = hammer;
|
||||
this.manager = null;
|
||||
|
||||
this.moving = false;
|
||||
this.lastRotation = 0;
|
||||
this.deltaRotation = 0;
|
||||
|
||||
this.speed = speed;
|
||||
this.stiffness = stiffness;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param manager {ControlsManager}
|
||||
*/
|
||||
start(manager) {
|
||||
this.manager = manager;
|
||||
|
||||
this.hammer.on("rotatestart", this.onTouchDown);
|
||||
this.hammer.on("rotatemove", this.onTouchMove);
|
||||
this.hammer.on("rotateend", this.onTouchUp);
|
||||
this.hammer.on("rotatecancel", this.onTouchUp);
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.hammer.off("rotatestart", this.onTouchDown);
|
||||
this.hammer.off("rotatemove", this.onTouchMove);
|
||||
this.hammer.off("rotateend", this.onTouchUp);
|
||||
this.hammer.off("rotatecancel", this.onTouchUp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param delta {number}
|
||||
* @param map {Map}
|
||||
*/
|
||||
update(delta, map) {
|
||||
if (this.deltaRotation === 0) return;
|
||||
|
||||
let smoothing = this.stiffness / (16.666 / delta);
|
||||
smoothing = MathUtils.clamp(smoothing, 0, 1);
|
||||
|
||||
this.manager.rotation += this.deltaRotation * smoothing * this.speed;
|
||||
|
||||
this.deltaRotation *= 1 - smoothing;
|
||||
if (Math.abs(this.deltaRotation) < 0.0001) {
|
||||
this.deltaRotation = 0;
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.deltaRotation = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param evt {object}
|
||||
*/
|
||||
onTouchDown = evt => {
|
||||
this.moving = true;
|
||||
this.deltaRotation = 0;
|
||||
this.lastRotation = evt.rotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param evt {object}
|
||||
*/
|
||||
onTouchMove = evt => {
|
||||
if(this.moving){
|
||||
let delta = evt.rotation - this.lastRotation;
|
||||
if (delta > 180) delta -= 360;
|
||||
if (delta < -180) delta += 360;
|
||||
|
||||
this.deltaRotation -= delta;
|
||||
}
|
||||
|
||||
this.lastRotation = evt.rotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param evt {object}
|
||||
*/
|
||||
onTouchUp = evt => {
|
||||
this.moving = false;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
export class TouchZoomControls {
|
||||
|
||||
/**
|
||||
* @param hammer {Manager}
|
||||
*/
|
||||
constructor(hammer) {
|
||||
this.hammer = hammer;
|
||||
this.manager = null;
|
||||
|
||||
this.moving = false;
|
||||
this.deltaZoom = 1;
|
||||
this.lastZoom = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param manager {ControlsManager}
|
||||
*/
|
||||
start(manager) {
|
||||
this.manager = manager;
|
||||
|
||||
this.hammer.on("zoomstart", this.onTouchDown);
|
||||
this.hammer.on("zoommove", this.onTouchMove);
|
||||
this.hammer.on("zoomend", this.onTouchUp);
|
||||
this.hammer.on("zoomcancel", this.onTouchUp);
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.hammer.off("zoomstart", this.onTouchDown);
|
||||
this.hammer.off("zoommove", this.onTouchMove);
|
||||
this.hammer.off("zoomend", this.onTouchUp);
|
||||
this.hammer.off("zoomcancel", this.onTouchUp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param delta {number}
|
||||
* @param map {Map}
|
||||
*/
|
||||
update(delta, map) {
|
||||
if (this.deltaZoom === 1) return;
|
||||
|
||||
this.manager.distance /= this.deltaZoom;
|
||||
this.deltaZoom = 1;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.deltaZoom = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param evt {object}
|
||||
*/
|
||||
onTouchDown = evt => {
|
||||
this.moving = true;
|
||||
this.lastZoom = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param evt {object}
|
||||
*/
|
||||
onTouchMove = evt => {
|
||||
if(this.moving){
|
||||
this.deltaZoom *= evt.scale / this.lastZoom;
|
||||
}
|
||||
|
||||
this.lastZoom = evt.scale;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param evt {object}
|
||||
*/
|
||||
onTouchUp = evt => {
|
||||
this.moving = false;
|
||||
}
|
||||
|
||||
}
|
138
BlueMapCommon/BlueMapVue/BlueMapWeb/src/map/LowresTileLoader.js
Normal file
138
BlueMapCommon/BlueMapVue/BlueMapWeb/src/map/LowresTileLoader.js
Normal file
@ -0,0 +1,138 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
import {pathFromCoords} from "../util/Utils";
|
||||
import {
|
||||
TextureLoader,
|
||||
Mesh,
|
||||
PlaneBufferGeometry,
|
||||
VertexColors,
|
||||
FrontSide,
|
||||
ShaderMaterial,
|
||||
NearestFilter,
|
||||
ClampToEdgeWrapping,
|
||||
NearestMipMapLinearFilter,
|
||||
Vector2
|
||||
} from "three";
|
||||
|
||||
export class LowresTileLoader {
|
||||
|
||||
constructor(tilePath, tileSettings, lod, vertexShader, fragmentShader, uniforms, loadBlocker = () => Promise.resolve(), tileCacheHash = 0) {
|
||||
Object.defineProperty( this, 'isLowresTileLoader', { value: true } );
|
||||
|
||||
this.tilePath = tilePath;
|
||||
this.tileSettings = tileSettings;
|
||||
this.lod = lod;
|
||||
this.loadBlocker = loadBlocker;
|
||||
this.tileCacheHash = tileCacheHash;
|
||||
|
||||
this.vertexShader = vertexShader;
|
||||
this.fragmentShader = fragmentShader;
|
||||
this.uniforms = uniforms;
|
||||
|
||||
this.textureLoader = new TextureLoader();
|
||||
this.geometry = new PlaneBufferGeometry(
|
||||
tileSettings.tileSize.x + 1, tileSettings.tileSize.z + 1,
|
||||
Math.ceil(100 / (lod * 2)), Math.ceil(100 / (lod * 2))
|
||||
);
|
||||
this.geometry.deleteAttribute('normal');
|
||||
this.geometry.deleteAttribute('uv');
|
||||
this.geometry.rotateX(-Math.PI / 2);
|
||||
this.geometry.translate(tileSettings.tileSize.x / 2 + 1, 0, tileSettings.tileSize.x / 2 + 1);
|
||||
}
|
||||
|
||||
load = (tileX, tileZ, cancelCheck = () => false) => {
|
||||
let tileUrl = this.tilePath + this.lod + "/" + pathFromCoords(tileX, tileZ) + '.png';
|
||||
|
||||
//await this.loadBlocker();
|
||||
return new Promise((resolve, reject) => {
|
||||
this.textureLoader.load(tileUrl + '?' + this.tileCacheHash,
|
||||
async texture => {
|
||||
texture.anisotropy = 1;
|
||||
texture.generateMipmaps = false;
|
||||
texture.magFilter = NearestFilter;
|
||||
texture.minFilter = texture.generateMipmaps ? NearestMipMapLinearFilter : NearestFilter;
|
||||
texture.wrapS = ClampToEdgeWrapping;
|
||||
texture.wrapT = ClampToEdgeWrapping;
|
||||
texture.flipY = false;
|
||||
texture.flatShading = true;
|
||||
|
||||
await this.loadBlocker();
|
||||
if (cancelCheck()){
|
||||
texture.dispose();
|
||||
reject({status: "cancelled"});
|
||||
return;
|
||||
}
|
||||
|
||||
const scale = Math.pow(this.tileSettings.lodFactor, this.lod - 1);
|
||||
|
||||
let material = new ShaderMaterial({
|
||||
uniforms: {
|
||||
...this.uniforms,
|
||||
tileSize: {
|
||||
value: new Vector2(this.tileSettings.tileSize.x, this.tileSettings.tileSize.z)
|
||||
},
|
||||
textureSize: {
|
||||
value: new Vector2(texture.image.width, texture.image.height)
|
||||
},
|
||||
textureImage: {
|
||||
type: 't',
|
||||
value: texture
|
||||
},
|
||||
lod: {
|
||||
value: this.lod
|
||||
},
|
||||
lodScale: {
|
||||
value: scale
|
||||
}
|
||||
},
|
||||
vertexShader: this.vertexShader,
|
||||
fragmentShader: this.fragmentShader,
|
||||
transparent: false,
|
||||
depthWrite: true,
|
||||
depthTest: true,
|
||||
vertexColors: VertexColors,
|
||||
side: FrontSide,
|
||||
wireframe: false,
|
||||
});
|
||||
|
||||
let object = new Mesh(this.geometry, material);
|
||||
|
||||
object.position.set(tileX * this.tileSettings.tileSize.x * scale, 0, tileZ * this.tileSettings.tileSize.z * scale);
|
||||
object.scale.set(scale, 1, scale);
|
||||
|
||||
object.userData.tileUrl = tileUrl;
|
||||
object.userData.tileType = "lowres";
|
||||
|
||||
object.updateMatrixWorld(true);
|
||||
|
||||
resolve(object);
|
||||
},
|
||||
undefined,
|
||||
reject
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
434
BlueMapCommon/BlueMapVue/BlueMapWeb/src/map/Map.js
Normal file
434
BlueMapCommon/BlueMapVue/BlueMapWeb/src/map/Map.js
Normal file
@ -0,0 +1,434 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
import {
|
||||
ClampToEdgeWrapping,
|
||||
Color,
|
||||
FileLoader,
|
||||
FrontSide,
|
||||
NearestFilter,
|
||||
NearestMipMapLinearFilter,
|
||||
Raycaster,
|
||||
Scene,
|
||||
ShaderMaterial,
|
||||
Texture,
|
||||
Vector3,
|
||||
VertexColors
|
||||
} from "three";
|
||||
import {alert, dispatchEvent, generateCacheHash, getPixel, hashTile, stringToImage, vecArrToObj} from "../util/Utils";
|
||||
import {TileManager} from "./TileManager";
|
||||
import {TileLoader} from "./TileLoader";
|
||||
import {LowresTileLoader} from "./LowresTileLoader";
|
||||
|
||||
export class Map {
|
||||
|
||||
/**
|
||||
* @param id {string}
|
||||
* @param dataUrl {string}
|
||||
* @param loadBlocker {function: Promise<void>}
|
||||
* @param events {EventTarget}
|
||||
*/
|
||||
constructor(id, dataUrl, loadBlocker, events = null) {
|
||||
Object.defineProperty( this, 'isMap', { value: true } );
|
||||
|
||||
this.loadBlocker = loadBlocker;
|
||||
this.events = events;
|
||||
|
||||
this.data = {
|
||||
id: id,
|
||||
sorting: 0,
|
||||
dataUrl: dataUrl,
|
||||
settingsUrl: dataUrl + "settings.json",
|
||||
texturesUrl: dataUrl + "textures.json",
|
||||
name: id,
|
||||
startPos: {x: 0, z: 0},
|
||||
skyColor: new Color(),
|
||||
ambientLight: 0,
|
||||
hires: {
|
||||
tileSize: {x: 32, z: 32},
|
||||
scale: {x: 1, z: 1},
|
||||
translate: {x: 2, z: 2}
|
||||
},
|
||||
lowres: {
|
||||
tileSize: {x: 32, z: 32},
|
||||
lodFactor: 5,
|
||||
lodCount: 3
|
||||
}
|
||||
};
|
||||
|
||||
this.raycaster = new Raycaster();
|
||||
|
||||
/** @type {ShaderMaterial[]} */
|
||||
this.hiresMaterial = null;
|
||||
/** @type {ShaderMaterial} */
|
||||
this.lowresMaterial = null;
|
||||
/** @type {Texture[]} */
|
||||
this.loadedTextures = [];
|
||||
|
||||
/** @type {TileManager} */
|
||||
this.hiresTileManager = null;
|
||||
/** @type {TileManager[]} */
|
||||
this.lowresTileManager = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads textures and materials for this map so it is ready to load map-tiles
|
||||
* @param hiresVertexShader {string}
|
||||
* @param hiresFragmentShader {string}
|
||||
* @param lowresVertexShader {string}
|
||||
* @param lowresFragmentShader {string}
|
||||
* @param uniforms {object}
|
||||
* @param tileCacheHash {number}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
load(hiresVertexShader, hiresFragmentShader, lowresVertexShader, lowresFragmentShader, uniforms, tileCacheHash = 0) {
|
||||
this.unload()
|
||||
|
||||
let settingsPromise = this.loadSettings();
|
||||
let textureFilePromise = this.loadTexturesFile();
|
||||
|
||||
this.lowresMaterial = this.createLowresMaterial(lowresVertexShader, lowresFragmentShader, uniforms);
|
||||
|
||||
return Promise.all([settingsPromise, textureFilePromise])
|
||||
.then(values => {
|
||||
let textures = values[1];
|
||||
if (textures === null) throw new Error("Failed to parse textures.json!");
|
||||
|
||||
this.hiresMaterial = this.createHiresMaterial(hiresVertexShader, hiresFragmentShader, uniforms, textures);
|
||||
|
||||
this.hiresTileManager = new TileManager(new TileLoader(`${this.data.dataUrl}tiles/0/`, this.hiresMaterial, this.data.hires, this.loadBlocker, tileCacheHash), this.onTileLoad("hires"), this.onTileUnload("hires"), this.events);
|
||||
this.hiresTileManager.scene.autoUpdate = false;
|
||||
|
||||
this.lowresTileManager = [];
|
||||
for (let i = 0; i < this.data.lowres.lodCount; i++) {
|
||||
this.lowresTileManager[i] = new TileManager(new LowresTileLoader(`${this.data.dataUrl}tiles/`, this.data.lowres, i + 1, lowresVertexShader, lowresFragmentShader, uniforms, async () => {}, tileCacheHash), this.onTileLoad("lowres"), this.onTileUnload("lowres"), this.events);
|
||||
this.lowresTileManager[i].scene.autoUpdate = false;
|
||||
}
|
||||
|
||||
alert(this.events, `Map '${this.data.id}' is loaded.`, "fine");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the settings of this map
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
loadSettings() {
|
||||
return this.loadSettingsFile()
|
||||
.then(worldSettings => {
|
||||
this.data.name = worldSettings.name ? worldSettings.name : this.data.name;
|
||||
|
||||
this.data.sorting = worldSettings.sorting ? worldSettings.sorting : this.data.sorting;
|
||||
|
||||
this.data.startPos = {...this.data.startPos, ...vecArrToObj(worldSettings.startPos, true)};
|
||||
|
||||
if (worldSettings.skyColor && worldSettings.skyColor.length >= 3) {
|
||||
this.data.skyColor.setRGB(
|
||||
worldSettings.skyColor[0],
|
||||
worldSettings.skyColor[1],
|
||||
worldSettings.skyColor[2]
|
||||
);
|
||||
}
|
||||
|
||||
this.data.ambientLight = worldSettings.ambientLight ? worldSettings.ambientLight : this.data.ambientLight;
|
||||
|
||||
if (worldSettings.hires === undefined) worldSettings.hires = {};
|
||||
if (worldSettings.lowres === undefined) worldSettings.lowres = {};
|
||||
|
||||
this.data.hires = {
|
||||
tileSize: {...this.data.hires.tileSize, ...vecArrToObj(worldSettings.hires.tileSize, true)},
|
||||
scale: {...this.data.hires.scale, ...vecArrToObj(worldSettings.hires.scale, true)},
|
||||
translate: {...this.data.hires.translate, ...vecArrToObj(worldSettings.hires.translate, true)}
|
||||
};
|
||||
this.data.lowres = {
|
||||
tileSize: {...this.data.lowres.tileSize, ...vecArrToObj(worldSettings.lowres.tileSize, true)},
|
||||
lodFactor: worldSettings.lowres.lodFactor !== undefined ? worldSettings.lowres.lodFactor : this.data.lowres.lodFactor,
|
||||
lodCount: worldSettings.lowres.lodCount !== undefined ? worldSettings.lowres.lodCount : this.data.lowres.lodCount
|
||||
};
|
||||
|
||||
alert(this.events, `Settings for map '${this.data.id}' loaded.`, "fine");
|
||||
});
|
||||
}
|
||||
|
||||
onTileLoad = layer => tile => {
|
||||
dispatchEvent(this.events, "bluemapMapTileLoaded", {
|
||||
tile: tile,
|
||||
layer: layer
|
||||
});
|
||||
}
|
||||
|
||||
onTileUnload = layer => tile => {
|
||||
dispatchEvent(this.events, "bluemapMapTileUnloaded", {
|
||||
tile: tile,
|
||||
layer: layer
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param x {number}
|
||||
* @param z {number}
|
||||
* @param hiresViewDistance {number}
|
||||
* @param lowresViewDistance {number}
|
||||
*/
|
||||
loadMapArea(x, z, hiresViewDistance, lowresViewDistance) {
|
||||
if (!this.isLoaded) return;
|
||||
|
||||
for (let i = this.lowresTileManager.length - 1; i >= 0; i--) {
|
||||
const lod = i + 1;
|
||||
const scale = Math.pow(this.data.lowres.lodFactor, lod - 1);
|
||||
const lowresX = Math.floor(x / (this.data.lowres.tileSize.x * scale));
|
||||
const lowresZ = Math.floor(z / (this.data.lowres.tileSize.z * scale));
|
||||
const lowresViewX = Math.floor(lowresViewDistance / this.data.lowres.tileSize.x);
|
||||
const lowresViewZ = Math.floor(lowresViewDistance / this.data.lowres.tileSize.z);
|
||||
this.lowresTileManager[i].loadAroundTile(lowresX, lowresZ, lowresViewX, lowresViewZ);
|
||||
}
|
||||
|
||||
const hiresX = Math.floor((x - this.data.hires.translate.x) / this.data.hires.tileSize.x);
|
||||
const hiresZ = Math.floor((z - this.data.hires.translate.z) / this.data.hires.tileSize.z);
|
||||
const hiresViewX = Math.floor(hiresViewDistance / this.data.hires.tileSize.x);
|
||||
const hiresViewZ = Math.floor(hiresViewDistance / this.data.hires.tileSize.z);
|
||||
this.hiresTileManager.loadAroundTile(hiresX, hiresZ, hiresViewX, hiresViewZ);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the settings.json file for this map
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
loadSettingsFile() {
|
||||
return new Promise((resolve, reject) => {
|
||||
alert(this.events, `Loading settings for map '${this.data.id}'...`, "fine");
|
||||
|
||||
let loader = new FileLoader();
|
||||
loader.setResponseType("json");
|
||||
loader.load(this.data.settingsUrl + "?" + generateCacheHash(),
|
||||
resolve,
|
||||
() => {},
|
||||
() => reject(`Failed to load the settings.json for map: ${this.data.id}`)
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the textures.json file for this map
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
loadTexturesFile() {
|
||||
return new Promise((resolve, reject) => {
|
||||
alert(this.events, `Loading textures for map '${this.data.id}'...`, "fine");
|
||||
|
||||
let loader = new FileLoader();
|
||||
loader.setResponseType("json");
|
||||
loader.load(this.data.texturesUrl + "?" + generateCacheHash(),
|
||||
resolve,
|
||||
() => {},
|
||||
() => reject(`Failed to load the textures.json for map: ${this.data.id}`)
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a hires Material with the given textures
|
||||
* @param vertexShader {string}
|
||||
* @param fragmentShader {string}
|
||||
* @param uniforms {object}
|
||||
* @param textures {{
|
||||
* resourcePath: string,
|
||||
* color: number[],
|
||||
* halfTransparent: boolean,
|
||||
* texture: string
|
||||
* }[]} the textures-data
|
||||
* @returns {ShaderMaterial[]} the hires Material (array because its a multi-material)
|
||||
*/
|
||||
createHiresMaterial(vertexShader, fragmentShader, uniforms, textures) {
|
||||
let materials = [];
|
||||
if (!Array.isArray(textures)) throw new Error("Invalid texture.json: 'textures' is not an array!")
|
||||
for (let i = 0; i < textures.length; i++) {
|
||||
let textureSettings = textures[i];
|
||||
|
||||
let color = textureSettings.color;
|
||||
if (!Array.isArray(color) || color.length < 4){
|
||||
color = [0, 0, 0, 0];
|
||||
}
|
||||
|
||||
let opaque = color[3] === 1;
|
||||
let transparent = !!textureSettings.halfTransparent;
|
||||
|
||||
let texture = new Texture();
|
||||
texture.image = stringToImage(textureSettings.texture);
|
||||
|
||||
texture.anisotropy = 1;
|
||||
texture.generateMipmaps = opaque || transparent;
|
||||
texture.magFilter = NearestFilter;
|
||||
texture.minFilter = texture.generateMipmaps ? NearestMipMapLinearFilter : NearestFilter;
|
||||
texture.wrapS = ClampToEdgeWrapping;
|
||||
texture.wrapT = ClampToEdgeWrapping;
|
||||
texture.flipY = false;
|
||||
texture.flatShading = true;
|
||||
texture.image.addEventListener("load", () => texture.needsUpdate = true);
|
||||
|
||||
this.loadedTextures.push(texture);
|
||||
|
||||
let material = new ShaderMaterial({
|
||||
uniforms: {
|
||||
...uniforms,
|
||||
textureImage: {
|
||||
type: 't',
|
||||
value: texture
|
||||
},
|
||||
transparent: { value: transparent }
|
||||
},
|
||||
vertexShader: vertexShader,
|
||||
fragmentShader: fragmentShader,
|
||||
transparent: transparent,
|
||||
depthWrite: true,
|
||||
depthTest: true,
|
||||
vertexColors: VertexColors,
|
||||
side: FrontSide,
|
||||
wireframe: false,
|
||||
});
|
||||
|
||||
material.needsUpdate = true;
|
||||
materials[i] = material;
|
||||
}
|
||||
|
||||
return materials;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a lowres Material
|
||||
* @param vertexShader {string}
|
||||
* @param fragmentShader {string}
|
||||
* @param uniforms {object}
|
||||
* @returns {ShaderMaterial} the hires Material
|
||||
*/
|
||||
createLowresMaterial(vertexShader, fragmentShader, uniforms) {
|
||||
return new ShaderMaterial({
|
||||
uniforms: uniforms,
|
||||
vertexShader: vertexShader,
|
||||
fragmentShader: fragmentShader,
|
||||
transparent: false,
|
||||
depthWrite: true,
|
||||
depthTest: true,
|
||||
vertexColors: VertexColors,
|
||||
side: FrontSide,
|
||||
wireframe: false
|
||||
});
|
||||
}
|
||||
|
||||
unload() {
|
||||
if (this.hiresTileManager) this.hiresTileManager.unload();
|
||||
this.hiresTileManager = null;
|
||||
|
||||
if (this.lowresTileManager) {
|
||||
for (let i = 0; i < this.lowresTileManager.length; i++) {
|
||||
this.lowresTileManager[i].unload();
|
||||
}
|
||||
this.lowresTileManager = null;
|
||||
}
|
||||
|
||||
if (this.hiresMaterial) this.hiresMaterial.forEach(material => material.dispose());
|
||||
this.hiresMaterial = null;
|
||||
|
||||
if (this.lowresMaterial) this.lowresMaterial.dispose();
|
||||
this.lowresMaterial = null;
|
||||
|
||||
this.loadedTextures.forEach(texture => texture.dispose());
|
||||
this.loadedTextures = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Ray-traces and returns the terrain-height at a specific location, returns <code>false</code> if there is no map-tile loaded at that location
|
||||
* @param x {number}
|
||||
* @param z {number}
|
||||
* @returns {boolean|number}
|
||||
*/
|
||||
terrainHeightAt(x, z) {
|
||||
if (!this.isLoaded) return false;
|
||||
|
||||
this.raycaster.set(
|
||||
new Vector3(x, 300, z), // ray-start
|
||||
new Vector3(0, -1, 0) // ray-direction
|
||||
);
|
||||
this.raycaster.near = 1;
|
||||
this.raycaster.far = 300;
|
||||
this.raycaster.layers.enableAll();
|
||||
|
||||
let hiresTileHash = hashTile(Math.floor((x - this.data.hires.translate.x) / this.data.hires.tileSize.x), Math.floor((z - this.data.hires.translate.z) / this.data.hires.tileSize.z));
|
||||
let tile = this.hiresTileManager.tiles.get(hiresTileHash);
|
||||
|
||||
if (tile?.model) {
|
||||
try {
|
||||
let intersects = this.raycaster.intersectObjects([tile.model]);
|
||||
if (intersects.length > 0) {
|
||||
return intersects[0].point.y;
|
||||
}
|
||||
} catch (ignore) {
|
||||
//empty
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.lowresTileManager.length; i++) {
|
||||
const lod = i + 1;
|
||||
const scale = Math.pow(this.data.lowres.lodFactor, lod - 1);
|
||||
const scaledTileSize = {
|
||||
x: this.data.lowres.tileSize.x * scale,
|
||||
z: this.data.lowres.tileSize.z * scale
|
||||
}
|
||||
const tileX = Math.floor(x / scaledTileSize.x);
|
||||
const tileZ = Math.floor(z / scaledTileSize.z);
|
||||
let lowresTileHash = hashTile(tileX, tileZ);
|
||||
tile = this.lowresTileManager[i].tiles.get(lowresTileHash);
|
||||
|
||||
if (!tile || !tile.model) continue;
|
||||
|
||||
const texture = tile.model.material.uniforms?.textureImage?.value?.image;
|
||||
if (texture == null) continue;
|
||||
|
||||
const color = getPixel(texture, x - tileX * scaledTileSize.x, z - tileZ * scaledTileSize.z + this.data.lowres.tileSize.z + 1);
|
||||
|
||||
let heightUnsigned = color[1] * 256.0 + color[2];
|
||||
if (heightUnsigned >= 32768.0) {
|
||||
return -(65535.0 - heightUnsigned);
|
||||
} else {
|
||||
return heightUnsigned;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.unload();
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get isLoaded() {
|
||||
return !!(this.hiresMaterial && this.lowresMaterial);
|
||||
}
|
||||
|
||||
}
|
103
BlueMapCommon/BlueMapVue/BlueMapWeb/src/map/Tile.js
Normal file
103
BlueMapCommon/BlueMapVue/BlueMapWeb/src/map/Tile.js
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
export class Tile {
|
||||
|
||||
/**
|
||||
* @param x {number}
|
||||
* @param z {number}
|
||||
* @param onLoad {function(Tile)}
|
||||
* @param onUnload {function(Tile)}
|
||||
*/
|
||||
constructor(x, z, onLoad, onUnload) {
|
||||
Object.defineProperty( this, 'isTile', { value: true } );
|
||||
|
||||
/** @type {THREE.Mesh} */
|
||||
this.model = null;
|
||||
|
||||
this.onLoad = onLoad;
|
||||
this.onUnload = onUnload;
|
||||
|
||||
this.x = x;
|
||||
this.z = z;
|
||||
|
||||
this.unloaded = true;
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param tileLoader {TileLoader}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
load(tileLoader) {
|
||||
if (this.loading) return Promise.reject("tile is already loading!");
|
||||
this.loading = true;
|
||||
|
||||
this.unload();
|
||||
|
||||
this.unloaded = false;
|
||||
return tileLoader.load(this.x, this.z, () => this.unloaded)
|
||||
.then(model => {
|
||||
if (this.unloaded){
|
||||
Tile.disposeModel(model);
|
||||
return;
|
||||
}
|
||||
|
||||
this.model = model;
|
||||
this.onLoad(this);
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
unload() {
|
||||
this.unloaded = true;
|
||||
if (this.model) {
|
||||
this.onUnload(this);
|
||||
|
||||
Tile.disposeModel(this.model);
|
||||
|
||||
this.model = null;
|
||||
}
|
||||
}
|
||||
|
||||
static disposeModel(model) {
|
||||
if (model.userData?.tileType === "hires") {
|
||||
model.geometry.dispose();
|
||||
}
|
||||
|
||||
else if (model.userData?.tileType === "lowres") {
|
||||
model.material.uniforms.textureImage.value.dispose();
|
||||
model.material.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get loaded() {
|
||||
return !!this.model;
|
||||
}
|
||||
}
|
100
BlueMapCommon/BlueMapVue/BlueMapWeb/src/map/TileLoader.js
Normal file
100
BlueMapCommon/BlueMapVue/BlueMapWeb/src/map/TileLoader.js
Normal file
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
import {pathFromCoords} from "../util/Utils";
|
||||
import {BufferGeometryLoader, FileLoader, Mesh} from "three";
|
||||
|
||||
export class TileLoader {
|
||||
|
||||
/**
|
||||
* @param tilePath {string}
|
||||
* @param material {THREE.Material | THREE.Material[]}
|
||||
* @param tileSettings {{
|
||||
* tileSize: {x: number, z: number},
|
||||
* scale: {x: number, z: number},
|
||||
* translate: {x: number, z: number}
|
||||
* }}
|
||||
* @param loadBlocker {function: Promise}
|
||||
* @param tileCacheHash {number}
|
||||
*/
|
||||
constructor(tilePath, material, tileSettings, loadBlocker = () => Promise.resolve(), tileCacheHash = 0) {
|
||||
Object.defineProperty( this, 'isTileLoader', { value: true } );
|
||||
|
||||
this.tilePath = tilePath;
|
||||
this.material = material;
|
||||
this.tileSettings = tileSettings;
|
||||
|
||||
this.tileCacheHash = tileCacheHash;
|
||||
|
||||
this.loadBlocker = loadBlocker;
|
||||
|
||||
this.fileLoader = new FileLoader();
|
||||
this.fileLoader.setResponseType('json');
|
||||
|
||||
this.bufferGeometryLoader = new BufferGeometryLoader();
|
||||
}
|
||||
|
||||
load = (tileX, tileZ, cancelCheck = () => false) => {
|
||||
let tileUrl = this.tilePath + pathFromCoords(tileX, tileZ) + '.json';
|
||||
|
||||
//await this.loadBlocker();
|
||||
return new Promise((resolve, reject) => {
|
||||
this.fileLoader.load(tileUrl + '?' + this.tileCacheHash,
|
||||
async json => {
|
||||
let geometryJson = json.tileGeometry || {};
|
||||
if (!geometryJson.type || geometryJson.type !== 'BufferGeometry'){
|
||||
reject({status: "empty"});
|
||||
return;
|
||||
}
|
||||
|
||||
await this.loadBlocker();
|
||||
if (cancelCheck()){
|
||||
reject({status: "cancelled"});
|
||||
return;
|
||||
}
|
||||
|
||||
let geometry = this.bufferGeometryLoader.parse(geometryJson);
|
||||
|
||||
let object = new Mesh(geometry, this.material);
|
||||
|
||||
let tileSize = this.tileSettings.tileSize;
|
||||
let translate = this.tileSettings.translate;
|
||||
let scale = this.tileSettings.scale;
|
||||
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 = tileUrl;
|
||||
object.userData.tileType = "hires";
|
||||
|
||||
object.updateMatrixWorld(true);
|
||||
|
||||
resolve(object);
|
||||
},
|
||||
() => {},
|
||||
reject
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
221
BlueMapCommon/BlueMapVue/BlueMapWeb/src/map/TileManager.js
Normal file
221
BlueMapCommon/BlueMapVue/BlueMapWeb/src/map/TileManager.js
Normal file
@ -0,0 +1,221 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
import { Vector2, Scene, Group } from 'three';
|
||||
import { Tile } from './Tile.js';
|
||||
import {alert, hashTile} from '../util/Utils.js';
|
||||
import {TileMap} from "./TileMap";
|
||||
|
||||
export class TileManager {
|
||||
|
||||
static tileMapSize = 100;
|
||||
static tileMapHalfSize = TileManager.tileMapSize / 2;
|
||||
|
||||
/**
|
||||
* @param tileLoader {TileLoader | LowresTileLoader}
|
||||
* @param onTileLoad {function(Tile)}
|
||||
* @param onTileUnload {function(Tile)}
|
||||
* @param events {EventTarget}
|
||||
*/
|
||||
constructor(tileLoader, onTileLoad = null, onTileUnload = null, events = null) {
|
||||
Object.defineProperty( this, 'isTileManager', { value: true } );
|
||||
|
||||
this.sceneParent = new Scene();
|
||||
this.scene = new Group();
|
||||
this.sceneParent.add(this.scene);
|
||||
|
||||
this.events = events;
|
||||
this.tileLoader = tileLoader;
|
||||
|
||||
this.onTileLoad = onTileLoad || function(){};
|
||||
this.onTileUnload = onTileUnload || function(){};
|
||||
|
||||
this.viewDistanceX = 1;
|
||||
this.viewDistanceZ = 1;
|
||||
this.centerTile = new Vector2(0, 0);
|
||||
|
||||
this.currentlyLoading = 0;
|
||||
this.loadTimeout = null;
|
||||
|
||||
//map of loaded tiles
|
||||
this.tiles = new Map();
|
||||
|
||||
// a canvas that keeps track of the loaded tiles, used for shaders
|
||||
this.tileMap = new TileMap(TileManager.tileMapSize, TileManager.tileMapSize);
|
||||
|
||||
this.unloaded = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param x {number}
|
||||
* @param z {number}
|
||||
* @param viewDistanceX {number}
|
||||
* @param viewDistanceZ {number}
|
||||
*/
|
||||
loadAroundTile(x, z, viewDistanceX, viewDistanceZ) {
|
||||
this.unloaded = false;
|
||||
|
||||
let unloadTiles = false;
|
||||
if (this.viewDistanceX > viewDistanceX || this.viewDistanceZ > viewDistanceZ) {
|
||||
unloadTiles = true;
|
||||
}
|
||||
|
||||
this.viewDistanceX = viewDistanceX;
|
||||
this.viewDistanceZ = viewDistanceZ;
|
||||
|
||||
if (viewDistanceX <= 0 || viewDistanceZ <= 0) {
|
||||
this.removeAllTiles();
|
||||
return;
|
||||
}
|
||||
|
||||
if (unloadTiles || this.centerTile.x !== x || this.centerTile.y !== z) {
|
||||
this.centerTile.set(x, z);
|
||||
this.removeFarTiles();
|
||||
|
||||
this.tileMap.setAll(TileMap.EMPTY);
|
||||
this.tiles.forEach(tile => {
|
||||
if (!tile.loading && !tile.unloaded) {
|
||||
this.tileMap.setTile(tile.x - this.centerTile.x + TileManager.tileMapHalfSize, tile.z - this.centerTile.y + TileManager.tileMapHalfSize, TileMap.LOADED);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.loadCloseTiles();
|
||||
}
|
||||
|
||||
unload() {
|
||||
this.unloaded = true;
|
||||
this.removeAllTiles();
|
||||
}
|
||||
|
||||
removeFarTiles() {
|
||||
this.tiles.forEach((tile, hash, map) => {
|
||||
if (
|
||||
tile.x + this.viewDistanceX < this.centerTile.x ||
|
||||
tile.x - this.viewDistanceX > this.centerTile.x ||
|
||||
tile.z + this.viewDistanceZ < this.centerTile.y ||
|
||||
tile.z - this.viewDistanceZ > this.centerTile.y
|
||||
) {
|
||||
tile.unload();
|
||||
map.delete(hash);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
removeAllTiles() {
|
||||
this.tileMap.setAll(TileMap.EMPTY);
|
||||
|
||||
this.tiles.forEach(tile => {
|
||||
tile.unload();
|
||||
});
|
||||
this.tiles.clear();
|
||||
}
|
||||
|
||||
loadCloseTiles = () => {
|
||||
if (this.unloaded) return;
|
||||
if (!this.loadNextTile()) return;
|
||||
|
||||
if (this.loadTimeout) clearTimeout(this.loadTimeout);
|
||||
|
||||
if (this.currentlyLoading < 8) {
|
||||
this.loadTimeout = setTimeout(this.loadCloseTiles, 0);
|
||||
} else {
|
||||
this.loadTimeout = setTimeout(this.loadCloseTiles, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
loadNextTile() {
|
||||
if (this.unloaded) return false;
|
||||
|
||||
let x = 0;
|
||||
let z = 0;
|
||||
let d = 1;
|
||||
let m = 1;
|
||||
|
||||
while (m < Math.max(this.viewDistanceX, this.viewDistanceZ) * 2 + 1) {
|
||||
while (2 * x * d < m) {
|
||||
if (this.tryLoadTile(this.centerTile.x + x, this.centerTile.y + z)) return true;
|
||||
x = x + d;
|
||||
}
|
||||
while (2 * z * d < m) {
|
||||
if (this.tryLoadTile(this.centerTile.x + x, this.centerTile.y + z)) return true;
|
||||
z = z + d;
|
||||
}
|
||||
d = -1 * d;
|
||||
m = m + 1;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param x {number}
|
||||
* @param z {number}
|
||||
* @returns {boolean}
|
||||
*/
|
||||
tryLoadTile(x, z) {
|
||||
if (this.unloaded) return false;
|
||||
|
||||
if (Math.abs(x - this.centerTile.x) > this.viewDistanceX) return false;
|
||||
if (Math.abs(z - this.centerTile.y) > this.viewDistanceZ) return false;
|
||||
|
||||
let tileHash = hashTile(x, z);
|
||||
|
||||
let tile = this.tiles.get(tileHash);
|
||||
if (tile !== undefined) return false;
|
||||
|
||||
this.currentlyLoading++;
|
||||
|
||||
tile = new Tile(x, z, this.handleLoadedTile, this.handleUnloadedTile);
|
||||
this.tiles.set(tileHash, tile);
|
||||
tile.load(this.tileLoader)
|
||||
.then(() => {
|
||||
if (this.loadTimeout) clearTimeout(this.loadTimeout);
|
||||
this.loadTimeout = setTimeout(this.loadCloseTiles, 0);
|
||||
})
|
||||
.catch(error => {})
|
||||
.finally(() => {
|
||||
this.currentlyLoading--;
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
handleLoadedTile = tile => {
|
||||
this.tileMap.setTile(tile.x - this.centerTile.x + TileManager.tileMapHalfSize, tile.z - this.centerTile.y + TileManager.tileMapHalfSize, TileMap.LOADED);
|
||||
|
||||
this.scene.add(tile.model);
|
||||
this.onTileLoad(tile);
|
||||
}
|
||||
|
||||
handleUnloadedTile = tile => {
|
||||
this.tileMap.setTile(tile.x - this.centerTile.x + TileManager.tileMapHalfSize, tile.z - this.centerTile.y + TileManager.tileMapHalfSize, TileMap.EMPTY);
|
||||
|
||||
this.scene.remove(tile.model);
|
||||
this.onTileUnload(tile);
|
||||
}
|
||||
}
|
81
BlueMapCommon/BlueMapVue/BlueMapWeb/src/map/TileMap.js
Normal file
81
BlueMapCommon/BlueMapVue/BlueMapWeb/src/map/TileMap.js
Normal file
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
import {ClampToEdgeWrapping, LinearFilter, NearestFilter, Texture} from "three";
|
||||
|
||||
export class TileMap {
|
||||
|
||||
static EMPTY = "#000";
|
||||
static LOADED = "#fff";
|
||||
|
||||
/**
|
||||
* @param width {number}
|
||||
* @param height {number}
|
||||
*/
|
||||
constructor(width, height) {
|
||||
this.canvas = document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas');
|
||||
this.canvas.width = width;
|
||||
this.canvas.height = height;
|
||||
|
||||
/**
|
||||
* @type CanvasRenderingContext2D
|
||||
*/
|
||||
this.tileMapContext = this.canvas.getContext('2d', {
|
||||
alpha: false,
|
||||
willReadFrequently: true,
|
||||
});
|
||||
|
||||
this.texture = new Texture(this.canvas);
|
||||
this.texture.generateMipmaps = false;
|
||||
this.texture.magFilter = LinearFilter;
|
||||
this.texture.minFilter = LinearFilter;
|
||||
this.texture.wrapS = ClampToEdgeWrapping;
|
||||
this.texture.wrapT = ClampToEdgeWrapping;
|
||||
this.texture.flipY = false;
|
||||
this.texture.needsUpdate = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param state {string}
|
||||
*/
|
||||
setAll(state) {
|
||||
this.tileMapContext.fillStyle = state;
|
||||
this.tileMapContext.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
|
||||
this.texture.needsUpdate = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param x {number}
|
||||
* @param z {number}
|
||||
* @param state {string}
|
||||
*/
|
||||
setTile(x, z, state) {
|
||||
this.tileMapContext.fillStyle = state;
|
||||
this.tileMapContext.fillRect(x, z, 1, 1);
|
||||
|
||||
this.texture.needsUpdate = true;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the 'Software'), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
import { ShaderChunk } from 'three';
|
||||
|
||||
export const HIRES_FRAGMENT_SHADER = `
|
||||
${ShaderChunk.logdepthbuf_pars_fragment}
|
||||
|
||||
#ifndef texture
|
||||
#define texture texture2D
|
||||
#endif
|
||||
|
||||
uniform sampler2D textureImage;
|
||||
uniform float sunlightStrength;
|
||||
uniform float ambientLight;
|
||||
|
||||
varying vec3 vPosition;
|
||||
//varying vec3 vWorldPosition;
|
||||
varying vec3 vNormal;
|
||||
varying vec2 vUv;
|
||||
varying vec3 vColor;
|
||||
varying float vAo;
|
||||
varying float vSunlight;
|
||||
varying float vBlocklight;
|
||||
//varying float vDistance;
|
||||
|
||||
void main() {
|
||||
vec4 color = texture(textureImage, vUv);
|
||||
if (color.a <= 0.01) discard;
|
||||
|
||||
//apply vertex-color
|
||||
color.rgb *= vColor.rgb;
|
||||
|
||||
//apply ao
|
||||
color.rgb *= vAo;
|
||||
|
||||
//apply light
|
||||
float light = mix(vBlocklight, max(vSunlight, vBlocklight), sunlightStrength);
|
||||
color.rgb *= mix(ambientLight, 1.0, light / 15.0);
|
||||
|
||||
gl_FragColor = color;
|
||||
|
||||
${ShaderChunk.logdepthbuf_fragment}
|
||||
}
|
||||
`;
|
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the 'Software'), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
import { ShaderChunk } from 'three';
|
||||
|
||||
export const HIRES_VERTEX_SHADER = `
|
||||
#include <common>
|
||||
${ShaderChunk.logdepthbuf_pars_vertex}
|
||||
|
||||
attribute float ao;
|
||||
attribute float sunlight;
|
||||
attribute float blocklight;
|
||||
|
||||
varying vec3 vPosition;
|
||||
varying vec3 vNormal;
|
||||
varying vec2 vUv;
|
||||
varying vec3 vColor;
|
||||
varying float vAo;
|
||||
varying float vSunlight;
|
||||
varying float vBlocklight;
|
||||
|
||||
void main() {
|
||||
vPosition = position;
|
||||
vNormal = normal;
|
||||
vUv = uv;
|
||||
vColor = color;
|
||||
vAo = ao;
|
||||
vSunlight = sunlight;
|
||||
vBlocklight = blocklight;
|
||||
|
||||
gl_Position = projectionMatrix * (viewMatrix * modelMatrix * vec4(position, 1));
|
||||
|
||||
${ShaderChunk.logdepthbuf_vertex}
|
||||
}
|
||||
`;
|
@ -0,0 +1,125 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the 'Software'), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
import { ShaderChunk } from 'three';
|
||||
|
||||
export const LOWRES_FRAGMENT_SHADER = `
|
||||
${ShaderChunk.logdepthbuf_pars_fragment}
|
||||
|
||||
#define PI 3.1415926535897932
|
||||
|
||||
#ifndef texture
|
||||
#define texture texture2D
|
||||
#endif
|
||||
|
||||
struct TileMap {
|
||||
sampler2D map;
|
||||
float size;
|
||||
vec2 scale;
|
||||
vec2 translate;
|
||||
vec2 pos;
|
||||
};
|
||||
|
||||
uniform float sunlightStrength;
|
||||
uniform float ambientLight;
|
||||
uniform TileMap hiresTileMap;
|
||||
uniform sampler2D textureImage;
|
||||
uniform vec2 tileSize;
|
||||
uniform vec2 textureSize;
|
||||
uniform float lod;
|
||||
uniform float lodScale;
|
||||
|
||||
varying vec3 vPosition;
|
||||
varying vec3 vWorldPosition;
|
||||
varying float vDistance;
|
||||
|
||||
float metaToHeight(vec4 meta) {
|
||||
float heightUnsigned = meta.g * 65280.0 + meta.b * 255.0;
|
||||
if (heightUnsigned >= 32768.0) {
|
||||
return -(65535.0 - heightUnsigned);
|
||||
} else {
|
||||
return heightUnsigned;
|
||||
}
|
||||
}
|
||||
|
||||
float metaToLight(vec4 meta) {
|
||||
return meta.r * 255.0;
|
||||
}
|
||||
|
||||
vec2 posToColorUV(vec2 pos) {
|
||||
return vec2(pos.x / textureSize.x, min(pos.y, tileSize.y) / textureSize.y);
|
||||
}
|
||||
|
||||
vec2 posToMetaUV(vec2 pos) {
|
||||
return vec2(pos.x / textureSize.x, pos.y / textureSize.y + 0.5);
|
||||
}
|
||||
|
||||
|
||||
void main() {
|
||||
//discard if hires tile is loaded at that position
|
||||
if (vDistance < 900.0 && texture(hiresTileMap.map, ((vWorldPosition.xz - hiresTileMap.translate) / hiresTileMap.scale - hiresTileMap.pos) / hiresTileMap.size + 0.5).r > 0.75) discard;
|
||||
|
||||
vec4 color = texture(textureImage, posToColorUV(vPosition.xz));
|
||||
vec4 meta = texture(textureImage, posToMetaUV(vPosition.xz));
|
||||
|
||||
float height = metaToHeight(meta);
|
||||
|
||||
float heightX = metaToHeight(texture(textureImage, posToMetaUV(vPosition.xz + vec2(1.0, 0.0))));
|
||||
float heightZ = metaToHeight(texture(textureImage, posToMetaUV(vPosition.xz + vec2(0.0, 1.0))));
|
||||
float heightDiff = ((height - heightX) + (height - heightZ)) / lodScale;
|
||||
float shade = clamp(heightDiff * 0.06, -0.2, 0.04);
|
||||
|
||||
float ao = 0.0;
|
||||
float aoStrength = 0.0;
|
||||
if(lod == 1.0) {
|
||||
aoStrength = smoothstep(PI - 0.8, PI - 0.2, acos(-clamp(viewMatrix[1][2], 0.0, 1.0)));
|
||||
aoStrength *= 1.0 - smoothstep(300.0, 500.0, vDistance);
|
||||
|
||||
if (aoStrength > 0.0) {
|
||||
const float r = 3.0;
|
||||
const float step = 0.2;
|
||||
const float o = step / r * 0.1;
|
||||
for (float vx = -r; vx <= r; vx++) {
|
||||
for (float vz = -r; vz <= r; vz++) {
|
||||
heightDiff = height - metaToHeight(texture(textureImage, posToMetaUV(vPosition.xz + vec2(vx * step, vz * step))));
|
||||
if (heightDiff < 0.0) {
|
||||
ao -= o;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
color.rgb += mix(shade, shade * 0.3 + ao, aoStrength);
|
||||
|
||||
float blockLight = metaToLight(meta);
|
||||
float light = mix(blockLight, 15.0, sunlightStrength);
|
||||
color.rgb *= mix(ambientLight, 1.0, light / 15.0);
|
||||
|
||||
gl_FragColor = color;
|
||||
|
||||
${ShaderChunk.logdepthbuf_fragment}
|
||||
}
|
||||
|
||||
`;
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the 'Software'), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
import { ShaderChunk } from 'three';
|
||||
|
||||
export const LOWRES_VERTEX_SHADER = `
|
||||
#include <common>
|
||||
${ShaderChunk.logdepthbuf_pars_vertex}
|
||||
|
||||
uniform sampler2D textureImage;
|
||||
uniform vec2 tileSize;
|
||||
uniform vec2 textureSize;
|
||||
|
||||
varying vec3 vPosition;
|
||||
varying vec3 vWorldPosition;
|
||||
varying float vDistance;
|
||||
|
||||
float metaToHeight(vec4 meta) {
|
||||
float heightUnsigned = meta.g * 65280.0 + meta.b * 255.0;
|
||||
if (heightUnsigned >= 32768.0) {
|
||||
return -(65535.0 - heightUnsigned);
|
||||
} else {
|
||||
return heightUnsigned;
|
||||
}
|
||||
}
|
||||
|
||||
vec2 posToMetaUV(vec2 pos) {
|
||||
return vec2(pos.x / textureSize.x, pos.y / textureSize.y + 0.5);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vPosition = position;
|
||||
|
||||
vec4 meta = texture(textureImage, posToMetaUV(position.xz));
|
||||
vPosition.y = metaToHeight(meta) + 1.0 - position.x * 0.0001 - position.z * 0.0002; //including small offset-tilt to prevent z-fighting
|
||||
|
||||
vec4 worldPos = modelMatrix * vec4(vPosition, 1);
|
||||
vec4 viewPos = viewMatrix * worldPos;
|
||||
|
||||
vWorldPosition = worldPos.xyz;
|
||||
vDistance = -viewPos.z;
|
||||
|
||||
gl_Position = projectionMatrix * viewPos;
|
||||
|
||||
${ShaderChunk.logdepthbuf_vertex}
|
||||
}
|
||||
|
||||
`;
|
467
BlueMapCommon/BlueMapVue/BlueMapWeb/src/markers/ExtrudeMarker.js
Normal file
467
BlueMapCommon/BlueMapVue/BlueMapWeb/src/markers/ExtrudeMarker.js
Normal file
@ -0,0 +1,467 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
import {Color, DoubleSide, ExtrudeBufferGeometry, Mesh, ShaderMaterial, Shape, Vector2} from "three";
|
||||
import {LineMaterial} from "../util/lines/LineMaterial";
|
||||
import {MARKER_FILL_VERTEX_SHADER} from "./MarkerFillVertexShader";
|
||||
import {MARKER_FILL_FRAGMENT_SHADER} from "./MarkerFillFragmentShader";
|
||||
import {Line2} from "../util/lines/Line2";
|
||||
import {deepEquals} from "../util/Utils";
|
||||
import {LineSegmentsGeometry} from "../util/lines/LineSegmentsGeometry";
|
||||
import {ObjectMarker} from "./ObjectMarker";
|
||||
|
||||
export class ExtrudeMarker extends ObjectMarker {
|
||||
|
||||
/**
|
||||
* @param markerId {string}
|
||||
*/
|
||||
constructor(markerId) {
|
||||
super(markerId);
|
||||
Object.defineProperty(this, 'isExtrudeMarker', {value: true});
|
||||
this.data.type = "extrude";
|
||||
|
||||
let zero = new Vector2();
|
||||
let shape = new Shape([zero, zero, zero]);
|
||||
this.fill = new ExtrudeMarkerFill(shape);
|
||||
this.border = new ExtrudeMarkerBorder(shape);
|
||||
this.border.renderOrder = -1; // render border before fill
|
||||
|
||||
this.add(this.border, this.fill);
|
||||
|
||||
this._markerData = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param minY {number}
|
||||
* @param maxY {number}
|
||||
*/
|
||||
setShapeY(minY, maxY) {
|
||||
let relativeY = maxY - this.position.y;
|
||||
let height = maxY - minY;
|
||||
this.fill.position.y = relativeY;
|
||||
this.border.position.y = relativeY;
|
||||
this.fill.scale.y = height;
|
||||
this.border.scale.y = height;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param shape {Shape}
|
||||
*/
|
||||
setShape(shape) {
|
||||
this.fill.updateGeometry(shape);
|
||||
this.border.updateGeometry(shape);
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {{r: number, g: number, b: number, a: number}} ColorLike
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param markerData {{
|
||||
* position: {x: number, y: number, z: number},
|
||||
* label: string,
|
||||
* detail: string,
|
||||
* shape: {x: number, z: number}[],
|
||||
* shapeMinY: number,
|
||||
* shapeMaxY: number,
|
||||
* link: string,
|
||||
* newTab: boolean,
|
||||
* depthTest: boolean,
|
||||
* lineWidth: number,
|
||||
* lineColor: ColorLike,
|
||||
* fillColor: ColorLike,
|
||||
* minDistance: number,
|
||||
* maxDistance: number
|
||||
* }}
|
||||
*/
|
||||
updateFromData(markerData) {
|
||||
super.updateFromData(markerData);
|
||||
|
||||
// update shape only if needed, based on last update-data
|
||||
if (
|
||||
!this._markerData.shape || !deepEquals(markerData.shape, this._markerData.shape) ||
|
||||
!this._markerData.position || !deepEquals(markerData.position, this._markerData.position)
|
||||
){
|
||||
this.setShape(this.createShapeFromData(markerData.shape));
|
||||
}
|
||||
|
||||
// update shapeY
|
||||
this.setShapeY((markerData.shapeMinY || 0) - 0.01, (markerData.shapeMaxY || 0) + 0.01); // offset by 0.01 to avoid z-fighting
|
||||
|
||||
// update depthTest
|
||||
this.border.depthTest = !!markerData.depthTest;
|
||||
this.fill.depthTest = !!markerData.depthTest;
|
||||
|
||||
// update border-width
|
||||
this.border.linewidth = markerData.lineWidth !== undefined ? markerData.lineWidth : 2;
|
||||
|
||||
// update border-color
|
||||
let bc = markerData.lineColor || {};
|
||||
this.border.color.setRGB((bc.r || 0) / 255, (bc.g || 0) / 255, (bc.b || 0) / 255);
|
||||
this.border.opacity = bc.a || 0;
|
||||
|
||||
// update fill-color
|
||||
let fc = markerData.fillColor || {};
|
||||
this.fill.color.setRGB((fc.r || 0) / 255, (fc.g || 0) / 255, (fc.b || 0) / 255);
|
||||
this.fill.opacity = fc.a || 0;
|
||||
|
||||
// update min/max distances
|
||||
let minDist = markerData.minDistance || 0;
|
||||
let maxDist = markerData.maxDistance !== undefined ? markerData.maxDistance : Number.MAX_VALUE;
|
||||
this.border.fadeDistanceMin = minDist;
|
||||
this.border.fadeDistanceMax = maxDist;
|
||||
this.fill.fadeDistanceMin = minDist;
|
||||
this.fill.fadeDistanceMax = maxDist;
|
||||
|
||||
// save used marker data for next update
|
||||
this._markerData = markerData;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
super.dispose();
|
||||
|
||||
this.fill.dispose();
|
||||
this.border.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Creates a shape from a data object, usually parsed json from a markers.json
|
||||
* @param shapeData {object}
|
||||
* @returns {Shape}
|
||||
*/
|
||||
createShapeFromData(shapeData) {
|
||||
/** @type {THREE.Vector2[]} **/
|
||||
let points = [];
|
||||
|
||||
if (Array.isArray(shapeData)){
|
||||
shapeData.forEach(point => {
|
||||
let x = (point.x || 0) - this.position.x + 0.01; // offset by 0.01 to avoid z-fighting
|
||||
let z = (point.z || 0) - this.position.z + 0.01;
|
||||
|
||||
points.push(new Vector2(x, z));
|
||||
});
|
||||
}
|
||||
|
||||
return new Shape(points);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ExtrudeMarkerFill extends Mesh {
|
||||
|
||||
/**
|
||||
* @param shape {Shape}
|
||||
*/
|
||||
constructor(shape) {
|
||||
let geometry = ExtrudeMarkerFill.createGeometry(shape);
|
||||
let material = new ShaderMaterial({
|
||||
vertexShader: MARKER_FILL_VERTEX_SHADER,
|
||||
fragmentShader: MARKER_FILL_FRAGMENT_SHADER,
|
||||
side: DoubleSide,
|
||||
depthTest: true,
|
||||
transparent: true,
|
||||
uniforms: {
|
||||
markerColor: { value: new Color() },
|
||||
markerOpacity: { value: 0 },
|
||||
fadeDistanceMin: { value: 0 },
|
||||
fadeDistanceMax: { value: Number.MAX_VALUE },
|
||||
}
|
||||
});
|
||||
|
||||
super(geometry, material);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Color}
|
||||
*/
|
||||
get color(){
|
||||
return this.material.uniforms.markerColor.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
get opacity() {
|
||||
return this.material.uniforms.markerOpacity.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param opacity {number}
|
||||
*/
|
||||
set opacity(opacity) {
|
||||
this.material.uniforms.markerOpacity.value = opacity;
|
||||
this.visible = opacity > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get depthTest() {
|
||||
return this.material.depthTest;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param test {boolean}
|
||||
*/
|
||||
set depthTest(test) {
|
||||
this.material.depthTest = test;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
get fadeDistanceMin() {
|
||||
return this.material.uniforms.fadeDistanceMin.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param min {number}
|
||||
*/
|
||||
set fadeDistanceMin(min) {
|
||||
this.material.uniforms.fadeDistanceMin.value = min;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
get fadeDistanceMax() {
|
||||
return this.material.uniforms.fadeDistanceMax.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param max {number}
|
||||
*/
|
||||
set fadeDistanceMax(max) {
|
||||
this.material.uniforms.fadeDistanceMax.value = max;
|
||||
}
|
||||
|
||||
onClick(event) {
|
||||
if (event.intersection) {
|
||||
if (event.intersection.distance > this.fadeDistanceMax) return false;
|
||||
if (event.intersection.distance < this.fadeDistanceMin) return false;
|
||||
}
|
||||
|
||||
return super.onClick(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param shape {Shape}
|
||||
*/
|
||||
updateGeometry(shape) {
|
||||
this.geometry.dispose();
|
||||
this.geometry = ExtrudeMarkerFill.createGeometry(shape);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.geometry.dispose();
|
||||
this.material.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param shape {Shape}
|
||||
* @returns {ExtrudeBufferGeometry}
|
||||
*/
|
||||
static createGeometry(shape) {
|
||||
let geometry = new ExtrudeBufferGeometry(shape, {
|
||||
depth: 1,
|
||||
steps: 5,
|
||||
bevelEnabled: false
|
||||
});
|
||||
geometry.rotateX(Math.PI / 2); //make y to z
|
||||
|
||||
return geometry;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ExtrudeMarkerBorder extends Line2 {
|
||||
|
||||
/**
|
||||
* @param shape {Shape}
|
||||
*/
|
||||
constructor(shape) {
|
||||
let geometry = new LineSegmentsGeometry();
|
||||
geometry.setPositions(ExtrudeMarkerBorder.createLinePoints(shape));
|
||||
|
||||
let material = new LineMaterial({
|
||||
color: new Color(),
|
||||
opacity: 0,
|
||||
transparent: true,
|
||||
linewidth: 1,
|
||||
depthTest: true,
|
||||
vertexColors: false,
|
||||
dashed: false,
|
||||
});
|
||||
material.uniforms.fadeDistanceMin = { value: 0 };
|
||||
material.uniforms.fadeDistanceMax = { value: Number.MAX_VALUE };
|
||||
|
||||
material.resolution.set(window.innerWidth, window.innerHeight);
|
||||
|
||||
super(geometry, material);
|
||||
|
||||
this.computeLineDistances();
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Color}
|
||||
*/
|
||||
get color(){
|
||||
return this.material.color;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
get opacity() {
|
||||
return this.material.opacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param opacity {number}
|
||||
*/
|
||||
set opacity(opacity) {
|
||||
this.material.opacity = opacity;
|
||||
this.visible = opacity > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
get linewidth() {
|
||||
return this.material.linewidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param width {number}
|
||||
*/
|
||||
set linewidth(width) {
|
||||
this.material.linewidth = width;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get depthTest() {
|
||||
return this.material.depthTest;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param test {boolean}
|
||||
*/
|
||||
set depthTest(test) {
|
||||
this.material.depthTest = test;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
get fadeDistanceMin() {
|
||||
return this.material.uniforms.fadeDistanceMin.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param min {number}
|
||||
*/
|
||||
set fadeDistanceMin(min) {
|
||||
this.material.uniforms.fadeDistanceMin.value = min;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
get fadeDistanceMax() {
|
||||
return this.material.uniforms.fadeDistanceMax.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param max {number}
|
||||
*/
|
||||
set fadeDistanceMax(max) {
|
||||
this.material.uniforms.fadeDistanceMax.value = max;
|
||||
}
|
||||
|
||||
onClick(event) {
|
||||
if (event.intersection) {
|
||||
if (event.intersection.distance > this.fadeDistanceMax) return false;
|
||||
if (event.intersection.distance < this.fadeDistanceMin) return false;
|
||||
}
|
||||
|
||||
return super.onClick(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param shape {Shape}
|
||||
*/
|
||||
updateGeometry(shape) {
|
||||
this.geometry = new LineSegmentsGeometry();
|
||||
this.geometry.setPositions(ExtrudeMarkerBorder.createLinePoints(shape));
|
||||
this.computeLineDistances();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param renderer {THREE.WebGLRenderer}
|
||||
*/
|
||||
onBeforeRender(renderer) {
|
||||
renderer.getSize(this.material.resolution);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.geometry.dispose();
|
||||
this.material.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param shape {Shape}
|
||||
* @returns {number[]}
|
||||
*/
|
||||
static createLinePoints(shape) {
|
||||
let points3d = [];
|
||||
let points = shape.getPoints(5);
|
||||
points.push(points[0]);
|
||||
|
||||
let prevPoint = null;
|
||||
points.forEach(point => {
|
||||
|
||||
// vertical line
|
||||
points3d.push(point.x, 0, point.y);
|
||||
points3d.push(point.x, -1, point.y);
|
||||
|
||||
if (prevPoint) {
|
||||
// line to previous point top
|
||||
points3d.push(prevPoint.x, 0, prevPoint.y);
|
||||
points3d.push(point.x, 0, point.y);
|
||||
|
||||
// line to previous point bottom
|
||||
points3d.push(prevPoint.x, -1, prevPoint.y);
|
||||
points3d.push(point.x, -1, point.y);
|
||||
}
|
||||
|
||||
prevPoint = point;
|
||||
});
|
||||
|
||||
return points3d;
|
||||
}
|
||||
|
||||
}
|
143
BlueMapCommon/BlueMapVue/BlueMapWeb/src/markers/HtmlMarker.js
Normal file
143
BlueMapCommon/BlueMapVue/BlueMapWeb/src/markers/HtmlMarker.js
Normal file
@ -0,0 +1,143 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
import {Marker} from "./Marker";
|
||||
import {CSS2DObject} from "../util/CSS2DRenderer";
|
||||
import {htmlToElement} from "../util/Utils";
|
||||
|
||||
export class HtmlMarker extends Marker {
|
||||
|
||||
/**
|
||||
* @param markerId {string}
|
||||
*/
|
||||
constructor(markerId) {
|
||||
super(markerId);
|
||||
Object.defineProperty(this, 'isHtmlMarker', {value: true});
|
||||
this.data.type = "html";
|
||||
|
||||
this.data.label = null;
|
||||
|
||||
this.data.classes = [];
|
||||
|
||||
this.elementObject = new CSS2DObject(htmlToElement(`<div id="bm-marker-${this.data.id}" class="bm-marker-${this.data.type}"></div>`));
|
||||
this.elementObject.onBeforeRender = (renderer, scene, camera) => this.onBeforeRender(renderer, scene, camera);
|
||||
|
||||
this.fadeDistanceMin = 0;
|
||||
this.fadeDistanceMax = Number.MAX_VALUE;
|
||||
|
||||
this.addEventListener( 'removed', () => {
|
||||
if (this.element?.parentNode) this.element.parentNode.removeChild(this.element);
|
||||
});
|
||||
|
||||
this.add(this.elementObject);
|
||||
}
|
||||
|
||||
onBeforeRender(renderer, scene, camera) {
|
||||
if (this.fadeDistanceMax === Number.MAX_VALUE && this.fadeDistanceMin <= 0){
|
||||
this.element.parentNode.style.opacity = undefined;
|
||||
} else {
|
||||
this.element.parentNode.style.opacity = Marker.calculateDistanceOpacity(this.position, camera, this.fadeDistanceMin, this.fadeDistanceMax).toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
get html() {
|
||||
return this.element.innerHTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param html {string}
|
||||
*/
|
||||
set html(html) {
|
||||
this.element.innerHTML = html;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {THREE.Vector2}
|
||||
*/
|
||||
get anchor() {
|
||||
return this.elementObject.anchor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Element}
|
||||
*/
|
||||
get element() {
|
||||
return this.elementObject.element.getElementsByTagName("div")[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param markerData {{
|
||||
* position: {x: number, y: number, z: number},
|
||||
* label: string,
|
||||
* anchor: {x: number, y: number},
|
||||
* html: string,
|
||||
* classes: string[],
|
||||
* minDistance: number,
|
||||
* maxDistance: number
|
||||
* }}
|
||||
*/
|
||||
updateFromData(markerData) {
|
||||
|
||||
// update position
|
||||
let pos = markerData.position || {};
|
||||
this.position.setX(pos.x || 0);
|
||||
this.position.setY(pos.y || 0);
|
||||
this.position.setZ(pos.z || 0);
|
||||
|
||||
// update label
|
||||
this.data.label = markerData.label || null;
|
||||
|
||||
// update anchor
|
||||
let anch = markerData.anchor || {};
|
||||
this.anchor.setX(anch.x || 0);
|
||||
this.anchor.setY(anch.y || 0);
|
||||
|
||||
// update html
|
||||
if (this.element.innerHTML !== markerData.html){
|
||||
this.element.innerHTML = markerData.html;
|
||||
}
|
||||
|
||||
// update style-classes
|
||||
if (this.data.classes !== markerData.classes) {
|
||||
this.data.classes = markerData.classes;
|
||||
this.element.classList.value = `bm-marker-${this.data.type}`;
|
||||
this.element.classList.add(...markerData.classes);
|
||||
}
|
||||
|
||||
// update min/max distances
|
||||
this.fadeDistanceMin = markerData.minDistance || 0;
|
||||
this.fadeDistanceMax = markerData.maxDistance !== undefined ? markerData.maxDistance : Number.MAX_VALUE;
|
||||
|
||||
}
|
||||
|
||||
dispose() {
|
||||
super.dispose();
|
||||
|
||||
if (this.element.parentNode) this.element.parentNode.removeChild(this.element);
|
||||
}
|
||||
|
||||
}
|
296
BlueMapCommon/BlueMapVue/BlueMapWeb/src/markers/LineMarker.js
Normal file
296
BlueMapCommon/BlueMapVue/BlueMapWeb/src/markers/LineMarker.js
Normal file
@ -0,0 +1,296 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
import {Color} from "three";
|
||||
import {LineMaterial} from "../util/lines/LineMaterial";
|
||||
import {LineGeometry} from "../util/lines/LineGeometry";
|
||||
import {Line2} from "../util/lines/Line2";
|
||||
import {deepEquals} from "../util/Utils";
|
||||
import {ObjectMarker} from "./ObjectMarker";
|
||||
|
||||
export class LineMarker extends ObjectMarker {
|
||||
|
||||
/**
|
||||
* @param markerId {string}
|
||||
*/
|
||||
constructor(markerId) {
|
||||
super(markerId);
|
||||
Object.defineProperty(this, 'isLineMarker', {value: true});
|
||||
this.data.type = "line";
|
||||
|
||||
this.line = new LineMarkerLine([0, 0, 0]);
|
||||
|
||||
this.add(this.line);
|
||||
|
||||
this._markerData = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param line {number[] | THREE.Vector3[] | THREE.Curve}
|
||||
*/
|
||||
setLine(line) {
|
||||
/** @type {number[]} */
|
||||
let points;
|
||||
|
||||
if (line.type === 'Curve' || line.type === 'CurvePath') {
|
||||
line = line.getPoints(5);
|
||||
}
|
||||
|
||||
if (Array.isArray(line)) {
|
||||
if (line.length === 0){
|
||||
points = [];
|
||||
} else if (line[0].isVector3) {
|
||||
points = [];
|
||||
line.forEach(point => {
|
||||
points.push(point.x, point.y, point.z);
|
||||
});
|
||||
} else {
|
||||
points = line;
|
||||
}
|
||||
} else {
|
||||
throw new Error("Invalid argument type!");
|
||||
}
|
||||
|
||||
this.line.updateGeometry(points);
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {{r: number, g: number, b: number, a: number}} ColorLike
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param markerData {{
|
||||
* position: {x: number, y: number, z: number},
|
||||
* label: string,
|
||||
* detail: string,
|
||||
* line: {x: number, y: number, z: number}[],
|
||||
* link: string,
|
||||
* newTab: boolean,
|
||||
* depthTest: boolean,
|
||||
* lineWidth: number,
|
||||
* lineColor: ColorLike,
|
||||
* minDistance: number,
|
||||
* maxDistance: number
|
||||
* }}
|
||||
*/
|
||||
updateFromData(markerData) {
|
||||
super.updateFromData(markerData);
|
||||
|
||||
// update shape only if needed, based on last update-data
|
||||
if (
|
||||
!this._markerData.line || !deepEquals(markerData.line, this._markerData.line) ||
|
||||
!this._markerData.position || !deepEquals(markerData.position, this._markerData.position)
|
||||
){
|
||||
this.setLine(this.createPointsFromData(markerData.line));
|
||||
}
|
||||
|
||||
// update depthTest
|
||||
this.line.depthTest = !!markerData.depthTest;
|
||||
|
||||
// update border-width
|
||||
this.line.linewidth = markerData.lineWidth !== undefined ? markerData.lineWidth : 2;
|
||||
|
||||
// update line-color
|
||||
let lc = markerData.lineColor || {};
|
||||
this.line.color.setRGB((lc.r || 0) / 255, (lc.g || 0) / 255, (lc.b || 0) / 255);
|
||||
this.line.opacity = lc.a || 0;
|
||||
|
||||
// update min/max distances
|
||||
let minDist = markerData.minDistance || 0;
|
||||
let maxDist = markerData.maxDistance !== undefined ? markerData.maxDistance : Number.MAX_VALUE;
|
||||
this.line.fadeDistanceMin = minDist;
|
||||
this.line.fadeDistanceMax = maxDist;
|
||||
|
||||
// save used marker data for next update
|
||||
this._markerData = markerData;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
super.dispose();
|
||||
|
||||
this.line.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Creates a shape from a data object, usually parsed json from a markers.json
|
||||
* @param shapeData {object}
|
||||
* @returns {number[]}
|
||||
*/
|
||||
createPointsFromData(shapeData) {
|
||||
/** @type {number[]} **/
|
||||
let points = [];
|
||||
|
||||
if (Array.isArray(shapeData)){
|
||||
shapeData.forEach(point => {
|
||||
let x = (point.x || 0) - this.position.x;
|
||||
let y = (point.y || 0) - this.position.y;
|
||||
let z = (point.z || 0) - this.position.z;
|
||||
|
||||
points.push(x, y, z);
|
||||
});
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class LineMarkerLine extends Line2 {
|
||||
|
||||
/**
|
||||
* @param points {number[]}
|
||||
*/
|
||||
constructor(points) {
|
||||
let geometry = new LineGeometry();
|
||||
geometry.setPositions(points);
|
||||
|
||||
let material = new LineMaterial({
|
||||
color: new Color(),
|
||||
opacity: 0,
|
||||
transparent: true,
|
||||
linewidth: 1,
|
||||
depthTest: true,
|
||||
vertexColors: false,
|
||||
dashed: false,
|
||||
});
|
||||
material.uniforms.fadeDistanceMin = { value: 0 };
|
||||
material.uniforms.fadeDistanceMax = { value: Number.MAX_VALUE };
|
||||
|
||||
material.resolution.set(window.innerWidth, window.innerHeight);
|
||||
|
||||
super(geometry, material);
|
||||
|
||||
this.computeLineDistances();
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Color}
|
||||
*/
|
||||
get color(){
|
||||
return this.material.color;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
get opacity() {
|
||||
return this.material.opacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param opacity {number}
|
||||
*/
|
||||
set opacity(opacity) {
|
||||
this.material.opacity = opacity;
|
||||
this.visible = opacity > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
get linewidth() {
|
||||
return this.material.linewidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param width {number}
|
||||
*/
|
||||
set linewidth(width) {
|
||||
this.material.linewidth = width;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get depthTest() {
|
||||
return this.material.depthTest;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param test {boolean}
|
||||
*/
|
||||
set depthTest(test) {
|
||||
this.material.depthTest = test;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
get fadeDistanceMin() {
|
||||
return this.material.uniforms.fadeDistanceMin.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param min {number}
|
||||
*/
|
||||
set fadeDistanceMin(min) {
|
||||
this.material.uniforms.fadeDistanceMin.value = min;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
get fadeDistanceMax() {
|
||||
return this.material.uniforms.fadeDistanceMax.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param max {number}
|
||||
*/
|
||||
set fadeDistanceMax(max) {
|
||||
this.material.uniforms.fadeDistanceMax.value = max;
|
||||
}
|
||||
|
||||
onClick(event) {
|
||||
if (event.intersection) {
|
||||
if (event.intersection.distance > this.fadeDistanceMax) return false;
|
||||
if (event.intersection.distance < this.fadeDistanceMin) return false;
|
||||
}
|
||||
|
||||
return super.onClick(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param points {number[]}
|
||||
*/
|
||||
updateGeometry(points) {
|
||||
this.geometry = new LineGeometry();
|
||||
this.geometry.setPositions(points);
|
||||
this.computeLineDistances();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param renderer {THREE.WebGLRenderer}
|
||||
*/
|
||||
onBeforeRender(renderer) {
|
||||
renderer.getSize(this.material.resolution);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.geometry.dispose();
|
||||
this.material.dispose();
|
||||
}
|
||||
|
||||
}
|
95
BlueMapCommon/BlueMapVue/BlueMapWeb/src/markers/Marker.js
Normal file
95
BlueMapCommon/BlueMapVue/BlueMapWeb/src/markers/Marker.js
Normal file
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
import {MathUtils, Object3D, Vector3} from "three";
|
||||
|
||||
export class Marker extends Object3D {
|
||||
|
||||
/**
|
||||
* @param markerId {string}
|
||||
*/
|
||||
constructor(markerId) {
|
||||
super();
|
||||
Object.defineProperty(this, 'isMarker', {value: true});
|
||||
|
||||
this.data = {
|
||||
id: markerId,
|
||||
type: "marker",
|
||||
position: this.position,
|
||||
visible: this.visible
|
||||
};
|
||||
|
||||
// redirect parent properties
|
||||
Object.defineProperty(this, "position", {
|
||||
get() { return this.data.position },
|
||||
set(value) { this.data.position = value }
|
||||
});
|
||||
Object.defineProperty(this, "visible", {
|
||||
get() { return this.data.visible },
|
||||
set(value) { this.data.visible = value }
|
||||
});
|
||||
}
|
||||
|
||||
dispose() {}
|
||||
|
||||
/**
|
||||
* Updates this marker from the provided data object, usually parsed form json from a markers.json
|
||||
* @param markerData {object}
|
||||
*/
|
||||
updateFromData(markerData) {}
|
||||
|
||||
// -- helper methods --
|
||||
|
||||
static _posRelativeToCamera = new Vector3();
|
||||
static _cameraDirection = new Vector3();
|
||||
|
||||
/**
|
||||
* @param position {Vector3}
|
||||
* @param camera {THREE.Camera}
|
||||
* @param fadeDistanceMax {number}
|
||||
* @param fadeDistanceMin {number}
|
||||
* @returns {number} - opacity between 0 and 1
|
||||
*/
|
||||
static calculateDistanceOpacity(position, camera, fadeDistanceMin, fadeDistanceMax) {
|
||||
let distance = Marker.calculateDistanceToCameraPlane(position, camera);
|
||||
let minDelta = (distance - fadeDistanceMin) / fadeDistanceMin;
|
||||
let maxDelta = (distance - fadeDistanceMax) / (fadeDistanceMax * 0.5);
|
||||
return Math.min(
|
||||
MathUtils.clamp(minDelta, 0, 1),
|
||||
1 - MathUtils.clamp(maxDelta + 1, 0, 1)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param position {Vector3}
|
||||
* @param camera {THREE.Camera}
|
||||
* @returns {number}
|
||||
*/
|
||||
static calculateDistanceToCameraPlane (position, camera) {
|
||||
Marker._posRelativeToCamera.subVectors(position, camera.position);
|
||||
camera.getWorldDirection(Marker._cameraDirection);
|
||||
return Marker._posRelativeToCamera.dot(Marker._cameraDirection);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the 'Software'), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
import { ShaderChunk } from 'three';
|
||||
|
||||
export const MARKER_FILL_FRAGMENT_SHADER = `
|
||||
${ShaderChunk.logdepthbuf_pars_fragment}
|
||||
|
||||
#define FLT_MAX 3.402823466e+38
|
||||
|
||||
varying vec3 vPosition;
|
||||
//varying vec3 vWorldPosition;
|
||||
//varying vec3 vNormal;
|
||||
//varying vec2 vUv;
|
||||
//varying vec3 vColor;
|
||||
varying float vDistance;
|
||||
|
||||
uniform vec3 markerColor;
|
||||
uniform float markerOpacity;
|
||||
|
||||
uniform float fadeDistanceMax;
|
||||
uniform float fadeDistanceMin;
|
||||
|
||||
void main() {
|
||||
vec4 color = vec4(markerColor, markerOpacity);
|
||||
|
||||
// distance fading
|
||||
float fdMax = FLT_MAX;
|
||||
if ( fadeDistanceMax > 0.0 ) fdMax = fadeDistanceMax;
|
||||
|
||||
float minDelta = (vDistance - fadeDistanceMin) / fadeDistanceMin;
|
||||
float maxDelta = (vDistance - fadeDistanceMax) / (fadeDistanceMax * 0.5);
|
||||
float distanceOpacity = min(
|
||||
clamp(minDelta, 0.0, 1.0),
|
||||
1.0 - clamp(maxDelta + 1.0, 0.0, 1.0)
|
||||
);
|
||||
|
||||
color.a *= distanceOpacity;
|
||||
|
||||
// apply vertex-color
|
||||
//color.rgb *= vColor.rgb;
|
||||
|
||||
gl_FragColor = color;
|
||||
|
||||
${ShaderChunk.logdepthbuf_fragment}
|
||||
}
|
||||
`;
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the 'Software'), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
import { ShaderChunk } from 'three';
|
||||
|
||||
export const MARKER_FILL_VERTEX_SHADER = `
|
||||
#include <common>
|
||||
${ShaderChunk.logdepthbuf_pars_vertex}
|
||||
|
||||
varying vec3 vPosition;
|
||||
//varying vec3 vWorldPosition;
|
||||
//varying vec3 vNormal;
|
||||
//varying vec2 vUv;
|
||||
//varying vec3 vColor;
|
||||
varying float vDistance;
|
||||
|
||||
void main() {
|
||||
vec4 worldPos = modelMatrix * vec4(position, 1);
|
||||
vec4 viewPos = viewMatrix * worldPos;
|
||||
|
||||
vPosition = position;
|
||||
//vWorldPosition = worldPos.xyz;
|
||||
//vNormal = normal;
|
||||
//vUv = uv;
|
||||
//vColor = vec3(1.0);
|
||||
vDistance = -viewPos.z;
|
||||
|
||||
gl_Position = projectionMatrix * viewPos;
|
||||
|
||||
${ShaderChunk.logdepthbuf_vertex}
|
||||
}
|
||||
`;
|
131
BlueMapCommon/BlueMapVue/BlueMapWeb/src/markers/MarkerManager.js
Normal file
131
BlueMapCommon/BlueMapVue/BlueMapWeb/src/markers/MarkerManager.js
Normal file
@ -0,0 +1,131 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
import {FileLoader} from "three";
|
||||
import {MarkerSet} from "./MarkerSet";
|
||||
import {alert, generateCacheHash} from "../util/Utils";
|
||||
|
||||
/**
|
||||
* A manager for loading and updating markers from a file
|
||||
*/
|
||||
export class MarkerManager {
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param root {MarkerSet} - The scene to which all markers will be added
|
||||
* @param fileUrl {string} - The marker file from which this manager updates its markers
|
||||
* @param events {EventTarget}
|
||||
*/
|
||||
constructor(root, fileUrl, events = null) {
|
||||
Object.defineProperty(this, 'isMarkerManager', {value: true});
|
||||
|
||||
this.root = root;
|
||||
this.fileUrl = fileUrl;
|
||||
this.events = events;
|
||||
this.disposed = false;
|
||||
|
||||
/** @type {NodeJS.Timeout} */
|
||||
this._updateInterval = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the automatic-update frequency, setting this to 0 or negative disables automatic updates (default).
|
||||
* This is better than using setInterval() on update() because this will wait for the update to finish before requesting the next update.
|
||||
* @param ms - interval in milliseconds
|
||||
*/
|
||||
setAutoUpdateInterval(ms) {
|
||||
if (this._updateInterval) clearTimeout(this._updateInterval);
|
||||
if (ms > 0) {
|
||||
let autoUpdate = () => {
|
||||
if (this.disposed) return;
|
||||
this.update()
|
||||
.then(success => {
|
||||
if (success) {
|
||||
this._updateInterval = setTimeout(autoUpdate, ms);
|
||||
} else {
|
||||
this._updateInterval = setTimeout(autoUpdate, Math.max(ms, 1000 * 15));
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
alert(this.events, e, "warning");
|
||||
this._updateInterval = setTimeout(autoUpdate, Math.max(ms, 1000 * 15));
|
||||
});
|
||||
};
|
||||
|
||||
this._updateInterval = setTimeout(autoUpdate, ms);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the marker-file and updates all managed markers.
|
||||
* @returns {Promise<object>} - A promise completing when the markers finished updating
|
||||
*/
|
||||
update() {
|
||||
return this.loadMarkerFile()
|
||||
.then(markerFileData => this.updateFromData(markerFileData));
|
||||
}
|
||||
|
||||
/**
|
||||
* @protected
|
||||
* @param markerData
|
||||
*/
|
||||
updateFromData(markerData) {}
|
||||
|
||||
/**
|
||||
* Stops automatic-updates and disposes all markersets and markers managed by this manager
|
||||
*/
|
||||
dispose() {
|
||||
this.disposed = true;
|
||||
this.setAutoUpdateInterval(0);
|
||||
this.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all markers managed by this marker-manager
|
||||
*/
|
||||
clear() {
|
||||
this.root.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Loads the marker file
|
||||
* @returns {Promise<Object>} - A promise completing with the parsed json object from the loaded file
|
||||
*/
|
||||
loadMarkerFile() {
|
||||
return new Promise((resolve, reject) => {
|
||||
let loader = new FileLoader();
|
||||
loader.setResponseType("json");
|
||||
loader.load(this.fileUrl + "?" + generateCacheHash(),
|
||||
markerFileData => {
|
||||
if (!markerFileData) reject(`Failed to parse '${this.fileUrl}'!`);
|
||||
else resolve(markerFileData);
|
||||
},
|
||||
() => {},
|
||||
() => reject(`Failed to load '${this.fileUrl}'!`)
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
}
|
215
BlueMapCommon/BlueMapVue/BlueMapWeb/src/markers/MarkerSet.js
Normal file
215
BlueMapCommon/BlueMapVue/BlueMapWeb/src/markers/MarkerSet.js
Normal file
@ -0,0 +1,215 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
import {Scene} from "three";
|
||||
import {alert} from "../util/Utils";
|
||||
import {ShapeMarker} from "./ShapeMarker";
|
||||
import {ExtrudeMarker} from "./ExtrudeMarker";
|
||||
import {LineMarker} from "./LineMarker";
|
||||
import {HtmlMarker} from "./HtmlMarker";
|
||||
import {PoiMarker} from "./PoiMarker";
|
||||
|
||||
export class MarkerSet extends Scene {
|
||||
|
||||
/**
|
||||
* @param id {string}
|
||||
*/
|
||||
constructor(id) {
|
||||
super();
|
||||
Object.defineProperty(this, 'isMarkerSet', {value: true});
|
||||
|
||||
/** @type {Map<string, MarkerSet>} */
|
||||
this.markerSets = new Map();
|
||||
/** @type {Map<string, Marker>} */
|
||||
this.markers = new Map();
|
||||
|
||||
this.data = {
|
||||
id: id,
|
||||
label: id,
|
||||
toggleable: true,
|
||||
defaultHide: false,
|
||||
markerSets: [],
|
||||
markers: [],
|
||||
visible: this.visible,
|
||||
};
|
||||
|
||||
Object.defineProperty(this, "visible", {
|
||||
get() { return this.data.visible },
|
||||
set(value) { this.data.visible = value }
|
||||
});
|
||||
}
|
||||
|
||||
updateFromData(data) {
|
||||
// update set info
|
||||
this.data.label = data.label || this.data.id;
|
||||
this.data.toggleable = !!data.toggleable;
|
||||
this.data.defaultHide = !!data.defaultHidden;
|
||||
|
||||
// update markerSets
|
||||
this.updateMarkerSetsFromData(data.markerSets);
|
||||
|
||||
// update markers
|
||||
this.updateMarkersFromData(data.markers);
|
||||
}
|
||||
|
||||
updateMarkerSetsFromData(data = {}, ignore = []) {
|
||||
let updatedMarkerSets = new Set(ignore);
|
||||
|
||||
// add & update MarkerSets
|
||||
Object.keys(data).forEach(markerSetId => {
|
||||
if (updatedMarkerSets.has(markerSetId)) return;
|
||||
updatedMarkerSets.add(markerSetId);
|
||||
|
||||
let markerSetData = data[markerSetId];
|
||||
try {
|
||||
this.updateMarkerSetFromData(markerSetId, markerSetData);
|
||||
} catch (err) {
|
||||
alert(this.events, err, "fine");
|
||||
}
|
||||
});
|
||||
|
||||
// remove not updated MarkerSets
|
||||
this.markerSets.forEach((markerSet, setId) => {
|
||||
if (!updatedMarkerSets.has(setId)) {
|
||||
this.remove(markerSet);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateMarkerSetFromData(markerSetId, data) {
|
||||
let markerSet = this.markerSets.get(markerSetId);
|
||||
|
||||
// create new if not existent
|
||||
if (!markerSet) {
|
||||
markerSet = new MarkerSet(markerSetId);
|
||||
this.add(markerSet);
|
||||
|
||||
if (data.defaultHidden) {
|
||||
markerSet.visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
// update
|
||||
markerSet.updateFromData(data);
|
||||
}
|
||||
|
||||
updateMarkersFromData(data = {}, ignore = []) {
|
||||
let updatedMarkers = new Set(ignore);
|
||||
|
||||
Object.keys(data).forEach(markerId => {
|
||||
if (updatedMarkers.has(markerId)) return;
|
||||
|
||||
let markerData = data[markerId];
|
||||
try {
|
||||
this.updateMarkerFromData(markerId, markerData);
|
||||
updatedMarkers.add(markerId);
|
||||
} catch (err) {
|
||||
alert(this.events, err, "fine");
|
||||
console.debug(err);
|
||||
}
|
||||
});
|
||||
|
||||
// remove not updated Markers
|
||||
this.markers.forEach((marker, markerId) => {
|
||||
if (!updatedMarkers.has(markerId)) {
|
||||
this.remove(marker);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateMarkerFromData(markerId, data) {
|
||||
if (!data.type) throw new Error("marker-data has no type!");
|
||||
let marker = this.markers.get(markerId);
|
||||
|
||||
// create new if not existent of wrong type
|
||||
if (!marker || marker.data.type !== data.type) {
|
||||
if (marker) this.remove(marker);
|
||||
|
||||
switch (data.type) {
|
||||
case "shape" : marker = new ShapeMarker(markerId); break;
|
||||
case "extrude" : marker = new ExtrudeMarker(markerId); break;
|
||||
case "line" : marker = new LineMarker(markerId); break;
|
||||
case "html" : marker = new HtmlMarker(markerId); break;
|
||||
case "poi" : marker = new PoiMarker(markerId); break;
|
||||
default : throw new Error(`Unknown marker-type: '${data.type}'`);
|
||||
}
|
||||
|
||||
this.add(marker);
|
||||
}
|
||||
|
||||
// update marker
|
||||
marker.updateFromData(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all markers and marker-sets
|
||||
*/
|
||||
clear() {
|
||||
[...this.data.markerSets].forEach(markerSet => this.remove(markerSet));
|
||||
[...this.data.markers].forEach(marker => this.remove(marker));
|
||||
}
|
||||
|
||||
add(...object) {
|
||||
if (object.length === 1) { //super.add() will re-invoke this method for each array-entry if it's more than one
|
||||
let o = object[0];
|
||||
if (o.isMarkerSet && !this.markerSets.has(o.data.id)) {
|
||||
this.markerSets.set(o.data.id, o);
|
||||
this.data.markerSets.push(o.data);
|
||||
}
|
||||
if (o.isMarker && !this.markers.has(o.data.id)) {
|
||||
this.markers.set(o.data.id, o);
|
||||
this.data.markers.push(o.data);
|
||||
}
|
||||
}
|
||||
|
||||
return super.add(...object);
|
||||
}
|
||||
|
||||
remove(...object) {
|
||||
if (object.length === 1) { //super.remove() will re-invoke this method for each array-entry if it's more than one
|
||||
let o = object[0];
|
||||
if (o.isMarkerSet) {
|
||||
let i = this.data.markerSets.indexOf(o.data);
|
||||
if (i > -1) this.data.markerSets.splice(i, 1);
|
||||
this.markerSets.delete(o.data.id);
|
||||
o.dispose();
|
||||
}
|
||||
if (o.isMarker) {
|
||||
let i = this.data.markers.indexOf(o.data);
|
||||
if (i > -1) this.data.markers.splice(i, 1);
|
||||
this.markers.delete(o.data.id);
|
||||
o.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
return super.remove(...object);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.children.forEach(child => {
|
||||
if (child.dispose) child.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
import { MarkerManager } from "./MarkerManager";
|
||||
import { PLAYER_MARKER_SET_ID } from "./PlayerMarkerManager";
|
||||
|
||||
export class NormalMarkerManager extends MarkerManager {
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param root {MarkerSet} - The scene to which all markers will be added
|
||||
* @param fileUrl {string} - The marker file from which this manager updates its markers
|
||||
* @param events {EventTarget}
|
||||
*/
|
||||
constructor(root, fileUrl, events = null) {
|
||||
super(root, fileUrl, events);
|
||||
}
|
||||
|
||||
/**
|
||||
* @protected
|
||||
* @override
|
||||
* @param markerData
|
||||
* @returns {boolean}
|
||||
*/
|
||||
updateFromData(markerData) {
|
||||
this.root.updateMarkerSetsFromData(markerData, [PLAYER_MARKER_SET_ID, "bm-popup-set"]);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
156
BlueMapCommon/BlueMapVue/BlueMapWeb/src/markers/ObjectMarker.js
Normal file
156
BlueMapCommon/BlueMapVue/BlueMapWeb/src/markers/ObjectMarker.js
Normal file
@ -0,0 +1,156 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
import {Marker} from "./Marker";
|
||||
import {CSS2DObject} from "../util/CSS2DRenderer";
|
||||
import {animate, htmlToElement} from "../util/Utils";
|
||||
import {Vector3} from "three";
|
||||
|
||||
export class ObjectMarker extends Marker {
|
||||
|
||||
/**
|
||||
* @param markerId {string}
|
||||
*/
|
||||
constructor(markerId) {
|
||||
super(markerId);
|
||||
Object.defineProperty(this, 'isObjectMarker', {value: true});
|
||||
this.data.type = "object";
|
||||
|
||||
this.data.label = null;
|
||||
this.data.detail = null;
|
||||
this.data.link = null;
|
||||
this.data.newTab = true;
|
||||
|
||||
this.lastClick = -1;
|
||||
}
|
||||
|
||||
onClick(event) {
|
||||
let pos = new Vector3();
|
||||
if (event.intersection) {
|
||||
pos.copy(event.intersection.pointOnLine || event.intersection.point);
|
||||
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);
|
||||
this.add(popup);
|
||||
popup.open();
|
||||
}
|
||||
|
||||
if (this.data.link){
|
||||
window.open(this.data.link, this.data.newTab ? '_blank' : '_self');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param markerData {{
|
||||
* position: {x: number, y: number, z: number},
|
||||
* label: string,
|
||||
* detail: string,
|
||||
* link: string,
|
||||
* newTab: boolean
|
||||
* }}
|
||||
*/
|
||||
updateFromData(markerData) {
|
||||
|
||||
// update position
|
||||
let pos = markerData.position || {};
|
||||
this.position.setX(pos.x || 0);
|
||||
this.position.setY(pos.y || 0);
|
||||
this.position.setZ(pos.z || 0);
|
||||
|
||||
// update label
|
||||
this.data.label = markerData.label || null;
|
||||
|
||||
//update detail
|
||||
this.data.detail = markerData.detail || null;
|
||||
|
||||
// update link
|
||||
this.data.link = markerData.link || null;
|
||||
this.data.newTab = !!markerData.newTab;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class LabelPopup extends CSS2DObject {
|
||||
|
||||
/**
|
||||
* @param label {string}
|
||||
*/
|
||||
constructor(label) {
|
||||
super(htmlToElement(`<div class="bm-marker-labelpopup">${label}</div>`));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param autoClose {boolean} - whether this object should be automatically closed and removed again on any other interaction
|
||||
*/
|
||||
open(autoClose = true) {
|
||||
let targetOpacity = this.element.style.opacity || 1;
|
||||
|
||||
this.element.style.opacity = 0;
|
||||
let inAnimation = animate(progress => {
|
||||
this.element.style.opacity = (progress * targetOpacity).toString();
|
||||
}, 300);
|
||||
|
||||
if (autoClose) {
|
||||
let removeHandler = evt => {
|
||||
if (evt.composedPath().includes(this.element)) return;
|
||||
|
||||
inAnimation.cancel();
|
||||
this.close();
|
||||
|
||||
window.removeEventListener("mousedown", removeHandler);
|
||||
window.removeEventListener("touchstart", removeHandler);
|
||||
window.removeEventListener("keydown", removeHandler);
|
||||
window.removeEventListener("mousewheel", removeHandler);
|
||||
};
|
||||
|
||||
window.addEventListener("mousedown", removeHandler);
|
||||
window.addEventListener("touchstart", removeHandler);
|
||||
window.addEventListener("keydown", removeHandler);
|
||||
window.addEventListener("mousewheel", removeHandler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param remove {boolean} - whether this object should be removed from its parent when the close-animation finished
|
||||
*/
|
||||
close(remove = true) {
|
||||
let startOpacity = parseFloat(this.element.style.opacity);
|
||||
|
||||
animate(progress => {
|
||||
this.element.style.opacity = (startOpacity - progress * startOpacity).toString();
|
||||
}, 300, completed => {
|
||||
if (remove && completed && this.parent) {
|
||||
this.parent.remove(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
151
BlueMapCommon/BlueMapVue/BlueMapWeb/src/markers/PlayerMarker.js
Normal file
151
BlueMapCommon/BlueMapVue/BlueMapWeb/src/markers/PlayerMarker.js
Normal file
@ -0,0 +1,151 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
import {Marker} from "./Marker";
|
||||
import {CSS2DObject} from "../util/CSS2DRenderer";
|
||||
import {animate, EasingFunctions, htmlToElement} from "../util/Utils";
|
||||
|
||||
export class PlayerMarker extends Marker {
|
||||
|
||||
/**
|
||||
* @param markerId {string}
|
||||
* @param playerUuid {string}
|
||||
* @param playerHead {string}
|
||||
*/
|
||||
constructor(markerId, playerUuid, playerHead = "assets/steve.png") {
|
||||
super(markerId);
|
||||
Object.defineProperty(this, 'isPlayerMarker', {value: true});
|
||||
this.data.type = "player";
|
||||
|
||||
this.data.playerUuid = playerUuid;
|
||||
this.data.name = playerUuid;
|
||||
this.data.playerHead = playerHead;
|
||||
|
||||
this.elementObject = new CSS2DObject(htmlToElement(`
|
||||
<div id="bm-marker-${this.data.id}" class="bm-marker-${this.data.type}">
|
||||
<img src="${this.data.playerHead}" alt="playerhead" draggable="false">
|
||||
<div class="bm-player-name"></div>
|
||||
</div>
|
||||
`));
|
||||
this.elementObject.onBeforeRender = (renderer, scene, camera) => this.onBeforeRender(renderer, scene, camera);
|
||||
|
||||
this.playerHeadElement = this.element.getElementsByTagName("img")[0];
|
||||
this.playerNameElement = this.element.getElementsByTagName("div")[0];
|
||||
|
||||
this.addEventListener( 'removed', () => {
|
||||
if (this.element.parentNode) this.element.parentNode.removeChild(this.element);
|
||||
});
|
||||
|
||||
this.playerHeadElement.addEventListener('error', () => {
|
||||
this.playerHeadElement.src = "assets/steve.png";
|
||||
}, {once: true});
|
||||
|
||||
this.add(this.elementObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Element}
|
||||
*/
|
||||
get element() {
|
||||
return this.elementObject.element.getElementsByTagName("div")[0];
|
||||
}
|
||||
|
||||
onBeforeRender(renderer, scene, camera) {
|
||||
let distance = Marker.calculateDistanceToCameraPlane(this.position, camera);
|
||||
|
||||
let value = "near";
|
||||
if (distance > 1000) {
|
||||
value = "med";
|
||||
}
|
||||
if (distance > 5000) {
|
||||
value = "far";
|
||||
}
|
||||
|
||||
this.element.setAttribute("distance-data", value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef PlayerLike {{
|
||||
* uuid: string,
|
||||
* name: string,
|
||||
* foreign: boolean,
|
||||
* position: {x: number, y: number, z: number},
|
||||
* rotation: {yaw: number, pitch: number, roll: number}
|
||||
* }}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param markerData {PlayerLike}
|
||||
*/
|
||||
updateFromData(markerData) {
|
||||
|
||||
// animate position update
|
||||
let pos = markerData.position || {};
|
||||
if (!this.position.x && !this.position.y && !this.position.z) {
|
||||
this.position.set(
|
||||
pos.x || 0,
|
||||
(pos.y || 0) + 1.8,
|
||||
pos.z || 0
|
||||
);
|
||||
} else {
|
||||
let startPos = {
|
||||
x: this.position.x,
|
||||
y: this.position.y,
|
||||
z: this.position.z,
|
||||
}
|
||||
let deltaPos = {
|
||||
x: (pos.x || 0) - startPos.x,
|
||||
y: ((pos.y || 0) + 1.8) - startPos.y,
|
||||
z: (pos.z || 0) - startPos.z,
|
||||
}
|
||||
if (deltaPos.x || deltaPos.y || deltaPos.z) {
|
||||
animate(progress => {
|
||||
let ease = EasingFunctions.easeInOutCubic(progress);
|
||||
this.position.set(
|
||||
startPos.x + deltaPos.x * ease || 0,
|
||||
startPos.y + deltaPos.y * ease || 0,
|
||||
startPos.z + deltaPos.z * ease || 0
|
||||
);
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
// update name
|
||||
let name = markerData.name || this.data.playerUuid;
|
||||
this.data.name = name;
|
||||
if (this.playerNameElement.innerHTML !== name)
|
||||
this.playerNameElement.innerHTML = name;
|
||||
|
||||
// update world
|
||||
this.data.foreign = markerData.foreign;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
super.dispose();
|
||||
|
||||
let element = this.elementObject.element;
|
||||
if (element.parentNode) element.parentNode.removeChild(element);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
import { PlayerMarkerSet } from "./PlayerMarkerSet";
|
||||
import { MarkerManager } from "./MarkerManager";
|
||||
|
||||
export const PLAYER_MARKER_SET_ID = "bm-players";
|
||||
|
||||
export class PlayerMarkerManager extends MarkerManager {
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param root {MarkerSet} - The scene to which all markers will be added
|
||||
* @param fileUrl {string} - The marker file from which this manager updates its markers
|
||||
* @param playerheadsUrl {string} - The url from which playerhead images should be loaded
|
||||
* @param events {EventTarget}
|
||||
*/
|
||||
constructor(root, fileUrl, playerheadsUrl, events = null) {
|
||||
super(root, fileUrl, events);
|
||||
|
||||
this.playerheadsUrl = playerheadsUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @protected
|
||||
* @override
|
||||
* @param markerFileData
|
||||
* @returns {boolean}
|
||||
*/
|
||||
updateFromData(markerFileData) {
|
||||
let playerMarkerSet = this.getPlayerMarkerSet();
|
||||
return playerMarkerSet.updateFromPlayerData(markerFileData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @returns {PlayerMarkerSet}
|
||||
*/
|
||||
getPlayerMarkerSet() {
|
||||
/** @type {PlayerMarkerSet} */
|
||||
let playerMarkerSet = /** @type {PlayerMarkerSet} */ this.root.markerSets.get(PLAYER_MARKER_SET_ID);
|
||||
|
||||
if (!playerMarkerSet) {
|
||||
playerMarkerSet = new PlayerMarkerSet(PLAYER_MARKER_SET_ID, this.playerheadsUrl);
|
||||
this.root.add(playerMarkerSet);
|
||||
}
|
||||
|
||||
return playerMarkerSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param playerUuid {string}
|
||||
* @returns {PlayerMarker | undefined}
|
||||
*/
|
||||
getPlayerMarker(playerUuid) {
|
||||
return this.getPlayerMarkerSet().getPlayerMarker(playerUuid)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import {MarkerSet} from "./MarkerSet";
|
||||
import {alert} from "../util/Utils";
|
||||
import {PlayerMarker} from "./PlayerMarker";
|
||||
|
||||
export class PlayerMarkerSet extends MarkerSet {
|
||||
|
||||
constructor(id, playerheadsUrl) {
|
||||
super(id);
|
||||
this.data.label = "Player";
|
||||
this.data.toggleable = true;
|
||||
this.data.defaultHide = false;
|
||||
|
||||
this.data.playerheadsUrl = playerheadsUrl;
|
||||
}
|
||||
|
||||
updateFromPlayerData(data) {
|
||||
if (!Array.isArray(data.players)) {
|
||||
this.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @type Set<Marker> */
|
||||
let updatedPlayerMarkers = new Set();
|
||||
|
||||
// update
|
||||
data.players.forEach(playerData => {
|
||||
try {
|
||||
let playerMarker = this.updatePlayerMarkerFromData(playerData);
|
||||
updatedPlayerMarkers.add(playerMarker);
|
||||
} catch (err) {
|
||||
alert(this.events, err, "fine");
|
||||
}
|
||||
});
|
||||
|
||||
// remove
|
||||
this.markers.forEach(playerMarker => {
|
||||
if (!updatedPlayerMarkers.has(playerMarker)) {
|
||||
this.remove(playerMarker);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
updatePlayerMarkerFromData(markerData) {
|
||||
let playerUuid = markerData.uuid;
|
||||
if (!playerUuid) throw new Error("player-data has no uuid!");
|
||||
let markerId = this.getPlayerMarkerId(playerUuid);
|
||||
|
||||
/** @type PlayerMarker */
|
||||
let marker = this.markers.get(markerId);
|
||||
|
||||
// create new if not existent of wrong type
|
||||
if (!marker || !marker.isPlayerMarker) {
|
||||
if (marker) this.remove(marker);
|
||||
marker = new PlayerMarker(markerId, playerUuid, `${this.data.playerheadsUrl}${playerUuid}.png`);
|
||||
this.add(marker);
|
||||
}
|
||||
|
||||
// update
|
||||
marker.updateFromData(markerData);
|
||||
|
||||
// hide if from different world
|
||||
marker.visible = !markerData.foreign;
|
||||
|
||||
return marker;
|
||||
}
|
||||
|
||||
getPlayerMarker(playerUuid) {
|
||||
return this.markers.get(this.getPlayerMarkerId(playerUuid));
|
||||
}
|
||||
|
||||
getPlayerMarkerId(playerUuid) {
|
||||
return "bm-player-" + playerUuid;
|
||||
}
|
||||
|
||||
}
|
147
BlueMapCommon/BlueMapVue/BlueMapWeb/src/markers/PoiMarker.js
Normal file
147
BlueMapCommon/BlueMapVue/BlueMapWeb/src/markers/PoiMarker.js
Normal file
@ -0,0 +1,147 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
import {HtmlMarker} from "./HtmlMarker";
|
||||
|
||||
export class PoiMarker extends HtmlMarker {
|
||||
|
||||
/**
|
||||
* @param markerId {string}
|
||||
*/
|
||||
constructor(markerId) {
|
||||
super(markerId);
|
||||
Object.defineProperty(this, 'isPoiMarker', {value: true});
|
||||
this.data.type = "poi";
|
||||
|
||||
this.data.detail = null;
|
||||
|
||||
this.html = `<img src="" alt="POI Icon (${this.data.id})" class="bm-marker-poi-icon" draggable="false" style="pointer-events: auto"><div class="bm-marker-poi-label"></div>`;
|
||||
|
||||
this.iconElement = this.element.getElementsByTagName("img").item(0);
|
||||
this.labelElement = this.element.getElementsByTagName("div").item(0);
|
||||
|
||||
this._lastIcon = null;
|
||||
}
|
||||
|
||||
onClick(event) {
|
||||
if (event.data.doubleTap) return false;
|
||||
|
||||
if (this.highlight || !this.data.label) return true;
|
||||
this.highlight = true;
|
||||
|
||||
let eventHandler = evt => {
|
||||
if (evt.composedPath().includes(this.element)) return;
|
||||
|
||||
this.highlight = false;
|
||||
|
||||
window.removeEventListener("mousedown", eventHandler);
|
||||
window.removeEventListener("touchstart", eventHandler);
|
||||
window.removeEventListener("keydown", eventHandler);
|
||||
window.removeEventListener("mousewheel", eventHandler);
|
||||
};
|
||||
|
||||
setTimeout(function () {
|
||||
window.addEventListener("mousedown", eventHandler);
|
||||
window.addEventListener("touchstart", eventHandler);
|
||||
window.addEventListener("keydown", eventHandler);
|
||||
window.addEventListener("mousewheel", eventHandler);
|
||||
}, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
set highlight(highlight) {
|
||||
if (highlight) {
|
||||
this.element.classList.add("bm-marker-highlight");
|
||||
} else {
|
||||
this.element.classList.remove("bm-marker-highlight");
|
||||
}
|
||||
}
|
||||
|
||||
get highlight() {
|
||||
return this.element.classList.contains("bm-marker-highlight");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param markerData {{
|
||||
* position: {x: number, y: number, z: number},
|
||||
* anchor: {x: number, y: number},
|
||||
* iconAnchor: {x: number, y: number},
|
||||
* label: string,
|
||||
* detail: string,
|
||||
* icon: string,
|
||||
* classes: string[],
|
||||
* minDistance: number,
|
||||
* maxDistance: number
|
||||
* }}
|
||||
*/
|
||||
updateFromData(markerData) {
|
||||
|
||||
// update position
|
||||
let pos = markerData.position || {};
|
||||
this.position.setX(pos.x || 0);
|
||||
this.position.setY(pos.y || 0);
|
||||
this.position.setZ(pos.z || 0);
|
||||
|
||||
// update anchor
|
||||
let anch = markerData.anchor || markerData.iconAnchor || {}; //"iconAnchor" for backwards compatibility
|
||||
this.iconElement.style.transform = `translate(${-anch.x}px, ${-anch.y}px)`;
|
||||
//this.anchor.setX(anch.x || 0);
|
||||
//this.anchor.setY(anch.y || 0);
|
||||
|
||||
|
||||
// update label
|
||||
if (this.data.label !== markerData.label){
|
||||
this.data.label = markerData.label || "";
|
||||
}
|
||||
|
||||
// update detail
|
||||
if (this.data.detail !== markerData.detail){
|
||||
this.data.detail = markerData.detail || this.data.label;
|
||||
this.labelElement.innerHTML = this.data.detail || "";
|
||||
}
|
||||
|
||||
// update icon
|
||||
if (this._lastIcon !== markerData.icon){
|
||||
this.iconElement.src = markerData.icon || "assets/poi.svg";
|
||||
this._lastIcon = markerData.icon;
|
||||
}
|
||||
|
||||
// update style-classes
|
||||
if (this.data.classes !== markerData.classes) {
|
||||
this.data.classes = markerData.classes;
|
||||
let highlight = this.element.classList.contains("bm-marker-highlight");
|
||||
|
||||
this.element.classList.value = `bm-marker-html`;
|
||||
if (highlight) this.element.classList.add("bm-marker-highlight");
|
||||
this.element.classList.add(...markerData.classes);
|
||||
}
|
||||
|
||||
// update min/max distances
|
||||
this.fadeDistanceMin = markerData.minDistance || 0;
|
||||
this.fadeDistanceMax = markerData.maxDistance !== undefined ? markerData.maxDistance : Number.MAX_VALUE;
|
||||
|
||||
}
|
||||
|
||||
}
|
441
BlueMapCommon/BlueMapVue/BlueMapWeb/src/markers/ShapeMarker.js
Normal file
441
BlueMapCommon/BlueMapVue/BlueMapWeb/src/markers/ShapeMarker.js
Normal file
@ -0,0 +1,441 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
import {Color, DoubleSide, Mesh, ShaderMaterial, Shape, ShapeBufferGeometry, Vector2} from "three";
|
||||
import {LineMaterial} from "../util/lines/LineMaterial";
|
||||
import {MARKER_FILL_VERTEX_SHADER} from "./MarkerFillVertexShader";
|
||||
import {MARKER_FILL_FRAGMENT_SHADER} from "./MarkerFillFragmentShader";
|
||||
import {LineGeometry} from "../util/lines/LineGeometry";
|
||||
import {Line2} from "../util/lines/Line2";
|
||||
import {deepEquals} from "../util/Utils";
|
||||
import {ObjectMarker} from "./ObjectMarker";
|
||||
|
||||
export class ShapeMarker extends ObjectMarker {
|
||||
|
||||
/**
|
||||
* @param markerId {string}
|
||||
*/
|
||||
constructor(markerId) {
|
||||
super(markerId);
|
||||
Object.defineProperty(this, 'isShapeMarker', {value: true});
|
||||
this.data.type = "shape";
|
||||
|
||||
let zero = new Vector2();
|
||||
let shape = new Shape([zero, zero, zero]);
|
||||
this.fill = new ShapeMarkerFill(shape);
|
||||
this.border = new ShapeMarkerBorder(shape);
|
||||
this.border.renderOrder = -1; // render border before fill
|
||||
|
||||
this.add(this.border, this.fill);
|
||||
|
||||
this._markerData = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param y {number}
|
||||
*/
|
||||
setShapeY(y) {
|
||||
let relativeY = y - this.position.y;
|
||||
this.fill.position.y = relativeY;
|
||||
this.border.position.y = relativeY;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param shape {Shape}
|
||||
*/
|
||||
setShape(shape) {
|
||||
this.fill.updateGeometry(shape);
|
||||
this.border.updateGeometry(shape);
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {{r: number, g: number, b: number, a: number}} ColorLike
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param markerData {{
|
||||
* position: {x: number, y: number, z: number},
|
||||
* label: string,
|
||||
* detail: string,
|
||||
* shape: {x: number, z: number}[],
|
||||
* shapeY: number,
|
||||
* height: number,
|
||||
* link: string,
|
||||
* newTab: boolean,
|
||||
* depthTest: boolean,
|
||||
* lineWidth: number,
|
||||
* borderColor: ColorLike,
|
||||
* lineColor: ColorLike,
|
||||
* fillColor: ColorLike,
|
||||
* minDistance: number,
|
||||
* maxDistance: number
|
||||
* }}
|
||||
*/
|
||||
updateFromData(markerData) {
|
||||
super.updateFromData(markerData);
|
||||
|
||||
// update shape only if needed, based on last update-data
|
||||
if (
|
||||
!this._markerData.shape || !deepEquals(markerData.shape, this._markerData.shape) ||
|
||||
!this._markerData.position || !deepEquals(markerData.position, this._markerData.position)
|
||||
){
|
||||
this.setShape(this.createShapeFromData(markerData.shape));
|
||||
}
|
||||
|
||||
// update shapeY
|
||||
this.setShapeY((markerData.shapeY || markerData.height || 0) + 0.01); //"height" for backwards compatibility, adding 0.01 to avoid z-fighting
|
||||
|
||||
// update depthTest
|
||||
this.border.depthTest = !!markerData.depthTest;
|
||||
this.fill.depthTest = !!markerData.depthTest;
|
||||
|
||||
// update border-width
|
||||
this.border.linewidth = markerData.lineWidth !== undefined ? markerData.lineWidth : 2;
|
||||
|
||||
// update border-color
|
||||
let bc = markerData.lineColor || markerData.borderColor || {}; //"borderColor" for backwards compatibility
|
||||
this.border.color.setRGB((bc.r || 0) / 255, (bc.g || 0) / 255, (bc.b || 0) / 255);
|
||||
this.border.opacity = bc.a || 0;
|
||||
|
||||
// update fill-color
|
||||
let fc = markerData.fillColor || {};
|
||||
this.fill.color.setRGB((fc.r || 0) / 255, (fc.g || 0) / 255, (fc.b || 0) / 255);
|
||||
this.fill.opacity = fc.a || 0;
|
||||
|
||||
// update min/max distances
|
||||
let minDist = markerData.minDistance || 0;
|
||||
let maxDist = markerData.maxDistance !== undefined ? markerData.maxDistance : Number.MAX_VALUE;
|
||||
this.border.fadeDistanceMin = minDist;
|
||||
this.border.fadeDistanceMax = maxDist;
|
||||
this.fill.fadeDistanceMin = minDist;
|
||||
this.fill.fadeDistanceMax = maxDist;
|
||||
|
||||
// save used marker data for next update
|
||||
this._markerData = markerData;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
super.dispose();
|
||||
|
||||
this.fill.dispose();
|
||||
this.border.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Creates a shape from a data object, usually parsed json from a markers.json
|
||||
* @param shapeData {object}
|
||||
* @returns {Shape}
|
||||
*/
|
||||
createShapeFromData(shapeData) {
|
||||
/** @type {THREE.Vector2[]} **/
|
||||
let points = [];
|
||||
|
||||
if (Array.isArray(shapeData)){
|
||||
shapeData.forEach(point => {
|
||||
let x = (point.x || 0) - this.position.x;
|
||||
let z = (point.z || 0) - this.position.z;
|
||||
|
||||
points.push(new Vector2(x, z));
|
||||
});
|
||||
}
|
||||
|
||||
return new Shape(points);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ShapeMarkerFill extends Mesh {
|
||||
|
||||
/**
|
||||
* @param shape {Shape}
|
||||
*/
|
||||
constructor(shape) {
|
||||
let geometry = ShapeMarkerFill.createGeometry(shape);
|
||||
let material = new ShaderMaterial({
|
||||
vertexShader: MARKER_FILL_VERTEX_SHADER,
|
||||
fragmentShader: MARKER_FILL_FRAGMENT_SHADER,
|
||||
side: DoubleSide,
|
||||
depthTest: true,
|
||||
transparent: true,
|
||||
uniforms: {
|
||||
markerColor: { value: new Color() },
|
||||
markerOpacity: { value: 0 },
|
||||
fadeDistanceMin: { value: 0 },
|
||||
fadeDistanceMax: { value: Number.MAX_VALUE },
|
||||
}
|
||||
});
|
||||
|
||||
super(geometry, material);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Color}
|
||||
*/
|
||||
get color(){
|
||||
return this.material.uniforms.markerColor.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
get opacity() {
|
||||
return this.material.uniforms.markerOpacity.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param opacity {number}
|
||||
*/
|
||||
set opacity(opacity) {
|
||||
this.material.uniforms.markerOpacity.value = opacity;
|
||||
this.visible = opacity > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get depthTest() {
|
||||
return this.material.depthTest;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param test {boolean}
|
||||
*/
|
||||
set depthTest(test) {
|
||||
this.material.depthTest = test;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
get fadeDistanceMin() {
|
||||
return this.material.uniforms.fadeDistanceMin.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param min {number}
|
||||
*/
|
||||
set fadeDistanceMin(min) {
|
||||
this.material.uniforms.fadeDistanceMin.value = min;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
get fadeDistanceMax() {
|
||||
return this.material.uniforms.fadeDistanceMax.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param max {number}
|
||||
*/
|
||||
set fadeDistanceMax(max) {
|
||||
this.material.uniforms.fadeDistanceMax.value = max;
|
||||
}
|
||||
|
||||
onClick(event) {
|
||||
if (event.intersection) {
|
||||
if (event.intersection.distance > this.fadeDistanceMax) return false;
|
||||
if (event.intersection.distance < this.fadeDistanceMin) return false;
|
||||
}
|
||||
|
||||
return super.onClick(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param shape {Shape}
|
||||
*/
|
||||
updateGeometry(shape) {
|
||||
this.geometry.dispose();
|
||||
this.geometry = ShapeMarkerFill.createGeometry(shape);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.geometry.dispose();
|
||||
this.material.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param shape {Shape}
|
||||
* @returns {ShapeBufferGeometry}
|
||||
*/
|
||||
static createGeometry(shape) {
|
||||
let geometry = new ShapeBufferGeometry(shape, 5);
|
||||
geometry.rotateX(Math.PI / 2); //make y to z
|
||||
|
||||
return geometry;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ShapeMarkerBorder extends Line2 {
|
||||
|
||||
/**
|
||||
* @param shape {Shape}
|
||||
*/
|
||||
constructor(shape) {
|
||||
let geometry = new LineGeometry();
|
||||
geometry.setPositions(ShapeMarkerBorder.createLinePoints(shape));
|
||||
|
||||
let material = new LineMaterial({
|
||||
color: new Color(),
|
||||
opacity: 0,
|
||||
transparent: true,
|
||||
linewidth: 1,
|
||||
depthTest: true,
|
||||
vertexColors: false,
|
||||
dashed: false,
|
||||
});
|
||||
material.uniforms.fadeDistanceMin = { value: 0 };
|
||||
material.uniforms.fadeDistanceMax = { value: Number.MAX_VALUE };
|
||||
|
||||
material.resolution.set(window.innerWidth, window.innerHeight);
|
||||
|
||||
super(geometry, material);
|
||||
|
||||
this.computeLineDistances();
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Color}
|
||||
*/
|
||||
get color(){
|
||||
return this.material.color;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
get opacity() {
|
||||
return this.material.opacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param opacity {number}
|
||||
*/
|
||||
set opacity(opacity) {
|
||||
this.material.opacity = opacity;
|
||||
this.visible = opacity > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
get linewidth() {
|
||||
return this.material.linewidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param width {number}
|
||||
*/
|
||||
set linewidth(width) {
|
||||
this.material.linewidth = width;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get depthTest() {
|
||||
return this.material.depthTest;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param test {boolean}
|
||||
*/
|
||||
set depthTest(test) {
|
||||
this.material.depthTest = test;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
get fadeDistanceMin() {
|
||||
return this.material.uniforms.fadeDistanceMin.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param min {number}
|
||||
*/
|
||||
set fadeDistanceMin(min) {
|
||||
this.material.uniforms.fadeDistanceMin.value = min;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
get fadeDistanceMax() {
|
||||
return this.material.uniforms.fadeDistanceMax.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param max {number}
|
||||
*/
|
||||
set fadeDistanceMax(max) {
|
||||
this.material.uniforms.fadeDistanceMax.value = max;
|
||||
}
|
||||
|
||||
onClick(event) {
|
||||
if (event.intersection) {
|
||||
if (event.intersection.distance > this.fadeDistanceMax) return false;
|
||||
if (event.intersection.distance < this.fadeDistanceMin) return false;
|
||||
}
|
||||
|
||||
return super.onClick(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param shape {Shape}
|
||||
*/
|
||||
updateGeometry(shape) {
|
||||
this.geometry = new LineGeometry();
|
||||
this.geometry.setPositions(ShapeMarkerBorder.createLinePoints(shape));
|
||||
this.computeLineDistances();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param renderer {THREE.WebGLRenderer}
|
||||
*/
|
||||
onBeforeRender(renderer) {
|
||||
renderer.getSize(this.material.resolution);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.geometry.dispose();
|
||||
this.material.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param shape {Shape}
|
||||
* @returns {number[]}
|
||||
*/
|
||||
static createLinePoints(shape) {
|
||||
let points3d = [];
|
||||
let points = shape.getPoints(5);
|
||||
points.forEach(point => points3d.push(point.x, 0, point.y));
|
||||
points3d.push(points[0].x, 0, points[0].y);
|
||||
|
||||
return points3d;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
export const SKY_FRAGMENT_SHADER = `
|
||||
uniform float sunlightStrength;
|
||||
uniform float ambientLight;
|
||||
uniform vec3 skyColor;
|
||||
|
||||
varying vec3 vPosition;
|
||||
|
||||
void main() {
|
||||
float horizonWidth = 0.005;
|
||||
float horizonHeight = 0.0;
|
||||
|
||||
vec4 color = vec4(skyColor * max(sunlightStrength * sunlightStrength, ambientLight), 1.0);
|
||||
float voidMultiplier = (clamp(vPosition.y - horizonHeight, -horizonWidth, horizonWidth) + horizonWidth) / (horizonWidth * 2.0);
|
||||
color.rgb *= voidMultiplier;
|
||||
|
||||
gl_FragColor = color;
|
||||
}
|
||||
`;
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
export const SKY_VERTEX_SHADER = `
|
||||
varying vec3 vPosition;
|
||||
void main() {
|
||||
vPosition = position;
|
||||
|
||||
gl_Position =
|
||||
projectionMatrix *
|
||||
modelViewMatrix *
|
||||
vec4(position, 1);
|
||||
}
|
||||
`;
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
import {BackSide, Mesh, Scene, ShaderMaterial, SphereGeometry} from 'three';
|
||||
|
||||
import {SKY_FRAGMENT_SHADER} from './SkyFragmentShader';
|
||||
import {SKY_VERTEX_SHADER} from './SkyVertexShader';
|
||||
|
||||
export class SkyboxScene extends Scene {
|
||||
|
||||
constructor(uniforms) {
|
||||
super();
|
||||
|
||||
this.autoUpdate = false;
|
||||
|
||||
Object.defineProperty(this, 'isSkyboxScene', {value: true});
|
||||
|
||||
let geometry = new SphereGeometry(1, 40, 5);
|
||||
let material = new ShaderMaterial({
|
||||
uniforms: uniforms,
|
||||
vertexShader: SKY_VERTEX_SHADER,
|
||||
fragmentShader: SKY_FRAGMENT_SHADER,
|
||||
side: BackSide
|
||||
});
|
||||
let skybox = new Mesh(geometry, material);
|
||||
|
||||
this.add(skybox);
|
||||
}
|
||||
|
||||
}
|
233
BlueMapCommon/BlueMapVue/BlueMapWeb/src/util/CSS2DRenderer.js
Normal file
233
BlueMapCommon/BlueMapVue/BlueMapWeb/src/util/CSS2DRenderer.js
Normal file
@ -0,0 +1,233 @@
|
||||
/**
|
||||
* @author mrdoob / http://mrdoob.com/
|
||||
*
|
||||
* adapted for bluemap's purposes
|
||||
*/
|
||||
|
||||
import {
|
||||
Matrix4,
|
||||
Object3D, Vector2,
|
||||
Vector3
|
||||
} from "three";
|
||||
import {dispatchEvent, htmlToElement} from "./Utils";
|
||||
|
||||
var CSS2DObject = function ( element ) {
|
||||
|
||||
Object3D.call( this );
|
||||
|
||||
this.element = document.createElement("div");
|
||||
let parent = element.parentNode;
|
||||
parent.replaceChild(this.element, element);
|
||||
this.element.appendChild(element);
|
||||
|
||||
this.element.style.position = 'absolute';
|
||||
|
||||
this.anchor = new Vector2();
|
||||
|
||||
this.events = null;
|
||||
|
||||
this.addEventListener( 'removed', function () {
|
||||
|
||||
this.traverse( function ( object ) {
|
||||
|
||||
if ( object.element instanceof Element && object.element.parentNode !== null ) {
|
||||
|
||||
object.element.parentNode.removeChild( object.element );
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
} );
|
||||
|
||||
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("click", handleClick);
|
||||
this.element.addEventListener("touch", handleClick);
|
||||
|
||||
};
|
||||
|
||||
CSS2DObject.prototype = Object.create( Object3D.prototype );
|
||||
CSS2DObject.prototype.constructor = CSS2DObject;
|
||||
|
||||
//
|
||||
|
||||
var CSS2DRenderer = function (events = null) {
|
||||
|
||||
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.events = events;
|
||||
|
||||
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, parentVisible ) {
|
||||
|
||||
if ( object instanceof CSS2DObject ) {
|
||||
|
||||
object.events = _this.events;
|
||||
|
||||
object.onBeforeRender( _this, scene, camera );
|
||||
|
||||
vector.setFromMatrixPosition( object.matrixWorld );
|
||||
vector.applyMatrix4( viewProjectionMatrix );
|
||||
|
||||
var element = object.element;
|
||||
var style = 'translate(' + ( vector.x * _widthHalf + _widthHalf - object.anchor.x) + 'px,' + ( - vector.y * _heightHalf + _heightHalf - object.anchor.y ) + 'px)';
|
||||
|
||||
element.style.WebkitTransform = style;
|
||||
element.style.MozTransform = style;
|
||||
element.style.oTransform = style;
|
||||
element.style.transform = style;
|
||||
|
||||
element.style.display = ( parentVisible && object.visible && vector.z >= - 1 && vector.z <= 1 && element.style.opacity !== "0" ) ? '' : '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, parentVisible && object.visible );
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
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, true );
|
||||
zOrder( scene );
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
export { CSS2DObject, CSS2DRenderer };
|
193
BlueMapCommon/BlueMapVue/BlueMapWeb/src/util/CombinedCamera.js
Normal file
193
BlueMapCommon/BlueMapVue/BlueMapWeb/src/util/CombinedCamera.js
Normal file
@ -0,0 +1,193 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
import {MathUtils, Matrix4, PerspectiveCamera} from "three";
|
||||
|
||||
export class CombinedCamera extends PerspectiveCamera {
|
||||
|
||||
/**
|
||||
* @param fov {number}
|
||||
* @param aspect {number}
|
||||
* @param near {number}
|
||||
* @param far {number}
|
||||
* @param ortho {number}
|
||||
*/
|
||||
constructor(fov, aspect, near, far, ortho) {
|
||||
super(fov, aspect, near, far);
|
||||
|
||||
this.needsUpdate = true;
|
||||
|
||||
this.data = {
|
||||
fov: this.fov,
|
||||
aspect: this.aspect,
|
||||
near: this.near,
|
||||
far: this.far,
|
||||
zoom: this.zoom,
|
||||
ortho: ortho,
|
||||
distance: 1,
|
||||
};
|
||||
|
||||
// redirect parent properties
|
||||
Object.defineProperty(this, "fov", {
|
||||
get() { return this.data.fov },
|
||||
set(value) { if (value !== this.data.fov) { this.data.fov = value; this.needsUpdate = true }}
|
||||
});
|
||||
Object.defineProperty(this, "aspect", {
|
||||
get() { return this.data.aspect },
|
||||
set(value) { if (value !== this.data.aspect) { this.data.aspect = value; this.needsUpdate = true }}
|
||||
});
|
||||
Object.defineProperty(this, "near", {
|
||||
get() { return this.data.near },
|
||||
set(value) { if (value !== this.data.near) { this.data.near = value; this.needsUpdate = true }}
|
||||
});
|
||||
Object.defineProperty(this, "far", {
|
||||
get() { return this.data.far },
|
||||
set(value) { if (value !== this.data.far) { this.data.far = value; this.needsUpdate = true }}
|
||||
});
|
||||
Object.defineProperty(this, "zoom", {
|
||||
get() { return this.data.zoom },
|
||||
set(value) { if (value !== this.data.zoom) { this.data.zoom = value; this.needsUpdate = true }}
|
||||
});
|
||||
|
||||
this.updateProjectionMatrix();
|
||||
}
|
||||
|
||||
updateProjectionMatrix() {
|
||||
if (!this.needsUpdate) return;
|
||||
|
||||
if (!this.ortographicProjection)
|
||||
this.ortographicProjection = new Matrix4();
|
||||
|
||||
if (!this.perspectiveProjection)
|
||||
this.perspectiveProjection = new Matrix4();
|
||||
|
||||
if (!this.data)
|
||||
this.data = {};
|
||||
|
||||
//copied from PerspectiveCamera
|
||||
const near = this.near;
|
||||
let top = near * Math.tan( MathUtils.DEG2RAD * 0.5 * this.fov ) / this.zoom;
|
||||
let height = 2 * top;
|
||||
let width = this.aspect * height;
|
||||
let left = - 0.5 * width;
|
||||
const view = this.view;
|
||||
|
||||
if ( this.view !== null && this.view.enabled ) {
|
||||
|
||||
const fullWidth = view.fullWidth,
|
||||
fullHeight = view.fullHeight;
|
||||
|
||||
left += view.offsetX * width / fullWidth;
|
||||
top -= view.offsetY * height / fullHeight;
|
||||
width *= view.width / fullWidth;
|
||||
height *= view.height / fullHeight;
|
||||
|
||||
}
|
||||
|
||||
const skew = this.filmOffset;
|
||||
if ( skew !== 0 ) left += near * skew / this.getFilmWidth();
|
||||
|
||||
// this part different to PerspectiveCamera
|
||||
let normalizedOrtho = -Math.pow(this.ortho - 1, 6) + 1;
|
||||
let orthoTop = Math.max(this.distance, 0.0001) * Math.tan( MathUtils.DEG2RAD * 0.5 * this.fov ) / this.zoom;
|
||||
let orthoHeight = 2 * orthoTop;
|
||||
let orthoWidth = this.aspect * orthoHeight;
|
||||
let orthoLeft = - 0.5 * orthoWidth;
|
||||
|
||||
this.perspectiveProjection.makePerspective( left, left + width, top, top - height, near, this.far );
|
||||
this.ortographicProjection.makeOrthographic( orthoLeft, orthoLeft + orthoWidth, orthoTop, orthoTop - orthoHeight, near, this.far );
|
||||
|
||||
for (let i = 0; i < 16; i++){
|
||||
this.projectionMatrix.elements[i] = (this.perspectiveProjection.elements[i] * (1 - normalizedOrtho)) + (this.ortographicProjection.elements[i] * normalizedOrtho);
|
||||
}
|
||||
// to here
|
||||
|
||||
this.projectionMatrixInverse.copy( this.projectionMatrix ).invert();
|
||||
|
||||
this.needsUpdate = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get isPerspectiveCamera() {
|
||||
return this.ortho < 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get isOrthographicCamera() {
|
||||
return !this.isPerspectiveCamera;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
get type() {
|
||||
return this.isPerspectiveCamera ? 'PerspectiveCamera' : 'OrthographicCamera';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param type {string}
|
||||
*/
|
||||
set type(type) {
|
||||
//ignore
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
get ortho() {
|
||||
return this.data.ortho;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value {number}
|
||||
*/
|
||||
set ortho(value) {
|
||||
if (value !== this.data.ortho){
|
||||
this.data.ortho = value;
|
||||
this.needsUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
get distance() {
|
||||
return this.data.distance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value {number}
|
||||
*/
|
||||
set distance(value) {
|
||||
if (value !== this.data.distance) {
|
||||
this.data.distance = value;
|
||||
this.needsUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
180
BlueMapCommon/BlueMapVue/BlueMapWeb/src/util/Stats.js
Normal file
180
BlueMapCommon/BlueMapVue/BlueMapWeb/src/util/Stats.js
Normal file
@ -0,0 +1,180 @@
|
||||
/**
|
||||
* Taken from https://github.com/mrdoob/three.js/blob/master/examples/jsm/libs/stats.module.js
|
||||
*/
|
||||
let Stats = function () {
|
||||
|
||||
let mode = 0;
|
||||
|
||||
let container = document.createElement( 'div' );
|
||||
container.style.cssText = 'position:absolute;bottom:5px;right:5px;cursor:pointer;opacity:0.9;z-index:10000';
|
||||
container.addEventListener( 'click', function ( event ) {
|
||||
|
||||
event.preventDefault();
|
||||
showPanel( ++ mode % container.children.length );
|
||||
|
||||
}, false );
|
||||
|
||||
//
|
||||
|
||||
function addPanel( panel ) {
|
||||
|
||||
container.appendChild( panel.dom );
|
||||
return panel;
|
||||
|
||||
}
|
||||
|
||||
function showPanel( id ) {
|
||||
|
||||
for ( let i = 0; i < container.children.length; i ++ ) {
|
||||
|
||||
container.children[ i ].style.display = i === id ? 'block' : 'none';
|
||||
|
||||
}
|
||||
|
||||
mode = id;
|
||||
|
||||
}
|
||||
|
||||
function hide() {
|
||||
showPanel(-1);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
let beginTime = ( performance || Date ).now(), prevTime = beginTime, frames = 0;
|
||||
let prevFrameTime = beginTime;
|
||||
|
||||
let fpsPanel = addPanel( new Stats.Panel( 'FPS', '#0ff', '#002' ) );
|
||||
let msPanel = addPanel( new Stats.Panel( 'MS (render)', '#0f0', '#020' ) );
|
||||
let lastFrameMsPanel = addPanel( new Stats.Panel( 'MS (all)', '#f80', '#210' ) );
|
||||
|
||||
let memPanel = null;
|
||||
if ( self.performance && self.performance.memory ) {
|
||||
|
||||
memPanel = addPanel( new Stats.Panel( 'MB', '#f08', '#201' ) );
|
||||
|
||||
}
|
||||
|
||||
showPanel( 0 );
|
||||
|
||||
return {
|
||||
|
||||
REVISION: 16,
|
||||
|
||||
dom: container,
|
||||
|
||||
addPanel: addPanel,
|
||||
showPanel: showPanel,
|
||||
hide: hide,
|
||||
|
||||
begin: function () {
|
||||
|
||||
beginTime = ( performance || Date ).now();
|
||||
|
||||
},
|
||||
|
||||
end: function () {
|
||||
|
||||
frames ++;
|
||||
|
||||
let time = ( performance || Date ).now();
|
||||
|
||||
msPanel.update( time - beginTime, 200 );
|
||||
lastFrameMsPanel.update( time - prevFrameTime, 200 )
|
||||
|
||||
if ( time >= prevTime + 1000 ) {
|
||||
|
||||
fpsPanel.update( ( frames * 1000 ) / ( time - prevTime ), 100 );
|
||||
|
||||
prevTime = time;
|
||||
frames = 0;
|
||||
|
||||
if ( memPanel ) {
|
||||
|
||||
let memory = performance.memory;
|
||||
memPanel.update( memory.usedJSHeapSize / 1048576, memory.jsHeapSizeLimit / 1048576 );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return time;
|
||||
|
||||
},
|
||||
|
||||
update: function () {
|
||||
|
||||
beginTime = this.end();
|
||||
prevFrameTime = beginTime;
|
||||
|
||||
},
|
||||
|
||||
// Backwards Compatibility
|
||||
|
||||
domElement: container,
|
||||
setMode: showPanel
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
Stats.Panel = function ( name, fg, bg ) {
|
||||
|
||||
let min = Infinity, max = 0, round = Math.round;
|
||||
let PR = round( window.devicePixelRatio || 1 );
|
||||
|
||||
let WIDTH = 160 * PR, HEIGHT = 96 * PR,
|
||||
TEXT_X = 3 * PR, TEXT_Y = 3 * PR,
|
||||
GRAPH_X = 3 * PR, GRAPH_Y = 15 * PR,
|
||||
GRAPH_WIDTH = 154 * PR, GRAPH_HEIGHT = 77 * PR;
|
||||
|
||||
let canvas = document.createElement( 'canvas' );
|
||||
canvas.width = WIDTH;
|
||||
canvas.height = HEIGHT;
|
||||
canvas.style.cssText = 'width:160px;height:96px';
|
||||
|
||||
let context = canvas.getContext( '2d' );
|
||||
context.font = 'bold ' + ( 9 * PR ) + 'px Helvetica,Arial,sans-serif';
|
||||
context.textBaseline = 'top';
|
||||
|
||||
context.fillStyle = bg;
|
||||
context.fillRect( 0, 0, WIDTH, HEIGHT );
|
||||
|
||||
context.fillStyle = fg;
|
||||
context.fillText( name, TEXT_X, TEXT_Y );
|
||||
context.fillRect( GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT );
|
||||
|
||||
context.fillStyle = bg;
|
||||
context.globalAlpha = 0.9;
|
||||
context.fillRect( GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT );
|
||||
|
||||
return {
|
||||
|
||||
dom: canvas,
|
||||
|
||||
update: function ( value, maxValue ) {
|
||||
|
||||
min = Math.min( min, value );
|
||||
max = Math.max( max, value );
|
||||
|
||||
context.fillStyle = bg;
|
||||
context.globalAlpha = 1;
|
||||
context.fillRect( 0, 0, WIDTH, GRAPH_Y );
|
||||
context.fillStyle = fg;
|
||||
context.fillText( round( value ) + ' ' + name + ' (' + round( min ) + '-' + round( max ) + ')', TEXT_X, TEXT_Y );
|
||||
|
||||
context.drawImage( canvas, GRAPH_X + PR, GRAPH_Y, GRAPH_WIDTH - PR, GRAPH_HEIGHT, GRAPH_X, GRAPH_Y, GRAPH_WIDTH - PR, GRAPH_HEIGHT );
|
||||
|
||||
context.fillRect( GRAPH_X + GRAPH_WIDTH - PR, GRAPH_Y, PR, GRAPH_HEIGHT );
|
||||
|
||||
context.fillStyle = bg;
|
||||
context.globalAlpha = 0.9;
|
||||
context.fillRect( GRAPH_X + GRAPH_WIDTH - PR, GRAPH_Y, PR, round( ( 1 - ( value / maxValue ) ) * GRAPH_HEIGHT ) );
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
export default Stats;
|
394
BlueMapCommon/BlueMapVue/BlueMapWeb/src/util/Utils.js
Normal file
394
BlueMapCommon/BlueMapVue/BlueMapWeb/src/util/Utils.js
Normal file
@ -0,0 +1,394 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
import {MathUtils, Vector2, Vector3} from "three";
|
||||
|
||||
export const VEC2_ZERO = new Vector2(0, 0);
|
||||
export const VEC3_ZERO = new Vector3(0, 0, 0);
|
||||
export const VEC3_X = new Vector3(1, 0, 0);
|
||||
export const VEC3_Y = new Vector3(0, 1, 0);
|
||||
export const VEC3_Z = new Vector3(0, 0, 1);
|
||||
|
||||
/**
|
||||
* Converts a url-encoded image string to an actual image-element
|
||||
* @param string {string}
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
export const stringToImage = string => {
|
||||
let image = document.createElementNS('http://www.w3.org/1999/xhtml', 'img');
|
||||
image.src = string;
|
||||
return image;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates an optimized path from x,z coordinates used by bluemap to save tiles
|
||||
* @param x {number}
|
||||
* @param z {number}
|
||||
* @returns {string}
|
||||
*/
|
||||
export const pathFromCoords = (x, z) => {
|
||||
let path = 'x';
|
||||
path += splitNumberToPath(x);
|
||||
|
||||
path += 'z';
|
||||
path += splitNumberToPath(z);
|
||||
|
||||
path = path.substring(0, path.length - 1);
|
||||
|
||||
return path;
|
||||
};
|
||||
|
||||
/**
|
||||
* Splits a number into an optimized folder-path used to save bluemap-tiles
|
||||
* @param num {number}
|
||||
* @returns {string}
|
||||
*/
|
||||
const splitNumberToPath = num => {
|
||||
let path = '';
|
||||
|
||||
if (num < 0) {
|
||||
num = -num;
|
||||
path += '-';
|
||||
}
|
||||
|
||||
let s = num.toString();
|
||||
|
||||
for (let i = 0; i < s.length; i++) {
|
||||
path += s.charAt(i) + '/';
|
||||
}
|
||||
|
||||
return path;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hashes tile-coordinates to be saved in a map
|
||||
* @param x {number}
|
||||
* @param z {number}
|
||||
* @returns {string}
|
||||
*/
|
||||
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
|
||||
* @param element {EventTarget} the element on that the event is dispatched
|
||||
* @param event {string}
|
||||
* @param detail {object}
|
||||
* @returns {undefined|void|boolean}
|
||||
*/
|
||||
export const dispatchEvent = (element, event, detail = {}) => {
|
||||
if (!element || !element.dispatchEvent) return;
|
||||
|
||||
return element.dispatchEvent(new CustomEvent(event, {
|
||||
detail: detail
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a "bluemapAlert" event with a message and a level.
|
||||
* The level can be anything, but the app uses the levels
|
||||
* - debug
|
||||
* - fine
|
||||
* - info
|
||||
* - warning
|
||||
* - error
|
||||
* @param element {EventTarget} the element on that the event is dispatched
|
||||
* @param message {object}
|
||||
* @param level {string}
|
||||
*/
|
||||
export const alert = (element, message, level = "info") => {
|
||||
|
||||
// alert event
|
||||
let printToConsole = dispatchEvent(element, "bluemapAlert", {
|
||||
message: message,
|
||||
level: level
|
||||
});
|
||||
|
||||
// log alert to console
|
||||
if (printToConsole !== false) {
|
||||
if (level === "info") {
|
||||
console.log(`[BlueMap/${level}]`, message);
|
||||
} else if (level === "warning") {
|
||||
console.warn(`[BlueMap/${level}]`, message);
|
||||
} else if (level === "error") {
|
||||
console.error(`[BlueMap/${level}]`, message);
|
||||
} else {
|
||||
console.debug(`[BlueMap/${level}]`, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Source: https://stackoverflow.com/questions/494143/creating-a-new-dom-element-from-an-html-string-using-built-in-dom-methods-or-pro/35385518#35385518
|
||||
*
|
||||
* @param html {string} representing a single element
|
||||
* @return {Element}
|
||||
*/
|
||||
export const htmlToElement = html => {
|
||||
let template = document.createElement('template');
|
||||
template.innerHTML = html.trim();
|
||||
return template.content.firstChild;
|
||||
}
|
||||
|
||||
/**
|
||||
* Source: https://stackoverflow.com/questions/494143/creating-a-new-dom-element-from-an-html-string-using-built-in-dom-methods-or-pro/35385518#35385518
|
||||
*
|
||||
* @param html {string} representing any number of sibling elements
|
||||
* @return {NodeList}
|
||||
*/
|
||||
export const htmlToElements = html => {
|
||||
let template = document.createElement('template');
|
||||
template.innerHTML = html;
|
||||
return template.content.childNodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules an animation
|
||||
* @param durationMs {number} the duration of the animation in ms
|
||||
* @param animationFrame {function(progress: number, deltaTime: number)} a function that is getting called each frame with the parameters (progress (0-1), deltaTime)
|
||||
* @param postAnimation {function(finished: boolean)} a function that gets called once after the animation is finished or cancelled. The function accepts one bool-parameter whether the animation was finished (true) or canceled (false)
|
||||
* @returns {{cancel: function()}} the animation object
|
||||
*/
|
||||
export const animate = function (animationFrame, durationMs = 1000, postAnimation = null) {
|
||||
let animation = {
|
||||
animationStart: -1,
|
||||
lastFrame: -1,
|
||||
cancelled: false,
|
||||
|
||||
frame: function (time) {
|
||||
if (this.cancelled) return;
|
||||
|
||||
if (this.animationStart === -1) {
|
||||
this.animationStart = time;
|
||||
this.lastFrame = time;
|
||||
}
|
||||
|
||||
let progress = durationMs === 0 ? 1 : MathUtils.clamp((time - this.animationStart) / durationMs, 0, 1);
|
||||
let deltaTime = time - this.lastFrame;
|
||||
|
||||
animationFrame(progress, deltaTime);
|
||||
|
||||
if (progress < 1) window.requestAnimationFrame(time => this.frame(time));
|
||||
else if (postAnimation) postAnimation(true);
|
||||
|
||||
this.lastFrame = time;
|
||||
},
|
||||
|
||||
cancel: function () {
|
||||
this.cancelled = true;
|
||||
if (postAnimation) postAnimation(false);
|
||||
}
|
||||
};
|
||||
|
||||
window.requestAnimationFrame(time => animation.frame(time));
|
||||
|
||||
return animation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Source: https://gist.github.com/gre/1650294
|
||||
* @type {{
|
||||
* easeOutCubic: (function(number): number),
|
||||
* linear: (function(number): number),
|
||||
* easeOutQuint: (function(number): number),
|
||||
* easeInQuart: (function(number): number),
|
||||
* easeInOutQuint: (function(number): number),
|
||||
* easeInQuad: (function(number): number),
|
||||
* easeOutQuart: (function(number): number),
|
||||
* easeInCubic: (function(number): number),
|
||||
* easeInQuint: (function(number): number),
|
||||
* easeOutQuad: (function(number): number),
|
||||
* easeInOutQuad: (function(number): number),
|
||||
* easeInOutCubic: (function(number): number),
|
||||
* easeInOutQuart: (function(number): number)
|
||||
* }}
|
||||
*/
|
||||
export const EasingFunctions = {
|
||||
// no easing, no acceleration
|
||||
linear: t => t,
|
||||
// accelerating from zero velocity
|
||||
easeInQuad: t => t*t,
|
||||
// decelerating to zero velocity
|
||||
easeOutQuad: t => t*(2-t),
|
||||
// acceleration until halfway, then deceleration
|
||||
easeInOutQuad: t => t<.5 ? 2*t*t : -1+(4-2*t)*t,
|
||||
// accelerating from zero velocity
|
||||
easeInCubic: t => t*t*t,
|
||||
// decelerating to zero velocity
|
||||
easeOutCubic: t => (--t)*t*t+1,
|
||||
// acceleration until halfway, then deceleration
|
||||
easeInOutCubic: t => t<.5 ? 4*t*t*t : (t-1)*(2*t-2)*(2*t-2)+1,
|
||||
// accelerating from zero velocity
|
||||
easeInQuart: t => t*t*t*t,
|
||||
// decelerating to zero velocity
|
||||
easeOutQuart: t => 1-(--t)*t*t*t,
|
||||
// acceleration until halfway, then deceleration
|
||||
easeInOutQuart: t => t<.5 ? 8*t*t*t*t : 1-8*(--t)*t*t*t,
|
||||
// accelerating from zero velocity
|
||||
easeInQuint: t => t*t*t*t*t,
|
||||
// decelerating to zero velocity
|
||||
easeOutQuint: t => 1+(--t)*t*t*t*t,
|
||||
// acceleration until halfway, then deceleration
|
||||
easeInOutQuint: t => t<.5 ? 16*t*t*t*t*t : 1+16*(--t)*t*t*t*t
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the offset position of an element
|
||||
*
|
||||
* Source: https://plainjs.com/javascript/styles/get-the-position-of-an-element-relative-to-the-document-24/
|
||||
*
|
||||
* @param element {Element}
|
||||
* @returns {{top: number, left: number}}
|
||||
*/
|
||||
export const elementOffset = element => {
|
||||
let rect = element.getBoundingClientRect(),
|
||||
scrollLeft = window.pageXOffset || document.documentElement.scrollLeft,
|
||||
scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
||||
return { top: rect.top + scrollTop, left: rect.left + scrollLeft }
|
||||
}
|
||||
|
||||
/**
|
||||
* Very simple deep equals, should not be used for complex objects. Is designed for comparing parsed json-objects.
|
||||
* @param object1 {object}
|
||||
* @param object2 {object}
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const deepEquals = (object1, object2) => {
|
||||
if (Object.is(object1, object2)) return true;
|
||||
|
||||
let type = typeof object1;
|
||||
if (type !== typeof object2) return false;
|
||||
|
||||
if (type === 'number' || type === 'boolean' || type === 'string') return false;
|
||||
|
||||
if (Array.isArray(object1)){
|
||||
let len = object1.length;
|
||||
if (len !== object2.length) return false;
|
||||
for (let i = 0; i < len; i++) {
|
||||
if (!deepEquals(object1[i], object2[i])) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
for (let property in object1) {
|
||||
if (!object1.hasOwnProperty(property)) continue;
|
||||
if (!deepEquals(object1[property], object2[property])) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds one listener to multiple events
|
||||
* @param target {EventTarget}
|
||||
* @param types {string|string[]}
|
||||
* @param listener {EventListenerOrEventListenerObject | null}
|
||||
* @param options {boolean | AddEventListenerOptions?}
|
||||
*/
|
||||
export const addEventListeners = (target, types, listener, options) => {
|
||||
if (!Array.isArray(types)){
|
||||
types = types.trim().split(" ");
|
||||
}
|
||||
|
||||
types.forEach(type => target.addEventListener(type, listener, options));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes one listener to multiple events
|
||||
* @param target {EventTarget}
|
||||
* @param types {string|string[]}
|
||||
* @param listener {EventListenerOrEventListenerObject | null}
|
||||
* @param options {boolean | EventListenerOptions?}
|
||||
*/
|
||||
export const removeEventListeners = (target, types, listener, options) => {
|
||||
if (!Array.isArray(types)){
|
||||
types = types.trim().split(" ");
|
||||
}
|
||||
|
||||
types.forEach(type => target.removeEventListener(type, listener, options));
|
||||
}
|
||||
|
||||
/**
|
||||
* Softly clamps towards a minimum value
|
||||
* @param value {number}
|
||||
* @param min {number}
|
||||
* @param stiffness {number}
|
||||
* @returns {number}
|
||||
*/
|
||||
export const softMin = (value, min, stiffness) => {
|
||||
if (value >= min) return value;
|
||||
let delta = min - value;
|
||||
if (delta < 0.0001) return min;
|
||||
return value + delta * stiffness;
|
||||
}
|
||||
|
||||
/**
|
||||
* Softly clamps towards a maximum value
|
||||
* @param value {number}
|
||||
* @param max {number}
|
||||
* @param stiffness {number}
|
||||
* @returns {number}
|
||||
*/
|
||||
export const softMax = (value, max, stiffness) => {
|
||||
if (value <= max) return value;
|
||||
let delta = value - max;
|
||||
if (delta < 0.0001) return max;
|
||||
return value - delta * stiffness;
|
||||
}
|
||||
|
||||
/**
|
||||
* Softly clamps towards a minimum and maximum value
|
||||
* @param value {number}
|
||||
* @param min {number}
|
||||
* @param max {number}
|
||||
* @param stiffness {number}
|
||||
* @returns {number}
|
||||
*/
|
||||
export const softClamp = (value, min, max, stiffness) => {
|
||||
return softMax(softMin(value, min, stiffness), max, stiffness);
|
||||
}
|
||||
|
||||
export const vecArrToObj = (val, useZ = false) => {
|
||||
if (val && val.length >= 2) {
|
||||
if (useZ) return {x: val[0], z: val[1]};
|
||||
return {x: val[0], y: val[1]};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
const pixel = document.createElement('canvas');
|
||||
pixel.width = 1;
|
||||
pixel.height = 1;
|
||||
const pixelContext = pixel.getContext('2d', {
|
||||
willReadFrequently: true
|
||||
});
|
||||
|
||||
export const getPixel = (img, x, y) => {
|
||||
pixelContext.drawImage(img, Math.floor(x), Math.floor(y), 1, 1, 0, 0, 1, 1);
|
||||
return pixelContext.getImageData(0, 0, 1, 1).data;
|
||||
}
|
25
BlueMapCommon/BlueMapVue/BlueMapWeb/src/util/lines/Line2.js
Normal file
25
BlueMapCommon/BlueMapVue/BlueMapWeb/src/util/lines/Line2.js
Normal file
@ -0,0 +1,25 @@
|
||||
|
||||
import { LineSegments2 } from "./LineSegments2";
|
||||
import { LineGeometry } from "./LineGeometry";
|
||||
import { LineMaterial } from "./LineMaterial";
|
||||
|
||||
var Line2 = function ( geometry, material ) {
|
||||
|
||||
if ( geometry === undefined ) geometry = new LineGeometry();
|
||||
if ( material === undefined ) material = new LineMaterial( { color: Math.random() * 0xffffff } );
|
||||
|
||||
LineSegments2.call( this, geometry, material );
|
||||
|
||||
this.type = 'Line2';
|
||||
|
||||
};
|
||||
|
||||
Line2.prototype = Object.assign( Object.create( LineSegments2.prototype ), {
|
||||
|
||||
constructor: Line2,
|
||||
|
||||
isLine2: true
|
||||
|
||||
} );
|
||||
|
||||
export { Line2 };
|
@ -0,0 +1,96 @@
|
||||
|
||||
import { LineSegmentsGeometry } from "./LineSegmentsGeometry";
|
||||
|
||||
var LineGeometry = function () {
|
||||
|
||||
LineSegmentsGeometry.call( this );
|
||||
|
||||
this.type = 'LineGeometry';
|
||||
|
||||
};
|
||||
|
||||
LineGeometry.prototype = Object.assign( Object.create( LineSegmentsGeometry.prototype ), {
|
||||
|
||||
constructor: LineGeometry,
|
||||
|
||||
isLineGeometry: true,
|
||||
|
||||
setPositions: function ( array ) {
|
||||
|
||||
// converts [ x1, y1, z1, x2, y2, z2, ... ] to pairs format
|
||||
|
||||
var length = array.length - 3;
|
||||
var points = new Float32Array( 2 * length );
|
||||
|
||||
for ( var i = 0; i < length; i += 3 ) {
|
||||
|
||||
points[ 2 * i ] = array[ i ];
|
||||
points[ 2 * i + 1 ] = array[ i + 1 ];
|
||||
points[ 2 * i + 2 ] = array[ i + 2 ];
|
||||
|
||||
points[ 2 * i + 3 ] = array[ i + 3 ];
|
||||
points[ 2 * i + 4 ] = array[ i + 4 ];
|
||||
points[ 2 * i + 5 ] = array[ i + 5 ];
|
||||
|
||||
}
|
||||
|
||||
LineSegmentsGeometry.prototype.setPositions.call( this, points );
|
||||
|
||||
return this;
|
||||
|
||||
},
|
||||
|
||||
setColors: function ( array ) {
|
||||
|
||||
// converts [ r1, g1, b1, r2, g2, b2, ... ] to pairs format
|
||||
|
||||
var length = array.length - 3;
|
||||
var colors = new Float32Array( 2 * length );
|
||||
|
||||
for ( var i = 0; i < length; i += 3 ) {
|
||||
|
||||
colors[ 2 * i ] = array[ i ];
|
||||
colors[ 2 * i + 1 ] = array[ i + 1 ];
|
||||
colors[ 2 * i + 2 ] = array[ i + 2 ];
|
||||
|
||||
colors[ 2 * i + 3 ] = array[ i + 3 ];
|
||||
colors[ 2 * i + 4 ] = array[ i + 4 ];
|
||||
colors[ 2 * i + 5 ] = array[ i + 5 ];
|
||||
|
||||
}
|
||||
|
||||
LineSegmentsGeometry.prototype.setColors.call( this, colors );
|
||||
|
||||
return this;
|
||||
|
||||
},
|
||||
|
||||
fromLine: function ( line ) {
|
||||
|
||||
var geometry = line.geometry;
|
||||
|
||||
if ( geometry.isGeometry ) {
|
||||
|
||||
this.setPositions( geometry.vertices );
|
||||
|
||||
} else if ( geometry.isBufferGeometry ) {
|
||||
|
||||
this.setPositions( geometry.attributes.position.array ); // assumes non-indexed
|
||||
|
||||
}
|
||||
|
||||
// set colors, maybe
|
||||
|
||||
return this;
|
||||
|
||||
},
|
||||
|
||||
copy: function ( /* source */ ) {
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
export { LineGeometry };
|
@ -0,0 +1,424 @@
|
||||
import {
|
||||
ShaderLib,
|
||||
ShaderMaterial,
|
||||
UniformsLib,
|
||||
UniformsUtils,
|
||||
Vector2
|
||||
} from "three";
|
||||
/**
|
||||
* parameters = {
|
||||
* color: <hex>,
|
||||
* linewidth: <float>,
|
||||
* dashed: <boolean>,
|
||||
* dashScale: <float>,
|
||||
* dashSize: <float>,
|
||||
* gapSize: <float>,
|
||||
* resolution: <Vector2>, // to be set by renderer
|
||||
* }
|
||||
*/
|
||||
|
||||
UniformsLib.line = {
|
||||
|
||||
linewidth: { value: 1 },
|
||||
resolution: { value: new Vector2( 1, 1 ) },
|
||||
dashScale: { value: 1 },
|
||||
dashSize: { value: 1 },
|
||||
gapSize: { value: 1 },
|
||||
opacity: { value: 1 }
|
||||
|
||||
};
|
||||
|
||||
ShaderLib[ 'line' ] = {
|
||||
|
||||
uniforms: UniformsUtils.merge( [
|
||||
UniformsLib.common,
|
||||
UniformsLib.fog,
|
||||
UniformsLib.line
|
||||
] ),
|
||||
|
||||
vertexShader:
|
||||
`
|
||||
#include <common>
|
||||
#include <color_pars_vertex>
|
||||
#include <fog_pars_vertex>
|
||||
#include <logdepthbuf_pars_vertex>
|
||||
#include <clipping_planes_pars_vertex>
|
||||
|
||||
uniform float linewidth;
|
||||
uniform vec2 resolution;
|
||||
|
||||
attribute vec3 instanceStart;
|
||||
attribute vec3 instanceEnd;
|
||||
|
||||
attribute vec3 instanceColorStart;
|
||||
attribute vec3 instanceColorEnd;
|
||||
|
||||
varying vec2 vUv;
|
||||
|
||||
varying float vDistance;
|
||||
|
||||
#ifdef USE_DASH
|
||||
|
||||
uniform float dashScale;
|
||||
attribute float instanceDistanceStart;
|
||||
attribute float instanceDistanceEnd;
|
||||
varying float vLineDistance;
|
||||
|
||||
#endif
|
||||
|
||||
void trimSegment( const in vec4 start, inout vec4 end ) {
|
||||
|
||||
// trim end segment so it terminates between the camera plane and the near plane
|
||||
|
||||
// conservative estimate of the near plane
|
||||
float a = projectionMatrix[ 2 ][ 2 ]; // 3nd entry in 3th column
|
||||
float b = projectionMatrix[ 3 ][ 2 ]; // 3nd entry in 4th column
|
||||
float nearEstimate = - 0.5 * b / a;
|
||||
|
||||
float alpha = ( nearEstimate - start.z ) / ( end.z - start.z );
|
||||
|
||||
end.xyz = mix( start.xyz, end.xyz, alpha );
|
||||
|
||||
}
|
||||
|
||||
void main() {
|
||||
|
||||
#ifdef USE_COLOR
|
||||
|
||||
vColor.xyz = ( position.y < 0.5 ) ? instanceColorStart : instanceColorEnd;
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef USE_DASH
|
||||
|
||||
vLineDistance = ( position.y < 0.5 ) ? dashScale * instanceDistanceStart : dashScale * instanceDistanceEnd;
|
||||
|
||||
#endif
|
||||
|
||||
float aspect = resolution.x / resolution.y;
|
||||
|
||||
vUv = uv;
|
||||
|
||||
// camera space
|
||||
vec4 start = modelViewMatrix * vec4( instanceStart, 1.0 );
|
||||
vec4 end = modelViewMatrix * vec4( instanceEnd, 1.0 );
|
||||
|
||||
// special case for perspective projection, and segments that terminate either in, or behind, the camera plane
|
||||
// clearly the gpu firmware has a way of addressing this issue when projecting into ndc space
|
||||
// but we need to perform ndc-space calculations in the shader, so we must address this issue directly
|
||||
// perhaps there is a more elegant solution -- WestLangley
|
||||
|
||||
bool perspective = ( projectionMatrix[ 2 ][ 3 ] == - 1.0 ); // 4th entry in the 3rd column
|
||||
|
||||
if ( perspective ) {
|
||||
|
||||
if ( start.z < 0.0 && end.z >= 0.0 ) {
|
||||
|
||||
trimSegment( start, end );
|
||||
|
||||
} else if ( end.z < 0.0 && start.z >= 0.0 ) {
|
||||
|
||||
trimSegment( end, start );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// clip space
|
||||
vec4 clipStart = projectionMatrix * start;
|
||||
vec4 clipEnd = projectionMatrix * end;
|
||||
|
||||
// ndc space
|
||||
vec2 ndcStart = clipStart.xy / clipStart.w;
|
||||
vec2 ndcEnd = clipEnd.xy / clipEnd.w;
|
||||
|
||||
// direction
|
||||
vec2 dir = ndcEnd - ndcStart;
|
||||
|
||||
// account for clip-space aspect ratio
|
||||
dir.x *= aspect;
|
||||
dir = normalize( dir );
|
||||
|
||||
// perpendicular to dir
|
||||
vec2 offset = vec2( dir.y, - dir.x );
|
||||
|
||||
// undo aspect ratio adjustment
|
||||
dir.x /= aspect;
|
||||
offset.x /= aspect;
|
||||
|
||||
// sign flip
|
||||
if ( position.x < 0.0 ) offset *= - 1.0;
|
||||
|
||||
// endcaps
|
||||
if ( position.y < 0.0 ) {
|
||||
|
||||
offset += - dir;
|
||||
|
||||
} else if ( position.y > 1.0 ) {
|
||||
|
||||
offset += dir;
|
||||
|
||||
}
|
||||
|
||||
// adjust for linewidth
|
||||
offset *= linewidth;
|
||||
|
||||
// adjust for clip-space to screen-space conversion // maybe resolution should be based on viewport ...
|
||||
offset /= resolution.y;
|
||||
|
||||
// select end
|
||||
vec4 clip = ( position.y < 0.5 ) ? clipStart : clipEnd;
|
||||
|
||||
// back to clip space
|
||||
offset *= clip.w;
|
||||
|
||||
clip.xy += offset;
|
||||
|
||||
gl_Position = clip;
|
||||
|
||||
vec4 mvPosition = ( position.y < 0.5 ) ? start : end; // this is an approximation
|
||||
|
||||
vDistance = -mvPosition.z;
|
||||
|
||||
#include <logdepthbuf_vertex>
|
||||
#include <clipping_planes_vertex>
|
||||
#include <fog_vertex>
|
||||
|
||||
}
|
||||
`,
|
||||
|
||||
fragmentShader:
|
||||
`
|
||||
#define FLT_MAX 3.402823466e+38
|
||||
|
||||
uniform vec3 diffuse;
|
||||
uniform float opacity;
|
||||
|
||||
uniform float fadeDistanceMax;
|
||||
uniform float fadeDistanceMin;
|
||||
|
||||
#ifdef USE_DASH
|
||||
|
||||
uniform float dashSize;
|
||||
uniform float gapSize;
|
||||
|
||||
#endif
|
||||
|
||||
varying float vLineDistance;
|
||||
|
||||
#include <common>
|
||||
#include <color_pars_fragment>
|
||||
#include <fog_pars_fragment>
|
||||
#include <logdepthbuf_pars_fragment>
|
||||
#include <clipping_planes_pars_fragment>
|
||||
|
||||
varying vec2 vUv;
|
||||
|
||||
varying float vDistance;
|
||||
|
||||
void main() {
|
||||
|
||||
#include <clipping_planes_fragment>
|
||||
|
||||
#ifdef USE_DASH
|
||||
|
||||
if ( vUv.y < - 1.0 || vUv.y > 1.0 ) discard; // discard endcaps
|
||||
|
||||
if ( mod( vLineDistance, dashSize + gapSize ) > dashSize ) discard; // todo - FIX
|
||||
|
||||
#endif
|
||||
|
||||
if ( abs( vUv.y ) > 1.0 ) {
|
||||
|
||||
float a = vUv.x;
|
||||
float b = ( vUv.y > 0.0 ) ? vUv.y - 1.0 : vUv.y + 1.0;
|
||||
float len2 = a * a + b * b;
|
||||
|
||||
if ( len2 > 1.0 ) discard;
|
||||
|
||||
}
|
||||
|
||||
// distance fading
|
||||
float fdMax = FLT_MAX;
|
||||
if ( fadeDistanceMax > 0.0 ) fdMax = fadeDistanceMax;
|
||||
|
||||
float minDelta = (vDistance - fadeDistanceMin) / fadeDistanceMin;
|
||||
float maxDelta = (vDistance - fadeDistanceMax) / (fadeDistanceMax * 0.5);
|
||||
float distanceOpacity = min(
|
||||
clamp(minDelta, 0.0, 1.0),
|
||||
1.0 - clamp(maxDelta + 1.0, 0.0, 1.0)
|
||||
);
|
||||
|
||||
vec4 diffuseColor = vec4( diffuse, opacity * distanceOpacity );
|
||||
|
||||
#include <logdepthbuf_fragment>
|
||||
#include <color_fragment>
|
||||
|
||||
gl_FragColor = vec4( diffuseColor.rgb, diffuseColor.a );
|
||||
|
||||
#include <tonemapping_fragment>
|
||||
#include <encodings_fragment>
|
||||
#include <fog_fragment>
|
||||
#include <premultiplied_alpha_fragment>
|
||||
|
||||
}
|
||||
`
|
||||
};
|
||||
|
||||
var LineMaterial = function ( parameters ) {
|
||||
|
||||
ShaderMaterial.call( this, {
|
||||
|
||||
type: 'LineMaterial',
|
||||
|
||||
uniforms: UniformsUtils.clone( ShaderLib[ 'line' ].uniforms ),
|
||||
|
||||
vertexShader: ShaderLib[ 'line' ].vertexShader,
|
||||
fragmentShader: ShaderLib[ 'line' ].fragmentShader,
|
||||
|
||||
clipping: true // required for clipping support
|
||||
|
||||
} );
|
||||
|
||||
this.dashed = false;
|
||||
|
||||
Object.defineProperties( this, {
|
||||
|
||||
color: {
|
||||
|
||||
enumerable: true,
|
||||
|
||||
get: function () {
|
||||
|
||||
return this.uniforms.diffuse.value;
|
||||
|
||||
},
|
||||
|
||||
set: function ( value ) {
|
||||
|
||||
this.uniforms.diffuse.value = value;
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
linewidth: {
|
||||
|
||||
enumerable: true,
|
||||
|
||||
get: function () {
|
||||
|
||||
return this.uniforms.linewidth.value;
|
||||
|
||||
},
|
||||
|
||||
set: function ( value ) {
|
||||
|
||||
this.uniforms.linewidth.value = value;
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
dashScale: {
|
||||
|
||||
enumerable: true,
|
||||
|
||||
get: function () {
|
||||
|
||||
return this.uniforms.dashScale.value;
|
||||
|
||||
},
|
||||
|
||||
set: function ( value ) {
|
||||
|
||||
this.uniforms.dashScale.value = value;
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
dashSize: {
|
||||
|
||||
enumerable: true,
|
||||
|
||||
get: function () {
|
||||
|
||||
return this.uniforms.dashSize.value;
|
||||
|
||||
},
|
||||
|
||||
set: function ( value ) {
|
||||
|
||||
this.uniforms.dashSize.value = value;
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
gapSize: {
|
||||
|
||||
enumerable: true,
|
||||
|
||||
get: function () {
|
||||
|
||||
return this.uniforms.gapSize.value;
|
||||
|
||||
},
|
||||
|
||||
set: function ( value ) {
|
||||
|
||||
this.uniforms.gapSize.value = value;
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
opacity: {
|
||||
|
||||
enumerable: true,
|
||||
|
||||
get: function () {
|
||||
|
||||
return this.uniforms.opacity.value;
|
||||
|
||||
},
|
||||
|
||||
set: function ( value ) {
|
||||
|
||||
this.uniforms.opacity.value = value;
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
resolution: {
|
||||
|
||||
enumerable: true,
|
||||
|
||||
get: function () {
|
||||
|
||||
return this.uniforms.resolution.value;
|
||||
|
||||
},
|
||||
|
||||
set: function ( value ) {
|
||||
|
||||
this.uniforms.resolution.value.copy( value );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
this.setValues( parameters );
|
||||
|
||||
};
|
||||
|
||||
LineMaterial.prototype = Object.create( ShaderMaterial.prototype );
|
||||
LineMaterial.prototype.constructor = LineMaterial;
|
||||
|
||||
LineMaterial.prototype.isLineMaterial = true;
|
||||
|
||||
export { LineMaterial };
|
@ -0,0 +1,209 @@
|
||||
import {
|
||||
InstancedInterleavedBuffer,
|
||||
InterleavedBufferAttribute,
|
||||
Line3,
|
||||
MathUtils,
|
||||
Matrix4,
|
||||
Mesh,
|
||||
Vector3,
|
||||
Vector4
|
||||
} from "three";
|
||||
import { LineSegmentsGeometry } from "./LineSegmentsGeometry";
|
||||
import { LineMaterial } from "./LineMaterial";
|
||||
|
||||
var LineSegments2 = function ( geometry, material ) {
|
||||
|
||||
if ( geometry === undefined ) geometry = new LineSegmentsGeometry();
|
||||
if ( material === undefined ) material = new LineMaterial( { color: Math.random() * 0xffffff } );
|
||||
|
||||
Mesh.call( this, geometry, material );
|
||||
|
||||
this.type = 'LineSegments2';
|
||||
|
||||
};
|
||||
|
||||
LineSegments2.prototype = Object.assign( Object.create( Mesh.prototype ), {
|
||||
|
||||
constructor: LineSegments2,
|
||||
|
||||
isLineSegments2: true,
|
||||
|
||||
computeLineDistances: ( function () { // for backwards-compatability, but could be a method of LineSegmentsGeometry...
|
||||
|
||||
var start = new Vector3();
|
||||
var end = new Vector3();
|
||||
|
||||
return function computeLineDistances() {
|
||||
|
||||
var geometry = this.geometry;
|
||||
|
||||
var instanceStart = geometry.attributes.instanceStart;
|
||||
var instanceEnd = geometry.attributes.instanceEnd;
|
||||
var lineDistances = new Float32Array( 2 * instanceStart.data.count );
|
||||
|
||||
for ( var i = 0, j = 0, l = instanceStart.data.count; i < l; i ++, j += 2 ) {
|
||||
|
||||
start.fromBufferAttribute( instanceStart, i );
|
||||
end.fromBufferAttribute( instanceEnd, i );
|
||||
|
||||
lineDistances[ j ] = ( j === 0 ) ? 0 : lineDistances[ j - 1 ];
|
||||
lineDistances[ j + 1 ] = lineDistances[ j ] + start.distanceTo( end );
|
||||
|
||||
}
|
||||
|
||||
var instanceDistanceBuffer = new InstancedInterleavedBuffer( lineDistances, 2, 1 ); // d0, d1
|
||||
|
||||
geometry.setAttribute( 'instanceDistanceStart', new InterleavedBufferAttribute( instanceDistanceBuffer, 1, 0 ) ); // d0
|
||||
geometry.setAttribute( 'instanceDistanceEnd', new InterleavedBufferAttribute( instanceDistanceBuffer, 1, 1 ) ); // d1
|
||||
|
||||
return this;
|
||||
|
||||
};
|
||||
|
||||
}() ),
|
||||
|
||||
raycast: ( function () {
|
||||
|
||||
var start = new Vector4();
|
||||
var end = new Vector4();
|
||||
|
||||
var ssOrigin = new Vector4();
|
||||
var ssOrigin3 = new Vector3();
|
||||
var mvMatrix = new Matrix4();
|
||||
var line = new Line3();
|
||||
var closestPoint = new Vector3();
|
||||
|
||||
return function raycast( raycaster, intersects ) {
|
||||
|
||||
if ( raycaster.camera === null ) {
|
||||
|
||||
console.error( 'LineSegments2: "Raycaster.camera" needs to be set in order to raycast against LineSegments2.' );
|
||||
|
||||
}
|
||||
|
||||
var threshold = ( raycaster.params.Line2 !== undefined ) ? raycaster.params.Line2.threshold || 0 : 0;
|
||||
|
||||
var ray = raycaster.ray;
|
||||
var camera = raycaster.camera;
|
||||
var projectionMatrix = camera.projectionMatrix;
|
||||
|
||||
var geometry = this.geometry;
|
||||
var material = this.material;
|
||||
var resolution = material.resolution;
|
||||
var lineWidth = material.linewidth + threshold;
|
||||
|
||||
var instanceStart = geometry.attributes.instanceStart;
|
||||
var instanceEnd = geometry.attributes.instanceEnd;
|
||||
|
||||
// pick a point 1 unit out along the ray to avoid the ray origin
|
||||
// sitting at the camera origin which will cause "w" to be 0 when
|
||||
// applying the projection matrix.
|
||||
ray.at( 1, ssOrigin );
|
||||
|
||||
// ndc space [ - 1.0, 1.0 ]
|
||||
ssOrigin.w = 1;
|
||||
ssOrigin.applyMatrix4( camera.matrixWorldInverse );
|
||||
ssOrigin.applyMatrix4( projectionMatrix );
|
||||
ssOrigin.multiplyScalar( 1 / ssOrigin.w );
|
||||
|
||||
// screen space
|
||||
ssOrigin.x *= resolution.x / 2;
|
||||
ssOrigin.y *= resolution.y / 2;
|
||||
ssOrigin.z = 0;
|
||||
|
||||
ssOrigin3.copy( ssOrigin );
|
||||
|
||||
var matrixWorld = this.matrixWorld;
|
||||
mvMatrix.multiplyMatrices( camera.matrixWorldInverse, matrixWorld );
|
||||
|
||||
for ( var i = 0, l = instanceStart.count; i < l; i ++ ) {
|
||||
|
||||
start.fromBufferAttribute( instanceStart, i );
|
||||
end.fromBufferAttribute( instanceEnd, i );
|
||||
|
||||
start.w = 1;
|
||||
end.w = 1;
|
||||
|
||||
// camera space
|
||||
start.applyMatrix4( mvMatrix );
|
||||
end.applyMatrix4( mvMatrix );
|
||||
|
||||
// clip space
|
||||
start.applyMatrix4( projectionMatrix );
|
||||
end.applyMatrix4( projectionMatrix );
|
||||
|
||||
// ndc space [ - 1.0, 1.0 ]
|
||||
start.multiplyScalar( 1 / start.w );
|
||||
end.multiplyScalar( 1 / end.w );
|
||||
|
||||
// skip the segment if it's outside the camera near and far planes
|
||||
var isBehindCameraNear = start.z < - 1 && end.z < - 1;
|
||||
var isPastCameraFar = start.z > 1 && end.z > 1;
|
||||
if ( isBehindCameraNear || isPastCameraFar ) {
|
||||
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
// screen space
|
||||
start.x *= resolution.x / 2;
|
||||
start.y *= resolution.y / 2;
|
||||
|
||||
end.x *= resolution.x / 2;
|
||||
end.y *= resolution.y / 2;
|
||||
|
||||
// create 2d segment
|
||||
line.start.copy( start );
|
||||
line.start.z = 0;
|
||||
|
||||
line.end.copy( end );
|
||||
line.end.z = 0;
|
||||
|
||||
// get closest point on ray to segment
|
||||
var param = line.closestPointToPointParameter( ssOrigin3, true );
|
||||
line.at( param, closestPoint );
|
||||
|
||||
// check if the intersection point is within clip space
|
||||
var zPos = MathUtils.lerp( start.z, end.z, param );
|
||||
var isInClipSpace = zPos >= - 1 && zPos <= 1;
|
||||
|
||||
var isInside = ssOrigin3.distanceTo( closestPoint ) < lineWidth * 0.5;
|
||||
|
||||
if ( isInClipSpace && isInside ) {
|
||||
|
||||
line.start.fromBufferAttribute( instanceStart, i );
|
||||
line.end.fromBufferAttribute( instanceEnd, i );
|
||||
|
||||
line.start.applyMatrix4( matrixWorld );
|
||||
line.end.applyMatrix4( matrixWorld );
|
||||
|
||||
var pointOnLine = new Vector3();
|
||||
var point = new Vector3();
|
||||
|
||||
ray.distanceSqToSegment( line.start, line.end, point, pointOnLine );
|
||||
|
||||
intersects.push( {
|
||||
|
||||
point: point,
|
||||
pointOnLine: pointOnLine,
|
||||
distance: ray.origin.distanceTo( point ),
|
||||
|
||||
object: this,
|
||||
face: null,
|
||||
faceIndex: i,
|
||||
uv: null,
|
||||
uv2: null,
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}() )
|
||||
|
||||
} );
|
||||
|
||||
export { LineSegments2 };
|
@ -0,0 +1,258 @@
|
||||
import {
|
||||
Box3,
|
||||
Float32BufferAttribute,
|
||||
InstancedBufferGeometry,
|
||||
InstancedInterleavedBuffer,
|
||||
InterleavedBufferAttribute,
|
||||
Sphere,
|
||||
Vector3,
|
||||
WireframeGeometry
|
||||
} from "three";
|
||||
|
||||
var LineSegmentsGeometry = function () {
|
||||
|
||||
InstancedBufferGeometry.call( this );
|
||||
|
||||
this.type = 'LineSegmentsGeometry';
|
||||
|
||||
var positions = [ - 1, 2, 0, 1, 2, 0, - 1, 1, 0, 1, 1, 0, - 1, 0, 0, 1, 0, 0, - 1, - 1, 0, 1, - 1, 0 ];
|
||||
var uvs = [ - 1, 2, 1, 2, - 1, 1, 1, 1, - 1, - 1, 1, - 1, - 1, - 2, 1, - 2 ];
|
||||
var index = [ 0, 2, 1, 2, 3, 1, 2, 4, 3, 4, 5, 3, 4, 6, 5, 6, 7, 5 ];
|
||||
|
||||
this.setIndex( index );
|
||||
this.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) );
|
||||
this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
|
||||
|
||||
};
|
||||
|
||||
LineSegmentsGeometry.prototype = Object.assign( Object.create( InstancedBufferGeometry.prototype ), {
|
||||
|
||||
constructor: LineSegmentsGeometry,
|
||||
|
||||
isLineSegmentsGeometry: true,
|
||||
|
||||
applyMatrix4: function ( matrix ) {
|
||||
|
||||
var start = this.attributes.instanceStart;
|
||||
var end = this.attributes.instanceEnd;
|
||||
|
||||
if ( start !== undefined ) {
|
||||
|
||||
start.applyMatrix4( matrix );
|
||||
|
||||
end.applyMatrix4( matrix );
|
||||
|
||||
start.needsUpdate = true;
|
||||
|
||||
}
|
||||
|
||||
if ( this.boundingBox !== null ) {
|
||||
|
||||
this.computeBoundingBox();
|
||||
|
||||
}
|
||||
|
||||
if ( this.boundingSphere !== null ) {
|
||||
|
||||
this.computeBoundingSphere();
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
},
|
||||
|
||||
setPositions: function ( array ) {
|
||||
|
||||
var lineSegments;
|
||||
|
||||
if ( array instanceof Float32Array ) {
|
||||
|
||||
lineSegments = array;
|
||||
|
||||
} else if ( Array.isArray( array ) ) {
|
||||
|
||||
lineSegments = new Float32Array( array );
|
||||
|
||||
}
|
||||
|
||||
var instanceBuffer = new InstancedInterleavedBuffer( lineSegments, 6, 1 ); // xyz, xyz
|
||||
|
||||
this.setAttribute( 'instanceStart', new InterleavedBufferAttribute( instanceBuffer, 3, 0 ) ); // xyz
|
||||
this.setAttribute( 'instanceEnd', new InterleavedBufferAttribute( instanceBuffer, 3, 3 ) ); // xyz
|
||||
|
||||
//
|
||||
|
||||
this.computeBoundingBox();
|
||||
this.computeBoundingSphere();
|
||||
|
||||
return this;
|
||||
|
||||
},
|
||||
|
||||
setColors: function ( array ) {
|
||||
|
||||
var colors;
|
||||
|
||||
if ( array instanceof Float32Array ) {
|
||||
|
||||
colors = array;
|
||||
|
||||
} else if ( Array.isArray( array ) ) {
|
||||
|
||||
colors = new Float32Array( array );
|
||||
|
||||
}
|
||||
|
||||
var instanceColorBuffer = new InstancedInterleavedBuffer( colors, 6, 1 ); // rgb, rgb
|
||||
|
||||
this.setAttribute( 'instanceColorStart', new InterleavedBufferAttribute( instanceColorBuffer, 3, 0 ) ); // rgb
|
||||
this.setAttribute( 'instanceColorEnd', new InterleavedBufferAttribute( instanceColorBuffer, 3, 3 ) ); // rgb
|
||||
|
||||
return this;
|
||||
|
||||
},
|
||||
|
||||
fromWireframeGeometry: function ( geometry ) {
|
||||
|
||||
this.setPositions( geometry.attributes.position.array );
|
||||
|
||||
return this;
|
||||
|
||||
},
|
||||
|
||||
fromEdgesGeometry: function ( geometry ) {
|
||||
|
||||
this.setPositions( geometry.attributes.position.array );
|
||||
|
||||
return this;
|
||||
|
||||
},
|
||||
|
||||
fromMesh: function ( mesh ) {
|
||||
|
||||
this.fromWireframeGeometry( new WireframeGeometry( mesh.geometry ) );
|
||||
|
||||
// set colors, maybe
|
||||
|
||||
return this;
|
||||
|
||||
},
|
||||
|
||||
fromLineSegments: function ( lineSegments ) {
|
||||
|
||||
var geometry = lineSegments.geometry;
|
||||
|
||||
if ( geometry.isGeometry ) {
|
||||
|
||||
this.setPositions( geometry.vertices );
|
||||
|
||||
} else if ( geometry.isBufferGeometry ) {
|
||||
|
||||
this.setPositions( geometry.attributes.position.array ); // assumes non-indexed
|
||||
|
||||
}
|
||||
|
||||
// set colors, maybe
|
||||
|
||||
return this;
|
||||
|
||||
},
|
||||
|
||||
computeBoundingBox: function () {
|
||||
|
||||
var box = new Box3();
|
||||
|
||||
return function computeBoundingBox() {
|
||||
|
||||
if ( this.boundingBox === null ) {
|
||||
|
||||
this.boundingBox = new Box3();
|
||||
|
||||
}
|
||||
|
||||
var start = this.attributes.instanceStart;
|
||||
var end = this.attributes.instanceEnd;
|
||||
|
||||
if ( start !== undefined && end !== undefined ) {
|
||||
|
||||
this.boundingBox.setFromBufferAttribute( start );
|
||||
|
||||
box.setFromBufferAttribute( end );
|
||||
|
||||
this.boundingBox.union( box );
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}(),
|
||||
|
||||
computeBoundingSphere: function () {
|
||||
|
||||
var vector = new Vector3();
|
||||
|
||||
return function computeBoundingSphere() {
|
||||
|
||||
if ( this.boundingSphere === null ) {
|
||||
|
||||
this.boundingSphere = new Sphere();
|
||||
|
||||
}
|
||||
|
||||
if ( this.boundingBox === null ) {
|
||||
|
||||
this.computeBoundingBox();
|
||||
|
||||
}
|
||||
|
||||
var start = this.attributes.instanceStart;
|
||||
var end = this.attributes.instanceEnd;
|
||||
|
||||
if ( start !== undefined && end !== undefined ) {
|
||||
|
||||
var center = this.boundingSphere.center;
|
||||
|
||||
this.boundingBox.getCenter( center );
|
||||
|
||||
var maxRadiusSq = 0;
|
||||
|
||||
for ( var i = 0, il = start.count; i < il; i ++ ) {
|
||||
|
||||
vector.fromBufferAttribute( start, i );
|
||||
maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) );
|
||||
|
||||
vector.fromBufferAttribute( end, i );
|
||||
maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) );
|
||||
|
||||
}
|
||||
|
||||
this.boundingSphere.radius = Math.sqrt( maxRadiusSq );
|
||||
|
||||
if ( isNaN( this.boundingSphere.radius ) ) {
|
||||
|
||||
console.error( 'THREE.LineSegmentsGeometry.computeBoundingSphere(): Computed radius is NaN. The instanced position data is likely to have NaN values.', this );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}(),
|
||||
|
||||
toJSON: function () {
|
||||
|
||||
},
|
||||
|
||||
applyMatrix: function ( matrix ) {
|
||||
|
||||
console.warn( 'THREE.LineSegmentsGeometry: applyMatrix() has been renamed to applyMatrix4().' );
|
||||
|
||||
return this.applyMatrix4( matrix );
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
export { LineSegmentsGeometry };
|
Loading…
Reference in New Issue
Block a user