feat(All):Initial
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,147 @@
|
||||
import * as THREE from 'three';
|
||||
import { CSM as _CSM } from 'three/examples/jsm/csm/CSM.js';
|
||||
import { useDispatchSignal } from '@/hooks';
|
||||
import App from "@/core/app/App";
|
||||
|
||||
// Cascaded Shadow Maps(级联阴影映射,CSM)
|
||||
class CSM {
|
||||
instance:_CSM | null = null;
|
||||
|
||||
constructor(options:IAppProject.CSM){
|
||||
this.enabled = options.enabled;
|
||||
}
|
||||
|
||||
get enabled(){
|
||||
return !!this.instance;
|
||||
}
|
||||
|
||||
set enabled(isEnabled:boolean){
|
||||
if (!isEnabled){
|
||||
if(!this.instance) return;
|
||||
|
||||
// 移除csm创建的对象
|
||||
this.instance.remove();
|
||||
// 销毁csm插入的shader
|
||||
this.instance.dispose();
|
||||
|
||||
this.instance = null;
|
||||
|
||||
useDispatchSignal("sceneGraphChanged");
|
||||
return;
|
||||
}
|
||||
|
||||
/* 以下是启用csm的逻辑 */
|
||||
if(this.instance){
|
||||
this.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
const _config = App.project.getKey("csm");
|
||||
this.instance = new _CSM({
|
||||
maxFar: _config.maxFar,
|
||||
cascades: 4,
|
||||
mode: _config.mode,
|
||||
shadowMapSize: _config.shadowMapSize,
|
||||
lightDirection: new THREE.Vector3(_config.lightDirectionX, _config.lightDirectionY, _config.lightDirectionZ).normalize(),
|
||||
lightIntensity: _config.lightIntensity,
|
||||
lightNear: 0.1,
|
||||
lightFar: _config.maxFar * 2,
|
||||
lightMargin: 200,
|
||||
camera: App.viewportCamera,
|
||||
parent: App.scene
|
||||
});
|
||||
|
||||
this.instance.fade = true;
|
||||
|
||||
this.instance.lights.forEach(light => {
|
||||
// 忽略对csm相关object的处理
|
||||
light.ignore = true;
|
||||
light.target.ignore = true;
|
||||
|
||||
// 设置的灯光颜色
|
||||
light.color = new THREE.Color(_config.lightColor);
|
||||
light.shadow.bias = -0.00001;
|
||||
})
|
||||
|
||||
// 将场景中的全部材质添加到csm
|
||||
Object.values(App.materials).forEach(material => {
|
||||
this.setupMaterial(material);
|
||||
})
|
||||
|
||||
this.instance.updateFrustums();
|
||||
|
||||
useDispatchSignal("sceneGraphChanged");
|
||||
}
|
||||
|
||||
reset() {
|
||||
if (!this.instance) return;
|
||||
|
||||
this.enabled = false;
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
// 材质添加到csm
|
||||
setupMaterial(material:THREE.Material){
|
||||
if(!this.instance) return;
|
||||
|
||||
material.shadowSide = THREE.BackSide;
|
||||
this.instance.setupMaterial(material);
|
||||
}
|
||||
|
||||
updateProperty(key,value){
|
||||
if (!this.instance) return;
|
||||
|
||||
this.instance[key] = value;
|
||||
|
||||
this.instance.updateFrustums();
|
||||
|
||||
useDispatchSignal("sceneGraphChanged");
|
||||
}
|
||||
|
||||
updateLightColor(color: string){
|
||||
if (!this.instance) return;
|
||||
|
||||
this.instance.lights.forEach(light => {
|
||||
light.color = new THREE.Color(color);
|
||||
})
|
||||
|
||||
useDispatchSignal("sceneGraphChanged");
|
||||
}
|
||||
|
||||
updateLightIntensity(intensity: number){
|
||||
if (!this.instance) return;
|
||||
|
||||
this.instance.lightIntensity = intensity;
|
||||
|
||||
this.instance.lights.forEach(light => {
|
||||
light.intensity = intensity;
|
||||
})
|
||||
|
||||
useDispatchSignal("sceneGraphChanged");
|
||||
}
|
||||
|
||||
updateLightDirection(direction: "x" | "y" | "z", value: number){
|
||||
if (!this.instance) return;
|
||||
|
||||
this.instance.lightDirection[direction] = value;
|
||||
|
||||
useDispatchSignal("sceneGraphChanged");
|
||||
}
|
||||
|
||||
updateFrustums(){
|
||||
if (!this.instance) return;
|
||||
|
||||
this.instance.updateFrustums();
|
||||
|
||||
useDispatchSignal("sceneGraphChanged");
|
||||
}
|
||||
|
||||
update(){
|
||||
if (!this.instance) return;
|
||||
|
||||
App.viewportCamera.updateMatrixWorld();
|
||||
this.instance.update();
|
||||
}
|
||||
}
|
||||
|
||||
export {CSM}
|
||||
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* @author ErSan
|
||||
* @email mlt131220@163.com
|
||||
* @date 2025/4/26 13:59
|
||||
* @description 应用的全局配置,会存储在本地缓存
|
||||
*/
|
||||
import {Storage} from "./Storage";
|
||||
import {deepAssign, getNestedProperty} from "@/utils";
|
||||
import {ROAMING_CHARACTERS} from "@/constant";
|
||||
|
||||
class Config {
|
||||
protected storage: Storage;
|
||||
public config: IAppConfig.Config;
|
||||
|
||||
constructor(storage: Storage) {
|
||||
this.storage = storage;
|
||||
|
||||
this.config = {
|
||||
// UI相关配置
|
||||
theme: 'os',
|
||||
mainColor: '#7FE7C4',
|
||||
// 历史记录功能是否启用
|
||||
history: false,
|
||||
// 快捷键相关配置
|
||||
shortcuts: {
|
||||
translate: 'w',
|
||||
rotate: 'e',
|
||||
scale: 'r',
|
||||
undo: 'z',
|
||||
focus: 'f',
|
||||
},
|
||||
//漫游角色
|
||||
roamingCharacter: ROAMING_CHARACTERS.JACKIE
|
||||
};
|
||||
|
||||
this.syncStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置初始配置
|
||||
*/
|
||||
setConfig(_config:Record<string, any>){
|
||||
deepAssign(this.config,_config);
|
||||
|
||||
this.syncStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
* 和本地存储中的配置同步
|
||||
*/
|
||||
syncStorage(){
|
||||
for (let key of Object.keys(this.config)) {
|
||||
this.storage.getConfigItem(key).then(_value => {
|
||||
if(_value === null){
|
||||
this.storage.setConfigItem(key, this.config[key])
|
||||
}else{
|
||||
let newVal = _value;
|
||||
// 有可能会在代码开发过程中增加新的配置项
|
||||
if(this.config[key] && typeof this.config[key] === "object"){
|
||||
newVal = Object.assign({},this.config[key],_value);
|
||||
}
|
||||
this.config[key] = newVal;
|
||||
|
||||
if(newVal !== _value){
|
||||
this.storage.setConfigItem(key, newVal)
|
||||
}
|
||||
}
|
||||
}).catch(() => {
|
||||
this.storage.setConfigItem(key, this.config[key])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置
|
||||
* @param {string} key 可以多层级,需用.分割,如a.b.c
|
||||
*/
|
||||
getKey(key:string): any {
|
||||
return getNestedProperty(this.config,key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置配置项
|
||||
* @param {string} key 可以多层级,需用.分割,如a.b.c
|
||||
* @param {unknown} value 配置项的值
|
||||
*/
|
||||
setKey(key:string,value:unknown) {
|
||||
const keys = key.split(".");
|
||||
|
||||
if(keys.length === 1){
|
||||
this.config[key] = value;
|
||||
this.storage.setConfigItem(key,value);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let obj = this.config;
|
||||
for (let i = 0; i < keys.length; i++){
|
||||
if(keys.length - i === 1){
|
||||
obj[keys[i]] = value;
|
||||
break;
|
||||
}
|
||||
|
||||
obj = obj[keys[i]];
|
||||
}
|
||||
this.storage.setConfigItem(keys[0],this.config[keys[0]]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取快捷键配置
|
||||
* @param {string} key
|
||||
*/
|
||||
getShortcutItem(key: string) {
|
||||
return this.config.shortcuts[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置快捷键
|
||||
* @param {string} key
|
||||
* @param {any} value
|
||||
*/
|
||||
setShortcutItem(key: string,value:any) {
|
||||
this.config.shortcuts[key] = value;
|
||||
return this.storage.setConfigItem("shortcuts", this.config.shortcuts)
|
||||
}
|
||||
|
||||
clear() {
|
||||
for (let key of Object.keys(this.config)) {
|
||||
this.storage.removeConfigItem(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {Config};
|
||||
@@ -0,0 +1,224 @@
|
||||
import type { Object3D } from 'three';
|
||||
import * as Commands from '@/core/commands/Commands';
|
||||
import {useSignal} from "@/hooks";
|
||||
import App from "@/core/app/App";
|
||||
|
||||
const {dispatch:useDispathSignal,setActive} = useSignal();
|
||||
|
||||
interface Undos{
|
||||
id:number,
|
||||
name?:string,
|
||||
updatable:boolean,
|
||||
object:Object3D,
|
||||
type:string,
|
||||
script,
|
||||
attributeName:string,
|
||||
inMemory:boolean,
|
||||
json:string,
|
||||
update:(T)=>void,
|
||||
toJSON:()=>string,
|
||||
fromJSON:(string)=>void,
|
||||
undo:()=>void,
|
||||
execute:()=>void,
|
||||
}
|
||||
|
||||
class History {
|
||||
public undos:Array<Undos>;
|
||||
public redos:Array<Undos>;
|
||||
protected lastCmdTime:number;
|
||||
protected idCounter:number;
|
||||
|
||||
constructor() {
|
||||
this.undos = [];
|
||||
this.redos = [];
|
||||
this.lastCmdTime = Date.now();
|
||||
this.idCounter = 0;
|
||||
}
|
||||
|
||||
execute( cmd, optionalName ) {
|
||||
const lastCmd = this.undos[this.undos.length - 1];
|
||||
const timeDifference = Date.now() - this.lastCmdTime;
|
||||
|
||||
const isUpdatableCmd = lastCmd &&
|
||||
lastCmd.updatable &&
|
||||
cmd.updatable &&
|
||||
lastCmd.object === cmd.object &&
|
||||
lastCmd.type === cmd.type &&
|
||||
lastCmd.script === cmd.script &&
|
||||
lastCmd.attributeName === cmd.attributeName;
|
||||
|
||||
if ( isUpdatableCmd && cmd.type === 'SetScriptValueCommand' ) {
|
||||
// 当cmd.type为“SetScriptValueCommand”时,将忽略时间差异
|
||||
lastCmd.update( cmd );
|
||||
cmd = lastCmd;
|
||||
} else if ( isUpdatableCmd && timeDifference < 500 ) {
|
||||
lastCmd.update( cmd );
|
||||
cmd = lastCmd;
|
||||
} else {
|
||||
// 该命令不可更新,并作为历史记录的新部分添加
|
||||
this.undos.push( cmd );
|
||||
cmd.id = ++ this.idCounter;
|
||||
}
|
||||
|
||||
cmd.name = ( optionalName !== undefined ) ? optionalName : cmd.name;
|
||||
cmd.execute();
|
||||
cmd.inMemory = true;
|
||||
|
||||
if (App.config.getKey('history')) {
|
||||
//在执行后立即序列化cmd,并将json附加到cmd
|
||||
cmd.json = cmd.toJSON();
|
||||
}
|
||||
|
||||
this.lastCmdTime = Date.now();
|
||||
|
||||
// 清除所有redo命令
|
||||
this.redos = [];
|
||||
useDispathSignal("historyChanged",cmd);
|
||||
}
|
||||
|
||||
undo() {
|
||||
let cmd:Undos | undefined = undefined;
|
||||
if (this.undos.length > 0) {
|
||||
cmd = this.undos.pop() as Undos;
|
||||
if ( cmd.inMemory === false ) {
|
||||
cmd.fromJSON( cmd.json );
|
||||
}
|
||||
}
|
||||
|
||||
if ( cmd !== undefined ) {
|
||||
cmd.undo();
|
||||
this.redos.push( cmd );
|
||||
useDispathSignal("historyChanged",cmd);
|
||||
}
|
||||
return cmd;
|
||||
}
|
||||
|
||||
redo():Undos |undefined {
|
||||
let cmd:Undos |undefined = undefined;
|
||||
if ( this.redos.length > 0 ) {
|
||||
cmd = this.redos.pop() as Undos;
|
||||
if ( cmd.inMemory === false ) {
|
||||
cmd.fromJSON( cmd.json );
|
||||
}
|
||||
}
|
||||
|
||||
if ( cmd !== undefined ) {
|
||||
cmd.execute();
|
||||
this.undos.push( cmd );
|
||||
useDispathSignal( "historyChanged",cmd );
|
||||
}
|
||||
|
||||
return cmd;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
const history:{undos?:Array<string>,redos?:Array<string>} = {};
|
||||
history.undos = [];
|
||||
history.redos = [];
|
||||
if (!App.config.getKey('history')) return history;
|
||||
|
||||
//将Undos附加到历史记录
|
||||
for ( let i = 0; i < this.undos.length; i ++ ) {
|
||||
if (this.undos[ i ].hasOwnProperty( 'json' )) {
|
||||
history.undos.push(this.undos[i].json);
|
||||
}
|
||||
}
|
||||
|
||||
//将Redos附加到历史记录
|
||||
for ( let i = 0; i < this.redos.length; i ++ ) {
|
||||
if (this.redos[ i ].hasOwnProperty( 'json' )) {
|
||||
history.redos.push(this.redos[ i ].json);
|
||||
}
|
||||
}
|
||||
return history;
|
||||
}
|
||||
|
||||
fromJSON( json ) {
|
||||
if ( json === undefined ) return;
|
||||
|
||||
for ( let i = 0; i < json.undos.length; i ++ ) {
|
||||
const cmdJSON = json.undos[ i ];
|
||||
//创建一个类型为"json.type"的新对象
|
||||
const cmd = new Commands[cmdJSON.type](App);
|
||||
cmd.json = cmdJSON;
|
||||
cmd.id = cmdJSON.id;
|
||||
cmd.name = cmdJSON.name;
|
||||
this.undos.push( cmd );
|
||||
//设置最后使用的idCounter
|
||||
this.idCounter = ( cmdJSON.id > this.idCounter ) ? cmdJSON.id : this.idCounter;
|
||||
}
|
||||
|
||||
for ( let i = 0; i < json.redos.length; i ++ ) {
|
||||
const cmdJSON = json.redos[ i ];
|
||||
const cmd = new Commands[cmdJSON.type](App);
|
||||
cmd.json = cmdJSON;
|
||||
cmd.id = cmdJSON.id;
|
||||
cmd.name = cmdJSON.name;
|
||||
this.redos.push( cmd );
|
||||
this.idCounter = ( cmdJSON.id > this.idCounter ) ? cmdJSON.id : this.idCounter;
|
||||
}
|
||||
|
||||
// 选择最后执行的undo命令
|
||||
useDispathSignal( "historyChanged",this.undos[ this.undos.length - 1 ] );
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.undos = [];
|
||||
this.redos = [];
|
||||
this.idCounter = 0;
|
||||
|
||||
useDispathSignal("historyChanged");
|
||||
}
|
||||
|
||||
goToState( id:number ) {
|
||||
setActive("sceneGraphChanged",false);
|
||||
setActive("historyChanged",false);
|
||||
|
||||
//下一个弹出的CMD
|
||||
let cmd:Undos |undefined = this.undos.length > 0 ? this.undos[ this.undos.length - 1 ] : undefined;
|
||||
if ( cmd === undefined || id > cmd.id ) {
|
||||
cmd = this.redo();
|
||||
while ( cmd !== undefined && id > cmd.id ) {
|
||||
cmd = this.redo();
|
||||
}
|
||||
} else {
|
||||
while ( true ) {
|
||||
cmd = this.undos[ this.undos.length - 1 ];
|
||||
if ( cmd === undefined || id === cmd.id ) break;
|
||||
this.undo();
|
||||
}
|
||||
}
|
||||
|
||||
setActive("sceneGraphChanged",true);
|
||||
setActive("historyChanged",true);
|
||||
|
||||
useDispathSignal("sceneGraphChanged");
|
||||
useDispathSignal("historyChanged",cmd);
|
||||
}
|
||||
|
||||
enableSerialization( id ) {
|
||||
/**
|
||||
* 因为可能有命令在 this.undos && this.redos
|
||||
* 没有被.toJSON()序列化的,我们返回
|
||||
*/
|
||||
this.goToState(-1);
|
||||
|
||||
setActive("sceneGraphChanged",false);
|
||||
setActive("historyChanged",false);
|
||||
|
||||
let cmd:Undos |undefined = this.redo();
|
||||
while ( cmd !== undefined ) {
|
||||
if (!cmd.hasOwnProperty('json')) {
|
||||
cmd.json = cmd.toJSON();
|
||||
}
|
||||
cmd = this.redo();
|
||||
}
|
||||
|
||||
setActive("sceneGraphChanged",true);
|
||||
setActive("historyChanged",true);
|
||||
|
||||
this.goToState( id );
|
||||
}
|
||||
}
|
||||
|
||||
export { History };
|
||||
@@ -0,0 +1,402 @@
|
||||
/**
|
||||
* @author ErSan
|
||||
* @email mlt131220@163.com
|
||||
* @date 2025/4/26 13:59
|
||||
* @description 当前项目相关信息
|
||||
*/
|
||||
import {getNestedProperty} from "@/utils";
|
||||
import type {App} from "../App";
|
||||
import {useRemoveSignal, useAddSignal, useDispatchSignal} from '@/hooks';
|
||||
import {FPS_OPTIONS} from "@/constant";
|
||||
|
||||
export const defaultProjectInfo = (): IAppProject.Info => ({
|
||||
// 项目运行是否启用xr
|
||||
xr: false,
|
||||
// 渲染器相关配置
|
||||
renderer: {
|
||||
// 渲染帧率上限,默认60
|
||||
fps: FPS_OPTIONS.HIGH,
|
||||
antialias: true,
|
||||
toneMapping: 0, // NoToneMapping
|
||||
toneMappingExposure: 1,
|
||||
shadow: {
|
||||
enabled: true,
|
||||
type: 2, // PCF Soft
|
||||
},
|
||||
},
|
||||
// 级联阴影映射
|
||||
csm: {
|
||||
enabled: false,
|
||||
fade: false,
|
||||
maxFar: 1000,
|
||||
mode: "practical",
|
||||
shadowMapSize: 2048,
|
||||
lightDirectionX: -1,
|
||||
lightDirectionY: -1,
|
||||
lightDirectionZ: -1,
|
||||
lightIntensity: 1,
|
||||
lightColor: "#ffffff"
|
||||
},
|
||||
// 后处理
|
||||
effect: {
|
||||
enabled: false,
|
||||
// 描边线
|
||||
Outline: {
|
||||
enabled: true,
|
||||
// 边缘的强度,值越高边框范围越大
|
||||
edgeStrength: Number(3.0),
|
||||
// 发光强度
|
||||
edgeGlow: Number(0.2),
|
||||
// 边缘浓度
|
||||
edgeThickness: Number(1.0),
|
||||
// 闪烁频率,值越大频率越低
|
||||
pulsePeriod: Number(0.0),
|
||||
// 禁用纹理以获得纯线的效果
|
||||
usePatternTexture: false,
|
||||
// 可见边缘的颜色
|
||||
visibleEdgeColor: "#ffee00",
|
||||
// 不可见边缘的颜色
|
||||
hiddenEdgeColor: "#ff6a00"
|
||||
},
|
||||
// 抗锯齿
|
||||
FXAA: {
|
||||
enabled: true,
|
||||
},
|
||||
// 辉光
|
||||
UnrealBloom: {
|
||||
enabled: false,
|
||||
// 光晕阈值,值越小,效果越明显
|
||||
threshold: 0,
|
||||
// 光晕强度
|
||||
strength: 1,
|
||||
// 光晕半径
|
||||
radius: 0
|
||||
},
|
||||
// 背景虚化
|
||||
Bokeh: {
|
||||
enabled: false,
|
||||
// 焦距,调整远近,对焦时才会清晰
|
||||
focus: 500.0,
|
||||
// 孔径,类似相机孔径调节
|
||||
aperture: 0.00005,
|
||||
// 最大模糊程度
|
||||
maxblur: 0.01
|
||||
},
|
||||
// 像素风
|
||||
Pixelate: {
|
||||
enabled: false,
|
||||
// 像素大小
|
||||
pixelSize: 6,
|
||||
// 法向边缘强度
|
||||
normalEdgeStrength: 0.3,
|
||||
// 深度边缘强度
|
||||
depthEdgeStrength: 0.4,
|
||||
},
|
||||
// 半色调
|
||||
Halftone: {
|
||||
enabled: false,
|
||||
// 形状:点,椭圆,线,正方形
|
||||
shape: 1,
|
||||
// 半径
|
||||
radius: 4,
|
||||
// R色旋转
|
||||
rotateR: Math.PI / 12,
|
||||
// G色旋转
|
||||
rotateG: Math.PI / 12 * 2,
|
||||
// B色旋转
|
||||
rotateB: Math.PI / 12 * 3,
|
||||
// 分散度
|
||||
scatter: 0,
|
||||
// 混合度
|
||||
blending: 1,
|
||||
// 混合模式:线性,相乘,相加,明亮,昏暗
|
||||
blendingMode: 1,
|
||||
// 灰度
|
||||
greyscale: false,
|
||||
},
|
||||
// LUT颜色滤镜
|
||||
LUT: {
|
||||
enabled: false,
|
||||
lut: 'Bourbon 64.CUBE',
|
||||
intensity: 1
|
||||
},
|
||||
// 运动残影
|
||||
Afterimage: {
|
||||
enabled: false,
|
||||
damp: 0.95
|
||||
}
|
||||
},
|
||||
// 天气
|
||||
weather: {
|
||||
fog: {
|
||||
enabled: false,
|
||||
type: "Fog", // Fog, FogExp2
|
||||
color: "#ffffff",
|
||||
near: 0.10,
|
||||
far: 50.0,
|
||||
density: 0.05,
|
||||
},
|
||||
rain: {
|
||||
enabled: false,
|
||||
speed: 0.4,
|
||||
color: "#ffffff",
|
||||
size: 0.5,
|
||||
radian: 95,
|
||||
alpha: 0.4
|
||||
},
|
||||
snow: {
|
||||
enabled: false,
|
||||
size: 0.5,
|
||||
density: 1.0,
|
||||
speed: 1.0,
|
||||
alpha: 0.5,
|
||||
accumulation: false,
|
||||
}
|
||||
},
|
||||
// 场景信息
|
||||
sceneInfo: {
|
||||
// 场景id,使用uuid
|
||||
id: "",
|
||||
// 场景名称
|
||||
sceneName: "",
|
||||
// 场景分类 城市、园区、工厂、楼宇、设备、其他...
|
||||
sceneType: "其他",
|
||||
// 场景描述
|
||||
sceneIntroduction: "",
|
||||
// 场景版本
|
||||
sceneVersion: 1,
|
||||
// 项目类型。0:Web3D-THREE 1:WebGIS-Cesium
|
||||
projectType: 0,
|
||||
// 场景封面图
|
||||
coverPicture: "",
|
||||
// 场景是否包含图纸
|
||||
hasDrawing: false,
|
||||
// 场景zip包地址
|
||||
zip: "",
|
||||
// 场景zip包大小
|
||||
zipSize: "0",
|
||||
// WebGIS-Cesium 类型项目的基础Cesium配置
|
||||
cesiumConfig: undefined
|
||||
},
|
||||
// 图纸
|
||||
drawing: {
|
||||
// 是否已上传图纸
|
||||
isUploaded: false,
|
||||
// 图片base64 / cad文件路径
|
||||
imgSrc: "",
|
||||
// 是否cad
|
||||
isCad: false,
|
||||
// cad图层信息
|
||||
layers: {},
|
||||
// 是否正在绘制矩形标记
|
||||
isDrawingRect: false,
|
||||
// 选中的矩形索引
|
||||
selectedRectIndex: -1,
|
||||
// 标记列表
|
||||
markList: [],
|
||||
// 标记图纸时的图纸属性信息
|
||||
imgInfo: {
|
||||
width: 0,
|
||||
height: 0
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
let drawingMarkDoneFn: null | ((type: "add" | "update", rect: IAppProject.DrawingMark) => void) = null;
|
||||
|
||||
class Project {
|
||||
public app: App
|
||||
public info: IAppProject.Info;
|
||||
|
||||
constructor(app: App) {
|
||||
this.app = app;
|
||||
|
||||
this.info = defaultProjectInfo();
|
||||
|
||||
drawingMarkDoneFn = this.drawingMarkListChange.bind(this);
|
||||
useAddSignal("drawingMarkDone", drawingMarkDoneFn);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置
|
||||
* @param {string} key 可以多层级,需用.分割,如a.b.c
|
||||
*/
|
||||
getKey(key: string): any {
|
||||
return getNestedProperty(this.info, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置配置项,配置变更自动执行相应处理
|
||||
* @param {string} key 可以多层级,需用.分割,如a.b.c
|
||||
* @param {unknown} value 配置项的值
|
||||
* @param {boolean} executeAction 是否自动执行相应处理
|
||||
*/
|
||||
setKey(key: string, value: unknown,executeAction: boolean = true) {
|
||||
const keys = key.split(".");
|
||||
|
||||
if (keys.length === 1) {
|
||||
this.info[key] = value;
|
||||
} else {
|
||||
let obj = this.info;
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
if (keys.length - i === 1) {
|
||||
obj[keys[i]] = value;
|
||||
break;
|
||||
}
|
||||
|
||||
obj = obj[keys[i]];
|
||||
}
|
||||
}
|
||||
|
||||
/* 执行相应处理 */
|
||||
if(!executeAction || ["xr","sceneInfo","drawing"].includes(keys[0])) return;
|
||||
|
||||
const secondProperty = keys[1];
|
||||
// 如果setKey传入的是第一层级的变更且不是特殊单层处理的属性,则遍历为第二层级递归以执行相应处理
|
||||
if(!secondProperty && !["renderer"].includes(key)) {
|
||||
const propertyValue = this.info[key];
|
||||
|
||||
Object.keys(propertyValue).forEach(secondKey => {
|
||||
this.setKey(`${key}.${secondKey}`, propertyValue[secondKey]);
|
||||
})
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (key.startsWith("renderer")) {
|
||||
if(!this.app.viewer) return;
|
||||
|
||||
if (["renderer.antialias","renderer"].includes(key)) {
|
||||
this.app.viewer.createEngine();
|
||||
} else {
|
||||
this.app.viewer.renderer.shadowMap.enabled = this.info.renderer.shadow.enabled;
|
||||
this.app.viewer.renderer.shadowMap.type = this.info.renderer.shadow.type;
|
||||
this.app.viewer.renderer.toneMapping = this.info.renderer.toneMapping;
|
||||
this.app.viewer.renderer.toneMappingExposure = this.info.renderer.toneMappingExposure;
|
||||
this.app.FPS = this.info.renderer.fps;
|
||||
|
||||
useDispatchSignal("rendererUpdated");
|
||||
}
|
||||
} else if (key.startsWith("csm")) {
|
||||
switch (key) {
|
||||
case "csm.enabled":
|
||||
this.app.csm.enabled = this.info.csm.enabled;
|
||||
break;
|
||||
case "csm.fade":
|
||||
case "csm.maxFar":
|
||||
case "csm.mode":
|
||||
this.app.csm.updateProperty(secondProperty, this.info.csm[secondProperty]);
|
||||
break;
|
||||
case "csm.shadowMapSize":
|
||||
this.app.csm.reset();
|
||||
break;
|
||||
case "csm.lightColor":
|
||||
this.app.csm.updateLightColor(this.info.csm.lightColor);
|
||||
break;
|
||||
case "csm.lightIntensity":
|
||||
this.app.csm.updateLightIntensity(this.info.csm.lightIntensity);
|
||||
break;
|
||||
case "csm.lightDirectionX":
|
||||
this.app.csm.updateLightDirection('x',this.info.csm.lightDirectionX);
|
||||
break;
|
||||
case "csm.lightDirectionY":
|
||||
this.app.csm.updateLightDirection('y',this.info.csm.lightDirectionY);
|
||||
break;
|
||||
case "csm.lightDirectionZ":
|
||||
this.app.csm.updateLightDirection('z',this.info.csm.lightDirectionZ);
|
||||
break;
|
||||
}
|
||||
}else if(key.startsWith("effect")){
|
||||
if(key === "effect.enabled"){
|
||||
useDispatchSignal("effectEnabledChange",this.info.effect.enabled);
|
||||
}else{
|
||||
useDispatchSignal("effectPassConfigChange",secondProperty,this.info.effect[secondProperty]);
|
||||
}
|
||||
}else if(key.startsWith("weather")){
|
||||
switch (key){
|
||||
case "weather.fog":
|
||||
useDispatchSignal("sceneFogSettingsChanged");
|
||||
break;
|
||||
case "weather.rain":
|
||||
useDispatchSignal("sceneRainSettingsChanged");
|
||||
break;
|
||||
case "weather.snow":
|
||||
useDispatchSignal("sceneSnowSettingsChanged");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置图纸src
|
||||
*/
|
||||
setDrawingSrc(src: string) {
|
||||
this.info.drawing.isCad = src.split(".").pop() === "dxf";
|
||||
this.info.drawing.imgSrc = src;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置图纸图层显示隐藏
|
||||
* @param layerName
|
||||
* @param visible
|
||||
*/
|
||||
setDrawingLayerVisible(layerName: string, visible: boolean) {
|
||||
this.info.drawing.layers[layerName].visible = visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置图纸所有图层显示隐藏
|
||||
* @param visible
|
||||
*/
|
||||
setDrawingLayerAllVisible(visible: boolean) {
|
||||
for (let key in this.info.drawing.layers) {
|
||||
this.info.drawing.layers[key].visible = visible;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 图纸标记变更
|
||||
* @param type
|
||||
* @param rect
|
||||
*/
|
||||
drawingMarkListChange(type: "add" | "update", rect: IAppProject.DrawingMark) {
|
||||
switch (type) {
|
||||
case "add":
|
||||
this.info.drawing.markList.push(rect);
|
||||
break;
|
||||
case "update":
|
||||
const index = this.info.drawing.markList.findIndex(item => item.modelUuid === rect.modelUuid);
|
||||
if (index !== -1) {
|
||||
this.info.drawing.markList[index] = rect;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置图纸配置,一般用于清除图纸状态
|
||||
*/
|
||||
resetDrawing() {
|
||||
this.info.drawing = defaultProjectInfo().drawing;
|
||||
}
|
||||
|
||||
// /**
|
||||
// * 清空所有项目配置
|
||||
// */
|
||||
// clear(){
|
||||
// const sceneInfo = {...this.info.sceneInfo};
|
||||
//
|
||||
// this.info = defaultProjectInfo();
|
||||
//
|
||||
// this.info.sceneInfo = sceneInfo;
|
||||
// }
|
||||
|
||||
dispose() {
|
||||
if (drawingMarkDoneFn) {
|
||||
useRemoveSignal("drawingMarkDone", drawingMarkDoneFn);
|
||||
drawingMarkDoneFn = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {Project};
|
||||
@@ -0,0 +1,18 @@
|
||||
import * as THREE from 'three';
|
||||
import Loader from "@/core/loader/Loader";
|
||||
|
||||
class Resource{
|
||||
constructor() { }
|
||||
|
||||
loadURLTexture(url: string | THREE.Texture, onload: (tex: THREE.Texture) => void = ()=>{}, onerror: (err: any) => void = ()=>{}) {
|
||||
if(url instanceof THREE.Texture) {
|
||||
onload(url);
|
||||
return url;
|
||||
}
|
||||
|
||||
const extension = url.split(".").pop()?.toLowerCase() as string;
|
||||
return Loader.loadUrlTexture(extension, url, onload,onerror);
|
||||
}
|
||||
}
|
||||
|
||||
export {Resource};
|
||||
@@ -0,0 +1,85 @@
|
||||
import {useDispatchSignal, useAddSignal} from '@/hooks';
|
||||
import { MeshLambertMaterial } from "three";
|
||||
import App from "@/core/app/App.ts";
|
||||
import Loader from "@/core/loader/Loader.ts";
|
||||
import * as THREE from "three";
|
||||
|
||||
class Selector {
|
||||
public lastIsIFC = false; // 上一次选中的是否是IFC模型
|
||||
public lastIFCModelID :number | null = null; // 上一次选中的IFC模型ID
|
||||
private preselectMat = new MeshLambertMaterial({
|
||||
transparent: true,
|
||||
opacity: 0.6,
|
||||
color: 0xff88ff,
|
||||
depthTest: false,
|
||||
});
|
||||
|
||||
constructor() {
|
||||
// signals
|
||||
useAddSignal("intersectionsDetected",async (intersects) => {
|
||||
if(this.lastIFCModelID !== null){
|
||||
// 移除之前IFC模型的高亮部分
|
||||
Loader._ifcLoader.ifcManager.removeSubset(this.lastIFCModelID, this.preselectMat);
|
||||
this.lastIFCModelID = null;
|
||||
}
|
||||
|
||||
if (intersects.length > 0) {
|
||||
const object = intersects[0].object;
|
||||
|
||||
// ---- 2023/8/10 添加IFC模型检测判断-----
|
||||
if(object.isIFC){
|
||||
const index = intersects[0].faceIndex;
|
||||
const geometry = object.geometry;
|
||||
const ifc = Loader._ifcLoader.ifcManager;
|
||||
const id = ifc.getExpressId(geometry, index);
|
||||
|
||||
this.lastIFCModelID = object.modelID;
|
||||
const props = await ifc.getItemProperties(this.lastIFCModelID as number, id,true);
|
||||
|
||||
useDispatchSignal("IFCPropertiesVisible",true,props)
|
||||
this.lastIsIFC = true;
|
||||
|
||||
// TODO 部件选中
|
||||
// 创建子集
|
||||
Loader._ifcLoader.ifcManager.createSubset({
|
||||
modelID: this.lastIFCModelID as number,
|
||||
ids: [id],
|
||||
material: this.preselectMat,
|
||||
scene: App.scene,
|
||||
removePrevious: true,
|
||||
});
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if(this.lastIsIFC){
|
||||
useDispatchSignal("IFCPropertiesVisible",false)
|
||||
this.lastIsIFC = false;
|
||||
}
|
||||
|
||||
if(object.proxy){
|
||||
this.select(object.proxy);
|
||||
} else {
|
||||
this.select(object);
|
||||
}
|
||||
} else {
|
||||
this.select( null );
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
select(object:THREE.Object3D | null) {
|
||||
if (App.selected === object) return;
|
||||
|
||||
App.selected = object;
|
||||
|
||||
useDispatchSignal("objectSelected",object, App.locked);
|
||||
useDispatchSignal("sceneGraphChanged");
|
||||
}
|
||||
|
||||
deselect() {
|
||||
this.select(null);
|
||||
}
|
||||
}
|
||||
|
||||
export {Selector};
|
||||
@@ -0,0 +1,61 @@
|
||||
import localforage from 'localforage';
|
||||
|
||||
class Storage {
|
||||
public dbs: { modelsDB: LocalForage,otherDB:LocalForage,configDB:LocalForage };
|
||||
|
||||
constructor() {
|
||||
this.dbs = this.initDB();
|
||||
}
|
||||
|
||||
initDB(){
|
||||
return {
|
||||
modelsDB: localforage.createInstance({
|
||||
name: 'modelsDB',
|
||||
}),
|
||||
otherDB: localforage.createInstance({
|
||||
name: 'otherDB'
|
||||
}),
|
||||
configDB: localforage.createInstance({
|
||||
name: 'configDB'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
setModel(key: string, value: any){
|
||||
this.dbs.modelsDB.setItem(key, value);
|
||||
}
|
||||
|
||||
async getModel(key: string){
|
||||
return await this.dbs.modelsDB.getItem(key);
|
||||
}
|
||||
|
||||
removeModel(key: string){
|
||||
return this.dbs.modelsDB.removeItem(key);
|
||||
}
|
||||
|
||||
setOtherItem(key: string, value: any){
|
||||
this.dbs.otherDB.setItem(key, value);
|
||||
}
|
||||
|
||||
async getOtherItem(key:string){
|
||||
return await this.dbs.otherDB.getItem(key);
|
||||
}
|
||||
|
||||
removeOtherItem(key: string){
|
||||
return this.dbs.otherDB.removeItem(key);
|
||||
}
|
||||
|
||||
setConfigItem(key: string, value: any){
|
||||
return this.dbs.configDB.setItem(key, value);
|
||||
}
|
||||
|
||||
async getConfigItem(key:string){
|
||||
return await this.dbs.configDB.getItem(key);
|
||||
}
|
||||
|
||||
removeConfigItem(key: string){
|
||||
return this.dbs.configDB.removeItem(key);
|
||||
}
|
||||
}
|
||||
|
||||
export {Storage};
|
||||
@@ -0,0 +1,7 @@
|
||||
export {Config} from "./Config";
|
||||
export {CSM} from "./CSM";
|
||||
export {History} from "./History";
|
||||
export {Project,defaultProjectInfo} from "./Project";
|
||||
export {Resource} from "./Resource";
|
||||
export {Selector} from "./Selector";
|
||||
export {Storage} from "./Storage";
|
||||
Reference in New Issue
Block a user