feat(All):Initial

This commit is contained in:
2025-10-04 23:36:07 +08:00
commit 2b4e5d2668
1321 changed files with 415958 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
/**
* Create a container.
*/
export function createDivContainer(): HTMLDivElement {
const div = document.createElement("div");
document.body.appendChild(div);
return div;
}
+34
View File
@@ -0,0 +1,34 @@
/**
* 下载blob二进制对象
* @param blob
* @param filename
*/
export function downloadBlob(blob, filename) {
const link = document.createElement('a');
if (link.href) {
URL.revokeObjectURL(link.href);
}
link.href = URL.createObjectURL(blob);
link.download = filename || 'data.json';
link.dispatchEvent(new MouseEvent('click'));
}
/**
* 下载ArrayBuffer对象
* @param buffer
* @param filename
*/
export function saveArrayBuffer(buffer, filename) {
downloadBlob(new Blob([buffer], {type: 'application/octet-stream'}), filename);
}
/**
* 下载text文档
* @param text
* @param filename
*/
export function saveString(text, filename) {
downloadBlob(new Blob([text], {type: 'text/plain'}), filename);
}
+36
View File
@@ -0,0 +1,36 @@
/**
* 递归访问嵌套属性
* @param {object} obj
* @param {string} path 属性路径字符串,eg: "a.b.c"
*/
export function getNestedProperty(obj:object, path:string):any {
return path.split('.').reduce((o, key) => o?.[key], obj);
}
/**
* 转义正则特殊字符
* @param {string} str
*/
export function escapeRegExp(str:string) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
/**
* 获取系统主题色
*/
export function getOsTheme(){
const isDarkTheme = window.matchMedia("(prefers-color-scheme: dark)"); // 是深色
if (isDarkTheme.matches) {
return 'dark';
} else {
return "light";
}
}
/**
* 获取rem的px值
*/
export function remToPxNumber(rem: number): number {
const f = parseFloat(document.documentElement.style.fontSize);
return f * rem;
}
+6
View File
@@ -0,0 +1,6 @@
export * from './object';
export * from './performance';
export * from './helper';
export * from './download';
export * from './verify';
export * from './dom';
+19
View File
@@ -0,0 +1,19 @@
/**
* 将对象source的值深度遍历赋值给target对象相同key
* @param target
* @param source
*/
export function deepAssign(target, source) {
for (const key in source) {
if (source.hasOwnProperty(key)) {
if (typeof source[key] === 'object' && source[key] !== null && !Array.isArray(source[key])) {
if (!target[key]){
target[key] = {};
}
deepAssign(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}
}
@@ -0,0 +1,42 @@
/**
* 防抖函数
* @param {Function} func - 需要防抖的函数
* @param {number} wait - 时间间隔(毫秒)
* @returns {Function} - 返回一个防抖后的函数
*/
export function debounce(func, wait): (...args: any[]) => void {
let timer: NodeJS.Timeout | null = null;
return function(){
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
func(...arguments)
}, wait);
};
}
/**
* 节流函数
* @param {Function} func - 需要节流的函数
* @param {number} wait - 时间间隔(毫秒),表示在这个时间间隔内最多执行一次函数
* @returns {Function} - 返回一个节流后的函数
*/
export function throttle(func, wait:number):(...args: any[]) => void {
// 上一次执行函数的时间戳,初始值为 0
let lastTime = 0;
// 返回一个闭包函数,作为节流后的函数
return function () {
// 获取当前时间戳
const now = Date.now();
// 如果当前时间与上一次执行时间的差值大于等于 wait,则执行函数
if (now - lastTime >= wait) {
// 更新上一次执行函数的时间戳
lastTime = now;
// 调用原始函数,并传入参数
func(...arguments);
}
};
}
+10
View File
@@ -0,0 +1,10 @@
/**
* 验证方法
*/
export const IS_MAC = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
export const isNil = (v) => v === null || v === undefined;
// 判断是否是空对象,排除数组
export const isEmptyObject = (obj:object) => typeof obj === 'object' && obj !== null && !Array.isArray(obj) && Object.keys(obj).length === 0;
+1
View File
@@ -0,0 +1 @@
export * from './pako';
+54
View File
@@ -0,0 +1,54 @@
import pako from "pako";
import { encode, decode } from 'js-base64';
// 压缩
export const zip = (data, needEncode = true) => {
if (!data) return data
// 判断数据是否需要转为JSON
const dataJson: string = typeof data !== 'string' ? JSON.stringify(data) : data
// 使用Base64.encode处理字符编码,兼容中文
const str = needEncode ? encode(dataJson) : dataJson;
let binaryString = pako.gzip(str) as number[];
let arr = Array.from(binaryString);
let s = "";
arr.forEach((item: number) => {
s += String.fromCharCode(item)
})
return btoa(s)
}
// 解压
export const unzip = (b64Data, needDecode = true) => {
let strData = atob(b64Data);
let charData = strData.split('').map(function (x) {
return x.charCodeAt(0);
});
let binData = new Uint8Array(charData);
let data = pako.ungzip(binData);
// ↓切片处理数据,防止内存溢出报错↓
let str = '';
const chunk = 8 * 1024
let i;
for (i = 0; i < data.length / chunk; i++) {
str += String.fromCharCode.apply(null, data.slice(i * chunk, (i + 1) * chunk));
}
str += String.fromCharCode.apply(null, data.slice(i * chunk));
// ↑切片处理数据,防止内存溢出报错↑
const unzipStr = needDecode ? decode(str) : str;
let result;
// 对象或数组进行JSON转换
try {
result = JSON.parse(unzipStr)
} catch (error) {
if (/Unexpected token o in JSON at position 0/.test(error as string)) {
// 如果没有转换成功,代表值为基本数据,直接赋值
result = unzipStr
}
}
return result;
}
+10
View File
@@ -0,0 +1,10 @@
/**
* Astral3D.Utils namespace
*/
export * from "./common";
export * from "./scene";
export * from "./handler";
export * from "./request";
export * from "./signals/signalRegister";
export {logger} from "./log/Logger";
export type {ILog} from "./log/Logger";
+120
View File
@@ -0,0 +1,120 @@
/**
* @author ErSan
* @email mlt131220@163.com
* @date 2025/2/24 下午2:21
* @description 日志记录器
*/
import {useAddSignal, useDispatchSignal,useRemoveSignal} from '@/hooks';
export interface ILog {
id: number;
message: string;
time: string;
level: string;
}
let _delLogFn,_clearLogFn,_historyChangedFn;
class Logger {
static Enum = Object.freeze({
TRACE: "trace",
DEBUG: "debug",
INFO: "info",
WARN: "warn",
ERROR: "error"
});
// 是否启用日志
enabled: boolean = true;
// 日志信息
logs: ILog[] = [];
constructor() {
_delLogFn = this.delLog.bind(this);
useAddSignal("deleteLog",_delLogFn);
_clearLogFn = this.clearLogs.bind(this);
useAddSignal("clearLogs",_clearLogFn);
_historyChangedFn = this.historyChanged.bind(this);
useAddSignal("historyChanged",_historyChangedFn);
}
log(methodName:string,message:string){
if(!this.enabled) return;
const _log = {
id: this.logs.length,
message,
level: methodName,
time: new Date().toLocaleString()
}
this.logs.unshift(_log);
useDispatchSignal("addLog", _log, this.logs);
}
trace(message:string) { this.log(Logger.Enum.TRACE, message); }
debug(message:string) { this.log(Logger.Enum.DEBUG, message); }
info(message:string) { this.log(Logger.Enum.INFO, message); }
warn(message:string) { this.log(Logger.Enum.WARN, message); }
error(message:string) { this.log(Logger.Enum.ERROR, message); }
/**
* 删除日志
* @param _log
*/
delLog(_log: ILog) {
this.logs = this.logs.filter(log => log.id!== _log.id);
}
/**
* 清空日志
*/
clearLogs() {
this.logs = [];
}
/**
* 历史记录变化回调
* @param cmd
*/
historyChanged(cmd){
if(!cmd?.name) return;
let msg = cmd.name;
const postposition = ['AddObjectCommand','RemoveObjectCommand','MoveObjectCommand'];
if(postposition.includes(cmd.type)){
msg = `${msg}: ${cmd.object.name} `;
}else if(cmd.object){
msg = `${cmd.object.name} ${msg.toLowerCase()}`;
}
if(cmd.newValue !== undefined && cmd.oldValue !== undefined){
let newValue = cmd.newValue;
let oldValue = cmd.oldValue;
if(typeof newValue === 'object'){
newValue = JSON.stringify(newValue);
}
if(typeof oldValue === 'object'){
oldValue = JSON.stringify(oldValue);
}
msg = `${msg}: ${oldValue}${newValue}`;
}
this.info(msg);
}
dispose() {
useRemoveSignal("deleteLog",_delLogFn)
_delLogFn = null;
useRemoveSignal("clearLogs",_clearLogFn)
_clearLogFn = null;
useRemoveSignal("historyChanged",_historyChangedFn)
_historyChangedFn = null;
}
}
export const logger = new Logger();
export default logger;
@@ -0,0 +1,74 @@
/**
* 创建一个handlerFetch
*
* @param limit 并发控制
* @param timeout 超时设
* @return function 返回一个函数
*/
export function fetchController(limit: number, timeout: number | boolean) {
limit = limit || 1;
timeout = timeout || false;
let count = 0, pool: any = [];
return function (url: string, options?: any) {
// 通过AbortController 控制 取消fetch 请求
let controller = new AbortController();
let signal = controller.signal;
// 判断是否需要超时
let isTimeout = options && options.timeout || timeout;
// 控制请求超时
let timeoutPromise = () => {
return new Promise((reject) => {
setTimeout(() => {
// resolve('请求超时');
reject('请求超时');
controller.abort();
}, options?.timeout || timeout)
})
}
// 返回fetch 本身
let taskPromise = () => new Promise((resolve, reject) => {
fetch(url, { signal, ...options }).then(res => {
resolve(res);
}).catch(err => {
reject(err)
})
});
// 通过Promise.race可以控制超时,并在访问结果中 去继续调用等待池中的请求
let task = () => (isTimeout ? Promise.race([timeoutPromise(), taskPromise()]) : taskPromise())
.then((res) => {
options.onSuccess && options.onSuccess(res)
next();
})
.catch((err) => {
options.onError && options.onError(err)
next();
});
// 定一个next 控制等待队列中的请求继续并发调用
let next = () => {
// 每执行一次next count - 1,然后比较当前的count 与 limit
// 如果小于limit 循环执行limit-count 次
count--;
if (count < limit && pool.length) {
let n = limit - count;
for (let i = 0; i < n; i++) {
let curTask: any = pool.shift();
curTask();
++count;
}
}
};
// 比较count与limit 大于等于limit的推入等待队列 小于limit的 count + 1,并执行fetch请求
if (count >= limit) {
pool.push(task);
} else {
++count;
task();
}
}
}
+1
View File
@@ -0,0 +1 @@
export * from './fetchController';
+118
View File
@@ -0,0 +1,118 @@
/**
* 性能状态监视器,基于stats.js
* 默认展示全部面板
*/
import ThreeStats from 'three/examples/jsm/libs/stats.module.js';
import Viewer from "@/core/viewer/Viewer.ts";
export class Stats {
private viewer: Viewer;
private threeStats: ThreeStats;
private panel = 0;
private _visible = true;
private fns:{
beforeRender:null | (() => void);
afterRender:null | (() => void);
} = {
beforeRender: null,
afterRender: null,
}
constructor(viewer: Viewer) {
this.viewer = viewer;
this.threeStats = new ThreeStats();
this.initEvent();
this.init();
}
get domElement():HTMLElement {
return this.threeStats.dom;
}
get visible(){
return this._visible;
}
set visible(visible:boolean) {
this._visible = visible;
this.domElement.style.display = visible ? "block" : 'none';
}
initEvent(){
this.fns.beforeRender = () => {
if(!this.visible) return;
this.threeStats.begin();
};
this.viewer.addEventListener("beforeRender",this.fns.beforeRender);
this.fns.afterRender = () => {
if(!this.visible) return;
this.threeStats.end();
};
this.viewer.addEventListener("afterRender",this.fns.afterRender);
}
init(){
const canvases = this.domElement.querySelectorAll("canvas");
canvases.forEach(canvas => {
canvas.style.width = "5rem";
canvas.style.height = "3rem";
canvas.style.display = "block";
});
}
showPanel(type: number | 'fps' | 'ms' | 'mb') {
if (typeof type === 'number') {
this.threeStats.showPanel(type);
this.panel = type;
return;
}
switch (type.toLowerCase()) {
case 'fps':
this.threeStats.showPanel(0);
this.panel = 0;
break;
case 'ms':
this.threeStats.showPanel(1);
this.panel = 1;
break;
case 'mb':
this.threeStats.showPanel(2);
this.panel = 2;
break;
}
}
showAllPanels(show:boolean) {
const canvases = this.domElement.querySelectorAll("canvas");
canvases.forEach(canvas => {
canvas.style.display = show ? "block" : "none";
});
if(!show){
this.showPanel(this.panel);
}
}
dispose(){
if(this.fns.beforeRender){
this.viewer.removeEventListener("beforeRender",this.fns.beforeRender);
this.fns.beforeRender = null;
}
if(this.fns.afterRender){
this.viewer.removeEventListener("afterRender",this.fns.afterRender);
this.fns.afterRender = null;
}
// @ts-ignore
this.threeStats = null;
}
}
+12
View File
@@ -0,0 +1,12 @@
import {Object3D,Box3,Vector3} from "three";
import CameraControls from "camera-controls";
export function focusObject(object:Object3D,controls:CameraControls,enableTransition: boolean = true){
const box3 = new Box3();
box3.setFromObject(object);
if(box3.isEmpty()){
box3.set(new Vector3(object.position.x-1, object.position.y-1, object.position.z-1), new Vector3(object.position.x+1, object.position.y+1, object.position.z+1));
}
return controls.fitToBox(box3,enableTransition);
}
+139
View File
@@ -0,0 +1,139 @@
import { Object3D,InstancedMesh,Mesh,Matrix4,AnimationAction,AnimationClip } from "three";
import App from "@/core/app/App";
export * from "./material";
export * from "./Stats";
export * from "./controls";
/**
* 获取对象到父对象的路径(结果不包含parentObject)
* @param parentObject
* @param object
* @param attr 对象属性名
* @param splitter 路径分隔符
*/
export function getParentPath(parentObject:Object3D,object:Object3D,attr = 'name',splitter = '/'){
if(!parentObject || !object) return '';
if(parentObject === object) return object[attr];
let path = [object[attr]];
const getPath = (obj) => {
if(!obj.parent) return;
if(obj.parent === parentObject) return;
path.unshift(obj.parent[attr]);
getPath(obj.parent);
}
getPath(object);
return path.join(splitter);
}
/**
* 获取鼠标按下的位置
* @param dom
* @param x
* @param y
*/
export function getMousePosition(dom: HTMLElement, x: number, y: number) {
const rect = dom.getBoundingClientRect();
return [(x - rect.left) / rect.width, (y - rect.top) / rect.height];
}
/**
* InstancedMesh 解出所有 mesh
*/
export function getMeshByInstancedMesh(instancedMesh:InstancedMesh){
const meshes:Mesh[] = [];
// if (instancedMesh.material === undefined) return meshes;
const matrixWorld = instancedMesh.matrixWorld;
const count = instancedMesh.count;
for (let instanceId = 0; instanceId < count; instanceId++) {
const _mesh = new Mesh();
const _instanceLocalMatrix = new Matrix4();
const _instanceWorldMatrix = new Matrix4();
_mesh.geometry = instancedMesh.geometry;
_mesh.material = instancedMesh.material;
// 计算每个实例的世界矩阵
instancedMesh.getMatrixAt(instanceId, _instanceLocalMatrix);
_instanceWorldMatrix.multiplyMatrices(matrixWorld, _instanceLocalMatrix);
// 网格表示这个单一实例
_mesh.matrixWorld = _instanceWorldMatrix;
meshes.push(_mesh);
}
return meshes;
}
/**
* 判断是否是group,因为导入有可能存在被定义为Object3D类型的group
*/
export function isGroup(object3D:Object3D){
return (object3D.isGroup || object3D.children.length > 0)
}
/**
* 判断是否是代理粒子发射器的3D对象
*/
export function isParticleObject(object:Object3D | null){
return object && object.type === "Particle" && object.emitter;
}
/**
* 判断是否是Billboard 3D对象
*/
export function isBillboardObject(object:Object3D | null){
return object && object.type === "Billboard" && object.options;
}
/**
* 判断是否是HtmlPanel 3D对象
*/
export function isHtmlPanelObject(object:Object3D | null){
return object && (object.isHtmlPanel || object.isHtmlSprite) && object.element;
}
/**
* 获取场景/物体中的所有动画
*/
export function getAnimations(object = App.scene) {
const animations: any = [];
object.traverse(function (object) {
animations.push(...object.animations);
});
return animations;
}
/**
* 获取场景/物体中的所有动画剪辑
*/
export function getAnimationClips(object:Object3D = App.scene) {
const animations: any = [];
object.traverse(function (object) {
object.animations.forEach(animation => {
if(animation instanceof AnimationAction){
animations.push(animation.getClip());
}
if(animation instanceof AnimationClip){
animations.push(animation);
}
})
});
return animations;
}
+134
View File
@@ -0,0 +1,134 @@
import JSZip from "jszip";
import * as THREE from "three";
import Loader from "@/core/loader/Loader.ts";
import App from "@/core/app/App.ts";
/**
* 解析材质zip包
*/
export function parseMaterialZip(zipFile: File): Promise<THREE.MeshStandardMaterial> {
return new Promise(async (resolve, reject) => {
const zip = new JSZip();
const zipContent = await zip.loadAsync(zipFile);
// 强制检查根目录下是否存在material.json
let materialJson: any = zipContent.file('material.json');
if (!materialJson) {
materialJson = {
textures: {},
properties: {}
}
// 获取所有文件路径列表(JSZip存储结构为 {路径: 文件对象})
const filePaths = Object.keys(zipContent.files);
for (let i = 0; i < filePaths.length; i++) {
const relativePath: string = filePaths[i];
const file = zipContent.file(relativePath);
if (!file) continue;
// 判断是否为文件(JSZip中目录路径以 '/' 结尾)
if (file.dir || relativePath.endsWith('/')) continue;
if (relativePath.includes("baseColor")) {
materialJson.textures.baseColor = relativePath;
} else if (relativePath.includes("normal")) {
materialJson.textures.normal = relativePath;
} else if (relativePath.includes("bump")) {
materialJson.textures.bump = relativePath;
} else if (relativePath.includes("displacement")) {
materialJson.textures.displacement = relativePath;
} else if (relativePath.includes("emissive")) {
materialJson.textures.emissive = relativePath;
} else if (relativePath.includes("alpha")) {
materialJson.textures.alpha = relativePath;
} else if (relativePath.includes("env")) {
materialJson.textures.env = relativePath;
} else if (relativePath.includes("light")) {
materialJson.textures.light = relativePath;
} else {
// arm可能在一张图上各占一个通道
if (relativePath.includes("roughness")) {
materialJson.textures.roughness = relativePath;
}
if (relativePath.includes("metalness")) {
materialJson.textures.metalness = relativePath;
}
if (relativePath.includes("ao")) {
materialJson.textures.ao = relativePath;
}
}
}
} else {
try {
materialJson = JSON.parse(await materialJson.async('text'));
} catch (error) {
reject(error);
}
}
// 并行加载所有纹理
const texturePromises = Object.entries(materialJson.textures).map(
async ([type, path]:any) => {
const textureFile = zipContent.file(path);
if (!textureFile) {
console.warn(`Texture file not found: ${path}`);
return { type, texture: null };
}
const extension = path.toString().split(".").pop()?.toLowerCase() || "jpg";
let textureBlob: Blob;
try {
// 特殊处理 EXR 格式
if (extension === "exr") {
const buffer = await textureFile.async("arraybuffer");
textureBlob = new Blob([buffer], { type: "image/x-exr" });
} else {
textureBlob = await textureFile.async("blob");
}
} catch (err) {
console.error(`Failed to load texture (${type}):`, err);
return { type, texture: null };
}
const textureUrl = URL.createObjectURL(textureBlob);
return new Promise<{ type: string; texture: THREE.Texture | null }>(
(resolve) => {
Loader.loadUrlTexture(
extension,
textureUrl,
(texture: THREE.Texture) => {
URL.revokeObjectURL(textureUrl); // 清理资源
resolve({ type, texture });
},
(error: Error) => {
URL.revokeObjectURL(textureUrl);
console.error(`Texture load error (${type}):`, error);
resolve({ type, texture: null });
}
);
}
);
}
);
// 等待所有纹理加载完成
const textureResults = await Promise.all(texturePromises);
const textures = textureResults.reduce((acc, { type, texture }) => {
if (texture) acc[type] = texture;
return acc;
}, {} as Record<string, THREE.Texture>);
// 处理无有效纹理的情况
if (Object.keys(textures).length === 0) {
throw new Error("No valid textures found in the zip file");
}
const material = await App.createPBRMaterial(textures, materialJson.properties || {});
resolve(material);
})
}
@@ -0,0 +1,157 @@
import Signal from 'signals';
interface SignalObj<T = any> {
add(listener: (...params: T[]) => void, listenerContext?: any, priority?: Number): void;
addOnce(listener: (...params: T[]) => void, listenerContext?: any, priority?: Number): void;
dispatch(...params: T[]): void;
remove(listener: (...params: T[]) => void, context?: any): void;
removeAll(): void;
setActive(active: boolean): void;
halt(): void;
dispose(): void;
has(listener: (...params: T[]) => void, context?: any): boolean;
active: boolean;
}
interface SignalRegister {
[s: string]: SignalObj;
}
/**
* App
* @path lib/core/App
*/
const appSignals: SignalRegister = {
// xr
enterXR: new Signal(),
offerXR: new Signal(),
leaveXR: new Signal(),
sceneCleared: new Signal(),
transformModeChanged: new Signal(),
snapChanged: new Signal(),
spaceChanged: new Signal(),
rendererCreated: new Signal(),
rendererUpdated: new Signal(),
rendererConfigUpdate: new Signal(),
rendererDetectKTX2Support: new Signal(),
sceneBackgroundChanged: new Signal(),
sceneEnvironmentChanged: new Signal(),
sceneFogSettingsChanged: new Signal(),
// 雨效果配置参数变更
sceneRainSettingsChanged: new Signal(),
// 雪效果配置参数变更
sceneSnowSettingsChanged: new Signal(),
sceneGraphChanged: new Signal(),
sceneRendered: new Signal(),
sceneResize: new Signal(),
cameraAdded: new Signal(),
cameraRemoved: new Signal(),
cameraChanged: new Signal(),
cameraReset: new Signal(),
geometryChanged: new Signal(),
objectSelected: new Signal(),
objectFocused: new Signal(),
objectFocusByUuid: new Signal(),
// 锁定模型
objectLocked: new Signal(),
// 解锁模型
objectUnlocked: new Signal(),
objectAdded: new Signal(),
objectChanged: new Signal(),
objectRemoved: new Signal(),
materialAdded: new Signal(),
materialChanged: new Signal(),
materialRemoved: new Signal(),
materialCurrentSlotChange: new Signal(),
scriptAdded: new Signal(),
scriptChanged: new Signal(),
scriptRemoved: new Signal(),
showGridChanged: new Signal(),
showHelpersChanged: new Signal(),
historyChanged: new Signal(),
// 场景主相机变更
viewportCameraChanged: new Signal(),
viewportShadingChanged: new Signal(),
intersectionsDetected: new Signal(),
pathTracerUpdated: new Signal(),
// 实例化ShaderMaterial类型内置材质
instantiateShaderMaterial: new Signal(),
// 场景加载完成
sceneLoadComplete: new Signal(),
// 添加日志
addLog: new Signal(),
// 删除日志
deleteLog: new Signal(),
// 清空日志
clearLogs: new Signal(),
// 动画更新渲染
animationMixerUpdate: new Signal(),
// 动画轨道时间变化(游标拖动)
timelineTimeChanged: new Signal(),
// 动画轨道行变化(添加/删除)
timelineRowChanged: new Signal(),
};
/**
* Viewer
* @path lib/core/Viewer
*/
const ViewerSignals: SignalRegister = {
// viewer 初始化完毕,即构造函数执行完毕
viewerInitCompleted: new Signal(),
// 插件注册
pluginInstall: new Signal(),
// 插件卸载
pluginUninstall: new Signal(),
IFCPropertiesVisible: new Signal(),
// 启用/禁用后处理
effectEnabledChange: new Signal(),
// 后处理通道配置变更
effectPassConfigChange: new Signal(),
// 粒子body使用的Object3D变更
particleBodyChanged: new Signal(),
// 粒子系统添加emitter后触发
particleSystemAddEmitter: new Signal(),
// 将emitter添加到粒子系统:fromJSON时触发
emitterAdd2ParticleSystem: new Signal(),
}
/**
* 图纸 相关
*/
const drawingSignals: SignalRegister = {
drawingMarkDone: new Signal(), // 新增/编辑 图纸标记完成回调
cadViewerResize: new Signal(), // 图纸面板移动
}
export const SignalsRegister: SignalRegister = {
...appSignals,
...ViewerSignals,
...drawingSignals,
};
// signal注册器
export const SignalsRegisterFn = (newSignals:Array<string>) => {
newSignals.forEach(key => {
SignalsRegister[key] = new Signal();
})
}