Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Frustum test app: draw frustum planes #4059

Merged
merged 1 commit into from
Dec 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 72 additions & 46 deletions test/apps/frustum-cull/app.js
Original file line number Diff line number Diff line change
@@ -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',
Expand All @@ -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 (
<DeckGL
controller={true}
initialViewState={INITIAL_VIEW_STATE}
views={VIEWS}
viewState={viewState}
layers={layers}
ref={this.deckRef}
onViewStateChange={this._onViewStateChange}
>
<StaticMap key="map" mapStyle="mapbox://styles/mapbox/light-v9" />
<SimpleMeshLayer
id="mesh"
data={[position.slice(0, 2)]}
mesh={new SphereGeometry({radius})}
getPosition={p => p}
getTranslation={[0, 0, position[2]]}
getColor={[255, 0, 0]}
/>
<div style={STYLE}>Culling Status: {this.state.cullStatus}</div>
<MapView id="main">
<StaticMap key="map" mapStyle="mapbox://styles/mapbox/light-v9" />
</MapView>
<MapView id="minimap">
<div style={MINIMAP_STYLE} />
</MapView>
<div style={LABEL_STYLE}>Culling Status: {cullStatus}</div>
</DeckGL>
);
}
Expand Down
70 changes: 70 additions & 0 deletions test/apps/frustum-cull/frustum-utils.js
Original file line number Diff line number Diff line change
@@ -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];
}