import { Env , getPropertyValue , ImageTexture } from "@here/harp-datasource-protocol" ; import { IconMaterial } from "@here/harp-materials" ; import { MemoryUsage } from "@here/harp-text-canvas" ; import { assert , LoggerManager , Math2D } from "@here/harp-utils" ; import * as THREE from "three" ; import { ImageItem } from "../image/Image" ; import { MapViewImageCache } from "../image/MapViewImageCache" ; import { MipMapGenerator } from "../image/MipMapGenerator" ; import { ScreenCollisions } from "../ScreenCollisions" ; import { PoiInfo , TextElement } from "../text/TextElement" ; import { BoxBuffer } from "./BoxBuffer" ; import { PoiManager } from "./PoiManager" ; const logger = LoggerManager . instance . create ( "PoiRenderer" ) ; /**
 * Neutral color used as `vColor` attribute of [[IconMaterial]] if no `iconColor` color was
 * specified.
 */ const neutralColor = new THREE . Color ( 1 , 1 , 1 ) ; /**
 * Temporary color instance used by `addPoi` to pass color derived from `iconBrightness` property.
 */ const tmpIconColor = new THREE . Color ( ) ; export interface PoiLayer { id : number ; scene : THREE . Scene ; } internal Buffer for POIs sharing same material and render order, renderable in a single draw call
 * (WebGL limits apply, see  BoxBuffer ).
  export class PoiBuffer { private m_refCount : number = 0 ; param buffer param layer - The  TextCanvas layer used to render the POIs.
      constructor ( readonly buffer , readonly layer , private readonly m_onDispose ) { } returns increaseRefCount ( ) { ++ this . m_refCount ; return this ; } returns decreaseRefCount ( ) { assert ( this . m_refCount > 0 ) ; if ( -- this . m_refCount === 0 ) { this . dispose ( ) ; } return this ; } private dispose ( ) { this . layer . scene . remove ( this . buffer . mesh ) ; this . buffer . dispose ( ) ; this . m_onDispose ( ) ; } } internal The `PoiBatch` contains the geometry and the material for all POIs that share the same icon image
 * ( {@link @here/harp-datasource-protocol#ImageTexture} ).
 *
 * There is a `PoiBatch` for every icon in a texture atlas, since the size of the icon in the atlas
 * as well as the texture coordinates are specified in the `PoiBatch`.
  class PoiBatch { static readonly trilinear : boolean = true ; private readonly m_poiBuffers : Map < number , PoiBuffer > ; private readonly m_material : IconMaterial ; param m_rendererCapabilities - The  THREE . WebGLCapabilities used for material
     * creation.
     *  param imageItem param m_onDispose constructor ( private readonly m_rendererCapabilities , readonly imageItem , private readonly m_onDispose ) { const premultipliedAlpha = true ; const texture = new THREE . Texture ( this . imageItem . image as any , THREE . UVMapping , undefined , undefined , PoiBatch . trilinear ? THREE . LinearFilter : THREE . LinearFilter , PoiBatch . trilinear ? THREE . LinearMipMapLinearFilter : THREE . LinearFilter , THREE . RGBAFormat ) ; if ( PoiBatch . trilinear && this . imageItem . mipMaps ) { texture . mipmaps = this . imageItem . mipMaps ; texture . image = texture . mipmaps [ 0 ] ; } texture . flipY = false ; texture . premultiplyAlpha = premultipliedAlpha ; texture . needsUpdate = true ; this . m_material = new IconMaterial ( { rendererCapabilities : this . m_rendererCapabilities , map : texture } ) ; this . m_poiBuffers = new Map ( ) ; } /**
     * Gets the  PoiBuffer for a given layer, creating it if necessary.
     *  param layer - The  PoiLayer to be used.
     *  returns The  PoiBuffer .
      getBuffer ( layer ) { let poiBuffer = this . m_poiBuffers . get ( layer . id ) ; if ( poiBuffer ) { return poiBuffer . increaseRefCount ( ) ; } const boxBuffer = new BoxBuffer ( this . m_material , layer . id ) ; const mesh = boxBuffer . mesh ; mesh . frustumCulled = false ; layer . scene . add ( mesh ) ; poiBuffer = new PoiBuffer ( boxBuffer , layer , ( ) => { this . disposeBuffer ( layer . id ) ; } ) ; this . m_poiBuffers . set ( layer . id , poiBuffer ) ; return poiBuffer . increaseRefCount ( ) ; } /**
     * Clean the `PoiBatch`, remove all icon boxes. Called before starting a new frame.
     */ reset ( ) { for ( const poiBuffer of this . m_poiBuffers . values ( ) ) { poiBuffer . buffer . reset ( ) ; } } /**
     * Update the geometry with all the added boxes during the frame.
     */ update ( ) { for ( const poiBuffer of this . m_poiBuffers . values ( ) ) { poiBuffer . buffer . updateBufferGeometry ( ) ; } } param screenPosition param pickCallback param image pickBoxes ( screenPosition , pickCallback , image ? ) { for ( const poiBuffer of this . m_poiBuffers . values ( ) ) { poiBuffer . buffer . pickBoxes ( screenPosition , pickCallback , image ) ; } } param info updateMemoryUsage ( info ) { if ( this . imageItem . image !== undefined ) { const imageBytes = this . imageItem . image . width * this . imageItem . image . height * 4 ; info . heapSize += imageBytes ; info . gpuSize += imageBytes ; } for ( const poiBuffer of this . m_poiBuffers . values ( ) ) { poiBuffer . buffer . updateMemoryUsage ( info ) ; } } private dispose ( ) { this . m_poiBuffers . clear ( ) ; this . m_material . map . dispose ( ) ; this . m_material . dispose ( ) ; this . m_onDispose ( ) ; } private disposeBuffer ( renderOrder ) { assert ( this . m_poiBuffers . size > 0 ) ; this . m_poiBuffers . delete ( renderOrder ) ; if ( this . m_poiBuffers . size === 0 ) { this . dispose ( ) ; } } } internal export class PoiBatchRegistry { private readonly m_batchMap : Map < string , PoiBatch > = new Map ( ) ; param m_rendererCapabilities - The  THREE . WebGLCapabilities to be used.
      constructor ( private readonly m_rendererCapabilities ) { } param poiInfo param layer - The  PoiLayer to render to.
      registerPoi ( poiInfo , layer ) { const { imageItem , imageTexture } = poiInfo ; if ( ! imageItem ) { return undefined ; } assert ( poiInfo . imageTextureName !== undefined ) ; const batchKey = imageTexture ?. image ?? poiInfo . imageTextureName ! ; let batch = this . m_batchMap . get ( batchKey ) ; if ( batch === undefined ) { batch = new PoiBatch ( this . m_rendererCapabilities , imageItem , ( ) => { this . deleteBatch ( batchKey ) ; } ) ; this . m_batchMap . set ( batchKey , batch ) ; } return batch . getBuffer ( layer ) ; } param poiInfo param screenBox param viewDistance param opacity addPoi ( poiInfo , screenBox , viewDistance , opacity ) { if ( poiInfo . isValid === false || ! poiInfo . buffer ) { logger . warn ( "PoiBatchRegistry: trying to add poiInfo without buffer prepared: " , poiInfo . poiName ) ; return ; } assert ( poiInfo . uvBox !== undefined ) ; let color ; if ( poiInfo . iconBrightness !== undefined ) { color = tmpIconColor . setScalar ( poiInfo . iconBrightness ) ; if ( poiInfo . iconColor !== undefined ) { color = tmpIconColor . multiply ( poiInfo . iconColor ) ; } } else if ( poiInfo . iconColor !== undefined ) { color = poiInfo . iconColor ; } else { color = neutralColor ; } poiInfo . buffer . buffer . addBox ( screenBox , poiInfo . uvBox ! , color , opacity , viewDistance , poiInfo . textElement ) ; } /**
     * Reset all batches, removing all content from the [[PoiBatch]]es. Called at the
     * beginning of a frame before the POIs are placed.
     */ reset ( ) { for ( const batch of this . m_batchMap . values ( ) ) { batch . reset ( ) ; } } /**
     * Update the geometry of all [[PoiBatch]]es. Called before rendering.
     */ update ( ) { for ( const batch of this . m_batchMap . values ( ) ) { batch . update ( ) ; } } /**
     * Fill the picking results for the pixel with the given screen coordinate. If multiple
     *  PoiInfo s are found, the order of the results is unspecified.
     *
     *  param screenPosition param pickCallback pickTextElements ( screenPosition , pickCallback ) { for ( const batch of this . m_batchMap . values ( ) ) { batch . pickBoxes ( screenPosition , pickCallback , batch . imageItem . image ) ; } } param info updateMemoryUsage ( info ) { for ( const batch of this . m_batchMap . values ( ) ) { batch . updateMemoryUsage ( info ) ; } } private deleteBatch ( batchKey ) { this . m_batchMap . delete ( batchKey ) ; } } const missingTextureName = new Map ( ) ; const SEARCH_CACHE_ATTEMPTS = 5 ; function findImageItem ( poiInfo , imageCaches , imageTexture ? ) { assert ( poiInfo . imageTextureName !== undefined ) ; const imageTextureName = imageTexture ? imageTexture . image : poiInfo . imageTextureName ! ; for ( const imageCache of imageCaches ) { const imageItem = imageCache . findImageByName ( imageTextureName ) ; if ( imageItem ) { missingTextureName . delete ( imageTextureName ) ; return imageItem ; } } const missingTextureCount = missingTextureName . get ( imageTextureName ) ; missingTextureName . set ( imageTextureName , missingTextureCount ? missingTextureCount + 1 : 0 ) ; if ( missingTextureName . get ( imageTextureName ) ! === SEARCH_CACHE_ATTEMPTS ) { logger . error ( `PoiRenderer::findImageItem: No imageItem found with name:
            '${ imageTexture ?. image ?? imageTextureName }'
            after ${ SEARCH_CACHE_ATTEMPTS } attempts.` ) ; } return undefined ; } internal export class PoiRenderer { param poiInfo param screenPosition param scale param env param screenBox returns static computeIconScreenBox ( poiInfo , screenPosition , scale , env , screenBox = new Math2D . Box ( ) ) { assert ( poiInfo . buffer !== undefined ) ; const width = poiInfo . computedWidth ! * scale ; const height = poiInfo . computedHeight ! * scale ; const technique = poiInfo . technique ; const iconXOffset = getPropertyValue ( technique . iconXOffset , env ) ; const iconYOffset = getPropertyValue ( technique . iconYOffset , env ) ; const centerX = screenPosition . x + ( typeof iconXOffset === "number" ? iconXOffset : 0 ) * scale ; const centerY = screenPosition . y + ( typeof iconYOffset === "number" ? iconYOffset : 0 ) * scale ; screenBox . x = centerX - width / 2 ; screenBox . y = centerY - height / 2 ; screenBox . w = width ; screenBox . h = height ; return screenBox ; } private readonly m_poiBatchRegistry : PoiBatchRegistry ; private readonly m_tempScreenBox = new Math2D . Box ( ) ; private readonly m_layers : PoiLayer [ ] = [ ] ; /**
     * Create the `PoiRenderer` for the specified  MapView .
     *
     *  param m_renderer - The  THREE . WebGLRenderer to be rendered to.
     *  param m_poiManager - The  PoiManager to be used.
     *  param m_imageCaches - The  ImageCache s to look for loaded images.
      constructor ( private readonly m_renderer , private readonly m_poiManager , private readonly m_imageCaches ) { this . m_poiBatchRegistry = new PoiBatchRegistry ( this . renderer . capabilities ) ; } get renderer ( ) : THREE . WebGLRenderer { return this . m_renderer ; } /**
     * Prepare the POI for rendering, and determine which  PoiBuffer should be used. If a
     *  PoiBuffer is assigned, the POI is ready to be rendered.
     *
     *  param pointLabel param env - TODO! The current zoomLevel level of  MapView returns prepareRender ( pointLabel , env ) { const poiInfo = pointLabel . poiInfo ; if ( poiInfo === undefined ) { return false ; } if ( poiInfo . buffer === undefined ) { this . preparePoi ( pointLabel , env ) ; } return poiInfo . buffer !== undefined ; } /**
     * Reset all batches, removing all content from the [[PoiBatchRegistry]]. Called at the
     * beginning of a frame before the POIs are placed.
     */ reset ( ) { this . m_poiBatchRegistry . reset ( ) ; } param poiInfo param screenPosition param screenCollisions param viewDistance param scale param allocateScreenSpace param opacity returns addPoi ( poiInfo , screenPosition , screenCollisions , viewDistance , scale , allocateScreenSpace , opacity , env ) { assert ( poiInfo . buffer !== undefined ) ; PoiRenderer . computeIconScreenBox ( poiInfo , screenPosition , scale , env , this . m_tempScreenBox ) ; if ( allocateScreenSpace ) { screenCollisions . allocate ( this . m_tempScreenBox ) ; } if ( opacity > 0 ) { if ( ! poiInfo . buffer ) { this . preparePoi ( poiInfo . textElement , env ) ; } this . m_poiBatchRegistry . addPoi ( poiInfo , this . m_tempScreenBox , viewDistance , opacity ) ; } } /**
     * Update the geometry of all [[PoiBatch]]es. Called before rendering.
     */ update ( ) { this . m_poiBatchRegistry . update ( ) ; } internal param layerId addLayer ( layerId ) { let result = this . getLayer ( layerId ) ; if ( result === undefined ) { result = { id : layerId , scene : new THREE . Scene ( ) } ; this . m_layers . push ( result ) ; this . m_layers . sort ( ( a , b ) => { return a . id - b . id ; } ) ; } return result ; } param layerId returns Selected  PoiLayer private getLayer ( layerId ) { return this . m_layers . find ( layer => layer . id === layerId ) ; } internal Returns all  PoiLayer s of this  PoiRenderer get layers ( ) : PoiLayer [ ] { return this . m_layers ; } param camera param layer render ( camera , layer ) { this . m_renderer . render ( layer . scene , camera ) ; } /**
     * Fill the picking results for the pixel with the given screen coordinate. If multiple
     *  PoiInfo s are found, the order of the results is unspecified.
     *
     *  param screenPosition param pickCallback pickTextElements ( screenPosition , pickCallback ) { this . m_poiBatchRegistry . pickTextElements ( screenPosition , pickCallback ) ; } param info getMemoryUsage ( info ) { this . m_poiBatchRegistry . updateMemoryUsage ( info ) ; } /**
     * Register the POI at the [[PoiBatchRegistry]] which may require some setup, for example
     * loading of the actual image.
     */ private preparePoi ( pointLabel , env ) { const poiInfo = pointLabel . poiInfo ; if ( ! poiInfo || ! pointLabel . visible ) { return ; } if ( poiInfo . buffer !== undefined || poiInfo . isValid === false ) { return ; } if ( poiInfo . poiTableName !== undefined ) { if ( this . m_poiManager . updatePoiFromPoiTable ( pointLabel ) ) { if ( ! pointLabel . visible ) { return ; } } else { return ; } } const imageTextureName = poiInfo . imageTextureName ; if ( imageTextureName === undefined ) { poiInfo . isValid = false ; return ; } const imageTexture = this . m_poiManager . getImageTexture ( imageTextureName ) ; const imageItem = findImageItem ( poiInfo , this . m_imageCaches , imageTexture ) ; if ( ! imageItem ) { poiInfo . imageItem = null ; return ; } if ( imageItem . loaded ) { this . setupPoiInfo ( poiInfo , imageItem , env , imageTexture ) ; return ; } if ( imageItem . loading ) { return ; } imageItem . loadImage ( ) . then ( loadedImageItem => { if ( loadedImageItem ?. image ) { this . setupPoiInfo ( poiInfo , loadedImageItem , env , imageTexture ) ; } } ) . catch ( error => { logger . error ( `preparePoi: Failed to load imageItem: '${ imageItem . url }` , error ) ; poiInfo . isValid = false ; } ) ; } param poiInfo -  PoiInfo to initialize.
     *  param imageTexture - Shared  {@link @here/harp-datasource-protocol#ImageTexture} ,
     *                       defines used area in atlas.
     *  param imageItem - Shared  ImageItem , contains cached image for texture.
     *  param env - The current zoom level of  MapView private setupPoiInfo ( poiInfo , imageItem , env , imageTexture ? ) { assert ( poiInfo . uvBox === undefined ) ; if ( ! imageItem . image ) { logger . error ( "setupPoiInfo: No imageItem/imageData found" ) ; poiInfo . isValid = false ; return ; } const technique = poiInfo . technique ; const imageWidth = imageItem . image . width ; const imageHeight = imageItem . image . height ; const paddedSize = MipMapGenerator . getPaddedSize ( imageWidth , imageHeight ) ; const trilinearFiltering = PoiBatch . trilinear && imageItem . mipMaps ; const paddedImageWidth = trilinearFiltering ? paddedSize . width : imageWidth ; const paddedImageHeight = trilinearFiltering ? paddedSize . height : imageHeight ; const iconWidth = imageTexture ?. width !== undefined ? imageTexture . width : imageWidth ; const iconHeight = imageTexture ?. height !== undefined ? imageTexture . height : imageHeight ; const width = imageTexture ?. width !== undefined ? imageTexture . width : imageWidth ; const height = imageTexture ?. height !== undefined ? imageTexture . height : imageHeight ; const xOffset = imageTexture ?. xOffset !== undefined ? imageTexture . xOffset : 0 ; const yOffset = imageTexture ?. yOffset !== undefined ? imageTexture . yOffset : 0 ; const minS = xOffset / paddedImageWidth ; const maxS = ( xOffset + width ) / paddedImageWidth ; const minT = yOffset / paddedImageHeight ; const maxT = ( yOffset + height ) / paddedImageHeight ; let iconScaleH = technique . iconScale !== undefined ? technique . iconScale : 1 ; let iconScaleV = technique . iconScale !== undefined ? technique . iconScale : 1 ; const screenWidth = getPropertyValue ( technique . screenWidth , env ) ; if ( screenWidth !== undefined && screenWidth !== null ) { iconScaleV = iconScaleH = screenWidth / iconWidth ; } const screenHeight = getPropertyValue ( technique . screenHeight , env ) ; if ( screenHeight !== undefined && screenHeight !== null ) { iconScaleV = screenHeight / iconHeight ; if ( screenWidth !== undefined ) { iconScaleH = iconScaleV ; } } poiInfo . computedWidth = iconWidth * iconScaleH ; poiInfo . computedHeight = iconHeight * iconScaleV ; poiInfo . uvBox = { s0 : minS , t0 : maxT , s1 : maxS , t1 : minT } ; poiInfo . imageItem = imageItem ; poiInfo . imageTexture = imageTexture ; poiInfo . buffer = this . m_poiBatchRegistry . registerPoi ( poiInfo , this . addLayer ( poiInfo . renderOrder ! ) ) ; poiInfo . isValid = true ; } }