32864296c304c0994d934ad851bbb3ddbc965ce3
[mouse-tracker-for-cubism.git] / example / demo.patch
1 diff -pruN --exclude build ./demo_clean/CMakeLists.txt ./demo_dev/CMakeLists.txt
2 --- ./demo_clean/CMakeLists.txt 2025-05-30 00:59:58.252401066 +0100
3 +++ ./demo_dev/CMakeLists.txt   2025-05-30 01:05:35.538986524 +0100
4 @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.16)
5  # Set app name.
6  set(APP_NAME Demo)
7  # Set directory paths.
8 -set(SDK_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../../..)
9 +set(SDK_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../CubismSdkForNative-5-r.4)
10  set(CORE_PATH ${SDK_ROOT_PATH}/Core)
11  set(FRAMEWORK_PATH ${SDK_ROOT_PATH}/Framework)
12  set(THIRD_PARTY_PATH ${SDK_ROOT_PATH}/Samples/OpenGL/thirdParty)
13 @@ -35,7 +35,7 @@ set(GLFW_INSTALL OFF CACHE BOOL "" FORCE
14  set(BUILD_UTILS OFF CACHE BOOL "" FORCE)
15  
16  # Specify version of compiler.
17 -set(CMAKE_CXX_STANDARD 14)
18 +set(CMAKE_CXX_STANDARD 17)
19  set(CMAKE_CXX_STANDARD_REQUIRED ON)
20  set(CMAKE_CXX_EXTENSIONS OFF)
21  
22 @@ -67,6 +67,11 @@ target_link_libraries(Framework Live2DCu
23  # Find opengl libraries.
24  find_package(OpenGL REQUIRED)
25  
26 +# Add MouseTrackerForCubism
27 +find_package(PkgConfig)
28 +pkg_check_modules(GTKMM gtkmm-3.0)
29 +add_subdirectory(../.. MouseTrackerForCubism_build)
30 +
31  # Make executable app.
32  add_executable(${APP_NAME})
33  # Add common source files.
34 @@ -79,9 +84,20 @@ target_link_libraries(${APP_NAME}
35    Framework
36    glfw
37    ${OPENGL_LIBRARIES}
38 +  MouseTrackerForCubism
39 +  stdc++fs
40  )
41  # Specify include directories.
42 -target_include_directories(${APP_NAME} PRIVATE ${STB_PATH})
43 +target_include_directories(${APP_NAME} PRIVATE ${STB_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/../../include ${GTKMM_INCLUDE_DIRS})
44 +
45 +# Copy GUI to build directory
46 +add_custom_command(
47 +  TARGET ${APP_NAME}
48 +  POST_BUILD
49 +  COMMAND
50 +    ${CMAKE_COMMAND} -E
51 +      copy ${CMAKE_CURRENT_SOURCE_DIR}/../../src/gui.glade $<TARGET_FILE_DIR:${APP_NAME}>/gui.glade
52 +)
53  
54  # Copy resource directory to build directory.
55  add_custom_command(
56 diff -pruN --exclude build ./demo_clean/scripts/make_gcc ./demo_dev/scripts/make_gcc
57 --- ./demo_clean/scripts/make_gcc       2025-05-30 00:59:58.252401066 +0100
58 +++ ./demo_dev/scripts/make_gcc 2023-05-28 09:11:29.467788463 +0100
59 @@ -5,42 +5,9 @@ set -ue
60  SCRIPT_PATH=$(cd $(dirname $0) && pwd)
61  CMAKE_PATH=$SCRIPT_PATH/..
62  BUILD_PATH=$SCRIPT_PATH/../build/make_gcc
63 -MINIMUM_DEMO="OFF"
64 -DATA=""
65 -
66 -if [ "$#" -ne 0 ]; then
67 - DATA="$1"
68 -fi
69 -
70 -while :
71 -do
72 -
73 - if [ -z "$DATA" ]; then
74 -   echo "Choose which format you would like to create the demo."
75 -   echo "Full version : 1"
76 -   echo "Minimum version : 2"
77 -   read -p "Your Choice : " DATA
78 - fi
79 -
80 - case "$DATA" in
81 -   "1" )
82 -     echo "Making Full Demo"
83 -     MINIMUM_DEMO="OFF"
84 -     break ;;
85 -   "2" )
86 -     echo "Making Minimum Demo"
87 -     MINIMUM_DEMO="ON"
88 -     break ;;
89 -   * )
90 -     echo "You need to enter a valid number."
91 -     DATA="" ;;
92 - esac
93 -done
94  
95  # Run CMake.
96  cmake -S "$CMAKE_PATH" \
97    -B "$BUILD_PATH" \
98 -  -D CMAKE_BUILD_TYPE=Release \
99 -  -D CSM_MINIMUM_DEMO=$MINIMUM_DEMO \
100 -  -D GLFW_BUILD_WAYLAND=OFF
101 -cd "$BUILD_PATH" && make
102 +  -D CMAKE_BUILD_TYPE=Release
103 +cd "$BUILD_PATH" && make -j4
104 diff -pruN --exclude build ./demo_clean/src/LAppDefine.cpp ./demo_dev/src/LAppDefine.cpp
105 --- ./demo_clean/src/LAppDefine.cpp     2025-05-30 00:59:58.252401066 +0100
106 +++ ./demo_dev/src/LAppDefine.cpp       2025-05-30 01:07:49.665920483 +0100
107 @@ -60,11 +60,11 @@ namespace LAppDefine {
108      const csmInt32 PriorityForce = 3;
109  
110      // デバッグ用ログの表示オプション
111 -    const csmBool DebugLogEnable = true;
112 +    const csmBool DebugLogEnable = false;
113      const csmBool DebugTouchLogEnable = false;
114  
115      // Frameworkから出力するログのレベル設定
116 -    const CubismFramework::Option::LogLevel CubismLoggingLevel = CubismFramework::Option::LogLevel_Verbose;
117 +    const CubismFramework::Option::LogLevel CubismLoggingLevel = CubismFramework::Option::LogLevel_Warning;
118  
119      // デフォルトのレンダーターゲットサイズ
120      const csmInt32 RenderTargetWidth = 1900;
121 diff -pruN --exclude build ./demo_clean/src/LAppDelegate.cpp ./demo_dev/src/LAppDelegate.cpp
122 --- ./demo_clean/src/LAppDelegate.cpp   2025-05-30 00:59:58.252401066 +0100
123 +++ ./demo_dev/src/LAppDelegate.cpp     2025-05-30 01:09:31.843395630 +0100
124 @@ -46,7 +46,8 @@ void LAppDelegate::ReleaseInstance()
125      s_instance = NULL;
126  }
127  
128 -bool LAppDelegate::Initialize()
129 +bool LAppDelegate::Initialize(int initWindowWidth, int initWindowHeight,
130 +                              const char *windowTitle)
131  {
132      if (DebugLogEnable)
133      {
134 @@ -64,7 +65,13 @@ bool LAppDelegate::Initialize()
135      }
136  
137      // Windowの生成_
138 -    _window = glfwCreateWindow(RenderTargetWidth, RenderTargetHeight, "SAMPLE", NULL, NULL);
139 +    _window = glfwCreateWindow(
140 +        initWindowWidth ? initWindowWidth : RenderTargetWidth,
141 +        initWindowHeight ? initWindowHeight : RenderTargetHeight,
142 +        windowTitle ? windowTitle : "SAMPLE",
143 +        NULL,
144 +        NULL);
145 +
146      if (_window == NULL)
147      {
148          if (DebugLogEnable)
149 @@ -96,10 +103,6 @@ bool LAppDelegate::Initialize()
150      glEnable(GL_BLEND);
151      glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
152  
153 -    //コールバック関数の登録
154 -    glfwSetMouseButtonCallback(_window, EventHandler::OnMouseCallBack);
155 -    glfwSetCursorPosCallback(_window, EventHandler::OnMouseCallBack);
156 -
157      // ウィンドウサイズ記憶
158      int width, height;
159      glfwGetWindowSize(LAppDelegate::GetInstance()->GetWindow(), &width, &height);
160 diff -pruN --exclude build ./demo_clean/src/LAppDelegate.hpp ./demo_dev/src/LAppDelegate.hpp
161 --- ./demo_clean/src/LAppDelegate.hpp   2025-05-30 00:59:58.252401066 +0100
162 +++ ./demo_dev/src/LAppDelegate.hpp     2025-05-30 01:10:00.192935412 +0100
163 @@ -40,7 +40,8 @@ public:
164      /**
165      * @brief   APPに必要なものを初期化する。
166      */
167 -    bool Initialize();
168 +    bool Initialize(int initWindowWidth = 0, int initWindowHeight = 0,
169 +                    const char *windowTitle = "SAMPLE");
170  
171      /**
172      * @brief   解放する。
173 diff -pruN --exclude build ./demo_clean/src/LAppLive2DManager.cpp ./demo_dev/src/LAppLive2DManager.cpp
174 --- ./demo_clean/src/LAppLive2DManager.cpp      2025-05-30 00:59:58.252401066 +0100
175 +++ ./demo_dev/src/LAppLive2DManager.cpp        2025-05-30 01:12:18.865322293 +0100
176 @@ -6,13 +6,7 @@
177   */
178  
179  #include "LAppLive2DManager.hpp"
180 -#include <stdio.h>
181 -#include <stdlib.h>
182 -#include <string.h>
183 -#include <dirent.h>
184 -#include <unistd.h>
185 -#include <libgen.h>
186 -#include <limits.h>
187 +#include <string>
188  #include <GL/glew.h>
189  #include <GLFW/glfw3.h>
190  #include <Rendering/CubismRenderer.hpp>
191 @@ -68,12 +62,11 @@ void LAppLive2DManager::ReleaseInstance(
192  
193  LAppLive2DManager::LAppLive2DManager()
194      : _viewMatrix(NULL)
195 -    , _sceneIndex(0)
196 +    , _projScaleFactor(1.0f)
197 +    , _translateX(0.0f)
198 +    , _translateY(0.0f)
199  {
200      _viewMatrix = new CubismMatrix44();
201 -    SetUpModel();
202 -
203 -    ChangeScene(_sceneIndex);
204  }
205  
206  LAppLive2DManager::~LAppLive2DManager()
207 @@ -92,60 +85,6 @@ void LAppLive2DManager::ReleaseAllModel(
208      _models.Clear();
209  }
210  
211 -void LAppLive2DManager::SetUpModel()
212 -{
213 -    // ResourcesPathの中にあるフォルダ名を全てクロールし、モデルが存在するフォルダを定義する。
214 -    // フォルダはあるが同名の.model3.jsonが見つからなかった場合はリストに含めない。
215 -    struct dirent *dirent;
216 -    csmString crawlPath(LAppDelegate::GetInstance()->GetExecuteAbsolutePath().c_str());
217 -    crawlPath += ResourcesPath;
218 -
219 -    DIR *pDir = opendir(crawlPath.GetRawString());
220 -    if (pDir == NULL) return;
221 -
222 -    _modelDir.Clear();
223 -
224 -    while ((dirent = readdir(pDir)) != NULL)
225 -    {
226 -        if ((dirent->d_type & DT_DIR) && strcmp(dirent->d_name, "..") != 0)
227 -        {
228 -            // フォルダと同名の.model3.jsonがあるか探索する
229 -            struct dirent *dirent2;
230 -
231 -            csmString modelName(dirent->d_name);
232 -
233 -            csmString modelPath(crawlPath);
234 -            modelPath += modelName;
235 -            modelPath.Append(1, '/');
236 -
237 -            csmString model3jsonName(modelName);
238 -            model3jsonName += ".model3.json";
239 -
240 -            DIR *pDir2 = opendir(modelPath.GetRawString());
241 -            while ((dirent2 = readdir(pDir2)) != NULL)
242 -            {
243 -                if (strcmp(dirent2->d_name, model3jsonName.GetRawString()) == 0)
244 -                {
245 -                    _modelDir.PushBack(csmString(dirent->d_name));
246 -                }
247 -            }
248 -            closedir(pDir2);
249 -        }
250 -    }
251 -    closedir(pDir);
252 -    qsort(_modelDir.GetPtr(), _modelDir.GetSize(), sizeof(csmString), CompareCsmString);
253 -}
254 -
255 -csmVector<csmString> LAppLive2DManager::GetModelDir() const
256 -{
257 -    return _modelDir;
258 -}
259 -
260 -csmInt32 LAppLive2DManager::GetModelDirSize() const
261 -{
262 -    return _modelDir.GetSize();
263 -}
264 -
265  LAppModel* LAppLive2DManager::GetModel(csmUint32 no) const
266  {
267      if (no < _models.GetSize())
268 @@ -172,26 +111,6 @@ void LAppLive2DManager::OnTap(csmFloat32
269      {
270          LAppPal::PrintLogLn("[APP]tap point: {x:%.2f y:%.2f}", x, y);
271      }
272 -
273 -    for (csmUint32 i = 0; i < _models.GetSize(); i++)
274 -    {
275 -        if (_models[i]->HitTest(HitAreaNameHead, x, y))
276 -        {
277 -            if (DebugLogEnable)
278 -            {
279 -                LAppPal::PrintLogLn("[APP]hit area: [%s]", HitAreaNameHead);
280 -            }
281 -            _models[i]->SetRandomExpression();
282 -        }
283 -        else if (_models[i]->HitTest(HitAreaNameBody, x, y))
284 -        {
285 -            if (DebugLogEnable)
286 -            {
287 -                LAppPal::PrintLogLn("[APP]hit area: [%s]", HitAreaNameBody);
288 -            }
289 -            _models[i]->StartRandomMotion(MotionGroupTapBody, PriorityNormal, FinishedMotion, BeganMotion);
290 -        }
291 -    }
292  }
293  
294  void LAppLive2DManager::OnUpdate() const
295 @@ -215,12 +134,15 @@ void LAppLive2DManager::OnUpdate() const
296          {
297              // 横に長いモデルを縦長ウィンドウに表示する際モデルの横サイズでscaleを算出する
298              model->GetModelMatrix()->SetWidth(2.0f);
299 -            projection.Scale(1.0f, static_cast<float>(width) / static_cast<float>(height));
300 +            projection.Scale(_projScaleFactor,
301 +                             _projScaleFactor * static_cast<float>(width) / static_cast<float>(height));
302          }
303          else
304          {
305 -            projection.Scale(static_cast<float>(height) / static_cast<float>(width), 1.0f);
306 +            projection.Scale(_projScaleFactor * static_cast<float>(height) / static_cast<float>(width),
307 +                             _projScaleFactor);
308          }
309 +        projection.Translate(_translateX, _translateY);
310  
311          // 必要があればここで乗算
312          if (_viewMatrix != NULL)
313 @@ -237,37 +159,15 @@ void LAppLive2DManager::OnUpdate() const
314      }
315  }
316  
317 -void LAppLive2DManager::NextScene()
318 +void LAppLive2DManager::SetModel(std::string modelName, bool useOldParamId)
319  {
320 -    csmInt32 no = (_sceneIndex + 1) % GetModelDirSize();
321 -    ChangeScene(no);
322 -}
323 -
324 -void LAppLive2DManager::ChangeScene(Csm::csmInt32 index)
325 -{
326 -    _sceneIndex = index;
327 -    if (DebugLogEnable)
328 -    {
329 -        LAppPal::PrintLogLn("[APP]model index: %d", _sceneIndex);
330 -    }
331 -
332 -    // ModelDir[]に保持したディレクトリ名から
333 -    // model3.jsonのパスを決定する.
334 -    // ディレクトリ名とmodel3.jsonの名前を一致させておくこと.
335 -    const csmString& model = _modelDir[index];
336 -    LAppPal::PrintLogLn("[APP]_modelDir: %s", model.GetRawString());
337 -
338 -    csmString modelPath(LAppDelegate::GetInstance()->GetExecuteAbsolutePath().c_str());
339 -    modelPath += ResourcesPath;
340 -    modelPath += model;
341 -    modelPath.Append(1, '/');
342 -
343 -    csmString modelJsonName(model);
344 +    std::string modelPath = LAppDelegate::GetInstance()->GetExecuteAbsolutePath() + ResourcesPath + modelName + "/";
345 +    std::string modelJsonName = modelName;
346      modelJsonName += ".model3.json";
347  
348      ReleaseAllModel();
349 -    _models.PushBack(new LAppModel());
350 -    _models[0]->LoadAssets(modelPath.GetRawString(), modelJsonName.GetRawString());
351 +    _models.PushBack(new LAppModel(useOldParamId));
352 +    _models[0]->LoadAssets(modelPath.c_str(), modelJsonName.c_str());
353  
354      /*
355       * モデル半透明表示を行うサンプルを提示する。
356 @@ -288,8 +188,8 @@ void LAppLive2DManager::ChangeScene(Csm:
357  
358  #if defined(USE_RENDER_TARGET) || defined(USE_MODEL_RENDER_TARGET)
359          // モデル個別にαを付けるサンプルとして、もう1体モデルを作成し、少し位置をずらす
360 -        _models.PushBack(new LAppModel());
361 -        _models[1]->LoadAssets(modelPath.GetRawString(), modelJsonName.GetRawString());
362 +        _models.PushBack(new LAppModel(useOldParamId));
363 +        _models[1]->LoadAssets(modelPath.c_str(), modelJsonName.c_str());
364          _models[1]->GetModelMatrix()->TranslateX(0.2f);
365  #endif
366  
367 @@ -317,3 +217,20 @@ void LAppLive2DManager::SetViewMatrix(Cu
368          _viewMatrix->GetArray()[i] = m->GetArray()[i];
369      }
370  }
371 +
372 +void LAppLive2DManager::SetTracker(MouseCursorTracker *tracker)
373 +{
374 +    for (auto it = _models.Begin(); it != _models.End(); ++it)
375 +    {
376 +        (*it)->SetTracker(tracker);
377 +    }
378 +}
379 +
380 +void LAppLive2DManager::SetProjectionScaleTranslate(float scaleFactor,
381 +                                                    float translateX,
382 +                                                    float translateY)
383 +{
384 +    _projScaleFactor = scaleFactor;
385 +    _translateX = translateX;
386 +    _translateY = translateY;
387 +}
388 diff -pruN --exclude build ./demo_clean/src/LAppLive2DManager.hpp ./demo_dev/src/LAppLive2DManager.hpp
389 --- ./demo_clean/src/LAppLive2DManager.hpp      2025-05-30 00:59:58.256401196 +0100
390 +++ ./demo_dev/src/LAppLive2DManager.hpp        2025-05-30 01:12:54.056756547 +0100
391 @@ -6,12 +6,15 @@
392   */
393  #pragma once
394  
395 +#include <string>
396  #include <CubismFramework.hpp>
397  #include <Math/CubismMatrix44.hpp>
398  #include <Type/csmVector.hpp>
399  
400  class LAppModel;
401  
402 +class MouseCursorTracker;
403 +
404  /**
405  * @brief サンプルアプリケーションにおいてCubismModelを管理するクラス<br>
406  *         モデル生成と破棄、タップイベントの処理、モデル切り替えを行う。
407 @@ -36,24 +39,6 @@ public:
408      static void ReleaseInstance();
409  
410      /**
411 -    * @brief   Resources フォルダにあるモデルフォルダ名をセットする
412 -    *
413 -    */
414 -    void SetUpModel();
415 -
416 -    /**
417 -    * @brief   Resources フォルダにあるモデルフォルダ名を取得する
418 -    *
419 -    */
420 -    Csm::csmVector<Csm::csmString> GetModelDir() const;
421 -
422 -    /**
423 -    * @brief   Resources フォルダにあるモデルフォルダのサイズを取得する
424 -    *
425 -    */
426 -    Csm::csmInt32 GetModelDirSize() const;
427 -
428 -    /**
429      * @brief   現在のシーンで保持しているモデルを返す
430      *
431      * @param[in]   no  モデルリストのインデックス値
432 @@ -90,16 +75,14 @@ public:
433      void OnUpdate() const;
434  
435      /**
436 -    * @brief   次のシーンに切り替える<br>
437 -    *           サンプルアプリケーションではモデルセットの切り替えを行う。
438 -    */
439 -    void NextScene();
440 -
441 -    /**
442 -    * @brief   シーンを切り替える<br>
443 -    *           サンプルアプリケーションではモデルセットの切り替えを行う。
444 -    */
445 -    void ChangeScene(Csm::csmInt32 index);
446 +     * @brief Set model data
447 +     *
448 +     * @param[in] modelName : Name of model, should be the same for both
449 +     *                        the directory and the model3.json file
450 +     * @param[in] useOldParamId : If true, translate new (Cubism 3+)
451 +     *                            parameter IDs to old (Cubism 2.1) ones
452 +     */
453 +    void SetModel(std::string modelName, bool useOldParamId);
454  
455      /**
456       * @brief   モデル個数を得る
457 @@ -112,6 +95,24 @@ public:
458       */
459      void SetViewMatrix(Live2D::Cubism::Framework::CubismMatrix44* m);
460  
461 +    /**
462 +     * @brief Set the pointer to the MouseCursorTracker instance
463 +     *
464 +     * @param[in] tracker : Pointer to MouseCursorTracker instance
465 +     */
466 +    void SetTracker(MouseCursorTracker *tracker);
467 +
468 +    /**
469 +     * @brief Set projection scale factor and translation parameters
470 +     *
471 +     * @param[in] scaleFactor : Scale factor applied in both X and Y directions
472 +     * @param[in] translateX : Translation in X direction
473 +     * @param[in] translateY : Translation in Y direction
474 +     */
475 +    void SetProjectionScaleTranslate(float scaleFactor,
476 +                                     float translateX,
477 +                                     float translateY);
478 +
479  private:
480      /**
481      * @brief  コンストラクタ
482 @@ -125,7 +126,8 @@ private:
483  
484      Csm::CubismMatrix44* _viewMatrix; ///< モデル描画に用いるView行列
485      Csm::csmVector<LAppModel*> _models; ///< モデルインスタンスのコンテナ
486 -    Csm::csmInt32 _sceneIndex; ///< 表示するシーンのインデックス値
487  
488 -    Csm::csmVector<Csm::csmString> _modelDir; ///< モデルディレクトリ名のコンテナ
489 +    float _projScaleFactor;
490 +    float _translateX;
491 +    float _translateY;
492  };
493 diff -pruN --exclude build ./demo_clean/src/LAppModel.cpp ./demo_dev/src/LAppModel.cpp
494 --- ./demo_clean/src/LAppModel.cpp      2025-05-30 00:59:58.256401196 +0100
495 +++ ./demo_dev/src/LAppModel.cpp        2025-05-30 01:17:12.780545257 +0100
496 @@ -21,26 +21,32 @@
497  #include "LAppTextureManager.hpp"
498  #include "LAppDelegate.hpp"
499  
500 +#include "mouse_cursor_tracker.h"
501 +
502 +#include <iostream>
503 +
504  using namespace Live2D::Cubism::Framework;
505  using namespace Live2D::Cubism::Framework::DefaultParameterId;
506  using namespace LAppDefine;
507  
508 -LAppModel::LAppModel()
509 +LAppModel::LAppModel(bool useOldParamId)
510      : LAppModel_Common()
511      , _modelSetting(NULL)
512      , _userTimeSeconds(0.0f)
513 +    , _tracker(nullptr)
514 +    , _useOldParamId(useOldParamId)
515  {
516      if (DebugLogEnable)
517      {
518          _debugMode = true;
519      }
520  
521 -    _idParamAngleX = CubismFramework::GetIdManager()->GetId(ParamAngleX);
522 -    _idParamAngleY = CubismFramework::GetIdManager()->GetId(ParamAngleY);
523 -    _idParamAngleZ = CubismFramework::GetIdManager()->GetId(ParamAngleZ);
524 -    _idParamBodyAngleX = CubismFramework::GetIdManager()->GetId(ParamBodyAngleX);
525 -    _idParamEyeBallX = CubismFramework::GetIdManager()->GetId(ParamEyeBallX);
526 -    _idParamEyeBallY = CubismFramework::GetIdManager()->GetId(ParamEyeBallY);
527 +    _idParamAngleX = CubismFramework::GetIdManager()->GetId(_(ParamAngleX));
528 +    _idParamAngleY = CubismFramework::GetIdManager()->GetId(_(ParamAngleY));
529 +    _idParamAngleZ = CubismFramework::GetIdManager()->GetId(_(ParamAngleZ));
530 +    _idParamBodyAngleX = CubismFramework::GetIdManager()->GetId(_(ParamBodyAngleX));
531 +    _idParamEyeBallX = CubismFramework::GetIdManager()->GetId(_(ParamEyeBallX));
532 +    _idParamEyeBallY = CubismFramework::GetIdManager()->GetId(_(ParamEyeBallY));
533  }
534  
535  LAppModel::~LAppModel()
536 @@ -179,7 +185,7 @@ void LAppModel::SetupModel(ICubismModelS
537          breathParameters.PushBack(CubismBreath::BreathParameterData(_idParamAngleY, 0.0f, 8.0f, 3.5345f, 0.5f));
538          breathParameters.PushBack(CubismBreath::BreathParameterData(_idParamAngleZ, 0.0f, 10.0f, 5.5345f, 0.5f));
539          breathParameters.PushBack(CubismBreath::BreathParameterData(_idParamBodyAngleX, 0.0f, 4.0f, 15.5345f, 0.5f));
540 -        breathParameters.PushBack(CubismBreath::BreathParameterData(CubismFramework::GetIdManager()->GetId(ParamBreath), 0.5f, 0.5f, 3.2345f, 0.5f));
541 +        breathParameters.PushBack(CubismBreath::BreathParameterData(CubismFramework::GetIdManager()->GetId(_(ParamBreath)), 0.5f, 0.5f, 3.2345f, 0.5f));
542  
543          _breath->SetParameters(breathParameters);
544      }
545 @@ -322,86 +328,117 @@ void LAppModel::Update()
546      const csmFloat32 deltaTimeSeconds = LAppPal::GetDeltaTime();
547      _userTimeSeconds += deltaTimeSeconds;
548  
549 -    _dragManager->Update(deltaTimeSeconds);
550 -    _dragX = _dragManager->GetX();
551 -    _dragY = _dragManager->GetY();
552 -
553 -    // モーションによるパラメータ更新の有無
554 -    csmBool motionUpdated = false;
555 -
556 -    //-----------------------------------------------------------------
557 -    _model->LoadParameters(); // 前回セーブされた状態をロード
558 -    if (_motionManager->IsFinished())
559 -    {
560 -        // モーションの再生がない場合、待機モーションの中からランダムで再生する
561 -        StartRandomMotion(MotionGroupIdle, PriorityIdle);
562 -    }
563 -    else
564 +    if (_tracker)
565      {
566 -        motionUpdated = _motionManager->UpdateMotion(_model, deltaTimeSeconds); // モーションを更新
567 -    }
568 -    _model->SaveParameters(); // 状態を保存
569 -    //-----------------------------------------------------------------
570 +        auto idMan = CubismFramework::GetIdManager();
571 +        auto params = _tracker->getParams();
572  
573 -    // 不透明度
574 -    _opacity = _model->GetModelOpacity();
575 +        _model->LoadParameters(); // 前回セーブされた状態をロード
576  
577 -    // まばたき
578 -    if (!motionUpdated)
579 -    {
580 -        if (_eyeBlink != NULL)
581 +        int paramsMotionPriority = static_cast<int>(params.motionPriority);
582 +
583 +        if (paramsMotionPriority != PriorityNone)
584          {
585 -            // メインモーションの更新がないとき
586 -            _eyeBlink->UpdateParameters(_model, deltaTimeSeconds); // 目パチ
587 +            StartMotion(params.motionGroup.c_str(), params.motionNumber,
588 +                        paramsMotionPriority);
589          }
590 -    }
591 +        else if (params.randomIdleMotion && _motionManager->IsFinished())
592 +        {
593 +            // モーションの再生がない場合、待機モーションの中からランダムで再生する
594 +            StartRandomMotion(MotionGroupIdle, PriorityIdle);
595 +        }
596 +        // FIXME pose does not return to normal after motion
597 +        // if we don't have randomIdleMotion set
598 +        else
599 +        {
600 +            _motionManager->UpdateMotion(_model, deltaTimeSeconds); // モーションを更新
601 +        }
602 +        _model->SaveParameters(); // 状態を保存
603  
604 -    if (_expressionManager != NULL)
605 -    {
606 -        _expressionManager->UpdateMotion(_model, deltaTimeSeconds); // 表情でパラメータ更新(相対変化)
607 -    }
608 +        if (params.expression != "")
609 +        {
610 +            SetExpression(params.expression.c_str());
611 +        }
612 +        if (_expressionManager != NULL)
613 +        {
614 +            _expressionManager->UpdateMotion(_model, deltaTimeSeconds); // 表情でパラメータ更新(相対変化)
615 +        }
616  
617 -    //ドラッグによる変化
618 -    //ドラッグによる顔の向きの調整
619 -    _model->AddParameterValue(_idParamAngleX, _dragX * 30); // -30から30の値を加える
620 -    _model->AddParameterValue(_idParamAngleY, _dragY * 30);
621 -    _model->AddParameterValue(_idParamAngleZ, _dragX * _dragY * -30);
622 +        bool autoBlink = params.autoBlink && _eyeBlink;
623 +        auto eyeLOpenIt = params.live2d.find("ParamEyeLOpen");
624 +        auto eyeROpenIt = params.live2d.find("ParamEyeROpen");
625  
626 -    //ドラッグによる体の向きの調整
627 -    _model->AddParameterValue(_idParamBodyAngleX, _dragX * 10); // -10から10の値を加える
628 +        if (autoBlink)
629 +        {
630 +            // Handle blink first
631 +            _eyeBlink->UpdateParameters(_model, deltaTimeSeconds);
632 +        }
633  
634 -    //ドラッグによる目の向きの調整
635 -    _model->AddParameterValue(_idParamEyeBallX, _dragX); // -1から1の値を加える
636 -    _model->AddParameterValue(_idParamEyeBallY, _dragY);
637 +        if (eyeLOpenIt != params.live2d.end())
638 +        {
639 +            // If value specified, override blinking
640 +            _model->SetParameterValue(idMan->GetId(_("ParamEyeLOpen")),
641 +                                      eyeLOpenIt->second);
642 +        }
643 +        else if (!autoBlink)
644 +        {
645 +            // If no value specified and no auto blink, set to 1
646 +            _model->SetParameterValue(idMan->GetId(_("ParamEyeLOpen")), 1);
647  
648 -    // 呼吸など
649 -    if (_breath != NULL)
650 -    {
651 -        _breath->UpdateParameters(_model, deltaTimeSeconds);
652 -    }
653 +        }
654  
655 -    // 物理演算の設定
656 -    if (_physics != NULL)
657 -    {
658 -        _physics->Evaluate(_model, deltaTimeSeconds);
659 -    }
660 +        if (eyeROpenIt != params.live2d.end())
661 +        {
662 +            _model->SetParameterValue(idMan->GetId(_("ParamEyeROpen")),
663 +                                      eyeROpenIt->second);
664 +        }
665 +        else if (!autoBlink)
666 +        {
667 +            _model->SetParameterValue(idMan->GetId(_("ParamEyeROpen")), 1);
668 +        }
669  
670 -    // リップシンクの設定
671 -    if (_lipSync)
672 -    {
673 -        // リアルタイムでリップシンクを行う場合、システムから音量を取得して0〜1の範囲で値を入力します。
674 -        csmFloat32 value = 0.0f;
675  
676 -        // 状態更新/RMS値取得
677 -        _wavFileHandler.Update(deltaTimeSeconds);
678 -        value = _wavFileHandler.GetRms();
679 +        if (params.useLipSync && _lipSync)
680 +        {
681 +            csmFloat32 value = params.lipSyncParam; // 0 to 1
682  
683 -        for (csmUint32 i = 0; i < _lipSyncIds.GetSize(); ++i)
684 +            for (csmUint32 i = 0; i < _lipSyncIds.GetSize(); ++i)
685 +            {
686 +                _model->AddParameterValue(_lipSyncIds[i], value, 0.8f);
687 +            }
688 +        }
689 +        else
690 +        {
691 +            _model->SetParameterValue(idMan->GetId(_("ParamMouthOpenY")),
692 +                                      params.live2d["ParamMouthOpenY"]);
693 +        }
694 +
695 +        for (auto const &entry : params.live2d)
696 +        {
697 +            std::string key = entry.first;
698 +            double val = entry.second;
699 +
700 +            if (key != "ParamEyeLOpen" && key != "ParamEyeROpen" &&
701 +                key != "ParamMouthOpenY")
702 +            {
703 +                _model->SetParameterValue(idMan->GetId(_(key)), val);
704 +            }
705 +        }
706 +
707 +        if (params.autoBreath && _breath)
708          {
709 -            _model->AddParameterValue(_lipSyncIds[i], value, 0.8f);
710 +            // Note: _model->LoadParameters and SaveParameters is needed
711 +            // before - see above.
712 +            _breath->UpdateParameters(_model, deltaTimeSeconds);
713          }
714      }
715  
716 +    // 物理演算の設定
717 +    if (_physics != NULL)
718 +    {
719 +        _physics->Evaluate(_model, deltaTimeSeconds);
720 +    }
721 +
722      // ポーズの設定
723      if (_pose != NULL)
724      {
725 @@ -464,7 +501,6 @@ CubismMotionQueueEntryHandle LAppModel::
726      {
727          csmString path = voice;
728          path = _modelHomeDir + path;
729 -        _wavFileHandler.Start(path);
730      }
731  
732      if (_debugMode)
733 @@ -616,3 +652,42 @@ Csm::Rendering::CubismOffscreenSurface_O
734  {
735      return _renderBuffer;
736  }
737 +
738 +void LAppModel::SetTracker(MouseCursorTracker *tracker)
739 +{
740 +    _tracker = tracker;
741 +}
742 +
743 +Csm::ICubismModelSetting* LAppModel::GetModelSetting(void) const
744 +{
745 +    return _modelSetting;
746 +}
747 +
748 +Csm::csmString LAppModel::_(std::string s)
749 +{
750 +    std::string ans;
751 +    if (_useOldParamId)
752 +    {
753 +        if (s == "ParamTere")
754 +        {
755 +            ans = "PARAM_CHEEK";
756 +        }
757 +        else
758 +        {
759 +            for (size_t i = 0; i < s.size(); i++)
760 +            {
761 +                if (std::isupper(s[i]) && i != 0)
762 +                {
763 +                    ans += '_';
764 +                }
765 +                ans += std::toupper(s[i]);
766 +            }
767 +        }
768 +    }
769 +    else
770 +    {
771 +        ans = s;
772 +    }
773 +    return csmString(ans.c_str());
774 +}
775 +
776 diff -pruN --exclude build ./demo_clean/src/LAppModel.hpp ./demo_dev/src/LAppModel.hpp
777 --- ./demo_clean/src/LAppModel.hpp      2025-05-30 00:59:58.252401066 +0100
778 +++ ./demo_dev/src/LAppModel.hpp        2025-05-30 01:15:06.963872011 +0100
779 @@ -12,8 +12,8 @@
780  #include <Type/csmRectF.hpp>
781  #include <Rendering/OpenGL/CubismOffscreenSurface_OpenGLES2.hpp>
782  
783 -#include "LAppWavFileHandler_Common.hpp"
784  #include "LAppModel_Common.hpp"
785 +#include "mouse_cursor_tracker.h"
786  
787  /**
788   * @brief ユーザーが実際に使用するモデルの実装クラス<br>
789 @@ -25,8 +25,11 @@ class LAppModel : public LAppModel_Commo
790  public:
791      /**
792       * @brief コンストラクタ
793 +     *
794 +     * @param[in] useOldParamId : If true, translate new (Cubism 3+)
795 +     *                            parameter IDs to old (Cubism 2.1)  ones
796       */
797 -    LAppModel();
798 +    LAppModel(bool useOldParamId);
799  
800      /**
801       * @brief デストラクタ
802 @@ -116,6 +119,15 @@ public:
803       */
804      Csm::Rendering::CubismOffscreenSurface_OpenGLES2& GetRenderBuffer();
805  
806 +    /**
807 +     * @brief Set the pointer to the MouseCursorTracker instance
808 +     *
809 +     * @param[in] tracker : Pointer to MouseCursorTracker instance
810 +     */
811 +    void SetTracker(MouseCursorTracker *tracker);
812 +
813 +    Csm::ICubismModelSetting* GetModelSetting(void) const;
814 +
815  protected:
816      /**
817       *  @brief  モデルを描画する処理。モデルを描画する空間のView-Projection行列を渡す。
818 @@ -169,6 +181,17 @@ private:
819      */
820      void ReleaseExpressions();
821  
822 +    /**
823 +     * @brief Translate new (Cubism 3+) parameter IDs to old (Cubism 2.1) ones
824 +     *
825 +     * @param[in] s : New parameter ID
826 +     *
827 +     * @return Old parameter ID
828 +     */
829 +    Csm::csmString _(std::string s);
830 +
831 +    bool _useOldParamId;
832 +
833      Csm::ICubismModelSetting* _modelSetting; ///< モデルセッティング情報
834      Csm::csmString _modelHomeDir; ///< モデルセッティングが置かれたディレクトリ
835      Csm::csmFloat32 _userTimeSeconds; ///< デルタ時間の積算値[秒]
836 @@ -185,7 +208,7 @@ private:
837      const Csm::CubismId* _idParamEyeBallX; ///< パラメータID: ParamEyeBallX
838      const Csm::CubismId* _idParamEyeBallY; ///< パラメータID: ParamEyeBallXY
839  
840 -    LAppWavFileHandler_Common _wavFileHandler; ///< wavファイルハンドラ
841 -
842      Csm::Rendering::CubismOffscreenSurface_OpenGLES2 _renderBuffer;   ///< フレームバッファ以外の描画先
843 +
844 +    MouseCursorTracker *_tracker;
845  };
846 diff -pruN --exclude build ./demo_clean/src/LAppTextureManager.cpp ./demo_dev/src/LAppTextureManager.cpp
847 --- ./demo_clean/src/LAppTextureManager.cpp     2025-05-30 00:59:58.252401066 +0100
848 +++ ./demo_dev/src/LAppTextureManager.cpp       2025-05-30 01:18:50.733874528 +0100
849 @@ -96,6 +96,46 @@ LAppTextureManager::TextureInfo* LAppTex
850  
851  }
852  
853 +LAppTextureManager::TextureInfo* LAppTextureManager::CreateTextureFromColor(
854 +    uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha
855 +)
856 +{
857 +    int width = 8, height = 8;
858 +
859 +    uint8_t pixels[height][width][4];
860 +    for (std::size_t h = 0; h < height; h++)
861 +    {
862 +        for (std::size_t w = 0; w < width; w++)
863 +        {
864 +            pixels[h][w][0] = red;
865 +            pixels[h][w][1] = green;
866 +            pixels[h][w][2] = blue;
867 +            pixels[h][w][3] = alpha;
868 +        }
869 +    }
870 +
871 +    GLuint textureId;
872 +    glGenTextures(1, &textureId);
873 +    glBindTexture(GL_TEXTURE_2D, textureId);
874 +    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
875 +
876 +    glGenerateMipmap(GL_TEXTURE_2D);
877 +    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
878 +    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
879 +    glBindTexture(GL_TEXTURE_2D, 0);
880 +
881 +
882 +    LAppTextureManager::TextureInfo* textureInfo = new LAppTextureManager::TextureInfo();
883 +    textureInfo->fileName = "";
884 +    textureInfo->width = width;
885 +    textureInfo->height = height;
886 +    textureInfo->id = textureId;
887 +
888 +    _texturesInfo.PushBack(textureInfo);
889 +
890 +    return textureInfo;
891 +}
892 +
893  void LAppTextureManager::ReleaseTextures()
894  {
895      for (Csm::csmUint32 i = 0; i < _texturesInfo.GetSize(); i++)
896 diff -pruN --exclude build ./demo_clean/src/LAppTextureManager.hpp ./demo_dev/src/LAppTextureManager.hpp
897 --- ./demo_clean/src/LAppTextureManager.hpp     2025-05-30 00:59:58.252401066 +0100
898 +++ ./demo_dev/src/LAppTextureManager.hpp       2025-05-30 01:19:14.566288429 +0100
899 @@ -41,6 +41,8 @@ public:
900      */
901      TextureInfo* CreateTextureFromPngFile(std::string fileName);
902  
903 +    TextureInfo *CreateTextureFromColor(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha = 255);
904 +
905      /**
906      * @brief 画像の解放
907      *
908 diff -pruN --exclude build ./demo_clean/src/LAppView.cpp ./demo_dev/src/LAppView.cpp
909 --- ./demo_clean/src/LAppView.cpp       2025-05-30 00:59:58.252401066 +0100
910 +++ ./demo_dev/src/LAppView.cpp 2025-05-30 01:24:23.734104120 +0100
911 @@ -81,9 +81,6 @@ void LAppView::Initialize(int width, int
912  void LAppView::Render()
913  {
914      _back->Render();
915 -    _gear->Render();
916 -    _power->Render();
917 -
918  
919      LAppLive2DManager* Live2DManager = LAppLive2DManager::GetInstance();
920  
921 @@ -125,35 +122,17 @@ void LAppView::InitializeSprite()
922      glfwGetWindowSize(LAppDelegate::GetInstance()->GetWindow(), &width, &height);
923  
924      LAppTextureManager* textureManager = LAppDelegate::GetInstance()->GetTextureManager();
925 -    const string resourcesPath = LAppDelegate::GetInstance()->GetExecuteAbsolutePath() + ResourcesPath;
926  
927 -    string imageName = BackImageName;
928 -    LAppTextureManager::TextureInfo* backgroundTexture = textureManager->CreateTextureFromPngFile(resourcesPath + imageName);
929 +
930 +    LAppTextureManager::TextureInfo* backgroundTexture =
931 +        textureManager->CreateTextureFromColor(0, 255, 0);
932  
933      float x = width * 0.5f;
934      float y = height * 0.5f;
935 -    float fWidth = static_cast<float>(backgroundTexture->width * 2.0f);
936 -    float fHeight = static_cast<float>(height) * 0.95f;
937 +    float fWidth = static_cast<float>(width);
938 +    float fHeight = static_cast<float>(height);
939      _back = new LAppSprite(x, y, fWidth, fHeight, backgroundTexture->id, programId);
940  
941 -    imageName = GearImageName;
942 -    LAppTextureManager::TextureInfo* gearTexture = textureManager->CreateTextureFromPngFile(resourcesPath + imageName);
943 -
944 -    x = static_cast<float>(width - gearTexture->width * 0.5f);
945 -    y = static_cast<float>(height - gearTexture->height * 0.5f);
946 -    fWidth = static_cast<float>(gearTexture->width);
947 -    fHeight = static_cast<float>(gearTexture->height);
948 -    _gear = new LAppSprite(x, y, fWidth, fHeight, gearTexture->id, programId);
949 -
950 -    imageName = PowerImageName;
951 -    LAppTextureManager::TextureInfo* powerTexture = textureManager->CreateTextureFromPngFile(resourcesPath + imageName);
952 -
953 -    x = static_cast<float>(width - powerTexture->width * 0.5f);
954 -    y = static_cast<float>(powerTexture->height * 0.5f);
955 -    fWidth = static_cast<float>(powerTexture->width);
956 -    fHeight = static_cast<float>(powerTexture->height);
957 -    _power = new LAppSprite(x, y, fWidth, fHeight, powerTexture->id, programId);
958 -
959      // 画面全体を覆うサイズ
960      x = width * 0.5f;
961      y = height * 0.5f;
962 @@ -192,18 +171,6 @@ void LAppView::OnTouchesEnded(float px,
963              LAppPal::PrintLogLn("[APP]touchesEnded x:%.2f y:%.2f", x, y);
964          }
965          live2DManager->OnTap(x, y);
966 -
967 -        // 歯車にタップしたか
968 -        if (_gear->IsHit(px, py))
969 -        {
970 -            live2DManager->NextScene();
971 -        }
972 -
973 -        // 電源ボタンにタップしたか
974 -        if (_power->IsHit(px, py))
975 -        {
976 -            LAppDelegate::GetInstance()->AppEnd();
977 -        }
978      }
979  }
980  
981 @@ -329,32 +296,4 @@ void LAppView::ResizeSprite()
982              _back->ResetRect(x, y, fWidth, fHeight);
983          }
984      }
985 -
986 -    if (_power)
987 -    {
988 -        GLuint id = _power->GetTextureId();
989 -        LAppTextureManager::TextureInfo* texInfo = textureManager->GetTextureInfoById(id);
990 -        if (texInfo)
991 -        {
992 -            x = static_cast<float>(width - texInfo->width * 0.5f);
993 -            y = static_cast<float>(texInfo->height * 0.5f);
994 -            fWidth = static_cast<float>(texInfo->width);
995 -            fHeight = static_cast<float>(texInfo->height);
996 -            _power->ResetRect(x, y, fWidth, fHeight);
997 -        }
998 -    }
999 -
1000 -    if (_gear)
1001 -    {
1002 -        GLuint id = _gear->GetTextureId();
1003 -        LAppTextureManager::TextureInfo* texInfo = textureManager->GetTextureInfoById(id);
1004 -        if (texInfo)
1005 -        {
1006 -            x = static_cast<float>(width - texInfo->width * 0.5f);
1007 -            y = static_cast<float>(height - texInfo->height * 0.5f);
1008 -            fWidth = static_cast<float>(texInfo->width);
1009 -            fHeight = static_cast<float>(texInfo->height);
1010 -            _gear->ResetRect(x, y, fWidth, fHeight);
1011 -        }
1012 -    }
1013  }
1014 diff -pruN --exclude build ./demo_clean/src/main.cpp ./demo_dev/src/main.cpp
1015 --- ./demo_clean/src/main.cpp   2025-05-30 00:59:58.252401066 +0100
1016 +++ ./demo_dev/src/main.cpp     2025-05-30 01:19:44.722858078 +0100
1017 @@ -5,18 +5,186 @@
1018   * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html.
1019   */
1020  
1021 +#include <thread>
1022 +#include <stdexcept>
1023 +#include <sstream>
1024 +#include <vector>
1025 +#include <utility>
1026 +#include <string>
1027 +
1028 +#ifdef __cpp_lib_filesystem
1029 +#include <filesystem>
1030 +namespace fs = std::filesystem;
1031 +#else
1032 +#include <experimental/filesystem>
1033 +namespace fs = std::experimental::filesystem;
1034 +#endif
1035 +
1036 +#include "ICubismModelSetting.hpp"
1037  #include "LAppDelegate.hpp"
1038 +#include "LAppLive2DManager.hpp"
1039 +#include "LAppModel.hpp"
1040 +#include "mouse_cursor_tracker.h"
1041 +
1042 +struct CmdArgs
1043 +{
1044 +    int windowWidth;
1045 +    int windowHeight;
1046 +    std::string windowTitle;
1047 +    std::string rootDir;
1048 +    float scaleFactor;
1049 +    float translateX;
1050 +    float translateY;
1051 +    std::string modelName;
1052 +    bool oldId; // If true, translate new (Cubism 3+) parameter IDs to old (Cubism 2.1) IDs
1053 +    std::string cfgPath; // Path to config file for MouseCursorTracker
1054 +};
1055 +
1056 +CmdArgs parseArgv(int argc, char *argv[])
1057 +{
1058 +    // I think the command-line args are simple enough to not justify using a library...
1059 +    CmdArgs cmdArgs;
1060 +    // Set default values
1061 +    cmdArgs.windowWidth = 600;
1062 +    cmdArgs.windowHeight = 600;
1063 +    cmdArgs.windowTitle = "MouseTrackerForCubism example";
1064 +    cmdArgs.rootDir = fs::current_path();
1065 +    cmdArgs.scaleFactor = 4.5f;
1066 +    cmdArgs.translateX = 0.0f;
1067 +    cmdArgs.translateY = -3.1f;
1068 +    cmdArgs.modelName = "Haru";
1069 +    cmdArgs.oldId = false;
1070 +    cmdArgs.cfgPath = "";
1071 +
1072 +    int i = 1;
1073 +    while (i < argc)
1074 +    {
1075 +        std::string arg = argv[i];
1076 +        std::stringstream ss;
1077 +
1078 +        if (arg == "--window-width" || arg == "-W") // capital W for consistency with height
1079 +        {
1080 +            ss << argv[i + 1];
1081 +            if (!(ss >> cmdArgs.windowWidth))
1082 +            {
1083 +                throw std::runtime_error("Invalid argument for window width");
1084 +            }
1085 +        }
1086 +        else if (arg == "--window-height" || arg == "-H") // avoiding "-h", typically for help
1087 +        {
1088 +            ss << argv[i + 1];
1089 +            if (!(ss >> cmdArgs.windowHeight))
1090 +            {
1091 +                throw std::runtime_error("Invalid argument for window height");
1092 +            }
1093 +        }
1094 +        else if (arg == "--window-title" || arg == "-t")
1095 +        {
1096 +            cmdArgs.windowTitle = argv[i + 1];
1097 +        }
1098 +        else if (arg == "--root-dir" || arg == "-d")
1099 +        {
1100 +            cmdArgs.rootDir = argv[i + 1];
1101 +        }
1102 +        else if (arg == "--scale-factor" || arg == "-f")
1103 +        {
1104 +            ss << argv[i + 1];
1105 +            if (!(ss >> cmdArgs.scaleFactor))
1106 +            {
1107 +                throw std::runtime_error("Invalid argument for scale factor");
1108 +            }
1109 +        }
1110 +        else if (arg == "--translate-x" || arg == "-x")
1111 +        {
1112 +            ss << argv[i + 1];
1113 +            if (!(ss >> cmdArgs.translateX))
1114 +            {
1115 +                throw std::runtime_error("Invalid argument for translate X");
1116 +            }
1117 +        }
1118 +        else if (arg == "--translate-y" || arg == "-y")
1119 +        {
1120 +            ss << argv[i + 1];
1121 +            if (!(ss >> cmdArgs.translateY))
1122 +            {
1123 +                throw std::runtime_error("Invalid argument for translate Y");
1124 +            }
1125 +        }
1126 +        else if (arg == "--model" || arg == "-m")
1127 +        {
1128 +            cmdArgs.modelName = argv[i + 1];
1129 +        }
1130 +        else if (arg == "--config" || arg == "-c")
1131 +        {
1132 +            cmdArgs.cfgPath = argv[i + 1];
1133 +        }
1134 +        else if (arg == "--old-param-id" || arg == "-o")
1135 +        {
1136 +            cmdArgs.oldId = (argv[i + 1][0] == '1');
1137 +        }
1138 +        else
1139 +        {
1140 +            throw std::runtime_error("Unrecognized argument: " + arg);
1141 +        }
1142 +
1143 +        i += 2;
1144 +    }
1145 +
1146 +    return cmdArgs;
1147 +}
1148  
1149  int main(int argc, char* argv[])
1150  {
1151 -    // create the application instance
1152 -    if (LAppDelegate::GetInstance()->Initialize() == GL_FALSE)
1153 +    auto cmdArgs = parseArgv(argc, argv);
1154 +
1155 +    LAppDelegate *delegate = LAppDelegate::GetInstance();
1156 +
1157 +    if (!delegate->Initialize(cmdArgs.windowWidth,
1158 +                              cmdArgs.windowHeight,
1159 +                              cmdArgs.windowTitle.c_str()))
1160      {
1161 -        return 1;
1162 +        throw std::runtime_error("Unable to initialize LAppDelegate");
1163      }
1164  
1165 -    LAppDelegate::GetInstance()->Run();
1166 +    LAppLive2DManager *manager = LAppLive2DManager::GetInstance();
1167 +    manager->SetModel(cmdArgs.modelName, cmdArgs.oldId);
1168 +
1169 +    manager->SetProjectionScaleTranslate(cmdArgs.scaleFactor,
1170 +                                         cmdArgs.translateX,
1171 +                                         cmdArgs.translateY);
1172 +
1173 +    LAppModel *model = manager->GetModel(0);
1174 +    if (!model) throw std::runtime_error("model is null");
1175 +
1176 +    Live2D::Cubism::Framework::ICubismModelSetting *modelSetting = model->GetModelSetting();
1177 +    if (!modelSetting) throw std::runtime_error("modelSetting is null");
1178 +
1179 +    std::vector<std::pair<std::string, int> > motions;
1180 +    int motionGroupCount = modelSetting->GetMotionGroupCount();
1181 +    for (int i = 0; i < motionGroupCount; i++)
1182 +    {
1183 +        const char *motionGroup = modelSetting->GetMotionGroupName(i);
1184 +        int motionCount = modelSetting->GetMotionCount(motionGroup);
1185 +        motions.push_back(std::make_pair(std::string(motionGroup), motionCount));
1186 +    }
1187 +
1188 +    std::vector<std::string> expressions;
1189 +    int expCount = modelSetting->GetExpressionCount();
1190 +    for (int i = 0; i < expCount; i++)
1191 +    {
1192 +        const char *expName = modelSetting->GetExpressionName(i);
1193 +        expressions.push_back(std::string(expName));
1194 +    }
1195 +
1196 +    MouseCursorTracker tracker(cmdArgs.cfgPath, motions, expressions);
1197 +
1198 +    std::thread trackerThread(&MouseCursorTracker::mainLoop, &tracker);
1199 +    manager->SetTracker(&tracker);
1200 +
1201 +    delegate->Run();
1202 +
1203 +    tracker.stop();
1204 +    trackerThread.join();
1205  
1206      return 0;
1207  }
1208 -