Files
EJM_Display/PublicFunctions/ObjLoader.cpp

1639 lines
64 KiB
C++
Raw Normal View History

2025-09-28 17:14:34 +08:00
#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 truefalselastError()
*/
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
* XY绿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 truefalse
*/
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
* XYZ三个轴上的最小值和最大值
* @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;
}