import { BufferAttribute , ColorUtils , Env , Expr , getPropertyValue , isExtrudedLineTechnique , isExtrudedPolygonTechnique , isShaderTechnique , isTextureBuffer , parseStringEncodedColor , ShaderTechnique , supportsTextures , Technique , TEXTURE_PROPERTY_KEYS , TextureBuffer , TextureProperties , TRANSPARENCY_PROPERTY_KEYS , Value } from "@here/harp-datasource-protocol" ; import { getTechniqueAutomaticAttrs , getTechniqueDescriptor } from "@here/harp-datasource-protocol/lib/TechniqueDescriptors" ; import { CirclePointsMaterial , disableBlending , enableBlending , HighPrecisionLineMaterial , MapMeshBasicMaterial , MapMeshStandardMaterial , RawShaderMaterial , SolidLineMaterial } from "@here/harp-materials" ; import { assert , LoggerManager , pick } from "@here/harp-utils" ; import * as THREE from "three" ; import { DisplacedMesh } from "./geometry/DisplacedMesh" ; import { SolidLineMesh } from "./geometry/SolidLineMesh" ; import { MapAdapterUpdateEnv , MapMaterialAdapter , StyledProperties } from "./MapMaterialAdapter" ; import { Circles , Squares } from "./MapViewPoints" ; import { toPixelFormat , toTextureDataType , toTextureFilter , toWrappingMode } from "./ThemeHelpers" ; import { Tile } from "./Tile" ; const logger = LoggerManager . instance . create ( "DecodedTileHelpers" ) ; /**
 * The structure of the options to pass into [[createMaterial]].
 */ export interface MaterialOptions { /**
     * The shader [[Technique]] to choose.
     */ technique : Technique ; /**
     * Environment used to evaluate dynamic technique attributes.
     *
     * Usually  MapView . env .
      env : Env ; see skipExtraProps ? : string [ ] ; /**
     * `RawShaderMaterial` instances need to know about the fog at instantiation in order to avoid
     * recompiling them manually later (ThreeJS does not update fog for `RawShaderMaterial`s).
     */ fog ? : boolean ; /**
     * Whether shadows are enabled or not, this is required because we change the material used.
     */ shadowsEnabled ? : boolean ; } function createTextureFromURL ( url , onLoad , onError , isObjectURL ) { const texture = new THREE . TextureLoader ( ) . load ( url , onLoad , undefined , onError ) ; if ( isObjectURL ) { texture . addEventListener ( "dispose" , ( ) => { URL . revokeObjectURL ( url ) ; } ) ; } } function createTextureFromRawImage ( textureBuffer , onLoad , onError ) { const properties = textureBuffer . dataTextureProperties ; if ( properties ) { const textureDataType = properties . type ? toTextureDataType ( properties . type ) : undefined ; const buffer = getTextureBuffer ( textureBuffer . buffer , textureDataType ) ; const texture = new THREE . DataTexture ( buffer , properties . width , properties . height , properties . format ? toPixelFormat ( properties . format ) : undefined , textureDataType ) ; onLoad ( texture ) ; } else { onError ( "no data texture properties provided." ) ; } } function initTextureProperties ( texture , properties ? ) { if ( ! properties ) { return ; } if ( properties . wrapS !== undefined ) { texture . wrapS = toWrappingMode ( properties . wrapS ) ; } if ( properties . wrapT !== undefined ) { texture . wrapT = toWrappingMode ( properties . wrapT ) ; } if ( properties . magFilter !== undefined ) { texture . magFilter = toTextureFilter ( properties . magFilter ) ; } if ( properties . minFilter !== undefined ) { texture . minFilter = toTextureFilter ( properties . minFilter ) ; } if ( properties . flipY !== undefined ) { texture . flipY = properties . flipY ; } if ( properties . repeatU !== undefined ) { texture . repeat . x = properties . repeatU ; } if ( properties . repeatV !== undefined ) { texture . repeat . y = properties . repeatV ; } } function createTexture ( material , texturePropertyName , options ) { const technique = options . technique ; let textureProperty = ( technique as any ) [ texturePropertyName ] ; if ( textureProperty === undefined ) { return undefined ; } const texturePromise = new Promise < THREE . Texture > ( ( resolve , reject ) => { const onLoad = ( texture ) => { const properties = ( technique as any ) [ texturePropertyName + "Properties" ] ; initTextureProperties ( texture , properties ) ; ( material as any ) [ texturePropertyName ] = texture ; material . needsUpdate = true ; resolve ( texture ) ; } ; const onError = ( error ) => { logger . error ( "#createMaterial: Failed to load texture: " , error ) ; reject ( error ) ; } ; if ( Expr . isExpr ( textureProperty ) ) { textureProperty = getPropertyValue ( textureProperty , options . env ) ; if ( ! textureProperty ) { onLoad ( new THREE . Texture ( ) ) ; return ; } } if ( typeof textureProperty === "string" ) { createTextureFromURL ( textureProperty , onLoad , onError , false ) ; } else if ( isTextureBuffer ( textureProperty ) ) { if ( textureProperty . type === "image/raw" ) { createTextureFromRawImage ( textureProperty , onLoad , onError ) ; } else { const textureBlob = new Blob ( [ textureProperty . buffer ] , { type : textureProperty . type } ) ; createTextureFromURL ( URL . createObjectURL ( textureBlob ) , onLoad , onError , true ) ; } } else if ( typeof textureProperty === "object" && ( textureProperty . nodeName === "IMG" || textureProperty . nodeName === "CANVAS" ) ) { onLoad ( new THREE . CanvasTexture ( textureProperty ) ) ; } } ) ; return texturePromise ; } param rendererCapabilities param options param onTextureCreated returns internal export function createMaterial ( rendererCapabilities , options , onTextureCreated ? ) { const technique = options . technique ; const Constructor = getMaterialConstructor ( technique , options . shadowsEnabled === true ) ; const settings = { } ; if ( Constructor === undefined ) { return undefined ; } if ( Constructor . prototype instanceof RawShaderMaterial ) { settings . rendererCapabilities = rendererCapabilities ; if ( Constructor !== HighPrecisionLineMaterial ) { settings . fog = options . fog ; } } if ( options . shadowsEnabled === true && technique . name === "fill" ) { settings . removeDiffuseLight = true ; } const material = new Constructor ( settings ) ; if ( technique . id !== undefined ) { material . name = technique . id ; } if ( isExtrudedPolygonTechnique ( technique ) ) { ( material as MapMeshStandardMaterial ) . flatShading = true ; } material . depthTest = isExtrudedPolygonTechnique ( technique ) && technique . depthTest !== false ; if ( supportsTextures ( technique ) ) { TEXTURE_PROPERTY_KEYS . forEach ( ( texturePropertyName ) => { const texturePromise = createTexture ( material , texturePropertyName , options ) ; if ( texturePromise ) { onTextureCreated ?. ( texturePromise ) ; } } ) ; } if ( isShaderTechnique ( technique ) ) { applyShaderTechniqueToMaterial ( technique , material ) ; } else { MapMaterialAdapter . create ( material , getMainMaterialStyledProps ( technique ) ) ; } return material ; } /**
 * Returns a [[THREE.BufferAttribute]] created from a provided
 *  {@link @here/harp-datasource-protocol#BufferAttribute} object.
 *
 *  param attribute internal export function getBufferAttribute ( attribute ) { switch ( attribute . type ) { case "float" : return new THREE . BufferAttribute ( new Float32Array ( attribute . buffer ) , attribute . itemCount ) ; case "uint8" : return new THREE . BufferAttribute ( new Uint8Array ( attribute . buffer ) , attribute . itemCount , attribute . normalized ) ; case "uint16" : return new THREE . BufferAttribute ( new Uint16Array ( attribute . buffer ) , attribute . itemCount , attribute . normalized ) ; case "uint32" : return new THREE . BufferAttribute ( new Uint32Array ( attribute . buffer ) , attribute . itemCount , attribute . normalized ) ; case "int8" : return new THREE . BufferAttribute ( new Int8Array ( attribute . buffer ) , attribute . itemCount , attribute . normalized ) ; case "int16" : return new THREE . BufferAttribute ( new Int16Array ( attribute . buffer ) , attribute . itemCount , attribute . normalized ) ; case "int32" : return new THREE . BufferAttribute ( new Int32Array ( attribute . buffer ) , attribute . itemCount , attribute . normalized ) ; default : throw new Error ( `unsupported buffer of type ${ attribute . type }` ) ; } } param technique returns internal export function usesObject3D ( technique ) { const name = technique . name ; return ( name !== undefined && name !== "text" && name !== "labeled-icon" && name !== "line-marker" && name !== "label-rejection-line" ) ; } param technique param geometry param material param tile param elevationEnabled internal export function buildObject ( technique , geometry , material , tile , elevationEnabled ) { assert ( technique . name !== undefined ) ; switch ( technique . name ) { case "extruded-line" : case "standard" : case "extruded-polygon" : case "fill" : return elevationEnabled ? new DisplacedMesh ( geometry , material , ( ) => ( { min : tile . elevationRange . minElevation , max : tile . elevationRange . maxElevation } ) ) : new THREE . Mesh ( geometry , material ) ; case "terrain" : return new THREE . Mesh ( geometry , material ) ; case "dashed-line" : case "solid-line" : return elevationEnabled ? new DisplacedMesh ( geometry , material , ( ) => ( { min : tile . elevationRange . minElevation , max : tile . elevationRange . maxElevation } ) , SolidLineMesh . raycast ) : new SolidLineMesh ( geometry , material ) ; case "circles" : return new Circles ( geometry , material ) ; case "squares" : return new Squares ( geometry , material ) ; case "line" : return new THREE . LineSegments ( geometry , material ) ; case "segments" : return new THREE . LineSegments ( geometry , material ) ; case "shader" : { assert ( isShaderTechnique ( technique ) , "Invalid technique" ) ; switch ( technique . primitive ) { case "line" : return new THREE . Line ( geometry , material ) ; case "segments" : return new THREE . LineSegments ( geometry , material ) ; case "point" : return new THREE . Points ( geometry , material ) ; case "mesh" : return new THREE . Mesh ( geometry , material ) ; } } } assert ( false , "Invalid technique" ) ; return new THREE . Object3D ( ) ; } internal export const BASE_TECHNIQUE_NON_MATERIAL_PROPS = [ "name" , "id" , "renderOrder" , "transient" ] ; internal export type MaterialConstructor = new ( params ) => THREE . Material ; param technique param shadowsEnabled internal export function getMaterialConstructor ( technique , shadowsEnabled ) { if ( technique . name === undefined ) { return undefined ; } switch ( technique . name ) { case "extruded-line" : if ( ! isExtrudedLineTechnique ( technique ) ) { throw new Error ( "Invalid extruded-line technique" ) ; } return technique . shading === "standard" ? MapMeshStandardMaterial : MapMeshBasicMaterial ; case "standard" : case "terrain" : case "extruded-polygon" : return MapMeshStandardMaterial ; case "dashed-line" : case "solid-line" : return SolidLineMaterial ; case "fill" : return shadowsEnabled ? MapMeshStandardMaterial : MapMeshBasicMaterial ; case "squares" : return THREE . PointsMaterial ; case "circles" : return CirclePointsMaterial ; case "line" : case "segments" : return THREE . LineBasicMaterial ; case "shader" : return THREE . ShaderMaterial ; case "text" : case "labeled-icon" : case "line-marker" : case "label-rejection-line" : return undefined ; } } /**
 * Styled properties of main material (created by [[createMaterial]]) managed by
 * [[MapObjectAdapter]].
 */ function getMainMaterialStyledProps ( technique ) { const automaticAttributes = getTechniqueAutomaticAttrs ( technique ) ; switch ( technique . name ) { case "dashed-line" : case "solid-line" : { const baseProps = pick ( technique , automaticAttributes ) ; baseProps . lineWidth = buildMetricValueEvaluator ( technique . lineWidth ?? 0 , technique . metricUnit ) ; baseProps . outlineWidth = buildMetricValueEvaluator ( technique . outlineWidth , technique . metricUnit ) ; baseProps . dashSize = buildMetricValueEvaluator ( technique . dashSize , technique . metricUnit ) ; baseProps . gapSize = buildMetricValueEvaluator ( technique . gapSize , technique . metricUnit ) ; baseProps . offset = buildMetricValueEvaluator ( technique . offset , technique . metricUnit ) ; return baseProps ; } case "fill" : return pick ( technique , automaticAttributes ) ; case "standard" : case "terrain" : case "extruded-polygon" : { const baseProps = pick ( technique , automaticAttributes ) ; if ( technique . vertexColors !== true ) { baseProps . color = technique . color ; } return baseProps ; } case "circles" : case "squares" : return pick ( technique , automaticAttributes ) ; case "extruded-line" : return pick ( technique , [ "color" , "wireframe" , "transparent" , "opacity" , "polygonOffset" , "polygonOffsetFactor" , "polygonOffsetUnits" , ... automaticAttributes ] ) ; case "line" : case "segments" : return pick ( technique , automaticAttributes ) ; default : return { } ; } } /**
 * Convert metric style property to expression that accounts  MapView . pixelToWorld if
 * `metricUnit === 'Pixel'`.
 *  internal export function buildMetricValueEvaluator ( value , metricUnit ) { if ( value === undefined || value === null ) { return value ; } if ( typeof value === "string" ) { if ( value . endsWith ( "px" ) ) { metricUnit = "Pixel" ; value = Number . parseFloat ( value ) ; } else if ( value . endsWith ( "m" ) ) { value = Number . parseFloat ( value ) ; } } if ( metricUnit === "Pixel" ) { return ( context ) => { const pixelToWorld = ( context . env . lookup ( "$pixelToMeters" ) as number ) ?? 1 ; const evaluated = getPropertyValue ( value , context . env ) ; return pixelToWorld * evaluated ; } ; } else { return value ; } } remarks see ColorUtils param technique param env -  {@link @here/harp-datasource-protocol#Env} instance
 *              used to evaluate  {@link @here/harp-datasource-protocol#Expr} *              based properties of `Technique`
 *  returns internal export function evaluateBaseColorProperty ( technique , env ) { const baseColorProp = getBaseColorProp ( technique ) ; if ( baseColorProp !== undefined ) { return evaluateColorProperty ( baseColorProp , env ) ; } return undefined ; } param technique param material internal function applyShaderTechniqueToMaterial ( technique , material ) { if ( technique . transparent ) { enableBlending ( material ) ; } else { disableBlending ( material ) ; } const params = technique . params as { [ key ] : any ; } ; const baseColorPropName = getBaseColorPropName ( technique ) ; const hasBaseColor = baseColorPropName && baseColorPropName in technique . params ; const props = Object . getOwnPropertyNames ( params ) . filter ( propertyName => { if ( baseColorPropName === propertyName || ( hasBaseColor && TRANSPARENCY_PROPERTY_KEYS . includes ( propertyName ) ) ) { return false ; } const prop = propertyName as keyof typeof params ; if ( prop === "name" ) { return false ; } return true ; } ) ; props . forEach ( propertyName => { applyTechniquePropertyToMaterial ( material , propertyName , params [ propertyName ] ) ; } ) ; if ( hasBaseColor ) { const propColor = baseColorPropName as keyof THREE . Material ; applyBaseColorToMaterial ( material , material [ propColor ] , technique , params [ propColor ] ) ; } } note param material param propertyName param techniqueAttrValue param env -  {@link @here/harp-datasource-protocol#Env} instance used
 *              to evaluate  {@link @here/harp-datasource-protocol#Expr} *              based properties of [[Technique]]
  function applyTechniquePropertyToMaterial ( material , propertyName , techniqueAttrValue , env ? ) { const m = material as any ; if ( m [ propertyName ] instanceof THREE . Color ) { applySecondaryColorToMaterial ( material [ propertyName as keyof THREE . Material ] , techniqueAttrValue , env ) ; } else { const value = evaluateProperty ( techniqueAttrValue , env ) ; if ( value !== null ) { m [ propertyName ] = value ; } } } remarks note param technique param material param prop param value param env -  {@link @here/harp-datasource-protocol#Env} instance used
 *              to evaluate  {@link @here/harp-datasource-protocol#Expr} *              based properties of `Technique`.
 *
 *  internal export function applySecondaryColorToMaterial ( materialColor , techniqueColor , env ? ) { let value = evaluateColorProperty ( techniqueColor , env ) ; if ( value === undefined ) { return ; } if ( ColorUtils . hasAlphaInHex ( value ) ) { logger . warn ( "Used RGBA value for technique color without transparency support!" ) ; value = ColorUtils . removeAlphaFromHex ( value ) ; } materialColor . setHex ( value ) ; } remarks note param technique param material param prop param value param env -  {@link @here/harp-datasource-protocol#Env} instance used to evaluate
 *               {@link @here/harp-datasource-protocol#Expr} based properties of [[Technique]]
 *
 *  internal export function applyBaseColorToMaterial ( material , materialColor , technique , techniqueColor , env ? ) { const colorValue = evaluateColorProperty ( techniqueColor , env ) ; if ( colorValue === undefined ) { return ; } const { r , g , b , a } = ColorUtils . getRgbaFromHex ( colorValue ) ; const tech = technique as any ; let opacity = a ; if ( tech . opacity !== undefined ) { opacity *= evaluateProperty ( tech . opacity , env ) ; } opacity = THREE . MathUtils . clamp ( opacity , 0 , 1 ) ; if ( material instanceof RawShaderMaterial ) { material . setOpacity ( opacity ) ; } else { material . opacity = opacity ; } materialColor . setRGB ( r , g , b ) ; const opaque = opacity >= 1.0 ; if ( ! opaque ) { enableBlending ( material ) ; } else { disableBlending ( material ) ; } } note param value param env -  {@link @here/harp-datasource-protocol#Env} instance used to evaluate
 *               {@link @here/harp-datasource-protocol#Expr} based properties of [[Technique]]
  function evaluateProperty ( value , env ? ) { if ( env !== undefined && Expr . isExpr ( value ) ) { value = getPropertyValue ( value , env ) ; } return value ; } remarks param env note param value param env -  {@link @here/harp-datasource-protocol#Env} instance used to evaluate
 *               {@link @here/harp-datasource-protocol#Expr} based properties of [[Technique]]
 *  internal export function evaluateColorProperty ( value , env ? ) { value = evaluateProperty ( value , env ) ; if ( value === undefined || value === null ) { return undefined ; } if ( typeof value === "number" ) { return value ; } if ( typeof value === "string" ) { const parsed = parseStringEncodedColor ( value ) ; if ( parsed !== undefined ) { return parsed ; } } logger . error ( `Unsupported color format: '${ value }'` ) ; return undefined ; } param technique returns function getBaseColorProp ( technique ) { const baseColorPropName = getBaseColorPropName ( technique ) ; if ( baseColorPropName !== undefined ) { if ( ! isShaderTechnique ( technique ) ) { const propColor = baseColorPropName as keyof typeof technique ; return technique [ propColor ] ; } else { const params = technique . params as { [ key ] : any ; } ; const propColor = baseColorPropName as keyof typeof params ; return params [ propColor ] ; } } return undefined ; } function getBaseColorPropName ( technique ) { return getTechniqueDescriptor ( technique ) ?. attrTransparencyColor ; } function getTextureBuffer ( buffer , textureDataType ) { if ( textureDataType === undefined ) { return new Uint8Array ( buffer ) ; } switch ( textureDataType ) { case THREE . UnsignedByteType : return new Uint8Array ( buffer ) ; case THREE . ByteType : return new Int8Array ( buffer ) ; case THREE . ShortType : return new Int16Array ( buffer ) ; case THREE . UnsignedShortType : return new Uint16Array ( buffer ) ; case THREE . IntType : return new Int32Array ( buffer ) ; case THREE . UnsignedIntType : return new Uint32Array ( buffer ) ; case THREE . FloatType : return new Float32Array ( buffer ) ; case THREE . HalfFloatType : return new Uint16Array ( buffer ) ; } throw new Error ( "Unsupported texture data type" ) ; }