import { EarthConstants , GeoCoordinates , GeoPolygon , GeoPolygonCoordinates , Projection , ProjectionType } from "@here/harp-geoutils" ; import { assert } from "@here/harp-utils" ; import { Frustum , Line3 , Matrix4 , PerspectiveCamera , Plane , Ray , Vector3 } from "three" ; import { TileCorners } from "./geometry/ProjectTilePlaneCorners" ; import { MapViewUtils } from "./Utils" ; import { ViewBounds } from "./ViewBounds" ; internal export class PlaneViewBounds implements ViewBounds { private readonly m_groundPlaneNormal = new Vector3 ( 0 , 0 , 1 ) ; private readonly m_groundPlane = new Plane ( this . m_groundPlaneNormal . clone ( ) ) ; constructor ( readonly camera , readonly projection , private readonly m_options ) { assert ( projection . type === ProjectionType . Planar ) ; } @override
      generate ( ) { const coordinates = [ ] ; this . addCanvasCornerIntersection ( coordinates ) ; if ( coordinates . length === 4 ) { return this . createPolygon ( coordinates ) ; } this . addHorizonIntersection ( coordinates ) ; const frustum = new Frustum ( ) . setFromProjectionMatrix ( new Matrix4 ( ) . multiplyMatrices ( this . camera . projectionMatrix , this . camera . matrixWorldInverse ) ) ; const worldCorners = this . getWorldConers ( this . projection ) as TileCorners ; if ( ! this . m_options . tileWrappingEnabled ) { [ worldCorners . ne , worldCorners . nw , worldCorners . se , worldCorners . sw ] . forEach ( corner => { this . addPointInFrustum ( corner , frustum , coordinates ) ; } ) ; } if ( ! this . m_options . tileWrappingEnabled ) { [ new Line3 ( worldCorners . sw , worldCorners . se ) , new Line3 ( worldCorners . ne , worldCorners . nw ) , new Line3 ( worldCorners . se , worldCorners . ne ) , new Line3 ( worldCorners . nw , worldCorners . sw ) ] . forEach ( edge => { this . addFrustumIntersection ( edge , frustum , coordinates ) ; } ) ; } else { const directionEast = new Vector3 ( ) . subVectors ( worldCorners . sw , worldCorners . se ) . normalize ( ) ; const directionWest = new Vector3 ( ) . subVectors ( worldCorners . se , worldCorners . sw ) . normalize ( ) ; [ new Ray ( worldCorners . se , directionEast ) , new Ray ( worldCorners . se , directionWest ) , new Ray ( worldCorners . ne , directionEast ) , new Ray ( worldCorners . ne , directionWest ) ] . forEach ( ray => { this . addFrustumIntersection ( ray , frustum , coordinates ) ; } ) ; } return this . createPolygon ( coordinates ) ; } private createPolygon ( coordinates ) { if ( coordinates . length > 2 ) { return new GeoPolygon ( coordinates as GeoPolygonCoordinates , true ) ; } return undefined ; } private getWorldConers ( projection ) { const worldBox = projection . worldExtent ( 0 , 0 ) ; return { sw : worldBox . min as Vector3 , se : new Vector3 ( worldBox . max . x , worldBox . min . y , 0 ) , nw : new Vector3 ( worldBox . min . x , worldBox . max . y , 0 ) , ne : worldBox . max as Vector3 } ; } private addNDCRayIntersection ( ndcPoints , geoPolygon ) { ndcPoints . forEach ( corner => { const intersection = MapViewUtils . rayCastWorldCoordinates ( { camera : this . camera , projection : this . projection } , corner [ 0 ] , corner [ 1 ] ) ; if ( intersection ) { this . validateAndAddToGeoPolygon ( intersection , geoPolygon ) ; } } ) ; } private addHorizonIntersection ( geoPolygon ) { const verticalHorizonPosition = this . getVerticalHorizonPositionInNDC ( ) ; if ( ! verticalHorizonPosition ) { return ; } this . addNDCRayIntersection ( [ [ - 1 , verticalHorizonPosition ] , [ 1 , verticalHorizonPosition ] ] , geoPolygon ) ; } private addCanvasCornerIntersection ( geoPolygon ) { this . addNDCRayIntersection ( [ [ - 1 , - 1 ] , [ 1 , - 1 ] , [ 1 , 1 ] , [ - 1 , 1 ] ] , geoPolygon ) ; } private validateAndAddToGeoPolygon ( point , geoPolygon ) { if ( this . isInVisibleMap ( point ) ) { geoPolygon . push ( this . projection . unprojectPoint ( point ) ) ; } } private isInVisibleMap ( point ) { if ( point . y < 0 || point . y > EarthConstants . EQUATORIAL_CIRCUMFERENCE ) { return false ; } if ( ! this . m_options . tileWrappingEnabled && ( point . x < 0 || point . x > EarthConstants . EQUATORIAL_CIRCUMFERENCE ) ) { return false ; } return true ; } private addPointInFrustum ( point , frustum , geoPolygon ) { if ( frustum . containsPoint ( point ) ) { const geoPoint = this . projection . unprojectPoint ( point ) ; geoPoint . altitude = 0 ; geoPolygon . push ( geoPoint ) ; } } private addFrustumIntersection ( edge , frustum , geoPolygon ) { frustum . planes . forEach ( plane => { let intersection = null ; const target = new Vector3 ( ) ; if ( edge instanceof Ray && edge . intersectsPlane ( plane ) ) { intersection = edge . intersectPlane ( plane , target ) ; } else if ( edge instanceof Line3 && plane . intersectsLine ( edge ) ) { intersection = plane . intersectLine ( edge , target ) ; } if ( intersection ) { if ( MapViewUtils . closeToFrustum ( intersection , this . camera ) ) { const geoIntersection = this . projection . unprojectPoint ( intersection ) ; geoIntersection . altitude = 0 ; geoPolygon . push ( geoIntersection ) ; } } } ) ; } private getVerticalHorizonPositionInNDC ( ) { const bottomMidFarPoint = new Vector3 ( - 1 , - 1 , 1 ) . unproject ( this . camera ) . add ( new Vector3 ( 1 , - 1 , 1 ) . unproject ( this . camera ) ) . multiplyScalar ( 0.5 ) ; const topMidFarPoint = new Vector3 ( - 1 , 1 , 1 ) . unproject ( this . camera ) . add ( new Vector3 ( 1 , 1 , 1 ) . unproject ( this . camera ) ) . multiplyScalar ( 0.5 ) ; const farPlaneVerticalCenterLine = new Line3 ( bottomMidFarPoint , topMidFarPoint ) ; const verticalHorizonPosition = new Vector3 ( ) ; if ( ! this . m_groundPlane . intersectLine ( farPlaneVerticalCenterLine , verticalHorizonPosition ) ) { return undefined ; } return verticalHorizonPosition . project ( this . camera ) . y ; } }