Vue使用Three.js加载glb (gltf) 文件模型及实现简单的选中高亮、测距、测面积

news/2025/2/25 19:33:55

安装:

javascript"># three.js

npm install --save three

 附中文网:

5. gltf不同文件形式(.glb) | Three.js中文网

附官网:

安装 – three.js docs

完整代码(简易demo):

javascript"><template>
  <div class="siteInspection" ref="threeContainer">
    <div class="measurement-buttons">
      <button @click="startDistanceMeasurement">测距</button>
      <button @click="startAreaMeasurement">测面积</button>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref, onMounted, onUnmounted } from "vue";
import * as THREE from "three";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";

const lzModel = ref("/lz.glb");  // 此处为你的 模型文件  .glb或 .gltf
const threeContainer = ref(null);

// 创建场景、相机和渲染器
let scene: THREE.Scene;
let camera: THREE.PerspectiveCamera;
let renderer: THREE.WebGLRenderer;
let model: THREE.Group | null = null;
let controls: OrbitControls;
let raycaster: THREE.Raycaster;
let mouse: THREE.Vector2;
let selectedObject: THREE.Mesh | null = null; // 用于跟踪当前选中的对象
let line: THREE.Line | null = null; // 用于存储高亮线
let label: THREE.Sprite | null = null; // 用于存储标签

// 测量相关变量
let isDistanceMeasuring = false;
let isAreaMeasuring = false;
let distancePoints: THREE.Vector3[] = [];
let areaPoints: THREE.Vector3[] = [];
let distanceLine: THREE.Line | null = null;
let areaLines: THREE.Line[] = [];
let distanceLabel: THREE.Sprite | null = null;
let areaLabel: THREE.Sprite | null = null;
let distanceDots: THREE.Mesh[] = [];
let areaDots: THREE.Mesh[] = [];

// 初始化
function initThree() {
  scene = new THREE.Scene();
  scene.background = new THREE.Color(0xffffff); // 设置背景颜色为亮色

  camera = new THREE.PerspectiveCamera(
    75,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
  );
  renderer = new THREE.WebGLRenderer();
  renderer.setSize(window.innerWidth, window.innerHeight);
  threeContainer.value.appendChild(renderer.domElement);

  // 添加光源
  const ambientLight = new THREE.AmbientLight(0x404040); // 环境光
  scene.add(ambientLight);

  const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
  directionalLight.position.set(1, 1, 1).normalize();
  scene.add(directionalLight);

  // 设置相机位置
  camera.position.z = 5;
  camera.lookAt(0, 0, 0);

  // 创建GLTF加载器对象
  const loader = new GLTFLoader();
  loader.load(lzModel.value, function (gltf: any) {
    model = gltf.scene;
    scene.add(model);
  });

  // 初始化OrbitControls
  controls = new OrbitControls(camera, renderer.domElement);
  controls.enableDamping = true; // 启用阻尼效果
  controls.dampingFactor = 0.25; // 阻尼系数
  controls.enableZoom = true; // 启用缩放
  controls.enablePan = true; // 启用平移

  // 初始化Raycaster和Vector2
  raycaster = new THREE.Raycaster();
  mouse = new THREE.Vector2();

  // 渲染循环
  function animate() {
    requestAnimationFrame(animate);
    controls.update(); // 更新OrbitControls
    renderer.render(scene, camera);
  }
  animate();

  // 清理事件监听器
  onUnmounted(() => {
    controls?.dispose();
    threeContainer.value?.removeEventListener("mousedown", onDocumentMouseDown);
    if (line) {
      scene.remove(line);
    }
    if (label) {
      scene.remove(label);
    }
    if (distanceLine) {
      scene.remove(distanceLine);
    }
    areaLines.forEach((areaLine) => {
      scene.remove(areaLine);
    });
    if (distanceLabel) {
      scene.remove(distanceLabel);
    }
    if (areaLabel) {
      scene.remove(areaLabel);
    }
    distanceDots.forEach((dot) => {
      scene.remove(dot);
    });
    areaDots.forEach((dot) => {
      scene.remove(dot);
    });
  });

  // 添加鼠标点击事件监听
  threeContainer.value.addEventListener("mousedown", onDocumentMouseDown);
}

