import { ViewRanges } from "@here/harp-datasource-protocol/lib/ViewRanges" ; import { EarthConstants , Projection , ProjectionType } from "@here/harp-geoutils" ; import { assert } from "@here/harp-utils" ; import * as THREE from "three" ; import { CameraUtils } from "./CameraUtils" ; import { ElevationProvider } from "./ElevationProvider" ; import { MapViewUtils } from "./Utils" ; const epsilon = 0.000001 ; namespace SphericalProj { const tmpVectors = [ new THREE . Vector3 ( ) , new THREE . Vector3 ( ) ] ; const raycaster = new THREE . Raycaster ( ) ; const sphere = new THREE . Sphere ( new THREE . Vector3 ( ) , EarthConstants . EQUATORIAL_RADIUS ) ; remarks see https param d param r function getHorizonDistance ( d , r ) { return d - r < epsilon ? 0 : Math . sqrt ( d * d - r * r ) ; } remarks see https param camera param d param minR param maxR export function getFarDistanceFromElevatedHorizon ( camera , d , minR , maxR ) { const t = getHorizonDistance ( d , minR ) ; const te = getHorizonDistance ( maxR , minR ) ; const normalToTanAngle = Math . asin ( minR / d ) ; const fwdToTanAngle = Math . abs ( normalToTanAngle - SphericalProj . getNormalToFwdAngle ( camera ) ) ; const far = Math . cos ( fwdToTanAngle ) * ( t + te ) ; return far ; } param camera param bottomFov param R returns export function getProjNearPlaneTanDistance ( camera , bottomFov , R ) { const fwdDir = camera . getWorldDirection ( tmpVectors [ 0 ] ) ; const camToTanVec = tmpVectors [ 1 ] . copy ( fwdDir ) . multiplyScalar ( - R ) . sub ( camera . position ) ; const near = camToTanVec . dot ( fwdDir ) ; const cosTanDirToFwdDir = near / camToTanVec . length ( ) ; return cosTanDirToFwdDir > Math . cos ( bottomFov ) ? near : undefined ; } param camera param ndcDir param R returns export function getProjSphereIntersectionDistance ( camera , ndcDir , R ) { raycaster . setFromCamera ( ndcDir , camera ) ; sphere . radius = R ; const intersection = raycaster . ray . intersectSphere ( sphere , tmpVectors [ 0 ] ) ; return intersection !== null ? intersection . sub ( camera . position ) . dot ( camera . getWorldDirection ( tmpVectors [ 1 ] ) ) : undefined ; } param camera returns export function getNormalToFwdAngle ( camera ) { const camToOriginDir = tmpVectors [ 0 ] . copy ( camera . position ) . negate ( ) . normalize ( ) ; const cosAngle = camToOriginDir . dot ( camera . getWorldDirection ( tmpVectors [ 1 ] ) ) ; return Math . acos ( THREE . MathUtils . clamp ( cosAngle , - 1.0 , 1.0 ) ) ; } } export interface ClipPlanesEvaluator { /**
     * Minimum elevation to be rendered, values beneath the sea level are negative.
     */ minElevation : number ; /**
     * Set maximum elevation to be rendered, values above sea level are positive.
     */ maxElevation : number ; remarks note param camera param projection param elevationProvider evaluateClipPlanes ( camera , projection , elevationProvider ? ) : ViewRanges ; } remarks export abstract class ElevationBasedClipPlanesEvaluator implements ClipPlanesEvaluator { private m_maxElevation : number ; private m_minElevation : number ; constructor ( maxElevation , minElevation ) { assert ( maxElevation >= minElevation ) ; this . m_minElevation = minElevation ; this . m_maxElevation = maxElevation ; } abstract evaluateClipPlanes ( camera , projection , elevationProvider ? ) ; remarks param elevation note note see minElevation set maxElevation ( elevation ) { this . m_maxElevation = elevation ; this . m_minElevation = Math . min ( elevation , this . m_minElevation ) ; } /**
     * Get maximum elevation to be covered by camera frustum.
     */ get maxElevation ( ) : number { return this . m_maxElevation ; } remarks param elevation note see https set minElevation ( elevation ) { this . m_minElevation = elevation ; this . m_maxElevation = Math . max ( elevation , this . m_maxElevation ) ; } /**
     * Get minimum elevation to be covered by camera frustum.
     */ get minElevation ( ) : number { return this . m_minElevation ; } } deprecated Default evaluator  TiltViewClipPlanesEvaluator supports top-down views.
 *
 *  remarks note export class TopViewClipPlanesEvaluator extends ElevationBasedClipPlanesEvaluator { /**
     * Helper for reducing number of objects created at runtime.
     */ protected m_tmpVectors : THREE . Vector3 [ ] = [ new THREE . Vector3 ( ) , new THREE . Vector3 ( ) , new THREE . Vector3 ( ) ] ; /**
     * Helper object for reducing performance impact.
     */ protected m_tmpQuaternion : THREE . Quaternion = new THREE . Quaternion ( ) ; private readonly m_minimumViewRange : ViewRanges ; remarks param maxElevation param minElevation param nearMin param nearFarMarginRatio param farMaxRatio note note You may treat [[minElevation]] and [[maxElevation]] parameters as the maximum and
     * minimum renderable elevation respectively along the surface normal, when camera is
     * constantly looking downwards (top-down view). If you need  ClipPlanesEvaluator for
     * cameras that support tilt or yaw please use  TiltViewClipPlanesEvaluator .
     *  note constructor ( maxElevation = EarthConstants . MAX_BUILDING_HEIGHT , minElevation = 0 , readonly nearMin = 1.0 , readonly nearFarMarginRatio = 0.05 , readonly farMaxRatio = 6.0 ) { super ( maxElevation , minElevation ) ; assert ( nearMin > 0 ) ; assert ( nearFarMarginRatio >= 0 ) ; assert ( farMaxRatio > 1.0 ) ; const nearFarMargin = nearFarMarginRatio * nearMin ; this . m_minimumViewRange = { near : nearMin , far : nearMin + nearFarMargin , minimum : this . nearMin , maximum : Math . max ( nearMin * farMaxRatio , nearMin + nearFarMargin ) } ; } @override  evaluateClipPlanes ( camera , projection , elevationProvider ? ) { assert ( camera instanceof THREE . PerspectiveCamera , "Unsupported camera type." ) ; const persCamera = camera as THREE . PerspectiveCamera ; if ( projection . type === ProjectionType . Spherical ) { return this . evaluateDistanceSphericalProj ( persCamera , projection , elevationProvider ) ; } else if ( projection . type === ProjectionType . Planar ) { return this . evaluateDistancePlanarProj ( persCamera , projection , elevationProvider ) ; } assert ( false , "Unsupported projection type" ) ; return { ... this . minimumViewRange } ; } note protected get minimumViewRange ( ) : ViewRanges { return this . m_minimumViewRange ; } protected evaluateDistancePlanarProj ( camera , projection , elevationProvider ? ) { assert ( projection . type !== ProjectionType . Spherical ) ; let nearPlane = this . nearMin ; let farPlane = this . nearMin * this . farMaxRatio ; const groundDistance = projection . groundDistance ( camera . position ) ; const farMax = groundDistance * this . farMaxRatio ; nearPlane = groundDistance - this . maxElevation ; farPlane = groundDistance - this . minElevation ; nearPlane = Math . max ( nearPlane , this . nearMin ) ; farPlane = Math . min ( farPlane , farMax ) ; const nearFarMargin = ( this . nearFarMarginRatio * ( nearPlane + farPlane ) ) / 2 ; nearPlane = Math . max ( nearPlane - nearFarMargin / 2 , this . nearMin ) ; farPlane = Math . max ( farPlane + nearFarMargin / 2 , nearPlane + nearFarMargin ) ; const viewRanges = { near : nearPlane , far : farPlane , minimum : this . nearMin , maximum : Math . max ( farMax , farPlane ) } ; return viewRanges ; } protected evaluateDistanceSphericalProj ( camera , projection , elevationProvider ? ) { assert ( projection . type === ProjectionType . Spherical ) ; const cameraAltitude = projection . groundDistance ( camera . position ) ; let nearPlane = cameraAltitude - this . maxElevation ; const r = EarthConstants . EQUATORIAL_RADIUS ; const d = Math . max ( epsilon , camera . position . length ( ) ) ; const alpha = Math . asin ( r / d ) ; let halfFovAngle = THREE . MathUtils . degToRad ( camera . fov / 2 ) ; if ( camera . aspect > 1 ) { halfFovAngle = MapViewUtils . calculateHorizontalFovByVerticalFov ( halfFovAngle * 2 , camera . aspect ) ; } const maxR = r + this . maxElevation ; const farTangent = SphericalProj . getFarDistanceFromElevatedHorizon ( camera , d , r , maxR ) ; let farPlane = halfFovAngle > alpha ? farTangent : this . getFovBasedFarPlane ( camera , d , r , 2 * halfFovAngle , projection ) ; const farMin = cameraAltitude - this . minElevation ; const farMax = cameraAltitude * this . farMaxRatio ; nearPlane = Math . max ( nearPlane , this . nearMin ) ; farPlane = Math . max ( farPlane , farMin ) ; const nearFarMargin = ( this . nearFarMarginRatio * ( nearPlane + farPlane ) ) / 2 ; nearPlane = Math . max ( nearPlane - nearFarMargin / 2 , this . nearMin ) ; farPlane = Math . max ( farPlane + nearFarMargin / 2 , nearPlane + nearFarMargin ) ; const viewRanges = { near : nearPlane , far : farPlane , minimum : this . nearMin , maximum : farMax } ; return viewRanges ; } protected getFovBasedFarPlane ( camera , d , r , fovAngle , projection ) { const dVec = camera . position ; camera . matrixWorld . extractBasis ( this . m_tmpVectors [ 0 ] , this . m_tmpVectors [ 1 ] , this . m_tmpVectors [ 2 ] ) ; this . m_tmpQuaternion . setFromAxisAngle ( this . m_tmpVectors [ 0 ] , fovAngle / 2 ) ; const cameraFwdVec = this . m_tmpVectors [ 2 ] ; const tVec = this . m_tmpVectors [ 1 ] . copy ( cameraFwdVec ) . applyQuaternion ( this . m_tmpQuaternion ) ; const cta = dVec . dot ( tVec ) ; if ( cta < 0 ) { const groundDistance = projection . groundDistance ( camera . position ) ; return groundDistance * this . farMaxRatio ; } const a2 = dVec . dot ( dVec ) - cta * cta ; const r2 = r * r ; assert ( a2 <= r2 , "Please use this evaluator only for top view camera poses." ) ; const tota = Math . sqrt ( r2 - a2 ) ; return cta - tota ; } } /**
 * Evaluates camera clipping planes taking into account ground distance and camera tilt (pitch)
 * angle (angle between look-at vector and ground surface normal).
 */ export class TiltViewClipPlanesEvaluator extends TopViewClipPlanesEvaluator { private readonly m_tmpV2 = new THREE . Vector2 ( ) ; @override  protected evaluateDistancePlanarProj ( camera , projection , elevationProvider ? ) { assert ( projection . type !== ProjectionType . Spherical ) ; const viewRanges = { ... this . minimumViewRange } ; const halfPiLimit = Math . PI / 2 - epsilon ; const z = projection . groundDistance ( camera . position ) ; const cameraTilt = MapViewUtils . extractCameraTilt ( camera , projection ) ; const topFov = CameraUtils . getTopFov ( camera ) ; const bottomFov = CameraUtils . getBottomFov ( camera ) ; const topAngle = THREE . MathUtils . clamp ( cameraTilt + topFov , - halfPiLimit , halfPiLimit ) ; const bottomAngle = THREE . MathUtils . clamp ( cameraTilt - bottomFov , - halfPiLimit , halfPiLimit ) ; const topDist = Math . max ( 0 , ( z - this . minElevation ) / Math . cos ( topAngle ) ) ; const bottomDist = Math . max ( 0 , ( z - this . maxElevation ) / Math . cos ( bottomAngle ) ) ; viewRanges . near = bottomDist * Math . cos ( bottomFov ) ; viewRanges . far = topDist * Math . cos ( topFov ) ; return this . applyViewRangeConstraints ( viewRanges , camera , projection , elevationProvider ) ; } @override  protected evaluateDistanceSphericalProj ( camera , projection , elevationProvider ? ) { assert ( projection . type === ProjectionType . Spherical ) ; const viewRanges = { ... this . minimumViewRange } ; viewRanges . near = this . computeNearDistSphericalProj ( camera , projection ) ; viewRanges . far = this . computeFarDistSphericalProj ( camera , projection ) ; return this . applyViewRangeConstraints ( viewRanges , camera , projection , elevationProvider ) ; } private computeNearDistSphericalProj ( camera , projection ) { assert ( projection . type === ProjectionType . Spherical ) ; const defaultNear = projection . groundDistance ( camera . position ) - this . maxElevation ; const cameraBelowMaxElevation = defaultNear <= 0 ; if ( cameraBelowMaxElevation ) { return 0 ; } const maxR = EarthConstants . EQUATORIAL_RADIUS + this . maxElevation ; const bottomFov = CameraUtils . getBottomFov ( camera ) ; const nearPlaneTanDist = SphericalProj . getProjNearPlaneTanDistance ( camera , bottomFov , maxR ) ; if ( nearPlaneTanDist !== undefined ) { return nearPlaneTanDist ; } CameraUtils . getPrincipalPoint ( camera , this . m_tmpV2 ) ; const checkTopIntersection = this . m_tmpV2 . y > 0 ; const bottomDist = SphericalProj . getProjSphereIntersectionDistance ( camera , this . m_tmpV2 . setComponent ( 1 , - 1 ) , maxR ) ; const topDist = checkTopIntersection ? SphericalProj . getProjSphereIntersectionDistance ( camera , this . m_tmpV2 . setComponent ( 1 , 1 ) , maxR ) : Infinity ; const near = Math . min ( bottomDist ?? Infinity , topDist ?? Infinity ) ; assert ( near !== Infinity , "No reference point for near distance found" ) ; return near ?? defaultNear ; } private computeFarDistSphericalProj ( camera , projection ) { assert ( projection . type === ProjectionType . Spherical ) ; const r = EarthConstants . EQUATORIAL_RADIUS ; const minR = r + this . minElevation ; const maxR = r + this . maxElevation ; const d = camera . position . length ( ) ; CameraUtils . getPrincipalPoint ( camera , this . m_tmpV2 ) ; const isRightIntersectionFarther = this . m_tmpV2 . x <= 0.0 ; const ndcX = isRightIntersectionFarther ? 1 : - 1 ; const checkBottomIntersection = this . m_tmpV2 . y > 0 ; const topDist = SphericalProj . getProjSphereIntersectionDistance ( camera , this . m_tmpV2 . set ( ndcX , 1 ) , minR ) ; const bottomDist = checkBottomIntersection ? SphericalProj . getProjSphereIntersectionDistance ( camera , this . m_tmpV2 . set ( ndcX , - 1 ) , minR ) : 0 ; const largestDist = Math . max ( topDist ?? Infinity , bottomDist ?? Infinity ) ; if ( largestDist !== Infinity ) { return largestDist ; } return SphericalProj . getFarDistanceFromElevatedHorizon ( camera , d , r , maxR ) ; } private applyViewRangeConstraints ( viewRanges , camera , projection , elevationProvider ? ) { const { distance } = MapViewUtils . getTargetAndDistance ( projection , camera , elevationProvider ) ; const farMin = projection . groundDistance ( camera . position ) - this . minElevation ; const farMax = distance * this . farMaxRatio ; viewRanges . near = Math . max ( viewRanges . near , this . nearMin ) ; viewRanges . far = THREE . MathUtils . clamp ( viewRanges . far , farMin , farMax ) ; const nearFarMargin = ( this . nearFarMarginRatio * ( viewRanges . near + viewRanges . far ) ) / 2 ; viewRanges . near = Math . max ( viewRanges . near - nearFarMargin / 2 , this . nearMin ) ; viewRanges . far = Math . max ( viewRanges . far + nearFarMargin / 2 , viewRanges . near + nearFarMargin ) ; viewRanges . minimum = this . nearMin ; viewRanges . maximum = farMax ; return viewRanges ; } } /**
 * Creates default  ClipPlanesEvaluator .
 *  internal export const createDefaultClipPlanesEvaluator = ( ) => new TiltViewClipPlanesEvaluator ( ) ;