Koishi 7 ماه پیش
والد
کامیت
3b69dc53ee

+ 1 - 0
package.json

@@ -17,6 +17,7 @@
     "echarts": "^6.0.0",
     "element-plus": "^2.10.6",
     "js-cookie": "^3.0.5",
+    "three": "^0.179.1",
     "vue": "^3.3.11",
     "vue-router": "^4.2.5"
   },

+ 46 - 0
src/components/modelUnpack.vue

@@ -0,0 +1,46 @@
+
+<template>
+  <div ref="threeCanvas" style="width: 100%; height: 100%"></div>
+</template>
+ 
+<script>
+import * as THREE from "@three/Three.js";
+
+import { TEngine } from "../threeTool/ThreeEngine";
+import { allBaseObject } from "../threeTool/ThreeBaseObject";
+import { allLights } from "../threeTool/ThreeLights";
+import { getColor } from "../threeTool/ThreeColor";
+import { ThreeAxes } from "../threeTool/ThreeAxes";
+
+import { GLTFLoader } from "@three/examples/jsm/loaders/GLTFLoader";
+import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader";
+
+export default {
+  name: "ThreeScene",
+
+  data() {
+    return {
+      ThreeEngine: null,
+    };
+  },
+
+  mounted() {
+    this.initThree();
+  },
+
+  methods: {
+    initThree() {
+      this.ThreeEngine = new TEngine({
+        dom: this.$refs.threeCanvas,
+        showGUI: false,
+        cameraOptions: {
+          fov: 45,
+          aspectRatio: window.innerWidth / window.innerHeight,
+          near: 0.1,
+          far: 99999,
+        },
+      });
+    },
+  },
+};
+</script>

+ 8 - 0
src/threeTool/ThreeAxes.js

@@ -0,0 +1,8 @@
+import { AxesHelper } from '@three/Three.js'
+
+export const ThreeAxes = [];
+
+// 世界坐标辅助器
+const axesHelper = new AxesHelper(5);
+axesHelper.disalbedDelete = true;
+ThreeAxes.push(axesHelper);

+ 26 - 0
src/threeTool/ThreeBaseObject.js

@@ -0,0 +1,26 @@
+import { BoxGeometry, Mesh, MeshStandardMaterial } from "@three/Three.js"
+import { getColor } from './ThreeColor';
+
+export const allBaseObject = []  // 返回所有基础模型
+
+// 创建地面
+export const box = new Mesh(
+    new BoxGeometry(20, 20, 20),  // 设置立方体的大小
+    new MeshStandardMaterial({   // 设置材质
+        color: 'rgb(36, 172, 242)',  // 设置材质的颜色
+        metalness: 0.5,   // 金属度 (1 最像金属,0 最不像金属)
+        roughness: 0   // 粗糙度(0 最光滑,1 最粗糙)
+    })
+)
+box.name = 'box'  // 设置模型 name
+
+// 给模型添加鼠标移入事件
+box.addEventListener("mouseenter", () => {
+    box.material.color = getColor("#ff3366")  // 修改材质颜色为红色
+})
+// 给模型添加鼠标移除事件
+box.addEventListener("mouseleave", () => {
+    box.material.color = getColor("rgb(36, 172, 242)") // 恢复模型的材质
+})
+
+allBaseObject.push(box)  // 添加到模型数组

+ 5 - 0
src/threeTool/ThreeColor.js

@@ -0,0 +1,5 @@
+import { Color } from '@three/Three.js'
+
+export function getColor(colorHex) {
+    return new Color(colorHex)
+}

+ 663 - 0
src/threeTool/ThreeEngine.js

