Compare commits
30 Commits
Author | SHA1 | Date |
---|---|---|
Nehon | e33862a632 | 7 years ago |
Nehon | 1cdd37786e | 7 years ago |
Nehon | 53f7f7490f | 7 years ago |
Nehon | bcd97c22eb | 7 years ago |
Nehon | c2fcdefd0d | 7 years ago |
Rémy Bouquet | 346173e2be | 7 years ago |
Rémy Bouquet | 909716fa03 | 7 years ago |
Rémy Bouquet | 84276ce099 | 7 years ago |
Nehon | 34114f5a82 | 7 years ago |
Nehon | 6ccacd257e | 7 years ago |
Rémy Bouquet | 9dc87b71e9 | 7 years ago |
Rémy Bouquet | ce88350abf | 7 years ago |
Rémy Bouquet | a8845a1506 | 7 years ago |
Rémy Bouquet | 449429974e | 7 years ago |
Nehon | dd5b90e281 | 7 years ago |
Nehon | 75a6e86e56 | 7 years ago |
Nehon | e99a093b5a | 7 years ago |
Nehon | f1a3593070 | 7 years ago |
Nehon | 8d39caec6a | 7 years ago |
Nehon | 8cf0f1a4d5 | 7 years ago |
Nehon | 1f3c0e4c84 | 7 years ago |
Nehon | a1a9486424 | 7 years ago |
Nehon | a4267393e1 | 7 years ago |
Nehon | 74f9a648d1 | 7 years ago |
Nehon | c630ac2e59 | 7 years ago |
Nehon | 56065d0d70 | 7 years ago |
Nehon | 0e3ab8dd14 | 7 years ago |
Nehon | 4040a1e412 | 7 years ago |
Nehon | 3eb890da38 | 7 years ago |
Nehon | 6b69da3480 | 7 years ago |
@ -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 (≥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); |
||||||
|
} |
@ -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]; |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
} |
@ -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 |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
} |
@ -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,3 @@ |
|||||||
|
void main(){ |
||||||
|
outVector3 = matrix3 * vector3; |
||||||
|
} |
@ -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,2 @@ |
|||||||
|
void main(){ |
||||||
|
} |
@ -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…
Reference in new issue