import { BaseLevelControl, EventType } from "@uino/thing";
import { LevelControlHelper } from "../LevelControlHelper";
import { LevelControls } from "../LevelControls";
// Store the current orderID of the level event
let _orderID = 1;
// Generate a new orderID
function _getOrderID() {
return '__CampusLevelControl__TagName__' + (++_orderID);
}
const __ = {
private: Symbol('private')
}
/**
* @class DefaultLevelControl
* 园区的默认层级控制类。
* @extends THING.BaseLevelControl
* @public
*/
class DefaultLevelControl extends BaseLevelControl {
static levelCondition = '';
static levelName = '';
static register() {
LevelControls.setLevelControlClass(this.levelName, this.levelCondition, this);
}
/**
* Constructor for DefaultLevelControl class.
* @param {Object} options - Options for the level control.
*/
constructor(options) {
super(options);
let _private = this[__.private] = {};
_private.enableFly = true;
_private.enableSkipLevel = true;
_private.enableOutlineColor = true;
_private.enableMouseEvent = true;
_private.enterLevelEventType = EventType.DBLClick;
_private.leaveLevelEventType = EventType.DBLClick;
_private.outlineColor = [1, 0.5019607843137255, 0];
_private.preferCachedViewpoint = false;
_private.isCameraFirst = false;
_private.viewpointCache = new Map();
_private.events = [];
_private.tagName = _getOrderID();
_private.mouseEnterObject = null;
_private._isSkipLevel = false;
_private.enableBrotherMouseEvent = true;
/**
* Function to cache viewpoint information.
* @param {Object} param - Parameters containing the object and level options.
* @private
*/
_private.cacheViewpoint = (param) => {
const current = param.current;
if (!current) {
return;
}
const next = param.next;
if (!next) {
return;
}
if (next.isChildOf(current)) {
const camera = current.app.camera;
_private.viewpointCache.set(current.uuid, {
position: camera.position,
target: camera.target
});
}
}
/**
* Function to retrieve viewpoint information from the level.
* @param {Object} object - The object to get the viewpoint from.
* @returns {Object} - The viewpoint information.
* @private
*/
_private.getViewpointFromLevel = (object) => {
if (object) {
const level = object.level;
if (level) {
return level.viewpoint;
}
}
}
/**
* Function to go back to the previous level.
* @private
*/
_private.backLevel = () => {
const app = this.app;
const current = app.level.current;
let parent = current.parent;
if (parent) {
app.levelManager.change(parent);
}
}
/**
* Function to register fly events.
* @param {Object} camera - The camera object for the level.
* @returns {Function} - Function to unregister the fly events.
* @private
*/
_private.registerFlyToEvent = (camera) => {
const flyEventTag = _getOrderID() + '_fly_tag_';
_private.registerEvent(EventType.MouseUp, () => {
camera.stopFlying();
}, flyEventTag);
_private.registerEvent(EventType.MouseDown, () => {
camera.stopFlying();
}, flyEventTag);
_private.registerEvent(EventType.Wheel, () => {
camera.stopFlying();
}, flyEventTag);
return () => {
_private.unregisterEvent(EventType.MouseUp, flyEventTag);
_private.unregisterEvent(EventType.MouseDown, flyEventTag);
_private.unregisterEvent(EventType.Wheel, flyEventTag);
}
}
/**
* Process fly param.
* @param {Object} param - Enter the parameters of the level.
* @returns {Object} - The fly Params.
* @private
*/
_private.processFlyParam = (param) => {
const current = param.current;
const camera = current.app.camera;
const unregisterCallback = _private.registerFlyToEvent(camera);
let flyConfig = {
target: current,
onStop: () => {
unregisterCallback();
},
onComplete: () => {
unregisterCallback();
}
};
let viewpoint = _private.viewpointCache.get(current.uuid);
if (!viewpoint || !_private.preferCachedViewpoint) {
const vp = _private.getViewpointFromLevel(current);
if (vp) {
viewpoint = vp;
}
}
_private.viewpointCache.delete(current.uuid);
if (viewpoint) {
flyConfig.target = viewpoint.target;
flyConfig.position = viewpoint.position;
}
return flyConfig;
}
_private.registerMouseEvent = () => {
_private.registerEvent(EventType.MouseEnter, (ev) => {
this.onMouseEnter(ev);
});
_private.registerEvent(EventType.MouseLeave, (ev) => {
this.onMouseLeave(ev);
});
_private.registerEvent(_private.enterLevelEventType, (ev) => {
if (ev.button === 0) {
if (_private.mouseEnterObject) {
let enterObj = _private.mouseEnterObject;
this.onMouseLeave();
this.onEnterObjectLevel(enterObj);
}
}
}, _private.tagName + '_enter');
_private.registerEvent(_private.leaveLevelEventType, (ev) => {
if (ev.button === 2) {
if (_private.mouseEnterObject) {
this.onMouseLeave();
}
this.onBackToParentLevel()
}
}, _private.tagName + '_leave');
}
_private.unregisterMouseEvent = () => {
_private.unregisterEvent(EventType.MouseEnter);
_private.unregisterEvent(EventType.MouseLeave);
_private.unregisterEvent(_private.enterLevelEventType, _private.tagName + '_enter');
_private.unregisterEvent(_private.leaveLevelEventType, _private.tagName + '_leave');
}
/**
* Register an event listener.
* @param {string} key - The event key.
* @param {Function} callback - The callback function for the event.
* @param {string} [tag] - The event tag (optional).
* @private
*/
_private.registerEvent = (key, callback, tag) => {
let tagName = tag || _private.tagName;
this.app.on(key, callback, tagName);
_private.events.push({ name: key, tag: tagName });
}
/**
* Unregister an event listener.
* @param {string} eventName - The name of the event to unregister.
* @param {string} [tag] - The event tag (optional).
* @private
*/
_private.unregisterEvent = (eventName, tag) => {
if (!eventName) {
return;
}
const app = this.app;
let events = _private.events;
for (let i = 0; i < events.length; i++) {
const element = events[i];
if (element.name === eventName) {
if (tag) {
if (tag === element.tag) {
app.off(eventName, tag);
events._removeAt(i);
}
} else {
app.off(eventName, element.tag);
events._removeAt(i);
}
return;
}
}
}
/**
* Unregister all event listeners.
* @private
*/
_private.unregisterAllEvents = () => {
const app = this.app;
_private.events.forEach(eventObject => {
app.off(eventObject.name, eventObject.tag);
});
}
_private.setLevelChangeEventType = (key, value) => {
if (value === _private[key]) {
return
}
if (this.isRunning) {
_private.unregisterMouseEvent()
}
if (value === EventType.DBLClick) {
_private[key] = EventType.DBLClick;
} else {
_private[key] = EventType.Click;
}
if (this.isRunning) {
_private.registerMouseEvent()
}
}
}
//#region Override
/**
* 当进入层级时。
* @param {Object} param - 进入层级的参数。
* @returns {Promise}
* @public
*/
async onEnter(param) {
super.onEnter(param);
let _private = this[__.private];
_private.levelParam = param;
let options = param.options;
let isSkip = false;
if (options.jumping && param.target !== param.current) {
isSkip = true;
}
if (_private.enableSkipLevel) {
_private._isSkipLevel = this.onSkipLevel(param);
if (_private._isSkipLevel) {
isSkip = true;
}
}
// 跳过 只执行 on set effect
if (isSkip) {
this.onSetEffect(param);
return;
}
// 相机优先
if (_private.isCameraFirst) {
await this.onSetCamera(param);
}
await this.onSetEffect(param);
if (this.app.root.dynamicLoad.enable) {
await this.app.root.dynamicLoad.waitForComplete();
}
// 如果动态加载结束后,当前层级已经退出,就不能再注册事件以及相机飞行
if (!this.isRunning) {
return;
}
this.onRegisterEvent(param);
// 相机不优先
if (!_private.isCameraFirst) {
await this.onSetCamera(param);
}
}
/**
* 当离开层级时。
* @param {Object} param - 离开层级的参数。
* @public
*/
async onLeave(param) {
super.onLeave(param);
const _private = this[__.private];
// 如果跳过了只执行 reset effect
await this.onResetEffect(param);
if (_private.enableSkipLevel && _private._isSkipLevel) {
_private._isSkipLevel = false;
return;
}
_private.cacheViewpoint(param);
this.onUnregisterEvent(param);
return Promise.resolve();
}
onEnable(param) {
super.onEnable(param);
this.onRegisterEvent(param);
}
onDisable(param) {
super.onDisable(param);
this.onUnregisterEvent(param);
}
/**
* 当注册事件时。
* @param {Object} param - 进入层级的参数。
* @public
*/
onRegisterEvent(param) {
let _private = this[__.private];
if (_private.enableMouseEvent) {
_private.registerMouseEvent();
}
}
/**
* 当取消注册事件时。
* @param {Object} param - 离开层级的参数。
* @public
*/
onUnregisterEvent(param) {
let _private = this[__.private];
_private.unregisterAllEvents();
}
/**
* 进入层级时应用效果。
* @param {Object} param - 进入层级的参数。
* @public
*/
onSetEffect(param) {
}
/**
* 离开层级时重置效果。
* @param {Object} param - 离开层级的参数。
* @public
*/
onResetEffect(param) {
this.onMouseLeave();
}
//#endregion
//#region Public function
/**
* 处理对象。
* @param {Object} object - 要处理的对象。
* @returns {Object|null} - 要进一步处理处理过的对象,如果没有合适的对象则返回null。
* @public
*/
filterObject(object) {
const _private = this[__.private];
if (object.tags.has('IgnoreLevel')) {
return null;
}
if (object.tags.has('Ground') || object.tags.has('Misc')) {
return null;
}
const parents = object.parents;
// 如果父节点包含Ground或Misc,则不处理
if (parents.find('tags:or(Misc,Ground)')) {
return null;
}
const current = object.app.levelManager.current;
// 兄弟节点
if (object.isBrotherOf(current)) {
return processResult(object);
}
// 查找当前层级在父节点列表中的索引
const currentLevelIndex = parents.indexOf(current);
// 如果传入对象的父是当前层级
if (currentLevelIndex === 0) {
return processResult(object);
}
// 如果当前层级是对象的某个父节点
else if (currentLevelIndex > 0) {
// 返回当前层级的直接子节点
return processResult(parents[currentLevelIndex - 1]);
}
const parent = current.parent;
const brotherIndex = parents.indexOf(parent);
if (brotherIndex > 0) {
return processResult(parents[brotherIndex - 1]);
}
return null;
function processResult(result) {
// 如果开启了过滤兄弟节点,并且当前是兄弟节点,则不处理
if (!_private.enableBrotherMouseEvent && result.isBrotherOf(current)) {
return null;
}
if (result != current) {
return result;
}
return result;
}
}
getLevelParam() {
if (this.isRunning) {
let _private = this[__.private];
return _private.levelParam;
}
return null;
}
//#endregion
//#region Public attribute
onSetCamera(param) {
let _private = this[__.private];
let flyParam = _private.processFlyParam(param);
if (_private.enableFly) {
return this.onFlyTo(flyParam);
} else {
this.app.camera.fit(flyParam);
}
return null;
}
/**
* 启动飞行动画到指定参数。
* @param {THING.LerpFlyToArgs} flyParam - 包含飞行动画参数的对象。
* @returns {Promise}
* @public
*/
onFlyTo(flyParam) {
return new Promise((resolve, reject) => {
let complete = flyParam.onComplete;
flyParam.onComplete = flyParam.onStop = () => {
complete && complete();
resolve();
}
this.app.camera.flyTo(flyParam);
});
}
/**
* 检查给定路径是否涉及跳过层级。(默认情况下,兄弟节点会被跳过)
* @param {Object} param - 包含层级变化参数的对象。
* @returns {boolean} 返回true表示跳过,返回false表示不跳过。
* @public
*/
onSkipLevel(param) {
const prevLevel = param.prev;
const path = param.path
if (!prevLevel || !path) {
return false;
}
const finalLevel = path[path.length - 1];
if (finalLevel && prevLevel.isBrotherOf(finalLevel)) {
return true;
}
return false;
}
/**
* 处理鼠标进入对象事件。(默认设置轮廓颜色)
* @param {THING.EventCallback} ev - 鼠标进入事件。
* @public
*/
onMouseEnter(ev) {
let _private = this[__.private];
const object = ev.object;
if (!object) {
return;
}
if (LevelControlHelper.getIgnore(object, 'ignoreEvent')) {
return;
}
const mapping = this.filterObject(object);
if (!mapping) {
return;
}
if (LevelControlHelper.getIgnore(mapping, 'ignoreEvent')) {
return;
}
_private.mouseEnterObject = mapping;
if (_private.enableOutlineColor) {
this.onSetOutlineColor(mapping, _private.outlineColor);
_private.mouseEnterObject.on(EventType.BeforeDestroy, () => {
_private.mouseEnterObject = null;
}, '__CampusLevelControl__Destroy__');
}
return mapping;
}
/**
* 处理鼠标离开对象事件。(默认清除轮廓颜色)
* @param {THING.EventCallback} ev - 鼠标离开事件。
* @public
*/
onMouseLeave(ev) {
let _private = this[__.private];
let enterObj = _private.mouseEnterObject;
if (!enterObj) {
return;
}
if (_private.enableOutlineColor) {
this.onSetOutlineColor(enterObj, null);
}
enterObj.off(EventType.BeforeDestroy, '__CampusLevelControl__Destroy__');
_private.mouseEnterObject = null;
return enterObj;
}
/**
* 进入对象层级的回调函数
* @param {THING.Object3D} ev - 对象。
* @public
*/
onEnterObjectLevel(object) {
object.app.level.change(object);
}
/**
* 返回到父级层级的回调函数
* @public
*/
onBackToParentLevel() {
let _private = this[__.private];
_private.backLevel();
}
onSetOutlineColor(object, color) {
if (!object) {
return;
}
LevelControlHelper.traverseByIgnore(object, 'ignoreStyle', (child) => {
if (color && !child.visible) {
return;
}
const style = child.body.style;
if (!style) {
return;
}
if (!color && !style.outlineColor) {
return;
}
if (!this.shouldOutline(child)) {
return;
}
style.outlineColor = color;
});
}
/**
* Determines whether an object and its children should be outlined on mouse enter.
* @param {THING.Object3D} object - The 3D object to check for outlining.
* @returns {Boolean} Returns `true` if the object should be outlined, `false` otherwise.
*/
shouldOutline(object) {
return true;
}
/**
* 启用/禁用相机飞行
* @type {Boolean}
* @public
*/
get enableFly() {
let _private = this[__.private];
return _private.enableFly;
}
set enableFly(value) {
let _private = this[__.private];
_private.enableFly = value;
}
/**
* 启用/禁用跳过层级
* @type {Boolean}
* @public
*/
get enableSkipLevel() {
let _private = this[__.private];
return _private.enableSkipLevel;
}
set enableSkipLevel(value) {
let _private = this[__.private];
_private.enableSkipLevel = value;
}
/**
* 启用/禁用设置轮廓颜色
* @type {Boolean}
* @public
*/
get enableOutlineColor() {
let _private = this[__.private];
return _private.enableOutlineColor;
}
set enableOutlineColor(value) {
let _private = this[__.private];
_private.enableOutlineColor = value;
}
/**
* 启用/禁用鼠标事件
* @type {Boolean}
* @public
*/
get enableMouseEvent() {
let _private = this[__.private];
return _private.enableMouseEvent;
}
set enableMouseEvent(value) {
let _private = this[__.private];
if (_private.enableMouseEvent === value) {
return;
}
_private.enableMouseEvent = value;
if (!this.isRunning) {
return;
}
if (value) {
_private.registerMouseEvent();
} else {
_private.unregisterMouseEvent();
}
}
/**
* 获取/设置轮廓颜色
* @type {Array<Number>}
* @public
*/
get outlineColor() {
let _private = this[__.private];
return _private.outlineColor;
}
set outlineColor(value) {
let _private = this[__.private];
_private.outlineColor = value;
}
/**
* 获取/设置进入层级的鼠标事件(仅限 'THING.EventType.Click' 或 'THING.EventType.DBLClick')
* @type {THING.EventType}
* @public
*/
get enterLevelEventType() {
let _private = this[__.private];
return _private.enterLevelEventType;
}
set enterLevelEventType(value) {
let _private = this[__.private];
_private.setLevelChangeEventType('enterLevelEventType', value)
}
/**
* 获取/设置离开层级的鼠标事件(仅限 'THING.EventType.Click' 或 'THING.EventType.DBLClick')
* @type {THING.EventType}
* @public
*/
get leaveLevelEventType() {
let _private = this[__.private];
return _private.leaveLevelEventType;
}
set leaveLevelEventType(value) {
let _private = this[__.private];
_private.setLevelChangeEventType('leaveLevelEventType', value)
}
/**
* 优先使用缓存视角
* @type {Boolean}
* @public
*/
get preferCachedViewpoint() {
const _private = this[__.private];
return _private.preferCachedViewpoint;
}
set preferCachedViewpoint(value) {
const _private = this[__.private];
_private.preferCachedViewpoint = !!value;
}
/**
* 相机优先 (进入时最先执行相机设置)
* @type {Boolean}
* @public
*/
get isCameraFirst() {
const _private = this[__.private];
return _private.isCameraFirst;
}
set isCameraFirst(value) {
const _private = this[__.private];
_private.isCameraFirst = !!value;
}
/**
* 启用/禁用兄弟节点鼠标事件
* @type {Boolean}
* @public
*/
get enableBrotherMouseEvent() {
const _private = this[__.private];
return _private.enableBrotherMouseEvent;
}
set enableBrotherMouseEvent(value) {
const _private = this[__.private];
_private.enableBrotherMouseEvent = !!value;
}
/**
* 获取是否是默认层级控制
* @type {Boolean}
* @public
*/
get isDefaultLevelControl() {
return true;
}
//#endregion
}
export { DefaultLevelControl };