UNPKG

tofu.js

Version:

a helper three.js library for building UC-AR

1,951 lines (1,675 loc) 299 kB
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