@@ -0,0 +1,663 @@
+import { WebGLRenderer, Scene, PerspectiveCamera, Vector3, MOUSE, Raycaster, Vector2, } from "@three/Three.js";
+import { CSS3DRenderer, CSS3DObject } from 'three/examples/jsm/renderers/CSS3DRenderer';
+import { OrbitControls } from "@three/examples/jsm/controls/OrbitControls";
+import { TransformControls } from "@three/examples/jsm/controls/TransformControls";
+import { GUI } from "@three/examples/jsm/libs/lil-gui.module.min.js";
+
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
+import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
+import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader";
+import { FBXLoader } from "three/examples/jsm/loaders/FBXLoader";
+import { TDSLoader } from "three/examples/jsm/loaders/TDSLoader";
+import { MTLLoader } from "three-obj-mtl-loader";
+
+import { createSprite } from "./ThreeLabel";
+
+import { getColor } from "./ThreeColor";
+import WebGL from "./WebGL";
+
+import * as THREE from "@three/Three.js";
+import TWEEN from "@tweenjs/tween.js";
+import store from "../../../store/index.js";
+
+import { compress, deCompress } from "@tools/zipString.js";
+
+import BASE from "@tools/basicTool.js";
+
+THREE.Cache.enabled = true;
+
+export class TEngine {
+    // 挂载的 DOM
+    dom = null;
+    // 场景
+    scene = null;
+    // 相机
+    camera = null;
+
+    renderer = null;
+
+    GUI = null;
+
+    css3DRenderer = null;
+
+    orbitControls = null;
+
+    /**
+     * 向场景中添加模型
+     * @param  {...any} object 模型列表
+     */
+    // addObject(...object) {
+    //     object.forEach(ele => {
+    //         this.scene.beforeAdd(ele);
+    //         this.scene.add(ele);
+    //         this.scene.afterAdd(ele);
+    //     });
+    // };
+    addObject(modelArray) {
+        modelArray.forEach(ele => {
+            this.scene.beforeAdd(ele);
+            this.scene.add(ele);
+            this.scene.afterAdd(ele);
+        });
+    };
+
+    getScene() {
+        return this.scene;
+    };
+
+    getCamera() {
+        return this.camera;
+    };
+
+    getRenderer() {
+        return this.renderer;
+    };
+
+    getGUI() {
+        return this.GUI;
+    }
+
+    getCss3DRenderer() {
+        return this.css3DRenderer;
+    }
+
+    getOrbitControls() {
+        return this.orbitControls;
+    }
+
+    exportScene(scene) {
+        // 将场景中的对象转换为JSON格式的数据
+        let jsonData = scene.toJSON();
+
+        const hasFloor = scene.children.some(ele => {
+            return ele.name === "网格地板" && ele.disalbedDelete
+        });
+
+        jsonData.hasFloor = hasFloor;
+        let sceneData = JSON.stringify({ hasFloor, sceneData: jsonData });
+        // 保存JSON数据到文件中
+        // 这里可以使用浏览器的文件API进行保存操作
+        // 这里只是一个简单的示例
+        let blob = new Blob([sceneData], { type: 'application/json' });
+        let url = URL.createObjectURL(blob);
+        let a = document.createElement('a');
+        a.href = url;
+        a.download = '3D场景文件.json';
+        a.click();
+    }
+
+    importScene(jsonData) {
+        // 解析JSON数据
+        const data = JSON.parse(jsonData);
+
+        // 根据解析的数据创建 Three.js 对象
+        const loader = new THREE.ObjectLoader();
+        const importedObjects = loader.parse(data.sceneData);
+        this.scene?.children?.forEach(ele => {
+            this.scene.remove(ele);
+        });
+
+        if (data.hasFloor) {
+            const gridHelper = new THREE.GridHelper(2000, 100, getColor("#888"), getColor("#888"));
+            gridHelper.name = "网格地板";
+            gridHelper.disalbedDelete = true;
+            this.scene.add(gridHelper);
+        }
+
+        // 添加对象到场景中
+        this.scene.add(importedObjects);
+    }
+
+    constructor(ThreeEngine = {}) {
+        // 场景
+        const scene = new Scene();
+        scene.beforeAdd = ThreeEngine.beforeAdd || scene.beforeAdd;
+        scene.afterAdd = ThreeEngine.afterAdd || scene.afterAdd;
+
+        // scene.background = getColor("#1890ff");
+        scene.background = new THREE.Color(0xadd8e6);
+
+        // 相机
+        const camera = new PerspectiveCamera(
+            // 视野角度(fov)
+            ThreeEngine?.cameraOptions?.fov || 75,
+            // 长宽比(aspect ratio)
+            ThreeEngine?.cameraOptions?.aspectRatio || window.innerWidth / window.innerHeight,
+            // 近截面(near)
+            ThreeEngine?.cameraOptions?.near || 0.1,
+            // 远截面(far)
+            ThreeEngine?.cameraOptions?.far || 1000
+        );
+
+        // 设置摄像机位置和朝向
+        camera.position.set(80, 100, 200); // 调整摄像机位置
+        camera.lookAt(new THREE.Vector3(0, 0, 0)); // 设置摄像机朝向场景中心
+
+        // 渲染器
+        const renderer = new WebGLRenderer({
+            antialias: true,  // 开启抗锯齿
+        });
+        // renderer.setSize(window.innerWidth, window.innerHeight);
+        const rendererBox = getComputedStyle(ThreeEngine.dom, null);
+        const renderSizeWidth = parseFloat(rendererBox.width);
+        const renderSizeHeight = parseFloat(rendererBox.height);
+        renderer.setSize(renderSizeWidth, renderSizeHeight);
+
+        ThreeEngine?.dom?.appendChild(renderer.domElement);
+
+        const css3DRenderer = new CSS3DRenderer();
+        css3DRenderer.setSize(renderSizeWidth, renderSizeHeight);
+        css3DRenderer.render(scene, camera);
+        css3DRenderer.domElement.style.position = 'absolute';
+        css3DRenderer.domElement.style.top = 0;
+        css3DRenderer.domElement.style.pointerEvents = 'none';
+        ThreeEngine.dom.appendChild(css3DRenderer.domElement);
+
+        ThreeEngine.dom.addEventListener("drop", (e) => {
+            // 获取拖拽的数据
+            const modelInfo = JSON.parse(e.dataTransfer.getData("setModel"));
+            this.addModel(modelInfo);
+            e.preventDefault();
+        });
+
+        // 设置鼠标功能键(轨道控制器)
+        const orbitControls = new OrbitControls(camera, renderer.domElement);
+        // 移动带阻尼
+        orbitControls.enableDamping = ThreeEngine.enableDamping || false;
+        // 设置阻尼系数
+        orbitControls.dampingFactor = ThreeEngine.dampingFactor || 0;
+        // 自动旋转
+        orbitControls.autoRotate = ThreeEngine.autoRotate || false;
+
+        orbitControls.mouseButtons = {
+            LEFT: null,  // 左键无功能
+            MIDDLE: MOUSE.PAN,  // 中键移动
+            RIGHT: MOUSE.ROTATE   // 右键旋转
+        }
+
+        if (ThreeEngine.showGridHelper) {
+            // 底图网格 (总长宽,分多少个网格,颜色,轴线颜色,和网格颜色 #e6e8ed)
+            const gridHelper = new THREE.GridHelper(2000, 100, getColor("#888"), getColor("#888"));
+            gridHelper.name = "网格地板";
+            gridHelper.disalbedDelete = true;
+            scene.add(gridHelper);
+        }
+
+        // 初始化射线发射器
+        let raycaster = new Raycaster();
+        // 初始化变换控制器
+        let transformControls = new TransformControls(camera, renderer.domElement);
+        // 将变换控制器添加至场景
+        scene.add(transformControls);
+
+        let transing = false;
+        transformControls.addEventListener("mouseDown", event => {
+            transing = true;
+        });
+
+        // 初始化鼠标位置
+        let mouse = new Vector2();
+        let x = 0;
+        let y = 0;
+        let width = 0;
+        let height = 0;
+
+        renderer.domElement.addEventListener("mousemove", event => {
+            x = event.offsetX;
+            y = event.offsetY;
+            width = renderer.domElement.offsetWidth;
+            height = renderer.domElement.offsetHeight;
+            mouse.x = x / width * 2 - 1;
+            mouse.y = -y * 2 / height + 1;
+
+            // 配置射线发射器
+            raycaster.setFromCamera(mouse, camera)
+            // 移除变换控制器
+            // scene.remove(transformControls);  
+            const intersection = raycaster.intersectObjects(scene.children);
+            if (intersection.length) {
+                const object = intersection[0].object;
+                // 如果当前物体不等于缓存的物体
+                if (object !== this.cacheObject) {
+                    // 如果有缓存物体先执行之前物体的离开事件
+                    if (this.cacheObject) {
+                        this.cacheObject.dispatchEvent({
+                            type: "mouseleave"
+                        });
+                    }
+                    object.dispatchEvent({  // 添加当前物体进入事件
+                        type: "mouseenter"
+                    });
+                } else if (object === this.cacheObject) {  // 如果当前物体等于缓存的物体
+                    // 执行移动事件
+                    object.dispatchEvent({
+                        type: "mousemove"
+                    });
+                }
+                this.cacheObject = object;
+            } else {
+                // 如果有缓存物体就先执行离开事件
+                if (this.cacheObject) {
+                    this.cacheObject.dispatchEvent({
+                        type: "mouseleave"
+                    });
+                }
+                this.cacheObject = null;
+            }
+        });
+
+        // 点击事件
+        if (!ThreeEngine.disabledMouseEvent) {
+            renderer.domElement.addEventListener("click", event => {
+                if (ThreeEngine.click) {
+                    // 移除变换控制器
+                    scene.remove(transformControls);
+                    // 停用变换控制器
+                    transformControls.enabled = false;
+                    // 配置射线发射器,传递鼠标和相机对象
+                    raycaster.setFromCamera(mouse, camera);
+                    // 获取射线发射器捕获的模型列表,传进去场景中所以模型,穿透的会返回我们
+                    const intersection = raycaster.intersectObjects(scene.children);
+                    ThreeEngine.click(intersection?.[0]?.object || {}, intersection);
+                } else {
+                    if (transing) {
+                        transing = false;
+                        return;
+                    }
+                    // 移除变换控制器
+                    scene.remove(transformControls);
+                    // 停用变换控制器
+                    transformControls.enabled = false;
+                    // 配置射线发射器,传递鼠标和相机对象
+                    raycaster.setFromCamera(mouse, camera);
+                    // 获取射线发射器捕获的模型列表,传进去场景中所以模型,穿透的会返回我们
+                    const intersection = raycaster.intersectObjects(scene.children);
+                    let object = {};
+                    if (intersection.length) {
+                        const baseType = ["Scene", "GridHelper", "DirectionalLight", "TransformControls", "AxesHelper"];
+                        const isBaseType = baseType.some(ele => {
+                            return ele === intersection[0].object.type;
+                        });
+                        if (!isBaseType) {
+                            const shiftKey = event.shiftKey;
+                            // 获取第一个模型
+                            if (shiftKey) {
+                                object = this.getRootModel(intersection[0].object);
+                            } else {
+                                object = this.getRootMaterialObject(intersection[0].object);
+                            }
+                            // 添加变换控制器
+                            scene.add(transformControls);
+                            // 启用变换控制器
+                            transformControls.enabled = true;
+                            transformControls.attach(object);
+                            ThreeEngine.selectModel && ThreeEngine.selectModel(object);
+                        } else {
+                            ThreeEngine.selectModel && ThreeEngine.selectModel(object);
+                        }
+                        localStorage.setItem("viewModel", this.getRootModel(object)?.userData?.modelName);
+                    } else {
+                        ThreeEngine.selectModel && ThreeEngine.selectModel(null);
+                        localStorage.setItem("viewModel", JSON.stringify({}));
+                    }
+                    store.commit("setActiveModel", object || {});
+                }
+
+                return false;
+            });
+
+            // 监听变换控制器模式更改
+            document.addEventListener("keyup", event => {
+                const shiftKey = event.shiftKey;
+                // 变换控制器为启用状态执行
+                if (transformControls.enabled) {
+                    // 鼠标按下e键,模式改为缩放
+                    if (event.key === "s") {
+                        transformControls.mode = "scale";
+                        return false;
+                    }
+                    // 鼠标按下r键,模式改为旋转
+                    if (event.key === "r") {
+                        transformControls.mode = "rotate";
+                        return false;
+                    }
+                    // 鼠标按下t键,模式改为平移
+                    if (event.key === "t") {
+                        transformControls.mode = "translate";
+                        return false;
+                    }
+                    // 按下 Delete ,删除选中模型的选中部分,按下 Shift + Delete,删除选中模型整体
+                    if (event.key === "Delete" && store.state.activeModel.id && !store.state.activeModel.disalbedDelete) {
+                        if (shiftKey) {
+                            const rootModel = this.getRootModel(store.state.activeModel);
+                            rootModel.geometry?.dispose();
+                            rootModel.material?.dispose();
+                            rootModel?.parent?.remove(rootModel);
+                        } else {
+                            const modelParentNode = store.state.activeModel.parent;
+                            store.state.activeModel.geometry?.dispose();
+                            store.state.activeModel.material?.dispose();
+                            modelParentNode.remove(store.state.activeModel);
+                        }
+                        scene.children.forEach(ele => {
+                            if (ele.isTransformControls) {
+                                scene.remove(ele);
+                            }
+                        });
+                        renderer.render(scene, camera);
+                        store.commit("setActiveModel", {});
+                    }
+                }
+            });
+
+            // 添加事件监听器
+            transformControls.addEventListener("mouseUp", () => {
+                ThreeEngine.modelValueChange && ThreeEngine.modelValueChange(this.activeModel);
+            });
+        }
+
+        const animate = () => {
+            orbitControls.update();
+            css3DRenderer.render(scene, camera);
+            requestAnimationFrame(animate);
+            renderer.render(scene, camera);
+            TWEEN.update();
+        };
+
+        if (WebGL.isWebGLAvailable()) {
+            animate();
+            window.addEventListener("resize", () => {
+                renderer.setSize(window.innerWidth, window.innerHeight);
+                camera.aspect = window.innerWidth / window.innerHeight;
+                camera.updateProjectionMatrix();
+            });
+        } else {
+            const warning = WebGL.getWebGLErrorMessage();
+            ThreeEngine?.dom?.appendChild(warning);
+        }
+
+        if (ThreeEngine.showGUI) {
+            this.GUI = new GUI();
+
+            const eventObject = {
+                requestFullscreen() {
+                    document.body.requestFullscreen();
+                },
+                exitFullscreen() {
+                    if (document.fullscreenElement) {
+                        document.exitFullscreen();
+                    }
+                }
+            }
+
+            const folder = this.GUI.addFolder("屏幕操作");
+
+            folder.add(eventObject, "requestFullscreen").name("全屏展示");
+            folder.add(eventObject, "exitFullscreen").name("退出全屏");
+        }
+
+        this.dom = ThreeEngine?.dom;
+        this.scene = scene;
+        this.camera = camera;
+        this.renderer = renderer;
+        this.css3DRenderer = css3DRenderer;
+        this.orbitControls = orbitControls;
+
+        this.fileLoaderMap = {
+            "glb": new GLTFLoader(),
+            "gltf": new GLTFLoader(),
+            "obj": new OBJLoader(),
+            "fbx": new FBXLoader(),
+            "3ds": new TDSLoader(),
+        }
+
+        // 读取模型
+        this.loadModel = ({ fileType = "glb", dirName, filePath, materialsPath }) => {
+            if (materialsPath) {
+                return new Promise((resolve, reject) => {
+                    const mtlLoader = new MTLLoader();
+                    mtlLoader.load(materialsPath, (materials) => {
+                        materials.preload();
+
+                        const loader = this.fileLoaderMap[fileType];
+                        loader.setMaterials(materials);
+                        loader.load(filePath, (result) => {
+                            switch (fileType) {
+                                case "glb":
+                                    resolve(result.scene);
+                                    break;
+                                case "gltf":
+                                    resolve(result.scene);
+                                    break;
+                                case "fbx":
+                                    resolve(result);
+                                    break;
+                                case "obj":
+                                    resolve(result);
+                                    break;
+                                case "3ds":
+                                    resolve(result);
+                                    break;
+                                default:
+                                    resolve({});
+                                    break;
+                            }
+                        });
+                    });
+                });
+            } else {
+                return new Promise((resolve, reject) => {
+                    const loader = this.fileLoaderMap[fileType];
+                    loader.setResourcePath(`./static/model/${fileType}/${dirName}/`);
+                    loader.load(filePath, (result) => {
+                        switch (fileType) {
+                            case "glb":
+                                resolve(result.scene);
+                                break;
+                            case "gltf":
+                                resolve(result.scene);
+                                break;
+                            case "fbx":
+                                resolve(result);
+                                break;
+                            case "obj":
+                                resolve(result);
+                                break;
+                            case "3ds":
+                                resolve(result);
+                                break;
+                            default:
+                                resolve({});
+                                break;
+                        }
+                    });
+                });
+            }
+        };
+
+        // 添加模型
+        this.addModel = (modelInfo) => {
+
+            BASE.showLoading({
+                text: "加载模型中...请稍后..."
+            });
+
+
+            // 首先检查缓存中是否已经有该模型数据
+            const cachedData = THREE.Cache.get(modelInfo.userData.modelName);
+            if (cachedData !== undefined) {
+                this.addObject([cachedData]);
+                BASE.closeLoading();
+            } else {
+                this.loadModel({
+                    fileType: modelInfo.modelType,
+                    dirName: modelInfo.modelName,
+                    filePath: `./static/model/${modelInfo.modelType}/${modelInfo.modelName}/model.${modelInfo.modelType}`,
+                }).then((sourceModel) => {
+                    let model = sourceModel;
+                    if (modelInfo.userData) {
+                        model.userData = Object.assign({ isRootModel: true }, model.userData, modelInfo.userData);
+                    }
+
+                    model.children.forEach((item) => {
+                        item.castShadow = true;
+                        item.receiveShadow = true;
+
+                        // const material = new THREE.MeshStandardMaterial({
+                        //     transparent: false,
+                        //     opacity: 1,
+                        // });
+                        // item.material = material;
+                    });
+
+                    const baseBoxSize = new THREE.Vector3(100, 100, 100);
+                    // 计算模型的包围盒(bounding box)
+                    const boundingBox = new THREE.Box3().setFromObject(model);
+                    const modelSize = new THREE.Vector3();
+                    boundingBox.getSize(modelSize);
+
+                    // 计算模型的最大尺寸
+                    const maxModelSize = Math.max(modelSize.x, modelSize.y, modelSize.z);
+
+                    // 计算缩放比例,使模型尺寸不超过基准盒子的尺寸
+                    const scaleFactor = baseBoxSize.clone().divideScalar(maxModelSize);
+
+                    // 将模型等比例缩放到适应基准盒子大小
+                    model.scale.set(scaleFactor.x, scaleFactor.y, scaleFactor.z);
+
+                    camera.position.z = 200;
+
+                    // const modelLabelArray = createLabel([
+                    //     { text: "测试机柜" }
+                    // ]);
+                    // modelLabelArray.forEach(ele => {
+                    //     model.add(ele);
+                    // });
+
+
+                    // const labelDiv = this.createDom();
+                    // labelDiv.innerHTML = "风机主体";
+                    // let css3DObject = new CSS3DObject(labelDiv);
+                    // css3DObject.position.set(...positionInfo)
+                    // css3DObject.scale.set(0.03, 0.03, 0.03);
+                    // model.add(css3DObject);
+
+
+                    model = this.formateModel(modelInfo, model);
+                    // createSprite(model);
+                    THREE.Cache.add(modelInfo.userData.modelName, model.clone());
+
+                    const idx = [];
+                    if (model.userData.modelName === "SolarPowerPlant") {
+                        model.traverse((ele) => {
+                            if (ele.name.indexOf("SolarPanel") !== -1 || ele.name.indexOf("Solar_Panel") !== -1) {
+                                ele.userData.modelName = "gfSolo";
+                                ele.userData.isRootMaterialObject = true;
+                                ele.userData.isRootModel = true;
+                            }
+                        });
+                    } else if (model.userData.modelName === "WindPowerSubstationPlant") {
+                        model.traverse((ele) => {
+                            if (ele.name.indexOf("WindTirbune") !== -1) {
+                                idx.push(ele)
+                                ele.userData.modelName = "fjSolo";
+                                ele.userData.isRootMaterialObject = true;
+                                ele.userData.isRootModel = true;
+                            }
+                        });
+                    }
+
+                    this.addObject([model]);
+                    BASE.closeLoading();
+                });
+            }
+        }
+
+        // 和石化模型文件,为其添加材质根节点标识与名称汉化
+        this.formateModel = (modelInfo, modelFile) => {
+            if (modelInfo.userData.modelName === "gfSolo") {
+                modelFile.name = "太阳能逆变器"
+                const nameArray = ["太阳能电池板", "太阳能支架"];
+                modelFile.children.forEach((ele, index) => {
+                    if (index < 2) {
+                        ele.name = nameArray[index];
+                        ele.userData.isRootMaterialObject = true;
+                    }
+                });
+            } else if (modelInfo.userData.modelName === "fjSolo") {
+                modelFile.name = "风力发电机"
+                const nameArray = ["风机塔筒", "风机叶片", "风机机舱"];
+                modelFile.children.forEach((ele, index) => {
+                    if (index < 3) {
+                        ele.name = nameArray[index];
+                        ele.userData.isRootMaterialObject = true;
+                    }
+                });
+            } else {
+                if (modelFile.children?.length) {
+                    modelFile.children.forEach((ele, index) => {
+                        if (index < 3) {
+                            ele.userData.isRootMaterialObject = true;
+                        }
+                    });
+                } else {
+                    modelFile.userData.isRootMaterialObject = true;
+                }
+            }
+            return modelFile;
+        }
+
+        // 获取模型材质根节点
+        this.getRootMaterialObject = (model) => {
+            if (model.userData?.isRootMaterialObject || !model.parent) {
+                return model;
+            } else if (!model.userData?.isRootMaterialObject && model.parent) {
+                return this.getRootMaterialObject(model.parent);
+            } else {
+                return model;
+            }
+        }
+
+        // 获取模型根节点
+        this.getRootModel = (model) => {
+            if (model.userData?.isRootModel || !model.parent) {
+                return model;
+            } else if (!model.userData?.isRootModel && model.parent) {
+                return this.getRootModel(model.parent);
+            } else {
+                return model;
+            }
+        }
+
+        // 创建 3D 标签 DOM
+        this.createDom = () => {
+            let dom = document.createElement("div");
+            dom.style.padding = "5px 10px";
+            dom.style.border = "1px solid skyblue";
+            dom.style.borderRadius = '5px';
+            dom.style.height = '20px';
+            dom.style.pointerEvents = 'none';
+            return dom;
+        }
+    }
+}

