dicom-microscopy-viewer-changed
Version:
Interactive web-based viewer for DICOM Microscopy Images
372 lines (327 loc) • 11 kB
JavaScript
/**
* @module ol/renderer/webgl/VectorLayer
*/
import BaseVector from '../../layer/BaseVector.js';
import MixedGeometryBatch from '../../render/webgl/MixedGeometryBatch.js';
import VectorEventType from '../../source/VectorEventType.js';
import VectorStyleRenderer from '../../render/webgl/VectorStyleRenderer.js';
import ViewHint from '../../ViewHint.js';
import WebGLLayerRenderer from './Layer.js';
import {DefaultUniform} from '../../webgl/Helper.js';
import {buffer, createEmpty, equals, getWidth} from '../../extent.js';
import {
create as createMat4,
fromTransform as mat4FromTransform,
} from '../../vec/mat4.js';
import {
create as createTransform,
multiply as multiplyTransform,
setFromArray as setFromTransform,
translate as translateTransform,
} from '../../transform.js';
import {listen, unlistenByKey} from '../../events.js';
export const Uniforms = {
...DefaultUniform,
RENDER_EXTENT: 'u_renderExtent', // intersection of layer, source, and view extent
GLOBAL_ALPHA: 'u_globalAlpha',
};
/**
* @typedef {import('../../render/webgl/VectorStyleRenderer.js').VectorStyle} VectorStyle
*/
/**
* @typedef {Object} Options
* @property {string} [className='ol-layer'] A CSS class name to set to the canvas element.
* @property {VectorStyle|Array<VectorStyle>} style Vector style as literal style or shaders; can also accept an array of styles
* @property {Array<import("./Layer").PostProcessesOptions>} [postProcesses] Post-processes definitions
*/
/**
* @classdesc
* Experimental WebGL vector renderer. Supports polygons, lines and points:
* * Polygons are broken down into triangles
* * Lines are rendered as strips of quads
* * Points are rendered as quads
*
* You need to provide vertex and fragment shaders as well as custom attributes for each type of geometry. All shaders
* can access the uniforms in the {@link module:ol/webgl/Helper~DefaultUniform} enum.
* The vertex shaders can access the following attributes depending on the geometry type:
* * For polygons: {@link module:ol/render/webgl/PolygonBatchRenderer~Attributes}
* * For line strings: {@link module:ol/render/webgl/LineStringBatchRenderer~Attributes}
* * For points: {@link module:ol/render/webgl/PointBatchRenderer~Attributes}
*
* Please note that the fragment shaders output should have premultiplied alpha, otherwise visual anomalies may occur.
*
* Note: this uses {@link module:ol/webgl/Helper~WebGLHelper} internally.
*/
class WebGLVectorLayerRenderer extends WebGLLayerRenderer {
/**
* @param {import("../../layer/Layer.js").default} layer Layer.
* @param {Options} options Options.
*/
constructor(layer, options) {
const uniforms = {
[Uniforms.RENDER_EXTENT]: [0, 0, 0, 0],
[Uniforms.GLOBAL_ALPHA]: 1,
};
super(layer, {
uniforms: uniforms,
postProcesses: options.postProcesses,
});
this.sourceRevision_ = -1;
this.previousExtent_ = createEmpty();
/**
* This transform is updated on every frame and is the composition of:
* - invert of the world->screen transform that was used when rebuilding buffers (see `this.renderTransform_`)
* - current world->screen transform
* @type {import("../../transform.js").Transform}
* @private
*/
this.currentTransform_ = createTransform();
this.tmpTransform_ = createTransform();
this.tmpMat4_ = createMat4();
/**
* @type {import("../../transform.js").Transform}
* @private
*/
this.currentFrameStateTransform_ = createTransform();
/**
* @type {Array<VectorStyle>}
* @private
*/
this.styles_ = [];
/**
* @type {Array<VectorStyleRenderer>}
* @private
*/
this.styleRenderers_ = [];
/**
* @type {Array<import('../../render/webgl/VectorStyleRenderer.js').WebGLBuffers>}
* @private
*/
this.buffers_ = [];
this.applyOptions_(options);
/**
* @private
*/
this.batch_ = new MixedGeometryBatch();
const source = this.getLayer().getSource();
this.batch_.addFeatures(source.getFeatures());
this.sourceListenKeys_ = [
listen(
source,
VectorEventType.ADDFEATURE,
this.handleSourceFeatureAdded_,
this
),
listen(
source,
VectorEventType.CHANGEFEATURE,
this.handleSourceFeatureChanged_,
this
),
listen(
source,
VectorEventType.REMOVEFEATURE,
this.handleSourceFeatureDelete_,
this
),
listen(
source,
VectorEventType.CLEAR,
this.handleSourceFeatureClear_,
this
),
];
}
/**
* @param {Options} options Options.
* @private
*/
applyOptions_(options) {
this.styles_ = Array.isArray(options.style)
? options.style
: [options.style];
}
/**
* @private
*/
createRenderers_() {
this.buffers_ = [];
this.styleRenderers_ = this.styles_.map(
(style) => new VectorStyleRenderer(style, this.helper)
);
}
reset(options) {
this.applyOptions_(options);
if (this.helper) {
this.createRenderers_();
}
super.reset(options);
}
afterHelperCreated() {
this.createRenderers_();
}
/**
* @param {import("../../source/Vector.js").VectorSourceEvent} event Event.
* @private
*/
handleSourceFeatureAdded_(event) {
const feature = event.feature;
this.batch_.addFeature(feature);
}
/**
* @param {import("../../source/Vector.js").VectorSourceEvent} event Event.
* @private
*/
handleSourceFeatureChanged_(event) {
const feature = event.feature;
this.batch_.changeFeature(feature);
}
/**
* @param {import("../../source/Vector.js").VectorSourceEvent} event Event.
* @private
*/
handleSourceFeatureDelete_(event) {
const feature = event.feature;
this.batch_.removeFeature(feature);
}
/**
* @private
*/
handleSourceFeatureClear_() {
this.batch_.clear();
}
/**
* @param {import("../../transform.js").Transform} batchInvertTransform Inverse of the transformation in which geometries are expressed
* @private
*/
applyUniforms_(batchInvertTransform) {
// world to screen matrix
setFromTransform(this.tmpTransform_, this.currentFrameStateTransform_);
multiplyTransform(this.tmpTransform_, batchInvertTransform);
this.helper.setUniformMatrixValue(
Uniforms.PROJECTION_MATRIX,
mat4FromTransform(this.tmpMat4_, this.tmpTransform_)
);
}
/**
* Render the layer.
* @param {import("../../Map.js").FrameState} frameState Frame state.
* @return {HTMLElement} The rendered element.
*/
renderFrame(frameState) {
const gl = this.helper.getGL();
this.preRender(gl, frameState);
this.helper.prepareDraw(frameState);
this.currentFrameStateTransform_ = this.helper.makeProjectionTransform(
frameState,
this.currentFrameStateTransform_
);
const layer = this.getLayer();
const vectorSource = layer.getSource();
const projection = frameState.viewState.projection;
const multiWorld = vectorSource.getWrapX() && projection.canWrapX();
const projectionExtent = projection.getExtent();
const extent = frameState.extent;
const worldWidth = multiWorld ? getWidth(projectionExtent) : null;
const endWorld = multiWorld
? Math.ceil((extent[2] - projectionExtent[2]) / worldWidth) + 1
: 1;
let world = multiWorld
? Math.floor((extent[0] - projectionExtent[0]) / worldWidth)
: 0;
translateTransform(this.currentFrameStateTransform_, world * worldWidth, 0);
do {
for (let i = 0, ii = this.styleRenderers_.length; i < ii; i++) {
const renderer = this.styleRenderers_[i];
const buffers = this.buffers_[i];
if (!buffers) {
continue;
}
renderer.render(buffers, frameState, () => {
this.applyUniforms_(buffers.invertVerticesTransform);
});
}
translateTransform(this.currentFrameStateTransform_, worldWidth, 0);
} while (++world < endWorld);
this.helper.finalizeDraw(frameState);
const canvas = this.helper.getCanvas();
const layerState = frameState.layerStatesArray[frameState.layerIndex];
const opacity = layerState.opacity;
if (opacity !== parseFloat(canvas.style.opacity)) {
canvas.style.opacity = String(opacity);
}
this.postRender(gl, frameState);
return canvas;
}
/**
* Determine whether renderFrame should be called.
* @param {import("../../Map.js").FrameState} frameState Frame state.
* @return {boolean} Layer is ready to be rendered.
*/
prepareFrameInternal(frameState) {
const layer = this.getLayer();
const vectorSource = layer.getSource();
const viewState = frameState.viewState;
const viewNotMoving =
!frameState.viewHints[ViewHint.ANIMATING] &&
!frameState.viewHints[ViewHint.INTERACTING];
const extentChanged = !equals(this.previousExtent_, frameState.extent);
const sourceChanged = this.sourceRevision_ < vectorSource.getRevision();
if (sourceChanged) {
this.sourceRevision_ = vectorSource.getRevision();
}
if (viewNotMoving && (extentChanged || sourceChanged)) {
const projection = viewState.projection;
const resolution = viewState.resolution;
const renderBuffer =
layer instanceof BaseVector ? layer.getRenderBuffer() : 0;
const extent = buffer(frameState.extent, renderBuffer * resolution);
vectorSource.loadFeatures(extent, resolution, projection);
this.ready = false;
const transform = this.helper.makeProjectionTransform(
frameState,
createTransform()
);
const generatePromises = this.styleRenderers_.map((renderer, i) =>
renderer.generateBuffers(this.batch_, transform).then((buffers) => {
this.buffers_[i] = buffers;
})
);
Promise.all(generatePromises).then(() => {
this.ready = true;
this.getLayer().changed();
});
this.previousExtent_ = frameState.extent.slice();
}
return true;
}
/**
* @param {import("../../coordinate.js").Coordinate} coordinate Coordinate.
* @param {import("../../Map.js").FrameState} frameState Frame state.
* @param {number} hitTolerance Hit tolerance in pixels.
* @param {import("../vector.js").FeatureCallback<T>} callback Feature callback.
* @param {Array<import("../Map.js").HitMatch<T>>} matches The hit detected matches with tolerance.
* @return {T|undefined} Callback result.
* @template T
*/
forEachFeatureAtCoordinate(
coordinate,
frameState,
hitTolerance,
callback,
matches
) {
return undefined;
}
/**
* Clean up.
*/
disposeInternal() {
this.sourceListenKeys_.forEach(function (key) {
unlistenByKey(key);
});
this.sourceListenKeys_ = null;
super.disposeInternal();
}
}
export default WebGLVectorLayerRenderer;