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();
相关文章:

VUE+THREE.JS 点击模型相机缓入查看模型相关信息
点击模型相机缓入查看模型相关信息 1.引入2.初始化CSS3DRenderer3.animate 加入一直执行渲染4.点击事件4.1 初始化renderer时加入监听事件4.2 触发点击事件 5. 关键代码分析5.1 移除模型5.2 创建模型上方的弹框5.3 相机缓入动画5.4 动画执行 1.引入 引入模型所要呈现的3DSprite…...

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…...

Halcon 简单的ORC 字体识别
文章目录 仿射变化识别 仿射变化 将图片进行矫正处理 dev_close_window() read_image(Image,C:/Users/Augustine/Desktop/halcon/image.png) *获取图片的大小 get_image_size(Image, Width, Height) *仿射运算获取图片的角度对图片进行矫正 *选中图片的区域 gen_rectangle1 (Re…...

12月7日作业
使用QT模仿一个登陆界面(模仿育碧Ubisoft登录界面) #include "myqq.h"MyQQ::MyQQ(QWidget *parent): QMainWindow(parent) {this->resize(880,550); //设置窗口大小this->setFixedSize(880,550); //固定窗口大小this->setStyleShee…...

【腾讯云HAI域探密】- AIGC应用助力企业降本增效之路
一、前言: 近年来,随着深度学习、大数据、人工智能、AI等技术领域的不断发展,机器学习是目前最火热的人工智能分支之一,是使用大量数据训练计算机程序,以实现智能决策、语音识别、图像处理等任务。 作者也是经过了以上…...

云原生之深入解析如何限制Kubernetes集群中文件描述符与线程数量
一、背景 linux 中为了防止进程恶意使用资源,系统使用 ulimit 来限制进程的资源使用情况(包括文件描述符,线程数,内存大小等)。同样地在容器化场景中,需要限制其系统资源的使用量。ulimit: docker 默认支持…...

Django的Auth模块
Auth模块 我们在创建好一个Django项目后执行数据库迁移命令会自动生成很多表 其中有auth_user等表 Django在启动之后就可以直接访问admin路由,需要输入用户名和密码,数据参考的就是auth_user表,并且必须是管理员才能进入 依赖于a…...

敏捷开发方法
理解: 极限编程(XP):敏捷开发的典型方法之一,是一种轻量级(敏捷)、高效,低风险、柔性、可预测的、科学的软件开发方法,它由价值观、原则、实践和行为4个部分组成。其中4大…...

vue 前端实现login页登陆 验证码
实现效果 // template <el-form :model"loginForm" :rules"fieldRules" ref"loginForm" label-position"left" label-width"0px" class"login-container"><span class"tool-bar"></sp…...

python 涉及opencv mediapipe知识,眨眼计数 供初学者参考
基本思路 我们知道正面侦测到人脸时,任意一只眼睛水平方向上的两个特征点构成水平距离,上下两个特征点构成垂直距离 当头像靠近或者远离摄像头时,垂直距离与水平距离的比值基本恒定 根据这一思路 当闭眼时 垂直距离变小 比值固定小于某一个…...

HTTP 和 HTTPS的区别
一、HTTP 1.明文传输,不安全 2.默认端口号:80 3.TCP三次握手即可 二、HTTPS 1.加密传输,更安全(在HTTP层与TCP层之间加上了SSL/TTL安全协议) SSL和TTL是在不同时期的两种叫法,含义相同。 2.默认端口号:443 3.TCP三…...

从零开始训练一个ChatGPT大模型(低资源,1B3)
macrogpt-prertrain 大模型全量预训练(1b3), 多卡deepspeed/单卡adafactor 源码地址:https://github.com/yongzhuo/MacroGPT-Pretrain.git 踩坑 1. 数据类型fp16不太行, 很容易就Nan了, 最好是fp32, tf32, 2. 单卡如果显存不够, 可以用优化器adafactor, 3. 如果…...

从文字到使用,一文读懂Kafka服务使用
🏆作者简介,普修罗双战士,一直追求不断学习和成长,在技术的道路上持续探索和实践。 🏆多年互联网行业从业经验,历任核心研发工程师,项目技术负责人。 🎉欢迎 👍点赞✍评论…...

什么是https加密协议?
前言: HTTPS(全称:Hypertext Transfer Protocol Secure) 是一个安全通信通道,它基于HTTP开发用于在客户计算机和服务器之间交换信息。它使用安全套接字层(SSL)进行信息交换,简单来说它是HTTP的安全版&…...

0012Java程序设计-ssm医院预约挂号及排队叫号系统
文章目录 **摘** **要**目 录系统实现5.2后端功能模块5.2.1管理员功能模块5.2.2医生功能模块 开发环境 摘 要 网络的广泛应用给生活带来了十分的便利。所以把医院预约挂号及排队叫号管理与现在网络相结合,利用java技术建设医院预约挂号及排队叫号系统,实…...

PaddleClas学习3——使用PPLCNet模型对车辆朝向进行识别(c++)
使用PPLCNet模型对车辆朝向进行识别 1 准备环境2 准备模型2.1 模型导出2.2 修改配置文件3 编译3.1 使用CMake生成项目文件3.2 编译3.3 执行3.4 添加后处理程序3.4.1 postprocess.h3.4.2 postprocess.cpp3.4.3 在cls.h中添加函数声明3.4.4 在cls.cpp中添加函数定义3.4.5 在main.…...

学习记录---kubernetes中备份和恢复etcd
一、简介 ETCD是kubernetes的重要组成部分,它主要用于存储kubernetes的所有元数据,我们在kubernetes中的所有资源(node、pod、deployment、service等),如果该组件出现问题,则可能会导致kubernetes无法使用、资源丢失等情况。因此…...

使用单例模式+观察者模式实现参数配置实时更新
使用vector存储观察者列表 #include <iostream> #include <vector> #include <functional> #include <algorithm>// 配置参数结构体 struct MyConfigStruct {int parameter1;std::string parameter2; };class Config { public:using Observer std::f…...

区块链实验室(28) - 拜占庭节点劫持区块链仿真
在以前的FISCO环境中仿真拜占庭节点攻击区块链网络。该环境共有100个节点,采用PBFT作为共识机制,节点编号分别为:Node0,Node,… ,Node99。这100个节点的前2010区块完全相同,自区块2011开始分叉。…...

聊聊AsyncHttpClient的ChannelPool
序 本文主要研究一下AsyncHttpClient的ChannelPool ChannelPool org/asynchttpclient/channel/ChannelPool.java public interface ChannelPool {/*** Add a channel to the pool** param channel an I/O channel* param partitionKey a key used to retrieve the cac…...

[MySQL] MySQL复合查询(多表查询、子查询)
前面我们学习了MySQL简单的单表查询。但是我们发现,在很多情况下单表查询并不能很好的满足我们的查询需求。本篇文章会重点讲解MySQL中的多表查询、子查询和一些复杂查询。希望本篇文章会对你有所帮助。 文章目录 一、基本查询回顾 二、多表查询 2、1 笛卡尔积 2、2…...

[架构之路-256]:目标系统 - 设计方法 - 软件工程 - 软件设计 - 架构设计 - 软件系统不同层次的复用与软件系统向越来越复杂的方向聚合
目录 前言: 一、CPU寄存器级的复用:CPU寄存器 二、指令级复用:二进制指令 三、过程级复用:汇编语言 四、函数级复用:C语言 五、对象级复用:C, Java, Python 六、组件级复用 七、服务级复用 八、微…...

C++初学教程三
目录 一、运算符 一、自增自减运算符 二、位运算符 三、关系运算符...

雷达点云数据.pcd格式转.bin格式
雷达点云数据.pcd格式转.bin格式 注意,方法1原则上可行,但是本人没整好pypcd的环境 方法2是绝对可以的。 方法1 1 源码如下: def pcb2bin1(): # save as bin formatimport os# import pypcdfrom pypcd import pypcdimport numpy as np…...

Fiddler抓包测试
模拟弱网测试 操作:一、Rules - Customize Rules (快捷键CtrlR)弹出编辑器 二、接着CtrlF查找m_SimulateModem标志位 三、默认上传300ms,下载150ms 四、更改后,继续Rules - Performances - Simulate Modem Speeds勾上 …...

视频处理关键知识
1 引言 视频技术发展到现在已经有100多年的历史,虽然比照相技术历史时间短,但在过去很长一段时间之内都是最重要的媒体。由于互联网在新世纪的崛起,使得传统的媒体技术有了更好的发展平台,应运而生了新的多媒体技术。而多媒体技术…...

LeetCode435. Non-overlapping Intervals
文章目录 一、题目二、题解 一、题目 Given an array of intervals intervals where intervals[i] [starti, endi], return the minimum number of intervals you need to remove to make the rest of the intervals non-overlapping. Example 1: Input: intervals [[1,2]…...

ffmpeg 实现多视频轨录制到同一个文件
引言 在视频录制中,有时会碰到这样一个需求,将不同摄像头的画面写入到一个视频文件,这个叫法很多,有的厂家叫合流模式,有的叫多画面多流模式。无论如何,它们的实质都是在一个视频文件上实现多路不同分辨率视…...

vue3中子组件调用父组件的方法
<script lang"ts" setup>前提 父组件: 子组件: const emit defineEmits([closeson]) 在子组件的方法中使用: emit(closeson)...

使用OkHttp上传本地图片及参数
下面以一个例子来讲解在项目中如何使用OKHttp来对本地图片做个最简单的上传功能,基本上无封装,只需要简单调用便可(对于OKHttp的引入不再单独做介绍)。 1:构建上传图片附带的参数(params) Map…...