UNPKG

infamous

Version:

A CSS3D/WebGL UI library.

321 lines (265 loc) 11.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _lowclass = _interopRequireDefault(require("lowclass")); var _Mixin = _interopRequireDefault(require("./Mixin")); var _XYZNumberValues = _interopRequireDefault(require("./XYZNumberValues")); var _Sizeable = _interopRequireDefault(require("./Sizeable")); var _Utility = require("./Utility"); var _props = require("./props"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } var _default = (0, _Mixin.default)(Base => { const Parent = _Sizeable.default.mixin(Base); // Transformable extends TreeNode (indirectly through Sizeable) because it // needs to be aware of its `parent` when calculating align adjustments. const Transformable = (0, _lowclass.default)('Transformable').extends(Parent, ({ Super }) => ({ static: { props: _objectSpread({}, Parent.props, { position: _props.props.XYZNumberValues, rotation: _props.props.XYZNumberValues, scale: _props.props.XYZNumberValues, origin: _props.props.XYZNumberValues, align: _props.props.XYZNumberValues, mountPoint: _props.props.XYZNumberValues, opacity: _props.props.number }) }, constructor(options = {}) { const self = Super(this).constructor(options); self._worldMatrix = null; return self; }, _setDefaultProperties() { Super(this)._setDefaultProperties(); Object.assign(this._properties, { position: new _XYZNumberValues.default(0, 0, 0), rotation: new _XYZNumberValues.default(0, 0, 0), scale: new _XYZNumberValues.default(1, 1, 1), origin: new _XYZNumberValues.default(0.5, 0.5, 0.5), align: new _XYZNumberValues.default(0, 0, 0), mountPoint: new _XYZNumberValues.default(0, 0, 0), opacity: 1, transform: new window.DOMMatrix() // untracked by SkateJS }); }, _setPropertyObservers() { Super(this)._setPropertyObservers(); this._properties.position.on('valuechanged', () => this.trigger('propertychange', 'position')); this._properties.rotation.on('valuechanged', () => this.trigger('propertychange', 'rotation')); this._properties.scale.on('valuechanged', () => this.trigger('propertychange', 'scale')); this._properties.origin.on('valuechanged', () => this.trigger('propertychange', 'origin')); this._properties.align.on('valuechanged', () => this.trigger('propertychange', 'align')); this._properties.mountPoint.on('valuechanged', () => this.trigger('propertychange', 'mountPoint')); }, /** * Takes all the current component values (position, rotation, etc) and * calculates a transformation DOMMatrix from them. See "W3C Geometry * Interfaces" to learn about DOMMatrix. * * @method * @private * @memberOf Node * * TODO #66: make sure this is called after size calculations when we * move _calcSize to a render task. */ _calculateMatrix() { // NOTE The only way to get an identity matrix with DOMMatrix API // in the current spec is to make a new DOMMatrix. This is wasteful // because it creates new memory. It'd be nice to have an // identity() method or similar. const matrix = new window.DOMMatrix(); // TODO FIXME For some reason, the root node (i.e. the Scene) // should not be translated or else the WebGL rendering glitches // out (this happened with my vanilla WebGL implementation as well // as with Three.js), so we return Identity if there's no parent. if (!this.parent) return matrix; const properties = this._properties; const thisSize = this._calculatedSize; // THREE-COORDS-TO-DOM-COORDS // translate the "mount point" back to the top/left of the element. // We offset this in ElementOperations#applyTransform. The Y value // is inverted because we invert it below. const threeJsPostAdjustment = [thisSize.x / 2, thisSize.y / 2, 0]; const alignAdjustment = [0, 0, 0]; // TODO If a Scene has a `parent`, it is not mounted directly into a // regular DOM element but rather it is child of a Node. In this // case we don't want the scene size to be based on observed size // of a regular DOM element, but relative to a parent Node just // like for all other Nodes. const parentSize = this._getParentSize(); // THREE-COORDS-TO-DOM-COORDS // translate the "align" back to the top/left of the parent element. // We offset this in ElementOperations#applyTransform. The Y // value is inverted because we invert it below. threeJsPostAdjustment[0] += -parentSize.x / 2; threeJsPostAdjustment[1] += -parentSize.y / 2; const { align } = properties; alignAdjustment[0] = parentSize.x * align.x; alignAdjustment[1] = parentSize.y * align.y; alignAdjustment[2] = parentSize.z * align.z; const mountPointAdjustment = [0, 0, 0]; const { mountPoint } = properties; mountPointAdjustment[0] = thisSize.x * mountPoint.x; mountPointAdjustment[1] = thisSize.y * mountPoint.y; mountPointAdjustment[2] = thisSize.z * mountPoint.z; const appliedPosition = []; const { position } = properties; appliedPosition[0] = position.x + alignAdjustment[0] - mountPointAdjustment[0]; appliedPosition[1] = position.y + alignAdjustment[1] - mountPointAdjustment[1]; appliedPosition[2] = position.z + alignAdjustment[2] - mountPointAdjustment[2]; matrix.translateSelf(appliedPosition[0] + threeJsPostAdjustment[0], // THREE-COORDS-TO-DOM-COORDS negate the Y value so that // Three.js' positive Y is downward. -(appliedPosition[1] + threeJsPostAdjustment[1]), appliedPosition[2] + threeJsPostAdjustment[2]); // origin calculation will go here: // - move by negative origin before rotating. // apply each axis rotation, in the x,y,z order. // THREE-COORDS-TO-DOM-COORDS: X rotation is negated here so that // Three rotates on X in the same direction as CSS 3D. It is // negated again when applied to DOM elements so they rotate as // expected in CSS 3D. // TODO #151: make rotation order configurable const { rotation } = properties; matrix.rotateAxisAngleSelf(1, 0, 0, rotation.x); matrix.rotateAxisAngleSelf(0, 1, 0, rotation.y); matrix.rotateAxisAngleSelf(0, 0, 1, rotation.z); // origin calculation will go here: // - move by positive origin after rotating. return matrix; }, // TODO: fix _isIdentity in DOMMatrix, it is returning true even if false. _calculateWorldMatricesInSubtree() { this._calculateWorldMatrixFromParent(); const children = this.subnodes; for (let i = 0, l = children.length; i < l; i += 1) { children[i]._calculateWorldMatricesInSubtree(); } }, _calculateWorldMatrixFromParent() { const parent = this.parent; if (parent) //this._worldMatrix = parent._worldMatrix.multiply(this._properties.transform) this._worldMatrix = this._properties.transform.multiply(parent._worldMatrix);else this._worldMatrix = this._properties.transform; this.trigger('worldMatrixUpdate'); }, // TODO rename "render" to "update". _render() { if (Super(this)._render) Super(this)._render(); // TODO: only run this when necessary (f.e. not if only opacity // changed) this._properties.transform = this._calculateMatrix(); }, /** * Set the position of the Transformable. * * @param {Object} newValue * @param {number} [newValue.x] The x-axis position to apply. * @param {number} [newValue.y] The y-axis position to apply. * @param {number} [newValue.z] The z-axis position to apply. */ set position(newValue) { this._setPropertyXYZ(Transformable, 'position', newValue); }, get position() { return Super(this).position; }, /** * @param {Object} newValue * @param {number} [newValue.x] The x-axis rotation to apply. * @param {number} [newValue.y] The y-axis rotation to apply. * @param {number} [newValue.z] The z-axis rotation to apply. */ set rotation(newValue) { this._setPropertyXYZ(Transformable, 'rotation', newValue); }, get rotation() { return Super(this).rotation; }, /** * @param {Object} newValue * @param {number} [newValue.x] The x-axis scale to apply. * @param {number} [newValue.y] The y-axis scale to apply. * @param {number} [newValue.z] The z-axis scale to apply. */ set scale(newValue) { this._setPropertyXYZ(Transformable, 'scale', newValue); }, get scale() { return Super(this).scale; }, /** * Set this Node's opacity. * * @param {number} opacity A floating point number between 0 and 1 * (inclusive). 0 is fully transparent, 1 is fully opaque. */ set opacity(newValue) { this._setPropertySingle('opacity', newValue); }, get opacity() { return Super(this).opacity; }, /** * Set the alignment of the Node. This determines at which point in this * Node's parent that this Node is mounted. * * @param {Object} newValue * @param {number} [newValue.x] The x-axis align to apply. * @param {number} [newValue.y] The y-axis align to apply. * @param {number} [newValue.z] The z-axis align to apply. */ set align(newValue) { this._setPropertyXYZ(Transformable, 'align', newValue); }, get align() { return Super(this).align; }, /** * Set the mount point of the Node. * * @param {Object} newValue * @param {number} [newValue.x] The x-axis mountPoint to apply. * @param {number} [newValue.y] The y-axis mountPoint to apply. * @param {number} [newValue.z] The z-axis mountPoint to apply. */ set mountPoint(newValue) { this._setPropertyXYZ(Transformable, 'mountPoint', newValue); }, get mountPoint() { return Super(this).mountPoint; } })); return Transformable; }); exports.default = _default;