UNPKG

sparkar-pftween

Version:

Spark AR library for tweening animation.

702 lines (701 loc) 27.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.PFTween = exports.Ease = void 0; const Time_1 = __importDefault(require("Time")); const Animation_1 = __importDefault(require("Animation")); const Reactive_1 = __importDefault(require("Reactive")); const Patches_1 = __importDefault(require("Patches")); const samplers = { linear: Animation_1.default.samplers.linear, easeInQuad: Animation_1.default.samplers.easeInQuad, easeOutQuad: Animation_1.default.samplers.easeOutQuad, easeInOutQuad: Animation_1.default.samplers.easeInOutQuad, easeInCubic: Animation_1.default.samplers.easeInCubic, easeOutCubic: Animation_1.default.samplers.easeOutCubic, easeInOutCubic: Animation_1.default.samplers.easeInOutCubic, easeInQuart: Animation_1.default.samplers.easeInQuart, easeOutQuart: Animation_1.default.samplers.easeOutQuart, easeInOutQuart: Animation_1.default.samplers.easeInOutQuart, easeInQuint: Animation_1.default.samplers.easeInQuint, easeOutQuint: Animation_1.default.samplers.easeOutQuint, easeInOutQuint: Animation_1.default.samplers.easeInOutQuint, easeInSine: Animation_1.default.samplers.easeInSine, easeOutSine: Animation_1.default.samplers.easeOutSine, easeInOutSine: Animation_1.default.samplers.easeInOutSine, easeInExpo: Animation_1.default.samplers.easeInExpo, easeOutExpo: Animation_1.default.samplers.easeOutExpo, easeInOutExpo: Animation_1.default.samplers.easeInOutExpo, easeInCirc: Animation_1.default.samplers.easeInCirc, easeOutCirc: Animation_1.default.samplers.easeOutCirc, easeInOutCirc: Animation_1.default.samplers.easeInOutCirc, easeInBack: Animation_1.default.samplers.easeInBack, easeOutBack: Animation_1.default.samplers.easeOutBack, easeInOutBack: Animation_1.default.samplers.easeInOutBack, easeInElastic: Animation_1.default.samplers.easeInElastic, easeOutElastic: Animation_1.default.samplers.easeOutElastic, easeInOutElastic: Animation_1.default.samplers.easeInOutElastic, easeInBounce: Animation_1.default.samplers.easeInBounce, easeOutBounce: Animation_1.default.samplers.easeOutBounce, easeInOutBounce: Animation_1.default.samplers.easeInOutBounce, linearPingPong: (begin, end) => Animation_1.default.samplers.polyline({ keyframes: [begin, end, begin] }), easePingPong: (begin, end) => Animation_1.default.samplers.polybezier({ keyframes: [begin, end, begin] }), punch: (begin, amount) => { if (Array.isArray(begin) && Array.isArray(amount)) { return Animation_1.default.samplers.polybezier({ keyframes: [ [begin[0] + (amount[0] / 5) * 4, begin[1] + (amount[1] / 5) * 4, begin[2] + (amount[2] / 5) * 4], [begin[0] - (amount[0] / 5) * 3, begin[1] - (amount[1] / 5) * 3, begin[2] - (amount[2] / 5) * 3], [begin[0] + (amount[0] / 5) * 2, begin[1] + (amount[1] / 5) * 2, begin[2] + (amount[2] / 5) * 2], [begin[0] - (amount[0] / 5) * 1, begin[1] - (amount[1] / 5) * 1, begin[2] - (amount[2] / 5) * 1], [begin[0], begin[1], begin[2]] ] }); } else { return Animation_1.default.samplers.polybezier({ keyframes: [ begin + (amount / 5) * 4, begin - (amount / 5) * 3, begin + (amount / 5) * 2, begin - (amount / 5) * 1, begin ] }); } }, }; exports.Ease = samplers; const degreeToRadian = 0.0174532925; const PFTweenManager = new class { constructor() { this.table = {}; } onKill(id, callback) { if (this.table[id] == undefined) { this.table[id] = []; } this.table[id].push(callback); } kill(id) { if (this.table[id] != undefined) { this.table[id].forEach(callback => callback()); delete this.table[id]; } } hasId(id) { return this.table[id] != undefined; } }; class PFTweenEvent { constructor() { this._events = []; } get events() { return this._events; } invoke(arg) { if (this._events.length > 0) { for (let i = 0; i < this._events.length; i++) { this._events[i](arg); } } ; } invokeOnMonitor(signal) { if (this._events.length == 0) { return; } if (Array.isArray(signal)) { let list = {}; for (let i = 0; i < signal.length; i++) { list[i] = signal[i]; } this._subscription = Reactive_1.default.monitorMany(list, { fireOnInitialValue: true }).subscribe(values => this.invoke(Object.values(values.newValues))); } else { this._subscription = signal.monitor({ fireOnInitialValue: true }).select('newValue').subscribe(value => this.invoke(value)); } } invokeOnEvent(eventSource) { if (this._events.length > 0) { this._subscription = eventSource.subscribe(value => this.invoke(value)); } } add(callback) { this._events.push(callback); } dispose() { if (this._subscription != undefined) { this._subscription.unsubscribe(); this._subscription = undefined; } this._events = []; } } class PFTweenEvents { constructor() { this.onStart = new PFTweenEvent(); this.onComplete = new PFTweenEvent(); this.onLoop = new PFTweenEvent(); this.onUpdate = new PFTweenEvent(); this.onFinally = new PFTweenEvent(); } dispose() { this.onStart.dispose(); this.onComplete.dispose(); this.onLoop.dispose(); this.onUpdate.dispose(); this.onFinally.dispose(); } } class PFTweenClipCancellation { constructor(value) { this.value = value; } cancel() { } } function instanceOfICurveProvider(object) { return 'evaluate' in object; } class PFTween { constructor(begin, end, durationMilliseconds) { begin = toNumber(begin); end = toNumber(end); this.config = { loopCount: undefined, isMirror: false, durationMilliseconds: durationMilliseconds, delayMilliseconds: 0, events: new PFTweenEvents(), id: undefined, useCustomCurve: false, curve: undefined, begin: begin, end: end, sampler: samplers.linear(begin, end), }; } static combine(...clips) { clips = clips.flat(); return value => Promise.all(clips.map(i => i(value))).then(endValues => Promise.resolve(value != undefined ? value : endValues)); } static concat(...clips) { clips = clips.flat(); return value => clips.slice(1).reduce((pre, cur) => pre.then(cur), clips[0](value)); } static combineProgress(...progresses) { progresses = progresses.flat(); const max = Math.max(...progresses.map(pftween => pftween.durationMilliseconds)); return { get durationMilliseconds() { return max; }, setProgress(progress) { for (let i = 0; i < progresses.length; i++) { const tween = progresses[i]; tween.setProgress(Reactive_1.default.fromRange(progress, 0, tween.durationMilliseconds / max)); } } }; } static concatProgerss(...progresses) { progresses = progresses.flat(); return PFTween.concatProgress(progresses); } static concatProgress(...progresses) { progresses = progresses.flat(); const total = progresses .map(pftween => pftween.durationMilliseconds) .reduce((pre, cur) => pre + cur, 0); return { get durationMilliseconds() { return total; }, setProgress(progress) { let start = 0; let startTimings = [0]; for (let i = 0; i < progresses.length; i++) { startTimings.push(start + progresses[i].durationMilliseconds / total); } for (let i = progresses.length - 1; i >= 0; i--) { const tween = progresses[i]; start = startTimings[i]; const end = start + tween.durationMilliseconds / total; tween.setProgress(Reactive_1.default.fromRange(progress, start, end)); } } }; } /** * Stop and unsubscribe all events of the animation. * @param id The id you set for to your animation. */ static kill(id) { PFTweenManager.kill(id); } /** * Check if the animation of this id exist. * @param id * @returns */ static hasId(id) { return PFTweenManager.hasId(id); } /** * Create a cancellation used for interrupt clips. * @param value The args you want to pass to clip. * @returns */ static newClipCancellation(value) { return new PFTweenClipCancellation(value); } setEase() { if (arguments.length == 1 && instanceOfICurveProvider(arguments[0])) { this.config.useCustomCurve = true; this.config.curve = arguments[0]; this.config.sampler = undefined; } else if (arguments.length == 1) { this.config.useCustomCurve = false; this.config.curve = undefined; this.config.sampler = arguments[0](this.config.begin, this.config.end); } return this; } setLoops() { if (arguments.length == 0) { this.config.loopCount = Infinity; } else if (arguments.length == 1 && typeof arguments[0] == 'boolean') { this.config.loopCount = Infinity; this.config.isMirror = arguments[0]; } else if (arguments.length == 1 && typeof arguments[0] == 'number') { this.config.loopCount = arguments[0]; } else if (arguments.length == 2 && typeof arguments[0] == 'number' && typeof arguments[1] == 'boolean') { this.config.loopCount = arguments[0]; this.config.isMirror = arguments[1]; } return this; } setMirror(isMirror = true) { this.config.isMirror = isMirror; return this; } /** * Delay to start animation, this will only delay the first time. */ setDelay(delayMilliseconds) { this.config.delayMilliseconds = delayMilliseconds; return this; } setId(id) { this.config.id = id; return this; } setAutoKill() { if (this.config.id == undefined) { this.setId(Symbol()); } this.config.events.onFinally.add(() => PFTweenManager.kill(this.config.id)); return this; } onStart(callback) { this.config.events.onStart.add(callback); return this; } onComplete(callback) { this.config.events.onComplete.add(callback); return this; } onUpdate(callback) { this.config.events.onUpdate.add(callback); return this; } onLoop(callback) { this.config.events.onLoop.add(callback); return this; } onStartVisible(sceneObject) { this.onStart(() => sceneObject.hidden = Reactive_1.default.val(false)); return this; } onAnimatingVisibleOnly(sceneObject) { this.onStartVisible(sceneObject); this.onCompleteHidden(sceneObject); return this; } onStartHidden(sceneObject) { this.onStart(() => sceneObject.hidden = Reactive_1.default.val(true)); return this; } onCompleteVisible(sceneObject) { this.onComplete(() => sceneObject.hidden = Reactive_1.default.val(false)); return this; } onCompleteHidden(sceneObject) { this.onComplete(() => sceneObject.hidden = Reactive_1.default.val(true)); return this; } onCompleteResetPosition(sceneObject) { const original = Reactive_1.default.pack3(sceneObject.transform.x.pinLastValue(), sceneObject.transform.y.pinLastValue(), sceneObject.transform.z.pinLastValue()); this.onComplete(() => sceneObject.transform.position = original); return this; } onCompleteResetRotation(sceneObject) { const original = { x: sceneObject.transform.rotationX.pinLastValue(), y: sceneObject.transform.rotationY.pinLastValue(), z: sceneObject.transform.rotationZ.pinLastValue(), }; this.onComplete(() => { sceneObject.transform.rotationX = original.x; sceneObject.transform.rotationY = original.y; sceneObject.transform.rotationZ = original.z; }); return this; } onCompleteResetScale(sceneObject) { const original = Reactive_1.default.scale(sceneObject.transform.scaleX.pinLastValue(), sceneObject.transform.scaleY.pinLastValue(), sceneObject.transform.scaleZ.pinLastValue()); this.onComplete(() => sceneObject.transform.scale = original); return this; } onCompleteResetOpacity(material) { const original = material.opacity.pinLastValue(); this.onComplete(() => material.opacity = original); return this; } /** * @deprecated Please use `onStart(callback)` instead. The callback of `onStart` receive the tween value. `bind` is equivalent to `onStart` now. */ bind(callback) { this.onStart(callback); return this; } /** * @deprecated Please use `build()` instead. `apply` is equivalent to `build` now. */ apply(autoPlay = true) { return this.build(autoPlay); } build(autoPlay = true) { const tweener = new PFTweener(this.config); if (autoPlay) { tweener.start(); } return tweener; } /** * Take input numbers and output them in a different order. * Input values correspond to the swizzle value (xyzw) in the order theyre inputted. For example, an input of (1,2,3) and a swizzle value of (yxz) would output (2,1,3). You can also use 0 and 1. For example, a swizzle value of (x01) would output (1,0,1). */ swizzle(specifier) { return this.setAutoKill().build().swizzle(specifier); } /** * Convert * @param name * @returns */ patch(name) { return this.setAutoKill().build().patch(name); } /** * Get an evaluable animation controller. * Please note that the `onCompleteEvent`, `onStartEvent`, `onLoopEvent` won't work. */ get progress() { this.config.events.onComplete.dispose(); this.config.events.onStart.dispose(); this.config.events.onLoop.dispose(); return new PFTweenProgress(this.config); } get clip() { let promiseResolve, resolveResult; this.onComplete(() => promiseResolve(resolveResult && (resolveResult.value || resolveResult.cancel) ? resolveResult : { value: resolveResult })); const tweener = this.build(false); return result => new Promise((resolve, reject) => { PFTweenManager.onKill(this.config.id, () => reject(`PFTween killed: ${String(this.config.id)}`)); resolveResult = result; promiseResolve = resolve; tweener.replay(); if (result instanceof PFTweenClipCancellation) { result.cancel = () => { tweener.stop(); reject('PFTween clip canceled'); }; } }); } get scalar() { return this.setAutoKill().build().scalar; } get pack2() { return this.setAutoKill().build().pack2; } get pack3() { return this.setAutoKill().build().pack3; } get pack4() { return this.setAutoKill().build().pack4; } /** * @deprecated Please use `pack3` instead. `scale` is equivalent to `pack3` now. */ get scale() { return this.pack3; } get quaternion() { return this.setAutoKill().build().quaternion; } get rgba() { return this.setAutoKill().build().rgba; } /** * @deprecated Please use `deg2rad` instead. `rotation` is equivalent to `deg2rad` now. */ get rotation() { return this.setAutoKill().build().rotation; } get deg2rad() { return this.setAutoKill().build().deg2rad; } get durationMilliseconds() { return this.config.durationMilliseconds; } } exports.PFTween = PFTween; class PFTweenValue { constructor(animate) { this.rawValue = animate; } /** * Take input numbers and output them in a different order. * Input values correspond to the swizzle value (xyzw) in the order theyre inputted. For example, an input of (1,2,3) and a swizzle value of (yxz) would output (2,1,3). You can also use 0 and 1. For example, a swizzle value of (x01) would output (1,0,1). */ swizzle(specifier) { return swizzle(this.rawValue, specifier); } patch(name) { if (!Array.isArray(this.rawValue)) { Patches_1.default.inputs.setScalar(name, this.rawValue); } else { switch (this.rawValue.length) { case 2: Patches_1.default.inputs.setPoint2D(name, Reactive_1.default.pack2(this.rawValue[0], this.rawValue[1])); break; case 3: Patches_1.default.inputs.setPoint(name, Reactive_1.default.pack3(this.rawValue[0], this.rawValue[1], this.rawValue[2])); break; case 4: Patches_1.default.inputs.setColor(name, Reactive_1.default.RGBA(this.rawValue[0], this.rawValue[1], this.rawValue[2], this.rawValue[3])); break; default: throw `Unsupported value length: ${this.rawValue.length} values from script to patch with the name '${name}'`; } } } get scalar() { if (Array.isArray(this.rawValue)) { return this.rawValue[0]; } else { return this.rawValue; } } get pack2() { if (Array.isArray(this.rawValue)) { return Reactive_1.default.pack2(this.rawValue[0] ? this.rawValue[0] : 0, this.rawValue[1] ? this.rawValue[1] : 0); } else { return Reactive_1.default.pack2(this.scalar, this.scalar); } } get pack3() { if (Array.isArray(this.rawValue)) { return Reactive_1.default.pack3(this.rawValue[0] ? this.rawValue[0] : 0, this.rawValue[1] ? this.rawValue[1] : 0, this.rawValue[2] ? this.rawValue[2] : 0); } else { return Reactive_1.default.pack3(this.scalar, this.scalar, this.scalar); } } /** * @deprecated Please use `pack3` instead. `scale` is equivalent to `pack3` now. */ get scale() { return this.pack3; } get pack4() { if (Array.isArray(this.rawValue)) { return Reactive_1.default.pack4(this.rawValue[0] ? this.rawValue[0] : 0, this.rawValue[1] ? this.rawValue[1] : 0, this.rawValue[2] ? this.rawValue[2] : 0, this.rawValue[3] ? this.rawValue[3] : 0); } else { return Reactive_1.default.pack4(this.scalar, this.scalar, this.scalar, this.scalar); } } get quaternion() { if (Array.isArray(this.rawValue) && this.rawValue.length == 4) { return Reactive_1.default.quaternion(this.rawValue[3], this.rawValue[0], this.rawValue[1], this.rawValue[2]); } else { throw `The length of tween value' mismatched, 'quaternion' expected 4 numbers but got ${Array.isArray(this.rawValue) ? this.rawValue.length : 1}.`; } } get rgba() { return this.pack4.toRGBA(); } /** * @deprecated Please use `deg2rad` instead. `rotation` is equivalent to `deg2rad` now. */ get rotation() { return this.deg2rad; } get deg2rad() { return this.scalar.mul(degreeToRadian); } } class PFTweenProgress { constructor(config) { this.durationMilliseconds = config.durationMilliseconds; this.config = config; } setProgress(progress) { const driver = Animation_1.default.valueDriver(progress, 0, 1); if (this.config.useCustomCurve) { const progress = Animation_1.default.animate(driver, Animation_1.default.samplers.linear(0, 1)); const y = this.config.curve.evaluate(progress); this.config.events.onUpdate.invokeOnMonitor(Animation_1.default.animate(Animation_1.default.valueDriver(y, 0, 1), Animation_1.default.samplers.linear(this.config.begin, this.config.end))); } else { this.config.events.onUpdate.invokeOnMonitor(Animation_1.default.animate(driver, this.config.sampler)); } } } class PFTweener extends PFTweenValue { constructor(config) { const driver = Animation_1.default.timeDriver({ durationMilliseconds: config.durationMilliseconds, loopCount: config.loopCount, mirror: config.isMirror }); const tween = super((() => { if (config.useCustomCurve) { const progress = Animation_1.default.animate(driver, Animation_1.default.samplers.linear(0, 1)); const y = config.curve.evaluate(progress); return Animation_1.default.animate(Animation_1.default.valueDriver(y, 0, 1), Animation_1.default.samplers.linear(config.begin, config.end)); } else { return Animation_1.default.animate(driver, config.sampler); } })()); // prevent unnecessary subscription if (config.loopCount != Infinity) { config.events.onComplete.invokeOnEvent(driver.onCompleted()); } else { config.events.onComplete.dispose(); } config.events.onLoop.invokeOnEvent(driver.onAfterIteration()); config.events.onUpdate.invokeOnMonitor(tween.rawValue); this.driver = driver; this.config = config; this.play = () => { config.events.onStart.invoke(tween); driver.start(); }; if (config.id != undefined) { PFTweenManager.onKill(config.id, () => { config.events.dispose(); driver.stop(); }); } } start() { if (this.config.delayMilliseconds != 0) { Time_1.default.setTimeout(this.play, this.config.delayMilliseconds); } else { this.play(); } } replay() { this.stop(true); this.start(); } reset() { this.driver.reset(); } reverse() { this.driver.reverse(); } stop(reset = false) { this.driver.stop(); if (reset) { this.driver.reset(); } } get isRunning() { return this.driver.isRunning(); } } /** Convert scalar signal to number, or the signal that contains 'xyzw' to array of numbers.*/ function toNumber(signal) { if (typeof signal == 'number') { return signal; } if (Array.isArray(signal)) { return signal; } if (signal.pinLastValue) { if (typeof signal.pinLastValue() == 'number') { return signal.pinLastValue(); } else { let arr = []; if (signal.x && signal.x.pinLastValue) arr.push(signal.x.pinLastValue()); if (signal.y && signal.y.pinLastValue) arr.push(signal.y.pinLastValue()); if (signal.z && signal.z.pinLastValue) arr.push(signal.z.pinLastValue()); if (signal.w && signal.w.pinLastValue) arr.push(signal.w.pinLastValue()); return arr; } } return undefined; } /** * Take input numbers and output them in a different order. * Input values correspond to the swizzle value (xyzw) in the order theyre inputted. For example, an input of (1,2,3) and a swizzle value of (yxz) would output (2,1,3). You can also use 0 and 1. For example, a swizzle value of (x01) would output (1,0,1). */ function swizzle(value, specifier) { const isArray = Array.isArray(value); const signal = (element) => { const swizzleSignal = (property) => { if (typeof (value) == 'number') { if (property == 'x') { return value; } else { throw `Specifier '${property}' in '${specifier}' can't be used with this signal.`; } } else if (value['pinLastValue'] != undefined && typeof value.pinLastValue() == 'number') { if (property == 'x') { return value; } else { throw `Specifier '${property}' in '${specifier}' can't be used with this signal.`; } } else { if (value[property] == undefined) { throw `Specifier '${property}' in '${specifier}' can't be used with this signal.`; } else { return value[property]; } } }; switch (element) { case '0': return 0; case '1': return 1; case 'x': return isArray ? (value[0] ? value[0] : 0) : swizzleSignal('x'); case 'y': return isArray ? (value[1] ? value[1] : 0) : swizzleSignal('y'); case 'z': return isArray ? (value[2] ? value[2] : 0) : swizzleSignal('z'); case 'w': return isArray ? (value[3] ? value[3] : 0) : swizzleSignal('w'); case 'r': return isArray ? (value[0] ? value[0] : 0) : swizzleSignal('x'); case 'g': return isArray ? (value[1] ? value[1] : 0) : swizzleSignal('y'); case 'b': return isArray ? (value[2] ? value[2] : 0) : swizzleSignal('z'); case 'a': return isArray ? (value[3] ? value[3] : 0) : swizzleSignal('w'); default: throw `Invalid swizzle element specifier: '${element}' in '${specifier}'`; } }; switch (specifier.length) { case 1: return signal(specifier[0]); case 2: return Reactive_1.default.pack2(signal(specifier[0]), signal(specifier[1])); case 3: return Reactive_1.default.pack3(signal(specifier[0]), signal(specifier[1]), signal(specifier[2])); case 4: return Reactive_1.default.pack4(signal(specifier[0]), signal(specifier[1]), signal(specifier[2]), signal(specifier[3])); default: throw `Invalid swizzle specifier: '${specifier}'`; } }