diff --git a/examples/first-person/app.js b/examples/first-person/app.js new file mode 100644 index 00000000..816484ff --- /dev/null +++ b/examples/first-person/app.js @@ -0,0 +1,203 @@ +import React, {useState, useRef} from 'react'; +import DeckGL from '@deck.gl/react'; +import {TerrainLayer, TileLayer} from '@deck.gl/geo-layers'; +import {useNextFrame, BasicControls, ResolutionGuide} from '@hubble.gl/react'; +import {DeckAdapter, DeckScene, CameraKeyframes, LayerKeyframes} from '@hubble.gl/core'; +import {easing} from 'popmotion'; +import {FirstPersonView} from '@deck.gl/core'; +import {BitmapLayer} from '@deck.gl/layers'; +const INITIAL_VIEW_STATE = { + latitude: 46.24, + longitude: -122.18, + width: 640, + height: 480, + zoom: 11, + bearing: 0, + // pitch: 0, + position: [0, 0, 1000] +}; + +// Set your mapbox token here +const MAPBOX_TOKEN = process.env.MapboxAccessToken; // eslint-disable-line + +const TERRAIN_IMAGE = `https://api.mapbox.com/v4/mapbox.terrain-rgb/{z}/{x}/{y}.png?access_token=${MAPBOX_TOKEN}`; +const SURFACE_IMAGE = `https://api.mapbox.com/v4/mapbox.satellite/{z}/{x}/{y}@2x.png?access_token=${MAPBOX_TOKEN}`; + +// https://docs.mapbox.com/help/troubleshooting/access-elevation-data/#mapbox-terrain-rgb +// Note - the elevation rendered by this example is greatly exagerated! +const ELEVATION_DECODER = { + rScaler: 6553.6, + gScaler: 25.6, + bScaler: 0.1, + offset: -10000 +}; + +const getCameraKeyframes = () => { + return new CameraKeyframes({ + timings: [0, 6000], + keyframes: [ + { + latitude: 46.24, + longitude: -122.18, + zoom: 11, + bearing: 0, + // pitch: 0, + width: 640, + height: 480, + position: [0, 0, 1000] + }, + { + latitude: 46.24, + longitude: -122.18, + zoom: 11.5, + bearing: 0, + pitch: 60, + position: [0, 0, 2000] + } + ], + easings: [easing.easeInOut] + }); +}; + +const getKeyframes = () => { + return { + terrain: new LayerKeyframes({ + layerId: 'terrain', + features: ['r', 'g', 'b'], + keyframes: [ + {r: 255, g: 255, b: 255}, + {r: 255, g: 0, b: 0}, + {r: 255, g: 255, b: 0}, + {r: 0, g: 255, b: 0}, + {r: 0, g: 255, b: 255}, + {r: 0, g: 0, b: 255}, + {r: 255, g: 0, b: 255}, + {r: 255, g: 255, b: 255} + ], + timings: [0, 2000, 4000, 6000, 8000, 10000, 12000, 14000], + easings: [ + easing.linear, + easing.linear, + easing.linear, + easing.linear, + easing.linear, + easing.linear, + easing.linear + ] + }) + }; +}; + +/** @type {import('@hubble.gl/core/src/types').FrameEncoderSettings} */ +const encoderSettings = { + framerate: 30, + webm: { + quality: 0.8 + }, + jpeg: { + quality: 0.8 + }, + gif: { + sampleInterval: 1000 + } +}; + +export default function App() { + const deckgl = useRef(null); + const [ready, setReady] = useState(false); + const [busy, setBusy] = useState(false); + const [viewState, setViewState] = useState(INITIAL_VIEW_STATE); + + const nextFrame = useNextFrame(); + const [duration] = useState(15000); + const [rainbow, setRainbow] = useState(false); + + const getDeckScene = animationLoop => { + return new DeckScene({ + animationLoop, + lengthMs: duration, + width: 640, + height: 480, + initialKeyframes: getKeyframes() + }); + }; + + const [adapter] = useState(new DeckAdapter(getDeckScene)); + + const getLayers = scene => { + const terrain = scene.keyframes.terrain.getFrame(); + return [ + new TerrainLayer({ + id: 'terrain', + minZoom: 0, + maxZoom: 23, + strategy: 'no-overlap', + elevationDecoder: ELEVATION_DECODER, + elevationData: TERRAIN_IMAGE, + texture: rainbow ? null : SURFACE_IMAGE, + wireframe: false, + color: [terrain.r, terrain.g, terrain.b] + }), + new TileLayer({ + data: SURFACE_IMAGE, + + minZoom: 0, + maxZoom: 23, + tileSize: 256, + + renderSubLayers: props => { + const { + bbox: {west, south, east, north} + } = props.tile; + + return new BitmapLayer(props, { + data: null, + image: props.data, + bounds: [west, south, east, north] + }); + } + }) + ]; + }; + + return ( +
+
+ +
+ { + setViewState(vs); + }} + controller={true} + parameters={{ + depthTest: false, + clearColor: [135 / 255, 206 / 255, 235 / 255, 1] + }} + {...adapter.getProps(deckgl, setReady, nextFrame, getLayers)} + /> +
+ {ready && ( + + )} +
+ +
+
+
+ ); +} diff --git a/examples/first-person/index.js b/examples/first-person/index.js new file mode 100644 index 00000000..7b133aed --- /dev/null +++ b/examples/first-person/index.js @@ -0,0 +1,6 @@ +import React from 'react'; +import {render} from 'react-dom'; +import App from './app'; + +document.body.style.backgroundColor = 'black'; +render(, document.body.appendChild(document.createElement('div'))); diff --git a/examples/first-person/package.json b/examples/first-person/package.json new file mode 100644 index 00000000..8dc08c71 --- /dev/null +++ b/examples/first-person/package.json @@ -0,0 +1,21 @@ +{ + "scripts": { + "start": "webpack-dev-server --progress --hot --open", + "start-local": "webpack-dev-server --env.local --progress --hot --open", + "clean": "rm -rf yarn.lock ./node_modules", + "bootstrap": "yarn clean && yarn" + }, + "dependencies": { + }, + "devDependencies": { + "@babel/core": "^7.12.1", + "@babel/plugin-proposal-class-properties": "^7.12.1", + "@babel/preset-env": "^7.12.1", + "@babel/preset-react": "^7.12.1", + "babel-loader": "^8.0.5", + "html-webpack-plugin": "^3.0.7", + "webpack": "^4.20.2", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.1.11" + } +} diff --git a/examples/first-person/webpack.config.js b/examples/first-person/webpack.config.js new file mode 100644 index 00000000..dd09ca81 --- /dev/null +++ b/examples/first-person/webpack.config.js @@ -0,0 +1,38 @@ +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const webpack = require('webpack'); + +const config = { + mode: 'development', + + entry: { + index: './index.js' + }, + + module: { + rules: [ + { + // Transpile ES6 to ES5 with babel + // Remove if your app does not use JSX or you don't need to support old browsers + test: /\.js$/, + loader: 'babel-loader', + exclude: [/node_modules/], + options: { + plugins: ['@babel/plugin-proposal-class-properties'], + presets: ['@babel/preset-env', '@babel/preset-react'] + } + } + ] + }, + + node: { + fs: 'empty' + }, + + plugins: [ + new HtmlWebpackPlugin({title: 'hubble.gl terrain example'}), + // Optional: Enables reading mapbox token from environment variable + new webpack.EnvironmentPlugin(['MapboxAccessToken']) + ] +}; + +module.exports = env => (env && env.local ? require('../webpack.config.local')(config) : config);