|
@@ -5,6 +5,16 @@ import { ref, watch, Ref, nextTick } from 'vue';
|
|
|
// 用于存储当前的门状态数据,方便外部调试或扩展
|
|
// 用于存储当前的门状态数据,方便外部调试或扩展
|
|
|
const gateList = ref<any[]>([]);
|
|
const gateList = ref<any[]>([]);
|
|
|
|
|
|
|
|
|
|
+// 模块级变量:存储当前的 handleGateAnimate,供外部组件直接调用 3D 动画
|
|
|
|
|
+let currentHandleGateAnimate: ((doors: any[]) => void) | null = null;
|
|
|
|
|
+
|
|
|
|
|
+/** 供外部(如 2D 组件)调用的 3D 动画触发函数 */
|
|
|
|
|
+export function trigger3DAnimation(doors: any[]) {
|
|
|
|
|
+ if (currentHandleGateAnimate) {
|
|
|
|
|
+ currentHandleGateAnimate(doors);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 初始化防火门 3D 动画
|
|
* 初始化防火门 3D 动画
|
|
|
* @param modal Three.js 模态框实例
|
|
* @param modal Three.js 模态框实例
|
|
@@ -14,36 +24,74 @@ export async function modalAnimate(modal: any, modalMonitorData: Ref<any, any>)
|
|
|
await nextTick();
|
|
await nextTick();
|
|
|
|
|
|
|
|
if (!modal || !modal.scene) {
|
|
if (!modal || !modal.scene) {
|
|
|
- console.error('模型加载失败');
|
|
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 获取主模型对象 (假设模型名称结构,根据实际模型调整)
|
|
|
|
|
- // 注意:请确保模型加载完成后调用此函数,或者在 modal.scene 中存在这些对象
|
|
|
|
|
- const fireDoorModal = modal.scene.getObjectByName('pidai')?.getObjectByName('PiDaiHang_1');
|
|
|
|
|
- if (!fireDoorModal) return;
|
|
|
|
|
|
|
+ // 尝试多种方式定位防火门模型的容器对象
|
|
|
|
|
+ let fireDoorModal: THREE.Object3D | undefined;
|
|
|
|
|
|
|
|
- resetModalParam(modal);
|
|
|
|
|
|
|
+ // 方式1:通过已知的层级路径 pidai → PiDaiHang_1
|
|
|
|
|
+ const pidaiObj = modal.scene.getObjectByName('pidai');
|
|
|
|
|
+ if (pidaiObj) {
|
|
|
|
|
+ fireDoorModal = pidaiObj.getObjectByName('PiDaiHang_1');
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 初始化时,强制找到所有门并归零,确保“关闭”状态是物理上的 0,0,0
|
|
|
|
|
- const initFireDoor = fireDoorModal.getObjectByName('men_1');
|
|
|
|
|
- const initBeltCover = fireDoorModal.getObjectByName('gaiban');
|
|
|
|
|
|
|
+ // 方式2:直接在场景中搜索 PiDaiHang_1
|
|
|
|
|
+ if (!fireDoorModal) {
|
|
|
|
|
+ fireDoorModal = modal.scene.getObjectByName('PiDaiHang_1');
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if (initFireDoor) {
|
|
|
|
|
- initFireDoor.rotation.set(0, 0, 0); // 强制归零
|
|
|
|
|
|
|
+ // 方式3:搜索名字中包含 PiDaiHang 的对象
|
|
|
|
|
+ if (!fireDoorModal) {
|
|
|
|
|
+ modal.scene.traverse((obj: THREE.Object3D) => {
|
|
|
|
|
+ if (!fireDoorModal && obj.name.includes('PiDaiHang')) {
|
|
|
|
|
+ fireDoorModal = obj;
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
- if (initBeltCover) {
|
|
|
|
|
- initBeltCover.rotation.set(0, 0, 0); // 强制归零
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 方式4:通过模型根节点 (modelName = 'pidai-fire-door') 搜索
|
|
|
|
|
+ if (!fireDoorModal) {
|
|
|
|
|
+ const modelRoot = modal.scene.getObjectByName('pidai-fire-door');
|
|
|
|
|
+ if (modelRoot) {
|
|
|
|
|
+ modelRoot.traverse((obj: THREE.Object3D) => {
|
|
|
|
|
+ if (!fireDoorModal && (obj.name.includes('PiDaiHang') || obj.name.includes('pidaihang') || obj.name.includes('PiDai'))) {
|
|
|
|
|
+ fireDoorModal = obj;
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (!fireDoorModal) {
|
|
if (!fireDoorModal) {
|
|
|
- console.warn('模型元素未找到');
|
|
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 重置控制器参数,保持视角稳定
|
|
|
|
|
|
|
+ // 类型收窄:确保后续代码中 fireDoorModal 为非 undefined
|
|
|
|
|
+ const doorContainer: THREE.Object3D = fireDoorModal;
|
|
|
|
|
+
|
|
|
resetModalParam(modal);
|
|
resetModalParam(modal);
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 在容器内查找与给定模式匹配的网格对象
|
|
|
|
|
+ * 优先精确匹配 men_{suffix} / gaiban_{suffix},找不到则回退到包含关键词的对象
|
|
|
|
|
+ */
|
|
|
|
|
+ const findMesh = (container: THREE.Object3D, patterns: string[]): THREE.Object3D | undefined => {
|
|
|
|
|
+ for (const pattern of patterns) {
|
|
|
|
|
+ const exact = container.getObjectByName(pattern);
|
|
|
|
|
+ if (exact) return exact;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 模糊匹配
|
|
|
|
|
+ let found: THREE.Object3D | undefined;
|
|
|
|
|
+ container.traverse((child: THREE.Object3D) => {
|
|
|
|
|
+ if (found) return;
|
|
|
|
|
+ const lower = child.name.toLowerCase();
|
|
|
|
|
+ if (patterns.some((p) => lower.includes(p.toLowerCase()))) {
|
|
|
|
|
+ found = child;
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ return found;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 核心动画处理函数
|
|
* 核心动画处理函数
|
|
|
* @param doors 门设备数据数组
|
|
* @param doors 门设备数据数组
|
|
@@ -51,34 +99,28 @@ export async function modalAnimate(modal: any, modalMonitorData: Ref<any, any>)
|
|
|
const handleGateAnimate = (doors: any[]) => {
|
|
const handleGateAnimate = (doors: any[]) => {
|
|
|
if (!doors || doors.length === 0) return;
|
|
if (!doors || doors.length === 0) return;
|
|
|
|
|
|
|
|
- doors.forEach((gateData: any) => {
|
|
|
|
|
- // 查找防火门对象 (men_1, men_2, ...)
|
|
|
|
|
- const fireDoorMesh = fireDoorModal.getObjectByName(`men_1`); // fallback 测试用
|
|
|
|
|
-
|
|
|
|
|
- // 查找皮带密闭对象 (gaiban_1, gaiban_2, ...)
|
|
|
|
|
- const beltCoverMesh = fireDoorModal.getObjectByName(`gaiban`); // fallback 测试用
|
|
|
|
|
|
|
+ doors.forEach((gateData: any, index: number) => {
|
|
|
|
|
+ // 根据门的索引查找对应的 3D 模型对象 (men_1, men_2, ...)
|
|
|
|
|
+ const doorSuffix = index + 1;
|
|
|
|
|
+ const fireDoorMesh = findMesh(doorContainer, [`men_${doorSuffix}`, `men_1`, 'men']);
|
|
|
|
|
+ const beltCoverMesh = findMesh(doorContainer, [`gaiban_${doorSuffix}`, 'gaiban', 'gaiban_1']);
|
|
|
|
|
|
|
|
// 获取状态值
|
|
// 获取状态值
|
|
|
- // 注意:数据结构需与 2D 视图一致,通常是 readData.frontGateOpen.value
|
|
|
|
|
const frontGateOpenVal = gateData['readData']?.['frontGateOpen']?.value;
|
|
const frontGateOpenVal = gateData['readData']?.['frontGateOpen']?.value;
|
|
|
const mbOpenVal = gateData['readData']?.['MBOpen']?.value;
|
|
const mbOpenVal = gateData['readData']?.['MBOpen']?.value;
|
|
|
|
|
|
|
|
- // 判断状态 (1 或 '1' 为打开)
|
|
|
|
|
- const isFireDoorOpen = frontGateOpenVal == 1;
|
|
|
|
|
- const isBeltCoverOpen = mbOpenVal == 1;
|
|
|
|
|
|
|
+ // 判断状态 (与 2D 动画保持一致:'1' 为打开)
|
|
|
|
|
+ const isFireDoorOpen = frontGateOpenVal == '1' || frontGateOpenVal == 1;
|
|
|
|
|
+ const isBeltCoverOpen = mbOpenVal == '1' || mbOpenVal == 1;
|
|
|
|
|
|
|
|
// 执行防火门动画
|
|
// 执行防火门动画
|
|
|
if (fireDoorMesh) {
|
|
if (fireDoorMesh) {
|
|
|
animateFireDoor(fireDoorMesh, isFireDoorOpen);
|
|
animateFireDoor(fireDoorMesh, isFireDoorOpen);
|
|
|
- } else {
|
|
|
|
|
- // console.warn(`Fire door mesh not found for alias: ${posAlias}`);
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 执行皮带密闭动画
|
|
// 执行皮带密闭动画
|
|
|
if (beltCoverMesh) {
|
|
if (beltCoverMesh) {
|
|
|
animateBeltCover(beltCoverMesh, isBeltCoverOpen);
|
|
animateBeltCover(beltCoverMesh, isBeltCoverOpen);
|
|
|
- } else {
|
|
|
|
|
- // console.warn(`Belt cover mesh not found for alias: ${posAlias}`);
|
|
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
};
|
|
};
|
|
@@ -91,14 +133,13 @@ export async function modalAnimate(modal: any, modalMonitorData: Ref<any, any>)
|
|
|
const animateFireDoor = (mesh: THREE.Object3D, isOpen: boolean) => {
|
|
const animateFireDoor = (mesh: THREE.Object3D, isOpen: boolean) => {
|
|
|
const targetRotationZ = isOpen ? THREE.MathUtils.degToRad(85) : 0;
|
|
const targetRotationZ = isOpen ? THREE.MathUtils.degToRad(85) : 0;
|
|
|
|
|
|
|
|
- // 使用 GSAP 进行平滑过渡
|
|
|
|
|
gsap.to(mesh.rotation, {
|
|
gsap.to(mesh.rotation, {
|
|
|
x: 0,
|
|
x: 0,
|
|
|
y: 0,
|
|
y: 0,
|
|
|
z: targetRotationZ,
|
|
z: targetRotationZ,
|
|
|
- duration: 1.5, // 动画时长,可根据需要调整
|
|
|
|
|
|
|
+ duration: 1.5,
|
|
|
ease: 'power2.inOut',
|
|
ease: 'power2.inOut',
|
|
|
- overwrite: true, // 覆盖之前的动画
|
|
|
|
|
|
|
+ overwrite: true,
|
|
|
});
|
|
});
|
|
|
};
|
|
};
|
|
|
|
|
|
|
@@ -110,17 +151,19 @@ export async function modalAnimate(modal: any, modalMonitorData: Ref<any, any>)
|
|
|
const animateBeltCover = (mesh: THREE.Object3D, isOpen: boolean) => {
|
|
const animateBeltCover = (mesh: THREE.Object3D, isOpen: boolean) => {
|
|
|
const targetRotationY = isOpen ? THREE.MathUtils.degToRad(-70) : 0;
|
|
const targetRotationY = isOpen ? THREE.MathUtils.degToRad(-70) : 0;
|
|
|
|
|
|
|
|
- // 使用 GSAP 进行平滑过渡
|
|
|
|
|
gsap.to(mesh.rotation, {
|
|
gsap.to(mesh.rotation, {
|
|
|
x: 0,
|
|
x: 0,
|
|
|
y: targetRotationY,
|
|
y: targetRotationY,
|
|
|
z: 0,
|
|
z: 0,
|
|
|
- duration: 1.5, // 动画时长
|
|
|
|
|
|
|
+ duration: 1.5,
|
|
|
ease: 'power2.inOut',
|
|
ease: 'power2.inOut',
|
|
|
overwrite: true,
|
|
overwrite: true,
|
|
|
});
|
|
});
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+ // 将处理函数挂载到模块级变量,供外部组件(如 2D 面板)调用
|
|
|
|
|
+ currentHandleGateAnimate = handleGateAnimate;
|
|
|
|
|
+
|
|
|
// 监听数据变化,驱动动画
|
|
// 监听数据变化,驱动动画
|
|
|
watch(
|
|
watch(
|
|
|
modalMonitorData,
|
|
modalMonitorData,
|
|
@@ -142,7 +185,7 @@ export async function modalAnimate(modal: any, modalMonitorData: Ref<any, any>)
|
|
|
modal.initCSS3Renderer('#css3dContainer');
|
|
modal.initCSS3Renderer('#css3dContainer');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 暴露方法供外部调用(可选)
|
|
|
|
|
|
|
+ // 暴露方法供外部调用
|
|
|
return {
|
|
return {
|
|
|
gateList,
|
|
gateList,
|
|
|
handleGateAnimate,
|
|
handleGateAnimate,
|
|
@@ -152,7 +195,6 @@ export async function modalAnimate(modal: any, modalMonitorData: Ref<any, any>)
|
|
|
function resetModalParam(modal: any) {
|
|
function resetModalParam(modal: any) {
|
|
|
if (!modal.orbitControls) return;
|
|
if (!modal.orbitControls) return;
|
|
|
|
|
|
|
|
- // 设置合适的控制器参数
|
|
|
|
|
modal.orbitControls.rotateSpeed = 0.5;
|
|
modal.orbitControls.rotateSpeed = 0.5;
|
|
|
modal.orbitControls.zoomSpeed = 0.6;
|
|
modal.orbitControls.zoomSpeed = 0.6;
|
|
|
modal.orbitControls.panSpeed = 0.5;
|
|
modal.orbitControls.panSpeed = 0.5;
|
|
@@ -163,7 +205,7 @@ function resetModalParam(modal: any) {
|
|
|
// 清理函数
|
|
// 清理函数
|
|
|
export function destroy(modal: any) {
|
|
export function destroy(modal: any) {
|
|
|
gateList.value = [];
|
|
gateList.value = [];
|
|
|
- // 如果有其他需要清理的资源,在这里处理
|
|
|
|
|
|
|
+ currentHandleGateAnimate = null;
|
|
|
if (modal.canvasContainer) {
|
|
if (modal.canvasContainer) {
|
|
|
// 移除事件监听器等
|
|
// 移除事件监听器等
|
|
|
}
|
|
}
|