Skip to content

Commit

Permalink
Viewport and controller fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
Ib Green committed Sep 7, 2017
1 parent 90da93c commit d61cb16
Show file tree
Hide file tree
Showing 25 changed files with 501 additions and 316 deletions.
8 changes: 8 additions & 0 deletions dev-docs/RFCs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ Implementation of non-trivial new deck.gl features should typically be started o

The core developers will review RFCs (and of course, comments from the community are always welcome). Recommended review criteria are being documented in [RFC Review Guidelines](../common/RFC-REVIEW-GUIDELINES.md).

## Longer-Terms RFCs

These are early ideas not yet associated with any release

| RFC | Author | Status | Description |
| --- | --- | --- | --- |
| [**Projection Mode Improvements**](vNext/projection-mode-improvements-rfc.md) | @ibgreen @pessimistress | **Draft** | Improvements to projection system |


## v5.0 RFCs

Expand Down
10 changes: 6 additions & 4 deletions dev-docs/RFCs/v5.0/controller-architecture-rfc.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@
## Motivation

* In deckgl v4.0 we introduced non-geospatial (orbit) controllers and viewports
* For deck.gl v5.0 we started working on first person controllers and viewports,
* For deck.gl v5.0 plus we are introducing a multiviewport architecture.
* For deck.gl v5.0 we started working on **first person controllers** and **first person viewports**, and we are also introducing a **multiviewport** architecture, and **viewport animation**.

The combination of various viewports in a single render, feeding them off the same "map" state data opens questions about controllers.

The combination of various viewports in a single render, feeding them off the same "map" state data

## Details

A critical step in this development was the decision to geospatially enable all viewports. This unifies the viewports to a significant extent
A critical step in this development was the decision to [geospatially enable all viewports](first-person-mercator-viewport-rfc.md). This unifies the API and capabilities of the various `Viewport` subclasses to a significant extent, in that they can all accept (optionally) a geospatial "anchor" or reference point, and on top of that a "meter" offset linear coordinate system.




# Proposal
Expand Down
57 changes: 41 additions & 16 deletions dev-docs/RFCs/v5.0/multi-viewport-rfc.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,24 +138,49 @@ react-map-gl provides a `StaticMap` that is a perfect backdrop for `WebMercatorV

In fact, the DeckGL component is usually set to a child of the MapGL component. When using multiple base maps, this obviously won't work.

The suggestion is to supply a new React component `<ViewportBaseComponents>` that walks the viewport descriptor list and looks for some attributes and renders base map components (absolutely positioned in CSS) in the same place as the deck.gl viewport will appear in the overlay.
The suggestion is to supply a new React component that walks the viewport descriptor list and looks for some attributes and renders base map components (absolutely positioned in CSS) in the same place as the deck.gl viewport will appear in the overlay.


## ViewportLayout

`ViewportLayout` is a react helper component that is intended to render base maps (or other base React components underneath DeckGL viewports.

Since deck.gl is WebGL based, all its viewports need to be in the same canvas (unless you use multiple DeckGL instances, but that can have significant resource and performance impact)

`ViewportLayout` takes a`viewports` prop and positions any children with `viewportId` prop matching the a viewport id under that viewport. (`viewports` is intended to be the same array passed to the `DeckGL` componentcontaining a possibly mixed array of `Viewports` and "viewport descriptors" ).


## Usage

```js
const viewports = [
// Non geospatial viewport
new Viewport({...}),
{viewport: new WebMercatorViewport({}), baseComponent: ReactMapGL, ...},
...
];

render() {
return (
<*Controller ...>
<ViewportBaseComponents width={} height={} viewports={viewports}/>
<DeckGL width={} height={} viewports={viewports} layers={...} .../>
</Controller>
)
}
const viewports = [
new FirstPersonViewport({...}),
new WebMercatorViewport({id: 'basemap', ...})
];

render() {
<ViewportLayout viewports={viewports}>

<StaticMap
viewportId='basemap'
{...viewportProps}/>

<DeckGL
width={viewportProps.width}
height={viewportProps.height}
viewports={viewports}
useDevicePixelRatio={false}
layers={this._renderLayers()}
onWebGLInitialized={this._initialize} />

</ViewportLayout>
}
```

New Properties
* `children` - Normally the DeckGL component is the last child is intentionally rendered on top.
* `viewports` - A singe viewport, or an array of `Viewport`s or "Viewport Descriptors". Will walk the list looking for viewport ids matching children viewportIds, rendering those components in the position and size specified by that viewport. Positioning is done with CSS styling on a wrapper div, sizing by width and height properties. Also injects the `visible: viewport.isMapSynched()` prop.


```
Expand Down
44 changes: 44 additions & 0 deletions dev-docs/RFCs/vNext/projection-mode-improvements-rfc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# RFC: Projection Mode Improvements

