|
@@ -2,9 +2,7 @@ package com.jme3.scene.plugins.gltf;
|
|
|
|
|
|
import com.google.gson.*;
|
|
|
import com.google.gson.stream.JsonReader;
|
|
|
-import com.jme3.animation.AnimControl;
|
|
|
-import com.jme3.animation.Animation;
|
|
|
-import com.jme3.animation.SpatialTrack;
|
|
|
+import com.jme3.animation.*;
|
|
|
import com.jme3.asset.*;
|
|
|
import com.jme3.material.Material;
|
|
|
import com.jme3.material.RenderState;
|
|
@@ -17,7 +15,9 @@ import com.jme3.util.mikktspace.MikktspaceTangentGenerator;
|
|
|
|
|
|
import java.io.*;
|
|
|
import java.nio.Buffer;
|
|
|
+import java.util.ArrayList;
|
|
|
import java.util.HashMap;
|
|
|
+import java.util.List;
|
|
|
import java.util.Map;
|
|
|
import java.util.logging.Level;
|
|
|
import java.util.logging.Logger;
|
|
@@ -45,6 +45,7 @@ public class GltfLoader implements AssetLoader {
|
|
|
private JsonArray images;
|
|
|
private JsonArray samplers;
|
|
|
private JsonArray animations;
|
|
|
+ private JsonArray skins;
|
|
|
|
|
|
private Material defaultMat;
|
|
|
private AssetInfo info;
|
|
@@ -52,9 +53,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;
|
|
|
|
|
|
+ Map<Skeleton, List<Spatial>> skinnedSpatials = new HashMap<>();
|
|
|
+
|
|
|
static {
|
|
|
defaultMaterialAdapters.put("pbrMetallicRoughness", new PBRMaterialAdapter());
|
|
|
}
|
|
@@ -94,10 +98,16 @@ public class GltfLoader implements AssetLoader {
|
|
|
images = root.getAsJsonArray("images");
|
|
|
samplers = root.getAsJsonArray("samplers");
|
|
|
animations = root.getAsJsonArray("animations");
|
|
|
+ skins = root.getAsJsonArray("skins");
|
|
|
+
|
|
|
+ readSkins();
|
|
|
|
|
|
JsonPrimitive defaultScene = root.getAsJsonPrimitive("scene");
|
|
|
|
|
|
Node n = loadScenes(defaultScene);
|
|
|
+
|
|
|
+ setupControls();
|
|
|
+
|
|
|
//only one scene let's not return the root.
|
|
|
if (n.getChildren().size() == 1) {
|
|
|
n = (Node) n.getChild(0);
|
|
@@ -131,7 +141,7 @@ public class GltfLoader implements AssetLoader {
|
|
|
sceneNode.setName(getAsString(scene.getAsJsonObject(), "name"));
|
|
|
JsonArray sceneNodes = scene.getAsJsonObject().getAsJsonArray("nodes");
|
|
|
for (JsonElement node : sceneNodes) {
|
|
|
- sceneNode.attachChild(loadNode(node.getAsInt()));
|
|
|
+ loadChild(sceneNode, node);
|
|
|
}
|
|
|
root.attachChild(sceneNode);
|
|
|
}
|
|
@@ -152,13 +162,19 @@ public class GltfLoader implements AssetLoader {
|
|
|
return root;
|
|
|
}
|
|
|
|
|
|
- private Spatial loadNode(int nodeIndex) throws IOException {
|
|
|
- Spatial spatial = fetchFromCache("nodes", nodeIndex, Spatial.class);
|
|
|
- if (spatial != null) {
|
|
|
- //If a spatial is referenced several times, it may be attached to different parents,
|
|
|
- // and it's not possible in JME, so we have to clone it.
|
|
|
- return spatial.clone();
|
|
|
+ private Object loadNode(int nodeIndex) throws IOException {
|
|
|
+ Object obj = fetchFromCache("nodes", nodeIndex, Object.class);
|
|
|
+ if (obj != null) {
|
|
|
+ if (obj instanceof Bone) {
|
|
|
+ //the node can be a previously loaded bone let's return it
|
|
|
+ return obj;
|
|
|
+ } else {
|
|
|
+ //If a spatial is referenced several times, it may be attached to different parents,
|
|
|
+ // and it's not possible in JME, so we have to clone it.
|
|
|
+ return ((Spatial) obj).clone();
|
|
|
+ }
|
|
|
}
|
|
|
+ Spatial spatial;
|
|
|
JsonObject nodeData = nodes.get(nodeIndex).getAsJsonObject();
|
|
|
JsonArray children = nodeData.getAsJsonArray("children");
|
|
|
Integer meshIndex = getAsInteger(nodeData, "mesh");
|
|
@@ -189,9 +205,16 @@ public class GltfLoader implements AssetLoader {
|
|
|
spatial = node;
|
|
|
}
|
|
|
|
|
|
+ Integer skinIndex = getAsInteger(nodeData, "skin");
|
|
|
+ if (skinIndex != null) {
|
|
|
+ Skeleton skeleton = fetchFromCache("skins", skinIndex, Skeleton.class);
|
|
|
+ List<Spatial> spatials = skinnedSpatials.get(skeleton);
|
|
|
+ spatials.add(spatial);
|
|
|
+ }
|
|
|
+
|
|
|
if (children != null) {
|
|
|
for (JsonElement child : children) {
|
|
|
- ((Node) spatial).attachChild(loadNode(child.getAsInt()));
|
|
|
+ loadChild(spatial, child);
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -204,6 +227,19 @@ public class GltfLoader implements AssetLoader {
|
|
|
return spatial;
|
|
|
}
|
|
|
|
|
|
+ private void loadChild(Spatial parent, JsonElement child) throws IOException {
|
|
|
+ int index = child.getAsInt();
|
|
|
+ Object loaded = loadNode(child.getAsInt());
|
|
|
+ if (loaded instanceof Spatial) {
|
|
|
+ ((Node) parent).attachChild((Spatial) loaded);
|
|
|
+ } else if (loaded instanceof Bone) {
|
|
|
+ //fetch the skeleton and add a skeletonControl to the node.
|
|
|
+ // Skeleton skeleton = fetchFromCache("skeletons", index, Skeleton.class);
|
|
|
+ // SkeletonControl control = new SkeletonControl(skeleton);
|
|
|
+ // parent.addControl(control);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private Transform loadTransforms(JsonObject nodeData) {
|
|
|
Transform transform = new Transform();
|
|
|
JsonArray matrix = nodeData.getAsJsonArray("matrix");
|
|
@@ -276,6 +312,21 @@ public class GltfLoader implements AssetLoader {
|
|
|
for (Map.Entry<String, JsonElement> entry : attributes.entrySet()) {
|
|
|
mesh.setBuffer(loadAccessorData(entry.getValue().getAsInt(), new VertexBufferPopulator(getVertexBufferType(entry.getKey()))));
|
|
|
}
|
|
|
+
|
|
|
+ if (mesh.getBuffer(VertexBuffer.Type.BoneIndex) != null) {
|
|
|
+ //the mesh has some skinning let's create needed buffers for HW skinning
|
|
|
+ //creating empty buffers for HW skinning
|
|
|
+ //the buffers will be setup if ever used.
|
|
|
+ VertexBuffer weightsHW = new VertexBuffer(VertexBuffer.Type.HWBoneWeight);
|
|
|
+ VertexBuffer indicesHW = new VertexBuffer(VertexBuffer.Type.HWBoneIndex);
|
|
|
+ //setting usage to cpuOnly so that the buffer is not send empty to the GPU
|
|
|
+ indicesHW.setUsage(VertexBuffer.Usage.CpuOnly);
|
|
|
+ weightsHW.setUsage(VertexBuffer.Usage.CpuOnly);
|
|
|
+ mesh.setBuffer(weightsHW);
|
|
|
+ mesh.setBuffer(indicesHW);
|
|
|
+ mesh.generateBindPose();
|
|
|
+ }
|
|
|
+
|
|
|
Geometry geom = new Geometry(null, mesh);
|
|
|
|
|
|
Integer materialIndex = getAsInteger(meshObject, "material");
|
|
@@ -302,6 +353,7 @@ public class GltfLoader implements AssetLoader {
|
|
|
geomArray[index] = geom;
|
|
|
index++;
|
|
|
|
|
|
+ //TODO skins
|
|
|
//TODO targets(morph anim...)
|
|
|
}
|
|
|
|
|
@@ -318,12 +370,14 @@ public class GltfLoader implements AssetLoader {
|
|
|
int byteOffset = getAsInteger(accessor, "byteOffset", 0);
|
|
|
Integer componentType = getAsInteger(accessor, "componentType");
|
|
|
assertNotNull(componentType, "No component type defined for accessor " + accessorIndex);
|
|
|
- boolean normalized = getAsBoolean(accessor, "normalized", false);
|
|
|
Integer count = getAsInteger(accessor, "count");
|
|
|
assertNotNull(count, "No count attribute defined for accessor " + accessorIndex);
|
|
|
String type = getAsString(accessor, "type");
|
|
|
assertNotNull(type, "No type attribute defined for accessor " + accessorIndex);
|
|
|
|
|
|
+ boolean normalized = getAsBoolean(accessor, "normalized", false);
|
|
|
+ //Some float data can be packed into short buffers, "normalized" means they have to be unpacked.
|
|
|
+ //TODO support packed data
|
|
|
//TODO min / max
|
|
|
//TODO sparse
|
|
|
//TODO extensions?
|
|
@@ -578,8 +632,10 @@ public class GltfLoader implements AssetLoader {
|
|
|
control.addAnim(anim);
|
|
|
|
|
|
} else {
|
|
|
- //At some pont we'll have bone animation
|
|
|
+ //At some point we'll have bone animation
|
|
|
//TODO support for bone animation.
|
|
|
+ System.err.println("animated");
|
|
|
+ System.err.println(node);
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -608,6 +664,100 @@ public class GltfLoader implements AssetLoader {
|
|
|
texture.setWrap(Texture.WrapAxis.T, wrapT);
|
|
|
}
|
|
|
|
|
|
+ private void readSkins() throws IOException {
|
|
|
+ if (skins == null) {
|
|
|
+ //no skins, no bone animation.
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ for (int index = 0; index < skins.size(); index++) {
|
|
|
+ JsonObject skin = skins.get(index).getAsJsonObject();
|
|
|
+
|
|
|
+ //each skin is a skeleton.
|
|
|
+ Integer rootIndex = getAsInteger(skin, "skeleton");
|
|
|
+ JsonArray joints = skin.getAsJsonArray("joints");
|
|
|
+ assertNotNull(joints, "No joints defined for skin");
|
|
|
+ Integer matricesIndex = getAsInteger(skin, "inverseBindMatrices");
|
|
|
+ Matrix4f[] inverseBindMatrices = null;
|
|
|
+ if (matricesIndex != null) {
|
|
|
+ inverseBindMatrices = loadAccessorData(matricesIndex, matrix4fArrayPopulator);
|
|
|
+ } else {
|
|
|
+ inverseBindMatrices = new Matrix4f[joints.size()];
|
|
|
+ for (int i = 0; i < inverseBindMatrices.length; i++) {
|
|
|
+ inverseBindMatrices[i] = new Matrix4f();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ System.err.println(inverseBindMatrices);
|
|
|
+
|
|
|
+ rootIndex = joints.get(0).getAsInt();
|
|
|
+
|
|
|
+ Bone[] bones = new Bone[joints.size()];
|
|
|
+ for (int i = 0; i < joints.size(); i++) {
|
|
|
+ bones[i] = loadNodeAsBone(joints.get(i).getAsInt(), inverseBindMatrices[i]);
|
|
|
+ }
|
|
|
+ for (int i = 0; i < joints.size(); i++) {
|
|
|
+ findChildren(joints.get(i).getAsInt());
|
|
|
+ }
|
|
|
+
|
|
|
+ Skeleton skeleton = new Skeleton(bones);
|
|
|
+ addToCache("skins", index, skeleton, nodes.size());
|
|
|
+ skinnedSpatials.put(skeleton, new ArrayList<Spatial>());
|
|
|
+
|
|
|
+ System.err.println(skeleton);
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private Bone loadNodeAsBone(int nodeIndex, Matrix4f inverseBindMatrix) throws IOException {
|
|
|
+
|
|
|
+ Bone bone = fetchFromCache("nodes", nodeIndex, Bone.class);
|
|
|
+ if (bone != null) {
|
|
|
+ return bone;
|
|
|
+ }
|
|
|
+ JsonObject nodeData = nodes.get(nodeIndex).getAsJsonObject();
|
|
|
+ JsonArray children = nodeData.getAsJsonArray("children");
|
|
|
+ String name = getAsString(nodeData, "name");
|
|
|
+ if (name == null) {
|
|
|
+ name = "Bone_" + nodeIndex;
|
|
|
+ }
|
|
|
+ bone = new Bone(name);
|
|
|
+ Transform boneTransforms = loadTransforms(nodeData);
|
|
|
+ Transform inverseBind = new Transform();
|
|
|
+ inverseBind.fromTransformMatrix(inverseBindMatrix);
|
|
|
+ // boneTransforms.combineWithParent(inverseBind);
|
|
|
+ bone.setBindTransforms(boneTransforms.getTranslation(), boneTransforms.getRotation(), boneTransforms.getScale());
|
|
|
+
|
|
|
+
|
|
|
+ addToCache("nodes", nodeIndex, bone, nodes.size());
|
|
|
+ return bone;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void findChildren(int nodeIndex) {
|
|
|
+ Bone bone = fetchFromCache("nodes", nodeIndex, Bone.class);
|
|
|
+ JsonObject nodeData = nodes.get(nodeIndex).getAsJsonObject();
|
|
|
+ JsonArray children = nodeData.getAsJsonArray("children");
|
|
|
+ if (children != null) {
|
|
|
+ for (JsonElement child : children) {
|
|
|
+ bone.addChild(fetchFromCache("nodes", child.getAsInt(), Bone.class));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void setupControls() {
|
|
|
+ for (Skeleton skeleton : skinnedSpatials.keySet()) {
|
|
|
+ List<Spatial> spatials = skinnedSpatials.get(skeleton);
|
|
|
+ Spatial spatial = null;
|
|
|
+ if (spatials.size() >= 1) {
|
|
|
+ spatial = findCommonAncestor(spatials);
|
|
|
+ } else {
|
|
|
+ spatial = spatials.get(0);
|
|
|
+ }
|
|
|
+ SkeletonControl control = new SkeletonControl(skeleton);
|
|
|
+ spatial.addControl(control);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private String loadMeshName(int meshIndex) {
|
|
|
JsonObject meshData = meshes.get(meshIndex).getAsJsonObject();
|
|
|
return getAsString(meshData, "name");
|
|
@@ -737,5 +887,25 @@ public class GltfLoader implements AssetLoader {
|
|
|
return data;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ private class Matrix4fArrayPopulator implements Populator<Matrix4f[]> {
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Matrix4f[] populate(Integer bufferViewIndex, int componentType, String type, int count, int byteOffset) throws IOException {
|
|
|
+
|
|
|
+ int numComponents = getNumberOfComponents(type);
|
|
|
+ int dataSize = numComponents * count;
|
|
|
+ Matrix4f[] data = new Matrix4f[count];
|
|
|
+
|
|
|
+ if (bufferViewIndex == null) {
|
|
|
+ //no referenced buffer, specs says to pad the data with zeros.
|
|
|
+ padBuffer(data, dataSize);
|
|
|
+ } else {
|
|
|
+ readBuffer(bufferViewIndex, byteOffset, dataSize, data, numComponents);
|
|
|
+ }
|
|
|
+
|
|
|
+ return data;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|