+ 93 - 0
src/threeTool/ThreeLabel.js

@@ -0,0 +1,93 @@
+import * as THREE from 'three';
+// // 标注位置对应的模型对象obj
+// export function createSprite(obj, state) {
+
+//     // 创建一个包裹模型的立方体对象
+//     const box = new THREE.Box3().setFromObject(obj);
+//     // 获取模型的尺寸信息
+//     const size = box.getSize(new THREE.Vector3());
+
+//     // 输出模型的宽度、高度和深度
+//     const { x, y, z } = size;
+
+//     const texLoader = new THREE.TextureLoader();
+//     let texture = texLoader.load("/static/img/labelBg.png");
+//     const spriteMaterial = new THREE.SpriteMaterial({
+//         map: texture,
+//     });
+
+//     const sprite = new THREE.Sprite(spriteMaterial);
+//     sprite.name = `${obj.name || "模型"}标记`;
+
+//     // 控制精灵大小
+//     sprite.scale.set(x + 10, y / 2, 1);
+//     // sprite.position.y = 5 / 2; // 标签底部箭头和空对象标注点重合
+//     obj.add(sprite); //tag会标注在空对象obj对应的位置
+// }
+
+import { CSS3DRenderer, CSS3DObject, CSS3DSprite } from 'three/examples/jsm/renderers/CSS3DRenderer.js';
+
+// 创建一个HTML标签
+function tag3D(name) {
+    // 创建div元素(作为标签)
+    var div = document.createElement('div');
+    div.innerHTML = name;
+    div.classList.add('tag');
+    //div元素包装为CSS3模型对象CSS3DObject
+    var label = new CSS3DObject(div);
+    div.style.pointerEvents = 'none';//避免HTML标签遮挡三维场景的鼠标事件
+    // 设置HTML元素标签在three.js世界坐标中位置
+    // label.position.set(x, y, z);
+    //缩放CSS3DObject模型对象
+    label.scale.set(0.2, 0.2, 0.2);//根据相机渲染范围控制HTML 3D标签尺寸
+    label.rotateY(Math.PI / 2);//控制HTML标签CSS3对象姿态角度
+    // label.rotateX(-Math.PI/2);
+    return label;//返回CSS3模型标签    
+}
+
+// 创建一个HTML标签
+export function createSprite(obj) {
+
+    // 创建一个包裹模型的立方体对象
+    const box = new THREE.Box3().setFromObject(obj);
+    // 获取模型的尺寸信息
+    const size = box.getSize(new THREE.Vector3());
+
+    // 输出模型的宽度、高度和深度
+    const { x, y, z } = size;
+
+    // 创建div元素(作为标签)
+    var div = document.createElement('div');
+    div.innerHTML = "风力发电机";
+    div.classList.add('modelLabel');
+    //div元素包装为CSS3模型对象CSS3DSprite
+    var label = new CSS3DSprite(div);
+    div.style.pointerEvents = 'none';//避免HTML标签遮挡三维场景的鼠标事件
+    // 设置HTML元素标签在three.js世界坐标中位置
+    label.position.set(x + x / 10, y / 2, z);
+    //缩放CSS3DSprite模型对象
+    label.scale.set(1, 1, 1);//根据相机渲染范围控制HTML 3D标签尺寸
+    // label.rotateY(Math.PI / 2);//控制HTML标签CSS3对象姿态角度
+    // label.rotateX(-Math.PI/2);
+
+    label.name = `${obj.name || "模型"}标记`;
+    label.userData.isLabelTag = true;
+
+    console.log(1122, { x, y, z })
+
+    obj.add(label);
+}
+
+// 创建一个CSS2渲染器CSS2DRenderer
+function labelRenderer(container) {
+    var labelRenderer = new CSS3DRenderer();
+    labelRenderer.setSize(container.offsetWidth, container.offsetHeight);
+    labelRenderer.domElement.style.position = 'absolute';
+    // 相对标签原位置位置偏移大小
+    labelRenderer.domElement.style.top = '0px';
+    labelRenderer.domElement.style.left = '0px';
+    // //设置.pointerEvents=none,以免模型标签HTML元素遮挡鼠标选择场景模型
+    labelRenderer.domElement.style.pointerEvents = 'none';
+    container.appendChild(labelRenderer.domElement);
+    return labelRenderer;
+}

