diff --git a/.eslintignore b/.eslintignore index 2cb7d2a..6de001d 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1 @@ -**/*.js +webpack.config.js diff --git a/src/game-loop/game-loop.ts b/src/game-loop/game-loop.ts index 93c40e2..2d5a60f 100644 --- a/src/game-loop/game-loop.ts +++ b/src/game-loop/game-loop.ts @@ -6,21 +6,20 @@ import { RenderPipeline } from '../pipelines/render/render-pipeline'; import { settings } from '../settings'; import { DeltaTimeCalculator } from '../utils/delta-time-calculator'; import { Random } from '../utils/random'; +import { sleep } from '../utils/sleep'; import { vec2 } from 'gl-matrix'; -export default class Renderer { +export default class GameLoop { private context: GPUCanvasContext; private adapter: GPUAdapter; private device: GPUDevice; - private queue: GPUQueue; private agentPipeline: AgentPipeline; private renderPipeline: RenderPipeline; private brushPipeline: BrushPipeline; private diffusionPipeline: DiffusionPipeline; - private preferredCanvasFormat: GPUTextureFormat; private trailMapA?: GPUTexture; private trailMapB?: GPUTexture; @@ -35,13 +34,9 @@ export default class Renderer { this.resize(); this.agentPipeline = new AgentPipeline(this.device, this.spawnAgents()); - this.renderPipeline = new RenderPipeline( - this.context, - this.device, - this.preferredCanvasFormat - ); this.brushPipeline = new BrushPipeline(this.device); this.diffusionPipeline = new DiffusionPipeline(this.device); + this.renderPipeline = new RenderPipeline(this.context, this.device); window.addEventListener('resize', this.resize.bind(this)); window.addEventListener('mousemove', this.onSwipe.bind(this)); @@ -59,12 +54,9 @@ export default class Renderer { return; } - const uv = vec2.fromValues( - event.clientX / this.canvas.width, - 1 - event.clientY / this.canvas.height + this.brushPipeline.addSwipe( + vec2.fromValues(event.clientX, this.canvas.height - event.clientY) ); - - this.brushPipeline.addSwipe(uv); } private spawnAgents(): Array { @@ -127,13 +119,11 @@ export default class Renderer { this.adapter = await gpu.requestAdapter(); this.device = await this.adapter.requestDevice(); // could request more resources - this.queue = this.device.queue; this.context = this.canvas.getContext('webgpu') as any; - this.preferredCanvasFormat = navigator.gpu.getPreferredCanvasFormat(); this.context.configure({ device: this.device, - format: this.preferredCanvasFormat, + format: gpu.getPreferredCanvasFormat(), alphaMode: 'premultiplied', }); } @@ -165,7 +155,7 @@ export default class Renderer { [this.trailMapA, this.trailMapB] = [this.trailMapB, this.trailMapA]; } - this.queue.submit([commandEncoder.finish()]); + this.device.queue.submit([commandEncoder.finish()]); // await sleep(200); requestAnimationFrame(this.render.bind(this)); diff --git a/src/index.ts b/src/index.ts index 86ebfb0..497a1f7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,10 @@ -import Renderer from './game-loop/game-loop'; +import GameLoop from './game-loop/game-loop'; import './styles/index.scss'; const main = () => { const canvas = document.querySelector('canvas'); - const renderer = new Renderer(canvas); - renderer.start(); + const game = new GameLoop(canvas); + game.start(); }; main(); diff --git a/src/pipelines/agents/agent-pipeline.ts b/src/pipelines/agents/agent-pipeline.ts index de863ce..00994c6 100644 --- a/src/pipelines/agents/agent-pipeline.ts +++ b/src/pipelines/agents/agent-pipeline.ts @@ -1,3 +1,4 @@ +import { smartCompile } from '../../utils/smart-compile'; import { CommonParameters } from '../common-parameters'; import { AGENT_SIZE_IN_BYTES, Agent } from './agent'; import { AgentSettings } from './agent-settings'; @@ -23,9 +24,7 @@ export class AgentPipeline { this.pipeline = device.createComputePipeline({ layout: 'auto', compute: { - module: device.createShaderModule({ - code: shader, - }), + module: smartCompile(device, shader), entryPoint: 'main', }, }); diff --git a/src/pipelines/brush/brush-pipeline.ts b/src/pipelines/brush/brush-pipeline.ts index 1fe9105..e5afdc4 100644 --- a/src/pipelines/brush/brush-pipeline.ts +++ b/src/pipelines/brush/brush-pipeline.ts @@ -1,3 +1,5 @@ +import { generateNoise } from '../../utils/graphics/noise/noise'; +import { smartCompile } from '../../utils/smart-compile'; import { CommonParameters } from '../common-parameters'; import { BrushSettings } from './brush-settings'; import shader from './brush.wgsl'; @@ -5,22 +7,34 @@ import shader from './brush.wgsl'; import { vec2 } from 'gl-matrix'; export class BrushPipeline { - private static readonly UNIFORM_COUNT = 4; + private static readonly UNIFORM_COUNT = 9; private static readonly MAX_LINE_COUNT = 100; private static readonly VERTICES_PER_LINE_SEGMENT = 6; + private static readonly ATTRIBUTES_PER_LINE_SEGMENT = 6; private readonly pipeline: GPURenderPipeline; private readonly uniforms: GPUBuffer; private readonly vertexBuffer: GPUBuffer; - private readonly linePoints: Array = []; + private readonly noise: GPUTexture; + private linePoints: Array = []; + private previousPoints: Array = []; + private nextPoint: vec2 | null = null; private bindGroup: GPUBindGroup; public constructor(private readonly device: GPUDevice) { + this.noise = generateNoise({ + device, + octaves: 4, + amplitude: 0.7, + gain: 0.6, + lacunarity: 4, + }); + this.vertexBuffer = device.createBuffer({ size: BrushPipeline.MAX_LINE_COUNT * BrushPipeline.VERTICES_PER_LINE_SEGMENT * - 2 * + BrushPipeline.ATTRIBUTES_PER_LINE_SEGMENT * Float32Array.BYTES_PER_ELEMENT, usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST, }); @@ -28,31 +42,45 @@ export class BrushPipeline { this.pipeline = device.createRenderPipeline({ layout: 'auto', vertex: { - module: device.createShaderModule({ - code: shader, - }), + module: smartCompile(device, shader), entryPoint: 'vertex', buffers: [ { - arrayStride: Float32Array.BYTES_PER_ELEMENT * 2, + arrayStride: Float32Array.BYTES_PER_ELEMENT * 6, attributes: [ { shaderLocation: 0, format: 'float32x2', offset: 0, }, + { + shaderLocation: 1, + format: 'float32x2', + offset: Float32Array.BYTES_PER_ELEMENT * 2, + }, + { + shaderLocation: 2, + format: 'float32x2', + offset: Float32Array.BYTES_PER_ELEMENT * 4, + }, ], }, ], }, fragment: { - module: device.createShaderModule({ - code: shader, - }), + module: smartCompile(device, shader), entryPoint: 'fragment', targets: [ { format: 'rgba16float', + blend: { + color: { + operation: 'max', + srcFactor: 'one', + dstFactor: 'one', + }, + alpha: {}, + }, }, ], }, @@ -75,29 +103,75 @@ export class BrushPipeline { buffer: this.uniforms, }, }, + { + binding: 1, + resource: this.device.createSampler({ + magFilter: 'linear', + minFilter: 'linear', + }), + }, + { + binding: 2, + resource: this.noise.createView(), + }, ], }); } public addSwipe(position: vec2) { - this.linePoints.push(position); + this.nextPoint = position; + // this.linePoints.push(position); } public clearSwipes() { this.linePoints.length = 0; + this.previousPoints.length = 0; + this.nextPoint = null; } public setParameters({ canvasSize, deltaTime, time, + brushWidth, + brushBlurWidth, }: CommonParameters & BrushSettings) { this.device.queue.writeBuffer( this.uniforms, 0, - new Float32Array([canvasSize[0], canvasSize[1], deltaTime, time]) + new Float32Array([...canvasSize, deltaTime, time, brushWidth / 2, brushBlurWidth]) ); + // this.linePoints = [ + // vec2.fromValues(0.1, 0.1), + // vec2.fromValues(0.8, 0.2), + // vec2.fromValues(0.75, 0.8), + // vec2.fromValues(0.1, 0.4), + // ].map((v) => vec2.multiply(v, v, canvasSize)); + + if (this.nextPoint == null) { + return; + } + this.previousPoints.push(this.nextPoint); + if (this.previousPoints.length < 3) { + 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.previousPoints.splice(0, this.previousPoints.length - 3); + this.device.queue.writeBuffer( this.vertexBuffer, 0, @@ -105,8 +179,8 @@ export class BrushPipeline { new Array(this.lineCount).fill(0).flatMap((_, i) => { const from = this.linePoints[i]; const to = this.linePoints[i + 1]; - const [a, b, c, d] = this.lineToRectangle(from, to, 0.01); - return [...a, ...b, ...c, ...b, ...c, ...d]; + const [a, b, c, d] = this.getSegmentBoundingBox(from, to, brushWidth / 2); + return [a, b, c, b, c, d].flatMap((v) => [...v, ...from, ...to]); }) ) ); @@ -116,16 +190,23 @@ export class BrushPipeline { return Math.max(0, this.linePoints.length - 1); } - private lineToRectangle(from: vec2, to: vec2, width: number): [vec2, vec2, vec2, vec2] { + private getSegmentBoundingBox(from: vec2, to: vec2, width: number): Array { const dir = vec2.sub(vec2.create(), to, from); + vec2.normalize(dir, dir); + const perp = vec2.fromValues(dir[1], -dir[0]); - vec2.normalize(perp, perp); - vec2.scale(perp, perp, width / 2); + + vec2.scale(dir, dir, width); + vec2.scale(perp, perp, width); + + const offsetStart = vec2.sub(vec2.create(), from, dir); + const offsetEnd = vec2.add(vec2.create(), to, dir); + return [ - vec2.add(vec2.create(), from, perp), - vec2.sub(vec2.create(), from, perp), - vec2.add(vec2.create(), to, perp), - vec2.sub(vec2.create(), to, perp), + vec2.add(vec2.create(), offsetStart, perp), + vec2.sub(vec2.create(), offsetStart, perp), + vec2.add(vec2.create(), offsetEnd, perp), + vec2.sub(vec2.create(), offsetEnd, perp), ]; } @@ -134,7 +215,6 @@ export class BrushPipeline { colorAttachments: [ { view: trailMapOut.createView(), - clearValue: { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }, loadOp: 'load', storeOp: 'store', }, @@ -145,9 +225,36 @@ export class BrushPipeline { passEncoder.setPipeline(this.pipeline); passEncoder.setBindGroup(0, this.bindGroup); passEncoder.setVertexBuffer(0, this.vertexBuffer); - passEncoder.draw(this.lineCount * BrushPipeline.VERTICES_PER_LINE_SEGMENT, 1); + passEncoder.draw(BrushPipeline.VERTICES_PER_LINE_SEGMENT * this.lineCount, 1); passEncoder.end(); - this.linePoints.splice(0, this.linePoints.length - 1); // clear the array + this.linePoints.splice(0, this.linePoints.length - 1); } } + +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]; +}; diff --git a/src/pipelines/brush/brush-settings.ts b/src/pipelines/brush/brush-settings.ts index 4c10a34..bbbbdb9 100644 --- a/src/pipelines/brush/brush-settings.ts +++ b/src/pipelines/brush/brush-settings.ts @@ -1 +1,4 @@ -export interface BrushSettings {} +export interface BrushSettings { + brushWidth: number; + brushBlurWidth: number; +} diff --git a/src/pipelines/brush/brush.wgsl b/src/pipelines/brush/brush.wgsl index 8bf1731..2145011 100644 --- a/src/pipelines/brush/brush.wgsl +++ b/src/pipelines/brush/brush.wgsl @@ -1,25 +1,49 @@ +struct Settings { + size : vec2, + deltaTime : f32, + time : f32, + brushWidth: f32, + brushBlurWidth: f32 +}; + +@group(0) @binding(0) var settings : Settings; +@group(0) @binding(1) var Sampler: sampler; +@group(0) @binding(2) var noise : texture_2d; + struct VertexOutput { @builtin(position) position : vec4, - @location(0) uv : vec2 + @location(0) screenPosition : vec2, + @location(1) start : vec2, + @location(2) end : vec2 } @vertex fn vertex( - @location(0) uv : vec2 + @location(0) screenPosition : vec2, + @location(1) @interpolate(flat) start : vec2, + @location(2) @interpolate(flat) end : vec2 ) -> VertexOutput { + let uv = screenPosition / settings.size; let position = uv * 2.0 - 1.0; - return VertexOutput(vec4(position, 0.0, 1.0), uv); + return VertexOutput(vec4(position, 0.0, 1.0), screenPosition, start, end); } -struct Settings { - size : vec2, - deltaTime : f32, - time : f32 -}; - -@group(0) @binding(0) var settings : Settings; - @fragment -fn fragment(@location(0) uv: vec2) -> @location(0) vec4 { - return vec4(1, settings.size.x * 0, 0, 1); +fn fragment( + @location(0) screenPosition: vec2, + @location(1) start: vec2, + @location(2) end: vec2 +) -> @location(0) vec4 { + let pa = (screenPosition - start); + let direction = (end - start); + let q = clamp(dot(pa, direction) / dot(direction, direction), 0, 1); + let noise = textureSample(noise, Sampler, screenPosition / settings.size); + + let distance = length(pa - direction * q) + noise.r * 5; + + if(distance > settings.brushWidth) { + discard; + } + + return vec4(clamp((settings.brushWidth - distance) / settings.brushBlurWidth, 0, 1)); } diff --git a/src/pipelines/diffusion/diffuse.wgsl b/src/pipelines/diffusion/diffuse.wgsl index 802886b..30e7d2c 100644 --- a/src/pipelines/diffusion/diffuse.wgsl +++ b/src/pipelines/diffusion/diffuse.wgsl @@ -5,8 +5,6 @@ struct Settings { diffusionRate : f32, decayRate : f32, - swipeRadius : f32, - swipeBlur : f32, }; @group(0) @binding(0) var settings : Settings; @@ -24,9 +22,11 @@ fn fragment(@location(0) uv: vec2) -> @location(0) vec4 { + textureSample(trailMap, Sampler, uv + vec2(1, 0) / settings.size) ); - return mix( + let mixed = mix( current, neighbours / 4.0, settings.diffusionRate ) * (1.0 - settings.decayRate); + + return clamp(mixed, vec4(0), vec4(1)); } diff --git a/src/pipelines/diffusion/diffusion-pipeline.ts b/src/pipelines/diffusion/diffusion-pipeline.ts index 53f1f4e..96a9881 100644 --- a/src/pipelines/diffusion/diffusion-pipeline.ts +++ b/src/pipelines/diffusion/diffusion-pipeline.ts @@ -1,4 +1,5 @@ -import { setUpFullScreenQuad } from '../../utils/full-screen-quad'; +import { setUpFullScreenQuad } from '../../utils/graphics/full-screen-quad/full-screen-quad'; +import { smartCompile } from '../../utils/smart-compile'; import { CommonParameters } from '../common-parameters'; import shader from './diffuse.wgsl'; import { DiffusionSettings } from './diffusion-settings'; @@ -21,9 +22,7 @@ export class DiffusionPipeline { layout: 'auto', vertex, fragment: { - module: device.createShaderModule({ - code: shader, - }), + module: smartCompile(device, shader), entryPoint: 'fragment', targets: [ { @@ -48,8 +47,6 @@ export class DiffusionPipeline { time, diffusionRate, decayRate, - swipeRadius, - swipeBlur, }: CommonParameters & DiffusionSettings) { this.device.queue.writeBuffer( this.uniforms, @@ -61,8 +58,6 @@ export class DiffusionPipeline { time, diffusionRate, decayRate, - swipeRadius, - swipeBlur, ]) ); } diff --git a/src/pipelines/diffusion/diffusion-settings.ts b/src/pipelines/diffusion/diffusion-settings.ts index 25d77ed..3b91e37 100644 --- a/src/pipelines/diffusion/diffusion-settings.ts +++ b/src/pipelines/diffusion/diffusion-settings.ts @@ -1,6 +1,4 @@ export interface DiffusionSettings { diffusionRate: number; decayRate: number; - swipeRadius: number; - swipeBlur: number; } diff --git a/src/pipelines/render/render-pipeline.ts b/src/pipelines/render/render-pipeline.ts index aba0533..94cc75d 100644 --- a/src/pipelines/render/render-pipeline.ts +++ b/src/pipelines/render/render-pipeline.ts @@ -1,4 +1,5 @@ -import { setUpFullScreenQuad } from '../../utils/full-screen-quad'; +import { setUpFullScreenQuad } from '../../utils/graphics/full-screen-quad/full-screen-quad'; +import { smartCompile } from '../../utils/smart-compile'; import { CommonParameters } from '../common-parameters'; import { RenderSettings } from './render-settings'; import shader from './render.wgsl'; @@ -15,8 +16,7 @@ export class RenderPipeline { public constructor( private readonly context: GPUCanvasContext, - private readonly device: GPUDevice, - preferredCanvasFormat: GPUTextureFormat + private readonly device: GPUDevice ) { const { buffer, vertex } = setUpFullScreenQuad(device); this.quadVertexBuffer = buffer; @@ -25,13 +25,11 @@ export class RenderPipeline { layout: 'auto', vertex, fragment: { - module: device.createShaderModule({ - code: shader, - }), + module: smartCompile(device, shader), entryPoint: 'fragment', targets: [ { - format: preferredCanvasFormat, + format: navigator.gpu.getPreferredCanvasFormat(), }, ], }, @@ -54,7 +52,7 @@ export class RenderPipeline { this.device.queue.writeBuffer( this.uniforms, 0, - new Float32Array([canvasSize[0], canvasSize[1], deltaTime, time]) + new Float32Array([...canvasSize, deltaTime, time]) ); } diff --git a/src/pipelines/render/render.wgsl b/src/pipelines/render/render.wgsl index 1eebaf3..6a72648 100644 --- a/src/pipelines/render/render.wgsl +++ b/src/pipelines/render/render.wgsl @@ -10,5 +10,6 @@ struct Settings { @fragment fn fragment(@location(0) uv: vec2) -> @location(0) vec4 { - return vec4(textureSample(TargetTexture, mySampler, uv).r * 1.0, settings.deltaTime * 0.0, 0.0, 1.0); + return vec4(textureSample(TargetTexture, mySampler, uv).rgb, 1.0); + return vec4(0, settings.deltaTime * 0.0, 0.0, 1.0); } diff --git a/src/settings.ts b/src/settings.ts index 8fe5e36..21f9b04 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -13,15 +13,15 @@ export const settings: GameLoopSettings & renderSpeed: 1, startingRadius: 0.15, + brushWidth: 20, + brushBlurWidth: 5, + trailWeight: 5, moveSpeed: 0.025, turnSpeed: 6, sensorAngleDegrees: 30, sensorOffsetDst: 0.025, - decayRate: 0.02, + decayRate: 0.005, diffusionRate: 0.8, - - swipeRadius: 0.003, - swipeBlur: 0.002, }; diff --git a/src/utils/full-screen-quad.ts b/src/utils/graphics/full-screen-quad/full-screen-quad.ts similarity index 91% rename from src/utils/full-screen-quad.ts rename to src/utils/graphics/full-screen-quad/full-screen-quad.ts index 00a2188..1afd862 100644 --- a/src/utils/full-screen-quad.ts +++ b/src/utils/graphics/full-screen-quad/full-screen-quad.ts @@ -1,3 +1,4 @@ +import { smartCompile } from '../../smart-compile'; import shader from './full-screen-quad.wgsl'; export const setUpFullScreenQuad = ( @@ -25,9 +26,7 @@ export const setUpFullScreenQuad = ( return { buffer, vertex: { - module: device.createShaderModule({ - code: shader, - }), + module: smartCompile(device, shader), entryPoint: 'vertex', buffers: [ { diff --git a/src/utils/full-screen-quad.wgsl b/src/utils/graphics/full-screen-quad/full-screen-quad.wgsl similarity index 100% rename from src/utils/full-screen-quad.wgsl rename to src/utils/graphics/full-screen-quad/full-screen-quad.wgsl diff --git a/src/utils/graphics/noise/noise.ts b/src/utils/graphics/noise/noise.ts new file mode 100644 index 0000000..05ece8b --- /dev/null +++ b/src/utils/graphics/noise/noise.ts @@ -0,0 +1,85 @@ +import { Random } from '../../random'; +import { smartCompile } from '../../smart-compile'; +import { setUpFullScreenQuad } from '../full-screen-quad/full-screen-quad'; +import noise from './noise.wgsl'; + +export const generateNoise = ({ + device, + width = 1024, + height = 1024, + octaves = 8, + lacunarity = 2, + amplitude = 0.5, + gain = 0.5, +}: { + device: GPUDevice; + width?: number; + height?: number; + octaves?: number; + lacunarity?: number; + amplitude?: number; + gain?: number; +}) => { + const { buffer, vertex } = setUpFullScreenQuad(device); + const quadVertexBuffer = buffer; + + const pipeline = device.createRenderPipeline({ + layout: 'auto', + vertex, + fragment: { + module: smartCompile(device, noise), + entryPoint: 'fragment', + constants: { + octaves, + lacunarity, + amplitude, + gain, + seedR: Random.getRandom(), + seedG: Random.getRandom(), + seedB: Random.getRandom(), + seedA: Random.getRandom(), + }, + targets: [ + { + format: 'rgba16float', + }, + ], + }, + primitive: { + topology: 'triangle-strip', + }, + }); + + const colorTexture = device.createTexture({ + size: { + width, + height, + depthOrArrayLayers: 1, + }, + format: 'rgba16float', + usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT, + }); + + const renderPassDescriptor: GPURenderPassDescriptor = { + colorAttachments: [ + { + view: colorTexture.createView(), + clearValue: { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }, + loadOp: 'clear', + storeOp: 'store', + }, + ], + }; + + const commandEncoder = device.createCommandEncoder(); + + const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); + passEncoder.setPipeline(pipeline); + passEncoder.setVertexBuffer(0, quadVertexBuffer); + passEncoder.draw(4, 1); + passEncoder.end(); + + device.queue.submit([commandEncoder.finish()]); + + return colorTexture; +}; diff --git a/src/utils/graphics/noise/noise.wgsl b/src/utils/graphics/noise/noise.wgsl new file mode 100644 index 0000000..78b8565 --- /dev/null +++ b/src/utils/graphics/noise/noise.wgsl @@ -0,0 +1,57 @@ +override octaves: i32; +override amplitude: f32; +override gain: f32; +override lacunarity: f32; + +override seedR: f32; +override seedG: f32; +override seedB: f32; +override seedA: f32; + +@fragment +fn fragment(@location(0) uv: vec2) -> @location(0) vec4 { + return vec4( + fbm(uv, seedR), + fbm(uv, seedG), + fbm(uv, seedB), + fbm(uv, seedA), + ); +} + +fn fbm(uv: vec2, seed: f32) -> f32 { + var st = uv; + var v = 0.0; + var a = amplitude; + + let shift = vec2(100.0); + let rot = mat2x2(cos(0.5), sin(0.5), + -sin(0.5), cos(0.50)); + + for (var i = 0; i < octaves; i++) { + v += a * noise(st, seed); + st = rot * st * lacunarity + shift; + a *= gain; + } + + return v; +} + +fn noise (st: vec2, seed: f32) -> f32 { + let i = floor(st); + let f = fract(st); + + let a = random(i, seed); + let b = random(i + vec2(1.0, 0.0), seed); + let c = random(i + vec2(0.0, 1.0), seed); + let d = random(i + vec2(1.0, 1.0), seed); + + let u = f * f * (3.0 - 2.0 * f); + + return mix(a, b, u.x) + + (c - a)* u.y * (1.0 - u.x) + + (d - b) * u.x * u.y; +} + +fn random(st: vec2, seed: f32) -> f32 { + return fract(sin(dot(st.xy, vec2(12.9898 + seed, 78.233 + seed)))* 43758.5453123 + seed); +} diff --git a/src/utils/random.ts b/src/utils/random.ts index 907e764..a793a91 100644 --- a/src/utils/random.ts +++ b/src/utils/random.ts @@ -7,11 +7,15 @@ export abstract class Random { Random._seed = value; } - public static getRandom(): number { + public static getRandomInt(): number { let t = (Random._seed += 0x6d2b79f5); t = Math.imul(t ^ (t >>> 15), t | 1); t ^= t + Math.imul(t ^ (t >>> 7), t | 61); - return ((t ^ (t >>> 14)) >>> 0) / 4294967296; + return (t ^ (t >>> 14)) >>> 0; + } + + public static getRandom(): number { + return Random.getRandomInt() / 4294967296; } public static randomBetween(from: number, to: number): number { diff --git a/src/utils/smart-compile.ts b/src/utils/smart-compile.ts new file mode 100644 index 0000000..73fc962 --- /dev/null +++ b/src/utils/smart-compile.ts @@ -0,0 +1,15 @@ +export const smartCompile = (device: GPUDevice, code: string) => { + const module = device.createShaderModule({ + code, + }); + + module + .getCompilationInfo() + .then((info) => + info.messages.forEach((message) => + console.warn(message.type, message.message, code.split('\n')[message.lineNum - 1]) + ) + ); + + return module; +};