VUE+THREE.JS 点击模型相机缓入查看模型相关信息

点击模型相机缓入查看模型相关信息

  • 1.引入
  • 2.初始化CSS3DRenderer
  • 3.animate 加入一直执行渲染
  • 4.点击事件
    • 4.1 初始化renderer时加入监听事件
    • 4.2 触发点击事件
  • 5. 关键代码分析
    • 5.1 移除模型
    • 5.2 创建模型上方的弹框
    • 5.3 相机缓入动画
    • 5.4 动画执行

1.引入

引入模型所要呈现的3DSprite精灵模型,优势在于可以随着视野的变化,跟随方向变化,大小是近大远小的模式

import { CSS3DRenderer, CSS3DSprite } from "three/examples/jsm/renderers/CSS3DRenderer.js";
import TWEEN from "@tweenjs/tween.js";//相机缓入动画

在这里插入图片描述

2.初始化CSS3DRenderer

// 初始化 CSS3DRenderer 设备信息框initObjectRender() {labelRender = new CSS3DRenderer();labelRender.setSize(this.$refs.draw.offsetWidth, this.$refs.draw.offsetHeight);labelRender.domElement.style.position = "absolute";labelRender.domElement.style.top = "0px";labelRender.domElement.style.pointerEvents = "none";document.getElementById("workshop").appendChild(labelRender.domElement);},

3.animate 加入一直执行渲染

labelRender.render(scene, camera);
TWEEN.update();

4.点击事件

4.1 初始化renderer时加入监听事件

renderer.domElement.addEventListener("click", this.onClick, false);

4.2 触发点击事件

