diff --git a/dust3d.pro b/dust3d.pro index 32e6526f..c9a5929a 100644 --- a/dust3d.pro +++ b/dust3d.pro @@ -538,6 +538,9 @@ HEADERS += src/motionbuilder.h SOURCES += src/statusbarlabel.cpp HEADERS += src/statusbarlabel.h +SOURCES += src/silhouetteimagegenerator.cpp +HEADERS += src/silhouetteimagegenerator.h + SOURCES += src/main.cpp HEADERS += src/version.h diff --git a/src/documentwindow.cpp b/src/documentwindow.cpp index 46c90747..c7c66b55 100644 --- a/src/documentwindow.cpp +++ b/src/documentwindow.cpp @@ -52,6 +52,7 @@ #include "objectxml.h" #include "rigxml.h" #include "statusbarlabel.h" +#include "silhouetteimagegenerator.h" int DocumentWindow::m_autoRecovered = false; @@ -80,6 +81,22 @@ const std::map &DocumentWindow::documentWindows() return g_documentWindows; } +DocumentWindow::GraphicsViewEditTarget DocumentWindow::graphicsViewEditTarget() +{ + return m_graphicsViewEditTarget; +} + +void DocumentWindow::updateGraphicsViewEditTarget(GraphicsViewEditTarget target) +{ + if (m_graphicsViewEditTarget == target) + return; + + m_graphicsViewEditTarget = target; + if (GraphicsViewEditTarget::Bone == m_graphicsViewEditTarget) + generateSilhouetteImage(); + emit graphicsViewEditTargetChanged(); +} + Document *DocumentWindow::document() { return m_document; @@ -520,12 +537,12 @@ DocumentWindow::DocumentWindow() : connect(boneLabel, &StatusBarLabel::clicked, this, [=]() { boneLabel->setSelected(true); shapeLabel->setSelected(false); - // TODO: + updateGraphicsViewEditTarget(GraphicsViewEditTarget::Bone); }); connect(shapeLabel, &StatusBarLabel::clicked, this, [=]() { shapeLabel->setSelected(true); boneLabel->setSelected(false); - // TODO: + updateGraphicsViewEditTarget(GraphicsViewEditTarget::Shape); }); /////////////////////// Status Bar End //////////////////////////// @@ -1354,6 +1371,9 @@ DocumentWindow::DocumentWindow() : connect(m_document, &Document::scriptChanged, m_document, &Document::runScript); connect(m_document, &Document::scriptModifiedFromExternal, m_document, &Document::runScript); + connect(m_document, &Document::skeletonChanged, this, &DocumentWindow::generateSilhouetteImage); + connect(m_graphicsWidget, &SkeletonGraphicsWidget::loadedTurnaroundImageChanged, this, &DocumentWindow::generateSilhouetteImage); + initShortCuts(this, m_graphicsWidget); connect(this, &DocumentWindow::initialized, m_document, &Document::uiReady); @@ -2634,3 +2654,47 @@ ModelWidget *DocumentWindow::modelWidget() { return m_modelRenderWidget; } + +void DocumentWindow::generateSilhouetteImage() +{ + if (GraphicsViewEditTarget::Bone != m_graphicsViewEditTarget || + nullptr != m_silhouetteImageGenerator) { + m_isSilhouetteImageObsolete = true; + return; + } + + m_isSilhouetteImageObsolete = false; + + const QImage *loadedTurnaroundImage = m_graphicsWidget->loadedTurnaroundImage(); + if (nullptr == loadedTurnaroundImage) { + return; + } + + QThread *thread = new QThread; + + Snapshot *snapshot = new Snapshot; + m_document->toSnapshot(snapshot); + + m_silhouetteImageGenerator = new SilhouetteImageGenerator(loadedTurnaroundImage->width(), + loadedTurnaroundImage->height(), snapshot); + m_silhouetteImageGenerator->moveToThread(thread); + connect(thread, &QThread::started, m_silhouetteImageGenerator, &SilhouetteImageGenerator::process); + connect(m_silhouetteImageGenerator, &SilhouetteImageGenerator::finished, this, &DocumentWindow::silhouetteImageReady); + connect(m_silhouetteImageGenerator, &SilhouetteImageGenerator::finished, thread, &QThread::quit); + connect(thread, &QThread::finished, thread, &QThread::deleteLater); + thread->start(); +} + +void DocumentWindow::silhouetteImageReady() +{ + QImage *image = m_silhouetteImageGenerator->takeResultImage(); + if (nullptr != image) + image->save("test.png"); + delete image; + + delete m_silhouetteImageGenerator; + m_silhouetteImageGenerator = nullptr; + + if (m_isSilhouetteImageObsolete) + generateSilhouetteImage(); +} diff --git a/src/documentwindow.h b/src/documentwindow.h index ba52e07b..6bec9d48 100644 --- a/src/documentwindow.h +++ b/src/documentwindow.h @@ -24,6 +24,7 @@ class SkeletonGraphicsWidget; class PartTreeWidget; class SpinnableAwesomeButton; +class SilhouetteImageGenerator; class DocumentWindow : public QMainWindow { @@ -33,11 +34,19 @@ signals: void uninialized(); void waitingExportFinished(const QString &filename, bool isSuccessful); void mouseTargetVertexPositionChanged(const QVector3D &position); + void graphicsViewEditTargetChanged(); public: + enum class GraphicsViewEditTarget + { + Shape, + Bone + }; + DocumentWindow(); ~DocumentWindow(); Document *document(); ModelWidget *modelWidget(); + GraphicsViewEditTarget graphicsViewEditTarget(); static DocumentWindow *createDocumentWindow(); static const std::map &documentWindows(); static void showAcknowlegements(); @@ -105,6 +114,9 @@ public slots: void generatePartPreviewImages(); void partPreviewImagesReady(); void updateRegenerateIcon(); + void updateGraphicsViewEditTarget(GraphicsViewEditTarget target); + void generateSilhouetteImage(); + void silhouetteImageReady(); private: void initLockButton(QPushButton *button); void setCurrentFilename(const QString &filename); @@ -242,6 +254,10 @@ private: SpinnableAwesomeButton *m_regenerateButton = nullptr; QWidget *m_paintWidget = nullptr; + + GraphicsViewEditTarget m_graphicsViewEditTarget = GraphicsViewEditTarget::Shape; + bool m_isSilhouetteImageObsolete = false; + SilhouetteImageGenerator *m_silhouetteImageGenerator = nullptr; public: static int m_autoRecovered; }; diff --git a/src/silhouetteimagegenerator.cpp b/src/silhouetteimagegenerator.cpp new file mode 100644 index 00000000..2143b560 --- /dev/null +++ b/src/silhouetteimagegenerator.cpp @@ -0,0 +1,146 @@ +#include +#include +#include "silhouetteimagegenerator.h" +#include "util.h" + +SilhouetteImageGenerator::SilhouetteImageGenerator(int width, int height, Snapshot *snapshot) : + m_width(width), + m_height(height), + m_snapshot(snapshot) +{ +} + +SilhouetteImageGenerator::~SilhouetteImageGenerator() +{ + delete m_snapshot; + delete m_resultImage; +} + +QImage *SilhouetteImageGenerator::takeResultImage() +{ + QImage *resultImage = m_resultImage; + m_resultImage = nullptr; + return resultImage; +} + +void SilhouetteImageGenerator::generate() +{ + if (m_width <= 0 || m_height <= 0 || nullptr == m_snapshot) + return; + + //float originX = valueOfKeyInMapOrEmpty(m_snapshot->canvas, "originX").toFloat(); + //float originY = valueOfKeyInMapOrEmpty(m_snapshot->canvas, "originY").toFloat(); + //float originZ = valueOfKeyInMapOrEmpty(m_snapshot->canvas, "originZ").toFloat(); + + delete m_resultImage; + m_resultImage = new QImage(m_width, m_height, QImage::Format_ARGB32); + m_resultImage->fill(QColor(0xE1, 0xD2, 0xBD)); + + struct NodeInfo + { + QVector3D position; + float radius; + }; + + std::map nodePositionMap; + for (const auto &node: m_snapshot->nodes) { + NodeInfo nodeInfo; + nodeInfo.position = QVector3D(valueOfKeyInMapOrEmpty(node.second, "x").toFloat(), + valueOfKeyInMapOrEmpty(node.second, "y").toFloat(), + valueOfKeyInMapOrEmpty(node.second, "z").toFloat()); + nodeInfo.radius = valueOfKeyInMapOrEmpty(node.second, "radius").toFloat(); + nodePositionMap.insert({node.first, nodeInfo}); + } + + QPainter painter; + painter.begin(m_resultImage); + painter.setRenderHint(QPainter::Antialiasing); + painter.setRenderHint(QPainter::HighQualityAntialiasing); + + painter.setPen(Qt::NoPen); + + QBrush brush; + brush.setColor(QColor(0x88, 0x80, 0x73)); + brush.setStyle(Qt::SolidPattern); + + painter.setBrush(brush); + + for (const auto &it: nodePositionMap) { + const auto &nodeInfo = it.second; + + painter.drawEllipse((nodeInfo.position.x() - nodeInfo.radius) * m_height, + (nodeInfo.position.y() - nodeInfo.radius) * m_height, + nodeInfo.radius * m_height * 2.0, + nodeInfo.radius * m_height * 2.0); + + painter.drawEllipse((nodeInfo.position.z() - nodeInfo.radius) * m_height, + (nodeInfo.position.y() - nodeInfo.radius) * m_height, + nodeInfo.radius * m_height * 2.0, + nodeInfo.radius * m_height * 2.0); + } + + for (int round = 0; round < 2; ++round) { + if (1 == round) + painter.setCompositionMode(QPainter::CompositionMode_Multiply); + for (const auto &edge: m_snapshot->edges) { + QString partId = valueOfKeyInMapOrEmpty(edge.second, "partId"); + QString fromNodeId = valueOfKeyInMapOrEmpty(edge.second, "from"); + QString toNodeId = valueOfKeyInMapOrEmpty(edge.second, "to"); + const auto &fromNodeInfo = nodePositionMap[fromNodeId]; + const auto &toNodeInfo = nodePositionMap[toNodeId]; + + { + QVector3D pointerOut = QVector3D(0.0, 0.0, 1.0); + QVector3D direction = (QVector3D(toNodeInfo.position.x(), toNodeInfo.position.y(), 0.0) - + QVector3D(fromNodeInfo.position.x(), fromNodeInfo.position.y(), 0.0)).normalized(); + QVector3D fromBaseDirection = QVector3D::crossProduct(pointerOut, direction); + QVector3D fromBaseRadius = fromBaseDirection * fromNodeInfo.radius; + QVector3D fromBaseFirstPoint = QVector3D(fromNodeInfo.position.x(), fromNodeInfo.position.y(), 0.0) - fromBaseRadius; + QVector3D fromBaseSecondPoint = QVector3D(fromNodeInfo.position.x(), fromNodeInfo.position.y(), 0.0) + fromBaseRadius; + QVector3D tobaseDirection = -fromBaseDirection; + QVector3D toBaseRadius = tobaseDirection * toNodeInfo.radius; + QVector3D toBaseFirstPoint = QVector3D(toNodeInfo.position.x(), toNodeInfo.position.y(), 0.0) - toBaseRadius; + QVector3D toBaseSecondPoint = QVector3D(toNodeInfo.position.x(), toNodeInfo.position.y(), 0.0) + toBaseRadius; + QPolygon polygon; + polygon.append(QPoint(fromBaseFirstPoint.x() * m_height, fromBaseFirstPoint.y() * m_height)); + polygon.append(QPoint(fromBaseSecondPoint.x() * m_height, fromBaseSecondPoint.y() * m_height)); + polygon.append(QPoint(toBaseFirstPoint.x() * m_height, toBaseFirstPoint.y() * m_height)); + polygon.append(QPoint(toBaseSecondPoint.x() * m_height, toBaseSecondPoint.y() * m_height)); + QPainterPath path; + path.addPolygon(polygon); + painter.fillPath(path, brush); + } + + { + QVector3D pointerOut = QVector3D(0.0, 0.0, 1.0); + QVector3D direction = (QVector3D(toNodeInfo.position.z(), toNodeInfo.position.y(), 0.0) - + QVector3D(fromNodeInfo.position.z(), fromNodeInfo.position.y(), 0.0)).normalized(); + QVector3D fromBaseDirection = QVector3D::crossProduct(pointerOut, direction); + QVector3D fromBaseRadius = fromBaseDirection * fromNodeInfo.radius; + QVector3D fromBaseFirstPoint = QVector3D(fromNodeInfo.position.z(), fromNodeInfo.position.y(), 0.0) - fromBaseRadius; + QVector3D fromBaseSecondPoint = QVector3D(fromNodeInfo.position.z(), fromNodeInfo.position.y(), 0.0) + fromBaseRadius; + QVector3D tobaseDirection = -fromBaseDirection; + QVector3D toBaseRadius = tobaseDirection * toNodeInfo.radius; + QVector3D toBaseFirstPoint = QVector3D(toNodeInfo.position.z(), toNodeInfo.position.y(), 0.0) - toBaseRadius; + QVector3D toBaseSecondPoint = QVector3D(toNodeInfo.position.z(), toNodeInfo.position.y(), 0.0) + toBaseRadius; + QPolygon polygon; + polygon.append(QPoint(fromBaseFirstPoint.x() * m_height, fromBaseFirstPoint.y() * m_height)); + polygon.append(QPoint(fromBaseSecondPoint.x() * m_height, fromBaseSecondPoint.y() * m_height)); + polygon.append(QPoint(toBaseFirstPoint.x() * m_height, toBaseFirstPoint.y() * m_height)); + polygon.append(QPoint(toBaseSecondPoint.x() * m_height, toBaseSecondPoint.y() * m_height)); + QPainterPath path; + path.addPolygon(polygon); + painter.fillPath(path, brush); + } + } + } + + painter.end(); +} + +void SilhouetteImageGenerator::process() +{ + generate(); + + emit finished(); +} diff --git a/src/silhouetteimagegenerator.h b/src/silhouetteimagegenerator.h new file mode 100644 index 00000000..b73c41eb --- /dev/null +++ b/src/silhouetteimagegenerator.h @@ -0,0 +1,26 @@ +#ifndef DUST3D_SILHOUETTE_IMAGE_GENERATOR_H +#define DUST3D_SILHOUETTE_IMAGE_GENERATOR_H +#include +#include +#include "snapshot.h" + +class SilhouetteImageGenerator : public QObject +{ + Q_OBJECT +public: + SilhouetteImageGenerator(int width, int height, Snapshot *snapshot); + ~SilhouetteImageGenerator(); + QImage *takeResultImage(); + void generate(); +signals: + void finished(); +public slots: + void process(); +private: + int m_width = 0; + int m_height = 0; + QImage *m_resultImage = nullptr; + Snapshot *m_snapshot = nullptr; +}; + +#endif diff --git a/src/skeletongraphicswidget.cpp b/src/skeletongraphicswidget.cpp index 4630475f..daeef23f 100644 --- a/src/skeletongraphicswidget.cpp +++ b/src/skeletongraphicswidget.cpp @@ -760,12 +760,19 @@ void SkeletonGraphicsWidget::turnaroundImageReady() } delete m_turnaroundLoader; m_turnaroundLoader = nullptr; + + emit loadedTurnaroundImageChanged(); if (m_turnaroundChanged) { updateTurnaround(); } } +const QImage *SkeletonGraphicsWidget::loadedTurnaroundImage() const +{ + return m_backgroundImage; +} + void SkeletonGraphicsWidget::updateCursor() { if (SkeletonDocumentEditMode::Add != m_document->editMode) { diff --git a/src/skeletongraphicswidget.h b/src/skeletongraphicswidget.h index ae8b24d1..8a8d7f4c 100644 --- a/src/skeletongraphicswidget.h +++ b/src/skeletongraphicswidget.h @@ -516,6 +516,7 @@ signals: void shortcutToggleRotation(); void createGriddedPartsFromNodes(const std::set &nodeIds); void addPartByPolygons(const QPolygonF &mainProfile, const QPolygonF &sideProfile, const QSizeF &canvasSize); + void loadedTurnaroundImageChanged(); public: SkeletonGraphicsWidget(const SkeletonDocument *document); std::map> nodeItemMap; @@ -547,6 +548,7 @@ public: void setMainProfileOnly(bool mainProfileOnly); bool inputWheelEventFromOtherWidget(QWheelEvent *event); bool rotated(); + const QImage *loadedTurnaroundImage() const; protected: void mouseMoveEvent(QMouseEvent *event) override; void wheelEvent(QWheelEvent *event) override;