فهرست منبع

大屏加载3d模型。

jiaxiaoqiang 7 ماه پیش
والد
کامیت
bd11a5f22e
4فایلهای تغییر یافته به همراه239 افزوده شده و 0 حذف شده
  1. 2 0
      package.json
  2. BIN
      public/black_box.fbx
  3. 190 0
      src/hooks/ThreeHelper.ts
  4. 47 0
      src/views/report/statistics/screens/line/middle3D.vue

+ 2 - 0
package.json

@@ -44,6 +44,7 @@
     "@kjgl77/datav-vue3": "^1.7.3",
     "@smallwei/avue": "^3.3.3",
     "@types/smallwei__avue": "^3.0.5",
+    "@types/three": "^0.168.0",
     "@vueup/vue-quill": "1.0.0-alpha.40",
     "@vueuse/core": "^10.9.0",
     "@wangeditor/editor": "^5.1.23",
@@ -70,6 +71,7 @@
     "sockjs-client": "1.6.1",
     "sortablejs": "^1.15.2",
     "stompjs": "^2.3.3",
+    "three": "^0.168.0",
     "uuid": "^9.0.1",
     "v-scale-screen": "2.0.12",
     "vue": "^3.4.21",

BIN
public/black_box.fbx


+ 190 - 0
src/hooks/ThreeHelper.ts

@@ -0,0 +1,190 @@
+import * as THREE from "three";
+import {
+  AxesHelper,
+  BoxGeometry,
+  Mesh,
+  MeshLambertMaterial,
+  PerspectiveCamera,
+  Vector2,
+} from "three";
+import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
+import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer";
+import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass";
+import { OutlinePass } from "three/examples/jsm/postprocessing/OutlinePass";
+import { FBXLoader } from "three/examples/jsm/loaders/FBXLoader";
+
+export default class ThreeHelper {
+  width: number = 0;
+  height: number = 0;
+  group: THREE.Group;
+  geometry: THREE.BoxGeometry;
+  material: THREE.MeshLambertMaterial;
+  mesh: THREE.Mesh;
+  camera: THREE.PerspectiveCamera;
+  axesHelper: THREE.AxesHelper;
+  scene: THREE.Scene;
+  light: THREE.PointLight;
+  renderer: THREE.WebGLRenderer | null = null;
+
+  constructor() {
+    this.group = new THREE.Group();
+
+    this.geometry = new BoxGeometry(60, 90, 80);
+    this.material = new MeshLambertMaterial({
+      color: 0x009900,
+      transparent: true,
+      opacity: 0.5,
+    });
+    this.mesh = new Mesh(this.geometry, this.material);
+    this.group.add(this.mesh);
+
+    this.camera = this.creatCamera();
+    this.axesHelper = new AxesHelper(400);
+    this.light = this.creatLight();
+
+    this.scene = new THREE.Scene();
+    this.scene.add(this.group);
+    this.scene.add(this.axesHelper);
+    this.scene.add(this.light);
+  }
+
+  creatLight = () => {
+    const light = new THREE.PointLight(0xffffff, 1.0);
+    light.intensity = 300.0; //光照强度
+    light.decay = 0.0; //设置光源不随距离衰减
+    light.position.set(1000, 1000, 1000);
+    return light;
+  };
+
+  creatRenderer = (dom: HTMLElement) => {
+    this.width = dom.clientWidth;
+    this.height = dom.clientHeight;
+
+    const renderer: THREE.WebGLRenderer = new THREE.WebGLRenderer({
+      antialias: true,
+    });
+    renderer.setSize(this.width, this.height);
+    renderer.setClearColor(0x000000, 0.4);
+    this.renderer = renderer;
+
+    // 改变相机观察目标
+    this.camera.aspect = this.width / this.height;
+    this.camera.updateProjectionMatrix();
+
+    // 注意相机控件OrbitControls会影响lookAt设置,注意手动设置OrbitControls的目标参数
+
+    const controls = new OrbitControls(this.camera, this.renderer.domElement);
+    // controls.target = mesh.position;
+    // controls.update();
+    controls.addEventListener("change", () => {
+      this.renderer!.render(this.scene, this.camera);
+    });
+
+    // console.log(this.renderer, dom);
+
+    dom.appendChild(renderer.domElement);
+
+    this.renderer.render(this.scene, this.camera);
+  };
+
+  loadFBX = (src: string, finish: () => void) => {
+    const loader = new FBXLoader();
+    loader.load(
+      src,
+      (fbx) => {
+        console.log("loadFBX", fbx);
+        this.group.add(fbx);
+        this.renderer!.render(this.scene, this.camera);
+        finish();
+      },
+      (event) => {
+        console.log(
+          "loadFBX" + (event.loaded / event.total) * 100 + "% loaded"
+        );
+      },
+      (error) => {
+        console.log("loadFBX", error);
+        finish();
+      }
+    );
+  };
+
+  addOutline = (
+    width: number = this.width,
+    height: number = this.height,
+    model?: THREE.Object3D
+  ) => {
+    const composer = new EffectComposer(this.renderer!);
+    const renderPass = new RenderPass(this.scene, this.camera);
+    const outlinePass = new OutlinePass(
+      new Vector2(width, height),
+      this.scene,
+      this.camera
+    );
+    //模型描边颜色,默认白色
+    outlinePass.visibleEdgeColor.set(0xffff00);
+    //高亮发光描边厚度
+    outlinePass.edgeThickness = 4;
+    //高亮描边发光强度
+    outlinePass.edgeStrength = 6;
+    //模型闪烁频率控制,默认0不闪烁
+    outlinePass.pulsePeriod = 2;
+    composer.addPass(renderPass);
+    composer.addPass(outlinePass);
+    // composer.render();
+
+    const that = this;
+
+    this.renderer!.domElement.addEventListener("click", function (event) {
+      // .offsetY、.offsetX以canvas画布左上角为坐标原点,单位px
+      const px = event.offsetX;
+      const py = event.offsetY;
+      //屏幕坐标px、py转WebGL标准设备坐标x、y
+      //width、height表示canvas画布宽高度
+      const x = (px / width) * 2 - 1;
+      const y = -(py / height) * 2 + 1;
+      //创建一个射线投射器`Raycaster`
+      const raycaster = new THREE.Raycaster();
+      //.setFromCamera()计算射线投射器`Raycaster`的射线属性.ray
+      // 形象点说就是在点击位置创建一条射线,射线穿过的模型代表选中
+      raycaster.setFromCamera(new THREE.Vector2(x, y), that.camera);
+      //.intersectObjects([mesh1, mesh2, mesh3])对参数中的网格模型对象进行射线交叉计算
+      // 未选中对象返回空数组[],选中一个对象,数组1个元素,选中两个对象,数组两个元素
+      const toAddModelArray = [];
+      if (model) {
+        toAddModelArray.push(model);
+      }
+      const intersects = raycaster.intersectObjects([
+        that.mesh,
+        ...toAddModelArray,
+      ]);
+      console.log("射线器返回的对象", intersects);
+      // intersects.length大于0说明,说明选中了模型
+      if (intersects.length > 0) {
+        outlinePass.selectedObjects = [intersects[0].object];
+        composer.render();
+      }
+    });
+  };
+
+  resizeRender = (dom: HTMLElement) => {
+    this.width = dom.clientWidth;
+    this.height = dom.clientHeight;
+    this.renderer!.setSize(this.width, this.height);
+    this.camera.aspect = this.width / this.height;
+    this.camera.updateProjectionMatrix();
+    this.renderer!.render(this.scene, this.camera);
+  };
+
+  private creatCamera = () => {
+    const camera = new PerspectiveCamera(
+      75,
+      window.innerWidth / window.innerHeight,
+      0.1,
+      3000
+    );
+    camera.position.set(1000, 1000, 1000);
+    camera.lookAt(0, 0, 0);
+    return camera;
+  };
+}

