Skip to content

Commit

Permalink
Move the PointerTexture rendering to a Worker and an OffscreenCanvas (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
ydaniv authored Dec 19, 2024
1 parent ad99b33 commit da57c45
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 115 deletions.
146 changes: 146 additions & 0 deletions demo/blob-texture-worker.js
Original file line number Diff line number Diff line change
@@ -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);
}
};
125 changes: 10 additions & 115 deletions demo/multi-pointer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -168,8 +65,6 @@ loadImage(
// // set media source
instance.setSource({media: img, width, height});

instance.play(() => {
pointerTexture.update();
});
instance.play();
}
});

0 comments on commit da57c45

Please sign in to comment.