tofu.js
Version:
a helper three.js library for building UC-AR
1,951 lines (1,675 loc) • 299 kB
JavaScript
import { AdditiveBlending, AlphaFormat, AmbientLight, AnimationClip, AnimationMixer, AnimationUtils, Bone, BufferAttribute, BufferGeometry, Camera, CanvasTexture, CircleBufferGeometry, ClampToEdgeWrapping, Color, Curve, CylinderBufferGeometry, DataTexture, DefaultLoadingManager, DirectionalLight, DoubleSide, Euler, EventDispatcher, FileLoader, FlatShading, FloatType, FrontSide, Group, InterleavedBuffer, InterleavedBufferAttribute, InterpolateDiscrete, InterpolateLinear, InterpolateSmooth, Line, LineLoop, LineSegments, LinearFilter, LinearMipMapLinearFilter, LinearMipMapNearestFilter, Loader, LuminanceAlphaFormat, LuminanceFormat, Matrix3, Matrix4, Mesh, MeshBasicMaterial, MeshLambertMaterial, MeshPhongMaterial, MeshStandardMaterial, MirroredRepeatWrapping, NearestFilter, NearestMipMapLinearFilter, NearestMipMapNearestFilter, NumberKeyframeTrack, Object3D, OrthographicCamera, PerspectiveCamera, PlaneBufferGeometry, PointLight, Points, PropertyBinding, Quaternion, QuaternionKeyframeTrack, RGBAFormat, RGBFormat, Raycaster, RepeatWrapping, Scene, ShaderLib, ShaderMaterial, Skeleton, SkinnedMesh, SphereBufferGeometry, SpotLight, StereoCamera, Texture, TextureLoader, TriangleFanDrawMode, TriangleStripDrawMode, UniformsUtils, UnsignedByteType, UnsignedShort4444Type, UnsignedShort5551Type, UnsignedShort565Type, Vector2, Vector3, VectorKeyframeTrack, VertexColors, VideoTexture, WebGLRenderTarget, WebGLRenderer } from 'three';
(function () {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame) {
window.requestAnimationFrame = function (callback) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function () {
callback(currTime + timeToCall);
}, timeToCall);
lastTime = currTime + timeToCall;
return id;
};
}
if (!window.cancelAnimationFrame) {
window.cancelAnimationFrame = function (id) {
clearTimeout(id);
};
}
window.RAF = window.requestAnimationFrame;
window.CAF = window.cancelAnimationFrame;
})();
/**
* 返回数据类型
* @param {*} val 需要判定的类型
* @return {String} 数据类型
*/
function _rt(val) {
return Object.prototype.toString.call(val);
}
/**
* Utils 常用工具箱
*
* @namespace Utils
*/
var Utils = {
/**
* 简单拷贝纯数据的JSON对象
*
* @static
* @memberof Utils
* @param {JSON} json 待拷贝的纯数据JSON
* @return {JSON} 拷贝后的纯数据JSON
*/
copyJSON: function copyJSON(json) {
return JSON.parse(JSON.stringify(json));
},
/**
* 将角度转化成弧度
*
* @static
* @memberof Utils
* @param {Number} degree 角度数
* @return {Number} 弧度数
*/
DTR: function DTR(degree) {
return degree * Math.PI / 180;
},
/**
* 将弧度转化成角度
*
* @static
* @memberof Utils
* @param {Number} radius 弧度数
* @return {Number} 角度数
*/
RTD: function RTD(radius) {
return radius * 180 / Math.PI;
},
/**
* 判断变量是否为数组类型
*
* @static
* @method
* @memberof Utils
* @param {Array} variable 待判断的变量
* @return {Boolean} 判断的结果
*/
isArray: function () {
var ks = _rt([]);
return function (variable) {
return _rt(variable) === ks;
};
}(),
/**
* 判断变量是否为对象类型
*
* @static
* @method
* @memberof Utils
* @param {Object} variable 待判断的变量
* @return {Boolean} 判断的结果
*/
isObject: function () {
var ks = _rt({});
return function (variable) {
return _rt(variable) === ks;
};
}(),
/**
* 判断变量是否为字符串类型
*
* @static
* @method
* @memberof Utils
* @param {String} variable 待判断的变量
* @return {Boolean} 判断的结果
*/
isString: function () {
var ks = _rt('s');
return function (variable) {
return _rt(variable) === ks;
};
}(),
/**
* 判断变量是否为数字类型
*
* @static
* @method
* @memberof Utils
* @param {Number} variable 待判断的变量
* @return {Boolean} 判断的结果
*/
isNumber: function () {
var ks = _rt(1);
return function (variable) {
return _rt(variable) === ks;
};
}(),
/**
* 判断变量是否为函数类型
*
* @static
* @method
* @memberof Utils
* @param {Function} variable 待判断的变量
* @return {Boolean} 判断的结果
*/
isFunction: function () {
var ks = _rt(function () {});
return function (variable) {
return _rt(variable) === ks;
};
}(),
/**
* 判断变量是否为undefined
*
* @static
* @method
* @memberof Utils
* @param {Function} variable 待判断的变量
* @return {Boolean} 判断的结果
*/
isUndefined: function isUndefined(variable) {
return typeof variable === 'undefined';
},
/**
* 判断变量是否为布尔型
*
* @static
* @method
* @memberof Utils
* @param {Function} variable 待判断的变量
* @return {Boolean} 判断的结果
*/
isBoolean: function () {
var ks = _rt(true);
return function (variable) {
return _rt(variable) === ks;
};
}(),
/**
* 强化的随机数,可以随机产生给定区间内的数字、随机输出数字内的项
*
* @static
* @method
* @memberof Utils
* @param {Array | Number} min 当只传入一个变量时变量应该为数字,否则为所给定区间较小的数字
* @param {Number} max 所给定区间较大的数字
* @return {ArrayItem | Number} 返回数组中大一项或者给定区间内的数字
*/
random: function random(min, max) {
if (this.isArray(min)) return min[~~(Math.random() * min.length)];
if (!this.isNumber(max)) {
max = min || 1;
min = 0;
}
return min + Math.random() * (max - min);
},
/**
* 阿基米德求模
*
* @static
* @method
* @memberof Utils
* @param {Number} n 当前值
* @param {Number} m 模
* @return {Number} 映射到模长内的值
*/
euclideanModulo: function euclideanModulo(n, m) {
return (n % m + m) % m;
},
/**
* 边界值域镜像
*
* @static
* @method
* @memberof Utils
* @param {Number} n 当前值
* @param {Number} min 值域下边界
* @param {Number} max 值域上边界
* @return {Number} 值域内反射到的值
*/
codomainBounce: function codomainBounce(n, min, max) {
if (n < min) return 2 * min - n;
if (n > max) return 2 * max - n;
return n;
},
/**
* 数字区间闭合,避免超出区间
*
* @static
* @method
* @memberof Utils
* @param {Number} x 待闭合到值
* @param {Number} a 闭合区间左边界
* @param {Number} b 闭合区间右边界
* @return {Number} 闭合后的值
*/
clamp: function clamp(x, a, b) {
return x < a ? a : x > b ? b : x;
},
/**
* 线性插值
*
* @static
* @method
* @memberof Utils
* @param {Number} x 输入的值
* @param {Number} min 输入值的下区间
* @param {Number} max 输入值的上区间
* @return {Number} 返回的值在区间[0,1]内
*/
linear: function linear(x, min, max) {
if (x <= min) return 0;
if (x >= max) return 1;
x = (x - min) / (max - min);
return x;
},
/**
* 平滑插值
*
* @static
* @method
* @memberof Utils
* @param {Number} x 输入的值
* @param {Number} min 输入值的下区间
* @param {Number} max 输入值的上区间
* @return {Number} 返回的值在区间[0,1]内
*/
smoothstep: function smoothstep(x, min, max) {
if (x <= min) return 0;
if (x >= max) return 1;
x = (x - min) / (max - min);
return x * x * (3 - 2 * x);
},
/**
* 更平滑的插值
*
* @static
* @method
* @memberof Utils
* @param {Number} x 输入的值
* @param {Number} min 输入值的下区间
* @param {Number} max 输入值的上区间
* @return {Number} 返回的值在区间[0,1]内
*/
smootherstep: function smootherstep(x, min, max) {
if (x <= min) return 0;
if (x >= max) return 1;
x = (x - min) / (max - min);
return x * x * x * (x * (x * 6 - 15) + 10);
}
};
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
var classCallCheck = function (instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
var createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
var get = function get(object, property, receiver) {
if (object === null) object = Function.prototype;
var desc = Object.getOwnPropertyDescriptor(object, property);
if (desc === undefined) {
var parent = Object.getPrototypeOf(object);
if (parent === null) {
return undefined;
} else {
return get(parent, property, receiver);
}
} else if ("value" in desc) {
return desc.value;
} else {
var getter = desc.get;
if (getter === undefined) {
return undefined;
}
return getter.call(receiver);
}
};
var inherits = function (subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
};
var possibleConstructorReturn = function (self, call) {
if (!self) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return call && (typeof call === "object" || typeof call === "function") ? call : self;
};
/**
* an animate class, root class
* @private
*/
var Animate = function (_EventDispatcher) {
inherits(Animate, _EventDispatcher);
/**
* config your animation
* @param {Object} options animate config
* @private
*/
function Animate(options) {
classCallCheck(this, Animate);
var _this = possibleConstructorReturn(this, (Animate.__proto__ || Object.getPrototypeOf(Animate)).call(this));
_this.object = options.object || {};
_this.duration = options.duration || 300;
_this.living = true;
_this.resident = options.resident || false;
_this.infinite = options.infinite || false;
_this.alternate = options.alternate || false;
_this.repeats = options.repeats || 0;
_this.delay = options.delay || 0;
_this.wait = options.wait || 0;
_this.timeScale = Utils.isNumber(options.timeScale) ? options.timeScale : 1;
if (options.onComplete) {
_this.on('complete', options.onComplete.bind(_this));
}
if (options.onUpdate) {
_this.on('update', options.onUpdate.bind(_this));
}
_this.init();
_this.paused = false;
return _this;
}
/**
* init animate state
* @private
*/
createClass(Animate, [{
key: 'init',
value: function init() {
this.direction = 1;
this.progress = 0;
this.repeatsCut = this.repeats;
this.delayCut = this.delay;
this.waitCut = this.wait;
}
/**
* update the timeline and get pose
*
* @private
* @param {Number} snippet time snippet
* @return {Object} pose state
*/
}, {
key: 'update',
value: function update(snippet) {
var snippetCache = this.direction * this.timeScale * snippet;
if (this.waitCut > 0) {
this.waitCut -= Math.abs(snippetCache);
return;
}
if (this.paused || !this.living || this.delayCut > 0) {
if (this.delayCut > 0) this.delayCut -= Math.abs(snippetCache);
return;
}
this.progress += snippetCache;
var isEnd = false;
var progressCache = this.progress;
if (this.spill()) {
if (this.repeatsCut > 0 || this.infinite) {
if (this.repeatsCut > 0) --this.repeatsCut;
this.delayCut = this.delay;
if (this.alternate) {
this.direction *= -1;
this.progress = Utils.codomainBounce(this.progress, 0, this.duration);
} else {
this.direction = 1;
this.progress = Utils.euclideanModulo(this.progress, this.duration);
}
} else {
isEnd = true;
}
}
var pose = void 0;
if (!isEnd) {
pose = this.nextPose();
this.emit('update', pose, this.progress / this.duration);
} else {
if (!this.resident) this.living = false;
this.progress = Utils.clamp(progressCache, 0, this.duration);
pose = this.nextPose();
this.emit('complete', pose, Math.abs(progressCache - this.progress));
}
return pose;
}
/**
* check progress was spill
*
* @private
* @return {Boolean} whether spill or not
*/
}, {
key: 'spill',
value: function spill() {
var bSpill = this.progress <= 0 && this.direction === -1;
var tSpill = this.progress >= this.duration && this.direction === 1;
return bSpill || tSpill;
}
/**
* get next pose, should be overwrite by sub-class
* @private
*/
}, {
key: 'nextPose',
value: function nextPose() {
console.warn('should be overwrite');
}
/**
* linear interpolation
* @private
* @param {number} p0 start value
* @param {number} p1 end value
* @param {number} t time rate
* @return {Number} interpolation value
*/
}, {
key: 'linear',
value: function linear(p0, p1, t) {
return (p1 - p0) * t + p0;
}
/**
* pause this animate
*/
}, {
key: 'pause',
value: function pause() {
this.paused = true;
}
/**
* restore this animate play
*/
}, {
key: 'restart',
value: function restart() {
this.paused = false;
}
/**
* stop this animate at last frame, will trigger `complete` event
*/
}, {
key: 'stop',
value: function stop() {
this.repeats = 0;
this.infinite = false;
this.progress = this.duration;
}
/**
* set this queue's timeScale
* @param {Number} speed set timescale
*/
}, {
key: 'setSpeed',
value: function setSpeed(speed) {
this.timeScale = speed;
}
/**
* cancle this animate, will not trigger `complete` event
*/
}, {
key: 'cancle',
value: function cancle() {
this.living = false;
}
}]);
return Animate;
}(EventDispatcher);
/**
* https://github.com/gre/bezier-easing
* BezierEasing - use bezier curve for transition easing function
* by Gaëtan Renaudeau 2014 - 2015 – MIT License
*/
var NEWTON_ITERATIONS = 4;
var NEWTON_MIN_SLOPE = 0.001;
var SUBDIVISION_PRECISION = 0.0000001;
var SUBDIVISION_MAX_ITERATIONS = 10;
var kSplineTableSize = 11;
var kSampleStepSize = 1.0 / (kSplineTableSize - 1.0);
var float32ArraySupported = typeof Float32Array === 'function';
/* eslint new-cap: 0 */
/**
* 公因式A
*
* @param {number} aA1 控制分量
* @param {number} aA2 控制分量
* @return {number} 整个公式中的A公因式的值
*/
function A(aA1, aA2) {
return 1.0 - 3.0 * aA2 + 3.0 * aA1;
}
/**
* 公因式B
*
* @param {number} aA1 控制分量1
* @param {number} aA2 控制分量2
* @return {number} 整个公式中的B公因式的值
*/
function B(aA1, aA2) {
return 3.0 * aA2 - 6.0 * aA1;
}
/**
* 公因式C
*
* @param {number} aA1 控制分量1
* @param {number} aA2 控制分量2
* @return {number} 整个公式中的C公因式的值
*/
function C(aA1) {
return 3.0 * aA1;
}
/**
* 获取aT处的值
*
* @param {number} aT 三次贝塞尔曲线的t自变量
* @param {number} aA1 控制分量1
* @param {number} aA2 控制分量2
* @return {number} 三次贝塞尔公式的因变量
*/
function calcBezier(aT, aA1, aA2) {
return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT;
}
/**
* 获取aT处的斜率
* @param {number} aT 三次贝塞尔曲线的t自变量
* @param {number} aA1 控制分量1
* @param {number} aA2 控制分量2
* @return {number} 三次贝塞尔公式的导数
*/
function getSlope(aT, aA1, aA2) {
return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1);
}
/**
*
* @param {number} aX x
* @param {number} aA a
* @param {number} aB b
* @param {number} mX1 min x
* @param {number} mX2 max x
* @return {number} 二分法猜测t的值
*/
function binarySubdivide(aX, aA, aB, mX1, mX2) {
var currentX = void 0;
var currentT = void 0;
var i = 0;
do {
currentT = aA + (aB - aA) / 2.0;
currentX = calcBezier(currentT, mX1, mX2) - aX;
if (currentX > 0.0) {
aB = currentT;
} else {
aA = currentT;
}
} while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS);
return currentT;
}
/**
* 牛顿迭代算法,进一步的获取精确的T值
* @param {number} aX x
* @param {number} aGuessT guess t
* @param {number} mX1 min x
* @param {number} mX2 max x
* @return {number} 获取更精确的T值
*/
function newtonRaphsonIterate(aX, aGuessT, mX1, mX2) {
for (var i = 0; i < NEWTON_ITERATIONS; ++i) {
var currentSlope = getSlope(aGuessT, mX1, mX2);
if (currentSlope === 0.0) {
return aGuessT;
}
var currentX = calcBezier(aGuessT, mX1, mX2) - aX;
aGuessT -= currentX / currentSlope;
}
return aGuessT;
}
/**
* cubic-bezier曲线的两个控制点,默认起始点为 0,结束点为 1
*
* @class
* @param {number} mX1 控制点1的x分量
* @param {number} mY1 控制点1的y分量
* @param {number} mX2 控制点2的x分量
* @param {number} mY2 控制点2的y分量
*/
function BezierEasing(mX1, mY1, mX2, mY2) {
if (!(mX1 >= 0 && mX1 <= 1 && mX2 >= 0 && mX2 <= 1)) {
throw new Error('bezier x values must be in [0, 1] range');
}
this.mX1 = mX1;
this.mY1 = mY1;
this.mX2 = mX2;
this.mY2 = mY2;
this.sampleValues = float32ArraySupported ? new Float32Array(kSplineTableSize) : new Array(kSplineTableSize);
this._preCompute();
}
BezierEasing.prototype._preCompute = function () {
// Precompute samples table
if (this.mX1 !== this.mY1 || this.mX2 !== this.mY2) {
for (var i = 0; i < kSplineTableSize; ++i) {
this.sampleValues[i] = calcBezier(i * kSampleStepSize, this.mX1, this.mX2);
}
}
};
BezierEasing.prototype._getTForX = function (aX) {
var intervalStart = 0.0;
var currentSample = 1;
var lastSample = kSplineTableSize - 1;
for (; currentSample !== lastSample && this.sampleValues[currentSample] <= aX; ++currentSample) {
intervalStart += kSampleStepSize;
}
--currentSample;
// Interpolate to provide an initial guess for t
var dist = (aX - this.sampleValues[currentSample]) / (this.sampleValues[currentSample + 1] - this.sampleValues[currentSample]);
var guessForT = intervalStart + dist * kSampleStepSize;
var initialSlope = getSlope(guessForT, this.mX1, this.mX2);
if (initialSlope >= NEWTON_MIN_SLOPE) {
return newtonRaphsonIterate(aX, guessForT, this.mX1, this.mX2);
} else if (initialSlope === 0.0) {
return guessForT;
}
return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize, this.mX1, this.mX2);
};
/**
* 通过x轴近似获取y的值
*
* @param {number} x x轴的偏移量
* @return {number} 与输入值x对应的y值
*/
BezierEasing.prototype.get = function (x) {
if (this.mX1 === this.mY1 && this.mX2 === this.mY2) return x;
if (x === 0) {
return 0;
}
if (x === 1) {
return 1;
}
return calcBezier(this._getTForX(x), this.mY1, this.mY2);
};
/* eslint no-cond-assign: "off" */
/* eslint new-cap: 0 */
/* eslint max-len: 0 */
/**
* Tween 缓动时间运动函数集合
*
* ```js
* dispay.animate({
* from: {x: 100},
* to: {x: 200},
* ease: JC.Tween.Ease.In, // 配置要调用的运动函数
* })
* ```
* @namespace Tween
*/
var Tween = {
Linear: {
None: function None(k) {
return k;
}
},
Ease: {
In: function () {
var bezier = new BezierEasing(0.42, 0, 1, 1);
return function (k) {
return bezier.get(k);
};
}(),
Out: function () {
var bezier = new BezierEasing(0, 0, 0.58, 1);
return function (k) {
return bezier.get(k);
};
}(),
InOut: function () {
var bezier = new BezierEasing(0.42, 0, 0.58, 1);
return function (k) {
return bezier.get(k);
};
}(),
Bezier: function Bezier(x1, y1, x2, y2) {
var bezier = new BezierEasing(x1, y1, x2, y2);
return function (k) {
return bezier.get(k);
};
}
},
Elastic: {
In: function In(k) {
if (k === 0) {
return 0;
}
if (k === 1) {
return 1;
}
return -Math.pow(2, 10 * (k - 1)) * Math.sin((k - 1.1) * 5 * Math.PI);
},
Out: function Out(k) {
if (k === 0) {
return 0;
}
if (k === 1) {
return 1;
}
return Math.pow(2, -10 * k) * Math.sin((k - 0.1) * 5 * Math.PI) + 1;
},
InOut: function InOut(k) {
if (k === 0) {
return 0;
}
if (k === 1) {
return 1;
}
k *= 2;
if (k < 1) {
return -0.5 * Math.pow(2, 10 * (k - 1)) * Math.sin((k - 1.1) * 5 * Math.PI);
}
return 0.5 * Math.pow(2, -10 * (k - 1)) * Math.sin((k - 1.1) * 5 * Math.PI) + 1;
}
},
Back: {
In: function In(k) {
var s = 1.70158;
return k * k * ((s + 1) * k - s);
},
Out: function Out(k) {
var s = 1.70158;
return --k * k * ((s + 1) * k + s) + 1;
},
InOut: function InOut(k) {
var s = 1.70158 * 1.525;
if ((k *= 2) < 1) {
return 0.5 * (k * k * ((s + 1) * k - s));
}
return 0.5 * ((k -= 2) * k * ((s + 1) * k + s) + 2);
}
},
Bounce: {
In: function In(k) {
return 1 - Tween.Bounce.Out(1 - k);
},
Out: function Out(k) {
if (k < 1 / 2.75) {
return 7.5625 * k * k;
} else if (k < 2 / 2.75) {
return 7.5625 * (k -= 1.5 / 2.75) * k + 0.75;
} else if (k < 2.5 / 2.75) {
return 7.5625 * (k -= 2.25 / 2.75) * k + 0.9375;
}
return 7.5625 * (k -= 2.625 / 2.75) * k + 0.984375;
},
InOut: function InOut(k) {
if (k < 0.5) {
return Tween.Bounce.In(k * 2) * 0.5;
}
return Tween.Bounce.Out(k * 2 - 1) * 0.5 + 0.5;
}
}
};
/* eslint guard-for-in: "off" */
/**
* transition class
* @private
*/
var Transition = function (_Animate) {
inherits(Transition, _Animate);
function Transition(options) {
classCallCheck(this, Transition);
// collect from pose, when from was not complete
var _this = possibleConstructorReturn(this, (Transition.__proto__ || Object.getPrototypeOf(Transition)).call(this, options));
options.from = options.from || {};
for (var i in options.to) {
if (Utils.isUndefined(options.from[i])) options.from[i] = _this.object.props(i);
}
/**
* timing function
*/
_this.ease = options.ease || Tween.Ease.InOut;
/**
* from pose
*/
_this.from = options.from;
/**
* to pose
*/
_this.to = options.to;
return _this;
}
/**
* calculate next frame pose
* @private
* @return {object} pose state
*/
createClass(Transition, [{
key: 'nextPose',
value: function nextPose() {
var pose = {};
var t = this.ease(this.progress / this.duration);
for (var i in this.to) {
pose[i] = this.linear(this.from[i], this.to[i], t);
this.object.props(i, pose[i]);
}
return pose;
}
}]);
return Transition;
}(Animate);
/**
* PathMotion class
* @private
*/
var PathMotion = function (_Animate) {
inherits(PathMotion, _Animate);
function PathMotion(options) {
classCallCheck(this, PathMotion);
var _this = possibleConstructorReturn(this, (PathMotion.__proto__ || Object.getPrototypeOf(PathMotion)).call(this, options));
if (!options.path || !(options.path instanceof Curve)) {
console.warn('path is not instanceof Curve');
}
/**
* path, extend curve
*/
_this.path = options.path;
/**
* timing function
*/
_this.ease = options.ease || Tween.Ease.InOut;
/**
* use lengthMode or not
*/
_this.lengthMode = Utils.isBoolean(options.lengthMode) ? options.lengthMode : false;
/**
* progress need clamp with [0, 1]
*/
_this.needClamp = Utils.isBoolean(options.needClamp) ? options.needClamp : true;
/**
* object need attach tangent
*/
_this.attachTangent = Utils.isBoolean(options.attachTangent) ? options.attachTangent : false;
return _this;
}
/**
* caculation next pose
* @return {Vector3} position
*/
createClass(PathMotion, [{
key: 'nextPose',
value: function nextPose() {
var t = this.ease(this.progress / this.duration);
if (this.needClamp) {
t = Utils.clamp(t, 0, 1);
}
var position = this.lengthMode ? this.path.getPointAt(t) : this.path.getPoint(t);
this.object.position.copy(position);
if (this.attachTangent) {
var direction = this.lengthMode ? this.path.getTangentAt(t) : this.path.getTangent(t);
this.object.lookAt(position.add(direction));
}
return position;
}
}]);
return PathMotion;
}(Animate);
// import Utils from '../utils/Utils';
/**
* AnimateRunner, composer any animation type
*
* @private
*/
var AnimateRunner = function (_Animate) {
inherits(AnimateRunner, _Animate);
/**
* config a runner animation
* @param {object} [options] runners config
*/
function AnimateRunner(options) {
classCallCheck(this, AnimateRunner);
var _this = possibleConstructorReturn(this, (AnimateRunner.__proto__ || Object.getPrototypeOf(AnimateRunner)).call(this, options));
_this.runners = options.runners;
_this.cursor = 0;
_this.queues = [];
_this.alternate = false;
_this.length = _this.runners.length;
// TODO: Is it necessary to exist ?
// this.propsMap = [];
// this.prepare();
return _this;
}
// prepare() {
// let i = 0;
// let j = 0;
// for (i = 0; i < this.runners.length; i++) {
// const runner = this.runners[i];
// if (Utils.isUndefined(runner.to)) continue;
// const keys = Object.keys(runner.to);
// for (j = 0; j < keys.length; j++) {
// const prop = keys[j];
// if (this.propsMap.indexOf(prop) === -1) this.propsMap.push(prop);
// }
// }
// for (i = 0; i < this.runners.length; i++) {
// const runner = this.runners[i];
// if (!runner.to) continue;
// for (j = 0; j < this.propsMap.length; j++) {
// const prop = this.propsMap[j];
// if (Utils.isUndefined(runner.to[prop])) runner.to[prop] = this.object.props(prop);
// }
// }
// }
/**
* init a runner
* @private
*/
createClass(AnimateRunner, [{
key: 'initRunner',
value: function initRunner() {
var runner = this.runners[this.cursor];
runner.infinite = false;
runner.resident = true;
runner.object = this.object;
var animate = null;
if (runner.path) {
animate = new PathMotion(runner);
} else if (runner.to) {
animate = new Transition(runner);
}
if (animate !== null) {
animate.on('complete', this.nextRunner.bind(this));
this.queues.push(animate);
}
}
/**
* step to next `runner`
* @param {Object} _ is hold for pose
* @param {Number} time time snippet
* @private
*/
}, {
key: 'nextRunner',
value: function nextRunner(_, time) {
this.queues[this.cursor].init();
this.cursor += this.direction;
this.timeSnippet = time;
}
/**
* get next pose
* @param {Number} snippetCache time snippet
* @return {Object} pose
*/
}, {
key: 'nextPose',
value: function nextPose(snippetCache) {
if (!this.queues[this.cursor] && this.runners[this.cursor]) {
this.initRunner();
}
if (this.timeSnippet >= 0) {
snippetCache += this.timeSnippet;
this.timeSnippet = 0;
}
return this.queues[this.cursor].update(snippetCache);
}
/**
* update state
* @param {Number} snippet time snippet
* @return {Object} pose
*/
}, {
key: 'update',
value: function update(snippet) {
if (this.wait > 0) {
this.wait -= Math.abs(snippet);
return;
}
if (this.paused || !this.living || this.delayCut > 0) {
if (this.delayCut > 0) this.delayCut -= Math.abs(snippet);
return;
}
var cc = this.cursor;
var pose = this.nextPose(this.direction * this.timeScale * snippet);
this.emit('update', {
index: cc,
pose: pose
}, this.progress / this.duration);
if (this.spill()) {
if (this.repeats > 0 || this.infinite) {
if (this.repeats > 0) --this.repeats;
this.delayCut = this.delay;
this.direction = 1;
this.cursor = 0;
} else {
if (!this.resident) this.living = false;
this.emit('complete', pose);
}
}
return pose;
}
/**
* check progress is spill?
* @return {Boolean} is spill
*/
}, {
key: 'spill',
value: function spill() {
// TODO: 这里应该保留溢出,不然会导致时间轴上的误差
var topSpill = this.cursor >= this.length;
return topSpill;
}
}]);
return AnimateRunner;
}(Animate);
/**
* timeline animations class
* @private
*/
var TimelineAnimations = function () {
function TimelineAnimations(object) {
classCallCheck(this, TimelineAnimations);
/**
* display object cache it for use after
*/
this.object = object;
/**
* object's all animates queue
*
* @member {Array}
*/
this.animates = [];
/**
* object's timeline update time-scale
*
* @member {Number}
*/
this.timeScale = 1;
/**
* pause the object's timeline update
*
* @member {Boolean}
*/
this.paused = false;
}
/**
* update this object animates queue
* @param {Number} snippet time snippet
*/
createClass(TimelineAnimations, [{
key: 'update',
value: function update(snippet) {
if (this.paused) return;
snippet = this.timeScale * snippet;
var cache = this.animates.slice(0);
for (var i = 0; i < cache.length; i++) {
if (!cache[i].living && !cache[i].resident) {
this.animates.splice(i, 1);
}
cache[i].update(snippet);
}
}
/**
* create a `animate` animation
*
* @private
* @param {object} options config for animate
* @param {boolean} clear whether clear all animation at before
* @return {Transition} transition animator
*/
}, {
key: 'animate',
value: function animate(options, clear) {
options.object = this.object;
return this._addMove(new Transition(options), clear);
}
/**
* create a `motion` animation
*
* @private
* @param {object} options config for motion
* @param {boolean} clear whether clear all animation at before
* @return {PathMotion} pathMotion animator
*/
}, {
key: 'motion',
value: function motion(options, clear) {
options.object = this.object;
return this._addMove(new PathMotion(options), clear);
}
/**
* create a `runners` animation
* @private
* @param {object} options config for motion
* @param {boolean} clear whether clear all animation at before
* @return {AnimateRunner} animateRunner animator
*/
}, {
key: 'runners',
value: function runners(options, clear) {
options.object = this.object;
return this._addMove(new AnimateRunner(options), clear);
}
/**
* create a `keyFrames` animation
* @private
* @param {object} options config for motion
* @param {boolean} clear whether clear all animation at before
* @return {KeyFrames} keyFrames animator
*/
// keyFrames(options, clear) {
// options.object = this.object;
// return this._addMove(new KeyFrames(options), clear);
// }
/**
* add animator into animates queue
* @private
* @param {object} animate animator
* @param {boolean} clear whether clear all animation at before
* @return {Transition} animator
*/
}, {
key: '_addMove',
value: function _addMove(animate, clear) {
if (clear) this.clear();
this.animates.push(animate);
return animate;
}
/**
* pause animates
*/
}, {
key: 'pause',
value: function pause() {
this.paused = true;
}
/**
* restore animates play
*/
}, {
key: 'restart',
value: function restart() {
this.paused = false;
}
/**
* set this queue's timeScale
* @param {Number} speed set timescale
*/
}, {
key: 'setSpeed',
value: function setSpeed(speed) {
this.timeScale = speed;
}
/**
* clear all animates
* @private
*/
}, {
key: 'clear',
value: function clear() {
this.animates.length = 0;
}
}]);
return TimelineAnimations;
}();
/**
* timeline scale, effect this display-node and it's children
*/
Object3D.prototype.timeScale = 1;
/**
* whether pause the timeline update with this display-node and it's children
*/
Object3D.prototype.paused = false;
/**
* scale display-object with xyz together
*
*/
Object.defineProperty(Object3D.prototype, 'scaleXYZ', {
get: function get() {
return this.scale.x;
},
set: function set(scale) {
this.scale.x = this.scale.y = this.scale.z = scale;
}
});
/**
* animate animation, let a display-object move from a pose to another pose
*
* ```js
* model.animate({
* from: { 'position.x': 100 },
* to: { 'position.x': 200 },
* ease: Tween.Bounce.Out, // use which timing-function default: `Tween.Ease.InOut`
* repeats: 10, // repeat 10 times, after compelete this animation loop
* infinite: true, // infinite repeat forevery
* alternate: true, // repeat with alternate, just like yoyo
* duration: 1000, // animation duration 1000ms
* wait: 100, // animation await sometime
* onUpdate: function(state,rate){}, // will be invoked when update pose
* onComplete: function(){ console.log('end'); } // will be invoked when update last pose (complete)
* });
* ```
*
* @param {Object} options animate animation config
* @param {Object} [options.from] which pose state this animate start with
* @param {Object} options.to which pose state this animate to
* @param {Tween} [options.ease=Tween.Ease.InOut] use which timing-function
* @param {Number} [options.repeats=0] whether animate repeat some times, it's weight lower than `options.infinite`
* @param {Number} [options.duration=300] animate duration
* @param {Number} [options.wait=0] animate wait time before first start, not effect next repeat
* @param {Number} [options.delay=0] animate delay time before start or restart, effect at repeat if it have
* @param {Boolean} [options.infinite=false] whether animate infinite repeat, it's weight higher than `options.repeats`
* @param {Boolean} [options.alternate=false] whether animate paly with alternate, just like yoyo
* @param {Function} [options.onUpdate] will be invoked when update pose
* @param {Function} [options.onComplete] will be invoked when update last pose (complete)
* @param {Boolean} clear whether clear display-object's animation queue
* @return {Animate} animate object
*/
Object3D.prototype.animate = function (options, clear) {
if (!this.timelineAnimations) this.initTimeline();
return this.timelineAnimations.animate(options, clear);
};
/**
* combine animation, let a display-object do animate one by one
*
* ```js
* display.runners({
* runners: [
* { from: {}, to: {} },
* { path: JC.BezierCurve([ point1, point2, point3, point4 ]) },
* ],
* repeats: 10, // repeat 10 times, after compelete this animation loop
* infinite: true, // infinite repeat forevery
* alternate: true, // repeat with alternate, just like yoyo
* duration: 1000, // animation duration 1000ms
* onUpdate: function(state,rate){}, // will be invoked when update pose
* onComplete: function(){ console.log('end'); } // will be invoked when update last pose (complete)
* });
* ```
*
* @param {Object} options combine animation config
* @param {Array} options.runners combine animation, support `animate`、`motion`
* @param {Number} [options.repeats=0] whether animate repeat some times, it's weight lower than `options.infinite`
* @param {Number} [options.duration=300] animate duration
* @param {Number} [options.wait=0] animate wait time before first start, not effect next repeat
* @param {Number} [options.delay=0] animate delay time before start or restart, effect at repeat if it have
* @param {Boolean} [options.infinite=false] whether animate infinite repeat, it's weight higher than `options.repeats`
* @param {Boolean} [options.alternate=false] whether animate paly with alternate, just like yoyo
* @param {Function} [options.onUpdate] will be invoked when update pose
* @param {Function} [options.onComplete] will be invoked when update last pose (complete)
* @param {Boolean} clear whether clear display-object's animation queue
* @return {Animate} Animate
*/
Object3D.prototype.runners = function (options, clear) {
if (!this.timelineAnimations) this.initTimeline();
return this.timelineAnimations.runners(options, clear);
};
/**
* motion animation, let a display-object move along with the path
*
* ```js
* display.motion({
* path: new SvgCurve('M10 10 H 90 V 90 H 10 L 10 10), // path, should instance of Curve
* attachTangent: true, // whether display-object should attach tangent or not
* ease: Tween.Bounce.Out, // use which timing-function default: `Tween.Ease.InOut`
* repeats: 10, // repeat 10 times, after compelete this animation loop
* infinite: true, // infinite repeat forevery
* alternate: true, // repeat with alternate, just like yoyo
* duration: 1000, // animation duration 1000ms
* wait: 100, // animation await sometime
* onUpdate: function(state,rate){}, // will be invoked when update pose
* onComplete: function(){ console.log('end'); } // will be invoked when update last pose (complete)
* });
* ```
* @param {Object} options motion animation config
* @param {Curve} options.path path, should instance of Curve
* @param {Boolean} [options.attachTangent=false] whether display-object should attach tangent or not
* @param {Boolean} [options.lengthMode=false] whether move with length-mode or not
* @param {Tween} [options.ease=Tween.Ease.InOut] use which timing-function
* @param {Number} [options.repeats=0] whether animate repeat some times, it's weight lower than `options.infinite`
* @param {Number} [options.duration=300] animate duration
* @param {Number} [options.wait=0] animate wait time before first start, not effect next repeat
* @param {Number} [options.delay=0] animate delay time before start or restart, effect at repeat if it have
* @param {Boolean} [options.infinite=false] whether animate infinite repeat, it's weight higher than `options.repeats`
* @param {Boolean} [options.alternate=false] whether animate paly with alternate, just like yoyo
* @param {Function} [options.onUpdate] will be invoked when update pose
* @param {Function} [options.onComplete] will be invoked when update last pose (complete)
* @param {Boolean} clear whether clear display-object's animation queue
* @return {Animate} Animate
*/
Object3D.prototype.motion = function (options, clear) {
if (!this.timelineAnimations) this.initTimeline();
return this.timelineAnimations.motion(options, clear);
};
/**
* initial timeline when it was not created
*
* @private
*/
Object3D.prototype.initTimeline = function () {
this.timelineAnimations = new TimelineAnimations(this);
};
/**
* update display-object animation
*
* @private
* @param {Number} snippet time snippet
*/
Object3D.prototype.updateAnimations = function (snippet) {
if (!this.timelineAnimations) return;
this.timelineAnimations.update(snippet);
};
/**
* update display-object timeline
*
* @param {Number} snippet time snippet
*/
Object3D.prototype.updateTimeline = function (snippet) {
if (this.paused) return;
snippet = this.timeScale * snippet;
this.emit('pretimeline', {
snippet: snippet
});
this.updateAnimations(snippet);
var i = 0;
var children = this.children;
var length = children.length;
while (i < length) {
children[i].updateTimeline(snippet);
i++;
}
this.emit('posttimeline', {
snippet: snippet
});
};
/**
* get depth property from this object
*
* @private
* @param {String} key property chain
* @param {Number} value want set which value
* @return {Number} return number
*/
Object3D.prototype.props = function (key, value) {
if (!key) return;
var rawKeys = key.split('.');
if (rawKeys.length === 0) {
if (Utils.isUndefined(value)) {
return this[key];
}
this[key] = value;
return value;
}
var prop = rawKeys.pop();
var head = rawKeys.join('.');
if (!this.keysMaps || !this.keysMaps[head]) {
this.linkProp(head, rawKeys);
}
if (Utils.isUndefined(value)) {
return this.keysMaps[head][prop];
}
this.keysMaps[head][prop] = value;
return value;
};
Object3D.prototype.linkProp = function (head, rawKeys) {
if (!this.keysMaps) this.keysMaps = {};
var prop = this;
for (var i = 0; i < rawKeys.length; i++) {
var key = rawKeys[i];
prop = prop[key];
}
this.keysMaps[head] = prop;
};
var Smooth = function () {
function Smooth(object, options) {
classCallCheck(this, Smooth);
options = options || {};
this.object = object;
this.to = new Vector3().copy(object);
this.now = this.to.clone();
this.speed = options.speed || 0.02;
this.noise = options.noise || 0.000001;
}
createClass(Smooth, [{
key: 'goto',
value: function goto(x, y, z) {
this.to.set(x, y, z);
}
}, {
key: 'update',
value: function update() {
var space = this.to.clone().sub(this.now).multiplyScalar(this.speed);
if (space.length() < this.noise) return;
this.now.add(space);
this.object.x = this.now.x;
this.object.y = this.now.y;
this.object.z = this.now.z;
}
}]);
return Smooth;
}();
var zee = new Vector3(0, 0, 1);
var euler = new Euler();
var q0 = new Quaternion();
var q1 = new Quaternion(-Math.sqrt(0.5), 0, 0, Math.sqrt(0.5)); // - PI/2 around the x-axis
var ROTATE_ORDER = 'YXZ';
var Orienter = function (_EventDispatcher) {
inherits(Orienter, _EventDispatcher);
function Orienter(options) {
classCallCheck(this, Orienter);
var _this = possibleConstructorReturn(this, (Orienter.__proto__ || Object.getPrototypeOf(Orienter)).call(this));
options = options || {};
_this._orient = _this._orient.bind(_this);
_this._change = _this._change.bind(_this);
_this.needFix = Utils.isBoolean(options.needFix) ? options.needFix : true;
_this.fix = 0;
_this.timing = 8;
_this.alpha = 0;
_this.beta = 0;
_this.gamma = 0;
_this.lon = 0;
_this.lat = 0;
_this.direction = 0;
_this.quaternion = new Quaternion();
_this.eared = false;
_this.nodce = true;
_this.connect();
return _this;
}
createClass(Orienter, [{
key: 'connect',
value: function connect() {
this._change();
if (this.eared) return;
this.eared = true;
window.addEventListener('deviceorientation', this._orient, false);
window.addEventListener('orientationchange', this._change, false);
}
}, {
key: 'disconnect',
value: function disconnect() {
this.eared = false;
window.removeEventListener('deviceorientation', this._orient, false);
window.removeEventListener('orientationchange', this._change, false);
}
}, {
key: '_orient',
value: function _orient(event) {
if (!Utils.isNumber(event.alpha) || !Utils.isNumber(event.beta) || !Utils.isNumber(event.gamma)) {
this.nodce = true;
return;
}
if (this.needFix) {
if (this.timing === 0) {
this.needFix = false;
this.fix = event.alpha;
}
this.timing--;
} else {
this.alpha = Utils.euclideanModulo(event.alpha - this.fix, 360);
}
this.nodce = false;
this.beta = event.beta;
this.gamma = event.gamma;
this.update();
this.emit('deviceorientation', {
alpha: this.alpha,
beta: this.beta,
gamma: this.gamma,
lon: this.lon,
lat: this.lat,
quaternion: this.quaternion
});
}
}, {
key: 'update',
value: function update() {
if (!this.eared || this.nodce) return;
var alpha = Utils.DTR(this.alpha);
var beta = Utils.DTR(this.beta);
var gamma = Utils.DTR(this.gamma);
euler.set(beta, alpha, -gamma, ROTATE_ORDER); // 'ZXY' for the device, but 'YXZ' for us
this.quaternion.setFromEuler(euler); // orient the device
this.quaternion.multiply(q1); // camera looks out the back of the device, not the top
this.quaternion.multiply(q0.setFromAxisAngle(zee, -this.direction)); // adjust for screen orientation
this.calculation();
}
}, {
key: 'calculation',
value: function calculation() {
this.lon = Utils.euclideanModulo(this.alpha + this.gamma, 360);
var face = new Vector3(0, 0, -1);
face.applyQuaternion(this.quaternion);
var xzFace = new Vector3(face.x, 0, face.z);
var cos = Utils.clamp(xzFace.dot(face), -1, 1);
var nor = face.y >= 0 ? 1 : -1;
var degree = Utils.RTD(Math.acos(cos));
this.lat = this.beta < 0 ? nor * (180 - degree) : nor * degree;
}
}, {
key: '_change',
value: function _change() {
this.direction = window.orientation || 0;
this.update();
}
}]);
return Orienter;
}(EventDispatcher);
/**
* used to link 3d-model with reality world, an ar-glue
*
* @param {Object} options config
* @param {String} options.name ar-glue name, same with `setMarkers` name-id
* @param {Boolean} [options.autoHide=true] whether auto-hide when marker have not detected
*/
var ARGlue = function (_Object3D) {
inherits(ARGlue, _Object3D);
function ARGlue(options) {
classCallCheck(this, ARGlue);
var _this = possibleConstructorReturn(this, (ARGlue.__proto__ || Object.getPrototypeOf(ARGlue)).call(this));
options = options || {};
/**
* unique name in all ar-glue object
*
* @member {String}
*/
_this.name = options.name;
/**
* whether auto-hide when marker have not detected
*
* @member {Boolean}
*/
_this.autoHide = Utils.isBoolean(options.autoHide) ? options.autoHide : true;
/**
* close this object matrixAutoUpdate, just recive matrix from `UC-AR`
*
* @member {Boolean}
*/
_this.matrixAutoUpdate = false;
/**
* class type, a mark to distinguish ar-glue and normal display-object
*
* @member {String}
*/
_this.type = 'ARGlue';
if (!options.name) {
console.error('ARGlue: this glue must have a name');
}
return _this;
}
/**
* update this glue pose matrix
* @param {Array} matrix pose matrix
* @param {Boolean} isDetected whether detected at this tick
*/
createClass(ARGlue, [{
key: 'updatePose',
value: function updatePose(matrix, isDetected) {
if (this.autoHide && !isDetected) {
this.visible = false;
} else {
this.visible = true;
}
this.matrix.fromArray(matrix);
}
}]);
return ARGlue;
}(Object3D);
function setDefault(check, value, spare) {
return check(value) ? value : spare;
}
/**
* ViewPort class, a default config for WebGLRenderer with-in UC-AR
*
* @private
* @param {Object} options custom config for WebGLRenderer
* @param {String|canvas} options.canvas `canvas-dom` or canvas `css-selector`
* @param {Boolean} [options.alpha=false] whether the canvas contains an alpha (transparency) buffer or not.
* @param {Boolean} [options.antialias=false] whether to perform antialiasing.
* @param {String} [options.precision='highp'] Shader precision, Can be `highp`, `mediump` or `lowp`.
* @param {Boolean} [options.premultipliedAlpha=true] whether the renderer will assume that colors have premultiplied alpha.
* @param {Boolean} [options.stencil=true] whether the drawing buffer has a stencil buffer of at least 8 bits.
* @param {Boolean} [options.preserveDrawingBuffer=false] whether to preserve the buffers until manually cleared or overwritten.
* @param {Boolean} [options.depth=true] whether the drawing buffer has a depth buffer of at least 16 bits.
* @param {Boolean} [options.logarithmicDepthBuffer] whether to use a logarithmic depth buffer.
*/
var ViewPort = function ViewPort(options) {
classCallCheck(this, ViewPort);
/**
* canvas dom element
*
* @member {canvas}
*/
this.canvas = Utils.isString(options.canvas) ? document.getElementById(options.canvas) || document.querySelector(options.canvas) : options.canvas;
/**
* Shader precision
*
* @member {String}
*/
this.precision = options.precision;
/**
* canvas contains an alpha (transparency) buffer or not
*
* @member {Boolean}
*/
this.alpha = setDefault(Utils.isBoolean, options.alpha, true);
/**
* whether to perform antialiasing
*
* @member {Boolean}
*/
this.antialias = setDefault(Utils.isBoolean, options.antialias, true);
/**
* whether the renderer will assume that colors have premultiplied alpha.
*
* @member {Boolean}
*/
this.premultiplied