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, direction, beforeCallback, afterCallback) {
	let results;

	// It's array
	if (values.length) {
		_lerpResults.length = 0;

		results = _lerpResults;
	}
	// It's object
	else {
		results = {};
	}

	let index = 0;
	// 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, index);
		}
		index++;
	}

	// Notify outside
	if (onUpdate) {
		onUpdate({ object, from, to, progress, value: results, direction, });
	}
}

// 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
 * 插值计算组件
 * @memberof THING
 * @extends THING.BaseComponent
 * @public
 */
class LerpComponent extends BaseComponent {

	static mustCopyWithInstance = true;

	/**
	 * 提供物体插值计算的功能组件
	 * </br>用于执行平滑的变换操作的物体插值计算功能组件。
	 */
	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;

		// The direction of path
		let direction = 1

		let fromIndex = 0, toIndex = 1;

		// Build lerp params
		let lerpParam = Object.assign({}, param);
		lerpParam['onRepeat'] = () => {
			// Reset start index
			curIndex = -1;
			if (loopType == LoopType.PingPong) {
				reverseMode = !reverseMode;
				direction = -direction;;
			}
			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);

			if (progress !== 1) {
				if (reverseMode) {
					fromIndex = index + 1;
					toIndex = index;
				} else {
					fromIndex = index;
					toIndex = index + 1;
				}
			}
			else {
				fromIndex = index - 1;
				toIndex = index;
			}