+ 20 - 0
src/threeTool/ThreeLights.js

@@ -0,0 +1,20 @@
+import { AmbientLight, SpotLight, PointLight, DirectionalLight } from "three";
+
+// 光线
+export const allLights = [];
+
+// 创建平行光
+const directionalLight = new DirectionalLight(0xffffff, 8); // 第一个参数是光的颜色,第二个参数是光的强度
+directionalLight.position.set(100, 1000, 100); // 设置光源位置
+
+// 创建点光源
+const pointLight = new PointLight(0xffffff, 10, 9999); // 第一个参数是光的颜色,第二个参数是光的强度,第三个参数是光的距离范围
+pointLight.position.set(100, 1000, 100); // 设置光源位置
+
+// 创建聚光灯
+const spotLight = new SpotLight(0xffffff, 1); // 第一个参数是光的颜色,第二个参数是光的强度
+spotLight.position.set(0, 100, 0); // 设置光源位置
+spotLight.target.position.set(0, 0, 0); // 设置光源照射目标位置
+
+
+allLights.push(directionalLight);

+ 78 - 0
src/threeTool/WebGL.js

@@ -0,0 +1,78 @@
+class WebGL {
+    static isWebGLAvailable() {
+        try {
+            const canvas = document.createElement('canvas');
+            return !!(window.WebGLRenderingContext && (canvas.getContext('webgl') || canvas.getContext('experimental-webgl')));
+        } catch (e) {
+            return false;
+        }
+    }
+
+    static isWebGL2Available() {
+        try {
+            const canvas = document.createElement('canvas');
+            return !!(window.WebGL2RenderingContext && canvas.getContext('webgl2'));
+        } catch (e) {
+            return false;
+        }
+    }
+
+    static isColorSpaceAvailable(colorSpace) {
+        try {
+            const canvas = document.createElement('canvas');
+            const ctx = window.WebGL2RenderingContext && canvas.getContext('webgl2');
+            ctx.drawingBufferColorSpace = colorSpace;
+            return ctx.drawingBufferColorSpace === colorSpace; // deepscan-disable-line SAME_OPERAND_VALUE
+        } catch (e) {
+            return false;
+        }
+    }
+
+    static getWebGLErrorMessage() {
+        return this.getErrorMessage(1);
+    }
+
+    static getWebGL2ErrorMessage() {
+        return this.getErrorMessage(2);
+    }
+
+    static getErrorMessage(version) {
+        const names = {
+            1: 'WebGL',
+            2: 'WebGL 2'
+        };
+
+        const contexts = {
+            1: window.WebGLRenderingContext,
+            2: window.WebGL2RenderingContext
+        };
+
+        let message = 'Your $0 does not seem to support <a href="http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation" style="color:#000">$1</a>';
+
+        const element = document.createElement('div');
+        element.id = 'webglmessage';
+        element.style.fontFamily = 'monospace';
+        element.style.fontSize = '13px';
+        element.style.fontWeight = 'normal';
+        element.style.textAlign = 'center';
+        element.style.background = '#fff';
+        element.style.color = '#000';
+        element.style.padding = '1.5em';
+        element.style.width = '400px';
+        element.style.margin = '5em auto 0';
+
+        if (contexts[version]) {
+            message = message.replace('$0', 'graphics card');
+        } else {
+            message = message.replace('$0', 'browser');
+        }
+
+        message = message.replace('$1', names[version]);
+        element.innerHTML = message;
+        return element;
+
+    }
+
+}
+
+export default WebGL;

