diff --git a/example/src/examples/Flow.js b/example/src/examples/Flow.js index ad40793..4cfe027 100644 --- a/example/src/examples/Flow.js +++ b/example/src/examples/Flow.js @@ -4,43 +4,18 @@ import flow_buffer from '../shader/flow_buffer.glsl?raw'; import ImageLoader from "../utils/ImageLoader"; export default class Flow { - constructor(wrapper, options = {}) { - this.wrapper = wrapper; + constructor(wrapper, options = {}) { + this.wrapper = wrapper; - this.renderer = ImageEffectRenderer.createTemporary(this.wrapper, flow_image, options); + this.renderer = ImageEffectRenderer.createTemporary(this.wrapper, flow_image, options); - this.mouseX = 0; - this.mouseY = 0; - this.prevMouseX = 0; - this.prevMouseY = 0; + this.renderer.createBuffer(0, flow_buffer); + this.renderer.buffers[0].setImage(0, this.renderer.buffers[0], {type: WebGLRenderingContext.FLOAT}); + this.renderer.setImage(1, this.renderer.buffers[0]); - this.renderer.createBuffer(0, flow_buffer); - this.renderer.buffers[0].setImage(0, this.renderer.buffers[0], {type: WebGLRenderingContext.FLOAT}); - this.renderer.setImage(1, this.renderer.buffers[0]); - - const canvas = this.renderer.canvas; - - canvas.onmousedown = () => { - this.mouseDown = true; - }; - - canvas.onmouseenter = canvas.onmousemove = (e) => { - const bounds = canvas.getBoundingClientRect(); - const x = Math.max(0, Math.min(1, (e.clientX - bounds.left) / bounds.width)); - const y = Math.max(0, Math.min(1, (e.clientY - bounds.top) / bounds.height)); - this.mouseX = x; - this.mouseY = 1 - y; - }; - - this.renderer.tick(() => { - this.renderer.buffers[0].setUniformVec4('uMouse', this.mouseX, this.mouseY, this.prevMouseX, this.prevMouseY); - this.prevMouseX = this.mouseX; - this.prevMouseY = this.mouseY; - }); - - ImageLoader.loadImages(['./paddo.jpg']).then(([mask]) => { - this.renderer.setImage(0, mask, {flipY: true}); - this.renderer.play(); - }); - } + ImageLoader.loadImages(['./paddo.jpg']).then(([mask]) => { + this.renderer.setImage(0, mask, {flipY: true}); + this.renderer.play(); + }); + } } diff --git a/example/src/examples/FluidDynamics.js b/example/src/examples/FluidDynamics.js index 06c76c7..18efc75 100644 --- a/example/src/examples/FluidDynamics.js +++ b/example/src/examples/FluidDynamics.js @@ -4,68 +4,41 @@ import fluid_paint from '../shader/fluid_paint.glsl?raw'; import fluid_image from '../shader/fluid_image.glsl?raw'; export default class FluidDynamics { - constructor(wrapper, options = {}) { - this.wrapper = wrapper; - - this.renderer = ImageEffectRenderer.createTemporary(this.wrapper, fluid_image, {loop: true, ...options}); - - this.mouseX = 0; - this.mouseY = 0; - this.prevMouseX = 0; - this.prevMouseY = 0; - - // fluid dynamics - this.renderer.createBuffer(0, fluid_dynamics, { - type: WebGLRenderingContext.FLOAT, - clampX: false, - clampY: false, - pixelRatio: 0.5 - }); - this.renderer.createBuffer(1, fluid_dynamics, { - type: WebGLRenderingContext.FLOAT, - clampX: false, - clampY: false, - pixelRatio: 0.5 - }); - this.renderer.createBuffer(2, fluid_dynamics, { - type: WebGLRenderingContext.FLOAT, - clampX: false, - clampY: false, - pixelRatio: 0.5 - }); - - this.renderer.buffers[0].setImage(0, this.renderer.buffers[2]); - this.renderer.buffers[1].setImage(0, this.renderer.buffers[0]); - this.renderer.buffers[2].setImage(0, this.renderer.buffers[1]); - - // fluid paint - this.renderer.createBuffer(3, fluid_paint, { - type: WebGLRenderingContext.FLOAT, - clampX: false, - clampY: false - }); - this.renderer.buffers[3].setImage(0, this.renderer.buffers[2]); - this.renderer.buffers[3].setImage(1, this.renderer.buffers[3]); - - this.renderer.setImage(0, this.renderer.buffers[3]); - - const canvas = this.renderer.canvas; - - canvas.onmouseenter = canvas.onmousemove = (e) => { - const bounds = canvas.getBoundingClientRect(); - const x = Math.max(0, Math.min(1, (e.clientX - bounds.left) / bounds.width)); - const y = Math.max(0, Math.min(1, (e.clientY - bounds.top) / bounds.height)); - this.mouseX = x; - this.mouseY = 1 - y; - }; - - this.renderer.tick(() => { - this.renderer.buffers[0].setUniformVec4('uMouse', this.mouseX, this.mouseY, this.prevMouseX, this.prevMouseY); - this.renderer.buffers[1].setUniformVec4('uMouse', this.mouseX, this.mouseY, this.prevMouseX, this.prevMouseY); - this.renderer.buffers[2].setUniformVec4('uMouse', this.mouseX, this.mouseY, this.prevMouseX, this.prevMouseY); - this.renderer.buffers[3].setUniformVec4('uMouse', this.mouseX, this.mouseY, this.prevMouseX, this.prevMouseY); - this.prevMouseX = this.mouseX; - this.prevMouseY = this.mouseY; - }); - } + constructor(wrapper, options = {}) { + this.wrapper = wrapper; + + this.renderer = ImageEffectRenderer.createTemporary(this.wrapper, fluid_image, {loop: true, ...options}); + + // fluid dynamics + this.renderer.createBuffer(0, fluid_dynamics, { + type: WebGLRenderingContext.FLOAT, + clampX: false, + clampY: false + }); + this.renderer.createBuffer(1, fluid_dynamics, { + type: WebGLRenderingContext.FLOAT, + clampX: false, + clampY: false + }); + this.renderer.createBuffer(2, fluid_dynamics, { + type: WebGLRenderingContext.FLOAT, + clampX: false, + clampY: false + }); + + this.renderer.buffers[0].setImage(0, this.renderer.buffers[2]); + this.renderer.buffers[1].setImage(0, this.renderer.buffers[0]); + this.renderer.buffers[2].setImage(0, this.renderer.buffers[1]); + + // fluid paint + this.renderer.createBuffer(3, fluid_paint, { + type: WebGLRenderingContext.FLOAT, + clampX: false, + clampY: false + }); + this.renderer.buffers[3].setImage(0, this.renderer.buffers[2]); + this.renderer.buffers[3].setImage(1, this.renderer.buffers[3]); + + this.renderer.setImage(0, this.renderer.buffers[3]); + } } diff --git a/example/src/shader/flow_buffer.glsl b/example/src/shader/flow_buffer.glsl index af04f5e..65b8c9f 100644 --- a/example/src/shader/flow_buffer.glsl +++ b/example/src/shader/flow_buffer.glsl @@ -1,9 +1,7 @@ -uniform vec4 uMouse; - vec3 mouseInput(vec2 uv) { - vec2 d = uv - uMouse.xy; + vec2 d = uv - iMouse.xy; d.x *= iResolution.x / iResolution.y; - return vec3((uMouse.zw-uMouse.xy) * 20. * smoothstep(.2, 0., length(d)), 0); + return vec3((iMouse.zw-iMouse.xy) * 20. * smoothstep(.2, 0., length(d)), 0); } void mainImage(out vec4 fragColor, in vec2 fragCoord) { diff --git a/example/src/shader/fluid_dynamics.glsl b/example/src/shader/fluid_dynamics.glsl index 32e0263..8fc3498 100644 --- a/example/src/shader/fluid_dynamics.glsl +++ b/example/src/shader/fluid_dynamics.glsl @@ -1,4 +1,3 @@ -uniform vec4 uMouse; uniform float uMouseDown; void mainImage(out vec4 fragColor, in vec2 fragCoord) { @@ -25,7 +24,7 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) { vec2 viscosityForce = 0.55*(tu.xy + td.xy + tr.xy + tl.xy - 4.0*me.xy); me.xyw = texture(iChannel0, uv - me.xy*(dt/iResolution.xy)).xyw; - vec2 externalForces = clamp(vec2(uMouse.xy - uMouse.zw) * (.4 / max(dot(uv - uMouse.xy, uv - uMouse.xy), .05)), -1., 1.); + vec2 externalForces = clamp(vec2(iMouse.xy - iMouse.zw) * (.4 / max(dot(uv - iMouse.xy, uv - iMouse.xy), .05)), -1., 1.); // Semi−lagrangian advection. me.xy += dt*(viscosityForce.xy + externalForces) - 0.2*DdX; diff --git a/example/src/shader/fluid_paint.glsl b/example/src/shader/fluid_paint.glsl index 1d84273..b443781 100644 --- a/example/src/shader/fluid_paint.glsl +++ b/example/src/shader/fluid_paint.glsl @@ -1,6 +1,3 @@ -uniform vec4 uMouse; -uniform float uMouseDown; - // The MIT License // Copyright © 2015 Inigo Quilez // https://www.shadertoy.com/view/ll2GD3 @@ -19,7 +16,7 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) { vec3 newCol = pal(iTime, vec3(0.5, 0.5, 0.5), vec3(0.5, 0.5, 0.5), vec3(1.0, 1.0, 1.0), vec3(0.0, 0.10, 0.20)); - col += newCol * 0.01*distance(uMouse.xy, uMouse.zw)/(dot(uv - uMouse.xy, uv - uMouse.xy)+0.002); + col += newCol * 0.01*distance(iMouse.xy, iMouse.zw)/(dot(uv - iMouse.xy, uv - iMouse.xy)+0.002); col = clamp(0.998 * col - 0.00005, 0., 5.); fragColor = vec4(col, 1.); diff --git a/src/lib/MouseListener.ts b/src/lib/MouseListener.ts new file mode 100644 index 0000000..5d6a940 --- /dev/null +++ b/src/lib/MouseListener.ts @@ -0,0 +1,31 @@ +let mouseX: number = -0; +let mouseY: number = -0; +let mouseBinded: boolean = false; + +export function bindMouseListener(container: HTMLElement) { + if (mouseBinded) { + return; + } + mouseBinded = true; + container.addEventListener('mousemove', (event) => { + mouseX = event.clientX; + mouseY = event.clientY; + }, {passive: true}); +} + +export function getMousePosition(): [number, number] { + return [mouseX, mouseY]; +} + +export type Rect = { + left: number, + top: number, + width: number, + height: number, +} + +export function getNormalizedMousePosition(container: Rect, mouse: [number, number]): [number, number] { + const x = (mouse[0] - container.left) / container.width; + const y = 1 - (mouse[1] - container.top) / container.height; + return [x, y]; +} \ No newline at end of file diff --git a/src/lib/Renderer.ts b/src/lib/Renderer.ts index 46a8068..858b8f1 100644 --- a/src/lib/Renderer.ts +++ b/src/lib/Renderer.ts @@ -43,7 +43,7 @@ export class Renderer { public gl: WebGLInstance; protected frame: number = 0; - + protected mouse: [number, number, number, number] = [0, 0, 0, 0]; private uniforms: { [k: string]: Uniform } = {}; private textures: Texture[] = []; @@ -55,6 +55,10 @@ export class Renderer { return this.program.shaderCompiled; } + public get iMouseUsed(): boolean { + return this.program.getUniformLocation('iMouse') !== null; + } + /** * Set an image to a slot for rendering. * Possible images can be image elements, video elements, canvas elements, or buffers. @@ -210,6 +214,9 @@ export class Renderer { this.setUniformFloat('iAspect', width / height); this.setUniformVec2('iResolution', width, height); + const mouse = this.main.mouse; + this.setUniformVec4('iMouse', mouse[0], mouse[1], mouse[2], mouse[3]); + this.gl.setUniforms(this.uniforms, this.program); this.gl.bindTextures(this.textures); this.gl.drawQuad(this.program.getAttributeLocation('aPos'), this.program.getAttributeLocation('aUV')); diff --git a/src/lib/RendererInstance.ts b/src/lib/RendererInstance.ts index 15580ae..f8a304a 100644 --- a/src/lib/RendererInstance.ts +++ b/src/lib/RendererInstance.ts @@ -3,6 +3,7 @@ import type {ImageEffectRendererOptions} from "./ImageEffectRenderer.js"; import {Renderer} from "./Renderer.js"; import {type BufferOptions, RendererBuffer} from "./RendererBuffer.js"; import Program from "./Program.js"; +import {bindMouseListener, getMousePosition, getNormalizedMousePosition} from "./MouseListener.js"; export class RendererInstance extends Renderer { private static index: number = 0; @@ -42,10 +43,10 @@ export class RendererInstance extends Renderer { this.canvas = this.gl.canvas; } Object.assign(this.canvas.style, { - inset: '0', - width: '100%', - height: '100%', - margin: '0', + inset: '0', + width: '100%', + height: '100%', + margin: '0', display: 'block', }); @@ -69,6 +70,10 @@ export class RendererInstance extends Renderer { return (this.options.loop || this.drawOneFrame) && this.width > 0 && this.height > 0 && (!this.options.asyncCompile || this.allShadersCompiled); } + public override get iMouseUsed(): boolean { + return super.iMouseUsed || this.buffers.some(buffer => buffer && buffer.iMouseUsed); + } + private get allShadersCompiled(): boolean { return this.shaderCompiled && this.buffers.every(buffer => buffer && buffer.shaderCompiled); } @@ -143,6 +148,12 @@ export class RendererInstance extends Renderer { this.tickFuncs.forEach(func => func(dt)); + if (this.iMouseUsed) { + const xprev = this.mouse[0], yprev = this.mouse[1]; + const [x, y] = getNormalizedMousePosition(this.container.getBoundingClientRect(), getMousePosition()); + this.mouse = [x, y, xprev, yprev]; + } + // update buffers this.buffers.forEach(buffer => { if (buffer) { @@ -164,6 +175,10 @@ export class RendererInstance extends Renderer { this._ready = true; this.readyFuncs.forEach(func => func()); this.readyFuncs = []; + + if (this.iMouseUsed) { + bindMouseListener(document.body); + } } } }