Files
EJM_Display/PublicFunctions/ObjLoader.cpp
2025-09-28 17:14:34 +08:00

1639 lines
64 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.

#include "ObjLoader.h"
#include <QVBoxLayout>
#include <QFile>
#include <QTextStream>
#include <QFileInfo>
#include <QDebug>
#include <QVBoxLayout>
#include <QSurfaceFormat>
#include <QFileInfo>
#include <QtMath>
/**
* @brief 构造函数
* 初始化3D场景相关成员变量启动动画定时器
* @param parent 父对象
*/
ObjLoader::ObjLoader(QObject *parent)
: QObject(parent),
m_3dView(nullptr),
m_viewContainer(nullptr),
m_rootEntity(nullptr),
m_mainCamera(nullptr),
m_cameraCtrl(nullptr),
m_wholeModelEntity(nullptr),
m_wholeModelMesh(nullptr),
m_wholeModelTrans(nullptr),
m_wholeModelCenter(QVector3D(0, 0, 0)),
m_wholeModelRadius(0.0f),
m_currentMaterial(""),
m_lastError("")
{
m_animTimer = new QTimer(this);
m_animTimer->setInterval(30); // 约33fps的动画刷新频率
m_animTimer->start();
}
/**
* @brief 析构函数
* 释放所有动态分配的3D实体、变换组件和资源避免内存泄漏
*/
ObjLoader::~ObjLoader()
{
// 1. 先销毁所有子模型(避免父模型先销毁导致子模型悬空)
QVector<QString> childDeviceNames;
for (const auto& device : m_deviceMap) {
if (!device.parentDeviceName.isEmpty()) {
childDeviceNames.append(device.name);
}
}
for (const auto& childName : childDeviceNames) {
auto it = m_deviceMap.find(childName);
if (it != m_deviceMap.end()) {
delete it->modelTransform;
delete it->transform;
delete it->modelEntity;
delete it->entity;
m_deviceMap.erase(it);
}
}
// 2. 再销毁父模型
for (auto &device : m_deviceMap) {
delete device.modelTransform;
delete device.transform;
delete device.modelEntity;
delete device.entity;
}
m_deviceMap.clear();
// 3. 清理场景资源
delete m_wholeModelEntity;
delete m_rootEntity;
delete m_3dView;
}
/**
* @brief ObjLoader::setEnDebug 是否启用qDebug打印信息
* @param En True=启用,False=禁用;
*/
void ObjLoader::setEnDebug(bool En){
EnDebug = En;
}
/**
* @brief 获取相机设备
* @return 当前创建的相机设备指针
*/
Qt3DRender::QCamera* ObjLoader::getCamera(){
return m_cameraEntity;
}
/**
* @brief 获取器件指针
* 从器件映射表中查找指定名称的器件
* @param deviceName 器件名称
* @return 找到返回Device指针否则返回nullptr并设置错误信息
*/
Device* ObjLoader::getDevice(const QString &deviceName)
{
if (m_deviceMap.contains(deviceName)) {
return &m_deviceMap[deviceName];
}
m_lastError = "未找到器件: " + deviceName;
return nullptr;
}
/**
* @brief 设置相机控制速度
* @param lookSpeed 旋转速度
* @param linearSpeed 移动速度
*/
void ObjLoader::setCameraSpeed(float lookSpeed, float linearSpeed)
{
if (m_cameraCtrl) {
m_cameraCtrl->setLookSpeed(lookSpeed);
m_cameraCtrl->setLinearSpeed(linearSpeed);
}
}
/**
* @brief 重置相机到默认位置
* 相机位置设置为模型中心沿Z轴偏移3倍半径的位置
*/
void ObjLoader::resetCamera()
{
if (m_mainCamera && m_wholeModelRadius > 0) {
QVector3D cameraPos = m_wholeModelCenter + QVector3D(0, 0, m_wholeModelRadius * 3.0f);
m_mainCamera->setPosition(cameraPos);
m_mainCamera->setViewCenter(QVector3D(0, 0, 0));
}
}
/**
* @brief 字符串分割工具函数
* 将输入字符串按指定分隔符分割,忽略空元素
* @param s 待分割的字符串
* @param sep 分隔符(默认空格)
* @return 分割后的字符串列表
*/
QVector<QString> ObjLoader::splitStr(const QString &s, const QString &sep)
{
return s.split(sep, Qt::SkipEmptyParts).toVector();
}
/**
* @brief 初始化3D场景
* 创建3D窗口、根实体、相机、光源和控制器设置透明背景
* @param containerWidget 用于显示3D场景的窗口容器
*/
void ObjLoader::init3DScene(QWidget *containerWidget)
{
if (!containerWidget) {
m_lastError = "无效的3D容器";
return;
}
if (EnDebug) qDebug() << "3D组件初始化" << "containerWidget" << containerWidget;
m_viewContainer = containerWidget;
// if (EnDebug) qDebug() << "3D组件初始化" << "基础容器透明设置";
// // 1. 基础容器透明设置(画框透明)
// m_viewContainer->setAttribute(Qt::WA_TranslucentBackground);
// m_viewContainer->setStyleSheet("background: transparent; border: none;");
// if (EnDebug) qDebug() << "3D组件初始化" << "配置OpenGL格式必须启用Alpha通道以支持透明";
// // 2. 配置OpenGL格式必须启用Alpha通道以支持透明
// QSurfaceFormat format = QSurfaceFormat::defaultFormat();
// format.setAlphaBufferSize(8); // 启用Alpha通道透明的核心
// format.setRenderableType(QSurfaceFormat::OpenGL);
// format.setVersion(3, 3);
// format.setProfile(QSurfaceFormat::CompatibilityProfile);
// QSurfaceFormat::setDefaultFormat(format);
if (EnDebug) qDebug() << "3D组件初始化" << "创建3D窗口并设置透明背景";
// 3. 创建3D窗口并设置透明背景
m_3dView = new Qt3DExtras::Qt3DWindow();
// m_3dView->setFormat(format);
if (EnDebug) qDebug() << "3D组件初始化" << "核心3D场景背景透明使用Qt3DExtras的QForwardRenderer";
// 核心3D场景背景透明使用Qt3DExtras的QForwardRenderer
Qt3DExtras::QForwardRenderer *frameGraph = m_3dView->defaultFrameGraph();
if (frameGraph) {
frameGraph->setClearColor(QColor(7, 19, 35)); // 全透明背景
//frameGraph->setClearColor(QColor(7, 19, 255)); // 全透明背景
}
if (EnDebug) qDebug() << "3D组件初始化" << "创建窗口容器(确保透明)";
// 4. 创建窗口容器(确保透明)
QWidget *container3D = QWidget::createWindowContainer(m_3dView, m_viewContainer);
// container3D->setAttribute(Qt::WA_TranslucentBackground);
// container3D->setStyleSheet("background: transparent !important;"); // 强制透明
// container3D->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
if (EnDebug) qDebug() << "3D组件初始化" << "布局设置";
// 5. 布局设置
QVBoxLayout *layout = new QVBoxLayout(m_viewContainer);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(container3D);
m_viewContainer->setLayout(layout);
if (EnDebug) qDebug() << "3D组件初始化" << "3D场景核心组件确保模型显示";
// 6. 3D场景核心组件确保模型显示
m_rootEntity = new Qt3DCore::QEntity();
m_3dView->setRootEntity(m_rootEntity);
if (EnDebug) qDebug() << "3D组件初始化" << "相机设置(确保能看到物体)";
// 7. 相机设置(确保能看到物体)
m_cameraEntity = m_3dView->camera();
m_cameraEntity->setPosition(QVector3D(0, 0, 10)); // 相机初始位置Z轴10单位处
m_cameraEntity->setViewCenter(QVector3D(0, 0, 0)); // 看向原点
if (EnDebug) qDebug() << "3D组件初始化" << "相机控制器(可拖动旋转查看)";
// 8. 相机控制器(可拖动旋转查看)
Qt3DExtras::QOrbitCameraController *controller = new Qt3DExtras::QOrbitCameraController(m_rootEntity);
controller->setCamera(m_cameraEntity);
if (EnDebug) qDebug() << "3D组件初始化" << "光源(确保模型可见)";
// 9. 光源(确保模型可见)
Qt3DCore::QEntity *lightEntity = new Qt3DCore::QEntity(m_cameraEntity);
light = new Qt3DRender::QDirectionalLight(lightEntity);
light->setColor(Qt::white);
light->setIntensity(1.5f); // 光源强度
lightEntity->addComponent(light);
Qt3DCore::QTransform *lightTransform = new Qt3DCore::QTransform(lightEntity);
lightTransform->setTranslation(m_cameraEntity->position()); // 光源位置与相机一致
lightEntity->addComponent(lightTransform);
}
/**
* @brief 初始化3D场景
* 创建3D窗口、根实体、相机、光源和控制器支持透明背景、场景占满容器、尺寸同步
* @param containerWidget 用于显示3D场景的窗口容器如QFrame
* @param enableTransparent 是否启用场景背景透明true=透明false=纯色背景)
* @return 初始化成功返回true失败返回false可通过lastError()获取错误信息)
*/
bool ObjLoader::init3DScene(QWidget *containerWidget, bool enableTransparent)
{
// 1. 基础校验与初始化
if (!containerWidget) {
m_lastError = "3D场景初始化失败容器Widget为空";
if (EnDebug) qCritical() << m_lastError;
return false;
}
m_viewContainer = containerWidget;
const QString containerName = m_viewContainer->objectName();
if (EnDebug) qDebug() << "[3D初始化] 目标容器:" << containerName
<< ",初始尺寸:" << m_viewContainer->size();
// 2. 强制解除父容器的尺寸约束核心解决Frame默认尺寸限制
m_viewContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // 允许拉伸
m_viewContainer->setMinimumSize(0, 0); // 取消最小尺寸限制避免Frame默认固定大小
m_viewContainer->setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); // 取消最大尺寸限制
m_viewContainer->setContentsMargins(0, 0, 0, 0); // 容器自身无内边距
// 强制父容器立即刷新尺寸避免UI缓存导致的延迟
m_viewContainer->updateGeometry();
m_viewContainer->repaint();
// 3. 透明模式配置(按需启用)
if (enableTransparent) {
m_viewContainer->setAttribute(Qt::WA_TranslucentBackground, true);
m_viewContainer->setStyleSheet("QFrame{background: transparent; border: none; padding: 0px; margin: 0px;}");
} else {
m_viewContainer->setStyleSheet("QFrame{background: #071323; border: none; padding: 0px; margin: 0px;}");
}
// 4. OpenGL格式配置确保渲染兼容性
QSurfaceFormat glFormat = QSurfaceFormat::defaultFormat();
glFormat.setRenderableType(QSurfaceFormat::OpenGL);
glFormat.setVersion(3, 3);
glFormat.setProfile(QSurfaceFormat::CompatibilityProfile);
if (enableTransparent) {
glFormat.setAlphaBufferSize(8);
glFormat.setDepthBufferSize(24);
}
QSurfaceFormat::setDefaultFormat(glFormat);
// 5. 创建3D窗口与渲染容器强制占满
m_3dView = new Qt3DExtras::Qt3DWindow();
m_3dView->setFormat(glFormat);
// 配置3D场景背景
Qt3DExtras::QForwardRenderer *frameGraph = m_3dView->defaultFrameGraph();
if (!frameGraph) {
m_lastError = "3D场景初始化失败获取帧图失败";
delete m_3dView;
return false;
}
frameGraph->setClearColor(enableTransparent ? QColor(0,0,0,0) : QColor(7,19,35));
// 6. 3D容器强制拉伸+无间隙(关键!)
QWidget *container3D = QWidget::createWindowContainer(m_3dView, m_viewContainer);
container3D->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // 最高拉伸优先级
container3D->setContentsMargins(0, 0, 0, 0); // 容器无内边距
container3D->setStyleSheet("background: transparent; padding: 0px; margin: 0px;");
// 强制3D容器初始尺寸与父容器完全一致避免初始空白
container3D->resize(m_viewContainer->size());
if (EnDebug) qDebug() << "[3D初始化] 3D容器初始尺寸" << container3D->size();
// 7. 布局锁定3D容器为唯一子组件无任何间隙
// 先清除父容器原有布局避免Qt Designer残留布局冲突
QLayout *oldLayout = m_viewContainer->layout();
if (oldLayout) {
oldLayout->deleteLater(); // 删除旧布局
if (EnDebug) qDebug() << "[3D初始化] 已清除容器原有布局";
}
// 创建新布局并锁定3D容器
QVBoxLayout *sceneLayout = new QVBoxLayout(m_viewContainer);
sceneLayout->setContentsMargins(0, 0, 0, 0); // 布局无内边距
sceneLayout->setSpacing(0); // 布局无间距
sceneLayout->setSizeConstraint(QLayout::SetFixedSize); // 布局尺寸跟随父容器
sceneLayout->addWidget(container3D);
// 强制布局立即生效
sceneLayout->activate();
m_viewContainer->setLayout(sceneLayout);
// 8. 3D场景核心组件根实体+相机+光源+控制器)
m_rootEntity = new Qt3DCore::QEntity();
m_3dView->setRootEntity(m_rootEntity);
// 相机:确保视野正常
m_cameraEntity = m_3dView->camera();
m_cameraEntity->setPosition(QVector3D(0, 0, 20));
m_cameraEntity->setViewCenter(QVector3D(0, 0, 0));
m_cameraEntity->setAspectRatio(float(m_viewContainer->width()) / float(m_viewContainer->height()));
// 控制器:支持鼠标交互
Qt3DExtras::QOrbitCameraController *camController = new Qt3DExtras::QOrbitCameraController(m_rootEntity);
camController->setCamera(m_cameraEntity);
// 光源:确保模型可见
Qt3DCore::QEntity *lightEntity = new Qt3DCore::QEntity(m_rootEntity);
light = new Qt3DRender::QDirectionalLight(lightEntity);
light->setColor(Qt::white);
light->setIntensity(2.0f);
light->setWorldDirection(QVector3D(-1, -2, -3).normalized());
lightEntity->addComponent(light);
Qt3DCore::QTransform *lightTransform = new Qt3DCore::QTransform(lightEntity);
lightTransform->setTranslation(QVector3D(10, 20, 30));
lightEntity->addComponent(lightTransform);
QTimer::singleShot(10, this, [this, containerPtr = m_viewContainer]() {
if (!containerPtr || !containerPtr->layout()) return;
QWidget *container3D = containerPtr->layout()->itemAt(0)->widget();
if (!container3D) return;
// 1. 实时获取父容器的初始有效尺寸排除0x0
QSize parentSize = containerPtr->contentsRect().size();
QSize validInitialSize = (parentSize.width() > 0 && parentSize.height() > 0)
? parentSize
: QSize(420, 230); // 兜底尺寸(匹配日志初始值)
// 2. 实时获取父容器当前尺寸(避免被外部缩小)
QSize currentParentSize = containerPtr->contentsRect().size();
QSize validCurrentSize = (currentParentSize.width() > 0 && currentParentSize.height() > 0)
? currentParentSize
: validInitialSize;
// 3. 最终尺寸:取“有效初始尺寸”和“当前有效尺寸”的最大值(禁止缩小)
QSize finalSize = validCurrentSize.expandedTo(validInitialSize);
container3D->resize(finalSize);
// 4. 同步相机宽高比(避免模型拉伸)
if (m_cameraEntity && finalSize.width() > 0 && finalSize.height() > 0) {
m_cameraEntity->setAspectRatio(float(finalSize.width()) / float(finalSize.height()));
}
// 调试日志:明确各尺寸来源
if (EnDebug) {
qDebug() << "[3D尺寸同步] 有效初始尺寸:" << validInitialSize
<< ",当前父容器尺寸:" << currentParentSize
<< ",最终锁定尺寸:" << finalSize;
}
});
return true;
}
/**
* @brief 加载器件(独立模型)
* 创建双层实体结构(外层控制位置,内层控制旋转/缩放解析并应用MTL材质
* @param deviceName 器件名称
* @param filePath OBJ文件路径
* @param position 初始位置
* @return 加载成功返回true
*/
bool ObjLoader::loadDevice(const QString &deviceName, const QString &filePath, const QVector3D &position)
{
if (m_deviceMap.contains(deviceName)) {
m_lastError = "器件名称已存在: " + deviceName;
return false;
}
if (!m_rootEntity) {
m_lastError = "3D场景未初始化请先调用init3DScene";
return false;
}
QFileInfo testFile(filePath);
if(!testFile.exists()){
m_lastError = "文件不存在: " + filePath;
if (EnDebug) qDebug() << filePath<<"设备 文件是否存在:" << testFile.exists() << ",绝对路径:" << testFile.absoluteFilePath();
return false;
}
// 1. 计算模型几何中心(用于预中心化,使旋转围绕模型自身中心)
QVector3D modelCenter = parseObjCenter(filePath);
float modelRadius = calculateModelRadiusFromFile(filePath);
if (EnDebug) qDebug() << "模型几何中心: " << modelCenter << " 模型半径: " << modelRadius;
// 2. 创建双层实体结构
Qt3DCore::QEntity *deviceEntity = new Qt3DCore::QEntity(m_rootEntity); // 外层实体
Qt3DCore::QTransform *deviceTransform = new Qt3DCore::QTransform();
deviceTransform->setTranslation(position); // 外层控制世界位置
deviceEntity->addComponent(deviceTransform);
// 内层实体:控制旋转和缩放
Qt3DCore::QEntity *modelEntity = new Qt3DCore::QEntity(deviceEntity);
Qt3DCore::QTransform *modelTransform = new Qt3DCore::QTransform();
modelTransform->setTranslation(-modelCenter); // 预中心化(将模型中心移至局部原点)
modelEntity->addComponent(modelTransform);
// 3. 加载模型网格
Qt3DRender::QMesh *deviceMesh = new Qt3DRender::QMesh();
deviceMesh->setSource(QUrl::fromLocalFile(filePath));
modelEntity->addComponent(deviceMesh);
// 4. 加载并应用MTL材质
Qt3DExtras::QPhongMaterial *material = new Qt3DExtras::QPhongMaterial();
material->setDiffuse(QColor(0, 255, 255,80)); // 默认白色
modelEntity->addComponent(material);
// 5. 存储器件信息
Device device;
device.name = deviceName;
device.entity = deviceEntity;
device.modelEntity = modelEntity;
device.transform = deviceTransform;
device.modelTransform = modelTransform;
device.worldPosition = position;
device.originalPosition = position;
device.currentRotation = QVector3D(0, 0, 0);
device.modelCenter = modelCenter;
device.scale = 1.0f;
device.isInitialized = true;
m_deviceMap[deviceName] = device;
return true;
}
bool ObjLoader::loadModel(const QString &deviceName, const QString &filePath, const QVector3D &position)
{
if (m_deviceMap.contains(deviceName)) {
m_lastError = "器件名称已存在: " + deviceName;
return false;
}
if (!m_rootEntity) {
m_lastError = "3D场景未初始化请先调用init3DScene";
return false;
}
QFileInfo testFile(filePath);
if(!testFile.exists()){
m_lastError = "文件不存在: " + filePath;
if (EnDebug) qDebug() << filePath<<"设备 文件是否存在:" << testFile.exists() << ",绝对路径:" << testFile.absoluteFilePath();
return false;
}
// 1. 计算模型几何中心和半径
QVector3D modelCenter = parseObjCenter(filePath);
float modelRadius = calculateModelRadiusFromFile(filePath);
if (EnDebug) qDebug() << "模型几何中心: " << modelCenter << " 模型半径: " << modelRadius;
// 2. 创建双层实体结构
Qt3DCore::QEntity *deviceEntity = new Qt3DCore::QEntity(m_rootEntity);
Qt3DCore::QTransform *deviceTransform = new Qt3DCore::QTransform();
QVector3D outerTranslation = position - modelCenter;
deviceTransform->setTranslation(outerTranslation);
deviceEntity->addComponent(deviceTransform);
Qt3DCore::QEntity *modelEntity = new Qt3DCore::QEntity(deviceEntity);
Qt3DCore::QTransform *modelTransform = new Qt3DCore::QTransform();
modelTransform->setTranslation(modelCenter);
modelEntity->addComponent(modelTransform);
// 3. 加载模型网格
Qt3DRender::QMesh *deviceMesh = new Qt3DRender::QMesh();
deviceMesh->setSource(QUrl::fromLocalFile(filePath));
modelEntity->addComponent(deviceMesh);
// 4. 加载并应用MTL材质
Qt3DExtras::QPhongMaterial *material = new Qt3DExtras::QPhongMaterial();
QFile objFile(filePath);
if (objFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
QTextStream stream(&objFile);
QString mtlPath; // MTL文件路径
QString useMtl; // 当前使用的材质名称
while (!stream.atEnd()) {
QString line = stream.readLine().trimmed();
if (line.startsWith("mtllib")) {
// 解析MTL文件路径
QStringList parts = line.split(" ");
if (parts.size() >= 2) {
mtlPath = QFileInfo(filePath).path() + "/" + parts[1];
parseMtlFile(mtlPath); // 解析MTL文件到m_materials
}
} else if (line.startsWith("usemtl")) {
// 解析当前使用的材质名称
QStringList parts = line.split(" ");
if (parts.size() >= 2) {
useMtl = parts[1];
}
}
}
objFile.close();
// 应用解析到的材质
if (m_materials.contains(useMtl)) {
Material mtl = m_materials[useMtl];
material->setAmbient(mtl.ambient);
material->setDiffuse(mtl.diffuse);
material->setSpecular(mtl.specular);
material->setShininess(mtl.shininess);
// 设置透明度通过漫反射颜色的alpha通道
QColor diffuse = mtl.diffuse;
diffuse.setAlphaF(mtl.opacity); // 0.0~1.0范围
material->setDiffuse(diffuse);
// 同时设置环境光的alpha可选
QColor ambient = mtl.ambient;
ambient.setAlphaF(mtl.opacity);
material->setAmbient(ambient);
if(EnDebug) qDebug() << "应用材质: " << useMtl << " 到设备: " << deviceName;
} else {
if(EnDebug) qDebug() << "未找到材质: " << useMtl << ",使用默认材质";
material->setDiffuse(QColor(255, 255, 255,50)); // 默认白色
}
}
// 5. 存储器件信息
Device device;
device.name = deviceName;
device.entity = deviceEntity;
device.modelEntity = modelEntity;
device.transform = deviceTransform;
device.modelTransform = modelTransform;
device.worldPosition = position;
device.originalPosition = position;
device.currentRotation = QVector3D(0, 0, 0);
device.modelCenter = modelCenter;
device.scale = 1.0f;
device.isInitialized = true;
m_deviceMap[deviceName] = device;
return true;
}
/**
* @brief 加载器件(独立模型)
* 创建双层实体结构(外层控制位置,内层控制旋转/缩放解析并应用MTL材质
* 加载完成后自动调整相机位置,确保模型可见
* @param deviceName 器件名称
* @param filePath OBJ文件路径
* @param position 初始位置
* @return 加载成功返回true
*/
//bool ObjLoader::loadModel(const QString &deviceName, const QString &filePath, const QVector3D &position)
//{
// if (m_deviceMap.contains(deviceName)) {
// m_lastError = "器件名称已存在: " + deviceName;
// return false;
// }
// if (!m_rootEntity) {
// m_lastError = "3D场景未初始化请先调用init3DScene";
// return false;
// }
// // 1. 计算模型几何中心和顶点数据
// QVector3D modelCenter = parseObjCenter(filePath);
// float modelRadius = calculateModelRadiusFromFile(filePath);
// if(EnDebug) qDebug() << "模型几何中心: " << modelCenter << " 顶点数据: " << modelRadius;
// // 2. 创建双层实体结构(关键修改:修正坐标变换逻辑)
// Qt3DCore::QEntity *deviceEntity = new Qt3DCore::QEntity(m_rootEntity);
// Qt3DCore::QTransform *deviceTransform = new Qt3DCore::QTransform();
// // 核心修复:外层平移 = 目标位置 - 模型局部中心
// // 确保模型几何中心与传入的position对齐
// QVector3D outerTranslation = position - modelCenter;
// deviceTransform->setTranslation(outerTranslation);
// deviceEntity->addComponent(deviceTransform);
// // 内层实体:抵消模型自身偏移,确保旋转中心正确
// Qt3DCore::QEntity *modelEntity = new Qt3DCore::QEntity(deviceEntity);
// Qt3DCore::QTransform *modelTransform = new Qt3DCore::QTransform();
// modelTransform->setTranslation(modelCenter); // 关键:与外层平移形成抵消
// modelEntity->addComponent(modelTransform);
// // 3. 加载模型网格
// Qt3DRender::QMesh *deviceMesh = new Qt3DRender::QMesh();
// deviceMesh->setSource(QUrl::fromLocalFile(filePath));
// modelEntity->addComponent(deviceMesh);
// // 4. 加载并应用MTL材质
// Qt3DExtras::QPhongMaterial *material = new Qt3DExtras::QPhongMaterial();
// QFile objFile(filePath);
// if (objFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
// QTextStream stream(&objFile);
// QString mtlPath; // MTL文件路径
// QString useMtl; // 当前使用的材质名称
// while (!stream.atEnd()) {
// QString line = stream.readLine().trimmed();
// if (line.startsWith("mtllib")) {
// // 解析MTL文件路径
// QStringList parts = line.split(" ");
// if (parts.size() >= 2) {
// mtlPath = QFileInfo(filePath).path() + "/" + parts[1];
// parseMtlFile(mtlPath); // 解析MTL文件到m_materials
// }
// } else if (line.startsWith("usemtl")) {
// // 解析当前使用的材质名称
// QStringList parts = line.split(" ");
// if (parts.size() >= 2) {
// useMtl = parts[1];
// }
// }
// }
// objFile.close();
// // 应用解析到的材质
// if (m_materials.contains(useMtl)) {
// Material mtl = m_materials[useMtl];
// material->setAmbient(mtl.ambient);
// material->setDiffuse(mtl.diffuse);
// material->setSpecular(mtl.specular);
// material->setShininess(mtl.shininess);
// // 设置透明度通过漫反射颜色的alpha通道
// QColor diffuse = mtl.diffuse;
// diffuse.setAlphaF(mtl.opacity); // 0.0~1.0范围
// material->setDiffuse(diffuse);
// // 同时设置环境光的alpha可选
// QColor ambient = mtl.ambient;
// ambient.setAlphaF(mtl.opacity);
// material->setAmbient(ambient);
// if(EnDebug) qDebug() << "应用材质: " << useMtl << " 到设备: " << deviceName;
// } else {
// if(EnDebug) qDebug() << "未找到材质: " << useMtl << ",使用默认材质";
// material->setDiffuse(QColor(0, 255, 255,10)); // 默认白色
// }
// }
// modelEntity->addComponent(material);
// // 5. 存储器件信息
// Device device;
// device.name = deviceName;
// device.entity = deviceEntity;
// device.modelEntity = modelEntity;
// device.transform = deviceTransform;
// device.modelTransform = modelTransform;
// device.worldPosition = position;
// device.originalPosition = position;
// device.currentRotation = QVector3D(0, 0, 0);
// device.modelCenter = modelCenter;
// device.scale = 1.0f;
// device.isInitialized = true;
// m_deviceMap[deviceName] = device;
// return true;
//}
/**
* @brief 从OBJ文件计算模型的包围球半径
* 解析OBJ文件中的顶点数据计算模型几何中心到最远顶点的距离作为模型的包围球半径
* 该半径用于确定相机与模型的合适距离,确保模型能完整显示在视野中
* @param filePath OBJ模型文件的路径
* @return 模型包围球半径单位与模型顶点坐标一致若文件解析失败则返回1.0f默认值
*/
float ObjLoader::calculateModelRadiusFromFile(const QString &filePath)
{
QVector<QVector3D> vertices;
QFile objFile(filePath);
if (!objFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
m_lastError = "无法打开OBJ文件计算半径: " + filePath;
return 1.0f; // 默认半径
}
QTextStream fileStream(&objFile);
while (!fileStream.atEnd()) {
QString line = fileStream.readLine().trimmed();
if (line.isEmpty() || line.startsWith('#')) continue;
QVector<QString> lineParts = splitStr(line);
if (lineParts.isEmpty()) continue;
// 只解析顶点位置("v x y z"格式)
if (lineParts[0] == "v" && lineParts.size() >= 4) {
vertices.append(QVector3D(
lineParts[1].toFloat(),
lineParts[2].toFloat(),
lineParts[3].toFloat()
));
}
}
objFile.close();
// 如果没有顶点数据,返回默认半径
if (vertices.isEmpty()) return 1.0f;
// 计算模型中心
QVector3D center = calculateBoundingBoxCenter(vertices);
// 找到距离中心最远的顶点,计算半径
float maxDistance = 0.0f;
for (const auto& v : vertices) {
float distance = (v - center).length();
if (distance > maxDistance) {
maxDistance = distance;
}
}
return maxDistance;
}
/**
* @brief 加载子器件(挂载到父器件下)
* 确保子器件与父器件的初始相对位置正确,并继承父器件的变换
* @param childDeviceName 子器件名称
* @param filePath OBJ文件路径
* @param parentDeviceName 父器件名称
* @param offsetDir 相对父器件的偏移方向
* @param diffuse 材质
* @return 加载成功返回true
*/
bool ObjLoader::loadChildDevice(
const QString &childDeviceName,
const QString &filePath,
const QString &parentDeviceName,
const QVector3D &offsetDir,
const QColor &diffuse
)
{
// 基础校验
if (m_deviceMap.contains(childDeviceName)) {
m_lastError = "子设备名称已存在: " + childDeviceName;
return false;
}
if (!m_rootEntity) {
m_lastError = "3D场景未初始化";
return false;
}
// 获取父设备(摇臂)
Device* parentDevice = getDevice(parentDeviceName);
if (!parentDevice || !parentDevice->isInitialized) {
m_lastError = "父设备不存在或未初始化: " + parentDeviceName;
return false;
}
QFileInfo testFile(filePath);
if(!testFile.exists()){
m_lastError = "文件不存在: " + parentDeviceName;
if (EnDebug) qDebug() << childDeviceName<<"设备 文件是否存在:" << testFile.exists() << ",绝对路径:" << testFile.absoluteFilePath();
return false;
}
// 计算子设备(滚筒)的几何中心
QVector3D childModelCenter = parseObjCenter(filePath);
// 精确计算相对偏移:确保父设备与子设备的几何中心相对位置正确
QVector3D modelCenter = parseObjCenter(filePath); //世界模型中心
QVector3D parentCenter = parentDevice->modelCenter; // 父设备几何中心
QVector3D installOffset = parentCenter + offsetDir - childModelCenter;// 模型几何中心
float modelRadius = calculateModelRadiusFromFile(filePath);//顶点
if (EnDebug) qDebug() << "模型几何中心: " << modelCenter << "父设备几何中心" <<parentCenter<<"模型几何中心"<<installOffset<< " 顶点位置: " << modelRadius;
// 创建子设备实体(挂载到父设备实体下,形成层级关系)
Qt3DCore::QEntity* childEntity = new Qt3DCore::QEntity(parentDevice->entity);
Qt3DCore::QTransform* childTransform = new Qt3DCore::QTransform();
childTransform->setTranslation(installOffset); // 设置精确计算的偏移
childEntity->addComponent(childTransform);
// 创建子设备模型实体(内层,处理旋转和缩放)
Qt3DCore::QEntity* childModelEntity = new Qt3DCore::QEntity(childEntity);
Qt3DCore::QTransform* childModelTrans = new Qt3DCore::QTransform();
childModelTrans->setTranslation(-childModelCenter); // 子模型预中心化
childModelEntity->addComponent(childModelTrans);
// 加载网格和材质
Qt3DRender::QMesh* childMesh = new Qt3DRender::QMesh();
childMesh->setSource(QUrl::fromLocalFile(filePath));
childModelEntity->addComponent(childMesh);
// 4. 加载并应用MTL材质
Qt3DExtras::QPhongMaterial *material = new Qt3DExtras::QPhongMaterial();
material->setDiffuse(diffuse); // 默认白色
// // 加载MTL材质
// Qt3DExtras::QPhongMaterial* childMat = new Qt3DExtras::QPhongMaterial();
// QString mtlPath; // MTL文件路径从OBJ中读取
// QString useMtlName; // 子设备使用的材质名称从OBJ中读取
// QString objBaseDir = QFileInfo(filePath).path() + "/"; // OBJ文件所在目录
// // 解析OBJ文件提取MTL路径和当前使用的材质名称
// QFile objFile(filePath);
// if (objFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
// QTextStream objStream(&objFile);
// while (!objStream.atEnd()) {
// QString line = objStream.readLine().trimmed();
// if (line.isEmpty() || line.startsWith('#')) continue; // 跳过注释和空行
// QVector<QString> lineParts = splitStr(line);
// if (lineParts.isEmpty()) continue;
// QString cmd = lineParts[0];
// // 读取MTL文件路径OBJ中的"mtllib"指令)
// if (cmd == "mtllib" && lineParts.size() >= 2) {
// mtlPath = objBaseDir + lineParts[1]; // 拼接MTL的绝对路径
// if (QFile::exists(mtlPath)) {
// parseMtlFile(mtlPath); // 解析MTL文件存入m_materials
// if(EnDebug) qDebug() << "[子设备材质] 已解析MTL文件" << mtlPath;
// } else {
// qWarning() << "[子设备材质] MTL文件不存在" << mtlPath;
// }
// }
// // 读取当前使用的材质名称OBJ中的"usemtl"指令)
// else if (cmd == "usemtl" && lineParts.size() >= 2) {
// useMtlName = lineParts[1]; // 记录子设备要使用的材质名称
// if(EnDebug) qDebug() << "[子设备材质] 子设备" << childDeviceName << "使用材质:" << useMtlName;
// }
// }
// objFile.close();
// } else {
// qWarning() << "[子设备材质] 无法打开OBJ文件" << filePath;
// }
// // 应用MTL材质优先使用解析到的材质无则用默认颜色
// if (!useMtlName.isEmpty() && m_materials.contains(useMtlName)) {
// Material mtl = m_materials[useMtlName];
// childMat->setAmbient(mtl.ambient);
// childMat->setDiffuse(mtl.diffuse);
// childMat->setSpecular(mtl.specular);
// childMat->setShininess(mtl.shininess);
// // 设置透明度通过漫反射颜色的alpha通道
// QColor diffuseWithAlpha = mtl.diffuse;
// diffuseWithAlpha.setAlphaF(mtl.opacity);
// childMat->setDiffuse(diffuseWithAlpha);
// if(EnDebug) qDebug() << "[子设备材质] 成功应用MTL材质" << useMtlName
// << " | 漫反射颜色:" << mtl.diffuse
// << " | 透明度:" << mtl.opacity;
// } else {
// // fallback使用默认颜色
// childMat->setDiffuse(QColor(100, 200, 255));
// if(EnDebug) qDebug() << "[子设备材质] 未找到指定材质,使用默认颜色:(100,200,255)";
// }
childModelEntity->addComponent(material); // 将材质添加到子设备模型实体
// 初始化子设备信息
Device childDevice;
childDevice.name = childDeviceName;
childDevice.entity = childEntity;
childDevice.modelEntity = childModelEntity;
childDevice.transform = childTransform;
childDevice.modelTransform = childModelTrans;
childDevice.worldPosition = parentDevice->worldPosition + installOffset;
childDevice.currentRotation = QVector3D(0, 0, 0);
childDevice.modelCenter = childModelCenter;
childDevice.scale = 1.0f;
childDevice.isInitialized = true;
childDevice.parentDeviceName = parentDeviceName;
childDevice.offsetToParent = installOffset; // 记录初始相对偏移
// 保存初始状态(用于后续精确复位)
childDevice.initialWorldPosition = childDevice.worldPosition;
childDevice.initialRotation = childDevice.currentRotation;
childDevice.initialTransformMatrix = childTransform->matrix();
childDevice.initialModelTransformMatrix = childModelTrans->matrix();
m_deviceMap[childDeviceName] = childDevice;
qInfo() << "子设备" << childDeviceName << "已挂载到" << parentDeviceName
<< ",初始相对偏移:" << installOffset;
return true;
}
/**
* @brief 旋转器件(绕自身中心)
* 将角度转换为弧度后应用到内层变换组件
* @param deviceName 器件名称
* @param xAngle X轴旋转角度
* @param yAngle Y轴旋转角度
* @param zAngle Z轴旋转角度
*/
void ObjLoader::rotateDevice(const QString &deviceName, float xAngle, float yAngle, float zAngle)
{
Device *device = getDevice(deviceName);
if (!device || !device->isInitialized) return;
// 打印传入的角度值(调试用)
if(EnDebug) qDebug() << "接收旋转参数:" << xAngle << "," << yAngle << "," << zAngle;
device->currentRotation = QVector3D(xAngle, yAngle, zAngle);
// 角度→弧度Qt3D的旋转函数接受弧度
float xRad = xAngle * M_PI / 180.0f;
float yRad = yAngle * M_PI / 180.0f;
float zRad = zAngle * M_PI / 180.0f;
// 应用旋转到内层变换(同时保持缩放)
device->modelTransform->setRotationX(xRad);
device->modelTransform->setRotationY(yRad);
device->modelTransform->setRotationZ(zRad);
device->modelTransform->setScale(device->scale);
}
/**
* @brief 移动器件(相对偏移)
* 更新器件的世界位置并应用到外层变换
* @param deviceName 器件名称
* @param offset 偏移量(世界坐标)
*/
void ObjLoader::moveDevice(const QString &deviceName, const QVector3D &offset)
{
Device *device = getDevice(deviceName);
if (!device || !device->isInitialized) return;
// 更新世界位置
device->worldPosition += offset;
device->transform->setTranslation(device->worldPosition);
}
/**
* @brief 设置器件位置(绝对位置)
* 直接设置器件的世界位置并应用到外层变换
* @param deviceName 器件名称
* @param targetPos 目标位置(世界坐标)
*/
void ObjLoader::setDevicePos(const QString &deviceName, const QVector3D &targetPos)
{
Device *device = getDevice(deviceName);
if (!device || !device->isInitialized) return;
// 更新世界位置
device->worldPosition = targetPos;
device->transform->setTranslation(device->worldPosition);
}
/**
* @brief 缩放器件
* 更新缩放因子并重新应用变换(确保旋转状态保持)
* @param deviceName 器件名称
* @param scaleFactor 缩放因子(>0
*/
void ObjLoader::scaleDevice(const QString &deviceName, float scaleFactor)
{
if (scaleFactor <= 0) return;
Device *device = getDevice(deviceName);
if (!device || !device->isInitialized) return;
// 保存新的缩放因子
device->scale = scaleFactor;
// 重新应用变换(保持当前旋转角度)
rotateDevice(deviceName,
device->currentRotation.x(),
device->currentRotation.y(),
device->currentRotation.z());
}
/**
* @brief 重置器件到初始状态
* 恢复初始位置、旋转和缩放
* @param deviceName 器件名称
*/
void ObjLoader::resetDevice(const QString &deviceName)
{
Device *device = getDevice(deviceName);
if (!device || !device->isInitialized) return;
// 重置所有属性
device->worldPosition = device->originalPosition;
device->currentRotation = QVector3D(0, 0, 0);
device->scale = 1.0f;
// 应用重置后的变换
device->transform->setTranslation(device->worldPosition);
rotateDevice(deviceName, 0, 0, 0);
}
/**
* @brief 从数据映射更新多个器件的属性
* 支持同时更新缩放、旋转和位置
* @param deviceData 器件数据映射
*/
void ObjLoader::updateDevicesFromData(const QMap<QString, QMap<QString, float>> &deviceData)
{
for (auto it = deviceData.begin(); it != deviceData.end(); ++it) {
const QString &deviceName = it.key();
const QMap<QString, float> &data = it.value();
// 处理缩放数据
if (data.contains("scale")) {
scaleDevice(deviceName, data["scale"]);
}
// 处理旋转数据
if (data.contains("xRot") && data.contains("yRot") && data.contains("zRot")) {
rotateDevice(deviceName,
data["xRot"],
data["yRot"],
data["zRot"]);
}
// 处理位置数据
if (data.contains("xPos") && data.contains("yPos") && data.contains("zPos")) {
setDevicePos(deviceName, QVector3D(
data["xPos"],
data["yPos"],
data["zPos"]
));
}
}
}
/**
* @brief 设置父器件位置(影响子器件)
* 无父级的器件直接设置位置,有子级的器件通过调整偏移更新
* @param deviceName 器件名称
* @param position 目标位置(世界坐标)
*/
void ObjLoader::setParentDevicePosition(const QString &deviceName, const QVector3D &position)
{
Device *device = getDevice(deviceName);
if (!device || !device->isInitialized) {
m_lastError = "模型不存在或未初始化: " + deviceName;
return;
}
// 只有无父级的模型才直接设置位置
if (device->parentDeviceName.isEmpty()) {
device->worldPosition = position;
device->transform->setTranslation(position); // 直接修改世界坐标
} else {
// 如果是子模型,使用相对偏移方法
adjustChildDeviceOffset(deviceName, position);
}
}
/**
* @brief 添加坐标轴 gizmo
* 创建X、Y绿、Z三条坐标轴端点带小球标记
* @param parent 父实体(默认根实体)
* @param length 坐标轴长度
*/
void ObjLoader::addAxisGizmo(Qt3DCore::QEntity *parent, float length)
{
if (!parent) parent = m_rootEntity; // 如果未指定父实体,使用根实体
if (!parent) return; // 确保父实体有效
// 创建单个轴的辅助函数(使用内置材质)
auto createAxis = [&](const QVector3D& end, const QColor& color) {
// 1. 创建线段几何
Qt3DRender::QGeometry *geometry = new Qt3DRender::QGeometry();
// 顶点数据(从原点到终点)
QByteArray vertexData;
vertexData.resize(2 * 3 * sizeof(float)); // 2个点每个点3个坐标
float *vertices = reinterpret_cast<float*>(vertexData.data());
// 起点(原点)
vertices[0] = 0.0f;
vertices[1] = 0.0f;
vertices[2] = 0.0f;
// 终点
vertices[3] = end.x();
vertices[4] = end.y();
vertices[5] = end.z();
// 创建顶点缓冲
Qt3DRender::QBuffer *vertexBuffer = new Qt3DRender::QBuffer(Qt3DRender::QBuffer::VertexBuffer);
vertexBuffer->setData(vertexData);
// 创建位置属性
Qt3DRender::QAttribute *positionAttribute = new Qt3DRender::QAttribute();
positionAttribute->setName(Qt3DRender::QAttribute::defaultPositionAttributeName());
positionAttribute->setVertexBaseType(Qt3DRender::QAttribute::Float);
positionAttribute->setVertexSize(3);
positionAttribute->setAttributeType(Qt3DRender::QAttribute::VertexAttribute);
positionAttribute->setBuffer(vertexBuffer);
positionAttribute->setByteOffset(0);
positionAttribute->setByteStride(3 * sizeof(float));
positionAttribute->setCount(2);
geometry->addAttribute(positionAttribute);
// 2. 创建线段渲染器
Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer();
renderer->setGeometry(geometry);
renderer->setPrimitiveType(Qt3DRender::QGeometryRenderer::Lines);
// 3. 使用内置材质(避免自定义着色器)
Qt3DExtras::QPhongMaterial *material = new Qt3DExtras::QPhongMaterial();
material->setDiffuse(color);
material->setAmbient(color);
material->setSpecular(color);
material->setShininess(1.0f);
// 4. 创建线段实体
Qt3DCore::QEntity *axisEntity = new Qt3DCore::QEntity(parent);
axisEntity->addComponent(renderer);
axisEntity->addComponent(material);
// 5. 在轴的端点添加一个小球标记
Qt3DCore::QEntity *marker = new Qt3DCore::QEntity(axisEntity);
Qt3DExtras::QSphereMesh *markerMesh = new Qt3DExtras::QSphereMesh();
markerMesh->setRadius(length * 0.05f); // 小球半径为轴长的5%
marker->addComponent(markerMesh);
Qt3DCore::QTransform *markerTransform = new Qt3DCore::QTransform();
markerTransform->setTranslation(end);
marker->addComponent(markerTransform);
Qt3DExtras::QPhongMaterial *markerMat = new Qt3DExtras::QPhongMaterial();
markerMat->setDiffuse(color);
marker->addComponent(markerMat);
return axisEntity;
};
// 创建三条坐标轴
createAxis(QVector3D(length, 0, 0), Qt::red); // X轴红色
createAxis(QVector3D(0, length, 0), Qt::green); // Y轴绿色
createAxis(QVector3D(0, 0, length), Qt::blue); // Z轴蓝色
}
/**
* @brief 解析OBJ文件计算模型中心
* 读取OBJ文件中的顶点数据计算包围盒中心
* @param filePath OBJ文件路径
* @return 模型中心坐标(局部坐标系)
*/
QVector3D ObjLoader::parseObjCenter(const QString &filePath)
{
QVector<QVector3D> vertices;
QFile objFile(filePath);
if (!objFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
m_lastError = "无法打开OBJ文件计算中心: " + filePath;
return QVector3D(0, 0, 0);
}
QTextStream fileStream(&objFile);
while (!fileStream.atEnd()) {
QString line = fileStream.readLine().trimmed();
if (line.isEmpty() || line.startsWith('#')) continue;
QVector<QString> lineParts = splitStr(line);
if (lineParts.isEmpty()) continue;
// 只解析顶点位置("v x y z"格式)
if (lineParts[0] == "v" && lineParts.size() >= 4) {
vertices.append(QVector3D(
lineParts[1].toFloat(),
lineParts[2].toFloat(),
lineParts[3].toFloat()
));
}
}
objFile.close();
return calculateBoundingBoxCenter(vertices);
}
/**
* @brief 打印器件的世界坐标信息
* 输出器件的外层位置、几何中心及实际世界中心
* @param deviceName 器件名称
*/
void ObjLoader::printDeviceWorldPos(const QString &deviceName)
{
Device *device = getDevice(deviceName);
if (!device || !device->isInitialized) {
qCritical() << "[定位错误] 设备'" << deviceName << "'未找到或未初始化";
return;
}
// 计算滚筒几何中心的世界坐标(外层位置 + 局部中心)
QVector3D deviceCenterWorld = device->worldPosition + device->modelCenter;
if(EnDebug) qDebug() << "=== 设备'" << deviceName << "'位置信息 ===";
if(EnDebug) qDebug() << "外层实体世界坐标(位置基准):" << device->worldPosition;
if(EnDebug) qDebug() << "几何中心(局部坐标):" << device->modelCenter;
if(EnDebug) qDebug() << "几何中心世界坐标(实际本体位置):" << deviceCenterWorld;
if(EnDebug) qDebug() << "当前旋转角度:" << device->currentRotation;
if(EnDebug) qDebug() << "缩放比例:" << device->scale;
}
/**
* @brief 更新整体模型(重建实体)
* 清除旧的整体模型实体,创建新的实体、变换和材质
*/
void ObjLoader::updateWholeModel()
{
delete m_wholeModelEntity;
m_wholeModelEntity = nullptr;
m_wholeModelMesh = nullptr;
m_wholeModelTrans = nullptr;
m_wholeModelEntity = new Qt3DCore::QEntity(m_rootEntity);
// 添加模型变换组件(预中心化)
m_wholeModelTrans = new Qt3DCore::QTransform();
m_wholeModelTrans->setTranslation(-m_wholeModelCenter);
m_wholeModelEntity->addComponent(m_wholeModelTrans);
// 添加网格组件
m_wholeModelMesh = new Qt3DRender::QMesh();
m_wholeModelEntity->addComponent(m_wholeModelMesh);
// 添加材质
Qt3DExtras::QPhongMaterial *material = new Qt3DExtras::QPhongMaterial();
material->setDiffuse(QColor(0xbeb32b)); // 金色
material->setSpecular(QColor(255, 255, 255));
material->setShininess(60.0f);
m_wholeModelEntity->addComponent(material);
}
/**
* @brief 摇臂围绕自身局部原点旋转,带动子设备
* 首次调用记录初始状态0°时复位其他角度计算旋转矩阵并应用
* @param deviceName 摇臂名称
* @param targetZAngle 目标Z轴角度
*/
void ObjLoader::rotateArmToAbsoluteZAngle(const QString &deviceName, float targetZAngle)
{
Device *armDevice = getDevice(deviceName);
if (!armDevice || !armDevice->isInitialized) {
m_lastError = deviceName + " ->设备不存在或未初始化: " + deviceName;
qCritical() << "[错误]" << m_lastError;
return;
}
/* 1. 首次调用时记录初始状态(只需一次) */
if (armDevice->initialRotation == QVector3D()) {
armDevice->initialRotation = QVector3D(0, 0, 0);
armDevice->initialWorldPosition = armDevice->worldPosition;
armDevice->initialTransformMatrix = armDevice->transform->matrix();
armDevice->initialModelTransformMatrix = armDevice->modelTransform->matrix();
}
/* 2. 0°复位 */
if (qFuzzyCompare(targetZAngle, 0.0f)) {
armDevice->worldPosition = armDevice->initialWorldPosition;
armDevice->currentRotation = armDevice->initialRotation;
armDevice->transform->setMatrix(armDevice->initialTransformMatrix);
armDevice->modelTransform->setMatrix(armDevice->initialModelTransformMatrix);
for (Device *child : getAllChildren(armDevice)) {
child->worldPosition = child->initialWorldPosition;
child->transform->setMatrix(child->initialTransformMatrix);
child->modelTransform->setMatrix(child->initialModelTransformMatrix);
}
return;
}
/* 3. 计算旋转矩阵绕摇臂自身局部原点0,0,0 */
QMatrix4x4 rotMat;
rotMat.setToIdentity();
rotMat.rotate(targetZAngle, 0, 0, 1); // 仅 Z 轴
/* 4. 应用旋转到外层 transform平移+旋转) */
armDevice->currentRotation.setZ(targetZAngle);
armDevice->transform->setRotationZ(targetZAngle); // 外层只转 Z
// 保持原平移不变
armDevice->transform->setTranslation(armDevice->worldPosition);
/* 5. 同步子设备:继承父旋转,更新世界坐标记录 */
for (Device *child : getAllChildren(armDevice)) {
// 子设备挂在 armDevice->entity 下,自动继承旋转
QMatrix4x4 worldMat = child->transform->matrix(); // 局部→世界矩阵
child->worldPosition = worldMat.column(3).toVector3D();
}
}
/**
* @brief 摇臂围绕自身局部原点绕X轴旋转带动子设备
* 首次调用记录初始状态0°时复位其他角度计算旋转矩阵并应用
* @param deviceName 摇臂名称
* @param targetXAngle 目标X轴角度
*/
void ObjLoader::rotateArmToAbsoluteXAngle(const QString &deviceName, float targetXAngle)
{
Device *armDevice = getDevice(deviceName);
if (!armDevice || !armDevice->isInitialized) {
m_lastError = "摇臂设备不存在或未初始化: " + deviceName;
qCritical() << "[错误]" << m_lastError;
return;
}
/* 1. 首次调用时记录初始状态(只需一次) */
if (armDevice->initialRotation == QVector3D()) {
armDevice->initialRotation = QVector3D(0, 0, 0);
armDevice->initialWorldPosition = armDevice->worldPosition;
armDevice->initialTransformMatrix = armDevice->transform->matrix();
armDevice->initialModelTransformMatrix = armDevice->modelTransform->matrix();
}
/* 2. 0°复位 */
if (qFuzzyCompare(targetXAngle, 0.0f)) {
armDevice->worldPosition = armDevice->initialWorldPosition;
armDevice->currentRotation = armDevice->initialRotation;
armDevice->transform->setMatrix(armDevice->initialTransformMatrix);
armDevice->modelTransform->setMatrix(armDevice->initialModelTransformMatrix);
for (Device *child : getAllChildren(armDevice)) {
child->worldPosition = child->initialWorldPosition;
child->transform->setMatrix(child->initialTransformMatrix);
child->modelTransform->setMatrix(child->initialModelTransformMatrix);
}
return;
}
/* 3. 计算旋转矩阵绕摇臂自身局部原点0,0,0的X轴 */
QMatrix4x4 rotMat;
rotMat.setToIdentity();
rotMat.rotate(targetXAngle, 1, 0, 0); // 仅 X 轴
/* 4. 应用旋转到外层 transform平移+旋转) */
armDevice->currentRotation.setX(targetXAngle);
armDevice->transform->setRotationX(targetXAngle); // 外层只转 X
// 保持原平移不变
armDevice->transform->setTranslation(armDevice->worldPosition);
/* 5. 同步子设备:继承父旋转,更新世界坐标记录 */
for (Device *child : getAllChildren(armDevice)) {
// 子设备挂在 armDevice->entity 下,自动继承旋转
QMatrix4x4 worldMat = child->transform->matrix(); // 局部→世界矩阵
child->worldPosition = worldMat.column(3).toVector3D();
}
}
/**
* @brief 摇臂围绕自身局部原点绕Y轴旋转带动子设备
* 首次调用记录初始状态0°时复位其他角度计算旋转矩阵并应用
* @param deviceName 摇臂名称
* @param targetYAngle 目标Y轴角度
*/
void ObjLoader::rotateArmToAbsoluteYAngle(const QString &deviceName, float targetYAngle)
{
Device *armDevice = getDevice(deviceName);
if (!armDevice || !armDevice->isInitialized) {
m_lastError = "摇臂设备不存在或未初始化: " + deviceName;
qCritical() << "[错误]" << m_lastError;
return;
}
/* 1. 首次调用时记录初始状态(只需一次) */
if (armDevice->initialRotation == QVector3D()) {
armDevice->initialRotation = QVector3D(0, 0, 0);
armDevice->initialWorldPosition = armDevice->worldPosition;
armDevice->initialTransformMatrix = armDevice->transform->matrix();
armDevice->initialModelTransformMatrix = armDevice->modelTransform->matrix();
}
/* 2. 0°复位 */
if (qFuzzyCompare(targetYAngle, 0.0f)) {
armDevice->worldPosition = armDevice->initialWorldPosition;
armDevice->currentRotation = armDevice->initialRotation;
armDevice->transform->setMatrix(armDevice->initialTransformMatrix);
armDevice->modelTransform->setMatrix(armDevice->initialModelTransformMatrix);
for (Device *child : getAllChildren(armDevice)) {
child->worldPosition = child->initialWorldPosition;
child->transform->setMatrix(child->initialTransformMatrix);
child->modelTransform->setMatrix(child->initialModelTransformMatrix);
}
return;
}
/* 3. 计算旋转矩阵绕摇臂自身局部原点0,0,0的Y轴 */
QMatrix4x4 rotMat;
rotMat.setToIdentity();
rotMat.rotate(targetYAngle, 0, 1, 0); // 仅 Y 轴
/* 4. 应用旋转到外层 transform平移+旋转) */
armDevice->currentRotation.setY(targetYAngle);
armDevice->transform->setRotationY(targetYAngle); // 外层只转 Y
// 保持原平移不变
armDevice->transform->setTranslation(armDevice->worldPosition);
/* 5. 同步子设备:继承父旋转,更新世界坐标记录 */
for (Device *child : getAllChildren(armDevice)) {
// 子设备挂在 armDevice->entity 下,自动继承旋转
QMatrix4x4 worldMat = child->transform->matrix(); // 局部→世界矩阵
child->worldPosition = worldMat.column(3).toVector3D();
}
}
/**
* @brief 调整子器件相对父器件的偏移量
* 用于更新子器件相对于其父器件的位置偏移,同时同步更新世界坐标
* @param childDeviceName 子器件名称
* @param newOffset 新的相对偏移量(父器件局部坐标系下)
*/
void ObjLoader::adjustChildDeviceOffset(const QString &childDeviceName, const QVector3D &newOffset)
{
// 获取子器件指针并验证有效性
Device *childDevice = getDevice(childDeviceName);
if (!childDevice || !childDevice->isInitialized || childDevice->parentDeviceName.isEmpty()) {
m_lastError = "子模型不存在或无父级: " + childDeviceName;
return;
}
// 更新相对偏移量并应用到变换组件
childDevice->offsetToParent = newOffset;
childDevice->transform->setTranslation(newOffset);
// 根据父器件位置更新子器件的世界坐标记录
Device *parentDevice = getDevice(childDevice->parentDeviceName);
if (parentDevice) {
childDevice->worldPosition = parentDevice->worldPosition + newOffset;
}
}
/**
* @brief 解析MTL材质文件
* 从MTL文件中提取材质属性环境光、漫反射、高光等并存储到材质映射表
* @param filePath MTL文件路径
* @return 解析成功返回true失败返回false
*/
bool ObjLoader::parseMtlFile(const QString &filePath)
{
QFile mtlFile(filePath);
// 尝试打开MTL文件
if (!mtlFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
m_lastError = "无法打开MTL文件: " + filePath;
return false;
}
// 获取MTL文件所在目录用于处理纹理路径
QString baseDir = QFileInfo(filePath).path() + "/";
Material currentMaterial; // 当前正在解析的材质
bool hasMaterial = false; // 是否正在处理一个材质
QTextStream in(&mtlFile);
while (!in.atEnd()) {
QString line = in.readLine().trimmed();
// 跳过空行和注释行
if (line.isEmpty() || line.startsWith('#')) continue;
// 分割行内容(忽略空元素)
QVector<QString> parts = splitStr(line);
if (parts.isEmpty()) continue;
QString type = parts[0]; // 指令类型
// 处理新材质定义
if (type == "newmtl" && parts.size() >= 2) {
// 如果已有未保存的材质,先保存到映射表
if (hasMaterial) {
m_materials[currentMaterial.name] = currentMaterial;
}
// 初始化新材质
currentMaterial = Material();
currentMaterial.name = parts[1];
hasMaterial = true;
}
// 处理环境光颜色(Ka)
else if (type == "Ka" && parts.size() >= 4) {
currentMaterial.ambient = QColor::fromRgbF(
parts[1].toFloat(), parts[2].toFloat(), parts[3].toFloat());
}
// 处理漫反射颜色(Kd)
else if (type == "Kd" && parts.size() >= 4) {
currentMaterial.diffuse = QColor::fromRgbF(
parts[1].toFloat(), parts[2].toFloat(), parts[3].toFloat());
}
// 处理高光颜色(Ks)
else if (type == "Ks" && parts.size() >= 4) {
currentMaterial.specular = QColor::fromRgbF(
parts[1].toFloat(), parts[2].toFloat(), parts[3].toFloat());
}
// 处理高光光泽度(Ns)
else if (type == "Ns" && parts.size() >= 2) {
currentMaterial.shininess = parts[1].toFloat();
}
// 处理漫反射纹理(map_Kd)
else if (type == "map_Kd" && parts.size() >= 2) {
currentMaterial.diffuseMap = baseDir + parts[1];
}
// 处理透明度(d)
else if (type == "d" && parts.size() >= 2) {
currentMaterial.opacity = parts[1].toFloat(); // 读取透明度值0.0~1.0
}
}
// 保存最后一个材质
if (hasMaterial) {
m_materials[currentMaterial.name] = currentMaterial;
}
mtlFile.close();
return true;
}
/**
* @brief 递归获取指定父器件的所有子器件(包括孙子辈)
* 用于获取器件的完整层级结构,支持多层级嵌套
* @param parentDevice 父器件指针
* @return 所有子器件(含嵌套子器件)的指针列表
*/
QList<Device*> ObjLoader::getAllChildren(Device *parentDevice)
{
QList<Device*> children;
if (!parentDevice) return children;
// 遍历所有器件查找直接子器件
for (auto it = m_deviceMap.begin(); it != m_deviceMap.end(); ++it) {
Device &device = it.value();
if (device.parentDeviceName == parentDevice->name && device.isInitialized) {
children.append(&device);
// 递归获取当前子器件的子器件(孙子辈)
QList<Device*> grandChildren = getAllChildren(&device);
children << grandChildren;
}
}
return children;
}
/**
* @brief 计算顶点集合的包围盒中心
* 通过找出X、Y、Z三个轴上的最小值和最大值计算包围盒的几何中心
* @param vertices 顶点坐标列表
* @return 包围盒中心坐标(局部坐标系)
*/
QVector3D ObjLoader::calculateBoundingBoxCenter(const QVector<QVector3D>& vertices)
{
if (vertices.isEmpty()) return QVector3D(0, 0, 0);
// 初始化最小/最大值为第一个顶点的坐标
float minX = vertices[0].x(), maxX = vertices[0].x();
float minY = vertices[0].y(), maxY = vertices[0].y();
float minZ = vertices[0].z(), maxZ = vertices[0].z();
// 遍历所有顶点,更新最小/最大值
for (const auto& v : vertices) {
minX = qMin(minX, v.x());
maxX = qMax(maxX, v.x());
minY = qMin(minY, v.y());
maxY = qMax(maxY, v.y());
minZ = qMin(minZ, v.z());
maxZ = qMax(maxZ, v.z());
}
// 计算并返回包围盒中心点坐标
return QVector3D(
(minX + maxX) / 2.0f,
(minY + maxY) / 2.0f,
(minZ + maxZ) / 2.0f
);
}
/**
* @brief 将摇臂的局部坐标系原点移动到指定世界坐标
* 调整摇臂的外层和内层变换,使其实体原点与目标世界坐标对齐,同时保持子器件相对位置
* @param deviceName 摇臂器件名称
* @param targetWorldPos 目标世界坐标(新的局部原点位置)
*/
void ObjLoader::moveArmLocalOriginTo(const QString &deviceName, const QVector3D &targetWorldPos)
{
// 1. 获取摇臂设备并验证
Device *armDevice = getDevice(deviceName);
if (!armDevice || !armDevice->isInitialized) {
m_lastError = "摇臂设备不存在或未初始化: " + deviceName;
qCritical() << "[错误]" << m_lastError;
return;
}
// 2. 记录当前世界位置(用于计算偏移)
QVector3D currentWorldPos = armDevice->worldPosition;
// 3. 计算原点偏移量:目标位置与当前位置的差值
QVector3D originOffset = targetWorldPos - currentWorldPos;
// 4. 调整外层变换:将实体原点移动到目标世界坐标
armDevice->transform->setTranslation(targetWorldPos);
armDevice->worldPosition = targetWorldPos;
// 5. 补偿内层变换:保持模型几何中心与局部原点的相对位置
// 原模型中心在局部坐标中为(modelCenter),通过偏移抵消外层变换的影响
QVector3D newModelTranslation = armDevice->modelTransform->translation() - originOffset;
armDevice->modelTransform->setTranslation(newModelTranslation);
// 6. 更新初始状态:确保后续旋转/复位操作基于新原点
armDevice->initialWorldPosition = targetWorldPos;
armDevice->initialTransformMatrix = armDevice->transform->matrix();
armDevice->initialModelTransformMatrix = armDevice->modelTransform->matrix();
// 7. 同步更新所有子设备(如滚筒)的位置,保持相对偏移
QList<Device*> children = getAllChildren(armDevice);
for (Device* child : children) {
// 计算子设备相对摇臂原原点的偏移量
QVector3D childRelativeOffset = child->worldPosition - currentWorldPos;
// 基于新原点计算子设备新位置
child->worldPosition = targetWorldPos + childRelativeOffset;
child->transform->setTranslation(child->worldPosition);
// 更新子设备的初始状态
child->initialWorldPosition = child->worldPosition;
child->initialTransformMatrix = child->transform->matrix();
}
// 标记局部旋转中心为新原点
armDevice->localPivot = QVector3D(0,0,0);
if(EnDebug) qDebug() << "[坐标系移动完成] 摇臂" << deviceName
<< "局部原点已移动到世界坐标:" << targetWorldPos
<< "| 偏移补偿量:" << originOffset;
}