From da57c4551d2fda07d54c50d8870053306bab49ee Mon Sep 17 00:00:00 2001 From: Yehonatan Daniv Date: Thu, 19 Dec 2024 21:30:29 +0200 Subject: [PATCH] Move the PointerTexture rendering to a Worker and an OffscreenCanvas (#63) --- demo/blob-texture-worker.js | 146 ++++++++++++++++++++++++++++++++++++ demo/multi-pointer.js | 125 +++--------------------------- 2 files changed, 156 insertions(+), 115 deletions(-) create mode 100644 demo/blob-texture-worker.js diff --git a/demo/blob-texture-worker.js b/demo/blob-texture-worker.js new file mode 100644 index 0000000..9b0b9b7 --- /dev/null +++ b/demo/blob-texture-worker.js @@ -0,0 +1,146 @@ +const easeOutSine = (t) => { + return Math.sin(t * Math.PI / 2); +}; + +const easeOutQuad = (t) => { + return -1 * t * (t - 2) ; +}; + +class PointerTexture { + constructor ({ target, width, height, radius, intensity, maxAge, ctx, forceDecay = 0.01 }){ + this.width = width; + this.height = height; + this.radius = radius || 100; + this.intensity = intensity; + this.maxAge = maxAge; + this.decayFactor = forceDecay; + this.last = null; + this.points = []; + this.ctx = ctx; + } + + clear() { + this.ctx.fillStyle = 'rgba(127, 127, 0, 1)'; + this.ctx.fillRect(0, 0, this.width, this.height); + } + + addPoint (e){ + const x = e.offsetX / this.width; + const y = e.offsetY / this.height; + const point = { + x, + y, + age: 0, + speed: 0, + vx: 0, + vy: 0, + }; + + if (this.last) { + const relativeX = point.x - this.last.x; + const relativeY = point.y - this.last.y; + + const speedSquared = relativeX ** 2 + relativeY ** 2; + const speed = Math.sqrt(speedSquared); + + point.vx = relativeX / (speed + 1e-5); + point.vy = relativeY / (speed + 1e-5); + + point.speed = Math.min(speedSquared * 1e3, 1); + } + + this.last = point; + + this.points.push(point); + } + + update () { + this.clear(); + this.points.forEach((point, i) => { + const decay = 1 - (point.age / this.maxAge); + const force = point.speed * decay ** 2 * this.decayFactor; + point.x += point.vx * force; + point.y += point.vy * force; + point.age += 1; + + if (point.age > this.maxAge) { + this.points.splice(i, 1); + + if (this.points.length === 0) { + this.last = null; + } + } else { + this.drawPoint(point); + } + }); + } + + drawPoint (point) { + const position = { + x: point.x * this.width, + y: point.y * this.height + }; + let intensity = 1; + if (point.age < this.maxAge * 0.3) { + intensity = easeOutSine(point.age / (this.maxAge * 0.3)); + } else { + intensity = easeOutQuad(1 - (point.age - this.maxAge * 0.3) / (this.maxAge * 0.7)); + } + intensity *= point.speed; + + const red = (1 - point.vx) / 2 * 255; + const green = (1 + point.vy) / 2 * 255; + const blue = intensity * 255; + + const offset = this.width * 5; + this.ctx.shadowOffsetX = offset; + this.ctx.shadowOffsetY = offset; + this.ctx.shadowBlur = this.radius; + this.ctx.shadowColor = `rgba(${red}, ${green}, ${blue}, ${this.intensity * intensity})`; + + this.ctx.beginPath(); + this.ctx.fillStyle = 'rgba(255, 0 , 0, 0, 1)'; + this.ctx.arc(position.x - offset, position.y - offset, this.radius, 0, 2 * Math.PI); + this.ctx.fill(); + } +} + +function play () { + function render(time) { + pointerTexture.update(); + requestAnimationFrame(render); + } + requestAnimationFrame(render); +} + +let pointerTexture; + +onmessage = (evt) => { + if (evt.data.type === 'init') { + const { + width, + height, + canvas, + radius, + intensity, + maxAge, + forceDecay, + } = evt.data; + const ctx = canvas.getContext('2d'); + + pointerTexture = new PointerTexture({ + width, + height, + ctx, + radius, + intensity, + maxAge, + forceDecay, + }); + + play(); + } + else if (evt.data.type === 'addpoint') { + pointerTexture.addPoint(evt.data.event); + } +}; diff --git a/demo/multi-pointer.js b/demo/multi-pointer.js index 5a78aa3..cb6fce9 100644 --- a/demo/multi-pointer.js +++ b/demo/multi-pointer.js @@ -10,114 +10,7 @@ const DEBUG = false; const target = document.querySelector('#target'); const mapTarget = document.createElement('canvas'); -const easeOutSine = (t) => { - return Math.sin(t * Math.PI / 2); -}; - -const easeOutQuad = (t) => { - return -1 * t * (t - 2) ; -}; - -class PointerTexture { - constructor ({ target, width, height, radius, intensity, maxAge, canvas, forceDecay = 0.01 }){ - this.width = width; - this.height = height; - this.radius = radius || 100; - this.intensity = intensity; - this.maxAge = maxAge; - this.decayFactor = forceDecay; - this.last = null; - this.points = []; - this.ctx = canvas.getContext('2d'); - - target.addEventListener('pointermove', this.addPoint.bind(this)); - } - - clear() { - this.ctx.fillStyle = 'rgba(127, 127, 0, 1)'; - this.ctx.fillRect(0, 0, this.width, this.height); - } - - addPoint (e){ - const x = e.offsetX / this.width; - const y = e.offsetY / this.height; - const point = { - x, - y, - age: 0, - speed: 0, - vx: 0, - vy: 0, - }; - - if (this.last) { - const relativeX = point.x - this.last.x; - const relativeY = point.y - this.last.y; - - const speedSquared = relativeX ** 2 + relativeY ** 2; - const speed = Math.sqrt(speedSquared); - - point.vx = relativeX / (speed + 1e-5); - point.vy = relativeY / (speed + 1e-5); - - point.speed = Math.min(speedSquared * 1e3, 1); - } - - this.last = point; - - this.points.push(point); - } - - update () { - this.clear(); - this.points.forEach((point, i) => { - const decay = 1 - (point.age / this.maxAge); - const force = point.speed * decay ** 2 * this.decayFactor; - point.x += point.vx * force; - point.y += point.vy * force; - point.age += 1; - - if (point.age > this.maxAge) { - this.points.splice(i, 1); - - if (this.points.length === 0) { - this.last = null; - } - } else { - this.drawPoint(point); - } - }); - } - - drawPoint (point) { - const position = { - x: point.x * this.width, - y: point.y * this.height - }; - let intensity = 1; - if (point.age < this.maxAge * 0.3) { - intensity = easeOutSine(point.age / (this.maxAge * 0.3)); - } else { - intensity = easeOutQuad(1 - (point.age - this.maxAge * 0.3) / (this.maxAge * 0.7)); - } - intensity *= point.speed; - - const red = (1 - point.vx) / 2 * 255; - const green = (1 + point.vy) / 2 * 255; - const blue = intensity * 255; - - const offset = this.width * 5; - this.ctx.shadowOffsetX = offset; - this.ctx.shadowOffsetY = offset; - this.ctx.shadowBlur = this.radius; - this.ctx.shadowColor = `rgba(${red}, ${green}, ${blue}, ${this.intensity * intensity})`; - - this.ctx.beginPath(); - this.ctx.fillStyle = 'rgba(255, 0 , 0, 0, 1)'; - this.ctx.arc(position.x - offset, position.y - offset, this.radius, 0, 2 * Math.PI); - this.ctx.fill(); - } -} +const worker = new Worker('./blob-texture-worker.js'); const MAP_WIDTH = 1396; const MAP_HEIGHT = 992; @@ -134,16 +27,20 @@ loadImage( mapTarget.width = width; mapTarget.height = height; - // const render = new Kampos({ target: mapTarget, effects: [pointer], noSource: true }); - const pointerTexture = new PointerTexture({ - target, + const offscreen = mapTarget.transferControlToOffscreen(); + worker.postMessage({ + type: 'init', + canvas: offscreen, width, height, - canvas: DEBUG ? target : mapTarget, radius: 130, intensity: 1.2, maxAge: 130, forceDecay: 0.01, + }, [offscreen]); + + target.addEventListener('pointermove', ({ offsetX, offsetY}) => { + worker.postMessage({ type: 'addpoint', event: { offsetX, offsetY} }); }); // create the main instance that renders the displaced image @@ -168,8 +65,6 @@ loadImage( // // set media source instance.setSource({media: img, width, height}); - instance.play(() => { - pointerTexture.update(); - }); + instance.play(); } });