react-fluid-animation
Version:
Fluid media animation for React powered by WebGL.
473 lines (385 loc) • 17.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = exports.defaultConfig = void 0;
var _glProgram = _interopRequireDefault(require("./gl-program"));
var _getGlContext = _interopRequireDefault(require("./get-gl-context"));
var _shaders = _interopRequireDefault(require("./shaders"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var defaultConfig = {
textureDownsample: 1,
densityDissipation: 0.98,
velocityDissipation: 0.99,
pressureDissipation: 0.8,
pressureIterations: 25,
curl: 30,
splatRadius: 0.005
};
exports.defaultConfig = defaultConfig;
var Pointer = function Pointer() {
_classCallCheck(this, Pointer);
this.id = -1;
this.x = 0;
this.y = 0;
this.dx = 0;
this.dy = 0;
this.down = false;
this.moved = false;
this.color = [30, 0, 300];
};
var FluidAnimation =
/*#__PURE__*/
function () {
function FluidAnimation(opts) {
var _this = this;
_classCallCheck(this, FluidAnimation);
_defineProperty(this, "onMouseMove", function (e) {
_this._pointers[0].moved = _this._pointers[0].down;
_this._pointers[0].dx = (e.offsetX - _this._pointers[0].x) * 10.0;
_this._pointers[0].dy = (e.offsetY - _this._pointers[0].y) * 10.0;
_this._pointers[0].x = e.offsetX;
_this._pointers[0].y = e.offsetY;
});
_defineProperty(this, "onMouseDown", function (e) {
_this._pointers[0].down = true;
_this._pointers[0].color = [Math.random() + 0.2, Math.random() + 0.2, Math.random() + 0.2];
});
_defineProperty(this, "onMouseUp", function (e) {
_this._pointers[0].down = false;
});
_defineProperty(this, "onTouchStart", function (e) {
for (var i = 0; i < e.touches.length; ++i) {
_this._pointers[i].down = true;
_this._pointers[i].color = [Math.random() + 0.2, Math.random() + 0.2, Math.random() + 0.2];
}
});
_defineProperty(this, "onTouchMove", function (e) {
for (var i = 0; i < e.touches.length; ++i) {
var touch = e.touches[i];
_this._pointers[i].moved = _this._pointers[i].down;
_this._pointers[i].dx = (touch.clientX - _this._pointers[i].x) * 10.0;
_this._pointers[i].dy = (touch.clientY - _this._pointers[i].y) * 10.0;
_this._pointers[i].x = touch.clientX;
_this._pointers[i].y = touch.clientY;
}
});
_defineProperty(this, "onTouchEnd", function (e) {
for (var i = 0; i < e.touches.length; ++i) {
_this._pointers[i].down = false;
}
});
_defineProperty(this, "_blit", function (destination) {
var gl = _this._gl;
gl.bindFramebuffer(gl.FRAMEBUFFER, destination);
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
});
var canvas = opts.canvas,
_opts$config = opts.config,
config = _opts$config === void 0 ? _objectSpread({}, defaultConfig, opts.config) : _opts$config;
this._canvas = canvas;
this._config = config;
this._pointers = [new Pointer()];
this._splatStack = [];
var _getGLContext = (0, _getGlContext.default)(canvas),
_gl = _getGLContext.gl,
ext = _getGLContext.ext;
this._gl = _gl;
this._ext = ext;
this._initPrograms();
this._initBlit();
this.resize();
this._time = Date.now();
this._timer = 0;
}
_createClass(FluidAnimation, [{
key: "addSplat",
value: function addSplat(splat) {
this._splatStack.push([splat]);
}
}, {
key: "addSplats",
value: function addSplats(splats) {
this._splatStack.push(Array.isArray(splats) ? splats : [splats]);
}
}, {
key: "addRandomSplats",
value: function addRandomSplats(count) {
var splats = [];
for (var i = 0; i < count; ++i) {
splats.push(this._getRandomSplat());
}
this.addSplats(splats);
}
}, {
key: "resize",
value: function resize() {
var _this$_canvas = this._canvas,
width = _this$_canvas.width,
height = _this$_canvas.height;
if (this._width !== width || this._height !== height) {
this._width = width;
this._height = height;
this._initFramebuffers();
}
}
}, {
key: "_initPrograms",
value: function _initPrograms() {
var gl = this._gl;
var ext = this._ext;
this._programs = {};
this._programs.clear = new _glProgram.default(gl, _shaders.default.vert, _shaders.default.clear);
this._programs.display = new _glProgram.default(gl, _shaders.default.vert, _shaders.default.display);
this._programs.splat = new _glProgram.default(gl, _shaders.default.vert, _shaders.default.splat);
this._programs.advection = new _glProgram.default(gl, _shaders.default.vert, ext.supportLinearFiltering ? _shaders.default.advection : _shaders.default.advectionManualFiltering);
this._programs.divergence = new _glProgram.default(gl, _shaders.default.vert, _shaders.default.divergence);
this._programs.curl = new _glProgram.default(gl, _shaders.default.vert, _shaders.default.curl);
this._programs.vorticity = new _glProgram.default(gl, _shaders.default.vert, _shaders.default.vorticity);
this._programs.pressure = new _glProgram.default(gl, _shaders.default.vert, _shaders.default.pressure);
this._programs.gradientSubtract = new _glProgram.default(gl, _shaders.default.vert, _shaders.default.gradientSubtract);
}
}, {
key: "_initFramebuffers",
value: function _initFramebuffers() {
var gl = this._gl;
var ext = this._ext;
function createFBO(texId, w, h, internalFormat, format, type, param) {
gl.activeTexture(gl.TEXTURE0 + texId);
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, param);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, param);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, w, h, 0, format, type, null);
var fbo = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
gl.viewport(0, 0, w, h);
gl.clear(gl.COLOR_BUFFER_BIT);
return [texture, fbo, texId];
}
function createDoubleFBO(texId, w, h, internalFormat, format, type, param) {
var fbo1 = createFBO(texId, w, h, internalFormat, format, type, param);
var fbo2 = createFBO(texId + 1, w, h, internalFormat, format, type, param);
return {
get read() {
return fbo1;
},
get write() {
return fbo2;
},
swap: function swap() {
var temp = fbo1;
fbo1 = fbo2;
fbo2 = temp;
}
};
}
this._textureWidth = gl.drawingBufferWidth >> this._config.textureDownsample;
this._textureHeight = gl.drawingBufferHeight >> this._config.textureDownsample;
var texType = ext.halfFloatTexType;
var rgba = ext.formatRGBA;
var rg = ext.formatRG;
var r = ext.formatR;
this._density = createDoubleFBO(2, this._textureWidth, this._textureHeight, rgba.internalFormat, rgba.format, texType, ext.supportLinearFiltering ? gl.LINEAR : gl.NEAREST);
this._velocity = createDoubleFBO(0, this._textureWidth, this._textureHeight, rg.internalFormat, rg.format, texType, ext.supportLinearFiltering ? gl.LINEAR : gl.NEAREST);
this._divergence = createFBO(4, this._textureWidth, this._textureHeight, r.internalFormat, r.format, texType, gl.NEAREST);
this._curl = createFBO(5, this._textureWidth, this._textureHeight, r.internalFormat, r.format, texType, gl.NEAREST);
this._pressure = createDoubleFBO(6, this._textureWidth, this._textureHeight, r.internalFormat, r.format, texType, gl.NEAREST);
}
}, {
key: "_initBlit",
value: function _initBlit() {
var gl = this._gl;
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, -1, 1, 1, 1, 1, -1]), gl.STATIC_DRAW);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 0, 2, 3]), gl.STATIC_DRAW);
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(0);
}
}, {
key: "_splat",
value: function _splat(x, y, dx, dy, color) {
var gl = this._gl;
this._programs.splat.bind();
gl.uniform1i(this._programs.splat.uniforms.uTarget, this._velocity.read[2]);
gl.uniform1f(this._programs.splat.uniforms.aspectRatio, this._canvas.width / this._canvas.height);
gl.uniform2f(this._programs.splat.uniforms.point, x / this._canvas.width, 1.0 - y / this._canvas.height);
gl.uniform3f(this._programs.splat.uniforms.color, dx, -dy, 1.0);
gl.uniform1f(this._programs.splat.uniforms.radius, this._config.splatRadius);
this._blit(this._velocity.write[1]);
this._velocity.swap();
gl.uniform1i(this._programs.splat.uniforms.uTarget, this._density.read[2]);
gl.uniform3f(this._programs.splat.uniforms.color, color[0] * 0.3, color[1] * 0.3, color[2] * 0.3);
this._blit(this._density.write[1]);
this._density.swap();
}
}, {
key: "_addSplat",
value: function _addSplat(splat) {
var x = splat.x,
y = splat.y,
dx = splat.dx,
dy = splat.dy,
color = splat.color;
if (x === undefined) return;
if (y === undefined) return;
if (dx === undefined) return;
if (dy === undefined) return;
if (color === undefined) return;
this._splat(x, y, dx, dy, color);
}
}, {
key: "_addSplats",
value: function _addSplats(splats) {
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = splats[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var splat = _step.value;
this._addSplat(splat);
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return != null) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
}
}, {
key: "_getRandomSplat",
value: function _getRandomSplat() {
var color = [Math.random() * 10, Math.random() * 10, Math.random() * 10];
var x = this._canvas.width * Math.random();
var y = this._canvas.height * Math.random();
var dx = 1000 * (Math.random() - 0.5);
var dy = 1000 * (Math.random() - 0.5);
return {
x: x,
y: y,
dx: dx,
dy: dy,
color: color
};
}
}, {
key: "update",
value: function update() {
var gl = this._gl;
var dt = Math.min((Date.now() - this._time) / 1000, 0.016);
this._time = Date.now();
this._timer += 0.0001;
var w = this._textureWidth;
var h = this._textureHeight;
var iW = 1.0 / w;
var iH = 1.0 / h;
gl.viewport(0, 0, w, h);
if (this._splatStack.length > 0) {
this._addSplats(this._splatStack.pop());
}
this._programs.advection.bind();
gl.uniform2f(this._programs.advection.uniforms.texelSize, iW, iH);
gl.uniform1i(this._programs.advection.uniforms.uVelocity, this._velocity.read[2]);
gl.uniform1i(this._programs.advection.uniforms.uSource, this._velocity.read[2]);
gl.uniform1f(this._programs.advection.uniforms.dt, dt);
gl.uniform1f(this._programs.advection.uniforms.dissipation, this._config.velocityDissipation);
this._blit(this._velocity.write[1]);
this._velocity.swap();
gl.uniform1i(this._programs.advection.uniforms.uVelocity, this._velocity.read[2]);
gl.uniform1i(this._programs.advection.uniforms.uSource, this._density.read[2]);
gl.uniform1f(this._programs.advection.uniforms.dissipation, this._config.densityDissipation);
this._blit(this._density.write[1]);
this._density.swap();
for (var i = 0; i < this._pointers.length; i++) {
var pointer = this._pointers[i];
if (pointer.moved) {
this._splat(pointer.x, pointer.y, pointer.dx, pointer.dy, pointer.color);
pointer.moved = false;
}
}
this._programs.curl.bind();
gl.uniform2f(this._programs.curl.uniforms.texelSize, iW, iH);
gl.uniform1i(this._programs.curl.uniforms.uVelocity, this._velocity.read[2]);
this._blit(this._curl[1]);
this._programs.vorticity.bind();
gl.uniform2f(this._programs.vorticity.uniforms.texelSize, iW, iH);
gl.uniform1i(this._programs.vorticity.uniforms.uVelocity, this._velocity.read[2]);
gl.uniform1i(this._programs.vorticity.uniforms.uCurl, this._curl[2]);
gl.uniform1f(this._programs.vorticity.uniforms.curl, this._config.curl);
gl.uniform1f(this._programs.vorticity.uniforms.dt, dt);
this._blit(this._velocity.write[1]);
this._velocity.swap();
this._programs.divergence.bind();
gl.uniform2f(this._programs.divergence.uniforms.texelSize, iW, iH);
gl.uniform1i(this._programs.divergence.uniforms.uVelocity, this._velocity.read[2]);
this._blit(this._divergence[1]);
this._programs.clear.bind();
var pressureTexId = this._pressure.read[2];
gl.activeTexture(gl.TEXTURE0 + pressureTexId);
gl.bindTexture(gl.TEXTURE_2D, this._pressure.read[0]);
gl.uniform1i(this._programs.clear.uniforms.uTexture, pressureTexId);
gl.uniform1f(this._programs.clear.uniforms.value, this._config.pressureDissipation);
this._blit(this._pressure.write[1]);
this._pressure.swap();
this._programs.pressure.bind();
gl.uniform2f(this._programs.pressure.uniforms.texelSize, iW, iH);
gl.uniform1i(this._programs.pressure.uniforms.uDivergence, this._divergence[2]);
pressureTexId = this._pressure.read[2];
gl.uniform1i(this._programs.pressure.uniforms.uPressure, pressureTexId);
gl.activeTexture(gl.TEXTURE0 + pressureTexId);
for (var _i = 0; _i < this._config.pressureIterations; _i++) {
gl.bindTexture(gl.TEXTURE_2D, this._pressure.read[0]);
this._blit(this._pressure.write[1]);
this._pressure.swap();
}
this._programs.gradientSubtract.bind();
gl.uniform2f(this._programs.gradientSubtract.uniforms.texelSize, iW, iH);
gl.uniform1i(this._programs.gradientSubtract.uniforms.uPressure, this._pressure.read[2]);
gl.uniform1i(this._programs.gradientSubtract.uniforms.uVelocity, this._velocity.read[2]);
this._blit(this._velocity.write[1]);
this._velocity.swap();
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
this._programs.display.bind();
gl.uniform1i(this._programs.display.uniforms.uTexture, this._density.read[2]);
this._blit(null);
}
}, {
key: "config",
get: function get() {
return this._config;
},
set: function set(config) {
this._config = config;
}
}, {
key: "width",
get: function get() {
return this._canvas.width;
}
}, {
key: "height",
get: function get() {
return this._canvas.height;
}
}]);
return FluidAnimation;
}();
exports.default = FluidAnimation;
//# sourceMappingURL=fluid-animation.js.map