			// 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, direction, fromIndex, toIndex });
			}
		};
		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;
		if (MathUtils.equalsVector3(from, to)) {
			return;
		}

		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

	/**
	 * 停止全部插值
	 * @public
	 * @example
	 * let lerp = box.lerp;
	 * lerp.stopAll();
	 */
	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;
	}

	/**
	 * 暂停全部插值
	 * @public
	 * @example
	 * let lerp = box.lerp;
	 * lerp.pauseAll();
	 */
	pauseAll() {
		let _private = this[__.private];

		_private.tweens.forEach(object => {
			object.tween.pause();
		});
	}

	/**
	 * 恢复全部插值
	 * @public
	 * @example
	 * let lerp = box.lerp;
	 * lerp.resumeAll();
	 */
	resumeAll() {
		let _private = this[__.private];

		_private.tweens.forEach(object => {
			object.tween.resume();
		});
	}

	/**
	 * 暂停插值
	 * @param {string} name 插值的名称,
	 * @public
	 * @example
	 * let lerp = box.lerp;
	 * lerp.pause('move');
	 */
	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();
			}
		}
	}

	/**
	 * 恢复插值
	 * @param {string} name 插值的名称,
	 * @public
	 * @example
	 * let lerp = box.lerp;
	 * lerp.resume('move');
	 */
	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();
			}
		}
	}

	/**
	 * 停止插值
	 * @param {string} name 插值的名称,
	 * @public
	 * @example
	 * let lerp = box.lerp;
	 * lerp.stop('move');
	 */
	stop(name) {
		if (!name) {
			return;
		}

		if (Utils.isArray(name)) {
			name.forEach(n => {
				this._stopTweenByName(n);
			});
		}
		else {
			this._stopTweenByName(name);
		}
	}

	/**
	 * 开始插值
	 * @param {LerpArgs} param 插值参数
	 * @param {string} name 插值的名称,停止插值时需要传入该参数
	 * @param {Function} trigger 触发器函数
	 * @public
	 * @example
	 * box.lerp.to({
	 *   from: {
	 *     position: [0, 0, 0]
	 *   },
	 *   to: {
	 *     position: [10, 10, 10]
	 *   },
	 *   time: 5000,
	 *   loopType: THING.LoopType.PingPong,
	 *   onUpdate: (e) => {
	 *     console.log(e.from, e.to, e.direction);
	 *   }
	 * })
	 */
	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);


		const directUpdateKeys = []
		keys.forEach(key => {
			directUpdateKeys.push(key.indexOf('/') === -1);
		})

		// 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 = THING.Utils.cloneObject(fromAttribute, false)

		let direction = 1;

		// 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, index) => {
			if (directUpdateKeys[index]) {
				object[key] = value;
			}
			else {
				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, direction, _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 => {
					if (loopType === LoopType.PingPong) {
						direction = -direction;
					}
					_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) => {
					if (!object.destroyed) {
						_update(object, origin, toAttribute, ev.value, onUpdate, progress, direction, _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>} 返回一个Promise对象
	 * @private
	 * @example
	 * box.lerp.toAsync({
	 *   from: {
	 *     position: [0, 0, 0]
	 *   },
	 *   to: {
	 *     position: [10, 10, 10]
	 *   },
	 * })
	 */
	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);
	}

	/**
	 * 将物体移动到指定位置
	 * @param {Array|object} value - 目标位置坐标数组[x,y,z]或包含移动参数的对象
	 * @param {object} param - 移动参数配置对象
	 * @param {Array<number>} param.to - 目标位置
	 * @param {Array<number>} param.target - 目标位置,与 to 参数作用相同
	 * @param {number} param.time - 移动时间(毫秒)
	 * @param {THING.SpaceType} param.spaceType - 坐标空间类型
	 * @param {boolean} param.orientToPath - 是否朝向移动方向
	 * @param {number} param.orientToPathDegree - 朝向偏移角度,在朝向路径方向的基础上偏移的角度,正值为顺时针,负值为逆时针
	 * @param {number} param.times - 循环次数
	 * @param {THING.LoopType} param.loopType - 循环类型
	 * @param {Function} param.onUpdate - 更新回调函数
	 * @param {Function} param.onComplete - 完成回调函数
	 * @param {Function} trigger - 触发器函数
	 * @returns {void}
	 * @example
	 * // 基础用法 - 移动到世界坐标系中的位置
	 * object.lerp.moveTo([10, 0, 10]);
	 *
	 * // 使用参数对象方式
	 * object.lerp.moveTo({
	 *   to: [10, 0, 10],           						// 目标位置
	 *   time: 2000,               							// 移动时间(毫秒)
	 *   spaceType: THING.SpaceType.World,    	// 坐标空间类型
	 *   orientToPath: true,       							// 是否朝向移动方向
	 *   orientToPathDegree: 45,                 // 朝向偏移角度
	 *   times: 4,                             	// 循环次数
	 *   loopType: THING.LoopType.PingPong,    	// 循环类型
	 *   onUpdate: (ev) => {      							// 更新回调
	 *     console.log(ev.from, ev.to, ev.direction);
	 *     console.log('Progress:', ev.progress);
	 *   },
	 *   onComplete: () => {      							// 完成回调
	 *     console.log('Movement completed');
	 *   }
	 * });
	 * @public
	 */
	moveTo(value, param = {}, trigger = null) {
		this.stopMoving();

		if (!Utils.isArray(value)) {
			param = value;
			value = param['to'];
		}

		let object = this._object;

		const originOnUpdate = param['onUpdate'];
		const onUpdate = (ev) => {
			const progress = ev.progress;
			const direction = ev.direction;
			const from = ev.from.localPosition || ev.from.position;
			const to = ev.to.localPosition || ev.to.position;
			const value = ev.value.localPosition || ev.value.position;

			const param = {
				object: ev.object,
				from: direction === 1 ? from : to,
				to: direction === 1 ? to : from,
				value,
				progress,
				direction,
			}

			originOnUpdate && originOnUpdate(param);
		}
		param['onUpdate'] = onUpdate;

		// Build lerp params
		let lerpParam = this._buildLerpParam(trigger, param);

		// Set the target position
		let spaceType = Utils.parseValue(param['spaceType'], SpaceType.World);
		switch (spaceType) {
			case SpaceType.World:
				lerpParam['to'] = { position: value };
				break;
			case SpaceType.Local:
				lerpParam['to'] = { localPosition: value };
				break;
			case SpaceType.Self:
				lerpParam['to'] = { position: object.selfToWorld(value) };
				break;
		}

		// Set object's quaternion
		let orientToPath = Utils.parseValue(param['orientToPath'], true);
		let orientToPathDegree = Utils.parseValue(param['orientToPathDegree'], 0);
		if (orientToPath) {
			let from = spaceType == SpaceType.Local ? object.localPosition : object.position;
			let to = lerpParam['to'];
			let up = param['up'] || [0, 1, 0];

			let target = value;
			if (spaceType == SpaceType.Self) {
				target = object.selfToWorld(value);
			} else if (spaceType == SpaceType.Local) {
				target = object.localToWorld(value);
			}

			// Apply orientToPathDegree if needed
			if (orientToPathDegree !== 0) {
				let direction = MathUtils.subVector(target, object.position);
				direction = MathUtils.rotateVector(direction, up, orientToPathDegree);
				target = MathUtils.addVector(object.position, direction);
			}

			this._lookAt(object, object.position, target, 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.localPosition || to;
					to = temp;

					let tempFrom = from, tempTo = to;
					if (spaceType == SpaceType.Local) {
						tempFrom = object.localToWorld(from);
						tempTo = object.localToWorld(to);
					}

					// Apply orientToPathDegree for return journey
					if (orientToPathDegree !== 0) {
						let direction = MathUtils.subVector(tempTo, tempFrom);
						direction = MathUtils.rotateVector(direction, up, orientToPathDegree);
						tempTo = MathUtils.addVector(tempFrom, direction);
					}

					this._lookAt(object, tempFrom, tempTo, 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));
		});
	}

	/**
	 * 将物体沿指定路径移动
	 * @param {Array|object} value - 目标路径坐标数组或包含移动参数的对象
	 * @param {object} param - 移动参数配置对象
	 * @param {Array<Array<number>>} param.path - 移动路径坐标数组
	 * @param {number} param.time - 移动时间(毫秒)
	 * @param {THING.SpaceType} param.spaceType - 坐标空间类型
	 * @param {boolean} param.orientToPath - 是否朝向移动方向
	 * @param {number} param.orientToPathDegree - 朝向偏移角度,在朝向路径方向的基础上偏移的角度,正值为顺时针,负值为逆时针
	 * @param {number} param.times - 循环次数
	 * @param {THING.LoopType} param.loopType - 循环类型
	 * @param {Function} param.onUpdate - 更新回调函数
	 * @param {Function} param.onComplete - 完成回调函数
	 * @param {Function} trigger - 触发器函数
	 * @returns {void}
	 * @example
	 * // 基础用法 - 直接传入路径数组
	 * object.lerp.movePath([
	 *   [0, 0, 0],
	 *   [10, 10, 0],
	 *   [10, 10, 10],
	 * ]);
	 *
	 * // 使用参数对象方式
	 * object.lerp.movePath({
	 *   path: [                              // 移动路径
	 *     [0, 0, 0],
	 *     [10, 10, 0],
	 *     [10, 10, 10],
	 *   ],
	 *   time: 2000,               							// 移动时间(毫秒)
	 *   spaceType: THING.SpaceType.World,    	// 坐标空间类型
	 *   orientToPath: true,       							// 是否朝向移动方向
	 *   orientToPathDegree: 45,                 // 朝向偏移角度
	 *   times: 4,                             	// 循环次数
	 *   loopType: THING.LoopType.PingPong,    	// 循环类型
	 *   onUpdate: (ev) => {      							// 更新回调
	 *     console.log(ev.from, ev.to, ev.fromIndex, ev.toIndex, ev.direction);
	 *     console.log('Progress:', ev.progress);
	 *   },
	 *   onComplete: () => {      							// 完成回调
	 *     console.log('Movement completed');
	 *   }
	 * });
	 * @public
	 */
	movePath(value, param = {}, trigger = null) {
		this.stopMoving();

		if (Utils.isObject(value)) {
			param = value;
			value = param['path'];
		}

		let orientToPath = Utils.parseValue(param['orientToPath'], true);
		let orientToPathDegree = Utils.parseValue(param['orientToPathDegree'], 0);
		let spaceType = Utils.parseValue(param['spaceType'], SpaceType.World);
		let up = param['up'] || [0, 1, 0];

		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;
			if (spaceType == SpaceType.Self) {
				from = object.parent.selfToWorld(from);
				to = object.parent.selfToWorld(to);
			} else if (spaceType == SpaceType.Local) {
				from = object.localToWorld(from);
				to = object.localToWorld(to);
			}

			// Set object's quaternion
			if (orientToPath) {
				// Apply orientToPathDegree if needed
				if (orientToPathDegree !== 0) {
					let direction = MathUtils.subVector(to, from);
					direction = MathUtils.rotateVector(direction, up, orientToPathDegree);
					to = MathUtils.addVector(from, direction);
				}
				this._lookAt(object, from, to, up);
			}

			if (onNext) {
				onNext(ev);
			}
		};

		if (orientToPath) {
			const loopType = param['loopType'];
			const quaternion = object.quaternion.slice();
			if (loopType && loopType === THING.LoopType.Repeat) {
				let oldRepeat = param['onRepeat'];
				param['onRepeat'] = (ev) => {
					object.quaternion = quaternion;

					if (oldRepeat) {
						oldRepeat(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);
	}

	/**
	 * 将物体缩放到指定大小
	 * @param {Array|object} value - 目标缩放值数组[x,y,z]或包含缩放参数的对象
	 * @param {object} param - 缩放参数配置对象
	 * @param {Array<number>} param.to - 目标缩放值
	 * @param {number} param.time - 缩放时间(毫秒)
	 * @param {THING.SpaceType} param.spaceType - 坐标空间类型,默认为 World
	 * @param {number} param.times - 循环次数
	 * @param {THING.LoopType} param.loopType - 循环类型
	 * @param {Function} param.onUpdate - 更新回调函数
	 * @param {Function} param.onComplete - 完成回调函数
	 * @param {Function} trigger - 触发器函数
	 * @returns {void}
	 * @example
	 * // 基础用法 - 直接传入缩放值数组
	 * object.lerp.scaleTo([2, 2, 2]);
	 *
	 * // 使用参数对象方式
	 * object.lerp.scaleTo({
	 *   to: [2, 2, 2],           						// 目标缩放值
	 *   time: 2000,               					// 缩放时间(毫秒)
	 *   spaceType: THING.SpaceType.World,    // 坐标空间类型
	 *   times: 4,                             // 循环次数
	 *   loopType: THING.LoopType.PingPong,   // 循环类型
	 *   onUpdate: (ev) => {      						// 更新回调
	 *     console.log(ev.from, ev.to, ev.direction);
	 *     console.log('Progress:', ev.progress);
	 *   },
	 *   onComplete: () => {      						// 完成回调
	 *     console.log('Scale completed');
	 *   }
	 * });
	 * 
	 * // 在本地坐标系中缩放
	 * object.lerp.scaleTo({
	 *   to: [2, 2, 2],
	 *   time: 2000,
	 *   spaceType: THING.SpaceType.Local
	 * });
	 * 
	 * // 相对于当前缩放值进行缩放
	 * object.lerp.scaleTo({
	 *   to: [2, 2, 2],
	 *   time: 2000,
	 *   spaceType: THING.SpaceType.Self
	 * });
	 * @public
	 */
	scaleTo(value, param = {}, trigger) {
		this.stopScaling();

		if (!Utils.isArray(value)) {
			param = value;
			value = param['to'];
		}

		// Build lerp params
		let lerpParam = this._buildLerpParam(trigger, param);
		let object = this.object;

		// Set the target scale
		let spaceType = Utils.parseValue(param['spaceType'], SpaceType.World);
		switch (spaceType) {
			case SpaceType.World:
				lerpParam['to'] = { scale: value };
				break;
			case SpaceType.Local:
				lerpParam['to'] = { localScale: value };
				break;
			case SpaceType.Self:
				lerpParam['to'] = { scale: MathUtils.scaleVector(object.scale, value) };
				break;
		}

		// 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);
	}

	/**
	 * 将物体旋转到指定角度
	 * @param {Array|object} value - 目标旋转值。可以是:
	 * - 欧拉角数组[x,y,z]表示旋转角度
	 * - 四元数数组[x,y,z,w]表示旋转
	 * - 包含旋转参数的对象
	 * @param {object} param - 旋转参数配置对象
	 * @param {Array<number>} param.to - 目标旋转值,可以是欧拉角[x,y,z]或四元数[x,y,z,w]
	 * @param {number} param.time - 旋转时间(毫秒)
	 * @param {THING.SpaceType} param.spaceType - 坐标空间类型,默认为 Local
	 * @param {number} param.times - 循环次数
	 * @param {THING.LoopType} param.loopType - 循环类型
	 * @param {Function} param.onUpdate - 更新回调函数
	 * @param {Function} param.onComplete - 完成回调函数
	 * @param {Function} trigger - 触发器函数
	 * @returns {void}
	 * @example
	 * // 基础用法 - 使用欧拉角旋转(x,y,z)
	 * object.lerp.rotateTo([90, 0, 0]); // 绕x轴旋转90度
	 * 
	 * // 使用四元数旋转(x,y,z,w)
	 * object.lerp.rotateTo([0, 0, 0, 1]);
	 *
	 * // 使用参数对象方式 - 欧拉角
	 * object.lerp.rotateTo({
	 *   to: [90, 0, 0],           						// 目标旋转角度
	 *   time: 2000,               					// 旋转时间(毫秒)
	 *   spaceType: THING.SpaceType.World,    // 坐标空间类型
	 *   times: 4,                             // 循环次数
	 *   loopType: THING.LoopType.PingPong,   // 循环类型
	 *   onUpdate: (ev) => {      						// 更新回调
	 *     console.log(ev.from, ev.to, ev.direction);
	 *     console.log('Progress:', ev.progress);
	 *   },
	 *   onComplete: () => {      						// 完成回调
	 *     console.log('Rotation completed');
	 *   }
	 * });
	 * 
	 * // 在世界坐标系中旋转
	 * object.lerp.rotateTo({
	 *   to: [90, 0, 0],
	 *   time: 2000,
	 *   spaceType: THING.SpaceType.World
	 * });
	 * 
	 * // 在本地坐标系中旋转(默认)
	 * object.lerp.rotateTo({
	 *   to: [90, 0, 0],
	 *   time: 2000,
	 *   spaceType: THING.SpaceType.Local
	 * });
	 * 
	 * // 相对于当前旋转进行旋转
	 * object.lerp.rotateTo({
	 *   to: [90, 0, 0],
	 *   time: 2000,
	 *   spaceType: THING.SpaceType.Self
	 * });
	 * @public
	 */
	rotateTo(value, param = {}, trigger) {
		this.stopRotating();

		if (!Utils.isArray(value)) {
			param = value;
			value = param['to'];
		}

		// Build lerp params
		let lerpParam = this._buildLerpParam(trigger, param);
		let object = this.object;

		// Set the target angels
		let spaceType = Utils.parseValue(param['spaceType'], SpaceType.Local);
		switch (spaceType) {
			case SpaceType.World:
				if (value.length == 3) {
					lerpParam['to'] = { angles: value };
				}
				else if (value.length == 4) {
					lerpParam['to'] = { quaternion: value };
				}
				else {
					lerpParam['to'] = { quaternion: MathUtils.getQuatFromAngles(value) };
				}
				break;
			case SpaceType.Local:
				if (value.length == 3) {
					lerpParam['to'] = { localAngles: value };
				}
				else if (value.length == 4) {
					lerpParam['to'] = { localQuaternion: value };
				}
				else {
					lerpParam['to'] = { localQuaternion: MathUtils.getQuatFromAngles(value) };
				}
				break;
			case SpaceType.Self:
				if (value.length == 3) {
					lerpParam['to'] = { localAngles: MathUtils.addVector(object.localAngles, value) };
				}
				else if (value.length == 4) {
					lerpParam['to'] = { quaternion: MathUtils.multiplyQuat(object.quaternion, value) };
				}
				else {
					lerpParam['to'] = { quaternion: MathUtils.multiplyQuat(object.quaternion, MathUtils.getQuatFromAngles(value)) };
				}
				break;
		}

		// 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

	/**
	 * 停止淡入淡出动作。
	 * @public
	 * @example
	 * let lerp = box.lerp;
	 * lerp.stopFading();
	 */
	stopFading() {
		this.stop(cFadingActionName);
	}

	/**
	 * 暂停淡入淡出动作。
	 * @public
	 * @example
	 * let lerp = box.lerp;
	 * lerp.pauseFading();
	 */
	pauseFading() {
		this.pause(cFadingActionName);
	}

	/**
	 * 恢复淡入淡出动作。
	 * @public
	 * @example
	 * let lerp = box.lerp;
	 * lerp.resumeFading();
	 */
	resumeFading() {
		this.resume(cFadingActionName);
	}

	/**
	 * 将对象淡入到完全不透明。
	 * @param {LerpArgs} param - 淡入动作的参数。
	 * @param {Function} trigger - 在淡入完成时调用的触发函数。
	 * @public
	 * @example
	 * let lerp = box.lerp;
	 * lerp.fadeIn({
	 *   from: {
	 *     style: {
	 *       opacity: 0
	 *     }
	 *   }
	 */
	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);
	}

	/**
	 * 将对象淡出到完全透明。
	 * @param {LerpArgs} param - 淡出动作的参数。
	 * @param {Function} trigger - 在淡出完成时调用的触发函数。
	 * @public
	 * @example
	 * let lerp = box.lerp;
	 * lerp.fadeOut({
	 *   from: {
	 *     style: {
	 *       opacity: 1
	 *     }
	 */
	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);
	}

	/**
	 * 异步将对象淡入。
	 * @param {LerpArgs} param - 淡入动作的参数。
	 * @returns {Promise} 一个在淡入完成时解析的承诺。
	 * @public
	 * @example
	 * let lerp = box.lerp;
	 * lerp.fadeInAsync({
	 *   from: {
	 *     style: {
	 *       opacity: 0
	 *     }
	 */
	fadeInAsync(param = {}) {
		return new Promise((resolve, reject) => {
			this.fadeIn(param, _buildAsyncTrigger(resolve, reject));
		});
	}

	/**
	 * 异步将对象淡出。
	 * @param {LerpArgs} param - 淡出动作的参数。
	 * @returns {Promise} 一个在淡出完成时解析的承诺。
	 * @public
	 * @example
	 * let lerp = box.lerp;
	 * lerp.fadeOutAsync({
	 *   from: {
	 *     style: {
	 *       opacity: 1
	 *     }
	 */
	fadeOutAsync(param = {}) {
		return new Promise((resolve, reject) => {
			this.fadeOut(param, _buildAsyncTrigger(resolve, reject));
		});
	}

	// #endregion

	// #region Flying

	/**
	 * @public
	 * @typedef {object} FlyInfoParams 计算飞行插值参数的对象
	 * @property {Array<number>} [position] 要飞到的位置
	 * @property {Array<number>|THING.BaseObject} target 飞行的目标位置,如果是一个物体,则自动选择其边界框的最佳位置。
	 * </br>飞行到目标位置后,物体的target属性表示物体的方向,即target和position的差表示物体的方向。
	 * </br>如果传入一个BaseObject,则target是目标对象的位置。如果没有传入position,则会根据目标对象的距离、水平角度和垂直角度来计算position。
	 * </br>参见 https://en.wikipedia.org/wiki/Spherical_coordinate_system
	 * @property {Array<number>} up 飞行到目标位置后物体的上方向。
	 * @property {number} distance 距离(仅适用于对象目标模式)。如果目标位置是一个Object3D对象,则表示与目标对象的距离。
	 * @property {number} horzAngle 水平角度(仅适用于对象目标模式)。如果目标位置是一个Object3D对象,则表示与目标对象的水平夹角。
	 */

	/**
	 * 获取飞行信息。
	 * @param {FlyInfoParams} param 参数。
	 * @returns {object} 飞行信息。
	 * @private
	 * @example
	 * let lerp = box.lerp;
	 * let info = lerp.getFlyInfo({
	 *   target: box,
	 * });
	 */
	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 || target.isBox3) {
			let aabb = target;
			if (target.isBaseObject) {
				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);

				if (this.object.isCamera) {
					// Calculate the distance by fov
					const factor = 0.5773502691896257 / Math.tan(0.5 * this.object.fov * Math.PI / 180); // 0.5773502691896257 = Math.tan( 60 * 0.5 * Math.PI / 180 )
					distance *= factor;
				}

				// 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 }