Implemented bone animation. This is still in WIP state, currently working for the most simple cases.
This commit is contained in:
parent
d7b2e08d95
commit
ea6c406979
@ -186,27 +186,24 @@ public final class Transform implements Savable, Cloneable, java.io.Serializable
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the values of this matrix acording to it's parent. Very similar to the concept of Node/Spatial transforms.
|
||||
* Changes the values of this matrix according to it's parent. Very similar to the concept of Node/Spatial transforms.
|
||||
* @param parent The parent matrix.
|
||||
* @return This matrix, after combining.
|
||||
*/
|
||||
public Transform combineWithParent(Transform parent) {
|
||||
//applying parent scale to local scale
|
||||
scale.multLocal(parent.scale);
|
||||
// rot.multLocal(parent.rot);
|
||||
//applying parent rotation to local rotation.
|
||||
parent.rot.mult(rot, rot);
|
||||
|
||||
// This here, is evil code
|
||||
// parent
|
||||
// .rot
|
||||
// .multLocal(translation)
|
||||
// .multLocal(parent.scale)
|
||||
// .addLocal(parent.translation);
|
||||
|
||||
//applying parent scale to local translation.
|
||||
translation.multLocal(parent.scale);
|
||||
//applying parent rotation to local translation, then applying parent translation to local translation.
|
||||
//Note that parent.rot.multLocal(translation) doesn't modify "parent.rot" but "translation"
|
||||
parent
|
||||
.rot
|
||||
.multLocal(translation)
|
||||
.addLocal(parent.translation);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,7 @@ import java.util.Map;
|
||||
|
||||
import com.jme3.animation.Bone;
|
||||
import com.jme3.animation.Skeleton;
|
||||
import com.jme3.bounding.*;
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
@ -94,6 +95,32 @@ public class SkeletonBone extends Node {
|
||||
for (Bone bone : skeleton.getRoots()) {
|
||||
createSkeletonGeoms(bone, boneShape, jointShape, boneLengths, skeleton, this, guessBonesOrientation);
|
||||
}
|
||||
this.updateModelBound();
|
||||
|
||||
|
||||
Sphere originShape = new Sphere(10, 10, 0.02f);
|
||||
originShape.setBuffer(VertexBuffer.Type.Color, 4, createFloatBuffer(originShape.getVertexCount() * 4));
|
||||
cb = originShape.getFloatBuffer(VertexBuffer.Type.Color);
|
||||
cb.rewind();
|
||||
for (int i = 0; i < jointShape.getVertexCount(); i++) {
|
||||
cb.put(0.4f).put(0.4f).put(0.05f).put(1f);
|
||||
}
|
||||
|
||||
Geometry origin = new Geometry("origin", originShape);
|
||||
BoundingVolume bv = this.getWorldBound();
|
||||
float scale = 1;
|
||||
if (bv.getType() == BoundingVolume.Type.AABB) {
|
||||
BoundingBox bb = (BoundingBox) bv;
|
||||
scale = (bb.getXExtent() + bb.getYExtent() + bb.getZExtent()) / 3f;
|
||||
} else if (bv.getType() == BoundingVolume.Type.Sphere) {
|
||||
BoundingSphere bs = (BoundingSphere) bv;
|
||||
scale = bs.getRadius();
|
||||
}
|
||||
origin.scale(scale);
|
||||
attachChild(origin);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
protected final void createSkeletonGeoms(Bone bone, Mesh boneShape, Mesh jointShape, Map<Integer, Float> boneLengths, Skeleton skeleton, Node parent, boolean guessBonesOrientation) {
|
||||
@ -120,6 +147,7 @@ public class SkeletonBone extends Node {
|
||||
Quaternion q = new Quaternion();
|
||||
q.lookAt(v, Vector3f.UNIT_Z);
|
||||
bGeom.setLocalRotation(q);
|
||||
boneLength = v.length();
|
||||
}
|
||||
//no child, the bone has the same direction as the parent bone.
|
||||
if (bone.getChildren().isEmpty()) {
|
||||
@ -184,12 +212,6 @@ public class SkeletonBone extends Node {
|
||||
}
|
||||
|
||||
|
||||
// private Quaternion getRotationBetweenVect(Vector3f v1, Vector3f v2){
|
||||
// Vector3f a =v1.cross(v2);
|
||||
// float w = FastMath.sqrt((v1.length() * v1.length()) * (v2.length() * v2.length())) + v1.dot(v2);
|
||||
// return new Quaternion(a.x, a.y, a.z, w).normalizeLocal() ;
|
||||
// }
|
||||
|
||||
protected final void updateSkeletonGeoms(Bone bone) {
|
||||
if (guessBonesOrientation && bone.getName().equalsIgnoreCase("Site")) {
|
||||
return;
|
||||
@ -205,7 +227,7 @@ public class SkeletonBone extends Node {
|
||||
}
|
||||
|
||||
/**
|
||||
* The method updates the geometry according to the poitions of the bones.
|
||||
* The method updates the geometry according to the positions of the bones.
|
||||
*/
|
||||
public void updateGeometry() {
|
||||
|
||||
|
@ -60,6 +60,11 @@ public class SkeletonDebugAppState extends AbstractAppState {
|
||||
public SkeletonDebugger addSkeleton(SkeletonControl skeletonControl, boolean guessBonesOrientation) {
|
||||
Skeleton skeleton = skeletonControl.getSkeleton();
|
||||
Spatial forSpatial = skeletonControl.getSpatial();
|
||||
return addSkeleton(skeleton, forSpatial, guessBonesOrientation);
|
||||
}
|
||||
|
||||
public SkeletonDebugger addSkeleton(Skeleton skeleton, Spatial forSpatial, boolean guessBonesOrientation) {
|
||||
|
||||
SkeletonDebugger sd = new SkeletonDebugger(forSpatial.getName() + "_Skeleton", skeleton, guessBonesOrientation);
|
||||
sd.setLocalTransform(forSpatial.getWorldTransform());
|
||||
if (forSpatial instanceof Node) {
|
||||
@ -113,6 +118,7 @@ public class SkeletonDebugAppState extends AbstractAppState {
|
||||
selectedBones.put(skeleton.getSkeleton(), selectedBone);
|
||||
System.err.println("-----------------------");
|
||||
System.err.println("Selected Bone : " + selectedBone.getName() + " in skeleton " + skeleton.getName());
|
||||
System.err.println("Root Bone : " + (selectedBone.getParent() == null));
|
||||
System.err.println("-----------------------");
|
||||
System.err.println("Bind translation: " + selectedBone.getBindPosition());
|
||||
System.err.println("Bind rotation: " + selectedBone.getBindRotation());
|
||||
|
@ -31,48 +31,257 @@
|
||||
*/
|
||||
package jme3test.model;
|
||||
|
||||
import com.jme3.animation.*;
|
||||
import com.jme3.app.ChaseCameraAppState;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.light.DirectionalLight;
|
||||
import com.jme3.light.PointLight;
|
||||
import com.jme3.input.KeyInput;
|
||||
import com.jme3.input.controls.ActionListener;
|
||||
import com.jme3.input.controls.KeyTrigger;
|
||||
import com.jme3.math.*;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.renderer.Limits;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.plugins.gltf.GltfModelKey;
|
||||
import com.jme3.scene.shape.Sphere;
|
||||
import com.jme3.scene.control.Control;
|
||||
import com.jme3.scene.debug.custom.SkeletonDebugAppState;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class TestGltfLoading extends SimpleApplication {
|
||||
|
||||
Node autoRotate = new Node("autoRotate");
|
||||
List<Spatial> assets = new ArrayList<>();
|
||||
Node probeNode;
|
||||
float time = 0;
|
||||
int assetIndex = 0;
|
||||
boolean useAutoRotate = true;
|
||||
private final static String indentString = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
|
||||
int duration = 2;
|
||||
boolean playAnim = true;
|
||||
|
||||
public static void main(String[] args) {
|
||||
TestGltfLoading app = new TestGltfLoading();
|
||||
app.start();
|
||||
}
|
||||
|
||||
/*
|
||||
WARNING this test case can't wok without the assets, and considering their size, they are not pushed into the repo
|
||||
you can find them here :
|
||||
https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0
|
||||
https://sketchfab.com/features/gltf
|
||||
You have to copy them in Model/gltf folder in the test-data project.
|
||||
*/
|
||||
public void simpleInitApp() {
|
||||
flyCam.setMoveSpeed(10f);
|
||||
viewPort.setBackgroundColor(ColorRGBA.DarkGray);
|
||||
|
||||
// sunset light
|
||||
SkeletonDebugAppState skeletonDebugAppState = new SkeletonDebugAppState();
|
||||
getStateManager().attach(skeletonDebugAppState);
|
||||
|
||||
// cam.setLocation(new Vector3f(4.0339394f, 2.645184f, 6.4627485f));
|
||||
// cam.setRotation(new Quaternion(-0.013950467f, 0.98604023f, -0.119502485f, -0.11510504f));
|
||||
cam.setFrustumPerspective(45f, (float) cam.getWidth() / cam.getHeight(), 0.1f, 100f);
|
||||
renderer.setDefaultAnisotropicFilter(Math.min(renderer.getLimits().get(Limits.TextureAnisotropy), 8));
|
||||
setPauseOnLostFocus(false);
|
||||
|
||||
flyCam.setMoveSpeed(5);
|
||||
flyCam.setDragToRotate(true);
|
||||
flyCam.setEnabled(false);
|
||||
viewPort.setBackgroundColor(new ColorRGBA().setAsSrgb(0.2f, 0.2f, 0.2f, 1.0f));
|
||||
rootNode.attachChild(autoRotate);
|
||||
probeNode = (Node) assetManager.loadModel("Scenes/defaultProbe.j3o");
|
||||
autoRotate.attachChild(probeNode);
|
||||
|
||||
// DirectionalLight dl = new DirectionalLight();
|
||||
// dl.setDirection(new Vector3f(-1f, -1.0f, -1f).normalizeLocal());
|
||||
// dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f));
|
||||
// rootNode.addLight(dl);
|
||||
//
|
||||
|
||||
// DirectionalLight dl2 = new DirectionalLight();
|
||||
// dl2.setDirection(new Vector3f(1f, 1.0f, 1f).normalizeLocal());
|
||||
// dl2.setColor(new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f));
|
||||
// rootNode.addLight(dl2);
|
||||
|
||||
PointLight pl = new PointLight(new Vector3f(5.0f, 5.0f, 5.0f), ColorRGBA.White, 30);
|
||||
rootNode.addLight(pl);
|
||||
PointLight pl1 = new PointLight(new Vector3f(-5.0f, -5.0f, -5.0f), ColorRGBA.White.mult(0.5f), 50);
|
||||
rootNode.addLight(pl1);
|
||||
// PointLight pl = new PointLight(new Vector3f(5.0f, 5.0f, 5.0f), ColorRGBA.White, 30);
|
||||
// rootNode.addLight(pl);
|
||||
// PointLight pl1 = new PointLight(new Vector3f(-5.0f, -5.0f, -5.0f), ColorRGBA.White.mult(0.5f), 50);
|
||||
// rootNode.addLight(pl1);
|
||||
|
||||
rootNode.attachChild(assetManager.loadModel("Models/gltf/box/box.gltf"));
|
||||
//rootNode.attachChild(assetManager.loadModel(new GltfModelKey("Models/gltf/duck/Duck.gltf")));
|
||||
// loadModel("Models/gltf/box/box.gltf", Vector3f.ZERO, 1);
|
||||
// loadModel("Models/gltf/duck/Duck.gltf", new Vector3f(0, -1, 0), 1);
|
||||
//loadModel("Models/gltf/damagedHelmet/damagedHelmet.gltf", Vector3f.ZERO, 1);
|
||||
//loadModel("Models/gltf/hornet/scene.gltf", new Vector3f(0, -0.5f, 0), 0.4f);
|
||||
// loadModel("Models/gltf/adamHead/adamHead.gltf", Vector3f.ZERO, 0.6f);
|
||||
//loadModel("Models/gltf/busterDrone/busterDrone.gltf", new Vector3f(0, 0f, 0), 0.8f);
|
||||
//loadModel("Models/gltf/animatedCube/AnimatedCube.gltf", Vector3f.ZERO, 0.5f);
|
||||
|
||||
//rootNode.attachChild(assetManager.loadModel("Models/gltf/hornet/scene.gltf"));
|
||||
//TODO need to pad tracks that doesn't have the same length than the animation.
|
||||
//loadModel("Models/gltf/BoxAnimated/BoxAnimated.gltf", new Vector3f(0, 0f, 0), 0.8f);
|
||||
|
||||
//loadModel("Models/gltf/RiggedFigure/RiggedSimple.gltf", new Vector3f(0, -0.3f, 0), 0.2f);
|
||||
//loadModel("Models/gltf/RiggedFigure/RiggedFigure.gltf", new Vector3f(0, -1f, 0), 1f);
|
||||
//loadModel("Models/gltf/CesiumMan/CesiumMan.gltf", new Vector3f(0, -1, 0), 1f);
|
||||
//loadModel("Models/gltf/BrainStem/BrainStem.gltf", new Vector3f(0, -1, 0), 1f);
|
||||
//loadModel("Models/gltf/Jaime/Jaime.gltf", new Vector3f(0, -1, 0), 1f);
|
||||
//loadModel("Models/gltf/GiantWorm/GiantWorm.gltf", new Vector3f(0, -1, 0), 1f);
|
||||
//loadModel("Models/gltf/RiggedFigure/WalkingLady.gltf", new Vector3f(0, -0.f, 0), 1f);
|
||||
// loadModel("Models/gltf/Monster/Monster.gltf", Vector3f.ZERO, 0.03f);
|
||||
|
||||
|
||||
probeNode.attachChild(assets.get(0));
|
||||
|
||||
ChaseCameraAppState chaseCam = new ChaseCameraAppState();
|
||||
chaseCam.setTarget(probeNode);
|
||||
getStateManager().attach(chaseCam);
|
||||
chaseCam.setInvertHorizontalAxis(true);
|
||||
chaseCam.setInvertVerticalAxis(true);
|
||||
chaseCam.setZoomSpeed(0.5f);
|
||||
chaseCam.setMinVerticalRotation(-FastMath.HALF_PI);
|
||||
chaseCam.setRotationSpeed(3);
|
||||
chaseCam.setDefaultDistance(3);
|
||||
chaseCam.setDefaultVerticalRotation(0.3f);
|
||||
|
||||
inputManager.addMapping("autorotate", new KeyTrigger(KeyInput.KEY_SPACE));
|
||||
inputManager.addListener(new ActionListener() {
|
||||
@Override
|
||||
public void onAction(String name, boolean isPressed, float tpf) {
|
||||
if (isPressed) {
|
||||
useAutoRotate = !useAutoRotate;
|
||||
|
||||
}
|
||||
}
|
||||
}, "autorotate");
|
||||
|
||||
inputManager.addMapping("toggleAnim", new KeyTrigger(KeyInput.KEY_RETURN));
|
||||
inputManager.addListener(new ActionListener() {
|
||||
@Override
|
||||
public void onAction(String name, boolean isPressed, float tpf) {
|
||||
if (isPressed) {
|
||||
playAnim = !playAnim;
|
||||
if (playAnim) {
|
||||
playFirstAnim(rootNode);
|
||||
} else {
|
||||
stopAnim(rootNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, "toggleAnim");
|
||||
|
||||
dumpScene(rootNode, 0);
|
||||
}
|
||||
|
||||
private <T extends Control> T findControl(Spatial s, Class<T> controlClass) {
|
||||
T ctrl = s.getControl(controlClass);
|
||||
if (ctrl != null) {
|
||||
return ctrl;
|
||||
}
|
||||
if (s instanceof Node) {
|
||||
Node n = (Node) s;
|
||||
for (Spatial spatial : n.getChildren()) {
|
||||
ctrl = findControl(spatial, controlClass);
|
||||
if (ctrl != null) {
|
||||
return ctrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void loadModel(String path, Vector3f offset, float scale) {
|
||||
Spatial s = assetManager.loadModel(path);
|
||||
s.scale(scale);
|
||||
s.move(offset);
|
||||
assets.add(s);
|
||||
if (playAnim) {
|
||||
playFirstAnim(s);
|
||||
}
|
||||
|
||||
// SkeletonControl ctrl = findControl(s, SkeletonControl.class);
|
||||
// //ctrl.getSpatial().removeControl(ctrl);
|
||||
// if (ctrl == null) {
|
||||
// return;
|
||||
// }
|
||||
// getStateManager().getState(SkeletonDebugAppState.class).addSkeleton(ctrl, true);
|
||||
// AnimControl aCtrl = findControl(s, AnimControl.class);
|
||||
// //ctrl.getSpatial().removeControl(ctrl);
|
||||
// if (aCtrl == null) {
|
||||
// return;
|
||||
// }
|
||||
// if (aCtrl.getSkeleton() != null) {
|
||||
// getStateManager().getState(SkeletonDebugAppState.class).addSkeleton(aCtrl.getSkeleton(), aCtrl.getSpatial(), true);
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
private void playFirstAnim(Spatial s) {
|
||||
|
||||
AnimControl control = s.getControl(AnimControl.class);
|
||||
if (control != null) {
|
||||
// if (control.getAnimationNames().size() > 0) {
|
||||
// control.createChannel().setAnim(control.getAnimationNames().iterator().next());
|
||||
// }
|
||||
for (String name : control.getAnimationNames()) {
|
||||
control.createChannel().setAnim(name);
|
||||
}
|
||||
|
||||
}
|
||||
if (s instanceof Node) {
|
||||
Node n = (Node) s;
|
||||
for (Spatial spatial : n.getChildren()) {
|
||||
playFirstAnim(spatial);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void stopAnim(Spatial s) {
|
||||
|
||||
AnimControl control = s.getControl(AnimControl.class);
|
||||
if (control != null) {
|
||||
for (int i = 0; i < control.getNumChannels(); i++) {
|
||||
AnimChannel ch = control.getChannel(i);
|
||||
ch.reset(true);
|
||||
}
|
||||
control.clearChannels();
|
||||
if (control.getSkeleton() != null) {
|
||||
control.getSkeleton().reset();
|
||||
}
|
||||
|
||||
}
|
||||
if (s instanceof Node) {
|
||||
Node n = (Node) s;
|
||||
for (Spatial spatial : n.getChildren()) {
|
||||
stopAnim(spatial);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void simpleUpdate(float tpf) {
|
||||
|
||||
if (!useAutoRotate) {
|
||||
return;
|
||||
}
|
||||
time += tpf;
|
||||
autoRotate.rotate(0, tpf * 0.5f, 0);
|
||||
if (time > duration) {
|
||||
assets.get(assetIndex).removeFromParent();
|
||||
assetIndex = (assetIndex + 1) % assets.size();
|
||||
if (assetIndex == 0) {
|
||||
duration = 10;
|
||||
}
|
||||
probeNode.attachChild(assets.get(assetIndex));
|
||||
time = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void dumpScene(Spatial s, int indent) {
|
||||
System.err.println(indentString.substring(0, indent) + s.getName() + " (" + s.getClass().getSimpleName() + ") / " +
|
||||
s.getLocalTransform().getTranslation().toString() + ", " +
|
||||
s.getLocalTransform().getRotation().toString() + ", " +
|
||||
s.getLocalTransform().getScale().toString());
|
||||
if (s instanceof Node) {
|
||||
Node n = (Node) s;
|
||||
for (Spatial spatial : n.getChildren()) {
|
||||
dumpScene(spatial, indent + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,10 +15,7 @@ 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.*;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@ -56,8 +53,9 @@ public class GltfLoader implements AssetLoader {
|
||||
private Matrix4fArrayPopulator matrix4fArrayPopulator = new Matrix4fArrayPopulator();
|
||||
private static Map<String, MaterialAdapter> defaultMaterialAdapters = new HashMap<>();
|
||||
private boolean useNormalsFlag = false;
|
||||
private Transform tmpTransforms = new Transform();
|
||||
|
||||
Map<Skeleton, List<Spatial>> skinnedSpatials = new HashMap<>();
|
||||
Map<SkinData, List<Spatial>> skinnedSpatials = new HashMap<>();
|
||||
|
||||
static {
|
||||
defaultMaterialAdapters.put("pbrMetallicRoughness", new PBRMaterialAdapter());
|
||||
@ -146,6 +144,13 @@ public class GltfLoader implements AssetLoader {
|
||||
root.attachChild(sceneNode);
|
||||
}
|
||||
|
||||
//update skeletons
|
||||
for (int i = 0; i < skins.size(); i++) {
|
||||
SkinData sd = fetchFromCache("skins", i, SkinData.class);
|
||||
sd.skeletonControl.getSkeleton().resetAndUpdate();
|
||||
sd.skeletonControl.getSkeleton().setBindingPose();
|
||||
}
|
||||
|
||||
//Loading animations
|
||||
if (animations != null) {
|
||||
for (int i = 0; i < animations.size(); i++) {
|
||||
@ -165,7 +170,7 @@ public class GltfLoader implements AssetLoader {
|
||||
private Object readNode(int nodeIndex) throws IOException {
|
||||
Object obj = fetchFromCache("nodes", nodeIndex, Object.class);
|
||||
if (obj != null) {
|
||||
if (obj instanceof Bone) {
|
||||
if (obj instanceof BoneWrapper) {
|
||||
//the node can be a previously loaded bone let's return it
|
||||
return obj;
|
||||
} else {
|
||||
@ -207,11 +212,13 @@ public class GltfLoader implements AssetLoader {
|
||||
|
||||
Integer skinIndex = getAsInteger(nodeData, "skin");
|
||||
if (skinIndex != null) {
|
||||
Skeleton skeleton = fetchFromCache("skins", skinIndex, Skeleton.class);
|
||||
List<Spatial> spatials = skinnedSpatials.get(skeleton);
|
||||
SkinData skinData = fetchFromCache("skins", skinIndex, SkinData.class);
|
||||
List<Spatial> spatials = skinnedSpatials.get(skinData);
|
||||
spatials.add(spatial);
|
||||
}
|
||||
|
||||
spatial.setLocalTransform(readTransforms(nodeData));
|
||||
|
||||
if (children != null) {
|
||||
for (JsonElement child : children) {
|
||||
readChild(spatial, child);
|
||||
@ -221,7 +228,6 @@ public class GltfLoader implements AssetLoader {
|
||||
if (spatial.getName() == null) {
|
||||
spatial.setName(getAsString(nodeData.getAsJsonObject(), "name"));
|
||||
}
|
||||
spatial.setLocalTransform(readTransforms(nodeData));
|
||||
|
||||
addToCache("nodes", nodeIndex, spatial, nodes.size());
|
||||
return spatial;
|
||||
@ -232,14 +238,34 @@ public class GltfLoader implements AssetLoader {
|
||||
Object loaded = readNode(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);
|
||||
} else if (loaded instanceof BoneWrapper) {
|
||||
//parent is the Armature Node, we have to apply its transforms to all the Bones' bind pose
|
||||
BoneWrapper bw = (BoneWrapper) loaded;
|
||||
//TODO this part is still not woking properly.
|
||||
applyTransformsToArmature(bw, parent.getLocalTransform());
|
||||
|
||||
//now we can remove the parent node as it's not meant as a real node.
|
||||
parent.removeFromParent();
|
||||
}
|
||||
}
|
||||
|
||||
private void applyTransformsToArmature(BoneWrapper boneWrapper, Transform transforms) {
|
||||
//Transforms are mean in model space, so we need some tricky transformation
|
||||
//We have this inverseBindMatrix provided in the gltf for each bone that transforms a vector from mesh's model space to bone's local space.
|
||||
//So it's inverse, transforms from bone's local space to mesh model space.
|
||||
// we need to transform the bone's bind transforms in this mesh model space, transform them with the transform given in this method,
|
||||
// then recompute their local space value according to parents model space
|
||||
Bone bone = boneWrapper.bone;
|
||||
tmpTransforms.setTranslation(bone.getBindPosition());
|
||||
tmpTransforms.setRotation(bone.getBindRotation());
|
||||
tmpTransforms.setScale(bone.getBindScale());
|
||||
|
||||
tmpTransforms.combineWithParent(transforms);
|
||||
|
||||
bone.setBindTransforms(tmpTransforms.getTranslation(), tmpTransforms.getRotation(), tmpTransforms.getScale());
|
||||
|
||||
}
|
||||
|
||||
private Transform readTransforms(JsonObject nodeData) {
|
||||
Transform transform = new Transform();
|
||||
JsonArray matrix = nodeData.getAsJsonArray("matrix");
|
||||
@ -592,7 +618,7 @@ public class GltfLoader implements AssetLoader {
|
||||
} else {
|
||||
//check if we are loading the same time array
|
||||
if (animData.times != times) {
|
||||
throw new AssetLoadException("Channel has different input accessors for samplers");
|
||||
// throw new AssetLoadException("Channel has different input accessors for samplers");
|
||||
}
|
||||
}
|
||||
if (animData.length == null) {
|
||||
@ -619,6 +645,7 @@ public class GltfLoader implements AssetLoader {
|
||||
List<Spatial> spatials = new ArrayList<>();
|
||||
Animation anim = new Animation();
|
||||
anim.setName(name);
|
||||
int skinIndex = -1;
|
||||
|
||||
for (int i = 0; i < animatedNodes.length; i++) {
|
||||
AnimData animData = animatedNodes[i];
|
||||
@ -635,28 +662,58 @@ public class GltfLoader implements AssetLoader {
|
||||
SpatialTrack track = new SpatialTrack(animData.times, animData.translations, animData.rotations, animData.scales);
|
||||
track.setTrackSpatial(s);
|
||||
anim.addTrack(track);
|
||||
} else {
|
||||
//At some point we'll have bone animation
|
||||
//TODO support for bone animation.
|
||||
System.err.println("animated");
|
||||
System.err.println(node);
|
||||
} else if (node instanceof BoneWrapper) {
|
||||
BoneWrapper b = (BoneWrapper) node;
|
||||
//apply the inverseBindMatrix to animation data.
|
||||
b.update(animData);
|
||||
BoneTrack track = new BoneTrack(b.boneIndex, animData.times, animData.translations, animData.rotations, animData.scales);
|
||||
anim.addTrack(track);
|
||||
if (skinIndex == -1) {
|
||||
skinIndex = b.skinIndex;
|
||||
} else {
|
||||
//Check if all bones affected by this animation are from the same skin, otherwise raise an error.
|
||||
if (skinIndex != b.skinIndex) {
|
||||
throw new AssetLoadException("Animation " + animationIndex + " (" + name + ") applies to bones that are not from the same skin: skin " + skinIndex + ", bone " + b.bone.getName() + " from skin " + b.skinIndex);
|
||||
}
|
||||
//else everything is fine.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!spatials.isEmpty()) {
|
||||
Spatial spatial = null;
|
||||
if (spatials.size() == 1) {
|
||||
spatial = spatials.get(0);
|
||||
} else {
|
||||
spatial = findCommonAncestor(spatials);
|
||||
if (skinIndex != -1) {
|
||||
//we have a bone animation.
|
||||
SkinData skin = fetchFromCache("skins", skinIndex, SkinData.class);
|
||||
if (skin.animControl == null) {
|
||||
skin.animControl = new AnimControl(skin.skeletonControl.getSkeleton());
|
||||
}
|
||||
skin.animControl.addAnim(anim);
|
||||
//the controls will be added to the right spatial in setupControls()
|
||||
}
|
||||
|
||||
AnimControl control = spatial.getControl(AnimControl.class);
|
||||
if (control == null) {
|
||||
control = new AnimControl();
|
||||
spatial.addControl(control);
|
||||
|
||||
if (!spatials.isEmpty()) {
|
||||
//Note that it's pretty unlikely to have an animation that is both a spatial animation and a bone animation...But you never know. The specs doesn't forbids it
|
||||
if (skinIndex != -1) {
|
||||
//there are some spatial tracks in this bone animation... or the other way around. Let's add the spatials in the skinnedSpatials.
|
||||
SkinData skin = fetchFromCache("skins", skinIndex, SkinData.class);
|
||||
List<Spatial> spat = skinnedSpatials.get(skin);
|
||||
spat.addAll(spatials);
|
||||
//the animControl will be added in the setupControls();
|
||||
} else {
|
||||
Spatial spatial = null;
|
||||
if (spatials.size() == 1) {
|
||||
spatial = spatials.get(0);
|
||||
} else {
|
||||
spatial = findCommonAncestor(spatials);
|
||||
}
|
||||
|
||||
AnimControl control = spatial.getControl(AnimControl.class);
|
||||
if (control == null) {
|
||||
control = new AnimControl();
|
||||
spatial.addControl(control);
|
||||
}
|
||||
control.addAnim(anim);
|
||||
}
|
||||
control.addAnim(anim);
|
||||
}
|
||||
}
|
||||
|
||||
@ -703,74 +760,113 @@ public class GltfLoader implements AssetLoader {
|
||||
}
|
||||
}
|
||||
|
||||
System.err.println(inverseBindMatrices);
|
||||
|
||||
rootIndex = joints.get(0).getAsInt();
|
||||
|
||||
boolean addRootIndex = true;
|
||||
Bone[] bones = new Bone[joints.size()];
|
||||
for (int i = 0; i < joints.size(); i++) {
|
||||
bones[i] = readNodeAsBone(joints.get(i).getAsInt(), inverseBindMatrices[i]);
|
||||
int boneIndex = joints.get(i).getAsInt();
|
||||
if (boneIndex == rootIndex) {
|
||||
addRootIndex = false;
|
||||
}
|
||||
bones[i] = readNodeAsBone(boneIndex, inverseBindMatrices[i], i, index);
|
||||
}
|
||||
|
||||
if (addRootIndex) {
|
||||
//sometimes the root bone is not part of the joint array. in that case we add it at the end of the bone list.
|
||||
//The bone won't deform the mesh, but that's pretty common with the root bone.
|
||||
Bone[] newBones = new Bone[bones.length + 1];
|
||||
System.arraycopy(bones, 0, newBones, 0, bones.length);
|
||||
//TODO actually a regular node or a geometry can be attached to a bone, we have to handle this and attach it ti the AttachementNode.
|
||||
newBones[bones.length] = readNodeAsBone(rootIndex, new Matrix4f(), bones.length, index);
|
||||
findChildren(rootIndex);
|
||||
bones = newBones;
|
||||
}
|
||||
|
||||
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);
|
||||
SkinData skinData = new SkinData();
|
||||
skinData.skeletonControl = new SkeletonControl(skeleton);
|
||||
addToCache("skins", index, skinData, nodes.size());
|
||||
skinnedSpatials.put(skinData, new ArrayList<Spatial>());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Bone readNodeAsBone(int nodeIndex, Matrix4f inverseBindMatrix) throws IOException {
|
||||
private Bone readNodeAsBone(int nodeIndex, Matrix4f inverseBindMatrix, int boneIndex, int skinIndex) throws IOException {
|
||||
|
||||
Bone bone = fetchFromCache("nodes", nodeIndex, Bone.class);
|
||||
if (bone != null) {
|
||||
return bone;
|
||||
BoneWrapper boneWrapper = fetchFromCache("nodes", nodeIndex, BoneWrapper.class);
|
||||
if (boneWrapper != null) {
|
||||
return boneWrapper.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);
|
||||
Bone bone = new Bone(name);
|
||||
Transform boneTransforms = readTransforms(nodeData);
|
||||
Transform inverseBind = new Transform();
|
||||
inverseBind.fromTransformMatrix(inverseBindMatrix);
|
||||
// boneTransforms.combineWithParent(inverseBind);
|
||||
bone.setBindTransforms(boneTransforms.getTranslation(), boneTransforms.getRotation(), boneTransforms.getScale());
|
||||
|
||||
addToCache("nodes", nodeIndex, new BoneWrapper(bone, boneIndex, skinIndex, inverseBindMatrix), nodes.size());
|
||||
//
|
||||
// System.err.println(bone.getName() + " " + inverseBindMatrix);
|
||||
// tmpTransforms.fromTransformMatrix(inverseBindMatrix);
|
||||
// System.err.println("t: " + tmpTransforms.getTranslation());
|
||||
// System.err.println("r: " + tmpTransforms.getRotation());
|
||||
// Quaternion q = tmpTransforms.getRotation();
|
||||
// float[] axis = new float[3];
|
||||
// q.toAngles(axis);
|
||||
// for (int i = 0; i < axis.length; i++) {
|
||||
// System.err.print(axis[i] + ", ");
|
||||
// }
|
||||
// System.err.println("");
|
||||
// System.err.println("s: " + tmpTransforms.getScale());
|
||||
|
||||
addToCache("nodes", nodeIndex, bone, nodes.size());
|
||||
return bone;
|
||||
}
|
||||
|
||||
private void findChildren(int nodeIndex) {
|
||||
Bone bone = fetchFromCache("nodes", nodeIndex, Bone.class);
|
||||
BoneWrapper bw = fetchFromCache("nodes", nodeIndex, BoneWrapper.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));
|
||||
int childIndex = child.getAsInt();
|
||||
BoneWrapper cbw = fetchFromCache("nodes", childIndex, BoneWrapper.class);
|
||||
if (cbw != null) {
|
||||
bw.bone.addChild(cbw.bone);
|
||||
bw.children.add(childIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setupControls() {
|
||||
for (Skeleton skeleton : skinnedSpatials.keySet()) {
|
||||
List<Spatial> spatials = skinnedSpatials.get(skeleton);
|
||||
Spatial spatial = null;
|
||||
for (SkinData skinData : skinnedSpatials.keySet()) {
|
||||
List<Spatial> spatials = skinnedSpatials.get(skinData);
|
||||
Spatial spatial;
|
||||
if (spatials.size() >= 1) {
|
||||
spatial = findCommonAncestor(spatials);
|
||||
} else {
|
||||
spatial = spatials.get(0);
|
||||
}
|
||||
SkeletonControl control = new SkeletonControl(skeleton);
|
||||
spatial.addControl(control);
|
||||
|
||||
AnimControl animControl = spatial.getControl(AnimControl.class);
|
||||
if (animControl != null) {
|
||||
//The spatial already has an anim control, we need to merge it with the one in skinData. Then remove it.
|
||||
for (String name : animControl.getAnimationNames()) {
|
||||
Animation anim = animControl.getAnim(name);
|
||||
skinData.animControl.addAnim(anim);
|
||||
}
|
||||
spatial.removeControl(animControl);
|
||||
}
|
||||
|
||||
spatial.addControl(skinData.animControl);
|
||||
spatial.addControl(skinData.skeletonControl);
|
||||
}
|
||||
}
|
||||
|
||||
@ -805,6 +901,43 @@ public class GltfLoader implements AssetLoader {
|
||||
float[] weights;
|
||||
}
|
||||
|
||||
private class BoneWrapper {
|
||||
Bone bone;
|
||||
int boneIndex;
|
||||
int skinIndex;
|
||||
Matrix4f bindMatrix = new Matrix4f();
|
||||
List<Integer> children = new ArrayList<>();
|
||||
|
||||
public BoneWrapper(Bone bone, int boneIndex, int skinIndex, Matrix4f inverseBindMatrix) {
|
||||
this.bone = bone;
|
||||
this.boneIndex = boneIndex;
|
||||
this.skinIndex = skinIndex;
|
||||
this.bindMatrix.set(inverseBindMatrix).invertLocal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the inverseBindMatrix to anim data.
|
||||
*/
|
||||
public void update(AnimData data) {
|
||||
Transform invBind = new Transform(bone.getBindPosition(), bone.getBindRotation(), bone.getBindScale());
|
||||
invBind = invBind.invert();
|
||||
//invBind.fromTransformMatrix(bindMatrix);
|
||||
for (int i = 0; i < data.translations.length; i++) {
|
||||
Transform t = new Transform(data.translations[i], data.rotations[i], data.scales[i]);
|
||||
t.combineWithParent(invBind);
|
||||
data.translations[i] = t.getTranslation();
|
||||
data.rotations[i] = t.getRotation();
|
||||
data.scales[i] = t.getScale();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SkinData {
|
||||
SkeletonControl skeletonControl;
|
||||
AnimControl animControl;
|
||||
}
|
||||
|
||||
private interface Populator<T> {
|
||||
T populate(Integer bufferViewIndex, int componentType, String type, int count, int byteOffset) throws IOException;
|
||||
}
|
||||
@ -837,7 +970,6 @@ public class GltfLoader implements AssetLoader {
|
||||
}
|
||||
vb.setupData(VertexBuffer.Usage.Dynamic, numComponents, format, buff);
|
||||
|
||||
|
||||
return vb;
|
||||
}
|
||||
|
||||
|
BIN
jme3-testdata/src/main/resources/Scenes/defaultProbe.j3o
Normal file
BIN
jme3-testdata/src/main/resources/Scenes/defaultProbe.j3o
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user