+ 9 - 0
src/threeTool/modelChartlet.js

@@ -0,0 +1,9 @@
+export default {
+    obj: {
+        SolarPowerPlant: {
+            // Grass: "GroundGrassTextureDiffuse",
+            // DirtyRoadPart: "DirtRoadTexture3",
+            // DirtyRoadPart001: "DirtRoadTexture4",
+        }
+    }
+}

+ 74 - 15
src/views/cesium.vue

@@ -113,6 +113,29 @@
       </template>
     </div>
   </div>
+
+  <el-dialog
+    class="modelDialog"
+    v-model="showFjDialog"
+    title="Tips"
+    top="50px"
+    width="80%"
+    :before-close="handleClose"
+  >
+    <el-tabs
+      v-model="showFjDialogActiveName"
+      style="width: 100%"
+      type="border-card"
+      @tab-click="handleClick"
+    >
+      <el-tab-pane label="基础信息" name="jcxx">基础信息</el-tab-pane>
+      <el-tab-pane label="视频监控" name="spjk">视频监控</el-tab-pane>
+      <el-tab-pane label="故障查看" name="gzck">故障查看</el-tab-pane>
+      <el-tab-pane label="模型解构" name="third">
+        <!-- <ModelUnpack /> -->
+      </el-tab-pane>
+    </el-tabs>
+  </el-dialog>
 </template>
 
 <script>
