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…...
未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?
编辑:陈萍萍的公主一点人工一点智能 未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战,在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…...
LeetCode - 394. 字符串解码
题目 394. 字符串解码 - 力扣(LeetCode) 思路 使用两个栈:一个存储重复次数,一个存储字符串 遍历输入字符串: 数字处理:遇到数字时,累积计算重复次数左括号处理:保存当前状态&a…...
五年级数学知识边界总结思考-下册
目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解:由来、作用与意义**一、知识点核心内容****二、知识点的由来:从生活实践到数学抽象****三、知识的作用:解决实际问题的工具****四、学习的意义:培养核心素养…...
NLP学习路线图(二十三):长短期记忆网络(LSTM)
在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...
css3笔记 (1) 自用
outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size:0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格ÿ…...
vue3+vite项目中使用.env文件环境变量方法
vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量,这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...
【分享】推荐一些办公小工具
1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由:大部分的转换软件需要收费,要么功能不齐全,而开会员又用不了几次浪费钱,借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...
Python Einops库:深度学习中的张量操作革命
Einops(爱因斯坦操作库)就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库,用类似自然语言的表达式替代了晦涩的API调用,彻底改变了深度学习工程…...
解析奥地利 XARION激光超声检测系统:无膜光学麦克风 + 无耦合剂的技术协同优势及多元应用
在工业制造领域,无损检测(NDT)的精度与效率直接影响产品质量与生产安全。奥地利 XARION开发的激光超声精密检测系统,以非接触式光学麦克风技术为核心,打破传统检测瓶颈,为半导体、航空航天、汽车制造等行业提供了高灵敏…...
go 里面的指针
指针 在 Go 中,指针(pointer)是一个变量的内存地址,就像 C 语言那样: a : 10 p : &a // p 是一个指向 a 的指针 fmt.Println(*p) // 输出 10,通过指针解引用• &a 表示获取变量 a 的地址 p 表示…...
