UNPKG

scratch-render

Version:
1,167 lines (1,081 loc) • 280 kB
/******/ (() => { // webpackBootstrap /******/ var __webpack_modules__ = ({ /***/ "./src/BitmapSkin.js" /*!***************************!*\ !*** ./src/BitmapSkin.js ***! \***************************/ (module, __unused_webpack_exports, __webpack_require__) { function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } } function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); } function _possibleConstructorReturn(t, e) { if (e && ("object" == _typeof(e) || "function" == typeof e)) return e; if (void 0 !== e) throw new TypeError("Derived constructors may only return object or undefined"); return _assertThisInitialized(t); } function _assertThisInitialized(e) { if (void 0 === e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); return e; } function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); } function _superPropGet(t, o, e, r) { var p = _get(_getPrototypeOf(1 & r ? t.prototype : t), o, e); return 2 & r && "function" == typeof p ? function (t) { return p.apply(e, t); } : p; } function _get() { return _get = "undefined" != typeof Reflect && Reflect.get ? Reflect.get.bind() : function (e, t, r) { var p = _superPropBase(e, t); if (p) { var n = Object.getOwnPropertyDescriptor(p, t); return n.get ? n.get.call(arguments.length < 3 ? e : r) : n.value; } }, _get.apply(null, arguments); } function _superPropBase(t, o) { for (; !{}.hasOwnProperty.call(t, o) && null !== (t = _getPrototypeOf(t));); return t; } function _getPrototypeOf(t) { return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) { return t.__proto__ || Object.getPrototypeOf(t); }, _getPrototypeOf(t); } function _inherits(t, e) { if ("function" != typeof e && null !== e) throw new TypeError("Super expression must either be null or a function"); t.prototype = Object.create(e && e.prototype, { constructor: { value: t, writable: !0, configurable: !0 } }), Object.defineProperty(t, "prototype", { writable: !1 }), e && _setPrototypeOf(t, e); } function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); } var twgl = __webpack_require__(/*! twgl.js */ "twgl.js"); var Skin = __webpack_require__(/*! ./Skin */ "./src/Skin.js"); var BitmapSkin = /*#__PURE__*/function (_Skin) { /** * Create a new Bitmap Skin. * @extends Skin * @param {!int} id - The ID for this Skin. * @param {!RenderWebGL} renderer - The renderer which will use this skin. */ function BitmapSkin(id, renderer) { var _this; _classCallCheck(this, BitmapSkin); _this = _callSuper(this, BitmapSkin, [id]); /** @type {!int} */ _this._costumeResolution = 1; /** @type {!RenderWebGL} */ _this._renderer = renderer; /** @type {Array<int>} */ _this._textureSize = [0, 0]; return _this; } /** * Dispose of this object. Do not use it after calling this method. */ _inherits(BitmapSkin, _Skin); return _createClass(BitmapSkin, [{ key: "dispose", value: function dispose() { if (this._texture) { this._renderer.gl.deleteTexture(this._texture); this._texture = null; } _superPropGet(BitmapSkin, "dispose", this, 3)([]); } /** * @return {Array<number>} the "native" size, in texels, of this skin. */ }, { key: "size", get: function get() { return [this._textureSize[0] / this._costumeResolution, this._textureSize[1] / this._costumeResolution]; } /** * @param {Array<number>} scale - The scaling factors to be used. * @return {WebGLTexture} The GL texture representation of this skin when drawing at the given scale. */ // eslint-disable-next-line no-unused-vars }, { key: "getTexture", value: function getTexture(scale) { return this._texture || _superPropGet(BitmapSkin, "getTexture", this, 3)([]); } /** * Set the contents of this skin to a snapshot of the provided bitmap data. * @param {ImageData|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} bitmapData - new contents for this skin. * @param {int} [costumeResolution=1] - The resolution to use for this bitmap. * @param {Array<number>} [rotationCenter] - Optional rotation center for the bitmap. If not supplied, it will be * calculated from the bounding box * @fires Skin.event:WasAltered */ }, { key: "setBitmap", value: function setBitmap(bitmapData, costumeResolution, rotationCenter) { if (!bitmapData.width || !bitmapData.height) { _superPropGet(BitmapSkin, "setEmptyImageData", this, 3)([]); return; } var gl = this._renderer.gl; // Preferably bitmapData is ImageData. ImageData speeds up updating // Silhouette and is better handled by more browsers in regards to // memory. var textureData = bitmapData; if (bitmapData instanceof HTMLCanvasElement) { // Given a HTMLCanvasElement get the image data to pass to webgl and // Silhouette. var context = bitmapData.getContext('2d'); textureData = context.getImageData(0, 0, bitmapData.width, bitmapData.height); } if (this._texture === null) { var textureOptions = { auto: false, wrap: gl.CLAMP_TO_EDGE }; this._texture = twgl.createTexture(gl, textureOptions); } this._setTexture(textureData); // Do these last in case any of the above throws an exception this._costumeResolution = costumeResolution || 2; this._textureSize = BitmapSkin._getBitmapSize(bitmapData); if (typeof rotationCenter === 'undefined') rotationCenter = this.calculateRotationCenter(); this._rotationCenter[0] = rotationCenter[0]; this._rotationCenter[1] = rotationCenter[1]; this.emit(Skin.Events.WasAltered); } /** * @param {ImageData|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} bitmapData - bitmap data to inspect. * @returns {Array<int>} the width and height of the bitmap data, in pixels. * @private */ }], [{ key: "_getBitmapSize", value: function _getBitmapSize(bitmapData) { if (bitmapData instanceof HTMLImageElement) { return [bitmapData.naturalWidth || bitmapData.width, bitmapData.naturalHeight || bitmapData.height]; } if (bitmapData instanceof HTMLVideoElement) { return [bitmapData.videoWidth || bitmapData.width, bitmapData.videoHeight || bitmapData.height]; } // ImageData or HTMLCanvasElement return [bitmapData.width, bitmapData.height]; } }]); }(Skin); module.exports = BitmapSkin; /***/ }, /***/ "./src/Drawable.js" /*!*************************!*\ !*** ./src/Drawable.js ***! \*************************/ (module, __unused_webpack_exports, __webpack_require__) { function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } } function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } var twgl = __webpack_require__(/*! twgl.js */ "twgl.js"); var Rectangle = __webpack_require__(/*! ./Rectangle */ "./src/Rectangle.js"); var RenderConstants = __webpack_require__(/*! ./RenderConstants */ "./src/RenderConstants.js"); var ShaderManager = __webpack_require__(/*! ./ShaderManager */ "./src/ShaderManager.js"); var Skin = __webpack_require__(/*! ./Skin */ "./src/Skin.js"); var EffectTransform = __webpack_require__(/*! ./EffectTransform */ "./src/EffectTransform.js"); var log = __webpack_require__(/*! ./util/log */ "./src/util/log.js"); /** * An internal workspace for calculating texture locations from world vectors * this is REUSED for memory conservation reasons * @type {twgl.v3} */ var __isTouchingPosition = twgl.v3.create(); var FLOATING_POINT_ERROR_ALLOWANCE = 1e-6; /** * Convert a scratch space location into a texture space float. Uses the * internal __isTouchingPosition as a return value, so this should be copied * if you ever need to get two local positions and store both. Requires that * the drawable inverseMatrix is up to date. * * @param {Drawable} drawable The drawable to get the inverse matrix and uniforms from * @param {twgl.v3} vec [x,y] scratch space vector * @return {twgl.v3} [x,y] texture space float vector - transformed by effects and matrix */ var getLocalPosition = function getLocalPosition(drawable, vec) { // Transfrom from world coordinates to Drawable coordinates. var localPosition = __isTouchingPosition; var v0 = vec[0]; var v1 = vec[1]; var m = drawable._inverseMatrix; // var v2 = v[2]; var d = v0 * m[3] + v1 * m[7] + m[15]; // The RenderWebGL quad flips the texture's X axis. So rendered bottom // left is 1, 0 and the top right is 0, 1. Flip the X axis so // localPosition matches that transformation. localPosition[0] = 0.5 - (v0 * m[0] + v1 * m[4] + m[12]) / d; localPosition[1] = (v0 * m[1] + v1 * m[5] + m[13]) / d + 0.5; // Fix floating point issues near 0. Filed https://github.com/LLK/scratch-render/issues/688 that // they're happening in the first place. // TODO: Check if this can be removed after render pull 479 is merged if (Math.abs(localPosition[0]) < FLOATING_POINT_ERROR_ALLOWANCE) localPosition[0] = 0; if (Math.abs(localPosition[1]) < FLOATING_POINT_ERROR_ALLOWANCE) localPosition[1] = 0; // Apply texture effect transform if the localPosition is within the drawable's space, // and any effects are currently active. if (drawable.enabledEffects !== 0 && localPosition[0] >= 0 && localPosition[0] < 1 && localPosition[1] >= 0 && localPosition[1] < 1) { EffectTransform.transformPoint(drawable, localPosition, localPosition); } return localPosition; }; var Drawable = /*#__PURE__*/function () { /** * An object which can be drawn by the renderer. * @todo double-buffer all rendering state (position, skin, effects, etc.) * @param {!int} id - This Drawable's unique ID. * @constructor */ function Drawable(id) { _classCallCheck(this, Drawable); /** @type {!int} */ this._id = id; /** * The uniforms to be used by the vertex and pixel shaders. * Some of these are used by other parts of the renderer as well. * @type {Object.<string,*>} * @private */ this._uniforms = { /** * The model matrix, to concat with projection at draw time. * @type {module:twgl/m4.Mat4} */ u_modelMatrix: twgl.m4.identity(), /** * The color to use in the silhouette draw mode. * @type {Array<number>} */ u_silhouetteColor: Drawable.color4fFromID(this._id) }; // Effect values are uniforms too var numEffects = ShaderManager.EFFECTS.length; for (var index = 0; index < numEffects; ++index) { var effectName = ShaderManager.EFFECTS[index]; var effectInfo = ShaderManager.EFFECT_INFO[effectName]; var converter = effectInfo.converter; this._uniforms[effectInfo.uniformName] = converter(0); } this._position = twgl.v3.create(0, 0); this._scale = twgl.v3.create(100, 100); this._direction = 90; this._transformDirty = true; this._rotationMatrix = twgl.m4.identity(); this._rotationTransformDirty = true; this._rotationAdjusted = twgl.v3.create(); this._rotationCenterDirty = true; this._skinScale = twgl.v3.create(0, 0, 0); this._skinScaleDirty = true; this._inverseMatrix = twgl.m4.identity(); this._inverseTransformDirty = true; this._visible = true; /** A bitmask identifying which effects are currently in use. * @readonly * @type {int} */ this.enabledEffects = 0; /** @todo move convex hull functionality, maybe bounds functionality overall, to Skin classes */ this._convexHullPoints = null; this._convexHullDirty = true; // The precise bounding box will be from the transformed convex hull points, // so initialize the array of transformed hull points in setConvexHullPoints. // Initializing it once per convex hull recalculation avoids unnecessary creation of twgl.v3 objects. this._transformedHullPoints = null; this._transformedHullDirty = true; this._skinWasAltered = this._skinWasAltered.bind(this); this.isTouching = this._isTouchingNever; } /** * Dispose of this Drawable. Do not use it after calling this method. */ return _createClass(Drawable, [{ key: "dispose", value: function dispose() { // Use the setter: disconnect events this.skin = null; } /** * Mark this Drawable's transform as dirty. * It will be recalculated next time it's needed. */ }, { key: "setTransformDirty", value: function setTransformDirty() { this._transformDirty = true; this._inverseTransformDirty = true; this._transformedHullDirty = true; } /** * @returns {number} The ID for this Drawable. */ }, { key: "id", get: function get() { return this._id; } /** * @returns {Skin} the current skin for this Drawable. */ }, { key: "skin", get: function get() { return this._skin; } /** * @param {Skin} newSkin - A new Skin for this Drawable. */, set: function set(newSkin) { if (this._skin !== newSkin) { if (this._skin) { this._skin.removeListener(Skin.Events.WasAltered, this._skinWasAltered); } this._skin = newSkin; if (this._skin) { this._skin.addListener(Skin.Events.WasAltered, this._skinWasAltered); } this._skinWasAltered(); } } /** * @returns {Array<number>} the current scaling percentages applied to this Drawable. [100,100] is normal size. */ }, { key: "scale", get: function get() { return [this._scale[0], this._scale[1]]; } /** * @returns {object.<string, *>} the shader uniforms to be used when rendering this Drawable. */ }, { key: "getUniforms", value: function getUniforms() { if (this._transformDirty) { this._calculateTransform(); } return this._uniforms; } /** * @returns {boolean} whether this Drawable is visible. */ }, { key: "getVisible", value: function getVisible() { return this._visible; } /** * Update the position if it is different. Marks the transform as dirty. * @param {Array.<number>} position A new position. */ }, { key: "updatePosition", value: function updatePosition(position) { if (this._position[0] !== position[0] || this._position[1] !== position[1]) { this._position[0] = Math.round(position[0]); this._position[1] = Math.round(position[1]); this.setTransformDirty(); } } /** * Update the direction if it is different. Marks the transform as dirty. * @param {number} direction A new direction. */ }, { key: "updateDirection", value: function updateDirection(direction) { if (this._direction !== direction) { this._direction = direction; this._rotationTransformDirty = true; this.setTransformDirty(); } } /** * Update the scale if it is different. Marks the transform as dirty. * @param {Array.<number>} scale A new scale. */ }, { key: "updateScale", value: function updateScale(scale) { if (this._scale[0] !== scale[0] || this._scale[1] !== scale[1]) { this._scale[0] = scale[0]; this._scale[1] = scale[1]; this._rotationCenterDirty = true; this._skinScaleDirty = true; this.setTransformDirty(); } } /** * Update visibility if it is different. Marks the convex hull as dirty. * @param {boolean} visible A new visibility state. */ }, { key: "updateVisible", value: function updateVisible(visible) { if (this._visible !== visible) { this._visible = visible; this.setConvexHullDirty(); } } /** * Update an effect. Marks the convex hull as dirty if the effect changes shape. * @param {string} effectName The name of the effect. * @param {number} rawValue A new effect value. */ }, { key: "updateEffect", value: function updateEffect(effectName, rawValue) { var effectInfo = ShaderManager.EFFECT_INFO[effectName]; if (rawValue) { this.enabledEffects |= effectInfo.mask; } else { this.enabledEffects &= ~effectInfo.mask; } var converter = effectInfo.converter; this._uniforms[effectInfo.uniformName] = converter(rawValue); if (effectInfo.shapeChanges) { this.setConvexHullDirty(); } } /** * Update the position, direction, scale, or effect properties of this Drawable. * @deprecated Use specific update* methods instead. * @param {object.<string,*>} properties The new property values to set. */ }, { key: "updateProperties", value: function updateProperties(properties) { if ('position' in properties) { this.updatePosition(properties.position); } if ('direction' in properties) { this.updateDirection(properties.direction); } if ('scale' in properties) { this.updateScale(properties.scale); } if ('visible' in properties) { this.updateVisible(properties.visible); } var numEffects = ShaderManager.EFFECTS.length; for (var index = 0; index < numEffects; ++index) { var effectName = ShaderManager.EFFECTS[index]; if (effectName in properties) { this.updateEffect(effectName, properties[effectName]); } } } /** * Calculate the transform to use when rendering this Drawable. * @private */ }, { key: "_calculateTransform", value: function _calculateTransform() { if (this._rotationTransformDirty) { var rotation = (270 - this._direction) * Math.PI / 180; // Calling rotationZ sets the destination matrix to a rotation // around the Z axis setting matrix components 0, 1, 4 and 5 with // cosine and sine values of the rotation. // twgl.m4.rotationZ(rotation, this._rotationMatrix); // twgl assumes the last value set to the matrix was anything. // Drawable knows, it was another rotationZ matrix, so we can skip // assigning the values that will never change. var c = Math.cos(rotation); var s = Math.sin(rotation); this._rotationMatrix[0] = c; this._rotationMatrix[1] = s; // this._rotationMatrix[2] = 0; // this._rotationMatrix[3] = 0; this._rotationMatrix[4] = -s; this._rotationMatrix[5] = c; // this._rotationMatrix[6] = 0; // this._rotationMatrix[7] = 0; // this._rotationMatrix[8] = 0; // this._rotationMatrix[9] = 0; // this._rotationMatrix[10] = 1; // this._rotationMatrix[11] = 0; // this._rotationMatrix[12] = 0; // this._rotationMatrix[13] = 0; // this._rotationMatrix[14] = 0; // this._rotationMatrix[15] = 1; this._rotationTransformDirty = false; } // Adjust rotation center relative to the skin. if (this._rotationCenterDirty && this.skin !== null) { // twgl version of the following in function work. // let rotationAdjusted = twgl.v3.subtract( // this.skin.rotationCenter, // twgl.v3.divScalar(this.skin.size, 2, this._rotationAdjusted), // this._rotationAdjusted // ); // rotationAdjusted = twgl.v3.multiply( // rotationAdjusted, this._scale, rotationAdjusted // ); // rotationAdjusted = twgl.v3.divScalar( // rotationAdjusted, 100, rotationAdjusted // ); // rotationAdjusted[1] *= -1; // Y flipped to Scratch coordinate. // rotationAdjusted[2] = 0; // Z coordinate is 0. // Locally assign rotationCenter and skinSize to keep from having // the Skin getter properties called twice while locally assigning // their components for readability. var rotationCenter = this.skin.rotationCenter; var skinSize = this.skin.size; var center0 = rotationCenter[0]; var center1 = rotationCenter[1]; var skinSize0 = skinSize[0]; var skinSize1 = skinSize[1]; var _scale = this._scale[0]; var _scale2 = this._scale[1]; var rotationAdjusted = this._rotationAdjusted; rotationAdjusted[0] = (center0 - skinSize0 / 2) * _scale / 100; rotationAdjusted[1] = (center1 - skinSize1 / 2) * _scale2 / 100 * -1; // rotationAdjusted[2] = 0; this._rotationCenterDirty = false; } if (this._skinScaleDirty && this.skin !== null) { // twgl version of the following in function work. // const scaledSize = twgl.v3.divScalar( // twgl.v3.multiply(this.skin.size, this._scale), // 100 // ); // // was NaN because the vectors have only 2 components. // scaledSize[2] = 0; // Locally assign skinSize to keep from having the Skin getter // properties called twice. var _skinSize = this.skin.size; var scaledSize = this._skinScale; scaledSize[0] = _skinSize[0] * this._scale[0] / 100; scaledSize[1] = _skinSize[1] * this._scale[1] / 100; // scaledSize[2] = 0; this._skinScaleDirty = false; } var modelMatrix = this._uniforms.u_modelMatrix; // twgl version of the following in function work. // twgl.m4.identity(modelMatrix); // twgl.m4.translate(modelMatrix, this._position, modelMatrix); // twgl.m4.multiply(modelMatrix, this._rotationMatrix, modelMatrix); // twgl.m4.translate(modelMatrix, this._rotationAdjusted, modelMatrix); // twgl.m4.scale(modelMatrix, scaledSize, modelMatrix); // Drawable configures a 3D matrix for drawing in WebGL, but most values // will never be set because the inputs are on the X and Y position axis // and the Z rotation axis. Drawable can bring the work inside // _calculateTransform and greatly reduce the ammount of math and array // assignments needed. var scale0 = this._skinScale[0]; var scale1 = this._skinScale[1]; var rotation00 = this._rotationMatrix[0]; var rotation01 = this._rotationMatrix[1]; var rotation10 = this._rotationMatrix[4]; var rotation11 = this._rotationMatrix[5]; var adjusted0 = this._rotationAdjusted[0]; var adjusted1 = this._rotationAdjusted[1]; var position0 = this._position[0]; var position1 = this._position[1]; // Commented assignments show what the values are when the matrix was // instantiated. Those values will never change so they do not need to // be reassigned. modelMatrix[0] = scale0 * rotation00; modelMatrix[1] = scale0 * rotation01; // modelMatrix[2] = 0; // modelMatrix[3] = 0; modelMatrix[4] = scale1 * rotation10; modelMatrix[5] = scale1 * rotation11; // modelMatrix[6] = 0; // modelMatrix[7] = 0; // modelMatrix[8] = 0; // modelMatrix[9] = 0; // modelMatrix[10] = 1; // modelMatrix[11] = 0; modelMatrix[12] = rotation00 * adjusted0 + rotation10 * adjusted1 + position0; modelMatrix[13] = rotation01 * adjusted0 + rotation11 * adjusted1 + position1; // modelMatrix[14] = 0; // modelMatrix[15] = 1; this._transformDirty = false; } /** * Whether the Drawable needs convex hull points provided by the renderer. * @return {boolean} True when no convex hull known, or it's dirty. */ }, { key: "needsConvexHullPoints", value: function needsConvexHullPoints() { return !this._convexHullPoints || this._convexHullDirty || this._convexHullPoints.length === 0; } /** * Set the convex hull to be dirty. * Do this whenever the Drawable's shape has possibly changed. */ }, { key: "setConvexHullDirty", value: function setConvexHullDirty() { this._convexHullDirty = true; } /** * Set the convex hull points for the Drawable. * @param {Array<Array<number>>} points Convex hull points, as [[x, y], ...] */ }, { key: "setConvexHullPoints", value: function setConvexHullPoints(points) { this._convexHullPoints = points; this._convexHullDirty = false; // Re-create the "transformed hull points" array. // We only do this when the hull points change to avoid unnecessary allocations and GC. this._transformedHullPoints = []; for (var i = 0; i < points.length; i++) { this._transformedHullPoints.push(twgl.v3.create()); } this._transformedHullDirty = true; } /** * @function * @name isTouching * Check if the world position touches the skin. * The caller is responsible for ensuring this drawable's inverse matrix & its skin's silhouette are up-to-date. * @see updateCPURenderAttributes * @param {twgl.v3} vec World coordinate vector. * @return {boolean} True if the world position touches the skin. */ // `updateCPURenderAttributes` sets this Drawable instance's `isTouching` method // to one of the following three functions: // If this drawable has no skin, set it to `_isTouchingNever`. // Otherwise, if this drawable uses nearest-neighbor scaling at its current scale, set it to `_isTouchingNearest`. // Otherwise, set it to `_isTouchingLinear`. // This allows several checks to be moved from the `isTouching` function to `updateCPURenderAttributes`. // eslint-disable-next-line no-unused-vars }, { key: "_isTouchingNever", value: function _isTouchingNever(vec) { return false; } }, { key: "_isTouchingNearest", value: function _isTouchingNearest(vec) { return this.skin.isTouchingNearest(getLocalPosition(this, vec)); } }, { key: "_isTouchingLinear", value: function _isTouchingLinear(vec) { return this.skin.isTouchingLinear(getLocalPosition(this, vec)); } /** * Get the precise bounds for a Drawable. * This function applies the transform matrix to the known convex hull, * and then finds the minimum box along the axes. * Before calling this, ensure the renderer has updated convex hull points. * @param {?Rectangle} result optional destination for bounds calculation * @return {!Rectangle} Bounds for a tight box around the Drawable. */ }, { key: "getBounds", value: function getBounds(result) { if (this.needsConvexHullPoints()) { throw new Error('Needs updated convex hull points before bounds calculation.'); } if (this._transformDirty) { this._calculateTransform(); } var transformedHullPoints = this._getTransformedHullPoints(); // Search through transformed points to generate box on axes. result = result || new Rectangle(); result.initFromPointsAABB(transformedHullPoints); return result; } /** * Get the precise bounds for the upper 8px slice of the Drawable. * Used for calculating where to position a text bubble. * Before calling this, ensure the renderer has updated convex hull points. * @param {?Rectangle} result optional destination for bounds calculation * @return {!Rectangle} Bounds for a tight box around a slice of the Drawable. */ }, { key: "getBoundsForBubble", value: function getBoundsForBubble(result) { if (this.needsConvexHullPoints()) { throw new Error('Needs updated convex hull points before bubble bounds calculation.'); } if (this._transformDirty) { this._calculateTransform(); } var slice = 8; // px, how tall the top slice to measure should be. var transformedHullPoints = this._getTransformedHullPoints(); var maxY = Math.max.apply(null, transformedHullPoints.map(function (p) { return p[1]; })); var filteredHullPoints = transformedHullPoints.filter(function (p) { return p[1] > maxY - slice; }); // Search through filtered points to generate box on axes. result = result || new Rectangle(); result.initFromPointsAABB(filteredHullPoints); return result; } /** * Get the rough axis-aligned bounding box for the Drawable. * Calculated by transforming the skin's bounds. * Note that this is less precise than the box returned by `getBounds`, * which is tightly snapped to account for a Drawable's transparent regions. * `getAABB` returns a much less accurate bounding box, but will be much * faster to calculate so may be desired for quick checks/optimizations. * @param {?Rectangle} result optional destination for bounds calculation * @return {!Rectangle} Rough axis-aligned bounding box for Drawable. */ }, { key: "getAABB", value: function getAABB(result) { if (this._transformDirty) { this._calculateTransform(); } var tm = this._uniforms.u_modelMatrix; result = result || new Rectangle(); result.initFromModelMatrix(tm); return result; } /** * Return the best Drawable bounds possible without performing graphics queries. * I.e., returns the tight bounding box when the convex hull points are already * known, but otherwise return the rough AABB of the Drawable. * @param {?Rectangle} result optional destination for bounds calculation * @return {!Rectangle} Bounds for the Drawable. */ }, { key: "getFastBounds", value: function getFastBounds(result) { if (!this.needsConvexHullPoints()) { return this.getBounds(result); } return this.getAABB(result); } /** * Transform all the convex hull points by the current Drawable's * transform. This allows us to skip recalculating the convex hull * for many Drawable updates, including translation, rotation, scaling. * @return {!Array.<!Array.number>} Array of glPoints which are Array<x, y> * @private */ }, { key: "_getTransformedHullPoints", value: function _getTransformedHullPoints() { if (!this._transformedHullDirty) { return this._transformedHullPoints; } var projection = twgl.m4.ortho(-1, 1, -1, 1, -1, 1); var skinSize = this.skin.size; var halfXPixel = 1 / skinSize[0] / 2; var halfYPixel = 1 / skinSize[1] / 2; var tm = twgl.m4.multiply(this._uniforms.u_modelMatrix, projection); for (var i = 0; i < this._convexHullPoints.length; i++) { var point = this._convexHullPoints[i]; var dstPoint = this._transformedHullPoints[i]; dstPoint[0] = 0.5 + -point[0] / skinSize[0] - halfXPixel; dstPoint[1] = point[1] / skinSize[1] - 0.5 + halfYPixel; twgl.m4.transformPoint(tm, dstPoint, dstPoint); } this._transformedHullDirty = false; return this._transformedHullPoints; } /** * Update the transform matrix and calculate it's inverse for collision * and local texture position purposes. */ }, { key: "updateMatrix", value: function updateMatrix() { if (this._transformDirty) { this._calculateTransform(); } // Get the inverse of the model matrix or update it. if (this._inverseTransformDirty) { var inverse = this._inverseMatrix; twgl.m4.copy(this._uniforms.u_modelMatrix, inverse); // The normal matrix uses a z scaling of 0 causing model[10] to be // 0. Getting a 4x4 inverse is impossible without a scaling in x, y, // and z. inverse[10] = 1; twgl.m4.inverse(inverse, inverse); this._inverseTransformDirty = false; } } /** * Update everything necessary to render this drawable on the CPU. */ }, { key: "updateCPURenderAttributes", value: function updateCPURenderAttributes() { this.updateMatrix(); // CPU rendering always occurs at the "native" size, so no need to scale up this._scale if (this.skin) { this.skin.updateSilhouette(this._scale); if (this.skin.useNearest(this._scale, this)) { this.isTouching = this._isTouchingNearest; } else { this.isTouching = this._isTouchingLinear; } } else { log.warn("Could not find skin for drawable with id: ".concat(this._id)); this.isTouching = this._isTouchingNever; } } /** * Respond to an internal change in the current Skin. * @private */ }, { key: "_skinWasAltered", value: function _skinWasAltered() { this._rotationCenterDirty = true; this._skinScaleDirty = true; this.setConvexHullDirty(); this.setTransformDirty(); } /** * Calculate a color to represent the given ID number. At least one component of * the resulting color will be non-zero if the ID is not RenderConstants.ID_NONE. * @param {int} id The ID to convert. * @returns {Array<number>} An array of [r,g,b,a], each component in the range [0,1]. */ }], [{ key: "color4fFromID", value: function color4fFromID(id) { id -= RenderConstants.ID_NONE; var r = (id >> 0 & 255) / 255.0; var g = (id >> 8 & 255) / 255.0; var b = (id >> 16 & 255) / 255.0; return [r, g, b, 1.0]; } /** * Calculate the ID number represented by the given color. If all components of * the color are zero, the result will be RenderConstants.ID_NONE; otherwise the result * will be a valid ID. * @param {int} r The red value of the color, in the range [0,255]. * @param {int} g The green value of the color, in the range [0,255]. * @param {int} b The blue value of the color, in the range [0,255]. * @returns {int} The ID represented by that color. */ }, { key: "color3bToID", value: function color3bToID(r, g, b) { var id; id = (r & 255) << 0; id |= (g & 255) << 8; id |= (b & 255) << 16; return id + RenderConstants.ID_NONE; } /** * Sample a color from a drawable's texture. * The caller is responsible for ensuring this drawable's inverse matrix & its skin's silhouette are up-to-date. * @see updateCPURenderAttributes * @param {twgl.v3} vec The scratch space [x,y] vector * @param {Drawable} drawable The drawable to sample the texture from * @param {Uint8ClampedArray} dst The "color4b" representation of the texture at point. * @param {number} [effectMask] A bitmask for which effects to use. Optional. * @returns {Uint8ClampedArray} The dst object filled with the color4b */ }, { key: "sampleColor4b", value: function sampleColor4b(vec, drawable, dst, effectMask) { var localPosition = getLocalPosition(drawable, vec); if (localPosition[0] < 0 || localPosition[1] < 0 || localPosition[0] > 1 || localPosition[1] > 1) { dst[0] = 0; dst[1] = 0; dst[2] = 0; dst[3] = 0; return dst; } var textColor = // commenting out to only use nearest for now // drawable.skin.useNearest(drawable._scale, drawable) ? drawable.skin._silhouette.colorAtNearest(localPosition, dst); // : drawable.skin._silhouette.colorAtLinear(localPosition, dst); if (drawable.enabledEffects === 0) return textColor; return EffectTransform.transformColor(drawable, textColor, effectMask); } }]); }(); module.exports = Drawable; /***/ }, /***/ "./src/EffectTransform.js" /*!********************************!*\ !*** ./src/EffectTransform.js ***! \********************************/ (module, __unused_webpack_exports, __webpack_require__) { function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } } function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } /** * @fileoverview * A utility to transform a texture coordinate to another texture coordinate * representing how the shaders apply effects. */ var twgl = __webpack_require__(/*! twgl.js */ "twgl.js"); var _require = __webpack_require__(/*! ./util/color-conversions */ "./src/util/color-conversions.js"), rgbToHsv = _require.rgbToHsv, hsvToRgb = _require.hsvToRgb; var ShaderManager = __webpack_require__(/*! ./ShaderManager */ "./src/ShaderManager.js"); /** * A texture coordinate is between 0 and 1. 0.5 is the center position. * @const {number} */ var CENTER_X = 0.5; /** * A texture coordinate is between 0 and 1. 0.5 is the center position. * @const {number} */ var CENTER_Y = 0.5; /** * Reused memory location for storing an HSV color value. * @type {Array<number>} */ var __hsv = [0, 0, 0]; var EffectTransform = /*#__PURE__*/function () { function EffectTransform() { _classCallCheck(this, EffectTransform); } return _createClass(EffectTransform, null, [{ key: "transformColor", value: /** * Transform a color in-place given the drawable's effect uniforms. Will apply * Ghost and Color and Brightness effects. * @param {Drawable} drawable The drawable to get uniforms from. * @param {Uint8ClampedArray} inOutColor The color to transform. * @param {number} [effectMask] A bitmask for which effects to use. Optional. * @returns {Uint8ClampedArray} dst filled with the transformed color */ function transformColor(drawable, inOutColor, effectMask) { // If the color is fully transparent, don't bother attempting any transformations. if (inOutColor[3] === 0) { return inOutColor; } var effects = drawable.enabledEffects; if (typeof effectMask === 'number') effects &= effectMask; var uniforms = drawable.getUniforms(); var enableColor = (effects & ShaderManager.EFFECT_INFO.color.mask) !== 0; var enableBrightness = (effects & ShaderManager.EFFECT_INFO.brightness.mask) !== 0; if (enableColor || enableBrightness) { // gl_FragColor.rgb /= gl_FragColor.a + epsilon; // Here, we're dividing by the (previously pre-multiplied) alpha to ensure HSV is properly calculated // for partially transparent pixels. // epsilon is present in the shader because dividing by 0 (fully transparent pixels) messes up calculations. // We're doing this with a Uint8ClampedArray here, so dividing by 0 just gives 255. We're later multiplying // by 0 again, so it won't affect results. var alpha = inOutColor[3] / 255; inOutColor[0] /= alpha; inOutColor[1] /= alpha; inOutColor[2] /= alpha; if (enableColor) { // vec3 hsv = convertRGB2HSV(gl_FragColor.xyz); var hsv = rgbToHsv(inOutColor, __hsv); // this code forces grayscale values to be slightly saturated // so that some slight change of hue will be visible // const float minLightness = 0.11 / 2.0; var minV = 0.11 / 2.0; // const float minSaturation = 0.09; var minS = 0.09; // if (hsv.z < minLightness) hsv = vec3(0.0, 1.0, minLightness); if (hsv[2] < minV) { hsv[0] = 0; hsv[1] = 1; hsv[2] = minV; // else if (hsv.y < minSaturation) hsv = vec3(0.0, minSaturation, hsv.z); } else if (hsv[1] < minS) { hsv[0] = 0; hsv[1] = minS; } // hsv.x = mod(hsv.x + u_color, 1.0); // if (hsv.x < 0.0) hsv.x += 1.0; hsv[0] = uniforms.u_color + hsv[0] + 1; // gl_FragColor.rgb = convertHSV2RGB(hsl); hsvToRgb(hsv, inOutColor); } if (enableBrightness) { var brightness = uniforms.u_brightness * 255; // gl_FragColor.rgb = clamp(gl_FragColor.rgb + vec3(u_brightness), vec3(0), vec3(1)); // We don't need to clamp because the Uint8ClampedArray does that for us inOutColor[0] += brightness; inOutColor[1] += brightness; inOutColor[2] += brightness; } // gl_FragColor.rgb *= gl_FragColor.a + epsilon; // Now we're doing the reverse, premultiplying by the alpha once again. inOutColor[0] *= alpha; inOutColor[1] *= alpha; inOutColor[2] *= alpha; } if ((effects & ShaderManager.EFFECT_INFO.ghost.mask) !== 0) { // gl_FragColor *= u_ghost inOutColor[0] *= uniforms.u_ghost; inOutColor[1] *= uniforms.u_ghost; inOutColor[2] *= uniforms.u_ghost; inOutColor[3] *= uniforms.u_ghost; } return inOutColor; } /** * Transform a texture coordinate to one that would be select after applying shader effects. * @param {Drawable} drawable The drawable whose effects to emulate. * @param {twgl.v3} vec The texture coordinate to transform. * @param {twgl.v3} dst A place to store the output coordinate. * @return {twgl.v3} dst - The coordinate after being transform by effects. */ }, { key: "transformPoint", value: function transformPoint(drawable, vec, dst) { twgl.v3.copy(vec, dst); var effects = drawable.enabledEffects; var uniforms = drawable.getUniforms(); if ((effects & ShaderManager.EFFECT_INFO.mosaic.mask) !== 0) { // texcoord0 = fract(u_mosaic * texcoord0); dst[0] = uniforms.u_mosaic * dst[0] % 1; dst[1] = uniforms.u_mosaic * dst[1] % 1; } if ((effects & ShaderManager.EFFECT_INFO.pixelate.mask) !== 0) { var skinUniforms = drawable.skin.getUniforms(); // vec2 pixelTexelSize = u_skinSize / u_pixelate; var texelX = skinUniforms.u_skinSize[0] / uniforms.u_pixelate; var texelY = skinUniforms.u_skinSize[1] / uniforms.u_pixelate; // texcoord0 = (floor(texcoord0 * pixelTexelSize) + kCenter) / // pixelTexelSize; dst[0] = (Math.floor(dst[0] * texelX) + CENTER_X) / texelX; dst[1] = (Math.floor(dst[1] * texelY) + CENTER_Y) / texelY; } if ((effects & ShaderManager.EFFECT_INFO.whirl.mask) !== 0) { // const float kRadius = 0.5; var RADIUS = 0.5; // vec2 offset = texcoord0 - kCenter; var offsetX = dst[0] - CENTER_X; var offsetY = dst[1] - CENTER_Y; // float offsetMagnitude = length(offset); var offsetMagnitude = Math.sqrt(Math.pow(offsetX, 2) + Math.pow(offsetY, 2)); // float whirlFactor = max(1.0 - (offsetMagnitude / kRadius), 0.0); var whirlFactor = Math.max(1.0 - offsetMagnitude / RADIUS, 0.0); // float whirlActual = u_whirl * whirlFactor * whirlFactor; var whirlActual = uniforms.u_whirl * whirlFactor * whirlFactor; // float sinWhirl = sin(whirlActual); var sinWhirl = Math.sin(whirlActual); // float cosWhirl = cos(whirlActual); var cosWhirl = Math.cos(whirlActual); // mat2 rotationMatrix = mat2( // cosWhirl, -sinWhirl, // sinWhirl, cosWhirl // ); var rot1 = cosWhirl; var rot2 = -sinWhirl; var rot3 = sinWhirl; var rot4 = cosWhirl; // texcoord0 = rotationMatrix * offset + kCenter; dst[0] = rot1 * offsetX + rot3 * offsetY + CENTER_X; dst[1] = rot2 * offsetX + rot4 * offsetY + CENTER_Y; } if ((effects & ShaderManager.EFFECT_INFO.fisheye.mask) !== 0) { // vec2 vec = (texcoord0 - kCenter) / kCenter; var vX = (dst[0] - CENTER_X) / CENTER_X; var vY = (dst[1] - CENTER_Y) / CENTER_Y; // float vecLength = length(vec); var vLength = Math.sqrt(vX * vX + vY * vY); // float r = pow(min(vecLength, 1.0), u_fisheye) * max(1.0, vecLength); var r = Math.pow(Math.min(vLength, 1), uniforms.u_fisheye) * Math.max(1, vLength); // vec2 unit = vec / vecLength; var unitX = vX / vLength; var unitY = vY / vLength; // texcoord0 = kCenter + r * unit * kCenter; dst[0] = CENTER_X + r * unitX * CENTER_X; dst[1] = CENTER_Y + r * unitY * CENTER_Y; } return dst; } }]); }(); module.exports = EffectTransform; /***/ }, /***/ "./src/PenSkin.js" /*!************************!*\ !*** ./src/PenSkin.js ***! \************************/ (module, __unused_webpack_exports, __webpack_require__) { function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finall