From 64ef14100d88c4ba44e4716540c85b5180ec3d65 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 8 May 2025 13:10:37 +0300 Subject: [PATCH] feat(skin manager): add elytra preview Signed-off-by: Trial97 --- .../ui/dialogs/skins/SkinManageDialog.cpp | 31 ++++++++++++--- launcher/ui/dialogs/skins/SkinManageDialog.ui | 7 ++++ .../ui/dialogs/skins/draw/BoxGeometry.cpp | 8 +++- launcher/ui/dialogs/skins/draw/BoxGeometry.h | 1 + launcher/ui/dialogs/skins/draw/Scene.cpp | 38 +++++++++++++++++-- launcher/ui/dialogs/skins/draw/Scene.h | 5 ++- .../dialogs/skins/draw/SkinOpenGLWindow.cpp | 5 +++ .../ui/dialogs/skins/draw/SkinOpenGLWindow.h | 1 + 8 files changed, 85 insertions(+), 11 deletions(-) diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.cpp b/launcher/ui/dialogs/skins/SkinManageDialog.cpp index 3bc0bc2d9..8e661d37c 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.cpp +++ b/launcher/ui/dialogs/skins/SkinManageDialog.cpp @@ -92,6 +92,10 @@ SkinManageDialog::SkinManageDialog(QWidget* parent, MinecraftAccountPtr acct) connect(contentsWidget->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), SLOT(selectionChanged(QItemSelection, QItemSelection))); connect(m_ui->listView, &QListView::customContextMenuRequested, this, &SkinManageDialog::show_context_menu); + connect(m_ui->elytraCB, &QCheckBox::stateChanged, this, [this]() { + m_skinPreview->setElytraVisible(m_ui->elytraCB->isChecked()); + on_capeCombo_currentIndexChanged(0); + }); setupCapes(); @@ -159,10 +163,24 @@ void SkinManageDialog::on_fileBtn_clicked() } } -QPixmap previewCape(QImage capeImage) +QPixmap previewCape(QImage capeImage, bool elytra = false) { + if (elytra) { + auto wing = capeImage.copy(34, 0, 12, 22); + QImage mirrored = wing.mirrored(true, false); + + QImage combined(wing.width() * 2 - 2, wing.height(), capeImage.format()); + combined.fill(Qt::transparent); + + QPainter painter(&combined); + painter.drawImage(0, 0, wing); + painter.drawImage(wing.width() - 2, 0, mirrored); + painter.end(); + return QPixmap::fromImage(combined.scaled(96, 176, Qt::IgnoreAspectRatio, Qt::FastTransformation)); + } return QPixmap::fromImage(capeImage.copy(1, 1, 10, 16).scaled(80, 128, Qt::IgnoreAspectRatio, Qt::FastTransformation)); } + void SkinManageDialog::setupCapes() { // FIXME: add a model for this, download/refresh the capes on demand @@ -208,7 +226,7 @@ void SkinManageDialog::setupCapes() } } if (!capeImage.isNull()) { - m_ui->capeCombo->addItem(previewCape(capeImage), cape.alias, cape.id); + m_ui->capeCombo->addItem(previewCape(capeImage, m_ui->elytraCB->isChecked()), cape.alias, cape.id); } else { m_ui->capeCombo->addItem(cape.alias, cape.id); } @@ -222,7 +240,8 @@ void SkinManageDialog::on_capeCombo_currentIndexChanged(int index) auto id = m_ui->capeCombo->currentData(); auto cape = m_capes.value(id.toString(), {}); if (!cape.isNull()) { - m_ui->capeImage->setPixmap(previewCape(cape).scaled(size() * (1. / 3), Qt::KeepAspectRatio, Qt::FastTransformation)); + m_ui->capeImage->setPixmap( + previewCape(cape, m_ui->elytraCB->isChecked()).scaled(size() * (1. / 3), Qt::KeepAspectRatio, Qt::FastTransformation)); } else { m_ui->capeImage->clear(); } @@ -319,14 +338,14 @@ bool SkinManageDialog::eventFilter(QObject* obj, QEvent* ev) return QDialog::eventFilter(obj, ev); } -void SkinManageDialog::on_action_Rename_Skin_triggered(bool checked) +void SkinManageDialog::on_action_Rename_Skin_triggered(bool) { if (!m_selectedSkinKey.isEmpty()) { m_ui->listView->edit(m_ui->listView->currentIndex()); } } -void SkinManageDialog::on_action_Delete_Skin_triggered(bool checked) +void SkinManageDialog::on_action_Delete_Skin_triggered(bool) { if (m_selectedSkinKey.isEmpty()) return; @@ -523,7 +542,7 @@ void SkinManageDialog::resizeEvent(QResizeEvent* event) auto id = m_ui->capeCombo->currentData(); auto cape = m_capes.value(id.toString(), {}); if (!cape.isNull()) { - m_ui->capeImage->setPixmap(previewCape(cape).scaled(s, Qt::KeepAspectRatio, Qt::FastTransformation)); + m_ui->capeImage->setPixmap(previewCape(cape, m_ui->elytraCB->isChecked()).scaled(s, Qt::KeepAspectRatio, Qt::FastTransformation)); } else { m_ui->capeImage->clear(); } diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.ui b/launcher/ui/dialogs/skins/SkinManageDialog.ui index 7e8b4bc46..065c5cafc 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.ui +++ b/launcher/ui/dialogs/skins/SkinManageDialog.ui @@ -59,6 +59,13 @@ Cape + + + + Elytra + + + diff --git a/launcher/ui/dialogs/skins/draw/BoxGeometry.cpp b/launcher/ui/dialogs/skins/draw/BoxGeometry.cpp index b4ab8d4cc..f91fe2f1f 100644 --- a/launcher/ui/dialogs/skins/draw/BoxGeometry.cpp +++ b/launcher/ui/dialogs/skins/draw/BoxGeometry.cpp @@ -180,7 +180,8 @@ QList getCubeUVs(float u, float v, float width, float height, float d } namespace opengl { -BoxGeometry::BoxGeometry(QVector3D size, QVector3D position) : m_indexBuf(QOpenGLBuffer::IndexBuffer), m_size(size), m_position(position) +BoxGeometry::BoxGeometry(QVector3D size, QVector3D position) + : QOpenGLFunctions(), m_indexBuf(QOpenGLBuffer::IndexBuffer), m_size(size), m_position(position) { initializeOpenGLFunctions(); @@ -274,4 +275,9 @@ BoxGeometry* BoxGeometry::Plane() return b; } + +void BoxGeometry::scale(const QVector3D& vector) +{ + m_matrix.scale(vector); +} } // namespace opengl \ No newline at end of file diff --git a/launcher/ui/dialogs/skins/draw/BoxGeometry.h b/launcher/ui/dialogs/skins/draw/BoxGeometry.h index 1a245bc14..fa1a4c622 100644 --- a/launcher/ui/dialogs/skins/draw/BoxGeometry.h +++ b/launcher/ui/dialogs/skins/draw/BoxGeometry.h @@ -36,6 +36,7 @@ class BoxGeometry : protected QOpenGLFunctions { void initGeometry(float u, float v, float width, float height, float depth, float textureWidth = 64, float textureHeight = 64); void rotate(float angle, const QVector3D& vector); + void scale(const QVector3D& vector); private: QOpenGLBuffer m_vertexBuf; diff --git a/launcher/ui/dialogs/skins/draw/Scene.cpp b/launcher/ui/dialogs/skins/draw/Scene.cpp index 45d0ba191..89a783622 100644 --- a/launcher/ui/dialogs/skins/draw/Scene.cpp +++ b/launcher/ui/dialogs/skins/draw/Scene.cpp @@ -18,9 +18,16 @@ */ #include "ui/dialogs/skins/draw/Scene.h" + +#include +#include +#include +#include + namespace opengl { -Scene::Scene(const QImage& skin, bool slim, const QImage& cape) : m_slim(slim), m_capeVisible(!cape.isNull()) +Scene::Scene(const QImage& skin, bool slim, const QImage& cape) : QOpenGLFunctions(), m_slim(slim), m_capeVisible(!cape.isNull()) { + initializeOpenGLFunctions(); m_staticComponents = { // head new opengl::BoxGeometry(QVector3D(8, 8, 8), QVector3D(0, 4, 0), QPoint(0, 0), QVector3D(8, 8, 8)), @@ -57,6 +64,19 @@ Scene::Scene(const QImage& skin, bool slim, const QImage& cape) : m_slim(slim), m_cape->rotate(10.8, QVector3D(1, 0, 0)); m_cape->rotate(180, QVector3D(0, 1, 0)); + auto leftWing = + new opengl::BoxGeometry(QVector3D(12, 22, 4), QVector3D(0, -13, -2), QPoint(22, 0), QVector3D(10, 20, 2), QSize(64, 32)); + leftWing->rotate(15, QVector3D(1, 0, 0)); + leftWing->rotate(15, QVector3D(0, 0, 1)); + leftWing->rotate(1, QVector3D(1, 0, 0)); + auto rightWing = + new opengl::BoxGeometry(QVector3D(12, 22, 4), QVector3D(0, -13, -2), QPoint(22, 0), QVector3D(10, 20, 2), QSize(64, 32)); + rightWing->scale(QVector3D(-1, 1, 1)); + rightWing->rotate(15, QVector3D(1, 0, 0)); + rightWing->rotate(15, QVector3D(0, 0, 1)); + rightWing->rotate(1, QVector3D(1, 0, 0)); + m_elytra << leftWing << rightWing; + // texture init m_skinTexture = new QOpenGLTexture(skin.mirrored()); m_skinTexture->setMinificationFilter(QOpenGLTexture::Nearest); @@ -68,7 +88,7 @@ Scene::Scene(const QImage& skin, bool slim, const QImage& cape) : m_slim(slim), } Scene::~Scene() { - for (auto array : { m_staticComponents, m_normalArms, m_slimArms }) { + for (auto array : { m_staticComponents, m_normalArms, m_slimArms, m_elytra }) { for (auto g : array) { delete g; } @@ -95,7 +115,15 @@ void Scene::draw(QOpenGLShaderProgram* program) if (m_capeVisible) { m_capeTexture->bind(); program->setUniformValue("texture", 0); - m_cape->draw(program); + if (!m_elytraVisible) { + m_cape->draw(program); + } else { + glDisable(GL_CULL_FACE); + for (auto e : m_elytra) { + e->draw(program); + } + glEnable(GL_CULL_FACE); + } m_capeTexture->release(); } } @@ -131,4 +159,8 @@ void Scene::setCapeVisible(bool visible) { m_capeVisible = visible; } +void Scene::setElytraVisible(bool elytraVisible) +{ + m_elytraVisible = elytraVisible; +} } // namespace opengl \ No newline at end of file diff --git a/launcher/ui/dialogs/skins/draw/Scene.h b/launcher/ui/dialogs/skins/draw/Scene.h index 3560d1d74..c9bba1f20 100644 --- a/launcher/ui/dialogs/skins/draw/Scene.h +++ b/launcher/ui/dialogs/skins/draw/Scene.h @@ -22,7 +22,7 @@ #include namespace opengl { -class Scene { +class Scene : protected QOpenGLFunctions { public: Scene(const QImage& skin, bool slim, const QImage& cape); virtual ~Scene(); @@ -32,15 +32,18 @@ class Scene { void setCape(const QImage& cape); void setMode(bool slim); void setCapeVisible(bool visible); + void setElytraVisible(bool elytraVisible); private: QList m_staticComponents; QList m_normalArms; QList m_slimArms; BoxGeometry* m_cape = nullptr; + QList m_elytra; QOpenGLTexture* m_skinTexture = nullptr; QOpenGLTexture* m_capeTexture = nullptr; bool m_slim = false; bool m_capeVisible = false; + bool m_elytraVisible = false; }; } // namespace opengl \ No newline at end of file diff --git a/launcher/ui/dialogs/skins/draw/SkinOpenGLWindow.cpp b/launcher/ui/dialogs/skins/draw/SkinOpenGLWindow.cpp index e1e539050..f035e6b91 100644 --- a/launcher/ui/dialogs/skins/draw/SkinOpenGLWindow.cpp +++ b/launcher/ui/dialogs/skins/draw/SkinOpenGLWindow.cpp @@ -263,3 +263,8 @@ void SkinOpenGLWindow::wheelEvent(QWheelEvent* event) m_distance = qMax(16.f, m_distance); // Clamp distance update(); // Trigger a repaint } +void SkinOpenGLWindow::setElytraVisible(bool visible) +{ + if (m_scene) + m_scene->setElytraVisible(visible); +} diff --git a/launcher/ui/dialogs/skins/draw/SkinOpenGLWindow.h b/launcher/ui/dialogs/skins/draw/SkinOpenGLWindow.h index e2c32da0f..2a06c23e5 100644 --- a/launcher/ui/dialogs/skins/draw/SkinOpenGLWindow.h +++ b/launcher/ui/dialogs/skins/draw/SkinOpenGLWindow.h @@ -43,6 +43,7 @@ class SkinOpenGLWindow : public QOpenGLWindow, protected QOpenGLFunctions { void updateScene(SkinModel* skin); void updateCape(const QImage& cape); + void setElytraVisible(bool visible); protected: void mousePressEvent(QMouseEvent* e) override;