diff -pruN --exclude build ./demo_clean/CMakeLists.txt ./demo_dev/CMakeLists.txt
---- ./demo_clean/CMakeLists.txt 2020-10-02 02:01:04.825787688 +0100
+--- ./demo_clean/CMakeLists.txt 2020-10-01 22:47:25.846828066 +0100
+++ ./demo_dev/CMakeLists.txt 2020-10-01 23:29:15.530233484 +0100
@@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.16)
# Set app name.
# Copy resource directory to build directory.
add_custom_command(
diff -pruN --exclude build ./demo_clean/scripts/make_gcc ./demo_dev/scripts/make_gcc
---- ./demo_clean/scripts/make_gcc 2020-10-02 02:01:04.825787688 +0100
-+++ ./demo_dev/scripts/make_gcc 2020-10-01 23:43:42.213875065 +0100
+--- ./demo_clean/scripts/make_gcc 2020-10-01 22:47:25.854827921 +0100
++++ ./demo_dev/scripts/make_gcc 2020-10-12 03:42:07.847955578 +0100
@@ -10,4 +10,4 @@ BUILD_PATH=$SCRIPT_PATH/../build/make_gc
cmake -S "$CMAKE_PATH" \
-B "$BUILD_PATH" \
-cd "$BUILD_PATH" && make
+cd "$BUILD_PATH" && make -j4
diff -pruN --exclude build ./demo_clean/src/CMakeLists.txt ./demo_dev/src/CMakeLists.txt
---- ./demo_clean/src/CMakeLists.txt 2020-10-02 02:01:04.829787750 +0100
+--- ./demo_clean/src/CMakeLists.txt 2020-10-01 22:47:25.850827994 +0100
+++ ./demo_dev/src/CMakeLists.txt 2020-10-01 22:47:24.842846271 +0100
@@ -19,6 +19,4 @@ target_sources(${APP_NAME}
${CMAKE_CURRENT_SOURCE_DIR}/LAppView.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/TouchManager.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/TouchManager.hpp
)
+diff -pruN --exclude build ./demo_clean/src/LAppDefine.cpp ./demo_dev/src/LAppDefine.cpp
+--- ./demo_clean/src/LAppDefine.cpp 2020-10-01 22:47:25.850827994 +0100
++++ ./demo_dev/src/LAppDefine.cpp 2020-10-18 04:59:13.238452938 +0100
+@@ -61,11 +61,11 @@ namespace LAppDefine {
+ const csmInt32 PriorityForce = 3;
+
+ // デバッグ用ログの表示オプション
+- const csmBool DebugLogEnable = true;
++ const csmBool DebugLogEnable = false;
+ const csmBool DebugTouchLogEnable = false;
+
+ // Frameworkから出力するログのレベル設定
+- const CubismFramework::Option::LogLevel CubismLoggingLevel = CubismFramework::Option::LogLevel_Verbose;
++ const CubismFramework::Option::LogLevel CubismLoggingLevel = CubismFramework::Option::LogLevel_Warning;
+
+ // デフォルトのレンダーターゲットサイズ
+ const csmInt32 RenderTargetWidth = 1900;
diff -pruN --exclude build ./demo_clean/src/LAppDelegate.cpp ./demo_dev/src/LAppDelegate.cpp
---- ./demo_clean/src/LAppDelegate.cpp 2020-10-02 02:01:04.829787750 +0100
+--- ./demo_clean/src/LAppDelegate.cpp 2020-10-01 22:47:25.850827994 +0100
+++ ./demo_dev/src/LAppDelegate.cpp 2020-10-01 22:47:24.698848890 +0100
@@ -45,7 +45,8 @@ void LAppDelegate::ReleaseInstance()
s_instance = NULL;
Csm::csmVector<string> LAppDelegate::Split(const std::string& baseString, char delimiter)
diff -pruN --exclude build ./demo_clean/src/LAppDelegate.hpp ./demo_dev/src/LAppDelegate.hpp
---- ./demo_clean/src/LAppDelegate.hpp 2020-10-02 02:01:04.829787750 +0100
+--- ./demo_clean/src/LAppDelegate.hpp 2020-10-01 22:47:25.850827994 +0100
+++ ./demo_dev/src/LAppDelegate.hpp 2020-10-01 22:47:24.842846271 +0100
@@ -40,7 +40,8 @@ public:
/**
-
-};
diff -pruN --exclude build ./demo_clean/src/LAppLive2DManager.cpp ./demo_dev/src/LAppLive2DManager.cpp
---- ./demo_clean/src/LAppLive2DManager.cpp 2020-10-02 02:01:04.829787750 +0100
+--- ./demo_clean/src/LAppLive2DManager.cpp 2020-10-01 22:47:25.850827994 +0100
+++ ./demo_dev/src/LAppLive2DManager.cpp 2020-10-02 02:00:49.961556700 +0100
@@ -52,9 +52,10 @@ void LAppLive2DManager::ReleaseInstance(
+ _translateY = translateY;
+}
diff -pruN --exclude build ./demo_clean/src/LAppLive2DManager.hpp ./demo_dev/src/LAppLive2DManager.hpp
---- ./demo_clean/src/LAppLive2DManager.hpp 2020-10-02 02:01:04.825787688 +0100
+--- ./demo_clean/src/LAppLive2DManager.hpp 2020-10-01 22:47:25.846828066 +0100
+++ ./demo_dev/src/LAppLive2DManager.hpp 2020-10-01 23:36:24.583055381 +0100
@@ -6,12 +6,15 @@
*/
+ float _translateY;
};
diff -pruN --exclude build ./demo_clean/src/LAppModel.cpp ./demo_dev/src/LAppModel.cpp
---- ./demo_clean/src/LAppModel.cpp 2020-10-02 02:01:04.825787688 +0100
-+++ ./demo_dev/src/LAppModel.cpp 2020-10-01 23:34:43.482626010 +0100
-@@ -21,6 +21,8 @@
+--- ./demo_clean/src/LAppModel.cpp 2020-10-01 22:47:25.850827994 +0100
++++ ./demo_dev/src/LAppModel.cpp 2020-10-18 09:26:08.998822685 +0100
+@@ -21,6 +21,10 @@
#include "LAppTextureManager.hpp"
#include "LAppDelegate.hpp"
+#include "mouse_cursor_tracker.h"
+
++#include <iostream>
++
using namespace Live2D::Cubism::Framework;
using namespace Live2D::Cubism::Framework::DefaultParameterId;
using namespace LAppDefine;
-@@ -49,6 +51,7 @@ LAppModel::LAppModel()
+@@ -49,6 +53,7 @@ LAppModel::LAppModel()
: CubismUserModel()
, _modelSetting(NULL)
, _userTimeSeconds(0.0f)
{
if (DebugLogEnable)
{
-@@ -128,30 +131,6 @@ void LAppModel::SetupModel(ICubismModelS
- DeleteBuffer(buffer, path.GetRawString());
- }
-
-- //Expression
-- if (_modelSetting->GetExpressionCount() > 0)
-- {
-- const csmInt32 count = _modelSetting->GetExpressionCount();
-- for (csmInt32 i = 0; i < count; i++)
-- {
-- csmString name = _modelSetting->GetExpressionName(i);
-- csmString path = _modelSetting->GetExpressionFileName(i);
-- path = _modelHomeDir + path;
--
-- buffer = CreateBuffer(path.GetRawString(), &size);
-- ACubismMotion* motion = LoadExpression(buffer, size, name.GetRawString());
--
-- if (_expressions[name] != NULL)
-- {
-- ACubismMotion::Delete(_expressions[name]);
-- _expressions[name] = NULL;
-- }
-- _expressions[name] = motion;
--
-- DeleteBuffer(buffer, path.GetRawString());
-- }
-- }
--
- //Physics
- if (strcmp(_modelSetting->GetPhysicsFileName(), "") != 0)
- {
-@@ -335,59 +314,72 @@ void LAppModel::Update()
+@@ -335,59 +340,110 @@ void LAppModel::Update()
const csmFloat32 deltaTimeSeconds = LAppPal::GetDeltaTime();
_userTimeSeconds += deltaTimeSeconds;
- //-----------------------------------------------------------------
- _model->LoadParameters(); // 前回セーブされた状態をロード
- if (_motionManager->IsFinished())
-+ if (_tracker)
- {
+- {
- // モーションの再生がない場合、待機モーションの中からランダムで再生する
- StartRandomMotion(MotionGroupIdle, PriorityIdle);
- }
- else
-- {
++ if (_tracker)
+ {
- motionUpdated = _motionManager->UpdateMotion(_model, deltaTimeSeconds); // モーションを更新
- }
- _model->SaveParameters(); // 状態を保存
- if (!motionUpdated)
- {
- if (_eyeBlink != NULL)
-+ // NOTE: Apparently, this LoadParameters/SaveParameters pair
-+ // is needed for auto breath to work.
+ _model->LoadParameters(); // 前回セーブされた状態をロード
-+ if (_motionManager->IsFinished() && params.randomMotion)
++
++ int paramsMotionPriority = static_cast<int>(params.motionPriority);
++
++ if (paramsMotionPriority != PriorityNone)
{
- // メインモーションの更新がないとき
- _eyeBlink->UpdateParameters(_model, deltaTimeSeconds); // 目パチ
++ StartMotion(params.motionGroup.c_str(), params.motionNumber,
++ paramsMotionPriority);
++ }
++ else if (params.randomIdleMotion && _motionManager->IsFinished())
++ {
+ // モーションの再生がない場合、待機モーションの中からランダムで再生する
+ StartRandomMotion(MotionGroupIdle, PriorityIdle);
}
- }
-+ else
-+ {
-+ _motionManager->UpdateMotion(_model, deltaTimeSeconds); // モーションを更新
-+ }
-+ _model->SaveParameters(); // 状態を保存
- if (_expressionManager != NULL)
- {
- _expressionManager->UpdateMotion(_model, deltaTimeSeconds); // 表情でパラメータ更新(相対変化)
- }
++ // FIXME pose does not return to normal after motion
++ // if we don't have randomIdleMotion set
++ else
++ {
++ _motionManager->UpdateMotion(_model, deltaTimeSeconds); // モーションを更新
++ }
++ _model->SaveParameters(); // 状態を保存
- //ドラッグによる変化
- //ドラッグによる顔の向きの調整
- _model->AddParameterValue(_idParamAngleX, _dragX * 30); // -30から30の値を加える
- _model->AddParameterValue(_idParamAngleY, _dragY * 30);
- _model->AddParameterValue(_idParamAngleZ, _dragX * _dragY * -30);
-+ if (params.autoBlink && _eyeBlink)
++ if (params.expression != "")
+ {
-+ _eyeBlink->UpdateParameters(_model, deltaTimeSeconds);
++ SetExpression(params.expression.c_str());
+ }
-+ else
++ if (_expressionManager != NULL)
+ {
-+ _model->SetParameterValue(idMan->GetId("ParamEyeLOpen"),
-+ params.leftEyeOpenness);
-+ _model->SetParameterValue(idMan->GetId("ParamEyeROpen"),
-+ params.rightEyeOpenness);
++ _expressionManager->UpdateMotion(_model, deltaTimeSeconds); // 表情でパラメータ更新(相対変化)
+ }
- //ドラッグによる体の向きの調整
- _model->AddParameterValue(_idParamBodyAngleX, _dragX * 10); // -10から10の値を加える
-+ _model->SetParameterValue(idMan->GetId("ParamMouthForm"),
-+ params.mouthForm);
++ bool autoBlink = params.autoBlink && _eyeBlink;
++ auto eyeLOpenIt = params.live2d.find("ParamEyeLOpen");
++ auto eyeROpenIt = params.live2d.find("ParamEyeROpen");
- //ドラッグによる目の向きの調整
- _model->AddParameterValue(_idParamEyeBallX, _dragX); // -1から1の値を加える
- _model->AddParameterValue(_idParamEyeBallY, _dragY);
-+ if (params.useLipSync && _lipSync)
++ if (autoBlink)
+ {
-+ csmFloat32 value = params.lipSyncParam; // 0 to 1
++ // Handle blink first
++ _eyeBlink->UpdateParameters(_model, deltaTimeSeconds);
++ }
- // 呼吸など
- if (_breath != NULL)
- {
- _breath->UpdateParameters(_model, deltaTimeSeconds);
++ if (eyeLOpenIt != params.live2d.end())
++ {
++ // If value specified, override blinking
++ _model->SetParameterValue(idMan->GetId("ParamEyeLOpen"),
++ eyeLOpenIt->second);
++ }
++ else if (!autoBlink)
++ {
++ // If no value specified and no auto blink, set to 1
++ _model->SetParameterValue(idMan->GetId("ParamEyeLOpen"), 1);
++
++ }
++
++ if (eyeROpenIt != params.live2d.end())
++ {
++ _model->SetParameterValue(idMan->GetId("ParamEyeROpen"),
++ eyeROpenIt->second);
++ }
++ else if (!autoBlink)
++ {
++ _model->SetParameterValue(idMan->GetId("ParamEyeROpen"), 1);
++ }
++
++
++ if (params.useLipSync && _lipSync)
++ {
++ csmFloat32 value = params.lipSyncParam; // 0 to 1
++
+ for (csmUint32 i = 0; i < _lipSyncIds.GetSize(); ++i)
+ {
+ _model->AddParameterValue(_lipSyncIds[i], value, 0.8f);
+ else
+ {
+ _model->SetParameterValue(idMan->GetId("ParamMouthOpenY"),
-+ params.mouthOpenness);
++ params.live2d["ParamMouthOpenY"]);
++ }
++
++ for (auto const &entry : params.live2d)
++ {
++ std::string key = entry.first;
++ double val = entry.second;
++
++ if (key != "ParamEyeLOpen" && key != "ParamEyeROpen" &&
++ key != "ParamMouthOpenY")
++ {
++ _model->SetParameterValue(idMan->GetId(key.c_str()), val);
++ }
+ }
+
-+ _model->SetParameterValue(idMan->GetId("ParamEyeLSmile"),
-+ params.leftEyeSmile);
-+ _model->SetParameterValue(idMan->GetId("ParamEyeRSmile"),
-+ params.rightEyeSmile);
-+ _model->SetParameterValue(idMan->GetId("ParamAngleX"),
-+ params.faceXAngle);
-+ _model->SetParameterValue(idMan->GetId("ParamAngleY"),
-+ params.faceYAngle);
-+ _model->SetParameterValue(idMan->GetId("ParamAngleZ"),
-+ params.faceZAngle);
+ if (params.autoBreath && _breath)
+ {
+ // Note: _model->LoadParameters and SaveParameters is needed
}
// 物理演算の設定
-@@ -396,17 +388,6 @@ void LAppModel::Update()
+@@ -396,17 +452,6 @@ void LAppModel::Update()
_physics->Evaluate(_model, deltaTimeSeconds);
}
// ポーズの設定
if (_pose != NULL)
{
-@@ -626,3 +607,9 @@ Csm::Rendering::CubismOffscreenFrame_Ope
+@@ -626,3 +671,14 @@ Csm::Rendering::CubismOffscreenFrame_Ope
{
return _renderBuffer;
}
+ _tracker = tracker;
+}
+
++Csm::ICubismModelSetting* LAppModel::GetModelSetting(void) const
++{
++ return _modelSetting;
++}
++
diff -pruN --exclude build ./demo_clean/src/LAppModel.hpp ./demo_dev/src/LAppModel.hpp
---- ./demo_clean/src/LAppModel.hpp 2020-10-02 02:01:04.829787750 +0100
-+++ ./demo_dev/src/LAppModel.hpp 2020-10-01 23:35:39.254849094 +0100
+--- ./demo_clean/src/LAppModel.hpp 2020-10-01 22:47:25.850827994 +0100
++++ ./demo_dev/src/LAppModel.hpp 2020-10-18 03:04:52.142045751 +0100
@@ -13,6 +13,7 @@
#include <Type/csmRectF.hpp>
#include <Rendering/OpenGL/CubismOffscreenSurface_OpenGLES2.hpp>
/**
* @brief ユーザーが実際に使用するモデルの実装クラス<br>
-@@ -113,6 +114,13 @@ public:
+@@ -113,6 +114,15 @@ public:
*/
Csm::Rendering::CubismOffscreenFrame_OpenGLES2& GetRenderBuffer();
+ */
+ void SetTracker(MouseCursorTracker *tracker);
+
++ Csm::ICubismModelSetting* GetModelSetting(void) const;
++
protected:
/**
* @brief モデルを描画する処理。モデルを描画する空間のView-Projection行列を渡す。
-@@ -183,6 +191,8 @@ private:
+@@ -183,6 +193,8 @@ private:
const Csm::CubismId* _idParamEyeBallY; ///< パラメータID: ParamEyeBallXY
Csm::Rendering::CubismOffscreenFrame_OpenGLES2 _renderBuffer; ///< フレームバッファ以外の描画先
diff -pruN --exclude build ./demo_clean/src/LAppPal.cpp ./demo_dev/src/LAppPal.cpp
---- ./demo_clean/src/LAppPal.cpp 2020-10-02 02:01:04.829787750 +0100
-+++ ./demo_dev/src/LAppPal.cpp 2020-10-01 22:47:24.722848453 +0100
+--- ./demo_clean/src/LAppPal.cpp 2020-10-01 22:47:25.850827994 +0100
++++ ./demo_dev/src/LAppPal.cpp 2020-10-18 04:57:43.289600308 +0100
@@ -6,6 +6,7 @@
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
-@@ -45,10 +46,7 @@ csmByte* LAppPal::LoadFileAsBytes(const
+@@ -36,7 +37,6 @@ csmByte* LAppPal::LoadFileAsBytes(const
+ if (stat(path, &statBuf) == 0)
+ {
+ size = statBuf.st_size;
+- PrintLog(path);
+ }
+
+ std::fstream file;
+@@ -45,10 +45,7 @@ csmByte* LAppPal::LoadFileAsBytes(const
file.open(path, std::ios::in | std::ios::binary);
if (!file.is_open())
{
}
file.read(buf, size);
diff -pruN --exclude build ./demo_clean/src/LAppTextureManager.cpp ./demo_dev/src/LAppTextureManager.cpp
---- ./demo_clean/src/LAppTextureManager.cpp 2020-10-02 02:01:04.833787812 +0100
+--- ./demo_clean/src/LAppTextureManager.cpp 2020-10-01 22:47:25.850827994 +0100
+++ ./demo_dev/src/LAppTextureManager.cpp 2020-10-01 22:47:24.654849690 +0100
@@ -96,6 +96,46 @@ LAppTextureManager::TextureInfo* LAppTex
{
for (Csm::csmUint32 i = 0; i < _textures.GetSize(); i++)
diff -pruN --exclude build ./demo_clean/src/LAppTextureManager.hpp ./demo_dev/src/LAppTextureManager.hpp
---- ./demo_clean/src/LAppTextureManager.hpp 2020-10-02 02:01:04.825787688 +0100
+--- ./demo_clean/src/LAppTextureManager.hpp 2020-10-01 22:47:25.846828066 +0100
+++ ./demo_dev/src/LAppTextureManager.hpp 2020-10-01 22:47:24.786847290 +0100
@@ -72,6 +72,8 @@ public:
*/
* @brief 画像の解放
*
diff -pruN --exclude build ./demo_clean/src/LAppView.cpp ./demo_dev/src/LAppView.cpp
---- ./demo_clean/src/LAppView.cpp 2020-10-02 02:01:04.833787812 +0100
+--- ./demo_clean/src/LAppView.cpp 2020-10-01 22:47:25.850827994 +0100
+++ ./demo_dev/src/LAppView.cpp 2020-10-01 22:47:24.602850636 +0100
@@ -13,7 +13,6 @@
#include "LAppLive2DManager.hpp"
- }
}
diff -pruN --exclude build ./demo_clean/src/LAppView.hpp ./demo_dev/src/LAppView.hpp
---- ./demo_clean/src/LAppView.hpp 2020-10-02 02:01:04.825787688 +0100
+--- ./demo_clean/src/LAppView.hpp 2020-10-01 22:47:25.846828066 +0100
+++ ./demo_dev/src/LAppView.hpp 2020-10-01 22:47:24.802846999 +0100
@@ -14,7 +14,6 @@
#include "CubismFramework.hpp"
// レンダリング先を別ターゲットにする方式の場合に使用
LAppSprite* _renderSprite; ///< モードによっては_renderBufferのテクスチャを描画
diff -pruN --exclude build ./demo_clean/src/main.cpp ./demo_dev/src/main.cpp
---- ./demo_clean/src/main.cpp 2020-10-02 02:01:04.825787688 +0100
-+++ ./demo_dev/src/main.cpp 2020-10-01 23:42:12.845205308 +0100
-@@ -5,18 +5,154 @@
+--- ./demo_clean/src/main.cpp 2020-10-01 22:47:25.846828066 +0100
++++ ./demo_dev/src/main.cpp 2020-10-18 07:03:46.194220443 +0100
+@@ -5,18 +5,182 @@
* that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html.
*/
+#include <thread>
+#include <stdexcept>
+#include <sstream>
++#include <vector>
++#include <utility>
++#include <string>
+
+#ifdef __cpp_lib_filesystem
+#include <filesystem>
+namespace fs = std::experimental::filesystem;
+#endif
+
++#include "ICubismModelSetting.hpp"
#include "LAppDelegate.hpp"
+#include "LAppLive2DManager.hpp"
++#include "LAppModel.hpp"
+#include "mouse_cursor_tracker.h"
+
+struct CmdArgs
- LAppDelegate::GetInstance()->Run();
+ delegate->SetRootDirectory(cmdArgs.rootDir);
+
-+ MouseCursorTracker tracker(cmdArgs.cfgPath);
-+
-+ std::thread trackerThread(&MouseCursorTracker::mainLoop, &tracker);
-+
+ LAppLive2DManager *manager = LAppLive2DManager::GetInstance();
+ manager->SetModel(cmdArgs.modelName);
+
+ manager->SetProjectionScaleTranslate(cmdArgs.scaleFactor,
+ cmdArgs.translateX,
+ cmdArgs.translateY);
++
++ LAppModel *model = manager->GetModel(0);
++ if (!model) throw std::runtime_error("model is null");
++
++ Live2D::Cubism::Framework::ICubismModelSetting *modelSetting = model->GetModelSetting();
++ if (!modelSetting) throw std::runtime_error("modelSetting is null");
++
++ std::vector<std::pair<std::string, int> > motions;
++ int motionGroupCount = modelSetting->GetMotionGroupCount();
++ for (int i = 0; i < motionGroupCount; i++)
++ {
++ const char *motionGroup = modelSetting->GetMotionGroupName(i);
++ int motionCount = modelSetting->GetMotionCount(motionGroup);
++ motions.push_back(std::make_pair(std::string(motionGroup), motionCount));
++ }
++
++ std::vector<std::string> expressions;
++ int expCount = modelSetting->GetExpressionCount();
++ for (int i = 0; i < expCount; i++)
++ {
++ const char *expName = modelSetting->GetExpressionName(i);
++ expressions.push_back(std::string(expName));
++ }
++
++ MouseCursorTracker tracker(cmdArgs.cfgPath, motions, expressions);
++
++ std::thread trackerThread(&MouseCursorTracker::mainLoop, &tracker);
+ manager->SetTracker(&tracker);
+
+ delegate->Run();
#include <fstream>
#include <sstream>
#include <vector>
+#include <utility>
+#include <algorithm>
#include <cstdlib>
#include <cmath>
+#include <cassert>
#include <iostream>
+#include <cctype>
+#include <mutex>
extern "C"
{
#include <xdo.h>
#include <pulse/simple.h>
+#include <readline/readline.h>
+#include <readline/history.h>
}
#include "mouse_cursor_tracker.h"
return std::sqrt(sum / count);
}
-MouseCursorTracker::MouseCursorTracker(std::string cfgPath)
+static std::vector<std::string> split(std::string s)
+{
+ std::vector<std::string> v;
+ std::string tmp;
+
+ for (std::size_t i = 0; i < s.length(); i++)
+ {
+ char c = s[i];
+ if (std::isspace(c))
+ {
+ if (tmp != "")
+ {
+ v.push_back(tmp);
+ tmp = "";
+ }
+ }
+ else
+ {
+ tmp += c;
+ }
+ }
+ if (tmp != "")
+ {
+ v.push_back(tmp);
+ }
+ return v;
+}
+
+
+/* Using readline callback functions means that we need to pass
+ * information from our class to the callback, and the only way
+ * to do so is using globals.
+ */
+
+// Taken from https://github.com/eliben/code-for-blog/blob/master/2016/readline-samples/utils.cpp
+static std::string longest_common_prefix(std::string s,
+ const std::vector<std::string>& candidates) {
+ assert(candidates.size() > 0);
+ if (candidates.size() == 1) {
+ return candidates[0];
+ }
+
+ std::string prefix(s);
+ while (true) {
+ // Each iteration of this loop advances to the next location in all the
+ // candidates and sees if they match up to it.
+ size_t nextloc = prefix.size();
+ auto i = candidates.begin();
+ if (i->size() <= nextloc) {
+ return prefix;
+ }
+ char nextchar = (*(i++))[nextloc];
+ for (; i != candidates.end(); ++i) {
+ if (i->size() <= nextloc || (*i)[nextloc] != nextchar) {
+ // Bail out if there's a mismatch for this candidate.
+ return prefix;
+ }
+ }
+ // All candidates have contents[nextloc] == nextchar, so we can safely
+ // extend the prefix.
+ prefix.append(1, nextchar);
+ }
+
+ assert(0 && "unreachable");
+}
+
+std::vector<std::string> commands =
+{
+ "help", "motion", "expression", "set", "clear"
+};
+
+std::vector<std::string> live2dParams =
+{ // https://docs.live2d.com/cubism-editor-manual/standard-parametor-list/?locale=ja
+ "ParamAngleX", "ParamAngleY", "ParamAngleZ",
+ "ParamEyeLOpen", "ParamEyeLSmile", "ParamEyeROpen", "ParamEyeRSmile",
+ "ParamEyeBallX", "ParamEyeBallY", "ParamEyeBallForm",
+ "ParamBrowLY", "ParamBrowRY", "ParamBrowLX", "ParamBrowRX",
+ "ParamBrowLAngle", "ParamBrowRAngle", "ParamBrowLForm", "ParamBrowRForm",
+ "ParamMouthForm", "ParamMouthOpenY", "ParamTere",
+ "ParamBodyAngleX", "ParamBodyAngleY", "ParamBodyAngleZ", "ParamBreath",
+ "ParamArmLA", "ParamArmRA", "ParamArmLB", "ParamArmRB",
+ "ParamHandL", "ParamHandR",
+ "ParamHairFront", "ParamHairSide", "ParamHairBack", "ParamHairFluffy",
+ "ParamShoulderY", "ParamBustX", "ParamBustY",
+ "ParamBaseX", "ParamBaseY"
+};
+std::vector<std::pair<std::string, int> > MCT_motions;
+std::vector<std::string> MCT_expressions;
+std::map<std::string, double> *MCT_overrideMap;
+
+static char **cliCompletionFunction(const char *textCStr, int start, int end)
+{
+ // Reference: https://github.com/eliben/code-for-blog/blob/master/2016/readline-samples/readline-complete-subcommand.cpp
+ rl_attempted_completion_over = 1;
+
+ std::string line(rl_line_buffer);
+
+ std::vector<std::string> cmdline = split(line);
+
+ std::vector<std::string> constructed;
+ std::vector<std::string> *vocab = nullptr;
+
+ if (cmdline.size() == 0 ||
+ (cmdline.size() == 1 && line.back() != ' ') ||
+ cmdline[0] == "help")
+ {
+ vocab = &commands;
+ }
+ else if (cmdline[0] == "motion")
+ {
+ for (auto it = MCT_motions.begin(); it != MCT_motions.end(); ++it)
+ {
+ if ((cmdline.size() == 1 && line.back() == ' ') ||
+ (cmdline.size() == 2 && line.back() != ' '))
+ { // motionGroup
+ {
+ constructed.push_back(it->first);
+ }
+ }
+ else if ((cmdline.size() == 2 && line.back() == ' ') ||
+ (cmdline.size() == 3 && line.back() != ' '))
+ { // motionNumber
+ if (it->first == cmdline[1])
+ {
+ for (int i = 0; i < it->second; i++)
+ {
+ constructed.push_back(std::to_string(i));
+ }
+ break;
+ }
+ }
+ else if (cmdline.size() <= 4)
+ { // priority
+ for (int i = 0; i < 4; i++)
+ {
+ constructed.push_back(std::to_string(i));
+ }
+ }
+ }
+
+ vocab = &constructed;
+ }
+ else if (cmdline[0] == "expression")
+ {
+ vocab = &MCT_expressions;
+ }
+ else if (cmdline[0] == "set")
+ {
+ if ((cmdline.size() % 2 == 0 && line.back() != ' ') ||
+ (cmdline.size() % 2 == 1 && line.back() == ' '))
+ {
+ vocab = &live2dParams;
+ }
+ }
+ else if (cmdline[0] == "clear")
+ {
+ constructed.push_back("all");
+ for (auto const &entry : *MCT_overrideMap)
+ {
+ constructed.push_back(entry.first);
+ }
+ vocab = &constructed;
+ }
+
+ if (!vocab)
+ {
+ return nullptr;
+ }
+
+ std::string text(textCStr);
+ std::vector<std::string> matches;
+ std::copy_if(vocab->begin(), vocab->end(), std::back_inserter(matches),
+ [&text](const std::string &s)
+ {
+ return (s.size() >= text.size() &&
+ s.compare(0, text.size(), text) == 0);
+ });
+
+ if (matches.empty())
+ {
+ return nullptr;
+ }
+
+ char** array =
+ static_cast<char**>(malloc((2 + matches.size()) * sizeof(*array)));
+ array[0] = strdup(longest_common_prefix(text, matches).c_str());
+ size_t ptr = 1;
+ for (const auto& m : matches) {
+ array[ptr++] = strdup(m.c_str());
+ }
+ array[ptr] = nullptr;
+ return array;
+}
+
+MouseCursorTracker::MouseCursorTracker(std::string cfgPath,
+ std::vector<std::pair<std::string, int> > motions,
+ std::vector<std::string> expressions)
: m_stop(false)
{
+ m_motionPriority = MotionPriority::none;
+ m_motionNumber = 0;
+
+
parseConfig(cfgPath);
m_xdo = xdo_new(nullptr);
}
m_getVolumeThread = std::thread(&MouseCursorTracker::audioLoop, this);
+ m_parseCommandThread = std::thread(&MouseCursorTracker::cliLoop, this);
+
+ MCT_motions = motions;
+ MCT_expressions = expressions;
+ MCT_overrideMap = &m_overrideMap;
}
void MouseCursorTracker::audioLoop(void)
delete[] buf;
}
+void MouseCursorTracker::cliLoop(void)
+{
+ rl_catch_signals = 0;
+ rl_attempted_completion_function = cliCompletionFunction;
+ while (!m_stop)
+ {
+ char *buf = readline(">> ");
+
+ if (buf)
+ {
+ std::string cmdline(buf);
+ free(buf);
+ processCommand(cmdline);
+ }
+ else
+ {
+ std::cout << "Exiting CLI loop. Use Ctrl+C to exit the whole process." << std::endl;
+ stop();
+ }
+ }
+}
+
+
+void MouseCursorTracker::processCommand(std::string cmdline)
+{
+ auto cmdSplit = split(cmdline);
+
+ if (cmdSplit.size() > 0)
+ {
+ add_history(cmdline.c_str());
+
+ if (cmdSplit[0] == "help")
+ {
+ if (cmdSplit.size() == 1)
+ {
+ std::cout << "Available commands: motion set clear\n"
+ << "Type \"help <command>\" for more help" << std::endl;
+ }
+ else if (cmdSplit[1] == "motion")
+ {
+ std::cout << "motion <motionGroup> <motionNumber> [<priority>]\n"
+ << "motionGroup: The motion name in the .model3.json file\n"
+ << "motionNumber: The index of this motion in the .model3.json file, 0-indexed\n"
+ << "priority: 0 = none, 1 = idle, 2 = normal, 3 = force (default normal)" << std::endl;
+ }
+ else if (cmdSplit[1] == "expression")
+ {
+ std::cout << "expression <expressionName>\n"
+ << "expressionName: Name of expression in the .model3.json file" << std::endl;
+ }
+ else if (cmdSplit[1] == "set")
+ {
+ std::cout << "set <param1> <value1> [<param2> <value2> ...]\n"
+ << "Set parameter value. Overrides any tracking."
+ << "See live2D documentation for full list of params" << std::endl;
+ }
+ else if (cmdSplit[1] == "clear")
+ {
+ std::cout << "clear <param1> [<param2> ...]\n"
+ << "Clear parameter value. Re-enables tracking if it was overridden by \"set\"\n"
+ << "You can also use \"clear all\" to clear everything" << std::endl;
+ }
+ else
+ {
+ std::cout << "Unrecognized command" << std::endl;
+ }
+ }
+ else if (cmdSplit[0] == "motion")
+ {
+ if (cmdSplit.size() == 3 || cmdSplit.size() == 4)
+ {
+ std::unique_lock<std::mutex> lock(m_motionMutex, std::defer_lock);
+ lock.lock();
+ m_motionGroup = cmdSplit[1];
+ try
+ {
+ m_motionNumber = std::stoi(cmdSplit[2]);
+ if (cmdSplit.size() == 4)
+ {
+ m_motionPriority = static_cast<MotionPriority>(std::stoi(cmdSplit[3]));
+ }
+ else
+ {
+ m_motionPriority = MotionPriority::normal;
+ }
+ }
+ catch (const std::exception &e)
+ {
+ std::cerr << "std::stoi failed" << std::endl;
+ }
+ lock.unlock();
+ }
+ else
+ {
+ std::cerr << "Incorrect command, expecting 2 or 3 arguments" << std::endl;
+ std::cerr << "motion motionGroup motionNumber [motionPriority]" << std::endl;
+ }
+ }
+ else if (cmdSplit[0] == "expression")
+ {
+ if (cmdSplit.size() == 2)
+ {
+ std::unique_lock<std::mutex> lock(m_motionMutex, std::defer_lock);
+ lock.lock();
+ m_expression = cmdSplit[1];
+ lock.unlock();
+ }
+ else
+ {
+ std::cerr << "Incorrect command, expecting 1 argument: expressionName" << std::endl;
+ }
+ }
+ else if (cmdSplit[0] == "set")
+ {
+ if (cmdSplit.size() % 2 != 1)
+ {
+ // "set param1 value1 param2 value2 ..."
+ std::cerr << "Incorrect number of arguments for command 'set'" << std::endl;
+ }
+ for (std::size_t i = 1; i < cmdSplit.size(); i += 2)
+ {
+ try
+ {
+ m_overrideMap[cmdSplit[i]] = std::stod(cmdSplit[i + 1]);
+ }
+ catch (const std::exception &e)
+ {
+ std::cerr << "std::stod failed" << std::endl;
+ }
+
+ std::cerr << "Debug: setting " << cmdSplit[i] << std::endl;
+ }
+ }
+ else if (cmdSplit[0] == "clear")
+ {
+ for (std::size_t i = 1; i < cmdSplit.size(); i++)
+ {
+ if (cmdSplit[i] == "all")
+ {
+ m_overrideMap.clear();
+ break;
+ }
+ std::size_t removed = m_overrideMap.erase(cmdSplit[i]);
+ if (removed == 0)
+ {
+ std::cerr << "Warning: key " << cmdSplit[i] << " not found" << std::endl;
+ }
+ }
+ }
+ else
+ {
+ std::cerr << "Unknown command" << std::endl;
+ }
+ }
+}
+
MouseCursorTracker::~MouseCursorTracker()
{
xdo_free(m_xdo);
m_getVolumeThread.join();
+ m_parseCommandThread.join();
pa_simple_free(m_pulse);
}
m_stop = true;
}
-MouseCursorTracker::Params MouseCursorTracker::getParams(void) const
+MouseCursorTracker::Params MouseCursorTracker::getParams(void)
{
Params params = Params();
- int xOffset = m_curPos.x - m_cfg.middle.x;
- int leftRange = m_cfg.middle.x - m_cfg.left;
- int rightRange = m_cfg.right - m_cfg.middle.x;
+ int xOffset = m_curPos.x - m_cfg.origin.x;
+ int leftRange = m_cfg.origin.x - m_cfg.left;
+ int rightRange = m_cfg.right - m_cfg.origin.x;
if (xOffset > 0) // i.e. to the right
{
- params.faceXAngle = 30.0 * xOffset / rightRange;
+ params.live2d["ParamAngleX"] = 30.0 * xOffset / rightRange;
}
else // to the left
{
- params.faceXAngle = 30.0 * xOffset / leftRange;
+ params.live2d["ParamAngleX"] = 30.0 * xOffset / leftRange;
}
- int yOffset = m_curPos.y - m_cfg.middle.y;
- int topRange = m_cfg.middle.y - m_cfg.top;
- int bottomRange = m_cfg.bottom - m_cfg.middle.y;
+ int yOffset = m_curPos.y - m_cfg.origin.y;
+ int topRange = m_cfg.origin.y - m_cfg.top;
+ int bottomRange = m_cfg.bottom - m_cfg.origin.y;
if (yOffset > 0) // downwards
{
- params.faceYAngle = -30.0 * yOffset / bottomRange;
+ params.live2d["ParamAngleY"] = -30.0 * yOffset / bottomRange;
}
else // upwards
{
- params.faceYAngle = -30.0 * yOffset / topRange;
+ params.live2d["ParamAngleY"] = -30.0 * yOffset / topRange;
}
- params.faceZAngle = 0;
-
- params.leftEyeOpenness = 1;
- params.rightEyeOpenness = 1;
-
params.autoBlink = m_cfg.autoBlink;
params.autoBreath = m_cfg.autoBreath;
- params.randomMotion = m_cfg.randomMotion;
+ params.randomIdleMotion = m_cfg.randomIdleMotion;
params.useLipSync = m_cfg.useLipSync;
- params.mouthForm = m_cfg.mouthForm;
+ params.live2d["ParamMouthForm"] = m_cfg.mouthForm;
if (m_cfg.useLipSync)
{
}
}
+ // Don't block in getParams()
+ std::unique_lock<std::mutex> lock(m_motionMutex, std::try_to_lock);
+ if (lock.owns_lock())
+ {
+ if (m_motionPriority != MotionPriority::none)
+ {
+ params.motionPriority = m_motionPriority;
+ params.motionGroup = m_motionGroup;
+ params.motionNumber = m_motionNumber;
+
+ m_motionPriority = MotionPriority::none;
+ m_motionGroup = "";
+ m_motionNumber = 0;
+ }
+ params.expression = m_expression;
+ m_expression = "";
+ lock.unlock();
+ }
// Leave everything else as zero
+ // Process overrides
+ for (auto const &x : m_overrideMap)
+ {
+ std::string key = x.first;
+ double val = x.second;
+
+ params.live2d[key] = val;
+ }
+
+
return params;
}
throw std::runtime_error("Error parsing autoBreath");
}
}
- else if (paramName == "randomMotion")
+ else if (paramName == "randomIdleMotion")
{
- if (!(ss >> m_cfg.randomMotion))
+ if (!(ss >> m_cfg.randomIdleMotion))
{
- throw std::runtime_error("Error parsing randomMotion");
+ throw std::runtime_error("Error parsing randomIdleMotion");
}
}
else if (paramName == "useLipSync")
throw std::runtime_error("Error parsing screen");
}
}
- else if (paramName == "middle_x")
+ else if (paramName == "origin_x")
{
- if (!(ss >> m_cfg.middle.x))
+ if (!(ss >> m_cfg.origin.x))
{
- throw std::runtime_error("Error parsing middle_x");
+ throw std::runtime_error("Error parsing origin_x");
}
}
- else if (paramName == "middle_y")
+ else if (paramName == "origin_y")
{
- if (!(ss >> m_cfg.middle.y))
+ if (!(ss >> m_cfg.origin.y))
{
- throw std::runtime_error("Error parsing middle_y");
+ throw std::runtime_error("Error parsing origin_y");
}
}
else if (paramName == "top")
m_cfg.sleepMs = 5;
m_cfg.autoBlink = true;
m_cfg.autoBreath = true;
- m_cfg.randomMotion = false;
+ m_cfg.randomIdleMotion = false;
m_cfg.useLipSync = true;
m_cfg.lipSyncGain = 10;
m_cfg.lipSyncCutOff = 0.15;
m_cfg.right = 1919; // These will be the full screen for 1920x1080
m_cfg.screen = 0;
- m_cfg.middle = {1600, 870}; // Somewhere near the bottom right
+ m_cfg.origin = {1600, 870}; // Somewhere near the bottom right
}