Skip to content

Commit 3d57a9a

Browse files
committed
fix motion out-of-bound bug
1 parent cb1b60e commit 3d57a9a

File tree

2 files changed

+190
-9
lines changed

2 files changed

+190
-9
lines changed

src/renderer/WebSDK/Framework/src/motion/cubismmotion.ts

Lines changed: 67 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { csmVector } from '../type/csmvector';
1414
import {
1515
CSM_ASSERT,
1616
CubismLogDebug,
17+
CubismLogError,
1718
CubismLogWarning
1819
} from '../utils/cubismdebug';
1920
import { ACubismMotion, FinishedMotionCallback } from './acubismmotion';
@@ -254,14 +255,26 @@ export class CubismMotion extends ACubismMotion {
254255
): CubismMotion {
255256
const ret = new CubismMotion();
256257

257-
ret.parse(buffer, size);
258-
ret._sourceFrameRate = ret._motionData.fps;
259-
ret._loopDurationSeconds = ret._motionData.duration;
260-
ret._onFinishedMotion = onFinishedMotionHandler;
258+
try {
259+
ret.parse(buffer, size);
261260

262-
// NOTE: Editorではループありのモーション書き出しは非対応
263-
// ret->_loop = (ret->_motionData->Loop > 0);
264-
return ret;
261+
// Check if motion data was successfully parsed
262+
if (!ret._motionData) {
263+
CubismLogError('Failed to parse motion data - motion data is null');
264+
return null;
265+
}
266+
267+
ret._sourceFrameRate = ret._motionData.fps;
268+
ret._loopDurationSeconds = ret._motionData.duration;
269+
ret._onFinishedMotion = onFinishedMotionHandler;
270+
271+
// NOTE: Editorではループありのモーション書き出しは非対応
272+
// ret->_loop = (ret->_motionData->Loop > 0);
273+
return ret;
274+
} catch (error) {
275+
CubismLogError(`Failed to create motion: ${error}`);
276+
return null;
277+
}
265278
}
266279

