Gltf loader now supports the new animation system
This commit is contained in:
parent
bbb3cf59b3
commit
abe094e74a
@ -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 target The Joint target of this track
|
||||||
* @param times a float array with the time of each frame
|
* @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;
|
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.ChaseCameraAppState;
|
||||||
import com.jme3.app.SimpleApplication;
|
import com.jme3.app.SimpleApplication;
|
||||||
import com.jme3.asset.plugins.FileLocator;
|
import com.jme3.asset.plugins.FileLocator;
|
||||||
@ -43,11 +44,10 @@ import com.jme3.renderer.Limits;
|
|||||||
import com.jme3.scene.Node;
|
import com.jme3.scene.Node;
|
||||||
import com.jme3.scene.Spatial;
|
import com.jme3.scene.Spatial;
|
||||||
import com.jme3.scene.control.Control;
|
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.*;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
//import com.jme3.scene.debug.custom.SkeletonDebugAppState;
|
|
||||||
|
|
||||||
public class TestGltfLoading extends SimpleApplication {
|
public class TestGltfLoading extends SimpleApplication {
|
||||||
|
|
||||||
@ -75,8 +75,8 @@ public class TestGltfLoading extends SimpleApplication {
|
|||||||
*/
|
*/
|
||||||
public void simpleInitApp() {
|
public void simpleInitApp() {
|
||||||
|
|
||||||
// SkeletonDebugAppState skeletonDebugAppState = new SkeletonDebugAppState();
|
ArmatureDebugAppState armatureDebugappState = new ArmatureDebugAppState();
|
||||||
// getStateManager().attach(skeletonDebugAppState);
|
getStateManager().attach(armatureDebugappState);
|
||||||
|
|
||||||
String folder = System.getProperty("user.home");
|
String folder = System.getProperty("user.home");
|
||||||
assetManager.registerLocator(folder, FileLocator.class);
|
assetManager.registerLocator(folder, FileLocator.class);
|
||||||
@ -109,28 +109,40 @@ public class TestGltfLoading extends SimpleApplication {
|
|||||||
// rootNode.addLight(pl);
|
// rootNode.addLight(pl);
|
||||||
// PointLight pl1 = new PointLight(new Vector3f(-5.0f, -5.0f, -5.0f), ColorRGBA.White.mult(0.5f), 50);
|
// PointLight pl1 = new PointLight(new Vector3f(-5.0f, -5.0f, -5.0f), ColorRGBA.White.mult(0.5f), 50);
|
||||||
// rootNode.addLight(pl1);
|
// 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/box/box.gltf", Vector3f.ZERO, 1);
|
||||||
// loadModel("Models/gltf/duck/Duck.gltf", new Vector3f(0, -1, 0), 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/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/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/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/RiggedFigure/RiggedFigure.gltf", new Vector3f(0, -1f, 0), 1f);
|
||||||
//loadModel("Models/gltf/CesiumMan/CesiumMan.gltf", new Vector3f(0, -1, 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/BrainStem/BrainStem.gltf", new Vector3f(0, -1, 0), 1f);
|
||||||
//loadModel("Models/gltf/Jaime/Jaime.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/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/Monster/Monster.gltf", Vector3f.ZERO, 0.03f);
|
||||||
|
|
||||||
// loadModel("Models/gltf/corset/Corset.gltf", new Vector3f(0, -1, 0), 20f);
|
// 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));
|
probeNode.attachChild(assets.get(0));
|
||||||
@ -157,6 +169,7 @@ public class TestGltfLoading extends SimpleApplication {
|
|||||||
}, "autorotate");
|
}, "autorotate");
|
||||||
|
|
||||||
inputManager.addMapping("toggleAnim", new KeyTrigger(KeyInput.KEY_RETURN));
|
inputManager.addMapping("toggleAnim", new KeyTrigger(KeyInput.KEY_RETURN));
|
||||||
|
|
||||||
inputManager.addListener(new ActionListener() {
|
inputManager.addListener(new ActionListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onAction(String name, boolean isPressed, float tpf) {
|
public void onAction(String name, boolean isPressed, float tpf) {
|
||||||
@ -170,6 +183,17 @@ public class TestGltfLoading extends SimpleApplication {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, "toggleAnim");
|
}, "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);
|
dumpScene(rootNode, 0);
|
||||||
}
|
}
|
||||||
@ -192,7 +216,9 @@ public class TestGltfLoading extends SimpleApplication {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void loadModel(String path, Vector3f offset, float scale) {
|
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.scale(scale);
|
||||||
s.move(offset);
|
s.move(offset);
|
||||||
assets.add(s);
|
assets.add(s);
|
||||||
@ -200,14 +226,15 @@ public class TestGltfLoading extends SimpleApplication {
|
|||||||
playFirstAnim(s);
|
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) {
|
if (ctrl == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ctrl.setHardwareSkinningPreferred(false);
|
//System.err.println(ctrl.getArmature().toString());
|
||||||
//getStateManager().getState(SkeletonDebugAppState.class).addSkeleton(ctrl, true);
|
//ctrl.setHardwareSkinningPreferred(false);
|
||||||
|
getStateManager().getState(ArmatureDebugAppState.class).addArmatureFrom(ctrl);
|
||||||
// AnimControl aCtrl = findControl(s, AnimControl.class);
|
// AnimControl aCtrl = findControl(s, AnimControl.class);
|
||||||
// //ctrl.getSpatial().removeControl(ctrl);
|
// //ctrl.getSpatial().removeControl(ctrl);
|
||||||
// if (aCtrl == null) {
|
// if (aCtrl == null) {
|
||||||
@ -219,17 +246,24 @@ public class TestGltfLoading extends SimpleApplication {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Queue<String> anims = new LinkedList<>();
|
||||||
|
AnimComposer composer;
|
||||||
|
|
||||||
private void playFirstAnim(Spatial s) {
|
private void playFirstAnim(Spatial s) {
|
||||||
|
|
||||||
AnimControl control = s.getControl(AnimControl.class);
|
AnimComposer control = s.getControl(AnimComposer.class);
|
||||||
if (control != null) {
|
if (control != null) {
|
||||||
// if (control.getAnimationNames().size() > 0) {
|
anims.clear();
|
||||||
// control.createChannel().setAnim(control.getAnimationNames().iterator().next());
|
for (String name : control.getAnimClipsNames()) {
|
||||||
// }
|
anims.add(name);
|
||||||
for (String name : control.getAnimationNames()) {
|
|
||||||
control.createChannel().setAnim(name);
|
|
||||||
}
|
}
|
||||||
|
if (anims.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String anim = anims.poll();
|
||||||
|
anims.add(anim);
|
||||||
|
control.setCurrentAnimClip(anim);
|
||||||
|
composer = control;
|
||||||
}
|
}
|
||||||
if (s instanceof Node) {
|
if (s instanceof Node) {
|
||||||
Node n = (Node) s;
|
Node n = (Node) s;
|
||||||
@ -241,17 +275,9 @@ public class TestGltfLoading extends SimpleApplication {
|
|||||||
|
|
||||||
private void stopAnim(Spatial s) {
|
private void stopAnim(Spatial s) {
|
||||||
|
|
||||||
AnimControl control = s.getControl(AnimControl.class);
|
AnimComposer control = s.getControl(AnimComposer.class);
|
||||||
if (control != null) {
|
if (control != null) {
|
||||||
for (int i = 0; i < control.getNumChannels(); i++) {
|
control.reset();
|
||||||
AnimChannel ch = control.getChannel(i);
|
|
||||||
ch.reset(true);
|
|
||||||
}
|
|
||||||
control.clearChannels();
|
|
||||||
if (control.getSkeleton() != null) {
|
|
||||||
control.getSkeleton().reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
if (s instanceof Node) {
|
if (s instanceof Node) {
|
||||||
Node n = (Node) s;
|
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.*;
|
||||||
import com.google.gson.stream.JsonReader;
|
import com.google.gson.stream.JsonReader;
|
||||||
import com.jme3.animation.*;
|
import com.jme3.anim.*;
|
||||||
import com.jme3.asset.*;
|
import com.jme3.asset.*;
|
||||||
import com.jme3.material.Material;
|
import com.jme3.material.Material;
|
||||||
import com.jme3.material.RenderState;
|
import com.jme3.material.RenderState;
|
||||||
@ -196,7 +196,7 @@ public class GltfLoader implements AssetLoader {
|
|||||||
public Object readNode(int nodeIndex) throws IOException {
|
public Object readNode(int nodeIndex) throws IOException {
|
||||||
Object obj = fetchFromCache("nodes", nodeIndex, Object.class);
|
Object obj = fetchFromCache("nodes", nodeIndex, Object.class);
|
||||||
if (obj != null) {
|
if (obj != null) {
|
||||||
if (obj instanceof BoneWrapper) {
|
if (obj instanceof JointWrapper) {
|
||||||
//the node can be a previously loaded bone let's return it
|
//the node can be a previously loaded bone let's return it
|
||||||
return obj;
|
return obj;
|
||||||
} else {
|
} else {
|
||||||
@ -274,9 +274,9 @@ public class GltfLoader implements AssetLoader {
|
|||||||
readChild(spatial, child);
|
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
|
//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;
|
bw.isRoot = true;
|
||||||
SkinData skinData = fetchFromCache("skins", bw.skinIndex, SkinData.class);
|
SkinData skinData = fetchFromCache("skins", bw.skinIndex, SkinData.class);
|
||||||
if (skinData == null) {
|
if (skinData == null) {
|
||||||
@ -792,44 +792,37 @@ public class GltfLoader implements AssetLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<Spatial> spatials = new ArrayList<>();
|
List<Spatial> spatials = new ArrayList<>();
|
||||||
Animation anim = new Animation();
|
AnimClip anim = new AnimClip(name);
|
||||||
anim.setName(name);
|
|
||||||
int skinIndex = -1;
|
int skinIndex = -1;
|
||||||
|
|
||||||
List<Bone> usedBones = new ArrayList<>();
|
List<Joint> usedJoints = new ArrayList<>();
|
||||||
for (int i = 0; i < tracks.length; i++) {
|
for (int i = 0; i < tracks.length; i++) {
|
||||||
TrackData trackData = tracks[i];
|
TrackData trackData = tracks[i];
|
||||||
if (trackData == null || trackData.timeArrays.isEmpty()) {
|
if (trackData == null || trackData.timeArrays.isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
trackData.update();
|
trackData.update();
|
||||||
if (trackData.length > anim.getLength()) {
|
|
||||||
anim.setLength(trackData.length);
|
|
||||||
}
|
|
||||||
Object node = fetchFromCache("nodes", i, Object.class);
|
Object node = fetchFromCache("nodes", i, Object.class);
|
||||||
if (node instanceof Spatial) {
|
if (node instanceof Spatial) {
|
||||||
Spatial s = (Spatial) node;
|
Spatial s = (Spatial) node;
|
||||||
spatials.add(s);
|
spatials.add(s);
|
||||||
SpatialTrack track = new SpatialTrack(trackData.times, trackData.translations, trackData.rotations, trackData.scales);
|
SpatialTrack track = new SpatialTrack(s, trackData.times, trackData.translations, trackData.rotations, trackData.scales);
|
||||||
track.setTrackSpatial(s);
|
|
||||||
anim.addTrack(track);
|
anim.addTrack(track);
|
||||||
} else if (node instanceof BoneWrapper) {
|
} else if (node instanceof JointWrapper) {
|
||||||
BoneWrapper b = (BoneWrapper) node;
|
JointWrapper jw = (JointWrapper) node;
|
||||||
//apply the inverseBindMatrix to animation data.
|
usedJoints.add(jw.joint);
|
||||||
b.update(trackData);
|
|
||||||
usedBones.add(b.bone);
|
|
||||||
|
|
||||||
if (skinIndex == -1) {
|
if (skinIndex == -1) {
|
||||||
skinIndex = b.skinIndex;
|
skinIndex = jw.skinIndex;
|
||||||
} else {
|
} else {
|
||||||
//Check if all bones affected by this animation are from the same skin, the track will be skipped.
|
//Check if all joints affected by this animation are from the same skin, the track will be skipped.
|
||||||
if (skinIndex != b.skinIndex) {
|
if (skinIndex != jw.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);
|
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;
|
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);
|
anim.addTrack(track);
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -840,22 +833,15 @@ public class GltfLoader implements AssetLoader {
|
|||||||
// instead of the local pose that is supposed to be the default
|
// instead of the local pose that is supposed to be the default
|
||||||
if (skinIndex != -1) {
|
if (skinIndex != -1) {
|
||||||
SkinData skin = fetchFromCache("skins", skinIndex, SkinData.class);
|
SkinData skin = fetchFromCache("skins", skinIndex, SkinData.class);
|
||||||
Skeleton skeleton = skin.skeletonControl.getSkeleton();
|
for (Joint joint : skin.joints) {
|
||||||
for (Bone bone : skin.bones) {
|
if (!usedJoints.contains(joint)) {// && !equalBindAndLocalTransforms(joint)
|
||||||
if (!usedBones.contains(bone) && !equalBindAndLocalTransforms(bone)) {
|
|
||||||
//create a track
|
//create a track
|
||||||
float[] times = new float[]{0, anim.getLength()};
|
float[] times = new float[]{0};
|
||||||
|
|
||||||
Vector3f t = bone.getLocalPosition().subtract(bone.getBindPosition());
|
Vector3f[] translations = new Vector3f[]{joint.getLocalTranslation()};
|
||||||
Quaternion r = tmpQuat.set(bone.getBindRotation()).inverse().multLocal(bone.getLocalRotation());
|
Quaternion[] rotations = new Quaternion[]{joint.getLocalRotation()};
|
||||||
Vector3f s = bone.getLocalScale().divide(bone.getBindScale());
|
Vector3f[] scales = new Vector3f[]{joint.getLocalScale()};
|
||||||
|
JointTrack track = new JointTrack(joint, times, translations, rotations, scales);
|
||||||
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);
|
|
||||||
anim.addTrack(track);
|
anim.addTrack(track);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -864,9 +850,9 @@ public class GltfLoader implements AssetLoader {
|
|||||||
anim = customContentManager.readExtensionAndExtras("animations", animation, anim);
|
anim = customContentManager.readExtensionAndExtras("animations", animation, anim);
|
||||||
|
|
||||||
if (skinIndex != -1) {
|
if (skinIndex != -1) {
|
||||||
//we have a bone animation.
|
//we have a armature animation.
|
||||||
SkinData skin = fetchFromCache("skins", skinIndex, SkinData.class);
|
SkinData skin = fetchFromCache("skins", skinIndex, SkinData.class);
|
||||||
skin.animControl.addAnim(anim);
|
skin.animComposer.addAnimClip(anim);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!spatials.isEmpty()) {
|
if (!spatials.isEmpty()) {
|
||||||
@ -885,12 +871,12 @@ public class GltfLoader implements AssetLoader {
|
|||||||
spatial = findCommonAncestor(spatials);
|
spatial = findCommonAncestor(spatials);
|
||||||
}
|
}
|
||||||
|
|
||||||
AnimControl control = spatial.getControl(AnimControl.class);
|
AnimComposer composer = spatial.getControl(AnimComposer.class);
|
||||||
if (control == null) {
|
if (composer == null) {
|
||||||
control = new AnimControl();
|
composer = new AnimComposer();
|
||||||
spatial.addControl(control);
|
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.
|
//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.
|
//Anyway we have other means to detect both armature structures and root bones.
|
||||||
|
|
||||||
JsonArray joints = skin.getAsJsonArray("joints");
|
JsonArray jsonJoints = skin.getAsJsonArray("joints");
|
||||||
assertNotNull(joints, "No joints defined for skin");
|
assertNotNull(jsonJoints, "No joints defined for skin");
|
||||||
int idx = allJoints.indexOf(joints);
|
int idx = allJoints.indexOf(jsonJoints);
|
||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
//skin already exists let's just set it in the cache
|
//skin already exists let's just set it in the cache
|
||||||
SkinData sd = fetchFromCache("skins", idx, SkinData.class);
|
SkinData sd = fetchFromCache("skins", idx, SkinData.class);
|
||||||
addToCache("skins", index, sd, nodes.size());
|
addToCache("skins", index, sd, nodes.size());
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} 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),
|
//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) {
|
if (matricesIndex != null) {
|
||||||
inverseBindMatrices = readAccessorData(matricesIndex, matrix4fArrayPopulator);
|
inverseBindMatrices = readAccessorData(matricesIndex, matrix4fArrayPopulator);
|
||||||
} else {
|
} else {
|
||||||
inverseBindMatrices = new Matrix4f[joints.size()];
|
inverseBindMatrices = new Matrix4f[jsonJoints.size()];
|
||||||
for (int i = 0; i < inverseBindMatrices.length; i++) {
|
for (int i = 0; i < inverseBindMatrices.length; i++) {
|
||||||
inverseBindMatrices[i] = new Matrix4f();
|
inverseBindMatrices[i] = new Matrix4f();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Bone[] bones = new Bone[joints.size()];
|
Joint[] joints = new Joint[jsonJoints.size()];
|
||||||
for (int i = 0; i < joints.size(); i++) {
|
for (int i = 0; i < jsonJoints.size(); i++) {
|
||||||
int boneIndex = joints.get(i).getAsInt();
|
int boneIndex = jsonJoints.get(i).getAsInt();
|
||||||
//we don't need the inverse bind matrix, we need the bind matrix so let's invert it.
|
Matrix4f inverseModelBindMatrix = inverseBindMatrices[i];
|
||||||
Matrix4f modelBindMatrix = inverseBindMatrices[i].invertLocal();
|
joints[i] = readNodeAsBone(boneIndex, i, index, inverseModelBindMatrix);
|
||||||
bones[i] = readNodeAsBone(boneIndex, i, index, modelBindMatrix);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < joints.size(); i++) {
|
for (int i = 0; i < jsonJoints.size(); i++) {
|
||||||
findChildren(joints.get(i).getAsInt());
|
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.
|
armature = customContentManager.readExtensionAndExtras("skin", skin, armature);
|
||||||
for (Bone bone : skeleton.getRoots()) {
|
|
||||||
BoneWrapper bw = findBoneWrapper(bone);
|
|
||||||
computeBindTransforms(bw, skeleton);
|
|
||||||
}
|
|
||||||
|
|
||||||
skeleton = customContentManager.readExtensionAndExtras("skin", skin, skeleton);
|
|
||||||
SkinData skinData = new SkinData();
|
SkinData skinData = new SkinData();
|
||||||
skinData.bones = bones;
|
skinData.joints = joints;
|
||||||
skinData.skeletonControl = new SkeletonControl(skeleton);
|
skinData.skinningControl = new SkinningControl(armature);
|
||||||
skinData.animControl = new AnimControl(skinData.skeletonControl.getSkeleton());
|
skinData.animComposer = new AnimComposer();
|
||||||
addToCache("skins", index, skinData, nodes.size());
|
addToCache("skins", index, skinData, nodes.size());
|
||||||
skinnedSpatials.put(skinData, new ArrayList<Spatial>());
|
skinnedSpatials.put(skinData, new ArrayList<Spatial>());
|
||||||
|
|
||||||
// Set local transforms.
|
armature.update();
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyPose(int index) {
|
public Joint readNodeAsBone(int nodeIndex, int jointIndex, int skinIndex, Matrix4f inverseModelBindMatrix) throws IOException {
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void computeBindTransforms(BoneWrapper boneWrapper, Skeleton skeleton) {
|
JointWrapper jointWrapper = fetchFromCache("nodes", nodeIndex, JointWrapper.class);
|
||||||
Bone bone = boneWrapper.bone;
|
if (jointWrapper != null) {
|
||||||
tmpTransforms.fromTransformMatrix(boneWrapper.modelBindMatrix);
|
return jointWrapper.joint;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
JsonObject nodeData = nodes.get(nodeIndex).getAsJsonObject();
|
JsonObject nodeData = nodes.get(nodeIndex).getAsJsonObject();
|
||||||
String name = getAsString(nodeData, "name");
|
String name = getAsString(nodeData, "name");
|
||||||
if (name == null) {
|
if (name == null) {
|
||||||
name = "Bone_" + nodeIndex;
|
name = "Joint_" + nodeIndex;
|
||||||
}
|
}
|
||||||
Bone bone = new Bone(name);
|
Joint joint = new Joint(name);
|
||||||
Transform boneTransforms = null;
|
Transform boneTransforms = null;
|
||||||
boneTransforms = readTransforms(nodeData);
|
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 {
|
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();
|
JsonObject nodeData = nodes.get(nodeIndex).getAsJsonObject();
|
||||||
JsonArray children = nodeData.getAsJsonArray("children");
|
JsonArray children = nodeData.getAsJsonArray("children");
|
||||||
|
|
||||||
if (children != null) {
|
if (children != null) {
|
||||||
for (JsonElement child : children) {
|
for (JsonElement child : children) {
|
||||||
int childIndex = child.getAsInt();
|
int childIndex = child.getAsInt();
|
||||||
if (bw.children.contains(childIndex)) {
|
if (jw.children.contains(childIndex)) {
|
||||||
//bone already has the child in its children
|
//bone already has the child in its children
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
BoneWrapper cbw = fetchFromCache("nodes", childIndex, BoneWrapper.class);
|
JointWrapper cjw = fetchFromCache("nodes", childIndex, JointWrapper.class);
|
||||||
if (cbw != null) {
|
if (cjw != null) {
|
||||||
bw.bone.addChild(cbw.bone);
|
jw.joint.addChild(cjw.joint);
|
||||||
bw.children.add(childIndex);
|
jw.children.add(childIndex);
|
||||||
} else {
|
} else {
|
||||||
//The child might be a Node
|
//The child might be a Node
|
||||||
//Creating a dummy node to read the subgraph
|
//Creating a dummy node to read the subgraph
|
||||||
@ -1089,7 +1014,7 @@ public class GltfLoader implements AssetLoader {
|
|||||||
Spatial s = n.getChild(0);
|
Spatial s = n.getChild(0);
|
||||||
//removing the spatial from the dummy node, it will be attached to the attachment node of the bone
|
//removing the spatial from the dummy node, it will be attached to the attachment node of the bone
|
||||||
s.removeFromParent();
|
s.removeFromParent();
|
||||||
bw.attachedSpatial = s;
|
jw.attachedSpatial = s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1113,19 +1038,19 @@ public class GltfLoader implements AssetLoader {
|
|||||||
skinData.rootBoneTransformOffset.combineWithParent(skinData.parent.getWorldTransform());
|
skinData.rootBoneTransformOffset.combineWithParent(skinData.parent.getWorldTransform());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (skinData.animControl != null && skinData.animControl.getSpatial() == null) {
|
if (skinData.animComposer != null && skinData.animComposer.getSpatial() == null) {
|
||||||
spatial.addControl(skinData.animControl);
|
spatial.addControl(skinData.animComposer);
|
||||||
}
|
}
|
||||||
spatial.addControl(skinData.skeletonControl);
|
spatial.addControl(skinData.skinningControl);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < nodes.size(); i++) {
|
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) {
|
if (bw == null || bw.attachedSpatial == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
SkinData skinData = fetchFromCache("skins", bw.skinIndex, SkinData.class);
|
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 {
|
private class JointWrapper {
|
||||||
Bone bone;
|
Joint joint;
|
||||||
int boneIndex;
|
int jointIndex;
|
||||||
int skinIndex;
|
int skinIndex;
|
||||||
Transform localTransform;
|
|
||||||
Transform localTransformOffset;
|
|
||||||
Matrix4f modelBindMatrix;
|
|
||||||
boolean isRoot = false;
|
boolean isRoot = false;
|
||||||
boolean localUpdated = false;
|
|
||||||
Spatial attachedSpatial;
|
Spatial attachedSpatial;
|
||||||
List<Integer> children = new ArrayList<>();
|
List<Integer> children = new ArrayList<>();
|
||||||
|
|
||||||
public BoneWrapper(Bone bone, int boneIndex, int skinIndex, Matrix4f modelBindMatrix, Transform localTransform) {
|
public JointWrapper(Joint joint, int jointIndex, int skinIndex) {
|
||||||
this.bone = bone;
|
this.joint = joint;
|
||||||
this.boneIndex = boneIndex;
|
this.jointIndex = jointIndex;
|
||||||
this.skinIndex = skinIndex;
|
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 {
|
private class SkinData {
|
||||||
SkeletonControl skeletonControl;
|
SkinningControl skinningControl;
|
||||||
AnimControl animControl;
|
AnimComposer animComposer;
|
||||||
Spatial parent;
|
Spatial parent;
|
||||||
Transform rootBoneTransformOffset;
|
Transform rootBoneTransformOffset;
|
||||||
Bone[] bones;
|
Joint[] joints;
|
||||||
boolean used = false;
|
boolean used = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package com.jme3.scene.plugins.gltf;
|
package com.jme3.scene.plugins.gltf;
|
||||||
|
|
||||||
import com.google.gson.*;
|
import com.google.gson.*;
|
||||||
import com.jme3.animation.Bone;
|
|
||||||
import com.jme3.asset.AssetInfo;
|
import com.jme3.asset.AssetInfo;
|
||||||
import com.jme3.asset.AssetLoadException;
|
import com.jme3.asset.AssetLoadException;
|
||||||
import com.jme3.math.*;
|
import com.jme3.math.*;
|
||||||
@ -686,11 +685,11 @@ public class GltfUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean equalBindAndLocalTransforms(Bone b) {
|
// public static boolean equalBindAndLocalTransforms(Joint b) {
|
||||||
return equalsEpsilon(b.getBindPosition(), b.getLocalPosition())
|
// return equalsEpsilon(b.getBindPosition(), b.getLocalPosition())
|
||||||
&& equalsEpsilon(b.getBindRotation(), b.getLocalRotation())
|
// && equalsEpsilon(b.getBindRotation(), b.getLocalRotation())
|
||||||
&& equalsEpsilon(b.getBindScale(), b.getLocalScale());
|
// && equalsEpsilon(b.getBindScale(), b.getLocalScale());
|
||||||
}
|
// }
|
||||||
|
|
||||||
private static float epsilon = 0.0001f;
|
private static float epsilon = 0.0001f;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user