@visactor/vrender-animate
Version:
This module provides a graph-based animation system for VRender.
192 lines (182 loc) • 12.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: !0
}), exports.Particle = void 0;
const CustomEffectBase_1 = require("./base/CustomEffectBase"), ImageProcessUtils_1 = require("./base/ImageProcessUtils");
class Particle extends CustomEffectBase_1.HybridEffectBase {
constructor(from, to, duration, easing, params) {
var _a, _b, _c, _d, _e;
super(from, to, duration, easing, params), this.particles = [], this.positionBuffer = null,
this.colorBuffer = null, this.particleConfig = {
effectType: (null === (_a = null == params ? void 0 : params.options) || void 0 === _a ? void 0 : _a.effectType) || "gravity",
count: (null === (_b = null == params ? void 0 : params.options) || void 0 === _b ? void 0 : _b.count) || 4e3,
size: (null === (_c = null == params ? void 0 : params.options) || void 0 === _c ? void 0 : _c.size) || 20,
strength: (null === (_d = null == params ? void 0 : params.options) || void 0 === _d ? void 0 : _d.strength) || 1.5,
useWebGL: void 0 === (null === (_e = null == params ? void 0 : params.options) || void 0 === _e ? void 0 : _e.useWebGL) || params.options.useWebGL
};
}
getShaderSources() {
return {
vertex: "\n attribute vec2 a_position;\n attribute vec4 a_color;\n attribute float a_size;\n\n uniform vec2 u_resolution;\n uniform float u_time;\n uniform float u_forceStrength;\n uniform int u_effectType;\n\n varying vec4 v_color;\n\n void main() {\n // 将像素坐标转换为剪辑空间坐标\n vec2 clipSpace = ((a_position / u_resolution) * 2.0) - 1.0;\n clipSpace.y = -clipSpace.y; // 翻转Y轴\n\n gl_Position = vec4(clipSpace, 0.0, 1.0);\n gl_PointSize = a_size;\n v_color = a_color;\n }\n ",
fragment: "\n precision mediump float;\n varying vec4 v_color;\n\n void main() {\n // 创建圆形粒子\n vec2 coord = gl_PointCoord - vec2(0.5);\n float distance = length(coord);\n\n if (distance > 0.5) {\n discard;\n }\n\n // 保持原始颜色,只调整透明度渐变\n gl_FragColor = vec4(v_color.rgb, v_color.a);\n }\n "
};
}
applyWebGLEffect(canvas) {
if (!this.gl || !this.program || !this.webglCanvas) return null;
this.setupWebGLState(canvas), 0 === this.particles.length && this.extractParticles(canvas),
this.updateParticles(canvas);
const gl = this.gl;
return gl.enable(gl.BLEND), gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA),
gl.useProgram(this.program), this.prepareAndDrawParticles(gl), this.webglCanvas;
}
applyCanvas2DEffect(canvas) {
const output = this.createOutputCanvas(canvas);
if (!output) return null;
const {canvas: outputCanvas, ctx: ctx} = output, progress = this.currentAnimationRatio;
switch (this.particleConfig.effectType) {
case "explode":
this.applyCanvas2DExplode(ctx, canvas, progress);
break;
case "gravity":
this.applyCanvas2DGravity(ctx, canvas, progress);
break;
case "vortex":
this.applyCanvas2DVortex(ctx, canvas, progress);
break;
default:
ctx.globalAlpha = Math.max(0, 1 - progress), ctx.drawImage(canvas, 0, 0);
}
return outputCanvas;
}
extractParticles(canvas) {
const tempCanvas = ImageProcessUtils_1.ImageProcessUtils.createTempCanvas(canvas.width, canvas.height, 1), tempCtx = tempCanvas.getContext("2d");
if (!tempCtx) return;
tempCtx.drawImage(canvas, 0, 0, tempCanvas.width, tempCanvas.height);
const data = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height).data;
this.particles = [];
const step = Math.max(1, Math.floor(Math.sqrt(tempCanvas.width * tempCanvas.height / (1.5 * this.particleConfig.count))));
for (let y = 0; y < tempCanvas.height; y += step) for (let x = 0; x < tempCanvas.width; x += step) {
const index = 4 * (y * tempCanvas.width + x), r = data[index], g = data[index + 1], b = data[index + 2], a = data[index + 3];
if (a > 5) {
const realX = x / tempCanvas.width * canvas.width, realY = y / tempCanvas.height * canvas.height, particle = {
x: realX,
y: realY,
originX: realX,
originY: realY,
vx: 0,
vy: 0,
r: r / 255,
g: g / 255,
b: b / 255,
a: Math.max(.6, a / 255),
life: 1,
size: this.particleConfig.size * (1 + .5 * Math.random())
};
this.particles.push(particle);
}
}
}
updateParticles(canvas) {
const centerX = canvas.width / 2, centerY = canvas.height / 2, progress = this.currentAnimationRatio, duration = this.getDurationFromParent(), isShortAnimation = duration < 2e3, timeMultiplier = isShortAnimation ? Math.max(1.5, 3e3 / duration) : 1, intensityBoost = isShortAnimation ? Math.min(2, 2e3 / duration) : 1;
this.particles.forEach((particle => {
const dx = particle.x - centerX, dy = particle.y - centerY, distance = Math.sqrt(dx * dx + dy * dy), angle = Math.atan2(dy, dx);
this.applyParticleForces(particle, angle, distance, progress, intensityBoost, canvas),
this.updateParticleProperties(particle, progress, isShortAnimation, timeMultiplier, intensityBoost);
}));
}
applyParticleForces(particle, angle, distance, progress, intensityBoost, canvas) {
const time = this.getAnimationTime();
switch (this.particleConfig.effectType) {
case "explode":
const explodeIntensity = progress * this.particleConfig.strength * intensityBoost * 5;
particle.vx += Math.cos(angle) * explodeIntensity, particle.vy += Math.sin(angle) * explodeIntensity;
break;
case "gravity":
this.applyGravityEffect(particle, progress, intensityBoost, canvas, time);
break;
case "vortex":
this.applyVortexEffect(particle, progress, intensityBoost, canvas, angle, distance);
}
}
applyGravityEffect(particle, progress, intensityBoost, canvas, time) {
const gravityThreshold = (particle.originX + .7 * particle.originY) / (canvas.width + canvas.height) * .8;
if (progress > gravityThreshold) {
const gravityProgress = (progress - gravityThreshold) / (1 - gravityThreshold), gravityForce = this.particleConfig.strength * gravityProgress * gravityProgress * 12 * intensityBoost;
particle.vy += gravityForce;
const turbulence = Math.sin(3 * time + .02 * particle.originX) * Math.cos(2 * time + .015 * particle.originY);
particle.vx += turbulence * this.particleConfig.strength * 2 * intensityBoost;
}
}
applyVortexEffect(particle, progress, intensityBoost, canvas, angle, distance) {
const centerX = canvas.width / 2, centerY = canvas.height / 2, spiralAngle = angle + progress * Math.PI * .8, targetRadius = distance + progress * Math.max(canvas.width, canvas.height) * .7 * 1.8, targetX = centerX + Math.cos(spiralAngle) * targetRadius, targetY = centerY + Math.sin(spiralAngle) * targetRadius, baseForce = progress * this.particleConfig.strength * .08 * intensityBoost;
particle.vx += (targetX - particle.x) * baseForce, particle.vy += (targetY - particle.y) * baseForce;
}
updateParticleProperties(particle, progress, isShortAnimation, timeMultiplier, intensityBoost) {
const dragCoeff = isShortAnimation ? .99 : .98;
if (particle.vx *= dragCoeff, particle.vy *= dragCoeff, particle.x += particle.vx,
particle.y += particle.vy, isShortAnimation) {
const lifeDecayRate = Math.max(.1, .5 / timeMultiplier);
particle.life = Math.max(0, 1 - progress * lifeDecayRate), particle.a = Math.max(.2, particle.life * Math.min(1, 1.2 * particle.a)),
particle.size = Math.max(.7 * this.particleConfig.size, this.particleConfig.size * (.5 + .5 * particle.life));
} else particle.life = Math.max(0, 1 - .2 * progress), particle.a = Math.max(.1, particle.life * Math.min(1, 1.5 * particle.a)),
particle.size = Math.max(.5 * this.particleConfig.size, this.particleConfig.size * (.3 + .7 * particle.life));
}
prepareAndDrawParticles(gl) {
const positions = new Float32Array(2 * this.particles.length), colors = new Float32Array(4 * this.particles.length), sizes = new Float32Array(this.particles.length);
this.particles.forEach(((particle, i) => {
positions[2 * i] = particle.x, positions[2 * i + 1] = particle.y, colors[4 * i] = particle.r,
colors[4 * i + 1] = particle.g, colors[4 * i + 2] = particle.b, colors[4 * i + 3] = Math.max(.1, particle.a),
sizes[i] = Math.max(6, 1.5 * particle.size);
})), this.updateParticleBuffers(gl, positions, colors, sizes), this.setParticleUniforms(gl),
gl.drawArrays(gl.POINTS, 0, this.particles.length), this.cleanupTempBuffers(gl);
}
updateParticleBuffers(gl, positions, colors, sizes) {
this.positionBuffer || (this.positionBuffer = gl.createBuffer()), gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer),
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.DYNAMIC_DRAW);
const positionLocation = gl.getAttribLocation(this.program, "a_position");
gl.enableVertexAttribArray(positionLocation), gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, !1, 0, 0),
this.colorBuffer || (this.colorBuffer = gl.createBuffer()), gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer),
gl.bufferData(gl.ARRAY_BUFFER, colors, gl.DYNAMIC_DRAW);
const colorLocation = gl.getAttribLocation(this.program, "a_color");
gl.enableVertexAttribArray(colorLocation), gl.vertexAttribPointer(colorLocation, 4, gl.FLOAT, !1, 0, 0);
const sizeBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, sizeBuffer), gl.bufferData(gl.ARRAY_BUFFER, sizes, gl.DYNAMIC_DRAW);
const sizeLocation = gl.getAttribLocation(this.program, "a_size");
gl.enableVertexAttribArray(sizeLocation), gl.vertexAttribPointer(sizeLocation, 1, gl.FLOAT, !1, 0, 0),
this._tempSizeBuffer = sizeBuffer;
}
setParticleUniforms(gl) {
const resolutionLocation = gl.getUniformLocation(this.program, "u_resolution"), timeLocation = gl.getUniformLocation(this.program, "u_time"), forceStrengthLocation = gl.getUniformLocation(this.program, "u_forceStrength"), effectTypeLocation = gl.getUniformLocation(this.program, "u_effectType");
gl.uniform2f(resolutionLocation, this.webglCanvas.width, this.webglCanvas.height),
gl.uniform1f(timeLocation, this.getAnimationTime()), gl.uniform1f(forceStrengthLocation, this.particleConfig.strength);
gl.uniform1i(effectTypeLocation, {
explode: 0,
vortex: 1,
gravity: 2
}[this.particleConfig.effectType] || 0);
}
cleanupTempBuffers(gl) {
const tempSizeBuffer = this._tempSizeBuffer;
tempSizeBuffer && (gl.deleteBuffer(tempSizeBuffer), delete this._tempSizeBuffer);
}
applyCanvas2DExplode(ctx, canvas, progress) {
const centerX = canvas.width / 2, centerY = canvas.height / 2;
ctx.save(), ctx.globalAlpha = Math.max(0, 1 - progress), ctx.translate(centerX, centerY);
const scale = 1 + .5 * progress;
ctx.scale(scale, scale), ctx.translate(-centerX, -centerY), ctx.drawImage(canvas, 0, 0),
ctx.restore();
}
applyCanvas2DGravity(ctx, canvas, progress) {
ctx.save(), ctx.globalAlpha = Math.max(0, 1 - progress);
const offsetY = progress * canvas.height * .3;
ctx.drawImage(canvas, 0, offsetY), ctx.restore();
}
applyCanvas2DVortex(ctx, canvas, progress) {
const centerX = canvas.width / 2, centerY = canvas.height / 2;
ctx.save(), ctx.globalAlpha = Math.max(0, 1 - progress), ctx.translate(centerX, centerY),
ctx.rotate(progress * Math.PI * 2), ctx.translate(-centerX, -centerY), ctx.drawImage(canvas, 0, 0),
ctx.restore();
}
}
exports.Particle = Particle;
//# sourceMappingURL=particle.js.map