267280
/**
@@ -767,13 +780,51 @@ export class CubismMotion extends ACubismMotion {
767780
CubismMotionCurve,
768781
true
769782
);
783+
784+
// Pre-calculate actual required sizes by analyzing the motion data
785+
let totalRequiredSegments = 0;
786+
let totalRequiredPoints = 0;
787+
788+
for (let curveCount = 0; curveCount < this._motionData.curveCount; ++curveCount) {
789+
const segmentCount = json.getMotionCurveSegmentCount(curveCount);
790+
791+
for (let segmentPos = 0; segmentPos < segmentCount; ) {
792+
totalRequiredSegments++;
793+
794+
if (segmentPos == 0) {
795+
totalRequiredPoints += 1; // First point
796+
segmentPos += 2;
797+
} else {
798+
const segmentType: CubismMotionSegmentType = json.getMotionCurveSegment(curveCount, segmentPos);
799+
800+
switch (segmentType) {
801+
case CubismMotionSegmentType.CubismMotionSegmentType_Linear:
802+
case CubismMotionSegmentType.CubismMotionSegmentType_Stepped:
803+
case CubismMotionSegmentType.CubismMotionSegmentType_InverseStepped:
804+
totalRequiredPoints += 1;
805+
segmentPos += 3;
806+
break;
807+
case CubismMotionSegmentType.CubismMotionSegmentType_Bezier:
808+
totalRequiredPoints += 3;
809+
segmentPos += 7;
810+
break;
811+
default:
812+
segmentPos += 3; // Default fallback
813+
totalRequiredPoints += 1;
814+
break;
815+
}
816+
}
817+
}
818+
}
819+
820+
// Use the calculated sizes (with a small buffer for safety)
770821
this._motionData.segments.updateSize(
771-
json.getMotionTotalSegmentCount(),
822+
totalRequiredSegments,
772823
CubismMotionSegment,
773824
true
774825
);
775826
this._motionData.points.updateSize(
776-
json.getMotionTotalPointCount(),
827+
totalRequiredPoints,
777828
CubismMotionPoint,
778829
true
779830
);
@@ -859,6 +910,13 @@ export class CubismMotion extends ACubismMotion {
859910
this._motionData.segments.at(totalSegmentCount).evaluate =
860911
linearEvaluate;
861912

913+
// Check if we have enough points in the array, expand if needed
914+
if (totalPointCount >= this._motionData.points.getSize()) {
915+
const newSize = Math.max(totalPointCount + 1, this._motionData.points.getSize() * 2);
916+
CubismLogWarning(`Expanding motion points array from ${this._motionData.points.getSize()} to ${newSize} for Linear segment`);
917+
this._motionData.points.updateSize(newSize, CubismMotionPoint, true);
918+
}
919+
862920
this._motionData.points.at(totalPointCount).time =
863921
json.getMotionCurveSegment(curveCount, segmentPosition + 1);
864922
this._motionData.points.at(totalPointCount).value =

src/renderer/src/hooks/canvas/use-live2d-model.ts

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,129 @@ export const useLive2DModel = ({
406406
}
407407
}, [isPet, electronApi]);
408408

409+
// Expose motion debugging functions to window for console testing
410+
useEffect(() => {
411+
const playMotion = (motionGroup: string, motionIndex: number = 0, priority: number = 3) => {
412+
const adapter = (window as any).getLAppAdapter?.();
413+
if (!adapter) {
414+
console.error('Live2D adapter not available');
415+
return false;
416+
}
417+
418+
const model = adapter.getModel();
419+
if (!model) {
420+
console.error('Live2D model not available');
421+
return false;
422+
}
423+
424+
try {
425+
console.log(`Playing motion: group="${motionGroup}", index=${motionIndex}, priority=${priority}`);
426+
const result = model.startMotion(motionGroup, motionIndex, priority);
427+
console.log('Motion start result:', result);
428+
return result;
429+
} catch (error) {
430+
console.error('Error playing motion:', error);
431+
return false;
432+
}
433+
};
434+
435+
const playRandomMotion = (motionGroup: string, priority: number = 3) => {
436+
const adapter = (window as any).getLAppAdapter?.();
437+
if (!adapter) {
438+
console.error('Live2D adapter not available');
439+
return false;
440+
}
441+
442+
const model = adapter.getModel();
443+
if (!model) {
444+
console.error('Live2D model not available');
445+
return false;
446+
}
447+
448+
try {
449+
console.log(`Playing random motion from group: "${motionGroup}", priority=${priority}`);
450+
const result = model.startRandomMotion(motionGroup, priority);
451+
console.log('Random motion start result:', result);
452+
return result;
453+
} catch (error) {
454+
console.error('Error playing random motion:', error);
455+
return false;
456+
}
457+
};
458+
459+
const getMotionInfo = () => {
460+
const adapter = (window as any).getLAppAdapter?.();
461+
if (!adapter) {
462+
console.error('Live2D adapter not available');
463+
return null;
464+
}
465+
466+
const model = adapter.getModel();
467+
if (!model) {
468+
console.error('Live2D model not available');
469+
return null;
470+
}
471+
472+
try {
473+
const motionGroups = [];
474+
const setting = model._modelSetting;
475+
if (setting) {
476+
// Get all motion groups
477+
const groups = setting._json?.FileReferences?.Motions;
478+
if (groups) {
479+
for (const groupName in groups) {
480+
const motions = groups[groupName];
481+
motionGroups.push({
482+
name: groupName,
483+
count: motions.length,
484+
motions: motions.map((motion: any, index: number) => ({
485+
index,
486+
file: motion.File
487+
}))
488+
});
489+
}
490+
}
491+
}
492+
493+
console.log('Available motion groups:', motionGroups);
494+
return motionGroups;
495+
} catch (error) {
496+
console.error('Error getting motion info:', error);
497+
return null;
498+
}
499+
};
500+
501+
// Expose to window for console access
502+
(window as any).Live2DDebug = {
503+
playMotion,
504+
playRandomMotion,
505+
getMotionInfo,
506+
// Helper functions
507+
help: () => {
508+
console.log(`
509+
Live2D Motion Debug Functions:
510+
- Live2DDebug.getMotionInfo() - Get all available motion groups and their motions
511+
- Live2DDebug.playMotion(group, index, priority) - Play specific motion
512+
- Live2DDebug.playRandomMotion(group, priority) - Play random motion from group
513+
- Live2DDebug.help() - Show this help
514+
515+
Example usage:
516+
Live2DDebug.getMotionInfo() // See available motions
517+
Live2DDebug.playMotion("", 0) // Play first motion from default group
518+
Live2DDebug.playRandomMotion("") // Play random motion from default group
519+
`);
520+
}
521+
};
522+
523+
console.log('Live2D Debug functions exposed to window.Live2DDebug');
524+
console.log('Type Live2DDebug.help() for usage information');
525+
526+
// Cleanup function
527+
return () => {
528+
delete (window as any).Live2DDebug;
529+
};
530+
}, []);
531+
409532
return {
410533
position,
411534
isDragging,

0 commit comments

Comments
 (0)