Gltf loader now supports the new animation system

monkanim
Nehon 7 years ago
parent a1a9486424
commit 1f3c0e4c84
  1. 2
      jme3-core/src/main/java/com/jme3/anim/JointTrack.java
  2. 114
      jme3-core/src/main/java/com/jme3/anim/SpatialTrack.java
  3. 102
      jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java
  4. 320
      jme3-examples/src/main/java/jme3test/model/TestGltfLoading2.java
  5. 322
      jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
  6. 11
      jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java

@ -55,7 +55,7 @@ public final class JointTrack extends TransformTrack implements JmeCloneable, Sa
}
/**
* Creates a bone track for the given bone index
* Creates a joint track for the given joint index
*
* @param target The Joint target of this track
* @param times a float array with the time of each frame

@ -0,0 +1,114 @@
/*
* Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.anim;
import com.jme3.export.*;
import com.jme3.math.*;
import com.jme3.scene.Spatial;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
/**
* Contains a list of transforms and times for each keyframe.
*
* @author Rémy Bouquet
*/
public final class SpatialTrack extends TransformTrack implements JmeCloneable, Savable {
private Spatial target;
/**
* Serialization-only. Do not use.
*/
public SpatialTrack() {
super();
}
/**
* Creates a spatial track for the given Spatial
*
* @param target The Spatial target of this track
* @param times a float array with the time of each frame
* @param translations the translation of the bone for each frame
* @param rotations the rotation of the bone for each frame
* @param scales the scale of the bone for each frame
*/
public SpatialTrack(Spatial target, float[] times, Vector3f[] translations, Quaternion[] rotations, Vector3f[] scales) {
super(times, translations, rotations, scales);
this.target = target;
}
@Override
public boolean interpolate(double t) {
setDefaultTransform(target.getLocalTransform());
boolean running = super.interpolate(t);
Transform transform = getInterpolatedTransform();
target.setLocalTransform(transform);
return running;
}
public void setTarget(Spatial target) {
this.target = target;
}
@Override
public Object jmeClone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException("Error cloning", e);
}
}
@Override
public void cloneFields(Cloner cloner, Object original) {
super.cloneFields(cloner, original);
this.target = cloner.clone(target);
}
@Override
public void write(JmeExporter ex) throws IOException {
super.write(ex);
OutputCapsule oc = ex.getCapsule(this);
oc.write(target, "target", null);
}
@Override
public void read(JmeImporter im) throws IOException {
super.read(im);
InputCapsule ic = im.getCapsule(this);
target = (Spatial) ic.readSavable("target", null);
}
}

