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

api(core) breaking interface change ideas for hubble v2 #259

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
206 changes: 206 additions & 0 deletions examples/workshop/hello-world/app-v2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
/* eslint-disable import/first */
/* global hubble, deck, popmotion */

// RENDER SETTINGS
const timecode = {
start: 0,
end: 3000,
framerate: 30
};

const resolution = {
width: 1920,
height: 1080
};

const previewEncoder = hubble.PreviewEncoder();
// const webmEncoder = hubble.WebMEncoder({quality: 0.99});
// const gifEncoder = hubble.GifEncoder({width: 480, height: 270});

// -- RENDER SETTINGS END

// ANIMATION SETTINGS
/**
import {
DeckAnimation,
DeckAdapter,
AnimationManager
} from "@hubble.gl/core";
import { anticipate, reverseEasing, easeIn } from "popmotion";
import { ScatterplotLayer, TextLayer } from "@deck.gl/layers";
*/

const BLUE = [37, 80, 129];

const VIEW_STATE = {
longitude: -122.402,
latitude: 37.79,
zoom: 14,
bearing: 0,
pitch: 0
};

const animation = new hubble.DeckAnimation({
getLayers: a => {
/* const frame = a.layerKeyframes['circle'].getFrame();
console.log(frame); */

return a.applyLayerKeyframes([
new deck.ScatterplotLayer({
id: 'circle',
data: [
{
position: [-122.402, 37.79],
color: BLUE,
radius: 1000
}
],
getFillColor: d => d.color,
getRadius: d => d.radius,
opacity: 1, // 0.1
radiusScale: 1 // 0.01
}),
new deck.TextLayer({
id: 'text',
data: [
{
position: [-122.402, 37.79],
text: 'Hola Mundo'
}
],
opacity: 1, // 0.1
getAngle: 0,
getPixelOffset: [0, -32],
getColor: [255, 255, 255],
getSize: 64
})
]);
},
layerKeyframes: [
{
id: 'circle',
keyframes: [
{
opacity: 0.1,
radiusScale: 0.01
},
{
opacity: 1,
radiusScale: 1
}
],
timings: [0, 1000],
easings: popmotion.cubicBezier(0.75, 0.25, 0.25, 0.75)
},
{
id: 'text',
keyframes: [
{
opacity: 0,
getPixelOffset: [0, -64]
},
{
opacity: 1,
getPixelOffset: [0, 0]
}
],
timings: [0, 1000]
}
]
});
// -- ANIMATION SETTINGS END

// VISUALIZATION
/* import { Deck } from "@deck.gl/core"; */

const BACKGROUND = [30 / 255, 30 / 255, 30 / 255, 1];

export const deckgl = new deck.Deck({
canvas: 'deck-canvas',
// Resolution Props
width: resolution.width,
height: resolution.height,
useDevicePixels: 1, // Otherwise retina displays will double resolution.
// Camera Props
initialViewState: VIEW_STATE,
controller: true,
// Visualization Props
parameters: {
// Background color. Most video formats don't fully support transparency
clearColor: BACKGROUND
}
});
// -- VISUALIZATION END

// RENDERER SETUP
const animator = new hubble.DeckAnimator({
deck: deckgl,
animations: [animation]
// drawOverride: (animator, deckgl) => {
// deckgl.setProps(animator.getProps({
// onNextFrame: animator.setProps // draw loop
// }));
// }
});

// In v2, draw loop goes inside DeckAnimator
function setProps() {
deckgl.setProps(
animator.getProps({
onNextFrame: setProps // draw loop
})
);
}

const embedVideo = blob => {
if (blob && blob.type === 'image/gif') {
const gifElement = document.getElementById('gif-render');
gifElement.style.display = 'block';
gifElement.src = URL.createObjectURL(blob);
}
if (blob && blob.type === 'video/webm') {
const videoElement = document.getElementById('video-render');
videoElement.style.display = 'block';
videoElement.setAttribute('controls', true);
videoElement.setAttribute('autoplay', true);
videoElement.src = URL.createObjectURL(blob);
videoElement.addEventListener('canplaythrough', () => {
videoElement.play();
});
}
};