@@ -126,11 +149,19 @@ import basicGeoJson from "../assets/geoJson/basic.json";
 import windLineJson from "../assets/geoJson/windLine_2017121300.json";
 
 import axios from "axios";
+
+// import ModelUnpack from "@/components/modelUnpack.vue";
 export default {
   name: "CesiumMap",
 
+  components: {
+    // ModelUnpack,
+  },
+
   data() {
     return {
+      showFjDialog: false,
+      showFjDialogActiveName: "jcxx",
       checkMode: false, // 调试模式
       allyShow: false,
       viewer: null,
@@ -263,25 +294,38 @@ export default {
       // 隐藏 Cesium Logo
       viewer.cesiumWidget.creditContainer.style.display = "none";
 
-      // 添加一些3D模型
-      //   const addModel = (name, lon, lat, height) => {
-      //     viewer.entities.add({
-      //       name: name,
-      //       position: Cesium.Cartesian3.fromDegrees(lon, lat, height),
-      //       model: {
-      //         uri: "/sample-data/models/CesiumAir/Cesium_Air.glb",
-      //         scale: 50000.0,
-      //       },
-      //     });
-      //   };
-
-      //   addModel("模型1", 116.4, 39.9, 1000);
-      //   addModel("模型2", 121.47, 31.23, 1000);
-
       this.viewer = viewer;
 
       this.setMapImageryProvider();
       this.initGeoJsonData();
+
+      // 添加一些3D模型
+      this.addModel(
+        "./static/model/fjSolo/model.glb",
+        "风机",
+        106.169866,
+        38.46637
+      );
+    },
+
+    addModel(uri, name, lon, lat) {
+      const hpRoll = new Cesium.HeadingPitchRoll(90.0, 0.0, 0.0);
+      const position = Cesium.Cartesian3.fromDegrees(lon, lat);
+      const orientation = Cesium.Transforms.headingPitchRollQuaternion(
+        position,
+        hpRoll
+      );
+      this.viewer.entities.add({
+        name, // 模型名称
+        position, // 模型位置
+        orientation, // 模型朝向
+        model: {
+          uri,
+          scale: 500.0,
+          // 模型贴地
+          heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+        },
+      });
     },
 
     // 初始化Cesium内部鼠标事件
@@ -960,4 +1004,19 @@ export default {
     }
   }
 }
+</style>
+<style lang="less">
+.modelDialog {
+  .el-dialog__body {
+    height: 750px;
+
+    .el-tabs {
+      height: 100%;
+
+      .el-tabs__content {
+        height: 100%;
+      }
+    }
+  }
+}
 </style>