Skip to content

Commit d651f02

Browse files
committed
fix: address memory leaks, solve multi-arm rendering, and refine hit detection
1 parent 1a572a5 commit d651f02

2 files changed

Lines changed: 61 additions & 63 deletions

File tree

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

Lines changed: 25 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import java.nio.file.Files;
1414
import java.nio.file.Path;
1515
import java.nio.file.StandardCopyOption;
16+
import java.util.HashMap;
17+
import java.util.Map;
1618

1719
import static org.lwjgl.glfw.GLFW.*;
1820
import static org.lwjgl.opengl.GL11.*;
@@ -21,9 +23,8 @@
2123
public class Main {
2224
private long window;
2325
private CubismUserModel model;
24-
private final float[] mvp = new float[]{
25-
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1
26-
};
26+
private final Map<String, byte[]> motionCache = new HashMap<>();
27+
private final float[] mvp = new float[]{1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1};
2728

2829
public void run() throws Exception {
2930
init();
@@ -38,15 +39,13 @@ private void init() {
3839
glfwMakeContextCurrent(window);
3940
GL.createCapabilities();
4041
glfwSwapInterval(1);
41-
42-
// Required for Live2D transparency and masking
4342
glEnable(GL_BLEND);
4443
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
4544

4645
glfwSetCursorPosCallback(window, (win, x, y) -> {
47-
try (MemoryStack stack = MemoryStack.stackPush()) {
48-
IntBuffer wb = stack.mallocInt(1), hb = stack.mallocInt(1);
49-
glfwGetWindowSize(window, wb, hb);
46+
try (MemoryStack s = MemoryStack.stackPush()) {
47+
IntBuffer wb = s.mallocInt(1), hb = s.mallocInt(1);
48+
glfwGetWindowSize(win, wb, hb);
5049
float nx = (float) (x / (wb.get(0) / 2.0) - 1.0);
5150
float ny = (float) (1.0 - y / (hb.get(0) / 2.0));
5251
if (model != null) model.setDragging(nx, ny);
@@ -55,21 +54,21 @@ private void init() {
5554

5655
glfwSetMouseButtonCallback(window, (win, button, action, mods) -> {
5756
if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS) {
58-
try (MemoryStack stack = MemoryStack.stackPush()) {
59-
DoubleBuffer xb = stack.mallocDouble(1), yb = stack.mallocDouble(1);
60-
IntBuffer wb = stack.mallocInt(1), hb = stack.mallocInt(1);
61-
glfwGetCursorPos(window, xb, yb);
62-
glfwGetWindowSize(window, wb, hb);
63-
57+
try (MemoryStack s = MemoryStack.stackPush()) {
58+
DoubleBuffer xb = s.mallocDouble(1), yb = s.mallocDouble(1);
59+
IntBuffer wb = s.mallocInt(1), hb = s.mallocInt(1);
60+
glfwGetCursorPos(win, xb, yb);
61+
glfwGetWindowSize(win, wb, hb);
6462
float nx = (float) (xb.get(0) / (wb.get(0) / 2.0) - 1.0);
6563
float ny = (float) (1.0 - yb.get(0) / (hb.get(0) / 2.0));
6664

67-
if (model.isHit("HitArea", nx, ny)) {
68-
System.out.println("Hit Body!");
69-
model.startMotion(load("/model/Hiyori/motions/Hiyori_m04.motion3.json"), 2, null);
70-
} else if (model.isHit("ArtMesh12", nx, ny) || model.isHit("ArtMesh01", nx, ny)) {
71-
System.out.println("Hit Head/Face!");
72-
model.startMotion(load("/model/Hiyori/motions/Hiyori_m01.motion3.json"), 2, null);
65+
// Check Head first
66+
if (model.isHit("ArtMesh12", nx, ny)) {
67+
System.out.println("Head!");
68+
model.startMotion(motionCache.get("m01"), 3, null);
69+
} else if (model.isHit("HitArea", nx, ny)) {
70+
System.out.println("Body!");
71+
model.startMotion(motionCache.get("m04"), 3, null);
7372
}
7473
} catch (Exception e) { e.printStackTrace(); }
7574
}
@@ -86,25 +85,21 @@ private void setup() throws Exception {
8685
model.loadPhysics(load("/model/Hiyori/Hiyori.physics3.json"));
8786
model.createRenderer();
8887

89-
System.out.println("Drawable IDs:");
90-
for (String id : model.getDrawableIds()) {
91-
System.out.println(" " + id);
92-
}
93-
9488
model.registerTexture(0, loadTex("/model/Hiyori/Hiyori.2048/texture_00.png"));
9589
model.registerTexture(1, loadTex("/model/Hiyori/Hiyori.2048/texture_01.png"));
90+
91+
motionCache.put("m01", load("/model/Hiyori/motions/Hiyori_m01.motion3.json"));
92+
motionCache.put("m04", load("/model/Hiyori/motions/Hiyori_m04.motion3.json"));
9693
}
9794

9895
private void loop() {
9996
while (!glfwWindowShouldClose(window)) {
10097
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
101-
102-
try (MemoryStack stack = MemoryStack.stackPush()) {
103-
IntBuffer w = stack.mallocInt(1), h = stack.mallocInt(1);
98+
try (MemoryStack s = MemoryStack.stackPush()) {
99+
IntBuffer w = s.mallocInt(1), h = s.mallocInt(1);
104100
glfwGetWindowSize(window, w, h);
105101
glViewport(0, 0, w.get(0), h.get(0));
106102
}
107-
108103
model.update(0.016f);
109104
model.draw(mvp);
110105
glfwSwapBuffers(window);
@@ -145,4 +140,4 @@ private int loadTex(String p) throws Exception {
145140
}
146141

147142
public static void main(String[] args) throws Exception { new Main().run(); }
148-
}
143+
}

native/src/CubismUserModel_JNI.cpp

Lines changed: 36 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
#include <Motion/CubismMotion.hpp>
66
#include <vector>
77
#include <string>
8-
#include <iostream>
8+
#include <map>
99

1010
using namespace Live2D::Cubism::Framework;
1111
using namespace Live2D::Cubism::Framework::Rendering;
@@ -20,6 +20,7 @@ class JniUserModel : public CubismUserModel {
2020
~JniUserModel() {
2121
JNIEnv* env = getEnv();
2222
if (env) env->DeleteGlobalRef(_javaObj);
23+
for (auto& pair : _expressionData) pair.second.clear();
2324
}
2425

2526
void loadModelCopy(const csmByte* buffer, csmSizeInt size) {
@@ -34,19 +35,23 @@ class JniUserModel : public CubismUserModel {
3435

3536
void loadPoseCopy(const csmByte* buffer, csmSizeInt size) {
3637
_poseData.assign(buffer, buffer + size);
37-
LoadPose(_poseData.data(), (csmSizeInt)_physicsData.size());
38+
LoadPose(_poseData.data(), (csmSizeInt)_poseData.size());
39+
}
40+
41+
void loadExpressionCopy(const csmByte* buffer, csmSizeInt size, const std::string& name) {
42+
auto& vec = _expressionData[name];
43+
vec.assign(buffer, buffer + size);
44+
LoadExpression(vec.data(), (csmSizeInt)vec.size(), name.c_str());
3845
}
3946

4047
void startMotion(const csmByte* buffer, csmSizeInt size, int priority) {
4148
auto* motion = CubismMotion::Create(buffer, size);
4249
if (!motion) return;
43-
4450
motion->SetFinishedMotionHandlerAndMotionCustomData([](ACubismMotion* self) {
45-
auto* model = static_cast<JniUserModel*>(self->GetFinishedMotionCustomData());
46-
model->notifyFinished();
51+
auto* m = static_cast<JniUserModel*>(self->GetFinishedMotionCustomData());
52+
m->notifyFinished();
4753
CubismMotion::Delete(static_cast<CubismMotion*>(self));
4854
}, this);
49-
5055
_motionManager->StartMotionPriority(motion, true, priority);
5156
}
5257

@@ -60,13 +65,18 @@ class JniUserModel : public CubismUserModel {
6065
env->DeleteLocalRef(name);
6166
}
6267

68+
bool isHitTransformed(const char* id, float x, float y) {
69+
if (!_model) return false;
70+
float mx = _modelMatrix->InvertTransformX(x);
71+
float my = _modelMatrix->InvertTransformY(y);
72+
return IsHit(CubismFramework::GetIdManager()->GetId(id), mx, my);
73+
}
74+
6375
void update(float dt) {
6476
if (!_model) return;
6577

6678
_model->LoadParameters();
67-
if (!_motionManager->IsFinished()) {
68-
_motionManager->UpdateMotion(_model, dt);
69-
}
79+
if (!_motionManager->IsFinished()) _motionManager->UpdateMotion(_model, dt);
7080
_model->SaveParameters();
7181

7282
if (_pose) _pose->UpdateParameters(_model, dt);
@@ -76,7 +86,6 @@ class JniUserModel : public CubismUserModel {
7686
auto* idm = CubismFramework::GetIdManager();
7787
_model->AddParameterValue(idm->GetId("ParamAngleX"), _dragManager->GetX() * 30.0f);
7888
_model->AddParameterValue(idm->GetId("ParamAngleY"), _dragManager->GetY() * 30.0f);
79-
_model->AddParameterValue(idm->GetId("ParamBodyAngleX"), _dragManager->GetX() * 10.0f);
8089
_model->AddParameterValue(idm->GetId("ParamEyeBallX"), _dragManager->GetX());
8190
_model->AddParameterValue(idm->GetId("ParamEyeBallY"), _dragManager->GetY());
8291
}
@@ -88,17 +97,14 @@ class JniUserModel : public CubismUserModel {
8897
private:
8998
JNIEnv* getEnv() {
9099
JNIEnv* env;
91-
if (_jvm->GetEnv((void**)&env, JNI_VERSION_1_6) == JNI_EDETACHED) {
92-
_jvm->AttachCurrentThread((void**)&env, nullptr);
93-
}
100+
if (_jvm->GetEnv((void**)&env, JNI_VERSION_1_6) == JNI_EDETACHED) _jvm->AttachCurrentThread((void**)&env, nullptr);
94101
return env;
95102
}
96103

97104
JavaVM* _jvm;
98105
jobject _javaObj;
99-
std::vector<csmByte> _mocData;
100-
std::vector<csmByte> _physicsData;
101-
std::vector<csmByte> _poseData;
106+
std::vector<csmByte> _mocData, _physicsData, _poseData;
107+
std::map<std::string, std::vector<csmByte>> _expressionData;
102108
};
103109

104110
extern "C" {
@@ -133,21 +139,21 @@ JNIEXPORT void JNICALL Java_dev_eatgrapes_live2d_CubismUserModel_loadPoseNative(
133139
}
134140

135141
JNIEXPORT void JNICALL Java_dev_eatgrapes_live2d_CubismUserModel_loadExpressionNative(JNIEnv* env, jclass, jlong ptr, jbyteArray buffer, jstring name) {
142+
const char* n = env->GetStringUTFChars(name, nullptr);
136143
jsize len = env->GetArrayLength(buffer);
137144
jbyte* data = env->GetByteArrayElements(buffer, nullptr);
138-
const char* n = env->GetStringUTFChars(name, nullptr);
139-
((JniUserModel*)ptr)->LoadExpression((const csmByte*)data, len, n);
140-
env->ReleaseStringUTFChars(name, n);
145+
((JniUserModel*)ptr)->loadExpressionCopy((const csmByte*)data, len, n);
141146
env->ReleaseByteArrayElements(buffer, data, JNI_ABORT);
147+
env->ReleaseStringUTFChars(name, n);
142148
}
143149

144150
JNIEXPORT void JNICALL Java_dev_eatgrapes_live2d_CubismUserModel_createRendererNative(JNIEnv*, jclass, jlong ptr) {
145151
((JniUserModel*)ptr)->CreateRenderer();
146152
}
147153

148154
JNIEXPORT void JNICALL Java_dev_eatgrapes_live2d_CubismUserModel_registerTextureNative(JNIEnv*, jclass, jlong ptr, jint index, jint textureId) {
149-
auto* renderer = ((JniUserModel*)ptr)->GetRenderer<CubismRenderer_OpenGLES2>();
150-
if (renderer) renderer->BindTexture(index, (GLuint)textureId);
155+
auto* r = ((JniUserModel*)ptr)->GetRenderer<CubismRenderer_OpenGLES2>();
156+
if (r) r->BindTexture(index, (GLuint)textureId);
151157
}
152158

153159
JNIEXPORT void JNICALL Java_dev_eatgrapes_live2d_CubismUserModel_setDraggingNative(JNIEnv*, jclass, jlong ptr, jfloat x, jfloat y) {
@@ -156,7 +162,7 @@ JNIEXPORT void JNICALL Java_dev_eatgrapes_live2d_CubismUserModel_setDraggingNati
156162

157163
JNIEXPORT jboolean JNICALL Java_dev_eatgrapes_live2d_CubismUserModel_isHitNative(JNIEnv* env, jclass, jlong ptr, jstring id, jfloat x, jfloat y) {
158164
const char* s = env->GetStringUTFChars(id, nullptr);
159-
bool hit = ((JniUserModel*)ptr)->IsHit(CubismFramework::GetIdManager()->GetId(s), x, y);
165+
bool hit = ((JniUserModel*)ptr)->isHitTransformed(s, x, y);
160166
env->ReleaseStringUTFChars(id, s);
161167
return hit;
162168
}
@@ -189,24 +195,21 @@ JNIEXPORT jfloat JNICALL Java_dev_eatgrapes_live2d_CubismUserModel_getCanvasHeig
189195
JNIEXPORT jobjectArray JNICALL Java_dev_eatgrapes_live2d_CubismUserModel_getDrawableIdsNative(JNIEnv* env, jclass, jlong ptr) {
190196
auto* model = ((JniUserModel*)ptr)->GetModel();
191197
int count = model->GetDrawableCount();
192-
jclass strCls = env->FindClass("java/lang/String");
193-
jobjectArray res = env->NewObjectArray(count, strCls, nullptr);
198+
jobjectArray res = env->NewObjectArray(count, env->FindClass("java/lang/String"), nullptr);
194199
for (int i = 0; i < count; i++) {
195-
env->SetObjectArrayElement(res, i, env->NewStringUTF(model->GetDrawableId(i)->GetString().GetRawString()));
200+
jstring s = env->NewStringUTF(model->GetDrawableId(i)->GetString().GetRawString());
201+
env->SetObjectArrayElement(res, i, s);
202+
env->DeleteLocalRef(s);
196203
}
197204
return res;
198205
}
199206

200207
JNIEXPORT void JNICALL Java_dev_eatgrapes_live2d_CubismUserModel_drawNative(JNIEnv* env, jclass, jlong ptr, jfloatArray matrix) {
201208
jfloat* m_ptr = env->GetFloatArrayElements(matrix, nullptr);
202-
CubismMatrix44 m;
203-
m.SetMatrix(m_ptr);
204-
auto* renderer = ((JniUserModel*)ptr)->GetRenderer<CubismRenderer_OpenGLES2>();
205-
if (renderer) {
206-
renderer->SetMvpMatrix(&m);
207-
renderer->DrawModel();
208-
}
209+
CubismMatrix44 m; m.SetMatrix(m_ptr);
210+
auto* r = ((JniUserModel*)ptr)->GetRenderer<CubismRenderer_OpenGLES2>();
211+
if (r) { r->SetMvpMatrix(&m); r->DrawModel(); }
209212
env->ReleaseFloatArrayElements(matrix, m_ptr, JNI_ABORT);
210213
}
211214

212-
}
215+
}

0 commit comments

Comments
 (0)