function onDocumentMouseDown(event: MouseEvent) {
  // 将鼠标位置归一化到-1到1之间
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

  // 更新射线投射器
  raycaster.setFromCamera(mouse, camera);

  // 计算交点
  const intersects = raycaster.intersectObjects(
    model ? model.children : [],
    true
  );

  if (intersects.length > 0) {
    const intersect = intersects[0];
    const point = intersect.point;

    if (isDistanceMeasuring) {
      if (event.button === 0) {
        // 左键点击
        distancePoints.push(point);
        addDot(point, distanceDots);
        if (distancePoints.length === 2) {
          calculateDistance();
        }
      } else if (event.button === 2) {
        // 右键点击
        cancelDistanceMeasurement();
      }
    } else if (isAreaMeasuring) {
      if (event.button === 0) {
        // 左键点击
        areaPoints.push(point);
        addDot(point, areaDots);
        if (areaPoints.length >= 3) {
          updateAreaLines();
          // calculateArea();
        }
      } else if (event.button === 2) {
        // 右键点击
        if (areaPoints.length >= 3) {
          calculateArea();
        } else {
          cancelAreaMeasurement();
        }
      }
    } else {
      // 原有的点击选中功能
      if (selectedObject) {
        (selectedObject.material as THREE.MeshStandardMaterial).emissive.set(
          0x000000
        ); // 恢复 emissive 颜色为黑色
        if (line) {
          scene.remove(line);
          line = null;
        }
        if (label) {
          scene.remove(label);
          label = null;
        }
      }

      if (intersect.object instanceof THREE.Mesh) {
        (intersect.object.material as THREE.MeshStandardMaterial).emissive.set(
          0x16d46b
        ); // 设置 emissive 颜色为绿色
        selectedObject = intersect.object; // 更新选中的对象

        // 创建线段
        const startPoint = intersect.point;
        const endPoint = startPoint.clone().add(new THREE.Vector3(0, 0, 0.5)); // 延伸到外部
        const geometry = new THREE.BufferGeometry().setFromPoints([
          startPoint,
          endPoint,
        ]);
        const material = new THREE.LineBasicMaterial({
          color: 0x16d46b,
        });
        line = new THREE.Line(geometry, material);
        scene.add(line);

        // 创建标签
        const canvas = document.createElement("canvas");
        const context = canvas.getContext("2d");
        if (context) {
          const textWidth = context.measureText(intersect.object.name).width;
          canvas.width = textWidth + 20;
          canvas.height = 50;
          context.font = "16px";
          // context.fillStyle = "rgba(0,0,0,0.8)";
          context.fillRect(0, 0, canvas.width, canvas.height);
          context.fillStyle = "#0179d4";
          context.textAlign = "center";
          context.fillText(intersect.object.name, canvas.width / 2, 30);
          const texture = new THREE.CanvasTexture(canvas);
          texture.needsUpdate = true; // 确保纹理更新
          const spriteMaterial = new THREE.SpriteMaterial({ map: texture });
          label = new THREE.Sprite(spriteMaterial);
          label.position.copy(endPoint);
          label.scale.set(0.1, 0.1, 1); // 调整标签大小
          scene.add(label);
        }
      }
    }
  } else {
    // 如果没有点击到任何对象,恢复当前选中的对象的颜色并移除线和标签
    if (selectedObject) {
      (selectedObject.material as THREE.MeshStandardMaterial).emissive.set(
        0x000000
      ); // 恢复 emissive 颜色为黑色
      selectedObject = null; // 清除选中的对象
      if (line) {
        scene.remove(line);
        line = null;
      }
      if (label) {
        scene.remove(label);
        label = null;
      }
    }
  }
}

function startDistanceMeasurement() {
  isDistanceMeasuring = true;
  isAreaMeasuring = false;
  distancePoints = [];
  distanceDots.forEach((dot) => {
    scene.remove(dot);
  });
  distanceDots = [];
  if (distanceLine) {
    scene.remove(distanceLine);
    distanceLine = null;
  }
  if (distanceLabel) {
    scene.remove(distanceLabel);
    distanceLabel = null;
  }
}

function startAreaMeasurement() {
  isAreaMeasuring = true;
  isDistanceMeasuring = false;
  areaPoints = [];
  areaDots.forEach((dot) => {
    scene.remove(dot);
  });
  areaDots = [];
  areaLines.forEach((areaLine) => {
    scene.remove(areaLine);
  });
  areaLines = [];
  if (areaLabel) {
    scene.remove(areaLabel);
    areaLabel = null;
  }
}

function calculateDistance() {
  const startPoint = distancePoints[0];
  const endPoint = distancePoints[1];
  const distance = startPoint.distanceTo(endPoint);

  // 创建线段
  const geometry = new THREE.BufferGeometry().setFromPoints([
    startPoint,
    endPoint,
  ]);
  const material = new THREE.LineBasicMaterial({
    color: 0x00ff00,
  });
  distanceLine = new THREE.Line(geometry, material);
  scene.add(distanceLine);

  // 创建标签
  const canvas = document.createElement("canvas");
  const context = canvas.getContext("2d");
  if (context) {
    const text = `距离: ${distance.toFixed(2)}`;
    const textWidth = context.measureText(text).width;
    canvas.width = textWidth + 25;
    canvas.height = 35;
    context.font = "12px Arial";
    context.fillStyle = "#ffffff";
    context.textAlign = "center";
    context.fillText(text, canvas.width / 2, 15);
    const texture = new THREE.CanvasTexture(canvas);
    texture.needsUpdate = true; // 确保纹理更新
    const spriteMaterial = new THREE.SpriteMaterial({ map: texture });
    distanceLabel = new THREE.Sprite(spriteMaterial);
    const midPoint = startPoint.clone().add(endPoint).divideScalar(2);
    distanceLabel.position.copy(midPoint);
    distanceLabel.scale.set(0.1, 0.1, 1); // 调整标签大小
    scene.add(distanceLabel);
  }

  isDistanceMeasuring = false;
}

function calculateArea() {
  let area = 0;
  const numPoints = areaPoints.length;
  for (let i = 0; i < numPoints; i++) {
    const j = (i + 1) % numPoints;
    area +=
      areaPoints[i].x * areaPoints[j].y - areaPoints[j].x * areaPoints[i].y;
  }
  area = Math.abs(area) / 2;

  // 创建标签
  const canvas = document.createElement("canvas");
  const context = canvas.getContext("2d");
  if (context) {
    const text = `面积: ${area.toFixed(2)}`;
    const textWidth = context.measureText(text).width;
    canvas.width = textWidth + 25;
    canvas.height = 35;
    context.font = "12px Arial";
    context.fillStyle = "#ffffff";
    context.textAlign = "center";
    context.fillText(text, canvas.width / 2, 15);
    const texture = new THREE.CanvasTexture(canvas);
    texture.needsUpdate = true; // 确保纹理更新
    const spriteMaterial = new THREE.SpriteMaterial({ map: texture });
    areaLabel = new THREE.Sprite(spriteMaterial);
    const centroid = new THREE.Vector3();
    areaPoints.forEach((point) => {
      centroid.add(point);
    });
    centroid.divideScalar(numPoints);
    areaLabel.position.copy(centroid);
    areaLabel.scale.set(0.1, 0.1, 1); // 调整标签大小
    scene.add(areaLabel);
  }

  isAreaMeasuring = false;
}

function cancelDistanceMeasurement() {
  isDistanceMeasuring = false;
  distancePoints = [];
  distanceDots.forEach((dot) => {
    scene.remove(dot);
  });
  distanceDots = [];
  if (distanceLine) {
    scene.remove(distanceLine);
    distanceLine = null;
  }
  if (distanceLabel) {
    scene.remove(distanceLabel);
    distanceLabel = null;
  }
}

function cancelAreaMeasurement() {
  isAreaMeasuring = false;
  areaPoints = [];
  areaDots.forEach((dot) => {
    scene.remove(dot);
  });
  areaDots = [];
  areaLines.forEach((areaLine) => {
    scene.remove(areaLine);
  });
  areaLines = [];
  if (areaLabel) {
    scene.remove(areaLabel);
    areaLabel = null;
  }
}

// 添加点击圆点
function addDot(point: THREE.Vector3, dots: THREE.Mesh[]) {
  const geometry = new THREE.SphereGeometry(0.01, 12, 12);
  const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
  const dot = new THREE.Mesh(geometry, material);
  dot.position.copy(point);
  scene.add(dot);
  dots.push(dot);
}

