diff --git a/modules/viv/layers/ImageLayer.js b/modules/viv/layers/ImageLayer.js index 784fcffa..9793f469 100644 --- a/modules/viv/layers/ImageLayer.js +++ b/modules/viv/layers/ImageLayer.js @@ -92,10 +92,8 @@ const ImageLayer = class extends CompositeLayer { this.state.abortController.abort(); } - updateState({ changeFlags, props, oldProps }) { - const { propsChanged } = changeFlags; - const loaderChanged = - typeof propsChanged === 'string' && propsChanged.includes('props.loader'); + updateState({ props, oldProps }) { + const loaderChanged = props.loader !== oldProps.loader; const loaderSelectionChanged = props.loaderSelection !== oldProps.loaderSelection; diff --git a/modules/viv/layers/MultiscaleImageLayer/MultiscaleImageLayer.js b/modules/viv/layers/MultiscaleImageLayer/MultiscaleImageLayer.js index 6098d0a5..b3556a14 100644 --- a/modules/viv/layers/MultiscaleImageLayer/MultiscaleImageLayer.js +++ b/modules/viv/layers/MultiscaleImageLayer/MultiscaleImageLayer.js @@ -11,9 +11,6 @@ import { SIGNAL_ABORTED } from '../../loaders/utils'; -// From https://github.com/visgl/deck.gl/pull/4616/files#diff-4d6a2e500c0e79e12e562c4f1217dc80R128 -const DECK_GL_TILE_SIZE = 512; - const defaultProps = { pickable: { type: 'boolean', value: true, compare: true }, onHover: { type: 'function', value: null, compare: false }, @@ -109,7 +106,6 @@ const MultiscaleImageLayer = class extends CompositeLayer { // https://github.com/visgl/deck.gl/pull/4616/files#diff-4d6a2e500c0e79e12e562c4f1217dc80R128 // The z level can be wrong for showing the correct scales because of the calculation deck.gl does // so we need to invert it for fetching tiles and minZoom/maxZoom. - const zoomOffset = Math.log2(DECK_GL_TILE_SIZE / tileSize); const getTileData = async ({ x, y, z, signal }) => { // Early return if no loaderSelection if (!loaderSelection || loaderSelection.length === 0) { @@ -122,7 +118,7 @@ const MultiscaleImageLayer = class extends CompositeLayer { // which felt odd to me to beign with. // The image-tile example works without, this but I have a feeling there is something // going on with our pyramids and/or rendering that is different. - const resolution = Math.round(-z + zoomOffset); + const resolution = Math.round(-z); const getTile = selection => { const config = { x, y, selection, signal }; return loader[resolution].getTile(config); @@ -176,20 +172,18 @@ const MultiscaleImageLayer = class extends CompositeLayer { id: `Tiled-Image-${id}`, getTileData, dtype, - // If you scale a matrix up or down, that is like zooming in or out. After - // https://github.com/visgl/deck.gl/pull/4616/files#diff-4d6a2e500c0e79e12e562c4f1217dc80R128, - // tileSize controls the zoom level that the tile indexer thinks you are at for fetching tiles. - // Because the indexing offsets `z` by math.log2(TILE_SIZE / tileSize), passing in - // tileSize * (1 / modelMatrix.getScale()[0]) from this layer as below to TileLayer gives an offset of - // math.log2(TILE_SIZE / (tileSize * (1 / modelMatrix.getScale()[0]))) = math.log2(TILE_SIZE / tileSize) + Math.log2(modelMatrix.getScale()[0]) - // as desired so that the z level used for indexing the tiles is larger (i.e more zoomed in) if the image is scaled larger, and vice-versa if scaled smaller. - tileSize: modelMatrix - ? tileSize * (1 / modelMatrix.getScale()[0]) - : tileSize, + tileSize, + // If you scale a matrix up or down, that is like zooming in or out. zoomOffset controls + // how the zoom level you fetch tiles at is offset, allowing us to fetch higher resolution tiles + // while at a lower "absolute" zoom level. If you didn't use this prop, an image that is scaled + // up would always look "low resolution" no matter the level of the image pyramid you are looking at. + zoomOffset: Math.round( + Math.log2(modelMatrix ? modelMatrix.getScale()[0] : 1) + ), extent: [0, 0, width, height], // See the above note within for why the use of zoomOffset and the rounding necessary. - minZoom: Math.round(-(loader.length - 1) + zoomOffset), - maxZoom: Math.round(zoomOffset), + minZoom: Math.round(-(loader.length - 1)), + maxZoom: 0, // We want a no-overlap caching strategy with an opacity < 1 to prevent // multiple rendered sublayers (some of which have been cached) from overlapping refinementStrategy: diff --git a/modules/viv/layers/VolumeLayer/VolumeLayer.js b/modules/viv/layers/VolumeLayer/VolumeLayer.js index 941a9346..daf500b0 100644 --- a/modules/viv/layers/VolumeLayer/VolumeLayer.js +++ b/modules/viv/layers/VolumeLayer/VolumeLayer.js @@ -1,11 +1,11 @@ import { CompositeLayer, COORDINATE_SYSTEM } from '@deck.gl/core'; import GL from '@luma.gl/constants'; -import { TextLayer } from '@deck.gl/layers'; +import { isWebGL2 } from '@luma.gl/core'; import { Matrix4 } from 'math.gl'; import XR3DLayer from '../XR3DLayer'; import { getPhysicalSizeScalingMatrix } from '../utils'; import { RENDERING_MODES } from '../../constants'; -import { getVolume } from './utils'; +import { getVolume, getTextLayer } from './utils'; const defaultProps = { pickable: false, @@ -39,7 +39,8 @@ const defaultProps = { compare: true }, onUpdate: { type: 'function', value: () => {}, compare: true }, - useProgressIndicator: { type: 'boolean', value: true, compare: true } + useProgressIndicator: { type: 'boolean', value: true, compare: true }, + useWebGL1Warning: { type: 'boolean', value: true, compare: true } }; /** @@ -61,6 +62,7 @@ const defaultProps = { * @property {function=} onViewportLoad Function that gets called when the data in the viewport loads. * @property {Array.=} clippingPlanes List of math.gl [Plane](https://math.gl/modules/culling/docs/api-reference/plane) objects. * @property {boolean=} useProgressIndicator Whether or not to use the default progress text + indicator (default is true) + * @property {boolean=} useWebGL1Warning Whether or not to use the default WebGL1 warning (default is true) * @property {function=} onUpdate A callback to be used for getting updates of the progress, ({ progress }) => {} */ @@ -69,20 +71,33 @@ const defaultProps = { * @ignore */ const VolumeLayer = class extends CompositeLayer { + clearState() { + this.setState({ + height: null, + width: null, + depth: null, + data: null, + physicalSizeScalingMatrix: null, + resolutionMatrix: null, + progress: 0, + abortController: null + }); + } + finalizeState() { this.state.abortController.abort(); } - updateState({ changeFlags, oldProps, props }) { - const { propsChanged } = changeFlags; - const loaderChanged = - typeof propsChanged === 'string' && propsChanged.includes('props.loader'); - const resolutionChanged = - typeof propsChanged === 'string' && - propsChanged.includes('props.resolution'); + updateState({ oldProps, props }) { + const loaderChanged = props.loader !== oldProps.loader; + const resolutionChanged = props.resolution !== oldProps.resolution; const loaderSelectionChanged = props.loaderSelection !== oldProps.loaderSelection; // Only fetch new data to render if loader has changed + if (resolutionChanged) { + // Clear last volume. + this.clearState(); + } if (loaderChanged || loaderSelectionChanged || resolutionChanged) { const { loader, @@ -140,7 +155,13 @@ const VolumeLayer = class extends CompositeLayer { } renderLayers() { - const { loader, id, resolution, useProgressIndicator } = this.props; + const { + loader, + id, + resolution, + useProgressIndicator, + useWebGL1Warning + } = this.props; const { dtype } = loader[resolution]; const { data, @@ -151,26 +172,27 @@ const VolumeLayer = class extends CompositeLayer { physicalSizeScalingMatrix, resolutionMatrix } = this.state; + const { gl } = this.context; + if (!isWebGL2(gl) && useWebGL1Warning) { + const { viewport } = this.context; + return getTextLayer( + [ + 'Volume rendering is only available on browsers that support WebGL2. If you', + 'are using Safari, you can turn on WebGL2 by navigating in the top menubar', + 'to check Develop > Experimental Features > WebGL 2.0 and then refreshing', + 'the page.' + ].join('\n'), + viewport, + id + ); + } if (!(width && height) && useProgressIndicator) { const { viewport } = this.context; - return new TextLayer({ - id: `loading-text-layer-${id}`, - coordinateSystem: COORDINATE_SYSTEM.CARTESIAN, - data: [ - { - text: `Loading Volume ${String((progress || 0) * 100).slice( - 0, - 5 - )}%...`, - position: viewport.position - } - ], - getColor: [220, 220, 220, 255], - getSize: 25, - sizeUnits: 'meters', - sizeScale: 2 ** -viewport.zoom, - fontFamily: 'Helvetica' - }); + return getTextLayer( + `Loading Volume ${String((progress || 0) * 100).slice(0, 5)}%...`, + viewport, + id + ); } return new XR3DLayer(this.props, { channelData: { data, width, height, depth }, diff --git a/modules/viv/layers/VolumeLayer/utils.js b/modules/viv/layers/VolumeLayer/utils.js index 1b9bab84..6cd77a19 100644 --- a/modules/viv/layers/VolumeLayer/utils.js +++ b/modules/viv/layers/VolumeLayer/utils.js @@ -1,3 +1,6 @@ +/* global globalThis */ +import { COORDINATE_SYSTEM } from '@deck.gl/core'; +import { TextLayer } from '@deck.gl/layers'; import { getImageSize } from '../../loaders/utils'; /** @@ -62,3 +65,21 @@ export async function getVolume({ depth: depthDownsampled }; } + +export const getTextLayer = (text, viewport, id) => { + return new TextLayer({ + id: `text-${id}`, + coordinateSystem: COORDINATE_SYSTEM.CARTESIAN, + data: [ + { + text, + position: viewport.position + } + ], + getColor: [220, 220, 220, 255], + getSize: 25, + sizeUnits: 'meters', + sizeScale: 2 ** -viewport.zoom, + fontFamily: 'Helvetica' + }); +};