diff --git a/src/render/draw_hillshade.js b/src/render/draw_hillshade.js index ae30f19bebf..c72c3912106 100644 --- a/src/render/draw_hillshade.js +++ b/src/render/draw_hillshade.js @@ -23,16 +23,17 @@ function drawHillshade(painter: Painter, sourceCache: SourceCache, layer: Hillsh const sourceMaxZoom = sourceCache.getSource().maxzoom; const depthMode = painter.depthModeForSublayer(0, DepthMode.ReadOnly); - const stencilMode = StencilMode.disabled; const colorMode = painter.colorModeForRenderPass(); - for (const tileID of tileIDs) { - const tile = sourceCache.getTile(tileID); + const [stencilModes, coords] = painter.renderPass === 'translucent' ? + painter.stencilConfigForOverlap(tileIDs) : [{}, tileIDs]; + + for (const coord of coords) { + const tile = sourceCache.getTile(coord); if (tile.needsHillshadePrepare && painter.renderPass === 'offscreen') { - prepareHillshade(painter, tile, layer, sourceMaxZoom, depthMode, stencilMode, colorMode); - continue; + prepareHillshade(painter, tile, layer, sourceMaxZoom, depthMode, StencilMode.disabled, colorMode); } else if (painter.renderPass === 'translucent') { - renderHillshade(painter, tile, layer, depthMode, stencilMode, colorMode); + renderHillshade(painter, tile, layer, depthMode, stencilModes[coord.overscaledZ], colorMode); } } @@ -52,15 +53,9 @@ function renderHillshade(painter, tile, layer, depthMode, stencilMode, colorMode const uniformValues = hillshadeUniformValues(painter, tile, layer); - if (tile.maskedBoundsBuffer && tile.maskedIndexBuffer && tile.segments) { - program.draw(context, gl.TRIANGLES, depthMode, stencilMode, colorMode, CullFaceMode.disabled, - uniformValues, layer.id, tile.maskedBoundsBuffer, - tile.maskedIndexBuffer, tile.segments); - } else { - program.draw(context, gl.TRIANGLES, depthMode, stencilMode, colorMode, CullFaceMode.disabled, - uniformValues, layer.id, painter.rasterBoundsBuffer, - painter.quadTriangleIndexBuffer, painter.rasterBoundsSegments); - } + program.draw(context, gl.TRIANGLES, depthMode, stencilMode, colorMode, CullFaceMode.disabled, + uniformValues, layer.id, painter.rasterBoundsBuffer, + painter.quadTriangleIndexBuffer, painter.rasterBoundsSegments); } // hillshade rendering is done in two steps. the prepare step first calculates the slope of the terrain in the x and y diff --git a/src/render/draw_raster.js b/src/render/draw_raster.js index ad9598308a4..d65cffaa900 100644 --- a/src/render/draw_raster.js +++ b/src/render/draw_raster.js @@ -16,18 +16,23 @@ import type {OverscaledTileID} from '../source/tile_id'; export default drawRaster; -function drawRaster(painter: Painter, sourceCache: SourceCache, layer: RasterStyleLayer, coords: Array) { +function drawRaster(painter: Painter, sourceCache: SourceCache, layer: RasterStyleLayer, tileIDs: Array) { if (painter.renderPass !== 'translucent') return; if (layer.paint.get('raster-opacity') === 0) return; + if (!tileIDs.length) return; const context = painter.context; const gl = context.gl; const source = sourceCache.getSource(); const program = painter.useProgram('raster'); - const stencilMode = StencilMode.disabled; const colorMode = painter.colorModeForRenderPass(); - const minTileZ = coords.length && coords[0].overscaledZ; + + const [stencilModes, coords] = source instanceof ImageSource ? [{}, tileIDs] : + painter.stencilConfigForOverlap(tileIDs); + + const minTileZ = coords[coords.length - 1].overscaledZ; + const align = !painter.options.moving; for (const coord of coords) { // Set the lower zoom level to sublayer 0, and higher zoom levels to higher sublayers @@ -64,16 +69,11 @@ function drawRaster(painter: Painter, sourceCache: SourceCache, layer: RasterSty const uniformValues = rasterUniformValues(posMatrix, parentTL || [0, 0], parentScaleBy || 1, fade, layer); if (source instanceof ImageSource) { - program.draw(context, gl.TRIANGLES, depthMode, stencilMode, colorMode, CullFaceMode.disabled, + program.draw(context, gl.TRIANGLES, depthMode, StencilMode.disabled, colorMode, CullFaceMode.disabled, uniformValues, layer.id, source.boundsBuffer, painter.quadTriangleIndexBuffer, source.boundsSegments); - } else if (tile.maskedBoundsBuffer && tile.maskedIndexBuffer && tile.segments) { - program.draw(context, gl.TRIANGLES, depthMode, stencilMode, colorMode, CullFaceMode.disabled, - uniformValues, layer.id, tile.maskedBoundsBuffer, - tile.maskedIndexBuffer, tile.segments, layer.paint, - painter.transform.zoom); } else { - program.draw(context, gl.TRIANGLES, depthMode, stencilMode, colorMode, CullFaceMode.disabled, + program.draw(context, gl.TRIANGLES, depthMode, stencilModes[coord.overscaledZ], colorMode, CullFaceMode.disabled, uniformValues, layer.id, painter.rasterBoundsBuffer, painter.quadTriangleIndexBuffer, painter.rasterBoundsSegments); } diff --git a/src/render/painter.js b/src/render/painter.js index 04e2189825e..30661433347 100644 --- a/src/render/painter.js +++ b/src/render/painter.js @@ -22,7 +22,6 @@ import StencilMode from '../gl/stencil_mode'; import ColorMode from '../gl/color_mode'; import CullFaceMode from '../gl/cull_face_mode'; import Texture from './texture'; -import updateTileMasks from './tile_mask'; import {clippingMaskUniformValues} from './program/clipping_mask_program'; import Color from '../style-spec/util/color'; import symbol from './draw_symbol'; @@ -296,6 +295,36 @@ class Painter { return new StencilMode({func: gl.EQUAL, mask: 0xFF}, this._tileClippingMaskIDs[tileID.key], 0x00, gl.KEEP, gl.KEEP, gl.REPLACE); } + /* + * Sort coordinates by Z as drawing tiles is done in Z-descending order. + * All children with the same Z write the same stencil value. Children + * stencil values are greater than parent's. This is used only for raster + * and raster-dem tiles, which are already clipped to tile boundaries, to + * mask area of tile overlapped by children tiles. + * Stencil ref values continue range used in _tileClippingMaskIDs. + * + * Returns [StencilMode for tile overscaleZ map, sortedCoords]. + */ + stencilConfigForOverlap(tileIDs: Array): [{[number]: $ReadOnly}, Array] { + const gl = this.context.gl; + const coords = tileIDs.sort((a, b) => b.overscaledZ - a.overscaledZ); + const minTileZ = coords[coords.length - 1].overscaledZ; + const stencilValues = coords[0].overscaledZ - minTileZ + 1; + if (stencilValues > 1) { + this.currentStencilSource = undefined; + if (this.nextStencilID + stencilValues > 256) { + this.clearStencil(); + } + const zToStencilMode = {}; + for (let i = 0; i < stencilValues; i++) { + zToStencilMode[i + minTileZ] = new StencilMode({func: gl.GEQUAL, mask: 0xFF}, i + this.nextStencilID, 0xFF, gl.KEEP, gl.KEEP, gl.REPLACE); + } + this.nextStencilID += stencilValues; + return [zToStencilMode, coords]; + } + return [{[minTileZ]: StencilMode.disabled}, coords]; + } + colorModeForRenderPass(): $ReadOnly { const gl = this.context.gl; if (this._showOverdrawInspector) { @@ -360,15 +389,6 @@ class Painter { coordsDescendingSymbol[id] = sourceCache.getVisibleCoordinates(true).reverse(); } - for (const id in sourceCaches) { - const sourceCache = sourceCaches[id]; - const source = sourceCache.getSource(); - if (source.type !== 'raster' && source.type !== 'raster-dem') continue; - const visibleTiles = []; - for (const coord of coordsAscending[id]) visibleTiles.push(sourceCache.getTile(coord)); - updateTileMasks(visibleTiles, this.context); - } - this.opaquePassCutoff = Infinity; for (let i = 0; i < layerIds.length; i++) { const layerId = layerIds[i]; diff --git a/src/render/tile_mask.js b/src/render/tile_mask.js deleted file mode 100644 index a88238a3105..00000000000 --- a/src/render/tile_mask.js +++ /dev/null @@ -1,104 +0,0 @@ -// @flow - -import {OverscaledTileID, CanonicalTileID} from '../source/tile_id'; - -import type Tile from './../source/tile'; -import type Context from '../gl/context'; - -export type Mask = { - [number]: CanonicalTileID -}; - -// Updates the TileMasks for all renderable tiles. A TileMask describes all regions -// within that tile that are *not* covered by other renderable tiles. -// Example: renderableTiles in our list are 2/1/3, 3/3/6, and 4/5/13. The schematic for creating the -// TileMask for 2/1/3 looks like this: -// -// ┌────────┬────────┬─────────────────┐ -// │ │ │#################│ -// │ 4/4/12 │ 4/5/12 │#################│ -// │ │ │#################│ -// ├──────3/2/6──────┤#####3/3/6#######│ -// │ │########│#################│ -// │ 4/4/13 │#4/5/13#│#################│ -// │ │########│#################│ -// ├────────┴──────2/1/3───────────────┤ -// │ │ │ -// │ │ │ -// │ │ │ -// │ 3/2/7 │ 3/3/7 │ -// │ │ │ -// │ │ │ -// │ │ │ -// └─────────────────┴─────────────────┘ -// -// The TileMask for 2/1/3 thus consists of the tiles 4/4/12, 4/5/12, 4/4/13, 3/2/7, and 3/3/7, -// but it does *not* include 4/5/13, and 3/3/6, since these are other renderableTiles. -// A TileMask always contains TileIDs *relative* to the tile it is generated for, so 2/1/3 is -// "subtracted" from these TileIDs. The final TileMask for 2/1/3 will thus be: -// -// ┌────────┬────────┬─────────────────┐ -// │ │ │#################│ -// │ 2/0/0 │ 2/1/0 │#################│ -// │ │ │#################│ -// ├────────┼────────┤#################│ -// │ │########│#################│ -// │ 2/0/1 │########│#################│ -// │ │########│#################│ -// ├────────┴────────┼─────────────────┤ -// │ │ │ -// │ │ │ -// │ │ │ -// │ 1/0/1 │ 1/1/1 │ -// │ │ │ -// │ │ │ -// │ │ │ -// └─────────────────┴─────────────────┘ -// -// Only other renderable tiles that are *children* of the tile we are generating the mask for will -// be considered. For example, adding TileID 4/8/13 to renderableTiles won't affect the TileMask for -// 2/1/3, since it is not a descendant of it. - -export default function(renderableTiles: Array, context: Context) { - const sortedRenderables = renderableTiles.sort((a, b) => { return a.tileID.isLessThan(b.tileID) ? -1 : b.tileID.isLessThan(a.tileID) ? 1 : 0; }); - - for (let i = 0; i < sortedRenderables.length; i++) { - const mask = {}; - const tile = sortedRenderables[i]; - const childArray = sortedRenderables.slice(i + 1); - // Try to add all remaining ids as children. We sorted the tile list - // by z earlier, so all preceding items cannot be children of the current - // tile. We also compute the lower bound of the next wrap, because items of the next wrap - // can never be children of the current wrap. - - computeTileMasks(tile.tileID.wrapped(), tile.tileID, childArray, new OverscaledTileID(0, tile.tileID.wrap + 1, 0, 0, 0), mask); - tile.setMask(mask, context); - } -} - -function computeTileMasks(rootTile: OverscaledTileID, ref: OverscaledTileID, childArray: Array, lowerBound: OverscaledTileID, mask: Mask) { - // If the reference or any of its children is found in the list, we need to recurse. - for (let i = 0; i < childArray.length; i++) { - const childTile = childArray[i]; - // childTile is from a larger wrap than the rootTile so it cannot be a child tile - if (lowerBound.isLessThan(childTile.tileID)) break; - // The current tile is masked out, so we don't need to add them to the mask set. - if (ref.key === childTile.tileID.key) { - return; - } else if (childTile.tileID.isChildOf(ref)) { - // There's at least one child tile that is masked out, so recursively descend - const children = ref.children(Infinity); - for (let j = 0; j < children.length; j++) { - const child = children[j]; - computeTileMasks(rootTile, child, childArray.slice(i), lowerBound, mask); - } - return; - } - } - // We couldn't find a child, so it's definitely a masked part. - // Compute the difference between the root tile ID and the reference tile ID, since TileMask - // elements are always relative (see below for explanation). - const diffZ = ref.overscaledZ - rootTile.overscaledZ; - const maskTileId = new CanonicalTileID(diffZ, ref.canonical.x - (rootTile.canonical.x << diffZ), ref.canonical.y - (rootTile.canonical.y << diffZ)); - mask[maskTileId.key] = mask[maskTileId.key] || maskTileId; -} diff --git a/src/source/geojson_source.js b/src/source/geojson_source.js index f9ce5f39ab9..16a9379f782 100644 --- a/src/source/geojson_source.js +++ b/src/source/geojson_source.js @@ -326,7 +326,6 @@ class GeoJSONSource extends Evented implements Source { unloadTile(tile: Tile) { tile.unloadVectorData(); - tile.clearMask(); this.actor.send('removeTile', {uid: tile.uid, type: this.type, source: this.id}); } diff --git a/src/source/raster_dem_tile_source.js b/src/source/raster_dem_tile_source.js index d4102dad134..cfd735330e2 100644 --- a/src/source/raster_dem_tile_source.js +++ b/src/source/raster_dem_tile_source.js @@ -123,7 +123,6 @@ class RasterDEMTileSource extends RasterTileSource implements Source { } if (tile.dem) delete tile.dem; delete tile.neighboringTiles; - tile.clearMask(); tile.state = 'unloaded'; if (tile.actor) { diff --git a/src/source/raster_tile_source.js b/src/source/raster_tile_source.js index 7da0ecfdb89..fa55b14722f 100644 --- a/src/source/raster_tile_source.js +++ b/src/source/raster_tile_source.js @@ -158,7 +158,6 @@ class RasterTileSource extends Evented implements Source { unloadTile(tile: Tile, callback: Callback) { if (tile.texture) this.map.painter.saveTileTexture(tile.texture); - tile.clearMask(); callback(); } diff --git a/src/source/tile.js b/src/source/tile.js index ad26b13adce..c4670332699 100644 --- a/src/source/tile.js +++ b/src/source/tile.js @@ -1,18 +1,13 @@ // @flow -import {uniqueId, deepEqual, parseCacheControl} from '../util/util'; +import {uniqueId, parseCacheControl} from '../util/util'; import {deserialize as deserializeBucket} from '../data/bucket'; import FeatureIndex from '../data/feature_index'; import GeoJSONFeature from '../util/vectortile_to_geojson'; import featureFilter from '../style-spec/feature_filter'; import SymbolBucket from '../data/bucket/symbol_bucket'; -import {RasterBoundsArray, CollisionBoxArray} from '../data/array_types'; -import rasterBoundsAttributes from '../data/raster_bounds_attributes'; -import EXTENT from '../data/extent'; -import Point from '@mapbox/point-geometry'; +import {CollisionBoxArray} from '../data/array_types'; import Texture from '../render/texture'; -import SegmentVector from '../data/segment'; -import {TriangleIndexArray} from '../data/index_array_type'; import browser from '../util/browser'; import EvaluationParameters from '../style/evaluation_parameters'; import SourceFeatureState from '../source/source_state'; @@ -27,10 +22,7 @@ import type DEMData from '../data/dem_data'; import type {AlphaImage} from '../util/image'; import type ImageAtlas from '../render/image_atlas'; import type ImageManager from '../render/image_manager'; -import type {Mask} from '../render/tile_mask'; import type Context from '../gl/context'; -import type IndexBuffer from '../gl/index_buffer'; -import type VertexBuffer from '../gl/vertex_buffer'; import type {OverscaledTileID} from './tile_id'; import type Framebuffer from '../gl/framebuffer'; import type Transform from '../geo/transform'; @@ -76,14 +68,10 @@ class Tile { placementSource: any; actor: ?Actor; vtLayers: {[string]: VectorTileLayer}; - mask: Mask; neighboringTiles: ?Object; dem: ?DEMData; aborted: ?boolean; - maskedBoundsBuffer: ?VertexBuffer; - maskedIndexBuffer: ?IndexBuffer; - segments: ?SegmentVector; needsHillshadePrepare: ?boolean; request: ?Cancelable; texture: any; @@ -322,72 +310,6 @@ class Tile { } } - clearMask() { - if (this.segments) { - this.segments.destroy(); - delete this.segments; - } - if (this.maskedBoundsBuffer) { - this.maskedBoundsBuffer.destroy(); - delete this.maskedBoundsBuffer; - } - if (this.maskedIndexBuffer) { - this.maskedIndexBuffer.destroy(); - delete this.maskedIndexBuffer; - } - - delete this.mask; - } - - setMask(mask: Mask, context: Context) { - - // don't redo buffer work if the mask is the same; - if (deepEqual(this.mask, mask)) return; - - this.clearMask(); - this.mask = mask; - - // We want to render the full tile, and keeping the segments/vertices/indices empty means - // using the global shared buffers for covering the entire tile. - if (deepEqual(mask, {'0': true})) return; - - const maskedBoundsArray = new RasterBoundsArray(); - const indexArray = new TriangleIndexArray(); - - this.segments = new SegmentVector(); - // Create a new segment so that we will upload (empty) buffers even when there is nothing to - // draw for this tile. - this.segments.prepareSegment(0, maskedBoundsArray, indexArray); - - const maskArray = Object.keys(mask); - for (let i = 0; i < maskArray.length; i++) { - const maskCoord = mask[+maskArray[i]]; - const vertexExtent = EXTENT >> maskCoord.z; - const tlVertex = new Point(maskCoord.x * vertexExtent, maskCoord.y * vertexExtent); - const brVertex = new Point(tlVertex.x + vertexExtent, tlVertex.y + vertexExtent); - - // not sure why flow is complaining here because it doesn't complain at L401 - const segment = (this.segments: any).prepareSegment(4, maskedBoundsArray, indexArray); - - maskedBoundsArray.emplaceBack(tlVertex.x, tlVertex.y, tlVertex.x, tlVertex.y); - maskedBoundsArray.emplaceBack(brVertex.x, tlVertex.y, brVertex.x, tlVertex.y); - maskedBoundsArray.emplaceBack(tlVertex.x, brVertex.y, tlVertex.x, brVertex.y); - maskedBoundsArray.emplaceBack(brVertex.x, brVertex.y, brVertex.x, brVertex.y); - - const offset = segment.vertexLength; - // 0, 1, 2 - // 1, 2, 3 - indexArray.emplaceBack(offset, offset + 1, offset + 2); - indexArray.emplaceBack(offset + 1, offset + 2, offset + 3); - - segment.vertexLength += 4; - segment.primitiveLength += 2; - } - - this.maskedBoundsBuffer = context.createVertexBuffer(maskedBoundsArray, rasterBoundsAttributes.members); - this.maskedIndexBuffer = context.createIndexBuffer(indexArray); - } - hasData() { return this.state === 'loaded' || this.state === 'reloading' || this.state === 'expired'; } diff --git a/src/source/vector_tile_source.js b/src/source/vector_tile_source.js index 957c2ef6b72..681c5b417d7 100644 --- a/src/source/vector_tile_source.js +++ b/src/source/vector_tile_source.js @@ -187,7 +187,6 @@ class VectorTileSource extends Evented implements Source { unloadTile(tile: Tile) { tile.unloadVectorData(); - tile.clearMask(); if (tile.actor) { tile.actor.send('removeTile', {uid: tile.uid, type: this.type, source: this.id}, undefined); } diff --git a/test/integration/render-tests/raster-masking/overlapping-vector/expected.png b/test/integration/render-tests/raster-masking/overlapping-vector/expected.png new file mode 100644 index 00000000000..043b1fdab05 Binary files /dev/null and b/test/integration/render-tests/raster-masking/overlapping-vector/expected.png differ diff --git a/test/integration/render-tests/raster-masking/overlapping-vector/style.json b/test/integration/render-tests/raster-masking/overlapping-vector/style.json new file mode 100644 index 00000000000..d5253448cf3 --- /dev/null +++ b/test/integration/render-tests/raster-masking/overlapping-vector/style.json @@ -0,0 +1,90 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 256 + } + }, + "center": [ + -122.48, + 37.84 + ], + "zoom": 14, + "sources": { + "contour": { + "type": "raster", + "tiles": [ + "local://tiles/{z}-{x}-{y}.contour.png" + ], + "maxzoom": 17, + "tileSize": 256 + }, + "geojson": { + "type": "geojson", + "data": { + "type": "Polygon", + "coordinates": [ + [ + [ + -123, + 37.839 + ], + [ + -123, + 37.843 + ], + [ + -122, + 37.843 + ], + [ + -122, + 37.839 + ], + [ + -123, + 37.839 + ] + ] + ] + } + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "raster", + "type": "raster", + "source": "contour", + "paint": { + "raster-fade-duration": 0 + } + }, + { + "id": "fill", + "type": "fill", + "source": "geojson", + "paint": { + "fill-antialias": false, + "fill-color": "green", + "fill-opacity": 0.2 + } + }, + { + "id": "raster-transparent", + "type": "raster", + "source": "contour", + "paint": { + "raster-fade-duration": 0, + "raster-opacity": 0.5, + "raster-hue-rotate": 90 + } + } + ] +} diff --git a/test/unit/source/tile.test.js b/test/unit/source/tile.test.js index 9f0a1d033e8..e36b2994892 100644 --- a/test/unit/source/tile.test.js +++ b/test/unit/source/tile.test.js @@ -8,7 +8,6 @@ import vtpbf from 'vt-pbf'; import FeatureIndex from '../../../src/data/feature_index'; import {CollisionBoxArray} from '../../../src/data/array_types'; import {extend} from '../../../src/util/util'; -import Context from '../../../src/gl/context'; import {serialize, deserialize} from '../../../src/util/web_worker_transfer'; test('querySourceFeatures', (t) => { @@ -133,46 +132,6 @@ test('querySourceFeatures', (t) => { t.end(); }); -test('Tile#setMask', (t) => { - - t.test('simple mask', (t) => { - const tile = new Tile(0, 0, 0); - const context = new Context(require('gl')(10, 10)); - const a = new OverscaledTileID(1, 0, 1, 0, 0); - const b = new OverscaledTileID(1, 0, 1, 1, 1); - const mask = {}; - mask[a.key] = a; - mask[b.key] = b; - tile.setMask(mask, context); - t.deepEqual(tile.mask, mask); - t.end(); - }); - - t.test('complex mask', (t) => { - const tile = new Tile(0, 0, 0); - const context = new Context(require('gl')(10, 10)); - const a = new OverscaledTileID(1, 0, 1, 0, 1); - const b = new OverscaledTileID(1, 0, 1, 1, 0); - const c = new OverscaledTileID(2, 0, 2, 2, 3); - const d = new OverscaledTileID(2, 0, 2, 3, 2); - const e = new OverscaledTileID(3, 0, 3, 6, 7); - const f = new OverscaledTileID(3, 0, 3, 7, 6); - const mask = {}; - mask[a.key] = a; - mask[b.key] = b; - mask[c.key] = c; - mask[d.key] = d; - mask[e.key] = e; - mask[f.key] = f; - tile.setMask(mask, context); - t.deepEqual(tile.mask, mask); - t.end(); - - }); - t.end(); - -}); - test('Tile#isLessThan', (t) => { t.test('correctly sorts tiles', (t) => { const tiles = [ diff --git a/test/unit/source/tile_mask.test.js b/test/unit/source/tile_mask.test.js deleted file mode 100644 index fd1728b1a9d..00000000000 --- a/test/unit/source/tile_mask.test.js +++ /dev/null @@ -1,151 +0,0 @@ -import {test} from '../../util/test'; -import updateTileMasks from '../../../src/render/tile_mask'; -import {OverscaledTileID} from '../../../src/source/tile_id'; - -test('computeTileMasks', (t) => { - class Tile { - constructor(z, x, y, w) { - const sourceMaxZoom = 16; - this.tileID = new OverscaledTileID(z, w || 0, Math.min(sourceMaxZoom, z), x, y); - } - - setMask(mask) { - this.mask = mask; - } - - getMask() { - return this.mask; - } - - hasData() { - return true; - } - } - - t.test('no children', (t) => { - const renderables = [new Tile(0, 0, 0) ]; - updateTileMasks(renderables); - t.deepEqual(Object.keys(renderables[0].mask), [new OverscaledTileID(0, 0, 0, 0, 0).key]); - - const renderables2 = [new Tile(4, 3, 8)]; - updateTileMasks(renderables2); - t.deepEqual(Object.keys(renderables2[0].mask), [new OverscaledTileID(0, 0, 0, 0, 0).key]); - - const renderables3 = [new Tile(1, 0, 0), new Tile(1, 1, 1)]; - updateTileMasks(renderables3); - t.deepEqual(renderables3.map((r) => { return Object.keys(r.mask); }), [[new OverscaledTileID(0, 0, 0, 0, 0).key], [new OverscaledTileID(0, 0, 0, 0, 0).key]]); - - const renderables4 = [new Tile(1, 0, 0), new Tile(2, 2, 3)]; - updateTileMasks(renderables4); - t.deepEqual(renderables4.map((r) => { return Object.keys(r.mask); }), [[new OverscaledTileID(0, 0, 0, 0, 0).key], [new OverscaledTileID(0, 0, 0, 0, 0).key]]); - t.end(); - }); - - t.test('parents with all four children', (t) => { - const renderables = [new Tile(0, 0, 0), new Tile(1, 0, 0), new Tile(1, 0, 1), new Tile(1, 1, 0), new Tile(1, 1, 1)]; - updateTileMasks(renderables); - t.deepEqual(renderables.map((r) => { return Object.keys(r.mask); }), [ - // empty mask -- i.e. don't draw anything because child tiles cover the whole parent tile - [], - [new OverscaledTileID(0, 0, 0, 0, 0).key], - [new OverscaledTileID(0, 0, 0, 0, 0).key], - [new OverscaledTileID(0, 0, 0, 0, 0).key], - [new OverscaledTileID(0, 0, 0, 0, 0).key]]); - t.end(); - }); - - t.test('parent and one child', (t) => { - const renderables = [new Tile(0, 0, 0), new Tile(1, 0, 0)]; - updateTileMasks(renderables); - t.deepEqual(renderables.map((r) => { return Object.keys(r.mask); }), [ - [ - new OverscaledTileID(1, 0, 1, 1, 0).key, - new OverscaledTileID(1, 0, 1, 0, 1).key, - new OverscaledTileID(1, 0, 1, 1, 1).key - ], - [new OverscaledTileID(0, 0, 0, 0, 0).key] - ]); - t.end(); - }); - - t.test('complex masks', (t) => { - const renderables = [new Tile(12, 1028, 1456), - new Tile(13, 2056, 2912), - new Tile(13, 2056, 2913), - new Tile(14, 4112, 5824), - new Tile(14, 4112, 5827), - new Tile(14, 4114, 5824), - new Tile(14, 4114, 5825)]; - updateTileMasks(renderables); - t.deepEqual(renderables.map((r) => { return Object.keys(r.mask); }), [ - [ - new OverscaledTileID(1, 0, 1, 1, 1).key.toString(), - new OverscaledTileID(2, 0, 2, 3, 0).key.toString(), - new OverscaledTileID(2, 0, 2, 3, 1).key.toString(), - ], - [ - new OverscaledTileID(1, 0, 1, 1, 0).key.toString(), - new OverscaledTileID(1, 0, 1, 0, 1).key.toString(), - new OverscaledTileID(1, 0, 1, 1, 1).key.toString() - ], - [ - new OverscaledTileID(1, 0, 1, 0, 0).key.toString(), - new OverscaledTileID(1, 0, 1, 1, 0).key.toString(), - new OverscaledTileID(1, 0, 1, 1, 1).key.toString() - ], - [new OverscaledTileID(0, 0, 0, 0, 0).key.toString()], - [new OverscaledTileID(0, 0, 0, 0, 0).key.toString()], - [new OverscaledTileID(0, 0, 0, 0, 0).key.toString()], - [new OverscaledTileID(0, 0, 0, 0, 0).key.toString()] - ]); - t.end(); - }); - - t.test('deep descendent masks', (t) => { - const renderables = [ new Tile(0, 0, 0), new Tile(4, 4, 4)]; - updateTileMasks(renderables); - t.deepEqual(renderables.map((r) => { return Object.keys(r.mask); }), [ - [ - new OverscaledTileID(2, 0, 2, 0, 0).key.toString(), - new OverscaledTileID(1, 0, 1, 1, 0).key.toString(), - new OverscaledTileID(2, 0, 2, 1, 0).key.toString(), - new OverscaledTileID(1, 0, 1, 0, 1).key.toString(), - new OverscaledTileID(1, 0, 1, 1, 1).key.toString(), - new OverscaledTileID(2, 0, 2, 0, 1).key.toString(), - new OverscaledTileID(3, 0, 3, 3, 2).key.toString(), - new OverscaledTileID(3, 0, 3, 2, 3).key.toString(), - new OverscaledTileID(3, 0, 3, 3, 3).key.toString(), - new OverscaledTileID(4, 0, 4, 5, 4).key.toString(), - new OverscaledTileID(4, 0, 4, 4, 5).key.toString(), - new OverscaledTileID(4, 0, 4, 5, 5).key.toString(), - ], - [ - new OverscaledTileID(0, 0, 0, 0, 0).key.toString() - ] - ]); - t.end(); - }); - - t.test('wrapped tile masks', (t) => { - const renderables = [new Tile(0, 0, 0, 1), new Tile(1, 0, 0, 1), new Tile(2, 2, 2, 1), new Tile(3, 7, 7, 1), new Tile(3, 6, 6, 1)]; - updateTileMasks(renderables); - t.deepEqual(renderables.map((r) => { return Object.keys(r.mask); }), [ - [ - new OverscaledTileID(1, 0, 1, 1, 0).key.toString(), - new OverscaledTileID(1, 0, 1, 0, 1).key.toString(), - new OverscaledTileID(2, 0, 2, 3, 2).key.toString(), - new OverscaledTileID(2, 0, 2, 2, 3).key.toString(), - new OverscaledTileID(3, 0, 3, 7, 6).key.toString(), - new OverscaledTileID(3, 0, 3, 6, 7).key.toString() - ], - [new OverscaledTileID(0, 0, 0, 0, 0).key.toString()], - [new OverscaledTileID(0, 0, 0, 0, 0).key.toString()], - [new OverscaledTileID(0, 0, 0, 0, 0).key.toString()], - [new OverscaledTileID(0, 0, 0, 0, 0).key.toString()] - - ]); - t.end(); - }); - - t.end(); -});