From e9a9d483db882534021df63a8d739c7259203696 Mon Sep 17 00:00:00 2001 From: Misaki Date: Thu, 25 Dec 2025 03:27:43 +0800 Subject: [PATCH] =?UTF-8?q?1.=20=E6=A8=A1=E5=9E=8B=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=8A=9F=E8=83=BD=EF=BC=9A=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89Live2D=E6=A8=A1=E5=9E=8B=E6=98=BE=E7=A4=BA=E6=AF=94?= =?UTF-8?q?=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Src/LAppLive2D/Inc/LAppLive2DManager.hpp | 31 +++++++++++----- .../Live2D/Src/LAppLive2D/Inc/LAppModel.hpp | 4 +-- .../Live2D/Src/LAppLive2D/Inc/LAppView.hpp | 18 +++++++--- .../Src/LAppLive2D/Src/LAppLive2DManager.cpp | 13 +++++++ .../Live2D/Src/LAppLive2D/Src/LAppView.cpp | 35 +++++++++++++++++++ LICENSE | 21 +++++++++++ src/Core/Src/GLCore.cpp | 2 +- src/Setting/Inc/ModelPage.h | 11 +++--- src/Setting/Src/ModelPage.cpp | 30 +++++++++++++++- 9 files changed, 143 insertions(+), 22 deletions(-) create mode 100644 LICENSE diff --git a/3rdparty/Live2D/Src/LAppLive2D/Inc/LAppLive2DManager.hpp b/3rdparty/Live2D/Src/LAppLive2D/Inc/LAppLive2DManager.hpp index 8f89cf9..2e7c5f9 100644 --- a/3rdparty/Live2D/Src/LAppLive2D/Inc/LAppLive2DManager.hpp +++ b/3rdparty/Live2D/Src/LAppLive2D/Inc/LAppLive2DManager.hpp @@ -39,23 +39,22 @@ public: /** * @brief Resources フォルダにあるモデルフォルダ名をセットする - * - * * 无需多言,这个也宣布弃用了,这个函数功能是自动扫描路径下所有模型 */ + [[deprecated("Do not use")]] void SetUpModel(); /** * @brief Resources フォルダにあるモデルフォルダ名を取得する * */ - Csm::csmVector GetModelDir() const; + [[nodiscard]] Csm::csmVector GetModelDir() const; /** * @brief Resources フォルダにあるモデルフォルダのサイズを取得する * */ - Csm::csmInt32 GetModelDirSize() const; + [[nodiscard]] Csm::csmInt32 GetModelDirSize() const; /** * @brief 現在のシーンで保持しているモデルを返す @@ -63,7 +62,7 @@ public: * @param[in] no モデルリストのインデックス値 * @return モデルのインスタンスを返す。インデックス値が範囲外の場合はNULLを返す。 */ - LAppModel* GetModel(Csm::csmUint32 no) const; + [[nodiscard]] LAppModel* GetModel(Csm::csmUint32 no) const; /** * @brief 現在のシーンで保持しているすべてのモデルを解放する @@ -110,16 +109,31 @@ public: [[deprecated("如需使用,请在单线程中使用本函数")]] void LoadModelFromPath(const std::string& modelPath, const std::string& fileName); - // 仅在内存中加载模型,不干扰当前运行状态(供子线程调用) + /** + * 模型显示大小比例实时缩放接口 + * @param Sacle 缩放比例 + */ + void ModelSizeChange(int Sacle); + + /** + * @brief 从指定路径加载模型 仅在内存中加载模型,不干扰当前运行状态(供子线程调用) + * @param modelPath 模型文件路径 + * @param fileName 模型文件名称 + * @return 模型实例 + */ LAppModel* LoadModelInstance(const std::string& modelPath, const std::string& fileName); - // 将已经加载好的模型应用到系统中(供主线程调用) + /** + * @brief 模型加载完成后,将模型挂载到系统中 + * @param model 模型实例 + */ void MountLoadedModel(LAppModel* model); /** * @brief 次のシーンに切り替える
* サンプルアプリケーションではモデルセットの切り替えを行う。 */ + [[deprecated("Do not use")]] void NextScene(); /** @@ -132,6 +146,7 @@ public: * 至于为什么修改,原生的SDK是自动查找指定目录的模型文件,自动加载的 * 但这样就与实际使用的需求大不相同,我们需要能够手动选择 */ + [[deprecated("Use LoadModelFromPath()")]] void ChangeScene(Csm::csmInt32 index); /** @@ -151,7 +166,7 @@ public: * @brief モデル個数を得る * @return 所持モデル個数 */ - Csm::csmUint32 GetModelNum() const; + [[nodiscard]] Csm::csmUint32 GetModelNum() const; /** * @brief viewMatrixをセットする diff --git a/3rdparty/Live2D/Src/LAppLive2D/Inc/LAppModel.hpp b/3rdparty/Live2D/Src/LAppLive2D/Inc/LAppModel.hpp index 44eb977..86b21f6 100644 --- a/3rdparty/Live2D/Src/LAppLive2D/Inc/LAppModel.hpp +++ b/3rdparty/Live2D/Src/LAppLive2D/Inc/LAppModel.hpp @@ -52,7 +52,7 @@ public: * @brief 获取 Live2D 模型的 Canvas 宽度像素 (在 Live2D 坐标系下) * @return Canvas 宽度 */ - Live2D::Cubism::Framework::csmFloat32 GetModelCanvasWidthPixel() const + [[nodiscard]] Live2D::Cubism::Framework::csmFloat32 GetModelCanvasWidthPixel() const { // _model 是 CubismModel 的基类指针 return _model ? _model->GetCanvasWidthPixel() : 0.0f; @@ -62,7 +62,7 @@ public: * @brief 获取 Live2D 模型的 Canvas 高度像素 (在 Live2D 坐标系下) * @return Canvas 高度 */ - Live2D::Cubism::Framework::csmFloat32 GetModelCanvasHeightPixel() const + [[nodiscard]] Live2D::Cubism::Framework::csmFloat32 GetModelCanvasHeightPixel() const { return _model ? _model->GetCanvasHeightPixel() : 0.0f; } diff --git a/3rdparty/Live2D/Src/LAppLive2D/Inc/LAppView.hpp b/3rdparty/Live2D/Src/LAppLive2D/Inc/LAppView.hpp index 212e569..e60ddd2 100644 --- a/3rdparty/Live2D/Src/LAppLive2D/Inc/LAppView.hpp +++ b/3rdparty/Live2D/Src/LAppLive2D/Inc/LAppView.hpp @@ -94,28 +94,36 @@ public: * * @param[in] deviceX デバイスX座標 */ - float TransformViewX(float deviceX) const; + [[nodiscard]] float TransformViewX(float deviceX) const; /** * @brief Y座標をView座標に変換する。 * * @param[in] deviceY デバイスY座標 */ - float TransformViewY(float deviceY) const; + [[nodiscard]] float TransformViewY(float deviceY) const; /** * @brief X座標をScreen座標に変換する。 * * @param[in] deviceX デバイスX座標 */ - float TransformScreenX(float deviceX) const; + [[nodiscard]] float TransformScreenX(float deviceX) const; /** * @brief Y座標をScreen座標に変換する。 * * @param[in] deviceY デバイスY座標 */ - float TransformScreenY(float deviceY) const; + [[nodiscard]] float TransformScreenY(float deviceY) const; + + /** + * @brief 检测坐标是否在模型上(跨平台穿透功能核心) + * @param deviceX 设备坐标X(鼠标位置) + * @param deviceY 设备坐标Y + * @return 是否击中模型 + */ + [[nodiscard]] bool IsModelHit(float deviceX, float deviceY) const; /** * @brief モデル1体を描画する直前にコールされる @@ -131,7 +139,7 @@ public: * @brief 別レンダリングターゲットにモデルを描画するサンプルで * 描画時のαを決定する */ - float GetSpriteAlpha(int assign) const; + [[nodiscard]] float GetSpriteAlpha(int assign) const; /** * @brief レンダリング先を切り替える diff --git a/3rdparty/Live2D/Src/LAppLive2D/Src/LAppLive2DManager.cpp b/3rdparty/Live2D/Src/LAppLive2D/Src/LAppLive2DManager.cpp index 762b648..e5c0b79 100644 --- a/3rdparty/Live2D/Src/LAppLive2D/Src/LAppLive2DManager.cpp +++ b/3rdparty/Live2D/Src/LAppLive2D/Src/LAppLive2DManager.cpp @@ -263,6 +263,19 @@ void LAppLive2DManager::OnUpdate() const } } #include + +void LAppLive2DManager::ModelSizeChange(const int Sacle = 15) +{ + // 加载完后根据模型大小来重新设置当前窗口大小 + const int width = static_cast(_models[0]->GetModel()->GetCanvasWidthPixel() / Sacle); + const int height = static_cast(_models[0]->GetModel()->GetCanvasHeightPixel() / Sacle); + + // 确保在主线程调用 UI 相关操作 + if(AppContext::GetGLCore()) { + AppContext::GetGLCore()->setWindowSize(width, height); + } + LAppPal::PrintLogLn("[APP]窗口尺寸重新设置为: W: %d H: %d", width, height); +} void LAppLive2DManager::LoadModelFromPath(const std::string& modelPath, const std::string& fileName) { const csmString modelPathStr(modelPath.c_str()); diff --git a/3rdparty/Live2D/Src/LAppLive2D/Src/LAppView.cpp b/3rdparty/Live2D/Src/LAppLive2D/Src/LAppView.cpp index 48a3672..54b4d6e 100644 --- a/3rdparty/Live2D/Src/LAppLive2D/Src/LAppView.cpp +++ b/3rdparty/Live2D/Src/LAppLive2D/Src/LAppView.cpp @@ -17,6 +17,7 @@ #include "LAppSprite.hpp" #include "LAppModel.hpp" + using namespace std; using namespace LAppDefine; @@ -265,6 +266,40 @@ float LAppView::TransformScreenY(float deviceY) const return _deviceToScreen->TransformY(deviceY); } +#include "Id/CubismIdManager.hpp" +bool LAppView::IsModelHit(const float deviceX, const float deviceY) const { + const LAppLive2DManager* live2DManager = LAppLive2DManager::GetInstance(); + if (!live2DManager || live2DManager->GetModelNum() == 0) { + return false; + } + + // 坐标转换(设备坐标 -> Live2D View 坐标) + const csmFloat32 viewX = _deviceToScreen->TransformX(deviceX); + const csmFloat32 viewY = _deviceToScreen->TransformY(deviceY); + + // 正确获取 ID Handle(在 SDK 初始化后调用) + static const Csm::CubismId* bodyId = nullptr; + static const Csm::CubismId* headId = nullptr; + if (!bodyId) { + bodyId = CubismFramework::GetIdManager()->GetId("Body"); + headId = CubismFramework::GetIdManager()->GetId("Head"); + } + + // 遍历所有模型进行碰撞检测 + const csmUint32 modelCount = live2DManager->GetModelNum(); + for (csmUint32 i = 0; i < modelCount; ++i) { + LAppModel* model = live2DManager->GetModel(i); + if (!model || !model->GetModel()) continue; + + // 检查命中区域(修正:使用 SDK 提供的 ID) + if (model->IsHit(bodyId, viewX, viewY) || model->IsHit(headId, viewX, viewY)) { + return true; // 击中模型 + } + } + + return false; // 未击中模型 +} + void LAppView::PreModelDraw(LAppModel& refModel) { // 別のレンダリングターゲットへ向けて描画する場合の使用するフレームバッファ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1e3557a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Misakiotoha + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/src/Core/Src/GLCore.cpp b/src/Core/Src/GLCore.cpp index 087d9ad..94a6912 100644 --- a/src/Core/Src/GLCore.cpp +++ b/src/Core/Src/GLCore.cpp @@ -61,7 +61,7 @@ GLCore::GLCore(int w, int h, QWidget *parent) TextRenderer::getInstance()->setGravity(600.0f); // 更快的下坠速度 TextRenderer::getInstance()->setDampFactor(0.85f); // 更强的弹性效果 - // this->setWindowFlag(Qt::FramelessWindowHint); // 设置无边框窗口 + this->setWindowFlag(Qt::FramelessWindowHint); // 设置无边框窗口 this->setWindowFlag(Qt::WindowStaysOnTopHint); // 设置窗口始终在顶部 this->setWindowFlag(Qt::Tool); // 隐藏应用程序图标 this->setAttribute(Qt::WA_TranslucentBackground); // 设置窗口背景透明 diff --git a/src/Setting/Inc/ModelPage.h b/src/Setting/Inc/ModelPage.h index 7c585b3..392ddde 100644 --- a/src/Setting/Inc/ModelPage.h +++ b/src/Setting/Inc/ModelPage.h @@ -12,12 +12,11 @@ #include "ElaPushButton.h" #include "ElaLineEdit.h" #include "ElaComboBox.h" +#include "ElaSlider.h" #include #include -class ElaLineEdit; -class ElaPushButton; class ModelPage final : public BasePage { Q_OBJECT @@ -31,9 +30,11 @@ public: private: // 设置当前模型 - ElaLineEdit* modelUrlEdit = nullptr; - ElaPushButton* modelChoosePushButton = nullptr; - ElaPushButton* modelUsePushButton = nullptr; + ElaLineEdit* modelUrlEdit = nullptr; /// 模型Url 编辑框 + ElaPushButton* modelChoosePushButton = nullptr; /// 选择模型按钮 + ElaPushButton* modelUsePushButton = nullptr; /// 使用模型按钮 + ElaSlider* modelSlider = nullptr; /// 滑块(用于设置模型实时大小) + QUrl modelFileUrl; QString modelFilePathFirst; QString modelFilePathSecond; diff --git a/src/Setting/Src/ModelPage.cpp b/src/Setting/Src/ModelPage.cpp index 52bd120..a861121 100644 --- a/src/Setting/Src/ModelPage.cpp +++ b/src/Setting/Src/ModelPage.cpp @@ -20,13 +20,16 @@ ModelPage::ModelPage(QWidget *parent) setWindowTitle("ModelPage"); - modelUrlEdit = new ElaLineEdit(this); + modelUrlEdit = new ElaLineEdit(this); // 模型Url Edit modelUrlEdit->setFixedWidth(300); modelUrlEdit->setPlaceholderText("用于显示当前的模型Url"); + modelChoosePushButton = new ElaPushButton("选择模型", this); modelChoosePushButton->setToolTip("选择.model3.json结尾的文件"); + modelUsePushButton = new ElaPushButton("使用模型", this); modelUsePushButton->setToolTip("使用选择的模型或Url对应的模型"); + ElaScrollPageArea *modelSetArea = new ElaScrollPageArea(this); QHBoxLayout *modelSetLayout = new QHBoxLayout(modelSetArea); ElaText *modelSetText = new ElaText("模型设置", this); @@ -37,6 +40,30 @@ ModelPage::ModelPage(QWidget *parent) modelSetLayout->addWidget(modelChoosePushButton); modelSetLayout->addWidget(modelUsePushButton); modelSetLayout->addSpacing(10); + + // 创建滑块控件和标签 + ElaText *modelSizeText = new ElaText("模型大小比例设置", this); + modelSizeText->setTextPixelSize(15); + modelSlider = new ElaSlider(this); // 滑块(用于设置模型实时大小) + modelSlider->setRange(0, 99); // 设置范围 + modelSlider->setValue(85); // 设置默认值 + modelSlider->setOrientation(Qt::Horizontal); // 水平方向 + // 创建独立的区域容器 + ElaScrollPageArea *modelSliderArea = new ElaScrollPageArea(this); + QHBoxLayout *modelSliderLayout = new QHBoxLayout(modelSliderArea); + + // 添加到布局(加一个标签显示数值) + ElaText *modelSliderValueText = new ElaText("85%", this); + modelSliderValueText->setTextPixelSize(14); + connect(modelSlider, &ElaSlider::valueChanged, this, [modelSliderValueText](const int value){ + modelSliderValueText->setText(QString("%1%").arg(value + 1)); + LAppLive2DManager::GetInstance()->ModelSizeChange(100 - value); // 实时更新模型大小 + }); + modelSliderLayout->addWidget(modelSizeText); + modelSliderLayout->addWidget(modelSlider); + modelSliderLayout->addWidget(modelSliderValueText); + modelSliderLayout->addStretch(); // 让内容靠左 + connect(modelChoosePushButton, &ElaPushButton::clicked, this, [this]() { // 创建对话框对象(使用 heap 分配,由 Qt 对象树管理内存) auto *fileDialog = new QFileDialog(this); @@ -140,6 +167,7 @@ ModelPage::ModelPage(QWidget *parent) centralWidget->setWindowTitle("模型设置"); QVBoxLayout *centerLayout = new QVBoxLayout(centralWidget); centerLayout->addWidget(modelSetArea); + centerLayout->addWidget(modelSliderArea); centerLayout->addStretch(); centerLayout->setContentsMargins(0, 0, 0, 0); addCentralWidget(centralWidget, true, true, 0);