//监听点击事件
onClick(event) {const raycaster = new THREE.Raycaster();const mouse = new THREE.Vector2();// 计算鼠标或触摸点的位置mouse.x = (event.clientX / this.$refs.draw.offsetWidth) * 2 - 1;mouse.y = -(event.clientY / this.$refs.draw.offsetHeight) * 2 + 1;// 更新射线   注意——> camera 是相机   定义到data里的raycaster.setFromCamera(mouse, camera);// 计算与所有对象的交点const intersects = raycaster.intersectObjects(scene.children, true);if (intersects.length > 0) {//获取点击模型的相关信息//以下为我的处理逻辑const interObj = intersects[0].object;//获取模型名称,此名称是用blender创建模型时,创建的名称const interName = this.getParentName(interObj);//模型的位置const interPoint = intersects[0].point;if (interName) {this.removeOthersEqp(interName); //移除此设备以外的设备this.getEqpInfo(interName, interObj, interPoint); //获取设备信息} else {console.log("获取世界坐标", interPoint.x, ",", interPoint.y, ",", interPoint.z);}}
},
//获取点击的设备名称
getParentName(data) {if (!data) {return;}const regex = /[^\_\)]+(?=\()/g;const eqpEnCode = data.name.match(regex);return eqpEnCode?.length > 0 ? eqpEnCode[0] : this.getParentName(data.parent);
},
//移除此设备以外的设备
removeOthersEqp(interName) {const meshes = scene.children.filter((o) => {return o.name !== `${interName}EqpInfo` && o.name.indexOf("EqpInfo") > -1;});meshes.forEach((l) => {l.remove(...l.children);});scene.remove(...meshes);
},
//获取设备信息
toolTipGroup: new THREE.Group(),//弹框参数
getEqpInfo(interName, interObj, interPoint) {// 获取设备详细信息let params = {system: "",enCode: interName,};getEqpInfoReq(params).then((res) => {if (res.code === 200) {const { encode, oeeStatus, taktTime, yield: resYield } = res.result;const shpereMesh = this.createCpointMesh(`${interName}EqpInfo`, interObj.position.x, interObj.position.y + 1000, interObj.position.z);this.toolTipGroup.add(shpereMesh);//关闭弹框标签const closeInfo = document.createElement("div");closeInfo.setAttribute("style", "width:100%;padding: 0.5rem 0.5rem 0 0;text-align:right");closeInfo.innerHTML = "<i class='iconfont icon-yuyinguanbi' style='font-size:0.5rem;color:#27eeea;cursor: pointer;'></i>";//弹框点击关闭事件closeInfo.onclick = function (event) {const meshes = scene.children.filter((o) => {return o.name === `${interName}EqpInfo`;});meshes.forEach((l) => {l.remove(...l.children);});scene.remove(...meshes);event.cancelBubble = true;};//基础信息展示const cardBaseInfo = `<div class='base-infos'><div class='base-info'><span class='name'>编码:</span><span>${encode}</span></div><div  class='base-info'><span class='name'>名称:</span><span>${interObj.name.match(/[^\(\)]+(?=\))/g)[0]}</span></div><div class='base-info'><span class='name'>状态:</span><span>${oeeStatus}</span></div></div>`;//设备其他信息const cardOthersInfo = `<div class='base-infos'><div class='base-info'><span class='name'>Yield:</span><span>${resYield}%</span></div><div class='base-info'><span class='name'>TaktTime:</span><span>${taktTime}</span></div></div>`;const cardInfo = document.createElement("div");cardInfo.style.padding = "0 0 1rem 0";cardInfo.innerHTML = cardBaseInfo + cardOthersInfo;const pContainer = document.createElement("div");pContainer.id = `${interName}EqpInfo`;pContainer.className = "workshop-tooltip";pContainer.style.pointerEvents = "none"; //避免HTML标签遮挡三维场景的鼠标事件pContainer.appendChild(closeInfo); //关闭按钮pContainer.appendChild(cardInfo); //基础信息const cPointLabel = new CSS3DSprite(pContainer);// cPointLabel.scale.set(5, 5, 5); //根据相机渲染范围控制HTML 3D标签尺寸cPointLabel.rotateY(Math.PI / 2); //控制HTML标签CSS3对象姿态角度cPointLabel.position.set(interObj.position.x, interObj.position.y + 1000, interObj.position.z);cPointLabel.name = `${interName}EqpInfo`;scene.add(cPointLabel);//相机位置移动this.handlePosition(interPoint, true);}});
},
//创建基础模型
createCpointMesh(name, x, y, z) {const geo = new THREE.BoxGeometry(0.1);const mat = new THREE.MeshBasicMaterial({ color: 0xff0000 });const mesh = new THREE.Mesh(geo, mat);mesh.position.set(x, y, z);mesh.name = name;return mesh;
},
// 动态调整相机位置
handlePosition(targetPosition, falg) {// 计算点击位置与当前相机位置之间的向量const direction = new THREE.Vector3().subVectors(targetPosition, camera.position);// 计算相机与目标位置之间的距离let distance = camera.position.distanceTo(targetPosition);// 以某种方式将距离转换为缩放因子let scaleFactor = falg ? this.functionOfDistance(distance) : 0;// 缩放向量,使其稍远一点// const scaleFactor = 0.5; // 缩放因子,可以根据需要进行调整const offset = direction.multiplyScalar(scaleFactor);const finalPosition = camera.position.clone().add(offset);// 创建 Tween 实例const startPosition = camera.position.clone();const duration = 1000; // 动画持续时间,单位毫秒tween = new TWEEN.Tween(startPosition).to(finalPosition, duration).onUpdate(() => {// 更新相机位置camera.position.copy(startPosition);camera.lookAt(targetPosition);}).start();
},
//计算距离
functionOfDistance(distance) {// 设定最小和最大距离以及对应的缩放因子const minDistance = 4100;const maxDistance = 18000;const minScaleFactor = 0;const maxScaleFactor = 0.8;if (distance < minDistance) {return minScaleFactor;} else if (distance > maxDistance) {return maxScaleFactor;}// 根据距离范围内的比例,计算缩放因子const ratio = (distance - minDistance) / (maxDistance - minDistance);return minScaleFactor + ratio * (maxScaleFactor - minScaleFactor);
},

5. 关键代码分析

5.1 移除模型

  • 1.获取想要移除的模型名称
const meshes = scene.children.filter((o) => {return o.name !== `${interName}EqpInfo` && o.name.indexOf("EqpInfo") > -1;
});
  • 2.移除模型的子模型
meshes.forEach((l) => {l.remove(...l.children);
});
  • 3.移除模型
scene.remove(...meshes)

5.2 创建模型上方的弹框

  • 1.创建基础模型
