|
|
@@ -0,0 +1,170 @@
|
|
|
+import * as THREE from 'three';
|
|
|
+import gsap from 'gsap';
|
|
|
+import { ref, watch, Ref, nextTick } from 'vue';
|
|
|
+
|
|
|
+// 用于存储当前的门状态数据,方便外部调试或扩展
|
|
|
+const gateList = ref<any[]>([]);
|
|
|
+
|
|
|
+/**
|
|
|
+ * 初始化防火门 3D 动画
|
|
|
+ * @param modal Three.js 模态框实例
|
|
|
+ * @param modalMonitorData 响应式数据对象,包含 devMonitorMap.door
|
|
|
+ */
|
|
|
+export async function modalAnimate(modal: any, modalMonitorData: Ref<any, any>) {
|
|
|
+ await nextTick();
|
|
|
+
|
|
|
+ if (!modal || !modal.scene) {
|
|
|
+ console.error('模型加载失败');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取主模型对象 (假设模型名称结构,根据实际模型调整)
|
|
|
+ // 注意:请确保模型加载完成后调用此函数,或者在 modal.scene 中存在这些对象
|
|
|
+ const fireDoorModal = modal.scene.getObjectByName('pidai')?.getObjectByName('PiDaiHang_1');
|
|
|
+ if (!fireDoorModal) return;
|
|
|
+
|
|
|
+ resetModalParam(modal);
|
|
|
+
|
|
|
+ // 初始化时,强制找到所有门并归零,确保“关闭”状态是物理上的 0,0,0
|
|
|
+ const initFireDoor = fireDoorModal.getObjectByName('men_1');
|
|
|
+ const initBeltCover = fireDoorModal.getObjectByName('gaiban');
|
|
|
+
|
|
|
+ if (initFireDoor) {
|
|
|
+ initFireDoor.rotation.set(0, 0, 0); // 强制归零
|
|
|
+ }
|
|
|
+ if (initBeltCover) {
|
|
|
+ initBeltCover.rotation.set(0, 0, 0); // 强制归零
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!fireDoorModal) {
|
|
|
+ console.warn('模型元素未找到');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 重置控制器参数,保持视角稳定
|
|
|
+ resetModalParam(modal);
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 核心动画处理函数
|
|
|
+ * @param doors 门设备数据数组
|
|
|
+ */
|
|
|
+ const handleGateAnimate = (doors: any[]) => {
|
|
|
+ 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 测试用
|
|
|
+
|
|
|
+ // 获取状态值
|
|
|
+ // 注意:数据结构需与 2D 视图一致,通常是 readData.frontGateOpen.value
|
|
|
+ const frontGateOpenVal = gateData['readData']?.['frontGateOpen']?.value;
|
|
|
+ const mbOpenVal = gateData['readData']?.['MBOpen']?.value;
|
|
|
+
|
|
|
+ // 判断状态 (1 或 '1' 为打开)
|
|
|
+ const isFireDoorOpen = frontGateOpenVal == 1;
|
|
|
+ const isBeltCoverOpen = mbOpenVal == 1;
|
|
|
+
|
|
|
+ // 执行防火门动画
|
|
|
+ if (fireDoorMesh) {
|
|
|
+ animateFireDoor(fireDoorMesh, isFireDoorOpen);
|
|
|
+ } else {
|
|
|
+ // console.warn(`Fire door mesh not found for alias: ${posAlias}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 执行皮带密闭动画
|
|
|
+ if (beltCoverMesh) {
|
|
|
+ animateBeltCover(beltCoverMesh, isBeltCoverOpen);
|
|
|
+ } else {
|
|
|
+ // console.warn(`Belt cover mesh not found for alias: ${posAlias}`);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 防火门动画
|
|
|
+ * 关闭: (0, 0, 0)
|
|
|
+ * 开启: (0, 0, 85度) -> 绕 Z 轴旋转
|
|
|
+ */
|
|
|
+ const animateFireDoor = (mesh: THREE.Object3D, isOpen: boolean) => {
|
|
|
+ const targetRotationZ = isOpen ? THREE.MathUtils.degToRad(85) : 0;
|
|
|
+
|
|
|
+ // 使用 GSAP 进行平滑过渡
|
|
|
+ gsap.to(mesh.rotation, {
|
|
|
+ x: 0,
|
|
|
+ y: 0,
|
|
|
+ z: targetRotationZ,
|
|
|
+ duration: 1.5, // 动画时长,可根据需要调整
|
|
|
+ ease: 'power2.inOut',
|
|
|
+ overwrite: true, // 覆盖之前的动画
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 皮带密闭动画
|
|
|
+ * 关闭: (0, 0, 0)
|
|
|
+ * 开启: (0, -70度, 0) -> 绕 Y 轴旋转
|
|
|
+ */
|
|
|
+ const animateBeltCover = (mesh: THREE.Object3D, isOpen: boolean) => {
|
|
|
+ const targetRotationY = isOpen ? THREE.MathUtils.degToRad(-70) : 0;
|
|
|
+
|
|
|
+ // 使用 GSAP 进行平滑过渡
|
|
|
+ gsap.to(mesh.rotation, {
|
|
|
+ x: 0,
|
|
|
+ y: targetRotationY,
|
|
|
+ z: 0,
|
|
|
+ duration: 1.5, // 动画时长
|
|
|
+ ease: 'power2.inOut',
|
|
|
+ overwrite: true,
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ // 监听数据变化,驱动动画
|
|
|
+ watch(
|
|
|
+ modalMonitorData,
|
|
|
+ (newData) => {
|
|
|
+ if (!newData) return;
|
|
|
+
|
|
|
+ // 获取门数据,优先使用 devMonitorMap.door (与 2D 视图一致)
|
|
|
+ const doors = newData.devMonitorMap?.door || [];
|
|
|
+ gateList.value = doors;
|
|
|
+
|
|
|
+ // 执行动画
|
|
|
+ handleGateAnimate(doors);
|
|
|
+ },
|
|
|
+ { immediate: true, deep: true }
|
|
|
+ );
|
|
|
+
|
|
|
+ // 初始化 CSS3D 容器 (如果需要显示 HTML 标签,否则可移除)
|
|
|
+ if (modal.initCSS3Renderer) {
|
|
|
+ modal.initCSS3Renderer('#css3dContainer');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 暴露方法供外部调用(可选)
|
|
|
+ return {
|
|
|
+ gateList,
|
|
|
+ handleGateAnimate,
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+function resetModalParam(modal: any) {
|
|
|
+ if (!modal.orbitControls) return;
|
|
|
+
|
|
|
+ // 设置合适的控制器参数
|
|
|
+ modal.orbitControls.rotateSpeed = 0.5;
|
|
|
+ modal.orbitControls.zoomSpeed = 0.6;
|
|
|
+ modal.orbitControls.panSpeed = 0.5;
|
|
|
+ modal.orbitControls.enableDamping = true;
|
|
|
+ modal.orbitControls.dampingFactor = 0.05;
|
|
|
+}
|
|
|
+
|
|
|
+// 清理函数
|
|
|
+export function destroy(modal: any) {
|
|
|
+ gateList.value = [];
|
|
|
+ // 如果有其他需要清理的资源,在这里处理
|
|
|
+ if (modal.canvasContainer) {
|
|
|
+ // 移除事件监听器等
|
|
|
+ }
|
|
|
+}
|