diff --git a/test/apps/frustum-cull/app.js b/test/apps/frustum-cull/app.js index e43246ccfe7..54029eb1190 100644 --- a/test/apps/frustum-cull/app.js +++ b/test/apps/frustum-cull/app.js @@ -1,21 +1,38 @@ -/* eslint-disable no-continue */ - +/* global window */ import React, {Component} from 'react'; import {render} from 'react-dom'; -import DeckGL, {SimpleMeshLayer, WebMercatorViewport} from 'deck.gl'; +import DeckGL, {MapView, SimpleMeshLayer, LineLayer, WebMercatorViewport} from 'deck.gl'; import {StaticMap} from 'react-map-gl'; import {SphereGeometry} from '@luma.gl/core'; -import {Vector3} from 'math.gl'; + +import {getCulling, getFrustumBounds} from './frustum-utils'; const INITIAL_VIEW_STATE = { - latitude: 51.47, - longitude: 0.45, - zoom: 7, - bearing: 0, - pitch: 0 + main: { + width: window.innerWidth, + height: window.innerHeight, + latitude: 51.47, + longitude: 0.45, + zoom: 8, + pitch: 30, + bearing: 45 + }, + minimap: { + latitude: 51.47, + longitude: 0.45, + zoom: 4 + } +}; + +const MINIMAP_STYLE = { + width: '100%', + height: '100%', + background: '#444', + position: 'relative', + zIndex: -1 }; -const STYLE = { +const LABEL_STYLE = { position: 'absolute', right: '10px', top: '10px', @@ -24,62 +41,71 @@ const STYLE = { border: '1px solid black' }; -const position = [-0.4531566, 51.4709959, 100000]; -const radius = 10000; -const testPosition = new Vector3(); +const POSITIONS = [[0.45, 51.47, 10000]]; +const MESH = new SphereGeometry({radius: 5000}); +const VIEWS = [ + new MapView({id: 'main', controller: true}), + new MapView({id: 'minimap', clear: true, x: 20, y: 20, width: '20%', height: '20%'}) +]; class Root extends Component { constructor(props) { super(props); this.deckRef = React.createRef(); this.state = { - cullStatus: 'IN' + viewState: INITIAL_VIEW_STATE }; this._onViewStateChange = this._onViewStateChange.bind(this); } - _onViewStateChange(params) { - const viewport = new WebMercatorViewport(params.viewState); + _onViewStateChange({viewState}) { + this.setState({ + viewState: {...INITIAL_VIEW_STATE, main: viewState} + }); + } - // Culling tests must be done in common space - const commonPosition = viewport.projectPosition(position); + render() { + const {viewState} = this.state; + const viewport = new WebMercatorViewport(viewState.main); + const outDir = getCulling(viewport, POSITIONS[0]); - // Extract frustum planes based on current view. - const frustumPlanes = viewport.getFrustumPlanes(); - let out = false; - let outDir = null; - for (const dir in frustumPlanes) { - const plane = frustumPlanes[dir]; + const frustomBounds = getFrustumBounds(viewport); + const cullStatus = outDir ? `OUT (${outDir})` : 'IN'; - if (testPosition.copy(commonPosition).dot(plane.normal) > plane.distance) { - out = true; - outDir = dir; - break; - } - } + const layers = [ + new SimpleMeshLayer({ + id: 'mesh', + data: POSITIONS, + mesh: MESH, + getPosition: p => p, + getColor: [255, 0, 0] + }), + new LineLayer({ + id: 'frustum', + data: frustomBounds, + getSourcePosition: d => d.source, + getTargetPosition: d => d.target, + getColor: d => d.color, + getWidth: 2 + }) + ]; - this.setState({cullStatus: out ? `OUT (${outDir})` : 'IN'}); - } - - render() { return ( - - p} - getTranslation={[0, 0, position[2]]} - getColor={[255, 0, 0]} - /> -
Culling Status: {this.state.cullStatus}
+ + + + +
+ +
Culling Status: {cullStatus}
); } diff --git a/test/apps/frustum-cull/frustum-utils.js b/test/apps/frustum-cull/frustum-utils.js new file mode 100644 index 00000000000..4457ec37beb --- /dev/null +++ b/test/apps/frustum-cull/frustum-utils.js @@ -0,0 +1,70 @@ +import {Vector3} from 'math.gl'; +import * as mat3 from 'gl-matrix/mat3'; + +const NEAR = [255, 0, 128]; +const FAR = [128, 0, 255]; +const MID = [0, 128, 255]; + +const scratchPosition = new Vector3(); + +export function getCulling(viewport, point) { + // Culling tests must be done in common space + const commonPosition = viewport.projectPosition(point); + + // Extract frustum planes based on current view. + const frustumPlanes = viewport.getFrustumPlanes(); + let outDir = null; + for (const dir in frustumPlanes) { + const plane = frustumPlanes[dir]; + + if (scratchPosition.copy(commonPosition).dot(plane.normal) > plane.distance) { + outDir = dir; + break; + } + } + + return outDir; +} + +export function getFrustumBounds(viewport) { + const planes = viewport.getFrustumPlanes(); + + const ntl = viewport.unprojectPosition(getIntersection(planes.near, planes.top, planes.left)); + const ntr = viewport.unprojectPosition(getIntersection(planes.near, planes.top, planes.right)); + const nbl = viewport.unprojectPosition(getIntersection(planes.near, planes.bottom, planes.left)); + const nbr = viewport.unprojectPosition(getIntersection(planes.near, planes.bottom, planes.right)); + const ftl = viewport.unprojectPosition(getIntersection(planes.far, planes.top, planes.left)); + const ftr = viewport.unprojectPosition(getIntersection(planes.far, planes.top, planes.right)); + const fbl = viewport.unprojectPosition(getIntersection(planes.far, planes.bottom, planes.left)); + const fbr = viewport.unprojectPosition(getIntersection(planes.far, planes.bottom, planes.right)); + + return [ + {source: ntl, target: ntr, color: NEAR}, // near top + {source: ntr, target: nbr, color: NEAR}, // near right + {source: nbr, target: nbl, color: NEAR}, // near bottom + {source: nbl, target: ntl, color: NEAR}, // near left + {source: ftl, target: ftr, color: FAR}, // far top + {source: ftr, target: fbr, color: FAR}, // far right + {source: fbr, target: fbl, color: FAR}, // far bottom + {source: fbl, target: ftl, color: FAR}, // far left + {source: ntl, target: ftl, color: MID}, // top left + {source: nbl, target: fbl, color: MID}, // bottom left + {source: ntr, target: ftr, color: MID}, // top right + {source: nbr, target: fbr, color: MID} // bottom right + ]; +} + +function getIntersection(plane1, plane2, plane3) { + // p.dot(plane.normal) = plane.distance + const vx = [plane1.normal[0], plane2.normal[0], plane3.normal[0]]; + const vy = [plane1.normal[1], plane2.normal[1], plane3.normal[1]]; + const vz = [plane1.normal[2], plane2.normal[2], plane3.normal[2]]; + const vd = [plane1.distance, plane2.distance, plane3.distance]; + + const numerator = mat3.determinant([vx, vy, vz].flat()); + const dx = mat3.determinant([vd, vy, vz].flat()); + const dy = mat3.determinant([vx, vd, vz].flat()); + const dz = mat3.determinant([vx, vy, vd].flat()); + + return [dx / numerator, dy / numerator, dz / numerator]; +}