Compute the 4 more weighted bone index for skin animation when there are several Joint buffers for a mesh.

fix-456
Nehon 7 years ago committed by Rémy Bouquet
parent 911d8d77ef
commit 225afd0f92
  1. 122
      jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
  2. 157
      jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java

@ -11,6 +11,8 @@ import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.*;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture2D;
import com.jme3.util.BufferUtils;
import com.jme3.util.IntMap;
import com.jme3.util.mikktspace.MikktspaceTangentGenerator;
import java.io.*;
@ -50,13 +52,12 @@ public class GltfLoader implements AssetLoader {
private FloatArrayPopulator floatArrayPopulator = new FloatArrayPopulator();
private Vector3fArrayPopulator vector3fArrayPopulator = new Vector3fArrayPopulator();
private QuaternionArrayPopulator quaternionArrayPopulator = new QuaternionArrayPopulator();
private Matrix4fArrayPopulator matrix4fArrayPopulator = new Matrix4fArrayPopulator();
private static Map<String, MaterialAdapter> defaultMaterialAdapters = new HashMap<>();
private boolean useNormalsFlag = false;
private Transform tmpTransforms = new Transform();
private Quaternion tmpQuat = new Quaternion();
Map<SkinData, List<Spatial>> skinnedSpatials = new HashMap<>();
IntMap<SkinBuffers> skinBuffers = new IntMap<>();
static {
defaultMaterialAdapters.put("pbrMetallicRoughness", new PBRMaterialAdapter());
@ -323,9 +324,29 @@ public class GltfLoader implements AssetLoader {
}
JsonObject attributes = meshObject.getAsJsonObject("attributes");
assertNotNull(attributes, "No attributes defined for mesh " + mesh);
skinBuffers.clear();
for (Map.Entry<String, JsonElement> entry : attributes.entrySet()) {
mesh.setBuffer(readAccessorData(entry.getValue().getAsInt(), new VertexBufferPopulator(getVertexBufferType(entry.getKey()))));
//special case for joints and weights buffer. If there are more than 4 bones per vertex, there might be several of them
//we need to read them all and to keep only the 4 that have the most weight on the vertex.
String bufferType = entry.getKey();
if (bufferType.startsWith("JOINTS")) {
SkinBuffers buffs = getSkinBuffers(bufferType);
SkinBuffers buffer = readAccessorData(entry.getValue().getAsInt(), new JointArrayPopulator());
buffs.joints = buffer.joints;
buffs.componentSize = buffer.componentSize;
} else if (bufferType.startsWith("WEIGHTS")) {
SkinBuffers buffs = getSkinBuffers(bufferType);
buffs.weights = readAccessorData(entry.getValue().getAsInt(), new FloatArrayPopulator());
} else {
VertexBuffer vb = readAccessorData(entry.getValue().getAsInt(), new VertexBufferPopulator(getVertexBufferType(bufferType)));
if (vb != null) {
mesh.setBuffer(vb);
}
}
}
handleSkinningBuffers(mesh, skinBuffers);
if (mesh.getBuffer(VertexBuffer.Type.BoneIndex) != null) {
//the mesh has some skinning let's create needed buffers for HW skinning
@ -374,6 +395,28 @@ public class GltfLoader implements AssetLoader {
return geomArray;
}
public static class WeightData {
float value;
short index;
int componentSize;
public WeightData(float value, short index, int componentSize) {
this.value = value;
this.index = index;
this.componentSize = componentSize;
}
}
private SkinBuffers getSkinBuffers(String bufferType) {
int bufIndex = getIndex(bufferType);
SkinBuffers buffs = skinBuffers.get(bufIndex);
if (buffs == null) {
buffs = new SkinBuffers();
skinBuffers.put(bufIndex, buffs);
}
return buffs;
}
private <R> R readAccessorData(int accessorIndex, Populator<R> populator) throws IOException {
assertNotNull(accessors, "No accessor attribute in the gltf file");
@ -399,8 +442,7 @@ public class GltfLoader implements AssetLoader {
return populator.populate(bufferViewIndex, componentType, type, count, byteOffset);
}
private void readBuffer(Integer bufferViewIndex, int byteOffset, int bufferSize, Object store, int numComponents) throws IOException {
private void readBuffer(Integer bufferViewIndex, int byteOffset, int bufferSize, Object store, int numComponents, int componentSize) throws IOException {
JsonObject bufferView = bufferViews.get(bufferViewIndex).getAsJsonObject();
Integer bufferIndex = getAsInteger(bufferView, "buffer");
@ -415,7 +457,7 @@ public class GltfLoader implements AssetLoader {
//int target = getAsInteger(bufferView, "target", 0);
byte[] data = readData(bufferIndex);
populateBuffer(store, data, bufferSize, byteOffset + bvByteOffset, byteStride, numComponents);
populateBuffer(store, data, bufferSize, byteOffset + bvByteOffset, byteStride, numComponents, componentSize);
//TODO extensions?
//TODO extras?
@ -606,7 +648,15 @@ public class GltfLoader implements AssetLoader {
//check if we are loading the same time array
//TODO specs actually don't forbid this...maybe remove this check and handle it.
if (animData.times != times) {
throw new AssetLoadException("Channel has different input accessors for samplers");
logger.log(Level.WARNING, "Channel has different input accessors for samplers");
// for (float time : animData.times) {
// System.err.print(time + ", ");
// }
// System.err.println("");
// for (float time : times) {
// System.err.print(time + ", ");
// }
// System.err.println("");
}
}
if (animData.length == null) {
@ -623,7 +673,6 @@ public class GltfLoader implements AssetLoader {
Quaternion[] rotations = readAccessorData(dataIndex, quaternionArrayPopulator);
animData.rotations = rotations;
}
}
if (name == null) {
@ -911,6 +960,20 @@ public class GltfLoader implements AssetLoader {
Transform armatureTransforms;
}
public static class SkinBuffers {
short[] joints;
float[] weights;
int componentSize;
public SkinBuffers(short[] joints, int componentSize) {
this.joints = joints;
this.componentSize = componentSize;
}
public SkinBuffers() {
}
}
private interface Populator<T> {
T populate(Integer bufferViewIndex, int componentType, String type, int count, int byteOffset) throws IOException;
}
@ -925,6 +988,11 @@ public class GltfLoader implements AssetLoader {
@Override
public VertexBuffer populate(Integer bufferViewIndex, int componentType, String type, int count, int byteOffset) throws IOException {
if (bufferType == null) {
logger.log(Level.WARNING, "could not assign data to any VertexBuffer type for buffer view " + bufferViewIndex);
return null;
}
VertexBuffer vb = new VertexBuffer(bufferType);
VertexBuffer.Format format = getVertexBufferFormat(componentType);
int numComponents = getNumberOfComponents(type);
@ -935,7 +1003,7 @@ public class GltfLoader implements AssetLoader {
//no referenced buffer, specs says to pad the buffer with zeros.
padBuffer(buff, bufferSize);
} else {
readBuffer(bufferViewIndex, byteOffset, bufferSize, buff, numComponents);
readBuffer(bufferViewIndex, byteOffset, bufferSize, buff, numComponents, format.getComponentSize());
}
if (bufferType == VertexBuffer.Type.Index) {
@ -955,13 +1023,13 @@ public class GltfLoader implements AssetLoader {
int numComponents = getNumberOfComponents(type);
int dataSize = numComponents * count;
float[] data = new float[count];
float[] data = new float[dataSize];
if (bufferViewIndex == null) {
//no referenced buffer, specs says to pad the data with zeros.
padBuffer(data, dataSize);
} else {
readBuffer(bufferViewIndex, byteOffset, dataSize, data, numComponents);
readBuffer(bufferViewIndex, byteOffset, dataSize, data, numComponents, 4);
}
return data;
@ -982,9 +1050,8 @@ public class GltfLoader implements AssetLoader {
//no referenced buffer, specs says to pad the data with zeros.
padBuffer(data, dataSize);
} else {
readBuffer(bufferViewIndex, byteOffset, dataSize, data, numComponents);
readBuffer(bufferViewIndex, byteOffset, dataSize, data, numComponents, 4);
}
return data;
}
}
@ -1002,30 +1069,47 @@ public class GltfLoader implements AssetLoader {
//no referenced buffer, specs says to pad the data with zeros.
padBuffer(data, dataSize);
} else {
readBuffer(bufferViewIndex, byteOffset, dataSize, data, numComponents);
readBuffer(bufferViewIndex, byteOffset, dataSize, data, numComponents, 4);
}
return data;
}
}
private class Matrix4fArrayPopulator implements Populator<Matrix4f[]> {
private class JointData {
short[] joints;
int componentSize;
public JointData(short[] joints, int componentSize) {
this.joints = joints;
this.componentSize = componentSize;
}
}
private class JointArrayPopulator implements Populator<SkinBuffers> {
@Override
public Matrix4f[] populate(Integer bufferViewIndex, int componentType, String type, int count, int byteOffset) throws IOException {
public SkinBuffers populate(Integer bufferViewIndex, int componentType, String type, int count, int byteOffset) throws IOException {
int numComponents = getNumberOfComponents(type);
//can be bytes or shorts.
int componentSize = 1;
if (componentType == 5123) {
componentSize = 2;
}
int dataSize = numComponents * count;
Matrix4f[] data = new Matrix4f[count];
short[] data = new short[dataSize];
if (bufferViewIndex == null) {
//no referenced buffer, specs says to pad the data with zeros.
padBuffer(data, dataSize);
} else {
readBuffer(bufferViewIndex, byteOffset, dataSize, data, numComponents);
readBuffer(bufferViewIndex, byteOffset, dataSize, data, numComponents, componentSize);
}
return data;
return new SkinBuffers(data, componentSize);
}
}
}

@ -10,20 +10,21 @@ import com.jme3.scene.Mesh;
import com.jme3.scene.Spatial;
import com.jme3.scene.VertexBuffer;
import com.jme3.texture.Texture;
import com.jme3.util.LittleEndien;
import com.jme3.util.*;
import java.io.*;
import java.nio.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Created by Nehon on 07/08/2017.
*/
public class GltfUtils {
private static final Logger logger = Logger.getLogger(GltfUtils.class.getName());
public static Mesh.Mode getMeshMode(Integer mode) {
if (mode == null) {
return Mesh.Mode.Triangles;
@ -120,11 +121,17 @@ public class GltfUtils {
case "WEIGHTS_0":
return VertexBuffer.Type.BoneWeight;
default:
throw new AssetLoadException("Unsupported buffer attribute: " + attribute);
logger.log(Level.WARNING, "Unsupported Vertex Buffer type " + attribute);
return null;
}
}
public static int getIndex(String name) {
String num = name.substring(name.lastIndexOf("_") + 1);
return Integer.parseInt(num);
}
public static Texture.MagFilter getMagFilter(Integer value) {
if (value == null) {
return null;
@ -201,7 +208,12 @@ public class GltfUtils {
}
buffer.rewind();
}
if (store instanceof float[]) {
if (store instanceof short[]) {
short[] array = (short[]) store;
for (int i = 0; i < array.length; i++) {
array[i] = 0;
}
} else if (store instanceof float[]) {
float[] array = (float[]) store;
for (int i = 0; i < array.length; i++) {
array[i] = 0;
@ -224,41 +236,43 @@ public class GltfUtils {
}
}
public static void populateBuffer(Object store, byte[] source, int length, int byteOffset, int byteStride, int numComponents) throws IOException {
public static void populateBuffer(Object store, byte[] source, int length, int byteOffset, int byteStride, int numComponents, int componentSize) throws IOException {
if (store instanceof Buffer) {
Buffer buffer = (Buffer) store;
buffer.clear();
if (buffer instanceof ByteBuffer) {
populateByteBuffer((ByteBuffer) buffer, source, length, byteOffset, byteStride, numComponents);
populateByteBuffer((ByteBuffer) buffer, source, length, byteOffset, byteStride, numComponents, componentSize);
return;
}
LittleEndien stream = getStream(source);
if (buffer instanceof ShortBuffer) {
populateShortBuffer((ShortBuffer) buffer, stream, length, byteOffset, byteStride, numComponents);
populateShortBuffer((ShortBuffer) buffer, stream, length, byteOffset, byteStride, numComponents, componentSize);
} else if (buffer instanceof IntBuffer) {
populateIntBuffer((IntBuffer) buffer, stream, length, byteOffset, byteStride, numComponents);
populateIntBuffer((IntBuffer) buffer, stream, length, byteOffset, byteStride, numComponents, componentSize);
} else if (buffer instanceof FloatBuffer) {
populateFloatBuffer((FloatBuffer) buffer, stream, length, byteOffset, byteStride, numComponents);
populateFloatBuffer((FloatBuffer) buffer, stream, length, byteOffset, byteStride, numComponents, componentSize);
}
buffer.rewind();
return;
}
LittleEndien stream = getStream(source);
if (store instanceof short[]) {
populateShortArray((short[]) store, stream, length, byteOffset, byteStride, numComponents, componentSize);
} else
if (store instanceof float[]) {
populateFloatArray((float[]) store, stream, length, byteOffset, byteStride, numComponents);
populateFloatArray((float[]) store, stream, length, byteOffset, byteStride, numComponents, componentSize);
} else if (store instanceof Vector3f[]) {
populateVector3fArray((Vector3f[]) store, stream, length, byteOffset, byteStride, numComponents);
populateVector3fArray((Vector3f[]) store, stream, length, byteOffset, byteStride, numComponents, componentSize);
} else if (store instanceof Quaternion[]) {
populateQuaternionArray((Quaternion[]) store, stream, length, byteOffset, byteStride, numComponents);
populateQuaternionArray((Quaternion[]) store, stream, length, byteOffset, byteStride, numComponents, componentSize);
} else if (store instanceof Matrix4f[]) {
populateMatrix4fArray((Matrix4f[]) store, stream, length, byteOffset, byteStride, numComponents);
populateMatrix4fArray((Matrix4f[]) store, stream, length, byteOffset, byteStride, numComponents, componentSize);
}
}
private static void populateByteBuffer(ByteBuffer buffer, byte[] source, int length, int byteOffset, int byteStride, int numComponents) {
private static void populateByteBuffer(ByteBuffer buffer, byte[] source, int length, int byteOffset, int byteStride, int numComponents, int componentSize) {
int index = byteOffset;
int componentSize = 1;
while (index < length + byteOffset) {
for (int i = 0; i < numComponents; i++) {
buffer.put(source[index + i]);
@ -267,9 +281,8 @@ public class GltfUtils {
}
}
private static void populateShortBuffer(ShortBuffer buffer, LittleEndien stream, int length, int byteOffset, int byteStride, int numComponents) throws IOException {
private static void populateShortBuffer(ShortBuffer buffer, LittleEndien stream, int length, int byteOffset, int byteStride, int numComponents, int componentSize) throws IOException {
int index = byteOffset;
int componentSize = 2;
int end = length * componentSize + byteOffset;
stream.skipBytes(byteOffset);
while (index < end) {
@ -280,9 +293,8 @@ public class GltfUtils {
}
}
private static void populateIntBuffer(IntBuffer buffer, LittleEndien stream, int length, int byteOffset, int byteStride, int numComponents) throws IOException {
private static void populateIntBuffer(IntBuffer buffer, LittleEndien stream, int length, int byteOffset, int byteStride, int numComponents, int componentSize) throws IOException {
int index = byteOffset;
int componentSize = 4;
int end = length * componentSize + byteOffset;
stream.skipBytes(byteOffset);
while (index < end) {
@ -293,9 +305,8 @@ public class GltfUtils {
}
}
private static void populateFloatBuffer(FloatBuffer buffer, LittleEndien stream, int length, int byteOffset, int byteStride, int numComponents) throws IOException {
private static void populateFloatBuffer(FloatBuffer buffer, LittleEndien stream, int length, int byteOffset, int byteStride, int numComponents, int componentSize) throws IOException {
int index = byteOffset;
int componentSize = 4;
int end = length * componentSize + byteOffset;
stream.skipBytes(byteOffset);
while (index < end) {
@ -306,9 +317,94 @@ public class GltfUtils {
}
}
private static void populateFloatArray(float[] array, LittleEndien stream, int length, int byteOffset, int byteStride, int numComponents) throws IOException {
private static void populateShortArray(short[] array, LittleEndien stream, int length, int byteOffset, int byteStride, int numComponents, int componentSize) throws IOException {
int index = byteOffset;
int end = length * componentSize + byteOffset;
stream.skipBytes(byteOffset);
int arrayIndex = 0;
while (index < end) {
for (int i = 0; i < numComponents; i++) {
if (componentSize == 2) {
array[arrayIndex] = stream.readShort();
} else {
array[arrayIndex] = stream.readByte();
}
arrayIndex++;
}
index += Math.max(componentSize * numComponents, byteStride);
}
}
public static byte[] toByteArray(short[] shortArray) {
byte[] bytes = new byte[shortArray.length];
for (int i = 0; i < shortArray.length; i++) {
bytes[i] = (byte) shortArray[i];
}
return bytes;
}
public static void handleSkinningBuffers(Mesh mesh, IntMap<GltfLoader.SkinBuffers> skinBuffers) {
if (skinBuffers.size() > 0) {
if (skinBuffers.size() == 1) {
GltfLoader.SkinBuffers buffs = skinBuffers.get(0);
setSkinBuffers(mesh, buffs.joints, skinBuffers.get(0).weights, buffs.componentSize);
} else {
int length = skinBuffers.get(0).joints.length;
short[] jointsArray = new short[length];
float[] weightsArray = new float[length];
List<GltfLoader.WeightData> weightData = new ArrayList<>();
int componentSize = 1;
for (int i = 0; i < weightsArray.length; i += 4) {
weightData.clear();
for (int j = 0; j < skinBuffers.size(); j++) {
GltfLoader.SkinBuffers buffs = skinBuffers.get(j);
for (int k = 0; k < 4; k++) {
weightData.add(new GltfLoader.WeightData(buffs.weights[i + k], buffs.joints[i + k], buffs.componentSize));
}
}
Collections.sort(weightData, new Comparator<GltfLoader.WeightData>() {
@Override
public int compare(GltfLoader.WeightData o1, GltfLoader.WeightData o2) {
if (o1.value > o2.value) {
return -1;
} else if (o1.value < o2.value) {
return 1;
} else {
return 0;
}
}
});
for (int j = 0; j < 4; j++) {
GltfLoader.WeightData data = weightData.get(j);
jointsArray[i + j] = data.index;
weightsArray[i + j] = data.value;
if (data.componentSize > componentSize) {
componentSize = data.componentSize;
}
}
}
setSkinBuffers(mesh, jointsArray, weightsArray, componentSize);
}
}
}
public static void setSkinBuffers(Mesh mesh, short[] jointsArray, float[] weightsArray, int componentSize) {
if (componentSize == 1) {
mesh.setBuffer(VertexBuffer.Type.BoneIndex, 4, BufferUtils.createByteBuffer(toByteArray(jointsArray)));
} else {
mesh.setBuffer(VertexBuffer.Type.BoneIndex, 4, BufferUtils.createShortBuffer(jointsArray));
}
mesh.setBuffer(VertexBuffer.Type.BoneWeight, 4, BufferUtils.createFloatBuffer(weightsArray));
}
private static void populateFloatArray(float[] array, LittleEndien stream, int length, int byteOffset, int byteStride, int numComponents, int componentSize) throws IOException {
int index = byteOffset;
int componentSize = 4;
int end = length * componentSize + byteOffset;
stream.skipBytes(byteOffset);
int arrayIndex = 0;
@ -322,9 +418,8 @@ public class GltfUtils {
}
}
private static void populateVector3fArray(Vector3f[] array, LittleEndien stream, int length, int byteOffset, int byteStride, int numComponents) throws IOException {
private static void populateVector3fArray(Vector3f[] array, LittleEndien stream, int length, int byteOffset, int byteStride, int numComponents, int componentSize) throws IOException {
int index = byteOffset;
int componentSize = 4;
int end = length * componentSize + byteOffset;
stream.skipBytes(byteOffset);
int arrayIndex = 0;
@ -341,9 +436,8 @@ public class GltfUtils {
}
}
private static void populateQuaternionArray(Quaternion[] array, LittleEndien stream, int length, int byteOffset, int byteStride, int numComponents) throws IOException {
private static void populateQuaternionArray(Quaternion[] array, LittleEndien stream, int length, int byteOffset, int byteStride, int numComponents, int componentSize) throws IOException {
int index = byteOffset;
int componentSize = 4;
int end = length * componentSize + byteOffset;
stream.skipBytes(byteOffset);
int arrayIndex = 0;
@ -361,9 +455,8 @@ public class GltfUtils {
}
}
private static void populateMatrix4fArray(Matrix4f[] array, LittleEndien stream, int length, int byteOffset, int byteStride, int numComponents) throws IOException {
private static void populateMatrix4fArray(Matrix4f[] array, LittleEndien stream, int length, int byteOffset, int byteStride, int numComponents, int componentSize) throws IOException {
int index = byteOffset;
int componentSize = 4;
int end = length * componentSize + byteOffset;
stream.skipBytes(byteOffset);
int arrayIndex = 0;

Loading…
Cancel
Save