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++;

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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