Compare commits

...

30 Commits

Author SHA1 Message Date
Nehon e33862a632 Remobes unused imports 7 years ago
Nehon 1cdd37786e Removes the default value for NumberOfTargetsBuffers 7 years ago
Nehon 53f7f7490f Some clean up in PBR j3MD 7 years ago
Nehon bcd97c22eb Proper serialisation for morph animation 7 years ago
Nehon c2fcdefd0d Adds a cpu fallback for morph animation when all morph targets cannot be handled on the gpu 7 years ago
Rémy Bouquet 346173e2be Hardware Morph animation implementation and glTF loading 7 years ago
Rémy Bouquet 909716fa03 Fixes link to original paper in shadow renderer 7 years ago
Rémy Bouquet 84276ce099 Better armature debugger 7 years ago
Nehon 34114f5a82 Enhanced hardware skinning test 7 years ago
Nehon 6ccacd257e fixes some isues with the gltfLoader when there are several skins 7 years ago
Rémy Bouquet 9dc87b71e9 Speed support and some clean up 7 years ago
Rémy Bouquet ce88350abf Better blending structure 7 years ago
Rémy Bouquet a8845a1506 Added a BlendAction that allows to blend animations 7 years ago
Rémy Bouquet 449429974e Added the base of the blending system. Smooth transition between anims 7 years ago
Nehon dd5b90e281 Better performance 7 years ago
Nehon 75a6e86e56 Ogre loader now loads animations and armature with the new system 7 years ago
Nehon e99a093b5a Better armature joint selection 7 years ago
Nehon f1a3593070 Display origin of the Armature debugger 7 years ago
Nehon 8d39caec6a Further Armature debugger enhancements 7 years ago
Nehon 8cf0f1a4d5 Added a way to toggle on/off the non deforming bones in the Armature debugger 7 years ago
Nehon 1f3c0e4c84 Gltf loader now supports the new animation system 7 years ago
Nehon a1a9486424 New anim system proper serialization 7 years ago
Nehon a4267393e1 Adds a MigrationUtil to migrate from old system to new system 7 years ago
Nehon 74f9a648d1 Adds support for different joint model transform accumulation strategy 7 years ago
Nehon c630ac2e59 better Armature debugger 7 years ago
Nehon 56065d0d70 Joint now uses a matrix for transform accumulation 7 years ago
Nehon 0e3ab8dd14 Draft of the new animation system 7 years ago
Nehon 4040a1e412 Adds an ArmatureDebugger 7 years ago
Nehon 3eb890da38 New Armature system 7 years ago
Nehon 6b69da3480 Allows build of 3.2 branch 7 years ago
  1. 2
      .gitignore
  2. 1
      .travis.yml
  3. 28
      jme3-bullet/src/common/java/com/jme3/bullet/control/KinematicRagdollControl.java
  4. 94
      jme3-core/src/main/java/com/jme3/anim/AnimClip.java
  5. 179
      jme3-core/src/main/java/com/jme3/anim/AnimComposer.java
  6. 12
      jme3-core/src/main/java/com/jme3/anim/AnimTrack.java
  7. 297
      jme3-core/src/main/java/com/jme3/anim/Armature.java
  8. 333
      jme3-core/src/main/java/com/jme3/anim/Joint.java
  9. 46
      jme3-core/src/main/java/com/jme3/anim/MatrixJointModelTransform.java
  10. 353
      jme3-core/src/main/java/com/jme3/anim/MorphControl.java
  11. 219
      jme3-core/src/main/java/com/jme3/anim/MorphTrack.java
  12. 43
      jme3-core/src/main/java/com/jme3/anim/SeparateJointModelTransform.java
  13. 744
      jme3-core/src/main/java/com/jme3/anim/SkinningControl.java
  14. 315
      jme3-core/src/main/java/com/jme3/anim/TransformTrack.java
  15. 95
      jme3-core/src/main/java/com/jme3/anim/Weights.java
  16. 13
      jme3-core/src/main/java/com/jme3/anim/interpolator/AnimInterpolator.java
  17. 149
      jme3-core/src/main/java/com/jme3/anim/interpolator/AnimInterpolators.java
  18. 136
      jme3-core/src/main/java/com/jme3/anim/interpolator/FrameInterpolator.java
  19. 97
      jme3-core/src/main/java/com/jme3/anim/tween/AbstractTween.java
  20. 6
      jme3-core/src/main/java/com/jme3/anim/tween/ContainsTweens.java
  21. 71
      jme3-core/src/main/java/com/jme3/anim/tween/Tween.java
  22. 619
      jme3-core/src/main/java/com/jme3/anim/tween/Tweens.java
  23. 46
      jme3-core/src/main/java/com/jme3/anim/tween/action/Action.java
  24. 33
      jme3-core/src/main/java/com/jme3/anim/tween/action/BaseAction.java
  25. 129
      jme3-core/src/main/java/com/jme3/anim/tween/action/BlendAction.java
  26. 10
      jme3-core/src/main/java/com/jme3/anim/tween/action/BlendSpace.java
  27. 92
      jme3-core/src/main/java/com/jme3/anim/tween/action/BlendableAction.java
  28. 86
      jme3-core/src/main/java/com/jme3/anim/tween/action/ClipAction.java
  29. 49
      jme3-core/src/main/java/com/jme3/anim/tween/action/LinearBlendSpace.java
  30. 200
      jme3-core/src/main/java/com/jme3/anim/util/AnimMigrationUtils.java
  31. 10
      jme3-core/src/main/java/com/jme3/anim/util/HasLocalTransform.java
  32. 20
      jme3-core/src/main/java/com/jme3/anim/util/JointModelTransform.java
  33. 56
      jme3-core/src/main/java/com/jme3/anim/util/Primitives.java
  34. 11
      jme3-core/src/main/java/com/jme3/anim/util/Weighted.java
  35. 2
      jme3-core/src/main/java/com/jme3/animation/AnimChannel.java
  36. 2
      jme3-core/src/main/java/com/jme3/animation/AnimControl.java
  37. 1
      jme3-core/src/main/java/com/jme3/animation/AnimEventListener.java
  38. 3
      jme3-core/src/main/java/com/jme3/animation/Animation.java
  39. 8
      jme3-core/src/main/java/com/jme3/animation/AudioTrack.java
  40. 2
      jme3-core/src/main/java/com/jme3/animation/Bone.java
  41. 2
      jme3-core/src/main/java/com/jme3/animation/BoneTrack.java
  42. 1
      jme3-core/src/main/java/com/jme3/animation/ClonableTrack.java
  43. 6
      jme3-core/src/main/java/com/jme3/animation/CompactArray.java
  44. 100
      jme3-core/src/main/java/com/jme3/animation/CompactFloatArray.java
  45. 15
      jme3-core/src/main/java/com/jme3/animation/EffectTrack.java
  46. 1
      jme3-core/src/main/java/com/jme3/animation/LoopMode.java
  47. 2
      jme3-core/src/main/java/com/jme3/animation/Pose.java
  48. 6
      jme3-core/src/main/java/com/jme3/animation/Skeleton.java
  49. 3
      jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java
  50. 6
      jme3-core/src/main/java/com/jme3/animation/SpatialTrack.java
  51. 1
      jme3-core/src/main/java/com/jme3/animation/Track.java
  52. 8
      jme3-core/src/main/java/com/jme3/animation/TrackInfo.java
  53. 17
      jme3-core/src/main/java/com/jme3/cinematic/events/AnimationEvent.java
  54. 7
      jme3-core/src/main/java/com/jme3/material/MatParamOverride.java
  55. 8
      jme3-core/src/main/java/com/jme3/material/Material.java
  56. 13
      jme3-core/src/main/java/com/jme3/math/EaseFunction.java
  57. 163
      jme3-core/src/main/java/com/jme3/math/Easing.java
  58. 247
      jme3-core/src/main/java/com/jme3/math/MathUtils.java
  59. 135
      jme3-core/src/main/java/com/jme3/math/Matrix4f.java
  60. 51
      jme3-core/src/main/java/com/jme3/math/Quaternion.java
  61. 31
      jme3-core/src/main/java/com/jme3/math/Transform.java
  62. 2
      jme3-core/src/main/java/com/jme3/renderer/RenderManager.java
  63. 87
      jme3-core/src/main/java/com/jme3/scene/Geometry.java
  64. 121
      jme3-core/src/main/java/com/jme3/scene/Mesh.java
  65. 3
      jme3-core/src/main/java/com/jme3/scene/Spatial.java
  66. 65
      jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java
  67. 208
      jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureDebugAppState.java
  68. 188
      jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureDebugger.java
  69. 124
      jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureInterJointsWire.java
  70. 332
      jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureNode.java
  71. 417
      jme3-core/src/main/java/com/jme3/scene/debug/custom/BoneShape.java
  72. 79
      jme3-core/src/main/java/com/jme3/scene/debug/custom/JointShape.java
  73. 238
      jme3-core/src/main/java/com/jme3/scene/debug/custom/SkeletonBone.java
  74. 156
      jme3-core/src/main/java/com/jme3/scene/debug/custom/SkeletonDebugAppState.java
  75. 218
      jme3-core/src/main/java/com/jme3/scene/debug/custom/SkeletonDebugger.java
  76. 141
      jme3-core/src/main/java/com/jme3/scene/debug/custom/SkeletonInterBoneWire.java
  77. 51
      jme3-core/src/main/java/com/jme3/scene/mesh/MorphTarget.java
  78. 22
      jme3-core/src/main/java/com/jme3/scene/shape/AbstractBox.java
  79. 9
      jme3-core/src/main/java/com/jme3/scene/shape/Box.java
  80. 9
      jme3-core/src/main/java/com/jme3/scene/shape/StripBox.java
  81. 4
      jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowRenderer.java
  82. 17
      jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md
  83. 10
      jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert
  84. 31
      jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md
  85. 15
      jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.vert
  86. 10
      jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert
  87. 78
      jme3-core/src/main/resources/Common/MatDefs/Misc/Billboard.j3md
  88. 59
      jme3-core/src/main/resources/Common/MatDefs/Misc/DashedLine.j3md
  89. 49
      jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md
  90. 6
      jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.vert
  91. 49
      jme3-core/src/main/resources/Common/MatDefs/Misc/fakeLighting.j3md
  92. 31
      jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/Mat3Vec3Mult.j3sn
  93. 3
      jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/Mat3Vec3Mult100.frag
  94. 35
      jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/Billboard.j3sn
  95. 19
      jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/Billboard100.frag
  96. 41
      jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/FixedScale.j3sn
  97. 8
      jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/FixedScale100.frag
  98. 57
      jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/texCoord.j3sn
  99. 2
      jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/texCoord100.frag
  100. 37
      jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Misc/Dashed.j3sn
  101. Some files were not shown because too many files have changed in this diff Show More

2
.gitignore vendored

@ -2,7 +2,7 @@
**/.classpath **/.classpath
**/.settings **/.settings
**/.project **/.project
**/out **/out/
/.gradle/ /.gradle/
/.nb-gradle/ /.nb-gradle/
/.idea/ /.idea/

@ -6,6 +6,7 @@ branches:
only: only:
- master - master
- v3.1 - v3.1
- /^v3.2.0-.*$/
matrix: matrix:
include: include:

