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

news/2024/6/29 12:16:34 标签: javascript, vue.js, 数码相机

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

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

1.引入

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

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

2.初始化CSS3DRenderer

javascript">// 初始化 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时加入监听事件

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

4.2 触发点击事件

javascript">//监听点击事件
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.获取想要移除的模型名称
javascript">const meshes = scene.children.filter((o) => {
	return o.name !== `${interName}EqpInfo` && o.name.indexOf("EqpInfo") > -1;
});
  • 2.移除模型的子模型
javascript">meshes.forEach((l) => {
	l.remove(...l.children);
});
  • 3.移除模型
javascript">scene.remove(...meshes)

5.2 创建模型上方的弹框

  • 1.创建基础模型
javascript">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 关闭按钮的渲染及触发
javascript">//关闭弹框标签
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标签遮挡三维场景的鼠标事件必须要写这个 要不然会导致模型无法推拽移动

javascript">//基础信息展示
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

javascript">// 动态调整相机位置
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,表示不缩放

javascript">//计算距离
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 动画执行

javascript">// 创建 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.niftyadmin.cn/n/5232070.html

相关文章

极兔快递查询入口,筛选出指定派件员的单号

批量查询极兔快递单号的物流信息&#xff0c;将指定派件员的单号筛选出来。 所需工具&#xff1a; 一个【快递批量查询高手】软件 极兔快递单号若干 操作步骤&#xff1a; 步骤1&#xff1a;运行【快递批量查询高手】软件&#xff0c;并登录 步骤2&#xff1a;点击主界面左上…

2023年小美赛A题论文无偿分享!!

A题论文分享 链接&#xff1a; https://pan.baidu.com/s/1cltswCuiPG30HhuysrEVuA 提取码&#xff1a;sxjm 欢迎大家批评指正&#xff0c; 为了方便大家摘抄&#xff0c;所有内容均为中文。但是&#xff01;&#xff01;该比赛是英文论文竞赛&#xff0c;大家需要全部自行…

负电源电压转换-TP7660H

负电源电压转换-TP7660H 简介引脚说明典型应用电路倍压与反压的应用电路 简介 TP7660H 是一款 DC/DC 电荷泵电压反转器专用集成电路。芯片能将输入范围为 2.5V&#xff5e;11V 的电压转换成相应的-2.5V&#xff5e;-11V 的输出&#xff0c;电压转换精度可达99.9%&#xff0c;电…

Linux基本指令(中篇)

目录 8.cp指令&#xff08;重要&#xff09; 9.mv指令&#xff08;重要&#xff09;&#xff1a; 10.cat指令&#xff08;适合查看小文件内容&#xff09; 11.more指令&#xff08;适合查看大文件内容&#xff09; 12.less指令&#xff08;重要&#xff09; 13.head指令和…

重生之我是一名程序员 47 ——字符串函数 (6)

哈喽啊大家晚上好 &#xff0c;前面呢已经给大家介绍了5个字符串函数&#xff0c;今天呢再给大家带来一个&#xff0c;它就是——strstr函数。 首先呢&#xff0c;还是先带大家认识一下它。strstr函数&#xff08;string search&#xff09;是C语言标准库中的一个字符串处理函…

论文精读 Co-DETR(Co-DINO、Co-Deformable-DETR)

DETRs with Collaborative Hybrid Assignments Training 基于协作混合分配训练的DETRs 论文链接&#xff1a;2211.12860.pdf (arxiv.org) 源码链接&#xff1a;https://github.com/Sense-X/Co-DETR 总结&#xff1a; Co-DETR基于DAB-DETR、Deformable-DETR和DINO网络进行了实…

机器学习笔记 - 基于百度飞桨PaddleSeg的人体分割模型以及TensorRT部署说明

一、简述 虽然Segment Anything用于图像分割的通用大模型看起来很酷(飞桨也提供分割一切的模型),但是个人感觉落地应用的时候心里还是更倾向于飞桨这种场景式的,因为需要用到一些人体分割的需求,所以这里主要是对飞桨高性能图像分割开发套件进行了解和使用,但是暂时不训练…

计算机网络测试题

一 单项选择题(5分) 1、假设要发送的数据为101110&#xff0c;采用CRC的生成多项式是X31&#xff0c;试求应添加在数据后面的余数。&#xff08;5分&#xff09; 110 011&#xff08;答案&#xff09; 101 001 实际得分&#xff1a;5分 二 填空题(95分) 1、以下3个子地址块…