diff --git a/documentation-readme.md b/documentation-readme.md index 25af767..1d5e8d6 100644 --- a/documentation-readme.md +++ b/documentation-readme.md @@ -10,13 +10,18 @@ The motivation behind this library and more in-depth information about the rende - [More complex example](https://github.com/schmelczerandras/sdf-2d-more-complex-example) - [Source code of the demo](https://github.com/schmelczerandras/sdf-2d-demo) -## Usage +## Usage (1st option) - To start using cutting-edge 2D graphics, first you have get a renderer instance. This is possible by calling the [compile function](globals.html#compile). - For this, some [DrawableDescriptors](interfaces/drawabledescriptor.html) has to be provided. - Optionally, default compile settings can overridden using [StartupSettings](interfaces/startupsettings.html). - After acquiring a renderer, the drawing of objects can be started through the [Renderer](interfaces/renderer.html) interface. +## Usage (2nd option) + +If you're planning on creating animated content, use the [runAnimation function](globals.html#run-animation) function to spare yourself from writing boilerplate code. +Further documentation on its usage is available in its [documentation](globals.html#run-animation). + ## Extending drawables > IƱigo Quilez has some great [2D SDF-s](https://iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm) diff --git a/src/main.ts b/src/main.ts index 326a043..2112eb2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,18 +6,12 @@ * @packageDocumentation */ -import { DrawableDescriptor } from './drawables/drawable-descriptor'; -import { Insights } from './graphics/rendering/insights'; -import { ContextAwareRenderer } from './graphics/rendering/renderer/context-aware-renderer'; -import { Renderer } from './graphics/rendering/renderer/renderer'; -import { StartupSettings } from './graphics/rendering/settings/startup-settings'; import { applyArrayPlugins } from './helper/array'; -/** @internal */ declare global { interface Array { - x: number; - y: number; + x: T; + y: T; } interface Float32Array { @@ -28,47 +22,13 @@ declare global { applyArrayPlugins(); -/** - * Compiles a new renderer instance. There can multiple renderers on a single page. - * > Asynchronous behaviour is required for parallel shader compiling. - * > Trying to draw before the returned promise resolves, results in no action taken. - * > Settings can be set before promise resolution and they will be applied later. - * - * The descriptors of every to-be-drawn objects are required before creating the renderer, - * allowing the compiler to only create the shaders that will actually be used. - * - * Example usage: - * - * ```js - * import { compile, hsl, CircleFactory, CircleLight } from 'sdf-2d'; - * - * const canvas = document.querySelector('canvas'); - * const Circle = CircleFactory(hsl(30, 66, 50)); - * const renderer = await compile(canvas, [Circle.descriptor, CircleLight.descriptor]); - * ``` - * - * @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 settingsOverrides Sensible defaults are provided, but these can be overridden. - */ -export async function compile( - canvas: HTMLCanvasElement, - descriptors: Array, - settingsOverrides: Partial = {} -): Promise { - return Insights.measureFunction('startup', async () => { - const renderer = new ContextAwareRenderer(canvas, descriptors, settingsOverrides); - await renderer.initializedPromise; - return renderer; - }); -} - +export * from './compile'; export * from './drawables/drawable'; export * from './drawables/drawable-descriptor'; export * from './drawables/lights/circle-light'; export * from './drawables/lights/flashlight'; export * from './drawables/shapes/circle-factory'; +export * from './drawables/shapes/colorful-circle'; export * from './drawables/shapes/droplet-factory'; export * from './drawables/shapes/inverted-tunnel-factory'; export * from './drawables/shapes/meta-circle-factory'; @@ -84,3 +44,5 @@ export * from './helper/colors/rgb'; export * from './helper/colors/rgb255'; export * from './helper/colors/rgba'; export * from './helper/colors/rgba255'; +export * from './helper/delta-time-calculator'; +export * from './run-animation'; diff --git a/src/run-animation.ts b/src/run-animation.ts new file mode 100644 index 0000000..94ad3a5 --- /dev/null +++ b/src/run-animation.ts @@ -0,0 +1,93 @@ +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 { + 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(); +}