-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
29373c6
commit cba5ddf
Showing
35 changed files
with
1,257 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
import { | ||
Texture2D | ||
// , readPixelsToArray | ||
} from '@luma.gl/core'; | ||
import {equals} from '@math.gl/core'; | ||
import MaskPass from '../../passes/mask-pass'; | ||
import Effect from '../../lib/effect'; | ||
import {OPERATION} from '../../lib/constants'; | ||
import {getMaskBounds, getMaskViewport} from './utils'; | ||
|
||
// Class to manage mask effect | ||
export default class MaskEffect extends Effect { | ||
constructor(props) { | ||
super(props); | ||
this.dummyMaskMap = null; | ||
this.empty = true; | ||
this.useInPicking = true; | ||
} | ||
|
||
preRender(gl, {layers, layerFilter, viewports, onViewportActive, views}) { | ||
if (!this.dummyMaskMap) { | ||
this.dummyMaskMap = new Texture2D(gl, { | ||
width: 1, | ||
height: 1 | ||
}); | ||
} | ||
|
||
const maskLayers = layers.filter(l => l.props.operation === OPERATION.MASK); | ||
if (maskLayers.length === 0) { | ||
this.empty = true; | ||
return; | ||
} | ||
this.empty = false; | ||
|
||
if (!this.maskPass) { | ||
// TODO - support multiple masks | ||
this.maskPass = new MaskPass(gl, {id: 'default-mask'}); | ||
this.maskMap = this.maskPass.maskMap; | ||
} | ||
|
||
// When the mask layer is changed the LayerManager will create a new instance | ||
const oldMaskLayers = this.maskLayers; | ||
const maskChanged = | ||
!oldMaskLayers || | ||
oldMaskLayers.length !== maskLayers.length || | ||
maskLayers.some((l, i) => l !== oldMaskLayers[i]); | ||
|
||
// To do: support multiple views | ||
const viewport = viewports[0]; | ||
|
||
if (maskChanged || !this.lastViewport || !viewport.equals(this.lastViewport)) { | ||
// Update mask FBO | ||
const {maskPass, maskMap} = this; | ||
this.lastViewport = viewport; | ||
this.maskLayers = maskLayers; | ||
|
||
const bounds = getMaskBounds({layers: maskLayers, viewport}); | ||
|
||
// TODO if the mask layer's data has changed and the data bounds are clipped | ||
// by the viewport, this condition will prevent the mask from rerendering | ||
if (!equals(bounds, this.lastBounds)) { | ||
this.lastBounds = bounds; | ||
|
||
const maskViewport = getMaskViewport({ | ||
bounds, | ||
viewport, | ||
width: maskMap.width, | ||
height: maskMap.height | ||
}); | ||
|
||
this.maskBounds = maskViewport.getBounds(); | ||
|
||
maskPass.render({ | ||
layers, | ||
layerFilter, | ||
viewports: [maskViewport], | ||
onViewportActive, | ||
views, | ||
moduleParameters: { | ||
devicePixelRatio: 1 | ||
} | ||
}); | ||
|
||
// // Debug show FBO contents on screen | ||
// const color = readPixelsToArray(maskMap); | ||
// let canvas = document.getElementById('fbo-canvas'); | ||
// if (!canvas) { | ||
// canvas = document.createElement('canvas'); | ||
// canvas.id = 'fbo-canvas'; | ||
// canvas.width = maskMap.width; | ||
// canvas.height = maskMap.height; | ||
// canvas.style.zIndex = 100; | ||
// canvas.style.position = 'absolute'; | ||
// canvas.style.right = 0; | ||
// canvas.style.border = 'blue 1px solid'; | ||
// canvas.style.width = '256px'; | ||
// canvas.style.transform = 'scaleY(-1)'; | ||
// document.body.appendChild(canvas); | ||
// } | ||
// const ctx = canvas.getContext('2d'); | ||
// const imageData = ctx.createImageData(maskMap.width, maskMap.height); | ||
// for (let i = 0; i < color.length; i += 4) { | ||
// imageData.data[i + 0] = color[i + 0]; | ||
// imageData.data[i + 1] = color[i + 1]; | ||
// imageData.data[i + 2] = color[i + 2]; | ||
// imageData.data[i + 3] = color[i + 3]; | ||
// } | ||
// ctx.putImageData(imageData, 0, 0); | ||
} | ||
} | ||
} | ||
|
||
getModuleParameters() { | ||
return { | ||
maskMap: this.empty ? this.dummyMaskMap : this.maskMap, | ||
maskBounds: this.maskBounds | ||
}; | ||
} | ||
|
||
cleanup() { | ||
if (this.dummyMaskMap) { | ||
this.dummyMaskMap.delete(); | ||
this.dummyMaskMap = null; | ||
} | ||
|
||
if (this.maskPass) { | ||
this.maskPass.delete(); | ||
this.maskPass = null; | ||
this.maskMap = null; | ||
} | ||
|
||
this.lastBounds = null; | ||
this.lastViewport = null; | ||
this.maskBounds = null; | ||
this.empty = true; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import OrthographicView from '../../views/orthographic-view'; | ||
import WebMercatorViewport from '../../viewports/web-mercator-viewport'; | ||
import {fitBounds} from '@math.gl/web-mercator'; | ||
/* | ||
* Compute the bounds of the mask in world space, such that it covers an | ||
* area currently visible (extended by a buffer) or the area of the masking | ||
* data, whichever is smaller | ||
*/ | ||
export function getMaskBounds({layers, viewport}) { | ||
// Join the bounds of layer data | ||
let bounds = null; | ||
for (const layer of layers) { | ||
const subLayerBounds = layer.getBounds(); | ||
if (subLayerBounds) { | ||
if (bounds) { | ||
bounds[0] = Math.min(bounds[0], subLayerBounds[0][0]); | ||
bounds[1] = Math.min(bounds[1], subLayerBounds[0][1]); | ||
bounds[2] = Math.max(bounds[2], subLayerBounds[1][0]); | ||
bounds[3] = Math.max(bounds[3], subLayerBounds[1][1]); | ||
} else { | ||
bounds = [ | ||
subLayerBounds[0][0], | ||
subLayerBounds[0][1], | ||
subLayerBounds[1][0], | ||
subLayerBounds[1][1] | ||
]; | ||
} | ||
} | ||
} | ||
const viewportBounds = viewport.getBounds(); | ||
if (!bounds) { | ||
return viewportBounds; | ||
} | ||
|
||
// Expand viewport bounds by 2X. Heurestically chosen to avoid masking | ||
// errors when mask is partially out of view | ||
const paddedBounds = _doubleBounds(viewportBounds); | ||
|
||
// When bounds of the mask are smaller than the viewport bounds simply use | ||
// mask bounds, so as to maximize resolution & avoid mask rerenders | ||
if ( | ||
bounds[2] - bounds[0] < paddedBounds[2] - paddedBounds[0] || | ||
bounds[3] - bounds[1] < paddedBounds[3] - paddedBounds[1] | ||
) { | ||
return bounds; | ||
} | ||
|
||
// As viewport shrinks, to avoid pixelation along mask edges | ||
// we need to reduce the bounds and only render the visible portion | ||
// of the mask. | ||
// We pad the viewport bounds to capture the section | ||
// of the mask just outside the viewport to correctly maskByInstance. | ||
// Intersect mask & padded viewport bounds | ||
bounds[0] = Math.max(bounds[0], paddedBounds[0]); | ||
bounds[1] = Math.max(bounds[1], paddedBounds[1]); | ||
bounds[2] = Math.min(bounds[2], paddedBounds[2]); | ||
bounds[3] = Math.min(bounds[3], paddedBounds[3]); | ||
return bounds; | ||
} | ||
|
||
/* | ||
* Compute viewport to render the mask into, covering the given bounds | ||
*/ | ||
export function getMaskViewport({bounds, viewport, width, height}) { | ||
if (viewport instanceof WebMercatorViewport) { | ||
const {longitude, latitude, zoom} = fitBounds({ | ||
width, | ||
height, | ||
bounds: [ | ||
[bounds[0], bounds[1]], | ||
[bounds[2], bounds[3]] | ||
], | ||
maxZoom: 20 | ||
}); | ||
return new WebMercatorViewport({longitude, latitude, zoom, width, height}); | ||
} | ||
|
||
const center = [(bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2, 0]; | ||
const scale = Math.min(20, width / (bounds[2] - bounds[0]), height / (bounds[3] - bounds[1])); | ||
|
||
return new OrthographicView().makeViewport({ | ||
width, | ||
height, | ||
viewState: { | ||
target: center, | ||
zoom: Math.log2(scale) | ||
} | ||
}); | ||
} | ||
|
||
function _doubleBounds(bounds) { | ||
const size = { | ||
x: bounds[2] - bounds[0], | ||
y: bounds[3] - bounds[1] | ||
}; | ||
const center = { | ||
x: bounds[0] + 0.5 * size.x, | ||
y: bounds[1] + 0.5 * size.y | ||
}; | ||
return [center.x - size.x, center.y - size.y, center.x + size.x, center.y + size.y]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.