From 780388d74bc173679f1123d1be57361112faa4bc Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Mon, 1 May 2023 13:01:12 +0100 Subject: [PATCH] Improve brush --- src/game-loop/game-loop-settings.ts | 4 +- src/game-loop/game-loop.ts | 13 ++-- src/pipelines/brush/brush-pipeline.ts | 91 +++++++++++++++++--------- src/settings.ts | 6 +- src/utils/catmull-rom-interpolation.ts | 28 -------- 5 files changed, 76 insertions(+), 66 deletions(-) delete mode 100644 src/utils/catmull-rom-interpolation.ts diff --git a/src/game-loop/game-loop-settings.ts b/src/game-loop/game-loop-settings.ts index e002067..de3189a 100644 --- a/src/game-loop/game-loop-settings.ts +++ b/src/game-loop/game-loop-settings.ts @@ -1,5 +1,7 @@ export interface GameLoopSettings { agentCount: number; - renderSpeed: number; startingRadius: number; + + renderSpeed: number; + simulatedDelayMs: number; } diff --git a/src/game-loop/game-loop.ts b/src/game-loop/game-loop.ts index d19ff81..8763c1e 100644 --- a/src/game-loop/game-loop.ts +++ b/src/game-loop/game-loop.ts @@ -6,6 +6,7 @@ import { DiffusionPipeline } from '../pipelines/diffusion/diffusion-pipeline'; import { RenderPipeline } from '../pipelines/render/render-pipeline'; import { settings } from '../settings'; import { DeltaTimeCalculator } from '../utils/delta-time-calculator'; +import { sleep } from '../utils/sleep'; import { spawnAgents } from './spawn-agents'; import { vec2 } from 'gl-matrix'; @@ -58,14 +59,16 @@ export default class GameLoop { this.renderPipeline = new RenderPipeline(context, this.device, this.commonState); window.addEventListener('resize', this.resize.bind(this)); + window.addEventListener('mousemove', this.onSwipe.bind(this)); window.addEventListener('mousedown', (e) => { + this.brushPipeline.clearSwipes(); this.isSwipeActive = true; this.onSwipe(e); }); - window.addEventListener('mouseup', (_) => { + window.addEventListener('mouseup', (e) => { + this.onSwipe(e); this.isSwipeActive = false; - this.brushPipeline.clearSwipes(); }); } @@ -115,7 +118,7 @@ export default class GameLoop { }); } - private render(time: DOMHighResTimeStamp) { + private async render(time: DOMHighResTimeStamp) { if (this.hasFinished) { return; } @@ -147,7 +150,9 @@ export default class GameLoop { this.device.queue.submit([commandEncoder.finish()]); - // await sleep(200); + if (settings.simulatedDelayMs > 0) { + await sleep(settings.simulatedDelayMs); + } requestAnimationFrame(this.render.bind(this)); } diff --git a/src/pipelines/brush/brush-pipeline.ts b/src/pipelines/brush/brush-pipeline.ts index fd3b722..9af60ab 100644 --- a/src/pipelines/brush/brush-pipeline.ts +++ b/src/pipelines/brush/brush-pipeline.ts @@ -1,6 +1,6 @@ -import { catmullRomInterpolation } from '../../utils/catmull-rom-interpolation'; -import { generateFbmNoise } from '../../utils/graphics/fbm-noise/fbm-noise'; +import { clamp } from '../../utils/clamp'; import { smartCompile } from '../../utils/graphics/smart-compile'; +import { last } from '../../utils/last'; import { CommonState } from '../common-state/common-state'; import { BrushSettings } from './brush-settings'; import shader from './brush.wgsl'; @@ -9,7 +9,7 @@ import { vec2 } from 'gl-matrix'; export class BrushPipeline { private static readonly UNIFORM_COUNT = 2; - private static readonly MAX_LINE_COUNT = 100; + private static readonly MAX_LINE_COUNT = 3; private static readonly VERTICES_PER_LINE_SEGMENT = 6; private static readonly ATTRIBUTES_PER_LINE_SEGMENT = 6; @@ -20,8 +20,7 @@ export class BrushPipeline { private readonly vertexBuffer: GPUBuffer; private linePoints: Array = []; - private previousPoints: Array = []; - private nextPoint: vec2 | null = null; + private actualPoints: Array = []; public constructor( private readonly device: GPUDevice, @@ -113,14 +112,11 @@ export class BrushPipeline { } public addSwipe(position: vec2) { - this.nextPoint = position; - // this.linePoints.push(position); + this.linePoints.push(position); } public clearSwipes() { this.linePoints.length = 0; - this.previousPoints.length = 0; - this.nextPoint = null; } public setParameters({ brushWidth, brushWidthRandomness }: BrushSettings) { @@ -130,36 +126,27 @@ export class BrushPipeline { new Float32Array([brushWidth / 2, brushWidthRandomness]) ); - if (this.nextPoint == null) { - return; - } - this.previousPoints.push(this.nextPoint); - if (this.previousPoints.length < 3) { + if (this.linePoints.length === 0) { return; } - this.linePoints = []; - for (let t = 0; t < 1; t += 1 / BrushPipeline.MAX_LINE_COUNT) { - this.linePoints.push( - catmullRomInterpolation( - this.previousPoints[0], - this.previousPoints[1], - this.previousPoints[2], - this.nextPoint, - t - ) - ); + this.actualPoints = this.linePoints.slice(); + + if (this.linePoints.length === 1) { + this.actualPoints.push(this.linePoints[0]); // allow single point swipes } - this.previousPoints.splice(0, this.previousPoints.length - 3); + if (this.actualPoints.length > BrushPipeline.MAX_LINE_COUNT + 1) { + this.actualPoints = BrushPipeline.subsampleLinePoints(this.actualPoints); + } this.device.queue.writeBuffer( this.vertexBuffer, 0, new Float32Array( new Array(this.lineCount).fill(0).flatMap((_, i) => { - const from = this.linePoints[i]; - const to = this.linePoints[i + 1]; + const from = this.actualPoints[i]; + const to = this.actualPoints[i + 1]; const [a, b, c, d] = this.getSegmentBoundingBox(from, to, brushWidth / 2); return [a, b, c, b, c, d].flatMap((v) => [...v, ...from, ...to]); }) @@ -167,14 +154,52 @@ export class BrushPipeline { ); } - private get lineCount() { - return Math.max(0, this.linePoints.length - 1); + private static subsampleLinePoints(points: Array): Array { + const lines = []; + for (let i = 0; i < points.length - 2; i++) { + lines.push({ + from: points[i], + to: points[i + 1], + length: vec2.dist(points[i], points[i + 1]), + }); + } + + const sumLength = lines.reduce((sum, line) => sum + line.length, 0); + + let currentLineIndex = 0; + let lineLengthSoFar = 0; + const result: Array = [points[0]]; + for (let i = 1; i < BrushPipeline.MAX_LINE_COUNT; i++) { + const t = (i * sumLength) / (BrushPipeline.MAX_LINE_COUNT + 1); + while (lineLengthSoFar + lines[currentLineIndex].length < t) { + lineLengthSoFar += lines[currentLineIndex].length; + currentLineIndex++; + } + + const line = lines[currentLineIndex]; + const position = vec2.lerp( + vec2.create(), + line.from, + line.to, + (t - lineLengthSoFar) / line.length + ); + + result.push(position); + } + + result.push(last(points)); + + return result; } private getSegmentBoundingBox(from: vec2, to: vec2, width: number): Array { - const dir = vec2.sub(vec2.create(), to, from); + let dir = vec2.sub(vec2.create(), to, from); vec2.normalize(dir, dir); + if (vec2.len(dir) === 0) { + dir = vec2.fromValues(1, 0); // allow single point swipes + } + const perp = vec2.fromValues(dir[1], -dir[0]); vec2.scale(dir, dir, width); @@ -231,4 +256,8 @@ export class BrushPipeline { ], }; } + + private get lineCount() { + return clamp(this.actualPoints.length - 1, 0, BrushPipeline.MAX_LINE_COUNT); + } } diff --git a/src/settings.ts b/src/settings.ts index a5b9700..f01d649 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -19,10 +19,12 @@ export const settings: GameLoopSettings & BrushSettings & DiffusionSettings & RenderSettings = { - agentCount: 500_000, - renderSpeed: 1, + agentCount: 500, startingRadius: 0.15, + renderSpeed: 1, + simulatedDelayMs: 0, + brushWidth: 20, brushWidthRandomness: 8, diff --git a/src/utils/catmull-rom-interpolation.ts b/src/utils/catmull-rom-interpolation.ts deleted file mode 100644 index 0d5fc14..0000000 --- a/src/utils/catmull-rom-interpolation.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { vec2 } from 'gl-matrix'; - -export const catmullRomInterpolation = ( - p0: vec2, - p1: vec2, - p2: vec2, - p3: vec2, - t: number -): vec2 => { - const t2 = t * t; - const t3 = t2 * t; - - const x = - 0.5 * - (2 * p1[0] + - (-p0[0] + p2[0]) * t + - (2 * p0[0] - 5 * p1[0] + 4 * p2[0] - p3[0]) * t2 + - (-p0[0] + 3 * p1[0] - 3 * p2[0] + p3[0]) * t3); - - const y = - 0.5 * - (2 * p1[1] + - (-p0[1] + p2[1]) * t + - (2 * p0[1] - 5 * p1[1] + 4 * p2[1] - p3[1]) * t2 + - (-p0[1] + 3 * p1[1] - 3 * p2[1] + p3[1]) * t3); - - return [x, y]; -};