@ -31,7 +31,8 @@
*/
package jme3test.model;
import com.jme3.animation.*;
import com.jme3.anim.AnimComposer;
import com.jme3.anim.SkinningControl;
import com.jme3.app.ChaseCameraAppState;
import com.jme3.app.SimpleApplication;
import com.jme3.asset.plugins.FileLocator;
@ -43,11 +44,10 @@ import com.jme3.renderer.Limits;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.Control;
import com.jme3.scene.debug.custom.ArmatureDebugAppState;
import com.jme3.scene.plugins.gltf.GltfModelKey;
import java.util.ArrayList;
import java.util.List;
//import com.jme3.scene.debug.custom.SkeletonDebugAppState;
import java.util.*;
public class TestGltfLoading extends SimpleApplication {
@ -75,8 +75,8 @@ public class TestGltfLoading extends SimpleApplication {
*/
public void simpleInitApp() {
// SkeletonDebugAppState skeletonDebugAppState = new SkeletonDebugAppState();
// getStateManager().attach(skeletonDebugAppState);
ArmatureDebugAppState armatureDebugappState = new ArmatureDebugAppState();
getStateManager().attach(armatureDebugappState);
String folder = System.getProperty("user.home");
assetManager.registerLocator(folder, FileLocator.class);
@ -109,28 +109,40 @@ public class TestGltfLoading extends SimpleApplication {
// rootNode.addLight(pl);
// PointLight pl1 = new PointLight(new Vector3f(-5.0f, -5.0f, -5.0f), ColorRGBA.White.mult(0.5f), 50);
// rootNode.addLight(pl1);
//loadModel("Models/gltf/nier/scene.gltf", new Vector3f(0, -1.5f, 0), 0.01f);
//loadModel("Models/gltf/izzy/scene.gltf", new Vector3f(0, -1, 0), 0.01f);
//loadModel("Models/gltf/darth/scene.gltf", new Vector3f(0, -1, 0), 0.01f);
//loadModel("Models/gltf/mech/scene.gltf", new Vector3f(0, -1, 0), 0.01f);
//loadModel("Models/gltf/elephant/scene.gltf", new Vector3f(0, -1, 0), 0.01f);
//loadModel("Models/gltf/buffalo/scene.gltf", new Vector3f(0, -1, 0), 0.1f);
//loadModel("Models/gltf/war/scene.gltf", new Vector3f(0, -1, 0), 0.1f);
//loadModel("Models/gltf/ganjaarl/scene.gltf", new Vector3f(0, -1, 0), 0.01f);
//loadModel("Models/gltf/hero/scene.gltf", new Vector3f(0, -1, 0), 0.1f);
//loadModel("Models/gltf/mercy/scene.gltf", new Vector3f(0, -1, 0), 0.01f);
//loadModel("Models/gltf/crab/scene.gltf", Vector3f.ZERO, 1);
//loadModel("Models/gltf/manta/scene.gltf", Vector3f.ZERO, 0.2f);
// loadModel("Models/gltf/bone/scene.gltf", Vector3f.ZERO, 0.1f);
// 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/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/busterDrone/busterDrone.gltf", new Vector3f(0, 0f, 0), 0.8f);
// loadModel("Models/gltf/animatedCube/AnimatedCube.gltf", Vector3f.ZERO, 0.5f);
//
// //loadModel("Models/gltf/BoxAnimated/BoxAnimated.gltf", new Vector3f(0, 0f, 0), 0.8f);
//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/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);
// 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);
// loadModel("Models/gltf/corset/Corset.gltf", new Vector3f(0, -1, 0), 20f);
loadModel("Models/gltf/boxInter/BoxInterleaved.gltf", new Vector3f(0, 0, 0), 1f);
// loadModel("Models/gltf/boxInter/BoxInterleaved.gltf", new Vector3f(0, 0, 0), 1f);
probeNode.attachChild(assets.get(0));
@ -157,6 +169,7 @@ public class TestGltfLoading extends SimpleApplication {
}, "autorotate");
inputManager.addMapping("toggleAnim", new KeyTrigger(KeyInput.KEY_RETURN));
inputManager.addListener(new ActionListener() {
@Override
public void onAction(String name, boolean isPressed, float tpf) {
@ -170,6 +183,17 @@ public class TestGltfLoading extends SimpleApplication {
}
}
}, "toggleAnim");
inputManager.addMapping("nextAnim", new KeyTrigger(KeyInput.KEY_RIGHT));
inputManager.addListener(new ActionListener() {
@Override
public void onAction(String name, boolean isPressed, float tpf) {
if (isPressed && composer != null) {
String anim = anims.poll();
anims.add(anim);
composer.setCurrentAnimClip(anim);
}
}
}, "nextAnim");
dumpScene(rootNode, 0);
}
@ -192,7 +216,9 @@ public class TestGltfLoading extends SimpleApplication {
}
private void loadModel(String path, Vector3f offset, float scale) {
Spatial s = assetManager.loadModel(path);
GltfModelKey k = new GltfModelKey(path);
//k.setKeepSkeletonPose(true);
Spatial s = assetManager.loadModel(k);
s.scale(scale);
s.move(offset);
assets.add(s);
@ -200,14 +226,15 @@ public class TestGltfLoading extends SimpleApplication {
playFirstAnim(s);
}
SkeletonControl ctrl = findControl(s, SkeletonControl.class);
SkinningControl ctrl = findControl(s, SkinningControl.class);
// //ctrl.getSpatial().removeControl(ctrl);
// ctrl.getSpatial().removeControl(ctrl);
if (ctrl == null) {
return;
}
ctrl.setHardwareSkinningPreferred(false);
//getStateManager().getState(SkeletonDebugAppState.class).addSkeleton(ctrl, true);
//System.err.println(ctrl.getArmature().toString());
//ctrl.setHardwareSkinningPreferred(false);
getStateManager().getState(ArmatureDebugAppState.class).addArmatureFrom(ctrl);
// AnimControl aCtrl = findControl(s, AnimControl.class);
// //ctrl.getSpatial().removeControl(ctrl);
// if (aCtrl == null) {
@ -219,17 +246,24 @@ public class TestGltfLoading extends SimpleApplication {
}
Queue<String> anims = new LinkedList<>();
AnimComposer composer;
private void playFirstAnim(Spatial s) {
AnimControl control = s.getControl(AnimControl.class);
AnimComposer control = s.getControl(AnimComposer.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);
anims.clear();
for (String name : control.getAnimClipsNames()) {
anims.add(name);
}
if (anims.isEmpty()) {
return;
}
String anim = anims.poll();
anims.add(anim);
control.setCurrentAnimClip(anim);
composer = control;
}
if (s instanceof Node) {
Node n = (Node) s;
@ -241,17 +275,9 @@ public class TestGltfLoading extends SimpleApplication {
private void stopAnim(Spatial s) {
AnimControl control = s.getControl(AnimControl.class);
AnimComposer control = s.getControl(AnimComposer.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();
}
control.reset();
}
if (s instanceof Node) {
Node n = (Node) s;

@ -1,320 +0,0 @@
/*
* Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package jme3test.model;
import com.jme3.animation.*;
import com.jme3.app.ChaseCameraAppState;
import com.jme3.app.SimpleApplication;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.math.*;
import com.jme3.renderer.Limits;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.Control;
import com.jme3.scene.plugins.gltf.GltfModelKey;
import java.util.*;
//import com.jme3.scene.debug.custom.SkeletonDebugAppState;
public class TestGltfLoading2 extends SimpleApplication {
Node autoRotate = new Node("autoRotate");
List<Spatial> assets = new ArrayList<>();
Node probeNode;
float time = 0;
int assetIndex = 0;
boolean useAutoRotate = false;
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) {
TestGltfLoading2 app = new TestGltfLoading2();
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() {
// 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);
//loadModel("Models/gltf/buffalo/scene.gltf", new Vector3f(0, -1, 0), 0.1f);
//loadModel("Models/gltf/war/scene.gltf", new Vector3f(0, -1, 0), 0.1f);
loadModel("Models/gltf/ganjaarl/scene.gltf", new Vector3f(0, -1, 0), 0.01f);
//loadModel("Models/gltf/hero/scene.gltf", new Vector3f(0, -1, 0), 0.1f);
//loadModel("Models/gltf/mercy/scene.gltf", new Vector3f(0, -1, 0), 0.01f);
//loadModel("Models/gltf/crab/scene.gltf", Vector3f.ZERO, 1);
//loadModel("Models/gltf/manta/scene.gltf", Vector3f.ZERO, 0.2f);
// loadModel("Models/gltf/bone/scene.gltf", Vector3f.ZERO, 0.1f);
// 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);
//
// //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);
// loadModel("Models/gltf/corset/Corset.gltf", new Vector3f(0, -1, 0), 20f);
// loadModel("Models/gltf/boxInter/BoxInterleaved.gltf", new Vector3f(0, 0, 0), 1f);
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");
inputManager.addMapping("nextAnim", new KeyTrigger(KeyInput.KEY_RIGHT));
inputManager.addListener(new ActionListener() {
@Override
public void onAction(String name, boolean isPressed, float tpf) {
if (isPressed && animControl != null) {
AnimChannel c = animControl.getChannel(0);
if (c == null) {
c = animControl.createChannel();
}
String anim = anims.poll();
anims.add(anim);
c.setAnim(anim);
}
}
}, "nextAnim");
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) {
GltfModelKey k = new GltfModelKey(path);
//k.setKeepSkeletonPose(true);
Spatial s = assetManager.loadModel(k);
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;
}
//System.err.println(ctrl.getArmature().toString());
//ctrl.setHardwareSkinningPreferred(false);
// getStateManager().getState(SkeletonDebugAppState.class).addSkeleton(ctrl, true);
// AnimControl aCtrl = findControl(s, AnimControl.class);
// //ctrl.getSpatial().removeControl(ctrl);
// if (aCtrl == null) {
// return;
// }
// if (aCtrl.getArmature() != null) {
// getStateManager().getState(SkeletonDebugAppState.class).addSkeleton(aCtrl.getArmature(), aCtrl.getSpatial(), true);
// }
}
Queue<String> anims = new LinkedList<>();
AnimControl animControl;
private void playFirstAnim(Spatial s) {
AnimControl control = s.getControl(AnimControl.class);
if (control != null) {
anims.clear();
for (String name : control.getAnimationNames()) {
anims.add(name);
}
if (anims.isEmpty()) {
return;
}
String anim = anims.poll();
anims.add(anim);
control.createChannel().setAnim(anim);
animControl = control;
}
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 (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);
}
}
}
}

@ -2,7 +2,7 @@ package com.jme3.scene.plugins.gltf;
import com.google.gson.*;
import com.google.gson.stream.JsonReader;
import com.jme3.animation.*;
import com.jme3.anim.*;
import com.jme3.asset.*;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
@ -196,7 +196,7 @@ public class GltfLoader implements AssetLoader {
public Object readNode(int nodeIndex) throws IOException {
Object obj = fetchFromCache("nodes", nodeIndex, Object.class);
if (obj != null) {
if (obj instanceof BoneWrapper) {
if (obj instanceof JointWrapper) {
//the node can be a previously loaded bone let's return it
return obj;
} else {
@ -274,9 +274,9 @@ public class GltfLoader implements AssetLoader {
readChild(spatial, child);
}
}
} else if (loaded instanceof BoneWrapper) {
} else if (loaded instanceof JointWrapper) {
//parent is the Armature Node, we have to apply its transforms to the root bone's animation data
BoneWrapper bw = (BoneWrapper) loaded;
JointWrapper bw = (JointWrapper) loaded;
bw.isRoot = true;
SkinData skinData = fetchFromCache("skins", bw.skinIndex, SkinData.class);
if (skinData == null) {
@ -792,44 +792,37 @@ public class GltfLoader implements AssetLoader {
}
List<Spatial> spatials = new ArrayList<>();
Animation anim = new Animation();
anim.setName(name);
AnimClip anim = new AnimClip(name);
int skinIndex = -1;
List<Bone> usedBones = new ArrayList<>();
List<Joint> usedJoints = new ArrayList<>();
for (int i = 0; i < tracks.length; i++) {
TrackData trackData = tracks[i];
if (trackData == null || trackData.timeArrays.isEmpty()) {
continue;
}
trackData.update();
if (trackData.length > anim.getLength()) {
anim.setLength(trackData.length);
}
Object node = fetchFromCache("nodes", i, Object.class);
if (node instanceof Spatial) {
Spatial s = (Spatial) node;
spatials.add(s);
SpatialTrack track = new SpatialTrack(trackData.times, trackData.translations, trackData.rotations, trackData.scales);
track.setTrackSpatial(s);
SpatialTrack track = new SpatialTrack(s, trackData.times, trackData.translations, trackData.rotations, trackData.scales);
anim.addTrack(track);
} else if (node instanceof BoneWrapper) {
BoneWrapper b = (BoneWrapper) node;
//apply the inverseBindMatrix to animation data.
b.update(trackData);
usedBones.add(b.bone);
} else if (node instanceof JointWrapper) {
JointWrapper jw = (JointWrapper) node;
usedJoints.add(jw.joint);
if (skinIndex == -1) {
skinIndex = b.skinIndex;
skinIndex = jw.skinIndex;
} else {
//Check if all bones affected by this animation are from the same skin, the track will be skipped.
if (skinIndex != b.skinIndex) {
logger.log(Level.WARNING, "Animation " + animationIndex + " (" + name + ") applies to bones that are not from the same skin: skin " + skinIndex + ", bone " + b.bone.getName() + " from skin " + b.skinIndex);
//Check if all joints affected by this animation are from the same skin, the track will be skipped.
if (skinIndex != jw.skinIndex) {
logger.log(Level.WARNING, "Animation " + animationIndex + " (" + name + ") applies to joints that are not from the same skin: skin " + skinIndex + ", joint " + jw.joint.getName() + " from skin " + jw.skinIndex);
continue;
}
}
BoneTrack track = new BoneTrack(b.boneIndex, trackData.times, trackData.translations, trackData.rotations, trackData.scales);
JointTrack track = new JointTrack(jw.joint, trackData.times, trackData.translations, trackData.rotations, trackData.scales);
anim.addTrack(track);
}
@ -840,22 +833,15 @@ public class GltfLoader implements AssetLoader {
// instead of the local pose that is supposed to be the default
if (skinIndex != -1) {
SkinData skin = fetchFromCache("skins", skinIndex, SkinData.class);
Skeleton skeleton = skin.skeletonControl.getSkeleton();
for (Bone bone : skin.bones) {
if (!usedBones.contains(bone) && !equalBindAndLocalTransforms(bone)) {
for (Joint joint : skin.joints) {
if (!usedJoints.contains(joint)) {// && !equalBindAndLocalTransforms(joint)
//create a track
float[] times = new float[]{0, anim.getLength()};
Vector3f t = bone.getLocalPosition().subtract(bone.getBindPosition());
Quaternion r = tmpQuat.set(bone.getBindRotation()).inverse().multLocal(bone.getLocalRotation());
Vector3f s = bone.getLocalScale().divide(bone.getBindScale());
float[] times = new float[]{0};
Vector3f[] translations = new Vector3f[]{t, t};
Quaternion[] rotations = new Quaternion[]{r, r};
Vector3f[] scales = new Vector3f[]{s, s};
int boneIndex = skeleton.getBoneIndex(bone);
BoneTrack track = new BoneTrack(boneIndex, times, translations, rotations, scales);
Vector3f[] translations = new Vector3f[]{joint.getLocalTranslation()};
Quaternion[] rotations = new Quaternion[]{joint.getLocalRotation()};
Vector3f[] scales = new Vector3f[]{joint.getLocalScale()};
JointTrack track = new JointTrack(joint, times, translations, rotations, scales);
anim.addTrack(track);
}
}
@ -864,9 +850,9 @@ public class GltfLoader implements AssetLoader {
anim = customContentManager.readExtensionAndExtras("animations", animation, anim);
if (skinIndex != -1) {
//we have a bone animation.
//we have a armature animation.
SkinData skin = fetchFromCache("skins", skinIndex, SkinData.class);
skin.animControl.addAnim(anim);
skin.animComposer.addAnimClip(anim);
}
if (!spatials.isEmpty()) {
@ -885,12 +871,12 @@ public class GltfLoader implements AssetLoader {
spatial = findCommonAncestor(spatials);
}
AnimControl control = spatial.getControl(AnimControl.class);
if (control == null) {
control = new AnimControl();
spatial.addControl(control);
AnimComposer composer = spatial.getControl(AnimComposer.class);
if (composer == null) {
composer = new AnimComposer();
spatial.addControl(composer);
}
control.addAnim(anim);
composer.addAnimClip(anim);
}
}
}
@ -932,16 +918,16 @@ public class GltfLoader implements AssetLoader {
//It's not mandatory and exporters tends to mix up how it should be used because the specs are not clear.
//Anyway we have other means to detect both armature structures and root bones.
JsonArray joints = skin.getAsJsonArray("joints");
assertNotNull(joints, "No joints defined for skin");
int idx = allJoints.indexOf(joints);
JsonArray jsonJoints = skin.getAsJsonArray("joints");
assertNotNull(jsonJoints, "No joints defined for skin");
int idx = allJoints.indexOf(jsonJoints);
if (idx >= 0) {
//skin already exists let's just set it in the cache
SkinData sd = fetchFromCache("skins", idx, SkinData.class);
addToCache("skins", index, sd, nodes.size());
continue;
} else {
allJoints.add(joints);
allJoints.add(jsonJoints);
}
//These inverse bind matrices, once inverted again, will give us the real bind pose of the bones (in model space),
@ -951,136 +937,75 @@ public class GltfLoader implements AssetLoader {
if (matricesIndex != null) {
inverseBindMatrices = readAccessorData(matricesIndex, matrix4fArrayPopulator);
} else {
inverseBindMatrices = new Matrix4f[joints.size()];
inverseBindMatrices = new Matrix4f[jsonJoints.size()];
for (int i = 0; i < inverseBindMatrices.length; i++) {
inverseBindMatrices[i] = new Matrix4f();
}
}
Bone[] bones = new Bone[joints.size()];
for (int i = 0; i < joints.size(); i++) {
int boneIndex = joints.get(i).getAsInt();
//we don't need the inverse bind matrix, we need the bind matrix so let's invert it.
Matrix4f modelBindMatrix = inverseBindMatrices[i].invertLocal();
bones[i] = readNodeAsBone(boneIndex, i, index, modelBindMatrix);
Joint[] joints = new Joint[jsonJoints.size()];
for (int i = 0; i < jsonJoints.size(); i++) {
int boneIndex = jsonJoints.get(i).getAsInt();
Matrix4f inverseModelBindMatrix = inverseBindMatrices[i];
joints[i] = readNodeAsBone(boneIndex, i, index, inverseModelBindMatrix);
}
for (int i = 0; i < joints.size(); i++) {
findChildren(joints.get(i).getAsInt());
for (int i = 0; i < jsonJoints.size(); i++) {
findChildren(jsonJoints.get(i).getAsInt());
}
Skeleton skeleton = new Skeleton(bones);
Armature armature = new Armature(joints);
//Compute bind transforms. We need to do it from root bone to leaves bone.
for (Bone bone : skeleton.getRoots()) {
BoneWrapper bw = findBoneWrapper(bone);
computeBindTransforms(bw, skeleton);
}
skeleton = customContentManager.readExtensionAndExtras("skin", skin, skeleton);
armature = customContentManager.readExtensionAndExtras("skin", skin, armature);
SkinData skinData = new SkinData();
skinData.bones = bones;
skinData.skeletonControl = new SkeletonControl(skeleton);
skinData.animControl = new AnimControl(skinData.skeletonControl.getSkeleton());
skinData.joints = joints;
skinData.skinningControl = new SkinningControl(armature);
skinData.animComposer = new AnimComposer();
addToCache("skins", index, skinData, nodes.size());
skinnedSpatials.put(skinData, new ArrayList<Spatial>());
// Set local transforms.
// The skeleton may come in a given pose, that is not the rest pose, so let 's apply it.
// We will need it later for animation
for (int i = 0; i < joints.size(); i++) {
applyPose(joints.get(i).getAsInt());
}
skeleton.updateWorldVectors();
//If the user didn't ask to keep the pose we reset the skeleton user control
if (!isKeepSkeletonPose(info)) {
for (Bone bone : bones) {
bone.setUserControl(false);
}
}
armature.update();
}
}
private void applyPose(int index) {
BoneWrapper bw = fetchFromCache("nodes", index, BoneWrapper.class);
bw.bone.setUserControl(true);
bw.bone.setLocalTranslation(bw.localTransform.getTranslation());
bw.bone.setLocalRotation(bw.localTransform.getRotation());
bw.bone.setLocalScale(bw.localTransform.getScale());
}
public Joint readNodeAsBone(int nodeIndex, int jointIndex, int skinIndex, Matrix4f inverseModelBindMatrix) throws IOException {
private void computeBindTransforms(BoneWrapper boneWrapper, Skeleton skeleton) {
Bone bone = boneWrapper.bone;
tmpTransforms.fromTransformMatrix(boneWrapper.modelBindMatrix);
if (bone.getParent() != null) {
//root bone, model transforms are the same as the local transforms
//but for child bones we need to combine it with the parents inverse model transforms.
tmpMat.setTranslation(bone.getParent().getModelSpacePosition());
tmpMat.setRotationQuaternion(bone.getParent().getModelSpaceRotation());
tmpMat.setScale(bone.getParent().getModelSpaceScale());
tmpMat.invertLocal();
tmpTransforms2.fromTransformMatrix(tmpMat);
tmpTransforms.combineWithParent(tmpTransforms2);
}
bone.setBindTransforms(tmpTransforms.getTranslation(), tmpTransforms.getRotation(), tmpTransforms.getScale());
//resets the local transforms to bind transforms for all bones.
//then computes the model transforms from local transforms for each bone.
skeleton.resetAndUpdate();
skeleton.setBindingPose();
for (Integer childIndex : boneWrapper.children) {
BoneWrapper child = fetchFromCache("nodes", childIndex, BoneWrapper.class);
computeBindTransforms(child, skeleton);
}
}
private BoneWrapper findBoneWrapper(Bone bone) {
for (int i = 0; i < nodes.size(); i++) {
BoneWrapper bw = fetchFromCache("nodes", i, BoneWrapper.class);
if (bw != null && bw.bone == bone) {
return bw;
}
}
return null;
}
public Bone readNodeAsBone(int nodeIndex, int boneIndex, int skinIndex, Matrix4f modelBindMatrix) throws IOException {
BoneWrapper boneWrapper = fetchFromCache("nodes", nodeIndex, BoneWrapper.class);
if (boneWrapper != null) {
return boneWrapper.bone;
JointWrapper jointWrapper = fetchFromCache("nodes", nodeIndex, JointWrapper.class);
if (jointWrapper != null) {
return jointWrapper.joint;
}
JsonObject nodeData = nodes.get(nodeIndex).getAsJsonObject();
String name = getAsString(nodeData, "name");
if (name == null) {
name = "Bone_" + nodeIndex;
name = "Joint_" + nodeIndex;
}
Bone bone = new Bone(name);
Joint joint = new Joint(name);
Transform boneTransforms = null;
boneTransforms = readTransforms(nodeData);
joint.setLocalTransform(boneTransforms);
joint.setInverseModelBindMatrix(inverseModelBindMatrix);
addToCache("nodes", nodeIndex, new BoneWrapper(bone, boneIndex, skinIndex, modelBindMatrix, boneTransforms), nodes.size());
addToCache("nodes", nodeIndex, new JointWrapper(joint, jointIndex, skinIndex), nodes.size());
return bone;
return joint;
}
private void findChildren(int nodeIndex) throws IOException {
BoneWrapper bw = fetchFromCache("nodes", nodeIndex, BoneWrapper.class);
JointWrapper jw = fetchFromCache("nodes", nodeIndex, JointWrapper.class);
JsonObject nodeData = nodes.get(nodeIndex).getAsJsonObject();
JsonArray children = nodeData.getAsJsonArray("children");
if (children != null) {
for (JsonElement child : children) {
int childIndex = child.getAsInt();
if (bw.children.contains(childIndex)) {
if (jw.children.contains(childIndex)) {
//bone already has the child in its children
continue;
}
BoneWrapper cbw = fetchFromCache("nodes", childIndex, BoneWrapper.class);
if (cbw != null) {
bw.bone.addChild(cbw.bone);
bw.children.add(childIndex);
JointWrapper cjw = fetchFromCache("nodes", childIndex, JointWrapper.class);
if (cjw != null) {
jw.joint.addChild(cjw.joint);
jw.children.add(childIndex);
} else {
//The child might be a Node
//Creating a dummy node to read the subgraph
@ -1089,7 +1014,7 @@ public class GltfLoader implements AssetLoader {
Spatial s = n.getChild(0);
//removing the spatial from the dummy node, it will be attached to the attachment node of the bone
s.removeFromParent();
bw.attachedSpatial = s;
jw.attachedSpatial = s;
}
}
@ -1113,19 +1038,19 @@ public class GltfLoader implements AssetLoader {
skinData.rootBoneTransformOffset.combineWithParent(skinData.parent.getWorldTransform());
}
if (skinData.animControl != null && skinData.animControl.getSpatial() == null) {
spatial.addControl(skinData.animControl);
if (skinData.animComposer != null && skinData.animComposer.getSpatial() == null) {
spatial.addControl(skinData.animComposer);
}
spatial.addControl(skinData.skeletonControl);
spatial.addControl(skinData.skinningControl);
}
for (int i = 0; i < nodes.size(); i++) {
BoneWrapper bw = fetchFromCache("nodes", i, BoneWrapper.class);
JointWrapper bw = fetchFromCache("nodes", i, JointWrapper.class);
if (bw == null || bw.attachedSpatial == null) {
continue;
}
SkinData skinData = fetchFromCache("skins", bw.skinIndex, SkinData.class);
skinData.skeletonControl.getAttachmentsNode(bw.bone.getName()).attachChild(bw.attachedSpatial);
skinData.skinningControl.getAttachmentsNode(bw.joint.getName()).attachChild(bw.attachedSpatial);
}
}
@ -1182,118 +1107,27 @@ public class GltfLoader implements AssetLoader {
}
private class BoneWrapper {
Bone bone;
int boneIndex;
private class JointWrapper {
Joint joint;
int jointIndex;
int skinIndex;
Transform localTransform;
Transform localTransformOffset;
Matrix4f modelBindMatrix;
boolean isRoot = false;
boolean localUpdated = false;
Spatial attachedSpatial;
List<Integer> children = new ArrayList<>();
public BoneWrapper(Bone bone, int boneIndex, int skinIndex, Matrix4f modelBindMatrix, Transform localTransform) {
this.bone = bone;
this.boneIndex = boneIndex;
public JointWrapper(Joint joint, int jointIndex, int skinIndex) {
this.joint = joint;
this.jointIndex = jointIndex;
this.skinIndex = skinIndex;
this.modelBindMatrix = modelBindMatrix;
this.localTransform = localTransform;
this.localTransformOffset = localTransform.clone();
}
/**
* Applies the inverse Bind transforms to anim data. and the armature transforms if relevant.
*/
public void update(TrackData data) {
Transform bindTransforms = new Transform(bone.getBindPosition(), bone.getBindRotation(), bone.getBindScale());
SkinData skinData = fetchFromCache("skins", skinIndex, SkinData.class);
if (!localUpdated) {
//LocalTransform of the bone are default position to use for animations when there is no track.
//We need to transform them so that JME can us them in blendAnimTransform.
reverseBlendAnimTransforms(localTransformOffset, bindTransforms);
localUpdated = true;
}
for (int i = 0; i < data.getNbKeyFrames(); i++) {
Vector3f translation = getTranslation(data, i);
Quaternion rotation = getRotation(data, i);
Vector3f scale = getScale(data, i);
Transform t = new Transform(translation, rotation, scale);
if (isRoot && skinData.rootBoneTransformOffset != null) {
//Apply the armature transforms to the root bone anim track.
t.combineWithParent(skinData.rootBoneTransformOffset);
}
reverseBlendAnimTransforms(t, bindTransforms);
if (data.translations != null) {
data.translations[i] = t.getTranslation();
}
if (data.rotations != null) {
data.rotations[i] = t.getRotation();
}
if (data.scales != null) {
data.scales[i] = t.getScale();
}
}
data.ensureTranslationRotations(localTransformOffset);
}
private void reverseBlendAnimTransforms(Transform t, Transform bindTransforms) {
//This is wrong
//You'd normally combine those transforms with transform.combineWithParent()
//Here we actually do in reverse what JME does to combine anim transforms with bind transfoms (add trans/mult rot/ mult scale)
//The code to fix is in Bone.blendAnimTransforms
//TODO fix blendAnimTransforms
t.getTranslation().subtractLocal(bindTransforms.getTranslation());
t.getScale().divideLocal(bindTransforms.getScale());
tmpQuat.set(bindTransforms.getRotation()).inverseLocal().multLocal(t.getRotation());
t.setRotation(tmpQuat);
}
private Vector3f getTranslation(TrackData data, int i) {
Vector3f translation;
if (data.translations == null) {
translation = bone.getLocalPosition();
} else {
translation = data.translations[i];
}
return translation;
}
private Quaternion getRotation(TrackData data, int i) {
Quaternion rotation;
if (data.rotations == null) {
rotation = bone.getLocalRotation();
} else {
rotation = data.rotations[i];
}
return rotation;
}
private Vector3f getScale(TrackData data, int i) {
Vector3f scale;
if (data.scales == null) {
scale = bone.getLocalScale();
} else {
scale = data.scales[i];
}
return scale;
}
}
private class SkinData {
SkeletonControl skeletonControl;
AnimControl animControl;
SkinningControl skinningControl;
AnimComposer animComposer;
Spatial parent;
Transform rootBoneTransformOffset;
Bone[] bones;
Joint[] joints;
boolean used = false;
}

@ -1,7 +1,6 @@
package com.jme3.scene.plugins.gltf;
import com.google.gson.*;
import com.jme3.animation.Bone;
import com.jme3.asset.AssetInfo;
import com.jme3.asset.AssetLoadException;
import com.jme3.math.*;
@ -686,11 +685,11 @@ public class GltfUtils {
}
}
public static boolean equalBindAndLocalTransforms(Bone b) {
return equalsEpsilon(b.getBindPosition(), b.getLocalPosition())
&& equalsEpsilon(b.getBindRotation(), b.getLocalRotation())
&& equalsEpsilon(b.getBindScale(), b.getLocalScale());
}
// public static boolean equalBindAndLocalTransforms(Joint b) {
// return equalsEpsilon(b.getBindPosition(), b.getLocalPosition())
// && equalsEpsilon(b.getBindRotation(), b.getLocalRotation())
// && equalsEpsilon(b.getBindScale(), b.getLocalScale());
// }
private static float epsilon = 0.0001f;

Loading…
Cancel
Save