const shpereMesh = this.createCpointMesh(`${interName}EqpInfo`, interObj.position.x, interObj.position.y + 1000, interObj.position.z);//创建基础模型
createCpointMesh(name, x, y, z) {const geo = new THREE.BoxGeometry(0.1);const mat = new THREE.MeshBasicMaterial({ color: 0xff0000 });const mesh = new THREE.Mesh(geo, mat);mesh.position.set(x, y, z);mesh.name = name;return mesh;
},
  • 2.创建动态div,渲染到基础模型中

由于我这里是一个弹框,我希望他能够点击关闭,所以多加了个关闭事件

  • 2.1 关闭按钮的渲染及触发
//关闭弹框标签
const closeInfo = document.createElement("div");
closeInfo.setAttribute("style", "width:100%;padding: 0.5rem 0.5rem 0 0;text-align:right");
closeInfo.innerHTML = "<i class='iconfont icon-yuyinguanbi' style='font-size:0.5rem;color:#27eeea;cursor: pointer;'></i>";
//弹框点击关闭事件
closeInfo.onclick = function (event) {const meshes = scene.children.filter((o) => {return o.name === `${interName}EqpInfo`;});meshes.forEach((l) => {l.remove(...l.children);});scene.remove(...meshes);event.cancelBubble = true;
};
  • 2.2 弹框信息显示

注意:pContainer.style.pointerEvents = "none"; //避免HTML标签遮挡三维场景的鼠标事件必须要写这个 要不然会导致模型无法推拽移动

//基础信息展示
const cardBaseInfo = `<div class='base-infos'><div class='base-info'><span class='name'>编码:</span><span>${encode}</span></div><div  class='base-info'><span class='name'>名称:</span><span>${interObj.name.match(/[^\(\)]+(?=\))/g)[0]}</span></div><div class='base-info'><span class='name'>状态:</span><span>${oeeStatus}</span></div></div>`;
//设备其他信息
const cardOthersInfo = `<div class='base-infos'><div class='base-info'><span class='name'>Yield:</span><span>${resYield}%</span></div><div class='base-info'><span class='name'>TaktTime:</span><span>${taktTime}</span></div></div>`;const cardInfo = document.createElement("div");
cardInfo.style.padding = "0 0 1rem 0";
cardInfo.innerHTML = cardBaseInfo + cardOthersInfo;const pContainer = document.createElement("div");
pContainer.id = `${interName}EqpInfo`;
pContainer.className = "workshop-tooltip";
pContainer.style.pointerEvents = "none"; //避免HTML标签遮挡三维场景的鼠标事件
pContainer.appendChild(closeInfo); //关闭按钮
pContainer.appendChild(cardInfo); //基础信息const cPointLabel = new CSS3DSprite(pContainer);
// cPointLabel.scale.set(5, 5, 5); //根据相机渲染范围控制HTML 3D标签尺寸
cPointLabel.rotateY(Math.PI / 2); //控制HTML标签CSS3对象姿态角度
cPointLabel.position.set(interObj.position.x, interObj.position.y + 1000, interObj.position.z);
cPointLabel.name = `${interName}EqpInfo`;
scene.add(cPointLabel);

5.3 相机缓入动画

动态的缩放因子是为了避免弹框占满整个屏幕,使其稍远一点,默认是1

// 动态调整相机位置
handlePosition(targetPosition, falg) {// 计算点击位置与当前相机位置之间的向量const direction = new THREE.Vector3().subVectors(targetPosition, camera.position);// 计算相机与目标位置之间的距离let distance = camera.position.distanceTo(targetPosition);// 以某种方式将距离转换为缩放因子let scaleFactor = falg ? this.functionOfDistance(distance) : 0;// 缩放向量,使其稍远一点// const scaleFactor = 0.5; // 缩放因子,可以根据需要进行调整const offset = direction.multiplyScalar(scaleFactor);const finalPosition = camera.position.clone().add(offset);	
},
  • 动态缩放因子的获取

也不能将缩放因子固定,因为当相近模型点击时,弹框会越来越近,直至占满整个屏幕,
所以:设定最小的距离和最大的距离,当模型相对于相机距离远,就设定缩放因子为0.8,
模型相对相机距离近,就设定缩放因子为0,表示不缩放

