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

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

安装:

# three.jsnpm 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);});// 初始化OrbitControlscontrols = new OrbitControls(camera, renderer.domElement);controls.enableDamping = true; // 启用阻尼效果controls.dampingFactor = 0.25; // 阻尼系数controls.enableZoom = true; // 启用缩放controls.enablePan = true; // 启用平移// 初始化Raycaster和Vector2raycaster = new THREE.Raycaster();mouse = new THREE.Vector2();// 渲染循环function animate() {requestAnimationFrame(animate);controls.update(); // 更新OrbitControlsrenderer.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.lryc.cn/news/542081.html

相关文章:

  • <el-table>右侧有空白列解决办法
  • Linux网络 网络层
  • 系统讨论Qt的并发编程——逻辑上下文的分类
  • 《Linux Shell 脚本深度探索:原理与高效编程》
  • 深入剖析:基于红黑树实现自定义 map 和 set 容器
  • 在大数据项目中如何设计和优化数据模型
  • JavaScript querySelector()、querySelectorAll() CSS选择器解析(DOM元素选择)
  • Linux系统中处理子进程的终止问题
  • Docker 不再难懂:快速掌握容器命令与架构原理
  • 取消票证会把指定的票证从数据库中删除,同时也会把票证和航班 等相关表中的关联关系一起删除。但在删除之前,它会先检查当前用户是否拥有这张票
  • 力扣-贪心-763 划分字母区间
  • 【Redis 原理】网络模型
  • cpp中的继承
  • DeepSeek全栈接入指南:从零到生产环境的深度实践
  • CSS 真的会阻塞文档解析吗?
  • 大模型的UI自动化:Cline 使用Playwright MCP Server完成测试
  • 碰撞检测 | 图解凸多边形分离轴定理(附ROS C++可视化)
  • Python 基本数据类型
  • 突破“第一崇拜“:五维心理重构之路
  • KubeKey一键安装部署k8s集群和KubeSphere详细教程
  • UE5网络通信架构解析
  • 实验3 知识表示与推理
  • 基于Springboot银行信用卡额度管理系统【附源码】
  • 达梦数据库学习笔记@1
  • 图像处理篇---图像处理中常见参数
  • AI Agent实战:打造京东广告主的超级助手 | 京东零售技术实践
  • 50周学习go语言:第1周 环境搭建
  • 4. MySQL 逻辑架构说明
  • 《AI与NLP:开启元宇宙社交互动新纪元》
  • 面对STM32的庞大体系,如何避免迷失在细节中?