| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258 |
- import * as THREE from 'three';
- import gsap from 'gsap';
- import { ref, watch, Ref, nextTick } from 'vue';
- // 用于存储当前的门状态数据,方便外部调试或扩展
- const gateList = ref<any[]>([]);
- // 模块级变量:存储当前的 handleGateAnimate,供外部组件直接调用 3D 动画
- let currentHandleGateAnimate: ((doors: any[]) => void) | null = null;
- /** 供外部(如 2D 组件)调用的 3D 动画触发函数 */
- export function trigger3DAnimation(doors: any[]) {
- if (currentHandleGateAnimate) {
- currentHandleGateAnimate(doors);
- } else {
- console.warn('[3D动画] currentHandleGateAnimate 未初始化,请确保 modalAnimate 已调用');
- }
- }
- /**
- * 初始化防火门 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) {
- return;
- }
- // 尝试多种方式定位防火门模型的容器对象
- let fireDoorModal: THREE.Object3D | undefined;
- // 方式1:通过已知的层级路径 pidai → PiDaiHang_1
- const pidaiObj = modal.scene.getObjectByName('pidai');
- if (pidaiObj) {
- fireDoorModal = pidaiObj.getObjectByName('PiDaiHang_1');
- }
- // 方式2:直接在场景中搜索 PiDaiHang_1
- if (!fireDoorModal) {
- fireDoorModal = modal.scene.getObjectByName('PiDaiHang_1');
- }
- // 方式3:搜索名字中包含 PiDaiHang 的对象
- if (!fireDoorModal) {
- modal.scene.traverse((obj: THREE.Object3D) => {
- if (!fireDoorModal && obj.name.includes('PiDaiHang')) {
- fireDoorModal = obj;
- }
- });
- }
- // 方式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) {
- return;
- }
- // 类型收窄:确保后续代码中 fireDoorModal 为非 undefined
- const doorContainer: THREE.Object3D = fireDoorModal;
- 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 门设备数据数组
- */
- const handleGateAnimate = (doors: any[]) => {
- if (!doors || doors.length === 0) return;
- doors.forEach((gateData: any, index: number) => {
- // 获取状态值
- const frontGateOpenVal = gateData['readData']?.['frontGateOpen']?.value;
- const mbOpenVal = gateData['readData']?.['MBOpen']?.value;
- const isFireDoorOpen = frontGateOpenVal == '1' || frontGateOpenVal == 1;
- const isBeltCoverOpen = mbOpenVal == '1' || mbOpenVal == 1;
- const hasBelt = mbOpenVal !== undefined && mbOpenVal !== null;
- if (hasBelt) {
- // 有皮带:控制 men_1(防火门)+ gaiban(皮带盖)
- const fireDoorMesh = findMesh(doorContainer, [`men_${index + 1}`, 'men_1', 'men']);
- const beltCoverMesh = findMesh(doorContainer, [`gaiban_${index + 1}`, 'gaiban', 'gaiban_1']);
- if (fireDoorMesh) {
- animateFireDoorWithBelt(fireDoorMesh, isFireDoorOpen);
- }
- if (beltCoverMesh) {
- animateBeltCover(beltCoverMesh, isBeltCoverOpen);
- }
- } else {
- // 无皮带:控制 men_2 + men3(两扇防火门向两边开)
- const leftDoorMesh = findMesh(doorContainer, ['men_2']);
- const rightDoorMesh = findMesh(doorContainer, ['men3']);
- if (leftDoorMesh) {
- animateFireDoorLeft(leftDoorMesh, isFireDoorOpen);
- }
- if (rightDoorMesh) {
- animateFireDoorRight(rightDoorMesh, isFireDoorOpen);
- }
- }
- });
- };
- /**
- * 有皮带时的防火门动画
- * 关闭: (0, 0, 0)
- * 开启: (0, 0, 85度) -> 绕 Z 轴旋转
- */
- const animateFireDoorWithBelt = (mesh: THREE.Object3D, isOpen: boolean) => {
- const targetRotationZ = isOpen ? THREE.MathUtils.degToRad(85) : 0;
- gsap.to(mesh.rotation, {
- x: 0,
- y: 0,
- z: targetRotationZ,
- duration: 1.5,
- ease: 'power2.inOut',
- overwrite: true,
- });
- };
- /**
- * 无皮带时左侧防火门动画 (men_2)
- * 关闭: (0, 0, 0)
- * 开启: (0, 0, -90度) -> 绕 Z 轴旋转
- */
- const animateFireDoorLeft = (mesh: THREE.Object3D, isOpen: boolean) => {
- const targetRotationZ = isOpen ? THREE.MathUtils.degToRad(-90) : 0;
- gsap.to(mesh.rotation, {
- x: 0,
- y: 0,
- z: targetRotationZ,
- duration: 1.5,
- ease: 'power2.inOut',
- overwrite: true,
- });
- };
- /**
- * 无皮带时右侧防火门动画 (men_3)
- * 关闭: (0, 0, 0)
- * 开启: (0, 0, 90度) -> 绕 Z 轴旋转
- */
- const animateFireDoorRight = (mesh: THREE.Object3D, isOpen: boolean) => {
- const targetRotationZ = isOpen ? THREE.MathUtils.degToRad(90) : 0;
- 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.to(mesh.rotation, {
- x: 0,
- y: targetRotationY,
- z: 0,
- duration: 1.5,
- ease: 'power2.inOut',
- overwrite: true,
- });
- };
- // 将处理函数挂载到模块级变量,供外部组件(如 2D 面板)调用
- currentHandleGateAnimate = handleGateAnimate;
- // 监听数据变化,驱动动画
- 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 = [];
- currentHandleGateAnimate = null;
- if (modal.canvasContainer) {
- // 移除事件监听器等
- }
- }
|