點擊模型相機緩入查看模型相關信息
- 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();