import { BaseComponent, LoopType, Math, MathUtils, Utils } from '@uino/thing';
const cRotateAroundActionName = '__rotateAround__';
/**
* @class RotateAroundComponent
* @summary 环绕旋转组件,为对象提供围绕指定目标(物体或世界坐标点)旋转的能力。
* 基于球坐标系插值算法,支持绕Y轴(俯仰角)和绕X轴(方位角)两个维度的平滑旋转,
* 可配置旋转时间、循环次数、插值类型以及各种生命周期回调。
* @memberof THING
* @extends THING.BaseComponent
* @public
* @example
* // 让 obj1 绕着 obj2 旋转一周
* obj1.addComponent(THING.EXTEND.RotateAroundComponent, 'rotateCtrl');
* obj1.rotateCtrl.start({
* object: obj2,
* yRotateAngle: 360,
* time: 5000,
* loopType: THING.LoopType.Repeat,
* onUpdate: function(ev) {
* console.log('旋转进度:', ev.progress);
* }
* });
*
* // 停止旋转
* obj1.rotateCtrl.stop();
*/
class RotateAroundComponent extends BaseComponent {
/**
* 环绕旋转组件类构造函数,用于实现对象环绕目标旋转的功能。
* @param {object} param 参数列表
*/
constructor(param = {}) {
super(param);
}
/**
* On add.
* @param {THING.BaseObject} object The object to add the component to.
*/
onAdd(object) {
super.onAdd(object);
}
/**
* On remove.
*/
onRemove() {
super.onRemove();
}
/**
* @typedef {object} RotateAroundLerpArgs
* @property {THING.BaseObject} object 环绕的目标对象。
* @property {Array<number>} target 世界坐标系下某个点的坐标(可从 object 中选择一个)。
* @property {number} yRotateAngle 绕Y轴旋转的角度(仰视平面,即垂直平面内的角度,范围0~180度)。
* @property {number} [xRotateAngle] 绕X轴旋转的角度(方位平面,即水平平面内的角度,范围0~360度)(此参数不能在地球上使用)。
* @property {number} [times=-1] 循环次数,-1表示无限循环。
* @property {number} [time=1000] 旋转所需时间(毫秒)。
* @property {number} [delayTime=0] 延迟时间(毫秒)。
* @property {THING.LerpType} [lerpType] 插值类型。
* @property {THING.LoopType} [loopType] 循环类型。
* @property {Function} onRepeat 重复动作回调函数。
* @property {Function} onStop 停止动作回调函数。
* @property {Function} onUpdate 更新动作回调函数。
* @property {Function} onComplete 完成动作回调函数。
* @public
*/
/**
* 开始让物体绕着某一个点或者物体旋转的操作。
* @param {RotateAroundLerpArgs} param 旋转的参数对象。
* @example
* // 让 obj1 绕着场景中的 obj2 旋转
* obj1.addComponent(THING.RotateAroundComponent, 'rotateController');
* obj1.rotateController.start({
* object: obj2, // 被绕的目标对象
* yRotateAngle: 90, // 绕Y轴旋转90度
* time: 2000, // 旋转时长2秒
* onComplete: function() {
* console.log('绕物体旋转完成');
* }
* });
*
* // 或者让 obj1 绕某个世界坐标点旋转
* obj1.rotateController.start({
* target: [10, 0, 20], // 绕这个点旋转
* yRotateAngle: 180, // 旋转180度
* time: 3000,
* onComplete: function() {
* console.log('绕点旋转结束');
* }
* });
* @public
*/
start(param) {
// Take out parameter configuration.
var targetObject = param['object'];
var target = param['target'];
var yRotateAngle = Utils.parseValue(param['yRotateAngle'] || param['angle'], 0);
var xRotateAngle = Utils.parseValue(param['xRotateAngle'], 0);
var time = param['time'];
var times = param['times'];
var delayTime = param['delayTime'];
var loopType = Utils.parseLoopType(param['loopType'] || 'once');
var lerpType = Utils.parseLerpType(param['lerpType']);
var onRepeat = Utils.parseValue(param['onRepeat'], param['repeat']);
var onUpdate = Utils.parseValue(param['onUpdate'], param['update']);
var onStop = Utils.parseValue(param['onStop'], param['stop']);
var onComplete = Utils.parseValue(param['onComplete'], param['complete']);
// Acceptable target as object input
if (target) {
if (target.isBaseObject) {
targetObject = target;
target = null;
}
}
// Check whether time is provided
if (time) {
if (!yRotateAngle && !xRotateAngle) {
Utils.error('Please provide yRotateAngle or xRotateAngle to rotate around object');
return;
}
}
else {
if (!yRotateAngle && !xRotateAngle) {
time = 0xffffff;
}
else {
time = 1000;
}
}
// Stop rotating.
this.stop();
// The reverse mode
let reverseMode = false;
// Build lerp params
var _repeat = () => {
if (loopType == LoopType.PingPong) {
reverseMode = !reverseMode;
}
if (onRepeat) {
onRepeat({ object: this.object, from: 0, to: 1 });
}
};
// Calculate relevant data of target points.
var position = this.object.position;
var targetPosition = target ? target : targetObject.position;
var direction = MathUtils.subVector(position, targetPosition);
var spherical = MathUtils.makeSphericalFromCartesianCoords(direction);
// Prepare rotation parameters.
var offset = [0, 0, 0];
var baseInfo = {
theta: spherical.theta,
phi: spherical.phi
}
var _update = (ev) => {
if (reverseMode) {
ev.progress = 1 - ev.progress;
}
if (yRotateAngle) {
spherical.theta = baseInfo.theta + ev.progress * Math.degToRad(yRotateAngle);
}
if (xRotateAngle) {
spherical.phi = baseInfo.phi + ev.progress * Math.degToRad(xRotateAngle);
}
var targetPosition = target ? target : targetObject.position;
spherical.radius = this.object.distanceTo(targetPosition);
MathUtils.makeSphericalSafe(spherical);
offset = MathUtils.getFromSphericalCoords(spherical);
targetPosition = MathUtils.addVector(targetPosition, offset);
this.object.position = targetPosition;
if (onUpdate) {
onUpdate({ object: this.object, progress: ev.progress });
}
}
// Start to rotate.
this.object.lerp.to({
from: { progress: 0 },
to: { progress: 1 },
time,
times,
delayTime,
loopType: loopType,
lerpType: lerpType,
onRepeat: _repeat,
onUpdate: _update,
onStop,
onComplete
}, cRotateAroundActionName);
}
/**
* 停止旋转。
* @public
*/
stop() {
this.object.lerp.stopRotating()
this.object.lerp.stopMoving()
this.object.lerp.stop(cRotateAroundActionName)
}
}
export { RotateAroundComponent }