import { GeometryType , getFeatureId , Technique } from "@here/harp-datasource-protocol" ; import { OrientedBox3 , TileKey } from "@here/harp-geoutils" ; import * as THREE from "three" ; import { IntersectParams } from "./IntersectParams" ; import { MapView } from "./MapView" ; import { MapViewPoints } from "./MapViewPoints" ; import { PickingRaycaster } from "./PickingRaycaster" ; import { PickListener } from "./PickListener" ; import { Tile , TileFeatureData } from "./Tile" ; import { MapViewUtils } from "./Utils" ; /**
 * Describes the general type of a picked object.
 */ export enum PickObjectType { /**
     * Unspecified.
     */ Unspecified = 0 , /**
     * A point object.
     */ Point , /**
     * A line object.
     */ Line , /**
     * An area object.
     */ Area , /**
     * The text part of a  TextElement Text , /**
     * The Icon of a  TextElement .
      Icon , /**
     * Any general 3D object, for example, a landmark.
     */ Object3D } /**
 * A general pick result. You can access the details of a picked geometry from the property
 * `intersection`, which is available if a geometry was hit. If a road was hit, a [[RoadPickResult]]
 * is returned, which has additional information, but no `intersection`.
 */ export interface PickResult { /**
     * General type of object.
     */ type : PickObjectType ; /**
     * A 2D point in screen coordinates, or a 3D point in world coordinates.
     */ point : THREE . Vector2 | THREE . Vector3 ; /**
     * Distance from the camera to the picking point; used to determine the closest object.
     */ distance : number ; /**
     * Uniquely identifies the data source which provided the picked object.
     */ dataSourceName : string | undefined ; /**
     * Data source order, useful for sorting a collection of picking results.
     * A number for objects/features coming from tiles (as those have data sources attached),
     * an undefined when objects are added via "mapView.mapAnchors.add(object)" - those are treated as
     * base layer objects during picking (same as "dataSourceOrder: 0").
     */ dataSourceOrder : number | undefined ; /**
     * Render order of the intersected object.
     */ renderOrder ? : number ; remarks The ID may be assigned by the object's  DataSource , for example in case of
     * Optimized Map Vector (OMV) and GeoJSON data sources.
      featureId ? : number | string ; /**
     * Defined for geometry only.
     */ intersection ? : THREE . Intersection ; /**
     * Defined for roads or if `enableTechniqueInfo` option is enabled.
     */ technique ? : Technique ; remarks This object points directly to
     * information contained in the original  TileFeatureData * stored in  MapView , and should
     * not be modified.
      userData ? : any ; /**
     * The tile key containing the picked object.
     */ tileKey ? : TileKey ; } const tmpV3 = new THREE . Vector3 ( ) ; const tmpOBB = new OrientedBox3 ( ) ; 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 ) ; } } } internal export class PickHandler { private readonly m_pickingRaycaster : PickingRaycaster ; constructor ( readonly mapView , readonly camera , public enablePickTechnique = false ) { this . m_pickingRaycaster = new PickingRaycaster ( mapView . renderer . getSize ( new THREE . Vector2 ( ) ) ) ; } param x param y param parameters - The intersection test behaviour may be adjusted by providing an instance
     * of  IntersectParams .
     *  returns intersectMapObjects ( x , y , parameters ? ) { const ndc = this . mapView . getNormalizedScreenCoordinates ( x , y ) ; const rayCaster = this . setupRaycaster ( x , y ) ; const pickListener = new 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 ) ; const checkedDependencies = new Set < number > ( ) ; for ( const { tile , distance } of intersectedTiles ) { if ( pickListener . done && pickListener . furthestResult ! . distance < distance ) { 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 ) ) ; } } 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 ; } param x param y return 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 ; } private createResult ( intersection , tile ? ) { const pickResult = { type : PickObjectType . Unspecified , point : intersection . point , distance : intersection . distance , dataSourceName : intersection . object . userData ?. dataSource , dataSourceOrder : tile ?. dataSource ?. dataSourceOrder , intersection , tileKey : 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 = intersection . object ?. renderOrder ; const featureData = intersection . object . userData . feature ; this . addObjInfo ( featureData , intersection , pickResult ) ; if ( pickResult . userData ) { const featureId = getFeatureId ( pickResult . userData ) ; pickResult . featureId = featureId === 0 ? undefined : featureId ; } let pickObjectType ; switch ( featureData . geometryType ) { case GeometryType . Point : case GeometryType . Text : pickObjectType = PickObjectType . Point ; break ; case GeometryType . Line : case GeometryType . ExtrudedLine : case GeometryType . SolidLine : case GeometryType . TextPath : pickObjectType = PickObjectType . Line ; break ; case GeometryType . Polygon : case GeometryType . ExtrudedPolygon : pickObjectType = PickObjectType . Area ; break ; case GeometryType . Object3D : pickObjectType = PickObjectType . Object3D ; break ; default : pickObjectType = PickObjectType . Unspecified ; } pickResult . type = pickObjectType ; return pickResult ; } private getIntersectedTiles ( rayCaster ) { const tiles = new Array < { tile : Tile ; distance : number ; } > ( ) ; 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 ) ; 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 ; } private addObjInfo ( featureData , intersect , pickResult ) { if ( featureData . objInfos === undefined ) { return ; } if ( pickResult . intersection ! . object instanceof 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 ! ; let objInfosIndex = 0 ; for ( const featureStartIndex of featureData . starts ) { if ( featureStartIndex > intersectIndex ) { break ; } objInfosIndex ++ ; } pickResult . userData = featureData . objInfos [ objInfosIndex - 1 ] ; } private setupRaycaster ( x , y ) { const camera = this . mapView . camera ; const rayCaster = this . raycasterFromScreenPoint ( x , y ) ; 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 = MapViewUtils . calculateWorldSizeByFocalLength ( this . mapView . focalLength , furthestDistance , 1 ) ; return rayCaster ; } }