+ 47 - 0
src/views/report/statistics/screens/line/middle3D.vue

@@ -0,0 +1,47 @@
+<template>
+  <div ref="containerRef" class="middle3D"></div>
+</template>
+
+<script lang="ts" setup>
+import ThreeHelper from "@/hooks/ThreeHelper";
+import * as THREE from "three";
+
+const containerRef = ref<HTMLDivElement>(null);
+
+const threeHelper = new ThreeHelper();
+
+onMounted(() => {
+  threeHelper.creatRenderer(containerRef.value);
+
+  threeHelper.loadFBX("/black_box.fbx", () => {
+    threeHelper.scene.traverse((child) => {
+      if (child instanceof THREE.Mesh) {
+        console.log("traverse", child.material);
+        if (child.material.length > 1) {
+          child.material[0].color.set(0x00ff00);
+          child.material[1].color.set(0x00ff00);
+          child.material[2].color.set(0x00ff00);
+          child.material[3].color.set(0x00ff00);
+          threeHelper.resizeRender(containerRef.value);
+        }
+      }
+    });
+  });
+
+  // threeHelper.addOutline();
+});
+
+window.addEventListener("resize", () => {
+  threeHelper.resizeRender(containerRef.value);
+  // threeHelper.addOutline();
+});
+</script>
+
+<style lang="scss" scoped>
+.middle3D {
+  height: 29vh;
+  width: 100%;
+  padding: 0 !important;
+  border: 2px solid red;
+}
+</style>