UNPKG

@allmaps/render

Version:

Render functions for WebGL and image buffers

874 lines (873 loc) 29.3 kB
import { throttle } from "lodash-es"; import { mergeOptions, pointsAndPointsToLines, lineStringToLines, hexToFractionalOpaqueRgba, subSetArray } from "@allmaps/stdlib"; import { red, yellow, green, darkblue, black, pink, blue, gray, white } from "@allmaps/tailwind"; import { TriangulatedWarpedMap } from "./TriangulatedWarpedMap.js"; import { WarpedMapEvent, WarpedMapEventType } from "../shared/events.js"; import { createHomogeneousTransform, invertHomogeneousTransform, applyHomogeneousTransform } from "../shared/homogeneous-transform.js"; import { createBuffer } from "../shared/webgl2.js"; import { getTilesAtOtherScaleFactors, tileKey } from "../shared/tiles.js"; const THROTTLE_UPDATE_TEXTURES_WAIT_MS = 200; const THROTTLE_UPDATE_TEXTURES_OPTIONS = { leading: true, trailing: true }; const DEFAULT_RENDER_LINE_GROUP_OPTIONS = { viewportSize: 6, color: black, viewportBorderSize: 0, borderColor: white }; const DEFAULT_RENDER_POINT_GROUP_OPTION = { viewportSize: 16, color: black, viewportBorderSize: 1, borderColor: white }; const DEFAULT_SPECIFIC_WEBGL2_WARPED_MAP_OPTIONS = { renderGcps: false, renderGcpsColor: blue, renderTransformedGcps: false, renderTransformedGcpsColor: pink, renderVectors: false, renderVectorsSize: 6, renderVectorsColor: black, renderFullMask: false, renderFullMaskSize: 8, renderFullMaskColor: green, renderAppliableMask: false, renderAppliableMaskSize: 8, renderAppliableMaskColor: pink, renderMask: false, renderMaskSize: 8, renderMaskColor: pink, opacity: 1, saturation: 1, removeColor: false, removeColorColor: black, removeColorThreshold: 0, removeColorHardness: 0.7, colorize: false, colorizeColor: pink, renderGrid: false, renderGridColor: black, distortionColor00: red, distortionColor01: darkblue, distortionColor1: green, distortionColor2: yellow, distortionColor3: red, debugTiles: false, debugTriangles: false, debugTriangulation: false }; const TEXTURES_MAX_HIGHER_LOG2_SCALE_FACTOR_DIFF = 5; const TEXTURES_MAX_LOWER_LOG2_SCALE_FACTOR_DIFF = 1; function createWebGL2WarpedMapFactory(gl, mapProgram, linesProgram, pointsProgram) { return (mapId, georeferencedMap, listOptions, mapOptions) => new WebGL2WarpedMap( mapId, georeferencedMap, gl, mapProgram, linesProgram, pointsProgram, listOptions, mapOptions ); } class WebGL2WarpedMap extends TriangulatedWarpedMap { gl; mapProgram; linesProgram; pointsProgram; mapVao = null; linesVao = null; pointsVao = null; lineGroups = []; pointGroups = []; // Consider to store cachedTilesByTileKey as a quadtree for faster lookups cachedTilesByTileKey; cachedTilesByTileUrl; cachedTilesForTexture = []; previousCachedTilesForTexture = []; cachedTilesTextureArray = null; cachedTilesResourceOriginPointsAndSizesTexture = null; cachedTilesScaleFactorsTexture = null; // About renderHomogeneousTransform and InvertedRenderHomogeneousTransform: // renderHomogeneousTransform is the product of: // - the viewport's projectedGeoToClipTransform (projected geo coordinates -> clip coordinates) // - the saved invertedRenderHomogeneousTransform (projected clip coordinates -> geo coordinates) // since updateVertexBuffers ('where to draw triangles') run with possibly a different Viewport then renderInternal ('drawing the triangles'), a difference caused by throttling, there needs to be an adjustment. // this adjustment is minimal: indeed, since invertedRenderHomogeneousTransform is set as the inverse of the viewport's projectedGeoToClipTransform in updateVertexBuffers() // this renderHomogeneousTransform is almost the identity transform [1, 0, 0, 1, 0, 0]. invertedRenderHomogeneousTransform; throttledUpdateTextures; /** * Creates an instance of WebGL2WarpedMap. * * @constructor * @param mapId - ID of the map * @param georeferencedMap - Georeferenced map used to construct the WarpedMap * @param gl - WebGL rendering context * @param mapProgram - WebGL program for map * @param options - WarpedMapOptions */ constructor(mapId, georeferencedMap, gl, mapProgram, linesProgram, pointsProgram, listOptions, mapOptions) { super(mapId, georeferencedMap, listOptions, mapOptions); this.cachedTilesByTileKey = /* @__PURE__ */ new Map(); this.cachedTilesByTileUrl = /* @__PURE__ */ new Map(); this.gl = gl; this.initializeWebGL(mapProgram, linesProgram, pointsProgram); this.invertedRenderHomogeneousTransform = createHomogeneousTransform(); this.throttledUpdateTextures = throttle( this.updateTextures.bind(this), THROTTLE_UPDATE_TEXTURES_WAIT_MS, THROTTLE_UPDATE_TEXTURES_OPTIONS ); } initializeWebGL(mapProgram, linesProgram, pointsProgram) { this.mapProgram = mapProgram; this.linesProgram = linesProgram; this.pointsProgram = pointsProgram; this.mapVao = this.gl.createVertexArray(); this.linesVao = this.gl.createVertexArray(); this.pointsVao = this.gl.createVertexArray(); this.cachedTilesTextureArray = this.gl.createTexture(); this.cachedTilesScaleFactorsTexture = this.gl.createTexture(); this.cachedTilesResourceOriginPointsAndSizesTexture = this.gl.createTexture(); } /** * Get default options */ static getDefaultOptions() { return mergeOptions( DEFAULT_SPECIFIC_WEBGL2_WARPED_MAP_OPTIONS, super.getDefaultOptions() ); } /** Set default options */ setDefaultOptions() { this.defaultOptions = WebGL2WarpedMap.getDefaultOptions(); } applyOptions(animationOptions) { const changedOptions = super.applyOptions(animationOptions); this.options.opacity = (this.listOptions?.opacity ?? this.defaultOptions.opacity) * (this.mapOptions?.opacity ?? 1); this.options.saturation = (this.listOptions?.saturation ?? this.defaultOptions.saturation) * (this.mapOptions?.saturation ?? 1); return changedOptions; } shouldRenderMap() { return super.shouldRenderMap() && this.options.renderMaps !== false && this.options.opacity !== 0; } shouldRenderLines() { return super.shouldRenderLines() && this.options.renderLines !== false && (this.options.renderFullMask || this.options.renderAppliableMask || this.options.renderMask || this.options.renderVectors); } shouldRenderPoints() { return super.shouldRenderPoints() && this.options.renderPoints !== false && (this.options.renderGcps || this.options.renderTransformedGcps || this.options.debugTriangulation); } /** * Update the vertex buffers of this warped map * * @param projectedGeoToClipHomogeneousTransform - Transform from projected geo coordinates to webgl2 coordinates in the [-1, 1] range. Equivalent to OpenLayers' projectionTransform. */ updateVertexBuffers(projectedGeoToClipHomogeneousTransform) { this.invertedRenderHomogeneousTransform = invertHomogeneousTransform( projectedGeoToClipHomogeneousTransform ); if (this.shouldRenderMap()) { this.updateVertexBuffersMap(projectedGeoToClipHomogeneousTransform); } if (this.shouldRenderLines()) { this.updateVertexBuffersLines(projectedGeoToClipHomogeneousTransform); } if (this.shouldRenderPoints()) { this.updateVertexBuffersPoints(projectedGeoToClipHomogeneousTransform); } } /** * Clear textures for this map */ clearTextures() { } /** * Add cached tile to the textures of this map and update textures * * @param cachedTile */ addCachedTileAndUpdateTextures(cachedTile) { this.cachedTilesByTileKey.set(cachedTile.fetchableTile.tileKey, cachedTile); this.cachedTilesByTileUrl.set(cachedTile.fetchableTile.tileUrl, cachedTile); this.throttledUpdateTextures(); } /** * Remove cached tile from the textures of this map and update textures * * @param tileUrl */ removeCachedTileAndUpdateTextures(tileUrl) { const cachedTile = this.cachedTilesByTileUrl.get(tileUrl); if (!cachedTile) { return; } this.cachedTilesByTileKey.delete(cachedTile.fetchableTile.tileKey); this.cachedTilesByTileUrl.delete(tileUrl); this.throttledUpdateTextures(); } cancelThrottledFunctions() { this.throttledUpdateTextures.cancel(); } destroy() { this.gl.deleteVertexArray(this.mapVao); this.gl.deleteVertexArray(this.linesVao); this.gl.deleteVertexArray(this.pointsVao); this.gl.deleteTexture(this.cachedTilesTextureArray); this.gl.deleteTexture(this.cachedTilesScaleFactorsTexture); this.gl.deleteTexture(this.cachedTilesResourceOriginPointsAndSizesTexture); this.cancelThrottledFunctions(); super.destroy(); } setLineGroups() { this.lineGroups = []; if (this.options.renderVectors) { this.lineGroups.push({ projectedGeoLines: pointsAndPointsToLines( this.projectedGeoPoints, this.projectedGeoTransformedResourcePoints ), projectedGeoPreviousLines: pointsAndPointsToLines( this.projectedGeoPoints, this.projectedGeoPreviousTransformedResourcePoints ), viewportSize: this.options.renderVectorsSize, color: this.options.renderVectorsColor, viewportBorderSize: this.options.renderVectorsBorderSize, borderColor: this.options.renderVectorsBorderColor }); } if (this.options.renderFullMask) { this.lineGroups.push({ projectedGeoLines: lineStringToLines(this.projectedGeoFullMask), viewportSize: this.options.renderFullMaskSize, color: this.options.renderFullMaskColor, viewportBorderSize: this.options.renderFullMaskBorderSize, borderColor: this.options.renderFullMaskBorderColor }); } if (this.options.renderAppliableMask) { this.lineGroups.push({ projectedGeoLines: lineStringToLines( this.projectedGeoTriangulationAppliableMask ), projectedGeoPreviousLines: lineStringToLines( this.projectedGeoPreviousTriangulationAppliableMask ), viewportSize: this.options.renderAppliableMaskSize, color: this.options.renderAppliableMaskColor, viewportBorderSize: this.options.renderAppliableMaskBorderSize, borderColor: this.options.renderAppliableMaskBorderColor }); } if (this.options.renderMask) { this.lineGroups.push({ projectedGeoLines: lineStringToLines( this.projectedGeoTriangulationMask ), projectedGeoPreviousLines: lineStringToLines( this.projectedGeoPreviousTriangulationMask ), viewportSize: this.options.renderMaskSize, color: this.options.renderMaskColor, viewportBorderSize: this.options.renderMaskBorderSize, borderColor: this.options.renderMaskBorderColor }); } } setPointGroups() { this.pointGroups = []; if (this.options.renderGcps) { this.pointGroups.push({ projectedGeoPoints: this.projectedGeoPoints, viewportSize: this.options.renderGcpsSize, color: this.options.renderGcpsColor, viewportBorderSize: this.options.renderGcpsBorderSize, borderColor: this.options.renderGcpsBorderColor }); } if (this.options.renderTransformedGcps) { this.pointGroups.push({ projectedGeoPoints: this.projectedGeoTransformedResourcePoints, projectedGeoPreviousPoints: this.projectedGeoPreviousTransformedResourcePoints, viewportSize: this.options.renderTransformedGcpsSize, color: this.options.renderTransformedGcpsColor, viewportBorderSize: this.options.renderTransformedGcpsBorderSize, borderColor: this.options.renderTransformedGcpsBorderColor }); } if (this.options.debugTriangulation) { this.pointGroups.push({ projectedGeoPoints: this.projectedGeoPreviousTrianglePoints, color: gray }); this.pointGroups.push({ projectedGeoPoints: this.projectedGeoTrianglePoints, color: yellow }); } } updateVertexBuffersMap(projectedGeoToClipHomogeneousTransform) { if (!this.mapVao) { return; } const gl = this.gl; const program = this.mapProgram; gl.bindVertexArray(this.mapVao); createBuffer( gl, program, new Float32Array(this.resourceTrianglePoints.flat()), 2, "a_resourceTrianglePoint" ); const clipPreviousTrianglePoints = this.projectedGeoPreviousTrianglePoints.map( (point) => applyHomogeneousTransform(projectedGeoToClipHomogeneousTransform, point) ); createBuffer( gl, program, new Float32Array(clipPreviousTrianglePoints.flat()), 2, "a_clipPreviousTrianglePoint" ); const clipTrianglePoints = this.projectedGeoTrianglePoints.map( (point) => applyHomogeneousTransform(projectedGeoToClipHomogeneousTransform, point) ); createBuffer( gl, program, new Float32Array(clipTrianglePoints.flat()), 2, "a_clipTrianglePoint" ); createBuffer( gl, program, new Float32Array(this.previousTrianglePointsDistortion), 1, "a_previousTrianglePointDistortion" ); createBuffer( gl, program, new Float32Array(this.trianglePointsDistortion), 1, "a_trianglePointDistortion" ); const trianglePointsTriangleIndex = new Float32Array( this.resourceTrianglePoints.length ).map((_v, i) => { return i; }); createBuffer( gl, program, trianglePointsTriangleIndex, 1, "a_trianglePointIndex" ); } updateVertexBuffersLines(projectedGeoToClipHomogeneousTransform) { if (!this.linesVao) { return; } const gl = this.gl; const program = this.linesProgram; gl.bindVertexArray(this.linesVao); this.setLineGroups(); const clipSixPoints = this.lineGroups.reduce( (accumulator, lineGroup) => accumulator.concat(lineGroup.projectedGeoLines), [] ).map((projectedGeoLine) => [ projectedGeoLine[0], projectedGeoLine[0], projectedGeoLine[0], projectedGeoLine[1], projectedGeoLine[1], projectedGeoLine[1] ]).flat().map( (point) => applyHomogeneousTransform(projectedGeoToClipHomogeneousTransform, point) ); createBuffer( gl, program, new Float32Array(clipSixPoints.flat()), 2, "a_clipPoint" ); const clipSixOtherPoints = this.lineGroups.reduce( (accumulator, lineGroup) => accumulator.concat(lineGroup.projectedGeoLines), [] ).map((projectedGeoLine) => [ projectedGeoLine[1], projectedGeoLine[1], projectedGeoLine[1], projectedGeoLine[0], projectedGeoLine[0], projectedGeoLine[0] ]).flat().map( (point) => applyHomogeneousTransform(projectedGeoToClipHomogeneousTransform, point) ); createBuffer( gl, program, new Float32Array(clipSixOtherPoints.flat()), 2, "a_clipOtherPoint" ); const clipSixPreviousPoints = this.lineGroups.reduce( (accumulator, lineGroup) => accumulator.concat( lineGroup.projectedGeoPreviousLines || lineGroup.projectedGeoLines ), [] ).map((projectedGeoLine) => [ projectedGeoLine[0], projectedGeoLine[0], projectedGeoLine[0], projectedGeoLine[1], projectedGeoLine[1], projectedGeoLine[1] ]).flat().map( (point) => applyHomogeneousTransform(projectedGeoToClipHomogeneousTransform, point) ); createBuffer( gl, program, new Float32Array(clipSixPreviousPoints.flat()), 2, "a_clipPreviousPoint" ); const clipSixPreviousOtherPoints = this.lineGroups.reduce( (accumulator, lineGroup) => accumulator.concat( lineGroup.projectedGeoPreviousLines || lineGroup.projectedGeoLines ), [] ).map((projectedGeoLine) => [ projectedGeoLine[1], projectedGeoLine[1], projectedGeoLine[1], projectedGeoLine[0], projectedGeoLine[0], projectedGeoLine[0] ]).flat().map( (point) => applyHomogeneousTransform(projectedGeoToClipHomogeneousTransform, point) ); createBuffer( gl, program, new Float32Array(clipSixPreviousOtherPoints.flat()), 2, "a_clipPreviousOtherPoint" ); const sixIsOtherPoints = this.lineGroups.reduce( (accumulator, lineGroup) => accumulator.concat( lineGroup.projectedGeoLines.flatMap((_projectedGeoLine) => [ 0, 0, 1, 0, 0, 1 ]) ), [] ); createBuffer( gl, program, new Float32Array(sixIsOtherPoints), 1, "a_isOtherPoint" ); const sixNormalSigns = this.lineGroups.reduce( (accumulator, lineGroup) => accumulator.concat( lineGroup.projectedGeoLines.flatMap((_projectedGeoLine) => [ 1, -1, 1, 1, -1, 1 ]) ), [] ); createBuffer( gl, program, new Float32Array(sixNormalSigns), 1, "a_normalSign" ); const viewportSizes = this.lineGroups.reduce( (accumulator, lineGroup) => accumulator.concat( lineGroup.projectedGeoLines.flatMap( (_projectedGeoLine) => Array(6).fill( lineGroup.viewportSize ?? DEFAULT_RENDER_LINE_GROUP_OPTIONS.viewportSize ) ) ), [] ); createBuffer( gl, program, new Float32Array(viewportSizes), 1, "a_viewportSize" ); const colors = this.lineGroups.reduce( (accumulator, lineGroup) => { const color = hexToFractionalOpaqueRgba( lineGroup.color ?? DEFAULT_RENDER_LINE_GROUP_OPTIONS.color ); return accumulator.concat( lineGroup.projectedGeoLines.flatMap( (_projectedGeoLine) => Array(6).fill(color) ) ); }, [] ); createBuffer(gl, program, new Float32Array(colors.flat()), 4, "a_color"); const viewportBorderSizes = this.lineGroups.reduce( (accumulator, lineGroup) => accumulator.concat( lineGroup.projectedGeoLines.flatMap( (_projectedGeoLine) => Array(6).fill( lineGroup.viewportBorderSize ?? DEFAULT_RENDER_LINE_GROUP_OPTIONS.viewportBorderSize ) ) ), [] ); createBuffer( gl, program, new Float32Array(viewportBorderSizes), 1, "a_viewportBorderSize" ); const borderColors = this.lineGroups.reduce( (accumulator, lineGroup) => { const color = hexToFractionalOpaqueRgba( lineGroup.borderColor ?? DEFAULT_RENDER_LINE_GROUP_OPTIONS.borderColor ); return accumulator.concat( lineGroup.projectedGeoLines.flatMap( (_projectedGeoLine) => Array(6).fill(color) ) ); }, [] ); createBuffer( gl, program, new Float32Array(borderColors.flat()), 4, "a_borderColor" ); } updateVertexBuffersPoints(projectedGeoToClipHomogeneousTransform) { if (!this.pointsVao) { return; } const gl = this.gl; const program = this.pointsProgram; gl.bindVertexArray(this.pointsVao); this.setPointGroups(); const clipPoints = this.pointGroups.reduce( (accumulator, pointGroup) => accumulator.concat(pointGroup.projectedGeoPoints), [] ).map( (point) => applyHomogeneousTransform(projectedGeoToClipHomogeneousTransform, point) ); createBuffer( gl, program, new Float32Array(clipPoints.flat()), 2, "a_clipPoint" ); const clipPreviousPoints = this.pointGroups.reduce( (accumulator, pointGroup) => accumulator.concat( pointGroup.projectedGeoPreviousPoints || pointGroup.projectedGeoPoints ), [] ).map( (point) => applyHomogeneousTransform(projectedGeoToClipHomogeneousTransform, point) ); createBuffer( gl, program, new Float32Array(clipPreviousPoints.flat()), 2, "a_clipPreviousPoint" ); const viewportSizes = this.pointGroups.reduce( (accumulator, pointGroup) => accumulator.concat( pointGroup.projectedGeoPoints.map( (_projectedGeoPoint) => pointGroup.viewportSize ?? DEFAULT_RENDER_POINT_GROUP_OPTION.viewportSize ) ), [] ); createBuffer( gl, program, new Float32Array(viewportSizes), 1, "a_viewportSize" ); const colors = this.pointGroups.reduce( (accumulator, pointGroup) => { const color = hexToFractionalOpaqueRgba( pointGroup.color ?? DEFAULT_RENDER_POINT_GROUP_OPTION.color ); return accumulator.concat( pointGroup.projectedGeoPoints.map((_projectedGeoPoint) => color) ); }, [] ); createBuffer(gl, program, new Float32Array(colors.flat()), 4, "a_color"); const viewportBorderSizes = this.pointGroups.reduce( (accumulator, pointGroup) => accumulator.concat( pointGroup.projectedGeoPoints.map( (_projectedGeoPoint) => pointGroup.viewportBorderSize ?? DEFAULT_RENDER_POINT_GROUP_OPTION.viewportBorderSize ) ), [] ); createBuffer( gl, program, new Float32Array(viewportBorderSizes), 1, "a_viewportBorderSize" ); const borderColors = this.pointGroups.reduce( (accumulator, pointGroup) => { const color = hexToFractionalOpaqueRgba( pointGroup.borderColor ?? DEFAULT_RENDER_POINT_GROUP_OPTION.borderColor ); return accumulator.concat( pointGroup.projectedGeoPoints.map((_projectedGeoPoint) => color) ); }, [] ); createBuffer( gl, program, new Float32Array(borderColors.flat()), 4, "a_borderColor" ); } async updateTextures() { const gl = this.gl; this.updateCachedTilesForTextures(); if (this.cachedTilesForTexture.length == 0 || this.cachedTilesForTexture.length !== 0 && subSetArray( this.previousCachedTilesForTexture.map( (textureTile) => textureTile.fetchableTile.tileUrl ), this.cachedTilesForTexture.map( (textureTile) => textureTile.fetchableTile.tileUrl ) )) { return; } if (!this.image) { return; } const requiredTextureWidth = this.tileSize[0]; const requiredTextureHeigt = this.tileSize[1]; const requiredTextureDepth = this.cachedTilesForTexture.length; gl.pixelStorei(gl.UNPACK_ALIGNMENT, 4); gl.bindTexture(gl.TEXTURE_2D_ARRAY, this.cachedTilesTextureArray); gl.texImage3D( gl.TEXTURE_2D_ARRAY, 0, gl.RGBA, requiredTextureWidth, requiredTextureHeigt, requiredTextureDepth, 0, gl.RGBA, gl.UNSIGNED_BYTE, null ); for (let i = 0; i < this.cachedTilesForTexture.length; i++) { const imageData = this.cachedTilesForTexture[i].data; if (imageData.width !== requiredTextureWidth || imageData.width !== requiredTextureWidth) { throw new Error("Cached tile doesn't fit in texture"); } const pbo = gl.createBuffer(); gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, pbo); gl.bufferData(gl.PIXEL_UNPACK_BUFFER, imageData.data, gl.STATIC_DRAW); gl.texSubImage3D( gl.TEXTURE_2D_ARRAY, 0, 0, 0, i, imageData.width, imageData.height, 1, gl.RGBA, gl.UNSIGNED_BYTE, 0 ); gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, null); } gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); const cachedTilesResourceOriginPointsAndSizes = this.cachedTilesForTexture.map((textureTile) => { if (textureTile && textureTile.fetchableTile && textureTile.fetchableTile.options && textureTile.fetchableTile.options.imageRequest && textureTile.fetchableTile.options.imageRequest.region) { return [ textureTile.fetchableTile.options.imageRequest.region.x, textureTile.fetchableTile.options.imageRequest.region.y, textureTile.fetchableTile.options.imageRequest.region.width, textureTile.fetchableTile.options.imageRequest.region.height ]; } else { throw new Error("Missing resource origin points and sizes"); } }); gl.bindTexture( gl.TEXTURE_2D, this.cachedTilesResourceOriginPointsAndSizesTexture ); gl.texImage2D( gl.TEXTURE_2D, 0, gl.R32I, 1, this.cachedTilesForTexture.length * 4, 0, gl.RED_INTEGER, gl.INT, new Int32Array(cachedTilesResourceOriginPointsAndSizes.flat()) ); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); const cachedTilesScaleFactors = this.cachedTilesForTexture.map( (textureTile) => textureTile.fetchableTile.tile.tileZoomLevel.scaleFactor ); gl.bindTexture(gl.TEXTURE_2D, this.cachedTilesScaleFactorsTexture); gl.texImage2D( gl.TEXTURE_2D, 0, gl.R32I, 1, this.cachedTilesForTexture.length, 0, gl.RED_INTEGER, gl.INT, new Int32Array(cachedTilesScaleFactors) ); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); this.dispatchEvent(new WarpedMapEvent(WarpedMapEventType.TEXTURESUPDATED)); } updateCachedTilesForTextures() { const cachedTiles = []; const cachedTilesAtOtherScaleFactors = []; const overviewCachedTiles = []; const spriteCachedTiles = []; for (const fetchableTile of this.fetchableTilesForViewport) { const cachedTile = this.cachedTilesByTileUrl.get(fetchableTile.tileUrl); if (cachedTile) { cachedTiles.push(cachedTile); } else { for (const cachedTile2 of this.getCachedTilesAtOtherScaleFactors( fetchableTile.tile )) { cachedTilesAtOtherScaleFactors.push(cachedTile2); } } } spriteCachedTiles.push( ...Array.from(this.cachedTilesByTileUrl.values()).filter( (cachedTile) => cachedTile.isTileFromSprites() ) ); for (const fetchableTile of this.overviewFetchableTilesForViewport) { const cachedTile = this.cachedTilesByTileUrl.get(fetchableTile.tileUrl); if (cachedTile) { const tileZoolLevelTilesCount = this.tileZoomLevelForViewport ? this.tileZoomLevelForViewport.rows * this.tileZoomLevelForViewport.columns : void 0; if (cachedTiles.length === 0 || tileZoolLevelTilesCount && cachedTiles.length < tileZoolLevelTilesCount) { overviewCachedTiles.push(cachedTile); } } } let cachedTilesForTextures = [ ...cachedTiles, ...cachedTilesAtOtherScaleFactors, ...spriteCachedTiles, ...overviewCachedTiles ]; const cachedTilesForTexturesByTileUrl = /* @__PURE__ */ new Map(); cachedTilesForTextures.forEach( (cachedTile) => cachedTilesForTexturesByTileUrl.set( cachedTile.fetchableTile.tileUrl, cachedTile ) ); cachedTilesForTextures = [...cachedTilesForTexturesByTileUrl.values()]; this.previousCachedTilesForTexture = this.cachedTilesForTexture; this.cachedTilesForTexture = cachedTilesForTextures; return; } getCachedTilesAtOtherScaleFactors(tile) { if (this.cachedTilesByTileUrl.size === 0) { return []; } if (!this.tileZoomLevelForViewport) { return []; } const cachedTiles = []; for (tile of getTilesAtOtherScaleFactors( tile, this.image, this.tileZoomLevelForViewport.scaleFactor, TEXTURES_MAX_LOWER_LOG2_SCALE_FACTOR_DIFF, TEXTURES_MAX_HIGHER_LOG2_SCALE_FACTOR_DIFF, this.tileInCachedTiles.bind(this) // Only consider tiles in cache, )) { const cachedTile = this.tileToCachedTile(tile); if (cachedTile) { cachedTiles.push(cachedTile); } else { throw new Error("Tile supposed to be in cache isn't."); } } return cachedTiles; } // Lookup by tileKey (zoomlevel, row, column) instead of tileUrl // Because computing the tileUrl for every tile is expensive tileToCachedTile(tile) { return this.cachedTilesByTileKey.get(tileKey(tile)); } tileInCachedTiles(tile) { return this.cachedTilesByTileKey.has(tileKey(tile)); } } export { WebGL2WarpedMap, createWebGL2WarpedMapFactory }; //# sourceMappingURL=WebGL2WarpedMap.js.map