From afbf821dbd67acd25d616f3347dfb7494cab3859 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Duhen?= Date: Mon, 16 Dec 2024 18:12:16 +0100 Subject: [PATCH 1/2] feat(LayeredMaterial): migrate to TypeScript Squashed commits (oldest first): - fix(tests): add new empty methods to mock Material - refactor: use interface where possible - fix: use setUniform for material showOutline set - fix(elevation): correct antilogy - refactor: rename layer to tile where relevant - fix(geoidlayer): revert wrong uniform access change --- src/Converter/convertToTile.js | 6 +- src/Core/View.js | 8 +- src/Layer/ColorLayer.js | 8 +- src/Layer/ElevationLayer.js | 4 +- src/Layer/LabelLayer.js | 4 +- src/Layer/RasterLayer.js | 4 +- src/Layer/TiledGeometryLayer.js | 4 +- src/Process/LayeredMaterialNodeProcessing.js | 34 +- src/Renderer/ColorLayersOrdering.js | 13 +- src/Renderer/LayeredMaterial.js | 275 ---------- src/Renderer/LayeredMaterial.ts | 542 +++++++++++++++++++ src/Renderer/RasterTile.js | 4 +- src/Renderer/Shader/TileFS.glsl | 2 +- src/Utils/DEMUtils.js | 4 +- test/unit/dataSourceProvider.js | 23 +- test/unit/demutils.js | 6 +- test/unit/layeredmaterial.js | 8 +- test/unit/layeredmaterialnodeprocessing.js | 12 +- test/unit/tilemesh.js | 2 + utils/debug/Debug.js | 2 +- utils/debug/TileDebug.js | 8 +- 21 files changed, 622 insertions(+), 351 deletions(-) delete mode 100644 src/Renderer/LayeredMaterial.js create mode 100644 src/Renderer/LayeredMaterial.ts diff --git a/src/Converter/convertToTile.js b/src/Converter/convertToTile.js index 6028fed174..a687cd543e 100644 --- a/src/Converter/convertToTile.js +++ b/src/Converter/convertToTile.js @@ -1,6 +1,6 @@ import * as THREE from 'three'; import TileMesh from 'Core/TileMesh'; -import LayeredMaterial from 'Renderer/LayeredMaterial'; +import { LayeredMaterial } from 'Renderer/LayeredMaterial'; import { newTileGeometry } from 'Core/Prefab/TileBuilder'; import ReferLayerProperties from 'Layer/ReferencingLayerProperties'; import { geoidLayerIsVisible } from 'Layer/GeoidLayer'; @@ -13,7 +13,7 @@ function setTileFromTiledLayer(tile, tileLayer) { } if (__DEBUG__) { - tile.material.showOutline = tileLayer.showOutline || false; + tile.material.setUniform('showOutline', tileLayer.showOutline || false); } if (tileLayer.isGlobeLayer) { @@ -73,7 +73,7 @@ export default { tile.geoidHeight = parent.geoidHeight; const geoidHeight = geoidLayerIsVisible(layer) ? tile.geoidHeight : 0; tile.setBBoxZ({ min: parent.obb.z.min, max: parent.obb.z.max, geoidHeight }); - tile.material.geoidHeight = geoidHeight; + tile.material.setUniform('geoidHeight', geoidHeight); } return tile; diff --git a/src/Core/View.js b/src/Core/View.js index 72dff1f4ea..5c1a29c1e1 100644 --- a/src/Core/View.js +++ b/src/Core/View.js @@ -200,7 +200,7 @@ class View extends THREE.EventDispatcher { this.mainLoop.gfxEngine.getWindowSize().y, options.camera); - this._frameRequesters = { }; + this._frameRequesters = {}; this._resizeListener = () => this.resize(); window.addEventListener('resize', this._resizeListener, false); @@ -217,8 +217,8 @@ class View extends THREE.EventDispatcher { // all layers must be ready const allReady = this.getLayers().every(layer => layer.ready); if (allReady && - this.mainLoop.scheduler.commandsWaitingExecutionCount() == 0 && - this.mainLoop.renderingState == RENDERING_PAUSED) { + this.mainLoop.scheduler.commandsWaitingExecutionCount() == 0 && + this.mainLoop.renderingState == RENDERING_PAUSED) { this.dispatchEvent({ type: VIEW_EVENTS.LAYERS_INITIALIZED }); this.removeFrameRequester(MAIN_LOOP_EVENTS.UPDATE_END, this._allLayersAreReadyCallback); } @@ -954,7 +954,7 @@ class View extends THREE.EventDispatcher { continue; } - for (const materialLayer of tile.object.material.getLayers(layers)) { + for (const materialLayer of tile.object.material.getTiles(layers)) { for (const texture of materialLayer.textures) { if (!texture.features) { continue; diff --git a/src/Layer/ColorLayer.js b/src/Layer/ColorLayer.js index 082879f4f6..9f569e0278 100644 --- a/src/Layer/ColorLayer.js +++ b/src/Layer/ColorLayer.js @@ -144,13 +144,13 @@ class ColorLayer extends RasterLayer { * @return {RasterColorTile} The raster color node added. */ setupRasterNode(node) { - const rasterColorNode = new RasterColorTile(node.material, this); + const rasterColorTile = new RasterColorTile(node.material, this); - node.material.addLayer(rasterColorNode); + node.material.addColorTile(rasterColorTile); // set up ColorLayer ordering. - node.material.setSequence(this.parent.colorLayersOrder); + node.material.setColorTileIds(this.parent.colorLayersOrder); - return rasterColorNode; + return rasterColorTile; } update(context, layer, node, parent) { diff --git a/src/Layer/ElevationLayer.js b/src/Layer/ElevationLayer.js index 2821298cab..d8b1a258b7 100644 --- a/src/Layer/ElevationLayer.js +++ b/src/Layer/ElevationLayer.js @@ -117,8 +117,8 @@ class ElevationLayer extends RasterLayer { setupRasterNode(node) { const rasterElevationNode = new RasterElevationTile(node.material, this); - node.material.addLayer(rasterElevationNode); - node.material.setSequenceElevation(this.id); + node.material.setElevationTile(rasterElevationNode); + node.material.setElevationTileId(this.id); // bounding box initialisation const updateBBox = () => node.setBBoxZ({ min: rasterElevationNode.min, max: rasterElevationNode.max, scale: this.scale, diff --git a/src/Layer/LabelLayer.js b/src/Layer/LabelLayer.js index ff2ffccc42..b81ccf6d78 100644 --- a/src/Layer/LabelLayer.js +++ b/src/Layer/LabelLayer.js @@ -474,8 +474,8 @@ class LabelLayer extends GeometryLayer { // Necessary event listener, to remove any Label attached to node.addEventListener('removed', () => this.removeNodeDomElement(node)); - if (labelsNode.needsAltitude && node.material.getElevationLayer()) { - node.material.getElevationLayer().addEventListener('rasterElevationLevelChanged', () => { labelsNode.needsUpdate = true; }); + if (labelsNode.needsAltitude && node.material.getElevationTile()) { + node.material.getElevationTile().addEventListener('rasterElevationLevelChanged', () => { labelsNode.needsUpdate = true; }); } if (this.performance) { diff --git a/src/Layer/RasterLayer.js b/src/Layer/RasterLayer.js index 7b18eb1665..703641f67b 100644 --- a/src/Layer/RasterLayer.js +++ b/src/Layer/RasterLayer.js @@ -1,5 +1,5 @@ import Layer from 'Layer/Layer'; -import { removeLayeredMaterialNodeLayer } from 'Process/LayeredMaterialNodeProcessing'; +import { removeLayeredMaterialNodeTile } from 'Process/LayeredMaterialNodeProcessing'; import textureConverter from 'Converter/textureConverter'; import { CACHE_POLICIES } from 'Core/Scheduler/Cache'; @@ -34,7 +34,7 @@ class RasterLayer extends Layer { this.cache.clear(); } for (const root of this.parent.level0Nodes) { - root.traverse(removeLayeredMaterialNodeLayer(this.id)); + root.traverse(removeLayeredMaterialNodeTile(this.id)); } } } diff --git a/src/Layer/TiledGeometryLayer.js b/src/Layer/TiledGeometryLayer.js index 1502e50a7a..080071143a 100644 --- a/src/Layer/TiledGeometryLayer.js +++ b/src/Layer/TiledGeometryLayer.js @@ -373,7 +373,7 @@ class TiledGeometryLayer extends GeometryLayer { */ static hasEnoughTexturesToSubdivide(context, node) { const layerUpdateState = node.layerUpdateState || {}; - let nodeLayer = node.material.getElevationLayer(); + let nodeLayer = node.material.getElevationTile(); for (const e of context.elevationLayers) { const extents = node.getExtentsByProjection(e.crs); @@ -403,7 +403,7 @@ class TiledGeometryLayer extends GeometryLayer { if (layerUpdateState[c.id] && layerUpdateState[c.id].inError()) { continue; } - nodeLayer = node.material.getLayer(c.id); + nodeLayer = node.material.getColorTile(c.id); if (c.source.extentInsideLimit(node.extent, zoom) && (!nodeLayer || nodeLayer.level < 0)) { return false; } diff --git a/src/Process/LayeredMaterialNodeProcessing.js b/src/Process/LayeredMaterialNodeProcessing.js index e53b513111..f06c9d0b2b 100644 --- a/src/Process/LayeredMaterialNodeProcessing.js +++ b/src/Process/LayeredMaterialNodeProcessing.js @@ -17,8 +17,8 @@ function refinementCommandCancellationFn(cmd) { // Cancel the command if the tile already has a better texture. // This is only needed for elevation layers, because we may have several // concurrent layers but we can only use one texture. - if (cmd.layer.isElevationLayer && cmd.requester.material.getElevationLayer() && - cmd.targetLevel <= cmd.requester.material.getElevationLayer().level) { + if (cmd.layer.isElevationLayer && cmd.requester.material.getElevationTile() && + cmd.targetLevel <= cmd.requester.material.getElevationTile().level) { return true; } @@ -55,7 +55,7 @@ export function updateLayeredMaterialNodeImagery(context, layer, node, parent) { return; } - let nodeLayer = material.getLayer(layer.id); + let nodeLayer = material.getTile(layer.id); // Initialisation if (node.layerUpdateState[layer.id] === undefined) { @@ -67,8 +67,8 @@ export function updateLayeredMaterialNodeImagery(context, layer, node, parent) { // parent texture if (!layer.noTextureParentOutsideLimit && parent.material && - parent.material.getLayer && - parent.material.getLayer(layer.id)) { + parent.material.getTile && + parent.material.getTile(layer.id)) { // ok, we're going to inherit our parent's texture } else { node.layerUpdateState[layer.id].noMoreUpdatePossible(); @@ -81,7 +81,7 @@ export function updateLayeredMaterialNodeImagery(context, layer, node, parent) { nodeLayer = layer.setupRasterNode(node); // Init the node by parent - const parentLayer = parent.material?.getLayer(layer.id); + const parentLayer = parent.material?.getTile(layer.id); nodeLayer.initFromParent(parentLayer, extentsDestination); } @@ -171,7 +171,7 @@ export function updateLayeredMaterialNodeElevation(context, layer, node, parent) return; } // Init elevation layer, and inherit from parent if possible - let nodeLayer = material.getElevationLayer(); + let nodeLayer = material.getElevationTile(); if (!nodeLayer) { nodeLayer = layer.setupRasterNode(node); } @@ -179,7 +179,7 @@ export function updateLayeredMaterialNodeElevation(context, layer, node, parent) if (node.layerUpdateState[layer.id] === undefined) { node.layerUpdateState[layer.id] = new LayerUpdateState(); - const parentLayer = parent.material?.getLayer(layer.id); + const parentLayer = parent.material?.getTile(layer.id); nodeLayer.initFromParent(parentLayer, extentsDestination); if (nodeLayer.level >= layer.source.zoom.min) { @@ -190,8 +190,8 @@ export function updateLayeredMaterialNodeElevation(context, layer, node, parent) // Possible conditions to *not* update the elevation texture if (layer.frozen || - !material.visible || - !node.layerUpdateState[layer.id].canTryUpdate()) { + !material.visible || + !node.layerUpdateState[layer.id].canTryUpdate()) { return; } @@ -232,19 +232,19 @@ export function updateLayeredMaterialNodeElevation(context, layer, node, parent) err => handlingError(err, node, layer, targetLevel, context.view)); } -export function removeLayeredMaterialNodeLayer(layerId) { +export function removeLayeredMaterialNodeTile(tileId) { /** * @param {TileMesh} node - The node to udpate. */ - return function removeLayeredMaterialNodeLayer(node) { - if (node.material?.removeLayer) { - if (node.material.elevationLayerIds.indexOf(layerId) > -1) { + return function removeLayeredMaterialNodeTile(node) { + if (node.material?.removeTile) { + if (node.material.elevationTile !== undefined) { node.setBBoxZ({ min: 0, max: 0 }); } - node.material.removeLayer(layerId); + node.material.removeTile(tileId); } - if (node.layerUpdateState && node.layerUpdateState[layerId]) { - delete node.layerUpdateState[layerId]; + if (node.layerUpdateState && node.layerUpdateState[tileId]) { + delete node.layerUpdateState[tileId]; } }; } diff --git a/src/Renderer/ColorLayersOrdering.js b/src/Renderer/ColorLayersOrdering.js index f91955d278..8e81f8926b 100644 --- a/src/Renderer/ColorLayersOrdering.js +++ b/src/Renderer/ColorLayersOrdering.js @@ -3,8 +3,8 @@ import { ImageryLayers } from 'Layer/Layer'; function updateLayersOrdering(geometryLayer, imageryLayers) { const sequence = ImageryLayers.getColorLayersIdOrderedBySequence(imageryLayers); const cO = function cO(object) { - if (object.material?.setSequence) { - object.material.setSequence(sequence); + if (object.material?.setColorTileIds) { + object.material.setColorTileIds(sequence); } }; @@ -38,7 +38,8 @@ export default { const previousSequence = ImageryLayers.getColorLayersIdOrderedBySequence(imageryLayers); ImageryLayers.moveLayerUp(layer, imageryLayers); updateLayersOrdering(view.tileLayer, imageryLayers); - view.dispatchEvent({ type: COLOR_LAYERS_ORDER_CHANGED, + view.dispatchEvent({ + type: COLOR_LAYERS_ORDER_CHANGED, previous: { sequence: previousSequence }, new: { sequence: ImageryLayers.getColorLayersIdOrderedBySequence(imageryLayers) }, }); @@ -64,7 +65,8 @@ export default { const previousSequence = ImageryLayers.getColorLayersIdOrderedBySequence(imageryLayers); ImageryLayers.moveLayerDown(layer, imageryLayers); updateLayersOrdering(view.tileLayer, imageryLayers); - view.dispatchEvent({ type: COLOR_LAYERS_ORDER_CHANGED, + view.dispatchEvent({ + type: COLOR_LAYERS_ORDER_CHANGED, previous: { sequence: previousSequence }, new: { sequence: ImageryLayers.getColorLayersIdOrderedBySequence(imageryLayers) }, }); @@ -91,7 +93,8 @@ export default { const previousSequence = ImageryLayers.getColorLayersIdOrderedBySequence(imageryLayers); ImageryLayers.moveLayerToIndex(layer, index, imageryLayers); updateLayersOrdering(view.tileLayer, imageryLayers); - view.dispatchEvent({ type: COLOR_LAYERS_ORDER_CHANGED, + view.dispatchEvent({ + type: COLOR_LAYERS_ORDER_CHANGED, previous: { sequence: previousSequence }, new: { sequence: ImageryLayers.getColorLayersIdOrderedBySequence(imageryLayers) }, }); diff --git a/src/Renderer/LayeredMaterial.js b/src/Renderer/LayeredMaterial.js deleted file mode 100644 index bf1d50c2bc..0000000000 --- a/src/Renderer/LayeredMaterial.js +++ /dev/null @@ -1,275 +0,0 @@ -import * as THREE from 'three'; -import TileVS from 'Renderer/Shader/TileVS.glsl'; -import TileFS from 'Renderer/Shader/TileFS.glsl'; -import ShaderUtils from 'Renderer/Shader/ShaderUtils'; -import Capabilities from 'Core/System/Capabilities'; -import RenderMode from 'Renderer/RenderMode'; -import CommonMaterial from 'Renderer/CommonMaterial'; - -const identityOffsetScale = new THREE.Vector4(0.0, 0.0, 1.0, 1.0); -const defaultTex = new THREE.Texture(); - -// from three.js packDepthToRGBA -const UnpackDownscale = 255 / 256; // 0..1 -> fraction (excluding 1) -const bitSh = new THREE.Vector4( - UnpackDownscale, - UnpackDownscale / 256.0, - UnpackDownscale / (256.0 * 256.0), - UnpackDownscale / (256.0 * 256.0 * 256.0), -); - -export function unpack1K(color, factor) { - return factor ? bitSh.dot(color) * factor : bitSh.dot(color); -} - -// Max sampler color count to LayeredMaterial -// Because there's a statement limitation to unroll, in getColorAtIdUv method -const maxSamplersColorCount = 15; -const samplersElevationCount = 1; - -export function getMaxColorSamplerUnitsCount() { - const maxSamplerUnitsCount = Capabilities.getMaxTextureUnitsCount(); - return Math.min(maxSamplerUnitsCount - samplersElevationCount, maxSamplersColorCount); -} - -export const colorLayerEffects = { - noEffect: 0, - removeLightColor: 1, - removeWhiteColor: 2, - customEffect: 3, -}; - -const defaultStructLayer = { - bias: 0, - noDataValue: -99999, - zmin: 0, - zmax: 0, - scale: 0, - mode: 0, - textureOffset: 0, - opacity: 0, - crs: 0, - effect_parameter: 0, - effect_type: colorLayerEffects.noEffect, - transparent: false, -}; - -function updateLayersUniforms(uniforms, olayers, max) { - // prepare convenient access to elevation or color uniforms - const layers = uniforms.layers.value; - const textures = uniforms.textures.value; - const offsetScales = uniforms.offsetScales.value; - const textureCount = uniforms.textureCount; - - // flatten the 2d array [i,j] -> layers[_layerIds[i]].textures[j] - let count = 0; - for (const layer of olayers) { - // textureOffset property is added to RasterTile - layer.textureOffset = count; - for (let i = 0, il = layer.textures.length; i < il; ++i, ++count) { - if (count < max) { - offsetScales[count] = layer.offsetScales[i]; - textures[count] = layer.textures[i]; - layers[count] = layer; - } - } - } - if (count > max) { - console.warn(`LayeredMaterial: Not enough texture units (${max} < ${count}), excess textures have been discarded.`); - } - textureCount.value = count; - - // WebGL 2.0 doesn't support the undefined uniforms. - // So the undefined uniforms are defined by default value. - for (let i = count; i < textures.length; i++) { - textures[i] = defaultTex; - offsetScales[i] = identityOffsetScale; - layers[i] = defaultStructLayer; - } -} - -export const ELEVATION_MODES = { - RGBA: 0, - COLOR: 1, - DATA: 2, -}; - -let nbSamplers; -const fragmentShader = []; -class LayeredMaterial extends THREE.ShaderMaterial { - #_visible = true; - constructor(options = {}, crsCount) { - super(options); - - nbSamplers = nbSamplers || [samplersElevationCount, getMaxColorSamplerUnitsCount()]; - - this.defines.NUM_VS_TEXTURES = nbSamplers[0]; - this.defines.NUM_FS_TEXTURES = nbSamplers[1]; - // TODO: We do not use the fog from the scene, is this a desired - // behavior? - this.defines.USE_FOG = 1; - this.defines.NUM_CRS = crsCount; - - CommonMaterial.setDefineMapping(this, 'ELEVATION', ELEVATION_MODES); - CommonMaterial.setDefineMapping(this, 'MODE', RenderMode.MODES); - CommonMaterial.setDefineProperty(this, 'mode', 'MODE', RenderMode.MODES.FINAL); - - if (__DEBUG__) { - this.defines.DEBUG = 1; - const outlineColors = [new THREE.Vector3(1.0, 0.0, 0.0)]; - if (crsCount > 1) { - outlineColors.push(new THREE.Vector3(1.0, 0.5, 0.0)); - } - CommonMaterial.setUniformProperty(this, 'showOutline', true); - CommonMaterial.setUniformProperty(this, 'outlineWidth', 0.008); - CommonMaterial.setUniformProperty(this, 'outlineColors', outlineColors); - } - - this.vertexShader = TileVS; - // three loop unrolling of ShaderMaterial only supports integer bounds, - // see https://github.com/mrdoob/three.js/issues/28020 - fragmentShader[crsCount] = fragmentShader[crsCount] || ShaderUtils.unrollLoops(TileFS, this.defines); - this.fragmentShader = fragmentShader[crsCount]; - - // Color uniforms - CommonMaterial.setUniformProperty(this, 'diffuse', new THREE.Color(0.04, 0.23, 0.35)); - CommonMaterial.setUniformProperty(this, 'opacity', this.opacity); - - // Lighting uniforms - CommonMaterial.setUniformProperty(this, 'lightingEnabled', false); - CommonMaterial.setUniformProperty(this, 'lightPosition', new THREE.Vector3(-0.5, 0.0, 1.0)); - - // Misc properties - CommonMaterial.setUniformProperty(this, 'fogDistance', 1000000000.0); - CommonMaterial.setUniformProperty(this, 'fogColor', new THREE.Color(0.76, 0.85, 1.0)); - CommonMaterial.setUniformProperty(this, 'overlayAlpha', 0); - CommonMaterial.setUniformProperty(this, 'overlayColor', new THREE.Color(1.0, 0.3, 0.0)); - CommonMaterial.setUniformProperty(this, 'objectId', 0); - - CommonMaterial.setUniformProperty(this, 'geoidHeight', 0.0); - - // > 0 produces gaps, - // < 0 causes oversampling of textures - // = 0 causes sampling artefacts due to bad estimation of texture-uv gradients - // best is a small negative number - CommonMaterial.setUniformProperty(this, 'minBorderDistance', -0.01); - - // LayeredMaterialLayers - this.layers = []; - this.elevationLayerIds = []; - this.colorLayerIds = []; - - // elevation layer uniforms, to be updated using updateUniforms() - this.uniforms.elevationLayers = new THREE.Uniform(new Array(nbSamplers[0]).fill(defaultStructLayer)); - this.uniforms.elevationTextures = new THREE.Uniform(new Array(nbSamplers[0]).fill(defaultTex)); - this.uniforms.elevationOffsetScales = new THREE.Uniform(new Array(nbSamplers[0]).fill(identityOffsetScale)); - this.uniforms.elevationTextureCount = new THREE.Uniform(0); - - // color layer uniforms, to be updated using updateUniforms() - this.uniforms.colorLayers = new THREE.Uniform(new Array(nbSamplers[1]).fill(defaultStructLayer)); - this.uniforms.colorTextures = new THREE.Uniform(new Array(nbSamplers[1]).fill(defaultTex)); - this.uniforms.colorOffsetScales = new THREE.Uniform(new Array(nbSamplers[1]).fill(identityOffsetScale)); - this.uniforms.colorTextureCount = new THREE.Uniform(0); - - // can't do an ES6 setter/getter here - Object.defineProperty(this, 'visible', { - // Knowing the visibility of a `LayeredMaterial` is useful. For example in a - // `GlobeView`, if you zoom in, "parent" tiles seems hidden; in fact, there - // are not, it is only their material (so `LayeredMaterial`) that is set to - // not visible. - - // Adding an event when changing this property can be useful to hide others - // things, like in `TileDebug`, or in later PR to come (#1303 for example). - // - // TODO : verify if there is a better mechanism to avoid this event - get() { return this.#_visible; }, - set(v) { - if (this.#_visible != v) { - this.#_visible = v; - this.dispatchEvent({ type: v ? 'shown' : 'hidden' }); - } - }, - }); - } - - getUniformByType(type) { - return { - layers: this.uniforms[`${type}Layers`], - textures: this.uniforms[`${type}Textures`], - offsetScales: this.uniforms[`${type}OffsetScales`], - textureCount: this.uniforms[`${type}TextureCount`], - }; - } - - updateLayersUniforms() { - const colorlayers = this.layers.filter(l => this.colorLayerIds.includes(l.id) && l.visible && l.opacity > 0); - colorlayers.sort((a, b) => this.colorLayerIds.indexOf(a.id) - this.colorLayerIds.indexOf(b.id)); - updateLayersUniforms(this.getUniformByType('color'), colorlayers, this.defines.NUM_FS_TEXTURES); - - if (this.elevationLayerIds.some(id => this.getLayer(id)) || - (this.uniforms.elevationTextureCount.value && !this.elevationLayerIds.length)) { - const elevationLayer = this.getElevationLayer() ? [this.getElevationLayer()] : []; - updateLayersUniforms(this.getUniformByType('elevation'), elevationLayer, this.defines.NUM_VS_TEXTURES); - } - this.layersNeedUpdate = false; - } - - dispose() { - this.dispatchEvent({ type: 'dispose' }); - this.layers.forEach(l => l.dispose(true)); - this.layers.length = 0; - this.layersNeedUpdate = true; - } - - // TODO: rename to setColorLayerIds and add setElevationLayerIds ? - setSequence(sequenceLayer) { - this.colorLayerIds = sequenceLayer; - this.layersNeedUpdate = true; - } - - setSequenceElevation(layerId) { - this.elevationLayerIds[0] = layerId; - this.layersNeedUpdate = true; - } - - removeLayer(layerId) { - const index = this.layers.findIndex(l => l.id === layerId); - if (index > -1) { - this.layers[index].dispose(); - this.layers.splice(index, 1); - const idSeq = this.colorLayerIds.indexOf(layerId); - if (idSeq > -1) { - this.colorLayerIds.splice(idSeq, 1); - } else { - this.elevationLayerIds = []; - } - } - } - - addLayer(rasterNode) { - if (rasterNode.layer.id in this.layers) { - console.warn('The "{layer.id}" layer was already present in the material, overwritting.'); - } - this.layers.push(rasterNode); - } - - getLayer(id) { - return this.layers.find(l => l.id === id); - } - - getLayers(ids) { - return this.layers.filter(l => ids.includes(l.id)); - } - - getElevationLayer() { - return this.layers.find(l => l.id === this.elevationLayerIds[0]); - } - - setElevationScale(scale) { - if (this.elevationLayerIds.length) { - this.getElevationLayer().scale = scale; - } - } -} - -export default LayeredMaterial; diff --git a/src/Renderer/LayeredMaterial.ts b/src/Renderer/LayeredMaterial.ts new file mode 100644 index 0000000000..8563020b0d --- /dev/null +++ b/src/Renderer/LayeredMaterial.ts @@ -0,0 +1,542 @@ +import * as THREE from 'three'; +// @ts-expect-error: importing non-ts file +import TileVS from 'Renderer/Shader/TileVS.glsl'; +// @ts-expect-error: importing non-ts file +import TileFS from 'Renderer/Shader/TileFS.glsl'; +import ShaderUtils from 'Renderer/Shader/ShaderUtils'; +import Capabilities from 'Core/System/Capabilities'; +import RenderMode from 'Renderer/RenderMode'; +import { RasterTile, RasterElevationTile, RasterColorTile } from './RasterTile'; + +const identityOffsetScale = new THREE.Vector4(0.0, 0.0, 1.0, 1.0); +const defaultTex = new THREE.Texture(); + +// from three.js packDepthToRGBA +const UnpackDownscale = 255 / 256; // 0..1 -> fraction (excluding 1) +const bitSh = new THREE.Vector4( + UnpackDownscale, + UnpackDownscale / 256.0, + UnpackDownscale / (256.0 * 256.0), + UnpackDownscale / (256.0 * 256.0 * 256.0), +); + +export function unpack1K(color: THREE.Vector4Like, factor: number): number { + return factor ? bitSh.dot(color) * factor : bitSh.dot(color); +} + +// Max sampler color count to LayeredMaterial +// Because there's a statement limitation to unroll, in getColorAtIdUv method +const maxSamplersColorCount = 15; +const samplersElevationCount = 1; + +export function getMaxColorSamplerUnitsCount(): number { + const maxSamplerUnitsCount = Capabilities.getMaxTextureUnitsCount(); + return Math.min( + maxSamplerUnitsCount - samplersElevationCount, + maxSamplersColorCount, + ); +} + +export const colorLayerEffects: Record = { + noEffect: 0, + removeLightColor: 1, + removeWhiteColor: 2, + customEffect: 3, +}; + +/** GPU struct for color layers */ +interface StructColorLayer { + textureOffset: number; + crs: number; + opacity: number; + effect_parameter: number; + effect_type: number; + transparent: boolean; +} + +/** GPU struct for elevation layers */ +interface StructElevationLayer { + scale: number; + bias: number; + mode: number; + zmin: number; + zmax: number; +} + +/** Default GPU struct values for initialization QoL */ +const defaultStructLayers: Readonly<{ + color: StructColorLayer, + elevation: StructElevationLayer +}> = { + color: { + textureOffset: 0, + crs: 0, + opacity: 0, + effect_parameter: 0, + effect_type: colorLayerEffects.noEffect, + transparent: false, + }, + elevation: { + scale: 0, + bias: 0, + mode: 0, + zmin: 0, + zmax: 0, + }, +}; + + +function updateLayersUniforms( + uniforms: { [name: string]: THREE.IUniform }, + tiles: RasterTile[], + max: number, +) { + // Aliases for readability + const uLayers = uniforms.layers.value; + const uTextures = uniforms.textures.value; + const uOffsetScales = uniforms.offsetScales.value; + const uTextureCount = uniforms.textureCount; + + // Flatten the 2d array: [i, j] -> layers[_layerIds[i]].textures[j] + let count = 0; + for (const tile of tiles) { + // FIXME: RasterElevationTile are always passed to this function alone + // so this works, but it's really not great even ignoring the dynamic + // addition of a field. + // @ts-expect-error: adding field to passed layer + tile.textureOffset = count; + + for ( + let i = 0; + i < tile.textures.length && count < max; + ++i, ++count + ) { + uOffsetScales[count] = tile.offsetScales[i]; + uTextures[count] = tile.textures[i]; + uLayers[count] = tile; + } + } + if (count > max) { + console.warn( + `LayeredMaterial: Not enough texture units (${max} < ${count}),` + + 'excess textures have been discarded.', + ); + } + uTextureCount.value = count; +} + +export const ELEVATION_MODES = { + RGBA: 0, + COLOR: 1, + DATA: 2, +}; + +/** + * Convenience type that wraps all of the generic type's fields in + * [THREE.IUniform]s. + */ +type MappedUniforms = { + [name in keyof Uniforms]: THREE.IUniform; +}; + +/** List of the uniform types required for a LayeredMaterial. */ +interface LayeredMaterialRawUniforms { + // Color + diffuse: THREE.Color; + opacity: number; + + // Lighting + lightingEnabled: boolean; + lightPosition: THREE.Vector3; + + // Misc + fogDistance: number; + fogColor: THREE.Color; + overlayAlpha: number; + overlayColor: THREE.Color; + objectId: number; + geoidHeight: number; + + // > 0 produces gaps, + // < 0 causes oversampling of textures + // = 0 causes sampling artefacts due to bad estimation of texture-uv + // gradients + // best is a small negative number + minBorderDistance: number; + + // Debug + showOutline: boolean, + outlineWidth: number, + outlineColors: THREE.Color[] + + // Elevation layers + elevationLayers: Array, + elevationTextures: Array, + elevationOffsetScales: Array, + elevationTextureCount: number, + + // Color layers + colorLayers: Array, + colorTextures: Array, + colorOffsetScales: Array, + colorTextureCount: number, +} + +let nbSamplers: [number, number] | undefined; +const fragmentShader: string[] = []; + +/** Replacing the default uniforms dynamic type with our own static map. */ +export type LayeredMaterialParameters = + Omit + & { uniforms?: MappedUniforms }; + +type DefineMapping> = { + [Name in Extract as `${Prefix}_${Name}`]: Mapping[Name] +}; + +/** Fills in a Partial object's field and narrows the type accordingly. */ +function fillInProp< + Obj extends Partial>, + Name extends keyof Obj, + Value extends Obj[Name], +>( + obj: Obj, + name: Name, + value: Value, +): asserts obj is Obj & { [P in Name]: Value } { + if (obj[name] === undefined) { + (obj as Record)[name] = value; + } +} + +type ElevationModeDefines = DefineMapping<'ELEVATION', typeof ELEVATION_MODES>; +type RenderModeDefines = DefineMapping<'MODE', typeof RenderMode.MODES>; +type LayeredMaterialDefines = { + NUM_VS_TEXTURES: number; + NUM_FS_TEXTURES: number; + USE_FOG: number; + NUM_CRS: number; + DEBUG: number; + MODE: number; +} & ElevationModeDefines + & RenderModeDefines; + +/** + * Initialiszes elevation and render mode defines and narrows the type + * accordingly. + */ +function initModeDefines( + defines: Partial, +): asserts defines is Partial & ElevationModeDefines & RenderModeDefines { + (Object.keys(ELEVATION_MODES) as (keyof typeof ELEVATION_MODES)[]) + .forEach(key => fillInProp(defines, `ELEVATION_${key}`, ELEVATION_MODES[key])); + (Object.keys(RenderMode.MODES) as (keyof typeof RenderMode.MODES)[]) + .forEach(key => fillInProp(defines, `MODE_${key}`, RenderMode.MODES[key])); +} + +/** Material that handles the overlap of multiple raster tiles. */ +export class LayeredMaterial extends THREE.ShaderMaterial { + private _visible = true; + + public colorTiles: RasterColorTile[]; + public elevationTile: RasterElevationTile | undefined; + + public colorTileIds: string[]; + public elevationTileId: string | undefined; + + public layersNeedUpdate: boolean; + + public override defines: LayeredMaterialDefines; + + constructor(options: LayeredMaterialParameters = {}, crsCount: number) { + super(options); + + nbSamplers ??= [samplersElevationCount, getMaxColorSamplerUnitsCount()]; + + const defines: Partial = {}; + + fillInProp(defines, 'NUM_VS_TEXTURES', nbSamplers[0]); + fillInProp(defines, 'NUM_FS_TEXTURES', nbSamplers[1]); + // TODO: We do not use the fog from the scene, is this a desired + // behavior? + fillInProp(defines, 'USE_FOG', 1); + fillInProp(defines, 'NUM_CRS', crsCount); + + initModeDefines(defines); + fillInProp(defines, 'MODE', RenderMode.MODES.FINAL); + + // @ts-expect-error: global constexpr + fillInProp(defines, 'DEBUG', +__DEBUG__); + + // @ts-expect-error: global constexpr + if (__DEBUG__) { + const outlineColors = [new THREE.Color(1.0, 0.0, 0.0)]; + if (crsCount > 1) { + outlineColors.push(new THREE.Color(1.0, 0.5, 0.0)); + } + + this.initUniforms({ + showOutline: true, + outlineWidth: 0.008, + outlineColors, + }); + } + + this.defines = defines; + + this.vertexShader = TileVS; + // three loop unrolling of ShaderMaterial only supports integer bounds, + // see https://github.com/mrdoob/three.js/issues/28020 + fragmentShader[crsCount] ??= ShaderUtils.unrollLoops(TileFS, defines); + this.fragmentShader = fragmentShader[crsCount]; + + this.initUniforms({ + // Color uniforms + diffuse: new THREE.Color(0.04, 0.23, 0.35), + opacity: this.opacity, + + // Lighting uniforms + lightingEnabled: false, + lightPosition: new THREE.Vector3(-0.5, 0.0, 1.0), + + // Misc properties + fogDistance: 1000000000.0, + fogColor: new THREE.Color(0.76, 0.85, 1.0), + overlayAlpha: 0, + overlayColor: new THREE.Color(1.0, 0.3, 0.0), + objectId: 0, + + geoidHeight: 0.0, + + // > 0 produces gaps, + // < 0 causes oversampling of textures + // = 0 causes sampling artefacts due to bad estimation of texture-uv + // gradients + // best is a small negative number + minBorderDistance: -0.01, + }); + + // LayeredMaterialLayers + this.colorTiles = []; + this.colorTileIds = []; + this.layersNeedUpdate = false; + + // elevation/color layer uniforms, to be updated using updateUniforms() + this.initUniforms({ + elevationLayers: new Array(nbSamplers[0]) + .fill(defaultStructLayers.elevation), + elevationTextures: new Array(nbSamplers[0]).fill(defaultTex), + elevationOffsetScales: new Array(nbSamplers[0]) + .fill(identityOffsetScale), + elevationTextureCount: 0, + + colorLayers: new Array(nbSamplers[1]) + .fill(defaultStructLayers.color), + colorTextures: new Array(nbSamplers[1]).fill(defaultTex), + colorOffsetScales: new Array(nbSamplers[1]) + .fill(identityOffsetScale), + colorTextureCount: 0, + }); + + // Can't do an ES6 getter/setter here because it would override the + // Material::visible property with accessors, which is not allowed. + Object.defineProperty(this, 'visible', { + // Knowing the visibility of a `LayeredMaterial` is useful. For + // example in a `GlobeView`, if you zoom in, "parent" tiles seems + // hidden; in fact, there are not, it is only their material (so + // `LayeredMaterial`) that is set to not visible. + + // Adding an event when changing this property can be useful to + // hide others things, like in `TileDebug`, or in later PR to come + // (#1303 for example). + + // TODO : verify if there is a better mechanism to avoid this event + get() { return this._visible; }, + set(v) { + if (this._visible != v) { + this._visible = v; + this.dispatchEvent({ type: v ? 'shown' : 'hidden' }); + } + }, + }); + + // setTimeout(() => console.log(this), 2); + } + + public get mode(): number { + return this.defines.MODE; + } + + public set mode(mode: number) { + if (this.defines.MODE != mode) { + this.defines.MODE = mode; + this.needsUpdate = true; + } + } + + public getUniform( + name: Name, + ): LayeredMaterialRawUniforms[Name] | undefined { + return this.uniforms[name]?.value; + } + + public setUniform< + Name extends keyof LayeredMaterialRawUniforms, + Value extends LayeredMaterialRawUniforms[Name], + >(name: Name, value: Value): void { + const uniform = this.uniforms[name]; + if (uniform === undefined) { + return; + } + if (uniform.value !== value) { + uniform.value = value; + } + } + + public initUniforms(uniforms: { + [Name in keyof LayeredMaterialRawUniforms + ]?: LayeredMaterialRawUniforms[Name] + }): void { + for (const [name, value] of Object.entries(uniforms)) { + if (this.uniforms[name] === undefined) { + this.uniforms[name] = { value }; + } + } + } + + public setUniforms(uniforms: { + [Name in keyof LayeredMaterialRawUniforms + ]?: LayeredMaterialRawUniforms[Name] + }): void { + for (const [name, value] of Object.entries(uniforms)) { + const uniform = this.uniforms[name]; + if (uniform === undefined) { + continue; + } + if (uniform.value !== value) { + uniform.value = value; + } + } + } + + public getLayerUniforms(type: Type): + MappedUniforms<{ + layers: Array, + textures: Array, + offsetScales: Array, + textureCount: number, + }> { + return { + layers: this.uniforms[`${type}Layers`], + textures: this.uniforms[`${type}Textures`], + offsetScales: this.uniforms[`${type}OffsetScales`], + textureCount: this.uniforms[`${type}TextureCount`], + }; + } + + public updateLayersUniforms(): void { + const colorlayers = this.colorTiles + .filter(rt => rt.visible && rt.opacity > 0); + colorlayers.sort((a, b) => + this.colorTileIds.indexOf(a.id) - this.colorTileIds.indexOf(b.id), + ); + + updateLayersUniforms( + this.getLayerUniforms('color'), + colorlayers, + this.defines.NUM_FS_TEXTURES, + ); + + if (this.elevationTileId !== undefined && this.getElevationTile()) { + if (this.elevationTile !== undefined) { + updateLayersUniforms( + this.getLayerUniforms('elevation'), + [this.elevationTile], + this.defines.NUM_VS_TEXTURES, + ); + } + } + + this.layersNeedUpdate = false; + } + + public dispose(): void { + this.dispatchEvent({ type: 'dispose' }); + + this.colorTiles.forEach(l => l.dispose(true)); + this.colorTiles.length = 0; + + this.elevationTile?.dispose(true); + + this.layersNeedUpdate = true; + } + + public setColorTileIds(ids: string[]): void { + this.colorTileIds = ids; + this.layersNeedUpdate = true; + } + + public setElevationTileId(id: string): void { + this.elevationTileId = id; + this.layersNeedUpdate = true; + } + + public removeTile(tileId: string): void { + const index = this.colorTiles.findIndex(l => l.id === tileId); + if (index > -1) { + this.colorTiles[index].dispose(); + this.colorTiles.splice(index, 1); + const idSeq = this.colorTileIds.indexOf(tileId); + if (idSeq > -1) { + this.colorTileIds.splice(idSeq, 1); + } else { + this.elevationTileId = undefined; + } + } + } + + public addColorTile(rasterTile: RasterColorTile) { + if (rasterTile.layer.id in this.colorTiles) { + console.warn( + 'Layer "{layer.id}" already present in material, overwriting.', + ); + } + this.colorTiles.push(rasterTile); + } + + public setElevationTile(rasterTile: RasterElevationTile) { + const old = this.elevationTile; + if (old !== undefined) { + old.dispose(); + } + + this.elevationTile = rasterTile; + } + + public getColorTile(id: string): RasterColorTile | undefined { + return this.colorTiles.find(l => l.id === id); + } + + public getElevationTile(): RasterElevationTile | undefined { + return this.elevationTile; + } + + public getTile(id: string): RasterTile | undefined { + return this.elevationTile?.id === id + ? this.elevationTile : this.colorTiles.find(l => l.id === id); + } + + public getTiles(ids: string[]): RasterTile[] { + // NOTE: this could instead be a mapping with an undefined in place of + // unfound IDs. Need to identify a use case for it though as it would + // probably have a performance cost (albeit minor in the grand scheme of + // things). + const res: RasterTile[] = this.colorTiles.filter(l => ids.includes(l.id)); + if (this.elevationTile !== undefined && ids.includes(this.elevationTile?.id)) { + res.push(this.elevationTile); + } + return res; + } +} diff --git a/src/Renderer/RasterTile.js b/src/Renderer/RasterTile.js index af87699426..8ae9c2f58a 100644 --- a/src/Renderer/RasterTile.js +++ b/src/Renderer/RasterTile.js @@ -27,7 +27,7 @@ function getIndiceWithPitch(i, pitch, w) { * * @class RasterTile */ -class RasterTile extends THREE.EventDispatcher { +export class RasterTile extends THREE.EventDispatcher { constructor(material, layer) { super(); this.layer = layer; @@ -112,8 +112,6 @@ class RasterTile extends THREE.EventDispatcher { } } -export default RasterTile; - export class RasterColorTile extends RasterTile { get effect_type() { return this.layer.effect_type; diff --git a/src/Renderer/Shader/TileFS.glsl b/src/Renderer/Shader/TileFS.glsl index 02065f7ffc..f1939430b9 100644 --- a/src/Renderer/Shader/TileFS.glsl +++ b/src/Renderer/Shader/TileFS.glsl @@ -42,7 +42,7 @@ void main() { gl_FragColor.rgb = mix(gl_FragColor.rgb, color.rgb, color.a); } - #if defined(DEBUG) + #if DEBUG == 1 if (showOutline) { #pragma unroll_loop for ( int i = 0; i < NUM_CRS; i ++) { diff --git a/src/Utils/DEMUtils.js b/src/Utils/DEMUtils.js index d4ad018c7a..004654b288 100644 --- a/src/Utils/DEMUtils.js +++ b/src/Utils/DEMUtils.js @@ -98,7 +98,7 @@ function tileAt(pt, tile) { return t; } } - const tileLayer = tile.material.getElevationLayer(); + const tileLayer = tile.material.getElevationTile(); if (tileLayer && tileLayer.level >= 0) { return tile; } @@ -334,7 +334,7 @@ function _readZ(layer, method, coord, nodes, cache) { } const tile = tileWithValidElevationTexture; - const tileLayer = tile.material.getElevationLayer(); + const tileLayer = tile.material.getElevationTile(); const src = tileLayer.textures[0]; // check cache value if existing diff --git a/test/unit/dataSourceProvider.js b/test/unit/dataSourceProvider.js index 6bbbbb52fa..175c4b2d99 100644 --- a/test/unit/dataSourceProvider.js +++ b/test/unit/dataSourceProvider.js @@ -19,7 +19,7 @@ import GeometryLayer from 'Layer/GeometryLayer'; import PlanarLayer from 'Core/Prefab/Planar/PlanarLayer'; import Style from 'Core/Style'; import Feature2Mesh from 'Converter/Feature2Mesh'; -import LayeredMaterial from 'Renderer/LayeredMaterial'; +import { LayeredMaterial } from 'Renderer/LayeredMaterial'; import { EMPTY_TEXTURE_ZOOM } from 'Renderer/RasterTile'; import sinon from 'sinon'; @@ -75,12 +75,12 @@ describe('Provide in Sources', function () { planarlayer.attach(colorlayer); planarlayer.attach(elevationlayer); - const fakeNode = { material, setBBoxZ: () => {}, addEventListener: () => {} }; + const fakeNode = { material, setBBoxZ: () => { }, addEventListener: () => { } }; colorlayer.setupRasterNode(fakeNode); elevationlayer.setupRasterNode(fakeNode); - nodeLayer = material.getLayer(colorlayer.id); - nodeLayerElevation = material.getLayer(elevationlayer.id); + nodeLayer = material.getColorTile(colorlayer.id); + nodeLayerElevation = material.getElevationTile(); featureLayer = new GeometryLayer('geom', new THREE.Group(), { crs: 'EPSG:4978', @@ -145,7 +145,7 @@ describe('Provide in Sources', function () { const tile = new TileMesh(geom, material, planarlayer, extent); material.visible = true; nodeLayer.level = EMPTY_TEXTURE_ZOOM; - tile.parent = { }; + tile.parent = {}; updateLayeredMaterialNodeImagery(context, colorlayer, tile, tile.parent); updateLayeredMaterialNodeImagery(context, colorlayer, tile, tile.parent); @@ -204,7 +204,7 @@ describe('Provide in Sources', function () { const tile = new TileMesh(geom, material, planarlayer, extent, zoom); material.visible = true; nodeLayer.level = EMPTY_TEXTURE_ZOOM; - tile.parent = { }; + tile.parent = {}; updateLayeredMaterialNodeImagery(context, colorlayer, tile, tile.parent); updateLayeredMaterialNodeImagery(context, colorlayer, tile, tile.parent); @@ -220,7 +220,7 @@ describe('Provide in Sources', function () { const tile = new TileMesh(geom, material, planarlayer, extent, zoom); material.visible = true; nodeLayer.level = EMPTY_TEXTURE_ZOOM; - tile.parent = { }; + tile.parent = {}; planarlayer.subdivideNode(context, tile); TileProvider.executeCommand(context.scheduler.commands[0]) @@ -290,7 +290,8 @@ describe('Provide in Sources', function () { nodeLayer.level = EMPTY_TEXTURE_ZOOM; tile.material.visible = true; featureLayer.source.uid = 22; - const colorlayerWfs = new ColorLayer('color', { crs: 'EPSG:3857', + const colorlayerWfs = new ColorLayer('color', { + crs: 'EPSG:3857', source: featureLayer.source, style: { fill: { @@ -335,14 +336,14 @@ describe('Provide in Sources', function () { const tile = new TileMesh(geom, new LayeredMaterial(), planarlayer, extent); tile.material.visible = true; nodeLayer.level = EMPTY_TEXTURE_ZOOM; - tile.parent = { }; + tile.parent = {}; updateLayeredMaterialNodeImagery(context, colorlayer, tile, tile.parent); updateLayeredMaterialNodeImagery(context, colorlayer, tile, tile.parent); DataSourceProvider.executeCommand(context.scheduler.commands[0]) .then((result) => { - tile.material.setSequence([colorlayer.id]); - tile.material.getLayer(colorlayer.id).setTextures(result, [new THREE.Vector4()]); + tile.material.setColorTileIds([colorlayer.id]); + tile.material.getColorTile(colorlayer.id).setTextures(result, [new THREE.Vector4()]); assert.equal(tile.material.uniforms.colorTextures.value[0].anisotropy, 1); tile.material.updateLayersUniforms(); assert.equal(tile.material.uniforms.colorTextures.value[0].anisotropy, 16); diff --git a/test/unit/demutils.js b/test/unit/demutils.js index d48e36187a..900ed6863f 100644 --- a/test/unit/demutils.js +++ b/test/unit/demutils.js @@ -84,7 +84,7 @@ describe('DemUtils', function () { geom.OBB = new OBB(new THREE.Vector3(), new THREE.Vector3(1, 1, 1)); const material = new THREE.Material(); const nodeLayer = new RasterElevationTile(material, elevationlayer); - material.getElevationLayer = () => nodeLayer; + material.getElevationTile = () => nodeLayer; const tile = new TileMesh(geom, material, viewer.tileLayer, extent, 5); tile.layerUpdateState[elevationlayer.id] = new LayerUpdateState(); tiles.push(tile); @@ -102,7 +102,7 @@ describe('DemUtils', function () { it('get elevation value at center with FAST_READ_Z', () => { const elevation = DEMUtils.getElevationValueAt(viewer.tileLayer, coord, DEMUtils.FAST_READ_Z, tiles); - assert.equal(elevation, ELEVATION); + assert.equal(elevation, ELEVATION); }); it('get terrain at center with PRECISE_READ_Z', () => { @@ -112,7 +112,7 @@ describe('DemUtils', function () { it('get terrain at center with FAST_READ_Z', () => { const elevation = DEMUtils.getTerrainObjectAt(viewer.tileLayer, coord, DEMUtils.FAST_READ_Z, tiles); - assert.equal(elevation.coord.z, ELEVATION); + assert.equal(elevation.coord.z, ELEVATION); }); }); diff --git a/test/unit/layeredmaterial.js b/test/unit/layeredmaterial.js index 89f171abf0..1a82264965 100644 --- a/test/unit/layeredmaterial.js +++ b/test/unit/layeredmaterial.js @@ -8,7 +8,7 @@ import TileMesh from 'Core/TileMesh'; import * as THREE from 'three'; import Tile from 'Core/Tile/Tile'; import OBB from 'Renderer/OBB'; -import LayeredMaterial from 'Renderer/LayeredMaterial'; +import { LayeredMaterial } from 'Renderer/LayeredMaterial'; import sinon from 'sinon'; import Fetcher from 'Provider/Fetcher'; import Renderer from './bootstrap'; @@ -40,7 +40,7 @@ describe('material state vs layer state', function () { const geom = new THREE.BufferGeometry(); geom.OBB = new OBB(new THREE.Vector3(), new THREE.Vector3(1, 1, 1)); node = new TileMesh(geom, material, view.tileLayer, extent); - node.parent = { }; + node.parent = {}; context = { view, scheduler: view.mainLoop.scheduler }; }); @@ -51,7 +51,7 @@ describe('material state vs layer state', function () { it('should correctly initialize opacity & visibility', () => { updateLayeredMaterialNodeImagery(context, layer, node, node.parent); - const nodeLayer = material.getLayer(layer.id); + const nodeLayer = material.getTile(layer.id); nodeLayer.textures.push(new THREE.Texture()); assert.equal(nodeLayer.opacity, layer.opacity); assert.equal(nodeLayer.visible, layer.visible); @@ -60,7 +60,7 @@ describe('material state vs layer state', function () { it('should update material opacity & visibility', () => { layer.opacity = 0.5; layer.visible = false; - const nodeLayer = material.getLayer(layer.id); + const nodeLayer = material.getTile(layer.id); assert.equal(nodeLayer.opacity, layer.opacity); assert.equal(nodeLayer.visible, layer.visible); }); diff --git a/test/unit/layeredmaterialnodeprocessing.js b/test/unit/layeredmaterialnodeprocessing.js index 38c7daf92d..c014904ba8 100644 --- a/test/unit/layeredmaterialnodeprocessing.js +++ b/test/unit/layeredmaterialnodeprocessing.js @@ -39,7 +39,7 @@ describe('updateLayeredMaterialNodeImagery', function () { const layer = new Layer('foo', { source, crs: 'EPSG:4326', - info: { update: () => {} }, + info: { update: () => { } }, }); layer.tileMatrixSets = [ 'EPSG:4326', @@ -53,7 +53,7 @@ describe('updateLayeredMaterialNodeImagery', function () { }; const nodeLayer = new RasterColorTile(material, layer); - material.getLayer = () => nodeLayer; + material.getTile = () => nodeLayer; beforeEach('reset state', function () { // clear commands array @@ -76,7 +76,7 @@ describe('updateLayeredMaterialNodeImagery', function () { const tile = new TileMesh(geom, material, layer, extent, 0); material.visible = false; nodeLayer.level = 0; - tile.parent = { }; + tile.parent = {}; updateLayeredMaterialNodeImagery(context, layer, tile, tile.parent); assert.equal(context.scheduler.commands.length, 0); }); @@ -85,7 +85,7 @@ describe('updateLayeredMaterialNodeImagery', function () { const tile = new TileMesh(geom, material, layer, extent, 3); material.visible = true; nodeLayer.level = 3; - tile.parent = { }; + tile.parent = {}; updateLayeredMaterialNodeImagery(context, layer, tile, tile.parent); assert.equal(context.scheduler.commands.length, 0); }); @@ -94,7 +94,7 @@ describe('updateLayeredMaterialNodeImagery', function () { const tile = new TileMesh(geom, material, layer, extent, 2); material.visible = true; nodeLayer.level = 1; - tile.parent = { }; + tile.parent = {}; // FIRST PASS: init Node From Parent and get out of the function // without any network fetch @@ -112,7 +112,7 @@ describe('updateLayeredMaterialNodeImagery', function () { // Emulate a situation where tile inherited a level 1 texture material.visible = true; nodeLayer.level = 1; - tile.parent = { }; + tile.parent = {}; source.isWMTSSource = true; source.tileMatrixSet = 'WGS84G'; // Emulate a situation where tile inherited a level 1 texture diff --git a/test/unit/tilemesh.js b/test/unit/tilemesh.js index aea00caef4..c8700d49d7 100644 --- a/test/unit/tilemesh.js +++ b/test/unit/tilemesh.js @@ -177,6 +177,8 @@ describe('TileMesh', function () { const material = new THREE.Material(); material.addLayer = () => { }; material.setSequenceElevation = () => { }; + material.setElevationTile = () => { }; + material.setElevationTileId = () => { }; it('event rasterElevationLevelChanged RasterElevationTile sets TileMesh bounding box ', () => { const tileMesh = new TileMesh(geom, material, planarlayer, tile.toExtent('EPSG:3857'), 0); diff --git a/utils/debug/Debug.js b/utils/debug/Debug.js index 55361cf5b6..01a177cc66 100644 --- a/utils/debug/Debug.js +++ b/utils/debug/Debug.js @@ -165,7 +165,7 @@ function Debug(view, datDebugTool, chartDivContainer) { function updateFogDistance(obj) { if (obj.material && fogDistance) { - obj.material.fogDistance = fogDistance; + obj.material.setUniform('fogDistance', fogDistance); } } diff --git a/utils/debug/TileDebug.js b/utils/debug/TileDebug.js index 1866698264..dc19bbebe6 100644 --- a/utils/debug/TileDebug.js +++ b/utils/debug/TileDebug.js @@ -31,7 +31,7 @@ let selectedNode; function selectTileAt(view, mouseOrEvt, showInfo = true) { if (selectedNode) { selectedNode.material.overlayAlpha = 0; - selectedNode.material.showOutline = view.tileLayer.showOutline; + selectedNode.material.setUniform('showOutline', view.tileLayer.showOutline); view.notifyChange(selectedNode); } @@ -44,7 +44,7 @@ function selectTileAt(view, mouseOrEvt, showInfo = true) { console.info(selectedNode); } selectedNode.material.overlayAlpha = 0.5; - selectedNode.material.showOutline = true; + selectedNode.material.setUniform('showOutline', true); view.notifyChange(selectedNode); } return selectedNode; @@ -79,7 +79,7 @@ export default function createTileDebugUI(datDebugTool, view, layer, debugInstan layer.showOutline = newValue; applyToNodeFirstMaterial(view, layer.object3d, layer, (material) => { - material.showOutline = newValue; + material.setUniform('showOutline', newValue); }); }); @@ -235,7 +235,7 @@ export default function createTileDebugUI(datDebugTool, view, layer, debugInstan circle.style.width = `${object.size}px`; circle.style.height = `${object.size}px`; circle.innerHTML = `${Math.floor(object.size)} px`; - circle.style.left = `${coords.x - object.size * 0.5}px`; + circle.style.left = `${coords.x - object.size * 0.5}px`; circle.style.top = `${coords.y - object.size * 0.5}px`; }) .onComplete(removeAnimationRequester) From 46b6bf9c9e56059a5c7809613cdcb1e24a687304 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Duhen?= Date: Wed, 22 Jan 2025 10:56:43 +0100 Subject: [PATCH 2/2] fix(InfoLayer): use new LayeredMaterial API --- src/Layer/InfoLayer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Layer/InfoLayer.js b/src/Layer/InfoLayer.js index d82677e744..0e5a17d31b 100644 --- a/src/Layer/InfoLayer.js +++ b/src/Layer/InfoLayer.js @@ -35,7 +35,7 @@ export class InfoTiledGeometryLayer extends InfoLayer { let layers = []; this.displayed.tiles.forEach((tile) => { const m = tile.material; - layers = [...new Set([...layers, ...m.colorLayerIds.filter(id => m.getLayer(id)), ...m.elevationLayerIds])]; + layers = [...new Set([...layers, ...m.colorTileIds.filter(id => m.getColorTile(id)), m.elevationTileId])]; }); return this.layer.attachedLayers.filter(l => layers.includes(l.id));