UNPKG

@here/harp-mapview

Version:

Functionality needed to render a map.

263 lines 11.9 kB
"use strict"; /* * Copyright (C) 2019-2021 HERE Europe B.V. * Licensed under Apache 2.0, see full license in LICENSE * SPDX-License-Identifier: Apache-2.0 */ Object.defineProperty(exports, "__esModule", { value: true }); exports.PickHandler = exports.PickObjectType = void 0; const harp_datasource_protocol_1 = require("@here/harp-datasource-protocol"); const harp_geoutils_1 = require("@here/harp-geoutils"); const THREE = require("three"); const MapViewPoints_1 = require("./MapViewPoints"); const PickingRaycaster_1 = require("./PickingRaycaster"); const PickListener_1 = require("./PickListener"); const Utils_1 = require("./Utils"); /** * Describes the general type of a picked object. */ var PickObjectType; (function (PickObjectType) { /** * Unspecified. */ PickObjectType[PickObjectType["Unspecified"] = 0] = "Unspecified"; /** * A point object. */ PickObjectType[PickObjectType["Point"] = 1] = "Point"; /** * A line object. */ PickObjectType[PickObjectType["Line"] = 2] = "Line"; /** * An area object. */ PickObjectType[PickObjectType["Area"] = 3] = "Area"; /** * The text part of a {@link TextElement} */ PickObjectType[PickObjectType["Text"] = 4] = "Text"; /** * The Icon of a {@link TextElement}. */ PickObjectType[PickObjectType["Icon"] = 5] = "Icon"; /** * Any general 3D object, for example, a landmark. */ PickObjectType[PickObjectType["Object3D"] = 6] = "Object3D"; })(PickObjectType = exports.PickObjectType || (exports.PickObjectType = {})); const tmpV3 = new THREE.Vector3(); const tmpOBB = new harp_geoutils_1.OrientedBox3(); // Intersects the dependent tile objects using the supplied raycaster. Note, because multiple // tiles can point to the same dependency we need to store which results we have already // raycasted, see checkedDependencies. function intersectDependentObjects(tile, intersects, rayCaster, checkedDependencies, mapView) { for (const tileKey of tile.dependencies) { const mortonCode = tileKey.mortonCode(); if (checkedDependencies.has(mortonCode)) { continue; } checkedDependencies.add(mortonCode); const otherTile = mapView.visibleTileSet.getCachedTile(tile.dataSource, tileKey, tile.offset, mapView.frameNumber); if (otherTile !== undefined) { rayCaster.intersectObjects(otherTile.objects, true, intersects); } } } /** * Handles the picking of scene geometry and roads. * @internal */ class PickHandler { constructor(mapView, camera, enablePickTechnique = false) { this.mapView = mapView; this.camera = camera; this.enablePickTechnique = enablePickTechnique; this.m_pickingRaycaster = new PickingRaycaster_1.PickingRaycaster(mapView.renderer.getSize(new THREE.Vector2())); } /** * Does a raycast on all objects in the scene; useful for picking. * * @param x - The X position in CSS/client coordinates, without the applied display ratio. * @param y - The Y position in CSS/client coordinates, without the applied display ratio. * @param parameters - The intersection test behaviour may be adjusted by providing an instance * of {@link IntersectParams}. * @returns the list of intersection results. */ intersectMapObjects(x, y, parameters) { const ndc = this.mapView.getNormalizedScreenCoordinates(x, y); const rayCaster = this.setupRaycaster(x, y); const pickListener = new PickListener_1.PickListener(parameters); if (this.mapView.textElementsRenderer !== undefined) { const { clientWidth, clientHeight } = this.mapView.canvas; const screenX = ndc.x * clientWidth * 0.5; const screenY = ndc.y * clientHeight * 0.5; const scenePosition = new THREE.Vector2(screenX, screenY); this.mapView.textElementsRenderer.pickTextElements(scenePosition, pickListener); } const intersects = []; const intersectedTiles = this.getIntersectedTiles(rayCaster); // This ensures that we check a given dependency only once (because multiple tiles could // have the same dependency). const checkedDependencies = new Set(); for (const { tile, distance } of intersectedTiles) { if (pickListener.done && pickListener.furthestResult.distance < distance) { // Stop when the listener has all results it needs and remaining tiles are further // away than then furthest pick result found so far. break; } intersects.length = 0; rayCaster.intersectObjects(tile.objects, true, intersects); intersectDependentObjects(tile, intersects, rayCaster, checkedDependencies, this.mapView); for (const intersect of intersects) { pickListener.addResult(this.createResult(intersect, tile)); } } // Intersect any objects added by the user. intersects.length = 0; for (const child of this.mapView.mapAnchors.children) { rayCaster.intersectObject(child, true, intersects); for (const intersect of intersects) { pickListener.addResult(this.createResult(intersect)); } } pickListener.finish(); return pickListener.results; } /** * Returns a ray caster using the supplied screen positions. * * @param x - The X position in css/client coordinates (without applied display ratio). * @param y - The Y position in css/client coordinates (without applied display ratio). * * @return Raycaster with origin at the camera and direction based on the supplied x / y screen * points. */ raycasterFromScreenPoint(x, y) { this.m_pickingRaycaster.setFromCamera(this.mapView.getNormalizedScreenCoordinates(x, y), this.camera); this.mapView.renderer.getSize(this.m_pickingRaycaster.canvasSize); return this.m_pickingRaycaster; } createResult(intersection, tile) { var _a, _b, _c; const pickResult = { type: PickObjectType.Unspecified, point: intersection.point, distance: intersection.distance, dataSourceName: (_a = intersection.object.userData) === null || _a === void 0 ? void 0 : _a.dataSource, dataSourceOrder: (_b = tile === null || tile === void 0 ? void 0 : tile.dataSource) === null || _b === void 0 ? void 0 : _b.dataSourceOrder, intersection, tileKey: tile === null || tile === void 0 ? void 0 : tile.tileKey }; if (intersection.object.userData === undefined || intersection.object.userData.feature === undefined) { return pickResult; } if (this.enablePickTechnique) { pickResult.technique = intersection.object.userData.technique; } pickResult.renderOrder = (_c = intersection.object) === null || _c === void 0 ? void 0 : _c.renderOrder; const featureData = intersection.object.userData.feature; this.addObjInfo(featureData, intersection, pickResult); if (pickResult.userData) { const featureId = harp_datasource_protocol_1.getFeatureId(pickResult.userData); pickResult.featureId = featureId === 0 ? undefined : featureId; } let pickObjectType; switch (featureData.geometryType) { case harp_datasource_protocol_1.GeometryType.Point: case harp_datasource_protocol_1.GeometryType.Text: pickObjectType = PickObjectType.Point; break; case harp_datasource_protocol_1.GeometryType.Line: case harp_datasource_protocol_1.GeometryType.ExtrudedLine: case harp_datasource_protocol_1.GeometryType.SolidLine: case harp_datasource_protocol_1.GeometryType.TextPath: pickObjectType = PickObjectType.Line; break; case harp_datasource_protocol_1.GeometryType.Polygon: case harp_datasource_protocol_1.GeometryType.ExtrudedPolygon: pickObjectType = PickObjectType.Area; break; case harp_datasource_protocol_1.GeometryType.Object3D: pickObjectType = PickObjectType.Object3D; break; default: pickObjectType = PickObjectType.Unspecified; } pickResult.type = pickObjectType; return pickResult; } getIntersectedTiles(rayCaster) { const tiles = new Array(); const tileList = this.mapView.visibleTileSet.dataSourceTileList; tileList.forEach(dataSourceTileList => { if (!dataSourceTileList.dataSource.enablePicking) { return; } dataSourceTileList.renderedTiles.forEach(tile => { tmpOBB.copy(tile.boundingBox); tmpOBB.position.sub(this.mapView.worldCenter); // This offset shifts the box by the given tile offset, see renderTileObjects in // MapView const worldOffsetX = tile.computeWorldOffsetX(); tmpOBB.position.x += worldOffsetX; const distance = tmpOBB.intersectsRay(rayCaster.ray); if (distance !== undefined) { tiles.push({ tile, distance }); } }); }); tiles.sort((lhs, rhs) => { return lhs.distance - rhs.distance; }); return tiles; } addObjInfo(featureData, intersect, pickResult) { if (featureData.objInfos === undefined) { return; } if (pickResult.intersection.object instanceof MapViewPoints_1.MapViewPoints) { pickResult.userData = featureData.objInfos[intersect.index]; return; } if (featureData.starts === undefined || featureData.starts.length === 0 || (typeof intersect.faceIndex !== "number" && intersect.index === undefined)) { if (featureData.objInfos.length === 1) { pickResult.userData = featureData.objInfos[0]; } return; } if (featureData.starts.length === 1) { pickResult.userData = featureData.objInfos[0]; return; } const intersectIndex = typeof intersect.faceIndex === "number" ? intersect.faceIndex * 3 : intersect.index; // TODO: Implement binary search. let objInfosIndex = 0; for (const featureStartIndex of featureData.starts) { if (featureStartIndex > intersectIndex) { break; } objInfosIndex++; } pickResult.userData = featureData.objInfos[objInfosIndex - 1]; } setupRaycaster(x, y) { const camera = this.mapView.camera; const rayCaster = this.raycasterFromScreenPoint(x, y); // A threshold must be set for picking of line and line segments, indicating the maximum // distance in world units from the ray to a line to consider it as picked. Use the world // units equivalent to one pixel at the furthest intersection (i.e. intersection with ground // or far plane). const furthestIntersection = this.mapView.getWorldPositionAt(x, y, true); const furthestDistance = camera.position.distanceTo(furthestIntersection) / this.mapView.camera.getWorldDirection(tmpV3).dot(rayCaster.ray.direction); rayCaster.params.Line.threshold = Utils_1.MapViewUtils.calculateWorldSizeByFocalLength(this.mapView.focalLength, furthestDistance, 1); return rayCaster; } } exports.PickHandler = PickHandler; //# sourceMappingURL=PickHandler.js.map