diff --git a/src/js/viewer/ViewerAPI.js b/src/js/viewer/ViewerAPI.js
index fef3186..6827bbf 100644
--- a/src/js/viewer/ViewerAPI.js
+++ b/src/js/viewer/ViewerAPI.js
@@ -7,6 +7,8 @@ import { ViewerMapAPI } from "./ViewerMapAPI.js"
import { ViewerState } from "./ViewerState.js";
import { libraryInfo } from "./LibraryInfo.js";
import { ViewerVersionAPI } from "./ViewerVersionAPI.js";
+import { ViewerContextItem } from "./ViewerContextItem.js";
+import { LON_SCALAR, LAN_SCALAR } from "./ViewerConfig.js";
// API provided by the viewer
@@ -27,7 +29,7 @@ export class ViewerAPI {
this.baseURL = baseURL;
this.listeners = [];
-
+
this.renderer;
// Load the metadata only once
$.ajax({
@@ -43,6 +45,7 @@ export class ViewerAPI {
this.pano = new ViewerPanoAPI(this);
this.map = new ViewerMapAPI(this);
}).then(() => {
+ document.addEventListener('mousedown', function (e) { e.preventDefault(); }, false);
// the only html element we work with (the pano-viewer div)
const panoDiv = document.getElementById('pano-viewer');
@@ -54,7 +57,7 @@ export class ViewerAPI {
this.renderer.sortObjects = false;
panoDiv.appendChild(this.renderer.domElement);
-
+
// start animation loop
this.animate();
});
@@ -73,12 +76,12 @@ export class ViewerAPI {
let minDistance = 1000000000;
let bestImg;
-
+
this.floor.currentFloor.viewerImages.forEach(element => {
const currLocalPos = this.toLocal(element.pos);
- const [dx, dz] = [localPos.x - currLocalPos.x, localPos.z - currLocalPos.z];
- const currDistance = Math.sqrt(dx * dx + dz * dz);
-
+ const [dx, dy, dz] = [localPos.x - currLocalPos.x, localPos.y - currLocalPos.y, localPos.z - currLocalPos.z];
+ const currDistance = Math.sqrt(dx * dx + dy * dy + dz * dz);
+
if (currDistance < minDistance) {
minDistance = currDistance;
bestImg = element;
@@ -136,11 +139,10 @@ export class ViewerAPI {
// Convert the local metric three.js coordinates used by the viewer to WGS 84 coordinates [longitude, latitude, z].
toGlobal(localCoords) {
// localCoords : THREE.Vector3 // Local coordinates used by the viewer
- const globalX = this.floor.origin[0] - ((localCoords.x / 1000) / 71.5);
- const globalY = this.floor.origin[1] - ((-localCoords.z / 1000) / 111.3);
- const globalZ = localCoords.y - this.floor.currentFloor.z;
+ const globalX = this.floor.origin[0] - ((localCoords.x / 1000) / LON_SCALAR);
+ const globalY = this.floor.origin[1] - ((localCoords.y / 1000) / LAN_SCALAR);
+ const globalZ = localCoords.z - this.floor.currentFloor.z;
- // the three js scene sees the y axis as the up-down axis so we have to swap with z
return [globalX, globalY, globalZ];
// Returns: [Number] : WGS 84 coordinates [longitude, latitude, z] (z value is floorZ + panoZ, where localCoords is just the panoZ)
}
@@ -150,13 +152,61 @@ export class ViewerAPI {
toLocal(globalCoords) {
// Distance calculation math taken from here https://www.mkompf.com/gps/distcalc.html
// The more accurate calculation breaks the pixel offset on the pre-created maps
- const dx = 71.5 * (this.floor.origin[0] - globalCoords[0]);
- const dz = 111.3 * (this.floor.origin[1] - globalCoords[1]);
+ const dx = LON_SCALAR * (this.floor.origin[0] - globalCoords[0]);
+ const dy = LAN_SCALAR * (this.floor.origin[1] - globalCoords[1]);
return new this.THREE.Vector3(
dx * 1000,
- globalCoords[2] + this.floor.currentFloor.z,
- -dz * 1000);
+ dy * 1000,
+ globalCoords[2] + this.floor.currentFloor.z);
+ }
+
+ eventMeshTest(x = 0, y = 0, z = -2) {
+ // visual test, spawn in white sphere at first image position in scene (offset specified by parameters)
+ const sphere = new THREE.SphereGeometry(1 / 5, 10, 10);
+ const testMesh = new THREE.Mesh(sphere, new THREE.MeshBasicMaterial());
+ const startPos = this.toLocal(this.image.currentImage.pos);
+ testMesh.position.set(startPos.x + x, startPos.y + y, startPos.z + z);
+
+ testMesh.vwr_onclick = function (xy, position) {
+ this.material.color.set(0xff0000); // as a test set color red
+ console.log("vwr_onclick is triggered.");
+ console.log("Pointer position: " , xy);
+ console.log("Local coordinate for pointer position: " , position);
+ return true;
+ }
+
+ testMesh.vwr_oncontext = function (xy, position) {
+ this.material.color.set(0x00ff00); // as a test set color green
+ console.log("vwr_oncontext is triggered.");
+ console.log("Pointer position: " , xy);
+ console.log("Local coordinate for pointer position: " , position);
+
+ //Creating callback function for context menu item:
+ let callback = function (key, options) {
+ var msg = 'clicked: ' + key;
+ (window.console && console.log(msg)) || alert(msg);
+ };
+
+ //Creating item objects
+ let itemEdit = new ViewerContextItem(callback, "edit", null, "Edit");
+ let itemCut = new ViewerContextItem(callback, "cut", null, "Cut");
+
+ //Creating list of item objects.
+ return [itemEdit, itemCut];
+ }
+
+ testMesh.vwr_onpointerenter = function () {
+ this.material.color.set(0xffff00); // as a test set color yellow
+ console.log("vwr_onpointerenter is triggered.");
+ }
+
+ testMesh.vwr_onpointerleave = function () {
+ this.material.color.set(0x0000ff); // as a test set color blue
+ console.log("vwr_onpointerleave is triggered.");
+ }
+
+ this.pano.addLayer(testMesh);
}
// TODO: swap() and big(wanted)
diff --git a/src/js/viewer/ViewerConfig.js b/src/js/viewer/ViewerConfig.js
index b75d97e..2e20d45 100644
--- a/src/js/viewer/ViewerConfig.js
+++ b/src/js/viewer/ViewerConfig.js
@@ -26,3 +26,9 @@ export const SCALING_MAP = 0.2;
// Describes the maximum zoom of the map.
export const MAP_ZOOM = 4;
+
+// Scalar for Longitude from degree to km
+export const LON_SCALAR = 71.5;
+
+// Scalar for Langitude from degree to km
+export const LAN_SCALAR = 111.3;
\ No newline at end of file
diff --git a/src/js/viewer/ViewerFloorAPI.js b/src/js/viewer/ViewerFloorAPI.js
index 4a261b0..2d2eb7b 100644
--- a/src/js/viewer/ViewerFloorAPI.js
+++ b/src/js/viewer/ViewerFloorAPI.js
@@ -1,5 +1,8 @@
"use strict";
+import { LON_SCALAR, LAN_SCALAR } from "./ViewerConfig.js";
+
+
export class ViewerFloorAPI {
constructor(data, viewerAPI) {
@@ -28,7 +31,7 @@ export class ViewerFloorAPI {
if (currentImage.id >= interval[0] && currentImage.id <= interval[1]) {
currentImage.floor = key;
- const [dx, dy] = [71.5 * (data.lon0 - currentImage.pos[0]), 111.3 * (data.lat0 - currentImage.pos[1])];
+ const [dx, dy] = [LON_SCALAR * (data.lon0 - currentImage.pos[0]), LAN_SCALAR * (data.lat0 - currentImage.pos[1])];
const offsetX = currentFloor.mapData.x + currentFloor.mapData.density * (dx * 1000);
const offsetY = currentFloor.mapData.y - currentFloor.mapData.density * (dy * 1000);
diff --git a/src/js/viewer/ViewerImage.js b/src/js/viewer/ViewerImage.js
index a30fe78..1323122 100644
--- a/src/js/viewer/ViewerImage.js
+++ b/src/js/viewer/ViewerImage.js
@@ -16,12 +16,7 @@ export class ViewerImage {
this.pos = [panoLon, panoLat, panoZ]; // : [Number] // WGS 84 coordinates [longitude, latitude, z] of this image
- // The quaternion data available in the json is not quite compatible with the translation we need in our scene
- const threeX = y;
- const threeY = z;
- const threeZ = x;
-
- this.orientation = new THREE.Quaternion(threeX, threeY, threeZ, w);
+ this.orientation = new THREE.Quaternion(x, y, z, w);
this.mapOffset; // : [offsetX, offsetY] // in pixels, offset from map png
diff --git a/src/js/viewer/ViewerMapAPI.js b/src/js/viewer/ViewerMapAPI.js
index fd15762..b52a428 100644
--- a/src/js/viewer/ViewerMapAPI.js
+++ b/src/js/viewer/ViewerMapAPI.js
@@ -1,6 +1,6 @@
"use strict";
-import { MAX_FOV, SCALING_MAP, MAP_ZOOM } from "./ViewerConfig.js";
+import { MAX_FOV, SCALING_MAP, MAP_ZOOM, LON_SCALAR, LAN_SCALAR } from "./ViewerConfig.js";
// Map (2D) Viewer API
// Specific API for the Map View
@@ -29,6 +29,16 @@ export class ViewerMapAPI {
this.lastLayerDirection = [];
this.redraw();
+
+ this.viewerAPI = viewerAPI;
+ let map = document.getElementById('map');
+ map.addEventListener('dblclick', (event) => this.onDoubleClick(event));
+
+ map.addEventListener('fullscreenchange', (event) => {
+ // If map set to full screen, hide the floor setting buttons
+ hideButtons( "floorOL");
+ });
+ this.control_button();
}
// Method: Add an event layer to the map (2D) view.
@@ -64,15 +74,15 @@ export class ViewerMapAPI {
}),
controls: ol.control.defaults({
// Hide Map rotation button
- rotate: false
- }).extend([
- // create fullScreen button
- new ol.control.FullScreen(),
- ]),
+ rotate: false,
+ zoom: false
+ }),
//Disable Zoom Control on MAP
- interactions: ol.interaction.defaults({ mouseWheelZoom: false }),
+ interactions: ol.interaction.defaults({doubleClickZoom :false}),
});
+
+
// create image layers for each floors
for (var i = 0; i < this.viewerFloorAPI.floors.length; i++) {
let mapData = this.viewerFloorAPI.floors[i].mapData
@@ -179,6 +189,9 @@ export class ViewerMapAPI {
this.map.addLayer(vectorLayerRed);
+ // set view to middle
+ this.setMiddle(this.posLon,this.posLan);
+
// save last vector layers for deleting next time
this.lastVectorLayer = currentVectorLayer;
this.lastVectorLayerRed = vectorLayerRed;
@@ -193,7 +206,7 @@ export class ViewerMapAPI {
var lonov = this.viewerViewState.lonov;
// temporary using 170 degree for correcting the starting zero degree of 2D map
- var direction = -(lonov + 180) * (Math.PI / 180) % 360;
+ var direction = lonov * (Math.PI / 180) % 360;
// remove prvious vector layers
@@ -259,9 +272,70 @@ export class ViewerMapAPI {
getLonLanCoordinates(position, mapdata){
// Compute the latitude and longitude in reference to the origin in WGS84 and aff offset of the map
- let lon = 87000 * (position[0] - this.viewerFloorAPI.origin[0]) + (mapdata.x / mapdata.density);
- let lan = 111000 * (position[1] - this.viewerFloorAPI.origin[1]) + (mapdata.y / mapdata.density);
+ let lon = LON_SCALAR * 1000 * (position[0] - this.viewerFloorAPI.origin[0]) + (mapdata.x / mapdata.density);
+ let lan = LAN_SCALAR * 1000 * (position[1] - this.viewerFloorAPI.origin[1]) + (mapdata.y / mapdata.density);
return [lon, lan];
}
+
+ onDoubleClick(event) {
+
+ var coord = [];
+ var mousePosition = [];
+ var mapdata = this.viewerFloorAPI.floors[this.viewerFloorAPI.currentFloorId].mapData;
+ var floor = this.viewerFloorAPI;
+ var z = this.viewerFloorAPI.floors[this.viewerFloorAPI.currentFloorId].z;
+ var viewerAPI = this.viewerAPI;
+
+ this.map.on('dblclick', function(event){
+
+ coord = event.coordinate;
+ mousePosition.push(((coord[0] - (mapdata.x / mapdata.density)) / (LON_SCALAR * 1000) ) + floor.origin[0]);
+ mousePosition.push(((coord[1] - (mapdata.y / mapdata.density)) / (LAN_SCALAR * 1000) ) + floor.origin[1]);
+
+ // move
+ viewerAPI.move(mousePosition[0],mousePosition[1],z);
+
+ })
+ }
+
+ setMiddle(poslon, poslan){
+ this.map.getView().setCenter([poslon,poslan]);
+ }
+
+ control_button(){
+ var zoom_in = document.getElementById('zoom-in');
+ var zoom_out = document.getElementById('zoom-out');
+ var full_screen = document.getElementById('full-screen');
+ var map = this.map;
+
+ zoom_in.addEventListener('click', function () {
+ var view = map.getView();
+ var zoom = view.getZoom();
+ view.setZoom(zoom + 1);
+ })
+
+ zoom_out.addEventListener('click', function () {
+ var view = map.getView();
+ var zoom = view.getZoom();
+ view.setZoom(zoom - 1);
+ })
+
+ full_screen.addEventListener('click', function () {
+ var elem = document.getElementById('map');
+ elem.requestFullscreen();
+ })
+ }
}
+function hideButtons(divId) {
+
+ //let divId = "floorOL";
+ var element = document.getElementById(divId);
+
+ /* Toggle to hide HTML div */
+ if (element.style.display === "none") {
+ element.style.display = "block";
+ } else {
+ element.style.display = "none";
+ }
+ }
\ No newline at end of file
diff --git a/src/js/viewer/ViewerPanoAPI.js b/src/js/viewer/ViewerPanoAPI.js
index 8b304df..10113a0 100644
--- a/src/js/viewer/ViewerPanoAPI.js
+++ b/src/js/viewer/ViewerPanoAPI.js
@@ -1,52 +1,61 @@
"use strict";
import { ViewerViewState } from "./ViewerViewState.js";
-import { DEFAULT_FOV, MAX_FOV, MIN_FOV, ZOOM_SPEED, PAN_SPEED } from "./ViewerConfig.js";
-import { EventLayer } from "./EventLayer.js";
+import { DEFAULT_FOV, MAX_FOV, MIN_FOV, ZOOM_SPEED, PAN_SPEED} from "./ViewerConfig.js";
import { EventPosition } from "./EventPosition.js";
export class ViewerPanoAPI {
constructor(viewerAPI) {
- this.scene = new THREE.Scene(); // three.js scene used by the panorama (3D) viewer
- this.camera = new THREE.PerspectiveCamera(DEFAULT_FOV, window.innerWidth / window.innerHeight, 1, 1100);
- this.viewerImageAPI = viewerAPI.image;
this.viewerAPI = viewerAPI;
- this.sphereRadius = 5;
+ this.addedLayers = new Set(); // EventMesh and EventLayer objects added via addLayer();
- this.viewerViewState = new ViewerViewState(DEFAULT_FOV, 0, 0);
- this.lastViewState;
- this.lastMousePos;
+ this.scene = new THREE.Scene(); // three.js scene used by the panorama (3D) viewer
+ this.camera = new THREE.PerspectiveCamera(DEFAULT_FOV, window.innerWidth / window.innerHeight, 1, 1100);
+ this.camera.up = new THREE.Vector3(0, 0, 1);
+ this.sphereRadius = 10;
- //initialize the eventLayer
- this.eventLayer = new EventLayer();
-
- // properties needed for display and depthAtPointer method
+ // property needed for display method
this.loadedMesh = null;
- this.depthCanvas = document.createElement("canvas");
- // Two new event listeneres are called to handle *how far* the user drags
- this.oPM = (event) => this.onPointerMove(event);
- this.oPU = () => this.onPointerUp();
+ // property needed for depthAtPointer method
+ this.depthCanvas = document.createElement("canvas");
+ // handeling zooming / panning / moving / resizing
const panoViewer = document.getElementById('pano-viewer');
+ this.viewerViewState = new ViewerViewState(DEFAULT_FOV, 0, 0);
+ this.lastViewState;
+ this.lastMousePos;
panoViewer.addEventListener('wheel', (event) => this.onDocumentMouseWheel(event));
panoViewer.addEventListener('pointerdown', (event) => this.onPointerDown(event));
panoViewer.addEventListener('dblclick', (event) => this.onDoubleClick(event));
+ window.addEventListener("resize", () => this.onWindowResize());
+ // Two new event listeneres are called to handle *how far* the user drags
+ this.oPM = (event) => this.onPointerMove(event);
+ this.oPU = () => this.onPointerUp();
+
+ // handeling EventMesh / EventLayer API integration
+ this.preMeshes = new Set(); // meshes that the mouse pointer is currently over
+ panoViewer.addEventListener('click', (event) => this.meshCheckClick(event));
+ panoViewer.addEventListener('contextmenu', (event) => {
+ event.preventDefault();
+ this.meshCheckRightClick(event);
+ });
+ panoViewer.addEventListener('pointermove', (event) => this.meshCheckMouseOver(event));
- $('#pano-viewer').mousedown((event) => this.onRightClick(event));
- this.display(this.viewerImageAPI.currentImageId);
+ this.display(this.viewerAPI.image.currentImageId);
}
// displays the panorama with idx *ImageNum* in the model
display(imageNum) {
- this.viewerImageAPI.currentImageId = imageNum;
+ this.viewerAPI.image.currentImageId = imageNum;
// create sphere
const sphere = new THREE.SphereGeometry(this.sphereRadius, 60, 40);
// invert the geometry on the x-axis so that we look out from the middle of the sphere
sphere.scale(-1, 1, 1);
+ sphere.rotateX(Math.PI / 2);
// load the 360-panorama image data (highest resolution hardcoded for now)
const texturePano = this.viewerAPI.textureLoader.load(
@@ -61,9 +70,9 @@ export class ViewerPanoAPI {
const image = new Image();
//image.crossOrigin = "use-credentials";
image.src = this.viewerAPI.baseURL +
- Math.trunc(this.viewerImageAPI.currentImage.id / 100) + '/' +
- this.viewerImageAPI.currentImage.id + 'd.png';
-
+ Math.trunc(imageNum / 100) + '/' +
+ imageNum + 'd.png';
+
image.addEventListener('load', () => {
this.depthCanvas.getContext("2d").drawImage(image, 0, 0);
}, false);
@@ -71,12 +80,12 @@ export class ViewerPanoAPI {
// put the texture on the spehere and add it to the scene
const mesh = new THREE.Mesh(sphere, new THREE.MeshBasicMaterial({ map: texturePano }));
-
+
// adjust for orientation offset
- mesh.applyQuaternion(this.viewerImageAPI.currentImage.orientation);
-
+ mesh.applyQuaternion(this.viewerAPI.image.currentImage.orientation);
+
// put in the correct position in the scene
- const localCoord = this.viewerAPI.toLocal(this.viewerImageAPI.currentImage.pos);
+ const localCoord = this.viewerAPI.toLocal(this.viewerAPI.image.currentImage.pos);
mesh.position.set(localCoord.x, localCoord.y, localCoord.z);
// check if other panorama was previously already loaded
@@ -86,7 +95,7 @@ export class ViewerPanoAPI {
this.scene.add(mesh);
this.loadedMesh = mesh;
-
+
// put camera inside sphere mesh
this.camera.position.set(localCoord.x, localCoord.y, localCoord.z);
}
@@ -100,7 +109,7 @@ export class ViewerPanoAPI {
const normalizedViewingDirection = lonLatToLocal(lonov, latov);
// adjust looking direction for offset of current mesh in scene
- const localCoord = this.viewerAPI.toLocal(this.viewerImageAPI.currentImage.pos);
+ const localCoord = this.viewerAPI.toLocal(this.viewerAPI.image.currentImage.pos);
this.camera.lookAt(localCoord.add(normalizedViewingDirection));
@@ -109,6 +118,23 @@ export class ViewerPanoAPI {
this.camera.updateProjectionMatrix();
}
+ // Add an event layer to the panorama (3D) viewer.
+ // param: EventLayer (or EventMesh) to add
+ addLayer(layer) {
+ if (!layer) return;
+ if (this.addedLayers.has(layer)) return;
+
+ this.scene.add(layer);
+ this.addedLayers.add(layer);
+ }
+
+ removeLayer(layer) {
+ if (!layer) return;
+ if (!this.addedLayers.has(layer)) return;
+
+ this.scene.remove(layer);
+ this.addedLayers.delete(layer);
+ }
// ----- Event handling functions for panning, zooming and moving -----
onPointerDown(event) {
@@ -124,7 +150,7 @@ export class ViewerPanoAPI {
onPointerMove(event) {
const scalingFactor = this.camera.fov / MAX_FOV;
- this.viewerViewState.setLonov((this.lastMousePos[0] - event.clientX) * PAN_SPEED * scalingFactor + this.lastViewState[0]);
+ this.viewerViewState.setLonov((event.clientX - this.lastMousePos[0]) * PAN_SPEED * scalingFactor + this.lastViewState[0]);
this.viewerViewState.setLatov((event.clientY - this.lastMousePos[1]) * PAN_SPEED * scalingFactor + this.lastViewState[1]);
this.viewerAPI.map.show_direction();
@@ -140,7 +166,7 @@ export class ViewerPanoAPI {
onDocumentMouseWheel(event) {
this.viewerViewState.fov = this.camera.fov + event.deltaY * ZOOM_SPEED;
-
+
this.view(this.viewerViewState.lonov, this.viewerViewState.latov, this.viewerViewState.fov);
this.camera.updateProjectionMatrix();
@@ -149,67 +175,125 @@ export class ViewerPanoAPI {
}
onDoubleClick(event) {
- const [adjustedLonov, adjustedLatov] = this.getAdjustedViewstate(event);
- const MEDIAN_WALKING_DISTANCE = 5; // in meter
- // distance to be walked along adjustedHorizontalAngle from current location
- const distance = MEDIAN_WALKING_DISTANCE + ((adjustedLatov / 85) * MEDIAN_WALKING_DISTANCE);
+ const currentPos = this.viewerAPI.image.currentImage.pos;
+ const newLocalPos = this.getCursorLocation(event);
+ const newPos = this.viewerAPI.toGlobal(newLocalPos);
- // convertedAngle converted to represent directions like specified in newLocationFromPointAngle
- const convertedAngle = (adjustedLonov < 180) ? -adjustedLonov : 360 - adjustedLonov;
-
- const currentPos = this.viewerImageAPI.currentImage.pos;
-
- const newPos = newLocationFromPointAngle(currentPos[0], currentPos[1], THREE.Math.degToRad(convertedAngle), distance);
this.viewerAPI.move(newPos[0], newPos[1], currentPos[2]);
- this.viewerAPI.propagateEvent("moved", this.viewerImageAPI.currentImage.id, true);
+ this.viewerAPI.propagateEvent("moved", this.viewerAPI.image.currentImage.id, true);
+ }
+
+ onWindowResize() {
+ this.camera.aspect = window.innerWidth / window.innerHeight;
+ this.camera.updateProjectionMatrix();
+
+ this.viewerAPI.renderer.setSize(window.innerWidth, window.innerHeight);
}
- onRightClick(event) {
- //if right mouse is clicked:
- if (event.which == 3) {
+ // ---- event handeling functions for EventMesh / EventLayer API interaction ----
+ getIntersectingMeshes(event) {
+ const raycaster = this.getRaycaster(event);
+
+ // calculate objects intersecting the picking ray
+ const intersects = raycaster.intersectObjects(this.scene.children);
+
+ // include only objects that are added meshes
+ const meshes = [];
+ for (const e in intersects) {
+ if (this.addedLayers.has(intersects[e].object)) {
+ // check if mesh is within sphere radius to camera
+ const dist = this.camera.position.distanceTo(intersects[e].object.position);
+ if (dist < this.sphereRadius) {
+ meshes.push(intersects[e].object);
+ }
+ }
+ }
- //get the current pointer position:
- const xy = new EventPosition(event);
+ return meshes;
+ }
+ meshCheckClick(event) {
+ const meshes = this.getIntersectingMeshes(event);
+ const xy = new EventPosition(event);
+ const location = this.getCursorLocation(event);
- //get the position of pointer in scene:
- const location = this.getCursorLocation(event);
+ for (let i = 0; i < meshes.length; i++) {
+ const mesh = meshes[i];
- //Set up the context menu:
- $.contextMenu({
- selector: '#pano-viewer',
- items: this.eventLayer.vwr_oncontext(xy, location),
- });
+ if (typeof mesh.vwr_onclick == "function") {
+ mesh.vwr_onclick(xy, location);
+ }
}
}
+ meshCheckRightClick(event) {
+ const meshes = this.getIntersectingMeshes(event);
+ const xy = new EventPosition(event);
+ const location = this.getCursorLocation(event);
+
+ for (let i = 0; i < meshes.length; i++) {
+ const mesh = meshes[i];
+
+ if (typeof mesh.vwr_oncontext == "function") {
+ const callback = mesh.vwr_oncontext(xy, location);
+
+ $.contextMenu({
+ selector: '#pano-viewer',
+ items: callback,
+ });
+ }
+ }
+ }
+
+ meshCheckMouseOver(event) {
+ const meshes = this.getIntersectingMeshes(event);
+
+ // check for meshes that mouse pointer is no longer over
+ this.preMeshes.forEach((preMesh) => {
+ if (!meshes.includes(preMesh)) {
+ if (typeof preMesh.vwr_onpointerleave == "function") {
+ // remove the current mesh
+ this.preMeshes.delete(preMesh);
+
+ preMesh.vwr_onpointerleave();
+ }
+ }
+ });
+
+ // check for meshes that mouse pointer is newly over
+ for (let i = 0; i < meshes.length; i++) {
+ const mesh = meshes[i];
+
+ //if the current mesh has not been entered before.
+ if (!this.preMeshes.has(mesh)) {
+ if (typeof mesh.vwr_onpointerenter == "function") {
+ // store the current mesh
+ this.preMeshes.add(mesh);
+
+ mesh.vwr_onpointerenter();
+ }
+ }
+ }
+ }
// returns: the depth information (in meter) of the panorama at the current curser position (event.clientX, event.clientY)
depthAtPointer(event) {
- const [adjustedLonov, adjustedLatov] = this.getAdjustedViewstate(event);
-
+ const raycaster = this.getRaycaster(event);
// because depth map is not rotated by quaternion like panorama mesh, the quaternion adjustment need to happen first
- const localPos = lonLatToLocal(adjustedLonov, adjustedLatov);
- const adjustedQuaternion = localPos.applyQuaternion(this.viewerImageAPI.currentImage.orientation);
- const [realLonov, realLatov] = localToLonLat(adjustedQuaternion);
+ const mappedCursorDirection = raycaster.ray.direction.applyQuaternion(this.viewerAPI.image.currentImage.orientation);
+ const [cursorLon, cursorLat] = localToLonLat(mappedCursorDirection);
- // pixel offsets in depth map at current curser position
- const pixelX = Math.trunc((realLonov / 360) * this.depthCanvas.width);
- const pixelY = Math.trunc((realLatov + 90) / 180 * this.depthCanvas.height);
+ // adjust to calculate pixel offset on image, values in [0;360, -90;90]
+ const [adjustedLonov, adjustedLatov] = [((180 - cursorLon) + 360) % 360, cursorLat];
- const offsetX = (pixelX >= 2) ? pixelX - 2 : 0;
- const offsetY = (pixelY >= 2) ? pixelY - 2 : 0;
+ // pixel offsets in depth map at current curser position
+ const pixelX = Math.trunc((adjustedLonov / 360) * this.depthCanvas.width);
+ const pixelY = Math.trunc((adjustedLatov + 90) / 180 * this.depthCanvas.height);
// convert pixel value to depth information
- const use5pixelAvg = false;
- let imgData;
- if (use5pixelAvg) {
- imgData = this.depthCanvas.getContext("2d").getImageData(offsetX, offsetY, 5, 5);
- } else {
- imgData = this.depthCanvas.getContext("2d").getImageData(pixelX, pixelY, 1, 1);
- }
- const [red, green, blue, alpha] = averagePixelValues(imgData.data);
+ const imgData = this.depthCanvas.getContext("2d").getImageData(pixelX, pixelY, 1, 1);
+ const [red, green, blue, alpha] = imgData.data;
// LSB red -> green -> blue MSB (ignore alpha)
const distanceMM = red | (green << 8) | (blue << 16);
@@ -220,121 +304,49 @@ export class ViewerPanoAPI {
// returns the current location of the cursor in the three js scene (Vector3)
getCursorLocation(event) {
- // param: event.x event.y current cursor position on screen
- const [adjustedLonov, adjustedLatov] = this.getAdjustedViewstate(event);
- const normalizedLocalViewingDir = lonLatToLocal(adjustedLonov, adjustedLatov);
-
- // adjust looking direction for offset of current mesh in scene
- const localCoord = this.viewerAPI.toLocal(this.viewerImageAPI.currentImage.pos);
-
- // get distance und extend viewing direction vector by distance
- const dist = this.depthAtPointer(event);
-
- localCoord.addScaledVector(normalizedLocalViewingDir, dist)
-
- return localCoord;
- }
-
- // returns [lonov, latov] at the current cursor position
- getAdjustedViewstate(event) {
- // find correct pixel position on equilateral projected depth map
- const halfWidth = window.innerWidth / 2;
- const halfHeight = window.innerHeight / 2;
-
- // horizontal (lonov) : image left -> 0, image right -> 360
- // vertical (latov) : image top -> 85, image bottom -> -85
- const horizontalOffset = (event.clientX - halfWidth) / halfWidth; // scaled between [-1,1] depending how left-right the mouse click is on the screen
- const verticalOffset = (halfHeight - event.clientY) / halfHeight; // scaled between [-1,1] depending how up-down the mouse click is on the screen
-
- const adjustedLonov = ((this.viewerViewState.lonov + (horizontalOffset * this.viewerViewState.fov / 2)) + 360) % 360;
- const adjustedLatov = Math.max(-85, Math.min(85, this.viewerViewState.latov + (verticalOffset * this.viewerViewState.fov / 2)));
+ const raycaster = this.getRaycaster(event);
+ // formula for position is currentLoc + direction*distance (where the direction is normalized)
+ const distance = this.depthAtPointer(event);
+ const cursorLocation = raycaster.ray.origin.addScaledVector(raycaster.ray.direction, distance);
- return [adjustedLonov, adjustedLatov];
+ return cursorLocation;
}
- visualTest(event) {
- console.info(this.viewerViewState);
- console.info(this.camera.getWorldDirection());
- console.info("current img original global ", this.viewerImageAPI.currentImage.pos)
- const loc = this.viewerAPI.toLocal(this.viewerImageAPI.currentImage.pos);
- console.info("current img pos loc ", loc);
- const b = this.viewerAPI.toGlobal(loc);
- console.info("back to global ", b);
- const bb = this.viewerAPI.toLocal(b);
- console.info("and back to loc one more time ", bb);
-
- // visual test, spawn in white sphere at cursor position in scene
- const direction = this.getCursorLocation(event);
- const sphere = new THREE.SphereGeometry(1 / this.depthAtPointer(event), 10, 10);
- const mesh = new THREE.Mesh(sphere, new THREE.MeshBasicMaterial());
- mesh.position.set(direction.x, direction.y, direction.z);
-
- if (this.testMesh != null) {
- this.scene.remove(this.testMesh);
- }
+ getRaycaster(event) {
+ // calculate mouse position in normalized device coordinates
+ // (-1 to +1) for both components
+ const mouse = new THREE.Vector2();
+ const raycaster = new THREE.Raycaster();
- this.scene.add(mesh);
+ mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
+ mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
- this.testMesh = mesh;
-
- console.info("current sphere pos ", direction);
- }
-}
-
-
-// takes in a location (in lot/lat), a direction (as a *angle*[rad, in birds eye view), and a distance (in meters) to move in the direction
-const newLocationFromPointAngle = (lon1, lat1, angle, distance) => {
- // angle: +-0 -> west, +pi/2 -> south, +-pi -> east, -pi/2 -> north
- let lon2, lat2;
-
- const dx = (distance / 1000) * Math.cos(angle);
- const dy = (distance / 1000) * Math.sin(angle);
-
- lon2 = lon1 - (dx / 71.5);
- lat2 = lat1 - (dy / 111.3);
-
- return [lon2, lat2];
-}
-
-const averagePixelValues = (data) => {
- const pixels = data.length / 4;
- let [red, green, blue, alpha] = [0, 0, 0, 0]; // sum of all pixel values
-
- for (let i = 0; i < data.length; i = i + 4) {
- red = red + data[i];
- green = green + data[i + 1];
- blue = blue + data[i + 2];
- alpha = alpha + data[i + 3];
+ raycaster.setFromCamera(mouse, this.camera);
+
+ return raycaster;
}
-
- // get average by dividing
- red = red / pixels;
- green = green / pixels;
- blue = blue / pixels;
- alpha = alpha / pixels;
-
- return [red, green, blue, alpha];
+
}
// returns a normalized Vector3 pointing in the direction specified by lonov latov
const lonLatToLocal = (lonov, latov) => {
const phi = THREE.MathUtils.degToRad(90 - latov);
const theta = THREE.MathUtils.degToRad(lonov);
-
+
const x = Math.sin(phi) * Math.cos(theta);
- const y = Math.cos(phi);
- const z = Math.sin(phi) * Math.sin(theta);
+ const y = Math.sin(phi) * Math.sin(theta);
+ const z = Math.cos(phi);
- return new THREE.Vector3(x, y, z);
+ return new THREE.Vector3(-x, -y, z);
}
// inverse operation to above
const localToLonLat = (vec) => {
- const phi = Math.acos(vec.y);
- const theta = Math.atan2(vec.z, vec.x);
+ const phi = Math.acos(vec.z);
+ const theta = Math.atan2(-vec.y, -vec.x);
- let latov = THREE.MathUtils.radToDeg(phi);
+ const latov = THREE.MathUtils.radToDeg(phi);
const lonov = (THREE.MathUtils.radToDeg(theta) + 360) % 360;
return [lonov, 90 - latov];
-}
+}
\ No newline at end of file