diff --git a/src/render/draw_background.js b/src/render/draw_background.js index 64c22178ddf..ba00f47e956 100644 --- a/src/render/draw_background.js +++ b/src/render/draw_background.js @@ -16,8 +16,8 @@ function drawBackground(painter: Painter, sourceCache: SourceCache, layer: Style const image = layer.paint['background-pattern']; const opacity = layer.paint['background-opacity']; - const isOpaque = !image && color[3] === 1 && opacity === 1; - if (painter.isOpaquePass !== isOpaque) return; + const pass = (!image && color[3] === 1 && opacity === 1) ? 'opaque' : 'translucent'; + if (painter.renderPass !== pass) return; gl.disable(gl.STENCIL_TEST); diff --git a/src/render/draw_circle.js b/src/render/draw_circle.js index 6d076e3ca84..bed8a212334 100644 --- a/src/render/draw_circle.js +++ b/src/render/draw_circle.js @@ -12,7 +12,7 @@ import type TileCoord from '../source/tile_coord'; module.exports = drawCircles; function drawCircles(painter: Painter, sourceCache: SourceCache, layer: CircleStyleLayer, coords: Array) { - if (painter.isOpaquePass) return; + if (painter.renderPass !== 'translucent') return; const gl = painter.gl; diff --git a/src/render/draw_fill.js b/src/render/draw_fill.js index b0a29f5db5b..5435a9c632b 100644 --- a/src/render/draw_fill.js +++ b/src/render/draw_fill.js @@ -14,15 +14,14 @@ function drawFill(painter: Painter, sourceCache: SourceCache, layer: FillStyleLa const gl = painter.gl; gl.enable(gl.STENCIL_TEST); - const isOpaque = - !layer.paint['fill-pattern'] && + const pass = (!layer.paint['fill-pattern'] && layer.isPaintValueFeatureConstant('fill-color') && layer.isPaintValueFeatureConstant('fill-opacity') && layer.paint['fill-color'][3] === 1 && - layer.paint['fill-opacity'] === 1; + layer.paint['fill-opacity'] === 1) ? 'opaque' : 'translucent'; // Draw fill - if (painter.isOpaquePass === isOpaque) { + if (painter.renderPass === pass) { // Once we switch to earcut drawing we can pull most of the WebGL setup // outside of this coords loop. painter.setDepthSublayer(1); @@ -30,7 +29,7 @@ function drawFill(painter: Painter, sourceCache: SourceCache, layer: FillStyleLa } // Draw stroke - if (!painter.isOpaquePass && layer.paint['fill-antialias']) { + if (painter.renderPass === 'translucent' && layer.paint['fill-antialias']) { painter.lineWidth(2); painter.depthMask(false); diff --git a/src/render/draw_fill_extrusion.js b/src/render/draw_fill_extrusion.js index 20cfdde4d26..0150a3ef1df 100644 --- a/src/render/draw_fill_extrusion.js +++ b/src/render/draw_fill_extrusion.js @@ -1,9 +1,6 @@ // @flow const glMatrix = require('@mapbox/gl-matrix'); -const VertexBuffer = require('../gl/vertex_buffer'); -const VertexArrayObject = require('./vertex_array_object'); -const PosArray = require('../data/pos_array'); const pattern = require('./pattern'); const mat3 = glMatrix.mat3; const mat4 = glMatrix.mat4; @@ -18,95 +15,51 @@ import type TileCoord from '../source/tile_coord'; module.exports = draw; function draw(painter: Painter, source: SourceCache, layer: FillExtrusionStyleLayer, coords: Array) { - if (painter.isOpaquePass) return; - if (layer.paint['fill-extrusion-opacity'] === 0) return; + if (painter.renderPass === '3d') { + const gl = painter.gl; - const gl = painter.gl; - gl.disable(gl.STENCIL_TEST); - gl.enable(gl.DEPTH_TEST); - painter.depthMask(true); - - // Create a new texture to which to render the extrusion layer. This approach - // allows us to adjust opacity on a per-layer basis (eliminating the interior - // walls per-feature opacity problem) - const texture = renderToTexture(gl, painter); + gl.disable(gl.STENCIL_TEST); + gl.enable(gl.DEPTH_TEST); - gl.clearColor(0, 0, 0, 0); - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + painter.clearColor(); + painter.depthMask(true); - for (let i = 0; i < coords.length; i++) { - drawExtrusion(painter, source, layer, coords[i]); + for (let i = 0; i < coords.length; i++) { + drawExtrusion(painter, source, layer, coords[i]); + } + } else if (painter.renderPass === 'translucent') { + drawExtrusionTexture(painter, layer); } - - // Unbind the framebuffer as a render target and render it to the map - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - renderTextureToMap(gl, painter, layer, texture); } -function renderToTexture(gl, painter) { - gl.activeTexture(gl.TEXTURE1); - - let texture = painter.viewportTexture; - if (!texture) { - texture = gl.createTexture(); - gl.bindTexture(gl.TEXTURE_2D, texture); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, painter.width, painter.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - painter.viewportTexture = texture; - } else { - gl.bindTexture(gl.TEXTURE_2D, texture); - } +function drawExtrusionTexture(painter, layer) { + const renderedTexture = painter.prerenderedFrames[layer.id]; + if (!renderedTexture) return; - let fbo = painter.viewportFbo; - if (!fbo) { - fbo = gl.createFramebuffer(); - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); - const depthRenderBuffer = gl.createRenderbuffer(); - gl.bindRenderbuffer(gl.RENDERBUFFER, depthRenderBuffer); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, painter.width, painter.height); - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthRenderBuffer); - painter.viewportFbo = fbo; - } else { - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); - } - - gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); - - return texture; -} - -function renderTextureToMap(gl, painter, layer, texture) { + const gl = painter.gl; const program = painter.useProgram('extrusionTexture'); + gl.disable(gl.STENCIL_TEST); + gl.disable(gl.DEPTH_TEST); + gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, texture); + gl.bindTexture(gl.TEXTURE_2D, renderedTexture.texture); gl.uniform1f(program.uniforms.u_opacity, layer.paint['fill-extrusion-opacity']); - gl.uniform1i(program.uniforms.u_image, 1); + gl.uniform1i(program.uniforms.u_image, 0); const matrix = mat4.create(); mat4.ortho(matrix, 0, painter.width, painter.height, 0, 0, 1); gl.uniformMatrix4fv(program.uniforms.u_matrix, false, matrix); - gl.disable(gl.DEPTH_TEST); - gl.uniform2f(program.uniforms.u_world, gl.drawingBufferWidth, gl.drawingBufferHeight); - const array = new PosArray(); - array.emplaceBack(0, 0); - array.emplaceBack(1, 0); - array.emplaceBack(0, 1); - array.emplaceBack(1, 1); - const buffer = new VertexBuffer(gl, array); - - const vao = new VertexArrayObject(); - vao.bind(gl, program, buffer); + renderedTexture.vao.bind(gl, program, renderedTexture.buffer); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); - gl.enable(gl.DEPTH_TEST); + // Since this texture has been rendered, make it available for reuse in the next frame. + painter.viewportFrames.push(renderedTexture); + delete painter.prerenderedFrames[layer.id]; } function drawExtrusion(painter, source, layer, coord) { diff --git a/src/render/draw_line.js b/src/render/draw_line.js index 68395cddc67..8b12a5027ae 100644 --- a/src/render/draw_line.js +++ b/src/render/draw_line.js @@ -10,7 +10,7 @@ import type LineBucket from '../data/bucket/line_bucket'; import type TileCoord from '../source/tile_coord'; module.exports = function drawLine(painter: Painter, sourceCache: SourceCache, layer: LineStyleLayer, coords: Array) { - if (painter.isOpaquePass) return; + if (painter.renderPass !== 'translucent') return; painter.setDepthSublayer(0); painter.depthMask(false); diff --git a/src/render/draw_raster.js b/src/render/draw_raster.js index ebbf170daf3..fb6613c4515 100644 --- a/src/render/draw_raster.js +++ b/src/render/draw_raster.js @@ -11,7 +11,7 @@ import type TileCoord from '../source/tile_coord'; module.exports = drawRaster; function drawRaster(painter: Painter, sourceCache: SourceCache, layer: StyleLayer, coords: Array) { - if (painter.isOpaquePass) return; + if (painter.renderPass !== 'translucent') return; const gl = painter.gl; const source = sourceCache.getSource(); diff --git a/src/render/draw_symbol.js b/src/render/draw_symbol.js index a8531396e70..f24a459409e 100644 --- a/src/render/draw_symbol.js +++ b/src/render/draw_symbol.js @@ -16,7 +16,7 @@ import type TileCoord from '../source/tile_coord'; module.exports = drawSymbols; function drawSymbols(painter: Painter, sourceCache: SourceCache, layer: SymbolStyleLayer, coords: Array) { - if (painter.isOpaquePass) return; + if (painter.renderPass !== 'translucent') return; const drawAcrossEdges = !layer.layout['text-allow-overlap'] && diff --git a/src/render/painter.js b/src/render/painter.js index 219e95be187..68bfc7c3276 100644 --- a/src/render/painter.js +++ b/src/render/painter.js @@ -14,6 +14,7 @@ const PosArray = require('../data/pos_array'); const {ProgramConfiguration} = require('../data/program_configuration'); const shaders = require('../shaders'); const Program = require('./program'); +const RenderTexture = require('./render_texture'); const draw = { symbol: require('./draw_symbol'), @@ -35,6 +36,8 @@ import type LineAtlas from './line_atlas'; import type SpriteAtlas from '../symbol/sprite_atlas'; import type GlyphSource from '../symbol/glyph_source'; +export type RenderPass = '3d' | 'opaque' | 'translucent'; + type PainterOptions = { showOverdrawInspector: boolean, showTileBoundaries: boolean, @@ -60,8 +63,10 @@ class Painter { emptyProgramConfiguration: ProgramConfiguration; width: number; height: number; - viewportTexture: WebGLTexture; - viewportFbo: WebGLFramebuffer; + viewportFrames: Array; + prerenderedFrames: { [string]: ?RenderTexture }; + depthRbo: WebGLRenderbuffer; + depthRboAttached: boolean; _depthMask: boolean; tileExtentBuffer: VertexBuffer; tileExtentVAO: VertexArrayObject; @@ -79,7 +84,7 @@ class Painter { spriteAtlas: SpriteAtlas; glyphSource: GlyphSource; depthRange: number; - isOpaquePass: boolean; + renderPass: RenderPass; currentLayer: number; id: string; _showOverdrawInspector: boolean; @@ -90,6 +95,8 @@ class Painter { this.gl = gl; this.transform = transform; this._tileTextures = {}; + this.prerenderedFrames = {}; + this.viewportFrames = []; this.frameHistory = new FrameHistory(); @@ -117,13 +124,15 @@ class Painter { this.height = height * browser.devicePixelRatio; gl.viewport(0, 0, this.width, this.height); - if (this.viewportTexture) { - this.gl.deleteTexture(this.viewportTexture); - this.viewportTexture = null; + for (const frame of this.viewportFrames) { + this.gl.deleteTexture(frame.texture); + this.gl.deleteFramebuffer(frame.fbo); } - if (this.viewportFbo) { - this.gl.deleteFramebuffer(this.viewportFbo); - this.viewportFbo = null; + this.viewportFrames = []; + + if (this.depthRbo) { + this.gl.deleteRenderbuffer(this.depthRbo); + this.depthRbo = null; } } @@ -257,9 +266,6 @@ class Painter { this.frameHistory.record(Date.now(), this.transform.zoom, style.getTransition().duration); - this.clearColor(); - this.clearDepth(); - for (const id in this.style.sourceCaches) { const sourceCache = this.style.sourceCaches[id]; if (sourceCache.used) { @@ -267,62 +273,156 @@ class Painter { } } - this.showOverdrawInspector(options.showOverdrawInspector); + const layerIds = this.style._order; - this.depthRange = (style._order.length + 2) * this.numSublayers * this.depthEpsilon; + // 3D pass + // We first create a renderbuffer that we'll use to preserve depth + // results across 3D layers, then render each 3D layer to its own + // framebuffer/texture, which we'll use later in the translucent pass + // to render to the main framebuffer. By doing this before we render to + // the main framebuffer we won't have to do an expensive framebuffer + // restore mid-render pass. + // The most important distinction of the 3D pass is that we use the + // depth buffer in an entirely different way (to represent 3D space) + // than we do in the 2D pass (to preserve layer order). + this.renderPass = '3d'; + { + // We'll wait and only attach the depth renderbuffer if we think we're + // rendering something. + let first = true; + + let sourceCache; + let coords = []; + + for (let i = 0; i < layerIds.length; i++) { + const layer = this.style._layers[layerIds[i]]; + + if (!layer.has3DPass() || layer.isHidden(this.transform.zoom)) continue; + + if (layer.source !== (sourceCache && sourceCache.id)) { + sourceCache = this.style.sourceCaches[layer.source]; + coords = []; + + if (sourceCache) { + this.clearStencil(); + coords = sourceCache.getVisibleCoordinates(); + } - this.isOpaquePass = true; - this.renderPass(); - this.isOpaquePass = false; - this.renderPass(); + coords.reverse(); + } - if (this.options.showTileBoundaries) { - const sourceCache = this.style.sourceCaches[Object.keys(this.style.sourceCaches)[0]]; - if (sourceCache) { - draw.debug(this, sourceCache, sourceCache.getVisibleCoordinates()); + if (!coords.length) continue; + + this._setup3DRenderbuffer(); + + const renderTarget = this.viewportFrames.pop() || new RenderTexture(this); + renderTarget.bindWithDepth(this.depthRbo); + + if (first) { + this.clearDepth(); + first = false; + } + + this.renderLayer(this, (sourceCache: any), layer, coords); + + renderTarget.unbind(); + this.prerenderedFrames[layer.id] = renderTarget; } } - } - renderPass() { - const layerIds = this.style._order; + // Clear buffers in preparation for drawing to the main framebuffer + this.clearColor(); + this.clearDepth(); + + this.showOverdrawInspector(options.showOverdrawInspector); - let sourceCache; - let coords = []; + this.depthRange = (style._order.length + 2) * this.numSublayers * this.depthEpsilon; + + // Opaque pass + // Draw opaque layers top-to-bottom first. + this.renderPass = 'opaque'; + { + let sourceCache; + let coords = []; - this.currentLayer = this.isOpaquePass ? layerIds.length - 1 : 0; + this.currentLayer = layerIds.length - 1; - if (this.isOpaquePass) { if (!this._showOverdrawInspector) { this.gl.disable(this.gl.BLEND); } - } else { - this.gl.enable(this.gl.BLEND); - } - for (let i = 0; i < layerIds.length; i++) { - const layer = this.style._layers[layerIds[this.currentLayer]]; + for (this.currentLayer; this.currentLayer >= 0; this.currentLayer--) { + const layer = this.style._layers[layerIds[this.currentLayer]]; - if (layer.source !== (sourceCache && sourceCache.id)) { - sourceCache = this.style.sourceCaches[layer.source]; - coords = []; + if (layer.source !== (sourceCache && sourceCache.id)) { + sourceCache = this.style.sourceCaches[layer.source]; + coords = []; - if (sourceCache) { - this.clearStencil(); - coords = sourceCache.getVisibleCoordinates(); - if (sourceCache.getSource().isTileClipped) { - this._renderTileClippingMasks(coords); + if (sourceCache) { + this.clearStencil(); + coords = sourceCache.getVisibleCoordinates(); + if (sourceCache.getSource().isTileClipped) { + this._renderTileClippingMasks(coords); + } } } - if (!this.isOpaquePass) { + this.renderLayer(this, (sourceCache: any), layer, coords); + } + } + + // Translucent pass + // Draw all other layers bottom-to-top. + this.renderPass = 'translucent'; + { + let sourceCache; + let coords = []; + + this.gl.enable(this.gl.BLEND); + + this.currentLayer = 0; + + for (this.currentLayer; this.currentLayer < layerIds.length; this.currentLayer++) { + const layer = this.style._layers[layerIds[this.currentLayer]]; + + if (layer.source !== (sourceCache && sourceCache.id)) { + sourceCache = this.style.sourceCaches[layer.source]; + coords = []; + + if (sourceCache) { + this.clearStencil(); + coords = sourceCache.getVisibleCoordinates(); + if (sourceCache.getSource().isTileClipped) { + this._renderTileClippingMasks(coords); + } + } + coords.reverse(); } + + this.renderLayer(this, (sourceCache: any), layer, coords); } + } - this.renderLayer(this, (sourceCache: any), layer, coords); - this.currentLayer += this.isOpaquePass ? -1 : 1; + if (this.options.showTileBoundaries) { + const sourceCache = this.style.sourceCaches[Object.keys(this.style.sourceCaches)[0]]; + if (sourceCache) { + draw.debug(this, sourceCache, sourceCache.getVisibleCoordinates()); + } + } + } + + _setup3DRenderbuffer() { + // All of the 3D textures will use the same depth renderbuffer. + if (!this.depthRbo) { + const gl = this.gl; + this.depthRbo = gl.createRenderbuffer(); + gl.bindRenderbuffer(gl.RENDERBUFFER, this.depthRbo); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, this.width, this.height); + gl.bindRenderbuffer(gl.RENDERBUFFER, null); } + + this.depthRboAttached = true; } depthMask(mask: boolean) { diff --git a/src/render/render_texture.js b/src/render/render_texture.js new file mode 100644 index 00000000000..9bd6963073e --- /dev/null +++ b/src/render/render_texture.js @@ -0,0 +1,55 @@ +// @flow + +const VertexBuffer = require('../gl/vertex_buffer'); +const VertexArrayObject = require('./vertex_array_object'); +const PosArray = require('../data/pos_array'); + +import type Painter from './painter'; + +class RenderTexture { + gl: WebGLRenderingContext; + texture: WebGLTexture; + fbo: WebGLFramebuffer; + buffer: VertexBuffer; + vao: VertexArrayObject; + + constructor(painter: Painter) { + const gl = this.gl = painter.gl; + + const texture = this.texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, painter.width, painter.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + + gl.bindTexture(gl.TEXTURE_2D, null); + + const fbo = this.fbo = gl.createFramebuffer(); + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); + + const array = new PosArray(); + array.emplaceBack(0, 0); + array.emplaceBack(1, 0); + array.emplaceBack(0, 1); + array.emplaceBack(1, 1); + this.buffer = new VertexBuffer(gl, array); + this.vao = new VertexArrayObject(); + } + + bindWithDepth(depthRbo: WebGLRenderbuffer) { + const gl = this.gl; + gl.bindFramebuffer(gl.FRAMEBUFFER, this.fbo); + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthRbo); + } + + unbind() { + const gl = this.gl; + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, null); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + } +} + +module.exports = RenderTexture; diff --git a/src/style/style.js b/src/style/style.js index e5b3a845982..26f4fb08663 100644 --- a/src/style/style.js +++ b/src/style/style.js @@ -244,7 +244,7 @@ class Style extends Evented { this.light = new Light(this.stylesheet.light); } - _serializeLayers(ids) { + _serializeLayers(ids: Array) { return ids.map((id) => this._layers[id].serialize()); } diff --git a/src/style/style_layer.js b/src/style/style_layer.js index baccfe43b11..2084555bbdd 100644 --- a/src/style/style_layer.js +++ b/src/style/style_layer.js @@ -381,6 +381,10 @@ class StyleLayer extends Evented { style: {glyphs: true, sprite: true} })); } + + has3DPass() { + return false; + } } module.exports = StyleLayer; diff --git a/src/style/style_layer/fill_extrusion_style_layer.js b/src/style/style_layer/fill_extrusion_style_layer.js index 614c6e72f8d..0645378f328 100644 --- a/src/style/style_layer/fill_extrusion_style_layer.js +++ b/src/style/style_layer/fill_extrusion_style_layer.js @@ -39,6 +39,10 @@ class FillExtrusionStyleLayer extends StyleLayer { bearing, pixelsToTileUnits); return multiPolygonIntersectsMultiPolygon(translatedPolygon, geometry); } + + has3DPass() { + return this.paint['fill-extrusion-opacity'] !== 0 && this.layout['visibility'] !== 'none'; + } } module.exports = FillExtrusionStyleLayer; diff --git a/test/integration/render-tests/fill-extrusion-multiple/multiple/expected.png b/test/integration/render-tests/fill-extrusion-multiple/multiple/expected.png new file mode 100644 index 00000000000..42faa2bc3fd Binary files /dev/null and b/test/integration/render-tests/fill-extrusion-multiple/multiple/expected.png differ diff --git a/test/integration/render-tests/fill-extrusion-multiple/multiple/style.json b/test/integration/render-tests/fill-extrusion-multiple/multiple/style.json new file mode 100644 index 00000000000..b185bb0ed29 --- /dev/null +++ b/test/integration/render-tests/fill-extrusion-multiple/multiple/style.json @@ -0,0 +1,155 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 256, + "diff": 0.0005 + } + }, + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "which": "a", + "property": 10 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -0.0003, + -0.0003 + ], + [ + -0.0003, + 0.0003 + ], + [ + 0.0003, + 0.0003 + ], + [ + 0.0003, + -0.0003 + ], + [ + -0.0003, + -0.0003 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "which": "b", + "property": 20 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -0.0002, + 0 + ], + [ + 0, + 0.0002 + ], + [ + 0.0002, + 0 + ], + [ + 0, + -0.0002 + ], + [ + -0.0002, + 0 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "which": "c", + "property": 30 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -0.00008, + -0.00008 + ], + [ + -0.00008, + 0.00008 + ], + [ + 0.00008, + 0.00008 + ], + [ + 0.00008, + -0.00008 + ], + [ + -0.00008, + -0.00008 + ] + ] + ] + } + } + ] + } + } + }, + "pitch": 60, + "zoom": 18, + "layers": [ + { + "id": "a", + "type": "fill-extrusion", + "source": "geojson", + "filter": ["==", "which", "a"], + "paint": { + "fill-extrusion-color": "red", + "fill-extrusion-height": 10 + } + }, + { + "id": "b", + "type": "fill-extrusion", + "source": "geojson", + "filter": ["==", "which", "b"], + "paint": { + "fill-extrusion-color": "blue", + "fill-extrusion-height": 20 + } + }, + { + "id": "c", + "type": "fill-extrusion", + "source": "geojson", + "filter": ["==", "which", "c"], + "paint": { + "fill-extrusion-color": "yellow", + "fill-extrusion-height": 30 + } + } + ] +} diff --git a/yarn.lock b/yarn.lock index e16615ec1ce..7fe1f0cfc38 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5622,18 +5622,12 @@ resolve@1.1.7, resolve@~1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" -resolve@^1.1.3, resolve@^1.1.4, resolve@^1.1.5, resolve@^1.1.6: +resolve@^1.1.3, resolve@^1.1.4, resolve@^1.1.5, resolve@^1.1.6, resolve@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.3.3.tgz#655907c3469a8680dc2de3a275a8fdd69691f0e5" dependencies: path-parse "^1.0.5" -resolve@^1.3.3: - version "1.4.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.4.0.tgz#a75be01c53da25d934a98ebd0e4c4a7312f92a86" - dependencies: - path-parse "^1.0.5" - restore-cursor@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"