@ -31,36 +31,23 @@
*/ */
package com.jme3.bullet.control; package com.jme3.bullet.control;
import com.jme3.animation.AnimControl; import com.jme3.animation.*;
import com.jme3.animation.Bone;
import com.jme3.animation.Skeleton;
import com.jme3.animation.SkeletonControl;
import com.jme3.bullet.PhysicsSpace; import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.collision.PhysicsCollisionEvent; import com.jme3.bullet.collision.*;
import com.jme3.bullet.collision.PhysicsCollisionListener;
import com.jme3.bullet.collision.PhysicsCollisionObject;
import com.jme3.bullet.collision.RagdollCollisionListener;
import com.jme3.bullet.collision.shapes.BoxCollisionShape; import com.jme3.bullet.collision.shapes.BoxCollisionShape;
import com.jme3.bullet.collision.shapes.HullCollisionShape; import com.jme3.bullet.collision.shapes.HullCollisionShape;
import com.jme3.bullet.control.ragdoll.HumanoidRagdollPreset; import com.jme3.bullet.control.ragdoll.*;
import com.jme3.bullet.control.ragdoll.RagdollPreset;
import com.jme3.bullet.control.ragdoll.RagdollUtils;
import com.jme3.bullet.joints.SixDofJoint; import com.jme3.bullet.joints.SixDofJoint;
import com.jme3.bullet.objects.PhysicsRigidBody; import com.jme3.bullet.objects.PhysicsRigidBody;
import com.jme3.export.InputCapsule; import com.jme3.export.*;
import com.jme3.export.JmeExporter; import com.jme3.math.*;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.export.Savable;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager; import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort; import com.jme3.renderer.ViewPort;
import com.jme3.scene.Node; import com.jme3.scene.Node;
import com.jme3.scene.Spatial; import com.jme3.scene.Spatial;
import com.jme3.util.TempVars; import com.jme3.util.TempVars;
import com.jme3.util.clone.JmeCloneable; import com.jme3.util.clone.JmeCloneable;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
import java.util.logging.Level; import java.util.logging.Level;
@ -91,7 +78,10 @@ import java.util.logging.Logger;
* </ul> </p> * </ul> </p>
* *
* @author Normen Hansen and Rémy Bouquet (Nehon) * @author Normen Hansen and Rémy Bouquet (Nehon)
*
* TODO this needs to be redone with the new animation system
*/ */
@Deprecated
public class KinematicRagdollControl extends AbstractPhysicsControl implements PhysicsCollisionListener, JmeCloneable { public class KinematicRagdollControl extends AbstractPhysicsControl implements PhysicsCollisionListener, JmeCloneable {
protected static final Logger logger = Logger.getLogger(KinematicRagdollControl.class.getName()); protected static final Logger logger = Logger.getLogger(KinematicRagdollControl.class.getName());

@ -0,0 +1,94 @@
package com.jme3.anim;
import com.jme3.anim.tween.Tween;
import com.jme3.export.*;
import com.jme3.util.SafeArrayList;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
/**
* Created by Nehon on 20/12/2017.
*/
public class AnimClip implements JmeCloneable, Savable {
private String name;
private double length;
private AnimTrack[] tracks;
public AnimClip() {
}
public AnimClip(String name) {
this.name = name;
}
public void setTracks(AnimTrack[] tracks) {
this.tracks = tracks;
for (AnimTrack track : tracks) {
if (track.getLength() > length) {
length = track.getLength();
}
}
}
public String getName() {
return name;
}
public double getLength() {
return length;
}
public AnimTrack[] getTracks() {
return tracks;
}
@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) {
AnimTrack[] newTracks = new AnimTrack[tracks.length];
for (int i = 0; i < tracks.length; i++) {
newTracks[i] = (cloner.clone(tracks[i]));
}
this.tracks = newTracks;
}
@Override
public void write(JmeExporter ex) throws IOException {
OutputCapsule oc = ex.getCapsule(this);
oc.write(name, "name", null);
oc.write(tracks, "tracks", null);
}
@Override
public void read(JmeImporter im) throws IOException {
InputCapsule ic = im.getCapsule(this);
name = ic.readString("name", null);
Savable[] arr = ic.readSavableArray("tracks", null);
if (arr != null) {
tracks = new AnimTrack[arr.length];
for (int i = 0; i < arr.length; i++) {
AnimTrack t = (AnimTrack) arr[i];
tracks[i] = t;
if (t.getLength() > length) {
length = t.getLength();
}
}
}
}
}

@ -0,0 +1,179 @@
package com.jme3.anim;
import com.jme3.anim.tween.Tween;
import com.jme3.anim.tween.Tweens;
import com.jme3.anim.tween.action.*;
import com.jme3.export.*;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.control.AbstractControl;
import com.jme3.util.clone.Cloner;
import java.io.IOException;
import java.util.*;
/**
* Created by Nehon on 20/12/2017.
*/
public class AnimComposer extends AbstractControl {
private Map<String, AnimClip> animClipMap = new HashMap<>();
private Action currentAction;
private Map<String, Action> actions = new HashMap<>();
private float globalSpeed = 1f;
private float time;
/**
* Retrieve an animation from the list of animations.
*
* @param name The name of the animation to retrieve.
* @return The animation corresponding to the given name, or null, if no
* such named animation exists.
*/
public AnimClip getAnimClip(String name) {
return animClipMap.get(name);
}
/**
* Adds an animation to be available for playing to this
* <code>AnimControl</code>.
*
* @param anim The animation to add.
*/
public void addAnimClip(AnimClip anim) {
animClipMap.put(anim.getName(), anim);
}
/**
* Remove an animation so that it is no longer available for playing.
*
* @param anim The animation to remove.
*/
public void removeAnimClip(AnimClip anim) {
if (!animClipMap.containsKey(anim.getName())) {
throw new IllegalArgumentException("Given animation does not exist "
+ "in this AnimControl");
}
animClipMap.remove(anim.getName());
}
public void setCurrentAction(String name) {
currentAction = action(name);
time = 0;
}
public Action action(String name) {
Action action = actions.get(name);
if (action == null) {
action = makeAction(name);
actions.put(name, action);
}
return action;
}
public Action makeAction(String name) {
Action action;
AnimClip clip = animClipMap.get(name);
if (clip == null) {
throw new IllegalArgumentException("Cannot find clip named " + name);
}
action = new ClipAction(clip);
return action;
}
public BaseAction actionSequence(String name, Tween... tweens) {
BaseAction action = new BaseAction(Tweens.sequence(tweens));
actions.put(name, action);
return action;
}
public BlendAction actionBlended(String name, BlendSpace blendSpace, String... clips) {
BlendableAction[] acts = new BlendableAction[clips.length];
for (int i = 0; i < acts.length; i++) {
BlendableAction ba = (BlendableAction) makeAction(clips[i]);
acts[i] = ba;
}
BlendAction action = new BlendAction(blendSpace, acts);
actions.put(name, action);
return action;
}
public void reset() {
currentAction = null;
time = 0;
}
public Collection<AnimClip> getAnimClips() {
return Collections.unmodifiableCollection(animClipMap.values());
}
public Collection<String> getAnimClipsNames() {
return Collections.unmodifiableCollection(animClipMap.keySet());
}
@Override
protected void controlUpdate(float tpf) {
if (currentAction != null) {
time += tpf;
boolean running = currentAction.interpolate(time * globalSpeed);
if (!running) {
time = 0;
}
}
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
}
public float getGlobalSpeed() {
return globalSpeed;
}
public void setGlobalSpeed(float globalSpeed) {
this.globalSpeed = globalSpeed;
}
@Override
public Object jmeClone() {
try {
AnimComposer clone = (AnimComposer) super.clone();
return clone;
} catch (CloneNotSupportedException ex) {
throw new AssertionError();
}
}
@Override
public void cloneFields(Cloner cloner, Object original) {
super.cloneFields(cloner, original);
Map<String, AnimClip> clips = new HashMap<>();
for (String key : animClipMap.keySet()) {
clips.put(key, cloner.clone(animClipMap.get(key)));
}
Map<String, Action> act = new HashMap<>();
for (String key : actions.keySet()) {
act.put(key, cloner.clone(actions.get(key)));
}
actions = act;
animClipMap = clips;
}
@Override
public void read(JmeImporter im) throws IOException {
super.read(im);
InputCapsule ic = im.getCapsule(this);
animClipMap = (Map<String, AnimClip>) ic.readStringSavableMap("animClipMap", new HashMap<String, AnimClip>());
}
@Override
public void write(JmeExporter ex) throws IOException {
super.write(ex);
OutputCapsule oc = ex.getCapsule(this);
oc.writeStringSavableMap(animClipMap, "animClipMap", new HashMap<String, AnimClip>());
}
}

@ -0,0 +1,12 @@
package com.jme3.anim;
import com.jme3.export.Savable;
import com.jme3.util.clone.JmeCloneable;
public interface AnimTrack<T> extends Savable, JmeCloneable {
public void getDataAtTime(double time, T store);
public double getLength();
}

@ -0,0 +1,297 @@
package com.jme3.anim;
import com.jme3.anim.util.JointModelTransform;
import com.jme3.asset.AssetLoadException;
import com.jme3.export.*;
import com.jme3.math.Matrix4f;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
import java.util.*;
/**
* Created by Nehon on 15/12/2017.
*/
public class Armature implements JmeCloneable, Savable {
private Joint[] rootJoints;
private Joint[] jointList;
/**
* Contains the skinning matrices, multiplying it by a vertex effected by a bone
* will cause it to go to the animated position.
*/
private transient Matrix4f[] skinningMatrixes;
private Class<? extends JointModelTransform> modelTransformClass = SeparateJointModelTransform.class;
/**
* Serialization only
*/
public Armature() {
}
/**
* Creates an armature from a joint list.
* The root joints are found automatically.
* <p>
* Note that using this constructor will cause the joints in the list
* to have their bind pose recomputed based on their local transforms.
*
* @param jointList The list of joints to manage by this Armature
*/
public Armature(Joint[] jointList) {
this.jointList = jointList;
List<Joint> rootJointList = new ArrayList<>();
for (int i = jointList.length - 1; i >= 0; i--) {
Joint joint = jointList[i];
instanciateJointModelTransform(joint);
if (joint.getParent() == null) {
rootJointList.add(joint);
}
}
rootJoints = rootJointList.toArray(new Joint[rootJointList.size()]);
createSkinningMatrices();
for (int i = rootJoints.length - 1; i >= 0; i--) {
Joint rootJoint = rootJoints[i];
rootJoint.update();
}
}
/**
* Update all joints sin this Amature.
*/
public void update() {
for (Joint rootJoint : rootJoints) {
rootJoint.update();
}
}
private void createSkinningMatrices() {
skinningMatrixes = new Matrix4f[jointList.length];
for (int i = 0; i < skinningMatrixes.length; i++) {
skinningMatrixes[i] = new Matrix4f();
}
}
/**
* Sets the JointModelTransform implementation
* Default is {@link MatrixJointModelTransform}
*
* @param modelTransformClass
* @see {@link JointModelTransform},{@link MatrixJointModelTransform},{@link SeparateJointModelTransform},
*/
public void setModelTransformClass(Class<? extends JointModelTransform> modelTransformClass) {
this.modelTransformClass = modelTransformClass;
if (jointList == null) {
return;
}
for (Joint joint : jointList) {
instanciateJointModelTransform(joint);
}
}
private void instanciateJointModelTransform(Joint joint) {
try {
joint.setJointModelTransform(modelTransformClass.newInstance());
} catch (InstantiationException | IllegalAccessException e) {
throw new IllegalArgumentException(e);
}
}
/**
* returns the array of all root joints of this Armature
*
* @return
*/
public Joint[] getRoots() {
return rootJoints;
}
public List<Joint> getJointList() {
return Arrays.asList(jointList);
}
/**
* return a joint for the given index
*
* @param index
* @return
*/
public Joint getJoint(int index) {
return jointList[index];
}
/**
* returns the joint with the given name
*
* @param name
* @return
*/
public Joint getJoint(String name) {
for (int i = 0; i < jointList.length; i++) {
if (jointList[i].getName().equals(name)) {
return jointList[i];
}
}
return null;
}
/**
* returns the bone index of the given bone
*
* @param joint
* @return
*/
public int getJointIndex(Joint joint) {
for (int i = 0; i < jointList.length; i++) {
if (jointList[i] == joint) {
return i;
}
}
return -1;
}
/**
* returns the joint index of the joint that has the given name
*
* @param name
* @return
*/
public int getJointIndex(String name) {
for (int i = 0; i < jointList.length; i++) {
if (jointList[i].getName().equals(name)) {
return i;
}
}
return -1;
}
/**
* Saves the current Armature state as its bind pose.
* Note that the bind pose is supposed to be the one where the armature is aligned with the mesh to deform.
* Saving this pose will affect how skinning works.
*/
public void saveBindPose() {
//make sure all bones are updated
update();
//Save the current pose as bind pose
for (Joint joint : jointList) {
joint.saveBindPose();
}
}
/**
* This methods sets this armature in its bind pose (aligned with the mesh to deform)
* Note that this is only useful for debugging purpose.
*/
public void applyBindPose() {
for (Joint joint : rootJoints) {
joint.applyBindPose();
}
}
/**
* Saves the current local transform as the initial transform.
* Initial transform is the one applied to the armature when loaded.
*/
public void saveInitialPose() {
for (Joint joint : jointList) {
joint.saveInitialPose();
}
}
/**
* Applies the initial pose to this armature
*/
public void applyInitialPose() {
for (Joint rootJoint : rootJoints) {
rootJoint.applyInitialPose();
}
}
/**
* Compute the skinning matrices for each bone of the armature that would be used to transform vertices of associated meshes
*
* @return
*/
public Matrix4f[] computeSkinningMatrices() {
for (int i = 0; i < jointList.length; i++) {
jointList[i].getOffsetTransform(skinningMatrixes[i]);
}
return skinningMatrixes;
}
/**
* returns the number of joints of this armature
*
* @return
*/
public int getJointCount() {
return jointList.length;
}
@Override
public Object jmeClone() {
try {
Armature clone = (Armature) super.clone();
return clone;
} catch (CloneNotSupportedException ex) {
throw new AssertionError();
}
}
@Override
public void cloneFields(Cloner cloner, Object original) {
this.rootJoints = cloner.clone(rootJoints);
this.jointList = cloner.clone(jointList);
this.skinningMatrixes = cloner.clone(skinningMatrixes);
for (Joint joint : jointList) {
instanciateJointModelTransform(joint);
}
}
@Override
public void read(JmeImporter im) throws IOException {
InputCapsule input = im.getCapsule(this);
Savable[] jointRootsAsSavable = input.readSavableArray("rootJoints", null);
rootJoints = new Joint[jointRootsAsSavable.length];
System.arraycopy(jointRootsAsSavable, 0, rootJoints, 0, jointRootsAsSavable.length);
Savable[] jointListAsSavable = input.readSavableArray("jointList", null);
jointList = new Joint[jointListAsSavable.length];
System.arraycopy(jointListAsSavable, 0, jointList, 0, jointListAsSavable.length);
String className = input.readString("modelTransformClass", MatrixJointModelTransform.class.getCanonicalName());
try {
modelTransformClass = (Class<? extends JointModelTransform>) Class.forName(className);
} catch (ClassNotFoundException e) {
throw new AssetLoadException("Cannnot find class for name " + className);
}
for (Joint joint : jointList) {
instanciateJointModelTransform(joint);
}
createSkinningMatrices();
for (Joint rootJoint : rootJoints) {
rootJoint.update();
}
applyInitialPose();
}
@Override
public void write(JmeExporter ex) throws IOException {
OutputCapsule output = ex.getCapsule(this);
output.write(rootJoints, "rootJoints", null);
output.write(jointList, "jointList", null);
output.write(modelTransformClass.getCanonicalName(), "modelTransformClass", MatrixJointModelTransform.class.getCanonicalName());
}
}

@ -0,0 +1,333 @@
package com.jme3.anim;
import com.jme3.anim.util.HasLocalTransform;
import com.jme3.anim.util.JointModelTransform;
import com.jme3.export.*;
import com.jme3.material.MatParamOverride;
import com.jme3.math.*;
import com.jme3.scene.*;
import com.jme3.shader.VarType;
import com.jme3.util.SafeArrayList;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* A Joint is the basic component of an armature designed to perform skeletal animation
* Created by Nehon on 15/12/2017.
*/
public class Joint implements Savable, JmeCloneable, HasLocalTransform {
private String name;
private Joint parent;
private SafeArrayList<Joint> children = new SafeArrayList<>(Joint.class);
private Geometry targetGeometry;
/**
* The attachment node.
*/
private Node attachedNode;
/**
* The transform of the joint in local space. Relative to its parent.
* Or relative to the model's origin for the root joint.
*/
private Transform localTransform = new Transform();
/**
* The initial transform of the joint in local space. Relative to its parent.
* Or relative to the model's origin for the root joint.
* this transform is the transform applied when the armature is loaded.
*/
private Transform initialTransform = new Transform();
/**
* The transform of the joint in model space. Relative to the origin of the model.
* this is either a MatrixJointModelTransform or a SeparateJointModelTransform
*/
private JointModelTransform jointModelTransform;
/**
* The matrix used to transform affected vertices position into the joint model space.
* Used for skinning.
*/
private Matrix4f inverseModelBindMatrix = new Matrix4f();
public Joint() {
}
public Joint(String name) {
this.name = name;
}
/**
* Updates world transforms for this bone and it's children.
*/
public final void update() {
this.updateModelTransforms();
for (Joint child : children.getArray()) {
child.update();
}
}
/**
* Updates the model transforms for this bone, and, possibly the attach node
* if not null.
* <p>
* The model transform of this bone is computed by combining the parent's
* model transform with this bones' local transform.
*/
public final void updateModelTransforms() {
jointModelTransform.updateModelTransform(localTransform, parent);
updateAttachNode();
}
/**
* Update the local transform of the attachments node.
*/
private void updateAttachNode() {
if (attachedNode == null) {
return;
}
Node attachParent = attachedNode.getParent();
if (attachParent == null || targetGeometry == null
|| targetGeometry.getParent() == attachParent
&& targetGeometry.getLocalTransform().isIdentity()) {
/*
* The animated meshes are in the same coordinate system as the
* attachments node: no further transforms are needed.
*/
attachedNode.setLocalTransform(getModelTransform());
} else {
Spatial loopSpatial = targetGeometry;
Transform combined = getModelTransform().clone();
/*
* Climb the scene graph applying local transforms until the
* attachments node's parent is reached.
*/
while (loopSpatial != attachParent && loopSpatial != null) {
Transform localTransform = loopSpatial.getLocalTransform();
combined.combineWithParent(localTransform);
loopSpatial = loopSpatial.getParent();
}
attachedNode.setLocalTransform(combined);
}
}
/**
* Stores the skinning transform in the specified Matrix4f.
* The skinning transform applies the animation of the bone to a vertex.
* <p>
* This assumes that the world transforms for the entire bone hierarchy
* have already been computed, otherwise this method will return undefined
* results.
*
* @param outTransform
*/
void getOffsetTransform(Matrix4f outTransform) {
jointModelTransform.getOffsetTransform(outTransform, inverseModelBindMatrix);
}
/**
* Sets the current localTransform as the Bind transform.
*/
protected void saveBindPose() {
//Note that the whole Armature must be updated before calling this method.
getModelTransform().toTransformMatrix(inverseModelBindMatrix);
inverseModelBindMatrix.invertLocal();
}
/**
* Sets the current local transforms as the initial transform.
*/
protected void saveInitialPose() {
initialTransform.set(localTransform);
}
/**
* Sets the local transform with the bind transforms
*/
protected void applyBindPose() {
jointModelTransform.applyBindPose(localTransform, inverseModelBindMatrix, parent);
updateModelTransforms();
for (Joint child : children.getArray()) {
child.applyBindPose();
}
}
/**
* Sets the local transform with the initial transform
*/
protected void applyInitialPose() {
setLocalTransform(initialTransform);
updateModelTransforms();
for (Joint child : children.getArray()) {
child.applyInitialPose();
}
}
protected JointModelTransform getJointModelTransform() {
return jointModelTransform;
}
protected void setJointModelTransform(JointModelTransform jointModelTransform) {
this.jointModelTransform = jointModelTransform;
}
public Vector3f getLocalTranslation() {
return localTransform.getTranslation();
}
public Quaternion getLocalRotation() {
return localTransform.getRotation();
}
public Vector3f getLocalScale() {
return localTransform.getScale();
}
public void setLocalTranslation(Vector3f translation) {
localTransform.setTranslation(translation);
}
public void setLocalRotation(Quaternion rotation) {
localTransform.setRotation(rotation);
}
public void setLocalScale(Vector3f scale) {
localTransform.setScale(scale);
}
public void addChild(Joint child) {
children.add(child);
child.parent = this;
}
public void setName(String name) {
this.name = name;
}
public void setLocalTransform(Transform localTransform) {
this.localTransform.set(localTransform);
}
public void setInverseModelBindMatrix(Matrix4f inverseModelBindMatrix) {
this.inverseModelBindMatrix = inverseModelBindMatrix;
}
public String getName() {
return name;
}
public Joint getParent() {
return parent;
}
public List<Joint> getChildren() {
return children;
}
/**
* Access the attachments node of this joint. If this joint doesn't already
* have an attachments node, create one. Models and effects attached to the
* attachments node will follow this bone's motions.
*
* @param jointIndex this bone's index in its armature (&ge;0)
* @param targets a list of geometries animated by this bone's skeleton (not
* null, unaffected)
*/
Node getAttachmentsNode(int jointIndex, SafeArrayList<Geometry> targets) {
targetGeometry = null;
/*
* Search for a geometry animated by this particular bone.
*/
for (Geometry geometry : targets) {
Mesh mesh = geometry.getMesh();
if (mesh != null && mesh.isAnimatedByJoint(jointIndex)) {
targetGeometry = geometry;
break;
}
}
if (attachedNode == null) {
attachedNode = new Node(name + "_attachnode");
attachedNode.setUserData("AttachedBone", this);
//We don't want the node to have a numBone set by a parent node so we force it to null
attachedNode.addMatParamOverride(new MatParamOverride(VarType.Int, "NumberOfBones", null));
}
return attachedNode;
}
public Transform getLocalTransform() {
return localTransform;
}
public Transform getModelTransform() {
return jointModelTransform.getModelTransform();
}
public Matrix4f getInverseModelBindMatrix() {
return inverseModelBindMatrix;
}
@Override
public Object jmeClone() {
try {
Joint clone = (Joint) super.clone();
return clone;
} catch (CloneNotSupportedException ex) {
throw new AssertionError();
}
}
@Override
public void cloneFields(Cloner cloner, Object original) {
this.children = cloner.clone(children);
this.parent = cloner.clone(parent);
this.attachedNode = cloner.clone(attachedNode);
this.targetGeometry = cloner.clone(targetGeometry);
this.localTransform = cloner.clone(localTransform);
this.inverseModelBindMatrix = cloner.clone(inverseModelBindMatrix);
}
@Override
@SuppressWarnings("unchecked")
public void read(JmeImporter im) throws IOException {
InputCapsule input = im.getCapsule(this);
name = input.readString("name", null);
attachedNode = (Node) input.readSavable("attachedNode", null);
targetGeometry = (Geometry) input.readSavable("targetGeometry", null);
initialTransform = (Transform) input.readSavable("initialTransform", new Transform());
inverseModelBindMatrix = (Matrix4f) input.readSavable("inverseModelBindMatrix", inverseModelBindMatrix);
ArrayList<Joint> childList = input.readSavableArrayList("children", null);
for (int i = childList.size() - 1; i >= 0; i--) {
this.addChild(childList.get(i));
}
}
@Override
public void write(JmeExporter ex) throws IOException {
OutputCapsule output = ex.getCapsule(this);
output.write(name, "name", null);
output.write(attachedNode, "attachedNode", null);
output.write(targetGeometry, "targetGeometry", null);
output.write(initialTransform, "initialTransform", new Transform());
output.write(inverseModelBindMatrix, "inverseModelBindMatrix", new Matrix4f());
output.writeSavableArrayList(new ArrayList(children), "children", null);
}
}

@ -0,0 +1,46 @@
package com.jme3.anim;
import com.jme3.anim.util.JointModelTransform;
import com.jme3.math.Matrix4f;
import com.jme3.math.Transform;
/**
* This JointModelTransform implementation accumulate joints transforms in a Matrix4f to properly
* support non uniform scaling in an armature hierarchy
*/
public class MatrixJointModelTransform implements JointModelTransform {
private Matrix4f modelTransformMatrix = new Matrix4f();
private Transform modelTransform = new Transform();
@Override
public void updateModelTransform(Transform localTransform, Joint parent) {
localTransform.toTransformMatrix(modelTransformMatrix);
if (parent != null) {
((MatrixJointModelTransform) parent.getJointModelTransform()).getModelTransformMatrix().mult(modelTransformMatrix, modelTransformMatrix);
}
modelTransform.fromTransformMatrix(modelTransformMatrix);
}
public void getOffsetTransform(Matrix4f outTransform, Matrix4f inverseModelBindMatrix) {
outTransform.set(modelTransformMatrix).mult(inverseModelBindMatrix, outTransform);
}
@Override
public void applyBindPose(Transform localTransform, Matrix4f inverseModelBindMatrix, Joint parent) {
modelTransformMatrix.set(inverseModelBindMatrix).invertLocal(); // model transform = model bind
if (parent != null) {
((MatrixJointModelTransform) parent.getJointModelTransform()).getModelTransformMatrix().invert().mult(modelTransformMatrix, modelTransformMatrix);
}
localTransform.fromTransformMatrix(modelTransformMatrix);
}
public Matrix4f getModelTransformMatrix() {
return modelTransformMatrix;
}
@Override
public Transform getModelTransform() {
return modelTransform;
}
}

@ -0,0 +1,353 @@
package com.jme3.anim;
import com.jme3.export.Savable;
import com.jme3.material.*;
import com.jme3.renderer.*;
import com.jme3.scene.*;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.mesh.MorphTarget;
import com.jme3.shader.VarType;
import com.jme3.util.BufferUtils;
import com.jme3.util.SafeArrayList;
import java.nio.FloatBuffer;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A control that handle morph animation for Position, Normal and Tangent buffers.
* All stock shaders only support morphing these 3 buffers, but note that MorphTargets can have any type of buffers.
* If you want to use other types of buffers you will need a custom MorphControl and a custom shader.
*
* @author Rémy Bouquet
*/
public class MorphControl extends AbstractControl implements Savable {
private static final Logger logger = Logger.getLogger(MorphControl.class.getName());
private static final int MAX_MORPH_BUFFERS = 14;
private final static float MIN_WEIGHT = 0.005f;
private SafeArrayList<Geometry> targets = new SafeArrayList<>(Geometry.class);
private TargetLocator targetLocator = new TargetLocator();
private boolean approximateTangents = true;
private MatParamOverride nullNumberOfBones = new MatParamOverride(VarType.Int, "NumberOfBones", null);
private float[] tmpPosArray;
private float[] tmpNormArray;
private float[] tmpTanArray;
private static final VertexBuffer.Type bufferTypes[] = VertexBuffer.Type.values();
@Override
protected void controlUpdate(float tpf) {
if (!enabled) {
return;
}
// gathering geometries in the sub graph.
// This must be done in the update phase as the gathering might add a matparam override
targets.clear();
this.spatial.depthFirstTraversal(targetLocator);
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
if (!enabled) {
return;
}
for (Geometry geom : targets) {
Mesh mesh = geom.getMesh();
if (!geom.isDirtyMorph()) {
continue;
}
Material m = geom.getMaterial();
float weights[] = geom.getMorphState();
MorphTarget morphTargets[] = mesh.getMorphTargets();
float matWeights[];
//Number of buffer to handle for each morph target
int targetNumBuffers = getTargetNumBuffers(morphTargets[0]);
int maxGPUTargets = getMaxGPUTargets(rm, geom, m, targetNumBuffers);
MatParam param2 = m.getParam("MorphWeights");
matWeights = (float[]) param2.getValue();
int nbGPUTargets = 0;
int lastGpuTargetIndex = 0;
int boundBufferIdx = 0;
float cpuWeightSum = 0;
// binding the morphTargets buffer to the mesh morph buffers
for (int i = 0; i < morphTargets.length; i++) {
// discard weights below the threshold
if (weights[i] < MIN_WEIGHT) {
continue;
}
if (nbGPUTargets >= maxGPUTargets) {
// we already bound all the available gpu slots we need to merge the remaining morph targets.
cpuWeightSum += weights[i];
continue;
}
lastGpuTargetIndex = i;
// binding the morph target's buffers to the mesh morph buffers.
MorphTarget t = morphTargets[i];
boundBufferIdx = bindMorphtargetBuffer(mesh, targetNumBuffers, boundBufferIdx, t);
// setting the weight in the mat param array
matWeights[nbGPUTargets] = weights[i];
nbGPUTargets++;
}
if (nbGPUTargets < matWeights.length) {
// if we have less simultaneous GPU targets than the length of the weight array, the array is padded with 0
for (int i = nbGPUTargets; i < matWeights.length; i++) {
matWeights[i] = 0;
}
} else if (cpuWeightSum > 0) {
// we have more simultaneous morph targets than available gpu slots,
// we merge the additional morph targets and bind them to the last gpu slot
MorphTarget mt = geom.getFallbackMorphTarget();
if (mt == null) {
mt = initCpuMorphTarget(geom);
geom.setFallbackMorphTarget(mt);
}
// adding the last Gpu target weight
cpuWeightSum += matWeights[nbGPUTargets - 1];
ensureTmpArraysCapacity(geom.getVertexCount() * 3, targetNumBuffers);
// merging all remaining targets in tmp arrays
for (int i = lastGpuTargetIndex; i < morphTargets.length; i++) {
if (weights[i] < MIN_WEIGHT) {
continue;
}
float weight = weights[i] / cpuWeightSum;
MorphTarget t = geom.getMesh().getMorphTargets()[i];
mergeMorphTargets(targetNumBuffers, weight, t, i == lastGpuTargetIndex);
}
// writing the tmp arrays to the float buffer
writeCpuBuffer(targetNumBuffers, mt);
// binding the merged morph target
bindMorphtargetBuffer(mesh, targetNumBuffers, (nbGPUTargets - 1) * targetNumBuffers, mt);
// setting the eight of the merged targets
matWeights[nbGPUTargets - 1] = cpuWeightSum;
}
geom.setDirtyMorph(false);
}
}
private int getMaxGPUTargets(RenderManager rm, Geometry geom, Material mat, int targetNumBuffers) {
if (geom.getNbSimultaneousGPUMorph() > -1) {
return geom.getNbSimultaneousGPUMorph();
}
// Evaluate the number of CPU slots remaining for morph buffers.
int nbMaxBuffers = getRemainingBuffers(geom.getMesh(), rm.getRenderer());
int realNumTargetsBuffers = geom.getMesh().getMorphTargets().length * targetNumBuffers;
// compute the max number of targets to send to the GPU
int maxGPUTargets = Math.min(realNumTargetsBuffers, Math.min(nbMaxBuffers, MAX_MORPH_BUFFERS)) / targetNumBuffers;
MatParam param = mat.getParam("MorphWeights");
if (param == null) {
// init the mat param if it doesn't exists.
float[] wts = new float[maxGPUTargets];
mat.setParam("MorphWeights", VarType.FloatArray, wts);
}
mat.setInt("NumberOfTargetsBuffers", targetNumBuffers);
// test compile the shader to find the accurate number of remaining attributes slots
boolean compilationOk = false;
// Note that if ever the shader has an unrelated issue we want to break at some point, hence the maxGPUTargets > 0
while (!compilationOk && maxGPUTargets > 0) {
// setting the maximum number as the real number may change every frame and trigger a shader recompilation since it's bound to a define.
mat.setInt("NumberOfMorphTargets", maxGPUTargets);
try {
// preload the spatial. this will trigger a shader compilation that will fail if the number of attributes is over the limit.
rm.preloadScene(spatial);
compilationOk = true;
} catch (RendererException e) {
logger.log(Level.FINE, geom.getName() + ": failed at " + maxGPUTargets);
// the compilation failed let's decrement the number of targets an try again.
maxGPUTargets--;
}
}
logger.log(Level.FINE, geom.getName() + ": " + maxGPUTargets);
// set the number of GPU morph on the geom to not have to recompute it next frame.
geom.setNbSimultaneousGPUMorph(maxGPUTargets);
return maxGPUTargets;
}
private int bindMorphtargetBuffer(Mesh mesh, int targetNumBuffers, int boundBufferIdx, MorphTarget t) {
int start = VertexBuffer.Type.MorphTarget0.ordinal();
if (targetNumBuffers >= 1) {
activateBuffer(mesh, boundBufferIdx, start, t.getBuffer(VertexBuffer.Type.Position));
boundBufferIdx++;
}
if (targetNumBuffers >= 2) {
activateBuffer(mesh, boundBufferIdx, start, t.getBuffer(VertexBuffer.Type.Normal));
boundBufferIdx++;
}
if (!approximateTangents && targetNumBuffers == 3) {
activateBuffer(mesh, boundBufferIdx, start, t.getBuffer(VertexBuffer.Type.Tangent));
boundBufferIdx++;
}
return boundBufferIdx;
}
private void writeCpuBuffer(int targetNumBuffers, MorphTarget mt) {
if (targetNumBuffers >= 1) {
FloatBuffer dest = mt.getBuffer(VertexBuffer.Type.Position);
dest.rewind();
dest.put(tmpPosArray, 0, dest.capacity());
}
if (targetNumBuffers >= 2) {
FloatBuffer dest = mt.getBuffer(VertexBuffer.Type.Normal);
dest.rewind();
dest.put(tmpNormArray, 0, dest.capacity());
}
if (!approximateTangents && targetNumBuffers == 3) {
FloatBuffer dest = mt.getBuffer(VertexBuffer.Type.Tangent);
dest.rewind();
dest.put(tmpTanArray, 0, dest.capacity());
}
}
private void mergeMorphTargets(int targetNumBuffers, float weight, MorphTarget t, boolean init) {
if (targetNumBuffers >= 1) {
mergeTargetBuffer(tmpPosArray, weight, t.getBuffer(VertexBuffer.Type.Position), init);
}
if (targetNumBuffers >= 2) {
mergeTargetBuffer(tmpNormArray, weight, t.getBuffer(VertexBuffer.Type.Normal), init);
}
if (!approximateTangents && targetNumBuffers == 3) {
mergeTargetBuffer(tmpTanArray, weight, t.getBuffer(VertexBuffer.Type.Tangent), init);
}
}
private void ensureTmpArraysCapacity(int capacity, int targetNumBuffers) {
if (targetNumBuffers >= 1) {
tmpPosArray = ensureCapacity(tmpPosArray, capacity);
}
if (targetNumBuffers >= 2) {
tmpNormArray = ensureCapacity(tmpNormArray, capacity);
}
if (!approximateTangents && targetNumBuffers == 3) {
tmpTanArray = ensureCapacity(tmpTanArray, capacity);
}
}
private void mergeTargetBuffer(float[] array, float weight, FloatBuffer src, boolean init) {
src.rewind();
for (int j = 0; j < src.capacity(); j++) {
if (init) {
array[j] = 0;
}
array[j] += weight * src.get();
}
}
private void activateBuffer(Mesh mesh, int idx, int start, FloatBuffer b) {
VertexBuffer.Type t = bufferTypes[start + idx];
VertexBuffer vb = mesh.getBuffer(t);
// only set the buffer if it's different
if (vb == null || vb.getData() != b) {
mesh.setBuffer(t, 3, b);
}
}
private float[] ensureCapacity(float[] tmpArray, int size) {
if (tmpArray == null || tmpArray.length < size) {
return new float[size];
}
return tmpArray;
}
private MorphTarget initCpuMorphTarget(Geometry geom) {
MorphTarget res = new MorphTarget();
MorphTarget mt = geom.getMesh().getMorphTargets()[0];
FloatBuffer b = mt.getBuffer(VertexBuffer.Type.Position);
if (b != null) {
res.setBuffer(VertexBuffer.Type.Position, BufferUtils.createFloatBuffer(b.capacity()));
}
b = mt.getBuffer(VertexBuffer.Type.Normal);
if (b != null) {
res.setBuffer(VertexBuffer.Type.Normal, BufferUtils.createFloatBuffer(b.capacity()));
}
if (!approximateTangents) {
b = mt.getBuffer(VertexBuffer.Type.Tangent);
if (b != null) {
res.setBuffer(VertexBuffer.Type.Tangent, BufferUtils.createFloatBuffer(b.capacity()));
}
}
return res;
}
private int getTargetNumBuffers(MorphTarget morphTarget) {
int num = 0;
if (morphTarget.getBuffer(VertexBuffer.Type.Position) != null) num++;
if (morphTarget.getBuffer(VertexBuffer.Type.Normal) != null) num++;
// if tangents are not needed we don't count the tangent buffer
if (!approximateTangents && morphTarget.getBuffer(VertexBuffer.Type.Tangent) != null) {
num++;
}
return num;
}
/**
* Computes the number of remaining buffers on this mesh.
* This is supposed to give a hint on how many attributes will be used in the material and computes the remaining available slots for the morph attributes.
* However, the shader can declare attributes that are not used and not bound to a real buffer.
* That's why we attempt to compile the shader later on to avoid any compilation crash.
* This method is here to avoid too much render test iteration.
*
* @param mesh
* @param renderer
* @return
*/
private int getRemainingBuffers(Mesh mesh, Renderer renderer) {
int nbUsedBuffers = 0;
for (VertexBuffer vb : mesh.getBufferList().getArray()) {
boolean isMorphBuffer = vb.getBufferType().ordinal() >= VertexBuffer.Type.MorphTarget0.ordinal() && vb.getBufferType().ordinal() <= VertexBuffer.Type.MorphTarget9.ordinal();
if (vb.getBufferType() == VertexBuffer.Type.Index || isMorphBuffer) continue;
if (vb.getUsage() != VertexBuffer.Usage.CpuOnly) {
nbUsedBuffers++;
}
}
return renderer.getLimits().get(Limits.VertexAttributes) - nbUsedBuffers;
}
public void setApproximateTangents(boolean approximateTangents) {
this.approximateTangents = approximateTangents;
}
public boolean isApproximateTangents() {
return approximateTangents;
}
private class TargetLocator extends SceneGraphVisitorAdapter {
@Override
public void visit(Geometry geom) {
MatParam p = geom.getMaterial().getMaterialDef().getMaterialParam("MorphWeights");
if (p == null) {
return;
}
Mesh mesh = geom.getMesh();
if (mesh != null && mesh.hasMorphTargets()) {
targets.add(geom);
// If the mesh is in a subgraph of a node with a SkinningControl it might have hardware skinning activated through mat param override even if it's not skinned.
// this code makes sure that if the mesh has no hardware skinning buffers hardware skinning won't be activated.
// this is important, because if HW skinning is activated the shader will declare 2 additional useless attributes,
// and we desperately need all the attributes we can find for Morph animation.
if (mesh.getBuffer(VertexBuffer.Type.HWBoneIndex) == null && !geom.getLocalMatParamOverrides().contains(nullNumberOfBones)) {
geom.addMatParamOverride(nullNumberOfBones);
}
}
}
}
}

@ -0,0 +1,219 @@
/*
* 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.anim.interpolator.FrameInterpolator;
import com.jme3.animation.*;
import com.jme3.export.*;
import com.jme3.scene.Geometry;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
/**
* Contains a list of weights and times for each keyframe.
*
* @author Rémy Bouquet
*/
public class MorphTrack implements AnimTrack<float[]> {
private double length;
private Geometry target;
/**
* Weights and times for track.
*/
private float[] weights;
private FrameInterpolator interpolator = FrameInterpolator.DEFAULT;
private float[] times;
private int nbMorphTargets;
/**
* Serialization-only. Do not use.
*/
public MorphTrack() {
}
/**
* Creates a morph track with the given Geometry as a target
*
* @param times a float array with the time of each frame
* @param weights the morphs for each frames
*/
public MorphTrack(Geometry target, float[] times, float[] weights, int nbMorphTargets) {
this.target = target;
this.nbMorphTargets = nbMorphTargets;
this.setKeyframes(times, weights);
}
/**
* return the array of weights of this track
*
* @return
*/
public float[] getWeights() {
return weights;
}
/**
* returns the arrays of time for this track
*
* @return
*/
public float[] getTimes() {
return times;
}
/**
* Sets the keyframes times for this Joint track
*
* @param times the keyframes times
*/
public void setTimes(float[] times) {
if (times.length == 0) {
throw new RuntimeException("TransformTrack with no keyframes!");
}
this.times = times;
length = times[times.length - 1] - times[0];
}
/**
* Set the weight for this morph track
*
* @param times a float array with the time of each frame
* @param weights the weights of the morphs for each frame
*/
public void setKeyframes(float[] times, float[] weights) {
setTimes(times);
if (weights != null) {
if (times == null) {
throw new RuntimeException("MorphTrack doesn't have any time for key frames, please call setTimes first");
}
this.weights = weights;
assert times != null && times.length == weights.length;
}
}
@Override
public double getLength() {
return length;
}
@Override
public void getDataAtTime(double t, float[] store) {
float time = (float) t;
int lastFrame = times.length - 1;
if (time < 0 || lastFrame == 0) {
if (weights != null) {
System.arraycopy(weights,0,store,0, nbMorphTargets);
}
return;
}
int startFrame = 0;
int endFrame = 1;
float blend = 0;
if (time >= times[lastFrame]) {
startFrame = lastFrame;
time = time - times[startFrame] + times[startFrame - 1];
blend = (time - times[startFrame - 1])
/ (times[startFrame] - times[startFrame - 1]);
} else {
// use lastFrame so we never overflow the array
int i;
for (i = 0; i < lastFrame && times[i] < time; i++) {
startFrame = i;
endFrame = i + 1;
}
blend = (time - times[startFrame])
/ (times[endFrame] - times[startFrame]);
}
interpolator.interpolateWeights(blend, startFrame, weights, nbMorphTargets, store);
}
public void setFrameInterpolator(FrameInterpolator interpolator) {
this.interpolator = interpolator;
}
public Geometry getTarget() {
return target;
}
public void setTarget(Geometry target) {
this.target = target;
}
@Override
public void write(JmeExporter ex) throws IOException {
OutputCapsule oc = ex.getCapsule(this);
oc.write(weights, "weights", null);
oc.write(times, "times", null);
oc.write(target, "target", null);
oc.write(nbMorphTargets, "nbMorphTargets", 0);
}
@Override
public void read(JmeImporter im) throws IOException {
InputCapsule ic = im.getCapsule(this);
weights = ic.readFloatArray("weights", null);
times = ic.readFloatArray("times", null);
target = (Geometry) ic.readSavable("target", null);
nbMorphTargets = ic.readInt("nbMorphTargets", 0);
setTimes(times);
}
@Override
public Object jmeClone() {
try {
MorphTrack clone = (MorphTrack) super.clone();
return clone;
} catch (CloneNotSupportedException ex) {
throw new AssertionError();
}
}
@Override
public void cloneFields(Cloner cloner, Object original) {
this.target = cloner.clone(target);
}
}

@ -0,0 +1,43 @@
package com.jme3.anim;
import com.jme3.anim.util.JointModelTransform;
import com.jme3.math.Matrix4f;
import com.jme3.math.Transform;
/**
* This JointModelTransform implementation accumulates model transform in a Transform class
* This does NOT support proper non uniform scale in the armature hierarchy.
* But the effect might be useful in some circumstances.
* Note that this is how the old animation system was working, so you might want to use this
* if your model has non uniform scale and was migrated from old j3o model.
*/
public class SeparateJointModelTransform implements JointModelTransform {
private Transform modelTransform = new Transform();
@Override
public void updateModelTransform(Transform localTransform, Joint parent) {
modelTransform.set(localTransform);
if (parent != null) {
modelTransform.combineWithParent(parent.getModelTransform());
}
}
public void getOffsetTransform(Matrix4f outTransform, Matrix4f inverseModelBindMatrix) {
modelTransform.toTransformMatrix(outTransform).mult(inverseModelBindMatrix, outTransform);
}
@Override
public void applyBindPose(Transform localTransform, Matrix4f inverseModelBindMatrix, Joint parent) {
localTransform.fromTransformMatrix(inverseModelBindMatrix.invert());
if (parent != null) {
localTransform.combineWithParent(parent.getModelTransform().invert());
}
}
@Override
public Transform getModelTransform() {
return modelTransform;
}
}

@ -0,0 +1,744 @@
/*
* Copyright (c) 2009-2017 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.material.MatParamOverride;
import com.jme3.math.FastMath;
import com.jme3.math.Matrix4f;
import com.jme3.renderer.*;
import com.jme3.scene.*;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.mesh.IndexBuffer;
import com.jme3.shader.VarType;
import com.jme3.util.SafeArrayList;
import com.jme3.util.TempVars;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
import java.nio.Buffer;
import java.nio.FloatBuffer;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* The Skinning control deforms a model according to an armature, It handles the
* computation of the deformation matrices and performs the transformations on
* the mesh
* <p>
* It can perform software skinning or Hardware skinning
*
* @author Rémy Bouquet Based on SkeletonControl by Kirill Vainer
*/
public class SkinningControl extends AbstractControl implements Cloneable, JmeCloneable {
private static final Logger logger = Logger.getLogger(SkinningControl.class.getName());
/**
* The armature of the model.
*/
private Armature armature;
/**
* List of geometries affected by this control.
*/
private SafeArrayList<Geometry> targets = new SafeArrayList<>(Geometry.class);
/**
* Used to track when a mesh was updated. Meshes are only updated if they
* are visible in at least one camera.
*/
private boolean wasMeshUpdated = false;
/**
* User wishes to use hardware skinning if available.
*/
private transient boolean hwSkinningDesired = true;
/**
* Hardware skinning is currently being used.
*/
private transient boolean hwSkinningEnabled = false;
/**
* Hardware skinning was tested on this GPU, results
* are stored in {@link #hwSkinningSupported} variable.
*/
private transient boolean hwSkinningTested = false;
/**
* If hardware skinning was {@link #hwSkinningTested tested}, then
* this variable will be set to true if supported, and false if otherwise.
*/
private transient boolean hwSkinningSupported = false;
/**
* Bone offset matrices, recreated each frame
*/
private transient Matrix4f[] offsetMatrices;
private MatParamOverride numberOfJointsParam;
private MatParamOverride jointMatricesParam;
/**
* Serialization only. Do not use.
*/
public SkinningControl() {
}
/**
* Creates a armature control. The list of targets will be acquired
* automatically when the control is attached to a node.
*
* @param armature the armature
*/
public SkinningControl(Armature armature) {
if (armature == null) {
throw new IllegalArgumentException("armature cannot be null");
}
this.armature = armature;
this.numberOfJointsParam = new MatParamOverride(VarType.Int, "NumberOfBones", null);
this.jointMatricesParam = new MatParamOverride(VarType.Matrix4Array, "BoneMatrices", null);
}
private void switchToHardware() {
numberOfJointsParam.setEnabled(true);
jointMatricesParam.setEnabled(true);
// Next full 10 bones (e.g. 30 on 24 bones)
int numBones = ((armature.getJointCount() / 10) + 1) * 10;
numberOfJointsParam.setValue(numBones);
for (Geometry geometry : targets) {
Mesh mesh = geometry.getMesh();
if (mesh != null && mesh.isAnimated()) {
mesh.prepareForAnim(false);
}
}
}
private void switchToSoftware() {
numberOfJointsParam.setEnabled(false);
jointMatricesParam.setEnabled(false);
for (Geometry geometry : targets) {
Mesh mesh = geometry.getMesh();
if (mesh != null && mesh.isAnimated()) {
mesh.prepareForAnim(true);
}
}
}
private boolean testHardwareSupported(RenderManager rm) {
//Only 255 bones max supported with hardware skinning
if (armature.getJointCount() > 255) {
return false;
}
switchToHardware();
try {
rm.preloadScene(spatial);
return true;
} catch (RendererException e) {
logger.log(Level.WARNING, "Could not enable HW skinning due to shader compile error:", e);
return false;
}
}
/**
* Specifies if hardware skinning is preferred. If it is preferred and
* supported by GPU, it shall be enabled, if its not preferred, or not
* supported by GPU, then it shall be disabled.
*
* @param preferred
* @see #isHardwareSkinningUsed()
*/
public void setHardwareSkinningPreferred(boolean preferred) {
hwSkinningDesired = preferred;
}
/**
* @return True if hardware skinning is preferable to software skinning.
* Set to false by default.
* @see #setHardwareSkinningPreferred(boolean)
*/
public boolean isHardwareSkinningPreferred() {
return hwSkinningDesired;
}
/**
* @return True is hardware skinning is activated and is currently used, false otherwise.
*/
public boolean isHardwareSkinningUsed() {
return hwSkinningEnabled;
}
/**
* If specified the geometry has an animated mesh, add its mesh and material
* to the lists of animation targets.
*/
private void findTargets(Geometry geometry) {
Mesh mesh = geometry.getMesh();
if (mesh != null && mesh.isAnimated()) {
targets.add(geometry);
}
}
private void findTargets(Node node) {
for (Spatial child : node.getChildren()) {
if (child instanceof Geometry) {
findTargets((Geometry) child);
} else if (child instanceof Node) {
findTargets((Node) child);
}
}
}
@Override
public void setSpatial(Spatial spatial) {
Spatial oldSpatial = this.spatial;
super.setSpatial(spatial);
updateTargetsAndMaterials(spatial);
if (oldSpatial != null) {
oldSpatial.removeMatParamOverride(numberOfJointsParam);
oldSpatial.removeMatParamOverride(jointMatricesParam);
}
if (spatial != null) {
spatial.removeMatParamOverride(numberOfJointsParam);
spatial.removeMatParamOverride(jointMatricesParam);
spatial.addMatParamOverride(numberOfJointsParam);
spatial.addMatParamOverride(jointMatricesParam);
}
}
private void controlRenderSoftware() {
resetToBind(); // reset morph meshes to bind pose
offsetMatrices = armature.computeSkinningMatrices();
for (Geometry geometry : targets) {
Mesh mesh = geometry.getMesh();
// NOTE: This assumes code higher up has
// already ensured this mesh is animated.
// Otherwise a crash will happen in skin update.
softwareSkinUpdate(mesh, offsetMatrices);
}
}
private void controlRenderHardware() {
offsetMatrices = armature.computeSkinningMatrices();
jointMatricesParam.setValue(offsetMatrices);
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
if (!wasMeshUpdated) {
updateTargetsAndMaterials(spatial);
// Prevent illegal cases. These should never happen.
assert hwSkinningTested || (!hwSkinningTested && !hwSkinningSupported && !hwSkinningEnabled);
assert !hwSkinningEnabled || (hwSkinningEnabled && hwSkinningTested && hwSkinningSupported);
if (hwSkinningDesired && !hwSkinningTested) {
hwSkinningTested = true;
hwSkinningSupported = testHardwareSupported(rm);
if (hwSkinningSupported) {
hwSkinningEnabled = true;
Logger.getLogger(SkinningControl.class.getName()).log(Level.INFO, "Hardware skinning engaged for {0}", spatial);
} else {
switchToSoftware();
}
} else if (hwSkinningDesired && hwSkinningSupported && !hwSkinningEnabled) {
switchToHardware();
hwSkinningEnabled = true;
} else if (!hwSkinningDesired && hwSkinningEnabled) {
switchToSoftware();
hwSkinningEnabled = false;
}
if (hwSkinningEnabled) {
controlRenderHardware();
} else {
controlRenderSoftware();
}
wasMeshUpdated = true;
}
}
@Override
protected void controlUpdate(float tpf) {
wasMeshUpdated = false;
armature.update();
}
//only do this for software updates
void resetToBind() {
for (Geometry geometry : targets) {
Mesh mesh = geometry.getMesh();
if (mesh != null && mesh.isAnimated()) {
Buffer bwBuff = mesh.getBuffer(Type.BoneWeight).getData();
Buffer biBuff = mesh.getBuffer(Type.BoneIndex).getData();
if (!biBuff.hasArray() || !bwBuff.hasArray()) {
mesh.prepareForAnim(true); // prepare for software animation
}
VertexBuffer bindPos = mesh.getBuffer(Type.BindPosePosition);
VertexBuffer bindNorm = mesh.getBuffer(Type.BindPoseNormal);
VertexBuffer pos = mesh.getBuffer(Type.Position);
VertexBuffer norm = mesh.getBuffer(Type.Normal);
FloatBuffer pb = (FloatBuffer) pos.getData();
FloatBuffer nb = (FloatBuffer) norm.getData();
FloatBuffer bpb = (FloatBuffer) bindPos.getData();
FloatBuffer bnb = (FloatBuffer) bindNorm.getData();
pb.clear();
nb.clear();
bpb.clear();
bnb.clear();
//reseting bind tangents if there is a bind tangent buffer
VertexBuffer bindTangents = mesh.getBuffer(Type.BindPoseTangent);
if (bindTangents != null) {
VertexBuffer tangents = mesh.getBuffer(Type.Tangent);
FloatBuffer tb = (FloatBuffer) tangents.getData();
FloatBuffer btb = (FloatBuffer) bindTangents.getData();
tb.clear();
btb.clear();
tb.put(btb).clear();
}
pb.put(bpb).clear();
nb.put(bnb).clear();
}
}
}
@Override
public Object jmeClone() {
return super.jmeClone();
}
@Override
public void cloneFields(Cloner cloner, Object original) {
super.cloneFields(cloner, original);
this.armature = cloner.clone(armature);
// If the targets were cloned then this will clone them. If the targets
// were shared then this will share them.
this.targets = cloner.clone(targets);
this.numberOfJointsParam = cloner.clone(numberOfJointsParam);
this.jointMatricesParam = cloner.clone(jointMatricesParam);
}
/**
* Access the attachments node of the named bone. If the bone doesn't
* already have an attachments node, create one and attach it to the scene
* graph. Models and effects attached to the attachments node will follow
* the bone's motions.
*
* @param jointName the name of the joint
* @return the attachments node of the joint
*/
public Node getAttachmentsNode(String jointName) {
Joint b = armature.getJoint(jointName);
if (b == null) {
throw new IllegalArgumentException("Given bone name does not exist "
+ "in the armature.");
}
updateTargetsAndMaterials(spatial);
int boneIndex = armature.getJointIndex(b);
Node n = b.getAttachmentsNode(boneIndex, targets);
/*
* Select a node to parent the attachments node.
*/
Node parent;
if (spatial instanceof Node) {
parent = (Node) spatial; // the usual case
} else {
parent = spatial.getParent();
}
parent.attachChild(n);
return n;
}
/**
* returns the armature of this control
*
* @return
*/
public Armature getArmature() {
return armature;
}
/**
* Enumerate the target meshes of this control.
*
* @return a new array
*/
public Mesh[] getTargets() {
Mesh[] result = new Mesh[targets.size()];
int i = 0;
for (Geometry geometry : targets) {
Mesh mesh = geometry.getMesh();
result[i] = mesh;
i++;
}
return result;
}
/**
* Update the mesh according to the given transformation matrices
*
* @param mesh then mesh
* @param offsetMatrices the transformation matrices to apply
*/
private void softwareSkinUpdate(Mesh mesh, Matrix4f[] offsetMatrices) {
VertexBuffer tb = mesh.getBuffer(Type.Tangent);
if (tb == null) {
//if there are no tangents use the classic skinning
applySkinning(mesh, offsetMatrices);
} else {
//if there are tangents use the skinning with tangents
applySkinningTangents(mesh, offsetMatrices, tb);
}
}
/**
* Method to apply skinning transforms to a mesh's buffers
*
* @param mesh the mesh
* @param offsetMatrices the offset matices to apply
*/
private void applySkinning(Mesh mesh, Matrix4f[] offsetMatrices) {
int maxWeightsPerVert = mesh.getMaxNumWeights();
if (maxWeightsPerVert <= 0) {
throw new IllegalStateException("Max weights per vert is incorrectly set!");
}
int fourMinusMaxWeights = 4 - maxWeightsPerVert;
// NOTE: This code assumes the vertex buffer is in bind pose
// resetToBind() has been called this frame
VertexBuffer vb = mesh.getBuffer(Type.Position);
FloatBuffer fvb = (FloatBuffer) vb.getData();
fvb.rewind();
VertexBuffer nb = mesh.getBuffer(Type.Normal);
FloatBuffer fnb = (FloatBuffer) nb.getData();
fnb.rewind();
// get boneIndexes and weights for mesh
IndexBuffer ib = IndexBuffer.wrapIndexBuffer(mesh.getBuffer(Type.BoneIndex).getData());
FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData();
wb.rewind();
float[] weights = wb.array();
int idxWeights = 0;
TempVars vars = TempVars.get();
float[] posBuf = vars.skinPositions;
float[] normBuf = vars.skinNormals;
int iterations = (int) FastMath.ceil(fvb.limit() / ((float) posBuf.length));
int bufLength = posBuf.length;
for (int i = iterations - 1; i >= 0; i--) {
// read next set of positions and normals from native buffer
bufLength = Math.min(posBuf.length, fvb.remaining());
fvb.get(posBuf, 0, bufLength);
fnb.get(normBuf, 0, bufLength);
int verts = bufLength / 3;
int idxPositions = 0;
// iterate vertices and apply skinning transform for each effecting bone
for (int vert = verts - 1; vert >= 0; vert--) {
// Skip this vertex if the first weight is zero.
if (weights[idxWeights] == 0) {
idxPositions += 3;
idxWeights += 4;
continue;
}
float nmx = normBuf[idxPositions];
float vtx = posBuf[idxPositions++];
float nmy = normBuf[idxPositions];
float vty = posBuf[idxPositions++];
float nmz = normBuf[idxPositions];
float vtz = posBuf[idxPositions++];
float rx = 0, ry = 0, rz = 0, rnx = 0, rny = 0, rnz = 0;
for (int w = maxWeightsPerVert - 1; w >= 0; w--) {
float weight = weights[idxWeights];
Matrix4f mat = offsetMatrices[ib.get(idxWeights++)];
rx += (mat.m00 * vtx + mat.m01 * vty + mat.m02 * vtz + mat.m03) * weight;
ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight;
rz += (mat.m20 * vtx + mat.m21 * vty + mat.m22 * vtz + mat.m23) * weight;
rnx += (nmx * mat.m00 + nmy * mat.m01 + nmz * mat.m02) * weight;
rny += (nmx * mat.m10 + nmy * mat.m11 + nmz * mat.m12) * weight;
rnz += (nmx * mat.m20 + nmy * mat.m21 + nmz * mat.m22) * weight;
}
idxWeights += fourMinusMaxWeights;
idxPositions -= 3;
normBuf[idxPositions] = rnx;
posBuf[idxPositions++] = rx;
normBuf[idxPositions] = rny;
posBuf[idxPositions++] = ry;
normBuf[idxPositions] = rnz;
posBuf[idxPositions++] = rz;
}
fvb.position(fvb.position() - bufLength);
fvb.put(posBuf, 0, bufLength);
fnb.position(fnb.position() - bufLength);
fnb.put(normBuf, 0, bufLength);
}
vars.release();
vb.updateData(fvb);
nb.updateData(fnb);
}
/**
* Specific method for skinning with tangents to avoid cluttering the
* classic skinning calculation with null checks that would slow down the
* process even if tangents don't have to be computed. Also the iteration
* has additional indexes since tangent has 4 components instead of 3 for
* pos and norm
*
* @param mesh the mesh
* @param offsetMatrices the offsetMatrices to apply
* @param tb the tangent vertexBuffer
*/
private void applySkinningTangents(Mesh mesh, Matrix4f[] offsetMatrices, VertexBuffer tb) {
int maxWeightsPerVert = mesh.getMaxNumWeights();
if (maxWeightsPerVert <= 0) {
throw new IllegalStateException("Max weights per vert is incorrectly set!");
}
int fourMinusMaxWeights = 4 - maxWeightsPerVert;
// NOTE: This code assumes the vertex buffer is in bind pose
// resetToBind() has been called this frame
VertexBuffer vb = mesh.getBuffer(Type.Position);
FloatBuffer fvb = (FloatBuffer) vb.getData();
fvb.rewind();
VertexBuffer nb = mesh.getBuffer(Type.Normal);
FloatBuffer fnb = (FloatBuffer) nb.getData();
fnb.rewind();
FloatBuffer ftb = (FloatBuffer) tb.getData();
ftb.rewind();
// get boneIndexes and weights for mesh
IndexBuffer ib = IndexBuffer.wrapIndexBuffer(mesh.getBuffer(Type.BoneIndex).getData());
FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData();
wb.rewind();
float[] weights = wb.array();
int idxWeights = 0;
TempVars vars = TempVars.get();
float[] posBuf = vars.skinPositions;
float[] normBuf = vars.skinNormals;
float[] tanBuf = vars.skinTangents;
int iterations = (int) FastMath.ceil(fvb.limit() / ((float) posBuf.length));
int bufLength = 0;
int tanLength = 0;
for (int i = iterations - 1; i >= 0; i--) {
// read next set of positions and normals from native buffer
bufLength = Math.min(posBuf.length, fvb.remaining());
tanLength = Math.min(tanBuf.length, ftb.remaining());
fvb.get(posBuf, 0, bufLength);
fnb.get(normBuf, 0, bufLength);
ftb.get(tanBuf, 0, tanLength);
int verts = bufLength / 3;
int idxPositions = 0;
//tangents has their own index because of the 4 components
int idxTangents = 0;
// iterate vertices and apply skinning transform for each effecting bone
for (int vert = verts - 1; vert >= 0; vert--) {
// Skip this vertex if the first weight is zero.
if (weights[idxWeights] == 0) {
idxTangents += 4;
idxPositions += 3;
idxWeights += 4;
continue;
}
float nmx = normBuf[idxPositions];
float vtx = posBuf[idxPositions++];
float nmy = normBuf[idxPositions];
float vty = posBuf[idxPositions++];
float nmz = normBuf[idxPositions];
float vtz = posBuf[idxPositions++];
float tnx = tanBuf[idxTangents++];
float tny = tanBuf[idxTangents++];
float tnz = tanBuf[idxTangents++];
// skipping the 4th component of the tangent since it doesn't have to be transformed
idxTangents++;
float rx = 0, ry = 0, rz = 0, rnx = 0, rny = 0, rnz = 0, rtx = 0, rty = 0, rtz = 0;
for (int w = maxWeightsPerVert - 1; w >= 0; w--) {
float weight = weights[idxWeights];
Matrix4f mat = offsetMatrices[ib.get(idxWeights++)];
rx += (mat.m00 * vtx + mat.m01 * vty + mat.m02 * vtz + mat.m03) * weight;
ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight;
rz += (mat.m20 * vtx + mat.m21 * vty + mat.m22 * vtz + mat.m23) * weight;
rnx += (nmx * mat.m00 + nmy * mat.m01 + nmz * mat.m02) * weight;
rny += (nmx * mat.m10 + nmy * mat.m11 + nmz * mat.m12) * weight;
rnz += (nmx * mat.m20 + nmy * mat.m21 + nmz * mat.m22) * weight;
rtx += (tnx * mat.m00 + tny * mat.m01 + tnz * mat.m02) * weight;
rty += (tnx * mat.m10 + tny * mat.m11 + tnz * mat.m12) * weight;
rtz += (tnx * mat.m20 + tny * mat.m21 + tnz * mat.m22) * weight;
}
idxWeights += fourMinusMaxWeights;
idxPositions -= 3;
normBuf[idxPositions] = rnx;
posBuf[idxPositions++] = rx;
normBuf[idxPositions] = rny;
posBuf[idxPositions++] = ry;
normBuf[idxPositions] = rnz;
posBuf[idxPositions++] = rz;
idxTangents -= 4;
tanBuf[idxTangents++] = rtx;
tanBuf[idxTangents++] = rty;
tanBuf[idxTangents++] = rtz;
//once again skipping the 4th component of the tangent
idxTangents++;
}
fvb.position(fvb.position() - bufLength);
fvb.put(posBuf, 0, bufLength);
fnb.position(fnb.position() - bufLength);
fnb.put(normBuf, 0, bufLength);
ftb.position(ftb.position() - tanLength);
ftb.put(tanBuf, 0, tanLength);
}
vars.release();
vb.updateData(fvb);
nb.updateData(fnb);
tb.updateData(ftb);
}
@Override
public void write(JmeExporter ex) throws IOException {
super.write(ex);
OutputCapsule oc = ex.getCapsule(this);
oc.write(armature, "armature", null);
oc.write(numberOfJointsParam, "numberOfBonesParam", null);
oc.write(jointMatricesParam, "boneMatricesParam", null);
}
@Override
public void read(JmeImporter im) throws IOException {
super.read(im);
InputCapsule in = im.getCapsule(this);
armature = (Armature) in.readSavable("armature", null);
numberOfJointsParam = (MatParamOverride) in.readSavable("numberOfBonesParam", null);
jointMatricesParam = (MatParamOverride) in.readSavable("boneMatricesParam", null);
if (numberOfJointsParam == null) {
numberOfJointsParam = new MatParamOverride(VarType.Int, "NumberOfBones", null);
jointMatricesParam = new MatParamOverride(VarType.Matrix4Array, "BoneMatrices", null);
getSpatial().addMatParamOverride(numberOfJointsParam);
getSpatial().addMatParamOverride(jointMatricesParam);
}
}
/**
* Update the lists of animation targets.
*
* @param spatial the controlled spatial
*/
private void updateTargetsAndMaterials(Spatial spatial) {
targets.clear();
if (spatial instanceof Node) {
findTargets((Node) spatial);
} else if (spatial instanceof Geometry) {
findTargets((Geometry) spatial);
}
}
}

@ -0,0 +1,315 @@
/*
* 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.anim.interpolator.FrameInterpolator;
import com.jme3.anim.tween.Tween;
import com.jme3.anim.util.HasLocalTransform;
import com.jme3.animation.CompactQuaternionArray;
import com.jme3.animation.CompactVector3Array;
import com.jme3.export.*;
import com.jme3.math.*;
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 class TransformTrack implements AnimTrack<Transform> {
private double length;
private HasLocalTransform target;
/**
* Transforms and times for track.
*/
private CompactVector3Array translations;
private CompactQuaternionArray rotations;
private CompactVector3Array scales;
private FrameInterpolator interpolator = FrameInterpolator.DEFAULT;
private float[] times;
/**
* Serialization-only. Do not use.
*/
public TransformTrack() {
}
/**
* Creates a transform track for the given bone index
*
* @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 TransformTrack(HasLocalTransform target, float[] times, Vector3f[] translations, Quaternion[] rotations, Vector3f[] scales) {
this.target = target;
this.setKeyframes(times, translations, rotations, scales);
}
/**
* return the array of rotations of this track
*
* @return
*/
public Quaternion[] getRotations() {
return rotations.toObjectArray();
}
/**
* returns the array of scales for this track
*
* @return
*/
public Vector3f[] getScales() {
return scales == null ? null : scales.toObjectArray();
}
/**
* returns the arrays of time for this track
*
* @return
*/
public float[] getTimes() {
return times;
}
/**
* returns the array of translations of this track
*
* @return
*/
public Vector3f[] getTranslations() {
return translations.toObjectArray();
}
/**
* Sets the keyframes times for this Joint track
*
* @param times the keyframes times
*/
public void setTimes(float[] times) {
if (times.length == 0) {
throw new RuntimeException("TransformTrack with no keyframes!");
}
this.times = times;
length = times[times.length - 1] - times[0];
}
/**
* Set the translations for this joint track
*
* @param translations the translation of the bone for each frame
*/
public void setKeyframesTranslation(Vector3f[] translations) {
if (times == null) {
throw new RuntimeException("TransformTrack doesn't have any time for key frames, please call setTimes first");
}
if (translations.length == 0) {
throw new RuntimeException("TransformTrack with no translation keyframes!");
}
this.translations = new CompactVector3Array();
this.translations.add(translations);
this.translations.freeze();
assert times != null && times.length == translations.length;
}
/**
* Set the scales for this joint track
*
* @param scales the scales of the bone for each frame
*/
public void setKeyframesScale(Vector3f[] scales) {
if (times == null) {
throw new RuntimeException("TransformTrack doesn't have any time for key frames, please call setTimes first");
}
if (scales.length == 0) {
throw new RuntimeException("TransformTrack with no scale keyframes!");
}
this.scales = new CompactVector3Array();
this.scales.add(scales);
this.scales.freeze();
assert times != null && times.length == scales.length;
}
/**
* Set the rotations for this joint track
*
* @param rotations the rotations of the bone for each frame
*/
public void setKeyframesRotation(Quaternion[] rotations) {
if (times == null) {
throw new RuntimeException("TransformTrack doesn't have any time for key frames, please call setTimes first");
}
if (rotations.length == 0) {
throw new RuntimeException("TransformTrack with no rotation keyframes!");
}
this.rotations = new CompactQuaternionArray();
this.rotations.add(rotations);
this.rotations.freeze();
assert times != null && times.length == rotations.length;
}
/**
* Set the translations, rotations and scales for this bone 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 void setKeyframes(float[] times, Vector3f[] translations, Quaternion[] rotations, Vector3f[] scales) {
setTimes(times);
if (translations != null) {
setKeyframesTranslation(translations);
}
if (rotations != null) {
setKeyframesRotation(rotations);
}
if (scales != null) {
setKeyframesScale(scales);
}
}
public double getLength() {
return length;
}
public void getDataAtTime(double t, Transform transform) {
float time = (float) t;
int lastFrame = times.length - 1;
if (time < 0 || lastFrame == 0) {
if (translations != null) {
translations.get(0, transform.getTranslation());
}
if (rotations != null) {
rotations.get(0, transform.getRotation());
}
if (scales != null) {
scales.get(0, transform.getScale());
}
return;
}
int startFrame = 0;
int endFrame = 1;
float blend = 0;
if (time >= times[lastFrame]) {
startFrame = lastFrame;
time = time - times[startFrame] + times[startFrame - 1];
blend = (time - times[startFrame - 1])
/ (times[startFrame] - times[startFrame - 1]);
} else {
// use lastFrame so we never overflow the array
int i;
for (i = 0; i < lastFrame && times[i] < time; i++) {
startFrame = i;
endFrame = i + 1;
}
blend = (time - times[startFrame])
/ (times[endFrame] - times[startFrame]);
}
Transform interpolated = interpolator.interpolate(blend, startFrame, translations, rotations, scales, times);
if (translations != null) {
transform.setTranslation(interpolated.getTranslation());
}
if (rotations != null) {
transform.setRotation(interpolated.getRotation());
}
if (scales != null) {
transform.setScale(interpolated.getScale());
}
}
public void setFrameInterpolator(FrameInterpolator interpolator) {
this.interpolator = interpolator;
}
public HasLocalTransform getTarget() {
return target;
}
public void setTarget(HasLocalTransform target) {
this.target = target;
}
@Override
public void write(JmeExporter ex) throws IOException {
OutputCapsule oc = ex.getCapsule(this);
oc.write(translations, "translations", null);
oc.write(rotations, "rotations", null);
oc.write(times, "times", null);
oc.write(scales, "scales", null);
oc.write(target, "target", null);
}
@Override
public void read(JmeImporter im) throws IOException {
InputCapsule ic = im.getCapsule(this);
translations = (CompactVector3Array) ic.readSavable("translations", null);
rotations = (CompactQuaternionArray) ic.readSavable("rotations", null);
times = ic.readFloatArray("times", null);
scales = (CompactVector3Array) ic.readSavable("scales", null);
target = (HasLocalTransform) ic.readSavable("target", null);
setTimes(times);
}
@Override
public Object jmeClone() {
try {
TransformTrack clone = (TransformTrack) super.clone();
return clone;
} catch (CloneNotSupportedException ex) {
throw new AssertionError();
}
}
@Override
public void cloneFields(Cloner cloner, Object original) {
this.target = cloner.clone(target);
}
}

@ -0,0 +1,95 @@
package com.jme3.anim;
import java.util.ArrayList;
public class Weights {//} extends Savable, JmeCloneable{
private final static float MIN_WEIGHT = 0.005f;
private int[] indices;
private float[] data;
private int size;
public Weights(float[] array, int start, int length) {
ArrayList<Float> list = new ArrayList<>();
ArrayList<Integer> idx = new ArrayList<>();
for (int i = start; i < length; i++) {
float val = array[i];
if (val > MIN_WEIGHT) {
list.add(val);
idx.add(i);
}
}
size = list.size();
data = new float[size];
indices = new int[size];
for (int i = 0; i < size; i++) {
data[i] = list.get(i);
indices[i] = idx.get(i);
}
}
public int getSize() {
return size;
}
// public Weights(float[] array, int start, int length) {
// LinkedList<Float> list = new LinkedList<>();
// LinkedList<Integer> idx = new LinkedList<>();
// for (int i = start; i < length; i++) {
// float val = array[i];
// if (val > MIN_WEIGHT) {
// int index = insert(list, val);
// if (idx.size() < index) {
// idx.add(i);
// } else {
// idx.add(index, i);
// }
// }
// }
// data = new float[list.size()];
// for (int i = 0; i < data.length; i++) {
// data[i] = list.get(i);
// }
//
// indices = new int[idx.size()];
// for (int i = 0; i < indices.length; i++) {
// indices[i] = idx.get(i);
// }
// }
//
// private int insert(LinkedList<Float> list, float value) {
// for (int i = 0; i < list.size(); i++) {
// float w = list.get(i);
// if (value > w) {
// list.add(i, value);
// return i;
// }
// }
//
// list.add(value);
// return list.size();
// }
@Override
public String toString() {
StringBuilder b = new StringBuilder();
for (int i = 0; i < indices.length; i++) {
b.append(indices[i]).append(",");
}
b.append("\n");
for (int i = 0; i < data.length; i++) {
b.append(data[i]).append(",");
}
return b.toString();
}
public static void main(String... args) {
// 6 7 4 8
float values[] = {0, 0, 0, 0, 0.5f, 0.001f, 0.7f, 0.6f, 0.2f, 0, 0, 0};
Weights w = new Weights(values, 0, values.length);
System.err.println(w);
}
}

@ -0,0 +1,13 @@
package com.jme3.anim.interpolator;
import static com.jme3.anim.interpolator.FrameInterpolator.TrackDataReader;
import static com.jme3.anim.interpolator.FrameInterpolator.TrackTimeReader;
/**
* Created by nehon on 15/04/17.
*/
public abstract class AnimInterpolator<T> {
public abstract T interpolate(float t, int currentIndex, TrackDataReader<T> data, TrackTimeReader times, T store);
}

@ -0,0 +1,149 @@
package com.jme3.anim.interpolator;
import com.jme3.math.*;
import static com.jme3.anim.interpolator.FrameInterpolator.TrackDataReader;
import static com.jme3.anim.interpolator.FrameInterpolator.TrackTimeReader;
/**
* Created by nehon on 15/04/17.
*/
public class AnimInterpolators {
//Rotation interpolators
public static final AnimInterpolator<Quaternion> NLerp = new AnimInterpolator<Quaternion>() {
private Quaternion next = new Quaternion();
@Override
public Quaternion interpolate(float t, int currentIndex, TrackDataReader<Quaternion> data, TrackTimeReader times, Quaternion store) {
data.getEntryClamp(currentIndex, store);
data.getEntryClamp(currentIndex + 1, next);
store.nlerp(next, t);
return store;
}
};
public static final AnimInterpolator<Quaternion> SLerp = new AnimInterpolator<Quaternion>() {
private Quaternion next = new Quaternion();
@Override
public Quaternion interpolate(float t, int currentIndex, TrackDataReader<Quaternion> data, TrackTimeReader times, Quaternion store) {
data.getEntryClamp(currentIndex, store);
data.getEntryClamp(currentIndex + 1, next);
//MathUtils.slerpNoInvert(store, next, t, store);
MathUtils.slerp(store, next, t, store);
return store;
}
};
public static final AnimInterpolator<Quaternion> SQuad = new AnimInterpolator<Quaternion>() {
private Quaternion a = new Quaternion();
private Quaternion b = new Quaternion();
private Quaternion q0 = new Quaternion();
private Quaternion q1 = new Quaternion();
private Quaternion q2 = new Quaternion();
private Quaternion q3 = new Quaternion();
@Override
public Quaternion interpolate(float t, int currentIndex, TrackDataReader<Quaternion> data, TrackTimeReader times, Quaternion store) {
data.getEntryModSkip(currentIndex - 1, q0);
data.getEntryModSkip(currentIndex, q1);
data.getEntryModSkip(currentIndex + 1, q2);
data.getEntryModSkip(currentIndex + 2, q3);
MathUtils.squad(q0, q1, q2, q3, a, b, t, store);
return store;
}
};
//Position / Scale interpolators
public static final AnimInterpolator<Vector3f> LinearVec3f = new AnimInterpolator<Vector3f>() {
private Vector3f next = new Vector3f();
@Override
public Vector3f interpolate(float t, int currentIndex, TrackDataReader<Vector3f> data, TrackTimeReader times, Vector3f store) {
data.getEntryClamp(currentIndex, store);
data.getEntryClamp(currentIndex + 1, next);
store.interpolateLocal(next, t);
return store;
}
};
/**
* CatmullRom interpolation
*/
public static final CatmullRomInterpolator CatmullRom = new CatmullRomInterpolator();
public static class CatmullRomInterpolator extends AnimInterpolator<Vector3f> {
private Vector3f p0 = new Vector3f();
private Vector3f p1 = new Vector3f();
private Vector3f p2 = new Vector3f();
private Vector3f p3 = new Vector3f();
private float tension = 0.7f;
public CatmullRomInterpolator(float tension) {
this.tension = tension;
}
public CatmullRomInterpolator() {
}
@Override
public Vector3f interpolate(float t, int currentIndex, TrackDataReader<Vector3f> data, TrackTimeReader times, Vector3f store) {
data.getEntryModSkip(currentIndex - 1, p0);
data.getEntryModSkip(currentIndex, p1);
data.getEntryModSkip(currentIndex + 1, p2);
data.getEntryModSkip(currentIndex + 2, p3);
FastMath.interpolateCatmullRom(t, tension, p0, p1, p2, p3, store);
return store;
}
}
//Time Interpolators
public static class TimeInterpolator extends AnimInterpolator<Float> {
private EaseFunction ease;
public TimeInterpolator(EaseFunction ease) {
this.ease = ease;
}
@Override
public Float interpolate(float t, int currentIndex, TrackDataReader<Float> data, TrackTimeReader times, Float store) {
return ease.apply(t);
}
}
//in
public static final TimeInterpolator easeInQuad = new TimeInterpolator(Easing.inQuad);
public static final TimeInterpolator easeInCubic = new TimeInterpolator(Easing.inCubic);
public static final TimeInterpolator easeInQuart = new TimeInterpolator(Easing.inQuart);
public static final TimeInterpolator easeInQuint = new TimeInterpolator(Easing.inQuint);
public static final TimeInterpolator easeInBounce = new TimeInterpolator(Easing.inBounce);
public static final TimeInterpolator easeInElastic = new TimeInterpolator(Easing.inElastic);
//out
public static final TimeInterpolator easeOutQuad = new TimeInterpolator(Easing.outQuad);
public static final TimeInterpolator easeOutCubic = new TimeInterpolator(Easing.outCubic);
public static final TimeInterpolator easeOutQuart = new TimeInterpolator(Easing.outQuart);
public static final TimeInterpolator easeOutQuint = new TimeInterpolator(Easing.outQuint);
public static final TimeInterpolator easeOutBounce = new TimeInterpolator(Easing.outBounce);
public static final TimeInterpolator easeOutElastic = new TimeInterpolator(Easing.outElastic);
//inout
public static final TimeInterpolator easeInOutQuad = new TimeInterpolator(Easing.inOutQuad);
public static final TimeInterpolator easeInOutCubic = new TimeInterpolator(Easing.inOutCubic);
public static final TimeInterpolator easeInOutQuart = new TimeInterpolator(Easing.inOutQuart);
public static final TimeInterpolator easeInOutQuint = new TimeInterpolator(Easing.inOutQuint);
public static final TimeInterpolator easeInOutBounce = new TimeInterpolator(Easing.inOutBounce);
public static final TimeInterpolator easeInOutElastic = new TimeInterpolator(Easing.inOutElastic);
//extra
public static final TimeInterpolator smoothStep = new TimeInterpolator(Easing.smoothStep);
public static final TimeInterpolator smootherStep = new TimeInterpolator(Easing.smootherStep);
public static final TimeInterpolator constant = new TimeInterpolator(Easing.constant);
}

@ -0,0 +1,136 @@
package com.jme3.anim.interpolator;
import com.jme3.animation.*;
import com.jme3.math.*;
/**
* Created by nehon on 15/04/17.
*/
public class FrameInterpolator {
public static final FrameInterpolator DEFAULT = new FrameInterpolator();
private AnimInterpolator<Float> timeInterpolator;
private AnimInterpolator<Vector3f> translationInterpolator = AnimInterpolators.LinearVec3f;
private AnimInterpolator<Quaternion> rotationInterpolator = AnimInterpolators.NLerp;
private AnimInterpolator<Vector3f> scaleInterpolator = AnimInterpolators.LinearVec3f;
private TrackDataReader<Vector3f> translationReader = new TrackDataReader<>();
private TrackDataReader<Quaternion> rotationReader = new TrackDataReader<>();
private TrackDataReader<Vector3f> scaleReader = new TrackDataReader<>();
private TrackTimeReader timesReader = new TrackTimeReader();
private Transform transforms = new Transform();
public Transform interpolate(float t, int currentIndex, CompactVector3Array translations, CompactQuaternionArray rotations, CompactVector3Array scales, float[] times){
timesReader.setData(times);
if( timeInterpolator != null){
t = timeInterpolator.interpolate(t,currentIndex, null, timesReader, null );
}
if(translations != null) {
translationReader.setData(translations);
translationInterpolator.interpolate(t, currentIndex, translationReader, timesReader, transforms.getTranslation());
}
if(rotations != null) {
rotationReader.setData(rotations);
rotationInterpolator.interpolate(t, currentIndex, rotationReader, timesReader, transforms.getRotation());
}
if(scales != null){
scaleReader.setData(scales);
scaleInterpolator.interpolate(t, currentIndex, scaleReader, timesReader, transforms.getScale());
}
return transforms;
}
public void interpolateWeights(float t, int currentIndex, float[] weights, int nbMorphTargets, float[] store) {
int start = currentIndex * nbMorphTargets;
for (int i = 0; i < nbMorphTargets; i++) {
int current = start + i;
int next = current + nbMorphTargets;
if (next >= weights.length) {
next = current;
}
float val = FastMath.interpolateLinear(t, weights[current], weights[next]);
store[i] = val;
}
}
public void setTimeInterpolator(AnimInterpolator<Float> timeInterpolator) {
this.timeInterpolator = timeInterpolator;
}
public void setTranslationInterpolator(AnimInterpolator<Vector3f> translationInterpolator) {
this.translationInterpolator = translationInterpolator;
}
public void setRotationInterpolator(AnimInterpolator<Quaternion> rotationInterpolator) {
this.rotationInterpolator = rotationInterpolator;
}
public void setScaleInterpolator(AnimInterpolator<Vector3f> scaleInterpolator) {
this.scaleInterpolator = scaleInterpolator;
}
public static class TrackTimeReader {
private float[] data;
protected void setData(float[] data) {
this.data = data;
}
public float getEntry(int index) {
return data[mod(index, data.length)];
}
public int getLength() {
return data.length;
}
}
public static class TrackDataReader<T> {
private CompactArray<T> data;
protected void setData(CompactArray<T> data) {
this.data = data;
}
public T getEntryMod(int index, T store) {
return data.get(mod(index, data.getTotalObjectSize()), store);
}
public T getEntryClamp(int index, T store) {
index = (int) FastMath.clamp(index, 0, data.getTotalObjectSize() - 1);
return data.get(index, store);
}
public T getEntryModSkip(int index, T store) {
int total = data.getTotalObjectSize();
if (index == -1) {
index--;
} else if (index >= total) {
index++;
}
index = mod(index, total);
return data.get(index, store);
}
}
/**
* Euclidean modulo (cycle on 0,n instead of -n,0; 0,n)
*
* @param val
* @param n
* @return
*/
private static int mod(int val, int n) {
return ((val % n) + n) % n;
}
}

@ -0,0 +1,97 @@
/*
* $Id$
*
* Copyright (c) 2015, Simsilica, LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. 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.
*
* 3. Neither the name of the copyright holder 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 HOLDER 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.tween;
import com.jme3.export.*;
import java.io.IOException;
/**
* Base implementation of the Tween interface that provides
* default implementations of the getLength() and interopolate()
* methods that provide common tween clamping and bounds checking.
* Subclasses need only override the doInterpolate() method and
* the rest is handled for them.
*
* @author Paul Speed
*/
public abstract class AbstractTween implements Tween {
private double length;
protected AbstractTween(double length) {
this.length = length;
}
@Override
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
/**
* Default implementation clamps the time value, converts
* it to 0 to 1.0 based on getLength(), and calls doInterpolate().
*/
@Override
public boolean interpolate(double t) {
if (t < 0) {
return true;
}
// Scale t to be between 0 and 1 for our length
if (length == 0) {
t = 1;
} else {
t = t / length;
}
boolean done = false;
if (t >= 1.0) {
t = 1.0;
done = true;
}
doInterpolate(t);
return !done;
}
protected abstract void doInterpolate(double t);
}

@ -0,0 +1,6 @@
package com.jme3.anim.tween;
public interface ContainsTweens {
public Tween[] getTweens();
}

@ -0,0 +1,71 @@
/*
* $Id$
*
* Copyright (c) 2015, Simsilica, LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. 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.
*
* 3. Neither the name of the copyright holder 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 HOLDER 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.tween;
import com.jme3.export.Savable;
/**
* Represents some action that interpolates across input between 0
* and some length value. (For example, movement, rotation, fading.)
* It's also possible to have zero length 'instant' tweens.
*
* @author Paul Speed
*/
public interface Tween extends Cloneable {
/**
* Returns the length of the tween. If 't' represents time in
* seconds then this is the notional time in seconds that the tween
* will run. Note: all of the caveats are because tweens may be
* externally scaled in such a way that 't' no longer represents
* actual time.
*/
public double getLength();
/**
* Sets the implementation specific interpolation to the
* specified 'tween' value as a value in the range from 0 to
* getLength(). If the value is greater or equal to getLength()
* then it is internally clamped and the method returns false.
* If 't' is still in the tween's range then this method returns
* true.
*/
public boolean interpolate(double t);
}

@ -0,0 +1,619 @@
/*
* $Id$
*
* Copyright (c) 2015, Simsilica, LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. 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.
*
* 3. Neither the name of the copyright holder 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 HOLDER 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.tween;
import com.jme3.anim.util.Primitives;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Static utility methods for creating common generic Tween objects.
*
* @author Paul Speed
*/
public class Tweens {
static Logger log = Logger.getLogger(Tweens.class.getName());
private static final CurveFunction SMOOTH = new SmoothStep();
private static final CurveFunction SINE = new Sine();
/**
* Creates a tween that will interpolate over an entire sequence
* of tweens in order.
*/
public static Tween sequence(Tween... delegates) {
return new Sequence(delegates);
}
/**
* Creates a tween that will interpolate over an entire list
* of tweens in parallel, ie: all tweens will be run at the same
* time.
*/
public static Tween parallel(Tween... delegates) {
return new Parallel(delegates);
}
/**
* Creates a tween that will perform a no-op until the length
* has expired.
*/
public static Tween delay(double length) {
return new Delay(length);
}
/**
* Creates a tween that scales the specified delegate tween or tweens
* to the desired length. If more than one tween is specified then they
* are wrapped in a sequence using the sequence() method.
*/
public static Tween stretch(double desiredLength, Tween... delegates) {
if (delegates.length == 1) {
return new Stretch(delegates[0], desiredLength);
}
return new Stretch(sequence(delegates), desiredLength);
}
/**
* Creates a tween that uses a sine function to smooth step the time value
* for the specified delegate tween or tweens. These 'curved' wrappers
* can be used to smooth the interpolation of another tween.
*/
public static Tween sineStep(Tween... delegates) {
if (delegates.length == 1) {
return new Curve(delegates[0], SINE);
}
return new Curve(sequence(delegates), SINE);
}
/**
* Creates a tween that uses a hermite function to smooth step the time value
* for the specified delegate tween or tweens. This is similar to GLSL's
* smoothstep(). These 'curved' wrappers can be used to smooth the interpolation
* of another tween.
*/
public static Tween smoothStep(Tween... delegates) {
if (delegates.length == 1) {
return new Curve(delegates[0], SMOOTH);
}
return new Curve(sequence(delegates), SMOOTH);
}
/**
* Creates a Tween that will call the specified method and optional arguments
* whenever supplied a time value greater than or equal to 0. This creates
* an "instant" tween of length 0.
*/
public static Tween callMethod(Object target, String method, Object... args) {
return new CallMethod(target, method, args);
}
/**
* Creates a Tween that will call the specified method and optional arguments,
* including the time value scaled between 0 and 1. The method must take
* a float or double value as its first or last argument, in addition to whatever
* optional arguments are specified.
* <p>
* <p>For example:</p>
* <pre>Tweens.callTweenMethod(1, myObject, "foo", "bar")</pre>
* <p>Would work for any of the following method signatures:</p>
* <pre>
* void foo( float t, String arg )
* void foo( double t, String arg )
* void foo( String arg, float t )
* void foo( String arg, double t )
* </pre>
*/
public static Tween callTweenMethod(double length, Object target, String method, Object... args) {
return new CallTweenMethod(length, target, method, args);
}
private static interface CurveFunction {
public double curve(double input);
}
/**
* Curve function for Hermite interpolation ala GLSL smoothstep().
*/
private static class SmoothStep implements CurveFunction {
@Override
public double curve(double t) {
if (t < 0) {
return 0;
} else if (t > 1) {
return 1;
}
return t * t * (3 - 2 * t);
}
}
private static class Sine implements CurveFunction {
@Override
public double curve(double t) {
if (t < 0) {
return 0;
} else if (t > 1) {
return 1;
}
// Sine starting at -90 will go from -1 to 1 through 0
double result = Math.sin(t * Math.PI - Math.PI * 0.5);
return (result + 1) * 0.5;
}
}
private static class Curve implements Tween {
private final Tween delegate;
private final CurveFunction func;
private final double length;
public Curve(Tween delegate, CurveFunction func) {
this.delegate = delegate;
this.func = func;
this.length = delegate.getLength();
}
@Override
public double getLength() {
return length;
}
@Override
public boolean interpolate(double t) {
// Sanity check the inputs
if (t < 0) {
return true;
}
if (length == 0) {
// Caller did something strange but we'll allow it
return delegate.interpolate(t);
}
t = func.curve(t / length);
return delegate.interpolate(t * length);
}
@Override
public String toString() {
return getClass().getSimpleName() + "[delegate=" + delegate + ", func=" + func + "]";
}
}
private static class Sequence implements Tween, ContainsTweens {
private final Tween[] delegates;
private int current = 0;
private double baseTime;
private double length;
public Sequence(Tween... delegates) {
this.delegates = delegates;
for (Tween t : delegates) {
length += t.getLength();
}
}
@Override
public double getLength() {
return length;
}
@Override
public boolean interpolate(double t) {
// Sanity check the inputs
if (t < 0) {
return true;
}
if (t < baseTime) {
// We've rolled back before the current sequence step
// which means we need to reset and start forward
// again. We have no idea how to 'roll back' and
// this is the only way to maintain consistency.
// The only 'normal' case where this happens is when looping
// in which case a full rollback is appropriate.
current = 0;
baseTime = 0;
}
if (current >= delegates.length) {
return false;
}
// Skip any that are done
while (!delegates[current].interpolate(t - baseTime)) {
// Time to go to the next one
baseTime += delegates[current].getLength();
current++;
if (current >= delegates.length) {
return false;
}
}
return true;
}
@Override
public String toString() {
return getClass().getSimpleName() + "[delegates=" + Arrays.asList(delegates) + "]";
}
@Override
public Tween[] getTweens() {
return delegates;
}
}
private static class Parallel implements Tween, ContainsTweens {
private final Tween[] delegates;
private final boolean[] done;
private double length;
private double lastTime;
public Parallel(Tween... delegates) {
this.delegates = delegates;
done = new boolean[delegates.length];
for (Tween t : delegates) {
if (t.getLength() > length) {
length = t.getLength();
}
}
}
@Override
public double getLength() {
return length;
}
protected void reset() {
for (int i = 0; i < done.length; i++) {
done[i] = false;
}
}
@Override
public boolean interpolate(double t) {
// Sanity check the inputs
if (t < 0) {
return true;
}
if (t < lastTime) {
// We've rolled back before the last time we were given.
// This means we may have 'done'ed a few tasks that now
// need to be run again. Better to just reset and start
// over. As mentioned in the Sequence task, the only 'normal'
// use-case for time rolling backwards is when looping. And
// in that case, we want to start from the beginning anyway.
reset();
}
lastTime = t;
int runningCount = delegates.length;
for (int i = 0; i < delegates.length; i++) {
if (!done[i]) {
done[i] = !delegates[i].interpolate(t);
}
if (done[i]) {
runningCount--;
}
}
return runningCount > 0;
}
@Override
public String toString() {
return getClass().getSimpleName() + "[delegates=" + Arrays.asList(delegates) + "]";
}
@Override
public Tween[] getTweens() {
return delegates;
}
}
private static class Delay extends AbstractTween {
public Delay(double length) {
super(length);
}
@Override
protected void doInterpolate(double t) {
}
}
private static class Stretch implements Tween, ContainsTweens {
private final Tween[] delegate = new Tween[1];
private final double length;
private final double scale;
public Stretch(Tween delegate, double length) {
this.delegate[0] = delegate;
this.length = length;
// Caller desires delegate to be 'length' instead of
// it's actual length so we will calculate a time scale
// If the desired length is longer than delegate's then
// we need to feed time in slower, ie: scale < 1
if (length != 0) {
this.scale = delegate.getLength() / length;
} else {
this.scale = 0;
}
}
@Override
public double getLength() {
return length;
}
@Override
public Tween[] getTweens() {
return delegate;
}
@Override
public boolean interpolate(double t) {
if (t < 0) {
return true;
}
if (length > 0) {
t *= scale;
} else {
t = length;
}
return delegate[0].interpolate(t);
}
@Override
public String toString() {
return getClass().getSimpleName() + "[delegate=" + delegate[0] + ", length=" + length + "]";
}
}
private static class CallMethod extends AbstractTween {
private Object target;
private Method method;
private Object[] args;
public CallMethod(Object target, String methodName, Object... args) {
super(0);
if (target == null) {
throw new IllegalArgumentException("Target cannot be null.");
}
this.target = target;
this.args = args;
// Lookup the method
if (args == null) {
this.method = findMethod(target.getClass(), methodName);
} else {
this.method = findMethod(target.getClass(), methodName, args);
}
if (this.method == null) {
throw new IllegalArgumentException("Method not found for:" + methodName + " on type:" + target.getClass());
}
this.method.setAccessible(true);
}
private static Method findMethod(Class type, String name, Object... args) {
for (Method m : type.getDeclaredMethods()) {
if (!Objects.equals(m.getName(), name)) {
continue;
}
Class[] paramTypes = m.getParameterTypes();
if (paramTypes.length != args.length) {
continue;
}
int matches = 0;
for (int i = 0; i < args.length; i++) {
if (paramTypes[i].isInstance(args[i])
|| Primitives.wrap(paramTypes[i]).isInstance(args[i])) {
matches++;
}
}
if (matches == args.length) {
return m;
}
}
if (type.getSuperclass() != null) {
return findMethod(type.getSuperclass(), name, args);
}
return null;
}
@Override
protected void doInterpolate(double t) {
try {
method.invoke(target, args);
} catch (IllegalAccessException e) {
throw new RuntimeException("Error running method:" + method + " for object:" + target, e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Error running method:" + method + " for object:" + target, e);
}
}
@Override
public String toString() {
return getClass().getSimpleName() + "[method=" + method + ", parms=" + Arrays.asList(args) + "]";
}
}
private static class CallTweenMethod extends AbstractTween {
private Object target;
private Method method;
private Object[] args;
private int tIndex = -1;
private boolean isFloat = false;
public CallTweenMethod(double length, Object target, String methodName, Object... args) {
super(length);
if (target == null) {
throw new IllegalArgumentException("Target cannot be null.");
}
this.target = target;
// Lookup the method
this.method = findMethod(target.getClass(), methodName, args);
if (this.method == null) {
throw new IllegalArgumentException("Method not found for:" + methodName + " on type:" + target.getClass());
}
this.method.setAccessible(true);
// So now setup the real args list
this.args = new Object[args.length + 1];
if (tIndex == 0) {
for (int i = 0; i < args.length; i++) {
this.args[i + 1] = args[i];
}
} else {
for (int i = 0; i < args.length; i++) {
this.args[i] = args[i];
}
}
}
private static boolean isFloatType(Class type) {
return type == Float.TYPE || type == Float.class;
}
private static boolean isDoubleType(Class type) {
return type == Double.TYPE || type == Double.class;
}
private Method findMethod(Class type, String name, Object... args) {
for (Method m : type.getDeclaredMethods()) {
if (!Objects.equals(m.getName(), name)) {
continue;
}
Class[] paramTypes = m.getParameterTypes();
if (paramTypes.length != args.length + 1) {
if (log.isLoggable(Level.FINE)) {
log.log(Level.FINE, "Param lengths of [" + m + "] differ. method arg count:" + paramTypes.length + " lookging for:" + (args.length + 1));
}
continue;
}
// We accept the 't' parameter as either first or last
// so we'll see which one matches.
if (isFloatType(paramTypes[0]) || isDoubleType(paramTypes[0])) {
// Try it as the first parameter
int matches = 0;
for (int i = 1; i < paramTypes.length; i++) {
if (paramTypes[i].isInstance(args[i - 1])) {
matches++;
}
}
if (matches == args.length) {
// Then this is our method and this is how we are configured
tIndex = 0;
isFloat = isFloatType(paramTypes[0]);
} else {
if (log.isLoggable(Level.FINE)) {
log.log(Level.FINE, m + " Leading float check failed because of type mismatches, for:" + m);
}
}
}
if (tIndex >= 0) {
return m;
}
// Else try it at the end
int last = paramTypes.length - 1;
if (isFloatType(paramTypes[last]) || isDoubleType(paramTypes[last])) {
int matches = 0;
for (int i = 0; i < last; i++) {
if (paramTypes[i].isInstance(args[i])) {
matches++;
}
}
if (matches == args.length) {
// Then this is our method and this is how we are configured
tIndex = last;
isFloat = isFloatType(paramTypes[last]);
return m;
} else {
if (log.isLoggable(Level.FINE)) {
log.log(Level.FINE, "Trailing float check failed because of type mismatches, for:" + m);
}
}
}
}
if (type.getSuperclass() != null) {
return findMethod(type.getSuperclass(), name, args);
}
return null;
}
@Override
protected void doInterpolate(double t) {
try {
if (isFloat) {
args[tIndex] = (float) t;
} else {
args[tIndex] = t;
}
method.invoke(target, args);
} catch (IllegalAccessException e) {
throw new RuntimeException("Error running method:" + method + " for object:" + target, e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Error running method:" + method + " for object:" + target, e);
}
}
@Override
public String toString() {
return getClass().getSimpleName() + "[method=" + method + ", parms=" + Arrays.asList(args) + "]";
}
}
}

@ -0,0 +1,46 @@
package com.jme3.anim.tween.action;
import com.jme3.anim.tween.Tween;
public abstract class Action implements Tween {
protected Action[] actions;
private double length;
private double speed = 1;
protected Action(Tween... tweens) {
this.actions = new Action[tweens.length];
for (int i = 0; i < tweens.length; i++) {
Tween tween = tweens[i];
if (tween instanceof Action) {
this.actions[i] = (Action) tween;
} else {
this.actions[i] = new BaseAction(tween);
}
}
}
@Override
public boolean interpolate(double t) {
return subInterpolate(t * speed);
}
public abstract boolean subInterpolate(double t);
@Override
public double getLength() {
return length;
}
protected void setLength(double length) {
this.length = length;
}
public double getSpeed() {
return speed;
}
public void setSpeed(double speed) {
this.speed = speed;
}
}

@ -0,0 +1,33 @@
package com.jme3.anim.tween.action;
import com.jme3.anim.tween.ContainsTweens;
import com.jme3.anim.tween.Tween;
import com.jme3.util.SafeArrayList;
public class BaseAction extends Action {
private Tween tween;
private SafeArrayList<Action> subActions = new SafeArrayList<>(Action.class);
public BaseAction(Tween tween) {
this.tween = tween;
setLength(tween.getLength());
gatherActions(tween);
}
private void gatherActions(Tween tween) {
if (tween instanceof Action) {
subActions.add((Action) tween);
} else if (tween instanceof ContainsTweens) {
Tween[] tweens = ((ContainsTweens) tween).getTweens();
for (Tween t : tweens) {
gatherActions(t);
}
}
}
@Override
public boolean subInterpolate(double t) {
return tween.interpolate(t);
}
}

@ -0,0 +1,129 @@
package com.jme3.anim.tween.action;
import com.jme3.anim.util.HasLocalTransform;
import com.jme3.math.Transform;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
public class BlendAction extends BlendableAction {
private int firstActiveIndex;
private int secondActiveIndex;
private BlendSpace blendSpace;
private float blendWeight;
private double[] timeFactor;
private Map<HasLocalTransform, Transform> targetMap = new HashMap<>();
public BlendAction(BlendSpace blendSpace, BlendableAction... actions) {
super(actions);
timeFactor = new double[actions.length];
this.blendSpace = blendSpace;
blendSpace.setBlendAction(this);
for (BlendableAction action : actions) {
if (action.getLength() > getLength()) {
setLength(action.getLength());
}
Collection<HasLocalTransform> targets = action.getTargets();
for (HasLocalTransform target : targets) {
Transform t = targetMap.get(target);
if (t == null) {
t = new Transform();
targetMap.put(target, t);
}
}
}
//Blending effect maybe unexpected when blended animation don't have the same length
//Stretching any action that doesn't have the same length.
for (int i = 0; i < this.actions.length; i++) {
this.timeFactor[i] = 1;
if (this.actions[i].getLength() != getLength()) {
double actionLength = this.actions[i].getLength();
if (actionLength > 0 && getLength() > 0) {
this.timeFactor[i] = this.actions[i].getLength() / getLength();
}
}
}
}
public void doInterpolate(double t) {
blendWeight = blendSpace.getWeight();
BlendableAction firstActiveAction = (BlendableAction) actions[firstActiveIndex];
BlendableAction secondActiveAction = (BlendableAction) actions[secondActiveIndex];
firstActiveAction.setCollectTransformDelegate(this);
secondActiveAction.setCollectTransformDelegate(this);
//only interpolate the first action if the weight if below 1.
if (blendWeight < 1f) {
firstActiveAction.setWeight(1f);
firstActiveAction.interpolate(t * timeFactor[firstActiveIndex]);
if (blendWeight == 0) {
for (HasLocalTransform target : targetMap.keySet()) {
collect(target, targetMap.get(target));
}
}
}
//Second action should be interpolated
secondActiveAction.setWeight(blendWeight);
secondActiveAction.interpolate(t * timeFactor[secondActiveIndex]);
firstActiveAction.setCollectTransformDelegate(null);
secondActiveAction.setCollectTransformDelegate(null);
}
protected Action[] getActions() {
return actions;
}
public BlendSpace getBlendSpace() {
return blendSpace;
}
protected void setFirstActiveIndex(int index) {
this.firstActiveIndex = index;
}
protected void setSecondActiveIndex(int index) {
this.secondActiveIndex = index;
}
@Override
public Collection<HasLocalTransform> getTargets() {
return targetMap.keySet();
}
@Override
public void collectTransform(HasLocalTransform target, Transform t, float weight, BlendableAction source) {
Transform tr = targetMap.get(target);
if (weight == 1) {
tr.set(t);
} else if (weight > 0) {
tr.interpolateTransforms(tr, t, weight);
}
if (source == actions[secondActiveIndex]) {
collect(target, tr);
}
}
private void collect(HasLocalTransform target, Transform tr) {
if (collectTransformDelegate != null) {
collectTransformDelegate.collectTransform(target, tr, this.getWeight(), this);
} else {
if (getTransitionWeight() == 1) {
target.setLocalTransform(tr);
} else {
Transform trans = target.getLocalTransform();
trans.interpolateTransforms(trans, tr, getTransitionWeight());
target.setLocalTransform(trans);
}
}
}
}

@ -0,0 +1,10 @@
package com.jme3.anim.tween.action;
public interface BlendSpace {
public void setBlendAction(BlendAction action);
public float getWeight();
public void setValue(float value);
}

@ -0,0 +1,92 @@
package com.jme3.anim.tween.action;
import com.jme3.anim.tween.AbstractTween;
import com.jme3.anim.tween.Tween;
import com.jme3.anim.util.HasLocalTransform;
import com.jme3.math.Transform;
import java.util.Collection;
public abstract class BlendableAction extends Action {
protected BlendableAction collectTransformDelegate;
private float transitionWeight = 1.0f;
private double transitionLength = 0.4f;
private float weight = 1f;
private TransitionTween transition = new TransitionTween(transitionLength);
public BlendableAction(Tween... tweens) {
super(tweens);
}
public void setCollectTransformDelegate(BlendableAction delegate) {
this.collectTransformDelegate = delegate;
}
@Override
public boolean subInterpolate(double t) {
// Sanity check the inputs
if (t < 0) {
return true;
}
if (collectTransformDelegate == null) {
if (transition.getLength() > getLength()) {
transition.setLength(getLength());
}
transition.interpolate(t);
} else {
transitionWeight = 1f;
}
if (weight == 0) {
//weight is 0 let's not interpolate
return t < getLength();
}
doInterpolate(t);
return t < getLength();
}
public float getWeight() {
return weight;
}
public void setWeight(float weight) {
this.weight = weight;
}
protected abstract void doInterpolate(double t);
public abstract Collection<HasLocalTransform> getTargets();
public abstract void collectTransform(HasLocalTransform target, Transform t, float weight, BlendableAction source);
public double getTransitionLength() {
return transitionLength;
}
public void setTransitionLength(double transitionLength) {
this.transitionLength = transitionLength;
}
protected float getTransitionWeight() {
return transitionWeight;
}
private class TransitionTween extends AbstractTween {
public TransitionTween(double length) {
super(length);
}
@Override
protected void doInterpolate(double t) {
transitionWeight = (float) t;
}
}
}

@ -0,0 +1,86 @@
package com.jme3.anim.tween.action;
import com.jme3.anim.*;
import com.jme3.anim.tween.AbstractTween;
import com.jme3.anim.util.HasLocalTransform;
import com.jme3.math.Transform;
import com.jme3.scene.Geometry;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class ClipAction extends BlendableAction {
private AnimClip clip;
private Transform transform = new Transform();
public ClipAction(AnimClip clip) {
this.clip = clip;
setLength(clip.getLength());
}
@Override
public void doInterpolate(double t) {
AnimTrack[] tracks = clip.getTracks();
for (AnimTrack track : tracks) {
if (track instanceof TransformTrack) {
interpolateTransformTrack(t, (TransformTrack) track);
} else if (track instanceof MorphTrack) {
interpolateMorphTrack(t, (MorphTrack) track);
}
}
}
private void interpolateTransformTrack(double t, TransformTrack track) {
HasLocalTransform target = track.getTarget();
transform.set(target.getLocalTransform());
track.getDataAtTime(t, transform);
if (collectTransformDelegate != null) {
collectTransformDelegate.collectTransform(target, transform, getWeight(), this);
} else {
this.collectTransform(target, transform, getTransitionWeight(), this);
}
}
private void interpolateMorphTrack(double t, MorphTrack track) {
Geometry target = track.getTarget();
float[] weights = target.getMorphState();
track.getDataAtTime(t, weights);
target.setMorphState(weights);
// if (collectTransformDelegate != null) {
// collectTransformDelegate.collectTransform(target, transform, getWeight(), this);
// } else {
// this.collectTransform(target, transform, getTransitionWeight(), this);
// }
}
public void reset() {
}
@Override
public Collection<HasLocalTransform> getTargets() {
List<HasLocalTransform> targets = new ArrayList<>(clip.getTracks().length);
for (AnimTrack track : clip.getTracks()) {
if (track instanceof TransformTrack) {
targets.add(((TransformTrack) track).getTarget());
}
}
return targets;
}
@Override
public void collectTransform(HasLocalTransform target, Transform t, float weight, BlendableAction source) {
if (weight == 1f) {
target.setLocalTransform(t);
} else {
Transform tr = target.getLocalTransform();
tr.interpolateTransforms(tr, t, weight);
target.setLocalTransform(tr);
}
}
}

@ -0,0 +1,49 @@
package com.jme3.anim.tween.action;
public class LinearBlendSpace implements BlendSpace {
private BlendAction action;
private float value;
private float maxValue;
private float minValue;
private float step;
public LinearBlendSpace(float minValue, float maxValue) {
this.maxValue = maxValue;
this.minValue = minValue;
}
@Override
public void setBlendAction(BlendAction action) {
this.action = action;
Action[] actions = action.getActions();
step = (maxValue - minValue) / (float) (actions.length - 1);
}
@Override
public float getWeight() {
Action[] actions = action.getActions();
float lowStep = minValue, highStep = minValue;
int lowIndex = 0, highIndex = 0;
for (int i = 0; i < actions.length && highStep < value; i++) {
lowStep = highStep;
lowIndex = i;
highStep += step;
}
highIndex = lowIndex + 1;
action.setFirstActiveIndex(lowIndex);
action.setSecondActiveIndex(highIndex);
if (highStep == lowStep) {
return 0;
}
return (value - lowStep) / (highStep - lowStep);
}
@Override
public void setValue(float value) {
this.value = value;
}
}

@ -0,0 +1,200 @@
package com.jme3.anim.util;
import com.jme3.anim.*;
import com.jme3.animation.*;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.*;
import java.util.*;
public class AnimMigrationUtils {
private static AnimControlVisitor animControlVisitor = new AnimControlVisitor();
private static SkeletonControlVisitor skeletonControlVisitor = new SkeletonControlVisitor();
public static Spatial migrate(Spatial source) {
Map<Skeleton, Armature> skeletonArmatureMap = new HashMap<>();
animControlVisitor.setMappings(skeletonArmatureMap);
source.depthFirstTraversal(animControlVisitor);
skeletonControlVisitor.setMappings(skeletonArmatureMap);
source.depthFirstTraversal(skeletonControlVisitor);
return source;
}
private static class AnimControlVisitor implements SceneGraphVisitor {
Map<Skeleton, Armature> skeletonArmatureMap;
@Override
public void visit(Spatial spatial) {
AnimControl control = spatial.getControl(AnimControl.class);
if (control != null) {
AnimComposer composer = new AnimComposer();
Skeleton skeleton = control.getSkeleton();
if (skeleton == null) {
//only bone anim for now
return;
}
Joint[] joints = new Joint[skeleton.getBoneCount()];
for (int i = 0; i < skeleton.getBoneCount(); i++) {
Bone b = skeleton.getBone(i);
Joint j = joints[i];
if (j == null) {
j = fromBone(b);
joints[i] = j;
}
for (Bone bone : b.getChildren()) {
int index = skeleton.getBoneIndex(bone);
Joint joint = joints[index];
if (joint == null) {
joint = fromBone(bone);
}
j.addChild(joint);
joints[index] = joint;
}
}
Armature armature = new Armature(joints);
armature.saveBindPose();
skeletonArmatureMap.put(skeleton, armature);
List<TransformTrack> tracks = new ArrayList<>();
for (String animName : control.getAnimationNames()) {
tracks.clear();
Animation anim = control.getAnim(animName);
AnimClip clip = new AnimClip(animName);
Joint[] staticJoints = new Joint[joints.length];
System.arraycopy(joints, 0, staticJoints, 0, joints.length);
for (Track track : anim.getTracks()) {
if (track instanceof BoneTrack) {
BoneTrack boneTrack = (BoneTrack) track;
int index = boneTrack.getTargetBoneIndex();
Bone bone = skeleton.getBone(index);
Joint joint = joints[index];
TransformTrack jointTrack = fromBoneTrack(boneTrack, bone, joint);
tracks.add(jointTrack);
//this joint is animated let's remove it from the static joints
staticJoints[index] = null;
}
//TODO spatial tracks , Effect tracks, Audio tracks
}
for (int i = 0; i < staticJoints.length; i++) {
padJointTracks(tracks, staticJoints[i]);
}
clip.setTracks(tracks.toArray(new TransformTrack[tracks.size()]));
composer.addAnimClip(clip);
}
spatial.removeControl(control);
spatial.addControl(composer);
}
}
public void setMappings(Map<Skeleton, Armature> skeletonArmatureMap) {
this.skeletonArmatureMap = skeletonArmatureMap;
}
}
public static void padJointTracks(List<TransformTrack> tracks, Joint staticJoint) {
Joint j = staticJoint;
if (j != null) {
// joint has no track , we create one with the default pose
float[] times = new float[]{0};
Vector3f[] translations = new Vector3f[]{j.getLocalTranslation()};
Quaternion[] rotations = new Quaternion[]{j.getLocalRotation()};
Vector3f[] scales = new Vector3f[]{j.getLocalScale()};
TransformTrack track = new TransformTrack(j, times, translations, rotations, scales);
tracks.add(track);
}
}
private static class SkeletonControlVisitor implements SceneGraphVisitor {
Map<Skeleton, Armature> skeletonArmatureMap;
@Override
public void visit(Spatial spatial) {
SkeletonControl control = spatial.getControl(SkeletonControl.class);
if (control != null) {
Armature armature = skeletonArmatureMap.get(control.getSkeleton());
SkinningControl skinningControl = new SkinningControl(armature);
Map<String, List<Spatial>> attachedSpatials = new HashMap<>();
for (int i = 0; i < control.getSkeleton().getBoneCount(); i++) {
Bone b = control.getSkeleton().getBone(i);
Node n = control.getAttachmentsNode(b.getName());
n.removeFromParent();
if (!n.getChildren().isEmpty()) {
attachedSpatials.put(b.getName(), n.getChildren());
}
}
spatial.removeControl(control);
spatial.addControl(skinningControl);
for (String name : attachedSpatials.keySet()) {
List<Spatial> spatials = attachedSpatials.get(name);
for (Spatial child : spatials) {
skinningControl.getAttachmentsNode(name).attachChild(child);
}
}
}
}
public void setMappings(Map<Skeleton, Armature> skeletonArmatureMap) {
this.skeletonArmatureMap = skeletonArmatureMap;
}
}
public static TransformTrack fromBoneTrack(BoneTrack boneTrack, Bone bone, Joint joint) {
float[] times = new float[boneTrack.getTimes().length];
int length = times.length;
System.arraycopy(boneTrack.getTimes(), 0, times, 0, length);
//translation
Vector3f[] translations = new Vector3f[length];
if (boneTrack.getTranslations() != null) {
for (int i = 0; i < boneTrack.getTranslations().length; i++) {
Vector3f oldTrans = boneTrack.getTranslations()[i];
Vector3f newTrans = new Vector3f();
newTrans.set(bone.getBindPosition()).addLocal(oldTrans);
translations[i] = newTrans;
}
}
//rotation
Quaternion[] rotations = new Quaternion[length];
if (boneTrack.getRotations() != null) {
for (int i = 0; i < boneTrack.getRotations().length; i++) {
Quaternion oldRot = boneTrack.getRotations()[i];
Quaternion newRot = new Quaternion();
newRot.set(bone.getBindRotation()).multLocal(oldRot);
rotations[i] = newRot;
}
}
//scale
Vector3f[] scales = new Vector3f[length];
if (boneTrack.getScales() != null) {
for (int i = 0; i < boneTrack.getScales().length; i++) {
Vector3f oldScale = boneTrack.getScales()[i];
Vector3f newScale = new Vector3f();
newScale.set(bone.getBindScale()).multLocal(oldScale);
scales[i] = newScale;
}
}
TransformTrack t = new TransformTrack(joint, times, translations, rotations, scales);
return t;
}
private static Joint fromBone(Bone b) {
Joint j = new Joint(b.getName());
j.setLocalTranslation(b.getBindPosition());
j.setLocalRotation(b.getBindRotation());
j.setLocalScale(b.getBindScale());
return j;
}
}

@ -0,0 +1,10 @@
package com.jme3.anim.util;
import com.jme3.export.Savable;
import com.jme3.math.Transform;
public interface HasLocalTransform extends Savable {
public void setLocalTransform(Transform transform);
public Transform getLocalTransform();
}

@ -0,0 +1,20 @@
package com.jme3.anim.util;
import com.jme3.anim.Joint;
import com.jme3.math.Matrix4f;
import com.jme3.math.Transform;
/**
* Implementations of this interface holds accumulated model transform of a Joint.
* Implementation might choose different accumulation strategy.
*/
public interface JointModelTransform {
void updateModelTransform(Transform localTransform, Joint parent);
void getOffsetTransform(Matrix4f outTransform, Matrix4f inverseModelBindMatrix);
void applyBindPose(Transform localTransform, Matrix4f inverseModelBindMatrix, Joint parent);
Transform getModelTransform();
}

@ -0,0 +1,56 @@
package com.jme3.anim.util;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* This is a guava method used in {@link com.jme3.anim.tween.Tweens} class.
* Maybe we should just add guava as a dependency in the engine...
* //TODO do something about this.
*/
public class Primitives {
/**
* A map from primitive types to their corresponding wrapper types.
*/
private static final Map<Class<?>, Class<?>> PRIMITIVE_TO_WRAPPER_TYPE;
static {
Map<Class<?>, Class<?>> primToWrap = new HashMap<>(16);
primToWrap.put(boolean.class, Boolean.class);
primToWrap.put(byte.class, Byte.class);
primToWrap.put(char.class, Character.class);
primToWrap.put(double.class, Double.class);
primToWrap.put(float.class, Float.class);
primToWrap.put(int.class, Integer.class);
primToWrap.put(long.class, Long.class);
primToWrap.put(short.class, Short.class);
primToWrap.put(void.class, Void.class);
PRIMITIVE_TO_WRAPPER_TYPE = Collections.unmodifiableMap(primToWrap);
}
/**
* Returns the corresponding wrapper type of {@code type} if it is a primitive type; otherwise
* returns {@code type} itself. Idempotent.
* <p>
* <pre>
* wrap(int.class) == Integer.class
* wrap(Integer.class) == Integer.class
* wrap(String.class) == String.class
* </pre>
*/
public static <T> Class<T> wrap(Class<T> type) {
if (type == null) {
throw new IllegalArgumentException("type is null");
}
// cast is safe: long.class and Long.class are both of type Class<Long>
@SuppressWarnings("unchecked")
Class<T> wrapped = (Class<T>) PRIMITIVE_TO_WRAPPER_TYPE.get(type);
return (wrapped == null) ? type : wrapped;
}
}

@ -0,0 +1,11 @@
package com.jme3.anim.util;
import com.jme3.anim.tween.action.Action;
import com.jme3.math.Transform;
public interface Weighted {
// public void setWeight(float weight);
public void setParentAction(Action action);
}

@ -33,6 +33,7 @@ package com.jme3.animation;
import com.jme3.math.FastMath; import com.jme3.math.FastMath;
import com.jme3.util.TempVars; import com.jme3.util.TempVars;
import java.util.BitSet; import java.util.BitSet;
/** /**
@ -46,6 +47,7 @@ import java.util.BitSet;
* *
* @author Kirill Vainer * @author Kirill Vainer
*/ */
@Deprecated
public final class AnimChannel { public final class AnimChannel {
private static final float DEFAULT_BLEND_TIME = 0.15f; private static final float DEFAULT_BLEND_TIME = 0.15f;

@ -64,7 +64,9 @@ import java.util.Map;
* 1) Morph/Pose animation * 1) Morph/Pose animation
* *
* @author Kirill Vainer * @author Kirill Vainer
* @deprecated use {@link com.jme3.anim.AnimComposer}
*/ */
@Deprecated
public final class AnimControl extends AbstractControl implements Cloneable, JmeCloneable { public final class AnimControl extends AbstractControl implements Cloneable, JmeCloneable {
/** /**

@ -37,6 +37,7 @@ package com.jme3.animation;
* *
* @author Kirill Vainer * @author Kirill Vainer
*/ */
@Deprecated
public interface AnimEventListener { public interface AnimEventListener {
/** /**

@ -37,13 +37,16 @@ import com.jme3.util.SafeArrayList;
import com.jme3.util.TempVars; import com.jme3.util.TempVars;
import com.jme3.util.clone.Cloner; import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable; import com.jme3.util.clone.JmeCloneable;
import java.io.IOException; import java.io.IOException;
/** /**
* The animation class updates the animation target with the tracks of a given type. * The animation class updates the animation target with the tracks of a given type.
* *
* @author Kirill Vainer, Marcin Roguski (Kaelthas) * @author Kirill Vainer, Marcin Roguski (Kaelthas)
* @deprecated use {@link com.jme3.anim.AnimClip}
*/ */
@Deprecated
public class Animation implements Savable, Cloneable, JmeCloneable { public class Animation implements Savable, Cloneable, JmeCloneable {
/** /**

@ -32,15 +32,12 @@
package com.jme3.animation; package com.jme3.animation;
import com.jme3.audio.AudioNode; import com.jme3.audio.AudioNode;
import com.jme3.export.InputCapsule; import com.jme3.export.*;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.scene.Node; import com.jme3.scene.Node;
import com.jme3.scene.Spatial; import com.jme3.scene.Spatial;
import com.jme3.util.TempVars; import com.jme3.util.TempVars;
import com.jme3.util.clone.Cloner; import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException; import java.io.IOException;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -62,6 +59,7 @@ import java.util.logging.Logger;
* *
* @author Nehon * @author Nehon
*/ */
@Deprecated
public class AudioTrack implements ClonableTrack { public class AudioTrack implements ClonableTrack {
private static final Logger logger = Logger.getLogger(AudioTrack.class.getName()); private static final Logger logger = Logger.getLogger(AudioTrack.class.getName());

@ -67,7 +67,9 @@ import java.util.ArrayList;
* *
* @author Kirill Vainer * @author Kirill Vainer
* @author Rémy Bouquet * @author Rémy Bouquet
* @deprecated use {@link com.jme3.anim.Joint}
*/ */
@Deprecated
public final class Bone implements Savable, JmeCloneable { public final class Bone implements Savable, JmeCloneable {
// Version #2: Changed naming of transforms as they were misleading // Version #2: Changed naming of transforms as they were misleading

@ -44,7 +44,9 @@ import java.util.BitSet;
* Contains a list of transforms and times for each keyframe. * Contains a list of transforms and times for each keyframe.
* *
* @author Kirill Vainer * @author Kirill Vainer
* @deprecated use {@link com.jme3.anim.JointTrack}
*/ */
@Deprecated
public final class BoneTrack implements JmeCloneable, Track { public final class BoneTrack implements JmeCloneable, Track {
/** /**

@ -44,6 +44,7 @@ import com.jme3.util.clone.JmeCloneable;
* *
* @author Nehon * @author Nehon
*/ */
@Deprecated
public interface ClonableTrack extends Track, JmeCloneable { public interface ClonableTrack extends Track, JmeCloneable {
/** /**

@ -44,7 +44,7 @@ import java.util.Map;
*/ */
public abstract class CompactArray<T> implements JmeCloneable { public abstract class CompactArray<T> implements JmeCloneable {
private Map<T, Integer> indexPool = new HashMap<T, Integer>(); protected Map<T, Integer> indexPool = new HashMap<T, Integer>();
protected int[] index; protected int[] index;
protected float[] array; protected float[] array;
private boolean invalid; private boolean invalid;
@ -114,6 +114,10 @@ public abstract class CompactArray<T> implements JmeCloneable {
indexPool.clear(); indexPool.clear();
} }
protected void setInvalid(boolean invalid) {
this.invalid = invalid;
}
/** /**
* @param index * @param index
* @param value * @param value

@ -0,0 +1,100 @@
/*
* 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.animation;
import com.jme3.export.*;
import com.jme3.math.Vector3f;
import java.io.IOException;
/**
* Serialize and compress Float by indexing similar values
* @author Lim, YongHoon
*/
public class CompactFloatArray extends CompactArray<Float> implements Savable {
/**
* Creates a compact vector array
*/
public CompactFloatArray() {
}
/**
* creates a compact vector array
* @param dataArray the data array
* @param index the indices
*/
public CompactFloatArray(float[] dataArray, int[] index) {
super(dataArray, index);
}
@Override
protected final int getTupleSize() {
return 1;
}
@Override
protected final Class<Float> getElementClass() {
return Float.class;
}
@Override
public void write(JmeExporter ex) throws IOException {
serialize();
OutputCapsule out = ex.getCapsule(this);
out.write(array, "array", null);
out.write(index, "index", null);
}
@Override
public void read(JmeImporter im) throws IOException {
InputCapsule in = im.getCapsule(this);
array = in.readFloatArray("array", null);
index = in.readIntArray("index", null);
}
public void fill(int startIndex, float[] store ){
for (int i = 0; i < store.length; i++) {
store[i] = get(startIndex + i, null);
}
}
@Override
protected void serialize(int i, Float data) {
array[i] = data;
}
@Override
protected Float deserialize(int i, Float store) {
return array[i];
}
}

@ -32,10 +32,7 @@
package com.jme3.animation; package com.jme3.animation;
import com.jme3.effect.ParticleEmitter; import com.jme3.effect.ParticleEmitter;
import com.jme3.export.InputCapsule; import com.jme3.export.*;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.renderer.RenderManager; import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort; import com.jme3.renderer.ViewPort;
import com.jme3.scene.Node; import com.jme3.scene.Node;
@ -67,6 +64,7 @@ import java.util.logging.Logger;
* *
* @author Nehon * @author Nehon
*/ */
@Deprecated
public class EffectTrack implements ClonableTrack { public class EffectTrack implements ClonableTrack {
private static final Logger logger = Logger.getLogger(EffectTrack.class.getName()); private static final Logger logger = Logger.getLogger(EffectTrack.class.getName());
@ -130,15 +128,17 @@ public class EffectTrack implements ClonableTrack {
@Override @Override
protected void controlRender(RenderManager rm, ViewPort vp) { protected void controlRender(RenderManager rm, ViewPort vp) {
} }
}; }
//Anim listener that stops the Emmitter when the animation is finished or changed. //Anim listener that stops the Emmitter when the animation is finished or changed.
private class OnEndListener implements AnimEventListener { private class OnEndListener implements AnimEventListener {
@Override
public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) { public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
stop(); stop();
} }
@Override
public void onAnimChange(AnimControl control, AnimChannel channel, String animName) { public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {
} }
} }
@ -188,6 +188,7 @@ public class EffectTrack implements ClonableTrack {
* @see Track#setTime(float, float, com.jme3.animation.AnimControl, * @see Track#setTime(float, float, com.jme3.animation.AnimControl,
* com.jme3.animation.AnimChannel, com.jme3.util.TempVars) * com.jme3.animation.AnimChannel, com.jme3.util.TempVars)
*/ */
@Override
public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) { public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) {
if (time >= length) { if (time >= length) {
@ -233,6 +234,7 @@ public class EffectTrack implements ClonableTrack {
* *
* @return length of the track * @return length of the track
*/ */
@Override
public float getLength() { public float getLength() {
return length; return length;
} }
@ -325,6 +327,7 @@ public class EffectTrack implements ClonableTrack {
return null; return null;
} }
@Override
public void cleanUp() { public void cleanUp() {
TrackInfo t = (TrackInfo) emitter.getUserData("TrackInfo"); TrackInfo t = (TrackInfo) emitter.getUserData("TrackInfo");
t.getTracks().remove(this); t.getTracks().remove(this);
@ -413,6 +416,7 @@ public class EffectTrack implements ClonableTrack {
* @param ex exporter * @param ex exporter
* @throws IOException exception * @throws IOException exception
*/ */
@Override
public void write(JmeExporter ex) throws IOException { public void write(JmeExporter ex) throws IOException {
OutputCapsule out = ex.getCapsule(this); OutputCapsule out = ex.getCapsule(this);
//reset the particle emission rate on the emitter before saving. //reset the particle emission rate on the emitter before saving.
@ -431,6 +435,7 @@ public class EffectTrack implements ClonableTrack {
* @param im importer * @param im importer
* @throws IOException Exception * @throws IOException Exception
*/ */
@Override
public void read(JmeImporter im) throws IOException { public void read(JmeImporter im) throws IOException {
InputCapsule in = im.getCapsule(this); InputCapsule in = im.getCapsule(this);
this.particlesPerSeconds = in.readFloat("particlesPerSeconds", 0); this.particlesPerSeconds = in.readFloat("particlesPerSeconds", 0);

@ -35,6 +35,7 @@ package com.jme3.animation;
* <code>LoopMode</code> determines how animations repeat, or if they * <code>LoopMode</code> determines how animations repeat, or if they
* do not repeat. * do not repeat.
*/ */
@Deprecated
public enum LoopMode { public enum LoopMode {
/** /**
* The animation will play repeatedly, when it reaches the end * The animation will play repeatedly, when it reaches the end

@ -34,12 +34,14 @@ package com.jme3.animation;
import com.jme3.export.*; import com.jme3.export.*;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
import com.jme3.util.BufferUtils; import com.jme3.util.BufferUtils;
import java.io.IOException; import java.io.IOException;
import java.nio.FloatBuffer; import java.nio.FloatBuffer;
/** /**
* A pose is a list of offsets that say where a mesh vertices should be for this pose. * A pose is a list of offsets that say where a mesh vertices should be for this pose.
*/ */
@Deprecated
public final class Pose implements Savable, Cloneable { public final class Pose implements Savable, Cloneable {
private String name; private String name;

@ -31,11 +31,13 @@
*/ */
package com.jme3.animation; package com.jme3.animation;
import com.jme3.anim.Armature;
import com.jme3.export.*; import com.jme3.export.*;
import com.jme3.math.Matrix4f; import com.jme3.math.Matrix4f;
import com.jme3.util.TempVars; import com.jme3.util.TempVars;
import com.jme3.util.clone.JmeCloneable;
import com.jme3.util.clone.Cloner; import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -46,7 +48,9 @@ import java.util.List;
* animated matrixes. * animated matrixes.
* *
* @author Kirill Vainer * @author Kirill Vainer
* @deprecated use {@link Armature}
*/ */
@Deprecated
public final class Skeleton implements Savable, JmeCloneable { public final class Skeleton implements Savable, JmeCloneable {
private Bone[] rootBones; private Bone[] rootBones;

@ -31,6 +31,7 @@
*/ */
package com.jme3.animation; package com.jme3.animation;
import com.jme3.anim.SkinningControl;
import com.jme3.export.*; import com.jme3.export.*;
import com.jme3.material.MatParamOverride; import com.jme3.material.MatParamOverride;
import com.jme3.math.FastMath; import com.jme3.math.FastMath;
@ -57,7 +58,9 @@ import java.util.logging.Logger;
* the mesh * the mesh
* *
* @author Rémy Bouquet Based on AnimControl by Kirill Vainer * @author Rémy Bouquet Based on AnimControl by Kirill Vainer
* @deprecated use {@link SkinningControl}
*/ */
@Deprecated
public class SkeletonControl extends AbstractControl implements Cloneable, JmeCloneable { public class SkeletonControl extends AbstractControl implements Cloneable, JmeCloneable {
/** /**

@ -31,10 +31,7 @@
*/ */
package com.jme3.animation; package com.jme3.animation;
import com.jme3.export.InputCapsule; import com.jme3.export.*;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.math.Quaternion; import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
import com.jme3.scene.Spatial; import com.jme3.scene.Spatial;
@ -48,6 +45,7 @@ import java.io.IOException;
* *
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
@Deprecated
public class SpatialTrack implements JmeCloneable, Track { public class SpatialTrack implements JmeCloneable, Track {
/** /**

@ -34,6 +34,7 @@ package com.jme3.animation;
import com.jme3.export.Savable; import com.jme3.export.Savable;
import com.jme3.util.TempVars; import com.jme3.util.TempVars;
@Deprecated
public interface Track extends Savable, Cloneable { public interface Track extends Savable, Cloneable {
/** /**

@ -31,13 +31,10 @@
*/ */
package com.jme3.animation; package com.jme3.animation;
import com.jme3.export.InputCapsule; import com.jme3.export.*;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.export.Savable;
import com.jme3.util.clone.Cloner; import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable; import com.jme3.util.clone.JmeCloneable;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@ -50,6 +47,7 @@ import java.util.ArrayList;
* *
* @author Nehon * @author Nehon
*/ */
@Deprecated
public class TrackInfo implements Savable, JmeCloneable { public class TrackInfo implements Savable, JmeCloneable {
ArrayList<Track> tracks = new ArrayList<Track>(); ArrayList<Track> tracks = new ArrayList<Track>();

@ -31,24 +31,16 @@
*/ */
package com.jme3.cinematic.events; package com.jme3.cinematic.events;
import com.jme3.animation.AnimChannel; import com.jme3.animation.*;
import com.jme3.animation.AnimControl;
import com.jme3.animation.LoopMode;
import com.jme3.app.Application; import com.jme3.app.Application;
import com.jme3.cinematic.Cinematic; import com.jme3.cinematic.Cinematic;
import com.jme3.cinematic.PlayState; import com.jme3.cinematic.PlayState;
import com.jme3.export.InputCapsule; import com.jme3.export.*;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.scene.Node; import com.jme3.scene.Node;
import com.jme3.scene.Spatial; import com.jme3.scene.Spatial;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
import java.io.IOException; import java.io.IOException;
import java.util.Collection; import java.util.*;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger; import java.util.logging.Logger;
/** /**
@ -60,6 +52,7 @@ import java.util.logging.Logger;
* *
* @author Nehon * @author Nehon
*/ */
@Deprecated
public class AnimationEvent extends AbstractCinematicEvent { public class AnimationEvent extends AbstractCinematicEvent {
// Version #2: directly keeping track on the model instead of trying to retrieve // Version #2: directly keeping track on the model instead of trying to retrieve

@ -140,6 +140,9 @@ public final class MatParamOverride extends MatParam {
super.write(ex); super.write(ex);
OutputCapsule oc = ex.getCapsule(this); OutputCapsule oc = ex.getCapsule(this);
oc.write(enabled, "enabled", true); oc.write(enabled, "enabled", true);
if (value == null) {
oc.write(true, "isNull", false);
}
} }
@Override @Override
@ -147,5 +150,9 @@ public final class MatParamOverride extends MatParam {
super.read(im); super.read(im);
InputCapsule ic = im.getCapsule(this); InputCapsule ic = im.getCapsule(this);
enabled = ic.readBoolean("enabled", true); enabled = ic.readBoolean("enabled", true);
boolean isNull = ic.readBoolean("isNull", false);
if (isNull) {
setValue(null);
}
} }
} }

@ -836,7 +836,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
* *
* @param renderManager The render manager to preload for * @param renderManager The render manager to preload for
*/ */
public void preload(RenderManager renderManager) { public void preload(RenderManager renderManager, Geometry geometry) {
if (technique == null) { if (technique == null) {
selectTechnique(TechniqueDef.DEFAULT_TECHNIQUE_NAME, renderManager); selectTechnique(TechniqueDef.DEFAULT_TECHNIQUE_NAME, renderManager);
} }
@ -847,9 +847,11 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
if (techniqueDef.isNoRender()) { if (techniqueDef.isNoRender()) {
return; return;
} }
// Get world overrides
SafeArrayList<MatParamOverride> overrides = geometry.getWorldMatParamOverrides();
Shader shader = technique.makeCurrent(renderManager, null, null, null, rendererCaps); Shader shader = technique.makeCurrent(renderManager, overrides, null, null, rendererCaps);
updateShaderMaterialParameters(renderer, shader, null, null); updateShaderMaterialParameters(renderer, shader, overrides, null);
renderManager.getRenderer().setShader(shader); renderManager.getRenderer().setShader(shader);
} }

@ -0,0 +1,13 @@
package com.jme3.math;
/**
* Created by Nehon on 26/03/2017.
*/
public interface EaseFunction {
/**
* @param value a value from 0 to 1. Passing a value out of this range will have unexpected behavior.
* @return
*/
float apply(float value);
}

@ -0,0 +1,163 @@
package com.jme3.math;
/**
* Expose several Easing function from Robert Penner
* Created by Nehon on 26/03/2017.
*/
public class Easing {
public static EaseFunction constant = new EaseFunction() {
@Override
public float apply(float value) {
return 0;
}
};
/**
* In
*/
public static EaseFunction linear = new EaseFunction() {
@Override
public float apply(float value) {
return value;
}
};
public static EaseFunction inQuad = new EaseFunction() {
@Override
public float apply(float value) {
return value * value;
}
};
public static EaseFunction inCubic = new EaseFunction() {
@Override
public float apply(float value) {
return value * value * value;
}
};
public static EaseFunction inQuart = new EaseFunction() {
@Override
public float apply(float value) {
return value * value * value * value;
}
};
public static EaseFunction inQuint = new EaseFunction() {
@Override
public float apply(float value) {
return value * value * value * value * value;
}
};
/**
* Out Elastic and bounce
*/
public static EaseFunction outElastic = new EaseFunction() {
@Override
public float apply(float value) {
return FastMath.pow(2f, -10f * value) * FastMath.sin((value - 0.3f / 4f) * (2f * FastMath.PI) / 0.3f) + 1f;
}
};
public static EaseFunction outBounce = new EaseFunction() {
@Override
public float apply(float value) {
if (value < (1f / 2.75f)) {
return (7.5625f * value * value);
} else if (value < (2f / 2.75f)) {
return (7.5625f * (value -= (1.5f / 2.75f)) * value + 0.75f);
} else if (value < (2.5 / 2.75)) {
return (7.5625f * (value -= (2.25f / 2.75f)) * value + 0.9375f);
} else {
return (7.5625f * (value -= (2.625f / 2.75f)) * value + 0.984375f);
}
}
};
/**
* In Elastic and bounce
*/
public static EaseFunction inElastic = new Invert(outElastic);
public static EaseFunction inBounce = new Invert(outBounce);
/**
* Out
*/
public static EaseFunction outQuad = new Invert(inQuad);
public static EaseFunction outCubic = new Invert(inCubic);
public static EaseFunction outQuart = new Invert(inQuart);
public static EaseFunction outQuint = new Invert(inQuint);
/**
* inOut
*/
public static EaseFunction inOutQuad = new InOut(inQuad, outQuad);
public static EaseFunction inOutCubic = new InOut(inCubic, outCubic);
public static EaseFunction inOutQuart = new InOut(inQuart, outQuart);
public static EaseFunction inOutQuint = new InOut(inQuint, outQuint);
public static EaseFunction inOutElastic = new InOut(inElastic, outElastic);
public static EaseFunction inOutBounce = new InOut(inBounce, outBounce);
/**
* Extra functions
*/
public static EaseFunction smoothStep = new EaseFunction() {
@Override
public float apply(float t) {
return t * t * (3f - 2f * t);
}
};
public static EaseFunction smootherStep = new EaseFunction() {
@Override
public float apply(float t) {
return t * t * t * (t * (t * 6f - 15f) + 10f);
}
};
/**
* An Ease function composed of 2 sb function for custom in and out easing
*/
public static class InOut implements EaseFunction {
private EaseFunction in;
private EaseFunction out;
public InOut(EaseFunction in, EaseFunction out) {
this.in = in;
this.out = out;
}
@Override
public float apply(float value) {
if (value < 0.5) {
value = value * 2;
return inQuad.apply(value) / 2;
} else {
value = (value - 0.5f) * 2;
return outQuad.apply(value) / 2 + 0.5f;
}
}
}
private static class Invert implements EaseFunction {
private EaseFunction func;
public Invert(EaseFunction func) {
this.func = func;
}
@Override
public float apply(float value) {
return 1f - func.apply(1f - value);
}
}
}

@ -0,0 +1,247 @@
package com.jme3.math;
import com.jme3.renderer.Camera;
import com.jme3.util.TempVars;
/**
* Created by Nehon on 23/04/2017.
*/
public class MathUtils {
public static Quaternion log(Quaternion q, Quaternion store) {
float a = FastMath.acos(q.w);
float sina = FastMath.sin(a);
store.w = 0;
if (sina > 0) {
store.x = a * q.x / sina;
store.y = a * q.y / sina;
store.z = a * q.z / sina;
} else {
store.x = 0;
store.y = 0;
store.z = 0;
}
return store;
}
public static Quaternion exp(Quaternion q, Quaternion store) {
float len = FastMath.sqrt(q.x * q.x + q.y * q.y + q.z * q.z);
float sinLen = FastMath.sin(len);
float cosLen = FastMath.cos(len);
store.w = cosLen;
if (len > 0) {
store.x = sinLen * q.x / len;
store.y = sinLen * q.y / len;
store.z = sinLen * q.z / len;
} else {
store.x = 0;
store.y = 0;
store.z = 0;
}
return store;
}
//! This version of slerp, used by squad, does not check for theta > 90.
public static Quaternion slerpNoInvert(Quaternion q1, Quaternion q2, float t, Quaternion store) {
float dot = q1.dot(q2);
if (dot > -0.95f && dot < 0.95f) {
float angle = FastMath.acos(dot);
float sin1 = FastMath.sin(angle * (1 - t));
float sin2 = FastMath.sin(angle * t);
float sin3 = FastMath.sin(angle);
store.x = (q1.x * sin1 + q2.x * sin2) / sin3;
store.y = (q1.y * sin1 + q2.y * sin2) / sin3;
store.z = (q1.z * sin1 + q2.z * sin2) / sin3;
store.w = (q1.w * sin1 + q2.w * sin2) / sin3;
System.err.println("real slerp");
} else {
// if the angle is small, use linear interpolation
store.set(q1).nlerp(q2, t);
System.err.println("nlerp");
}
return store;
}
public static Quaternion slerp(Quaternion q1, Quaternion q2, float t, Quaternion store) {
float dot = (q1.x * q2.x) + (q1.y * q2.y) + (q1.z * q2.z)
+ (q1.w * q2.w);
if (dot < 0.0f) {
// Negate the second quaternion and the result of the dot product
q2.x = -q2.x;
q2.y = -q2.y;
q2.z = -q2.z;
q2.w = -q2.w;
dot = -dot;
}
// Set the first and second scale for the interpolation
float scale0 = 1 - t;
float scale1 = t;
// Check if the angle between the 2 quaternions was big enough to
// warrant such calculations
if (dot < 0.9f) {// Get the angle between the 2 quaternions,
// and then store the sin() of that angle
float theta = FastMath.acos(dot);
float invSinTheta = 1f / FastMath.sin(theta);
// Calculate the scale for q1 and q2, according to the angle and
// it's sine value
scale0 = FastMath.sin((1 - t) * theta) * invSinTheta;
scale1 = FastMath.sin((t * theta)) * invSinTheta;
// Calculate the x, y, z and w values for the quaternion by using a
// special
// form of linear interpolation for quaternions.
store.x = (scale0 * q1.x) + (scale1 * q2.x);
store.y = (scale0 * q1.y) + (scale1 * q2.y);
store.z = (scale0 * q1.z) + (scale1 * q2.z);
store.w = (scale0 * q1.w) + (scale1 * q2.w);
} else {
store.x = (scale0 * q1.x) + (scale1 * q2.x);
store.y = (scale0 * q1.y) + (scale1 * q2.y);
store.z = (scale0 * q1.z) + (scale1 * q2.z);
store.w = (scale0 * q1.w) + (scale1 * q2.w);
store.normalizeLocal();
}
// Return the interpolated quaternion
return store;
}
// //! Given 3 quaternions, qn-1,qn and qn+1, calculate a control point to be used in spline interpolation
// private static Quaternion spline(Quaternion qnm1, Quaternion qn, Quaternion qnp1, Quaternion store, Quaternion tmp) {
// store.set(-qn.x, -qn.y, -qn.z, qn.w);
// //store.set(qn).inverseLocal();
// tmp.set(store);
//
// log(store.multLocal(qnm1), store);
// log(tmp.multLocal(qnp1), tmp);
// store.addLocal(tmp).multLocal(1f / -4f);
// exp(store, tmp);
// store.set(tmp).multLocal(qn);
//
// return store.normalizeLocal();
// //return qn * (((qni * qnm1).log() + (qni * qnp1).log()) / -4).exp();
// }
//! Given 3 quaternions, qn-1,qn and qn+1, calculate a control point to be used in spline interpolation
private static Quaternion spline(Quaternion qnm1, Quaternion qn, Quaternion qnp1, Quaternion store, Quaternion tmp) {
Quaternion invQn = new Quaternion(-qn.x, -qn.y, -qn.z, qn.w);
log(invQn.mult(qnp1), tmp);
log(invQn.mult(qnm1), store);
store.addLocal(tmp).multLocal(-1f / 4f);
exp(store, tmp);
store.set(qn).multLocal(tmp);
return store.normalizeLocal();
//return qn * (((qni * qnm1).log() + (qni * qnp1).log()) / -4).exp();
}
//! spherical cubic interpolation
public static Quaternion squad(Quaternion q0, Quaternion q1, Quaternion q2, Quaternion q3, Quaternion a, Quaternion b, float t, Quaternion store) {
spline(q0, q1, q2, a, store);
spline(q1, q2, q3, b, store);
slerp(a, b, t, store);
slerp(q1, q2, t, a);
return slerp(a, store, 2 * t * (1 - t), b);
//slerpNoInvert(a, b, t, store);
//slerpNoInvert(q1, q2, t, a);
//return slerpNoInvert(a, store, 2 * t * (1 - t), b);
// quaternion c = slerpNoInvert(q1, q2, t),
// d = slerpNoInvert(a, b, t);
// return slerpNoInvert(c, d, 2 * t * (1 - t));
}
/**
* Returns the shortest distance between a Ray and a segment.
* The segment is defined by a start position and an end position in world space
* The distance returned will be in world space (world units).
* If the camera parameter is not null the distance will be returned in screen space (pixels)
*
* @param ray The ray
* @param segStart The start position of the segment in world space
* @param segEnd The end position of the segment in world space
* @param camera The renderer camera if the distance is required in screen space. Null if the distance is required in world space
* @return the shortest distance between the ray and the segment or -1 if no solution is found.
*/
public static float raySegmentShortestDistance(Ray ray, Vector3f segStart, Vector3f segEnd, Camera camera) {
// Algorithm is ported from the C algorithm of
// Paul Bourke at http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline3d/
TempVars vars = TempVars.get();
Vector3f resultSegmentPoint1 = vars.vect1;
Vector3f resultSegmentPoint2 = vars.vect2;
Vector3f p1 = segStart;
Vector3f p2 = segEnd;
Vector3f p3 = ray.origin;
Vector3f p4 = vars.vect3.set(ray.getDirection()).multLocal(Math.min(ray.getLimit(), 1000)).addLocal(ray.getOrigin());
Vector3f p13 = vars.vect4.set(p1).subtractLocal(p3);
Vector3f p43 = vars.vect5.set(p4).subtractLocal(p3);
if (p43.lengthSquared() < 0.0001) {
vars.release();
return -1;
}
Vector3f p21 = vars.vect6.set(p2).subtractLocal(p1);
if (p21.lengthSquared() < 0.0001) {
vars.release();
return -1;
}
double d1343 = p13.x * (double) p43.x + (double) p13.y * p43.y + (double) p13.z * p43.z;
double d4321 = p43.x * (double) p21.x + (double) p43.y * p21.y + (double) p43.z * p21.z;
double d1321 = p13.x * (double) p21.x + (double) p13.y * p21.y + (double) p13.z * p21.z;
double d4343 = p43.x * (double) p43.x + (double) p43.y * p43.y + (double) p43.z * p43.z;
double d2121 = p21.x * (double) p21.x + (double) p21.y * p21.y + (double) p21.z * p21.z;
double denom = d2121 * d4343 - d4321 * d4321;
if (Math.abs(denom) < 0.0001) {
vars.release();
return -1;
}
double numer = d1343 * d4321 - d1321 * d4343;
double mua = numer / denom;
double mub = (d1343 + d4321 * (mua)) / d4343;
resultSegmentPoint1.x = (float) (p1.x + mua * p21.x);
resultSegmentPoint1.y = (float) (p1.y + mua * p21.y);
resultSegmentPoint1.z = (float) (p1.z + mua * p21.z);
resultSegmentPoint2.x = (float) (p3.x + mub * p43.x);
resultSegmentPoint2.y = (float) (p3.y + mub * p43.y);
resultSegmentPoint2.z = (float) (p3.z + mub * p43.z);
//check if result 1 is in the segment section.
float startToPoint = vars.vect3.set(resultSegmentPoint1).subtractLocal(segStart).lengthSquared();
float endToPoint = vars.vect3.set(resultSegmentPoint1).subtractLocal(segEnd).lengthSquared();
float segLength = vars.vect3.set(segEnd).subtractLocal(segStart).lengthSquared();
if (startToPoint > segLength || endToPoint > segLength) {
vars.release();
return -1;
}
if (camera != null) {
//camera is not null let's convert the points in screen space
camera.getScreenCoordinates(resultSegmentPoint1, resultSegmentPoint1);
camera.getScreenCoordinates(resultSegmentPoint2, resultSegmentPoint2);
}
float length = resultSegmentPoint1.subtractLocal(resultSegmentPoint2).length();
vars.release();
return length;
}
}

@ -34,6 +34,7 @@ package com.jme3.math;
import com.jme3.export.*; import com.jme3.export.*;
import com.jme3.util.BufferUtils; import com.jme3.util.BufferUtils;
import com.jme3.util.TempVars; import com.jme3.util.TempVars;
import java.io.IOException; import java.io.IOException;
import java.nio.FloatBuffer; import java.nio.FloatBuffer;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -1023,96 +1024,95 @@ public final class Matrix4f implements Savable, Cloneable, java.io.Serializable
store = new Matrix4f(); store = new Matrix4f();
} }
float temp00, temp01, temp02, temp03; TempVars v = TempVars.get();
float temp10, temp11, temp12, temp13; float[] m = v.matrixWrite;
float temp20, temp21, temp22, temp23;
float temp30, temp31, temp32, temp33;
temp00 = m00 * in2.m00 m[0] = m00 * in2.m00
+ m01 * in2.m10 + m01 * in2.m10
+ m02 * in2.m20 + m02 * in2.m20
+ m03 * in2.m30; + m03 * in2.m30;
temp01 = m00 * in2.m01 m[1] = m00 * in2.m01
+ m01 * in2.m11 + m01 * in2.m11
+ m02 * in2.m21 + m02 * in2.m21
+ m03 * in2.m31; + m03 * in2.m31;
temp02 = m00 * in2.m02 m[2] = m00 * in2.m02
+ m01 * in2.m12 + m01 * in2.m12
+ m02 * in2.m22 + m02 * in2.m22
+ m03 * in2.m32; + m03 * in2.m32;
temp03 = m00 * in2.m03 m[3] = m00 * in2.m03
+ m01 * in2.m13 + m01 * in2.m13
+ m02 * in2.m23 + m02 * in2.m23
+ m03 * in2.m33; + m03 * in2.m33;
temp10 = m10 * in2.m00 m[4] = m10 * in2.m00
+ m11 * in2.m10 + m11 * in2.m10
+ m12 * in2.m20 + m12 * in2.m20
+ m13 * in2.m30; + m13 * in2.m30;
temp11 = m10 * in2.m01 m[5] = m10 * in2.m01
+ m11 * in2.m11 + m11 * in2.m11
+ m12 * in2.m21 + m12 * in2.m21
+ m13 * in2.m31; + m13 * in2.m31;
temp12 = m10 * in2.m02 m[6] = m10 * in2.m02
+ m11 * in2.m12 + m11 * in2.m12
+ m12 * in2.m22 + m12 * in2.m22
+ m13 * in2.m32; + m13 * in2.m32;
temp13 = m10 * in2.m03 m[7] = m10 * in2.m03
+ m11 * in2.m13 + m11 * in2.m13
+ m12 * in2.m23 + m12 * in2.m23
+ m13 * in2.m33; + m13 * in2.m33;
temp20 = m20 * in2.m00 m[8] = m20 * in2.m00
+ m21 * in2.m10 + m21 * in2.m10
+ m22 * in2.m20 + m22 * in2.m20
+ m23 * in2.m30; + m23 * in2.m30;
temp21 = m20 * in2.m01 m[9] = m20 * in2.m01
+ m21 * in2.m11 + m21 * in2.m11
+ m22 * in2.m21 + m22 * in2.m21
+ m23 * in2.m31; + m23 * in2.m31;
temp22 = m20 * in2.m02 m[10] = m20 * in2.m02
+ m21 * in2.m12 + m21 * in2.m12
+ m22 * in2.m22 + m22 * in2.m22
+ m23 * in2.m32; + m23 * in2.m32;
temp23 = m20 * in2.m03 m[11] = m20 * in2.m03
+ m21 * in2.m13 + m21 * in2.m13
+ m22 * in2.m23 + m22 * in2.m23
+ m23 * in2.m33; + m23 * in2.m33;
temp30 = m30 * in2.m00 m[12] = m30 * in2.m00
+ m31 * in2.m10 + m31 * in2.m10
+ m32 * in2.m20 + m32 * in2.m20
+ m33 * in2.m30; + m33 * in2.m30;
temp31 = m30 * in2.m01 m[13] = m30 * in2.m01
+ m31 * in2.m11 + m31 * in2.m11
+ m32 * in2.m21 + m32 * in2.m21
+ m33 * in2.m31; + m33 * in2.m31;
temp32 = m30 * in2.m02 m[14] = m30 * in2.m02
+ m31 * in2.m12 + m31 * in2.m12
+ m32 * in2.m22 + m32 * in2.m22
+ m33 * in2.m32; + m33 * in2.m32;
temp33 = m30 * in2.m03 m[15] = m30 * in2.m03
+ m31 * in2.m13 + m31 * in2.m13
+ m32 * in2.m23 + m32 * in2.m23
+ m33 * in2.m33; + m33 * in2.m33;
store.m00 = temp00;
store.m01 = temp01;
store.m02 = temp02;
store.m03 = temp03;
store.m10 = temp10;
store.m11 = temp11;
store.m12 = temp12;
store.m13 = temp13;
store.m20 = temp20;
store.m21 = temp21;
store.m22 = temp22;
store.m23 = temp23;
store.m30 = temp30;
store.m31 = temp31;
store.m32 = temp32;
store.m33 = temp33;
store.m00 = m[0];
store.m01 = m[1];
store.m02 = m[2];
store.m03 = m[3];
store.m10 = m[4];
store.m11 = m[5];
store.m12 = m[6];
store.m13 = m[7];
store.m20 = m[8];
store.m21 = m[9];
store.m22 = m[10];
store.m23 = m[11];
store.m30 = m[12];
store.m31 = m[13];
store.m32 = m[14];
store.m33 = m[15];
v.release();
return store; return store;
} }
@ -1709,8 +1709,8 @@ public final class Matrix4f implements Savable, Cloneable, java.io.Serializable
return new Vector3f(m03, m13, m23); return new Vector3f(m03, m13, m23);
} }
public void toTranslationVector(Vector3f vector) { public Vector3f toTranslationVector(Vector3f vector) {
vector.set(m03, m13, m23); return vector.set(m03, m13, m23);
} }
public Quaternion toRotationQuat() { public Quaternion toRotationQuat() {
@ -1719,8 +1719,9 @@ public final class Matrix4f implements Savable, Cloneable, java.io.Serializable
return quat; return quat;
} }
public void toRotationQuat(Quaternion q) { public Quaternion toRotationQuat(Quaternion q) {
q.fromRotationMatrix(toRotationMatrix()); return q.fromRotationMatrix(m00, m01, m02, m10,
m11, m12, m20, m21, m22);
} }
public Matrix3f toRotationMatrix() { public Matrix3f toRotationMatrix() {
@ -1753,15 +1754,16 @@ public final class Matrix4f implements Savable, Cloneable, java.io.Serializable
/** /**
* Retreives the scale vector from the matrix and stores it into a given * Retreives the scale vector from the matrix and stores it into a given
* vector. * vector.
* *
* @param the * @param store the vector where the scale will be stored
* vector where the scale will be stored * @return the store vector
*/ */
public void toScaleVector(Vector3f vector) { public Vector3f toScaleVector(Vector3f store) {
float scaleX = (float) Math.sqrt(m00 * m00 + m10 * m10 + m20 * m20); float scaleX = (float) Math.sqrt(m00 * m00 + m10 * m10 + m20 * m20);
float scaleY = (float) Math.sqrt(m01 * m01 + m11 * m11 + m21 * m21); float scaleY = (float) Math.sqrt(m01 * m01 + m11 * m11 + m21 * m21);
float scaleZ = (float) Math.sqrt(m02 * m02 + m12 * m12 + m22 * m22); float scaleZ = (float) Math.sqrt(m02 * m02 + m12 * m12 + m22 * m22);
vector.set(scaleX, scaleY, scaleZ); store.set(scaleX, scaleY, scaleZ);
return store;
} }
/** /**
@ -1775,25 +1777,30 @@ public final class Matrix4f implements Savable, Cloneable, java.io.Serializable
* the Z scale * the Z scale
*/ */
public void setScale(float x, float y, float z) { public void setScale(float x, float y, float z) {
TempVars vars = TempVars.get();
vars.vect1.set(m00, m10, m20); float length = m00 * m00 + m10 * m10 + m20 * m20;
vars.vect1.normalizeLocal().multLocal(x); if (length != 0f) {
m00 = vars.vect1.x; length = length == 1 ? x : (x / FastMath.sqrt(length));
m10 = vars.vect1.y; m00 *= length;
m20 = vars.vect1.z; m10 *= length;
m20 *= length;
vars.vect1.set(m01, m11, m21); }
vars.vect1.normalizeLocal().multLocal(y);
m01 = vars.vect1.x; length = m01 * m01 + m11 * m11 + m21 * m21;
m11 = vars.vect1.y; if (length != 0f) {
m21 = vars.vect1.z; length = length == 1 ? y : (y / FastMath.sqrt(length));
m01 *= length;
vars.vect1.set(m02, m12, m22); m11 *= length;
vars.vect1.normalizeLocal().multLocal(z); m21 *= length;
m02 = vars.vect1.x; }
m12 = vars.vect1.y;
m22 = vars.vect1.z; length = m02 * m02 + m12 * m12 + m22 * m22;
vars.release(); if (length != 0f) {
length = length == 1 ? z : (z / FastMath.sqrt(length));
m02 *= length;
m12 *= length;
m22 *= length;
}
} }
/** /**

@ -33,10 +33,8 @@ package com.jme3.math;
import com.jme3.export.*; import com.jme3.export.*;
import com.jme3.util.TempVars; import com.jme3.util.TempVars;
import java.io.Externalizable;
import java.io.IOException; import java.io.*;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.logging.Logger; import java.util.logging.Logger;
/** /**
@ -452,10 +450,55 @@ public final class Quaternion implements Savable, Cloneable, java.io.Serializabl
return result; return result;
} }
/**
* <code>toTransformMatrix</code> converts this quaternion to a transform
* matrix. The result is stored in result.
* Note this method won't preserve the scale of the given matrix.
*
* @param store The Matrix3f to store the result in.
* @return the transform matrix with the rotation representation of this quaternion.
*/
public Matrix4f toTransformMatrix(Matrix4f store) {
float norm = norm();
// we explicitly test norm against one here, saving a division
// at the cost of a test and branch. Is it worth it?
float s = (norm == 1f) ? 2f : (norm > 0f) ? 2f / norm : 0;
// compute xs/ys/zs first to save 6 multiplications, since xs/ys/zs
// will be used 2-4 times each.
float xs = x * s;
float ys = y * s;
float zs = z * s;
float xx = x * xs;
float xy = x * ys;
float xz = x * zs;
float xw = w * xs;
float yy = y * ys;
float yz = y * zs;
float yw = w * ys;
float zz = z * zs;
float zw = w * zs;
// using s=2/norm (instead of 1/norm) saves 9 multiplications by 2 here
store.m00 = 1 - (yy + zz);
store.m01 = (xy - zw);
store.m02 = (xz + yw);
store.m10 = (xy + zw);
store.m11 = 1 - (xx + zz);
store.m12 = (yz - xw);
store.m20 = (xz - yw);
store.m21 = (yz + xw);
store.m22 = 1 - (xx + yy);
return store;
}
/** /**
* <code>toRotationMatrix</code> converts this quaternion to a rotational * <code>toRotationMatrix</code> converts this quaternion to a rotational
* matrix. The result is stored in result. 4th row and 4th column values are * matrix. The result is stored in result. 4th row and 4th column values are
* untouched. Note: the result is created from a normalized version of this quat. * untouched. Note: the result is created from a normalized version of this quat.
* Note that this method will preserve the scale of the given matrix
* *
* @param result * @param result
* The Matrix4f to store the result in. * The Matrix4f to store the result in.

@ -32,6 +32,8 @@
package com.jme3.math; package com.jme3.math;
import com.jme3.export.*; import com.jme3.export.*;
import com.jme3.util.TempVars;
import java.io.IOException; import java.io.IOException;
/** /**
@ -174,13 +176,14 @@ public final class Transform implements Savable, Cloneable, java.io.Serializable
} }
/** /**
* Sets this matrix to the interpolation between the first matrix and the second by delta amount. * Sets this transform to the interpolation between the first transform and the second by delta amount.
* @param t1 The beginning transform. * @param t1 The beginning transform.
* @param t2 The ending transform. * @param t2 The ending transform.
* @param delta An amount between 0 and 1 representing how far to interpolate from t1 to t2. * @param delta An amount between 0 and 1 representing how far to interpolate from t1 to t2.
*/ */
public void interpolateTransforms(Transform t1, Transform t2, float delta) { public void interpolateTransforms(Transform t1, Transform t2, float delta) {
this.rot.slerp(t1.rot,t2.rot,delta); t1.rot.nlerp(t2.rot, delta);
this.rot.set(t1.rot);
this.translation.interpolateLocal(t1.translation,t2.translation,delta); this.translation.interpolateLocal(t1.translation,t2.translation,delta);
this.scale.interpolateLocal(t1.scale,t2.scale,delta); this.scale.interpolateLocal(t1.scale,t2.scale,delta);
} }
@ -257,17 +260,25 @@ public final class Transform implements Savable, Cloneable, java.io.Serializable
} }
public Matrix4f toTransformMatrix() { public Matrix4f toTransformMatrix() {
Matrix4f trans = new Matrix4f(); return toTransformMatrix(null);
trans.setTranslation(translation); }
trans.setRotationQuaternion(rot);
trans.setScale(scale); public Matrix4f toTransformMatrix(Matrix4f store) {
return trans; if (store == null) {
store = new Matrix4f();
}
store.setTranslation(translation);
rot.toTransformMatrix(store);
store.setScale(scale);
return store;
} }
public void fromTransformMatrix(Matrix4f mat) { public void fromTransformMatrix(Matrix4f mat) {
translation.set(mat.toTranslationVector()); TempVars vars = TempVars.get();
rot.set(mat.toRotationQuat()); translation.set(mat.toTranslationVector(vars.vect1));
scale.set(mat.toScaleVector()); rot.set(mat.toRotationQuat(vars.quat1));
scale.set(mat.toScaleVector(vars.vect2));
vars.release();
} }
public Transform invert() { public Transform invert() {

@ -660,7 +660,7 @@ public class RenderManager {
throw new IllegalStateException("No material is set for Geometry: " + gm.getName()); throw new IllegalStateException("No material is set for Geometry: " + gm.getName());
} }
gm.getMaterial().preload(this); gm.getMaterial().preload(this, gm);
Mesh mesh = gm.getMesh(); Mesh mesh = gm.getMesh();
if (mesh != null if (mesh != null
&& mesh.getVertexCount() != 0 && mesh.getVertexCount() != 0

@ -43,6 +43,7 @@ import com.jme3.material.Material;
import com.jme3.math.Matrix4f; import com.jme3.math.Matrix4f;
import com.jme3.renderer.Camera; import com.jme3.renderer.Camera;
import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.mesh.MorphTarget;
import com.jme3.util.TempVars; import com.jme3.util.TempVars;
import com.jme3.util.clone.Cloner; import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.IdentityCloneFunction; import com.jme3.util.clone.IdentityCloneFunction;
@ -86,6 +87,16 @@ public class Geometry extends Spatial {
*/ */
protected int startIndex = -1; protected int startIndex = -1;
/**
* Morph state variable for morph animation
*/
private float[] morphState;
private boolean dirtyMorph = true;
// a Morph target that will be used to merge all targets that
// can't be handled on the cpu on each frame.
private MorphTarget fallbackMorphTarget;
private int nbSimultaneousGPUMorph = -1;
/** /**
* Serialization only. Do not use. * Serialization only. Do not use.
*/ */
@ -248,7 +259,7 @@ public class Geometry extends Spatial {
@Override @Override
public void setMaterial(Material material) { public void setMaterial(Material material) {
this.material = material; this.material = material;
nbSimultaneousGPUMorph = -1;
if (isGrouped()) { if (isGrouped()) {
groupNode.onMaterialChange(this); groupNode.onMaterialChange(this);
} }
@ -576,6 +587,80 @@ public class Geometry extends Spatial {
this.material = cloner.clone(material); this.material = cloner.clone(material);
} }
public void setMorphState(float[] state) {
if (mesh == null || mesh.getMorphTargets().length == 0){
return;
}
int nbMorphTargets = mesh.getMorphTargets().length;
if (morphState == null) {
morphState = new float[nbMorphTargets];
}
System.arraycopy(state, 0, morphState, 0, morphState.length);
this.dirtyMorph = true;
}
/**
* returns true if the morph state has changed on the last frame.
* @return
*/
public boolean isDirtyMorph() {
return dirtyMorph;
}
/**
* Seting this to true will stop this geometry morph buffer to be updated,
* unless the morph state changes
* @param dirtyMorph
*/
public void setDirtyMorph(boolean dirtyMorph) {
this.dirtyMorph = dirtyMorph;
}
/**
* returns the morph state of this Geometry.
* Used internally by the MorphControl.
* @return
*/
public float[] getMorphState() {
if (morphState == null) {
morphState = new float[mesh.getMorphTargets().length];
}
return morphState;
}
/**
* Return the number of morph targets that can be handled on the GPU simultaneously for this geometry.
* Note that it depends on the material set on this geometry.
* This number is computed and set by the MorphControl, so it might be available only after the first frame.
* Else it's set to -1.
* @return the number of simultaneous morph targets handled on the GPU
*/
public int getNbSimultaneousGPUMorph() {
return nbSimultaneousGPUMorph;
}
/**
* Sets the number of morph targets that can be handled on the GPU simultaneously for this geometry.
* Note that it depends on the material set on this geometry.
* This number is computed and set by the MorphControl, so it might be available only after the first frame.
* Else it's set to -1.
* WARNING: setting this manually might crash the shader compilation if set too high. Do it at your own risk.
* @param nbSimultaneousGPUMorph the number of simultaneous morph targets to be handled on the GPU.
*/
public void setNbSimultaneousGPUMorph(int nbSimultaneousGPUMorph) {
this.nbSimultaneousGPUMorph = nbSimultaneousGPUMorph;
}
public MorphTarget getFallbackMorphTarget() {
return fallbackMorphTarget;
}
public void setFallbackMorphTarget(MorphTarget fallbackMorphTarget) {
this.fallbackMorphTarget = fallbackMorphTarget;
}
@Override @Override
public void write(JmeExporter ex) throws IOException { public void write(JmeExporter ex) throws IOException {
super.write(ex); super.write(ex);

@ -39,24 +39,18 @@ import com.jme3.collision.bih.BIHTree;
import com.jme3.export.*; import com.jme3.export.*;
import com.jme3.material.Material; import com.jme3.material.Material;
import com.jme3.material.RenderState; import com.jme3.material.RenderState;
import com.jme3.math.Matrix4f; import com.jme3.math.*;
import com.jme3.math.Triangle; import com.jme3.scene.VertexBuffer.*;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.VertexBuffer.Format;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.VertexBuffer.Usage;
import com.jme3.scene.mesh.*; import com.jme3.scene.mesh.*;
import com.jme3.util.BufferUtils; import com.jme3.util.*;
import com.jme3.util.IntMap;
import com.jme3.util.IntMap.Entry; import com.jme3.util.IntMap.Entry;
import com.jme3.util.SafeArrayList;
import com.jme3.util.clone.Cloner; import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable; import com.jme3.util.clone.JmeCloneable;
import java.io.IOException; import java.io.IOException;
import java.nio.*; import java.nio.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
/** /**
* <code>Mesh</code> is used to store rendering data. * <code>Mesh</code> is used to store rendering data.
@ -171,8 +165,8 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
private CollisionData collisionTree = null; private CollisionData collisionTree = null;
private SafeArrayList<VertexBuffer> buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class); private SafeArrayList<VertexBuffer> buffersList = new SafeArrayList<>(VertexBuffer.class);
private IntMap<VertexBuffer> buffers = new IntMap<VertexBuffer>(); private IntMap<VertexBuffer> buffers = new IntMap<>();
private VertexBuffer[] lodLevels; private VertexBuffer[] lodLevels;
private float pointSize = 1; private float pointSize = 1;
private float lineWidth = 1; private float lineWidth = 1;
@ -190,6 +184,8 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
private Mode mode = Mode.Triangles; private Mode mode = Mode.Triangles;
private SafeArrayList<MorphTarget> morphTargets;
/** /**
* Creates a new mesh with no {@link VertexBuffer vertex buffers}. * Creates a new mesh with no {@link VertexBuffer vertex buffers}.
*/ */
@ -210,7 +206,7 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
clone.meshBound = meshBound.clone(); clone.meshBound = meshBound.clone();
clone.collisionTree = collisionTree != null ? collisionTree : null; clone.collisionTree = collisionTree != null ? collisionTree : null;
clone.buffers = buffers.clone(); clone.buffers = buffers.clone();
clone.buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class,buffersList); clone.buffersList = new SafeArrayList<>(VertexBuffer.class, buffersList);
clone.vertexArrayID = -1; clone.vertexArrayID = -1;
if (elementLengths != null) { if (elementLengths != null) {
clone.elementLengths = elementLengths.clone(); clone.elementLengths = elementLengths.clone();
@ -240,8 +236,8 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
//clone.collisionTree = collisionTree != null ? collisionTree : null; //clone.collisionTree = collisionTree != null ? collisionTree : null;
clone.collisionTree = null; // it will get re-generated in any case clone.collisionTree = null; // it will get re-generated in any case
clone.buffers = new IntMap<VertexBuffer>(); clone.buffers = new IntMap<>();
clone.buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class); clone.buffersList = new SafeArrayList<>(VertexBuffer.class);
for (VertexBuffer vb : buffersList.getArray()){ for (VertexBuffer vb : buffersList.getArray()){
VertexBuffer bufClone = vb.clone(); VertexBuffer bufClone = vb.clone();
clone.buffers.put(vb.getBufferType().ordinal(), bufClone); clone.buffers.put(vb.getBufferType().ordinal(), bufClone);
@ -704,7 +700,7 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
*/ */
@Deprecated @Deprecated
public void setInterleaved(){ public void setInterleaved(){
ArrayList<VertexBuffer> vbs = new ArrayList<VertexBuffer>(); ArrayList<VertexBuffer> vbs = new ArrayList<>();
vbs.addAll(buffersList); vbs.addAll(buffersList);
// ArrayList<VertexBuffer> vbs = new ArrayList<VertexBuffer>(buffers.values()); // ArrayList<VertexBuffer> vbs = new ArrayList<VertexBuffer>(buffers.values());
@ -827,8 +823,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
* {@link #setInterleaved() interleaved} format. * {@link #setInterleaved() interleaved} format.
*/ */
public void updateCounts(){ public void updateCounts(){
if (getBuffer(Type.InterleavedData) != null) if (getBuffer(Type.InterleavedData) != null) {
throw new IllegalStateException("Should update counts before interleave"); throw new IllegalStateException("Should update counts before interleave");
}
VertexBuffer pb = getBuffer(Type.Position); VertexBuffer pb = getBuffer(Type.Position);
VertexBuffer ib = getBuffer(Type.Index); VertexBuffer ib = getBuffer(Type.Index);
@ -851,11 +848,13 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
*/ */
public int getTriangleCount(int lod){ public int getTriangleCount(int lod){
if (lodLevels != null){ if (lodLevels != null){
if (lod < 0) if (lod < 0) {
throw new IllegalArgumentException("LOD level cannot be < 0"); throw new IllegalArgumentException("LOD level cannot be < 0");
}
if (lod >= lodLevels.length) if (lod >= lodLevels.length) {
throw new IllegalArgumentException("LOD level "+lod+" does not exist!"); throw new IllegalArgumentException("LOD level " + lod + " does not exist!");
}
return computeNumElements(lodLevels[lod].getData().limit()); return computeNumElements(lodLevels[lod].getData().limit());
}else if (lod == 0){ }else if (lod == 0){
@ -975,8 +974,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
* Sets the mesh's VAO ID. Internal use only. * Sets the mesh's VAO ID. Internal use only.
*/ */
public void setId(int id){ public void setId(int id){
if (vertexArrayID != -1) if (vertexArrayID != -1) {
throw new IllegalStateException("ID has already been set."); throw new IllegalStateException("ID has already been set.");
}
vertexArrayID = id; vertexArrayID = id;
} }
@ -1044,8 +1044,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
* @throws IllegalArgumentException If the buffer type is already set * @throws IllegalArgumentException If the buffer type is already set
*/ */
public void setBuffer(VertexBuffer vb){ public void setBuffer(VertexBuffer vb){
if (buffers.containsKey(vb.getBufferType().ordinal())) if (buffers.containsKey(vb.getBufferType().ordinal())) {
throw new IllegalArgumentException("Buffer type already set: "+vb.getBufferType()); throw new IllegalArgumentException("Buffer type already set: " + vb.getBufferType());
}
buffers.put(vb.getBufferType().ordinal(), vb); buffers.put(vb.getBufferType().ordinal(), vb);
buffersList.add(vb); buffersList.add(vb);
@ -1158,8 +1159,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
*/ */
public FloatBuffer getFloatBuffer(Type type) { public FloatBuffer getFloatBuffer(Type type) {
VertexBuffer vb = getBuffer(type); VertexBuffer vb = getBuffer(type);
if (vb == null) if (vb == null) {
return null; return null;
}
return (FloatBuffer) vb.getData(); return (FloatBuffer) vb.getData();
} }
@ -1173,8 +1175,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
*/ */
public ShortBuffer getShortBuffer(Type type) { public ShortBuffer getShortBuffer(Type type) {
VertexBuffer vb = getBuffer(type); VertexBuffer vb = getBuffer(type);
if (vb == null) if (vb == null) {
return null; return null;
}
return (ShortBuffer) vb.getData(); return (ShortBuffer) vb.getData();
} }
@ -1186,8 +1189,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
* @return A virtual or wrapped index buffer to read the data as a list * @return A virtual or wrapped index buffer to read the data as a list
*/ */
public IndexBuffer getIndicesAsList(){ public IndexBuffer getIndicesAsList(){
if (mode == Mode.Hybrid) if (mode == Mode.Hybrid) {
throw new UnsupportedOperationException("Hybrid mode not supported"); throw new UnsupportedOperationException("Hybrid mode not supported");
}
IndexBuffer ib = getIndexBuffer(); IndexBuffer ib = getIndexBuffer();
if (ib != null){ if (ib != null){
@ -1216,8 +1220,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
*/ */
public IndexBuffer getIndexBuffer() { public IndexBuffer getIndexBuffer() {
VertexBuffer vb = getBuffer(Type.Index); VertexBuffer vb = getBuffer(Type.Index);
if (vb == null) if (vb == null) {
return null; return null;
}
return IndexBuffer.wrapIndexBuffer(vb.getData()); return IndexBuffer.wrapIndexBuffer(vb.getData());
} }
@ -1240,8 +1245,8 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
IndexBuffer indexBuf = getIndexBuffer(); IndexBuffer indexBuf = getIndexBuffer();
int numIndices = indexBuf.size(); int numIndices = indexBuf.size();
IntMap<Integer> oldIndicesToNewIndices = new IntMap<Integer>(numIndices); IntMap<Integer> oldIndicesToNewIndices = new IntMap<>(numIndices);
ArrayList<Integer> newIndicesToOldIndices = new ArrayList<Integer>(); ArrayList<Integer> newIndicesToOldIndices = new ArrayList<>();
int newIndex = 0; int newIndex = 0;
for (int i = 0; i < numIndices; i++) { for (int i = 0; i < numIndices; i++) {
@ -1352,14 +1357,17 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
*/ */
public void scaleTextureCoordinates(Vector2f scaleFactor){ public void scaleTextureCoordinates(Vector2f scaleFactor){
VertexBuffer tc = getBuffer(Type.TexCoord); VertexBuffer tc = getBuffer(Type.TexCoord);
if (tc == null) if (tc == null) {
throw new IllegalStateException("The mesh has no texture coordinates"); throw new IllegalStateException("The mesh has no texture coordinates");
}
if (tc.getFormat() != VertexBuffer.Format.Float) if (tc.getFormat() != VertexBuffer.Format.Float) {
throw new UnsupportedOperationException("Only float texture coord format is supported"); throw new UnsupportedOperationException("Only float texture coord format is supported");
}
if (tc.getNumComponents() != 2) if (tc.getNumComponents() != 2) {
throw new UnsupportedOperationException("Only 2D texture coords are supported"); throw new UnsupportedOperationException("Only 2D texture coords are supported");
}
FloatBuffer fb = (FloatBuffer) tc.getData(); FloatBuffer fb = (FloatBuffer) tc.getData();
fb.clear(); fb.clear();
@ -1446,13 +1454,23 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
getBuffer(Type.HWBoneIndex) != null; getBuffer(Type.HWBoneIndex) != null;
} }
/**
* @deprecated use isAnimatedByJoint
* @param boneIndex
* @return
*/
@Deprecated
public boolean isAnimatedByBone(int boneIndex) {
return isAnimatedByJoint(boneIndex);
}
/** /**
* Test whether the specified bone animates this mesh. * Test whether the specified bone animates this mesh.
* *
* @param boneIndex the bone's index in its skeleton * @param jointIndex the bone's index in its skeleton
* @return true if the specified bone animates this mesh, otherwise false * @return true if the specified bone animates this mesh, otherwise false
*/ */
public boolean isAnimatedByBone(int boneIndex) { public boolean isAnimatedByJoint(int jointIndex) {
VertexBuffer biBuf = getBuffer(VertexBuffer.Type.BoneIndex); VertexBuffer biBuf = getBuffer(VertexBuffer.Type.BoneIndex);
VertexBuffer wBuf = getBuffer(VertexBuffer.Type.BoneWeight); VertexBuffer wBuf = getBuffer(VertexBuffer.Type.BoneWeight);
if (biBuf == null || wBuf == null) { if (biBuf == null || wBuf == null) {
@ -1472,7 +1490,7 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
/* /*
* Test each vertex to determine whether the bone affects it. * Test each vertex to determine whether the bone affects it.
*/ */
int biByte = boneIndex; int biByte = jointIndex;
for (int vIndex = 0; vIndex < numVertices; vIndex++) { for (int vIndex = 0; vIndex < numVertices; vIndex++) {
for (int wIndex = 0; wIndex < 4; wIndex++) { for (int wIndex = 0; wIndex < 4; wIndex++) {
int bIndex = boneIndexBuffer.get(); int bIndex = boneIndexBuffer.get();
@ -1501,16 +1519,26 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
return patchVertexCount; return patchVertexCount;
} }
public void addMorphTarget(MorphTarget target) {
if (morphTargets == null) {
morphTargets = new SafeArrayList<>(MorphTarget.class);
}
morphTargets.add(target);
}
public MorphTarget[] getMorphTargets() {
return morphTargets.getArray();
}
public boolean hasMorphTargets() {
return morphTargets != null && !morphTargets.isEmpty();
}
@Override
public void write(JmeExporter ex) throws IOException { public void write(JmeExporter ex) throws IOException {
OutputCapsule out = ex.getCapsule(this); OutputCapsule out = ex.getCapsule(this);
// HashMap<String, VertexBuffer> map = new HashMap<String, VertexBuffer>();
// for (Entry<VertexBuffer> buf : buffers){
// if (buf.getValue() != null)
// map.put(buf.getKey()+"a", buf.getValue());
// }
// out.writeStringSavableMap(map, "buffers", null);
out.write(meshBound, "modelBound", null); out.write(meshBound, "modelBound", null);
out.write(vertCount, "vertCount", -1); out.write(vertCount, "vertCount", -1);
out.write(elementCount, "elementCount", -1); out.write(elementCount, "elementCount", -1);
@ -1545,8 +1573,12 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
} }
out.write(lodLevels, "lodLevels", null); out.write(lodLevels, "lodLevels", null);
if (morphTargets != null) {
out.writeSavableArrayList(new ArrayList(morphTargets), "morphTargets", null);
}
} }
@Override
public void read(JmeImporter im) throws IOException { public void read(JmeImporter im) throws IOException {
InputCapsule in = im.getCapsule(this); InputCapsule in = im.getCapsule(this);
meshBound = (BoundingVolume) in.readSavable("modelBound", null); meshBound = (BoundingVolume) in.readSavable("modelBound", null);
@ -1583,6 +1615,11 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
lodLevels = new VertexBuffer[lodLevelsSavable.length]; lodLevels = new VertexBuffer[lodLevelsSavable.length];
System.arraycopy( lodLevelsSavable, 0, lodLevels, 0, lodLevels.length); System.arraycopy( lodLevelsSavable, 0, lodLevels, 0, lodLevels.length);
} }
ArrayList<Savable> l = in.readSavableArrayList("morphTargets", null);
if (l != null) {
morphTargets = new SafeArrayList(MorphTarget.class, l);
}
} }
} }

@ -31,6 +31,7 @@
*/ */
package com.jme3.scene; package com.jme3.scene;
import com.jme3.anim.util.HasLocalTransform;
import com.jme3.asset.AssetKey; import com.jme3.asset.AssetKey;
import com.jme3.asset.CloneableSmartAsset; import com.jme3.asset.CloneableSmartAsset;
import com.jme3.bounding.BoundingVolume; import com.jme3.bounding.BoundingVolume;
@ -67,7 +68,7 @@ import java.util.logging.Logger;
* @author Joshua Slack * @author Joshua Slack
* @version $Revision: 4075 $, $Data$ * @version $Revision: 4075 $, $Data$
*/ */
public abstract class Spatial implements Savable, Cloneable, Collidable, CloneableSmartAsset, JmeCloneable { public abstract class Spatial implements Savable, Cloneable, Collidable, CloneableSmartAsset, JmeCloneable, HasLocalTransform {
private static final Logger logger = Logger.getLogger(Spatial.class.getName()); private static final Logger logger = Logger.getLogger(Spatial.class.getName());

@ -212,7 +212,42 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
* Format should be {@link Format#Float} and number of components * Format should be {@link Format#Float} and number of components
* should be 16. * should be 16.
*/ */
InstanceData InstanceData,
/**
* Morph animations targets.
* Supports up tp 14 morph target buffers at the same time
* Limited due to the limited number of attributes you can bind to a vertex shader usually 16
* <p>
* MorphTarget buffers are either POSITION, NORMAL or TANGENT buffers.
* So we can support up to
* 14 simultaneous POSITION targets
* 7 simultaneous POSITION and NORMAL targets
* 4 simultaneous POSTION, NORMAL and TANGENT targets.
* <p>
* Note that the MorphControl will find how many buffers can be supported for each mesh/material combination.
* Note that all buffers have 3 components (Vector3f) even the Tangent buffer that
* does not contain the w (handedness) component that will not be interpolated for morph animation.
* <p>
* Note that those buffers contain the difference between the base buffer (POSITION, NORMAL or TANGENT) and the target value
* So that you can interpolate with a MADD operation in the vertex shader
* position = weight * diffPosition + basePosition;
*/
MorphTarget0,
MorphTarget1,
MorphTarget2,
MorphTarget3,
MorphTarget4,
MorphTarget5,
MorphTarget6,
MorphTarget7,
MorphTarget8,
MorphTarget9,
MorphTarget10,
MorphTarget11,
MorphTarget12,
MorphTarget13,
} }
/** /**
@ -241,7 +276,7 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
* Mesh data is <em>not</em> sent to GPU at all. It is only * Mesh data is <em>not</em> sent to GPU at all. It is only
* used by the CPU. * used by the CPU.
*/ */
CpuOnly; CpuOnly
} }
/** /**
@ -610,8 +645,9 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
return 0; return 0;
} }
int elements = data.limit() / components; int elements = data.limit() / components;
if (format == Format.Half) if (format == Format.Half) {
elements /= 2; elements /= 2;
}
return elements; return elements;
} }
@ -642,14 +678,17 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
* argument. * argument.
*/ */
public void setupData(Usage usage, int components, Format format, Buffer data){ public void setupData(Usage usage, int components, Format format, Buffer data){
if (id != -1) if (id != -1) {
throw new UnsupportedOperationException("Data has already been sent. Cannot setupData again."); throw new UnsupportedOperationException("Data has already been sent. Cannot setupData again.");
}
if (usage == null || format == null || data == null) if (usage == null || format == null || data == null) {
throw new IllegalArgumentException("None of the arguments can be null"); throw new IllegalArgumentException("None of the arguments can be null");
}
if (data.isReadOnly()) if (data.isReadOnly()) {
throw new IllegalArgumentException( "VertexBuffer data cannot be read-only." ); throw new IllegalArgumentException("VertexBuffer data cannot be read-only.");
}
if (bufType != Type.InstanceData) { if (bufType != Type.InstanceData) {
if (components < 1 || components > 4) { if (components < 1 || components > 4) {
@ -720,11 +759,13 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
* Converts single floating-point data to {@link Format#Half half} floating-point data. * Converts single floating-point data to {@link Format#Half half} floating-point data.
*/ */
public void convertToHalf(){ public void convertToHalf(){
if (id != -1) if (id != -1) {
throw new UnsupportedOperationException("Data has already been sent."); throw new UnsupportedOperationException("Data has already been sent.");
}
if (format != Format.Float) if (format != Format.Float) {
throw new IllegalStateException("Format must be float!"); throw new IllegalStateException("Format must be float!");
}
int numElements = data.limit() / components; int numElements = data.limit() / components;
format = Format.Half; format = Format.Half;
@ -913,8 +954,9 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
* match. * match.
*/ */
public void copyElements(int inIndex, VertexBuffer outVb, int outIndex, int len){ public void copyElements(int inIndex, VertexBuffer outVb, int outIndex, int len){
if (outVb.format != format || outVb.components != components) if (outVb.format != format || outVb.components != components) {
throw new IllegalArgumentException("Buffer format mismatch. Cannot copy"); throw new IllegalArgumentException("Buffer format mismatch. Cannot copy");
}
int inPos = inIndex * components; int inPos = inIndex * components;
int outPos = outIndex * components; int outPos = outIndex * components;
@ -981,8 +1023,9 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
* of elements with the given number of components in each element. * of elements with the given number of components in each element.
*/ */
public static Buffer createBuffer(Format format, int components, int numElements){ public static Buffer createBuffer(Format format, int components, int numElements){
if (components < 1 || components > 4) if (components < 1 || components > 4) {
throw new IllegalArgumentException("Num components must be between 1 and 4"); throw new IllegalArgumentException("Num components must be between 1 and 4");
}
int total = numElements * components; int total = numElements * components;

@ -0,0 +1,208 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.jme3.scene.debug.custom;
import com.jme3.anim.*;
import com.jme3.app.Application;
import com.jme3.app.state.BaseAppState;
import com.jme3.collision.CollisionResults;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.*;
import com.jme3.light.DirectionalLight;
import com.jme3.math.*;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.*;
import java.util.*;
/**
* @author Nehon
*/
public class ArmatureDebugAppState extends BaseAppState {
public static final float CLICK_MAX_DELAY = 0.2f;
private Node debugNode = new Node("debugNode");
private Map<Armature, ArmatureDebugger> armatures = new HashMap<>();
private Map<Armature, Joint> selectedBones = new HashMap<>();
private Application app;
private boolean displayAllJoints = false;
private float clickDelay = -1;
Vector3f tmp = new Vector3f();
Vector3f tmp2 = new Vector3f();
ViewPort vp;
@Override
protected void initialize(Application app) {
vp = app.getRenderManager().createMainView("debug", app.getCamera());
vp.attachScene(debugNode);
vp.setClearDepth(true);
this.app = app;
for (ArmatureDebugger armatureDebugger : armatures.values()) {
armatureDebugger.initialize(app.getAssetManager(), app.getCamera());
}
app.getInputManager().addListener(actionListener, "shoot", "toggleJoints");
app.getInputManager().addMapping("shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT), new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
app.getInputManager().addMapping("toggleJoints", new KeyTrigger(KeyInput.KEY_F10));
debugNode.addLight(new DirectionalLight(new Vector3f(-1f, -1f, -1f).normalizeLocal()));
debugNode.addLight(new DirectionalLight(new Vector3f(1f, 1f, 1f).normalizeLocal(), new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f)));
vp.setEnabled(false);
}
@Override
protected void cleanup(Application app) {
}
@Override
protected void onEnable() {
vp.setEnabled(true);
}
@Override
protected void onDisable() {
vp.setEnabled(false);
}
@Override
public void update(float tpf) {
if (clickDelay > -1) {
clickDelay += tpf;
}
debugNode.updateLogicalState(tpf);
debugNode.updateGeometricState();
}
public ArmatureDebugger addArmatureFrom(SkinningControl skinningControl) {
Armature armature = skinningControl.getArmature();
Spatial forSpatial = skinningControl.getSpatial();
return addArmatureFrom(armature, forSpatial);
}
public ArmatureDebugger addArmatureFrom(Armature armature, Spatial forSpatial) {
ArmatureDebugger ad = armatures.get(armature);
if(ad != null){
return ad;
}
JointInfoVisitor visitor = new JointInfoVisitor(armature);
forSpatial.depthFirstTraversal(visitor);
ad = new ArmatureDebugger(forSpatial.getName() + "_Armature", armature, visitor.deformingJoints);
ad.setLocalTransform(forSpatial.getWorldTransform());
if (forSpatial instanceof Node) {
List<Geometry> geoms = new ArrayList<>();
findGeoms((Node) forSpatial, geoms);
if (geoms.size() == 1) {
ad.setLocalTransform(geoms.get(0).getWorldTransform());
}
}
armatures.put(armature, ad);
debugNode.attachChild(ad);
if (isInitialized()) {
ad.initialize(app.getAssetManager(), app.getCamera());
}
return ad;
}
private void findGeoms(Node node, List<Geometry> geoms) {
for (Spatial spatial : node.getChildren()) {
if (spatial instanceof Geometry) {
geoms.add((Geometry) spatial);
} else if (spatial instanceof Node) {
findGeoms((Node) spatial, geoms);
}
}
}
private ActionListener actionListener = new ActionListener() {
public void onAction(String name, boolean isPressed, float tpf) {
if (name.equals("shoot") && isPressed) {
clickDelay = 0;
}
if (name.equals("shoot") && !isPressed && clickDelay < CLICK_MAX_DELAY) {
Vector2f click2d = app.getInputManager().getCursorPosition();
CollisionResults results = new CollisionResults();
Vector3f click3d = app.getCamera().getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 0f, tmp);
Vector3f dir = app.getCamera().getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 1f, tmp2).subtractLocal(click3d);
Ray ray = new Ray(click3d, dir);
debugNode.collideWith(ray, results);
if (results.size() == 0) {
for (ArmatureDebugger ad : armatures.values()) {
ad.select(null);
}
return;
}
// The closest result is the target that the player picked:
Geometry target = results.getClosestCollision().getGeometry();
for (ArmatureDebugger ad : armatures.values()) {
Joint selectedjoint = ad.select(target);
if (selectedjoint != null) {
selectedBones.put(ad.getArmature(), selectedjoint);
System.err.println("-----------------------");
System.err.println("Selected Joint : " + selectedjoint.getName() + " in armature " + ad.getName());
System.err.println("Root Bone : " + (selectedjoint.getParent() == null));
System.err.println("-----------------------");
System.err.println("Local translation: " + selectedjoint.getLocalTranslation());
System.err.println("Local rotation: " + selectedjoint.getLocalRotation());
System.err.println("Local scale: " + selectedjoint.getLocalScale());
System.err.println("---");
System.err.println("Model translation: " + selectedjoint.getModelTransform().getTranslation());
System.err.println("Model rotation: " + selectedjoint.getModelTransform().getRotation());
System.err.println("Model scale: " + selectedjoint.getModelTransform().getScale());
System.err.println("---");
System.err.println("Bind inverse Transform: ");
System.err.println(selectedjoint.getInverseModelBindMatrix());
return;
}
}
}
if (name.equals("toggleJoints") && isPressed) {
displayAllJoints = !displayAllJoints;
for (ArmatureDebugger ad : armatures.values()) {
ad.displayNonDeformingJoint(displayAllJoints);
}
}
}
};
// public Map<Skeleton, Bone> getSelectedBones() {
// return selectedBones;
// }
public Node getDebugNode() {
return debugNode;
}
public void setDebugNode(Node debugNode) {
this.debugNode = debugNode;
}
private class JointInfoVisitor extends SceneGraphVisitorAdapter {
List<Joint> deformingJoints = new ArrayList<>();
Armature armature;
public JointInfoVisitor(Armature armature) {
this.armature = armature;
}
@Override
public void visit(Geometry g) {
for (Joint joint : armature.getJointList()) {
if (g.getMesh().isAnimatedByJoint(armature.getJointIndex(joint))) {
deformingJoints.add(joint);
}
}
}
}
}

@ -0,0 +1,188 @@
package com.jme3.scene.debug.custom;
/*
* 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.
*/
import com.jme3.anim.Armature;
import com.jme3.anim.Joint;
import com.jme3.asset.AssetManager;
import com.jme3.collision.Collidable;
import com.jme3.collision.CollisionResults;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
import com.jme3.math.Vector2f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.texture.Texture;
import java.util.List;
/**
* The class that creates a mesh to display how bones behave. If it is supplied
* with the bones' lengths it will show exactly how the bones look like on the
* scene. If not then only connections between each bone heads will be shown.
*/
public class ArmatureDebugger extends Node {
/**
* The lines of the bones or the wires between their heads.
*/
private ArmatureNode armatureNode;
private Armature armature;
private Node joints;
private Node outlines;
private Node wires;
/**
* The dotted lines between a bone's tail and the had of its children. Not
* available if the length data was not provided.
*/
private ArmatureInterJointsWire interJointWires;
public ArmatureDebugger() {
}
/**
* Creates a debugger with no length data. The wires will be a connection
* between the bones' heads only. The points will show the bones' heads only
* and no dotted line of inter bones connection will be visible.
*
* @param name the name of the debugger's node
* @param armature the armature that will be shown
*/
public ArmatureDebugger(String name, Armature armature, List<Joint> deformingJoints) {
super(name);
this.armature = armature;
armature.update();
joints = new Node("joints");
outlines = new Node("outlines");
wires = new Node("bones");
this.attachChild(joints);
this.attachChild(outlines);
this.attachChild(wires);
Node ndJoints = new Node("non deforming Joints");
Node ndOutlines = new Node("non deforming Joints outlines");
Node ndWires = new Node("non deforming Joints wires");
joints.attachChild(ndJoints);
outlines.attachChild(ndOutlines);
wires.attachChild(ndWires);
Node outlineDashed = new Node("Outlines Dashed");
Node wiresDashed = new Node("Wires Dashed");
wiresDashed.attachChild(new Node("dashed non defrom"));
outlineDashed.attachChild(new Node("dashed non defrom"));
outlines.attachChild(outlineDashed);
wires.attachChild(wiresDashed);
armatureNode = new ArmatureNode(armature, joints, wires, outlines, deformingJoints);
this.attachChild(armatureNode);
displayNonDeformingJoint(false);
}
public void displayNonDeformingJoint(boolean display) {
joints.getChild(0).setCullHint(display ? CullHint.Dynamic : CullHint.Always);
outlines.getChild(0).setCullHint(display ? CullHint.Dynamic : CullHint.Always);
wires.getChild(0).setCullHint(display ? CullHint.Dynamic : CullHint.Always);
((Node) outlines.getChild(1)).getChild(0).setCullHint(display ? CullHint.Dynamic : CullHint.Always);
((Node) wires.getChild(1)).getChild(0).setCullHint(display ? CullHint.Dynamic : CullHint.Always);
}
public void initialize(AssetManager assetManager, Camera camera) {
armatureNode.setCamera(camera);
Material matJoints = new Material(assetManager, "Common/MatDefs/Misc/Billboard.j3md");
Texture t = assetManager.loadTexture("Common/Textures/dot.png");
matJoints.setTexture("Texture", t);
matJoints.getAdditionalRenderState().setDepthTest(false);
matJoints.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
joints.setQueueBucket(RenderQueue.Bucket.Translucent);
joints.setMaterial(matJoints);
Material matWires = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
matWires.setBoolean("VertexColor", true);
matWires.getAdditionalRenderState().setLineWidth(3);
wires.setMaterial(matWires);
Material matOutline = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
matOutline.setBoolean("VertexColor", true);
matOutline.getAdditionalRenderState().setLineWidth(5);
outlines.setMaterial(matOutline);
Material matOutline2 = new Material(assetManager, "Common/MatDefs/Misc/DashedLine.j3md");
matOutline2.getAdditionalRenderState().setLineWidth(1);
outlines.getChild(1).setMaterial(matOutline2);
Material matWires2 = new Material(assetManager, "Common/MatDefs/Misc/DashedLine.j3md");
matWires2.getAdditionalRenderState().setLineWidth(1);
wires.getChild(1).setMaterial(matWires2);
}
public Armature getArmature() {
return armature;
}
@Override
public void updateLogicalState(float tpf) {
super.updateLogicalState(tpf);
armatureNode.updateGeometry();
}
@Override
public int collideWith(Collidable other, CollisionResults results) {
return armatureNode.collideWith(other, results);
}
protected Joint select(Geometry g) {
return armatureNode.select(g);
}
/**
* @return the armature wires
*/
public ArmatureNode getBoneShapes() {
return armatureNode;
}
/**
* @return the dotted line between bones (can be null)
*/
public ArmatureInterJointsWire getInterJointWires() {
return interJointWires;
}
}

@ -0,0 +1,124 @@
package com.jme3.scene.debug.custom;
/*
* 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.
*/
import com.jme3.math.Vector3f;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Type;
import java.nio.FloatBuffer;
/**
* A class that displays a dotted line between a bone tail and its childrens' heads.
*
* @author Marcin Roguski (Kaelthas)
*/
public class ArmatureInterJointsWire extends Mesh {
private Vector3f tmp = new Vector3f();
public ArmatureInterJointsWire(Vector3f start, Vector3f[] ends) {
setMode(Mode.Lines);
updateGeometry(start, ends);
}
protected void updateGeometry(Vector3f start, Vector3f[] ends) {
float[] pos = new float[ends.length * 3 + 3];
pos[0] = start.x;
pos[1] = start.y;
pos[2] = start.z;
int index;
for (int i = 0; i < ends.length; i++) {
index = i * 3 + 3;
pos[index] = ends[i].x;
pos[index + 1] = ends[i].y;
pos[index + 2] = ends[i].z;
}
setBuffer(Type.Position, 3, pos);
float[] texCoord = new float[ends.length * 2 + 2];
texCoord[0] = 0;
texCoord[1] = 0;
for (int i = 0; i < ends.length * 2; i++) {
texCoord[i + 2] = tmp.set(start).subtractLocal(ends[i / 2]).length();
}
setBuffer(Type.TexCoord, 2, texCoord);
float[] normal = new float[ends.length * 3 + 3];
for (int i = 0; i < ends.length * 3 + 3; i += 3) {
normal[i] = start.x;
normal[i + 1] = start.y;
normal[i + 2] = start.z;
}
setBuffer(Type.Normal, 3, normal);
short[] id = new short[ends.length * 2];
index = 1;
for (int i = 0; i < ends.length * 2; i += 2) {
id[i] = 0;
id[i + 1] = (short) (index);
index++;
}
setBuffer(Type.Index, 2, id);
updateBound();
}
/**
* Update the start and end points of the line.
*/
public void updatePoints(Vector3f start, Vector3f[] ends) {
VertexBuffer posBuf = getBuffer(Type.Position);
FloatBuffer fb = (FloatBuffer) posBuf.getData();
fb.rewind();
fb.put(start.x).put(start.y).put(start.z);
for (int i = 0; i < ends.length; i++) {
fb.put(ends[i].x);
fb.put(ends[i].y);
fb.put(ends[i].z);
}
posBuf.updateData(fb);
VertexBuffer normBuf = getBuffer(Type.Normal);
fb = (FloatBuffer) normBuf.getData();
fb.rewind();
for (int i = 0; i < ends.length * 3 + 3; i += 3) {
fb.put(start.x);
fb.put(start.y);
fb.put(start.z);
}
normBuf.updateData(fb);
}
}

@ -0,0 +1,332 @@
package com.jme3.scene.debug.custom;
/*
* 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.
*/
import com.jme3.anim.Armature;
import com.jme3.anim.Joint;
import com.jme3.collision.*;
import com.jme3.math.*;
import com.jme3.renderer.Camera;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.*;
import com.jme3.scene.shape.Line;
import java.nio.FloatBuffer;
import java.util.*;
/**
* The class that displays either wires between the bones' heads if no length
* data is supplied and full bones' shapes otherwise.
*/
public class ArmatureNode extends Node {
public static final float PIXEL_BOX = 10f;
/**
* The armature to be displayed.
*/
private Armature armature;
/**
* The map between the bone index and its length.
*/
private Map<Joint, Geometry[]> jointToGeoms = new HashMap<>();
private Map<Geometry, Joint> geomToJoint = new HashMap<>();
private Joint selectedJoint = null;
private Vector3f tmp = new Vector3f();
private Vector2f tmpv2 = new Vector2f();
private final static ColorRGBA selectedColor = ColorRGBA.Orange;
private final static ColorRGBA selectedColorJ = ColorRGBA.Yellow;
private final static ColorRGBA outlineColor = ColorRGBA.LightGray;
private final static ColorRGBA baseColor = new ColorRGBA(0.05f, 0.05f, 0.05f, 1f);
private Camera camera;
/**
* Creates a wire with bone lengths data. If the data is supplied then the
* wires will show each full bone (from head to tail).
*
* @param armature the armature that will be shown
*/
public ArmatureNode(Armature armature, Node joints, Node wires, Node outlines, List<Joint> deformingJoints) {
this.armature = armature;
Geometry origin = new Geometry("Armature Origin", new JointShape());
setColor(origin, ColorRGBA.Green);
attach(joints, true, origin);
for (Joint joint : armature.getRoots()) {
createSkeletonGeoms(joint, joints, wires, outlines, deformingJoints);
}
this.updateModelBound();
}
protected final void createSkeletonGeoms(Joint joint, Node joints, Node wires, Node outlines, List<Joint> deformingJoints) {
Vector3f start = joint.getModelTransform().getTranslation().clone();
Vector3f[] ends = null;
if (!joint.getChildren().isEmpty()) {
ends = new Vector3f[joint.getChildren().size()];
}
for (int i = 0; i < joint.getChildren().size(); i++) {
ends[i] = joint.getChildren().get(i).getModelTransform().getTranslation().clone();
}
boolean deforms = deformingJoints.contains(joint);
Geometry jGeom = new Geometry(joint.getName() + "Joint", new JointShape());
jGeom.setLocalTranslation(start);
attach(joints, deforms, jGeom);
Geometry bGeom = null;
Geometry bGeomO = null;
if (ends == null) {
geomToJoint.put(jGeom, joint);
} else {
Mesh m = null;
Mesh mO = null;
Node wireAttach = wires;
Node outlinesAttach = outlines;
if (ends.length == 1) {
m = new Line(start, ends[0]);
mO = new Line(start, ends[0]);
} else {
m = new ArmatureInterJointsWire(start, ends);
mO = new ArmatureInterJointsWire(start, ends);
wireAttach = (Node) wires.getChild(1);
outlinesAttach = null;
}
bGeom = new Geometry(joint.getName() + "Bone", m);
setColor(bGeom, outlinesAttach == null ? outlineColor : baseColor);
geomToJoint.put(bGeom, joint);
bGeom.setUserData("start", getWorldTransform().transformVector(start, start));
for (int i = 0; i < ends.length; i++) {
getWorldTransform().transformVector(ends[i], ends[i]);
}
bGeom.setUserData("end", ends);
bGeom.setQueueBucket(RenderQueue.Bucket.Transparent);
attach(wireAttach, deforms, bGeom);
if (outlinesAttach != null) {
bGeomO = new Geometry(joint.getName() + "BoneOutline", mO);
setColor(bGeomO, outlineColor);
attach(outlinesAttach, deforms, bGeomO);
}
}
jointToGeoms.put(joint, new Geometry[]{jGeom, bGeom, bGeomO});
for (Joint child : joint.getChildren()) {
createSkeletonGeoms(child, joints, wires, outlines, deformingJoints);
}
}
public void setCamera(Camera camera) {
this.camera = camera;
}
private void attach(Node parent, boolean deforms, Geometry geom) {
if (deforms) {
parent.attachChild(geom);
} else {
((Node) parent.getChild(0)).attachChild(geom);
}
}
protected Joint select(Geometry g) {
if (g == null) {
resetSelection();
return null;
}
Joint j = geomToJoint.get(g);
if (j != null) {
if (selectedJoint == j) {
return null;
}
resetSelection();
selectedJoint = j;
Geometry[] geomArray = jointToGeoms.get(selectedJoint);
setColor(geomArray[0], selectedColorJ);
if (geomArray[1] != null) {
setColor(geomArray[1], selectedColor);
}
if (geomArray[2] != null) {
setColor(geomArray[2], baseColor);
}
return j;
}
return null;
}
private void resetSelection() {
if (selectedJoint == null) {
return;
}
Geometry[] geoms = jointToGeoms.get(selectedJoint);
setColor(geoms[0], ColorRGBA.White);
if (geoms[1] != null) {
setColor(geoms[1], geoms[2] == null ? outlineColor : baseColor);
}
if (geoms[2] != null) {
setColor(geoms[2], outlineColor);
}
selectedJoint = null;
}
protected Joint getSelectedJoint() {
return selectedJoint;
}
protected final void updateSkeletonGeoms(Joint joint) {
Geometry[] geoms = jointToGeoms.get(joint);
if (geoms != null) {
Geometry jGeom = geoms[0];
jGeom.setLocalTranslation(joint.getModelTransform().getTranslation());
Geometry bGeom = geoms[1];
if (bGeom != null) {
Vector3f start = bGeom.getUserData("start");
Vector3f[] ends = bGeom.getUserData("end");
start.set(joint.getModelTransform().getTranslation());
if (ends != null) {
for (int i = 0; i < joint.getChildren().size(); i++) {
ends[i].set(joint.getChildren().get(i).getModelTransform().getTranslation());
}
updateBoneMesh(bGeom, start, ends);
Geometry bGeomO = geoms[2];
if (bGeomO != null) {
updateBoneMesh(bGeomO, start, ends);
}
bGeom.setUserData("start", getWorldTransform().transformVector(start, start));
for (int i = 0; i < ends.length; i++) {
getWorldTransform().transformVector(ends[i], ends[i]);
}
bGeom.setUserData("end", ends);
}
}
}
for (Joint child : joint.getChildren()) {
updateSkeletonGeoms(child);
}
}
public int pick(Vector2f cursor, CollisionResults results) {
for (Geometry g : geomToJoint.keySet()) {
if (g.getMesh() instanceof JointShape) {
camera.getScreenCoordinates(g.getWorldTranslation(), tmp);
if (cursor.x <= tmp.x + PIXEL_BOX && cursor.x >= tmp.x - PIXEL_BOX
&& cursor.y <= tmp.y + PIXEL_BOX && cursor.y >= tmp.y - PIXEL_BOX) {
CollisionResult res = new CollisionResult();
res.setGeometry(g);
results.addCollision(res);
}
}
}
return 0;
}
@Override
public int collideWith(Collidable other, CollisionResults results) {
if (!(other instanceof Ray)) {
return 0;
}
// first try a 2D pick;
camera.getScreenCoordinates(((Ray)other).getOrigin(),tmp);
tmpv2.x = tmp.x;
tmpv2.y = tmp.y;
int nbHit = pick(tmpv2, results);
if (nbHit > 0) {
return nbHit;
}
for (Geometry g : geomToJoint.keySet()) {
if (g.getMesh() instanceof JointShape) {
continue;
}
Vector3f start = g.getUserData("start");
Vector3f[] ends = g.getUserData("end");
for (int i = 0; i < ends.length; i++) {
float len = MathUtils.raySegmentShortestDistance((Ray) other, start, ends[i], camera);
if (len > 0 && len < PIXEL_BOX) {
CollisionResult res = new CollisionResult();
res.setGeometry(g);
results.addCollision(res);
nbHit++;
}
}
}
return nbHit;
}
private void updateBoneMesh(Geometry geom, Vector3f start, Vector3f[] ends) {
if (geom.getMesh() instanceof ArmatureInterJointsWire) {
((ArmatureInterJointsWire) geom.getMesh()).updatePoints(start, ends);
} else if (geom.getMesh() instanceof Line) {
((Line) geom.getMesh()).updatePoints(start, ends[0]);
}
geom.updateModelBound();
}
private void setColor(Geometry g, ColorRGBA color) {
float[] colors = new float[g.getMesh().getVertexCount() * 4];
for (int i = 0; i < g.getMesh().getVertexCount() * 4; i += 4) {
colors[i] = color.r;
colors[i + 1] = color.g;
colors[i + 2] = color.b;
colors[i + 3] = color.a;
}
VertexBuffer colorBuff = g.getMesh().getBuffer(VertexBuffer.Type.Color);
if (colorBuff == null) {
g.getMesh().setBuffer(VertexBuffer.Type.Color, 4, colors);
} else {
FloatBuffer cBuff = (FloatBuffer) colorBuff.getData();
cBuff.rewind();
cBuff.put(colors);
colorBuff.updateData(cBuff);
}
}
/**
* The method updates the geometry according to the positions of the bones.
*/
public void updateGeometry() {
armature.update();
for (Joint joint : armature.getRoots()) {
updateSkeletonGeoms(joint);
}
}
}

@ -1,417 +0,0 @@
/*
* Copyright (c) 2009-2018 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.
*/
// $Id: Cylinder.java 4131 2009-03-19 20:15:28Z blaine.dev $
package com.jme3.scene.debug.custom;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.mesh.IndexBuffer;
import com.jme3.util.BufferUtils;
import static com.jme3.util.BufferUtils.*;
import java.io.IOException;
import java.nio.FloatBuffer;
/**
* A simple cylinder, defined by its height and radius.
* (Ported to jME3)
*
* @author Mark Powell
* @version $Revision: 4131 $, $Date: 2009-03-19 16:15:28 -0400 (Thu, 19 Mar 2009) $
*/
public class BoneShape extends Mesh {
private int axisSamples;
private int radialSamples;
private float radius;
private float radius2;
private float height;
private boolean closed;
private boolean inverted;
/**
* Default constructor for serialization only. Do not use.
*/
public BoneShape() {
}
/**
* Creates a new Cylinder. By default its center is the origin. Usually, a
* higher sample number creates a better looking cylinder, but at the cost
* of more vertex information.
*
* @param axisSamples Number of triangle samples along the axis.
* @param radialSamples Number of triangle samples along the radial.
* @param radius The radius of the cylinder.
* @param height The cylinder's height.
*/
public BoneShape(int axisSamples, int radialSamples,
float radius, float height) {
this(axisSamples, radialSamples, radius, height, false);
}
/**
* Creates a new Cylinder. By default its center is the origin. Usually, a
* higher sample number creates a better looking cylinder, but at the cost
* of more vertex information. <br>
* If the cylinder is closed the texture is split into axisSamples parts:
* top most and bottom most part is used for top and bottom of the cylinder,
* rest of the texture for the cylinder wall. The middle of the top is
* mapped to texture coordinates (0.5, 1), bottom to (0.5, 0). Thus you need
* a suited distorted texture.
*
* @param axisSamples Number of triangle samples along the axis.
* @param radialSamples Number of triangle samples along the radial.
* @param radius The radius of the cylinder.
* @param height The cylinder's height.
* @param closed true to create a cylinder with top and bottom surface
*/
public BoneShape(int axisSamples, int radialSamples,
float radius, float height, boolean closed) {
this(axisSamples, radialSamples, radius, height, closed, false);
}
/**
* Creates a new Cylinder. By default its center is the origin. Usually, a
* higher sample number creates a better looking cylinder, but at the cost
* of more vertex information. <br>
* If the cylinder is closed the texture is split into axisSamples parts:
* top most and bottom most part is used for top and bottom of the cylinder,
* rest of the texture for the cylinder wall. The middle of the top is
* mapped to texture coordinates (0.5, 1), bottom to (0.5, 0). Thus you need
* a suited distorted texture.
*
* @param axisSamples Number of triangle samples along the axis.
* @param radialSamples Number of triangle samples along the radial.
* @param radius The radius of the cylinder.
* @param height The cylinder's height.
* @param closed true to create a cylinder with top and bottom surface
* @param inverted true to create a cylinder that is meant to be viewed from the
* interior.
*/
public BoneShape(int axisSamples, int radialSamples,
float radius, float height, boolean closed, boolean inverted) {
this(axisSamples, radialSamples, radius, radius, height, closed, inverted);
}
public BoneShape(int axisSamples, int radialSamples,
float radius, float radius2, float height, boolean closed, boolean inverted) {
super();
updateGeometry(axisSamples, radialSamples, radius, radius2, height, closed, inverted);
}
/**
* @return the number of samples along the cylinder axis
*/
public int getAxisSamples() {
return axisSamples;
}
/**
* @return Returns the height.
*/
public float getHeight() {
return height;
}
/**
* @return number of samples around cylinder
*/
public int getRadialSamples() {
return radialSamples;
}
/**
* @return Returns the radius.
*/
public float getRadius() {
return radius;
}
public float getRadius2() {
return radius2;
}
/**
* @return true if end caps are used.
*/
public boolean isClosed() {
return closed;
}
/**
* @return true if normals and uvs are created for interior use
*/
public boolean isInverted() {
return inverted;
}
/**
* Rebuilds the cylinder based on a new set of parameters.
*
* @param axisSamples the number of samples along the axis.
* @param radialSamples the number of samples around the radial.
* @param radius the radius of the bottom of the cylinder.
* @param radius2 the radius of the top of the cylinder.
* @param height the cylinder's height.
* @param closed should the cylinder have top and bottom surfaces.
* @param inverted is the cylinder is meant to be viewed from the inside.
*/
public void updateGeometry(int axisSamples, int radialSamples,
float radius, float radius2, float height, boolean closed, boolean inverted) {
this.axisSamples = axisSamples + (closed ? 2 : 0);
this.radialSamples = radialSamples;
this.radius = radius;
this.radius2 = radius2;
this.height = height;
this.closed = closed;
this.inverted = inverted;
// VertexBuffer pvb = getBuffer(Type.Position);
// VertexBuffer nvb = getBuffer(Type.Normal);
// VertexBuffer tvb = getBuffer(Type.TexCoord);
// Vertices
int vertCount = axisSamples * (radialSamples + 1) + (closed ? 2 : 0);
setBuffer(Type.Position, 3, createVector3Buffer(getFloatBuffer(Type.Position), vertCount));
// Normals
setBuffer(Type.Normal, 3, createVector3Buffer(getFloatBuffer(Type.Normal), vertCount));
// Texture co-ordinates
setBuffer(Type.TexCoord, 2, createVector2Buffer(vertCount));
int triCount = ((closed ? 2 : 0) + 2 * (axisSamples - 1)) * radialSamples;
setBuffer(Type.Index, 3, createShortBuffer(getShortBuffer(Type.Index), 3 * triCount));
//Color
setBuffer(Type.Color, 4, createFloatBuffer(vertCount * 4));
// generate geometry
float inverseRadial = 1.0f / radialSamples;
float inverseAxisLess = 1.0f / (closed ? axisSamples - 3 : axisSamples - 1);
float inverseAxisLessTexture = 1.0f / (axisSamples - 1);
float halfHeight = 0.5f * height;
// Generate points on the unit circle to be used in computing the mesh
// points on a cylinder slice.
float[] sin = new float[radialSamples + 1];
float[] cos = new float[radialSamples + 1];
for (int radialCount = 0; radialCount < radialSamples; radialCount++) {
float angle = FastMath.TWO_PI * inverseRadial * radialCount;
cos[radialCount] = FastMath.cos(angle);
sin[radialCount] = FastMath.sin(angle);
}
sin[radialSamples] = sin[0];
cos[radialSamples] = cos[0];
// calculate normals
Vector3f[] vNormals = null;
Vector3f vNormal = Vector3f.UNIT_Z;
if ((height != 0.0f) && (radius != radius2)) {
vNormals = new Vector3f[radialSamples];
Vector3f vHeight = Vector3f.UNIT_Z.mult(height);
Vector3f vRadial = new Vector3f();
for (int radialCount = 0; radialCount < radialSamples; radialCount++) {
vRadial.set(cos[radialCount], sin[radialCount], 0.0f);
Vector3f vRadius = vRadial.mult(radius);
Vector3f vRadius2 = vRadial.mult(radius2);
Vector3f vMantle = vHeight.subtract(vRadius2.subtract(vRadius));
Vector3f vTangent = vRadial.cross(Vector3f.UNIT_Z);
vNormals[radialCount] = vMantle.cross(vTangent).normalize();
}
}
FloatBuffer nb = getFloatBuffer(Type.Normal);
FloatBuffer pb = getFloatBuffer(Type.Position);
FloatBuffer tb = getFloatBuffer(Type.TexCoord);
FloatBuffer cb = getFloatBuffer(Type.Color);
cb.rewind();
for (int i = 0; i < vertCount; i++) {
cb.put(0.05f).put(0.05f).put(0.05f).put(1f);
}
// generate the cylinder itself
Vector3f tempNormal = new Vector3f();
for (int axisCount = 0, i = 0; axisCount < axisSamples; axisCount++, i++) {
float axisFraction;
float axisFractionTexture;
int topBottom = 0;
if (!closed) {
axisFraction = axisCount * inverseAxisLess; // in [0,1]
axisFractionTexture = axisFraction;
} else {
if (axisCount == 0) {
topBottom = -1; // bottom
axisFraction = 0;
axisFractionTexture = inverseAxisLessTexture;
} else if (axisCount == axisSamples - 1) {
topBottom = 1; // top
axisFraction = 1;
axisFractionTexture = 1 - inverseAxisLessTexture;
} else {
axisFraction = (axisCount - 1) * inverseAxisLess;
axisFractionTexture = axisCount * inverseAxisLessTexture;
}
}
// compute center of slice
float z = height * axisFraction;
Vector3f sliceCenter = new Vector3f(0, 0, z);
// compute slice vertices with duplication at end point
int save = i;
for (int radialCount = 0; radialCount < radialSamples; radialCount++, i++) {
float radialFraction = radialCount * inverseRadial; // in [0,1)
tempNormal.set(cos[radialCount], sin[radialCount], 0.0f);
if (vNormals != null) {
vNormal = vNormals[radialCount];
} else if (radius == radius2) {
vNormal = tempNormal;
}
if (topBottom == 0) {
if (!inverted)
nb.put(vNormal.x).put(vNormal.y).put(vNormal.z);
else
nb.put(-vNormal.x).put(-vNormal.y).put(-vNormal.z);
} else {
nb.put(0).put(0).put(topBottom * (inverted ? -1 : 1));
}
tempNormal.multLocal((radius - radius2) * axisFraction + radius2)
.addLocal(sliceCenter);
pb.put(tempNormal.x).put(tempNormal.y).put(tempNormal.z);
tb.put((inverted ? 1 - radialFraction : radialFraction))
.put(axisFractionTexture);
}
BufferUtils.copyInternalVector3(pb, save, i);
BufferUtils.copyInternalVector3(nb, save, i);
tb.put((inverted ? 0.0f : 1.0f))
.put(axisFractionTexture);
}
if (closed) {
pb.put(0).put(0).put(-halfHeight); // bottom center
nb.put(0).put(0).put(-1 * (inverted ? -1 : 1));
tb.put(0.5f).put(0);
pb.put(0).put(0).put(halfHeight); // top center
nb.put(0).put(0).put(1 * (inverted ? -1 : 1));
tb.put(0.5f).put(1);
}
IndexBuffer ib = getIndexBuffer();
int index = 0;
// Connectivity
for (int axisCount = 0, axisStart = 0; axisCount < axisSamples - 1; axisCount++) {
int i0 = axisStart;
int i1 = i0 + 1;
axisStart += radialSamples + 1;
int i2 = axisStart;
int i3 = i2 + 1;
for (int i = 0; i < radialSamples; i++) {
if (closed && axisCount == 0) {
if (!inverted) {
ib.put(index++, i0++);
ib.put(index++, vertCount - 2);
ib.put(index++, i1++);
} else {
ib.put(index++, i0++);
ib.put(index++, i1++);
ib.put(index++, vertCount - 2);
}
} else if (closed && axisCount == axisSamples - 2) {
ib.put(index++, i2++);
ib.put(index++, inverted ? vertCount - 1 : i3++);
ib.put(index++, inverted ? i3++ : vertCount - 1);
} else {
ib.put(index++, i0++);
ib.put(index++, inverted ? i2 : i1);
ib.put(index++, inverted ? i1 : i2);
ib.put(index++, i1++);
ib.put(index++, inverted ? i2++ : i3++);
ib.put(index++, inverted ? i3++ : i2++);
}
}
}
updateBound();
}
@Override
public void read(JmeImporter e) throws IOException {
super.read(e);
InputCapsule capsule = e.getCapsule(this);
axisSamples = capsule.readInt("axisSamples", 0);
radialSamples = capsule.readInt("radialSamples", 0);
radius = capsule.readFloat("radius", 0);
radius2 = capsule.readFloat("radius2", 0);
height = capsule.readFloat("height", 0);
closed = capsule.readBoolean("closed", false);
inverted = capsule.readBoolean("inverted", false);
}
@Override
public void write(JmeExporter e) throws IOException {
super.write(e);
OutputCapsule capsule = e.getCapsule(this);
capsule.write(axisSamples, "axisSamples", 0);
capsule.write(radialSamples, "radialSamples", 0);
capsule.write(radius, "radius", 0);
capsule.write(radius2, "radius2", 0);
capsule.write(height, "height", 0);
capsule.write(closed, "closed", false);
capsule.write(inverted, "inverted", false);
}
}

@ -0,0 +1,79 @@
/*
* Copyright (c) 2009-2010 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.scene.debug.custom;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer.Type;
public class JointShape extends Mesh {
/**
* Serialization only. Do not use.
*/
public JointShape() {
float width = 1;
float height = 1;
setBuffer(Type.Position, 3, new float[]{-width * 0.5f, -width * 0.5f, 0,
width * 0.5f, -width * 0.5f, 0,
width * 0.5f, height * 0.5f, 0,
-width * 0.5f, height * 0.5f, 0
});
setBuffer(Type.TexCoord, 2, new float[]{0, 0,
1, 0,
1, 1,
0, 1});
setBuffer(Type.Normal, 3, new float[]{0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1});
setBuffer(Type.Color, 4, new float[]{1, 1, 1, 1,
1, 1, 1, 1,
1, 1, 1, 1,
1, 1, 1, 1});
setBuffer(Type.Index, 3, new short[]{0, 1, 2,
0, 2, 3});
updateBound();
setStatic();
}
}

@ -1,238 +0,0 @@
package com.jme3.scene.debug.custom;
/*
* 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.
*/
import java.util.Map;
import com.jme3.animation.Bone;
import com.jme3.animation.Skeleton;
import com.jme3.bounding.*;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.shape.Sphere;
import static com.jme3.util.BufferUtils.createFloatBuffer;
import java.nio.FloatBuffer;
import java.util.HashMap;
/**
* The class that displays either wires between the bones' heads if no length
* data is supplied and full bones' shapes otherwise.
*/
public class SkeletonBone extends Node {
/**
* The skeleton to be displayed.
*/
private Skeleton skeleton;
/**
* The map between the bone index and its length.
*/
private Map<Bone, Node> boneNodes = new HashMap<Bone, Node>();
private Map<Node, Bone> nodeBones = new HashMap<Node, Bone>();
private Node selectedNode = null;
private boolean guessBonesOrientation = false;
/**
* Creates a wire with bone lengths data. If the data is supplied then the
* wires will show each full bone (from head to tail).
*
* @param skeleton the skeleton that will be shown
* @param boneLengths a map between the bone's index and the bone's length
*/
public SkeletonBone(Skeleton skeleton, Map<Integer, Float> boneLengths, boolean guessBonesOrientation) {
this.skeleton = skeleton;
this.skeleton.reset();
this.skeleton.updateWorldVectors();
this.guessBonesOrientation = guessBonesOrientation;
BoneShape boneShape = new BoneShape(5, 12, 0.02f, 0.07f, 1f, false, false);
Sphere jointShape = new Sphere(10, 10, 0.1f);
jointShape.setBuffer(VertexBuffer.Type.Color, 4, createFloatBuffer(jointShape.getVertexCount() * 4));
FloatBuffer cb = jointShape.getFloatBuffer(VertexBuffer.Type.Color);
cb.rewind();
for (int i = 0; i < jointShape.getVertexCount(); i++) {
cb.put(0.05f).put(0.05f).put(0.05f).put(1f);
}
for (Bone bone : skeleton.getRoots()) {
createSkeletonGeoms(bone, boneShape, jointShape, boneLengths, skeleton, this, guessBonesOrientation);
}
this.updateModelBound();
Sphere originShape = new Sphere(10, 10, 0.02f);
originShape.setBuffer(VertexBuffer.Type.Color, 4, createFloatBuffer(originShape.getVertexCount() * 4));
cb = originShape.getFloatBuffer(VertexBuffer.Type.Color);
cb.rewind();
for (int i = 0; i < jointShape.getVertexCount(); i++) {
cb.put(0.4f).put(0.4f).put(0.05f).put(1f);
}
Geometry origin = new Geometry("origin", originShape);
BoundingVolume bv = this.getWorldBound();
float scale = 1;
if (bv.getType() == BoundingVolume.Type.AABB) {
BoundingBox bb = (BoundingBox) bv;
scale = (bb.getXExtent() + bb.getYExtent() + bb.getZExtent()) / 3f;
} else if (bv.getType() == BoundingVolume.Type.Sphere) {
BoundingSphere bs = (BoundingSphere) bv;
scale = bs.getRadius();
}
origin.scale(scale);
attachChild(origin);
}
protected final void createSkeletonGeoms(Bone bone, Mesh boneShape, Mesh jointShape, Map<Integer, Float> boneLengths, Skeleton skeleton, Node parent, boolean guessBonesOrientation) {
if (guessBonesOrientation && bone.getName().equalsIgnoreCase("Site")) {
//BVH skeleton have a useless end point bone named Site
return;
}
Node n = new Node(bone.getName() + "Node");
Geometry bGeom = new Geometry(bone.getName(), boneShape);
Geometry jGeom = new Geometry(bone.getName() + "Joint", jointShape);
n.setLocalTranslation(bone.getLocalPosition());
n.setLocalRotation(bone.getLocalRotation());
float boneLength = boneLengths.get(skeleton.getBoneIndex(bone));
n.setLocalScale(bone.getLocalScale());
bGeom.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X).normalizeLocal());
if (guessBonesOrientation) {
//One child only, the bone direction is from the parent joint to the child joint.
if (bone.getChildren().size() == 1) {
Vector3f v = bone.getChildren().get(0).getLocalPosition();
Quaternion q = new Quaternion();
q.lookAt(v, Vector3f.UNIT_Z);
bGeom.setLocalRotation(q);
boneLength = v.length();
}
//no child, the bone has the same direction as the parent bone.
if (bone.getChildren().isEmpty()) {
if (parent.getChildren().size() > 0) {
bGeom.setLocalRotation(parent.getChild(0).getLocalRotation());
} else {
//no parent, let's use the bind orientation of the bone
bGeom.setLocalRotation(bone.getBindRotation());
}
}
}
bGeom.setLocalScale(boneLength);
jGeom.setLocalScale(boneLength);
n.attachChild(bGeom);
n.attachChild(jGeom);
//tip
if (bone.getChildren().size() != 1) {
Geometry gt = jGeom.clone();
gt.scale(0.8f);
Vector3f v = new Vector3f(0, boneLength, 0);
if (guessBonesOrientation) {
if (bone.getChildren().isEmpty()) {
if (parent.getChildren().size() > 0) {
gt.setLocalTranslation(bGeom.getLocalRotation().mult(parent.getChild(0).getLocalRotation()).mult(v, v));
} else {
gt.setLocalTranslation(bGeom.getLocalRotation().mult(bone.getBindRotation()).mult(v, v));
}
}
} else {
gt.setLocalTranslation(v);
}
n.attachChild(gt);
}
boneNodes.put(bone, n);
nodeBones.put(n, bone);
parent.attachChild(n);
for (Bone childBone : bone.getChildren()) {
createSkeletonGeoms(childBone, boneShape, jointShape, boneLengths, skeleton, n, guessBonesOrientation);
}
}
protected Bone select(Geometry g) {
Node parentNode = g.getParent();
if (parent != null) {
Bone b = nodeBones.get(parentNode);
if (b != null) {
selectedNode = parentNode;
}
return b;
}
return null;
}
protected Node getSelectedNode() {
return selectedNode;
}
protected final void updateSkeletonGeoms(Bone bone) {
if (guessBonesOrientation && bone.getName().equalsIgnoreCase("Site")) {
return;
}
Node n = boneNodes.get(bone);
n.setLocalTranslation(bone.getLocalPosition());
n.setLocalRotation(bone.getLocalRotation());
n.setLocalScale(bone.getLocalScale());
for (Bone childBone : bone.getChildren()) {
updateSkeletonGeoms(childBone);
}
}
/**
* The method updates the geometry according to the positions of the bones.
*/
public void updateGeometry() {
for (Bone bone : skeleton.getRoots()) {
updateSkeletonGeoms(bone);
}
}
}

@ -1,156 +0,0 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.jme3.scene.debug.custom;
import com.jme3.animation.Bone;
import com.jme3.animation.Skeleton;
import com.jme3.animation.SkeletonControl;
import com.jme3.app.Application;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.collision.CollisionResults;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.math.Ray;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Nehon
*/
public class SkeletonDebugAppState extends AbstractAppState {
private Node debugNode = new Node("debugNode");
private Map<Skeleton, SkeletonDebugger> skeletons = new HashMap<Skeleton, SkeletonDebugger>();
private Map<Skeleton, Bone> selectedBones = new HashMap<Skeleton, Bone>();
private Application app;
@Override
public void initialize(AppStateManager stateManager, Application app) {
ViewPort vp = app.getRenderManager().createMainView("debug", app.getCamera());
vp.attachScene(debugNode);
vp.setClearDepth(true);
this.app = app;
for (SkeletonDebugger skeletonDebugger : skeletons.values()) {
skeletonDebugger.initialize(app.getAssetManager());
}
app.getInputManager().addListener(actionListener, "shoot");
app.getInputManager().addMapping("shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT), new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
super.initialize(stateManager, app);
}
@Override
public void update(float tpf) {
debugNode.updateLogicalState(tpf);
debugNode.updateGeometricState();
}
public SkeletonDebugger addSkeleton(SkeletonControl skeletonControl, boolean guessBonesOrientation) {
Skeleton skeleton = skeletonControl.getSkeleton();
Spatial forSpatial = skeletonControl.getSpatial();
return addSkeleton(skeleton, forSpatial, guessBonesOrientation);
}
public SkeletonDebugger addSkeleton(Skeleton skeleton, Spatial forSpatial, boolean guessBonesOrientation) {
SkeletonDebugger sd = new SkeletonDebugger(forSpatial.getName() + "_Skeleton", skeleton, guessBonesOrientation);
sd.setLocalTransform(forSpatial.getWorldTransform());
if (forSpatial instanceof Node) {
List<Geometry> geoms = new ArrayList<>();
findGeoms((Node) forSpatial, geoms);
if (geoms.size() == 1) {
sd.setLocalTransform(geoms.get(0).getWorldTransform());
}
}
skeletons.put(skeleton, sd);
debugNode.attachChild(sd);
if (isInitialized()) {
sd.initialize(app.getAssetManager());
}
return sd;
}
private void findGeoms(Node node, List<Geometry> geoms) {
for (Spatial spatial : node.getChildren()) {
if (spatial instanceof Geometry) {
geoms.add((Geometry) spatial);
} else if (spatial instanceof Node) {
findGeoms((Node) spatial, geoms);
}
}
}
/**
* Pick a Target Using the Mouse Pointer. <ol><li>Map "pick target" action
* to a MouseButtonTrigger. <li>flyCam.setEnabled(false);
* <li>inputManager.setCursorVisible(true); <li>Implement action in
* AnalogListener (TODO).</ol>
*/
private ActionListener actionListener = new ActionListener() {
public void onAction(String name, boolean isPressed, float tpf) {
if (name.equals("shoot") && isPressed) {
CollisionResults results = new CollisionResults();
Vector2f click2d = app.getInputManager().getCursorPosition();
Vector3f click3d = app.getCamera().getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 0f).clone();
Vector3f dir = app.getCamera().getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 1f).subtractLocal(click3d);
Ray ray = new Ray(click3d, dir);
debugNode.collideWith(ray, results);
if (results.size() > 0) {
// The closest result is the target that the player picked:
Geometry target = results.getClosestCollision().getGeometry();
for (SkeletonDebugger skeleton : skeletons.values()) {
Bone selectedBone = skeleton.select(target);
if (selectedBone != null) {
selectedBones.put(skeleton.getSkeleton(), selectedBone);
System.err.println("-----------------------");
System.err.println("Selected Bone : " + selectedBone.getName() + " in skeleton " + skeleton.getName());
System.err.println("Root Bone : " + (selectedBone.getParent() == null));
System.err.println("-----------------------");
System.err.println("Bind translation: " + selectedBone.getBindPosition());
System.err.println("Bind rotation: " + selectedBone.getBindRotation());
System.err.println("Bind scale: " + selectedBone.getBindScale());
System.err.println("---");
System.err.println("Local translation: " + selectedBone.getLocalPosition());
System.err.println("Local rotation: " + selectedBone.getLocalRotation());
System.err.println("Local scale: " + selectedBone.getLocalScale());
System.err.println("---");
System.err.println("Model translation: " + selectedBone.getModelSpacePosition());
System.err.println("Model rotation: " + selectedBone.getModelSpaceRotation());
System.err.println("Model scale: " + selectedBone.getModelSpaceScale());
System.err.println("---");
System.err.println("Bind inverse Transform: ");
System.err.println(selectedBone.getBindInverseTransform());
return;
}
}
}
}
}
};
public Map<Skeleton, Bone> getSelectedBones() {
return selectedBones;
}
public Node getDebugNode() {
return debugNode;
}
public void setDebugNode(Node debugNode) {
this.debugNode = debugNode;
}
}

@ -1,218 +0,0 @@
package com.jme3.scene.debug.custom;
/*
* 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.
*/
import com.jme3.animation.Bone;
import java.util.Map;
import com.jme3.animation.Skeleton;
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.scene.BatchNode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.VertexBuffer;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* The class that creates a mesh to display how bones behave. If it is supplied
* with the bones' lengths it will show exactly how the bones look like on the
* scene. If not then only connections between each bone heads will be shown.
*/
public class SkeletonDebugger extends BatchNode {
/**
* The lines of the bones or the wires between their heads.
*/
private SkeletonBone bones;
private Skeleton skeleton;
/**
* The dotted lines between a bone's tail and the had of its children. Not
* available if the length data was not provided.
*/
private SkeletonInterBoneWire interBoneWires;
private List<Bone> selectedBones = new ArrayList<Bone>();
public SkeletonDebugger() {
}
/**
* Creates a debugger with no length data. The wires will be a connection
* between the bones' heads only. The points will show the bones' heads only
* and no dotted line of inter bones connection will be visible.
*
* @param name the name of the debugger's node
* @param skeleton the skeleton that will be shown
*/
public SkeletonDebugger(String name, Skeleton skeleton, boolean guessBonesOrientation) {
super(name);
this.skeleton = skeleton;
skeleton.reset();
skeleton.updateWorldVectors();
Map<Integer, Float> boneLengths = new HashMap<Integer, Float>();
for (Bone bone : skeleton.getRoots()) {
computeLength(bone, boneLengths, skeleton);
}
bones = new SkeletonBone(skeleton, boneLengths, guessBonesOrientation);
this.attachChild(bones);
interBoneWires = new SkeletonInterBoneWire(skeleton, boneLengths, guessBonesOrientation);
Geometry g = new Geometry(name + "_interwires", interBoneWires);
g.setBatchHint(BatchHint.Never);
this.attachChild(g);
}
protected void initialize(AssetManager assetManager) {
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", new ColorRGBA(0.05f, 0.05f, 0.05f, 1.0f));//new ColorRGBA(0.1f, 0.1f, 0.1f, 1.0f)
setMaterial(mat);
Material mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat2.setBoolean("VertexColor", true);
bones.setMaterial(mat2);
batch();
}
@Override
public final void setMaterial(Material material) {
if (batches.isEmpty()) {
for (int i = 0; i < children.size(); i++) {
children.get(i).setMaterial(material);
}
} else {
super.setMaterial(material);
}
}
public Skeleton getSkeleton() {
return skeleton;
}
private void computeLength(Bone b, Map<Integer, Float> boneLengths, Skeleton skeleton) {
if (b.getChildren().isEmpty()) {
if (b.getParent() != null) {
boneLengths.put(skeleton.getBoneIndex(b), boneLengths.get(skeleton.getBoneIndex(b.getParent())) * 0.75f);
} else {
boneLengths.put(skeleton.getBoneIndex(b), 0.1f);
}
} else {
float length = Float.MAX_VALUE;
for (Bone bone : b.getChildren()) {
float len = b.getModelSpacePosition().subtract(bone.getModelSpacePosition()).length();
if (len < length) {
length = len;
}
}
boneLengths.put(skeleton.getBoneIndex(b), length);
for (Bone bone : b.getChildren()) {
computeLength(bone, boneLengths, skeleton);
}
}
}
@Override
public void updateLogicalState(float tpf) {
super.updateLogicalState(tpf);
bones.updateGeometry();
if (interBoneWires != null) {
interBoneWires.updateGeometry();
}
}
ColorRGBA selectedColor = ColorRGBA.Orange;
ColorRGBA baseColor = new ColorRGBA(0.05f, 0.05f, 0.05f, 1f);
protected Bone select(Geometry g) {
Node oldNode = bones.getSelectedNode();
Bone b = bones.select(g);
if (b == null) {
return null;
}
if (oldNode != null) {
markSelected(oldNode, false);
}
markSelected(bones.getSelectedNode(), true);
return b;
}
/**
* @return the skeleton wires
*/
public SkeletonBone getBoneShapes() {
return bones;
}
/**
* @return the dotted line between bones (can be null)
*/
public SkeletonInterBoneWire getInterBoneWires() {
return interBoneWires;
}
protected void markSelected(Node n, boolean selected) {
ColorRGBA c = baseColor;
if (selected) {
c = selectedColor;
}
for (Spatial spatial : n.getChildren()) {
if (spatial instanceof Geometry) {
Geometry geom = (Geometry) spatial;
Geometry batch = (Geometry) getChild(getName() + "-batch0");
VertexBuffer vb = batch.getMesh().getBuffer(VertexBuffer.Type.Color);
FloatBuffer color = (FloatBuffer) vb.getData();
// System.err.println(getName() + "." + geom.getName() + " index " + getGeometryStartIndex(geom) * 4 + "/" + color.limit());
color.position(getGeometryStartIndex(geom) * 4);
for (int i = 0; i < geom.getVertexCount(); i++) {
color.put(c.r).put(c.g).put(c.b).put(c.a);
}
color.rewind();
vb.updateData(color);
}
}
}
}

@ -1,141 +0,0 @@
package com.jme3.scene.debug.custom;
/*
* 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.
*/
import java.nio.FloatBuffer;
import java.util.Map;
import com.jme3.animation.Bone;
import com.jme3.animation.Skeleton;
import com.jme3.math.Vector3f;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Format;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.VertexBuffer.Usage;
import com.jme3.util.BufferUtils;
/**
* A class that displays a dotted line between a bone tail and its childrens' heads.
*
* @author Marcin Roguski (Kaelthas)
*/
public class SkeletonInterBoneWire extends Mesh {
private static final int POINT_AMOUNT = 10;
/**
* The amount of connections between bones.
*/
private int connectionsAmount;
/**
* The skeleton that will be showed.
*/
private Skeleton skeleton;
/**
* The map between the bone index and its length.
*/
private Map<Integer, Float> boneLengths;
private boolean guessBonesOrientation = false;
/**
* Creates buffers for points. Each line has POINT_AMOUNT of points.
*
* @param skeleton the skeleton that will be showed
* @param boneLengths the lengths of the bones
*/
public SkeletonInterBoneWire(Skeleton skeleton, Map<Integer, Float> boneLengths, boolean guessBonesOrientation) {
this.skeleton = skeleton;
for (Bone bone : skeleton.getRoots()) {
this.countConnections(bone);
}
this.setMode(Mode.Points);
this.setPointSize(2);
this.boneLengths = boneLengths;
VertexBuffer pb = new VertexBuffer(Type.Position);
FloatBuffer fpb = BufferUtils.createFloatBuffer(POINT_AMOUNT * connectionsAmount * 3);
pb.setupData(Usage.Stream, 3, Format.Float, fpb);
this.setBuffer(pb);
this.guessBonesOrientation = guessBonesOrientation;
this.updateCounts();
}
/**
* The method updates the geometry according to the poitions of the bones.
*/
public void updateGeometry() {
VertexBuffer vb = this.getBuffer(Type.Position);
FloatBuffer posBuf = this.getFloatBuffer(Type.Position);
posBuf.clear();
for (int i = 0; i < skeleton.getBoneCount(); ++i) {
Bone bone = skeleton.getBone(i);
Vector3f parentTail = bone.getModelSpacePosition().add(bone.getModelSpaceRotation().mult(Vector3f.UNIT_Y.mult(boneLengths.get(i))));
if (guessBonesOrientation) {
parentTail = bone.getModelSpacePosition();
}
for (Bone child : bone.getChildren()) {
Vector3f childHead = child.getModelSpacePosition();
Vector3f v = childHead.subtract(parentTail);
float pointDelta = v.length() / POINT_AMOUNT;
v.normalizeLocal().multLocal(pointDelta);
Vector3f pointPosition = parentTail.clone();
for (int j = 0; j < POINT_AMOUNT; ++j) {
posBuf.put(pointPosition.getX()).put(pointPosition.getY()).put(pointPosition.getZ());
pointPosition.addLocal(v);
}
}
}
posBuf.flip();
vb.updateData(posBuf);
this.updateBound();
}
/**
* Th method couns the connections between bones.
*
* @param bone the bone where counting starts
*/
private void countConnections(Bone bone) {
for (Bone child : bone.getChildren()) {
++connectionsAmount;
this.countConnections(child);
}
}
}

@ -0,0 +1,51 @@
package com.jme3.scene.mesh;
import com.jme3.export.*;
import com.jme3.scene.VertexBuffer;
import java.io.IOException;
import java.nio.Buffer;
import java.nio.FloatBuffer;
import java.util.EnumMap;
import java.util.Map;
public class MorphTarget implements Savable {
private EnumMap<VertexBuffer.Type, FloatBuffer> buffers = new EnumMap<>(VertexBuffer.Type.class);
public void setBuffer(VertexBuffer.Type type, FloatBuffer buffer) {
buffers.put(type, buffer);
}
public FloatBuffer getBuffer(VertexBuffer.Type type) {
return buffers.get(type);
}
public EnumMap<VertexBuffer.Type, FloatBuffer> getBuffers() {
return buffers;
}
public int getNumBuffers() {
return buffers.size();
}
@Override
public void write(JmeExporter ex) throws IOException {
OutputCapsule oc = ex.getCapsule(this);
for (Map.Entry<VertexBuffer.Type, FloatBuffer> entry : buffers.entrySet()) {
Buffer roData = entry.getValue().asReadOnlyBuffer();
oc.write((FloatBuffer) roData, entry.getKey().name(),null);
}
}
@Override
public void read(JmeImporter im) throws IOException {
InputCapsule ic = im.getCapsule(this);
for (VertexBuffer.Type type : VertexBuffer.Type.values()) {
FloatBuffer b = ic.readFloatBuffer(type.name(), null);
if(b!= null){
setBuffer(type, b);
}
}
}
}

@ -31,12 +31,10 @@
*/ */
package com.jme3.scene.shape; package com.jme3.scene.shape;
import com.jme3.export.InputCapsule; import com.jme3.export.*;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
import com.jme3.scene.Mesh; import com.jme3.scene.Mesh;
import java.io.IOException; import java.io.IOException;
/** /**
@ -88,12 +86,12 @@ public abstract class AbstractBox extends Mesh {
/** /**
* Convert the indices into the list of vertices that define the box's geometry. * Convert the indices into the list of vertices that define the box's geometry.
*/ */
protected abstract void duUpdateGeometryIndices(); protected abstract void doUpdateGeometryIndices();
/** /**
* Update the normals of each of the box's planes. * Update the normals of each of the box's planes.
*/ */
protected abstract void duUpdateGeometryNormals(); protected abstract void doUpdateGeometryNormals();
/** /**
* Update the points that define the texture of the box. * Update the points that define the texture of the box.
@ -101,14 +99,14 @@ public abstract class AbstractBox extends Mesh {
* It's a one-to-one ratio, where each plane of the box has its own copy * It's a one-to-one ratio, where each plane of the box has its own copy
* of the texture. That is, the texture is repeated one time for each face. * of the texture. That is, the texture is repeated one time for each face.
*/ */
protected abstract void duUpdateGeometryTextures(); protected abstract void doUpdateGeometryTextures();
/** /**
* Update the position of the vertices that define the box. * Update the position of the vertices that define the box.
* <p> * <p>
* These eight points are determined from the minimum and maximum point. * These eight points are determined from the minimum and maximum point.
*/ */
protected abstract void duUpdateGeometryVertices(); protected abstract void doUpdateGeometryVertices();
/** /**
* Get the center point of this box. * Get the center point of this box.
@ -145,10 +143,10 @@ public abstract class AbstractBox extends Mesh {
* need to call this method afterwards in order to update the box. * need to call this method afterwards in order to update the box.
*/ */
public final void updateGeometry() { public final void updateGeometry() {
duUpdateGeometryVertices(); doUpdateGeometryVertices();
duUpdateGeometryNormals(); doUpdateGeometryNormals();
duUpdateGeometryTextures(); doUpdateGeometryTextures();
duUpdateGeometryIndices(); doUpdateGeometryIndices();
setStatic(); setStatic();
} }

@ -35,6 +35,7 @@ package com.jme3.scene.shape;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.VertexBuffer.Type;
import com.jme3.util.BufferUtils; import com.jme3.util.BufferUtils;
import java.nio.FloatBuffer; import java.nio.FloatBuffer;
/** /**
@ -144,25 +145,25 @@ public class Box extends AbstractBox {
return new Box(center.clone(), xExtent, yExtent, zExtent); return new Box(center.clone(), xExtent, yExtent, zExtent);
} }
protected void duUpdateGeometryIndices() { protected void doUpdateGeometryIndices() {
if (getBuffer(Type.Index) == null){ if (getBuffer(Type.Index) == null){
setBuffer(Type.Index, 3, BufferUtils.createShortBuffer(GEOMETRY_INDICES_DATA)); setBuffer(Type.Index, 3, BufferUtils.createShortBuffer(GEOMETRY_INDICES_DATA));
} }
} }
protected void duUpdateGeometryNormals() { protected void doUpdateGeometryNormals() {
if (getBuffer(Type.Normal) == null){ if (getBuffer(Type.Normal) == null){
setBuffer(Type.Normal, 3, BufferUtils.createFloatBuffer(GEOMETRY_NORMALS_DATA)); setBuffer(Type.Normal, 3, BufferUtils.createFloatBuffer(GEOMETRY_NORMALS_DATA));
} }
} }
protected void duUpdateGeometryTextures() { protected void doUpdateGeometryTextures() {
if (getBuffer(Type.TexCoord) == null){ if (getBuffer(Type.TexCoord) == null){
setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(GEOMETRY_TEXTURE_DATA)); setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(GEOMETRY_TEXTURE_DATA));
} }
} }
protected void duUpdateGeometryVertices() { protected void doUpdateGeometryVertices() {
FloatBuffer fpb = BufferUtils.createVector3Buffer(24); FloatBuffer fpb = BufferUtils.createVector3Buffer(24);
Vector3f[] v = computeVertices(); Vector3f[] v = computeVertices();
fpb.put(new float[] { fpb.put(new float[] {

@ -35,6 +35,7 @@ package com.jme3.scene.shape;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.VertexBuffer.Type;
import com.jme3.util.BufferUtils; import com.jme3.util.BufferUtils;
import java.nio.FloatBuffer; import java.nio.FloatBuffer;
/** /**
@ -138,13 +139,13 @@ public class StripBox extends AbstractBox {
return new StripBox(center.clone(), xExtent, yExtent, zExtent); return new StripBox(center.clone(), xExtent, yExtent, zExtent);
} }
protected void duUpdateGeometryIndices() { protected void doUpdateGeometryIndices() {
if (getBuffer(Type.Index) == null){ if (getBuffer(Type.Index) == null){
setBuffer(Type.Index, 3, BufferUtils.createShortBuffer(GEOMETRY_INDICES_DATA)); setBuffer(Type.Index, 3, BufferUtils.createShortBuffer(GEOMETRY_INDICES_DATA));
} }
} }
protected void duUpdateGeometryNormals() { protected void doUpdateGeometryNormals() {
if (getBuffer(Type.Normal) == null){ if (getBuffer(Type.Normal) == null){
float[] normals = new float[8 * 3]; float[] normals = new float[8 * 3];
@ -163,13 +164,13 @@ public class StripBox extends AbstractBox {
} }
} }
protected void duUpdateGeometryTextures() { protected void doUpdateGeometryTextures() {
if (getBuffer(Type.TexCoord) == null){ if (getBuffer(Type.TexCoord) == null){
setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(GEOMETRY_TEXTURE_DATA)); setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(GEOMETRY_TEXTURE_DATA));
} }
} }
protected void duUpdateGeometryVertices() { protected void doUpdateGeometryVertices() {
FloatBuffer fpb = BufferUtils.createVector3Buffer(8 * 3); FloatBuffer fpb = BufferUtils.createVector3Buffer(8 * 3);
Vector3f[] v = computeVertices(); Vector3f[] v = computeVertices();
fpb.put(new float[] { fpb.put(new float[] {

@ -57,7 +57,7 @@ import java.io.IOException;
* are from the camera, the smaller they are to maximize the resolution used of * are from the camera, the smaller they are to maximize the resolution used of
* the shadow map.<br> This results in a better quality shadow than standard * the shadow map.<br> This results in a better quality shadow than standard
* shadow mapping.<br> for more informations on this read this <a * shadow mapping.<br> for more informations on this read this <a
* href="http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html">http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html</a><br> * href="https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch10.html">https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch10.html</a><br>
* <p/> * <p/>
* @author Rémy Bouquet aka Nehon * @author Rémy Bouquet aka Nehon
*/ */
@ -83,7 +83,7 @@ public class DirectionalLightShadowRenderer extends AbstractShadowRenderer {
/** /**
* Create a DirectionalLightShadowRenderer More info on the technique at <a * Create a DirectionalLightShadowRenderer More info on the technique at <a
* href="http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html">http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html</a> * href="https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch10.html">https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch10.html</a>
* *
* @param assetManager the application asset manager * @param assetManager the application asset manager
* @param shadowMapSize the size of the rendered shadowmaps (512,1024,2048, * @param shadowMapSize the size of the rendered shadowmaps (512,1024,2048,

@ -111,6 +111,11 @@ MaterialDef Phong Lighting {
Int NumberOfBones Int NumberOfBones
Matrix4Array BoneMatrices Matrix4Array BoneMatrices
// For Morph animation
FloatArray MorphWeights
Int NumberOfMorphTargets
Int NumberOfTargetsBuffers
//For instancing //For instancing
Boolean UseInstancing Boolean UseInstancing
@ -152,6 +157,8 @@ MaterialDef Phong Lighting {
SPHERE_MAP : EnvMapAsSphereMap SPHERE_MAP : EnvMapAsSphereMap
NUM_BONES : NumberOfBones NUM_BONES : NumberOfBones
INSTANCING : UseInstancing INSTANCING : UseInstancing
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
} }
} }
@ -191,6 +198,8 @@ MaterialDef Phong Lighting {
SPHERE_MAP : EnvMapAsSphereMap SPHERE_MAP : EnvMapAsSphereMap
NUM_BONES : NumberOfBones NUM_BONES : NumberOfBones
INSTANCING : UseInstancing INSTANCING : UseInstancing
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
} }
} }
@ -210,6 +219,8 @@ MaterialDef Phong Lighting {
DISCARD_ALPHA : AlphaDiscardThreshold DISCARD_ALPHA : AlphaDiscardThreshold
NUM_BONES : NumberOfBones NUM_BONES : NumberOfBones
INSTANCING : UseInstancing INSTANCING : UseInstancing
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
} }
ForcedRenderState { ForcedRenderState {
@ -247,6 +258,8 @@ MaterialDef Phong Lighting {
NUM_BONES : NumberOfBones NUM_BONES : NumberOfBones
INSTANCING : UseInstancing INSTANCING : UseInstancing
BACKFACE_SHADOWS: BackfaceShadows BACKFACE_SHADOWS: BackfaceShadows
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
} }
ForcedRenderState { ForcedRenderState {
@ -274,6 +287,8 @@ MaterialDef Phong Lighting {
DIFFUSEMAP_ALPHA : DiffuseMap DIFFUSEMAP_ALPHA : DiffuseMap
NUM_BONES : NumberOfBones NUM_BONES : NumberOfBones
INSTANCING : UseInstancing INSTANCING : UseInstancing
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
} }
} }
@ -296,6 +311,8 @@ MaterialDef Phong Lighting {
NUM_BONES : NumberOfBones NUM_BONES : NumberOfBones
INSTANCING : UseInstancing INSTANCING : UseInstancing
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
} }
} }

@ -2,6 +2,8 @@
#import "Common/ShaderLib/Instancing.glsllib" #import "Common/ShaderLib/Instancing.glsllib"
#import "Common/ShaderLib/Skinning.glsllib" #import "Common/ShaderLib/Skinning.glsllib"
#import "Common/ShaderLib/Lighting.glsllib" #import "Common/ShaderLib/Lighting.glsllib"
#import "Common/ShaderLib/MorphAnim.glsllib"
#ifdef VERTEX_LIGHTING #ifdef VERTEX_LIGHTING
#import "Common/ShaderLib/BlinnPhongLighting.glsllib" #import "Common/ShaderLib/BlinnPhongLighting.glsllib"
#endif #endif
@ -90,6 +92,14 @@ void main(){
vec3 modelSpaceTan = inTangent.xyz; vec3 modelSpaceTan = inTangent.xyz;
#endif #endif
#ifdef NUM_MORPH_TARGETS
#if defined(NORMALMAP) && !defined(VERTEX_LIGHTING)
Morph_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan);
#else
Morph_Compute(modelSpacePos, modelSpaceNorm);
#endif
#endif
#ifdef NUM_BONES #ifdef NUM_BONES
#ifndef VERTEX_LIGHTING #ifndef VERTEX_LIGHTING
Skinning_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan); Skinning_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan);

@ -28,6 +28,9 @@ MaterialDef PBR Lighting {
Texture2D RoughnessMap -LINEAR Texture2D RoughnessMap -LINEAR
//Metallic and Roughness are packed respectively in the b and g channel of a single map //Metallic and Roughness are packed respectively in the b and g channel of a single map
// r: unspecified
// g: Roughness
// b: Metallic
Texture2D MetallicRoughnessMap -LINEAR Texture2D MetallicRoughnessMap -LINEAR
// Texture of the emissive parts of the material // Texture of the emissive parts of the material
@ -47,17 +50,6 @@ MaterialDef PBR Lighting {
Color Specular : 1.0 1.0 1.0 1.0 Color Specular : 1.0 1.0 1.0 1.0
Float Glossiness : 1.0 Float Glossiness : 1.0
Vector4 ProbeData
// Prefiltered Env Map for indirect specular lighting
TextureCubeMap PrefEnvMap -LINEAR
// Irradiance map for indirect diffuse lighting
TextureCubeMap IrradianceMap -LINEAR
//integrate BRDF map for indirect Lighting
Texture2D IntegrateBRDF -LINEAR
// Parallax/height map // Parallax/height map
Texture2D ParallaxMap -LINEAR Texture2D ParallaxMap -LINEAR
@ -111,6 +103,11 @@ MaterialDef PBR Lighting {
Int NumberOfBones Int NumberOfBones
Matrix4Array BoneMatrices Matrix4Array BoneMatrices
// For Morph animation
FloatArray MorphWeights
Int NumberOfMorphTargets
Int NumberOfTargetsBuffers
//For instancing //For instancing
Boolean UseInstancing Boolean UseInstancing
@ -158,6 +155,8 @@ MaterialDef PBR Lighting {
NORMAL_TYPE: NormalType NORMAL_TYPE: NormalType
VERTEX_COLOR : UseVertexColor VERTEX_COLOR : UseVertexColor
AO_MAP: LightMapAsAOMap AO_MAP: LightMapAsAOMap
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
} }
} }
@ -178,6 +177,8 @@ MaterialDef PBR Lighting {
DISCARD_ALPHA : AlphaDiscardThreshold DISCARD_ALPHA : AlphaDiscardThreshold
NUM_BONES : NumberOfBones NUM_BONES : NumberOfBones
INSTANCING : UseInstancing INSTANCING : UseInstancing
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
} }
ForcedRenderState { ForcedRenderState {
@ -215,6 +216,8 @@ MaterialDef PBR Lighting {
NUM_BONES : NumberOfBones NUM_BONES : NumberOfBones
INSTANCING : UseInstancing INSTANCING : UseInstancing
BACKFACE_SHADOWS: BackfaceShadows BACKFACE_SHADOWS: BackfaceShadows
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
} }
ForcedRenderState { ForcedRenderState {
@ -247,6 +250,8 @@ MaterialDef PBR Lighting {
NUM_BONES : NumberOfBones NUM_BONES : NumberOfBones
INSTANCING : UseInstancing INSTANCING : UseInstancing
BACKFACE_SHADOWS: BackfaceShadows BACKFACE_SHADOWS: BackfaceShadows
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
} }
ForcedRenderState { ForcedRenderState {
@ -272,6 +277,8 @@ MaterialDef PBR Lighting {
Defines { Defines {
NUM_BONES : NumberOfBones NUM_BONES : NumberOfBones
INSTANCING : UseInstancing INSTANCING : UseInstancing
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
} }
} }
@ -291,6 +298,8 @@ MaterialDef PBR Lighting {
NEED_TEXCOORD1 NEED_TEXCOORD1
NUM_BONES : NumberOfBones NUM_BONES : NumberOfBones
INSTANCING : UseInstancing INSTANCING : UseInstancing
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
} }
} }

@ -1,10 +1,9 @@
#import "Common/ShaderLib/GLSLCompat.glsllib" #import "Common/ShaderLib/GLSLCompat.glsllib"
#import "Common/ShaderLib/Instancing.glsllib" #import "Common/ShaderLib/Instancing.glsllib"
#import "Common/ShaderLib/Skinning.glsllib" #import "Common/ShaderLib/Skinning.glsllib"
#import "Common/ShaderLib/MorphAnim.glsllib"
uniform vec4 m_BaseColor; uniform vec4 m_BaseColor;
uniform vec4 g_AmbientLightColor; uniform vec4 g_AmbientLightColor;
varying vec2 texCoord; varying vec2 texCoord;
@ -38,11 +37,19 @@ void main(){
vec3 modelSpaceTan = inTangent.xyz; vec3 modelSpaceTan = inTangent.xyz;
#endif #endif
#ifdef NUM_MORPH_TARGETS
#if defined(NORMALMAP) && !defined(VERTEX_LIGHTING)
Morph_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan);
#else
Morph_Compute(modelSpacePos, modelSpaceNorm);
#endif
#endif
#ifdef NUM_BONES #ifdef NUM_BONES
#if defined(NORMALMAP) && !defined(VERTEX_LIGHTING) #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING)
Skinning_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan); Skinning_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan);
#else #else
Skinning_Compute(modelSpacePos, modelSpaceNorm); Skinning_Compute(modelSpacePos, modelSpaceNorm);
#endif #endif
#endif #endif

@ -2,6 +2,8 @@
#import "Common/ShaderLib/Instancing.glsllib" #import "Common/ShaderLib/Instancing.glsllib"
#import "Common/ShaderLib/Skinning.glsllib" #import "Common/ShaderLib/Skinning.glsllib"
#import "Common/ShaderLib/Lighting.glsllib" #import "Common/ShaderLib/Lighting.glsllib"
#import "Common/ShaderLib/MorphAnim.glsllib"
#ifdef VERTEX_LIGHTING #ifdef VERTEX_LIGHTING
#import "Common/ShaderLib/BlinnPhongLighting.glsllib" #import "Common/ShaderLib/BlinnPhongLighting.glsllib"
#endif #endif
@ -84,6 +86,14 @@ void main(){
vec3 modelSpaceTan = inTangent.xyz; vec3 modelSpaceTan = inTangent.xyz;
#endif #endif
#ifdef NUM_MORPH_TARGETS
#if defined(NORMALMAP) && !defined(VERTEX_LIGHTING)
Morph_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan);
#else
Morph_Compute(modelSpacePos, modelSpaceNorm);
#endif
#endif
#ifdef NUM_BONES #ifdef NUM_BONES
#if defined(NORMALMAP) && !defined(VERTEX_LIGHTING) #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING)
Skinning_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan); Skinning_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan);

@ -0,0 +1,78 @@
MaterialDef Billboard {
MaterialParameters {
Float SpriteHeight : 10
Texture2D Texture
}
Technique {
WorldParameters {
WorldViewMatrix
ProjectionMatrix
WorldMatrix
CameraDirection
ViewPort
CameraPosition
}
VertexShaderNodes {
ShaderNode TexCoord {
Definition: AttributeToVarying: Common/MatDefs/ShaderNodes/Basic/AttributeToVarying.j3sn
InputMappings {
vec2Variable = Attr.inTexCoord
vec4Variable = Attr.inColor
}
OutputMappings {
}
}
ShaderNode FixedScale {
Definition: FixedScale: Common/MatDefs/ShaderNodes/Common/FixedScale.j3sn
InputMappings {
projectionMatrix = WorldParam.ProjectionMatrix
worldMatrix = WorldParam.WorldMatrix
cameraDir = WorldParam.CameraDirection
viewport = WorldParam.ViewPort
modelPosition = Attr.inPosition
cameraPos = WorldParam.CameraPosition
spriteHeight = MatParam.SpriteHeight
}
OutputMappings {
}
}
ShaderNode Billboard {
Definition: Billboard: Common/MatDefs/ShaderNodes/Common/Billboard.j3sn
InputMappings {
worldViewMatrix = WorldParam.WorldViewMatrix
projectionMatrix = WorldParam.ProjectionMatrix
modelPosition = Attr.inPosition
scale = FixedScale.scale
}
OutputMappings {
Global.position = projPosition
}
}
}
FragmentShaderNodes {
ShaderNode TextureFetch {
Definition: TextureFetch: Common/MatDefs/ShaderNodes/Basic/TextureFetch.j3sn
InputMappings {
textureMap = MatParam.Texture
texCoord = TexCoord.vec2Variable
}
OutputMappings {
}
}
ShaderNode ColorMult {
Definition: ColorMult: Common/MatDefs/ShaderNodes/Basic/ColorMult.j3sn
InputMappings {
color1 = TextureFetch.outColor
color2 = TexCoord.vec4Variable
}
OutputMappings {
Global.color = outColor
}
}
}
}
}

@ -0,0 +1,59 @@
MaterialDef DashedLine {
MaterialParameters {
}
Technique {
WorldParameters {
WorldViewProjectionMatrix
Resolution
}
VertexShaderNodes {
ShaderNode TransformPosition {
Definition: TransformPosition: Common/MatDefs/ShaderNodes/Basic/TransformPosition.j3sn
InputMappings {
transformsMatrix = WorldParam.WorldViewProjectionMatrix
inputPosition = Attr.inNormal
}
OutputMappings {
}
}
ShaderNode PerspectiveDivide {
Definition: PerspectiveDivide: Common/MatDefs/ShaderNodes/Misc/PerspectiveDivide.j3sn
InputMappings {
inVec = TransformPosition.outPosition
}
OutputMappings {
}
}
ShaderNode CommonVert {
Definition: CommonVert: Common/MatDefs/ShaderNodes/Common/CommonVert.j3sn
InputMappings {
worldViewProjectionMatrix = WorldParam.WorldViewProjectionMatrix
modelPosition = Attr.inPosition
vertColor = Attr.inColor
texCoord1 = Attr.inTexCoord
}
OutputMappings {
Global.position = projPosition
}
}
}
FragmentShaderNodes {
ShaderNode Dashed {
Definition: Dashed: Common/MatDefs/ShaderNodes/Misc/Dashed.j3sn
InputMappings {
texCoord = CommonVert.texCoord1
inColor = CommonVert.vertColor
resolution = WorldParam.Resolution
startPos = PerspectiveDivide.outVec
}
OutputMappings {
Global.color = outColor
}
}
}
}
}

@ -20,6 +20,11 @@ MaterialDef Unshaded {
Int NumberOfBones Int NumberOfBones
Matrix4Array BoneMatrices Matrix4Array BoneMatrices
// For Morph animation
FloatArray MorphWeights
Int NumberOfMorphTargets
Int NumberOfTargetsBuffers
// Alpha threshold for fragment discarding // Alpha threshold for fragment discarding
Float AlphaDiscardThreshold (AlphaTestFallOff) Float AlphaDiscardThreshold (AlphaTestFallOff)
@ -76,26 +81,30 @@ MaterialDef Unshaded {
HAS_COLOR : Color HAS_COLOR : Color
NUM_BONES : NumberOfBones NUM_BONES : NumberOfBones
DISCARD_ALPHA : AlphaDiscardThreshold DISCARD_ALPHA : AlphaDiscardThreshold
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
} }
} }
Technique PreNormalPass { Technique PreNormalPass {
VertexShader GLSL100 GLSL150 : Common/MatDefs/SSAO/normal.vert VertexShader GLSL100 GLSL150 : Common/MatDefs/SSAO/normal.vert
FragmentShader GLSL100 GLSL150 : Common/MatDefs/SSAO/normal.frag FragmentShader GLSL100 GLSL150 : Common/MatDefs/SSAO/normal.frag
WorldParameters { WorldParameters {
WorldViewProjectionMatrix WorldViewProjectionMatrix
WorldViewMatrix WorldViewMatrix
NormalMatrix NormalMatrix
ViewProjectionMatrix ViewProjectionMatrix
ViewMatrix ViewMatrix
} }
Defines { Defines {
NUM_BONES : NumberOfBones NUM_BONES : NumberOfBones
INSTANCING : UseInstancing INSTANCING : UseInstancing
} NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
}
} }
Technique PreShadow { Technique PreShadow {
@ -115,6 +124,8 @@ MaterialDef Unshaded {
DISCARD_ALPHA : AlphaDiscardThreshold DISCARD_ALPHA : AlphaDiscardThreshold
NUM_BONES : NumberOfBones NUM_BONES : NumberOfBones
INSTANCING : UseInstancing INSTANCING : UseInstancing
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
} }
ForcedRenderState { ForcedRenderState {
@ -150,8 +161,10 @@ MaterialDef Unshaded {
PSSM : Splits PSSM : Splits
POINTLIGHT : LightViewProjectionMatrix5 POINTLIGHT : LightViewProjectionMatrix5
NUM_BONES : NumberOfBones NUM_BONES : NumberOfBones
INSTANCING : UseInstancing INSTANCING : UseInstancing
BACKFACE_SHADOWS: BackfaceShadows BACKFACE_SHADOWS: BackfaceShadows
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
} }
ForcedRenderState { ForcedRenderState {
@ -177,8 +190,10 @@ MaterialDef Unshaded {
HAS_GLOWMAP : GlowMap HAS_GLOWMAP : GlowMap
HAS_GLOWCOLOR : GlowColor HAS_GLOWCOLOR : GlowColor
NUM_BONES : NumberOfBones NUM_BONES : NumberOfBones
INSTANCING : UseInstancing INSTANCING : UseInstancing
HAS_POINTSIZE : PointSize HAS_POINTSIZE : PointSize
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
} }
} }
} }

@ -1,6 +1,7 @@
#import "Common/ShaderLib/GLSLCompat.glsllib" #import "Common/ShaderLib/GLSLCompat.glsllib"
#import "Common/ShaderLib/Skinning.glsllib" #import "Common/ShaderLib/Skinning.glsllib"
#import "Common/ShaderLib/Instancing.glsllib" #import "Common/ShaderLib/Instancing.glsllib"
#import "Common/ShaderLib/MorphAnim.glsllib"
attribute vec3 inPosition; attribute vec3 inPosition;
@ -38,6 +39,11 @@ void main(){
#endif #endif
vec4 modelSpacePos = vec4(inPosition, 1.0); vec4 modelSpacePos = vec4(inPosition, 1.0);
#ifdef NUM_MORPH_TARGETS
Morph_Compute(modelSpacePos);
#endif
#ifdef NUM_BONES #ifdef NUM_BONES
Skinning_Compute(modelSpacePos); Skinning_Compute(modelSpacePos);
#endif #endif

@ -0,0 +1,49 @@
MaterialDef FakeLighting {
MaterialParameters {
Vector4 Color
}
Technique {
WorldParameters {
WorldViewProjectionMatrix
NormalMatrix
}
VertexShaderNodes {
ShaderNode Mat3Vec3Mult {
Definition: Mat3Vec3Mult: Common/MatDefs/ShaderNodes/Basic/Mat3Vec3Mult.j3sn
InputMappings {
matrix3 = WorldParam.NormalMatrix
vector3 = Attr.inNormal
}
OutputMappings {
}
}
ShaderNode CommonVert {
Definition: CommonVert: Common/MatDefs/ShaderNodes/Common/CommonVert.j3sn
InputMappings {
worldViewProjectionMatrix = WorldParam.WorldViewProjectionMatrix
modelPosition = Attr.inPosition
}
OutputMappings {
Global.position = projPosition
}
}
}
FragmentShaderNodes {
ShaderNode FakeLighting {
Definition: FakeLighting: Common/MatDefs/ShaderNodes/Misc/fakeLighting.j3sn
InputMappings {
inColor = MatParam.Color
normal = Mat3Vec3Mult.outVector3.xyz
}
OutputMappings {
Global.color = outColor
}
}
}
}
}

@ -0,0 +1,31 @@
ShaderNodeDefinitions{
ShaderNodeDefinition Mat3Vec3Mult {
//Vertex/Fragment
Type: Vertex
//Shader GLSL<version>: <Path to shader>
Shader GLSL100: Common/MatDefs/ShaderNodes/Basic/Mat3Vec3Mult100.frag
Documentation{
//type documentation here. This is optional but recommended
//@input <glsltype> <varName> <description>
@input mat3 matrix3 the mat3
@input vec3 vector3 the vec3
//@output <glslType> <varName> <description>
@output vec3 outVector3 the output vector
}
Input {
//all the node inputs
//<glslType> <varName>
mat3 matrix3
vec3 vector3
}
Output {
//all the node outputs
//<glslType> <varName>
vec3 outVector3
}
}
}

@ -0,0 +1,35 @@
ShaderNodeDefinitions{
ShaderNodeDefinition Billboard {
//Vertex/Fragment
Type: Vertex
//Shader GLSL<version>: <Path to shader>
Shader GLSL100: Common/MatDefs/ShaderNodes/Common/Billboard100.frag
Documentation{
//type documentation here. This is optional but recommended
//@input <glsltype> <varName> <description>
@input mat4 worldViewMatrix The worldView matrix
@input mat4 projectionMatrix The projection matrix
@input vec3 modelPosition the vertex position
@input float scale the scale of the billboard (defautl 1)
//@output <glslType> <varName> <description>
@output vec4 projPosition The position in projection space
}
Input {
//all the node inputs
//<glslType> <varName>
mat4 worldViewMatrix
mat4 projectionMatrix
vec3 modelPosition
float scale 1
}
Output {
//all the node outputs
//<glslType> <varName>
vec4 projPosition
}
}
}

@ -0,0 +1,19 @@
void main(){
// First colunm.
worldViewMatrix[0][0] = scale;
worldViewMatrix[0][1] = 0.0;
worldViewMatrix[0][2] = 0.0;
// Second colunm.
worldViewMatrix[1][0] = 0.0;
worldViewMatrix[1][1] = scale;
worldViewMatrix[1][2] = 0.0;
// Thrid colunm.
worldViewMatrix[2][0] = 0.0;
worldViewMatrix[2][1] = 0.0;
worldViewMatrix[2][2] = scale;
vec4 position = worldViewMatrix * vec4(modelPosition,1.0);
projPosition = projectionMatrix * position;
}

@ -0,0 +1,41 @@
ShaderNodeDefinitions{
ShaderNodeDefinition FixedScale {
//Vertex/Fragment
Type: Vertex
//Shader GLSL<version>: <Path to shader>
Shader GLSL100: Common/MatDefs/ShaderNodes/Common/FixedScale100.frag
Documentation{
//type documentation here. This is optional but recommended
//@input <glsltype> <varName> <description>
@input vec4 viewport The viewport information (right, top, left, bottom)
@input vec3 cameraDir The direction of the camera
@input vec3 cameraPos The position of the camera
@input mat4 worldMatrix The world matrix
@input mat4 projectionMatrix The projection matrix
@input vec3 modelPosition the vertex position
@input float spriteHeight the desired image height in pixel
//@output <glslType> <varName> <description>
@output float scale The constant scale
}
Input {
//all the node inputs
//<glslType> <varName>
vec4 viewport
vec3 cameraDir
vec3 cameraPos
mat4 worldMatrix
mat4 projectionMatrix
vec3 modelPosition
float spriteHeight 10.0
}
Output {
//all the node outputs
//<glslType> <varName>
float scale
}
}
}

@ -0,0 +1,8 @@
void main(){
vec4 worldPos = worldMatrix * vec4(0.0, 0.0, 0.0, 1.0);
vec3 dir = worldPos.xyz - cameraPos;
float distance = dot(cameraDir, dir);
float m11 = projectionMatrix[1][1];
float halfHeight = (viewport.w - viewport.y) * 0.5;
scale = ((distance/halfHeight) * spriteHeight)/m11;
}

@ -0,0 +1,57 @@
ShaderNodeDefinitions{
ShaderNodeDefinition TexCoord {
//Vertex/Fragment
Type: Vertex
//Shader GLSL<version>: <Path to shader>
Shader GLSL100: Common/MatDefs/ShaderNodes/Common/texCoord100.frag
Documentation{
//type documentation here. This is optional but recommended
//@input <glsltype> <varName> <description>
@input vec2 texCoord The input texture Coord
@input vec2 texCoord2 The input texture Coord
@input vec2 texCoord3 The input texture Coord
@input vec2 texCoord4 The input texture Coord
@input vec2 texCoord5 The input texture Coord
@input vec2 texCoord6 The input texture Coord
@input vec2 texCoord7 The input texture Coord
@input vec2 texCoord8 The input texture Coord
//@output <glslType> <varName> <description>
@output vec2 texCoord The input texture Coord
@output vec2 texCoord2 The input texture Coord
@output vec2 texCoord3 The input texture Coord
@output vec2 texCoord4 The input texture Coord
@output vec2 texCoord5 The input texture Coord
@output vec2 texCoord6 The input texture Coord
@output vec2 texCoord7 The input texture Coord
@output vec2 texCoord8 The input texture Coord
}
Input {
//all the node inputs
//<glslType> <varName>
vec2 texCoord
vec2 texCoord2
vec2 texCoord3
vec2 texCoord4
vec2 texCoord5
vec2 texCoord6
vec2 texCoord7
vec2 texCoord8
}
Output {
//all the node outputs
//<glslType> <varName>
vec2 texCoord
vec2 texCoord2
vec2 texCoord3
vec2 texCoord4
vec2 texCoord5
vec2 texCoord6
vec2 texCoord7
vec2 texCoord8
}
}
}

@ -0,0 +1,37 @@
ShaderNodeDefinitions{
ShaderNodeDefinition Dashed {
//Vertex/Fragment
Type: Fragment
//Shader GLSL<version>: <Path to shader>
Shader GLSL100: Common/MatDefs/ShaderNodes/Misc/Dashed100.frag
Documentation{
//type documentation here. This is optional but recommended
Output a dashed line (better have a LINE mode mesh)
//@input <glsltype> <varName> <description>
@input vec2 texCoord the texture coordinates
@input vec4 inColor The input color
//@output <glslType> <varName> <description>
@output vec4 outColor The modified output color
}
Input {
//all the node inputs
//<glslType> <varName>
vec2 texCoord
vec4 inColor
vec4 startPos
vec2 resolution
}
Output {
//all the node outputs
//<glslType> <varName>
vec4 outColor
}
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save