1639 lines
64 KiB
C++
1639 lines
64 KiB
C++
#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;
|
||
}
|