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

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

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

javaScript 复制代码
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();
相关推荐
Sheldon一蓑烟雨任平生13 小时前
Vue3 插件(可选独立模块复用)
vue.js·vue3·插件·vue3 插件·可选独立模块·插件使用方式·插件中的依赖注入
鱼与宇14 小时前
苍穹外卖-VUE
前端·javascript·vue.js
用户479492835691514 小时前
Safari 中文输入法的诡异 Bug:为什么输入 @ 会变成 @@? ## 开头 做 @ 提及功能的时候,测试同学用 Safari 测出了个奇怪的问题
前端·javascript·浏览器
裴嘉靖15 小时前
Vue 生成 PDF 完整教程
前端·vue.js·pdf
毕设小屋vx ylw28242615 小时前
Java开发、Java Web应用、前端技术及Vue项目
java·前端·vue.js
冴羽16 小时前
今日苹果 App Store 前端源码泄露,赶紧 fork 一份看看
前端·javascript·typescript
蒜香拿铁16 小时前
Angular【router路由】
前端·javascript·angular.js
时间的情敌16 小时前
Vite 大型项目优化方案
vue.js
西洼工作室16 小时前
高效管理搜索历史:Vue持久化实践
前端·javascript·vue.js
代码改善世界16 小时前
仓颉语言相机拍照功能实现深度解析
数码相机