Skip to content

Live2D export: single-keyframe motion curves produce invalid motion3.json #150

Description

@Aionfatedio

Problem

When exporting a Live2D model, any parameter curve that has only one keyframe (i.e. a parameter held constant for the whole motion) is written to motion3.json as a curve whose Segments array contains only the initial point [time, value] with no segment after it.

This is invalid per the motion3.json spec (a curve must contain at least one segment). Such a file loads fine in the Unity runtime (it never parses motion3.json), but crashes the Cubism Editor "Viewer for OW" and the Cubism SDK motion parser :

  java.lang.IndexOutOfBoundsException: Index N out of bounds for length N
      at com.live2d.sdk.cubism... 

Reproduce

  1. Extract a Live2D model that contains a motion where at least one parameter curve has a single keyframe (constant value).
  2. Open the exported *.model3.json (or *.moc3) in Cubism Editor's Viewer for OW (or load it with any Cubism SDK).
  3. Loading aborts with IndexOutOfBoundsException: Index N out of bounds for length N.

Cause

In AssetStudioUtility/CubismLive2DExtractor/CubismMotion3Json.cs:

Each curve's Segments is seeded with the initial point, then segments are appended in a loop:

Segments = new List<float> { firstTime, firstValue };
for (var j = 1; j < curve.Count; j++)
    AddSegments(...);   

If curve.Count == 1, the loop never runs, so Segments stays [time, value] (no segment). A Cubism parser reads the initial point, advances the segment position to index 2, then unconditionally reads the segment type at index 2 — past the end of the 2-element array. Because the SDK pre-sizes its global point/segment buffers from Meta.TotalSegmentCount / Meta.TotalPointCount, the overrun surfaces as Index N out of bounds for length N.

Proposed fix

After the segment loop in both constructors, if no segment was produced, append a constant Linear segment spanning to
the motion duration, and update the counters.

Added to CubismMotion3Json:

private static void EnsureTerminatingSegment(
    SerializableCurve cubismCurve, float duration, float fps,
    ref int totalPointCount, ref int totalSegmentCount)
{
    if (cubismCurve.Segments.Count > 2)   // already has at least one segment
        return;
    var startTime = cubismCurve.Segments[0];
    var value     = cubismCurve.Segments[1];
    var step      = fps > 0f ? 1f / fps : 1f / 30f;
    var endTime   = duration > startTime ? duration : startTime + step;
    cubismCurve.Segments.Add(0f);       // Linear segment ID
    cubismCurve.Segments.Add(endTime);
    cubismCurve.Segments.Add(value);
    totalPointCount  += 1;
    totalSegmentCount++;
}

Call it right after each segment-building loop:

// constructor (CubismFadeMotionData):
}   // end of: for (var j = 1; j < ...m_Curve.Count; j++)
EnsureTerminatingSegment(Curves[actualCurveCount], Meta.Duration, Meta.Fps, ref totalPointCount, ref totalSegmentCount);
actualCurveCount++;
totalPointCount++;

// constructor (ImportedKeyframedAnimation):
}   // end of: for (var j = 1; j < track.Curve.Count; j++)
EnsureTerminatingSegment(Curves[i], Meta.Duration, Meta.Fps, ref totalPointCount, ref totalSegmentCount);
totalPointCount++;

Metadata

Metadata

Assignees

No one assigned

    Labels

    live2DCubism Live2D

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions