当前位置: 首页 > article >正文

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

安装:

# three.js

npm install --save three

 附中文网:

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

附官网:

安装 – three.js docs

完整代码(简易demo):

<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.kler.cn/a/561193.html

相关文章:

  • 使用Kafka进行实时数据流处理的场景
  • Sky Hackathon 清水湾的水 AI美食助手
  • 数据结构:Map set - 习题(三)
  • 智能物联赋能城市照明升级——塔能科技的创新实践与城市转型
  • Reactor和Paroactor模型
  • [特殊字符]清华大学:DeepSeek从入门到精通.pdf(清华领航,驾驭DeepSeek,开启AI新境界)
  • 【Python爬虫(69)】解锁游戏数据宝藏:Python爬虫实战攻略
  • 基于TensorFlow.js与Web Worker的智能证件照生成方案
  • 阿里云 ACS:高效、弹性、低成本的容器计算解决方案
  • docker 中安装postgres
  • 基于YOLO11深度学习的半导体芯片缺陷检测系统【python源码+Pyqt5界面+数据集+训练代码】
  • 加油小程序实战教程01需求分析
  • Minio分布式多节点多驱动器集群部署
  • [AI]从零开始的树莓派运行DeepSeek模型教程
  • MySQL数据库的基本命令
  • Linux系统:服务器常见服务默认IP端口合集
  • AI时代前端开发技能变革与ScriptEcho:拥抱AI,提升效率
  • 利用时间戳校验防止爬虫滥用接口
  • 免费 MLOps 课程:学习机器学习运维的完整流程
  • Linux第十四节 — 环境变量和进程地址空间