Skip to content

Commit eedc019

Browse files
committed
fix: resolve jittery idle transition by using native SDK motion looping and priority system
1 parent 9b57b0c commit eedc019

3 files changed

Lines changed: 37 additions & 25 deletions

File tree

binding/src/main/java/dev/eatgrapes/live2d/CubismUserModel.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ public CubismUserModel() {
3737
public boolean isHit(String drawableId, float x, float y) { return isHitNative(_ptr, drawableId, x, y); }
3838
private static native boolean isHitNative(long ptr, String drawableId, float x, float y);
3939

40-
public void startMotion(byte[] buffer, int priority, Consumer<String> onFinished) {
40+
public void startMotion(byte[] buffer, int priority, boolean loop, Consumer<String> onFinished) {
4141
this.motionFinishedCallback = onFinished;
42-
startMotionNative(_ptr, buffer, priority);
42+
startMotionNative(_ptr, buffer, priority, loop);
4343
}
44-
private static native void startMotionNative(long ptr, byte[] buffer, int priority);
44+
private static native void startMotionNative(long ptr, byte[] buffer, int priority, boolean loop);
4545

4646
private void onMotionFinished(String name) {
4747
if (motionFinishedCallback != null) {
@@ -70,4 +70,4 @@ private void onMotionFinished(String name) {
7070
@Override
7171
public void close() { deleteNative(_ptr); }
7272
private static native void deleteNative(long ptr);
73-
}
73+
}

example/src/main/java/dev/eatgrapes/live2d/example/Main.java

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
public class Main {
2424
private long window;
2525
private CubismUserModel model;
26-
private final Map<String, byte[]> motionCache = new HashMap<>();
26+
private final Map<String, byte[]> motions = new HashMap<>();
2727
private final float[] mvp = new float[]{1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1};
2828

2929
public void run() throws Exception {
@@ -47,7 +47,6 @@ private void init() {
4747
IntBuffer wb = s.mallocInt(1), hb = s.mallocInt(1);
4848
glfwGetWindowSize(win, wb, hb);
4949
float aspect = (float) wb.get(0) / hb.get(0);
50-
// Map screen to NDC then to Model Space aspect
5150
float nx = (float) (x / (wb.get(0) / 2.0) - 1.0) * aspect;
5251
float ny = (float) (1.0 - y / (hb.get(0) / 2.0));
5352
if (model != null) model.setDragging(nx, ny);
@@ -61,14 +60,14 @@ private void init() {
6160
IntBuffer wb = s.mallocInt(1), hb = s.mallocInt(1);
6261
glfwGetCursorPos(win, xb, yb);
6362
glfwGetWindowSize(win, wb, hb);
64-
6563
float aspect = (float) wb.get(0) / hb.get(0);
6664
float nx = (float) (xb.get(0) / (wb.get(0) / 2.0) - 1.0) * aspect;
6765
float ny = (float) (1.0 - yb.get(0) / (hb.get(0) / 2.0));
6866

6967
if (model.isHit("HitArea", nx, ny)) {
7068
System.out.println("Hit Body!");
71-
model.startMotion(motionCache.get("m04"), 3, null);
69+
// Priority 3: Override the idle motion
70+
model.startMotion(motions.get("m04"), 3, false, null);
7271
}
7372
} catch (Exception e) { e.printStackTrace(); }
7473
}
@@ -88,8 +87,11 @@ private void setup() throws Exception {
8887
model.registerTexture(0, loadTex("/model/Hiyori/Hiyori.2048/texture_00.png"));
8988
model.registerTexture(1, loadTex("/model/Hiyori/Hiyori.2048/texture_01.png"));
9089

91-
motionCache.put("m01", load("/model/Hiyori/motions/Hiyori_m01.motion3.json"));
92-
motionCache.put("m04", load("/model/Hiyori/motions/Hiyori_m04.motion3.json"));
90+
motions.put("idle", load("/model/Hiyori/motions/Hiyori_m01.motion3.json"));
91+
motions.put("m04", load("/model/Hiyori/motions/Hiyori_m04.motion3.json"));
92+
93+
// Start background idle loop once at priority 1
94+
model.startMotion(motions.get("idle"), 1, true, null);
9395
}
9496

9597
private void loop() {
@@ -100,14 +102,9 @@ private void loop() {
100102
IntBuffer w = s.mallocInt(1), h = s.mallocInt(1);
101103
glfwGetWindowSize(window, w, h);
102104
glViewport(0, 0, w.get(0), h.get(0));
103-
104105
float aspect = (float) w.get(0) / h.get(0);
105-
// Simple ortho projection to maintain aspect ratio
106106
for (int i = 0; i < 16; i++) mvp[i] = 0;
107-
mvp[0] = 1.0f / aspect;
108-
mvp[5] = 1.0f;
109-
mvp[10] = 1.0f;
110-
mvp[15] = 1.0f;
107+
mvp[0] = 1.0f / aspect; mvp[5] = 1.0f; mvp[10] = 1.0f; mvp[15] = 1.0f;
111108
}
112109

113110
model.update(0.016f);

native/src/CubismUserModel_JNI.cpp

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <string>
88
#include <map>
99
#include <mutex>
10+
#include <algorithm>
1011

1112
using namespace Live2D::Cubism::Framework;
1213
using namespace Live2D::Cubism::Framework::Rendering;
@@ -42,39 +43,51 @@ class JniUserModel : public CubismUserModel {
4243
LoadPose(_poseBuffer.data(), (csmSizeInt)_poseBuffer.size());
4344
}
4445

45-
void startMotionCopy(const csmByte* buffer, csmSizeInt size, int priority) {
46+
void startMotionCopy(const csmByte* buffer, csmSizeInt size, int priority, bool loop) {
4647
std::vector<csmByte> motionBuf(buffer, buffer + size);
4748
auto* motion = CubismMotion::Create(motionBuf.data(), (csmSizeInt)motionBuf.size());
4849
if (!motion) return;
50+
51+
motion->SetLoop(loop);
4952
_motionBuffers[motion] = std::move(motionBuf);
53+
5054
motion->SetFinishedMotionHandlerAndMotionCustomData([](ACubismMotion* self) {
5155
auto* m = static_cast<CubismMotion*>(self);
5256
auto* model = static_cast<JniUserModel*>(m->GetFinishedMotionCustomData());
5357
model->queueFinishedMotion(m);
5458
}, this);
55-
_motionManager->StartMotionPriority(motion, true, priority);
59+
60+
_motionManager->StartMotionPriority(motion, false, priority);
5661
}
5762

5863
void queueFinishedMotion(CubismMotion* motion) {
5964
std::lock_guard<std::mutex> lock(_pendingMutex);
60-
_pendingDeletion.push_back(motion);
65+
if (std::find(_pendingDeletion.begin(), _pendingDeletion.end(), motion) == _pendingDeletion.end()) {
66+
_pendingDeletion.push_back(motion);
67+
}
6168
}
6269

6370
void update(float dt) {
6471
if (!_model) return;
72+
6573
{
6674
std::lock_guard<std::mutex> lock(_pendingMutex);
6775
for (auto* m : _pendingDeletion) {
68-
_motionBuffers.erase(m);
69-
CubismMotion::Delete(m);
70-
notifyFinished();
76+
if (_motionBuffers.count(m)) {
77+
_motionBuffers.erase(m);
78+
CubismMotion::Delete(m);
79+
notifyFinished();
80+
}
7181
}
7282
_pendingDeletion.clear();
7383
}
84+
7485
_model->LoadParameters();
7586
_motionManager->UpdateMotion(_model, dt);
7687
_model->SaveParameters();
88+
7789
if (_pose) _pose->UpdateParameters(_model, dt);
90+
7891
if (_dragManager) {
7992
_dragManager->Update(dt);
8093
auto* idm = CubismFramework::GetIdManager();
@@ -83,6 +96,7 @@ class JniUserModel : public CubismUserModel {
8396
_model->AddParameterValue(idm->GetId("ParamEyeBallX"), _dragManager->GetX());
8497
_model->AddParameterValue(idm->GetId("ParamEyeBallY"), _dragManager->GetY());
8598
}
99+
86100
if (_physics) _physics->Evaluate(_model, dt);
87101
_model->Update();
88102
}
@@ -108,6 +122,7 @@ class JniUserModel : public CubismUserModel {
108122
if (_jvm->GetEnv((void**)&env, JNI_VERSION_1_6) == JNI_EDETACHED) _jvm->AttachCurrentThread((void**)&env, nullptr);
109123
return env;
110124
}
125+
111126
JavaVM* _jvm;
112127
jobject _javaObj = nullptr;
113128
std::vector<csmByte> _mocBuffer, _physicsBuffer, _poseBuffer;
@@ -183,10 +198,10 @@ JNIEXPORT jboolean JNICALL Java_dev_eatgrapes_live2d_CubismUserModel_isHitNative
183198
return hit;
184199
}
185200

186-
JNIEXPORT void JNICALL Java_dev_eatgrapes_live2d_CubismUserModel_startMotionNative(JNIEnv* env, jclass, jlong ptr, jbyteArray buffer, jint priority) {
201+
JNIEXPORT void JNICALL Java_dev_eatgrapes_live2d_CubismUserModel_startMotionNative(JNIEnv* env, jclass, jlong ptr, jbyteArray buffer, jint priority, jboolean loop) {
187202
jsize len = env->GetArrayLength(buffer);
188203
jbyte* data = env->GetByteArrayElements(buffer, nullptr);
189-
((JniUserModel*)ptr)->startMotionCopy((const csmByte*)data, len, priority);
204+
((JniUserModel*)ptr)->startMotionCopy((const csmByte*)data, len, priority, loop);
190205
env->ReleaseByteArrayElements(buffer, data, JNI_ABORT);
191206
}
192207

@@ -228,4 +243,4 @@ JNIEXPORT void JNICALL Java_dev_eatgrapes_live2d_CubismUserModel_drawNative(JNIE
228243
env->ReleaseFloatArrayElements(matrix, m_ptr, JNI_ABORT);
229244
}
230245

231-
}
246+
}

0 commit comments

Comments
 (0)