function updateAreaLines() {
  areaLines.forEach((areaLine) => {
    scene.remove(areaLine);
  });
  areaLines = [];
  const numPoints = areaPoints.length;
  for (let i = 0; i < numPoints; i++) {
    const j = (i + 1) % numPoints;
    const geometry = new THREE.BufferGeometry().setFromPoints([
      areaPoints[i],
      areaPoints[j],
    ]);
    const material = new THREE.LineBasicMaterial({
      color: 0x00ff00,
    });
    const line = new THREE.Line(geometry, material);
    scene.add(line);
    areaLines.push(line);
  }
}

onMounted(() => {
  initThree();
});
</script>

<style lang="scss" scoped>
.siteInspection {
  width: 100%;
  height: 100vh;
  position: relative;
}

.measurement-buttons {
  position: absolute;
  bottom: 10px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  gap: 10px;
}
</style>

 


http://www.niftyadmin.cn/n/5865887.html

相关文章

【复习】计算机网络

网络模型 OSI 应用层&#xff1a;给应用程序提供统一的接口表示层&#xff1a;把数据转换成兼容另一个系统能识别的格式会话层&#xff1a;负责建立、管理、终止表示层实体之间的通信会话传输层&#xff1a;负责端到端的数据传输网络层&#xff1a;负责数据的路由、转发、分片…

2024-2025 学年广东省职业院校技能大赛 “信息安全管理与评估”赛项 技能测试试卷(二)

2024-2025 学年广东省职业院校技能大赛 “信息安全管理与评估”赛项 技能测试试卷&#xff08;二&#xff09; 第一部分&#xff1a;网络平台搭建与设备安全防护任务书第二部分&#xff1a;网络安全事件响应、数字取证调查、应用程序安全任务书任务 1&#xff1a;应急响应&…

3. Spring Cloud LoadBalancer 入门与使用

一、什么是 LoadBalancer? LoadBalancer(负载均衡器)是一种网络设备或软件机制&#xff0c;用于分发传入的网络流量负载(请求)到多个后端目标服务器上&#xff0c;从而实现系统资源的均衡利用和提高系统的可用性和性能。 1.1 负载均衡分类 服务器负载均衡是在服务端通过硬件…

Seata分布式事务【详解分布式事务AT模式、2PC两阶段提交协议、Seata Server(TC)环境搭建,附有示例+代码】

文章目录 六.Seata 分布式事务6.1 分布式事务介绍6.2 常见分布式事务解决方案6.3 2PC两阶段提交协议&#xff1a;Prepare&#xff1a;提交事务请求Commit&#xff1a;执行事务提交2PC问题 6.4 AT 模式介绍6.5 Seata是什么&#xff1f;6.6 Seata快速开始Seata Server&#xff08…

Apache Pinpoint工具介绍

Apache Pinpoint&#xff1a;分布式系统性能分析与链路追踪 一、Pinpoint 简介 Apache Pinpoint 是一个开源的 分布式追踪系统&#xff0c;专为微服务架构设计&#xff0c;支持 HTTP、RPC、MQTT 等协议的调用链追踪。其核心功能包括&#xff1a; 链路可视化&#xff1a;展示…

一个用于测试内存屏障差异的 C 语言示例程序

下面是一个用于测试内存屏障差异的 C 语言示例程序,演示在弱内存模型(如 ARM Cortex-A35)中,指令重排序可能导致的数据不一致问题,并对比不同同步机制的效果: 测试目标 验证以下场景: 无内存屏障:多线程环境下可能出现数据竞争。内存屏障:强制指令顺序,避免数据竞争…

香港多IP站群服务器

香港多IP站群服务器是指一种具有多个独立IP地址、能够同时运行多个网站或应用的服务器。这种服务器特别适用于需要大量IP地址来扩展和管理多个站点的企业或个人&#xff0c;尤其是在SEO优化、网络营销、以及需要避免IP封锁或限制的场景下。下面是对香港多IP站群服务器的详细扩展…

如何使用爬虫获取淘宝商品详情:API返回值说明与案例指南

在电商数据分析和运营中&#xff0c;获取淘宝商品详情是常见的需求。淘宝开放平台提供了丰富的API接口&#xff0c;允许开发者通过合法的方式获取商品信息。本文将详细介绍如何使用PHP编写爬虫&#xff0c;通过淘宝API获取商品详情&#xff0c;并解析API返回值的含义和结构。 一…