//计算距离
functionOfDistance(distance) {// 设定最小和最大距离以及对应的缩放因子(可视情况调整)const minDistance = 4100;const maxDistance = 18000;const minScaleFactor = 0;const maxScaleFactor = 0.8;if (distance < minDistance) {return minScaleFactor;} else if (distance > maxDistance) {return maxScaleFactor;}// 根据距离范围内的比例,计算缩放因子const ratio = (distance - minDistance) / (maxDistance - minDistance);return minScaleFactor + ratio * (maxScaleFactor - minScaleFactor);
},

5.4 动画执行

// 创建 Tween 实例
const startPosition = camera.position.clone();
const duration = 1000; // 动画持续时间,单位毫秒
tween = new TWEEN.Tween(startPosition).to(finalPosition, duration).onUpdate(() => {// 更新相机位置camera.position.copy(startPosition);camera.lookAt(targetPosition);}).start();

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/207191.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Dexie 查询sql速度优化

Dexie查询速度慢的原因主要一个优化点是复杂查询下的count执行。 以下摘自Dexie官方文档&#xff1a;https://dexie.org/docs/Collection/Collection.count() If executed on simple queries, the native IndexedDB ObjectStore count() method will be called (fast execution…

对标Gen-2!Meta发布新模型,进军文生视频赛道

随着扩散模型的飞速发展&#xff0c;诞生了Midjourney、DALLE 3、Stable Difusion等一大批出色的文生图模型。但在文生视频领域却进步缓慢&#xff0c;因为文生视频多数采用逐帧生成的方式,这类自回归方法运算效率低下、成本高。 即便使用先生成关键帧,再生成中间帧新方法。如…

Flink Window中典型的增量聚合(ReduceFunction / AggregateFunction)

一、什么是增量聚合函数 在Flink Window中定义了窗口分配器&#xff0c;我们只是知道了数据属于哪个窗口&#xff0c;可以将数据收集起来了&#xff1b;至于收集起来到底要做什么&#xff0c;其实还完全没有头绪&#xff0c;这也就是窗口函数所需要做的事情。所以在窗口分配器…

听GPT 讲Rust源代码--src/tools(9)

File: rust/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs 在Rust源代码中&#xff0c;apply_demorgan.rs文件位于rust-analyzer工具的ide-assists库中&#xff0c;其作用是实现一个辅助函数&#xff0c;用于在代码中应用De Morgan定律的变换。 …

Android : 篮球记分器app _简单应用

示例图&#xff1a; 1.导包 在build.gradle 中 加入 // 使用androidx版本库implementation androidx.lifecycle:lifecycle-extensions:2.1.0-alpha03 2. 开启dataBinding android{...// 步骤1.开启data bindingdataBinding {enabled true}...} 3.写个类继承 ViewModel pac…

自下而上-存储全栈(TiDB/RockDB/SPDK/fuse/ceph/NVMe/ext4)存储技术专家成长路线

数字化时代的到来带来了大规模数据的产生&#xff0c;各行各业都面临着数据爆炸的挑战。 随着云计算、物联网、人工智能等新兴技术的发展&#xff0c;对存储技术的需求也越来越多样化。不同应用场景对存储的容量、性能、可靠性和成本等方面都有不同的要求。具备存储技术知识和技…

机器学习-聚类问题

前言 聚类算法又叫做”无监督分类“&#xff0c;目标是通过对无标记训练样本来揭示数据的内在性质及 规律&#xff0c;为进一步的数据分析提供基础。 Kmeans 作为聚类算法的典型代表&#xff0c;Kmeans可以说是最简单的聚类算法&#xff0c;没有之一&#xff0c;那她是怎么完…

MySQL为何偏爱B+树索引

一、MySQL、B树概念 MySQL是一种关系型数据库&#xff0c;它使用SQL语言来操作数据。SQL语言可以实现对数据的增删改查等操作&#xff0c;但是如果数据量很大&#xff0c;那么这些操作的效率就会很低。为了提高效率&#xff0c;MySQL引入了索引的概念。 索引是一种数据结构&am…

人体关键点检测1:人体姿势估计数据集

人体关键点检测1&#xff1a;人体姿势估计数据集 目录 人体关键点检测1&#xff1a;人体姿势估计数据集 1.人体姿态估计 2.人体姿势估计数据集 &#xff08;1&#xff09;COCO数据集 &#xff08;2&#xff09;MPII数据集 &#xff08;3&#xff09;Human3.6M &#xf…

MS5228/5248/5268:2.7V 到 5.5V、 12/14/16Bit、内置基准、八通道数模转换器

MS5228/MS5248/MS5268 是一款 12/14/16bit 八通道输出的电压型 DAC &#xff0c;内部集成上电复位电路、可选内部基准、接口采用四线串口模式&#xff0c; 最高工作频率可以到 40MHz &#xff0c;可以兼容 SPI 、 QSPI 、 DSP 接口和 Microwire 串口。输出接到一个 …

<习题集><LeetCode><链表><2/19/21/23/24>

目录 2. 两数相加 19. 删除链表的倒数第 N 个结点 21. 合并两个有序链表 23. 合并 K 个升序链表 24. 两两交换链表中的节点 2. 两数相加 https://leetcode.cn/problems/add-two-numbers/ public ListNode addTwoNumbers(ListNode l1, ListNode l2) {//head是cur链表头节点…

Go语言实现深度学习的正向传播和反向传播

文章目录 开发前言开发理论图解理论数据类型数学函数数据节点统一抽象变量数据节点常量数据节点单目运算封装双目运算封装算子节点统一抽象基础算子加法算子减法算子乘法算子除法算子指数算子对数算子正切算子正弦算子余弦算子数据流图正向传播反向传播正向训练反向训练运行示例…

船舶机电设备振动数据采集监控系统解决方案

船舶运行中&#xff0c;通常需要通过振动数据采集系统对船舶的各个机电设备运行进行监控&#xff0c;有助于在设备故障时快速预警&#xff0c;进行诊断、分析和维护&#xff0c;保证船舶机电设备正常工作&#xff0c;从而确保工作人员及船舶的安全。 船舶各种机电设备会产生大…

【模型量化】神经网络量化基础及代码学习总结

1 量化的介绍 量化是减少神经网络计算时间和能耗的最有效的方法之一。在神经网络量化中&#xff0c;权重和激活张量存储在比训练时通常使用的16-bit或32-bit更低的比特精度。当从32-bit降低到8-bit&#xff0c;存储张量的内存开销减少了4倍&#xff0c;矩阵乘法的计算成本则二…

ALNS算法中随机化重要性的评价

文章概述 本研究分析了在海上提货和交付问题中使用的ALNS元启发式算法中的随机化成分。研究者提出了简单的确定性替代方案&#xff0c;并通过实验比较了随机化和确定性成分的性能。结果表明&#xff0c;初始实现的简单确定性替代方案能够与随机化成分的性能相匹配。这项研究为…

IDEA使用git从远程仓库获取项目

将地址填入url中 然后直接clone就行

《Easy3d+Qt+VTK》学习

《Easy3dQtVTK》学习-1、编译与配置 一、编译二、配置注 一、编译 1、 资源下载&#xff1a;easy3d giuhub 2、解压缩 3、用qt打开CMakeLists.txt即可 4、点击项目&#xff0c;选择debug或者release&#xff0c;图中3处可自行选择&#xff0c;因为我的qt版本是6&#xff0c…

在linux上如何运用虚拟数据优化器VDO

本章主要介绍虚拟化数据优化器。 什么是虚拟数据优化器VDO 创建VDO设备以节约硬盘空间 16.1 了解什么是VDO VDO全称是Virtual Data Optimize&#xff08;虚拟数据优化)&#xff0c;主要是为了节省硬盘空间。 现在假设有两个文件file1和 file2&#xff0c;大小都是10G。file…

cpu 300% 爆满 内存占用不高 排查

top查询 cpu最高的PID ps -ef | grep PID 查看具体哪一个jar服务 jstack -l PID > ./jstack.log 下载/打印进程的线程栈信息 可以加信息简单分析 或进一步 查看堆内存使用情况 jmap -heap Java进程id jstack.log 信息示例 Full thread dump Java HotSpot(TM) 64-Bit Se…

横向扩展统一存储与备份服务器功能

Infortrend 更新了GS&#xff0c;GSe&#xff0c;GSe Pro统一存储系列的备份服务器功能。该功能降低数据备份成本&#xff0c;并提供灵活的备份策略。通过备份服务器功能&#xff0c;用户可以通过多种途径实现数据备份&#xff0c;包括公有云&#xff08;兼容S3&#xff09;、文…