3312 lines
103 KiB
JavaScript
3312 lines
103 KiB
JavaScript
import {
|
|
create as create2,
|
|
fromTransform
|
|
} from "./chunk-L47B4DRW.js";
|
|
import {
|
|
DataTile_default,
|
|
LRUCache_default,
|
|
asArrayLike,
|
|
asImageLike
|
|
} from "./chunk-FPVJKBJO.js";
|
|
import {
|
|
Tile_default
|
|
} from "./chunk-QL7JR4NF.js";
|
|
import {
|
|
createOrUpdate,
|
|
getKey
|
|
} from "./chunk-SQ4UGRSZ.js";
|
|
import {
|
|
ImageTile_default
|
|
} from "./chunk-MESSQWK4.js";
|
|
import {
|
|
TileRange_default
|
|
} from "./chunk-LRXO5GLT.js";
|
|
import {
|
|
Layer_default
|
|
} from "./chunk-C66424RK.js";
|
|
import {
|
|
TileState_default
|
|
} from "./chunk-5D2XPBR2.js";
|
|
import {
|
|
BooleanType,
|
|
CallExpression,
|
|
ColorType,
|
|
NumberArrayType,
|
|
NumberType,
|
|
Ops,
|
|
SizeType,
|
|
StringType,
|
|
newParsingContext,
|
|
parse,
|
|
typeName
|
|
} from "./chunk-E5F6ZCFZ.js";
|
|
import {
|
|
EventType_default as EventType_default2,
|
|
Event_default,
|
|
Property_default
|
|
} from "./chunk-S5OMZ56B.js";
|
|
import {
|
|
asArray
|
|
} from "./chunk-GMHZLYJW.js";
|
|
import {
|
|
toSize
|
|
} from "./chunk-PPP4FLHO.js";
|
|
import {
|
|
createCanvasContext2D
|
|
} from "./chunk-YWIWRQT2.js";
|
|
import {
|
|
SAFARI_BUG_237906
|
|
} from "./chunk-5XHD7RSF.js";
|
|
import {
|
|
apply,
|
|
compose,
|
|
create,
|
|
reset,
|
|
rotate,
|
|
scale,
|
|
translate
|
|
} from "./chunk-JFONEOYG.js";
|
|
import {
|
|
fromUserExtent
|
|
} from "./chunk-XZU4LSFD.js";
|
|
import {
|
|
boundingExtent,
|
|
containsCoordinate,
|
|
getIntersection,
|
|
getRotatedViewport,
|
|
isEmpty
|
|
} from "./chunk-CKDBVGKM.js";
|
|
import {
|
|
assert
|
|
} from "./chunk-QFCIXVZ3.js";
|
|
import {
|
|
abstract,
|
|
getUid
|
|
} from "./chunk-H47PV7W6.js";
|
|
import {
|
|
Disposable_default,
|
|
EventType_default,
|
|
Target_default
|
|
} from "./chunk-KJXIHBKT.js";
|
|
import {
|
|
descending
|
|
} from "./chunk-FQY6EMA7.js";
|
|
import {
|
|
clear
|
|
} from "./chunk-5RHQVMYD.js";
|
|
|
|
// node_modules/ol/webgl.js
|
|
var ARRAY_BUFFER = 34962;
|
|
var ELEMENT_ARRAY_BUFFER = 34963;
|
|
var STREAM_DRAW = 35040;
|
|
var STATIC_DRAW = 35044;
|
|
var DYNAMIC_DRAW = 35048;
|
|
var UNSIGNED_BYTE = 5121;
|
|
var UNSIGNED_SHORT = 5123;
|
|
var UNSIGNED_INT = 5125;
|
|
var FLOAT = 5126;
|
|
var CONTEXT_IDS = ["experimental-webgl", "webgl", "webkit-3d", "moz-webgl"];
|
|
function getContext(canvas, attributes) {
|
|
attributes = Object.assign(
|
|
{
|
|
preserveDrawingBuffer: true,
|
|
antialias: SAFARI_BUG_237906 ? false : true
|
|
// https://bugs.webkit.org/show_bug.cgi?id=237906
|
|
},
|
|
attributes
|
|
);
|
|
const ii = CONTEXT_IDS.length;
|
|
for (let i = 0; i < ii; ++i) {
|
|
try {
|
|
const context = canvas.getContext(CONTEXT_IDS[i], attributes);
|
|
if (context) {
|
|
return (
|
|
/** @type {!WebGLRenderingContext} */
|
|
context
|
|
);
|
|
}
|
|
} catch {
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// node_modules/ol/webgl/Buffer.js
|
|
var BufferUsage = {
|
|
STATIC_DRAW,
|
|
STREAM_DRAW,
|
|
DYNAMIC_DRAW
|
|
};
|
|
var WebGLArrayBuffer = class {
|
|
/**
|
|
* @param {number} type Buffer type, either ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER.
|
|
* @param {number} [usage] Intended usage, either `STATIC_DRAW`, `STREAM_DRAW` or `DYNAMIC_DRAW`.
|
|
* Default is `STATIC_DRAW`.
|
|
*/
|
|
constructor(type, usage) {
|
|
this.array_ = null;
|
|
this.type_ = type;
|
|
assert(
|
|
type === ARRAY_BUFFER || type === ELEMENT_ARRAY_BUFFER,
|
|
"A `WebGLArrayBuffer` must either be of type `ELEMENT_ARRAY_BUFFER` or `ARRAY_BUFFER`"
|
|
);
|
|
this.usage_ = usage !== void 0 ? usage : BufferUsage.STATIC_DRAW;
|
|
}
|
|
/**
|
|
* Populates the buffer with an array of the given size (all values will be zeroes).
|
|
* @param {number} size Array size
|
|
* @return {WebGLArrayBuffer} This
|
|
*/
|
|
ofSize(size) {
|
|
this.array_ = new (getArrayClassForType(this.type_))(size);
|
|
return this;
|
|
}
|
|
/**
|
|
* Populates the buffer with an array of the given size.
|
|
* @param {Array<number>} array Numerical array
|
|
* @return {WebGLArrayBuffer} This
|
|
*/
|
|
fromArray(array) {
|
|
this.array_ = getArrayClassForType(this.type_).from(array);
|
|
return this;
|
|
}
|
|
/**
|
|
* Populates the buffer with a raw binary array buffer.
|
|
* @param {ArrayBuffer} buffer Raw binary buffer to populate the array with. Note that this buffer must have been
|
|
* initialized for the same typed array class.
|
|
* @return {WebGLArrayBuffer} This
|
|
*/
|
|
fromArrayBuffer(buffer) {
|
|
this.array_ = new (getArrayClassForType(this.type_))(buffer);
|
|
return this;
|
|
}
|
|
/**
|
|
* @return {number} Buffer type.
|
|
*/
|
|
getType() {
|
|
return this.type_;
|
|
}
|
|
/**
|
|
* Will return null if the buffer was not initialized
|
|
* @return {Float32Array|Uint32Array|null} Array.
|
|
*/
|
|
getArray() {
|
|
return this.array_;
|
|
}
|
|
/**
|
|
* @param {Float32Array|Uint32Array} array Array.
|
|
*/
|
|
setArray(array) {
|
|
const ArrayType = getArrayClassForType(this.type_);
|
|
if (!(array instanceof ArrayType)) {
|
|
throw new Error(`Expected ${ArrayType}`);
|
|
}
|
|
this.array_ = array;
|
|
}
|
|
/**
|
|
* @return {number} Usage.
|
|
*/
|
|
getUsage() {
|
|
return this.usage_;
|
|
}
|
|
/**
|
|
* Will return 0 if the buffer is not initialized
|
|
* @return {number} Array size
|
|
*/
|
|
getSize() {
|
|
return this.array_ ? this.array_.length : 0;
|
|
}
|
|
};
|
|
function getArrayClassForType(type) {
|
|
switch (type) {
|
|
case ARRAY_BUFFER:
|
|
return Float32Array;
|
|
case ELEMENT_ARRAY_BUFFER:
|
|
return Uint32Array;
|
|
default:
|
|
return Float32Array;
|
|
}
|
|
}
|
|
var Buffer_default = WebGLArrayBuffer;
|
|
|
|
// node_modules/ol/webgl/ContextEventType.js
|
|
var ContextEventType_default = {
|
|
LOST: "webglcontextlost",
|
|
RESTORED: "webglcontextrestored"
|
|
};
|
|
|
|
// node_modules/ol/webgl/PostProcessingPass.js
|
|
var DEFAULT_VERTEX_SHADER = `
|
|
precision mediump float;
|
|
|
|
attribute vec2 a_position;
|
|
varying vec2 v_texCoord;
|
|
varying vec2 v_screenCoord;
|
|
|
|
uniform vec2 u_screenSize;
|
|
|
|
void main() {
|
|
v_texCoord = a_position * 0.5 + 0.5;
|
|
v_screenCoord = v_texCoord * u_screenSize;
|
|
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
}
|
|
`;
|
|
var DEFAULT_FRAGMENT_SHADER = `
|
|
precision mediump float;
|
|
|
|
uniform sampler2D u_image;
|
|
uniform float u_opacity;
|
|
|
|
varying vec2 v_texCoord;
|
|
|
|
void main() {
|
|
gl_FragColor = texture2D(u_image, v_texCoord) * u_opacity;
|
|
}
|
|
`;
|
|
var WebGLPostProcessingPass = class {
|
|
/**
|
|
* @param {Options} options Options.
|
|
*/
|
|
constructor(options) {
|
|
this.gl_ = options.webGlContext;
|
|
const gl = this.gl_;
|
|
this.scaleRatio_ = options.scaleRatio || 1;
|
|
this.renderTargetTexture_ = gl.createTexture();
|
|
this.renderTargetTextureSize_ = null;
|
|
this.frameBuffer_ = gl.createFramebuffer();
|
|
this.depthBuffer_ = gl.createRenderbuffer();
|
|
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
|
|
gl.shaderSource(
|
|
vertexShader,
|
|
options.vertexShader || DEFAULT_VERTEX_SHADER
|
|
);
|
|
gl.compileShader(vertexShader);
|
|
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
|
|
gl.shaderSource(
|
|
fragmentShader,
|
|
options.fragmentShader || DEFAULT_FRAGMENT_SHADER
|
|
);
|
|
gl.compileShader(fragmentShader);
|
|
this.renderTargetProgram_ = gl.createProgram();
|
|
gl.attachShader(this.renderTargetProgram_, vertexShader);
|
|
gl.attachShader(this.renderTargetProgram_, fragmentShader);
|
|
gl.linkProgram(this.renderTargetProgram_);
|
|
this.renderTargetVerticesBuffer_ = gl.createBuffer();
|
|
const verticesArray = [-1, -1, 1, -1, -1, 1, 1, -1, 1, 1, -1, 1];
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.renderTargetVerticesBuffer_);
|
|
gl.bufferData(
|
|
gl.ARRAY_BUFFER,
|
|
new Float32Array(verticesArray),
|
|
gl.STATIC_DRAW
|
|
);
|
|
this.renderTargetAttribLocation_ = gl.getAttribLocation(
|
|
this.renderTargetProgram_,
|
|
"a_position"
|
|
);
|
|
this.renderTargetUniformLocation_ = gl.getUniformLocation(
|
|
this.renderTargetProgram_,
|
|
"u_screenSize"
|
|
);
|
|
this.renderTargetOpacityLocation_ = gl.getUniformLocation(
|
|
this.renderTargetProgram_,
|
|
"u_opacity"
|
|
);
|
|
this.renderTargetTextureLocation_ = gl.getUniformLocation(
|
|
this.renderTargetProgram_,
|
|
"u_image"
|
|
);
|
|
this.uniforms_ = [];
|
|
options.uniforms && Object.keys(options.uniforms).forEach((name) => {
|
|
this.uniforms_.push({
|
|
value: options.uniforms[name],
|
|
location: gl.getUniformLocation(this.renderTargetProgram_, name)
|
|
});
|
|
});
|
|
}
|
|
getRenderTargetTexture() {
|
|
return this.renderTargetTexture_;
|
|
}
|
|
/**
|
|
* Get the WebGL rendering context
|
|
* @return {WebGLRenderingContext} The rendering context.
|
|
*/
|
|
getGL() {
|
|
return this.gl_;
|
|
}
|
|
/**
|
|
* Initialize the render target texture of the post process, make sure it is at the
|
|
* right size and bind it as a render target for the next draw calls.
|
|
* The last step to be initialized will be the one where the primitives are rendered.
|
|
* @param {import("../Map.js").FrameState} frameState current frame state
|
|
*/
|
|
init(frameState) {
|
|
const gl = this.getGL();
|
|
const textureSize = [
|
|
gl.drawingBufferWidth * this.scaleRatio_,
|
|
gl.drawingBufferHeight * this.scaleRatio_
|
|
];
|
|
gl.bindFramebuffer(gl.FRAMEBUFFER, this.getFrameBuffer());
|
|
gl.bindRenderbuffer(gl.RENDERBUFFER, this.getDepthBuffer());
|
|
gl.viewport(0, 0, textureSize[0], textureSize[1]);
|
|
if (!this.renderTargetTextureSize_ || this.renderTargetTextureSize_[0] !== textureSize[0] || this.renderTargetTextureSize_[1] !== textureSize[1]) {
|
|
this.renderTargetTextureSize_ = textureSize;
|
|
const level = 0;
|
|
const internalFormat = gl.RGBA;
|
|
const border = 0;
|
|
const format = gl.RGBA;
|
|
const type = gl.UNSIGNED_BYTE;
|
|
const data = null;
|
|
gl.bindTexture(gl.TEXTURE_2D, this.renderTargetTexture_);
|
|
gl.texImage2D(
|
|
gl.TEXTURE_2D,
|
|
level,
|
|
internalFormat,
|
|
textureSize[0],
|
|
textureSize[1],
|
|
border,
|
|
format,
|
|
type,
|
|
data
|
|
);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
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);
|
|
gl.framebufferTexture2D(
|
|
gl.FRAMEBUFFER,
|
|
gl.COLOR_ATTACHMENT0,
|
|
gl.TEXTURE_2D,
|
|
this.renderTargetTexture_,
|
|
0
|
|
);
|
|
gl.renderbufferStorage(
|
|
gl.RENDERBUFFER,
|
|
gl.DEPTH_COMPONENT16,
|
|
textureSize[0],
|
|
textureSize[1]
|
|
);
|
|
gl.framebufferRenderbuffer(
|
|
gl.FRAMEBUFFER,
|
|
gl.DEPTH_ATTACHMENT,
|
|
gl.RENDERBUFFER,
|
|
this.depthBuffer_
|
|
);
|
|
}
|
|
}
|
|
/**
|
|
* Render to the next postprocessing pass (or to the canvas if final pass).
|
|
* @param {import("../Map.js").FrameState} frameState current frame state
|
|
* @param {WebGLPostProcessingPass} [nextPass] Next pass, optional
|
|
* @param {function(WebGLRenderingContext, import("../Map.js").FrameState):void} [preCompose] Called before composing.
|
|
* @param {function(WebGLRenderingContext, import("../Map.js").FrameState):void} [postCompose] Called before composing.
|
|
*/
|
|
apply(frameState, nextPass, preCompose, postCompose) {
|
|
const gl = this.getGL();
|
|
const size = frameState.size;
|
|
gl.bindFramebuffer(
|
|
gl.FRAMEBUFFER,
|
|
nextPass ? nextPass.getFrameBuffer() : null
|
|
);
|
|
gl.activeTexture(gl.TEXTURE0);
|
|
gl.bindTexture(gl.TEXTURE_2D, this.renderTargetTexture_);
|
|
if (!nextPass) {
|
|
const canvasId = getUid(gl.canvas);
|
|
if (!frameState.renderTargets[canvasId]) {
|
|
const attributes = gl.getContextAttributes();
|
|
if (attributes && attributes.preserveDrawingBuffer) {
|
|
gl.clearColor(0, 0, 0, 0);
|
|
gl.clearDepth(1);
|
|
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
|
}
|
|
frameState.renderTargets[canvasId] = true;
|
|
}
|
|
}
|
|
gl.disable(gl.DEPTH_TEST);
|
|
gl.enable(gl.BLEND);
|
|
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.renderTargetVerticesBuffer_);
|
|
gl.useProgram(this.renderTargetProgram_);
|
|
gl.enableVertexAttribArray(this.renderTargetAttribLocation_);
|
|
gl.vertexAttribPointer(
|
|
this.renderTargetAttribLocation_,
|
|
2,
|
|
gl.FLOAT,
|
|
false,
|
|
0,
|
|
0
|
|
);
|
|
gl.uniform2f(this.renderTargetUniformLocation_, size[0], size[1]);
|
|
gl.uniform1i(this.renderTargetTextureLocation_, 0);
|
|
const opacity = frameState.layerStatesArray[frameState.layerIndex].opacity;
|
|
gl.uniform1f(this.renderTargetOpacityLocation_, opacity);
|
|
this.applyUniforms(frameState);
|
|
if (preCompose) {
|
|
preCompose(gl, frameState);
|
|
}
|
|
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
if (postCompose) {
|
|
postCompose(gl, frameState);
|
|
}
|
|
}
|
|
/**
|
|
* @return {WebGLFramebuffer} Frame buffer
|
|
*/
|
|
getFrameBuffer() {
|
|
return this.frameBuffer_;
|
|
}
|
|
/**
|
|
* @return {WebGLRenderbuffer} Depth buffer
|
|
*/
|
|
getDepthBuffer() {
|
|
return this.depthBuffer_;
|
|
}
|
|
/**
|
|
* Sets the custom uniforms based on what was given in the constructor.
|
|
* @param {import("../Map.js").FrameState} frameState Frame state.
|
|
* @private
|
|
*/
|
|
applyUniforms(frameState) {
|
|
const gl = this.getGL();
|
|
let value;
|
|
let textureSlot = 1;
|
|
this.uniforms_.forEach(function(uniform) {
|
|
value = typeof uniform.value === "function" ? uniform.value(frameState) : uniform.value;
|
|
if (value instanceof HTMLCanvasElement || value instanceof ImageData) {
|
|
if (!uniform.texture) {
|
|
uniform.texture = gl.createTexture();
|
|
}
|
|
gl.activeTexture(gl[`TEXTURE${textureSlot}`]);
|
|
gl.bindTexture(gl.TEXTURE_2D, uniform.texture);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
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);
|
|
if (value instanceof ImageData) {
|
|
gl.texImage2D(
|
|
gl.TEXTURE_2D,
|
|
0,
|
|
gl.RGBA,
|
|
gl.RGBA,
|
|
value.width,
|
|
value.height,
|
|
0,
|
|
gl.UNSIGNED_BYTE,
|
|
new Uint8Array(value.data)
|
|
);
|
|
} else {
|
|
gl.texImage2D(
|
|
gl.TEXTURE_2D,
|
|
0,
|
|
gl.RGBA,
|
|
gl.RGBA,
|
|
gl.UNSIGNED_BYTE,
|
|
value
|
|
);
|
|
}
|
|
gl.uniform1i(uniform.location, textureSlot++);
|
|
} else if (Array.isArray(value)) {
|
|
switch (value.length) {
|
|
case 2:
|
|
gl.uniform2f(uniform.location, value[0], value[1]);
|
|
return;
|
|
case 3:
|
|
gl.uniform3f(uniform.location, value[0], value[1], value[2]);
|
|
return;
|
|
case 4:
|
|
gl.uniform4f(
|
|
uniform.location,
|
|
value[0],
|
|
value[1],
|
|
value[2],
|
|
value[3]
|
|
);
|
|
return;
|
|
default:
|
|
return;
|
|
}
|
|
} else if (typeof value === "number") {
|
|
gl.uniform1f(uniform.location, value);
|
|
}
|
|
});
|
|
}
|
|
};
|
|
var PostProcessingPass_default = WebGLPostProcessingPass;
|
|
|
|
// node_modules/ol/webgl/Helper.js
|
|
var DefaultUniform = {
|
|
PROJECTION_MATRIX: "u_projectionMatrix",
|
|
SCREEN_TO_WORLD_MATRIX: "u_screenToWorldMatrix",
|
|
TIME: "u_time",
|
|
ZOOM: "u_zoom",
|
|
RESOLUTION: "u_resolution",
|
|
ROTATION: "u_rotation",
|
|
VIEWPORT_SIZE_PX: "u_viewportSizePx",
|
|
PIXEL_RATIO: "u_pixelRatio",
|
|
HIT_DETECTION: "u_hitDetection"
|
|
};
|
|
var AttributeType = {
|
|
UNSIGNED_BYTE,
|
|
UNSIGNED_SHORT,
|
|
UNSIGNED_INT,
|
|
FLOAT
|
|
};
|
|
var canvasCache = {};
|
|
function getSharedCanvasCacheKey(key) {
|
|
return "shared/" + key;
|
|
}
|
|
var uniqueCanvasCacheKeyCount = 0;
|
|
function getUniqueCanvasCacheKey() {
|
|
const key = "unique/" + uniqueCanvasCacheKeyCount;
|
|
uniqueCanvasCacheKeyCount += 1;
|
|
return key;
|
|
}
|
|
function getOrCreateContext(key) {
|
|
let cacheItem = canvasCache[key];
|
|
if (!cacheItem) {
|
|
const canvas = document.createElement("canvas");
|
|
canvas.width = 1;
|
|
canvas.height = 1;
|
|
canvas.style.position = "absolute";
|
|
canvas.style.left = "0";
|
|
const context = getContext(canvas);
|
|
cacheItem = { users: 0, context };
|
|
canvasCache[key] = cacheItem;
|
|
}
|
|
cacheItem.users += 1;
|
|
return cacheItem.context;
|
|
}
|
|
function releaseCanvas(key) {
|
|
const cacheItem = canvasCache[key];
|
|
if (!cacheItem) {
|
|
return;
|
|
}
|
|
cacheItem.users -= 1;
|
|
if (cacheItem.users > 0) {
|
|
return;
|
|
}
|
|
const gl = cacheItem.context;
|
|
const extension = gl.getExtension("WEBGL_lose_context");
|
|
if (extension) {
|
|
extension.loseContext();
|
|
}
|
|
const canvas = gl.canvas;
|
|
canvas.width = 1;
|
|
canvas.height = 1;
|
|
delete canvasCache[key];
|
|
}
|
|
var WebGLHelper = class extends Disposable_default {
|
|
/**
|
|
* @param {Options} [options] Options.
|
|
*/
|
|
constructor(options) {
|
|
super();
|
|
options = options || {};
|
|
this.boundHandleWebGLContextLost_ = this.handleWebGLContextLost.bind(this);
|
|
this.boundHandleWebGLContextRestored_ = this.handleWebGLContextRestored.bind(this);
|
|
this.canvasCacheKey_ = options.canvasCacheKey ? getSharedCanvasCacheKey(options.canvasCacheKey) : getUniqueCanvasCacheKey();
|
|
this.gl_ = getOrCreateContext(this.canvasCacheKey_);
|
|
this.bufferCache_ = {};
|
|
this.extensionCache_ = {};
|
|
this.currentProgram_ = null;
|
|
this.needsToBeRecreated_ = false;
|
|
const canvas = this.gl_.canvas;
|
|
canvas.addEventListener(
|
|
ContextEventType_default.LOST,
|
|
this.boundHandleWebGLContextLost_
|
|
);
|
|
canvas.addEventListener(
|
|
ContextEventType_default.RESTORED,
|
|
this.boundHandleWebGLContextRestored_
|
|
);
|
|
this.offsetRotateMatrix_ = create();
|
|
this.offsetScaleMatrix_ = create();
|
|
this.tmpMat4_ = create2();
|
|
this.uniformLocationsByProgram_ = {};
|
|
this.attribLocationsByProgram_ = {};
|
|
this.uniforms_ = [];
|
|
if (options.uniforms) {
|
|
this.setUniforms(options.uniforms);
|
|
}
|
|
this.postProcessPasses_ = options.postProcesses ? options.postProcesses.map(
|
|
(options2) => new PostProcessingPass_default({
|
|
webGlContext: this.gl_,
|
|
scaleRatio: options2.scaleRatio,
|
|
vertexShader: options2.vertexShader,
|
|
fragmentShader: options2.fragmentShader,
|
|
uniforms: options2.uniforms
|
|
})
|
|
) : [new PostProcessingPass_default({ webGlContext: this.gl_ })];
|
|
this.shaderCompileErrors_ = null;
|
|
this.startTime_ = Date.now();
|
|
this.maxAttributeCount_ = this.gl_.getParameter(
|
|
this.gl_.MAX_VERTEX_ATTRIBS
|
|
);
|
|
}
|
|
/**
|
|
* @param {Object<string, UniformValue>} uniforms Uniform definitions.
|
|
*/
|
|
setUniforms(uniforms) {
|
|
this.uniforms_ = [];
|
|
this.addUniforms(uniforms);
|
|
}
|
|
/**
|
|
* @param {Object<string, UniformValue>} uniforms Uniform definitions.
|
|
*/
|
|
addUniforms(uniforms) {
|
|
for (const name in uniforms) {
|
|
this.uniforms_.push({
|
|
name,
|
|
value: uniforms[name]
|
|
});
|
|
}
|
|
}
|
|
/**
|
|
* @param {string} canvasCacheKey The canvas cache key.
|
|
* @return {boolean} The provided key matches the one this helper was constructed with.
|
|
*/
|
|
canvasCacheKeyMatches(canvasCacheKey) {
|
|
return this.canvasCacheKey_ === getSharedCanvasCacheKey(canvasCacheKey);
|
|
}
|
|
/**
|
|
* Get a WebGL extension. If the extension is not supported, null is returned.
|
|
* Extensions are cached after they are enabled for the first time.
|
|
* @param {string} name The extension name.
|
|
* @return {Object|null} The extension or null if not supported.
|
|
*/
|
|
getExtension(name) {
|
|
if (name in this.extensionCache_) {
|
|
return this.extensionCache_[name];
|
|
}
|
|
const extension = this.gl_.getExtension(name);
|
|
this.extensionCache_[name] = extension;
|
|
return extension;
|
|
}
|
|
/**
|
|
* Just bind the buffer if it's in the cache. Otherwise create
|
|
* the WebGL buffer, bind it, populate it, and add an entry to
|
|
* the cache.
|
|
* @param {import("./Buffer").default} buffer Buffer.
|
|
*/
|
|
bindBuffer(buffer) {
|
|
const gl = this.gl_;
|
|
const bufferKey = getUid(buffer);
|
|
let bufferCache = this.bufferCache_[bufferKey];
|
|
if (!bufferCache) {
|
|
const webGlBuffer = gl.createBuffer();
|
|
bufferCache = {
|
|
buffer,
|
|
webGlBuffer
|
|
};
|
|
this.bufferCache_[bufferKey] = bufferCache;
|
|
}
|
|
gl.bindBuffer(buffer.getType(), bufferCache.webGlBuffer);
|
|
}
|
|
/**
|
|
* Update the data contained in the buffer array; this is required for the
|
|
* new data to be rendered
|
|
* @param {import("./Buffer").default} buffer Buffer.
|
|
*/
|
|
flushBufferData(buffer) {
|
|
const gl = this.gl_;
|
|
this.bindBuffer(buffer);
|
|
gl.bufferData(buffer.getType(), buffer.getArray(), buffer.getUsage());
|
|
}
|
|
/**
|
|
* @param {import("./Buffer.js").default} buf Buffer.
|
|
*/
|
|
deleteBuffer(buf) {
|
|
const bufferKey = getUid(buf);
|
|
delete this.bufferCache_[bufferKey];
|
|
}
|
|
/**
|
|
* Clean up.
|
|
* @override
|
|
*/
|
|
disposeInternal() {
|
|
const canvas = this.gl_.canvas;
|
|
canvas.removeEventListener(
|
|
ContextEventType_default.LOST,
|
|
this.boundHandleWebGLContextLost_
|
|
);
|
|
canvas.removeEventListener(
|
|
ContextEventType_default.RESTORED,
|
|
this.boundHandleWebGLContextRestored_
|
|
);
|
|
releaseCanvas(this.canvasCacheKey_);
|
|
delete this.gl_;
|
|
}
|
|
/**
|
|
* Clear the buffer & set the viewport to draw.
|
|
* Post process passes will be initialized here, the first one being bound as a render target for
|
|
* subsequent draw calls.
|
|
* @param {import("../Map.js").FrameState} frameState current frame state
|
|
* @param {boolean} [disableAlphaBlend] If true, no alpha blending will happen.
|
|
* @param {boolean} [enableDepth] If true, enables depth testing.
|
|
*/
|
|
prepareDraw(frameState, disableAlphaBlend, enableDepth) {
|
|
const gl = this.gl_;
|
|
const canvas = this.getCanvas();
|
|
const size = frameState.size;
|
|
const pixelRatio = frameState.pixelRatio;
|
|
if (canvas.width !== size[0] * pixelRatio || canvas.height !== size[1] * pixelRatio) {
|
|
canvas.width = size[0] * pixelRatio;
|
|
canvas.height = size[1] * pixelRatio;
|
|
canvas.style.width = size[0] + "px";
|
|
canvas.style.height = size[1] + "px";
|
|
}
|
|
for (let i = this.postProcessPasses_.length - 1; i >= 0; i--) {
|
|
this.postProcessPasses_[i].init(frameState);
|
|
}
|
|
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
gl.clearColor(0, 0, 0, 0);
|
|
gl.depthRange(0, 1);
|
|
gl.clearDepth(1);
|
|
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
|
gl.enable(gl.BLEND);
|
|
gl.blendFunc(gl.ONE, disableAlphaBlend ? gl.ZERO : gl.ONE_MINUS_SRC_ALPHA);
|
|
if (enableDepth) {
|
|
gl.enable(gl.DEPTH_TEST);
|
|
gl.depthFunc(gl.LEQUAL);
|
|
} else {
|
|
gl.disable(gl.DEPTH_TEST);
|
|
}
|
|
}
|
|
/**
|
|
* @param {WebGLFramebuffer|null} frameBuffer The frame buffer.
|
|
* @param {WebGLTexture} [texture] The texture.
|
|
*/
|
|
bindFrameBuffer(frameBuffer, texture) {
|
|
const gl = this.getGL();
|
|
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
|
|
if (texture) {
|
|
gl.framebufferTexture2D(
|
|
gl.FRAMEBUFFER,
|
|
gl.COLOR_ATTACHMENT0,
|
|
gl.TEXTURE_2D,
|
|
texture,
|
|
0
|
|
);
|
|
}
|
|
}
|
|
/**
|
|
* Bind the frame buffer from the initial render.
|
|
*/
|
|
bindInitialFrameBuffer() {
|
|
const gl = this.getGL();
|
|
const frameBuffer = this.postProcessPasses_[0].getFrameBuffer();
|
|
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
|
|
const texture = this.postProcessPasses_[0].getRenderTargetTexture();
|
|
gl.framebufferTexture2D(
|
|
gl.FRAMEBUFFER,
|
|
gl.COLOR_ATTACHMENT0,
|
|
gl.TEXTURE_2D,
|
|
texture,
|
|
0
|
|
);
|
|
}
|
|
/**
|
|
* Prepare a program to use a texture.
|
|
* @param {WebGLTexture} texture The texture.
|
|
* @param {number} slot The texture slot.
|
|
* @param {string} uniformName The corresponding uniform name.
|
|
*/
|
|
bindTexture(texture, slot, uniformName) {
|
|
const gl = this.gl_;
|
|
gl.activeTexture(gl.TEXTURE0 + slot);
|
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
gl.uniform1i(this.getUniformLocation(uniformName), slot);
|
|
}
|
|
/**
|
|
* Set up an attribute array buffer for use in the vertex shader.
|
|
* @param {import("./Buffer").default} buffer The buffer.
|
|
* @param {string} attributeName The attribute name.
|
|
* @param {number} size The number of components per attribute vertex.
|
|
*/
|
|
bindAttribute(buffer, attributeName, size) {
|
|
const gl = this.getGL();
|
|
this.bindBuffer(buffer);
|
|
const index = this.getAttributeLocation(attributeName);
|
|
gl.enableVertexAttribArray(index);
|
|
gl.vertexAttribPointer(index, size, gl.FLOAT, false, 0, 0);
|
|
}
|
|
/**
|
|
* Clear the render target & bind it for future draw operations.
|
|
* This is similar to `prepareDraw`, only post processes will not be applied.
|
|
* Note: the whole viewport will be drawn to the render target, regardless of its size.
|
|
* @param {import("../Map.js").FrameState} frameState current frame state
|
|
* @param {import("./RenderTarget.js").default} renderTarget Render target to draw to
|
|
* @param {boolean} [disableAlphaBlend] If true, no alpha blending will happen.
|
|
* @param {boolean} [enableDepth] If true, enables depth testing.
|
|
*/
|
|
prepareDrawToRenderTarget(frameState, renderTarget, disableAlphaBlend, enableDepth) {
|
|
const gl = this.gl_;
|
|
const size = renderTarget.getSize();
|
|
gl.bindFramebuffer(gl.FRAMEBUFFER, renderTarget.getFramebuffer());
|
|
gl.bindRenderbuffer(gl.RENDERBUFFER, renderTarget.getDepthbuffer());
|
|
gl.viewport(0, 0, size[0], size[1]);
|
|
gl.bindTexture(gl.TEXTURE_2D, renderTarget.getTexture());
|
|
gl.clearColor(0, 0, 0, 0);
|
|
gl.depthRange(0, 1);
|
|
gl.clearDepth(1);
|
|
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
|
gl.enable(gl.BLEND);
|
|
gl.blendFunc(gl.ONE, disableAlphaBlend ? gl.ZERO : gl.ONE_MINUS_SRC_ALPHA);
|
|
if (enableDepth) {
|
|
gl.enable(gl.DEPTH_TEST);
|
|
gl.depthFunc(gl.LEQUAL);
|
|
} else {
|
|
gl.disable(gl.DEPTH_TEST);
|
|
}
|
|
}
|
|
/**
|
|
* Execute a draw call based on the currently bound program, texture, buffers, attributes.
|
|
* @param {number} start Start index.
|
|
* @param {number} end End index.
|
|
*/
|
|
drawElements(start, end) {
|
|
const gl = this.gl_;
|
|
this.getExtension("OES_element_index_uint");
|
|
const elementType = gl.UNSIGNED_INT;
|
|
const elementSize = 4;
|
|
const numItems = end - start;
|
|
const offsetInBytes = start * elementSize;
|
|
gl.drawElements(gl.TRIANGLES, numItems, elementType, offsetInBytes);
|
|
}
|
|
/**
|
|
* Apply the successive post process passes which will eventually render to the actual canvas.
|
|
* @param {import("../Map.js").FrameState} frameState current frame state
|
|
* @param {function(WebGLRenderingContext, import("../Map.js").FrameState):void} [preCompose] Called before composing.
|
|
* @param {function(WebGLRenderingContext, import("../Map.js").FrameState):void} [postCompose] Called before composing.
|
|
*/
|
|
finalizeDraw(frameState, preCompose, postCompose) {
|
|
for (let i = 0, ii = this.postProcessPasses_.length; i < ii; i++) {
|
|
if (i === ii - 1) {
|
|
this.postProcessPasses_[i].apply(
|
|
frameState,
|
|
null,
|
|
preCompose,
|
|
postCompose
|
|
);
|
|
} else {
|
|
this.postProcessPasses_[i].apply(
|
|
frameState,
|
|
this.postProcessPasses_[i + 1]
|
|
);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* @return {HTMLCanvasElement} Canvas.
|
|
*/
|
|
getCanvas() {
|
|
return (
|
|
/** @type {HTMLCanvasElement} */
|
|
this.gl_.canvas
|
|
);
|
|
}
|
|
/**
|
|
* Get the WebGL rendering context
|
|
* @return {WebGLRenderingContext} The rendering context.
|
|
*/
|
|
getGL() {
|
|
return this.gl_;
|
|
}
|
|
/**
|
|
* Sets the default matrix uniforms for a given frame state. This is called internally in `prepareDraw`.
|
|
* @param {import("../Map.js").FrameState} frameState Frame state.
|
|
*/
|
|
applyFrameState(frameState) {
|
|
const size = frameState.size;
|
|
const rotation = frameState.viewState.rotation;
|
|
const pixelRatio = frameState.pixelRatio;
|
|
this.setUniformFloatValue(
|
|
DefaultUniform.TIME,
|
|
(Date.now() - this.startTime_) * 1e-3
|
|
);
|
|
this.setUniformFloatValue(DefaultUniform.ZOOM, frameState.viewState.zoom);
|
|
this.setUniformFloatValue(
|
|
DefaultUniform.RESOLUTION,
|
|
frameState.viewState.resolution
|
|
);
|
|
this.setUniformFloatValue(DefaultUniform.PIXEL_RATIO, pixelRatio);
|
|
this.setUniformFloatVec2(DefaultUniform.VIEWPORT_SIZE_PX, [
|
|
size[0],
|
|
size[1]
|
|
]);
|
|
this.setUniformFloatValue(DefaultUniform.ROTATION, rotation);
|
|
}
|
|
/**
|
|
* Sets the `u_hitDetection` uniform.
|
|
* @param {boolean} enabled Whether to enable the hit detection code path
|
|
*/
|
|
applyHitDetectionUniform(enabled) {
|
|
const loc = this.getUniformLocation(DefaultUniform.HIT_DETECTION);
|
|
this.getGL().uniform1i(loc, enabled ? 1 : 0);
|
|
if (enabled) {
|
|
this.setUniformFloatValue(DefaultUniform.PIXEL_RATIO, 0.5);
|
|
}
|
|
}
|
|
/**
|
|
* Sets the custom uniforms based on what was given in the constructor. This is called internally in `prepareDraw`.
|
|
* @param {import("../Map.js").FrameState} frameState Frame state.
|
|
*/
|
|
applyUniforms(frameState) {
|
|
const gl = this.gl_;
|
|
let value;
|
|
let textureSlot = 0;
|
|
this.uniforms_.forEach((uniform) => {
|
|
value = typeof uniform.value === "function" ? uniform.value(frameState) : uniform.value;
|
|
if (value instanceof HTMLCanvasElement || value instanceof HTMLImageElement || value instanceof ImageData || value instanceof WebGLTexture) {
|
|
if (value instanceof WebGLTexture && !uniform.texture) {
|
|
uniform.prevValue = void 0;
|
|
uniform.texture = value;
|
|
} else if (!uniform.texture) {
|
|
uniform.prevValue = void 0;
|
|
uniform.texture = gl.createTexture();
|
|
}
|
|
this.bindTexture(uniform.texture, textureSlot, uniform.name);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
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 imageReady = !(value instanceof HTMLImageElement) || /** @type {HTMLImageElement} */
|
|
value.complete;
|
|
if (!(value instanceof WebGLTexture) && imageReady && uniform.prevValue !== value) {
|
|
uniform.prevValue = value;
|
|
gl.texImage2D(
|
|
gl.TEXTURE_2D,
|
|
0,
|
|
gl.RGBA,
|
|
gl.RGBA,
|
|
gl.UNSIGNED_BYTE,
|
|
value
|
|
);
|
|
}
|
|
textureSlot++;
|
|
} else if (Array.isArray(value) && value.length === 6) {
|
|
this.setUniformMatrixValue(
|
|
uniform.name,
|
|
fromTransform(this.tmpMat4_, value)
|
|
);
|
|
} else if (Array.isArray(value) && value.length <= 4) {
|
|
switch (value.length) {
|
|
case 2:
|
|
gl.uniform2f(
|
|
this.getUniformLocation(uniform.name),
|
|
value[0],
|
|
value[1]
|
|
);
|
|
return;
|
|
case 3:
|
|
gl.uniform3f(
|
|
this.getUniformLocation(uniform.name),
|
|
value[0],
|
|
value[1],
|
|
value[2]
|
|
);
|
|
return;
|
|
case 4:
|
|
gl.uniform4f(
|
|
this.getUniformLocation(uniform.name),
|
|
value[0],
|
|
value[1],
|
|
value[2],
|
|
value[3]
|
|
);
|
|
return;
|
|
default:
|
|
return;
|
|
}
|
|
} else if (typeof value === "number") {
|
|
gl.uniform1f(this.getUniformLocation(uniform.name), value);
|
|
}
|
|
});
|
|
}
|
|
/**
|
|
* Set up a program for use. The program will be set as the current one. Then, the uniforms used
|
|
* in the program will be set based on the current frame state and the helper configuration.
|
|
* @param {WebGLProgram} program Program.
|
|
* @param {import("../Map.js").FrameState} [frameState] Frame state.
|
|
*/
|
|
useProgram(program, frameState) {
|
|
this.disableAllAttributes_();
|
|
const gl = this.gl_;
|
|
gl.useProgram(program);
|
|
this.currentProgram_ = program;
|
|
if (frameState) {
|
|
this.applyFrameState(frameState);
|
|
this.applyUniforms(frameState);
|
|
}
|
|
}
|
|
/**
|
|
* Will attempt to compile a vertex or fragment shader based on source
|
|
* On error, the shader will be returned but
|
|
* `gl.getShaderParameter(shader, gl.COMPILE_STATUS)` will return `true`
|
|
* Use `gl.getShaderInfoLog(shader)` to have details
|
|
* @param {string} source Shader source
|
|
* @param {ShaderType} type VERTEX_SHADER or FRAGMENT_SHADER
|
|
* @return {WebGLShader} Shader object
|
|
*/
|
|
compileShader(source, type) {
|
|
const gl = this.gl_;
|
|
const shader = gl.createShader(type);
|
|
gl.shaderSource(shader, source);
|
|
gl.compileShader(shader);
|
|
return shader;
|
|
}
|
|
/**
|
|
* Create a program for a vertex and fragment shader. Throws if shader compilation fails.
|
|
* @param {string} fragmentShaderSource Fragment shader source.
|
|
* @param {string} vertexShaderSource Vertex shader source.
|
|
* @return {WebGLProgram} Program
|
|
*/
|
|
getProgram(fragmentShaderSource, vertexShaderSource) {
|
|
const gl = this.gl_;
|
|
const fragmentShader = this.compileShader(
|
|
fragmentShaderSource,
|
|
gl.FRAGMENT_SHADER
|
|
);
|
|
const vertexShader = this.compileShader(
|
|
vertexShaderSource,
|
|
gl.VERTEX_SHADER
|
|
);
|
|
const program = gl.createProgram();
|
|
gl.attachShader(program, fragmentShader);
|
|
gl.attachShader(program, vertexShader);
|
|
gl.linkProgram(program);
|
|
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
|
|
const message = `Fragment shader compilation failed: ${gl.getShaderInfoLog(
|
|
fragmentShader
|
|
)}`;
|
|
throw new Error(message);
|
|
}
|
|
gl.deleteShader(fragmentShader);
|
|
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
|
|
const message = `Vertex shader compilation failed: ${gl.getShaderInfoLog(
|
|
vertexShader
|
|
)}`;
|
|
throw new Error(message);
|
|
}
|
|
gl.deleteShader(vertexShader);
|
|
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
const message = `GL program linking failed: ${gl.getProgramInfoLog(
|
|
program
|
|
)}`;
|
|
throw new Error(message);
|
|
}
|
|
return program;
|
|
}
|
|
/**
|
|
* Will get the location from the shader or the cache
|
|
* @param {string} name Uniform name
|
|
* @return {WebGLUniformLocation} uniformLocation
|
|
*/
|
|
getUniformLocation(name) {
|
|
const programUid = getUid(this.currentProgram_);
|
|
if (this.uniformLocationsByProgram_[programUid] === void 0) {
|
|
this.uniformLocationsByProgram_[programUid] = {};
|
|
}
|
|
if (this.uniformLocationsByProgram_[programUid][name] === void 0) {
|
|
this.uniformLocationsByProgram_[programUid][name] = this.gl_.getUniformLocation(this.currentProgram_, name);
|
|
}
|
|
return this.uniformLocationsByProgram_[programUid][name];
|
|
}
|
|
/**
|
|
* Will get the location from the shader or the cache
|
|
* @param {string} name Attribute name
|
|
* @return {number} attribLocation
|
|
*/
|
|
getAttributeLocation(name) {
|
|
const programUid = getUid(this.currentProgram_);
|
|
if (this.attribLocationsByProgram_[programUid] === void 0) {
|
|
this.attribLocationsByProgram_[programUid] = {};
|
|
}
|
|
if (this.attribLocationsByProgram_[programUid][name] === void 0) {
|
|
this.attribLocationsByProgram_[programUid][name] = this.gl_.getAttribLocation(this.currentProgram_, name);
|
|
}
|
|
return this.attribLocationsByProgram_[programUid][name];
|
|
}
|
|
/**
|
|
* Sets the given transform to apply the rotation/translation/scaling of the given frame state.
|
|
* The resulting transform can be used to convert world space coordinates to view coordinates in the [-1, 1] range.
|
|
* @param {import("../Map.js").FrameState} frameState Frame state.
|
|
* @param {import("../transform").Transform} transform Transform to update.
|
|
* @return {import("../transform").Transform} The updated transform object.
|
|
*/
|
|
makeProjectionTransform(frameState, transform) {
|
|
const size = frameState.size;
|
|
const rotation = frameState.viewState.rotation;
|
|
const resolution = frameState.viewState.resolution;
|
|
const center = frameState.viewState.center;
|
|
compose(
|
|
transform,
|
|
0,
|
|
0,
|
|
2 / (resolution * size[0]),
|
|
2 / (resolution * size[1]),
|
|
-rotation,
|
|
-center[0],
|
|
-center[1]
|
|
);
|
|
return transform;
|
|
}
|
|
/**
|
|
* Give a value for a standard float uniform
|
|
* @param {string} uniform Uniform name
|
|
* @param {number} value Value
|
|
*/
|
|
setUniformFloatValue(uniform, value) {
|
|
this.gl_.uniform1f(this.getUniformLocation(uniform), value);
|
|
}
|
|
/**
|
|
* Give a value for a vec2 uniform
|
|
* @param {string} uniform Uniform name
|
|
* @param {Array<number>} value Array of length 4.
|
|
*/
|
|
setUniformFloatVec2(uniform, value) {
|
|
this.gl_.uniform2fv(this.getUniformLocation(uniform), value);
|
|
}
|
|
/**
|
|
* Give a value for a vec4 uniform
|
|
* @param {string} uniform Uniform name
|
|
* @param {Array<number>} value Array of length 4.
|
|
*/
|
|
setUniformFloatVec4(uniform, value) {
|
|
this.gl_.uniform4fv(this.getUniformLocation(uniform), value);
|
|
}
|
|
/**
|
|
* Give a value for a standard matrix4 uniform
|
|
* @param {string} uniform Uniform name
|
|
* @param {Array<number>} value Matrix value
|
|
*/
|
|
setUniformMatrixValue(uniform, value) {
|
|
this.gl_.uniformMatrix4fv(this.getUniformLocation(uniform), false, value);
|
|
}
|
|
/**
|
|
* Disable all vertex attributes.
|
|
* @private
|
|
*/
|
|
disableAllAttributes_() {
|
|
for (let i = 0; i < this.maxAttributeCount_; i++) {
|
|
this.gl_.disableVertexAttribArray(i);
|
|
}
|
|
}
|
|
/**
|
|
* Will set the currently bound buffer to an attribute of the shader program. Used by `#enableAttributes`
|
|
* internally.
|
|
* @param {string} attribName Attribute name
|
|
* @param {number} size Number of components per attributes
|
|
* @param {number} type UNSIGNED_INT, UNSIGNED_BYTE, UNSIGNED_SHORT or FLOAT
|
|
* @param {number} stride Stride in bytes (0 means attribs are packed)
|
|
* @param {number} offset Offset in bytes
|
|
* @private
|
|
*/
|
|
enableAttributeArray_(attribName, size, type, stride, offset) {
|
|
const location = this.getAttributeLocation(attribName);
|
|
if (location < 0) {
|
|
return;
|
|
}
|
|
this.gl_.enableVertexAttribArray(location);
|
|
this.gl_.vertexAttribPointer(location, size, type, false, stride, offset);
|
|
}
|
|
/**
|
|
* Will enable the following attributes to be read from the currently bound buffer,
|
|
* i.e. tell the GPU where to read the different attributes in the buffer. An error in the
|
|
* size/type/order of attributes will most likely break the rendering and throw a WebGL exception.
|
|
* @param {Array<AttributeDescription>} attributes Ordered list of attributes to read from the buffer
|
|
*/
|
|
enableAttributes(attributes) {
|
|
const stride = computeAttributesStride(attributes);
|
|
let offset = 0;
|
|
for (let i = 0; i < attributes.length; i++) {
|
|
const attr = attributes[i];
|
|
this.enableAttributeArray_(
|
|
attr.name,
|
|
attr.size,
|
|
attr.type || FLOAT,
|
|
stride,
|
|
offset
|
|
);
|
|
offset += attr.size * getByteSizeFromType(attr.type);
|
|
}
|
|
}
|
|
/**
|
|
* WebGL context was lost
|
|
* @param {WebGLContextEvent} event The context loss event.
|
|
* @private
|
|
*/
|
|
handleWebGLContextLost(event) {
|
|
clear(this.bufferCache_);
|
|
this.currentProgram_ = null;
|
|
event.preventDefault();
|
|
}
|
|
/**
|
|
* WebGL context was restored
|
|
* @private
|
|
*/
|
|
handleWebGLContextRestored() {
|
|
this.needsToBeRecreated_ = true;
|
|
}
|
|
/**
|
|
* Returns whether this helper needs to be recreated, as the context was lost and then restored.
|
|
* @return {boolean} Whether this helper needs to be recreated.
|
|
*/
|
|
needsToBeRecreated() {
|
|
return this.needsToBeRecreated_;
|
|
}
|
|
/**
|
|
* Will create or reuse a given webgl texture and apply the given size. If no image data
|
|
* specified, the texture will be empty, otherwise image data will be used and the `size`
|
|
* parameter will be ignored. If a Uint8Array is provided for data, a size must also be provided.
|
|
* Note: wrap parameters are set to clamp to edge, min filter is set to linear.
|
|
* @param {Array<number>} size Expected size of the texture
|
|
* @param {ImageData|HTMLImageElement|HTMLCanvasElement|Uint8Array|null} data Image data/object to bind to the texture
|
|
* @param {WebGLTexture} [texture] Existing texture to reuse
|
|
* @param {boolean} [nearest] Use gl.NEAREST for min/mag filter.
|
|
* @return {WebGLTexture} The generated texture
|
|
*/
|
|
createTexture(size, data, texture, nearest) {
|
|
const gl = this.gl_;
|
|
texture = texture || gl.createTexture();
|
|
const filter = nearest ? gl.NEAREST : gl.LINEAR;
|
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);
|
|
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 level = 0;
|
|
const internalFormat = gl.RGBA;
|
|
const border = 0;
|
|
const format = gl.RGBA;
|
|
const type = gl.UNSIGNED_BYTE;
|
|
if (data instanceof Uint8Array) {
|
|
gl.texImage2D(
|
|
gl.TEXTURE_2D,
|
|
level,
|
|
internalFormat,
|
|
size[0],
|
|
size[1],
|
|
border,
|
|
format,
|
|
type,
|
|
data
|
|
);
|
|
} else if (data) {
|
|
gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, format, type, data);
|
|
} else {
|
|
gl.texImage2D(
|
|
gl.TEXTURE_2D,
|
|
level,
|
|
internalFormat,
|
|
size[0],
|
|
size[1],
|
|
border,
|
|
format,
|
|
type,
|
|
null
|
|
);
|
|
}
|
|
return texture;
|
|
}
|
|
};
|
|
function computeAttributesStride(attributes) {
|
|
let stride = 0;
|
|
for (let i = 0; i < attributes.length; i++) {
|
|
const attr = attributes[i];
|
|
stride += attr.size * getByteSizeFromType(attr.type);
|
|
}
|
|
return stride;
|
|
}
|
|
function getByteSizeFromType(type) {
|
|
switch (type) {
|
|
case AttributeType.UNSIGNED_BYTE:
|
|
return Uint8Array.BYTES_PER_ELEMENT;
|
|
case AttributeType.UNSIGNED_SHORT:
|
|
return Uint16Array.BYTES_PER_ELEMENT;
|
|
case AttributeType.UNSIGNED_INT:
|
|
return Uint32Array.BYTES_PER_ELEMENT;
|
|
case AttributeType.FLOAT:
|
|
default:
|
|
return Float32Array.BYTES_PER_ELEMENT;
|
|
}
|
|
}
|
|
var Helper_default = WebGLHelper;
|
|
|
|
// node_modules/ol/webgl/BaseTileRepresentation.js
|
|
var BaseTileRepresentation = class extends Target_default {
|
|
/**
|
|
* @param {TileRepresentationOptions<TileType>} options The tile representation options.
|
|
*/
|
|
constructor(options) {
|
|
super();
|
|
this.tile;
|
|
this.handleTileChange_ = this.handleTileChange_.bind(this);
|
|
this.gutter = options.gutter || 0;
|
|
this.helper = options.helper;
|
|
this.loaded = false;
|
|
this.ready = false;
|
|
}
|
|
/**
|
|
* @param {TileType} tile Tile.
|
|
*/
|
|
setTile(tile) {
|
|
if (tile !== this.tile) {
|
|
if (this.tile) {
|
|
this.tile.removeEventListener(EventType_default.CHANGE, this.handleTileChange_);
|
|
}
|
|
this.tile = tile;
|
|
this.loaded = tile.getState() === TileState_default.LOADED;
|
|
if (this.loaded) {
|
|
this.uploadTile();
|
|
} else {
|
|
if (tile instanceof ImageTile_default) {
|
|
const image = tile.getImage();
|
|
if (image instanceof Image && !image.crossOrigin) {
|
|
image.crossOrigin = "anonymous";
|
|
}
|
|
}
|
|
tile.addEventListener(EventType_default.CHANGE, this.handleTileChange_);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* @abstract
|
|
* @protected
|
|
*/
|
|
uploadTile() {
|
|
abstract();
|
|
}
|
|
setReady() {
|
|
this.ready = true;
|
|
this.dispatchEvent(EventType_default.CHANGE);
|
|
}
|
|
handleTileChange_() {
|
|
if (this.tile.getState() === TileState_default.LOADED) {
|
|
this.loaded = true;
|
|
this.uploadTile();
|
|
}
|
|
}
|
|
/**
|
|
* @param {import("./Helper.js").default} helper The WebGL helper.
|
|
*/
|
|
setHelper(helper) {
|
|
this.helper = helper;
|
|
if (this.helper && this.loaded) {
|
|
this.uploadTile();
|
|
}
|
|
}
|
|
/**
|
|
* @override
|
|
*/
|
|
disposeInternal() {
|
|
this.setHelper(null);
|
|
this.tile.removeEventListener(EventType_default.CHANGE, this.handleTileChange_);
|
|
}
|
|
};
|
|
var BaseTileRepresentation_default = BaseTileRepresentation;
|
|
|
|
// node_modules/ol/webgl/TileTexture.js
|
|
function bindAndConfigure(gl, texture, interpolate) {
|
|
const resampleFilter = interpolate ? gl.LINEAR : gl.NEAREST;
|
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
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);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, resampleFilter);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, resampleFilter);
|
|
}
|
|
function uploadImageTexture(gl, texture, image, interpolate) {
|
|
bindAndConfigure(gl, texture, interpolate);
|
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
|
|
}
|
|
function uploadDataTexture(helper, texture, data, size, bandCount, interpolate) {
|
|
const gl = helper.getGL();
|
|
let textureType;
|
|
let canInterpolate;
|
|
if (data instanceof Float32Array) {
|
|
textureType = gl.FLOAT;
|
|
helper.getExtension("OES_texture_float");
|
|
const extension = helper.getExtension("OES_texture_float_linear");
|
|
canInterpolate = extension !== null;
|
|
} else {
|
|
textureType = gl.UNSIGNED_BYTE;
|
|
canInterpolate = true;
|
|
}
|
|
bindAndConfigure(gl, texture, interpolate && canInterpolate);
|
|
const bytesPerRow = data.byteLength / size[1];
|
|
let unpackAlignment = 1;
|
|
if (bytesPerRow % 8 === 0) {
|
|
unpackAlignment = 8;
|
|
} else if (bytesPerRow % 4 === 0) {
|
|
unpackAlignment = 4;
|
|
} else if (bytesPerRow % 2 === 0) {
|
|
unpackAlignment = 2;
|
|
}
|
|
let format;
|
|
switch (bandCount) {
|
|
case 1: {
|
|
format = gl.LUMINANCE;
|
|
break;
|
|
}
|
|
case 2: {
|
|
format = gl.LUMINANCE_ALPHA;
|
|
break;
|
|
}
|
|
case 3: {
|
|
format = gl.RGB;
|
|
break;
|
|
}
|
|
case 4: {
|
|
format = gl.RGBA;
|
|
break;
|
|
}
|
|
default: {
|
|
throw new Error(`Unsupported number of bands: ${bandCount}`);
|
|
}
|
|
}
|
|
const oldUnpackAlignment = gl.getParameter(gl.UNPACK_ALIGNMENT);
|
|
gl.pixelStorei(gl.UNPACK_ALIGNMENT, unpackAlignment);
|
|
gl.texImage2D(
|
|
gl.TEXTURE_2D,
|
|
0,
|
|
format,
|
|
size[0],
|
|
size[1],
|
|
0,
|
|
format,
|
|
textureType,
|
|
data
|
|
);
|
|
gl.pixelStorei(gl.UNPACK_ALIGNMENT, oldUnpackAlignment);
|
|
}
|
|
var pixelContext = null;
|
|
function createPixelContext() {
|
|
pixelContext = createCanvasContext2D(1, 1, void 0, {
|
|
willReadFrequently: true
|
|
});
|
|
}
|
|
var TileTexture = class extends BaseTileRepresentation_default {
|
|
/**
|
|
* @param {import("./BaseTileRepresentation.js").TileRepresentationOptions<TileType>} options The tile texture options.
|
|
*/
|
|
constructor(options) {
|
|
super(options);
|
|
this.textures = [];
|
|
this.renderSize_ = toSize(
|
|
options.grid.getTileSize(options.tile.tileCoord[0])
|
|
);
|
|
this.bandCount = NaN;
|
|
const coords = new Buffer_default(ARRAY_BUFFER, STATIC_DRAW);
|
|
coords.fromArray([
|
|
0,
|
|
// P0
|
|
1,
|
|
1,
|
|
// P1
|
|
1,
|
|
1,
|
|
// P2
|
|
0,
|
|
0,
|
|
// P3
|
|
0
|
|
]);
|
|
this.helper.flushBufferData(coords);
|
|
this.coords = coords;
|
|
this.setTile(options.tile);
|
|
}
|
|
/**
|
|
* @override
|
|
* @param {import("./Helper.js").default} helper The WebGL helper.
|
|
*/
|
|
setHelper(helper) {
|
|
var _a;
|
|
const gl = (_a = this.helper) == null ? void 0 : _a.getGL();
|
|
if (gl) {
|
|
this.helper.deleteBuffer(this.coords);
|
|
for (let i = 0; i < this.textures.length; ++i) {
|
|
gl.deleteTexture(this.textures[i]);
|
|
}
|
|
}
|
|
super.setHelper(helper);
|
|
if (helper) {
|
|
helper.flushBufferData(this.coords);
|
|
}
|
|
}
|
|
/**
|
|
* @override
|
|
*/
|
|
uploadTile() {
|
|
const helper = this.helper;
|
|
const gl = helper.getGL();
|
|
const tile = this.tile;
|
|
this.textures.length = 0;
|
|
let data;
|
|
if (tile instanceof ImageTile_default || tile instanceof Tile_default) {
|
|
data = tile.getImage();
|
|
} else {
|
|
data = tile.getData();
|
|
}
|
|
const image = asImageLike(data);
|
|
if (image) {
|
|
const texture = gl.createTexture();
|
|
this.textures.push(texture);
|
|
this.bandCount = 4;
|
|
uploadImageTexture(gl, texture, image, tile.interpolate);
|
|
this.setReady();
|
|
return;
|
|
}
|
|
data = asArrayLike(data);
|
|
const sourceTileSize = (
|
|
/** @type {DataTile} */
|
|
tile.getSize()
|
|
);
|
|
const pixelSize = [
|
|
sourceTileSize[0] + 2 * this.gutter,
|
|
sourceTileSize[1] + 2 * this.gutter
|
|
];
|
|
const isFloat = data instanceof Float32Array;
|
|
const pixelCount = pixelSize[0] * pixelSize[1];
|
|
const DataType = isFloat ? Float32Array : Uint8Array;
|
|
const bytesPerElement = DataType.BYTES_PER_ELEMENT;
|
|
const bytesPerRow = data.byteLength / pixelSize[1];
|
|
this.bandCount = Math.floor(bytesPerRow / bytesPerElement / pixelSize[0]);
|
|
const textureCount = Math.ceil(this.bandCount / 4);
|
|
if (textureCount === 1) {
|
|
const texture = gl.createTexture();
|
|
this.textures.push(texture);
|
|
uploadDataTexture(
|
|
helper,
|
|
texture,
|
|
data,
|
|
pixelSize,
|
|
this.bandCount,
|
|
tile.interpolate
|
|
);
|
|
this.setReady();
|
|
return;
|
|
}
|
|
const textureDataArrays = new Array(textureCount);
|
|
for (let textureIndex = 0; textureIndex < textureCount; ++textureIndex) {
|
|
const texture = gl.createTexture();
|
|
this.textures.push(texture);
|
|
const bandCount = textureIndex < textureCount - 1 ? 4 : (this.bandCount - 1) % 4 + 1;
|
|
textureDataArrays[textureIndex] = new DataType(pixelCount * bandCount);
|
|
}
|
|
let dataIndex = 0;
|
|
let rowOffset = 0;
|
|
const colCount = pixelSize[0] * this.bandCount;
|
|
for (let rowIndex = 0; rowIndex < pixelSize[1]; ++rowIndex) {
|
|
for (let colIndex = 0; colIndex < colCount; ++colIndex) {
|
|
const dataValue = data[rowOffset + colIndex];
|
|
const pixelIndex = Math.floor(dataIndex / this.bandCount);
|
|
const bandIndex = colIndex % this.bandCount;
|
|
const textureIndex = Math.floor(bandIndex / 4);
|
|
const textureData = textureDataArrays[textureIndex];
|
|
const bandCount = textureData.length / pixelCount;
|
|
const textureBandIndex = bandIndex % 4;
|
|
textureData[pixelIndex * bandCount + textureBandIndex] = dataValue;
|
|
++dataIndex;
|
|
}
|
|
rowOffset += bytesPerRow / bytesPerElement;
|
|
}
|
|
for (let textureIndex = 0; textureIndex < textureCount; ++textureIndex) {
|
|
const texture = this.textures[textureIndex];
|
|
const textureData = textureDataArrays[textureIndex];
|
|
const bandCount = textureData.length / pixelCount;
|
|
uploadDataTexture(
|
|
helper,
|
|
texture,
|
|
textureData,
|
|
pixelSize,
|
|
bandCount,
|
|
tile.interpolate
|
|
);
|
|
}
|
|
this.setReady();
|
|
}
|
|
/**
|
|
* @param {import("../DataTile.js").ImageLike} image The image.
|
|
* @param {number} renderCol The column index (in rendered tile space).
|
|
* @param {number} renderRow The row index (in rendered tile space).
|
|
* @return {Uint8ClampedArray|null} The data.
|
|
* @private
|
|
*/
|
|
getImagePixelData_(image, renderCol, renderRow) {
|
|
const gutter = this.gutter;
|
|
const renderWidth = this.renderSize_[0];
|
|
const renderHeight = this.renderSize_[1];
|
|
if (!pixelContext) {
|
|
createPixelContext();
|
|
}
|
|
pixelContext.clearRect(0, 0, 1, 1);
|
|
const sourceWidth = image.width;
|
|
const sourceHeight = image.height;
|
|
const sourceWidthWithoutGutter = sourceWidth - 2 * gutter;
|
|
const sourceHeightWithoutGutter = sourceHeight - 2 * gutter;
|
|
const sourceCol = gutter + Math.floor(sourceWidthWithoutGutter * (renderCol / renderWidth));
|
|
const sourceRow = gutter + Math.floor(sourceHeightWithoutGutter * (renderRow / renderHeight));
|
|
let data;
|
|
try {
|
|
pixelContext.drawImage(image, sourceCol, sourceRow, 1, 1, 0, 0, 1, 1);
|
|
data = pixelContext.getImageData(0, 0, 1, 1).data;
|
|
} catch {
|
|
pixelContext = null;
|
|
return null;
|
|
}
|
|
return data;
|
|
}
|
|
/**
|
|
* @param {import("../DataTile.js").ArrayLike} data The data.
|
|
* @param {import("../size.js").Size} sourceSize The size.
|
|
* @param {number} renderCol The column index (in rendered tile space).
|
|
* @param {number} renderRow The row index (in rendered tile space).
|
|
* @return {import("../DataTile.js").ArrayLike|null} The data.
|
|
* @private
|
|
*/
|
|
getArrayPixelData_(data, sourceSize, renderCol, renderRow) {
|
|
const gutter = this.gutter;
|
|
const renderWidth = this.renderSize_[0];
|
|
const renderHeight = this.renderSize_[1];
|
|
const sourceWidthWithoutGutter = sourceSize[0];
|
|
const sourceHeightWithoutGutter = sourceSize[1];
|
|
const sourceWidth = sourceWidthWithoutGutter + 2 * gutter;
|
|
const sourceHeight = sourceHeightWithoutGutter + 2 * gutter;
|
|
const sourceCol = gutter + Math.floor(sourceWidthWithoutGutter * (renderCol / renderWidth));
|
|
const sourceRow = gutter + Math.floor(sourceHeightWithoutGutter * (renderRow / renderHeight));
|
|
if (data instanceof DataView) {
|
|
const bytesPerPixel = data.byteLength / (sourceWidth * sourceHeight);
|
|
const offset2 = bytesPerPixel * (sourceRow * sourceWidth + sourceCol);
|
|
const buffer = data.buffer.slice(offset2, offset2 + bytesPerPixel);
|
|
return new DataView(buffer);
|
|
}
|
|
const offset = this.bandCount * (sourceRow * sourceWidth + sourceCol);
|
|
return data.slice(offset, offset + this.bandCount);
|
|
}
|
|
/**
|
|
* Get data for a pixel. If the tile is not loaded, null is returned.
|
|
* @param {number} renderCol The column index (in rendered tile space).
|
|
* @param {number} renderRow The row index (in rendered tile space).
|
|
* @return {import("../DataTile.js").ArrayLike|null} The data.
|
|
*/
|
|
getPixelData(renderCol, renderRow) {
|
|
if (!this.loaded) {
|
|
return null;
|
|
}
|
|
if (this.tile instanceof DataTile_default) {
|
|
const data = this.tile.getData();
|
|
const arrayData = asArrayLike(data);
|
|
if (arrayData) {
|
|
const sourceSize = this.tile.getSize();
|
|
return this.getArrayPixelData_(
|
|
arrayData,
|
|
sourceSize,
|
|
renderCol,
|
|
renderRow
|
|
);
|
|
}
|
|
return this.getImagePixelData_(asImageLike(data), renderCol, renderRow);
|
|
}
|
|
return this.getImagePixelData_(this.tile.getImage(), renderCol, renderRow);
|
|
}
|
|
};
|
|
var TileTexture_default = TileTexture;
|
|
|
|
// node_modules/ol/renderer/webgl/Layer.js
|
|
var WebGLLayerRenderer = class _WebGLLayerRenderer extends Layer_default {
|
|
/**
|
|
* @param {LayerType} layer Layer.
|
|
* @param {Options} [options] Options.
|
|
*/
|
|
constructor(layer, options) {
|
|
super(layer);
|
|
options = options || {};
|
|
this.inversePixelTransform_ = create();
|
|
this.postProcesses_ = options.postProcesses;
|
|
this.uniforms_ = options.uniforms;
|
|
this.helper;
|
|
this.onMapChanged_ = () => {
|
|
this.clearCache();
|
|
this.removeHelper();
|
|
};
|
|
layer.addChangeListener(Property_default.MAP, this.onMapChanged_);
|
|
this.dispatchPreComposeEvent = this.dispatchPreComposeEvent.bind(this);
|
|
this.dispatchPostComposeEvent = this.dispatchPostComposeEvent.bind(this);
|
|
}
|
|
/**
|
|
* @param {WebGLRenderingContext} context The WebGL rendering context.
|
|
* @param {import("../../Map.js").FrameState} frameState Frame state.
|
|
* @protected
|
|
*/
|
|
dispatchPreComposeEvent(context, frameState) {
|
|
const layer = this.getLayer();
|
|
if (layer.hasListener(EventType_default2.PRECOMPOSE)) {
|
|
const event = new Event_default(
|
|
EventType_default2.PRECOMPOSE,
|
|
void 0,
|
|
frameState,
|
|
context
|
|
);
|
|
layer.dispatchEvent(event);
|
|
}
|
|
}
|
|
/**
|
|
* @param {WebGLRenderingContext} context The WebGL rendering context.
|
|
* @param {import("../../Map.js").FrameState} frameState Frame state.
|
|
* @protected
|
|
*/
|
|
dispatchPostComposeEvent(context, frameState) {
|
|
const layer = this.getLayer();
|
|
if (layer.hasListener(EventType_default2.POSTCOMPOSE)) {
|
|
const event = new Event_default(
|
|
EventType_default2.POSTCOMPOSE,
|
|
void 0,
|
|
frameState,
|
|
context
|
|
);
|
|
layer.dispatchEvent(event);
|
|
}
|
|
}
|
|
/**
|
|
* Reset options (only handles uniforms).
|
|
* @param {Options} options Options.
|
|
*/
|
|
reset(options) {
|
|
this.uniforms_ = options.uniforms;
|
|
if (this.helper) {
|
|
this.helper.setUniforms(this.uniforms_);
|
|
}
|
|
}
|
|
/**
|
|
* @protected
|
|
*/
|
|
removeHelper() {
|
|
if (this.helper) {
|
|
this.helper.dispose();
|
|
delete this.helper;
|
|
}
|
|
}
|
|
/**
|
|
* Determine whether renderFrame should be called.
|
|
* @param {import("../../Map.js").FrameState} frameState Frame state.
|
|
* @return {boolean} Layer is ready to be rendered.
|
|
* @override
|
|
*/
|
|
prepareFrame(frameState) {
|
|
if (this.getLayer().getRenderSource()) {
|
|
let incrementGroup = true;
|
|
let groupNumber = -1;
|
|
let className;
|
|
for (let i = 0, ii = frameState.layerStatesArray.length; i < ii; i++) {
|
|
const layer = frameState.layerStatesArray[i].layer;
|
|
const renderer = layer.getRenderer();
|
|
if (!(renderer instanceof _WebGLLayerRenderer)) {
|
|
incrementGroup = true;
|
|
continue;
|
|
}
|
|
const layerClassName = layer.getClassName();
|
|
if (incrementGroup || layerClassName !== className) {
|
|
groupNumber += 1;
|
|
incrementGroup = false;
|
|
}
|
|
className = layerClassName;
|
|
if (renderer === this) {
|
|
break;
|
|
}
|
|
}
|
|
const canvasCacheKey = "map/" + frameState.mapId + "/group/" + groupNumber;
|
|
if (!this.helper || !this.helper.canvasCacheKeyMatches(canvasCacheKey) || this.helper.needsToBeRecreated()) {
|
|
this.removeHelper();
|
|
this.helper = new Helper_default({
|
|
postProcesses: this.postProcesses_,
|
|
uniforms: this.uniforms_,
|
|
canvasCacheKey
|
|
});
|
|
if (className) {
|
|
this.helper.getCanvas().className = className;
|
|
}
|
|
this.afterHelperCreated();
|
|
}
|
|
}
|
|
return this.prepareFrameInternal(frameState);
|
|
}
|
|
/**
|
|
* @protected
|
|
*/
|
|
afterHelperCreated() {
|
|
}
|
|
/**
|
|
* Determine whether renderFrame should be called.
|
|
* @param {import("../../Map.js").FrameState} frameState Frame state.
|
|
* @return {boolean} Layer is ready to be rendered.
|
|
* @protected
|
|
*/
|
|
prepareFrameInternal(frameState) {
|
|
return true;
|
|
}
|
|
/**
|
|
* @protected
|
|
*/
|
|
clearCache() {
|
|
}
|
|
/**
|
|
* Clean up.
|
|
* @override
|
|
*/
|
|
disposeInternal() {
|
|
var _a;
|
|
this.clearCache();
|
|
this.removeHelper();
|
|
(_a = this.getLayer()) == null ? void 0 : _a.removeChangeListener(
|
|
Property_default.MAP,
|
|
this.onMapChanged_
|
|
);
|
|
super.disposeInternal();
|
|
}
|
|
/**
|
|
* @param {import("../../render/EventType.js").default} type Event type.
|
|
* @param {WebGLRenderingContext} context The rendering context.
|
|
* @param {import("../../Map.js").FrameState} frameState Frame state.
|
|
* @private
|
|
*/
|
|
dispatchRenderEvent_(type, context, frameState) {
|
|
const layer = this.getLayer();
|
|
if (layer.hasListener(type)) {
|
|
compose(
|
|
this.inversePixelTransform_,
|
|
0,
|
|
0,
|
|
frameState.pixelRatio,
|
|
-frameState.pixelRatio,
|
|
0,
|
|
0,
|
|
-frameState.size[1]
|
|
);
|
|
const event = new Event_default(
|
|
type,
|
|
this.inversePixelTransform_,
|
|
frameState,
|
|
context
|
|
);
|
|
layer.dispatchEvent(event);
|
|
}
|
|
}
|
|
/**
|
|
* @param {WebGLRenderingContext} context The rendering context.
|
|
* @param {import("../../Map.js").FrameState} frameState Frame state.
|
|
* @protected
|
|
*/
|
|
preRender(context, frameState) {
|
|
this.dispatchRenderEvent_(EventType_default2.PRERENDER, context, frameState);
|
|
}
|
|
/**
|
|
* @param {WebGLRenderingContext} context The rendering context.
|
|
* @param {import("../../Map.js").FrameState} frameState Frame state.
|
|
* @protected
|
|
*/
|
|
postRender(context, frameState) {
|
|
this.dispatchRenderEvent_(EventType_default2.POSTRENDER, context, frameState);
|
|
}
|
|
};
|
|
var Layer_default2 = WebGLLayerRenderer;
|
|
|
|
// node_modules/ol/renderer/webgl/TileLayerBase.js
|
|
var Uniforms = {
|
|
TILE_TRANSFORM: "u_tileTransform",
|
|
TRANSITION_ALPHA: "u_transitionAlpha",
|
|
DEPTH: "u_depth",
|
|
RENDER_EXTENT: "u_renderExtent",
|
|
// intersection of layer, source, and view extent
|
|
PATTERN_ORIGIN: "u_patternOrigin",
|
|
RESOLUTION: "u_resolution",
|
|
ZOOM: "u_zoom",
|
|
GLOBAL_ALPHA: "u_globalAlpha",
|
|
PROJECTION_MATRIX: "u_projectionMatrix",
|
|
SCREEN_TO_WORLD_MATRIX: "u_screenToWorldMatrix"
|
|
};
|
|
function depthForZ(z) {
|
|
return 1 / (z + 2);
|
|
}
|
|
function newTileRepresentationLookup() {
|
|
return { tileIds: /* @__PURE__ */ new Set(), representationsByZ: {} };
|
|
}
|
|
function lookupHasTile(tileRepresentationLookup, tile) {
|
|
return tileRepresentationLookup.tileIds.has(getUid(tile));
|
|
}
|
|
function addTileRepresentationToLookup(tileRepresentationLookup, tileRepresentation, z) {
|
|
const representationsByZ = tileRepresentationLookup.representationsByZ;
|
|
if (!(z in representationsByZ)) {
|
|
representationsByZ[z] = /* @__PURE__ */ new Set();
|
|
}
|
|
representationsByZ[z].add(tileRepresentation);
|
|
tileRepresentationLookup.tileIds.add(getUid(tileRepresentation.tile));
|
|
}
|
|
function getRenderExtent(frameState, extent) {
|
|
const layerState = frameState.layerStatesArray[frameState.layerIndex];
|
|
if (layerState.extent) {
|
|
extent = getIntersection(
|
|
extent,
|
|
fromUserExtent(layerState.extent, frameState.viewState.projection)
|
|
);
|
|
}
|
|
const source = (
|
|
/** @type {import("../../source/Tile.js").default} */
|
|
layerState.layer.getRenderSource()
|
|
);
|
|
if (!source.getWrapX()) {
|
|
const gridExtent = source.getTileGridForProjection(frameState.viewState.projection).getExtent();
|
|
if (gridExtent) {
|
|
extent = getIntersection(extent, gridExtent);
|
|
}
|
|
}
|
|
return extent;
|
|
}
|
|
function getCacheKey(source, tileCoord) {
|
|
return `${getUid(source)},${source.getKey()},${source.getRevision()},${getKey(tileCoord)}`;
|
|
}
|
|
var WebGLBaseTileLayerRenderer = class extends Layer_default2 {
|
|
/**
|
|
* @param {LayerType} tileLayer Tile layer.
|
|
* @param {Options} options Options.
|
|
*/
|
|
constructor(tileLayer, options) {
|
|
super(tileLayer, {
|
|
uniforms: options.uniforms,
|
|
postProcesses: options.postProcesses
|
|
});
|
|
this.renderComplete = false;
|
|
this.tileTransform_ = create();
|
|
this.tempMat4 = create2();
|
|
this.tempTileRange_ = new TileRange_default(0, 0, 0, 0);
|
|
this.tempTileCoord_ = createOrUpdate(0, 0, 0);
|
|
this.tempSize_ = [0, 0];
|
|
const cacheSize = options.cacheSize !== void 0 ? options.cacheSize : 512;
|
|
this.tileRepresentationCache = new LRUCache_default(cacheSize);
|
|
this.frameState = null;
|
|
this.renderedProjection_ = void 0;
|
|
}
|
|
/**
|
|
* @param {Options} options Options.
|
|
* @override
|
|
*/
|
|
reset(options) {
|
|
super.reset({
|
|
uniforms: options.uniforms
|
|
});
|
|
}
|
|
/**
|
|
* Determine whether renderFrame should be called.
|
|
* @param {import("../../Map.js").FrameState} frameState Frame state.
|
|
* @return {boolean} Layer is ready to be rendered.
|
|
* @override
|
|
*/
|
|
prepareFrameInternal(frameState) {
|
|
if (!this.renderedProjection_) {
|
|
this.renderedProjection_ = frameState.viewState.projection;
|
|
} else if (frameState.viewState.projection !== this.renderedProjection_) {
|
|
this.clearCache();
|
|
this.renderedProjection_ = frameState.viewState.projection;
|
|
}
|
|
const layer = this.getLayer();
|
|
const source = layer.getRenderSource();
|
|
if (!source) {
|
|
return false;
|
|
}
|
|
if (isEmpty(getRenderExtent(frameState, frameState.extent))) {
|
|
return false;
|
|
}
|
|
return source.getState() === "ready";
|
|
}
|
|
/**
|
|
* @abstract
|
|
* @param {import("../../webgl/BaseTileRepresentation.js").TileRepresentationOptions<TileType>} options tile representation options
|
|
* @return {TileRepresentation} A new tile representation
|
|
* @protected
|
|
*/
|
|
createTileRepresentation(options) {
|
|
return abstract();
|
|
}
|
|
/**
|
|
* @param {import("../../Map.js").FrameState} frameState Frame state.
|
|
* @param {import("../../extent.js").Extent} extent The extent to be rendered.
|
|
* @param {number} initialZ The zoom level.
|
|
* @param {TileRepresentationLookup} tileRepresentationLookup The zoom level.
|
|
* @param {number} preload Number of additional levels to load.
|
|
*/
|
|
enqueueTiles(frameState, extent, initialZ, tileRepresentationLookup, preload) {
|
|
const viewState = frameState.viewState;
|
|
const tileLayer = this.getLayer();
|
|
const tileSource = tileLayer.getRenderSource();
|
|
const tileGrid = tileSource.getTileGridForProjection(viewState.projection);
|
|
const gutter = tileSource.getGutterForProjection(viewState.projection);
|
|
const tileSourceKey = getUid(tileSource);
|
|
if (!(tileSourceKey in frameState.wantedTiles)) {
|
|
frameState.wantedTiles[tileSourceKey] = {};
|
|
}
|
|
const wantedTiles = frameState.wantedTiles[tileSourceKey];
|
|
const tileRepresentationCache = this.tileRepresentationCache;
|
|
const map = tileLayer.getMapInternal();
|
|
const minZ = Math.max(
|
|
initialZ - preload,
|
|
tileGrid.getMinZoom(),
|
|
tileGrid.getZForResolution(
|
|
Math.min(
|
|
tileLayer.getMaxResolution(),
|
|
map ? map.getView().getResolutionForZoom(Math.max(tileLayer.getMinZoom(), 0)) : tileGrid.getResolution(0)
|
|
),
|
|
tileSource.zDirection
|
|
)
|
|
);
|
|
const rotation = viewState.rotation;
|
|
const viewport = rotation ? getRotatedViewport(
|
|
viewState.center,
|
|
viewState.resolution,
|
|
rotation,
|
|
frameState.size
|
|
) : void 0;
|
|
for (let z = initialZ; z >= minZ; --z) {
|
|
const tileRange = tileGrid.getTileRangeForExtentAndZ(
|
|
extent,
|
|
z,
|
|
this.tempTileRange_
|
|
);
|
|
const tileResolution = tileGrid.getResolution(z);
|
|
for (let x = tileRange.minX; x <= tileRange.maxX; ++x) {
|
|
for (let y = tileRange.minY; y <= tileRange.maxY; ++y) {
|
|
if (rotation && !tileGrid.tileCoordIntersectsViewport([z, x, y], viewport)) {
|
|
continue;
|
|
}
|
|
const tileCoord = createOrUpdate(z, x, y, this.tempTileCoord_);
|
|
const cacheKey = getCacheKey(tileSource, tileCoord);
|
|
let tileRepresentation;
|
|
let tile;
|
|
if (tileRepresentationCache.containsKey(cacheKey)) {
|
|
tileRepresentation = tileRepresentationCache.get(cacheKey);
|
|
tile = tileRepresentation.tile;
|
|
}
|
|
if (!tileRepresentation || tileRepresentation.tile.key !== tileSource.getKey()) {
|
|
tile = tileSource.getTile(
|
|
z,
|
|
x,
|
|
y,
|
|
frameState.pixelRatio,
|
|
viewState.projection
|
|
);
|
|
if (!tile) {
|
|
continue;
|
|
}
|
|
}
|
|
if (lookupHasTile(tileRepresentationLookup, tile)) {
|
|
continue;
|
|
}
|
|
if (!tileRepresentation) {
|
|
tileRepresentation = this.createTileRepresentation({
|
|
tile,
|
|
grid: tileGrid,
|
|
helper: this.helper,
|
|
gutter
|
|
});
|
|
tileRepresentationCache.set(cacheKey, tileRepresentation);
|
|
} else {
|
|
tileRepresentation.setTile(tile);
|
|
}
|
|
addTileRepresentationToLookup(
|
|
tileRepresentationLookup,
|
|
tileRepresentation,
|
|
z
|
|
);
|
|
const tileQueueKey = tile.getKey();
|
|
wantedTiles[tileQueueKey] = true;
|
|
if (tile.getState() === TileState_default.IDLE) {
|
|
if (!frameState.tileQueue.isKeyQueued(tileQueueKey)) {
|
|
frameState.tileQueue.enqueue([
|
|
tile,
|
|
tileSourceKey,
|
|
tileGrid.getTileCoordCenter(tileCoord),
|
|
tileResolution
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* @param {import("../../Map.js").FrameState} frameState Frame state.
|
|
* @param {boolean} tilesWithAlpha True if at least one of the rendered tiles has alpha
|
|
* @protected
|
|
*/
|
|
beforeTilesRender(frameState, tilesWithAlpha) {
|
|
this.helper.prepareDraw(this.frameState, !tilesWithAlpha, true);
|
|
}
|
|
/**
|
|
* @param {import("../../Map.js").FrameState} frameState Frame state.
|
|
* @return {boolean} If returns false, tile mask rendering will be skipped
|
|
* @protected
|
|
*/
|
|
beforeTilesMaskRender(frameState) {
|
|
return false;
|
|
}
|
|
/**
|
|
* @param {TileRepresentation} tileRepresentation Tile representation
|
|
* @param {import("../../transform.js").Transform} tileTransform Tile transform
|
|
* @param {import("../../Map.js").FrameState} frameState Frame state
|
|
* @param {import("../../extent.js").Extent} renderExtent Render extent
|
|
* @param {number} tileResolution Tile resolution
|
|
* @param {import("../../size.js").Size} tileSize Tile size
|
|
* @param {import("../../coordinate.js").Coordinate} tileOrigin Tile origin
|
|
* @param {import("../../extent.js").Extent} tileExtent tile Extent
|
|
* @param {number} depth Depth
|
|
* @param {number} gutter Gutter
|
|
* @param {number} alpha Alpha
|
|
* @protected
|
|
*/
|
|
renderTile(tileRepresentation, tileTransform, frameState, renderExtent, tileResolution, tileSize, tileOrigin, tileExtent, depth, gutter, alpha) {
|
|
}
|
|
/**
|
|
* @param {TileRepresentation} tileRepresentation Tile representation
|
|
* @param {number} tileZ Tile Z
|
|
* @param {import("../../extent.js").Extent} extent Render extent
|
|
* @param {number} depth Depth
|
|
* @protected
|
|
*/
|
|
renderTileMask(tileRepresentation, tileZ, extent, depth) {
|
|
}
|
|
drawTile_(frameState, tileRepresentation, tileZ, gutter, extent, alphaLookup, tileGrid) {
|
|
if (!tileRepresentation.ready) {
|
|
return;
|
|
}
|
|
const tile = tileRepresentation.tile;
|
|
const tileCoord = tile.tileCoord;
|
|
const tileCoordKey = getKey(tileCoord);
|
|
const alpha = tileCoordKey in alphaLookup ? alphaLookup[tileCoordKey] : 1;
|
|
const tileResolution = tileGrid.getResolution(tileZ);
|
|
const tileSize = toSize(tileGrid.getTileSize(tileZ), this.tempSize_);
|
|
const tileOrigin = tileGrid.getOrigin(tileZ);
|
|
const tileExtent = tileGrid.getTileCoordExtent(tileCoord);
|
|
const depth = alpha < 1 ? -1 : depthForZ(tileZ);
|
|
if (alpha < 1) {
|
|
frameState.animate = true;
|
|
}
|
|
const viewState = frameState.viewState;
|
|
const centerX = viewState.center[0];
|
|
const centerY = viewState.center[1];
|
|
const tileWidthWithGutter = tileSize[0] + 2 * gutter;
|
|
const tileHeightWithGutter = tileSize[1] + 2 * gutter;
|
|
const aspectRatio = tileWidthWithGutter / tileHeightWithGutter;
|
|
const centerI = (centerX - tileOrigin[0]) / (tileSize[0] * tileResolution);
|
|
const centerJ = (tileOrigin[1] - centerY) / (tileSize[1] * tileResolution);
|
|
const tileScale = viewState.resolution / tileResolution;
|
|
const tileCenterI = tileCoord[1];
|
|
const tileCenterJ = tileCoord[2];
|
|
reset(this.tileTransform_);
|
|
scale(
|
|
this.tileTransform_,
|
|
2 / (frameState.size[0] * tileScale / tileWidthWithGutter),
|
|
-2 / (frameState.size[1] * tileScale / tileWidthWithGutter)
|
|
);
|
|
rotate(this.tileTransform_, viewState.rotation);
|
|
scale(this.tileTransform_, 1, 1 / aspectRatio);
|
|
translate(
|
|
this.tileTransform_,
|
|
(tileSize[0] * (tileCenterI - centerI) - gutter) / tileWidthWithGutter,
|
|
(tileSize[1] * (tileCenterJ - centerJ) - gutter) / tileHeightWithGutter
|
|
);
|
|
this.renderTile(
|
|
/** @type {TileRepresentation} */
|
|
tileRepresentation,
|
|
this.tileTransform_,
|
|
frameState,
|
|
extent,
|
|
tileResolution,
|
|
tileSize,
|
|
tileOrigin,
|
|
tileExtent,
|
|
depth,
|
|
gutter,
|
|
alpha
|
|
);
|
|
}
|
|
/**
|
|
* Render the layer.
|
|
* @param {import("../../Map.js").FrameState} frameState Frame state.
|
|
* @return {HTMLElement} The rendered element.
|
|
* @override
|
|
*/
|
|
renderFrame(frameState) {
|
|
this.frameState = frameState;
|
|
this.renderComplete = true;
|
|
const gl = this.helper.getGL();
|
|
this.preRender(gl, frameState);
|
|
const viewState = frameState.viewState;
|
|
const tileLayer = this.getLayer();
|
|
const tileSource = tileLayer.getRenderSource();
|
|
const tileGrid = tileSource.getTileGridForProjection(viewState.projection);
|
|
const gutter = tileSource.getGutterForProjection(viewState.projection);
|
|
const extent = getRenderExtent(frameState, frameState.extent);
|
|
const z = tileGrid.getZForResolution(
|
|
viewState.resolution,
|
|
tileSource.zDirection
|
|
);
|
|
const tileRepresentationLookup = newTileRepresentationLookup();
|
|
const preload = tileLayer.getPreload();
|
|
if (frameState.nextExtent) {
|
|
const targetZ = tileGrid.getZForResolution(
|
|
viewState.nextResolution,
|
|
tileSource.zDirection
|
|
);
|
|
const nextExtent = getRenderExtent(frameState, frameState.nextExtent);
|
|
this.enqueueTiles(
|
|
frameState,
|
|
nextExtent,
|
|
targetZ,
|
|
tileRepresentationLookup,
|
|
preload
|
|
);
|
|
}
|
|
this.enqueueTiles(frameState, extent, z, tileRepresentationLookup, 0);
|
|
if (preload > 0) {
|
|
setTimeout(() => {
|
|
this.enqueueTiles(
|
|
frameState,
|
|
extent,
|
|
z - 1,
|
|
tileRepresentationLookup,
|
|
preload - 1
|
|
);
|
|
}, 0);
|
|
}
|
|
const alphaLookup = {};
|
|
let blend = false;
|
|
const representationsByZ = tileRepresentationLookup.representationsByZ;
|
|
if (z in representationsByZ) {
|
|
const uid = getUid(this);
|
|
const time = frameState.time;
|
|
for (const tileRepresentation of representationsByZ[z]) {
|
|
const tile = tileRepresentation.tile;
|
|
if (tile.getState() === TileState_default.EMPTY) {
|
|
continue;
|
|
}
|
|
const tileCoord = tile.tileCoord;
|
|
if (tileRepresentation.ready) {
|
|
const alpha = tile.getAlpha(uid, time);
|
|
if (alpha === 1) {
|
|
tile.endTransition(uid);
|
|
continue;
|
|
}
|
|
blend = true;
|
|
const tileCoordKey = getKey(tileCoord);
|
|
alphaLookup[tileCoordKey] = alpha;
|
|
}
|
|
this.renderComplete = false;
|
|
const coveredByChildren = this.findAltTiles_(
|
|
tileGrid,
|
|
tileCoord,
|
|
z + 1,
|
|
tileRepresentationLookup
|
|
);
|
|
if (coveredByChildren) {
|
|
continue;
|
|
}
|
|
const minZoom = tileGrid.getMinZoom();
|
|
for (let parentZ = z - 1; parentZ >= minZoom; --parentZ) {
|
|
const coveredByParent = this.findAltTiles_(
|
|
tileGrid,
|
|
tileCoord,
|
|
parentZ,
|
|
tileRepresentationLookup
|
|
);
|
|
if (coveredByParent) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
const zs = Object.keys(representationsByZ).map(Number).sort(descending);
|
|
const renderTileMask = this.beforeTilesMaskRender(frameState);
|
|
if (renderTileMask) {
|
|
for (let j = 0, jj = zs.length; j < jj; ++j) {
|
|
const tileZ = zs[j];
|
|
for (const tileRepresentation of representationsByZ[tileZ]) {
|
|
const tileCoord = tileRepresentation.tile.tileCoord;
|
|
const tileCoordKey = getKey(tileCoord);
|
|
if (tileCoordKey in alphaLookup) {
|
|
continue;
|
|
}
|
|
const tileExtent = tileGrid.getTileCoordExtent(tileCoord);
|
|
this.renderTileMask(
|
|
/** @type {TileRepresentation} */
|
|
tileRepresentation,
|
|
tileZ,
|
|
tileExtent,
|
|
depthForZ(tileZ)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
this.beforeTilesRender(frameState, blend);
|
|
for (let j = 0, jj = zs.length; j < jj; ++j) {
|
|
const tileZ = zs[j];
|
|
for (const tileRepresentation of representationsByZ[tileZ]) {
|
|
const tileCoord = tileRepresentation.tile.tileCoord;
|
|
const tileCoordKey = getKey(tileCoord);
|
|
if (tileCoordKey in alphaLookup) {
|
|
continue;
|
|
}
|
|
this.drawTile_(
|
|
frameState,
|
|
tileRepresentation,
|
|
tileZ,
|
|
gutter,
|
|
extent,
|
|
alphaLookup,
|
|
tileGrid
|
|
);
|
|
}
|
|
}
|
|
if (z in representationsByZ) {
|
|
for (const tileRepresentation of representationsByZ[z]) {
|
|
const tileCoord = tileRepresentation.tile.tileCoord;
|
|
const tileCoordKey = getKey(tileCoord);
|
|
if (tileCoordKey in alphaLookup) {
|
|
this.drawTile_(
|
|
frameState,
|
|
tileRepresentation,
|
|
z,
|
|
gutter,
|
|
extent,
|
|
alphaLookup,
|
|
tileGrid
|
|
);
|
|
}
|
|
}
|
|
}
|
|
this.beforeFinalize(frameState);
|
|
this.helper.finalizeDraw(
|
|
frameState,
|
|
this.dispatchPreComposeEvent,
|
|
this.dispatchPostComposeEvent
|
|
);
|
|
const canvas = this.helper.getCanvas();
|
|
const tileRepresentationCache = this.tileRepresentationCache;
|
|
while (tileRepresentationCache.canExpireCache()) {
|
|
const tileRepresentation = tileRepresentationCache.pop();
|
|
tileRepresentation.dispose();
|
|
}
|
|
this.postRender(gl, frameState);
|
|
return canvas;
|
|
}
|
|
/**
|
|
* @param {import("../../Map.js").FrameState} frameState Frame state.
|
|
* @protected
|
|
*/
|
|
beforeFinalize(frameState) {
|
|
return;
|
|
}
|
|
/**
|
|
* Look for tiles covering the provided tile coordinate at an alternate
|
|
* zoom level. Loaded tiles will be added to the provided tile representation lookup.
|
|
* @param {import("../../tilegrid/TileGrid.js").default} tileGrid The tile grid.
|
|
* @param {import("../../tilecoord.js").TileCoord} tileCoord The target tile coordinate.
|
|
* @param {number} altZ The alternate zoom level.
|
|
* @param {TileRepresentationLookup} tileRepresentationLookup Lookup of
|
|
* tile representations by zoom level.
|
|
* @return {boolean} The tile coordinate is covered by loaded tiles at the alternate zoom level.
|
|
* @private
|
|
*/
|
|
findAltTiles_(tileGrid, tileCoord, altZ, tileRepresentationLookup) {
|
|
const tileRange = tileGrid.getTileRangeForTileCoordAndZ(
|
|
tileCoord,
|
|
altZ,
|
|
this.tempTileRange_
|
|
);
|
|
if (!tileRange) {
|
|
return false;
|
|
}
|
|
let covered = true;
|
|
const tileRepresentationCache = this.tileRepresentationCache;
|
|
const source = this.getLayer().getRenderSource();
|
|
for (let x = tileRange.minX; x <= tileRange.maxX; ++x) {
|
|
for (let y = tileRange.minY; y <= tileRange.maxY; ++y) {
|
|
const cacheKey = getCacheKey(source, [altZ, x, y]);
|
|
let loaded = false;
|
|
if (tileRepresentationCache.containsKey(cacheKey)) {
|
|
const tileRepresentation = tileRepresentationCache.get(cacheKey);
|
|
if (tileRepresentation.ready && !lookupHasTile(tileRepresentationLookup, tileRepresentation.tile)) {
|
|
addTileRepresentationToLookup(
|
|
tileRepresentationLookup,
|
|
tileRepresentation,
|
|
altZ
|
|
);
|
|
loaded = true;
|
|
}
|
|
}
|
|
if (!loaded) {
|
|
covered = false;
|
|
}
|
|
}
|
|
}
|
|
return covered;
|
|
}
|
|
/**
|
|
* @override
|
|
*/
|
|
clearCache() {
|
|
super.clearCache();
|
|
const tileRepresentationCache = this.tileRepresentationCache;
|
|
tileRepresentationCache.forEach(
|
|
(tileRepresentation) => tileRepresentation.dispose()
|
|
);
|
|
tileRepresentationCache.clear();
|
|
}
|
|
/**
|
|
* @override
|
|
*/
|
|
afterHelperCreated() {
|
|
super.afterHelperCreated();
|
|
this.tileRepresentationCache.forEach(
|
|
(tileRepresentation) => tileRepresentation.setHelper(this.helper)
|
|
);
|
|
}
|
|
/**
|
|
* Clean up.
|
|
* @override
|
|
*/
|
|
disposeInternal() {
|
|
super.disposeInternal();
|
|
delete this.frameState;
|
|
}
|
|
};
|
|
var TileLayerBase_default = WebGLBaseTileLayerRenderer;
|
|
|
|
// node_modules/ol/renderer/webgl/TileLayer.js
|
|
var Uniforms2 = {
|
|
...Uniforms,
|
|
TILE_TEXTURE_ARRAY: "u_tileTextures",
|
|
TEXTURE_PIXEL_WIDTH: "u_texturePixelWidth",
|
|
TEXTURE_PIXEL_HEIGHT: "u_texturePixelHeight",
|
|
TEXTURE_RESOLUTION: "u_textureResolution",
|
|
// map units per texture pixel
|
|
TEXTURE_ORIGIN_X: "u_textureOriginX",
|
|
// map x coordinate of left edge of texture
|
|
TEXTURE_ORIGIN_Y: "u_textureOriginY"
|
|
// map y coordinate of top edge of texture
|
|
};
|
|
var Attributes = {
|
|
TEXTURE_COORD: "a_textureCoord"
|
|
};
|
|
var attributeDescriptions = [
|
|
{
|
|
name: Attributes.TEXTURE_COORD,
|
|
size: 2,
|
|
type: AttributeType.FLOAT
|
|
}
|
|
];
|
|
var WebGLTileLayerRenderer = class extends TileLayerBase_default {
|
|
/**
|
|
* @param {LayerType} tileLayer Tile layer.
|
|
* @param {Options} options Options.
|
|
*/
|
|
constructor(tileLayer, options) {
|
|
super(tileLayer, options);
|
|
this.program_;
|
|
this.vertexShader_ = options.vertexShader;
|
|
this.fragmentShader_ = options.fragmentShader;
|
|
this.indices_ = new Buffer_default(ELEMENT_ARRAY_BUFFER, STATIC_DRAW);
|
|
this.indices_.fromArray([0, 1, 3, 1, 2, 3]);
|
|
this.paletteTextures_ = options.paletteTextures || [];
|
|
}
|
|
/**
|
|
* @param {Options} options Options.
|
|
* @override
|
|
*/
|
|
reset(options) {
|
|
super.reset(options);
|
|
if (this.helper) {
|
|
const gl = this.helper.getGL();
|
|
for (const paletteTexture of this.paletteTextures_) {
|
|
paletteTexture.delete(gl);
|
|
}
|
|
}
|
|
this.vertexShader_ = options.vertexShader;
|
|
this.fragmentShader_ = options.fragmentShader;
|
|
this.paletteTextures_ = options.paletteTextures || [];
|
|
if (this.helper) {
|
|
this.program_ = this.helper.getProgram(
|
|
this.fragmentShader_,
|
|
this.vertexShader_
|
|
);
|
|
const gl = this.helper.getGL();
|
|
for (const paletteTexture of this.paletteTextures_) {
|
|
paletteTexture.getTexture(gl);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* @override
|
|
*/
|
|
afterHelperCreated() {
|
|
super.afterHelperCreated();
|
|
const gl = this.helper.getGL();
|
|
for (const paletteTexture of this.paletteTextures_) {
|
|
paletteTexture.getTexture(gl);
|
|
}
|
|
this.program_ = this.helper.getProgram(
|
|
this.fragmentShader_,
|
|
this.vertexShader_
|
|
);
|
|
this.helper.flushBufferData(this.indices_);
|
|
}
|
|
/**
|
|
* @override
|
|
*/
|
|
removeHelper() {
|
|
if (this.helper) {
|
|
const gl = this.helper.getGL();
|
|
for (const paletteTexture of this.paletteTextures_) {
|
|
paletteTexture.delete(gl);
|
|
}
|
|
}
|
|
super.removeHelper();
|
|
}
|
|
/**
|
|
* @override
|
|
*/
|
|
createTileRepresentation(options) {
|
|
return new TileTexture_default(options);
|
|
}
|
|
/**
|
|
* @override
|
|
*/
|
|
beforeTilesRender(frameState, tilesWithAlpha) {
|
|
super.beforeTilesRender(frameState, tilesWithAlpha);
|
|
this.helper.useProgram(this.program_, frameState);
|
|
}
|
|
/**
|
|
* @override
|
|
*/
|
|
renderTile(tileTexture, tileTransform, frameState, renderExtent, tileResolution, tileSize, tileOrigin, tileExtent, depth, gutter, alpha) {
|
|
const gl = this.helper.getGL();
|
|
this.helper.bindBuffer(tileTexture.coords);
|
|
this.helper.bindBuffer(this.indices_);
|
|
this.helper.enableAttributes(attributeDescriptions);
|
|
let textureSlot = 0;
|
|
while (textureSlot < tileTexture.textures.length) {
|
|
const uniformName = `${Uniforms2.TILE_TEXTURE_ARRAY}[${textureSlot}]`;
|
|
this.helper.bindTexture(
|
|
tileTexture.textures[textureSlot],
|
|
textureSlot,
|
|
uniformName
|
|
);
|
|
++textureSlot;
|
|
}
|
|
for (let paletteIndex = 0; paletteIndex < this.paletteTextures_.length; ++paletteIndex) {
|
|
const paletteTexture = this.paletteTextures_[paletteIndex];
|
|
const texture = paletteTexture.getTexture(gl);
|
|
this.helper.bindTexture(texture, textureSlot, paletteTexture.name);
|
|
++textureSlot;
|
|
}
|
|
const viewState = frameState.viewState;
|
|
const tileWidthWithGutter = tileSize[0] + 2 * gutter;
|
|
const tileHeightWithGutter = tileSize[1] + 2 * gutter;
|
|
const tile = tileTexture.tile;
|
|
const tileCoord = tile.tileCoord;
|
|
const tileCenterI = tileCoord[1];
|
|
const tileCenterJ = tileCoord[2];
|
|
this.helper.setUniformMatrixValue(
|
|
Uniforms2.TILE_TRANSFORM,
|
|
fromTransform(this.tempMat4, tileTransform)
|
|
);
|
|
this.helper.setUniformFloatValue(Uniforms2.TRANSITION_ALPHA, alpha);
|
|
this.helper.setUniformFloatValue(Uniforms2.DEPTH, depth);
|
|
let gutterExtent = renderExtent;
|
|
if (gutter > 0) {
|
|
gutterExtent = tileExtent;
|
|
getIntersection(gutterExtent, renderExtent, gutterExtent);
|
|
}
|
|
this.helper.setUniformFloatVec4(Uniforms2.RENDER_EXTENT, gutterExtent);
|
|
this.helper.setUniformFloatValue(Uniforms2.RESOLUTION, viewState.resolution);
|
|
this.helper.setUniformFloatValue(Uniforms2.ZOOM, viewState.zoom);
|
|
this.helper.setUniformFloatValue(
|
|
Uniforms2.TEXTURE_PIXEL_WIDTH,
|
|
tileWidthWithGutter
|
|
);
|
|
this.helper.setUniformFloatValue(
|
|
Uniforms2.TEXTURE_PIXEL_HEIGHT,
|
|
tileHeightWithGutter
|
|
);
|
|
this.helper.setUniformFloatValue(
|
|
Uniforms2.TEXTURE_RESOLUTION,
|
|
tileResolution
|
|
);
|
|
this.helper.setUniformFloatValue(
|
|
Uniforms2.TEXTURE_ORIGIN_X,
|
|
tileOrigin[0] + tileCenterI * tileSize[0] * tileResolution - gutter * tileResolution
|
|
);
|
|
this.helper.setUniformFloatValue(
|
|
Uniforms2.TEXTURE_ORIGIN_Y,
|
|
tileOrigin[1] - tileCenterJ * tileSize[1] * tileResolution + gutter * tileResolution
|
|
);
|
|
this.helper.drawElements(0, this.indices_.getSize());
|
|
}
|
|
/**
|
|
* @param {import("../../pixel.js").Pixel} pixel Pixel.
|
|
* @return {Uint8ClampedArray|Uint8Array|Float32Array|DataView} Data at the pixel location.
|
|
* @override
|
|
*/
|
|
getData(pixel) {
|
|
const gl = this.helper.getGL();
|
|
if (!gl) {
|
|
return null;
|
|
}
|
|
const frameState = this.frameState;
|
|
if (!frameState) {
|
|
return null;
|
|
}
|
|
const layer = this.getLayer();
|
|
const coordinate = apply(
|
|
frameState.pixelToCoordinateTransform,
|
|
pixel.slice()
|
|
);
|
|
const viewState = frameState.viewState;
|
|
const layerExtent = layer.getExtent();
|
|
if (layerExtent) {
|
|
if (!containsCoordinate(
|
|
fromUserExtent(layerExtent, viewState.projection),
|
|
coordinate
|
|
)) {
|
|
return null;
|
|
}
|
|
}
|
|
const sources = layer.getSources(
|
|
boundingExtent([coordinate]),
|
|
viewState.resolution
|
|
);
|
|
let i, source, tileGrid;
|
|
for (i = sources.length - 1; i >= 0; --i) {
|
|
source = sources[i];
|
|
if (source.getState() === "ready") {
|
|
tileGrid = source.getTileGridForProjection(viewState.projection);
|
|
if (source.getWrapX()) {
|
|
break;
|
|
}
|
|
const gridExtent = tileGrid.getExtent();
|
|
if (!gridExtent || containsCoordinate(gridExtent, coordinate)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (i < 0) {
|
|
return null;
|
|
}
|
|
const tileTextureCache = this.tileRepresentationCache;
|
|
for (let z = tileGrid.getZForResolution(viewState.resolution); z >= tileGrid.getMinZoom(); --z) {
|
|
const tileCoord = tileGrid.getTileCoordForCoordAndZ(coordinate, z);
|
|
const cacheKey = getCacheKey(source, tileCoord);
|
|
if (!tileTextureCache.containsKey(cacheKey)) {
|
|
continue;
|
|
}
|
|
const tileTexture = tileTextureCache.get(cacheKey);
|
|
const tile = tileTexture.tile;
|
|
if (tile.getState() === TileState_default.EMPTY) {
|
|
return null;
|
|
}
|
|
if (!tileTexture.loaded) {
|
|
continue;
|
|
}
|
|
const tileOrigin = tileGrid.getOrigin(z);
|
|
const tileSize = toSize(tileGrid.getTileSize(z));
|
|
const tileResolution = tileGrid.getResolution(z);
|
|
const col = (coordinate[0] - tileOrigin[0]) / tileResolution - tileCoord[1] * tileSize[0];
|
|
const row = (tileOrigin[1] - coordinate[1]) / tileResolution - tileCoord[2] * tileSize[1];
|
|
return tileTexture.getPixelData(col, row);
|
|
}
|
|
return null;
|
|
}
|
|
/**
|
|
* Clean up.
|
|
* @override
|
|
*/
|
|
disposeInternal() {
|
|
const helper = this.helper;
|
|
if (helper) {
|
|
const gl = helper.getGL();
|
|
for (const paletteTexture of this.paletteTextures_) {
|
|
paletteTexture.delete(gl);
|
|
}
|
|
this.paletteTextures_.length = 0;
|
|
gl.deleteProgram(this.program_);
|
|
delete this.program_;
|
|
helper.deleteBuffer(this.indices_);
|
|
}
|
|
super.disposeInternal();
|
|
delete this.indices_;
|
|
}
|
|
};
|
|
var TileLayer_default = WebGLTileLayerRenderer;
|
|
|
|
// node_modules/ol/webgl/PaletteTexture.js
|
|
var PaletteTexture = class {
|
|
/**
|
|
* @param {string} name The name of the texture.
|
|
* @param {Uint8Array} data The texture data.
|
|
*/
|
|
constructor(name, data) {
|
|
this.name = name;
|
|
this.data = data;
|
|
this.texture_ = null;
|
|
}
|
|
/**
|
|
* @param {WebGLRenderingContext} gl Rendering context.
|
|
* @return {WebGLTexture} The texture.
|
|
*/
|
|
getTexture(gl) {
|
|
if (!this.texture_) {
|
|
const texture = gl.createTexture();
|
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
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);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
gl.texImage2D(
|
|
gl.TEXTURE_2D,
|
|
0,
|
|
gl.RGBA,
|
|
this.data.length / 4,
|
|
1,
|
|
0,
|
|
gl.RGBA,
|
|
gl.UNSIGNED_BYTE,
|
|
this.data
|
|
);
|
|
this.texture_ = texture;
|
|
}
|
|
return this.texture_;
|
|
}
|
|
/**
|
|
* @param {WebGLRenderingContext} gl Rendering context.
|
|
*/
|
|
delete(gl) {
|
|
if (this.texture_) {
|
|
gl.deleteTexture(this.texture_);
|
|
}
|
|
this.texture_ = null;
|
|
}
|
|
};
|
|
var PaletteTexture_default = PaletteTexture;
|
|
|
|
// node_modules/ol/expr/gpu.js
|
|
function computeOperatorFunctionName(operator, context) {
|
|
return `operator_${operator}_${Object.keys(context.functions).length}`;
|
|
}
|
|
function numberToGlsl(v) {
|
|
const s = v.toString();
|
|
return s.includes(".") ? s : s + ".0";
|
|
}
|
|
function arrayToGlsl(array) {
|
|
if (array.length < 2 || array.length > 4) {
|
|
throw new Error(
|
|
"`formatArray` can only output `vec2`, `vec3` or `vec4` arrays."
|
|
);
|
|
}
|
|
return `vec${array.length}(${array.map(numberToGlsl).join(", ")})`;
|
|
}
|
|
function colorToGlsl(color) {
|
|
const array = asArray(color);
|
|
const alpha = array.length > 3 ? array[3] : 1;
|
|
return arrayToGlsl([array[0] / 255, array[1] / 255, array[2] / 255, alpha]);
|
|
}
|
|
function sizeToGlsl(size) {
|
|
const array = toSize(size);
|
|
return arrayToGlsl(array);
|
|
}
|
|
var stringToFloatMap = {};
|
|
var stringToFloatCounter = 0;
|
|
function getStringNumberEquivalent(string) {
|
|
if (!(string in stringToFloatMap)) {
|
|
stringToFloatMap[string] = stringToFloatCounter++;
|
|
}
|
|
return stringToFloatMap[string];
|
|
}
|
|
function stringToGlsl(string) {
|
|
return numberToGlsl(getStringNumberEquivalent(string));
|
|
}
|
|
function uniformNameForVariable(variableName) {
|
|
return "u_var_" + variableName;
|
|
}
|
|
function newCompilationContext() {
|
|
return {
|
|
variables: {},
|
|
properties: {},
|
|
functions: {},
|
|
bandCount: 0,
|
|
featureId: false,
|
|
geometryType: false
|
|
};
|
|
}
|
|
var GET_BAND_VALUE_FUNC = "getBandValue";
|
|
var PALETTE_TEXTURE_ARRAY = "u_paletteTextures";
|
|
var FEATURE_ID_PROPERTY_NAME = "featureId";
|
|
var GEOMETRY_TYPE_PROPERTY_NAME = "geometryType";
|
|
var UNDEFINED_PROP_VALUE = -9999999;
|
|
function buildExpression(encoded, type, parsingContext, compilationContext) {
|
|
const expression = parse(encoded, type, parsingContext);
|
|
return compile(expression, type, compilationContext);
|
|
}
|
|
function createCompiler(output) {
|
|
return (context, expression, type) => {
|
|
const length = expression.args.length;
|
|
const args = new Array(length);
|
|
for (let i = 0; i < length; ++i) {
|
|
args[i] = compile(expression.args[i], type, context);
|
|
}
|
|
return output(args, context);
|
|
};
|
|
}
|
|
var compilers = {
|
|
[Ops.Get]: (context, expression) => {
|
|
const firstArg = (
|
|
/** @type {LiteralExpression} */
|
|
expression.args[0]
|
|
);
|
|
const propName = (
|
|
/** @type {string} */
|
|
firstArg.value
|
|
);
|
|
const isExisting = propName in context.properties;
|
|
if (!isExisting) {
|
|
context.properties[propName] = {
|
|
name: propName,
|
|
type: expression.type
|
|
};
|
|
}
|
|
return "a_prop_" + propName;
|
|
},
|
|
[Ops.Id]: (context) => {
|
|
context.featureId = true;
|
|
return "a_" + FEATURE_ID_PROPERTY_NAME;
|
|
},
|
|
[Ops.GeometryType]: (context) => {
|
|
context.geometryType = true;
|
|
return "a_" + GEOMETRY_TYPE_PROPERTY_NAME;
|
|
},
|
|
[Ops.LineMetric]: () => "currentLineMetric",
|
|
// this variable is assumed to always be present in shaders, default is 0.
|
|
[Ops.Var]: (context, expression) => {
|
|
const firstArg = (
|
|
/** @type {LiteralExpression} */
|
|
expression.args[0]
|
|
);
|
|
const varName = (
|
|
/** @type {string} */
|
|
firstArg.value
|
|
);
|
|
const isExisting = varName in context.variables;
|
|
if (!isExisting) {
|
|
context.variables[varName] = {
|
|
name: varName,
|
|
type: expression.type
|
|
};
|
|
}
|
|
return uniformNameForVariable(varName);
|
|
},
|
|
[Ops.Has]: (context, expression) => {
|
|
const firstArg = (
|
|
/** @type {LiteralExpression} */
|
|
expression.args[0]
|
|
);
|
|
const propName = (
|
|
/** @type {string} */
|
|
firstArg.value
|
|
);
|
|
const isExisting = propName in context.properties;
|
|
if (!isExisting) {
|
|
context.properties[propName] = {
|
|
name: propName,
|
|
type: expression.type
|
|
};
|
|
}
|
|
return `(a_prop_${propName} != ${numberToGlsl(UNDEFINED_PROP_VALUE)})`;
|
|
},
|
|
[Ops.Resolution]: () => "u_resolution",
|
|
[Ops.Zoom]: () => "u_zoom",
|
|
[Ops.Time]: () => "u_time",
|
|
[Ops.Any]: createCompiler((compiledArgs) => `(${compiledArgs.join(` || `)})`),
|
|
[Ops.All]: createCompiler((compiledArgs) => `(${compiledArgs.join(` && `)})`),
|
|
[Ops.Not]: createCompiler(([value]) => `(!${value})`),
|
|
[Ops.Equal]: createCompiler(
|
|
([firstValue, secondValue]) => `(${firstValue} == ${secondValue})`
|
|
),
|
|
[Ops.NotEqual]: createCompiler(
|
|
([firstValue, secondValue]) => `(${firstValue} != ${secondValue})`
|
|
),
|
|
[Ops.GreaterThan]: createCompiler(
|
|
([firstValue, secondValue]) => `(${firstValue} > ${secondValue})`
|
|
),
|
|
[Ops.GreaterThanOrEqualTo]: createCompiler(
|
|
([firstValue, secondValue]) => `(${firstValue} >= ${secondValue})`
|
|
),
|
|
[Ops.LessThan]: createCompiler(
|
|
([firstValue, secondValue]) => `(${firstValue} < ${secondValue})`
|
|
),
|
|
[Ops.LessThanOrEqualTo]: createCompiler(
|
|
([firstValue, secondValue]) => `(${firstValue} <= ${secondValue})`
|
|
),
|
|
[Ops.Multiply]: createCompiler(
|
|
(compiledArgs) => `(${compiledArgs.join(" * ")})`
|
|
),
|
|
[Ops.Divide]: createCompiler(
|
|
([firstValue, secondValue]) => `(${firstValue} / ${secondValue})`
|
|
),
|
|
[Ops.Add]: createCompiler((compiledArgs) => `(${compiledArgs.join(" + ")})`),
|
|
[Ops.Subtract]: createCompiler(
|
|
([firstValue, secondValue]) => `(${firstValue} - ${secondValue})`
|
|
),
|
|
[Ops.Clamp]: createCompiler(
|
|
([value, min, max]) => `clamp(${value}, ${min}, ${max})`
|
|
),
|
|
[Ops.Mod]: createCompiler(([value, modulo]) => `mod(${value}, ${modulo})`),
|
|
[Ops.Pow]: createCompiler(([value, power]) => `pow(${value}, ${power})`),
|
|
[Ops.Abs]: createCompiler(([value]) => `abs(${value})`),
|
|
[Ops.Floor]: createCompiler(([value]) => `floor(${value})`),
|
|
[Ops.Ceil]: createCompiler(([value]) => `ceil(${value})`),
|
|
[Ops.Round]: createCompiler(([value]) => `floor(${value} + 0.5)`),
|
|
[Ops.Sin]: createCompiler(([value]) => `sin(${value})`),
|
|
[Ops.Cos]: createCompiler(([value]) => `cos(${value})`),
|
|
[Ops.Atan]: createCompiler(([firstValue, secondValue]) => {
|
|
return secondValue !== void 0 ? `atan(${firstValue}, ${secondValue})` : `atan(${firstValue})`;
|
|
}),
|
|
[Ops.Sqrt]: createCompiler(([value]) => `sqrt(${value})`),
|
|
[Ops.Match]: createCompiler((compiledArgs) => {
|
|
const input = compiledArgs[0];
|
|
const fallback = compiledArgs[compiledArgs.length - 1];
|
|
let result = null;
|
|
for (let i = compiledArgs.length - 3; i >= 1; i -= 2) {
|
|
const match = compiledArgs[i];
|
|
const output = compiledArgs[i + 1];
|
|
result = `(${input} == ${match} ? ${output} : ${result || fallback})`;
|
|
}
|
|
return result;
|
|
}),
|
|
[Ops.Between]: createCompiler(
|
|
([value, min, max]) => `(${value} >= ${min} && ${value} <= ${max})`
|
|
),
|
|
[Ops.Interpolate]: createCompiler(([exponent, input, ...compiledArgs]) => {
|
|
let result = "";
|
|
for (let i = 0; i < compiledArgs.length - 2; i += 2) {
|
|
const stop1 = compiledArgs[i];
|
|
const output1 = result || compiledArgs[i + 1];
|
|
const stop2 = compiledArgs[i + 2];
|
|
const output2 = compiledArgs[i + 3];
|
|
let ratio;
|
|
if (exponent === numberToGlsl(1)) {
|
|
ratio = `(${input} - ${stop1}) / (${stop2} - ${stop1})`;
|
|
} else {
|
|
ratio = `(pow(${exponent}, (${input} - ${stop1})) - 1.0) / (pow(${exponent}, (${stop2} - ${stop1})) - 1.0)`;
|
|
}
|
|
result = `mix(${output1}, ${output2}, clamp(${ratio}, 0.0, 1.0))`;
|
|
}
|
|
return result;
|
|
}),
|
|
[Ops.Case]: createCompiler((compiledArgs) => {
|
|
const fallback = compiledArgs[compiledArgs.length - 1];
|
|
let result = null;
|
|
for (let i = compiledArgs.length - 3; i >= 0; i -= 2) {
|
|
const condition = compiledArgs[i];
|
|
const output = compiledArgs[i + 1];
|
|
result = `(${condition} ? ${output} : ${result || fallback})`;
|
|
}
|
|
return result;
|
|
}),
|
|
[Ops.In]: createCompiler(([needle, ...haystack], context) => {
|
|
const funcName = computeOperatorFunctionName("in", context);
|
|
const tests = [];
|
|
for (let i = 0; i < haystack.length; i += 1) {
|
|
tests.push(` if (inputValue == ${haystack[i]}) { return true; }`);
|
|
}
|
|
context.functions[funcName] = `bool ${funcName}(float inputValue) {
|
|
${tests.join("\n")}
|
|
return false;
|
|
}`;
|
|
return `${funcName}(${needle})`;
|
|
}),
|
|
[Ops.Array]: createCompiler(
|
|
(args) => `vec${args.length}(${args.join(", ")})`
|
|
),
|
|
[Ops.Color]: createCompiler((compiledArgs) => {
|
|
if (compiledArgs.length === 1) {
|
|
return `vec4(vec3(${compiledArgs[0]} / 255.0), 1.0)`;
|
|
}
|
|
if (compiledArgs.length === 2) {
|
|
return `vec4(vec3(${compiledArgs[0]} / 255.0), ${compiledArgs[1]})`;
|
|
}
|
|
const rgb = compiledArgs.slice(0, 3).map((color) => `${color} / 255.0`);
|
|
if (compiledArgs.length === 3) {
|
|
return `vec4(${rgb.join(", ")}, 1.0)`;
|
|
}
|
|
const alpha = compiledArgs[3];
|
|
return `vec4(${rgb.join(", ")}, ${alpha})`;
|
|
}),
|
|
[Ops.Band]: createCompiler(([band, xOffset, yOffset], context) => {
|
|
if (!(GET_BAND_VALUE_FUNC in context.functions)) {
|
|
let ifBlocks = "";
|
|
const bandCount = context.bandCount || 1;
|
|
for (let i = 0; i < bandCount; i++) {
|
|
const colorIndex = Math.floor(i / 4);
|
|
let bandIndex = i % 4;
|
|
if (i === bandCount - 1 && bandIndex === 1) {
|
|
bandIndex = 3;
|
|
}
|
|
const textureName = `${Uniforms2.TILE_TEXTURE_ARRAY}[${colorIndex}]`;
|
|
ifBlocks += ` if (band == ${i + 1}.0) {
|
|
return texture2D(${textureName}, v_textureCoord + vec2(dx, dy))[${bandIndex}];
|
|
}
|
|
`;
|
|
}
|
|
context.functions[GET_BAND_VALUE_FUNC] = `float getBandValue(float band, float xOffset, float yOffset) {
|
|
float dx = xOffset / ${Uniforms2.TEXTURE_PIXEL_WIDTH};
|
|
float dy = yOffset / ${Uniforms2.TEXTURE_PIXEL_HEIGHT};
|
|
${ifBlocks}
|
|
}`;
|
|
}
|
|
return `${GET_BAND_VALUE_FUNC}(${band}, ${xOffset ?? "0.0"}, ${yOffset ?? "0.0"})`;
|
|
}),
|
|
[Ops.Palette]: (context, expression) => {
|
|
const [index, ...colors] = expression.args;
|
|
const numColors = colors.length;
|
|
const palette = new Uint8Array(numColors * 4);
|
|
for (let i = 0; i < colors.length; i++) {
|
|
const parsedValue = (
|
|
/** @type {string | Array<number>} */
|
|
/** @type {LiteralExpression} */
|
|
colors[i].value
|
|
);
|
|
const color = asArray(parsedValue);
|
|
const offset = i * 4;
|
|
palette[offset] = color[0];
|
|
palette[offset + 1] = color[1];
|
|
palette[offset + 2] = color[2];
|
|
palette[offset + 3] = color[3] * 255;
|
|
}
|
|
if (!context.paletteTextures) {
|
|
context.paletteTextures = [];
|
|
}
|
|
const paletteName = `${PALETTE_TEXTURE_ARRAY}[${context.paletteTextures.length}]`;
|
|
const paletteTexture = new PaletteTexture_default(paletteName, palette);
|
|
context.paletteTextures.push(paletteTexture);
|
|
const compiledIndex = compile(index, NumberType, context);
|
|
return `texture2D(${paletteName}, vec2((${compiledIndex} + 0.5) / ${numColors}.0, 0.5))`;
|
|
}
|
|
// TODO: unimplemented
|
|
// Ops.Number
|
|
// Ops.String
|
|
// Ops.Coalesce
|
|
// Ops.Concat
|
|
// Ops.ToString
|
|
};
|
|
function compile(expression, returnType, context) {
|
|
if (expression instanceof CallExpression) {
|
|
const compiler = compilers[expression.operator];
|
|
if (compiler === void 0) {
|
|
throw new Error(
|
|
`No compiler defined for this operator: ${JSON.stringify(
|
|
expression.operator
|
|
)}`
|
|
);
|
|
}
|
|
return compiler(context, expression, returnType);
|
|
}
|
|
if ((expression.type & NumberType) > 0) {
|
|
return numberToGlsl(
|
|
/** @type {number} */
|
|
expression.value
|
|
);
|
|
}
|
|
if ((expression.type & BooleanType) > 0) {
|
|
return expression.value.toString();
|
|
}
|
|
if ((expression.type & StringType) > 0) {
|
|
return stringToGlsl(expression.value.toString());
|
|
}
|
|
if ((expression.type & ColorType) > 0) {
|
|
return colorToGlsl(
|
|
/** @type {Array<number> | string} */
|
|
expression.value
|
|
);
|
|
}
|
|
if ((expression.type & NumberArrayType) > 0) {
|
|
return arrayToGlsl(
|
|
/** @type {Array<number>} */
|
|
expression.value
|
|
);
|
|
}
|
|
if ((expression.type & SizeType) > 0) {
|
|
return sizeToGlsl(
|
|
/** @type {number|import('../size.js').Size} */
|
|
expression.value
|
|
);
|
|
}
|
|
throw new Error(
|
|
`Unexpected expression ${expression.value} (expected type ${typeName(
|
|
returnType
|
|
)})`
|
|
);
|
|
}
|
|
|
|
// node_modules/ol/render/webgl/compileUtil.js
|
|
function expressionToGlsl(compilationContext, value, expectedType) {
|
|
const parsingContext = newParsingContext();
|
|
return buildExpression(
|
|
value,
|
|
expectedType,
|
|
parsingContext,
|
|
compilationContext
|
|
);
|
|
}
|
|
function packColor(color) {
|
|
const array = asArray(color);
|
|
const r = array[0] * 256;
|
|
const g = array[1];
|
|
const b = array[2] * 256;
|
|
const a = Math.round(array[3] * 255);
|
|
return [r + g, b + a];
|
|
}
|
|
var UNPACK_COLOR_FN = `vec4 unpackColor(vec2 packedColor) {
|
|
return vec4(
|
|
fract(floor(packedColor[0] / 256.0) / 256.0),
|
|
fract(packedColor[0] / 256.0),
|
|
fract(floor(packedColor[1] / 256.0) / 256.0),
|
|
fract(packedColor[1] / 256.0)
|
|
);
|
|
}`;
|
|
function getGlslSizeFromType(type) {
|
|
if (type === ColorType || type === SizeType) {
|
|
return 2;
|
|
}
|
|
if (type === NumberArrayType) {
|
|
return 4;
|
|
}
|
|
return 1;
|
|
}
|
|
function getGlslTypeFromType(type) {
|
|
const size = getGlslSizeFromType(type);
|
|
if (size > 1) {
|
|
return (
|
|
/** @type {'vec2'|'vec3'|'vec4'} */
|
|
`vec${size}`
|
|
);
|
|
}
|
|
return "float";
|
|
}
|
|
function applyContextToBuilder(builder, context) {
|
|
for (const varName in context.variables) {
|
|
const variable = context.variables[varName];
|
|
const uniformName = uniformNameForVariable(variable.name);
|
|
let glslType = getGlslTypeFromType(variable.type);
|
|
if (variable.type === ColorType) {
|
|
glslType = "vec4";
|
|
}
|
|
builder.addUniform(uniformName, glslType);
|
|
}
|
|
for (const propName in context.properties) {
|
|
const property = context.properties[propName];
|
|
const glslType = getGlslTypeFromType(property.type);
|
|
const attributeName = `a_prop_${property.name}`;
|
|
if (property.type === ColorType) {
|
|
builder.addAttribute(
|
|
attributeName,
|
|
glslType,
|
|
`unpackColor(${attributeName})`,
|
|
"vec4"
|
|
);
|
|
builder.addVertexShaderFunction(UNPACK_COLOR_FN);
|
|
} else {
|
|
builder.addAttribute(attributeName, glslType);
|
|
}
|
|
}
|
|
for (const functionName in context.functions) {
|
|
builder.addVertexShaderFunction(context.functions[functionName]);
|
|
builder.addFragmentShaderFunction(context.functions[functionName]);
|
|
}
|
|
}
|
|
function generateUniformsFromContext(context, variables) {
|
|
const uniforms = {};
|
|
for (const varName in context.variables) {
|
|
const variable = context.variables[varName];
|
|
const uniformName = uniformNameForVariable(variable.name);
|
|
uniforms[uniformName] = () => {
|
|
const value = variables[variable.name];
|
|
if (typeof value === "number") {
|
|
return value;
|
|
}
|
|
if (typeof value === "boolean") {
|
|
return value ? 1 : 0;
|
|
}
|
|
if (variable.type === ColorType) {
|
|
const color = [...asArray(value || "#eee")];
|
|
color[0] /= 255;
|
|
color[1] /= 255;
|
|
color[2] /= 255;
|
|
color[3] ?? (color[3] = 1);
|
|
return color;
|
|
}
|
|
if (typeof value === "string") {
|
|
return getStringNumberEquivalent(value);
|
|
}
|
|
return value;
|
|
};
|
|
}
|
|
return uniforms;
|
|
}
|
|
function generateAttributesFromContext(context) {
|
|
const attributes = {};
|
|
for (const propName in context.properties) {
|
|
const property = context.properties[propName];
|
|
const callback = (feature) => {
|
|
const value = feature.get(property.name);
|
|
if (property.type === ColorType) {
|
|
return packColor([...asArray(value || "#eee")]);
|
|
}
|
|
if (typeof value === "string") {
|
|
return getStringNumberEquivalent(value);
|
|
}
|
|
if (typeof value === "boolean") {
|
|
return value ? 1 : 0;
|
|
}
|
|
return value;
|
|
};
|
|
attributes[`prop_${property.name}`] = {
|
|
size: getGlslSizeFromType(property.type),
|
|
callback
|
|
};
|
|
}
|
|
return attributes;
|
|
}
|
|
|
|
export {
|
|
ARRAY_BUFFER,
|
|
ELEMENT_ARRAY_BUFFER,
|
|
DYNAMIC_DRAW,
|
|
Buffer_default,
|
|
DefaultUniform,
|
|
AttributeType,
|
|
Layer_default2 as Layer_default,
|
|
Uniforms2 as Uniforms,
|
|
Attributes,
|
|
TileLayer_default,
|
|
numberToGlsl,
|
|
colorToGlsl,
|
|
getStringNumberEquivalent,
|
|
stringToGlsl,
|
|
uniformNameForVariable,
|
|
newCompilationContext,
|
|
PALETTE_TEXTURE_ARRAY,
|
|
FEATURE_ID_PROPERTY_NAME,
|
|
GEOMETRY_TYPE_PROPERTY_NAME,
|
|
UNDEFINED_PROP_VALUE,
|
|
expressionToGlsl,
|
|
getGlslSizeFromType,
|
|
getGlslTypeFromType,
|
|
applyContextToBuilder,
|
|
generateUniformsFromContext,
|
|
generateAttributesFromContext
|
|
};
|
|
//# sourceMappingURL=chunk-PZZAKLYX.js.map
|