infamous
Version:
A CSS3D/WebGL UI library.
321 lines (265 loc) • 11.1 kB
JavaScript
"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;