shader-doodle
Version:
A friendly web-component for writing and rendering shaders.
416 lines (362 loc) • 13.3 kB
JavaScript
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
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 _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function");
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
writable: true,
configurable: true
}
});
if (superClass) _setPrototypeOf(subClass, superClass);
}
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}
function _setPrototypeOf(o, p) {
_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _setPrototypeOf(o, p);
}
function isNativeReflectConstruct() {
if (typeof Reflect === "undefined" || !Reflect.construct) return false;
if (Reflect.construct.sham) return false;
if (typeof Proxy === "function") return true;
try {
Date.prototype.toString.call(Reflect.construct(Date, [], function () {}));
return true;
} catch (e) {
return false;
}
}
function _construct(Parent, args, Class) {
if (isNativeReflectConstruct()) {
_construct = Reflect.construct;
} else {
_construct = function _construct(Parent, args, Class) {
var a = [null];
a.push.apply(a, args);
var Constructor = Function.bind.apply(Parent, a);
var instance = new Constructor();
if (Class) _setPrototypeOf(instance, Class.prototype);
return instance;
};
}
return _construct.apply(null, arguments);
}
function _isNativeFunction(fn) {
return Function.toString.call(fn).indexOf("[native code]") !== -1;
}
function _wrapNativeSuper(Class) {
var _cache = typeof Map === "function" ? new Map() : undefined;
_wrapNativeSuper = function _wrapNativeSuper(Class) {
if (Class === null || !_isNativeFunction(Class)) return Class;
if (typeof Class !== "function") {
throw new TypeError("Super expression must either be null or a function");
}
if (typeof _cache !== "undefined") {
if (_cache.has(Class)) return _cache.get(Class);
_cache.set(Class, Wrapper);
}
function Wrapper() {
return _construct(Class, arguments, _getPrototypeOf(this).constructor);
}
Wrapper.prototype = Object.create(Class.prototype, {
constructor: {
value: Wrapper,
enumerable: false,
writable: true,
configurable: true
}
});
return _setPrototypeOf(Wrapper, Class);
};
return _wrapNativeSuper(Class);
}
function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return self;
}
function _possibleConstructorReturn(self, call) {
if (call && (typeof call === "object" || typeof call === "function")) {
return call;
}
return _assertThisInitialized(self);
}
var DEFAULT_VS = "\nattribute vec2 position;\n\nvoid main() {\n gl_Position = vec4(position, 0.0, 1.0);\n}";
var TEMPLATE = document.createElement('template');
TEMPLATE.innerHTML = "\n<style>\n :host {\n position: relative;\n display: inline-block;\n width: 250px;\n height: 250px;\n }\n :host > canvas {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n width: 100%;\n border-radius: inherit;\n }\n</style>\n";
var SHADERTOY_IO = /\(\s*out\s+vec4\s+(\S+)\s*,\s*in\s+vec2\s+(\S+)\s*\)/;
var ShaderDoodle =
/*#__PURE__*/
function (_HTMLElement) {
_inherits(ShaderDoodle, _HTMLElement);
function ShaderDoodle() {
var _this;
_classCallCheck(this, ShaderDoodle);
_this = _possibleConstructorReturn(this, _getPrototypeOf(ShaderDoodle).call(this));
_this.shadow = _this.attachShadow({
mode: 'open'
});
_this.shadow.appendChild(TEMPLATE.content.cloneNode(true));
return _this;
}
_createClass(ShaderDoodle, [{
key: "connectedCallback",
value: function connectedCallback() {
var _this2 = this;
this.mounted = true;
setTimeout(function () {
if (!_this2.textContent.trim()) return false;
try {
_this2.init();
} catch (e) {
_this2.textContent = '';
console.error(e && e.message || 'Error in shader-doodle.');
}
});
}
}, {
key: "disconnectedCallback",
value: function disconnectedCallback() {
this.mounted = false;
this.canvas.removeEventListener('mousedown', this.mouseDown);
this.canvas.removeEventListener('mousemove', this.mouseMove);
this.canvas.removeEventListener('mouseup', this.mouseUp);
clearAnimationFrame(this.animationFrame);
}
}, {
key: "init",
value: function init() {
var _this3 = this;
this.useST = this.hasAttribute('shadertoy');
var fs = this.textContent;
this.uniforms = {
resolution: {
name: this.useST ? 'iResolution' : 'u_resolution',
type: 'vec2',
value: [0, 0]
},
time: {
name: this.useST ? 'iTime' : 'u_time',
type: 'float',
value: 0
},
delta: {
name: this.useST ? 'iTimeDelta' : 'u_delta',
type: 'float',
value: 0
},
date: {
name: this.useST ? 'iDate' : 'u_date',
type: 'vec4',
value: [0, 0, 0, 0]
},
frame: {
name: this.useST ? 'iFrame' : 'u_frame',
type: 'int',
value: 0
},
mouse: {
name: this.useST ? 'iMouse' : 'u_mouse',
type: this.useST ? 'vec4' : 'vec2',
value: this.useST ? [0, 0, 0, 0] : [0, 0]
}
};
this.canvas = document.createElement('canvas');
this.shadow.appendChild(this.canvas);
var gl = this.gl = this.canvas.getContext('webgl');
this.updateRect(); // format/replace special shadertoy io
if (this.useST) {
var io = fs.match(SHADERTOY_IO);
fs = fs.replace('mainImage', 'main');
fs = fs.replace(SHADERTOY_IO, '()');
fs = (io ? "#define ".concat(io[1], " gl_FragColor\n#define ").concat(io[2], " gl_FragCoord.xy\n") : '') + fs;
}
var uniformString = Object.values(this.uniforms).reduce(function (acc, uniform) {
return acc + "uniform ".concat(uniform.type, " ").concat(uniform.name, ";\n");
}, '');
fs = uniformString + fs;
fs = 'precision highp float;\n' + fs;
gl.clearColor(0, 0, 0, 0);
this.vertexShader = this.makeShader(gl.VERTEX_SHADER, DEFAULT_VS);
this.fragmentShader = this.makeShader(gl.FRAGMENT_SHADER, fs);
this.program = this.makeProgram(this.vertexShader, this.fragmentShader);
this.vertices = new Float32Array([-1, 1, 1, 1, 1, -1, -1, 1, 1, -1, -1, -1]);
this.buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.STATIC_DRAW);
gl.useProgram(this.program);
this.program.position = gl.getAttribLocation(this.program, 'position');
gl.enableVertexAttribArray(this.program.position);
gl.vertexAttribPointer(this.program.position, 2, gl.FLOAT, false, 0, 0); // get all uniform locations from shaders
Object.values(this.uniforms).forEach(function (uniform) {
uniform.location = gl.getUniformLocation(_this3.program, uniform.name);
});
this._bind('mouseDown', 'mouseMove', 'mouseUp', 'render');
this.canvas.addEventListener('mousedown', this.mouseDown);
this.canvas.addEventListener('mousemove', this.mouseMove);
this.canvas.addEventListener('mouseup', this.mouseUp);
this.render();
}
}, {
key: "render",
value: function render(timestamp) {
if (!this || !this.mounted || !this.gl) return;
var gl = this.gl;
this.updateTimeUniforms(timestamp);
this.updateRect();
gl.clear(gl.COLOR_BUFFER_BIT);
Object.values(this.uniforms).forEach(function (_ref) {
var type = _ref.type,
location = _ref.location,
value = _ref.value;
var method = type.match(/vec/) ? "".concat(type[type.length - 1], "fv") : "1".concat(type[0]);
gl["uniform".concat(method)](location, value);
});
gl.drawArrays(gl.TRIANGLES, 0, this.vertices.length / 2);
this.ticking = false;
this.animationFrame = requestAnimationFrame(this.render);
}
}, {
key: "mouseDown",
value: function mouseDown(e) {
if (this.useST) {
this.mousedown = true;
var _this$rect = this.rect,
top = _this$rect.top,
left = _this$rect.left,
height = _this$rect.height;
this.uniforms.mouse.value[2] = e.clientX - Math.floor(left);
this.uniforms.mouse.value[3] = Math.floor(height) - (e.clientY - Math.floor(top));
}
}
}, {
key: "mouseMove",
value: function mouseMove(e) {
if (!this.ticking && (!this.useST || this.mousedown)) {
var _this$rect2 = this.rect,
top = _this$rect2.top,
left = _this$rect2.left,
height = _this$rect2.height;
this.uniforms.mouse.value[0] = e.clientX - Math.floor(left);
this.uniforms.mouse.value[1] = Math.floor(height) - (e.clientY - Math.floor(top));
this.ticking = true;
}
}
}, {
key: "mouseUp",
value: function mouseUp(e) {
if (this.useST) {
this.mousedown = false;
this.uniforms.mouse.value[2] = 0;
this.uniforms.mouse.value[3] = 0;
}
}
}, {
key: "updateTimeUniforms",
value: function updateTimeUniforms(timestamp) {
var delta = this.lastTime ? (timestamp - this.lastTime) / 1000 : 0;
this.lastTime = timestamp;
this.uniforms.time.value += delta;
this.uniforms.delta.value = delta;
this.uniforms.frame.value++;
var d = new Date();
this.uniforms.date.value[0] = d.getFullYear();
this.uniforms.date.value[1] = d.getMonth() + 1;
this.uniforms.date.value[2] = d.getDate();
this.uniforms.date.value[3] = d.getHours() * 60 * 60 + d.getMinutes() * 60 + d.getSeconds() + d.getMilliseconds() * 0.001;
}
}, {
key: "updateRect",
value: function updateRect() {
this.rect = this.canvas.getBoundingClientRect();
var _this$rect3 = this.rect,
width = _this$rect3.width,
height = _this$rect3.height;
var widthChanged = this.canvas.width !== width;
var heightChanged = this.canvas.height !== height;
if (widthChanged) {
this.canvas.width = this.uniforms.resolution.value[0] = width;
}
if (heightChanged) {
this.canvas.height = this.uniforms.resolution.value[1] = height;
}
if (widthChanged || heightChanged) {
this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
}
}
}, {
key: "makeShader",
value: function makeShader(type, string) {
var gl = this.gl;
var shader = gl.createShader(type);
gl.shaderSource(shader, string);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
var compilationLog = gl.getShaderInfoLog(shader);
gl.deleteShader(shader);
console.warn(compilationLog, '\nin shader:\n', string);
}
return shader;
}
}, {
key: "makeProgram",
value: function makeProgram() {
var gl = this.gl;
var program = gl.createProgram();
for (var _len = arguments.length, shaders = new Array(_len), _key = 0; _key < _len; _key++) {
shaders[_key] = arguments[_key];
}
shaders.forEach(function (shader) {
gl.attachShader(program, shader);
});
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
var linkLog = gl.getProgramInfoLog(this.program);
console.warn(linkLog);
}
return program;
}
}, {
key: "_bind",
value: function _bind() {
var _this4 = this;
for (var _len2 = arguments.length, methods = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
methods[_key2] = arguments[_key2];
}
methods.forEach(function (method) {
return _this4[method] = _this4[method].bind(_this4);
});
}
}]);
return ShaderDoodle;
}(_wrapNativeSuper(HTMLElement));
customElements.define('shader-doodle', ShaderDoodle);