Clean up code and enable strict TS
This commit is contained in:
parent
f350b1ff37
commit
0735dd764f
21 changed files with 124 additions and 119 deletions
2
definitions.d.ts
vendored
2
definitions.d.ts
vendored
|
|
@ -2,5 +2,3 @@ declare module '*.wgsl?raw' {
|
||||||
const content: string;
|
const content: string;
|
||||||
export default content;
|
export default content;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare const __BUILD_DATE__: number;
|
|
||||||
|
|
|
||||||
25
index.html
25
index.html
|
|
@ -22,10 +22,9 @@
|
||||||
<meta property="og:image:width" content="1920" />
|
<meta property="og:image:width" content="1920" />
|
||||||
<meta property="og:image:height" content="1920" />
|
<meta property="og:image:height" content="1920" />
|
||||||
|
|
||||||
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
<link rel="apple-touch-icon" href="/favicon.svg" />
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
<link rel="manifest" href="/manifest.webmanifest" />
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
|
||||||
|
|
||||||
<title>Just a bunch of blobs</title>
|
<title>Just a bunch of blobs</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
@ -51,9 +50,21 @@
|
||||||
<section>
|
<section>
|
||||||
<h1>Just a bunch of blobs</h1>
|
<h1>Just a bunch of blobs</h1>
|
||||||
<p>
|
<p>
|
||||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Eaque itaque
|
A million autonomous agents wander a 2D field. Each one lays down a faint
|
||||||
perspiciatis nesciunt, molestiae officiis dignissimos porro! Provident totam
|
trail and follows trails it senses ahead. Two generations are competing for
|
||||||
sit enim, dolores dicta possimus ex assumenda earum, ea tempore, aut quidem?
|
territory: the older one fades, the newer one spreads.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Drag your finger or mouse anywhere on the canvas to paint a wall. Walls slow
|
||||||
|
the new generation down and let the old one breathe a little longer. Open
|
||||||
|
<em>Settings</em> to retune sensors, decay rates and aggression.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Runs entirely on your GPU via WebGPU compute shaders — no servers, no
|
||||||
|
tracking, no analytics. Source on
|
||||||
|
<a href="https://github.com/schmelczer/webgpu" target="_blank" rel="noopener"
|
||||||
|
>GitHub</a
|
||||||
|
>.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
||||||
6
public/favicon.svg
Normal file
6
public/favicon.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||||
|
<rect width="64" height="64" rx="14" fill="#b7455e" />
|
||||||
|
<circle cx="22" cy="26" r="9" fill="#fff" opacity="0.95" />
|
||||||
|
<circle cx="42" cy="32" r="11" fill="#fff" opacity="0.85" />
|
||||||
|
<circle cx="28" cy="44" r="7" fill="#fff" opacity="0.75" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 312 B |
22
public/manifest.webmanifest
Normal file
22
public/manifest.webmanifest
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"name": "Just a bunch of blobs",
|
||||||
|
"short_name": "Blobs",
|
||||||
|
"description": "A WebGPU agent simulation: a million blobs leave trails, infect each other across generations, and react to your brush.",
|
||||||
|
"start_url": "/",
|
||||||
|
"scope": "/",
|
||||||
|
"display": "fullscreen",
|
||||||
|
"display_override": ["fullscreen", "standalone", "minimal-ui"],
|
||||||
|
"orientation": "any",
|
||||||
|
"background_color": "#b7455e",
|
||||||
|
"theme_color": "#b7455e",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/favicon.svg",
|
||||||
|
"sizes": "any",
|
||||||
|
"type": "image/svg+xml",
|
||||||
|
"purpose": "any maskable"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"categories": ["entertainment", "graphics"],
|
||||||
|
"lang": "en"
|
||||||
|
}
|
||||||
|
|
@ -1,2 +1 @@
|
||||||
export const isProduction: boolean = import.meta.env.PROD;
|
export const isProduction: boolean = import.meta.env.PROD;
|
||||||
export const lastEdit = new Date(__BUILD_DATE__);
|
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,9 @@ export default class GameLoop {
|
||||||
private readonly hasFinishedPromise: Promise<void> = new Promise(
|
private readonly hasFinishedPromise: Promise<void> = new Promise(
|
||||||
(resolve) => (this.resolveHasFinished = resolve)
|
(resolve) => (this.resolveHasFinished = resolve)
|
||||||
);
|
);
|
||||||
private resolveHasFinished: () => void;
|
private resolveHasFinished!: () => void;
|
||||||
|
|
||||||
private isSwipeActive = false;
|
private activePointerId: number | null = null;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
private readonly canvas: HTMLCanvasElement,
|
private readonly canvas: HTMLCanvasElement,
|
||||||
|
|
@ -73,38 +73,48 @@ export default class GameLoop {
|
||||||
this.renderPipeline = new RenderPipeline(context, this.device, this.commonState);
|
this.renderPipeline = new RenderPipeline(context, this.device, this.commonState);
|
||||||
|
|
||||||
window.addEventListener('resize', this.resize.bind(this));
|
window.addEventListener('resize', this.resize.bind(this));
|
||||||
canvas.addEventListener('mousemove', this.onSwipe.bind(this));
|
canvas.addEventListener('pointerdown', this.onPointerDown.bind(this));
|
||||||
canvas.addEventListener('touchmove', this.onSwipe.bind(this));
|
canvas.addEventListener('pointermove', this.onPointerMove.bind(this));
|
||||||
canvas.addEventListener('mousedown', (e) => {
|
canvas.addEventListener('pointerup', this.onPointerUp.bind(this));
|
||||||
if (!this.isSwipeActive) {
|
canvas.addEventListener('pointercancel', this.onPointerUp.bind(this));
|
||||||
this.brushPipeline.clearSwipes();
|
}
|
||||||
this.isSwipeActive = true;
|
|
||||||
}
|
|
||||||
this.onSwipe(e);
|
|
||||||
});
|
|
||||||
|
|
||||||
canvas.addEventListener('touchstart', (e) => {
|
private onPointerDown(event: PointerEvent) {
|
||||||
if (!this.isSwipeActive) {
|
if (this.activePointerId !== null) {
|
||||||
this.brushPipeline.clearSwipes();
|
return;
|
||||||
this.isSwipeActive = true;
|
}
|
||||||
}
|
this.activePointerId = event.pointerId;
|
||||||
this.onSwipe(e);
|
this.canvas.setPointerCapture(event.pointerId);
|
||||||
});
|
this.brushPipeline.clearSwipes();
|
||||||
|
this.addSwipeAt(event);
|
||||||
|
}
|
||||||
|
|
||||||
window.addEventListener('mouseup', (e) => {
|
private onPointerMove(event: PointerEvent) {
|
||||||
this.onSwipe(e);
|
if (event.pointerId !== this.activePointerId) {
|
||||||
this.isSwipeActive = false;
|
return;
|
||||||
});
|
}
|
||||||
|
this.addSwipeAt(event);
|
||||||
|
}
|
||||||
|
|
||||||
window.addEventListener('touchend', (e) => {
|
private onPointerUp(event: PointerEvent) {
|
||||||
this.onSwipe(e);
|
if (event.pointerId !== this.activePointerId) {
|
||||||
this.isSwipeActive = false;
|
return;
|
||||||
});
|
}
|
||||||
|
this.addSwipeAt(event);
|
||||||
|
this.canvas.releasePointerCapture(event.pointerId);
|
||||||
|
this.activePointerId = null;
|
||||||
|
}
|
||||||
|
|
||||||
window.addEventListener('touchcancel', (e) => {
|
private addSwipeAt(event: PointerEvent) {
|
||||||
this.onSwipe(e);
|
const position = vec2.fromValues(
|
||||||
this.isSwipeActive = false;
|
event.clientX * this.devicePixelRatio,
|
||||||
});
|
this.canvas.height - event.clientY * this.devicePixelRatio
|
||||||
|
);
|
||||||
|
this.brushPipeline.addSwipe(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
private get isSwipeActive(): boolean {
|
||||||
|
return this.activePointerId !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async start(): Promise<void> {
|
public async start(): Promise<void> {
|
||||||
|
|
@ -135,25 +145,6 @@ export default class GameLoop {
|
||||||
return this.agentGenerationPipeline.maxAgentCount;
|
return this.agentGenerationPipeline.maxAgentCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
private onSwipe(event: MouseEvent | TouchEvent) {
|
|
||||||
if (!this.isSwipeActive || !event) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event instanceof TouchEvent && event.touches.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const x = event instanceof MouseEvent ? event.clientX : event.touches[0].clientX;
|
|
||||||
const y = event instanceof MouseEvent ? event.clientY : event.touches[0].clientY;
|
|
||||||
|
|
||||||
const position = vec2.fromValues(
|
|
||||||
x * this.devicePixelRatio,
|
|
||||||
this.canvas.height - y * this.devicePixelRatio
|
|
||||||
);
|
|
||||||
this.brushPipeline.addSwipe(position);
|
|
||||||
}
|
|
||||||
|
|
||||||
private resize() {
|
private resize() {
|
||||||
this.canvas.width = this.canvas.clientWidth * this.devicePixelRatio;
|
this.canvas.width = this.canvas.clientWidth * this.devicePixelRatio;
|
||||||
this.canvas.height = this.canvas.clientHeight * this.devicePixelRatio;
|
this.canvas.height = this.canvas.clientHeight * this.devicePixelRatio;
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,12 @@ import { vec3 } from 'gl-matrix';
|
||||||
|
|
||||||
import { settings } from '../settings';
|
import { settings } from '../settings';
|
||||||
import { hsl } from '../utils/hsl';
|
import { hsl } from '../utils/hsl';
|
||||||
import { last } from '../utils/last';
|
|
||||||
import { Random } from '../utils/random';
|
import { Random } from '../utils/random';
|
||||||
|
|
||||||
const hues = [settings.startColorHue];
|
const hues = [settings.startColorHue];
|
||||||
|
|
||||||
for (let i = 0; i < 100; i++) {
|
for (let i = 0; i < 100; i++) {
|
||||||
hues.push((last(hues) + Random.randomBetween(90, 240)) % 360);
|
hues.push((hues[hues.length - 1] + Random.randomBetween(90, 240)) % 360);
|
||||||
}
|
}
|
||||||
|
|
||||||
const colors = hues.map((hue) =>
|
const colors = hues.map((hue) =>
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@ export interface SpawnAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GameRules {
|
export class GameRules {
|
||||||
private static readonly DEAFULT_SPAWN_INTERVAL = 8;
|
private static readonly DEFAULT_SPAWN_INTERVAL = 8;
|
||||||
private static readonly DEAFULT_SPAWN_TIME_LENGTH = 2;
|
private static readonly DEFAULT_SPAWN_TIME_LENGTH = 2;
|
||||||
private static readonly DEFAULT_SPAWN_RADIUS = 20;
|
private static readonly DEFAULT_SPAWN_RADIUS = 20;
|
||||||
|
|
||||||
private lastSpawnTimeInSeconds = 0;
|
private lastSpawnTimeInSeconds = 0;
|
||||||
|
|
@ -41,14 +41,14 @@ export class GameRules {
|
||||||
public getSpawnAction(timeInSeconds: number, canvasSize: vec2): SpawnAction {
|
public getSpawnAction(timeInSeconds: number, canvasSize: vec2): SpawnAction {
|
||||||
if (
|
if (
|
||||||
this.lastSpawnAction &&
|
this.lastSpawnAction &&
|
||||||
timeInSeconds - this.lastSpawnTimeInSeconds < GameRules.DEAFULT_SPAWN_TIME_LENGTH
|
timeInSeconds - this.lastSpawnTimeInSeconds < GameRules.DEFAULT_SPAWN_TIME_LENGTH
|
||||||
) {
|
) {
|
||||||
return this.lastSpawnAction;
|
return this.lastSpawnAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentSpawnInterval = mix(
|
this.currentSpawnInterval = mix(
|
||||||
GameRules.DEAFULT_SPAWN_INTERVAL,
|
GameRules.DEFAULT_SPAWN_INTERVAL,
|
||||||
GameRules.DEAFULT_SPAWN_INTERVAL / 5,
|
GameRules.DEFAULT_SPAWN_INTERVAL / 5,
|
||||||
clamp01((timeInSeconds - this.lastGenerationChangeTimeInSeconds) / 120)
|
clamp01((timeInSeconds - this.lastGenerationChangeTimeInSeconds) / 120)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ html > body {
|
||||||
> canvas {
|
> canvas {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
touch-action: none;
|
||||||
cursor:
|
cursor:
|
||||||
url('../assets/icons/brush.svg') 0 24,
|
url('../assets/icons/brush.svg') 0 24,
|
||||||
auto;
|
auto;
|
||||||
|
|
@ -165,27 +165,18 @@ html > body {
|
||||||
}
|
}
|
||||||
|
|
||||||
&.info::after {
|
&.info::after {
|
||||||
-webkit-mask-image: url('../assets/icons/info.svg');
|
|
||||||
mask-image: url('../assets/icons/info.svg');
|
mask-image: url('../assets/icons/info.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.maximize-full-screen::after {
|
&.maximize-full-screen::after {
|
||||||
-webkit-mask-image: url('../assets/icons/maximize.svg');
|
|
||||||
mask-image: url('../assets/icons/maximize.svg');
|
mask-image: url('../assets/icons/maximize.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.minimize-full-screen::after {
|
&.minimize-full-screen::after {
|
||||||
-webkit-mask-image: url('../assets/icons/minimize.svg');
|
|
||||||
mask-image: url('../assets/icons/minimize.svg');
|
mask-image: url('../assets/icons/minimize.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.settings::after {
|
&.settings::after {
|
||||||
-webkit-mask-image: url('../assets/icons/settings.svg');
|
|
||||||
mask-image: url('../assets/icons/settings.svg');
|
mask-image: url('../assets/icons/settings.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.restart::after {
|
&.restart::after {
|
||||||
-webkit-mask-image: url('../assets/icons/restart.svg');
|
|
||||||
mask-image: url('../assets/icons/restart.svg');
|
mask-image: url('../assets/icons/restart.svg');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -194,6 +185,8 @@ html > body {
|
||||||
> main.pages {
|
> main.pages {
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: var(--main-color) transparent;
|
||||||
&::-webkit-scrollbar-track,
|
&::-webkit-scrollbar-track,
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
|
|
||||||
17
src/index.ts
17
src/index.ts
|
|
@ -1,4 +1,4 @@
|
||||||
import { isProduction, lastEdit } from './constants';
|
import { isProduction } from './constants';
|
||||||
import GameLoop from './game-loop/game-loop';
|
import GameLoop from './game-loop/game-loop';
|
||||||
import { GameRules } from './game-loop/game-rules';
|
import { GameRules } from './game-loop/game-rules';
|
||||||
|
|
||||||
|
|
@ -30,9 +30,7 @@ const elements = {
|
||||||
settingsButton: document.querySelector('button.settings') as HTMLButtonElement,
|
settingsButton: document.querySelector('button.settings') as HTMLButtonElement,
|
||||||
restartButton: document.querySelector('button.restart') as HTMLButtonElement,
|
restartButton: document.querySelector('button.restart') as HTMLButtonElement,
|
||||||
canvas: document.querySelector('canvas') as HTMLCanvasElement,
|
canvas: document.querySelector('canvas') as HTMLCanvasElement,
|
||||||
canvasContainer: document.querySelector('main.canvas-container') as HTMLCanvasElement,
|
|
||||||
errorContainer: document.querySelector('.errors-container') as HTMLDivElement,
|
errorContainer: document.querySelector('.errors-container') as HTMLDivElement,
|
||||||
// counters: document.querySelector('.counters > pre') as HTMLPreElement,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
|
|
@ -90,16 +88,6 @@ const main = async () => {
|
||||||
sliders.forEach((slider) => slider.updateSliderValueBasedOnSource());
|
sliders.forEach((slider) => slider.updateSliderValueBasedOnSource());
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log({ lastEdit });
|
|
||||||
|
|
||||||
// const updateCounters = () => {
|
|
||||||
// elements.counters.innerHTML = `FPS: ${deltaTimeCalculator.fps.toFixed(2)}
|
|
||||||
// current gen: ${formatNumber(game?.aliveAgentCounts.currentGenerationCount ?? 0)}
|
|
||||||
// next gen: ${formatNumber(game?.aliveAgentCounts.nextGenerationCount ?? 0)}`;
|
|
||||||
// window.requestAnimationFrame(updateCounters);
|
|
||||||
// };
|
|
||||||
// updateCounters();
|
|
||||||
|
|
||||||
while (!shouldStop) {
|
while (!shouldStop) {
|
||||||
const gameRules = new GameRules(performance.now() / 1000);
|
const gameRules = new GameRules(performance.now() / 1000);
|
||||||
game = new GameLoop(elements.canvas, gpu, deltaTimeCalculator, gameRules);
|
game = new GameLoop(elements.canvas, gpu, deltaTimeCalculator, gameRules);
|
||||||
|
|
@ -111,7 +99,8 @@ const main = async () => {
|
||||||
await game.start();
|
await game.start();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ErrorHandler.addError(Severity.ERROR, e.stack);
|
const message = e instanceof Error ? (e.stack ?? e.message) : String(e);
|
||||||
|
ErrorHandler.addError(Severity.ERROR, message);
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,16 @@ export const setUpSettingsPage = (
|
||||||
settingsPage: HTMLDivElement,
|
settingsPage: HTMLDivElement,
|
||||||
maxAgentCount: number
|
maxAgentCount: number
|
||||||
): Array<SettingsSlider<any>> => {
|
): Array<SettingsSlider<any>> => {
|
||||||
const sliders = [
|
const sliders: Array<SettingsSlider<any>> = [
|
||||||
!isProduction &&
|
...(isProduction
|
||||||
new SettingsSlider(settings, 'renderSpeed', {
|
? []
|
||||||
min: 1,
|
: [
|
||||||
max: 10,
|
new SettingsSlider(settings, 'renderSpeed', {
|
||||||
rounding: Math.round,
|
min: 1,
|
||||||
}),
|
max: 10,
|
||||||
|
rounding: Math.round,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
|
||||||
new SettingsSlider(settings, 'agentCount', {
|
new SettingsSlider(settings, 'agentCount', {
|
||||||
min: 1,
|
min: 1,
|
||||||
|
|
@ -102,11 +105,9 @@ export const setUpSettingsPage = (
|
||||||
|
|
||||||
const sliderContainerElement = document.createElement('div');
|
const sliderContainerElement = document.createElement('div');
|
||||||
|
|
||||||
sliders
|
sliders.forEach((slider) => {
|
||||||
.filter((v) => v)
|
sliderContainerElement.appendChild(slider.element);
|
||||||
.forEach((slider) => {
|
});
|
||||||
sliderContainerElement.appendChild(slider.element);
|
|
||||||
});
|
|
||||||
|
|
||||||
settingsPage.appendChild(sliderContainerElement);
|
settingsPage.appendChild(sliderContainerElement);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { vec2 } from 'gl-matrix';
|
||||||
import { getWorkgroupCounts } from '../../utils/graphics/get-workgroup-counts';
|
import { getWorkgroupCounts } from '../../utils/graphics/get-workgroup-counts';
|
||||||
import { smartCompile } from '../../utils/graphics/smart-compile';
|
import { smartCompile } from '../../utils/graphics/smart-compile';
|
||||||
import { CommonState } from '../common-state/common-state';
|
import { CommonState } from '../common-state/common-state';
|
||||||
import agentSchme from './agent-generation/agent-schema.wgsl?raw';
|
import agentSchema from './agent-generation/agent-schema.wgsl?raw';
|
||||||
import { AgentSettings } from './agent-settings';
|
import { AgentSettings } from './agent-settings';
|
||||||
import shader from './agent.wgsl?raw';
|
import shader from './agent.wgsl?raw';
|
||||||
|
|
||||||
|
|
@ -32,7 +32,7 @@ export class AgentPipeline {
|
||||||
bindGroupLayouts: [commonState.bindGroupLayout, this.bindGroupLayout],
|
bindGroupLayouts: [commonState.bindGroupLayout, this.bindGroupLayout],
|
||||||
}),
|
}),
|
||||||
compute: {
|
compute: {
|
||||||
module: smartCompile(device, CommonState.shaderCode, agentSchme, shader),
|
module: smartCompile(device, CommonState.shaderCode, agentSchema, shader),
|
||||||
entryPoint: 'main',
|
entryPoint: 'main',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -188,7 +188,7 @@ export class BrushPipeline {
|
||||||
result.push(position);
|
result.push(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
result.push(last(points));
|
result.push(last(points)!);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ export class DiffusionPipeline {
|
||||||
private readonly pipeline: GPURenderPipeline;
|
private readonly pipeline: GPURenderPipeline;
|
||||||
private readonly uniforms: GPUBuffer;
|
private readonly uniforms: GPUBuffer;
|
||||||
private readonly vertexBuffer: GPUBuffer;
|
private readonly vertexBuffer: GPUBuffer;
|
||||||
private readonly noise: GPUTextureView;
|
|
||||||
|
|
||||||
private bindGroup?: GPUBindGroup;
|
private bindGroup?: GPUBindGroup;
|
||||||
private previousTrailMapIn?: GPUTextureView;
|
private previousTrailMapIn?: GPUTextureView;
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,9 @@ p {
|
||||||
|
|
||||||
html {
|
html {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
-webkit-font-smooth: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
}
|
}
|
||||||
|
|
||||||
.large-button {
|
.large-button {
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,6 @@ export class DeltaTimeCalculator {
|
||||||
}
|
}
|
||||||
|
|
||||||
public get fps() {
|
public get fps() {
|
||||||
return 1 / this.deltaTimeAccumulator;
|
return this.deltaTimeAccumulator ? 1 / this.deltaTimeAccumulator : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ export class ErrorHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static addMetadata(key: string, value: any) {
|
public static addMetadata(key: string, value: any) {
|
||||||
const serialized = {};
|
const serialized: Record<string, any> = {};
|
||||||
for (const k in value) {
|
for (const k in value) {
|
||||||
serialized[k] = value[k];
|
serialized[k] = value[k];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,5 +83,5 @@ export const generateNoise = ({
|
||||||
textureCache.set(cacheKey, colorTexture);
|
textureCache.set(cacheKey, colorTexture);
|
||||||
}
|
}
|
||||||
|
|
||||||
return textureCache.get(cacheKey).createView();
|
return textureCache.get(cacheKey)!.createView();
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ import { vec2 } from 'gl-matrix';
|
||||||
import { CopyPipeline } from '../../pipelines/copy/copy-pipeline';
|
import { CopyPipeline } from '../../pipelines/copy/copy-pipeline';
|
||||||
|
|
||||||
export class ResizableTexture {
|
export class ResizableTexture {
|
||||||
private texture: GPUTexture;
|
private texture!: GPUTexture;
|
||||||
private textureView: GPUTextureView;
|
private textureView!: GPUTextureView;
|
||||||
private readonly copyPipeline: CopyPipeline;
|
private readonly copyPipeline: CopyPipeline;
|
||||||
private size: vec2 | null = null;
|
private size: vec2 | null = null;
|
||||||
|
|
||||||
|
|
@ -35,7 +35,7 @@ export class ResizableTexture {
|
||||||
|
|
||||||
const newTextureView = newTexture.createView();
|
const newTextureView = newTexture.createView();
|
||||||
|
|
||||||
if (this.textureView) {
|
if (this.size) {
|
||||||
const commandEncoder = this.device.createCommandEncoder();
|
const commandEncoder = this.device.createCommandEncoder();
|
||||||
this.copyPipeline.execute(
|
this.copyPipeline.execute(
|
||||||
commandEncoder,
|
commandEncoder,
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,7 @@
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
|
||||||
"noImplicitAny": false,
|
"strict": true,
|
||||||
"strictNullChecks": false,
|
|
||||||
"useUnknownInCatchVariables": false,
|
|
||||||
"noUnusedLocals": false,
|
"noUnusedLocals": false,
|
||||||
"noUnusedParameters": false
|
"noUnusedParameters": false
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,6 @@ import { viteSingleFile } from 'vite-plugin-singlefile';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [viteSingleFile()],
|
plugins: [viteSingleFile()],
|
||||||
define: {
|
|
||||||
__BUILD_DATE__: Date.now(),
|
|
||||||
},
|
|
||||||
build: {
|
build: {
|
||||||
target: 'es2022',
|
target: 'es2022',
|
||||||
cssCodeSplit: false,
|
cssCodeSplit: false,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue