import { enableContextLostSimulator } from './graphics/graphics-library/helper/enable-context-lost-simulator'; import { FpsQualityAutoscaler } from './graphics/rendering/fps-quality-autoscaler'; import { ContextAwareRenderer } from './graphics/rendering/renderer/context-aware-renderer'; import { RuntimeSettings } from './graphics/rendering/settings/runtime-settings'; import { StartupSettings } from './graphics/rendering/settings/startup-settings'; import { DeltaTimeCalculator } from './helper/delta-time-calculator'; import { DrawableDescriptor, Renderer } from './main'; /** * Implements the boilerplate code required to run real-time animations * in the browser. An FPS based autoscaler is also used. This creates an additional `fps` * key in the renderers `insights` property. * * Example usage: * * ```html * * ``` * > The canvas needs to have a fixed size specified by CSS. * ```js * import { CircleFactory, CircleLight, hsl, runAnimation } from 'sdf-2d'; * * const canvas = document.querySelector('canvas'); * const Circle = CircleFactory(hsl(180, 100, 40)); * * runAnimation(canvas, [Circle.descriptor, CircleLight.descriptor], (renderer, time) => { * renderer.addDrawable( * new Circle([150 + 50 * Math.cos(time / 1000), 75 + 50 * Math.sin(time / 1000)], 25) * ); * renderer.addDrawable(new CircleLight([150, 75], hsl(270, 100, 40), 0.1)); * return true; * }); * ``` * * @param canvas The returned renderer will only be able to draw to this canvas. * @param descriptors The descriptor of every single object (and light) that * ever needs to be drawn by this renderer has to be given before compiling. * @param animate This function will be called before rendering each frame. * `renderDrawables` must not be called by the animate function. It should return `true` * if the animation should be continued. To break out of the animation loop, a `false` (falsy) * return value must be given. * @param startupSettingOverrides Sensible defaults are provided, but these can be overridden. * @param initialRuntimeSettingOverrides Sensible defaults are provided, but these can be overridden. */ export async function runAnimation( canvas: HTMLCanvasElement, descriptors: Array, animate: ( renderer: Renderer, currentTimeInMilliseconds: DOMHighResTimeStamp, deltaTimeInMilliseconds: DOMHighResTimeStamp ) => boolean, startupSettingOverrides: Partial = {}, initialRuntimeSettingOverrides: Partial = {} ): Promise { if (startupSettingOverrides.enableContextLostSimulator) { enableContextLostSimulator(canvas); } const renderer = new ContextAwareRenderer(canvas, descriptors, startupSettingOverrides); const deltaTimeCalculator = new DeltaTimeCalculator(); let triggerIsOver: () => void; const isOver = new Promise((resolve) => (triggerIsOver = resolve)); renderer.setRuntimeSettings(initialRuntimeSettingOverrides); const autoscaler = new FpsQualityAutoscaler(renderer); await renderer.initializedPromise; let startTime: DOMHighResTimeStamp | null = null; const handleFrame = (currentTime: DOMHighResTimeStamp) => { if (startTime === null) { startTime = currentTime; } const deltaTime = deltaTimeCalculator.getNextDeltaTime(currentTime); autoscaler.addDeltaTime(deltaTime); if (renderer.insights) { renderer.insights.fps = autoscaler.FPS; } const shouldStop = animate(renderer, currentTime - startTime, deltaTime); renderer.renderDrawables(); if (!shouldStop) { triggerIsOver(); } else { requestAnimationFrame(handleFrame); } }; requestAnimationFrame(handleFrame); await isOver; deltaTimeCalculator.destroy(); renderer.destroy(); }