diff --git a/README.md b/README.md index 98b3ef6..a8b3f4d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,29 @@ # 中国石化加油站项目 +## 🌼v1.1.0 + +### 🌻 简介 + +> 时间:2022、1、22 周六 +> 优化加载 +> 增加镜头限制、增加吸附功能 + +### 🌻 详情 + +> 前端部分 + +- [🚩 重大变更] +- [🌱 新增] +- [🍀 完善] + +> 三维部分 + +- [🚩 重大变更] 增加对关联复制模型的支持,可以大幅减少 bin 文件的大小,增加解析速度与渲染速度 +- [🚩 重大变更] 暂时停止 1+n 的模型加载模式(indexDB 问题以在另外的地方进行解决),充分利用并发加载 +- [🌱 新增] 摄像头旋转时设置限制,避免看到场景的底部 +- [🌱 新增] 增加建筑吸附、对齐到场景中的功能,确定对齐规则,使用“WAI”节点作为插槽 +- [🍀 完善] + ## 🌼v1.0.9 ### 🌻 简介 diff --git a/src/app/babylon/controller/scene-manager.ts b/src/app/babylon/controller/scene-manager.ts index 3a62441..4f1d9fc 100644 --- a/src/app/babylon/controller/scene-manager.ts +++ b/src/app/babylon/controller/scene-manager.ts @@ -11,6 +11,7 @@ import { EventState, HemisphericLight, HighlightLayer, + InstancedMesh, IParticleSystem, ISceneLoaderProgressEvent, Mesh, @@ -57,10 +58,13 @@ import { ModelInfo_mark_multiArrow } from '../model/info/mark/other/mark-plan-mu import { ModelInfo_mark_particle } from '../model/info/mark/other/mark-plan-particle-info'; import { FacilityPosType, ModelData_facility } from '../model/data/model-data/model-data-facility'; import { LoadTool } from '../tool/load-tool'; +import { flatten } from 'earcut'; //场景管理器 export class SceneManager { + + //----------------Camera-----------------\\ public engine: Engine; @@ -75,7 +79,7 @@ export class SceneManager { static s_openLight: boolean = true;//开启光照效果(关闭是要同时关闭阴影) - static s_openShadow: boolean = true;//开启阴影(必须开启阴影) + static s_openShadow: boolean = true;//开启阴影(必须开启光照) static s_openSkyBox: boolean = true;//使用天空盒 static s_environmentCubeTexture: CubeTexture;//环境所用的cubeTexture static s_openEnvironmentReflection: boolean = true;//使用环境反射 @@ -161,7 +165,7 @@ export class SceneManager { ); camera.minZ = 0;//最近拍摄距离 camera.maxZ = 6000; //摄像机拍摄的最远距离 - // camera.upperBetaLimit = 1.5; //beta方向上的旋转限制(防止看到模型底面) + camera.upperBetaLimit = 1.5; //beta方向上的旋转限制(防止看到模型底面) camera.lowerRadiusLimit = 1; //相机距离拍摄目标的最小距离(防止穿插) camera.setTarget(Vector3.Zero()); //设置拍摄目标 camera.radius = 100; @@ -229,7 +233,7 @@ export class SceneManager { } else { - if (this.sunLight == null) { + if (this.sunLight != null) { this.sunLight.setEnabled(false); } @@ -241,7 +245,7 @@ export class SceneManager { * 初始化阴影 */ public updateShadow() { - if (SceneManager.s_openShadow) { + if (SceneManager.s_openLight && SceneManager.s_openShadow) { if (this.shadowGenerator == null) { this.shadowGenerator = new ShadowGenerator(2048, this.sunLight); } @@ -663,7 +667,7 @@ export class SceneManager { canPick = true; //} - if (SceneManager.s_openShadow) { + if (SceneManager.s_openShadow && !(newMeshes[i] instanceof InstancedMesh)) { newMeshes[i].receiveShadows = true; // let mat = newMeshes[i].material; @@ -873,13 +877,22 @@ export class SceneManager { */ public static meshAdsorbY(mover: AbstractMesh, target: AbstractMesh, pickPos: Vector3) { - ModeManager.log("吸附模型" + target); + // console.log("吸附模型" + target); - let targetRoot = SceneManager.getRootTransformNode(target); + let targetRoot = SceneManager.getRootTransformNode(target) as AbstractMesh; if (mover == targetRoot) { return; } + + //先通过吸附点吸附 + let byAdsorbPoint = BabylonTool.adsorb2MeshByPoint(mover, targetRoot, "WAI") + + //完成吸附了 + if (byAdsorbPoint) { + return; + } + let aimPos_y: number = 0; let direction = 0.5; diff --git a/src/app/babylon/model/info/model/model-info-building.ts b/src/app/babylon/model/info/model/model-info-building.ts index 3597855..42f7327 100644 --- a/src/app/babylon/model/info/model/model-info-building.ts +++ b/src/app/babylon/model/info/model/model-info-building.ts @@ -12,6 +12,7 @@ import { StatusManager } from "src/app/babylon/controller/status/status-manager" import { BabylonUIStyleTool } from "src/app/babylon/tool/babylon-ui-style-tool"; import { GizmoTool } from "src/app/babylon/tool/gizmo-tool"; import { TsTool } from "src/app/babylon/tool/ts-tool"; +import { BuildingWindow } from "src/app/babylon/view/building-window/building-window"; import { FacilityWindow } from "src/app/babylon/view/facility-window/facility-window"; @@ -27,7 +28,7 @@ export class ModelInfo_building extends ModelInfo { facilityInfos: FacilityInfoByType[] = []; buildingType: BuildingType = BuildingType.Normal; - neiRoot: TransformNode; //facilityRoot参照的节点 + anchorRoot: TransformNode; //facilityRoot参照的节点 updateObserver: Observer; onSetModelBox() { @@ -38,30 +39,29 @@ export class ModelInfo_building extends ModelInfo { initFacilityRoot() { let allTransformNode = this.modelBox.getChildTransformNodes(); - this.neiRoot = null; + this.anchorRoot = null; for (let i = 0; i < allTransformNode.length; i++) { if (TsTool.stringContain(allTransformNode[i].name, "nei")) { - this.neiRoot = allTransformNode[i]; + this.anchorRoot = allTransformNode[i]; break; } } - if (this.neiRoot == null) { + if (this.anchorRoot == null) { for (let i = 0; i < allTransformNode.length; i++) { if (TsTool.stringContain(allTransformNode[i].name, "WAI")) { - this.neiRoot = allTransformNode[i]; - + this.anchorRoot = allTransformNode[i]; break; } } } - if (this.neiRoot == null) { + if (this.anchorRoot == null) { if (!TsTool.stringContain(this.modelBox.name, "Box")) { console.error("没有关键节点", this.modelBox.name); } - this.neiRoot = this.modelBox; + this.anchorRoot = this.modelBox; } if (this.facilityRoot != null) { @@ -207,13 +207,17 @@ export class ModelInfo_building extends ModelInfo { onBeforeRender() { // console.log(this.modelBox.absolutePosition); - this.facilityRoot.position = this.neiRoot.absolutePosition.clone(); - this.facilityRoot.rotation = this.neiRoot.rotation.clone(); + this.facilityRoot.position = this.anchorRoot.absolutePosition.clone(); + this.facilityRoot.rotation = this.anchorRoot.rotation.clone(); //if (this.modelBox.rotationQuaternion != null) { - this.facilityRoot.rotationQuaternion = this.neiRoot.absoluteRotationQuaternion.clone(); - if (this.neiRoot.parent != null && (this.neiRoot.parent as TransformNode).scaling.y < 0) { + this.facilityRoot.rotationQuaternion = this.anchorRoot.absoluteRotationQuaternion.clone(); + if (this.anchorRoot.parent != null && (this.anchorRoot.parent as TransformNode).scaling.y < 0) { this.facilityRoot.scaling.y = -1; } + if (TsTool.stringContain(this.anchorRoot.name, "WAI") && BuildingWindow.instance != null) { + BuildingWindow.instance.setLimitCameraY_min(this.anchorRoot.absolutePosition.y); + } + //} } diff --git a/src/app/babylon/tool/babylon-tool.ts b/src/app/babylon/tool/babylon-tool.ts index a3a6cde..4209798 100644 --- a/src/app/babylon/tool/babylon-tool.ts +++ b/src/app/babylon/tool/babylon-tool.ts @@ -10,6 +10,7 @@ import { ISceneLoaderPluginAsync, ISceneLoaderProgressEvent, Mesh, + Node, NodeMaterial, PBRMaterial, QuadraticEase, @@ -146,10 +147,14 @@ export class BabylonTool { ).then(function (result) { LoadTool.remove(modelPath); isSuccess = true; - onSuccess(result.meshes, result.particleSystems, result.skeletons, result.animationGroups); - }).catch(function (result) { - onError(scene, "load error", result); + if (onSuccess) { + onSuccess(result.meshes, result.particleSystems, result.skeletons, result.animationGroups); + } + }).catch(function (result) { + if (onError) { + onError(scene, "load error", result); + } }); // setTimeout(() => { @@ -274,14 +279,14 @@ export class BabylonTool { } else { let alpha = BabylonTool.alpha_N; - camera.target = BabylonTool.getWorldPosition(mesh).clone(); + camera.target = BabylonTool.getWorldPosition(mesh); camera.radius = maxScaleAxis + 50; camera.alpha = alpha; camera.beta = 1; BabylonTool.AnimMoveCameraTarget( camera, 60, - BabylonTool.getWorldPosition(mesh).clone(), + BabylonTool.getWorldPosition(mesh), maxScaleAxis + 5 ); } @@ -537,6 +542,56 @@ export class BabylonTool { } + /** + * 根据特殊节点名匹配,吸附两个mesh + * 现在移动者身上找到包含key的节点,然后以此节点名去目标者身上找对齐的节点 + * @param mover 移动者 + * @param targetRoot 目标者 + * @param key 特殊字符串 + */ + public static adsorb2MeshByPoint(mover: AbstractMesh, targetRoot: AbstractMesh, key: string) { + //吸附成功 + let isSuccess = false; + //找到移动者的对齐点 + + let moverAdsorbNode = BabylonTool.getNodeContainKey(mover, key); + + if (moverAdsorbNode != null && moverAdsorbNode.length > 0 && targetRoot instanceof Mesh) { + let moverAdsorbPoint = moverAdsorbNode[0] as AbstractMesh; + let aimNode = BabylonTool.getNodeContainKey(targetRoot, moverAdsorbPoint.name); + if (aimNode != null && aimNode.length > 0) { + let aimAdsorbPoint = aimNode[0] as AbstractMesh; + let aimPos = aimAdsorbPoint.getAbsolutePosition(); + + //世界坐标下,移动者的吸附点相对于根节点的偏移 + let moverLocalPos = moverAdsorbPoint.getAbsolutePosition().subtract(mover.getAbsolutePosition()); + let moverNewPos = aimPos.subtract(moverLocalPos); + mover.setAbsolutePosition(moverNewPos); + + isSuccess = true; + } + } + return isSuccess; + } + + /** + * 获取包含某关键字的子节点 + * 特殊字符:Adsorb_ + * @param mesh + */ + public static getNodeContainKey(mesh: AbstractMesh, key: string) { + let moverAdsorbNode = mesh.getChildren((node: Node) => { + if (TsTool.stringContain(node.name, key)) { + return true; + } + else { + return false; + } + }, false) + return moverAdsorbNode; + } + + } //输入提示界面 diff --git a/src/app/babylon/tool/mesh-pool.ts b/src/app/babylon/tool/mesh-pool.ts index 101ff0d..3a927e7 100644 --- a/src/app/babylon/tool/mesh-pool.ts +++ b/src/app/babylon/tool/mesh-pool.ts @@ -307,7 +307,7 @@ export class MeshPoolInfo { // let box = MeshBuilder.CreateBox("box", { size: 1 }); //用于测试阴影 // box.setParent(modelInfo.modelBox); // box.position = new Vector3(0, 2, 0); - if (SceneManager.s_openShadow) { + if (SceneManager.s_openLight && SceneManager.s_openShadow) { SceneManager.Instance.shadowGenerator.addShadowCaster(modelInfo.modelBox, true); // console.log("添加到阴影", modelInfo.modelBox.name); } diff --git a/src/app/babylon/view/building-window/building-window.ts b/src/app/babylon/view/building-window/building-window.ts index 0d2486b..674b25d 100644 --- a/src/app/babylon/view/building-window/building-window.ts +++ b/src/app/babylon/view/building-window/building-window.ts @@ -342,6 +342,7 @@ export class BuildingWindow extends UIBase { onUpdate() { this.updateUVAnim(); + this.updateLimitCameraY(); } @@ -553,47 +554,58 @@ export class BuildingWindow extends UIBase { } } - if (allModelNumber > 1) { + //暂时关闭 1+ n的加载模式 + // if (allModelNumber > 1) { - if (firstType == BuildingType.Environment) { - instance.createOneBuildingItem(firstItem, 0, (uiItem, index) => { - instance.onChangeCurrentBuildingItem(uiItem, false); - // uiItem.lookAt(false); - if (buildingDatas_Environment.length > 1) { - instance.addBuildings(BuildingType.Environment, 1); - } + // if (firstType == BuildingType.Environment) { + // instance.createOneBuildingItem(firstItem, 0, (uiItem, index) => { + // instance.onChangeCurrentBuildingItem(uiItem, false); + // // uiItem.lookAt(false); + // if (buildingDatas_Environment.length > 1) { + // instance.addBuildings(BuildingType.Environment, 1); + // } - instance.addBuildings(BuildingType.Normal, 0); + // instance.addBuildings(BuildingType.Normal, 0); - }); + // }); - } - else { - instance.createOneBuildingItem(firstItem, 0, (uiItem, index) => { - instance.onChangeCurrentBuildingItem(uiItem, false); - // uiItem.lookAt(false); - if (buildingDatas_mormal.length > 1) { - instance.addBuildings(BuildingType.Normal, 1); - } - instance.addBuildings(BuildingType.Environment, 0); + // } + // else { + // instance.createOneBuildingItem(firstItem, 0, (uiItem, index) => { + // instance.onChangeCurrentBuildingItem(uiItem, false); + // // uiItem.lookAt(false); + // if (buildingDatas_mormal.length > 1) { + // instance.addBuildings(BuildingType.Normal, 1); + // } + // instance.addBuildings(BuildingType.Environment, 0); - }); - } + // }); + // } - } - else { + // } + // else { - instance.addBuildings(BuildingType.Normal, 0, (uiItem, index) => { - if (index == 0 && uiItem.buildingInfo.isEnable) { - instance.onChangeCurrentBuildingItem(uiItem, false); - //uiItem.lookAt(false); - // console.log("默认选中" + uiItem.buildingInfo.buildingData.normalData.key); - } - }); + // instance.addBuildings(BuildingType.Normal, 0, (uiItem, index) => { + // if (index == 0 && uiItem.buildingInfo.isEnable) { + // instance.onChangeCurrentBuildingItem(uiItem, false); + // //uiItem.lookAt(false); + // // console.log("默认选中" + uiItem.buildingInfo.buildingData.normalData.key); + // } + // }); - instance.addBuildings(BuildingType.Environment, 0); - } + // instance.addBuildings(BuildingType.Environment, 0); + // } + + instance.addBuildings(BuildingType.Normal, 0, (uiItem, index) => { + if (index == 0 && uiItem.buildingInfo.isEnable) { + instance.onChangeCurrentBuildingItem(uiItem, false); + //uiItem.lookAt(false); + // console.log("默认选中" + uiItem.buildingInfo.buildingData.normalData.key); + } + }); + + instance.addBuildings(BuildingType.Environment, 0); } @@ -1091,8 +1103,6 @@ export class BuildingWindow extends UIBase { } - - /** * 保存展示模块当前数据 */ @@ -1128,6 +1138,39 @@ export class BuildingWindow extends UIBase { //#endregion + //#region 限制摄像机Y + + /** + * 摄像机Y高度的限制 + */ + limitCameraY_min: number = null; + + /** + * 设置相机target最小Y + * @param value + */ + setLimitCameraY_min(value: number) { + this.limitCameraY_min = value; + } + + /** + * 限制摄像机Y + */ + updateLimitCameraY() { + if (SceneManager.Instance.defaultCamera != null) { + if (this.limitCameraY_min != null) { + if (SceneManager.Instance.defaultCamera.target.y < this.limitCameraY_min) { + SceneManager.Instance.defaultCamera.target.y = this.limitCameraY_min; + } + } + + } + + + } + + + //#endregion } diff --git a/src/app/pages/left-domain/left-domain.component.ts b/src/app/pages/left-domain/left-domain.component.ts index a6bbadc..33e512a 100644 --- a/src/app/pages/left-domain/left-domain.component.ts +++ b/src/app/pages/left-domain/left-domain.component.ts @@ -371,8 +371,8 @@ export class LeftDomainComponent implements OnInit { } else if (this.selectPlanId === item.id && this.selectNodeId === e.id) { //取消选中 this.selectPlanId = null this.selectNodeId = null - PlanComponent.instance.beforeEmergencyPlan = new MarkPlanData(-99, "请选择节点") - PlanComponent.instance.beforePlanNode = new MarkNodeData(-99, "请选择节点") + PlanComponent.instance.beforeEmergencyPlan = new MarkPlanData(-99, "请选择应急预案") + PlanComponent.instance.beforePlanNode = new MarkNodeData(-99, "请选择应急预案") this.updateFatherData(index) //更新/初始化父组件 数据 MarkWindow.instance.selectMarkNode(null, null) } diff --git a/src/app/pages/plan/plan.component.html b/src/app/pages/plan/plan.component.html index 7bfc1d1..cce3dcc 100644 --- a/src/app/pages/plan/plan.component.html +++ b/src/app/pages/plan/plan.component.html @@ -143,8 +143,8 @@ - + +
diff --git a/src/app/pages/plan/plan.component.ts b/src/app/pages/plan/plan.component.ts index 84d816d..3b47b46 100644 --- a/src/app/pages/plan/plan.component.ts +++ b/src/app/pages/plan/plan.component.ts @@ -497,8 +497,8 @@ export class PlanComponent implements OnInit { } allMarkPlanData: AllMarkPlanData; //处置预案节点 数据 - beforeEmergencyPlan: MarkPlanData = new MarkPlanData(-99, "请选择节点"); //当前选择 应急预案 - beforePlanNode: MarkNodeData = new MarkNodeData(-99, "请选择节点"); //当前选择 预案节点 + beforeEmergencyPlan: MarkPlanData = new MarkPlanData(-99, "请选择应急预案"); //当前选择 应急预案 + beforePlanNode: MarkNodeData = new MarkNodeData(-99, "请选择应急预案"); //当前选择 预案节点 nzCurrent: number = -1; //当前选择 预案节点Index isSuspend: boolean = false; //是否暂停 自动切换节点 progressList: number[] = []; //进度条 条/值 @@ -506,8 +506,8 @@ export class PlanComponent implements OnInit { //初始化 应急预案模块 initializePlan() { - this.beforeEmergencyPlan = new MarkPlanData(-99, "请选择节点") - this.beforePlanNode = new MarkNodeData(-99, "请选择节点") + this.beforeEmergencyPlan = new MarkPlanData(-99, "请选择应急预案") + this.beforePlanNode = new MarkNodeData(-99, "请选择应急预案") this.isSuspend = false //初始化暂停状态 this.progressList = [] window.clearTimeout(this.updateTimer) //清除定时器