Files
EJM_Display/PublicFunctions/ObjLoader.h
2025-09-30 15:36:46 +08:00

680 lines
23 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#ifndef OBJLOADER_H
#define OBJLOADER_H
#include <QObject>
#include <QVector>
#include <QVector2D>
#include <QVector3D>
#include <Qt3DCore>
#include <Qt3DRender>
#include <Qt3DExtras>
#include <QString>
#include <QMap>
#include <QColor>
#include <QWidget>
#include <QTimer>
#include <QtConcurrent/QtConcurrent>
#include <QFutureWatcher>
#include <QMutex>
#include <QTimer>
/**
* @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.01.0表示完全不透明
*/
float opacity;
};
/**
* @brief 面结构
* 存储OBJ文件中的一个面包含组成面的顶点索引和使用的材质名称
*/
struct Face {
/**
* @brief 组成面的顶点索引列表
* 存储构成当前面的所有顶点的索引信息(位置、纹理、法向量)
*/
QVector<VertexIndex> 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<QString, QMap<QString, float>> &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<QVector3D>& 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<Device*> 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<QString> 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<QString, Device> m_deviceMap;
/**
* @brief m_animTimer 动画定时器用于驱动3D场景中的动画更新
*/
QTimer *m_animTimer;
/**
* @brief m_positions 顶点位置列表存储模型所有顶点的3D坐标
*/
QVector<QVector3D> m_positions;
/**
* @brief m_texCoords 纹理坐标列表存储顶点对应的2D纹理映射坐标
*/
QVector<QVector2D> m_texCoords;
/**
* @brief m_normals 法向量列表,存储顶点的法向量信息,用于光照计算
*/
QVector<QVector3D> m_normals;
/**
* @brief m_faces 面列表,存储模型的面信息,每个面由多个顶点索引组成
*/
QVector<Face> m_faces;
/**
* @brief m_materials 材质映射表通过材质名称快速查找对应的Material对象
*/
QMap<QString, Material> m_materials;
/**
* @brief m_tempVertices 临时顶点存储,用于在计算模型中心等操作时临时存放顶点数据
*/
QVector<QVector3D> 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<QString> m_loadingDevices; // 跟踪加载中的器件,避免重复加载
};
#endif // OBJLOADER_H