import { Flags } from '@uino/base-thing';
import { Utils } from '../common/Utils';
import { MathUtils } from '../math/MathUtils';
import { BaseComponent } from './BaseComponent';
import { LerpType, LoopType, EventType, SpaceType } from '../const';
const __ = {
private: Symbol('private'),
}
const cMoveToActionName = '__moveTo__';
const cMovePathActionName = '__movePath__';
const cScaleToActionName = '__scaleTo__';
const cRotateToActionName = '__rotateTo__';
const cFadingActionName = '__fading__';
const cFlyingActionNames = ['__positionFlying__', '__targetFlying__'];
const Flag = {
Flying: 1 << 0,
};
let _lerpResults = [];
// #region Private Functions
function _buildAttribute(keys, object, attribute, callback) {
if (Utils.isArray(attribute)) {
return attribute.slice(0);
}
let result = {};
keys.forEach(key => {
let value = attribute[key];
if (Utils.isNull(value)) {
value = object.getAttribute(key);
}
else {
if (object.hasAttribute(key)) {
if (callback) {
callback(key, value);
}
}
}
result[key] = value;
});
return result;
}
function _buildAsyncTrigger(resolve, reject) {
return {
start: () => {
},
stop: () => {
resolve('stop');
},
update: () => {
},
complete: () => {
resolve('complete');
},
onStart: () => {
},
onStop: () => {
resolve('onStop');
},
onUpdate: () => {
},
onComplete: () => {
resolve('onComplete');
},
};
}
function _invokeCallback(object, value, callback, trigger, key) {
if (callback) {
callback({ object, value });
}
if (trigger && trigger[key]) {
trigger[key]();
}
}
function _update(object, from, to, values, onUpdate, progress, beforeCallback, afterCallback) {
let results;
// It's array
if (values.length) {
_lerpResults.length = 0;
results = _lerpResults;
}
// It's object
else {
results = {};
}
// Update values
for (let key in values) {
let value;
// Use slerp to work with quaternion
if (key == 'quaternion' || key == 'localQuaternion') {
value = MathUtils.slerp(to[key + '_start'], to[key], progress);
}
else {
value = values[key];
}
if (beforeCallback) {
beforeCallback(key, value);
}
results[key] = value;
if (afterCallback) {
afterCallback(key, value);
}
}
// Notify outside
if (onUpdate) {
onUpdate({ object, from, to, progress, value: results });
}
}
// Parse lerp to arguments.
function _parseLerpArgs(param, name, trigger) {
let options;
// Check whether it's number->number lerping mode
if (Utils.isNumber(param) && Utils.isNumber(name)) {
let fromNumber = param;
let toNumber = name;
options = {
from: { 'current': fromNumber },
to: { 'current': toNumber }
};
// Use trigger as lerping name
if (Utils.isString(trigger)) {
name = trigger;
}
else {
name = '';
// Check whether trigger is options
if (Utils.isObject(trigger)) {
for (let key in trigger) {
options[key] = trigger[key];
}
trigger = {};
}
}
}
else {
options = Object.assign({}, param);
}
options['name'] = name;
options['trigger'] = trigger;
return options;
}
function _buildUVTransformValues(from, to) {
let uv = {};
if (Utils.isValid(to.rotation)) {
uv.rotation = from.rotation;
}
if (to.offset) {
uv.offset = from.offset.slice(0);
}
if (to.repeat) {
uv.repeat = from.repeat.slice(0);
}
if (to.center) {
uv.center = from.center.slice(0);
}
return uv;
}
function _buildUVTransformName(slotType) {
return `__UVTransform_${slotType.toLowerCase()}__`;
}
function _parseLerpingOptions(options = {}) {
return {
loopType: Utils.parseLoopType(options['loopType']),
lerpType: Utils.parseLerpType(options['lerpType']),
time: options['time'],
duration: options['duration'],
times: options['times'],
loop: options['loop']
};
}
// #endregion
/**
* @class LerpComponent
* The object lerp component.
* @memberof THING
* @extends THING.BaseComponent
* @public
*/
class LerpComponent extends BaseComponent {
static mustCopyWithInstance = true;
/**
* The interpolation of object what could do all smoothing transforming jobs.
*/
constructor() {
super();
this[__.private] = {};
let _private = this[__.private];
_private.flags = new Flags();
_private.tweens = [];
}
// #region Private
_buildLerpParam(trigger, param) {
let { lerpType, loop, loopType, times, duration, time, delayTime } = param;
let onRepeat = Utils.parseValue(param['onRepeat'], param['repeat']);
let onStart = Utils.parseValue(param['onStart'], param['start']);
let onStop = Utils.parseValue(param['onStop'], param['stop']);
let onUpdate = Utils.parseValue(param['onUpdate'], param['update']);
let onComplete = Utils.parseValue(param['onComplete'], param['complete']);
let canTriggerRepeat = false;
return {
lerpType,
loop,
loopType,
times,
duration,
time,
delayTime,
onRepeat: () => {
canTriggerRepeat = true;
},
onStart: () => {
if (onStart) {
onStart({ object: this.object });
}
if (trigger && trigger.onStart) {
trigger.onStart();
}
},
onStop: () => {
if (onStop) {
onStop({ object: this.object });
}
if (trigger && trigger.onStop) {
trigger.onStop();
}
},
onUpdate: (ev) => {
if (canTriggerRepeat) {
canTriggerRepeat = false;
if (onRepeat) {
onRepeat({ object: this.object });
}
if (trigger && trigger.onRepeat) {
trigger.onRepeat();
}
}
if (onUpdate) {
onUpdate(ev, ev.progress);
}
if (trigger && trigger.onUpdate) {
trigger.onUpdate();
}
},
onComplete: () => {
if (onComplete) {
onComplete({ object: this.object });
}
if (trigger && trigger.onComplete) {
trigger.onComplete();
}
}
};
}
_stopTweenByName(name) {
let _private = this[__.private];
let tweens = _private.tweens;
let index = tweens.findIndex(a => { return a.name == name; });
if (index === -1) {
return false;
}
tweens[index].tween.stop();
return true;
}
_removeTweenByName(name) {
let _private = this[__.private];
let tweens = _private.tweens;
let index = tweens.findIndex(a => { return a.name == name; });
if (index === -1) {
return false;
}
tweens._removeAt(index);
return true;
}
_getTweenByName(name) {
let _private = this[__.private];
let tweens = _private.tweens;
let index = tweens.findIndex(a => { return a.name == name; });
if (index === -1) {
return null;
}
return tweens[index].tween;
}
_lerpPoints(value, param, name, trigger) {
// Parse arguments
let loopType = Utils.parseLoopType(param['loopType']);
let onRepeat = Utils.parseValue(param['onRepeat'], param['repeat']);
let onStart = Utils.parseValue(param['onStart'], param['start']);
let onStop = Utils.parseValue(param['onStop'], param['stop']);
let onNext = Utils.parseValue(param['onNext'], param['next']);
let onUpdate = Utils.parseValue(param['onUpdate'], param['update']);
let onComplete = Utils.parseValue(param['onComplete'], param['complete']);
// Copy path to move
let path = value.slice(0);
// Check whether it's closure path
if (param['closure']) {
path.push(value[0]);
}
// Move object to start position
let object = this.object;
// The reverse mode
let reverseMode = false;
// The current index of path
let curIndex = -1;
// Build lerp params
let lerpParam = Object.assign({}, param);
lerpParam['onRepeat'] = () => {
// Reset start index
curIndex = -1;
if (loopType == LoopType.PingPong) {
reverseMode = !reverseMode;
}
else {
// Set the initial position of the object as the starting point of the path.
object.position = path[0];
}
if (onRepeat) {
onRepeat({ object, from: path[0], to: path[1] });
}
};
lerpParam['onStart'] = () => {
// Set the initial position of the object as the starting point of the path.
object.position = path[0];
if (onStart) {
onStart({ object, from: path[0], to: path[1] });
}
};
lerpParam['onStop'] = () => {
if (onStop) {
onStop({ object });
}
};
lerpParam['onUpdate'] = (ev) => {
let progress = ev.value.progress;
// Get the position by progress
let { point, index, from, to } = MathUtils.lerpPoints(path, steps, progress);
// Exchange from and to if it's in reserve mode
if (reverseMode) {
let cur = from;
from = to;
to = cur;
}
// Check whether reach next path
if (curIndex != index) {
if (to) {
if (onNext) {
onNext({ object, from, to });
}
}
curIndex = index;
}
// Notify outside
if (onUpdate) {
onUpdate({ object, point, from, to, progress });
}
};
lerpParam['onComplete'] = () => {
if (onComplete) {
onComplete({ object, progress: 1 });
}
};
// Start to lerp points
let options = this._buildLerpParam(trigger, lerpParam);
// Get the total distance of path
let steps = MathUtils.getPointsSteps(path);
if (!steps.length) {
if (options.onComplete) {
options.onComplete();
}
return;
}
options['from'] = { progress: 0 };
options['to'] = { progress: 1 };
this.to(options, name);
}
_lookAt(object, from, to, up) {
up = up || object.up;
object.quaternion = MathUtils.getQuatFromTarget(from, to, up);
}
// #endregion
// #region BaseComponent Interface
onImport(param) {
let uv = param['uv'];
if (uv) {
for (let key in uv) {
let value = uv[key]['value'];
let options = _parseLerpingOptions(uv[key]['options']);
this.uvTransformTo(key, value, options);
}
}
}
onRemove() {
this.stopAll();
super.onRemove();
}
// #endregion
// #region Common
stopAll() {
let _private = this[__.private];
// We must copy tweens here to prevent missing stop tween action with 'tween.stop()' interface
let tweens = _private.tweens.slice(0);
tweens.forEach(object => {
object.tween.stop();
});
_private.tweens.length = 0;
}
pauseAll() {
let _private = this[__.private];
_private.tweens.forEach(object => {
object.tween.pause();
});
}
resumeAll() {
let _private = this[__.private];
_private.tweens.forEach(object => {
object.tween.resume();
});
}
pause(name) {
if (!name) {
return;
}
if (Utils.isArray(name)) {
name.forEach(n => {
this.pause(n);
});
}
else {
let tween = this._getTweenByName(name);
if (tween) {
tween.pause();
}
}
}
resume(name) {
if (!name) {
return;
}
if (Utils.isArray(name)) {
name.forEach(n => {
this.resume(n);
});
}
else {
let tween = this._getTweenByName(name);
if (tween) {
tween.resume();
}
}
}
stop(name) {
if (!name) {
return;
}
if (Utils.isArray(name)) {
name.forEach(n => {
this._stopTweenByName(n);
});
}
else {
this._stopTweenByName(name);
}
}
/**
* Start lerp.
* @param {LerpArgs} param The parameters.
* @param {String} name The lerp name, if user want to stop it later then need to provide it.
* @public
*/
to(param = {}, name, trigger) {
let options = _parseLerpArgs(param, name, trigger);
// Get the binding object
let object = this.object;
// Parse arguments
let syncMode = Utils.parseValue(options['syncMode'], true);
let from = options['from'] || {};
let to = options['to'] || {};
let lerpType = Utils.parseLerpType(options['lerpType']) || LerpType.Linear.None;
let loopType = Utils.parseLoopType(options['loopType']);
let times = Utils.parseValue(options['times'], -1);
let duration = Utils.parseNumber(options['duration'], Utils.parseNumber(options['time'], 1000));
let delayTime = Utils.parseValue(options['delayTime'], 0);
let onRepeat = Utils.parseValue(options['onRepeat'], options['repeat']);
let onStart = Utils.parseValue(options['onStart'], options['start']);
let onStop = Utils.parseValue(options['onStop'], options['stop']);
let onResume = Utils.parseValue(options['onResume'], options['resume']);
let onPause = Utils.parseValue(options['onPause'], options['pause']);
let onUpdate = Utils.parseValue(options['onUpdate'], options['update']);
let onComplete = Utils.parseValue(options['onComplete'], options['complete']);
let noInterp = Utils.parseBoolean(options['noInterp'], false);
let loop = options['loop'];
if (Utils.isValid(loop)) {
// true indicates -1, false indicates 1
if (Utils.isBoolean(loop)) {
times = loop ? -1 : 1;
} else {
times = loop;
}
if (!Utils.isValid(loopType)) {
loopType = LoopType.Repeat;
}
}
// Get unique keys
let keys = Utils.getUnionKeys(from, to);
// Build the source attributes
let fromAttribute = _buildAttribute(keys, object, from, (key, value) => {
if (syncMode) {
object.setAttribute(key, value);
}
});
// Build the target attributes
let toAttribute = _buildAttribute(keys, object, to);
// Copy quaterion for lerping
if (fromAttribute['quaternion']) {
toAttribute['quaternion_start'] = fromAttribute['quaternion'].slice(0);
}
else if (fromAttribute['localQuaternion']) {
toAttribute['localQuaternion_start'] = fromAttribute['localQuaternion'].slice(0);
}
// Clone original data
let origin = Object.assign({}, fromAttribute);
// Stop current action
if (options['name']) {
this.stop(options['name']);
}
// Get the update attribute callback function
let _onBeforeUpdateAttributeCallback = options['onBeforeUpdateAttribute'];
let _onAfterUpdateAttributeCallback = syncMode ? (key, value) => {
object.setAttribute(key, value);
} : options['onAfterUpdateAttribute'];
let _private = this[__.private];
// Do it now, because it not provide any time info
if (!duration && !delayTime) {
_invokeCallback(object, fromAttribute, onStart, options['trigger'], 'onStart');
_update(object, origin, toAttribute, toAttribute, onUpdate, 1, _onBeforeUpdateAttributeCallback, _onAfterUpdateAttributeCallback);
_invokeCallback(object, fromAttribute, onComplete, options['trigger'], 'onComplete');
}
// Start to lerp
else {
let tween = this.app.tweenManager.lerpTo(fromAttribute, toAttribute, duration, delayTime, { noInterp })
.times(times)
.easing(lerpType)
.looping(loopType)
.onRepeat(ev => {
_invokeCallback(object, ev.value, onRepeat, options['trigger'], 'onRepeat');
})
.onStart(ev => {
_invokeCallback(object, ev.value, onStart, options['trigger'], 'onStart');
})
.onParseValue((start, end, key) => {
if (key === 'color') {
start[key] = Utils.parseColor(start[key]);
end[key] = Utils.parseColor(end[key]);
}
})
.onStop(ev => {
_invokeCallback(object, ev.value, onStop, options['trigger'], 'onStop');
this._removeTweenByName(options['name']);
})
.onResume(ev => {
_invokeCallback(object, ev.value, onResume, options['trigger'], 'onResume');
})
.onPause(ev => {
_invokeCallback(object, ev.value, onPause, options['trigger'], 'onPause');
})
.onUpdate((ev, progress) => {
_update(object, origin, toAttribute, ev.value, onUpdate, progress, _onBeforeUpdateAttributeCallback, _onAfterUpdateAttributeCallback);
})
.onComplete(ev => {
this._removeTweenByName(options['name']);
_invokeCallback(object, ev.value, onComplete, options['trigger'], 'onComplete');
})
.start();
// Bind name and easy to debug
if (_DEBUG) {
tween._tween._name = options['name'];
}
// Update tweens
_private.tweens.push({ name: options['name'], tween });
}
}
/**
* Lerp to in duration (async).
* @param {LerpArgs} param The parameters.
* @param {String} name The lerp name, if user want to stop it later then need to provide it.
* @returns {Promise<any>}
* @private
*/
toAsync(param = {}, name) {
return new Promise((resolve, reject) => {
this.to(param, name, _buildAsyncTrigger(resolve, reject));
});
}
lerpPoints(value, param = {}, name = '', trigger = null) {
if (name) {
this.stop(name);
}
this._lerpPoints(value, param, name, trigger);
}
lerpPointsAsync(value, param = {}, name = '') {
return new Promise((resolve, reject) => {
this.lerpPoints(value, param, name, _buildAsyncTrigger(resolve, reject));
});
}
// #endregion
// #region Moving
stopMoving() {
this.stop(cMoveToActionName);
this.stop(cMovePathActionName);
}
pauseMoving() {
this.pause(cMoveToActionName);
this.pause(cMovePathActionName);
}
resumeMoving() {
this.resume(cMoveToActionName);
this.resume(cMovePathActionName);
}
moveForward(distance, param = {}) {
let object = this.object;
let target = MathUtils.getPositionOnDirection(object.position, object.forward, distance);
this.moveTo(target, param);
}
moveTo(value, param = {}, trigger = null) {
this.stopMoving();
if (!Utils.isArray(value)) {
param = value;
value = param['to'];
}
let object = this._object;
// Build lerp params
let lerpParam = this._buildLerpParam(trigger, param);
// Set the target position
let spaceType = Utils.parseValue(param['spaceType'], SpaceType.World);
if (spaceType == SpaceType.World) {
lerpParam['to'] = { position: value };
}
else {
lerpParam['to'] = { localPosition: value };
}
// Set object's quaternion
let orientToPath = Utils.parseValue(param['orientToPath'], true);
if (orientToPath) {
let from = object.position;
let to = lerpParam['to'];
let up = param['up'];
if (!MathUtils.equalsVector3(object.position, value)) {
this._lookAt(object, object.position, value, up);
}
if (lerpParam['loopType'] == LoopType.PingPong) {
let onRepeat = lerpParam['onRepeat'];
lerpParam['onRepeat'] = (ev) => {
// Swap from and to position
let temp = from;
from = to.position || to;
to = temp;
// Set object's quaternion
this._lookAt(object, from, to, up);
if (onRepeat) {
onRepeat(ev);
}
};
}
}
// Start to move
this.to(lerpParam, cMoveToActionName);
}
moveToAsync(value, param = {}) {
return new Promise((resolve, reject) => {
this.moveTo(value, param, _buildAsyncTrigger(resolve, reject));
});
}
movePath(value, param = {}, trigger = null) {
this.stopMoving();
if (Utils.isObject(value)) {
param = value;
value = param['path'];
}
let orientToPath = Utils.parseValue(param['orientToPath'], true);
let spaceType = Utils.parseValue(param['spaceType'], SpaceType.World);
let up = param['up'];
let object = this._object;
let onUpdate = Utils.parseValue(param['onUpdate'], param['update']);
param['onUpdate'] = (ev) => {
let point = ev.point;
// Set the target position
if (spaceType == SpaceType.World) {
object.position = point;
}
else {
object.localPosition = point;
}
if (onUpdate) {
onUpdate(ev);
}
};
let onNext = Utils.parseValue(param['onNext'], param['next']);
param['onNext'] = (ev) => {
let { from, to } = ev;
// Set object's quaternion
if (orientToPath) {
this._lookAt(object, from, to, up);
}
if (onNext) {
onNext(ev);
}
};
this._lerpPoints(value, param, cMovePathActionName, trigger);
}
movePathAsync(value, param = {}) {
return new Promise((resolve, reject) => {
this.movePath(value, param, _buildAsyncTrigger(resolve, reject));
});
}
// #endregion
// #region Scaling
stopScaling() {
this.stop(cScaleToActionName);
}
pauseScaling() {
this.pause(cScaleToActionName);
}
resumeScaling() {
this.resume(cScaleToActionName);
}
scaleTo(value, param = {}, trigger) {
this.stopScaling();
if (!Utils.isArray(value)) {
param = value;
value = param['to'];
}
// Build lerp params
let lerpParam = this._buildLerpParam(trigger, param);
// Set the target scale
let spaceType = Utils.parseValue(param['spaceType'], SpaceType.World);
if (spaceType == SpaceType.World) {
lerpParam['to'] = { scale: value };
}
else {
lerpParam['to'] = { localScale: value };
}
// Start to scale
this.to(lerpParam, cScaleToActionName);
}
scaleToAsync(value, param = {}) {
return new Promise((resolve, reject) => {
this.scaleTo(value, param, _buildAsyncTrigger(resolve, reject));
});
}
// #endregion
// #region Rotating
stopRotating() {
this.stop(cRotateToActionName);
}
pauseRotating() {
this.pause(cRotateToActionName);
}
resumeRotating() {
this.resume(cRotateToActionName);
}
rotateTo(value, param = {}, trigger) {
this.stopRotating();
if (!Utils.isArray(value)) {
param = value;
value = param['to'];
}
// Build lerp params
let lerpParam = this._buildLerpParam(trigger, param);
// Set the target angels
let spaceType = Utils.parseValue(param['spaceType'], SpaceType.Local);
if (spaceType == SpaceType.World) {
if (value.length == 3) {
lerpParam['to'] = { angles: value };
}
else {
lerpParam['to'] = { quaternion: MathUtils.getQuatFromAngles(value) };
}
}
else {
if (value.length == 3) {
lerpParam['to'] = { localAngles: value };
}
else {
lerpParam['to'] = { localQuaternion: MathUtils.getQuatFromAngles(value) };
}
}
// Start to rotate
this.to(lerpParam, cRotateToActionName);
}
rotateToAsync(value, param = {}) {
return new Promise((resolve, reject) => {
this.rotateTo(value, param, _buildAsyncTrigger(resolve, reject));
});
}
// #endregion
// #region Fading
stopFading() {
this.stop(cFadingActionName);
}
pauseFading() {
this.pause(cFadingActionName);
}
resumeFading() {
this.resume(cFadingActionName);
}
fadeIn(param = {}, trigger) {
this.stopFading();
// Get the binding object
let object = this.object;
if (!object.style) {
return;
}
// Build lerp params
let lerpParam = this._buildLerpParam(trigger, param);
lerpParam['from'] = {
style: {
opacity: 0
}
};
lerpParam['to'] = {
style: {
opacity: 1
}
};
// Start to fade
this.to(lerpParam, cFadingActionName);
}
fadeOut(param = {}, trigger) {
this.stopFading();
// Get the binding object
let object = this.object;
if (!object.style) {
return;
}
// Build lerp params
let lerpParam = this._buildLerpParam(trigger, param);
lerpParam['from'] = {
style: {
opacity: 1
}
};
lerpParam['to'] = {
style: {
opacity: 0
}
};
// Start to fade
this.to(lerpParam, cFadingActionName);
}
fadeInAsync(param = {}) {
return new Promise((resolve, reject) => {
this.fadeIn(param, _buildAsyncTrigger(resolve, reject));
});
}
fadeOutAsync(param = {}) {
return new Promise((resolve, reject) => {
this.fadeOut(param, _buildAsyncTrigger(resolve, reject));
});
}
// #endregion
// #region Flying
/**
* Get the fly info.
* @param {Object} param The parameters.
* @param {Array<Number>} position The start position.
* @param {Array<Number>|THING.BaseObject} target The target position.
* @param {Array<Number>} up The up direction.
* @param {Number} distance The distance(only works for object target mode).
* @param {Number} horzAngle The horz angle(only works for object target mode).
* @param {Number} vertAngle The vert angle(only works for object target mode).
* @returns {Object}
* @private
*/
getFlyInfo(param = {}) {
let position = param['position'];
let target = param['target'];
let up = param['up'];
let object = this.object;
// Build position info
let result = {
position: {
from: object.position,
to: position ? position : object.position,
}
};
// Build target info
let targetPosition = object.target;
if (targetPosition) {
result.target = {
from: targetPosition,
to: null,
};
}
// Build up info
if (up) {
result.up = {
from: object.up,
to: up,
};
}
// It's object
if (target.isBaseObject) {
let aabb = target.getAABB();
// If we do not provide position then try to calculate by angles and distance
if (!position) {
// If do not provide any angles then use 45 as default degree
let defaultDegree = 0;
if (Utils.isNull(param['horzAngle']) && Utils.isNull(param['vertAngle'])) {
defaultDegree = 45;
}
// We make distance a little more far
let radiusFactor = Utils.parseValue(param['radiusFactor'], 2.5);
let distance = Utils.parseValue(param['distance'], aabb.radius * radiusFactor);
let horzAngle = Utils.parseValue(param['horzAngle'], defaultDegree);
let vertAngle = Utils.parseValue(param['vertAngle'], defaultDegree);
// Prevent horz angles calculation bug
if (vertAngle == 90) {
vertAngle -= 0.1;
}
let offset = MathUtils.getOffsetFromAngles(horzAngle, vertAngle, distance);
result.position.to = MathUtils.addVector(aabb.center, offset);
}
if (result.target) {
result.target.to = aabb.center;
}
}
// It's position
else {
if (result.target) {
result.target.to = target;
}
}
return result;
}
stopFlying() {
this.stop(cFlyingActionNames);
}
pauseFlying() {
this.pause(cFlyingActionNames);
}
resumeFlying() {
this.resume(cFlyingActionNames);
}
flyTo(param = {}, trigger) {
let _private = this[__.private];
param = Utils.parseFlyParam(param);
// Parse arguments
let duration = param['duration'];
let time = param['time'];
let delayTime = Utils.parseValue(param['delayTime'], 0);
let lerpType = param['lerpType'] || LerpType.Linear.None;
let positionLerpType = Utils.parseValue(param['positionLerpType'], lerpType);
let targetLerpType = Utils.parseValue(param['targetLerpType'], lerpType);
let upLerpType = Utils.parseValue(param['upLerpType'], lerpType);
let onStart = Utils.parseValue(param['onStart'], param['start']);
let onStop = Utils.parseValue(param['onStop'], param['stop']);
let onResume = Utils.parseValue(param['onResume'], param['resume']);
let onPause = Utils.parseValue(param['onPause'], param['pause']);
let onUpdate = Utils.parseValue(param['onUpdate'], param['update']);
let onComplete = Utils.parseValue(param['onComplete'], param['complete']);
// Prepare for flying
let that = this;
// Stop previous flying action
this.stop(cFlyingActionNames);
// Parse fly info
let info = this.getFlyInfo(param);
// Start to fly with position
this.to({
syncMode: false,
from: info.position.from,
to: info.position.to,
duration,
time,
delayTime,
lerpType: positionLerpType,
onStart: function () {
_private.flags.enable(Flag.Flying, true);
that.object.trigger(EventType.StartFlying);
if (onStart) {
onStart({ object: that.object });
}
if (trigger && trigger.onStart) {
trigger.onStart();
}
},
onStop: function () {
_private.flags.enable(Flag.Flying, false);
that.object.trigger(EventType.StopFlying);
if (onStop) {
onStop({ object: that.object });
}
if (trigger && trigger.onStop) {
trigger.onStop();
}
},
onResume: function () {
_private.flags.enable(Flag.Flying, true);
if (onResume) {
onResume({ object: that.object });
}
},
onPause: function () {
_private.flags.enable(Flag.Flying, false);
if (onPause) {
onPause({ object: that.object });
}
},
onUpdate: function (ev) {
that.object.position = ev.value;
that.object.trigger(EventType.Flying);
if (onUpdate) {
onUpdate({ progress: ev.progress, object: that.object });
}
if (trigger && trigger.onUpdate) {
trigger.onUpdate();
}
},
onComplete: function () {
_private.flags.enable(Flag.Flying, false);
that.object.trigger(EventType.CompleteFlying);
if (onComplete) {
onComplete({ object: that.object });
}
if (trigger && trigger.onComplete) {
trigger.onComplete();
}
}
}, cFlyingActionNames[0]);
// Start to fly with target
if (info.target) {
// Start to fly with up
if (info.up) {
this.to({
syncMode: false,
from: { up: info.up.from, target: info.target.from },
to: { up: info.up.to, target: info.target.to },
duration,
time,
delayTime,
lerpType: upLerpType,
onUpdate: function (ev) {
let up = ev.value.up;
that.object.lookAt(ev.value.target, { up });
}
}, cFlyingActionNames[1]);
}
else {
this.to({
syncMode: false,
from: info.target.from,
to: info.target.to,
duration,
time,
delayTime,
lerpType: targetLerpType,
onUpdate: function (ev) {
that.object.lookAt(ev.value);
}
}, cFlyingActionNames[1]);
}
}
}
flyToAsync(param = {}) {
return new Promise((resolve, reject) => {
this.flyTo(param, _buildAsyncTrigger(resolve, reject));
});
}
fit(param = {}) {
this.stopFlying();
param = Utils.parseFlyParam(param);
param['target'] = param['target'] || this.app.root;
let flyInfo = this.getFlyInfo(param);
this.object.position = flyInfo.position.to;
if (flyInfo.target) {
this.object.lookAt(flyInfo.target.to);
}
}
get isFlying() {
let _private = this[__.private];
return _private.flags.has(Flag.Flying);
}
// #endregion
// #region UV
stopUVTransform(slotType) {
if (!slotType) {
return;
}
this.stop(_buildUVTransformName(slotType));
}
pauseUVTransform(slotType) {
if (!slotType) {
return;
}
this.pause(_buildUVTransformName(slotType));
}
resumeUVTransform(slotType) {
if (!slotType) {
return;
}
this.resume(_buildUVTransformName(slotType));
}
uvTransformTo(slotType, value, param, trigger) {
if (!slotType) {
return;
}
// We can provide value as param to run
if (param === undefined) {
param = value;
}
this.stopUVTransform();
// Get the UV transform info
let object = this.object;
let style = object.style;
let from = param['from'] || style.getUV(slotType);
let to = param['to'] || value;
// Build lerp params
let lerpParam = this._buildLerpParam(trigger, param);
lerpParam['from'] = _buildUVTransformValues(from, to);
lerpParam['to'] = to;
lerpParam['syncMode'] = false;
lerpParam['onAfterUpdateAttribute'] = (key, value) => {
if (object.invisible) {
return;
}
style.setUV(slotType, key, value);
};
// Start to rotate
this.to(lerpParam, _buildUVTransformName(slotType));
}
uvTransformToAsync(slotType, value, param) {
if (!slotType) {
return;
}
return new Promise((resolve, reject) => {
this.uvTransformTo(slotType, value, param, _buildAsyncTrigger(resolve, reject));
});
}
// #endregion
}
export { LerpComponent }