* **Authors**: Ib Green and Xiaoji Chen
* **Date**: Sep 2017
* **Status**: Draft

Notes:
* See discussions at end of [Issue 677](https://github.com/uber/deck.gl/issues/677)
* Also see [PR 904](https://github.com/uber/deck.gl/pull/904)


## Motivation

Cartographic Projections are a core functionality of deck.gl.


## Proposal: Support Preprojected Web Mercator Coordinates (Tile 0)

Add a `COORDINATE_SYSTEM.TILE_ZERO` mode. It would allow the app to preproject lng/lats in JavaScript to numbers between 0-512.

* The shader already knows the scale so it could apply it without the app having to modify distance scales.
* Implementing this mode is easy. I mainly want to make sure there is a use case for these modes before we add more complexity to the framework.

The main advantage I see would be somewhat faster rendering (no need for vertex shader to reproject lng/lats every frame).

But note that the precision advantages only comes when we use offsets instead of absolute coordinates, so this mode would need to support fp64.

Maybe we would also add a `COORDINATE_SYSTEM.TILE_ZERO_OFFSETS` mode? But that makes things ever more complex to describe...


## Proposal: Prop to supply JavaScript Project Function

What I have been thinking is to offer the user to provide a simple JS function that maps any position (from any other coordinates) to one of our supported coordinate systems. This function would be called during position attribute generation. I.e we'd still show a mercator projected world but be able to correctly position coordinates specified in other projections. If the project function projected to TILE_ZERO it would have perf advantages during render too.



## Proposal: Replaceable Project Shader Module for Custom Projections

The shadertools module system was designed with a focus on interfaces, with an intention of allowing modules to be replaced with similar modules implementing the same interface. It would take a little more work on shadertools, but it is almost there. This was mostly intended for replacing e.g. lighting but would most likely work for projections as well.

Also shadertools already supports a generic getUniforms system that allows each module to opt in and extract what uniforms it needs from a common JS object, so modules that have the same GLSL interface could still listen for different JS props.

This should allow apps to plug in a replacement project module as you suggest, that could pick up any uniforms it wanted from the layers' props, e.g. your suggested projectionParams.

131 changes: 96 additions & 35 deletions examples/layer-browser/src/app.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
/* global window, document */
import DeckGL, {COORDINATE_SYSTEM, experimental} from 'deck.gl';
const {ReflectionEffect} = experimental;
import {
COORDINATE_SYSTEM,
WebMercatorViewport,
FirstPersonViewport,
ViewportController,
// FirstPersonState,
MapState,
experimental
} from 'deck.gl';

const {
DeckGLMultiView: DeckGL,
ViewportLayout,
ReflectionEffect
} = experimental;

import React, {PureComponent} from 'react';
import ReactDOM from 'react-dom';
import autobind from 'react-autobind';
import MapboxGLMap from 'react-map-gl';

import {StaticMap} from 'react-map-gl';
import {FPSStats} from 'react-stats';

import {Matrix4, setParameters} from 'luma.gl';
Expand Down Expand Up @@ -42,6 +56,7 @@ class App extends PureComponent {
// immutable: false,
// Effects are experimental for now. Will be enabled in the future
// effects: false,
multiview: false,
separation: 0,
pickingRadius: 0
// the rotation controls works only for layers in
Expand Down Expand Up @@ -185,33 +200,6 @@ class App extends PureComponent {
return modelMatrix;
}

_renderMap() {
const {width, height, mapViewState, settings: {effects, pickingRadius}} = this.state;
return (
<MapboxGLMap
mapboxApiAccessToken={MapboxAccessToken || 'no_token'}
width={width} height={height}
{ ...mapViewState }
onViewportChange={this._onViewportChange}>

<DeckGL ref="deckgl"
debug
id="default-deckgl-overlay"
width={width} height={height}
{...mapViewState}
pickingRadius={pickingRadius}
onWebGLInitialized={ this._onWebGLInitialized }
onLayerHover={ this._onHover }
onLayerClick={ this._onClick }
layers={this._renderExamples()}
effects={effects ? this._effects : []}
/>

<FPSStats isActive/>
</MapboxGLMap>
);
}

_renderNoTokenWarning() {
/* eslint-disable max-len */
return (
Expand All @@ -223,17 +211,86 @@ class App extends PureComponent {
/* eslint-disable max-len */
}

_getViewports() {
const {width, height, mapViewState, settings: {multiview}} = this.state;
return [
multiview && new FirstPersonViewport({
...mapViewState,
width,
height: multiview ? height / 2 : height,
position: [0, 0, 50]
}),
new WebMercatorViewport({
id: 'basemap', ...mapViewState,
width,
height: multiview ? height / 2 : height,
y: multiview ? height / 2 : 0
})
];
}

_renderMap() {
const {width, height, mapViewState, settings: {effects, pickingRadius}} = this.state;

const viewports = this._getViewports();

return (
<div style={{backgroundColor: '#eeeeee'}}>
<ViewportController
viewportState={MapState}
{...mapViewState}
width={width}
height={height}
onViewportChange={this._onViewportChange} >

<ViewportLayout viewports={viewports}>
<StaticMap
viewportId="basemap"
{...mapViewState}
mapboxApiAccessToken={MapboxAccessToken || 'no_token'}
width={width}
height={height}
onViewportChange={this._onViewportChange}/>

<FPSStats isActive/>

<DeckGL
ref="deckgl"
id="default-deckgl-overlay"
width={width}
height={height}
viewports={viewports}
layers={this._renderExamples()}
effects={effects ? this._effects : []}
debug={false}
pickingRadius={pickingRadius}
onWebGLInitialized={this._onWebGLInitialized}
onLayerHover={this._onHover}
onLayerClick={this._onClick}
/>

</ViewportLayout>

</ViewportController>
</div>
);
}

render() {
const {settings, activeExamples, hoveredItem, clickedItem, queriedItems} = this.state;

return (
<div>
{ this._renderMap() }
{ !MapboxAccessToken && this._renderNoTokenWarning() }
{this._renderMap()}
{!MapboxAccessToken && this._renderNoTokenWarning()}
<div id="control-panel">
<button onClick={this._onQueryVisibleObjects}>Query Objects</button>
<div style={{textAlign: 'center', padding: '5px 0 5px'}}>
<button onClick={this._onQueryVisibleObjects}>
<b>Query Visble Objects</b>
</button>
</div>
<LayerControls
title="Composite Settings"
title="Common Settings"
settings={settings}
onChange={this._onUpdateContainerSettings}/>
<LayerSelector
Expand All @@ -242,7 +299,11 @@ class App extends PureComponent {
onToggleLayer={this._onToggleLayer}
onUpdateLayer={this._onUpdateLayerSettings} />
</div>
<LayerInfo ref="infoPanel" hovered={hoveredItem} clicked={clickedItem} queried={queriedItems} />
<LayerInfo
ref="infoPanel"
hovered={hoveredItem}
clicked={clickedItem}
queried={queriedItems} />
</div>
);
}
Expand Down
4 changes: 2 additions & 2 deletions examples/layer-browser/src/components/layer-info.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ export default class LayerInfo extends PureComponent {
<span>Layer: { clicked.layer.id } Object: { this._infoToString(clicked) }</span>
</div>) }
{ queried && (<div>
<h4>Query</h4>
<span>{ queried.length } Objects found</span>
<h4>Query Visible Objects</h4>
<span>{ queried.length } visible objects found (see console)</span>
</div>) }
</div>
);
Expand Down
27 changes: 14 additions & 13 deletions examples/wip/first-person-map/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,18 +239,12 @@ class Root extends Component {
height: viewportProps.height / 2,
fovy: fov
}),
{
viewport: new WebMercatorViewport({
...viewportProps,
y: viewportProps.height / 2,
height: viewportProps.height / 2
}),
component: <StaticMap
{...viewportProps}
mapStyle="mapbox://styles/mapbox/dark-v9"
onViewportChange={this._onViewportChange.bind(this)}
mapboxApiAccessToken={MAPBOX_TOKEN}/>
}
new WebMercatorViewport({
id: 'basemap',
...viewportProps,
y: viewportProps.height / 2,
height: viewportProps.height / 2
})
];
}

Expand All @@ -263,14 +257,21 @@ class Root extends Component {
<div style={{backgroundColor: '#000'}}>

<ViewportController
StateClass={FirstPersonState}
viewportState={FirstPersonState}
{...viewportProps}
width={viewportProps.width}
height={viewportProps.height}
onViewportChange={this._onViewportChange} >

<ViewportLayout viewports={viewports}>

<StaticMap
viewportId="basemap"
{...viewportProps}
mapStyle="mapbox://styles/mapbox/dark-v9"
onViewportChange={this._onViewportChange.bind(this)}
mapboxApiAccessToken={MAPBOX_TOKEN}/>

<DeckGL
id="first-person"
width={viewportProps.width}
Expand Down

0 comments on commit d61cb16

Please sign in to comment.