diff --git a/src/meshgenerator.cpp b/src/meshgenerator.cpp index a1f76c5b..ca5925ae 100644 --- a/src/meshgenerator.cpp +++ b/src/meshgenerator.cpp @@ -75,55 +75,9 @@ QImage *MeshGenerator::takePartPreview(const QString &partId) return resultImage; } -void MeshGenerator::resolveBoundingBox(QRectF *mainProfile, QRectF *sideProfile) +void MeshGenerator::resolveBoundingBox(QRectF *mainProfile, QRectF *sideProfile, const QString &partId) { - float left = 0; - bool leftFirstTime = true; - float right = 0; - bool rightFirstTime = true; - float top = 0; - bool topFirstTime = true; - float bottom = 0; - bool bottomFirstTime = true; - float zLeft = 0; - bool zLeftFirstTime = true; - float zRight = 0; - bool zRightFirstTime = true; - for (const auto &nodeIt: m_snapshot->nodes) { - float radius = valueOfKeyInMapOrEmpty(nodeIt.second, "radius").toFloat(); - float x = valueOfKeyInMapOrEmpty(nodeIt.second, "x").toFloat(); - float y = valueOfKeyInMapOrEmpty(nodeIt.second, "y").toFloat(); - float z = valueOfKeyInMapOrEmpty(nodeIt.second, "z").toFloat(); - if (leftFirstTime || x - radius < left) { - left = x - radius; - leftFirstTime = false; - } - if (topFirstTime || y - radius < top) { - top = y - radius; - topFirstTime = false; - } - if (rightFirstTime || x + radius > right) { - right = x + radius; - rightFirstTime = false; - } - if (bottomFirstTime || y + radius > bottom) { - bottom = y + radius; - bottomFirstTime = false; - } - if (zLeftFirstTime || z - radius < zLeft) { - zLeft = z - radius; - zLeftFirstTime = false; - } - if (zRightFirstTime || z + radius > zRight) { - zRight = z + radius; - zRightFirstTime = false; - } - } - *mainProfile = QRectF(QPointF(left, top), QPointF(right, bottom)); - *sideProfile = QRectF(QPointF(zLeft, top), QPointF(zRight, bottom)); - qDebug() << "resolveBoundingBox left:" << left << "top:" << top << "right:" << right << "bottom:" << bottom << " zLeft:" << zLeft << "zRight:" << zRight; - qDebug() << "mainHeight:" << mainProfile->height() << "mainWidth:" << mainProfile->width(); - qDebug() << "sideHeight:" << sideProfile->height() << "sideWidth:" << sideProfile->width(); + m_snapshot->resolveBoundingBox(mainProfile, sideProfile, partId); } void MeshGenerator::process() @@ -145,6 +99,19 @@ void MeshGenerator::process() float mainProfileMiddleX = mainProfile.x() + mainProfile.width() / 2; float sideProfileMiddleX = sideProfile.x() + sideProfile.width() / 2; float mainProfileMiddleY = mainProfile.y() + mainProfile.height() / 2; + float originX = valueOfKeyInMapOrEmpty(m_snapshot->canvas, "originX").toFloat(); + float originY = valueOfKeyInMapOrEmpty(m_snapshot->canvas, "originY").toFloat(); + float originZ = valueOfKeyInMapOrEmpty(m_snapshot->canvas, "originZ").toFloat(); + bool originSettled = false; + if (originX > 0 && originY > 0 && originZ > 0) { + qDebug() << "Use settled origin: " << originX << originY << originZ << " calculated:" << mainProfileMiddleX << mainProfileMiddleY << sideProfileMiddleX; + mainProfileMiddleX = originX; + mainProfileMiddleY = originY; + sideProfileMiddleX = originZ; + originSettled = true; + } else { + qDebug() << "No settled origin, calculated:" << mainProfileMiddleX << mainProfileMiddleY << sideProfileMiddleX; + } for (const auto &partIdIt: m_snapshot->partIdList) { const auto &part = m_snapshot->parts.find(partIdIt); @@ -241,7 +208,19 @@ void MeshGenerator::process() int meshId = meshlite_bmesh_generate_mesh(meshliteContext, bmeshId); if (meshlite_bmesh_error_count(meshliteContext, bmeshId) != 0) broken = true; - bool subdived = isTrueValueString(part->second["subdived"]); + bool xMirrored = isTrueValueString(valueOfKeyInMapOrEmpty(part->second, "xMirrored")); + bool zMirrored = isTrueValueString(valueOfKeyInMapOrEmpty(part->second, "zMirrored")); + int xMirroredMeshId = 0; + int zMirroredMeshId = 0; + if (xMirrored || zMirrored) { + if (xMirrored) { + xMirroredMeshId = meshlite_mirror_in_x(meshliteContext, meshId, 0); + } + if (zMirrored) { + zMirroredMeshId = meshlite_mirror_in_z(meshliteContext, meshId, 0); + } + } + bool subdived = isTrueValueString(valueOfKeyInMapOrEmpty(part->second, "subdived")); if (m_requirePartPreviewMap.find(partIdIt) != m_requirePartPreviewMap.end()) { ModelOfflineRender *render = m_partPreviewRenderMap[partIdIt]; int trimedMeshId = meshlite_trim(meshliteContext, meshId, 1); @@ -255,10 +234,19 @@ void MeshGenerator::process() QImage *image = new QImage(render->toImage(QSize(Theme::previewImageSize, Theme::previewImageSize))); m_partPreviewMap[partIdIt] = image; } - if (subdived) + if (subdived) { subdivMeshIds.push_back(meshId); - else + if (xMirroredMeshId) + subdivMeshIds.push_back(xMirroredMeshId); + if (zMirroredMeshId) + subdivMeshIds.push_back(zMirroredMeshId); + } else { meshIds.push_back(meshId); + if (xMirroredMeshId) + meshIds.push_back(xMirroredMeshId); + if (zMirroredMeshId) + meshIds.push_back(zMirroredMeshId); + } } if (!subdivMeshIds.empty()) { @@ -307,8 +295,11 @@ void MeshGenerator::process() QImage *image = new QImage(m_previewRender->toImage(QSize(Theme::previewImageSize, Theme::previewImageSize))); m_preview = image; } - int trimedMeshId = meshlite_trim(meshliteContext, mergedMeshId, 1); - m_mesh = new Mesh(meshliteContext, trimedMeshId, broken); + int finalMeshId = mergedMeshId; + if (!originSettled) { + finalMeshId = meshlite_trim(meshliteContext, mergedMeshId, 1); + } + m_mesh = new Mesh(meshliteContext, finalMeshId, broken); } if (m_previewRender) { diff --git a/src/meshgenerator.h b/src/meshgenerator.h index 0699c019..eab33e32 100644 --- a/src/meshgenerator.h +++ b/src/meshgenerator.h @@ -36,7 +36,7 @@ private: std::map m_partPreviewRenderMap; QThread *m_thread; private: - void resolveBoundingBox(QRectF *mainProfile, QRectF *sideProfile); + void resolveBoundingBox(QRectF *mainProfile, QRectF *sideProfile, const QString &partId=QString()); static bool enableDebug; }; diff --git a/src/skeletondocument.cpp b/src/skeletondocument.cpp index 64980491..9862f65c 100644 --- a/src/skeletondocument.cpp +++ b/src/skeletondocument.cpp @@ -13,11 +13,16 @@ unsigned long SkeletonDocument::m_maxSnapshot = 1000; SkeletonDocument::SkeletonDocument() : + // public + originX(0), + originY(0), + originZ(0), + editMode(SkeletonDocumentEditMode::Select), + // private m_resultMeshIsObsolete(false), - m_resultMesh(nullptr), m_meshGenerator(nullptr), - m_batchChangeRefCount(0), - editMode(SkeletonDocumentEditMode::Select) + m_resultMesh(nullptr), + m_batchChangeRefCount(0) { } @@ -285,6 +290,11 @@ const SkeletonEdge *SkeletonDocument::findEdgeByNodes(QUuid firstNodeId, QUuid s return nullptr; } +bool SkeletonDocument::originSettled() const +{ + return originX > 0 && originY > 0 && originZ > 0; +} + void SkeletonDocument::addEdge(QUuid fromNodeId, QUuid toNodeId) { if (findEdgeByNodes(fromNodeId, toNodeId)) { @@ -431,6 +441,15 @@ void SkeletonDocument::moveNodeBy(QUuid nodeId, float x, float y, float z) emit skeletonChanged(); } +void SkeletonDocument::moveOriginBy(float x, float y, float z) +{ + originX += x; + originY += y; + originZ += z; + emit originChanged(); + emit skeletonChanged(); +} + void SkeletonDocument::setNodeOrigin(QUuid nodeId, float x, float y, float z) { auto it = nodeMap.find(nodeId); @@ -552,6 +571,8 @@ void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot, const std::setparts[part["id"]] = part; @@ -591,10 +612,26 @@ void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot, const std::setpartIdList.push_back(partIdIt.toString()); } + std::map canvas; + canvas["originX"] = QString::number(originX); + canvas["originY"] = QString::number(originY); + canvas["originZ"] = QString::number(originZ); + snapshot->canvas = canvas; } void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot) { + const auto &originXit = snapshot.canvas.find("originX"); + const auto &originYit = snapshot.canvas.find("originY"); + const auto &originZit = snapshot.canvas.find("originZ"); + if (originXit != snapshot.canvas.end() && + originYit != snapshot.canvas.end() && + originZit != snapshot.canvas.end()) { + originX = originXit->second.toFloat(); + originY = originYit->second.toFloat(); + originZ = originZit->second.toFloat(); + } + std::map oldNewIdMap; for (const auto &partKv : snapshot.parts) { SkeletonPart part; @@ -604,6 +641,8 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot) part.locked = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "locked")); part.subdived = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "subdived")); part.disabled = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "disabled")); + part.xMirrored = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "xMirrored")); + part.zMirrored = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "zMirrored")); partMap[part.id] = part; } for (const auto &nodeKv : snapshot.nodes) { @@ -664,11 +703,15 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot) } emit partListChanged(); + emit originChanged(); emit skeletonChanged(); } void SkeletonDocument::reset() { + originX = 0; + originY = 0; + originZ = 0; nodeMap.clear(); edgeMap.clear(); partMap.clear(); @@ -824,6 +867,51 @@ void SkeletonDocument::setPartDisableState(QUuid partId, bool disabled) emit skeletonChanged(); } +void SkeletonDocument::settleOrigin() +{ + if (originSettled()) + return; + SkeletonSnapshot snapshot; + toSnapshot(&snapshot); + QRectF mainProfile; + QRectF sideProfile; + snapshot.resolveBoundingBox(&mainProfile, &sideProfile); + originX = mainProfile.x() + mainProfile.width() / 2; + originY = mainProfile.y() + mainProfile.height() / 2; + originZ = sideProfile.x() + sideProfile.width() / 2; + emit originChanged(); +} + +void SkeletonDocument::setPartXmirrorState(QUuid partId, bool mirrored) +{ + auto part = partMap.find(partId); + if (part == partMap.end()) { + qDebug() << "Part not found:" << partId; + return; + } + if (part->second.xMirrored == mirrored) + return; + part->second.xMirrored = mirrored; + settleOrigin(); + emit partXmirrorStateChanged(partId); + emit skeletonChanged(); +} + +void SkeletonDocument::setPartZmirrorState(QUuid partId, bool mirrored) +{ + auto part = partMap.find(partId); + if (part == partMap.end()) { + qDebug() << "Part not found:" << partId; + return; + } + if (part->second.zMirrored == mirrored) + return; + part->second.zMirrored = mirrored; + settleOrigin(); + emit partZmirrorStateChanged(partId); + emit skeletonChanged(); +} + void SkeletonDocument::saveSnapshot() { if (m_undoItems.size() + 1 > m_maxSnapshot) diff --git a/src/skeletondocument.h b/src/skeletondocument.h index f6487bf0..4af46f75 100644 --- a/src/skeletondocument.h +++ b/src/skeletondocument.h @@ -68,13 +68,17 @@ public: bool locked; bool subdived; bool disabled; + bool xMirrored; + bool zMirrored; QImage preview; std::vector nodeIds; SkeletonPart(const QUuid &withId=QUuid()) : visible(true), locked(false), subdived(false), - disabled(false) + disabled(false), + xMirrored(false), + zMirrored(false) { id = withId.isNull() ? QUuid::createUuid() : withId; } @@ -84,6 +88,8 @@ public: locked = other.locked; subdived = other.subdived; disabled = other.disabled; + xMirrored = other.xMirrored; + zMirrored = other.zMirrored; } }; @@ -133,7 +139,15 @@ signals: void partVisibleStateChanged(QUuid partId); void partSubdivStateChanged(QUuid partId); void partDisableStateChanged(QUuid partId); + void partXmirrorStateChanged(QUuid partId); + void partZmirrorStateChanged(QUuid partId); void cleanup(); + void originChanged(); +public: // need initialize + float originX; + float originY; + float originZ; + SkeletonDocumentEditMode editMode; public: SkeletonDocument(); ~SkeletonDocument(); @@ -142,7 +156,7 @@ public: std::map edgeMap; std::vector partIds; QImage turnaround; - SkeletonDocumentEditMode editMode; + QImage preview; void toSnapshot(SkeletonSnapshot *snapshot, const std::set &limitNodeIds=std::set()) const; void fromSnapshot(const SkeletonSnapshot &snapshot); void addFromSnapshot(const SkeletonSnapshot &snapshot); @@ -151,13 +165,13 @@ public: const SkeletonPart *findPart(QUuid partId) const; const SkeletonEdge *findEdgeByNodes(QUuid firstNodeId, QUuid secondNodeId) const; Mesh *takeResultMesh(); - QImage preview; void updateTurnaround(const QImage &image); bool hasPastableContentInClipboard() const; bool undoable() const; bool redoable() const; bool isNodeEditable(QUuid nodeId) const; bool isEdgeEditable(QUuid edgeId) const; + bool originSettled() const; public slots: void removeNode(QUuid nodeId); void removeEdge(QUuid edgeId); @@ -167,6 +181,7 @@ public slots: void moveNodeBy(QUuid nodeId, float x, float y, float z); void setNodeOrigin(QUuid nodeId, float x, float y, float z); void setNodeRadius(QUuid nodeId, float radius); + void moveOriginBy(float x, float y, float z); void addEdge(QUuid fromNodeId, QUuid toNodeId); void setEditMode(SkeletonDocumentEditMode mode); void uiReady(); @@ -176,6 +191,8 @@ public slots: void setPartVisibleState(QUuid partId, bool visible); void setPartSubdivState(QUuid partId, bool subdived); void setPartDisableState(QUuid partId, bool disabled); + void setPartXmirrorState(QUuid partId, bool mirrored); + void setPartZmirrorState(QUuid partId, bool mirrored); void saveSnapshot(); void undo(); void redo(); @@ -190,14 +207,16 @@ private: void splitPartByEdge(std::vector> *groups, QUuid edgeId); bool isPartReadonly(QUuid partId) const; QUuid createNode(float x, float y, float z, float radius, QUuid fromNodeId); -private: + void settleOrigin(); +private: // need initialize bool m_resultMeshIsObsolete; MeshGenerator *m_meshGenerator; Mesh *m_resultMesh; + int m_batchChangeRefCount; +private: static unsigned long m_maxSnapshot; std::deque m_undoItems; std::deque m_redoItems; - int m_batchChangeRefCount; }; #endif diff --git a/src/skeletondocumentwindow.cpp b/src/skeletondocumentwindow.cpp index 518b1147..de5a3cf1 100644 --- a/src/skeletondocumentwindow.cpp +++ b/src/skeletondocumentwindow.cpp @@ -360,6 +360,7 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : connect(graphicsWidget, &SkeletonGraphicsWidget::batchChangeBegin, m_document, &SkeletonDocument::batchChangeBegin); connect(graphicsWidget, &SkeletonGraphicsWidget::batchChangeEnd, m_document, &SkeletonDocument::batchChangeEnd); connect(graphicsWidget, &SkeletonGraphicsWidget::breakEdge, m_document, &SkeletonDocument::breakEdge); + connect(graphicsWidget, &SkeletonGraphicsWidget::moveOriginBy, m_document, &SkeletonDocument::moveOriginBy); connect(graphicsWidget, &SkeletonGraphicsWidget::changeTurnaround, this, &SkeletonDocumentWindow::changeTurnaround); connect(graphicsWidget, &SkeletonGraphicsWidget::save, this, &SkeletonDocumentWindow::save); @@ -374,6 +375,7 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : connect(m_document, &SkeletonDocument::nodeOriginChanged, graphicsWidget, &SkeletonGraphicsWidget::nodeOriginChanged); connect(m_document, &SkeletonDocument::partVisibleStateChanged, graphicsWidget, &SkeletonGraphicsWidget::partVisibleStateChanged); connect(m_document, &SkeletonDocument::cleanup, graphicsWidget, &SkeletonGraphicsWidget::removeAllContent); + connect(m_document, &SkeletonDocument::originChanged, graphicsWidget, &SkeletonGraphicsWidget::originChanged); connect(m_document, &SkeletonDocument::partListChanged, partListWidget, &SkeletonPartListWidget::partListChanged); connect(m_document, &SkeletonDocument::partPreviewChanged, partListWidget, &SkeletonPartListWidget::partPreviewChanged); @@ -381,6 +383,8 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : connect(m_document, &SkeletonDocument::partVisibleStateChanged, partListWidget, &SkeletonPartListWidget::partVisibleStateChanged); connect(m_document, &SkeletonDocument::partSubdivStateChanged, partListWidget, &SkeletonPartListWidget::partSubdivStateChanged); connect(m_document, &SkeletonDocument::partDisableStateChanged, partListWidget, &SkeletonPartListWidget::partDisableStateChanged); + connect(m_document, &SkeletonDocument::partXmirrorStateChanged, partListWidget, &SkeletonPartListWidget::partXmirrorStateChanged); + connect(m_document, &SkeletonDocument::partZmirrorStateChanged, partListWidget, &SkeletonPartListWidget::partZmirrorStateChanged); connect(m_document, &SkeletonDocument::cleanup, partListWidget, &SkeletonPartListWidget::partListChanged); connect(m_document, &SkeletonDocument::skeletonChanged, m_document, &SkeletonDocument::generateMesh); diff --git a/src/skeletongraphicswidget.cpp b/src/skeletongraphicswidget.cpp index ca6b6880..ad47f1a7 100644 --- a/src/skeletongraphicswidget.cpp +++ b/src/skeletongraphicswidget.cpp @@ -16,22 +16,28 @@ SkeletonGraphicsWidget::SkeletonGraphicsWidget(const SkeletonDocument *document) : m_document(document), + m_backgroundItem(nullptr), m_turnaroundChanged(false), m_turnaroundLoader(nullptr), m_dragStarted(false), + m_moveStarted(false), m_cursorNodeItem(nullptr), m_cursorEdgeItem(nullptr), m_addFromNodeItem(nullptr), - m_moveStarted(false), m_hoveredNodeItem(nullptr), m_hoveredEdgeItem(nullptr), - m_lastAddedX(0), - m_lastAddedY(0), - m_lastAddedZ(0), + m_lastAddedX(false), + m_lastAddedY(false), + m_lastAddedZ(false), m_selectionItem(nullptr), m_rangeSelectionStarted(false), m_mouseEventFromSelf(false), - m_lastRot(0) + m_moveHappened(false), + m_lastRot(0), + m_mainOriginItem(nullptr), + m_sideOriginItem(nullptr), + m_hoveredOriginItem(nullptr), + m_checkedOriginItem(nullptr) { setRenderHint(QPainter::Antialiasing, false); setBackgroundBrush(QBrush(QWidget::palette().color(QWidget::backgroundRole()), Qt::SolidPattern)); @@ -60,6 +66,14 @@ SkeletonGraphicsWidget::SkeletonGraphicsWidget(const SkeletonDocument *document) m_selectionItem->hide(); scene()->addItem(m_selectionItem); + m_mainOriginItem = new SkeletonGraphicsOriginItem(SkeletonProfile::Main); + m_mainOriginItem->hide(); + scene()->addItem(m_mainOriginItem); + + m_sideOriginItem = new SkeletonGraphicsOriginItem(SkeletonProfile::Side); + m_sideOriginItem->hide(); + scene()->addItem(m_sideOriginItem); + scene()->setSceneRect(rect()); setMouseTracking(true); @@ -432,19 +446,30 @@ bool SkeletonGraphicsWidget::mouseMove(QMouseEvent *event) SkeletonDocumentEditMode::Add == m_document->editMode) { SkeletonGraphicsNodeItem *newHoverNodeItem = nullptr; SkeletonGraphicsEdgeItem *newHoverEdgeItem = nullptr; + SkeletonGraphicsOriginItem *newHoverOriginItem = nullptr; QList items = scene()->items(mouseEventScenePos(event)); for (auto it = items.begin(); it != items.end(); it++) { QGraphicsItem *item = *it; if (item->data(0) == "node") { newHoverNodeItem = (SkeletonGraphicsNodeItem *)item; - break; } else if (item->data(0) == "edge") { newHoverEdgeItem = (SkeletonGraphicsEdgeItem *)item; + } else if (item->data(0) == "origin") { + newHoverOriginItem = (SkeletonGraphicsOriginItem *)item; } } if (newHoverNodeItem) { newHoverEdgeItem = nullptr; } + if (newHoverOriginItem != m_hoveredOriginItem) { + if (nullptr != m_hoveredOriginItem) { + m_hoveredOriginItem->setHovered(false); + } + m_hoveredOriginItem = newHoverOriginItem; + if (nullptr != m_hoveredOriginItem) { + m_hoveredOriginItem->setHovered(true); + } + } if (newHoverNodeItem != m_hoveredNodeItem) { if (nullptr != m_hoveredNodeItem) { m_hoveredNodeItem->setHovered(false); @@ -481,18 +506,28 @@ bool SkeletonGraphicsWidget::mouseMove(QMouseEvent *event) } if (SkeletonDocumentEditMode::Select == m_document->editMode) { - if (m_moveStarted && !m_rangeSelectionSet.empty()) { - QPointF mouseScenePos = mouseEventScenePos(event); - float byX = mouseScenePos.x() - m_lastScenePos.x(); - float byY = mouseScenePos.y() - m_lastScenePos.y(); - if (QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier)) { - rotateSelected(byX); - } else { - moveSelected(byX, byY); + if (m_moveStarted) { + if (m_checkedOriginItem) { + QPointF mouseScenePos = mouseEventScenePos(event); + float byX = mouseScenePos.x() - m_lastScenePos.x(); + float byY = mouseScenePos.y() - m_lastScenePos.y(); + moveCheckedOrigin(byX, byY); + m_lastScenePos = mouseScenePos; + m_moveHappened = true; + return true; + } else if (!m_rangeSelectionSet.empty()) { + QPointF mouseScenePos = mouseEventScenePos(event); + float byX = mouseScenePos.x() - m_lastScenePos.x(); + float byY = mouseScenePos.y() - m_lastScenePos.y(); + if (QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier)) { + rotateSelected(byX); + } else { + moveSelected(byX, byY); + } + m_lastScenePos = mouseScenePos; + m_moveHappened = true; + return true; } - m_lastScenePos = mouseScenePos; - m_moveHappened = true; - return true; } } @@ -529,6 +564,19 @@ void SkeletonGraphicsWidget::rotateSelected(int degree) } } +void SkeletonGraphicsWidget::moveCheckedOrigin(float byX, float byY) +{ + if (!m_checkedOriginItem) + return; + byX = sceneRadiusToUnified(byX); + byY = sceneRadiusToUnified(byY); + if (SkeletonProfile::Main == m_checkedOriginItem->profile()) { + emit moveOriginBy(byX, byY, 0); + } else { + emit moveOriginBy(0, byY, byX); + } +} + void SkeletonGraphicsWidget::moveSelected(float byX, float byY) { if (m_rangeSelectionSet.empty()) @@ -760,8 +808,25 @@ bool SkeletonGraphicsWidget::mousePress(QMouseEvent *event) return true; } } else if (SkeletonDocumentEditMode::Select == m_document->editMode) { - //if (m_mouseEventFromSelf) { - bool processed = false; + bool processed = false; + if (m_hoveredOriginItem != m_checkedOriginItem) { + if (m_checkedOriginItem) { + m_checkedOriginItem->setChecked(false); + m_checkedOriginItem->setHovered(false); + } + m_checkedOriginItem = m_hoveredOriginItem; + if (m_checkedOriginItem) { + m_checkedOriginItem->setChecked(true); + } + } + if (m_checkedOriginItem) { + if (!m_moveStarted) { + m_moveStarted = true; + m_lastScenePos = mouseEventScenePos(event); + m_moveHappened = false; + processed = true; + } + } else { if ((nullptr == m_hoveredNodeItem || m_rangeSelectionSet.find(m_hoveredNodeItem) == m_rangeSelectionSet.end()) && (nullptr == m_hoveredEdgeItem || m_rangeSelectionSet.find(m_hoveredEdgeItem) == m_rangeSelectionSet.end())) { if (!QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ControlModifier)) { @@ -792,10 +857,10 @@ bool SkeletonGraphicsWidget::mousePress(QMouseEvent *event) } } } - if (processed) { - return true; - } - //} + } + if (processed) { + return true; + } } } @@ -938,24 +1003,44 @@ bool SkeletonGraphicsWidget::keyPress(QKeyEvent *event) emit groupOperationAdded(); } } else if (event->key() == Qt::Key_Left) { - if (SkeletonDocumentEditMode::Select == m_document->editMode && hasSelection()) { - moveSelected(-1, 0); - emit groupOperationAdded(); + if (SkeletonDocumentEditMode::Select == m_document->editMode) { + if (m_checkedOriginItem) { + moveCheckedOrigin(-1, 0); + emit groupOperationAdded(); + } else if (hasSelection()) { + moveSelected(-1, 0); + emit groupOperationAdded(); + } } } else if (event->key() == Qt::Key_Right) { - if (SkeletonDocumentEditMode::Select == m_document->editMode && hasSelection()) { - moveSelected(1, 0); - emit groupOperationAdded(); + if (SkeletonDocumentEditMode::Select == m_document->editMode) { + if (m_checkedOriginItem) { + moveCheckedOrigin(1, 0); + emit groupOperationAdded(); + } else if (hasSelection()) { + moveSelected(1, 0); + emit groupOperationAdded(); + } } } else if (event->key() == Qt::Key_Up) { - if (SkeletonDocumentEditMode::Select == m_document->editMode && hasSelection()) { - moveSelected(0, -1); - emit groupOperationAdded(); + if (SkeletonDocumentEditMode::Select == m_document->editMode) { + if (m_checkedOriginItem) { + moveCheckedOrigin(0, -1); + emit groupOperationAdded(); + } else if (hasSelection()) { + moveSelected(0, -1); + emit groupOperationAdded(); + } } } else if (event->key() == Qt::Key_Down) { - if (SkeletonDocumentEditMode::Select == m_document->editMode && hasSelection()) { - moveSelected(0, 1); - emit groupOperationAdded(); + if (SkeletonDocumentEditMode::Select == m_document->editMode) { + if (m_checkedOriginItem) { + moveCheckedOrigin(0, 1); + emit groupOperationAdded(); + } else if (hasSelection()) { + moveSelected(0, 1); + emit groupOperationAdded(); + } } } else if (event->key() == Qt::Key_BracketLeft) { if (SkeletonDocumentEditMode::Select == m_document->editMode && hasSelection()) { @@ -971,6 +1056,19 @@ bool SkeletonGraphicsWidget::keyPress(QKeyEvent *event) return false; } +void SkeletonGraphicsWidget::originChanged() +{ + if (!m_document->originSettled()) { + m_mainOriginItem->hide(); + m_sideOriginItem->hide(); + return; + } + m_mainOriginItem->setOrigin(scenePosFromUnified(QPointF(m_document->originX, m_document->originY))); + m_sideOriginItem->setOrigin(scenePosFromUnified(QPointF(m_document->originZ, m_document->originY))); + m_mainOriginItem->show(); + m_sideOriginItem->show(); +} + void SkeletonGraphicsWidget::nodeAdded(QUuid nodeId) { const SkeletonNode *node = m_document->findNode(nodeId); diff --git a/src/skeletongraphicswidget.h b/src/skeletongraphicswidget.h index b828de62..3d824405 100644 --- a/src/skeletongraphicswidget.h +++ b/src/skeletongraphicswidget.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -15,6 +16,88 @@ #include "theme.h" #include "util.h" +class SkeletonGraphicsOriginItem : public QGraphicsPolygonItem +{ +public: + SkeletonGraphicsOriginItem(SkeletonProfile profile=SkeletonProfile::Unknown) : + m_profile(profile), + m_hovered(false), + m_checked(false) + { + setData(0, "origin"); + } + void updateAppearance() + { + QColor color = Theme::white; + + switch (m_profile) + { + case SkeletonProfile::Unknown: + break; + case SkeletonProfile::Main: + color = Theme::red; + break; + case SkeletonProfile::Side: + color = Theme::green; + break; + } + + QColor penColor = color; + penColor.setAlphaF(m_checked ? Theme::checkedAlpha : Theme::normalAlpha); + QPen pen(penColor); + pen.setWidth(0); + setPen(pen); + + QColor brushColor = color; + brushColor.setAlphaF((m_checked || m_hovered) ? Theme::checkedAlpha : Theme::normalAlpha); + QBrush brush(brushColor); + setBrush(brush); + } + void setOrigin(QPointF point) + { + m_origin = point; + QPolygonF triangle; + const int triangleRadius = 10; + triangle.append(QPointF(point.x() - triangleRadius, point.y())); + triangle.append(QPointF(point.x() + triangleRadius, point.y())); + triangle.append(QPointF(point.x(), point.y() - triangleRadius)); + triangle.append(QPointF(point.x() - triangleRadius, point.y())); + setPolygon(triangle); + updateAppearance(); + } + QPointF origin() + { + return m_origin; + } + SkeletonProfile profile() + { + return m_profile; + } + void setHovered(bool hovered) + { + m_hovered = hovered; + updateAppearance(); + } + void setChecked(bool checked) + { + m_checked = checked; + updateAppearance(); + } + bool checked() + { + return m_checked; + } + bool hovered() + { + return m_hovered; + } +private: + SkeletonProfile m_profile; + bool m_hovered; + bool m_checked; + QPointF m_origin; +}; + class SkeletonGraphicsSelectionItem : public QGraphicsRectItem { public: @@ -271,6 +354,7 @@ signals: void open(); void exportResult(); void breakEdge(QUuid edgeId); + void moveOriginBy(float x, float y, float z); public: SkeletonGraphicsWidget(const SkeletonDocument *document); std::map> nodeItemMap; @@ -327,6 +411,8 @@ public slots: void zoomSelected(float delta); void scaleSelected(float delta); void moveSelected(float byX, float byY); + void moveCheckedOrigin(float byX, float byY); + void originChanged(); private slots: void turnaroundImageReady(); private: @@ -344,15 +430,13 @@ private: bool isSingleNodeSelected(); void addItemToRangeSelection(QGraphicsItem *item); void removeItemFromRangeSelection(QGraphicsItem *item); -private: - QGraphicsPixmapItem *m_backgroundItem; +private: //need initalize const SkeletonDocument *m_document; + QGraphicsPixmapItem *m_backgroundItem; bool m_turnaroundChanged; TurnaroundLoader *m_turnaroundLoader; bool m_dragStarted; - QPoint m_lastGlobalPos; bool m_moveStarted; - QPointF m_lastScenePos; SkeletonGraphicsNodeItem *m_cursorNodeItem; SkeletonGraphicsEdgeItem *m_cursorEdgeItem; SkeletonGraphicsNodeItem *m_addFromNodeItem; @@ -362,12 +446,19 @@ private: float m_lastAddedY; float m_lastAddedZ; SkeletonGraphicsSelectionItem *m_selectionItem; - QPointF m_rangeSelectionStartPos; bool m_rangeSelectionStarted; - std::set m_rangeSelectionSet; bool m_mouseEventFromSelf; bool m_moveHappened; int m_lastRot; + SkeletonGraphicsOriginItem *m_mainOriginItem; + SkeletonGraphicsOriginItem *m_sideOriginItem; + SkeletonGraphicsOriginItem *m_hoveredOriginItem; + SkeletonGraphicsOriginItem *m_checkedOriginItem; +private: + std::set m_rangeSelectionSet; + QPoint m_lastGlobalPos; + QPointF m_lastScenePos; + QPointF m_rangeSelectionStartPos; }; class SkeletonGraphicsContainerWidget : public QWidget diff --git a/src/skeletonpartlistwidget.cpp b/src/skeletonpartlistwidget.cpp index bbf2b7c3..e90e061b 100644 --- a/src/skeletonpartlistwidget.cpp +++ b/src/skeletonpartlistwidget.cpp @@ -24,6 +24,14 @@ SkeletonPartWidget::SkeletonPartWidget(const SkeletonDocument *document, QUuid p updateButton(m_disableButton, QChar(fa::toggleon), false); initButton(m_disableButton); + m_xMirrorButton = new QPushButton(); + updateButton(m_xMirrorButton, QChar(fa::quoteleft), false); + initButton(m_xMirrorButton); + + m_zMirrorButton = new QPushButton(); + updateButton(m_zMirrorButton, QChar(fa::quoteright), false); + initButton(m_zMirrorButton); + m_previewLabel = new QLabel; QHBoxLayout *miniTopToolLayout = new QHBoxLayout; @@ -38,6 +46,8 @@ SkeletonPartWidget::SkeletonPartWidget(const SkeletonDocument *document, QUuid p miniBottomToolLayout->setSpacing(0); miniBottomToolLayout->setContentsMargins(0, 0, 0, 0); miniBottomToolLayout->addWidget(m_disableButton); + miniBottomToolLayout->addWidget(m_xMirrorButton); + miniBottomToolLayout->addWidget(m_zMirrorButton); miniBottomToolLayout->addStretch(); QWidget *hrWidget = new QWidget; @@ -60,37 +70,61 @@ SkeletonPartWidget::SkeletonPartWidget(const SkeletonDocument *document, QUuid p connect(this, &SkeletonPartWidget::setPartVisibleState, m_document, &SkeletonDocument::setPartVisibleState); connect(this, &SkeletonPartWidget::setPartSubdivState, m_document, &SkeletonDocument::setPartSubdivState); connect(this, &SkeletonPartWidget::setPartDisableState, m_document, &SkeletonDocument::setPartDisableState); + connect(this, &SkeletonPartWidget::setPartXmirrorState, m_document, &SkeletonDocument::setPartXmirrorState); + connect(this, &SkeletonPartWidget::setPartZmirrorState, m_document, &SkeletonDocument::setPartZmirrorState); connect(m_lockButton, &QPushButton::clicked, [=]() { - if (m_lockButton->text() == QChar(fa::lock)) { - emit setPartLockState(m_partId, false); - } else { - emit setPartLockState(m_partId, true); + const SkeletonPart *part = m_document->findPart(m_partId); + if (!part) { + qDebug() << "Part not found:" << m_partId; + return; } + emit setPartLockState(m_partId, !part->locked); }); connect(m_visibleButton, &QPushButton::clicked, [=]() { - if (m_visibleButton->text() == QChar(fa::eye)) { - emit setPartVisibleState(m_partId, false); - } else { - emit setPartVisibleState(m_partId, true); + const SkeletonPart *part = m_document->findPart(m_partId); + if (!part) { + qDebug() << "Part not found:" << m_partId; + return; } + emit setPartVisibleState(m_partId, !part->visible); }); connect(m_subdivButton, &QPushButton::clicked, [=]() { - if (m_subdivButton->text() == QChar(fa::cube)) { - emit setPartSubdivState(m_partId, true); - } else { - emit setPartSubdivState(m_partId, false); + const SkeletonPart *part = m_document->findPart(m_partId); + if (!part) { + qDebug() << "Part not found:" << m_partId; + return; } + emit setPartSubdivState(m_partId, !part->subdived); }); connect(m_disableButton, &QPushButton::clicked, [=]() { - if (m_disableButton->text() == QChar(fa::link)) { - emit setPartDisableState(m_partId, true); - } else { - emit setPartDisableState(m_partId, false); + const SkeletonPart *part = m_document->findPart(m_partId); + if (!part) { + qDebug() << "Part not found:" << m_partId; + return; } + emit setPartDisableState(m_partId, !part->disabled); + }); + + connect(m_xMirrorButton, &QPushButton::clicked, [=]() { + const SkeletonPart *part = m_document->findPart(m_partId); + if (!part) { + qDebug() << "Part not found:" << m_partId; + return; + } + emit setPartXmirrorState(m_partId, !part->xMirrored); + }); + + connect(m_zMirrorButton, &QPushButton::clicked, [=]() { + const SkeletonPart *part = m_document->findPart(m_partId); + if (!part) { + qDebug() << "Part not found:" << m_partId; + return; + } + emit setPartZmirrorState(m_partId, !part->zMirrored); }); } @@ -172,6 +206,32 @@ void SkeletonPartWidget::updateDisableButton() updateButton(m_disableButton, QChar(fa::link), false); } +void SkeletonPartWidget::updateXmirrorButton() +{ + const SkeletonPart *part = m_document->findPart(m_partId); + if (!part) { + qDebug() << "Part not found:" << m_partId; + return; + } + if (part->xMirrored) + updateButton(m_xMirrorButton, QChar(fa::quoteleft), true); + else + updateButton(m_xMirrorButton, QChar(fa::quoteleft), false); +} + +void SkeletonPartWidget::updateZmirrorButton() +{ + const SkeletonPart *part = m_document->findPart(m_partId); + if (!part) { + qDebug() << "Part not found:" << m_partId; + return; + } + if (part->zMirrored) + updateButton(m_zMirrorButton, QChar(fa::quoteright), true); + else + updateButton(m_zMirrorButton, QChar(fa::quoteright), false); +} + void SkeletonPartWidget::reload() { updatePreview(); @@ -179,6 +239,8 @@ void SkeletonPartWidget::reload() updateVisibleButton(); updateSubdivButton(); updateDisableButton(); + updateXmirrorButton(); + updateZmirrorButton(); } SkeletonPartListWidget::SkeletonPartListWidget(const SkeletonDocument *document) : @@ -273,3 +335,25 @@ void SkeletonPartListWidget::partDisableStateChanged(QUuid partId) SkeletonPartWidget *widget = (SkeletonPartWidget *)itemWidget(item->second); widget->updateDisableButton(); } + +void SkeletonPartListWidget::partXmirrorStateChanged(QUuid partId) +{ + auto item = m_itemMap.find(partId); + if (item == m_itemMap.end()) { + qDebug() << "Part item not found:" << partId; + return; + } + SkeletonPartWidget *widget = (SkeletonPartWidget *)itemWidget(item->second); + widget->updateXmirrorButton(); +} + +void SkeletonPartListWidget::partZmirrorStateChanged(QUuid partId) +{ + auto item = m_itemMap.find(partId); + if (item == m_itemMap.end()) { + qDebug() << "Part item not found:" << partId; + return; + } + SkeletonPartWidget *widget = (SkeletonPartWidget *)itemWidget(item->second); + widget->updateZmirrorButton(); +} diff --git a/src/skeletonpartlistwidget.h b/src/skeletonpartlistwidget.h index 9b7e1174..fbe3545c 100644 --- a/src/skeletonpartlistwidget.h +++ b/src/skeletonpartlistwidget.h @@ -14,6 +14,8 @@ signals: void setPartVisibleState(QUuid partId, bool visible); void setPartSubdivState(QUuid partId, bool subdived); void setPartDisableState(QUuid partId, bool disabled); + void setPartXmirrorState(QUuid partId, bool mirrored); + void setPartZmirrorState(QUuid partId, bool mirrored); public: SkeletonPartWidget(const SkeletonDocument *document, QUuid partId); void reload(); @@ -22,6 +24,8 @@ public: void updateVisibleButton(); void updateSubdivButton(); void updateDisableButton(); + void updateXmirrorButton(); + void updateZmirrorButton(); private: const SkeletonDocument *m_document; QUuid m_partId; @@ -30,6 +34,8 @@ private: QPushButton *m_lockButton; QPushButton *m_subdivButton; QPushButton *m_disableButton; + QPushButton *m_xMirrorButton; + QPushButton *m_zMirrorButton; QLabel *m_nameLabel; private: void initButton(QPushButton *button); @@ -49,6 +55,8 @@ public slots: void partVisibleStateChanged(QUuid partId); void partSubdivStateChanged(QUuid partId); void partDisableStateChanged(QUuid partId); + void partXmirrorStateChanged(QUuid partId); + void partZmirrorStateChanged(QUuid partId); private: const SkeletonDocument *m_document; std::map m_itemMap; diff --git a/src/skeletonsnapshot.cpp b/src/skeletonsnapshot.cpp index 872c1717..ac7cd675 100644 --- a/src/skeletonsnapshot.cpp +++ b/src/skeletonsnapshot.cpp @@ -1,2 +1,58 @@ +#include #include "skeletonsnapshot.h" -#include +#include "util.h" + +void SkeletonSnapshot::resolveBoundingBox(QRectF *mainProfile, QRectF *sideProfile, const QString &partId) +{ + float left = 0; + bool leftFirstTime = true; + float right = 0; + bool rightFirstTime = true; + float top = 0; + bool topFirstTime = true; + float bottom = 0; + bool bottomFirstTime = true; + float zLeft = 0; + bool zLeftFirstTime = true; + float zRight = 0; + bool zRightFirstTime = true; + for (const auto &nodeIt: nodes) { + if (!partId.isEmpty() && partId != valueOfKeyInMapOrEmpty(nodeIt.second, "partId")) + continue; + float radius = valueOfKeyInMapOrEmpty(nodeIt.second, "radius").toFloat(); + float x = valueOfKeyInMapOrEmpty(nodeIt.second, "x").toFloat(); + float y = valueOfKeyInMapOrEmpty(nodeIt.second, "y").toFloat(); + float z = valueOfKeyInMapOrEmpty(nodeIt.second, "z").toFloat(); + if (leftFirstTime || x - radius < left) { + left = x - radius; + leftFirstTime = false; + } + if (topFirstTime || y - radius < top) { + top = y - radius; + topFirstTime = false; + } + if (rightFirstTime || x + radius > right) { + right = x + radius; + rightFirstTime = false; + } + if (bottomFirstTime || y + radius > bottom) { + bottom = y + radius; + bottomFirstTime = false; + } + if (zLeftFirstTime || z - radius < zLeft) { + zLeft = z - radius; + zLeftFirstTime = false; + } + if (zRightFirstTime || z + radius > zRight) { + zRight = z + radius; + zRightFirstTime = false; + } + } + *mainProfile = QRectF(QPointF(left, top), QPointF(right, bottom)); + *sideProfile = QRectF(QPointF(zLeft, top), QPointF(zRight, bottom)); + qDebug() << "resolveBoundingBox left:" << left << "top:" << top << "right:" << right << "bottom:" << bottom << " zLeft:" << zLeft << "zRight:" << zRight; + qDebug() << "mainHeight:" << mainProfile->height() << "mainWidth:" << mainProfile->width(); + qDebug() << "sideHeight:" << sideProfile->height() << "sideWidth:" << sideProfile->width(); +} + + diff --git a/src/skeletonsnapshot.h b/src/skeletonsnapshot.h index 154984f6..38555eb6 100644 --- a/src/skeletonsnapshot.h +++ b/src/skeletonsnapshot.h @@ -14,6 +14,8 @@ public: std::map> edges; std::map> parts; std::vector partIdList; +public: + void resolveBoundingBox(QRectF *mainProfile, QRectF *sideProfile, const QString &partId=QString()); }; #endif diff --git a/thirdparty/meshlite/meshlite_unstable_vc14_x64/meshlite.dll b/thirdparty/meshlite/meshlite_unstable_vc14_x64/meshlite.dll old mode 100644 new mode 100755 index 00eb5c5f..293050b6 Binary files a/thirdparty/meshlite/meshlite_unstable_vc14_x64/meshlite.dll and b/thirdparty/meshlite/meshlite_unstable_vc14_x64/meshlite.dll differ diff --git a/thirdparty/meshlite/meshlite_unstable_vc14_x64/meshlite.dll.lib b/thirdparty/meshlite/meshlite_unstable_vc14_x64/meshlite.dll.lib old mode 100644 new mode 100755 index 4c8dfbb0..93f58b64 Binary files a/thirdparty/meshlite/meshlite_unstable_vc14_x64/meshlite.dll.lib and b/thirdparty/meshlite/meshlite_unstable_vc14_x64/meshlite.dll.lib differ diff --git a/thirdparty/meshlite/meshlite_unstable_vc14_x64/meshlite.h b/thirdparty/meshlite/meshlite_unstable_vc14_x64/meshlite.h old mode 100644 new mode 100755 index bf3640b1..13dc7e04 --- a/thirdparty/meshlite/meshlite_unstable_vc14_x64/meshlite.h +++ b/thirdparty/meshlite/meshlite_unstable_vc14_x64/meshlite.h @@ -42,6 +42,8 @@ int meshlite_bmesh_error_count(void *context, int bmesh_id); int meshlite_combine_adj_faces(void *context, int mesh_id); int meshlite_combine_coplanar_faces(void *context, int mesh_id); int meshlite_trim(void *context, int mesh_id, int normalize); +int meshlite_mirror_in_x(void *context, int mesh_id, float center_x); +int meshlite_mirror_in_z(void *context, int mesh_id, float center_z); #ifdef __cplusplus } diff --git a/thirdparty/meshlite/meshlite_unstable_vc14_x86/meshlite.dll b/thirdparty/meshlite/meshlite_unstable_vc14_x86/meshlite.dll old mode 100644 new mode 100755 index 7c1396a0..ac58c9e3 Binary files a/thirdparty/meshlite/meshlite_unstable_vc14_x86/meshlite.dll and b/thirdparty/meshlite/meshlite_unstable_vc14_x86/meshlite.dll differ diff --git a/thirdparty/meshlite/meshlite_unstable_vc14_x86/meshlite.dll.lib b/thirdparty/meshlite/meshlite_unstable_vc14_x86/meshlite.dll.lib old mode 100644 new mode 100755 index f5816438..1ebab955 Binary files a/thirdparty/meshlite/meshlite_unstable_vc14_x86/meshlite.dll.lib and b/thirdparty/meshlite/meshlite_unstable_vc14_x86/meshlite.dll.lib differ diff --git a/thirdparty/meshlite/meshlite_unstable_vc14_x86/meshlite.h b/thirdparty/meshlite/meshlite_unstable_vc14_x86/meshlite.h old mode 100644 new mode 100755 index bf3640b1..13dc7e04 --- a/thirdparty/meshlite/meshlite_unstable_vc14_x86/meshlite.h +++ b/thirdparty/meshlite/meshlite_unstable_vc14_x86/meshlite.h @@ -42,6 +42,8 @@ int meshlite_bmesh_error_count(void *context, int bmesh_id); int meshlite_combine_adj_faces(void *context, int mesh_id); int meshlite_combine_coplanar_faces(void *context, int mesh_id); int meshlite_trim(void *context, int mesh_id, int normalize); +int meshlite_mirror_in_x(void *context, int mesh_id, float center_x); +int meshlite_mirror_in_z(void *context, int mesh_id, float center_z); #ifdef __cplusplus }