Gltf loader now supports the new animation system
This commit is contained in:
parent
bbb3cf59b3
commit
abe094e74a
jme3-core/src/main/java/com/jme3/anim
jme3-examples/src/main/java/jme3test/model
jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf
@ -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
|
||||
|
114
jme3-core/src/main/java/com/jme3/anim/SpatialTrack.java
Normal file
114
jme3-core/src/main/java/com/jme3/anim/SpatialTrack.java
Normal file
@ -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,16 +109,28 @@ 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/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/RiggedFigure.gltf", new Vector3f(0, -1f, 0), 1f);
|
||||
@ -126,11 +138,11 @@ public class TestGltfLoading extends SimpleApplication {
|
||||
//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/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()};
|
||||
float[] times = new float[]{0};
|
||||
|
||||
Vector3f t = bone.getLocalPosition().subtract(bone.getBindPosition());
|
||||
Quaternion r = tmpQuat.set(bone.getBindRotation()).inverse().multLocal(bone.getLocalRotation());
|
||||
Vector3f s = bone.getLocalScale().divide(bone.getBindScale());
|
||||
|
||||
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…
x
Reference in New Issue
Block a user