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

news2024/11/19 17:30:45

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

  • 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.coloradmin.cn/o/1294479.html

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

相关文章

Python 案例实训教学,课程展示及结课存档优化|ModelWhale 版本更新

大雪时节&#xff0c;仲冬如约而至&#xff0c;我们也迎来了 ModelWhale 新一轮的版本更新。 本次更新中&#xff0c;ModelWhale 主要进行了以下功能迭代&#xff1a; 优化 课程大纲展示&#xff08;团队版✓&#xff09;优化 作业批量导出存档&#xff08;团队版✓&#xff…

Windows本地如何添加域名映射?(修改hosts文件)

1. DNS(域名系统) Domain Name System(域名系统)&#xff1a;为了加快定位IP地址的速度, 将域名映射进行层层缓存的系统. 目的&#xff1a;互联网通过IP&#xff08;10.223.146.45&#xff09;定位浏览器建立连接&#xff0c;但是我们不易区别IP&#xff0c;为了方便用户辨识I…

基于SpringBoot+maven+Mybatis+html慢性病报销系统(源码+数据库)

一、项目简介 本项目是一套基于SpringBootmavenMybatishtml慢性病报销系统&#xff0c;主要针对计算机相关专业的正在做bishe的学生和需要项目实战练习的Java学习者。 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目可以直接作为bishe使用。 项目都经过严格调试&a…

C程序设计—输入一行字符,分别统计出其中的英文字母、空格、数字和其它字符的个数。

#include <stdio.h> #include <ctype.h> // 包含ctype.h头文件&#xff0c;用于判断字符类型 int main() { char str[100]; // 定义字符数组&#xff0c;用于存储输入的字符串 int i, letters 0, space 0, digit 0, others 0; // 定义变量&#xff0c…

护眼灯有效果吗?考研必备护眼台灯推荐

据统计&#xff0c;中国人口的近视率约为10%至20%。 国家卫健委发布的中国首份眼健康白皮书显示&#xff0c;我国小学生近视率为47.2%&#xff0c;初中生近视率为75.8%&#xff0c;大学生近视率超过90%。据世界卫生组织统计数据显示&#xff0c;目前全球约有14亿近视人口&#…

反射加载SDK完成统一调用

文章目录 1、需求背景2、接口抽象类具体实现类3、疑问4、存在的问题5、通过反射加载SDK并完成调用5、补充&#xff1a;关于业务网关7、补充&#xff1a;关于SDK的开发 关键点&#xff1a; 接口抽象类&#xff08;半抽象半实现&#xff09;具体实现类业务网关反射加载SDK&#…

C++新经典模板与泛型编程:策略技术中的算法策略

策略技术中的算法策略 在之前博客中funcsum()函数模板中&#xff0c;实现了对数组元素的求和运算。求和在这里可以看作一种算法&#xff0c;扩展一下思路&#xff0c;对数组元素求差、求乘积、求最大值和最小值等&#xff0c;都可以看作算法。而当前的funcsum()函数模板中&…

H3.3K27M弥漫性中线胶质瘤的反义寡核苷酸治疗

今天给同学们分享一篇实验文章“Antisense oligonucleotide therapy for H3.3K27M diffuse midline glioma”&#xff0c;这篇文章发表在Sci Transl Med期刊上&#xff0c;影响因子为17.1。 结果解读&#xff1a; CRISPR-Cas9消耗H3.3K27M恢复了H3K27三甲基化&#xff0c;并延…

PHPstorm可选择版本的链接

PHPstorm可选择版本的链接 链接&#xff1a;https://www.jetbrains.com/phpstorm/download/other.html

class060 拓扑排序的扩展技巧【算法】

class060 拓扑排序的扩展技巧【算法】 算法讲解060【必备】拓扑排序的扩展技巧 2023-12-7 22:23:02 code1 P4017 最大食物链计数 // 最大食物链计数 // a -> b&#xff0c;代表a在食物链中被b捕食 // 给定一个有向无环图&#xff0c;返回 // 这个图中从最初级动物到最顶…

