diff --git a/package.json b/package.json
index c3daa84..e48c9b1 100644
--- a/package.json
+++ b/package.json
@@ -45,7 +45,7 @@
"resolve-url-loader": "^3.1.1",
"sass": "^1.26.3",
"sass-loader": "^9.0.3",
- "sdf-2d": "^0.5.0",
+ "sdf-2d": "^0.6.0",
"source-map-loader": "^1.1.0",
"svg-url-loader": "^6.0.0",
"terser-webpack-plugin": "^2.3.8",
diff --git a/src/helper/pretty-print.ts b/src/helper/pretty-print.ts
index 704a6fc..48fd593 100644
--- a/src/helper/pretty-print.ts
+++ b/src/helper/pretty-print.ts
@@ -1,4 +1,4 @@
export const prettyPrint = (o: any): string =>
- JSON.stringify(o, (_, v) => (v.toFixed ? Number(v.toFixed(3)) : v), ' ')
+ JSON.stringify(o, (_, v) => (v?.toFixed ? Number(v.toFixed(3)) : v), ' ')
.replace(/("|,|{|^\n)/g, '')
.replace(/(\W*}\n?)+/g, '\n\n');
diff --git a/src/index.html b/src/index.html
index cf390e1..2b8ae3e 100644
--- a/src/index.html
+++ b/src/index.html
@@ -25,7 +25,7 @@
SDF-2D
-
+
diff --git a/src/index.ts b/src/index.ts
index 50108da..c2ff4ce 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -7,22 +7,18 @@ import '../static/logo-white.svg';
import '../static/no-change/404.html';
import '../static/no-change/robots.txt';
import '../static/og-image.png';
-import { DeltaTimeCalculator } from './helper/delta-time-calculator';
import { removeUnnecessaryOutlines } from './helper/remove-unnecessary-outlines';
import { BlobScene } from './scenes/blob/blob-scene';
import { MetaballScene } from './scenes/metaball/metaball-scene';
import { RainScene } from './scenes/rain/rain-scene';
-import { Scene } from './scenes/scene';
import { TunnelScene } from './scenes/tunnel-scene';
import './styles/index.scss';
const scenes = [TunnelScene, MetaballScene, RainScene, BlobScene];
-const sceneIntervalInSeconds = 8;
glMatrix.setMatrixArrayType(Array);
removeUnnecessaryOutlines();
-const deltaTimeCalculator = new DeltaTimeCalculator();
const canvas = document.querySelector('canvas') as HTMLCanvasElement;
const errorText = document.querySelector('#error-text') as HTMLParamElement;
const errorsContainer = document.querySelector('#errors-container') as HTMLDivElement;
@@ -43,44 +39,13 @@ const handleTextToggle = () => {
toggleButton.addEventListener('click', handleTextToggle);
handleTextToggle();
-const handleScene = async (
- currentScene: Scene,
- NextSceneConstructor: new () => Scene
-): Promise => {
- let triggerIsOver: () => void;
- const isOver = new Promise((resolve) => (triggerIsOver = resolve));
- let timeSinceStart = 0;
-
- const handleFrame = (currentTime: DOMHighResTimeStamp) => {
- const deltaTime = deltaTimeCalculator.getNextDeltaTime(currentTime);
-
- currentScene.drawNextFrame(currentTime, deltaTime);
-
- if ((timeSinceStart += deltaTime) > sceneIntervalInSeconds * 1000) {
- triggerIsOver();
- } else {
- requestAnimationFrame(handleFrame);
- }
- };
-
- requestAnimationFrame(handleFrame);
-
- await isOver;
- currentScene.destroy();
-
- const nextScene = new NextSceneConstructor();
- await nextScene.initialize(canvas, overlay);
-
- return nextScene;
-};
-
const main = async () => {
try {
let i = 0;
- let currentScene: Scene = new scenes[i++]();
- await currentScene.initialize(canvas, overlay);
for (;;) {
- currentScene = await handleScene(currentScene, scenes[i++ % scenes.length]);
+ const currentScene = new scenes[i++ % scenes.length]();
+
+ await currentScene.run(canvas, overlay);
}
} catch (e) {
console.error(e);
diff --git a/src/scenes/blob/blob-scene.ts b/src/scenes/blob/blob-scene.ts
index f668a62..453cde1 100644
--- a/src/scenes/blob/blob-scene.ts
+++ b/src/scenes/blob/blob-scene.ts
@@ -1,65 +1,66 @@
import { vec2 } from 'gl-matrix';
-import { CircleFactory, CircleLight, compile, Renderer, rgb, rgb255 } from 'sdf-2d';
+import { CircleFactory, CircleLight, Renderer, rgb, rgb255, runAnimation } from 'sdf-2d';
import { prettyPrint } from '../../helper/pretty-print';
+import { settings } from '../../settings';
import { Scene } from '../scene';
import { Blob } from './blob';
const Circle = CircleFactory(rgb255(119, 143, 120));
export class BlobScene implements Scene {
- private renderer: Renderer;
private canvas: HTMLCanvasElement;
private overlay: HTMLDivElement;
- public async initialize(
- canvas: HTMLCanvasElement,
- overlay: HTMLDivElement
- ): Promise {
+ public async run(canvas: HTMLCanvasElement, overlay: HTMLDivElement): Promise {
this.canvas = canvas;
this.overlay = overlay;
- this.renderer = await compile(canvas, [
- Circle.descriptor,
- {
- ...CircleLight.descriptor,
- shaderCombinationSteps: [0, 1, 2],
- },
- Blob.descriptor,
- ]);
-
- this.renderer.setRuntimeSettings({
- ambientLight: rgb255(89, 25, 115),
- colorPalette: [
- rgb255(0, 0, 0),
- rgb255(119, 143, 120),
- rgb255(119, 143, 120),
- rgb255(224, 96, 126),
- ],
- });
const { width, height } = this.canvas.getBoundingClientRect();
const length = vec2.length([width, height]);
this.blob = new Blob([width / 2, -length / 4 + length / 2]);
+
+ await runAnimation(
+ canvas,
+ [
+ Circle.descriptor,
+ {
+ ...CircleLight.descriptor,
+ shaderCombinationSteps: [0, 1, 2],
+ },
+ Blob.descriptor,
+ ],
+ this.drawNextFrame.bind(this),
+ {},
+ {
+ ambientLight: rgb255(89, 25, 115),
+ enableHighDpiRendering: true,
+ colorPalette: [
+ rgb255(0, 0, 0),
+ rgb255(119, 143, 120),
+ rgb255(119, 143, 120),
+ rgb255(224, 96, 126),
+ ],
+ }
+ );
}
private blob: Blob;
- private deltaSinceStart = 0;
- public drawNextFrame(
+ private drawNextFrame(
+ renderer: Renderer,
currentTime: DOMHighResTimeStamp,
deltaTime: DOMHighResTimeStamp
- ): void {
+ ): boolean {
const { width, height } = this.canvas.getBoundingClientRect();
- this.deltaSinceStart += deltaTime;
- this.renderer.setViewArea([0, height], [width, height]);
+ renderer.setViewArea([0, height], [width, height]);
- this.renderer.autoscaleQuality(deltaTime);
- this.overlay.innerText = prettyPrint(this.renderer.insights);
+ this.overlay.innerText = prettyPrint(renderer.insights);
const length = vec2.length([width, height]);
- const q = (this.deltaSinceStart % 8000) / 4300;
- this.blob.animate(this.deltaSinceStart);
+ const q = (currentTime % 8000) / 4300;
+ this.blob.animate(currentTime);
[
new Circle([width / 2, -length / 4], length / 2),
this.blob,
@@ -79,12 +80,8 @@ export class BlobScene implements Scene {
rgb(0, 0.8, 1),
1
),
- ].forEach((d) => this.renderer.addDrawable(d));
+ ].forEach((d) => renderer.addDrawable(d));
- this.renderer.renderDrawables();
- }
-
- public destroy(): void {
- this.renderer.destroy();
+ return currentTime < settings.sceneTimeInMilliseconds;
}
}
diff --git a/src/scenes/metaball/metaball-scene.ts b/src/scenes/metaball/metaball-scene.ts
index f1f0926..9892663 100644
--- a/src/scenes/metaball/metaball-scene.ts
+++ b/src/scenes/metaball/metaball-scene.ts
@@ -1,45 +1,20 @@
import { vec2 } from 'gl-matrix';
-import { compile, Flashlight, Renderer, rgb, rgb255 } from 'sdf-2d';
+import { Flashlight, Renderer, rgb, rgb255, runAnimation } from 'sdf-2d';
import { prettyPrint } from '../../helper/pretty-print';
import { Random } from '../../helper/random';
+import { settings } from '../../settings';
import { Scene } from '../scene';
import { Metaball, MetaCircle } from './metaball';
export class MetaballScene implements Scene {
private circles: Array = [];
- private renderer: Renderer;
private canvas: HTMLCanvasElement;
private overlay: HTMLDivElement;
- public async initialize(
- canvas: HTMLCanvasElement,
- overlay: HTMLDivElement
- ): Promise {
+ public async run(canvas: HTMLCanvasElement, overlay: HTMLDivElement): Promise {
this.canvas = canvas;
this.overlay = overlay;
- this.renderer = await compile(canvas, [
- {
- ...Flashlight.descriptor,
- shaderCombinationSteps: [0, 2],
- },
- {
- ...MetaCircle.descriptor,
- shaderCombinationSteps: [0, 1, 2, 4, 8, 12, 16],
- },
- ]);
-
- this.renderer.setRuntimeSettings({
- ambientLight: rgb(0.1, 0.1, 0.3),
- colorPalette: [
- rgb(1, 1, 1),
- rgb(1, 1, 1),
- rgb(1, 1, 1),
- rgb(1, 1, 1),
- rgb(1, 1, 0),
- rgb255(186, 59, 70),
- ],
- });
for (let i = 0; i < 16; i++) {
this.circles.push(
@@ -55,26 +30,46 @@ export class MetaballScene implements Scene {
)
);
}
+
+ await runAnimation(
+ canvas,
+ [
+ {
+ ...Flashlight.descriptor,
+ shaderCombinationSteps: [0, 2],
+ },
+ {
+ ...MetaCircle.descriptor,
+ shaderCombinationSteps: [0, 1, 2, 4, 8, 12, 16],
+ },
+ ],
+ this.drawNextFrame.bind(this),
+ {},
+ {
+ ambientLight: rgb(0.1, 0.1, 0.3),
+ enableHighDpiRendering: true,
+ }
+ );
}
- public drawNextFrame(
+ private drawNextFrame(
+ renderer: Renderer,
currentTime: DOMHighResTimeStamp,
deltaTime: DOMHighResTimeStamp
- ): void {
+ ): boolean {
const { width, height } = this.canvas.getBoundingClientRect();
const viewAreaWidth = width / Math.max(width, height);
const viewAreaHeight = height / Math.max(width, height);
- this.renderer.setViewArea(
+ renderer.setViewArea(
vec2.fromValues(0, viewAreaHeight),
vec2.fromValues(viewAreaWidth, viewAreaHeight)
);
- this.renderer.autoscaleQuality(deltaTime);
- this.overlay.innerText = prettyPrint(this.renderer.insights);
+ this.overlay.innerText = prettyPrint(renderer.insights);
this.circles.forEach((c) => {
c.animate(currentTime / 2000, viewAreaWidth, viewAreaHeight);
- this.renderer.addDrawable(c.shape);
+ renderer.addDrawable(c.shape);
});
const light1 = new Flashlight(
@@ -90,13 +85,9 @@ export class MetaballScene implements Scene {
vec2.fromValues(-1, 1)
);
- this.renderer.addDrawable(light1);
- this.renderer.addDrawable(light2);
+ renderer.addDrawable(light1);
+ renderer.addDrawable(light2);
- this.renderer.renderDrawables();
- }
-
- public destroy(): void {
- this.renderer.destroy();
+ return currentTime < settings.sceneTimeInMilliseconds;
}
}
diff --git a/src/scenes/metaball/metaball.ts b/src/scenes/metaball/metaball.ts
index e007a14..592dc2f 100644
--- a/src/scenes/metaball/metaball.ts
+++ b/src/scenes/metaball/metaball.ts
@@ -1,8 +1,8 @@
import { vec2 } from 'gl-matrix';
-import { MetaCircleFactory } from 'sdf-2d';
+import { MetaCircleFactory, rgb255 } from 'sdf-2d';
import { Random } from '../../helper/random';
-export const MetaCircle = MetaCircleFactory(5);
+export const MetaCircle = MetaCircleFactory(rgb255(186, 59, 70));
export class Metaball {
public shape = new MetaCircle(vec2.create(), Random.getRandomInRange(0.025, 0.075));
diff --git a/src/scenes/rain/droplet.ts b/src/scenes/rain/droplet.ts
index c23c4ff..7f3c99f 100644
--- a/src/scenes/rain/droplet.ts
+++ b/src/scenes/rain/droplet.ts
@@ -1,8 +1,8 @@
import { vec2 } from 'gl-matrix';
-import { DropletFactory } from 'sdf-2d';
+import { DropletFactory, rgb } from 'sdf-2d';
import { Random } from '../../helper/random';
-export const Droplet = DropletFactory(1);
+export const Droplet = DropletFactory(rgb(0.3, 1, 1));
export class DropletWrapper {
public readonly drawable: InstanceType;
diff --git a/src/scenes/rain/rain-scene.ts b/src/scenes/rain/rain-scene.ts
index 84703c0..bf6bcc9 100644
--- a/src/scenes/rain/rain-scene.ts
+++ b/src/scenes/rain/rain-scene.ts
@@ -1,6 +1,7 @@
import { vec2 } from 'gl-matrix';
-import { CircleLight, compile, Renderer, rgb, rgb255 } from 'sdf-2d';
+import { CircleLight, Renderer, rgb, rgb255, runAnimation } from 'sdf-2d';
import { prettyPrint } from '../../helper/pretty-print';
+import { settings } from '../../settings';
import { Scene } from '../scene';
import { Droplet, DropletWrapper } from './droplet';
@@ -9,47 +10,47 @@ export class RainScene implements Scene {
private light1: CircleLight = new CircleLight(vec2.create(), rgb255(184, 41, 255), 2);
private light2: CircleLight = new CircleLight(vec2.create(), rgb255(255, 31, 109), 2);
- private renderer: Renderer;
private canvas: HTMLCanvasElement;
private overlay: HTMLDivElement;
- public async initialize(
- canvas: HTMLCanvasElement,
- overlay: HTMLDivElement
- ): Promise {
+ public async run(canvas: HTMLCanvasElement, overlay: HTMLDivElement): Promise {
this.canvas = canvas;
this.overlay = overlay;
- this.renderer = await compile(canvas, [
- {
- ...Droplet.descriptor,
- shaderCombinationSteps: [0, 1, 2, 4, 8, 12, 16, 24],
- },
- {
- ...CircleLight.descriptor,
- shaderCombinationSteps: [0, 2],
- },
- ]);
-
- this.renderer.setRuntimeSettings({
- ambientLight: rgb(0.2, 0.2, 0.2),
- colorPalette: [rgb(1, 1, 1), rgb(0.3, 1, 1), rgb(1, 1, 0), rgb(0.3, 1, 1)],
- });
-
for (let i = 0; i < (canvas.getBoundingClientRect().width / 800) * 20; i++) {
this.droplets.push(new DropletWrapper());
}
+
+ await runAnimation(
+ canvas,
+ [
+ {
+ ...Droplet.descriptor,
+ shaderCombinationSteps: [0, 1, 2, 4, 8, 12, 16, 24],
+ },
+ {
+ ...CircleLight.descriptor,
+ shaderCombinationSteps: [0, 2],
+ },
+ ],
+ this.drawNextFrame.bind(this),
+ {},
+ {
+ ambientLight: rgb(0.2, 0.2, 0.2),
+ enableHighDpiRendering: true,
+ }
+ );
}
- public drawNextFrame(
+ private drawNextFrame(
+ renderer: Renderer,
currentTime: DOMHighResTimeStamp,
deltaTime: DOMHighResTimeStamp
- ): void {
+ ): boolean {
const { width, height } = this.canvas.getBoundingClientRect();
- this.renderer.setViewArea(vec2.fromValues(0, height), vec2.fromValues(width, height));
- this.renderer.autoscaleQuality(deltaTime);
- this.overlay.innerText = prettyPrint(this.renderer.insights);
+ renderer.setViewArea(vec2.fromValues(0, height), vec2.fromValues(width, height));
+ this.overlay.innerText = prettyPrint(renderer.insights);
- const viewAreaSize = this.renderer.viewAreaSize;
+ const viewAreaSize = renderer.viewAreaSize;
vec2.set(this.light1.center, 0, viewAreaSize.y / 2);
vec2.set(this.light2.center, viewAreaSize.x, viewAreaSize.y / 2);
@@ -57,13 +58,9 @@ export class RainScene implements Scene {
this.droplets.forEach((d) => d.animate(currentTime, viewAreaSize));
[...this.droplets.map((d) => d.drawable), this.light1, this.light2].forEach((d) =>
- this.renderer.addDrawable(d)
+ renderer.addDrawable(d)
);
- this.renderer.renderDrawables();
- }
-
- public destroy(): void {
- this.renderer.destroy();
+ return currentTime < settings.sceneTimeInMilliseconds;
}
}
diff --git a/src/scenes/scene.ts b/src/scenes/scene.ts
index d99e786..43093d5 100644
--- a/src/scenes/scene.ts
+++ b/src/scenes/scene.ts
@@ -1,5 +1,3 @@
export interface Scene {
- initialize(canvas: HTMLCanvasElement, overlay: HTMLDivElement): Promise;
- drawNextFrame(currentTime: DOMHighResTimeStamp, deltaTime: DOMHighResTimeStamp): void;
- destroy(): void;
+ run(canvas: HTMLCanvasElement, overlay: HTMLDivElement): Promise;
}
diff --git a/src/scenes/tunnel-scene.ts b/src/scenes/tunnel-scene.ts
index c83d9e5..c18c595 100644
--- a/src/scenes/tunnel-scene.ts
+++ b/src/scenes/tunnel-scene.ts
@@ -1,24 +1,24 @@
import { vec2, vec3 } from 'gl-matrix';
import {
CircleLight,
- compile,
FilteringOptions,
InvertedTunnelFactory,
Renderer,
renderNoise,
rgb,
+ runAnimation,
WrapOptions,
} from 'sdf-2d';
import { clamp } from '../helper/clamp';
import { last } from '../helper/last';
import { prettyPrint } from '../helper/pretty-print';
import { Random } from '../helper/random';
+import { settings } from '../settings';
import { Scene } from './scene';
const InvertedTunnel = InvertedTunnelFactory(3);
export class TunnelScene implements Scene {
- private renderer: Renderer;
private canvas: HTMLCanvasElement;
private overlay: HTMLDivElement;
@@ -68,73 +68,69 @@ export class TunnelScene implements Scene {
}
}
- public async initialize(
- canvas: HTMLCanvasElement,
- overlay: HTMLDivElement
- ): Promise {
+ public async run(canvas: HTMLCanvasElement, overlay: HTMLDivElement): Promise {
const noiseTexture = await renderNoise([1024, 1], 15, 1);
this.canvas = canvas;
this.overlay = overlay;
- this.renderer = await compile(canvas, [
- {
- ...InvertedTunnel.descriptor,
- shaderCombinationSteps: [0, 1, 2, 4, 8, 12],
- },
- {
- ...CircleLight.descriptor,
- shaderCombinationSteps: [0, 1, 2, 3, 4, 5, 6, 7],
- },
- ]);
-
- this.renderer.setRuntimeSettings({
- isWorldInverted: true,
- ambientLight: rgb(0.35, 0.1, 0.45),
- colorPalette: [rgb(0.4, 0.5, 0.6), rgb(0, 0, 0), rgb(0, 0, 0), rgb(0, 0, 0)],
- textures: {
- noiseTexture: {
- source: noiseTexture,
- overrides: {
- maxFilter: FilteringOptions.LINEAR,
- wrapS: WrapOptions.MIRRORED_REPEAT,
- },
- },
- },
- });
for (let i = 0; i < 200; i++) {
this.generateTunnel();
}
+
+ await runAnimation(
+ canvas,
+ [
+ {
+ ...InvertedTunnel.descriptor,
+ shaderCombinationSteps: [0, 1, 2, 4, 8, 12],
+ },
+ {
+ ...CircleLight.descriptor,
+ shaderCombinationSteps: [0, 1, 2, 3, 4, 5, 6, 7],
+ },
+ ],
+ this.drawNextFrame.bind(this),
+ { lightPenetrationRatio: 0.5 },
+ {
+ isWorldInverted: true,
+ enableHighDpiRendering: true,
+ ambientLight: rgb(0.35, 0.1, 0.45),
+ colorPalette: [rgb(0.4, 0.5, 0.6), rgb(0, 0, 0), rgb(0, 0, 0), rgb(0, 0, 0)],
+ textures: {
+ noiseTexture: {
+ source: noiseTexture,
+ overrides: {
+ maxFilter: FilteringOptions.LINEAR,
+ wrapS: WrapOptions.MIRRORED_REPEAT,
+ },
+ },
+ },
+ }
+ );
}
private deltaSinceStart = 0;
- public drawNextFrame(
+ private drawNextFrame(
+ renderer: Renderer,
currentTime: DOMHighResTimeStamp,
deltaTime: DOMHighResTimeStamp
- ): void {
+ ): boolean {
const { width, height } = this.canvas.getBoundingClientRect();
this.deltaSinceStart += deltaTime;
const startX = this.deltaSinceStart / 3;
const endX = startX + width;
- this.renderer.setViewArea(
- vec2.fromValues(startX, height),
- vec2.fromValues(width, height)
- );
+ renderer.setViewArea(vec2.fromValues(startX, height), vec2.fromValues(width, height));
- this.renderer.autoscaleQuality(deltaTime);
- this.overlay.innerText = prettyPrint(this.renderer.insights);
+ this.overlay.innerText = prettyPrint(renderer.insights);
[
...this.tunnels.filter(
(t) => startX < t.to.x + t.toRadius && t.from.x - t.fromRadius <= endX
),
...this.lights,
- ].forEach((d) => this.renderer.addDrawable(d));
+ ].forEach((d) => renderer.addDrawable(d));
- this.renderer.renderDrawables();
- }
-
- public destroy(): void {
- this.renderer.destroy();
+ return currentTime < settings.sceneTimeInMilliseconds;
}
}
diff --git a/src/settings.ts b/src/settings.ts
new file mode 100644
index 0000000..3234bb3
--- /dev/null
+++ b/src/settings.ts
@@ -0,0 +1,3 @@
+export const settings = {
+ sceneTimeInMilliseconds: 7000,
+};
diff --git a/src/styles/index.scss b/src/styles/index.scss
index 3c435b8..133c215 100644
--- a/src/styles/index.scss
+++ b/src/styles/index.scss
@@ -66,12 +66,15 @@ body {
#overlay {
right: 0;
- font-size: 0.75rem;
- white-space: pre;
+ white-space: pre-wrap;
+ overflow-wrap: break-word;
font-family: 'Lucida Console', Monaco, monospace;
+ font-size: 0.75rem;
+ max-width: 400px;
@media (max-width: $breakpoint) {
font-size: 0.6rem;
+ max-width: 200px;
}
}
diff --git a/src/styles/mixins.scss b/src/styles/mixins.scss
index e119c57..93fefb4 100644
--- a/src/styles/mixins.scss
+++ b/src/styles/mixins.scss
@@ -1,5 +1,6 @@
@mixin card {
backdrop-filter: blur(16px);
+ background-color: rgba(0, 0, 0, 0.15);
border: 2px solid white;
border-radius: 12px;