const render = () => {
animator.render({
encoder: previewEncoder,
timecode,
onComplete: setProps,
onSave: embedVideo // display the rendered video in the UI
});
deckgl.redraw(true);
};
// -- RENDERER SETUP END

// RENDER RUNTIME
setProps();

animation.setOnLayersUpdate(layers => {
deckgl.setProps({
layers
});
});

// -- RENDER RUNTIME END

// ANIMATION SETTINGS UI
const scrubber = document.getElementById('scrubber');
scrubber.onchange = e =>
animator.seek({
timeMs: e.target.value
});
scrubber.setAttribute('max', timecode.end);

document.body.style.margin = '0px';
const reRenderElement = document.getElementById('re-render');
reRenderElement.onclick = render;
// -- ANIMATION UI END
4 changes: 2 additions & 2 deletions modules/core/src/animations/deck-animation.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import {CameraKeyframes, DeckLayerKeyframes} from '../keyframes';
import {MapViewKeyframes, DeckLayerKeyframes} from '../keyframes';
import Animation from './animation';

function noop() {}
Expand Down Expand Up @@ -60,7 +60,7 @@ export default class DeckAnimation extends Animation {
if (this.cameraKeyframe && cameraKeyframe) {
this.cameraKeyframe.set(cameraKeyframe);
} else if (cameraKeyframe) {
this.cameraKeyframe = new CameraKeyframes(cameraKeyframe);
this.cameraKeyframe = new MapViewKeyframes(cameraKeyframe);
this.unattachedKeyframes.push(this.cameraKeyframe);
}

Expand Down
4 changes: 2 additions & 2 deletions modules/core/src/animations/kepler-animation.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import {
CameraKeyframes,
MapViewKeyframes,
KeplerFilterKeyframes,
KeplerLayerKeyframes,
KeplerTripKeyframes
Expand Down Expand Up @@ -104,7 +104,7 @@ export default class KeplerAnimation extends Animation {
if (this.cameraKeyframe && cameraKeyframe) {
this.cameraKeyframe.set(cameraKeyframe);
} else if (cameraKeyframe) {
this.cameraKeyframe = new CameraKeyframes(cameraKeyframe);
this.cameraKeyframe = new MapViewKeyframes(cameraKeyframe);
this.unattachedKeyframes.push(this.cameraKeyframe);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {PreviewEncoder} from '../encoders';
import {AnimationManager} from '../animations';
import {VideoCapture} from '../capture/video-capture';

export default class DeckAdapter {
export default class DeckAnimator {
/** @type {any} */
deck;
/** @type {AnimationManager} */
Expand All @@ -37,28 +37,34 @@ export default class DeckAdapter {

/**
* @param {Object} params
* @param {AnimationManager} params.animationManager
* @param {any} params.deck
* @param {any[]} params.animations
* @param {WebGL2RenderingContext} params.glContext
*/
constructor({animationManager = undefined, glContext = undefined}) {
this.animationManager = animationManager || new AnimationManager({});
constructor({deck = undefined, animations = undefined, glContext = undefined}) {
this.animationManager = new AnimationManager({animations});
this.glContext = glContext;
this.videoCapture = new VideoCapture();
this.shouldAnimate = false;
this.enabled = false;
this.getProps = this.getProps.bind(this);
this.setDeckProps = this.setDeckProps.bind(this);
this.setDeck = this.setDeck.bind(this);
this.render = this.render.bind(this);
this.stop = this.stop.bind(this);
this.seek = this.seek.bind(this);
this.onAfterRender = this.onAfterRender.bind(this);
this.setDeck(deck);
}

setDeck(deck) {
this.deck = deck;
this.setDeckProps();
}

/**
* @param {Object} params
* @param {any} params.deck
* @param {any?} params.deck
* @param {(nextTimeMs: number) => void} params.onNextFrame
* @param {Object} params.extraProps
*/
Expand Down Expand Up @@ -86,19 +92,25 @@ export default class DeckAdapter {
return {...extraProps, ...props};
}

setDeckProps() {
this.deck.setProps(
this.getProps({
onNextFrame: this.setDeckProps // draw loop
})
);
}

/**
* @param {Object} params
* @param {typeof import('../encoders').FrameEncoder} params.Encoder
* @param {Partial<import('types').FormatConfigs>} params.formatConfigs
* @param {import('../encoders').FrameEncoder} params.encoder
* @param {string} params.filename
* @param {{start: number, end: number, framerate: number}} params.timecode
* @param {() => void} params.onStopped
* @param {(blob: Blob) => void} params.onSave
* @param {() => void} params.onComplete
*/
render({
Encoder = PreviewEncoder,
formatConfigs = {},
encoder = new PreviewEncoder(),
filename = undefined,
timecode = {start: 0, end: 0, framerate: 30},
onStopped = undefined,
Expand All @@ -107,14 +119,13 @@ export default class DeckAdapter {
}) {
this.shouldAnimate = true;
this.videoCapture.render({
Encoder,
formatConfigs,
encoder,
timecode,
filename,
onStop: () => this.stop({onStopped, onSave, onComplete})
});
this.enabled = true;
this.seek({timeMs: timecode.start});
this.seek(timecode.start);
}

/**
Expand All @@ -131,10 +142,9 @@ export default class DeckAdapter {
}

/**
* @param {Object} params
* @param {number} params.timeMs
* @param {number} timeMs
*/
seek({timeMs}) {
seek(timeMs) {
this.animationManager.timeline.setTime(timeMs);
this.animationManager.draw();
}
Expand All @@ -147,7 +157,7 @@ export default class DeckAdapter {
const areAllLayersLoaded = this.deck && this.deck.props.layers.every(layer => layer.isLoaded);
if (this.videoCapture.isRecording() && areAllLayersLoaded && readyToCapture) {
this.videoCapture.capture(this.deck.canvas, nextTimeMs => {
this.seek({timeMs: nextTimeMs});
this.seek(nextTimeMs);
proceedToNextFrame(nextTimeMs);
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
export {default as DeckAdapter} from './deck-adapter';
export {default as DeckAnimator} from './deck-animator';
8 changes: 4 additions & 4 deletions modules/core/src/capture/video-capture.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,19 +62,19 @@ export class VideoCapture {
/**
* Start recording.
* @param {Object} params
* @param {typeof FrameEncoder} params.Encoder
* @param {import('types').FormatConfigs} params.formatConfigs
* @param {FrameEncoder} params.encoder
* @param {{start: number, end: number, framerate: number, duration?: number}} params.timecode
* @param {() => void} params.onStop
*/
render({Encoder, formatConfigs, timecode, filename = undefined, onStop = undefined}) {
render({encoder, timecode, filename = undefined, onStop = undefined}) {
if (!this.isRecording()) {
console.time('render');
this.filename = this._sanitizeFilename(filename);
this.timecode = this._sanatizeTimecode(timecode);
console.log(`Starting recording for ${this.timecode.duration}ms.`);
this.onStop = onStop;
this.encoder = new Encoder({...formatConfigs, framerate: this.timecode.framerate});
this.encoder = encoder;
encoder.setFramerate(this.timecode.framerate);
this.recording = true;
this.encoder.start();
}
Expand Down
5 changes: 5 additions & 0 deletions modules/core/src/encoders/frame-encoder.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,9 @@ export default class FrameEncoder {
async save() {
throw new Error('Encoder: Implement a save function');
}

/** @type {(framerate: number) => void} */
setFramerate(framerate) {
this.framerate = framerate;
}
}
Loading