scratch-render
Version:
WebGL Renderer for Scratch 3.0
1,167 lines (1,081 loc) • 280 kB
JavaScript
/******/ (() => { // 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