游戏被攻击怎么办

随着科技的进步和互联网的普及&#xff0c;游戏行业也正在经历前所未有的变革。玩家们不再满足于传统的线下游戏&#xff0c;而是转向了线上游戏。然而&#xff0c;随着游戏的线上化&#xff0c;游戏安全问题也日益凸显。游戏受到攻击是游戏开发者永远的痛点&#xff0c;谈“D“…

Linus:我休假的时候也会带着电脑,否则会感觉很无聊

目录 Linux 内核最新版本动态 关于成为内核维护者 代码好写&#xff0c;人际关系难处理 内核维护者老龄化 内核中 Rust 的使用 关于 AI 的看法 参考 12.5-12.6 日&#xff0c;Linux 基金会组织的开源峰会&#xff08;OSS&#xff0c;Open Source Summit&#xff09;在日…

Enterprise Architect 12版本使用教程

Enterprise Architect 12版本使用教程 1.下载安装Enterprise Architect 122.Enterprise Architect原始DDL模板配置及存在的问题1.DDL Column Definition原始模板&#xff08;没有default值&#xff1a;可忽略&#xff09;2.DDL Data Type原始模板&#xff08;timestamp等时间字…

Diffusion 公式推导

Diffusion&#xff1a;通过扩散和逆扩散过程生成图像的生成式模型 中已经对 diffusion 的原理进行了直观地梳理&#xff0c;本文对其中的数学推导进行讲解&#xff0c;还是基于 DDPM。 目录 一. 预备知识1. 重参数技巧2. 高斯分布的可加性3. 扩散递推式的由来 二. 扩散过程1. 背…

企业计算机服务器中了360勒索病毒如何解密,勒索病毒解密数据恢复

网络技术的不断应用与发展&#xff0c;为企业的生产运营提供了极大便利&#xff0c;但随之而来的网络安全威胁也不断增加。近期&#xff0c;云天数据恢复中心接到很多企业的求助&#xff0c;企业的计算机服务器遭到了360后缀勒索病毒攻击&#xff0c;导致企业的所有数据被加密&…

AtCoder ABC周赛2023 11/4 (Sat) E题题解

目录 原题截图&#xff1a; 原题翻译 题目大意&#xff1a; 主要思路&#xff1a; 代码&#xff1a; 原题截图&#xff1a; 原题翻译 题目大意&#xff1a; 给你一个数组&#xff0c;给你一个公式&#xff0c;让你选k个元素&#xff0c;用公式算出最终得分。 主要思路&am…

云数据库与自建数据库有什么不同?

「自购服务器搭建数据库服务」&#xff0c;涉及到云服务器和物理机服务器的选择。这两者之间存在一定的差别。首先&#xff0c;物理机服务器需要更多的部署及维护操作&#xff0c;而云服务器则通过虚拟化技术提供了更便捷的资源管理和弹性伸缩能力。 总体来说&#xff0c;部署在…

移动云荣获OpenInfra社区“算力基础设施技术突破奖”

近日&#xff0c;一年一度的 OpenInfra Days China在北京召开&#xff0c;大会汇聚了全球各大云厂商的技术专家&#xff0c;共同分享全球前沿基础设施技术的展望和实践经验。在本次大会上&#xff0c;移动云荣获OpenInfra社区颁发的“算力基础设施技术突破奖”&#xff0c;表明…

销售技巧培训之如何提升顾问式销售技巧

销售技巧培训之如何提升顾问式销售技巧 在销售行业中&#xff0c;传统的“推销”模式往往注重产品的特点和优点&#xff0c;而忽视了客户的实际需求和感受。然而&#xff0c;随着消费者意识的提高和市场竞争的加剧&#xff0c;这种“产品导向”的销售方式已经不再适用。取而代…