#ifndef OBJLOADER_H #define OBJLOADER_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** * @brief 顶点索引结构 * 用于存储OBJ文件中面的顶点索引,关联位置、纹理坐标和法向量 */ struct VertexIndex { /** * @brief 位置索引 * 对应m_positions中的顶点位置数据索引 */ int position; /** * @brief 纹理坐标索引 * 对应m_texCoords中的纹理坐标数据索引 */ int texCoord; /** * @brief 法向量索引 * 对应m_normals中的法向量数据索引 */ int normal; /** * @brief 默认构造函数 * 初始化索引为-1(表示未使用) */ VertexIndex() : position(-1), texCoord(-1), normal(-1) {} /** * @brief 带参构造函数 * @param p 位置索引 * @param t 纹理坐标索引 * @param n 法向量索引 */ VertexIndex(int p, int t, int n) : position(p), texCoord(t), normal(n) {} }; /** * @brief 材质结构 * 存储从MTL文件解析的材质属性 */ struct Material { /** * @brief 材质名称 * 用于标识材质的唯一名称 */ QString name; /** * @brief 环境光颜色 * 对应MTL文件中的Ka属性,物体反射环境光的颜色 */ QColor ambient; /** * @brief 漫反射颜色 * 对应MTL文件中的Kd属性,物体漫反射的颜色 */ QColor diffuse; /** * @brief 高光颜色 * 对应MTL文件中的Ks属性,物体高光反射的颜色 */ QColor specular; /** * @brief 高光光泽度 * 对应MTL文件中的Ns属性,控制高光区域的大小,值越大高光越集中 */ float shininess; /** * @brief 漫反射纹理贴图路径 * 对应MTL文件中的map_Kd属性,漫反射纹理图片的路径 */ QString diffuseMap; /** * @brief 透明度 * 对应MTL文件中的d参数,值范围0.0-1.0,1.0表示完全不透明 */ float opacity; }; /** * @brief 面结构 * 存储OBJ文件中的一个面,包含组成面的顶点索引和使用的材质名称 */ struct Face { /** * @brief 组成面的顶点索引列表 * 存储构成当前面的所有顶点的索引信息(位置、纹理、法向量) */ QVector vertices; /** * @brief 面使用的材质名称 * 指向该面所应用的材质在m_materials中的名称 */ QString materialName; }; /** * @brief 器件信息结构 * 存储3D场景中一个器件(模型)的所有属性和关联对象 */ struct Device { /** * @brief 器件名称 * 用于唯一标识一个器件的名称 */ QString name; /** * @brief 器件实体(外层) * 器件的外层实体,主要用于控制器件在世界坐标系中的整体位置 */ Qt3DCore::QEntity* entity; /** * @brief 模型子实体(内层) * 器件的内层实体,嵌套在外层实体中,用于控制模型的旋转和缩放 */ Qt3DCore::QEntity* modelEntity; /** * @brief 外层变换组件 * 关联到外层实体,用于控制器件在世界坐标系中的位置变换 */ Qt3DCore::QTransform* transform; /** * @brief 内层变换组件 * 关联到内层实体,用于控制模型的旋转、缩放以及预中心化处理 */ Qt3DCore::QTransform* modelTransform; /** * @brief 世界坐标系中的位置 * 记录器件在世界坐标系中的当前位置 */ QVector3D worldPosition; /** * @brief 初始位置 * 记录器件加载时的初始位置,用于重置功能 */ QVector3D originalPosition; /** * @brief 当前旋转角度 * 记录器件当前绕X、Y、Z轴的旋转角度,单位为度 */ QVector3D currentRotation; /** * @brief 模型几何中心 * 模型在自身局部坐标系中的几何中心坐标 */ QVector3D modelCenter; /** * @brief 缩放因子 * 模型的缩放比例,默认值为1.0(原始大小) */ float scale; /** * @brief 初始化标志 * 标识器件是否已成功加载并初始化,true表示初始化完成 */ bool isInitialized; // 父子模型关联字段 /** * @brief 父模型名称 * 记录当前器件的父级器件名称,为空表示无父级 */ QString parentDeviceName; /** * @brief 相对于父模型的位置偏移 * 固定的位置偏移量,用于计算子器件相对于父器件的位置 */ QVector3D offsetToParent; /** * @brief 初始世界位置 * 记录器件初始状态下在世界坐标系中的位置,用于复位操作 */ QVector3D initialWorldPosition; /** * @brief 初始外层变换矩阵 * 记录外层实体初始状态下的变换矩阵,用于复位操作 */ QMatrix4x4 initialTransformMatrix; /** * @brief 初始内层变换矩阵 * 记录内层实体初始状态下的变换矩阵,用于复位操作 */ QMatrix4x4 initialModelTransformMatrix; /** * @brief 初始旋转角度 * 记录器件相对于父设备的初始旋转角度 */ QVector3D initialRotation; /** * @brief 局部坐标系下的旋转中心 * 器件在自身局部坐标系中的旋转中心,默认值为{0,0,0} */ QVector3D localPivot; /** * @brief 自定义旋转点 * 在世界坐标系中指定的旋转中心点坐标 */ QVector3D rotationCenter; /** * @brief 是否启用自定义旋转点 * 标识是否使用自定义的旋转点进行旋转,默认值为false */ bool useCustomRotationCenter; }; /** * @brief OBJ模型加载器类 * 负责3D场景初始化、OBJ/MTL文件加载、器件管理(位置/旋转/缩放控制)等功能 */ class ObjLoader : public QObject { Q_OBJECT public: /** * @brief 构造函数 * @param parent 父对象 */ explicit ObjLoader(QObject *parent = nullptr); /** * @brief 析构函数 * 释放所有3D资源和动态分配的对象 */ ~ObjLoader(); /** * @brief ObjLoader::setEnDebug 是否启用qDebug打印信息 * @param En True=启用,False=禁用; */ void setEnDebug(bool En); /** * @brief 初始化3D场景 * @param containerWidget 用于显示3D场景的窗口容器 */ void init3DScene(QWidget *containerWidget); /** * @brief 初始化3D场景 * 创建3D窗口、根实体、相机、光源和控制器,支持透明背景、场景占满容器、尺寸同步 * @param containerWidget 用于显示3D场景的窗口容器(如QFrame) * @param enableTransparent 是否启用场景背景透明(true=透明,false=纯色背景) * @return 初始化成功返回true,失败返回false(可通过lastError()获取错误信息) */ bool init3DScene(QWidget *containerWidget, bool enableTransparent); void loadModelAsync(const QString &deviceName, const QString &filePath, const QVector3D &position); /** * @brief 加载器件(独立模型) * 创建双层实体结构(外层控制位置,内层控制旋转/缩放),解析并应用MTL材质 * 加载完成后自动调整相机位置,确保模型可见 * @param deviceName 器件名称 * @param filePath OBJ文件路径 * @param position 初始位置 * @return 加载成功返回true */ bool loadModel(const QString &deviceName, const QString &filePath, const QVector3D &position = QVector3D(0,0,0)); /** * @brief 加载器件(独立模型,无父级) * @param deviceName 器件名称 * @param filePath OBJ文件路径 * @param position 初始位置(世界坐标) * @return 加载成功返回true,失败返回false */ bool loadDevice(const QString &deviceName, const QString &filePath, const QVector3D &position = QVector3D(0,0,0)); /** * @brief 加载子器件(挂载到父器件下) * 确保子器件与父器件的初始相对位置正确,并继承父器件的变换 * @param childDeviceName 子器件名称 * @param filePath OBJ文件路径 * @param parentDeviceName 父器件名称 * @param offsetDir 相对父器件的偏移方向 * @param diffuse 材质 * @return 加载成功返回true */ bool loadChildDevice( const QString &childDeviceName, const QString &filePath, const QString &parentDeviceName, const QVector3D &offsetDir = QVector3D(0, 0, 0), const QColor &diffuse = QColor(255, 255, 255,30) ); /** * @brief 从OBJ文件计算模型的包围球半径 * 解析OBJ文件中的顶点数据,计算模型几何中心到最远顶点的距离,作为模型的包围球半径 * 该半径用于确定相机与模型的合适距离,确保模型能完整显示在视野中 * @param filePath OBJ模型文件的路径 * @return 模型包围球半径(单位与模型顶点坐标一致),若文件解析失败则返回1.0f默认值 */ float calculateModelRadiusFromFile(const QString &filePath); /** * @brief 获取器件指针 * @param deviceName 器件名称 * @return 成功返回Device指针,失败返回nullptr */ Device* getDevice(const QString &deviceName); /** * @brief 旋转器件(绕自身中心) * @param deviceName 器件名称 * @param xAngle X轴旋转角度(度) * @param yAngle Y轴旋转角度(度) * @param zAngle Z轴旋转角度(度) */ void rotateDevice(const QString &deviceName, float xAngle, float yAngle, float zAngle); /** * @brief 移动器件(相对偏移) * @param deviceName 器件名称 * @param offset 偏移量(世界坐标) */ void moveDevice(const QString &deviceName, const QVector3D &offset); /** * @brief 设置器件位置(绝对位置) * @param deviceName 器件名称 * @param targetPos 目标位置(世界坐标) */ void setDevicePos(const QString &deviceName, const QVector3D &targetPos); /** * @brief 缩放器件 * @param deviceName 器件名称 * @param scaleFactor 缩放因子(>0) */ void scaleDevice(const QString &deviceName, float scaleFactor); /** * @brief 重置器件到初始状态 * @param deviceName 器件名称 */ void resetDevice(const QString &deviceName); /** * @brief 从数据映射更新多个器件的属性 * @param deviceData 器件数据映射(键:器件名称,值:属性键值对) */ void updateDevicesFromData(const QMap> &deviceData); /** * @brief 重置相机到默认位置 */ void resetCamera(); /** * @brief 设置相机控制速度 * @param lookSpeed 旋转速度 * @param linearSpeed 移动速度 */ void setCameraSpeed(float lookSpeed, float linearSpeed); /** * @brief 添加坐标轴 gizmo(辅助3D定位) * @param parent 父实体(默认使用根实体) * @param length 坐标轴长度 */ void addAxisGizmo(Qt3DCore::QEntity *parent = nullptr, float length = 5.0f); /** * @brief 调整子器件相对父器件的偏移 * @param childDeviceName 子器件名称 * @param newOffset 新的偏移量 */ void adjustChildDeviceOffset(const QString &childDeviceName, const QVector3D &newOffset); /** * @brief 设置父器件位置(同时影响子器件) * @param deviceName 父器件名称 * @param position 目标位置(世界坐标) */ void setParentDevicePosition(const QString &deviceName, const QVector3D &position); /** * @brief 打印器件的世界坐标信息 * @param deviceName 器件名称 */ void printDeviceWorldPos(const QString &deviceName); /** * @brief 获取相机设备 * @return 当前相机对象指针 */ Qt3DRender::QCamera* getCamera(); /** * @brief 解析OBJ文件计算模型中心 * @param filePath OBJ文件路径 * @return 模型中心坐标(局部坐标系) */ QVector3D parseObjCenter(const QString &filePath); /** * @brief 计算顶点集合的包围盒中心 * @param vertices 顶点列表 * @return 包围盒中心坐标 */ QVector3D calculateBoundingBoxCenter(const QVector& vertices); /** * @brief 摇臂围绕自身局部原点旋转到绝对Z轴角度 * @param deviceName 摇臂名称 * @param targetZAngle 目标角度(度) */ void rotateArmToAbsoluteZAngle(const QString &deviceName, float targetZAngle); /** * @brief 摇臂围绕自身局部原点绕X轴旋转,带动子设备 * 首次调用记录初始状态,0°时复位,其他角度计算旋转矩阵并应用 * @param deviceName 摇臂名称 * @param targetXAngle 目标X轴角度(度) */ void rotateArmToAbsoluteXAngle(const QString &deviceName, float targetXAngle); /** * @brief 摇臂围绕自身局部原点绕Y轴旋转,带动子设备 * 首次调用记录初始状态,0°时复位,其他角度计算旋转矩阵并应用 * @param deviceName 摇臂名称 * @param targetYAngle 目标Y轴角度(度) */ void rotateArmToAbsoluteYAngle(const QString &deviceName, float targetYAngle); /** * @brief 将摇臂的局部坐标系原点移动到世界坐标目标点 * @param deviceName 摇臂名称 * @param targetWorldPos 目标世界坐标 */ void moveArmLocalOriginTo(const QString &deviceName, const QVector3D &targetWorldPos); /** * @brief 从MTL文件加载材质属性并应用到Phong材质 * @param material 待应用材质属性的QPhongMaterial对象指针 * @param filePath MTL材质文件的绝对路径 * @details 该函数会解析MTL文件中的材质属性(如环境光、漫反射、高光、光泽度等), * 并将这些属性设置到传入的Phong材质对象中。如果MTL文件不存在或解析失败, * 材质将保持默认属性。 */ void loadMaterialFromMtl(Qt3DExtras::QPhongMaterial *material, const QString &filePath); /** * @brief 异步加载3D模型并创建器件实体 * @param deviceName 器件唯一标识名称 * @param filePath OBJ模型文件的绝对路径 * @param position 器件在世界坐标系中的初始位置 * @param modelCenter 模型几何中心(由预处理步骤计算得到) * @param modelRadius 模型包围球半径(由预处理步骤计算得到) * @return 加载成功返回true,失败返回false * @details 该函数在主线程中创建器件的实体结构(双层变换),并应用预处理得到的模型中心 * 和半径进行几何校正。加载过程包括创建实体、设置变换、加载网格和应用材质, * 所有3D对象操作均在主线程执行以确保线程安全。 */ bool loadModelAsync(const QString &deviceName, const QString &filePath, const QVector3D &position, QVector3D &modelCenter, float modelRadius); private slots: /** * @brief 批量预处理多个模型文件(在工作线程执行) * @param deviceNames 待预处理的器件名称列表 * @param basePath 模型文件所在的基础路径 * @details 该函数遍历器件名称列表,对每个器件对应的OBJ文件进行预处理,计算模型的 * 几何中心和包围球半径。预处理完成后会通过preprocessDone信号发送结果。 * 此函数需在工作线程中调用,避免阻塞UI线程。 */ void preprocessDevices(const QStringList& deviceNames, const QString& basePath); /** * @brief 预处理单个模型文件(在工作线程执行) * @param deviceName 待预处理的器件名称 * @param basePath 模型文件所在的基础路径 * @details 该函数根据器件名称和基础路径构建OBJ文件路径,解析文件计算模型的几何中心 * 和包围球半径。预处理完成后通过preprocessDone信号发送结果。此函数为原子操作, * 可被批量处理函数调用或单独调用。 */ void preprocessSingleDevice(const QString& deviceName, const QString& basePath); /** * @brief 相机状态变化时的回调函数 * @details 当相机的位置、看向的中心点或视野角度发生变化时,该函数会被触发。 * 函数会收集当前相机的关键信息,并通过cameraInfoUpdated信号发送出去。 * 主要用于实时反馈相机状态,辅助调试和交互。 */ void onCameraChanged(); signals: /** * @brief 模型预处理完成信号 * @param deviceName 预处理完成的器件名称 * @param center 模型的几何中心坐标 * @param radius 模型的包围球半径 * @details 当单个模型的预处理(计算几何中心和半径)完成后,会发送此信号。 * 主线程可通过连接此信号获取预处理结果,用于后续的模型加载和定位。 */ void preprocessDone(const QString& deviceName, QVector3D center, float radius); /** * @brief 相机信息更新信号 * @param position 相机当前在世界坐标系中的位置 * @param viewCenter 相机当前看向的中心点坐标 * @param fov 相机的视野角度(单位:度) * @details 当相机的位置、看向的中心点或视野角度发生变化时(通常由用户交互触发), * 会发送此信号。可用于实时显示相机状态或同步其他依赖相机信息的组件。 */ void cameraInfoUpdated(const QVector3D& position, const QVector3D& viewCenter, float fov); private: /** * @brief 计算整体模型的中心 */ void calculateModelCenter(); /** * @brief 获取指定父器件的所有子器件 * @param parentDevice 父器件指针 * @return 子器件列表 */ QList getAllChildren(Device *parentDevice); /** * @brief 计算模型的包围球半径 * @return 半径值 */ float calculateModelRadius(); /** * @brief 解析MTL材质文件 * @param filePath MTL文件路径 * @return 解析成功返回true,失败返回false */ bool parseMtlFile(const QString &filePath); /** * @brief 字符串分割工具函数 * @param s 待分割字符串 * @param sep 分隔符(默认空格) * @return 分割后的字符串列表 */ static QVector splitStr(const QString &s, const QString &sep = " "); /** * @brief 创建场景光源 */ void createSceneLights(); /** * @brief 更新整体模型(重建实体和变换) */ void updateWholeModel(); private: /** * @brief m_3dView 3D渲染窗口,负责3D场景的渲染输出 */ Qt3DExtras::Qt3DWindow *m_3dView; /** * @brief m_viewContainer 3D窗口容器,用于将3D渲染窗口嵌入到Qt Widget界面中 */ QWidget *m_viewContainer; /** * @brief m_rootEntity 场景根实体,是所有3D实体的父节点,构成场景的基础结构 */ Qt3DCore::QEntity *m_rootEntity; /** * @brief m_mainCamera 主相机(备用),用于观察3D场景的视角设备 */ Qt3DRender::QCamera *m_mainCamera; /** * @brief m_cameraCtrl 相机控制器,提供对相机的交互控制(如旋转、平移等) */ Qt3DExtras::QOrbitCameraController *m_cameraCtrl; /** * @brief light 方向光源,为3D场景提供照明,影响物体的可见性和渲染效果 */ Qt3DRender::QDirectionalLight *light; /** * @brief m_wholeModelEntity 整体模型实体,包含整体模型的所有组件 */ Qt3DCore::QEntity *m_wholeModelEntity; /** * @brief m_wholeModelMesh 整体模型网格,存储整体模型的几何顶点数据 */ Qt3DRender::QMesh *m_wholeModelMesh; /** * @brief m_wholeModelTrans 整体模型变换,控制整体模型的位置、旋转和缩放 */ Qt3DCore::QTransform *m_wholeModelTrans; /** * @brief m_deviceMap 器件映射表,通过器件名称快速查找对应的Device对象 */ QMap m_deviceMap; /** * @brief m_animTimer 动画定时器,用于驱动3D场景中的动画更新 */ QTimer *m_animTimer; /** * @brief m_positions 顶点位置列表,存储模型所有顶点的3D坐标 */ QVector m_positions; /** * @brief m_texCoords 纹理坐标列表,存储顶点对应的2D纹理映射坐标 */ QVector m_texCoords; /** * @brief m_normals 法向量列表,存储顶点的法向量信息,用于光照计算 */ QVector m_normals; /** * @brief m_faces 面列表,存储模型的面信息,每个面由多个顶点索引组成 */ QVector m_faces; /** * @brief m_materials 材质映射表,通过材质名称快速查找对应的Material对象 */ QMap m_materials; /** * @brief m_tempVertices 临时顶点存储,用于在计算模型中心等操作时临时存放顶点数据 */ QVector m_tempVertices; /** * @brief m_wholeModelCenter 整体模型中心,存储整体模型的几何中心坐标 */ QVector3D m_wholeModelCenter; /** * @brief m_wholeModelRadius 整体模型包围球半径,用于确定模型的大致尺寸范围 */ float m_wholeModelRadius; /** * @brief m_currentMaterial 当前使用的材质名称,记录当前正在应用的材质 */ QString m_currentMaterial; /** * @brief m_lastError 最后一次错误信息,存储最近发生的错误描述 */ QString m_lastError; /** * @brief m_cameraEntity 主相机实体,场景中实际使用的主相机对象 */ Qt3DRender::QCamera *m_cameraEntity; /** * @brief EnDebug 是否激活打印qDebug调试信息 */ bool EnDebug = false; QMutex m_mutex; // 线程安全锁 QSet m_loadingDevices; // 跟踪加载中的器件,避免重复加载 }; #endif // OBJLOADER_H