Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.LinkedList;
Expand Down Expand Up @@ -1135,6 +1136,7 @@ public void readAnimation(int animationIndex) throws IOException {
assertNotNull(dataIndex, "No output accessor Provided for animation sampler");

String interpolation = getAsString(sampler, "interpolation");
boolean cubicSpline = "CUBICSPLINE".equals(interpolation);
if (interpolation == null || !interpolation.equals("LINEAR")) {
// JME anim system only supports Linear interpolation (will be possible with monkanim though)
// TODO rework this once monkanim is core,
Expand All @@ -1153,18 +1155,30 @@ public void readAnimation(int animationIndex) throws IOException {
if (targetPath.equals("translation")) {
trackData.timeArrays.add(new TrackData.TimeData(times, TrackData.Type.Translation));
Vector3f[] translations = readAccessorData(dataIndex, vector3fArrayPopulator);
if (cubicSpline) {
translations = getCubicSplineValues(translations);
}
trackData.translations = translations;
} else if (targetPath.equals("scale")) {
trackData.timeArrays.add(new TrackData.TimeData(times, TrackData.Type.Scale));
Vector3f[] scales = readAccessorData(dataIndex, vector3fArrayPopulator);
if (cubicSpline) {
scales = getCubicSplineValues(scales);
}
trackData.scales = scales;
} else if (targetPath.equals("rotation")) {
trackData.timeArrays.add(new TrackData.TimeData(times, TrackData.Type.Rotation));
Quaternion[] rotations = readAccessorData(dataIndex, quaternionArrayPopulator);
if (cubicSpline) {
rotations = getCubicSplineValues(rotations);
}
trackData.rotations = rotations;
} else {
trackData.timeArrays.add(new TrackData.TimeData(times, TrackData.Type.Morph));
float[] weights = readAccessorData(dataIndex, floatArrayPopulator);
if (cubicSpline) {
weights = getCubicSplineValues(weights, times.length);
}
trackData.weights = weights;
hasMorphTrack = true;
}
Expand Down Expand Up @@ -1495,6 +1509,35 @@ private MorphTrack toMorphTrack(TrackData data, Spatial spatial) {
return new MorphTrack(g, data.times, data.weights, nbMorph);
}

private <T> T[] getCubicSplineValues(T[] data) {
if (data == null) {
throw new AssetLoadException("No output data defined for cubic spline animation sampler");
}
if (data.length % 3 != 0) {
throw new AssetLoadException("Cubic spline animation sampler output does not contain tangent/value triplets");
}
T[] values = Arrays.copyOf(data, data.length / 3);
for (int i = 0; i < values.length; i++) {
values[i] = data[i * 3 + 1];
}
return values;
}
Comment on lines +1512 to +1524
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential NullPointerExceptions, we should add a defensive null check for the data array before accessing its length or copying it.

    private <T> T[] getCubicSplineValues(T[] data) {
        if (data == null) {
            return null;
        }
        T[] values = Arrays.copyOf(data, data.length / 3);
        for (int i = 0; i < values.length; i++) {
            values[i] = data[i * 3 + 1];
        }
        return values;
    }


private float[] getCubicSplineValues(float[] data, int timesLength) {
if (data == null) {
throw new AssetLoadException("No output data defined for cubic spline animation sampler");
}
if (timesLength <= 0 || data.length % timesLength != 0 || (data.length / timesLength) % 3 != 0) {
throw new AssetLoadException("Cubic spline animation sampler output does not match input times");
}
int valuesPerTime = data.length / timesLength / 3;
float[] values = new float[timesLength * valuesPerTime];
for (int i = 0; i < timesLength; i++) {
System.arraycopy(data, (i * 3 + 1) * valuesPerTime, values, i * valuesPerTime, valuesPerTime);
}
return values;
}
Comment on lines +1526 to +1539
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential NullPointerExceptions and ArithmeticException (division by zero if timesLength is 0), we should add defensive checks for data and timesLength.

    private float[] getCubicSplineValues(float[] data, int timesLength) {
        if (data == null) {
            return null;
        }
        if (timesLength <= 0) {
            return new float[0];
        }
        int valuesPerTime = data.length / timesLength / 3;
        float[] values = new float[timesLength * valuesPerTime];
        for (int i = 0; i < timesLength; i++) {
            System.arraycopy(data, (i * 3 + 1) * valuesPerTime, values, i * valuesPerTime, valuesPerTime);
        }
        return values;
    }


public <T> T fetchFromCache(String name, int index, Class<T> type) {
Object[] data = dataCache.get(name);
if (data == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,17 @@ public void testLoadEmptyScene() {
}
}

@Test
public void testLoadCubicSplineScaleAnimation() {
try {
Spatial scene = assetManager.loadModel("gltf/cubicSplineScale.gltf");
dumpScene(scene, 0);
} catch (AssetLoadException ex) {
ex.printStackTrace();
Assertions.fail("Failed to import gltf model with cubic spline scale animation");
}
}

@Test
public void testLightsPunctualExtension() {
try {
Expand Down
77 changes: 77 additions & 0 deletions jme3-plugins/src/test/resources/gltf/cubicSplineScale.gltf
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
{
"asset": {
"version": "2.0"
},
"scene": 0,
"scenes": [
{
"nodes": [
0
]
}
],
"nodes": [
{
"name": "AnimatedNode"
}
],
"animations": [
{
"name": "CubicSplineScale",
"channels": [
{
"sampler": 0,
"target": {
"node": 0,
"path": "scale"
}
}
],
"samplers": [
{
"input": 0,
"interpolation": "CUBICSPLINE",
"output": 1
}
]
}
],
"buffers": [
{
"uri": "data:application/octet-stream;base64,AAAAAAAAgD8AAABAAABAQAAAgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAPwAAgD8AAAAAAACAPwAAgD8AAAAAAACAPwAAgD8AAAAAAAAAQAAAAEAAAAAAAAAAQAAAAEAAAAAAAAAAQAAAAEAAAAAAAABAQAAAQEAAAAAAAABAQAAAQEAAAAAAAABAQAAAQEAAAAAAAACAQAAAgEAAAAAAAACAQAAAgEAAAAAAAACAQAAAgEA=",
"byteLength": 200
}
],
"bufferViews": [
{
"buffer": 0,
"byteOffset": 0,
"byteLength": 20
},
{
"buffer": 0,
"byteOffset": 20,
"byteLength": 180
}
],
"accessors": [
{
"bufferView": 0,
"componentType": 5126,
"count": 5,
"type": "SCALAR",
"min": [
0
],
"max": [
4
]
},
{
"bufferView": 1,
"componentType": 5126,
"count": 15,
"type": "VEC3"
}
]
}
Loading