• R/O
  • HTTP
  • SSH
  • HTTPS

Commit

Tags
No Tags

Frequently used words (click to add to your profile)

javac++androidlinuxc#windowsobjective-ccocoa誰得qtpythonphprubygameguibathyscaphec計画中(planning stage)翻訳omegatframeworktwitterdomtestvb.netdirectxゲームエンジンbtronarduinopreviewer

Main repository of MikuMikuStudio


Commit MetaInfo

Revisionccbbcb888cf9386b780fe82f788ca3b68bb3cc69 (tree)
Time2013-07-07 03:10:38
Authorkobayasi <kobayasi@pscn...>
Commiterkobayasi

Log Message

copy from jme10694

Change Summary

Incremental Difference

--- a/engine/src/core/com/jme3/animation/AnimChannel.java
+++ b/engine/src/core/com/jme3/animation/AnimChannel.java
@@ -1,5 +1,5 @@
11 /*
2- * Copyright (c) 2009-2010 jMonkeyEngine
2+ * Copyright (c) 2009-2012 jMonkeyEngine
33 * All rights reserved.
44 *
55 * Redistribution and use in source and binary forms, with or without
@@ -29,7 +29,6 @@
2929 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
3030 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3131 */
32-
3332 package com.jme3.animation;
3433
3534 import com.jme3.math.FastMath;
@@ -61,38 +60,69 @@ public final class AnimChannel {
6160 private float speed;
6261 private float timeBlendFrom;
6362 private float speedBlendFrom;
63+ private boolean notified=false;
6464
6565 private LoopMode loopMode, loopModeBlendFrom;
6666
6767 private float blendAmount = 1f;
6868 private float blendRate = 0;
69-
69+
7070 private static float clampWrapTime(float t, float max, LoopMode loopMode){
71- if (max == Float.POSITIVE_INFINITY)
72- return t;
71+ if (t == 0) {
72+ return 0; // prevent division by 0 errors
73+ }
7374
74- if (t < 0f){
75- //float tMod = -(-t % max);
76- switch (loopMode){
77- case DontLoop:
78- return 0;
79- case Cycle:
80- return t;
81- case Loop:
82- return max - t;
83- }
84- }else if (t > max){
85- switch (loopMode){
86- case DontLoop:
87- return max;
88- case Cycle:
89- return /*-max;*/-(2f * max - t);
90- case Loop:
91- return t - max;
92- }
75+ switch (loopMode) {
76+ case Cycle:
77+ boolean sign = ((int) (t / max) % 2) != 0;
78+ float result;
79+
80+// if (t < 0){
81+// result = sign ? t % max : -(max + (t % max));
82+// } else {
83+ // NOTE: This algorithm seems stable for both high and low
84+ // tpf so for now its a keeper.
85+ result = sign ? -(max - (t % max)) : t % max;
86+// }
87+
88+// if (result <= 0 || result >= max) {
89+// System.out.println("SIGN: " + sign + ", RESULT: " + result + ", T: " + t + ", M: " + max);
90+// }
91+
92+ return result;
93+ case DontLoop:
94+ return t > max ? max : (t < 0 ? 0 : t);
95+ case Loop:
96+ return t % max;
9397 }
94-
9598 return t;
99+
100+
101+// if (max == Float.POSITIVE_INFINITY)
102+// return t;
103+//
104+// if (t < 0f){
105+// //float tMod = -(-t % max);
106+// switch (loopMode){
107+// case DontLoop:
108+// return 0;
109+// case Cycle:
110+// return t;
111+// case Loop:
112+// return max - t;
113+// }
114+// }else if (t > max){
115+// switch (loopMode){
116+// case DontLoop:
117+// return max;
118+// case Cycle:
119+// return -(2f * max - t) % max;
120+// case Loop:
121+// return t % max;
122+// }
123+// }
124+//
125+// return t;
96126 }
97127
98128 AnimChannel(AnimControl control){
@@ -206,7 +236,7 @@ public final class AnimChannel {
206236 */
207237 public void setAnim(String name, float blendTime){
208238 if (name == null)
209- throw new NullPointerException();
239+ throw new IllegalArgumentException("name cannot be null");
210240
211241 if (blendTime < 0f)
212242 throw new IllegalArgumentException("blendTime cannot be less than zero");
@@ -225,12 +255,15 @@ public final class AnimChannel {
225255 loopModeBlendFrom = loopMode;
226256 blendAmount = 0f;
227257 blendRate = 1f / blendTime;
258+ }else{
259+ blendFrom = null;
228260 }
229261
230262 animation = anim;
231263 time = 0;
232264 speed = 1f;
233265 loopMode = LoopMode.Loop;
266+ notified = false;
234267 }
235268
236269 /**
@@ -316,14 +349,33 @@ public final class AnimChannel {
316349 BitSet getAffectedBones(){
317350 return affectedBones;
318351 }
352+
353+ public void reset(boolean rewind){
354+ if(rewind){
355+ setTime(0);
356+ if(control.getSkeleton()!=null){
357+ control.getSkeleton().resetAndUpdate();
358+ }else{
359+ TempVars vars = TempVars.get();
360+ update(0, vars);
361+ vars.release();
362+ }
363+ }
364+ animation = null;
365+ // System.out.println("Setting notified false");
366+ notified = false;
367+ }
319368
320369 void update(float tpf, TempVars vars) {
321370 if (animation == null)
322371 return;
323372
324- if (blendFrom != null){
373+ if (blendFrom != null && blendAmount != 1.0f){
374+ // The blendFrom anim is set, the actual animation
375+ // playing will be set
376+// blendFrom.setTime(timeBlendFrom, 1f, control, this, vars);
325377 blendFrom.setTime(timeBlendFrom, 1f - blendAmount, control, this, vars);
326- //blendFrom.setTime(timeBlendFrom, control.skeleton, 1f - blendAmount, affectedBones);
378+
327379 timeBlendFrom += tpf * speedBlendFrom;
328380 timeBlendFrom = clampWrapTime(timeBlendFrom,
329381 blendFrom.getLength(),
@@ -339,25 +391,28 @@ public final class AnimChannel {
339391 blendFrom = null;
340392 }
341393 }
342-
394+
343395 animation.setTime(time, blendAmount, control, this, vars);
344- //animation.setTime(time, control.skeleton, blendAmount, affectedBones);
345396 time += tpf * speed;
346397
347398 if (animation.getLength() > 0){
348- if (time >= animation.getLength()) {
349- control.notifyAnimCycleDone(this, animation.getName());
350- } else if (time < 0) {
399+ if (!notified && (time >= animation.getLength() || time < 0)) {
400+ if (loopMode == LoopMode.DontLoop) {
401+ // Note that this flag has to be set before calling the notify
402+ // since the notify may start a new animation and then unset
403+ // the flag.
404+ notified = true;
405+ }
351406 control.notifyAnimCycleDone(this, animation.getName());
352407 }
353408 }
354409
355410 time = clampWrapTime(time, animation.getLength(), loopMode);
356411 if (time < 0){
412+ // Negative time indicates that speed should be inverted
413+ // (for cycle loop mode only)
357414 time = -time;
358415 speed = -speed;
359416 }
360-
361-
362417 }
363418 }
--- a/engine/src/core/com/jme3/animation/AnimControl.java
+++ b/engine/src/core/com/jme3/animation/AnimControl.java
@@ -1,5 +1,5 @@
11 /*
2- * Copyright (c) 2009-2010 jMonkeyEngine
2+ * Copyright (c) 2009-2012 jMonkeyEngine
33 * All rights reserved.
44 *
55 * Redistribution and use in source and binary forms, with or without
@@ -31,11 +31,7 @@
3131 */
3232 package com.jme3.animation;
3333
34-import com.jme3.export.JmeExporter;
35-import com.jme3.export.JmeImporter;
36-import com.jme3.export.InputCapsule;
37-import com.jme3.export.OutputCapsule;
38-import com.jme3.export.Savable;
34+import com.jme3.export.*;
3935 import com.jme3.renderer.RenderManager;
4036 import com.jme3.renderer.ViewPort;
4137 import com.jme3.scene.Mesh;
@@ -47,6 +43,7 @@ import java.io.IOException;
4743 import java.util.ArrayList;
4844 import java.util.Collection;
4945 import java.util.HashMap;
46+import java.util.Map.Entry;
5047
5148 /**
5249 * <code>AnimControl</code> is a Spatial control that allows manipulation
@@ -74,21 +71,17 @@ public final class AnimControl extends AbstractControl implements Cloneable {
7471 * Skeleton object must contain corresponding data for the targets' weight buffers.
7572 */
7673 Skeleton skeleton;
77-
7874 /** only used for backward compatibility */
7975 @Deprecated
8076 private SkeletonControl skeletonControl;
81-
8277 /**
8378 * List of animations
8479 */
85- HashMap<String, Animation> animationMap;
86-
80+ HashMap<String, Animation> animationMap = new HashMap<String, Animation>();
8781 /**
8882 * Animation channels
8983 */
9084 private transient ArrayList<AnimChannel> channels = new ArrayList<AnimChannel>();
91-
9285 /**
9386 * Animation event listeners
9487 */
@@ -120,13 +113,16 @@ public final class AnimControl extends AbstractControl implements Cloneable {
120113 AnimControl clone = (AnimControl) super.clone();
121114 clone.spatial = spatial;
122115 clone.channels = new ArrayList<AnimChannel>();
123-
124- if (skeleton != null){
116+ clone.listeners = new ArrayList<AnimEventListener>();
117+
118+ if (skeleton != null) {
125119 clone.skeleton = new Skeleton(skeleton);
126120 }
127-
128- // animationMap is reference-copied, animation data should be shared
129- // to reduce memory usage.
121+
122+ // animationMap is cloned, but only ClonableTracks will be cloned as they need a reference to a cloned spatial
123+ for (Entry<String, Animation> animEntry : animationMap.entrySet()) {
124+ clone.animationMap.put(animEntry.getKey(), animEntry.getValue().cloneForSpatial(spatial));
125+ }
130126
131127 return clone;
132128 } catch (CloneNotSupportedException ex) {
@@ -215,6 +211,11 @@ public final class AnimControl extends AbstractControl implements Cloneable {
215211 * @see AnimControl#createChannel()
216212 */
217213 public void clearChannels() {
214+ for (AnimChannel animChannel : channels) {
215+ for (AnimEventListener list : listeners) {
216+ list.onAnimCycleDone(this, animChannel, animChannel.getAnimationName());
217+ }
218+ }
218219 channels.clear();
219220 }
220221
@@ -276,7 +277,7 @@ public final class AnimControl extends AbstractControl implements Cloneable {
276277 */
277278 @Override
278279 public void setSpatial(Spatial spatial) {
279- if (spatial == null && skeletonControl != null){
280+ if (spatial == null && skeletonControl != null) {
280281 this.spatial.removeControl(skeletonControl);
281282 }
282283
@@ -316,13 +317,13 @@ public final class AnimControl extends AbstractControl implements Cloneable {
316317
317318 return a.getLength();
318319 }
319-
320+
320321 /**
321322 * Internal use only.
322323 */
323324 @Override
324325 protected void controlUpdate(float tpf) {
325- if (skeleton != null){
326+ if (skeleton != null) {
326327 skeleton.reset(); // reset skeleton to bind pose
327328 }
328329
@@ -332,7 +333,7 @@ public final class AnimControl extends AbstractControl implements Cloneable {
332333 }
333334 vars.release();
334335
335- if (skeleton != null){
336+ if (skeleton != null) {
336337 skeleton.updateWorldVectors();
337338 }
338339 }
@@ -357,12 +358,15 @@ public final class AnimControl extends AbstractControl implements Cloneable {
357358 super.read(im);
358359 InputCapsule in = im.getCapsule(this);
359360 skeleton = (Skeleton) in.readSavable("skeleton", null);
360- animationMap = (HashMap<String, Animation>) in.readStringSavableMap("animations", null);
361+ HashMap<String, Animation> loadedAnimationMap = (HashMap<String, Animation>) in.readStringSavableMap("animations", null);
362+ if (loadedAnimationMap != null) {
363+ animationMap = loadedAnimationMap;
364+ }
361365
362- if (im.getFormatVersion() == 0){
366+ if (im.getFormatVersion() == 0) {
363367 // Changed for backward compatibility with j3o files generated
364368 // before the AnimControl/SkeletonControl split.
365-
369+
366370 // If we find a target mesh array the AnimControl creates the
367371 // SkeletonControl for old files and add it to the spatial.
368372 // When backward compatibility won't be needed anymore this can deleted
--- a/engine/src/core/com/jme3/animation/AnimEventListener.java
+++ b/engine/src/core/com/jme3/animation/AnimEventListener.java
@@ -1,5 +1,5 @@
11 /*
2- * Copyright (c) 2009-2010 jMonkeyEngine
2+ * Copyright (c) 2009-2012 jMonkeyEngine
33 * All rights reserved.
44 *
55 * Redistribution and use in source and binary forms, with or without
@@ -29,7 +29,6 @@
2929 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
3030 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3131 */
32-
3332 package com.jme3.animation;
3433
3534 /**
--- a/engine/src/core/com/jme3/animation/Animation.java
+++ b/engine/src/core/com/jme3/animation/Animation.java
@@ -1,5 +1,5 @@
11 /*
2- * Copyright (c) 2009-2011 jMonkeyEngine
2+ * Copyright (c) 2009-2012 jMonkeyEngine
33 * All rights reserved.
44 *
55 * Redistribution and use in source and binary forms, with or without
@@ -31,14 +31,11 @@
3131 */
3232 package com.jme3.animation;
3333
34-import java.io.IOException;
35-
36-import com.jme3.export.InputCapsule;
37-import com.jme3.export.JmeExporter;
38-import com.jme3.export.JmeImporter;
39-import com.jme3.export.OutputCapsule;
40-import com.jme3.export.Savable;
34+import com.jme3.export.*;
35+import com.jme3.scene.Spatial;
36+import com.jme3.util.SafeArrayList;
4137 import com.jme3.util.TempVars;
38+import java.io.IOException;
4239
4340 /**
4441 * The animation class updates the animation target with the tracks of a given type.
@@ -46,27 +43,26 @@ import com.jme3.util.TempVars;
4643 * @author Kirill Vainer, Marcin Roguski (Kaelthas)
4744 */
4845 public class Animation implements Savable, Cloneable {
49-
46+
5047 /**
5148 * The name of the animation.
5249 */
5350 private String name;
54-
5551 /**
5652 * The length of the animation.
5753 */
5854 private float length;
59-
6055 /**
6156 * The tracks of the animation.
6257 */
63- private Track[] tracks;
64-
58+ private SafeArrayList<Track> tracks = new SafeArrayList<Track>(Track.class);
59+
6560 /**
6661 * Serialization-only. Do not use.
6762 */
68- public Animation() {}
69-
63+ public Animation() {
64+ }
65+
7066 /**
7167 * Creates a new <code>Animation</code> with the given name and length.
7268 *
@@ -77,24 +73,24 @@ public class Animation implements Savable, Cloneable {
7773 this.name = name;
7874 this.length = length;
7975 }
80-
76+
8177 /**
8278 * The name of the bone animation
8379 * @return name of the bone animation
8480 */
8581 public String getName() {
86- return name;
82+ return name;
8783 }
88-
84+
8985 /**
9086 * Returns the length in seconds of this animation
9187 *
9288 * @return the length in seconds of this animation
9389 */
9490 public float getLength() {
95- return length;
91+ return length;
9692 }
97-
93+
9894 /**
9995 * This method sets the current time of the animation.
10096 * This method behaves differently for every known track type.
@@ -106,77 +102,87 @@ public class Animation implements Savable, Cloneable {
106102 * @param channel the animation channel
107103 */
108104 void setTime(float time, float blendAmount, AnimControl control, AnimChannel channel, TempVars vars) {
109- if (tracks == null)
105+ if (tracks == null) {
110106 return;
111-
112- for (int i = 0; i < tracks.length; i++){
113- tracks[i].setTime(time, blendAmount, control, channel, vars);
114107 }
115-
116- /*
117- if (tracks != null && tracks.length > 0) {
118- Track<?> trackInstance = tracks[0];
119-
120- if (trackInstance instanceof SpatialTrack) {
121- Spatial spatial = control.getSpatial();
122- if (spatial != null) {
123- ((SpatialTrack) tracks[0]).setTime(time, spatial, blendAmount);
124- }
125- } else if (trackInstance instanceof BoneTrack) {
126- BitSet affectedBones = channel.getAffectedBones();
127- Skeleton skeleton = control.getSkeleton();
128- for (int i = 0; i < tracks.length; ++i) {
129- if (affectedBones == null || affectedBones.get(((BoneTrack) tracks[i]).getTargetIndex())) {
130- ((BoneTrack) tracks[i]).setTime(time, skeleton, blendAmount);
131- }
132- }
133- } else if (trackInstance instanceof PoseTrack) {
134- Spatial spatial = control.getSpatial();
135- List<Mesh> meshes = new ArrayList<Mesh>();
136- this.getMeshes(spatial, meshes);
137- if (meshes.size() > 0) {
138- Mesh[] targets = meshes.toArray(new Mesh[meshes.size()]);
139- for (int i = 0; i < tracks.length; ++i) {
140- ((PoseTrack) tracks[i]).setTime(time, targets, blendAmount);
141- }
142- }
143- }
108+
109+ for (Track track : tracks) {
110+ track.setTime(time, blendAmount, control, channel, vars);
144111 }
145- */
146112 }
147-
113+
148114 /**
149115 * Set the {@link Track}s to be used by this animation.
150- * <p>
151- * The array should be organized so that the appropriate Track can
152- * be retrieved based on a bone index.
153116 *
154- * @param tracks The tracks to set.
117+ * @param tracksArray The tracks to set.
118+ */
119+ public void setTracks(Track[] tracksArray) {
120+ for (Track track : tracksArray) {
121+ tracks.add(track);
122+ }
123+ }
124+
125+ /**
126+ * Adds a track to this animation
127+ * @param track the track to add
155128 */
156- public void setTracks(Track[] tracks){
157- this.tracks = tracks;
129+ public void addTrack(Track track) {
130+ tracks.add(track);
158131 }
159-
132+
133+ /**
134+ * removes a track from this animation
135+ * @param track the track to remove
136+ */
137+ public void removeTrack(Track track) {
138+ tracks.remove(track);
139+ if (track instanceof ClonableTrack) {
140+ ((ClonableTrack) track).cleanUp();
141+ }
142+ }
143+
160144 /**
161145 * Returns the tracks set in {@link #setTracks(com.jme3.animation.Track[]) }.
162146 *
163147 * @return the tracks set previously
164148 */
165149 public Track[] getTracks() {
166- return tracks;
150+ return tracks.getArray();
167151 }
168-
152+
169153 /**
170154 * This method creates a clone of the current object.
171155 * @return a clone of the current object
172156 */
173- @Override
174- public Animation clone() {
157+ @Override
158+ public Animation clone() {
175159 try {
176160 Animation result = (Animation) super.clone();
177- result.tracks = tracks.clone();
178- for (int i = 0; i < tracks.length; ++i) {
179- result.tracks[i] = this.tracks[i].clone();
161+ result.tracks = new SafeArrayList<Track>(Track.class);
162+ for (Track track : tracks) {
163+ result.tracks.add(track.clone());
164+ }
165+ return result;
166+ } catch (CloneNotSupportedException e) {
167+ throw new AssertionError();
168+ }
169+ }
170+
171+ /**
172+ *
173+ * @param spat
174+ * @return
175+ */
176+ public Animation cloneForSpatial(Spatial spat) {
177+ try {
178+ Animation result = (Animation) super.clone();
179+ result.tracks = new SafeArrayList<Track>(Track.class);
180+ for (Track track : tracks) {
181+ if (track instanceof ClonableTrack) {
182+ result.tracks.add(((ClonableTrack) track).cloneForSpatial(spat));
183+ } else {
184+ result.tracks.add(track);
185+ }
180186 }
181187 return result;
182188 } catch (CloneNotSupportedException e) {
@@ -188,13 +194,13 @@ public class Animation implements Savable, Cloneable {
188194 public String toString() {
189195 return getClass().getSimpleName() + "[name=" + name + ", length=" + length + ']';
190196 }
191-
192- @Override
197+
198+ @Override
193199 public void write(JmeExporter ex) throws IOException {
194200 OutputCapsule out = ex.getCapsule(this);
195201 out.write(name, "name", null);
196202 out.write(length, "length", 0f);
197- out.write(tracks, "tracks", null);
203+ out.write(tracks.getArray(), "tracks", null);
198204 }
199205
200206 @Override
@@ -202,9 +208,17 @@ public class Animation implements Savable, Cloneable {
202208 InputCapsule in = im.getCapsule(this);
203209 name = in.readString("name", null);
204210 length = in.readFloat("length", 0f);
205-
211+
206212 Savable[] arr = in.readSavableArray("tracks", null);
207- tracks = new Track[arr.length];
208- System.arraycopy(arr, 0, tracks, 0, arr.length);
213+ if (arr != null) {
214+ // NOTE: Backward compat only .. Some animations have no
215+ // tracks set at all even though it makes no sense.
216+ // Since there's a null check in setTime(),
217+ // its only appropriate that the check is made here as well.
218+ tracks = new SafeArrayList<Track>(Track.class);
219+ for (Savable savable : arr) {
220+ tracks.add((Track) savable);
221+ }
222+ }
209223 }
210224 }
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/AnimationFactory.java
@@ -0,0 +1,496 @@
1+/*
2+ * Copyright (c) 2009-2012 jMonkeyEngine
3+ * All rights reserved.
4+ *
5+ * Redistribution and use in source and binary forms, with or without
6+ * modification, are permitted provided that the following conditions are
7+ * met:
8+ *
9+ * * Redistributions of source code must retain the above copyright
10+ * notice, this list of conditions and the following disclaimer.
11+ *
12+ * * Redistributions in binary form must reproduce the above copyright
13+ * notice, this list of conditions and the following disclaimer in the
14+ * documentation and/or other materials provided with the distribution.
15+ *
16+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17+ * may be used to endorse or promote products derived from this software
18+ * without specific prior written permission.
19+ *
20+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+ */
32+package com.jme3.animation;
33+
34+import com.jme3.math.FastMath;
35+import com.jme3.math.Quaternion;
36+import com.jme3.math.Transform;
37+import com.jme3.math.Vector3f;
38+
39+/**
40+ * A convenience class to easily setup a spatial keyframed animation
41+ * you can add some keyFrames for a given time or a given keyFrameIndex, for translation rotation and scale.
42+ * The animationHelper will then generate an appropriate SpatialAnimation by interpolating values between the keyFrames.
43+ * <br><br>
44+ * Usage is : <br>
45+ * - Create the AnimationHelper<br>
46+ * - add some keyFrames<br>
47+ * - call the buildAnimation() method that will retruna new Animation<br>
48+ * - add the generated Animation to any existing AnimationControl<br>
49+ * <br><br>
50+ * Note that the first keyFrame (index 0) is defaulted with the identy transforms.
51+ * If you want to change that you have to replace this keyFrame with any transform you want.
52+ *
53+ * @author Nehon
54+ */
55+public class AnimationFactory {
56+
57+ /**
58+ * step for splitting rotation that have a n ange above PI/2
59+ */
60+ private final static float EULER_STEP = FastMath.QUARTER_PI * 3;
61+
62+ /**
63+ * enum to determine the type of interpolation
64+ */
65+ private enum Type {
66+
67+ Translation, Rotation, Scale;
68+ }
69+
70+ /**
71+ * Inner Rotation type class to kep track on a rotation Euler angle
72+ */
73+ protected class Rotation {
74+
75+ /**
76+ * The rotation Quaternion
77+ */
78+ Quaternion rotation = new Quaternion();
79+ /**
80+ * This rotation expressed in Euler angles
81+ */
82+ Vector3f eulerAngles = new Vector3f();
83+ /**
84+ * the index of the parent key frame is this keyFrame is a splitted rotation
85+ */
86+ int masterKeyFrame = -1;
87+
88+ public Rotation() {
89+ rotation.loadIdentity();
90+ }
91+
92+ void set(Quaternion rot) {
93+ rotation.set(rot);
94+ float[] a = new float[3];
95+ rotation.toAngles(a);
96+ eulerAngles.set(a[0], a[1], a[2]);
97+ }
98+
99+ void set(float x, float y, float z) {
100+ float[] a = {x, y, z};
101+ rotation.fromAngles(a);
102+ eulerAngles.set(x, y, z);
103+ }
104+ }
105+ /**
106+ * Name of the animation
107+ */
108+ protected String name;
109+ /**
110+ * frames per seconds
111+ */
112+ protected int fps;
113+ /**
114+ * Animation duration in seconds
115+ */
116+ protected float duration;
117+ /**
118+ * total number of frames
119+ */
120+ protected int totalFrames;
121+ /**
122+ * time per frame
123+ */
124+ protected float tpf;
125+ /**
126+ * Time array for this animation
127+ */
128+ protected float[] times;
129+ /**
130+ * Translation array for this animation
131+ */
132+ protected Vector3f[] translations;
133+ /**
134+ * rotation array for this animation
135+ */
136+ protected Quaternion[] rotations;
137+ /**
138+ * scales array for this animation
139+ */
140+ protected Vector3f[] scales;
141+ /**
142+ * The map of keyFrames to compute the animation. The key is the index of the frame
143+ */
144+ protected Vector3f[] keyFramesTranslation;
145+ protected Vector3f[] keyFramesScale;
146+ protected Rotation[] keyFramesRotation;
147+
148+ /**
149+ * Creates and AnimationHelper
150+ * @param duration the desired duration for the resulting animation
151+ * @param name the name of the resulting animation
152+ */
153+ public AnimationFactory(float duration, String name) {
154+ this(duration, name, 30);
155+ }
156+
157+ /**
158+ * Creates and AnimationHelper
159+ * @param duration the desired duration for the resulting animation
160+ * @param name the name of the resulting animation
161+ * @param fps the number of frames per second for this animation (default is 30)
162+ */
163+ public AnimationFactory(float duration, String name, int fps) {
164+ this.name = name;
165+ this.duration = duration;
166+ this.fps = fps;
167+ totalFrames = (int) (fps * duration) + 1;
168+ tpf = 1 / (float) fps;
169+ times = new float[totalFrames];
170+ translations = new Vector3f[totalFrames];
171+ rotations = new Quaternion[totalFrames];
172+ scales = new Vector3f[totalFrames];
173+ keyFramesTranslation = new Vector3f[totalFrames];
174+ keyFramesTranslation[0] = new Vector3f();
175+ keyFramesScale = new Vector3f[totalFrames];
176+ keyFramesScale[0] = new Vector3f(1, 1, 1);
177+ keyFramesRotation = new Rotation[totalFrames];
178+ keyFramesRotation[0] = new Rotation();
179+
180+ }
181+
182+ /**
183+ * Adds a key frame for the given Transform at the given time
184+ * @param time the time at which the keyFrame must be inserted
185+ * @param transform the transforms to use for this keyFrame
186+ */
187+ public void addTimeTransform(float time, Transform transform) {
188+ addKeyFrameTransform((int) (time / tpf), transform);
189+ }
190+
191+ /**
192+ * Adds a key frame for the given Transform at the given keyFrame index
193+ * @param keyFrameIndex the index at which the keyFrame must be inserted
194+ * @param transform the transforms to use for this keyFrame
195+ */
196+ public void addKeyFrameTransform(int keyFrameIndex, Transform transform) {
197+ addKeyFrameTranslation(keyFrameIndex, transform.getTranslation());
198+ addKeyFrameScale(keyFrameIndex, transform.getScale());
199+ addKeyFrameRotation(keyFrameIndex, transform.getRotation());
200+ }
201+
202+ /**
203+ * Adds a key frame for the given translation at the given time
204+ * @param time the time at which the keyFrame must be inserted
205+ * @param translation the translation to use for this keyFrame
206+ */
207+ public void addTimeTranslation(float time, Vector3f translation) {
208+ addKeyFrameTranslation((int) (time / tpf), translation);
209+ }
210+
211+ /**
212+ * Adds a key frame for the given translation at the given keyFrame index
213+ * @param keyFrameIndex the index at which the keyFrame must be inserted
214+ * @param translation the translation to use for this keyFrame
215+ */
216+ public void addKeyFrameTranslation(int keyFrameIndex, Vector3f translation) {
217+ Vector3f t = getTranslationForFrame(keyFrameIndex);
218+ t.set(translation);
219+ }
220+
221+ /**
222+ * Adds a key frame for the given rotation at the given time<br>
223+ * This can't be used if the interpolated angle is higher than PI (180°)<br>
224+ * Use {@link #addTimeRotationAngles(float time, float x, float y, float z)} instead that uses Euler angles rotations.<br> *
225+ * @param time the time at which the keyFrame must be inserted
226+ * @param rotation the rotation Quaternion to use for this keyFrame
227+ * @see #addTimeRotationAngles(float time, float x, float y, float z)
228+ */
229+ public void addTimeRotation(float time, Quaternion rotation) {
230+ addKeyFrameRotation((int) (time / tpf), rotation);
231+ }
232+
233+ /**
234+ * Adds a key frame for the given rotation at the given keyFrame index<br>
235+ * This can't be used if the interpolated angle is higher than PI (180°)<br>
236+ * Use {@link #addKeyFrameRotationAngles(int keyFrameIndex, float x, float y, float z)} instead that uses Euler angles rotations.
237+ * @param keyFrameIndex the index at which the keyFrame must be inserted
238+ * @param rotation the rotation Quaternion to use for this keyFrame
239+ * @see #addKeyFrameRotationAngles(int keyFrameIndex, float x, float y, float z)
240+ */
241+ public void addKeyFrameRotation(int keyFrameIndex, Quaternion rotation) {
242+ Rotation r = getRotationForFrame(keyFrameIndex);
243+ r.set(rotation);
244+ }
245+
246+ /**
247+ * Adds a key frame for the given rotation at the given time.<br>
248+ * Rotation is expressed by Euler angles values in radians.<br>
249+ * Note that the generated rotation will be stored as a quaternion and interpolated using a spherical linear interpolation (slerp)<br>
250+ * Hence, this method may create intermediate keyFrames if the interpolation angle is higher than PI to ensure continuity in animation<br>
251+ *
252+ * @param time the time at which the keyFrame must be inserted
253+ * @param x the rotation around the x axis (aka yaw) in radians
254+ * @param y the rotation around the y axis (aka roll) in radians
255+ * @param z the rotation around the z axis (aka pitch) in radians
256+ */
257+ public void addTimeRotationAngles(float time, float x, float y, float z) {
258+ addKeyFrameRotationAngles((int) (time / tpf), x, y, z);
259+ }
260+
261+ /**
262+ * Adds a key frame for the given rotation at the given key frame index.<br>
263+ * Rotation is expressed by Euler angles values in radians.<br>
264+ * Note that the generated rotation will be stored as a quaternion and interpolated using a spherical linear interpolation (slerp)<br>
265+ * Hence, this method may create intermediate keyFrames if the interpolation angle is higher than PI to ensure continuity in animation<br>
266+ *
267+ * @param keyFrameIndex the index at which the keyFrame must be inserted
268+ * @param x the rotation around the x axis (aka yaw) in radians
269+ * @param y the rotation around the y axis (aka roll) in radians
270+ * @param z the rotation around the z axis (aka pitch) in radians
271+ */
272+ public void addKeyFrameRotationAngles(int keyFrameIndex, float x, float y, float z) {
273+ Rotation r = getRotationForFrame(keyFrameIndex);
274+ r.set(x, y, z);
275+
276+ // if the delta of euler angles is higher than PI, we create intermediate keyframes
277+ // since we are using quaternions and slerp for rotation interpolation, we cannot interpolate over an angle higher than PI
278+ int prev = getPreviousKeyFrame(keyFrameIndex, keyFramesRotation);
279+ if (prev != -1) {
280+ //previous rotation keyframe
281+ Rotation prevRot = keyFramesRotation[prev];
282+ //the maximum delta angle (x,y or z)
283+ float delta = Math.max(Math.abs(x - prevRot.eulerAngles.x), Math.abs(y - prevRot.eulerAngles.y));
284+ delta = Math.max(delta, Math.abs(z - prevRot.eulerAngles.z));
285+ //if delta > PI we have to create intermediates key frames
286+ if (delta >= FastMath.PI) {
287+ //frames delta
288+ int dF = keyFrameIndex - prev;
289+ //angle per frame for x,y ,z
290+ float dXAngle = (x - prevRot.eulerAngles.x) / (float) dF;
291+ float dYAngle = (y - prevRot.eulerAngles.y) / (float) dF;
292+ float dZAngle = (z - prevRot.eulerAngles.z) / (float) dF;
293+
294+ // the keyFrame step
295+ int keyStep = (int) (((float) (dF)) / delta * (float) EULER_STEP);
296+ // the current keyFrame
297+ int cursor = prev + keyStep;
298+ while (cursor < keyFrameIndex) {
299+ //for each step we create a new rotation by interpolating the angles
300+ Rotation dr = getRotationForFrame(cursor);
301+ dr.masterKeyFrame = keyFrameIndex;
302+ dr.set(prevRot.eulerAngles.x + cursor * dXAngle, prevRot.eulerAngles.y + cursor * dYAngle, prevRot.eulerAngles.z + cursor * dZAngle);
303+ cursor += keyStep;
304+ }
305+
306+ }
307+ }
308+
309+ }
310+
311+ /**
312+ * Adds a key frame for the given scale at the given time
313+ * @param time the time at which the keyFrame must be inserted
314+ * @param scale the scale to use for this keyFrame
315+ */
316+ public void addTimeScale(float time, Vector3f scale) {
317+ addKeyFrameScale((int) (time / tpf), scale);
318+ }
319+
320+ /**
321+ * Adds a key frame for the given scale at the given keyFrame index
322+ * @param keyFrameIndex the index at which the keyFrame must be inserted
323+ * @param scale the scale to use for this keyFrame
324+ */
325+ public void addKeyFrameScale(int keyFrameIndex, Vector3f scale) {
326+ Vector3f s = getScaleForFrame(keyFrameIndex);
327+ s.set(scale);
328+ }
329+
330+ /**
331+ * returns the translation for a given frame index
332+ * creates the translation if it doesn't exists
333+ * @param keyFrameIndex index
334+ * @return the translation
335+ */
336+ private Vector3f getTranslationForFrame(int keyFrameIndex) {
337+ if (keyFrameIndex < 0 || keyFrameIndex > totalFrames) {
338+ throw new ArrayIndexOutOfBoundsException("keyFrameIndex must be between 0 and " + totalFrames + " (received " + keyFrameIndex + ")");
339+ }
340+ Vector3f v = keyFramesTranslation[keyFrameIndex];
341+ if (v == null) {
342+ v = new Vector3f();
343+ keyFramesTranslation[keyFrameIndex] = v;
344+ }
345+ return v;
346+ }
347+
348+ /**
349+ * returns the scale for a given frame index
350+ * creates the scale if it doesn't exists
351+ * @param keyFrameIndex index
352+ * @return the scale
353+ */
354+ private Vector3f getScaleForFrame(int keyFrameIndex) {
355+ if (keyFrameIndex < 0 || keyFrameIndex > totalFrames) {
356+ throw new ArrayIndexOutOfBoundsException("keyFrameIndex must be between 0 and " + totalFrames + " (received " + keyFrameIndex + ")");
357+ }
358+ Vector3f v = keyFramesScale[keyFrameIndex];
359+ if (v == null) {
360+ v = new Vector3f();
361+ keyFramesScale[keyFrameIndex] = v;
362+ }
363+ return v;
364+ }
365+
366+ /**
367+ * returns the rotation for a given frame index
368+ * creates the rotation if it doesn't exists
369+ * @param keyFrameIndex index
370+ * @return the rotation
371+ */
372+ private Rotation getRotationForFrame(int keyFrameIndex) {
373+ if (keyFrameIndex < 0 || keyFrameIndex > totalFrames) {
374+ throw new ArrayIndexOutOfBoundsException("keyFrameIndex must be between 0 and " + totalFrames + " (received " + keyFrameIndex + ")");
375+ }
376+ Rotation v = keyFramesRotation[keyFrameIndex];
377+ if (v == null) {
378+ v = new Rotation();
379+ keyFramesRotation[keyFrameIndex] = v;
380+ }
381+ return v;
382+ }
383+
384+ /**
385+ * Creates an Animation based on the keyFrames previously added to the helper.
386+ * @return the generated animation
387+ */
388+ public Animation buildAnimation() {
389+ interpolateTime();
390+ interpolate(keyFramesTranslation, Type.Translation);
391+ interpolate(keyFramesRotation, Type.Rotation);
392+ interpolate(keyFramesScale, Type.Scale);
393+
394+ SpatialTrack spatialTrack = new SpatialTrack(times, translations, rotations, scales);
395+
396+ //creating the animation
397+ Animation spatialAnimation = new Animation(name, duration);
398+ spatialAnimation.setTracks(new SpatialTrack[]{spatialTrack});
399+
400+ return spatialAnimation;
401+ }
402+
403+ /**
404+ * interpolates time values
405+ */
406+ private void interpolateTime() {
407+ for (int i = 0; i < totalFrames; i++) {
408+ times[i] = i * tpf;
409+ }
410+ }
411+
412+ /**
413+ * Interpolates over the key frames for the given keyFrame array and the given type of transform
414+ * @param keyFrames the keyFrames array
415+ * @param type the type of transforms
416+ */
417+ private void interpolate(Object[] keyFrames, Type type) {
418+ int i = 0;
419+ while (i < totalFrames) {
420+ //fetching the next keyFrame index transform in the array
421+ int key = getNextKeyFrame(i, keyFrames);
422+ if (key != -1) {
423+ //computing the frame span to interpolate over
424+ int span = key - i;
425+ //interating over the frames
426+ for (int j = i; j <= key; j++) {
427+ // computing interpolation value
428+ float val = (float) (j - i) / (float) span;
429+ //interpolationg depending on the transform type
430+ switch (type) {
431+ case Translation:
432+ translations[j] = FastMath.interpolateLinear(val, (Vector3f) keyFrames[i], (Vector3f) keyFrames[key]);
433+ break;
434+ case Rotation:
435+ Quaternion rot = new Quaternion();
436+ rotations[j] = rot.slerp(((Rotation) keyFrames[i]).rotation, ((Rotation) keyFrames[key]).rotation, val);
437+ break;
438+ case Scale:
439+ scales[j] = FastMath.interpolateLinear(val, (Vector3f) keyFrames[i], (Vector3f) keyFrames[key]);
440+ break;
441+ }
442+ }
443+ //jumping to the next keyFrame
444+ i = key;
445+ } else {
446+ //No more key frame, filling the array witht he last transform computed.
447+ for (int j = i; j < totalFrames; j++) {
448+
449+ switch (type) {
450+ case Translation:
451+ translations[j] = ((Vector3f) keyFrames[i]).clone();
452+ break;
453+ case Rotation:
454+ rotations[j] = ((Quaternion) ((Rotation) keyFrames[i]).rotation).clone();
455+ break;
456+ case Scale:
457+ scales[j] = ((Vector3f) keyFrames[i]).clone();
458+ break;
459+ }
460+ }
461+ //we're done
462+ i = totalFrames;
463+ }
464+ }
465+ }
466+
467+ /**
468+ * Get the index of the next keyFrame that as a transform
469+ * @param index the start index
470+ * @param keyFrames the keyFrames array
471+ * @return the index of the next keyFrame
472+ */
473+ private int getNextKeyFrame(int index, Object[] keyFrames) {
474+ for (int i = index + 1; i < totalFrames; i++) {
475+ if (keyFrames[i] != null) {
476+ return i;
477+ }
478+ }
479+ return -1;
480+ }
481+
482+ /**
483+ * Get the index of the previous keyFrame that as a transform
484+ * @param index the start index
485+ * @param keyFrames the keyFrames array
486+ * @return the index of the previous keyFrame
487+ */
488+ private int getPreviousKeyFrame(int index, Object[] keyFrames) {
489+ for (int i = index - 1; i >= 0; i--) {
490+ if (keyFrames[i] != null) {
491+ return i;
492+ }
493+ }
494+ return -1;
495+ }
496+}
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/AudioTrack.java
@@ -0,0 +1,304 @@
1+/*
2+ * Copyright (c) 2009-2012 jMonkeyEngine
3+ * All rights reserved.
4+ *
5+ * Redistribution and use in source and binary forms, with or without
6+ * modification, are permitted provided that the following conditions are
7+ * met:
8+ *
9+ * * Redistributions of source code must retain the above copyright
10+ * notice, this list of conditions and the following disclaimer.
11+ *
12+ * * Redistributions in binary form must reproduce the above copyright
13+ * notice, this list of conditions and the following disclaimer in the
14+ * documentation and/or other materials provided with the distribution.
15+ *
16+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17+ * may be used to endorse or promote products derived from this software
18+ * without specific prior written permission.
19+ *
20+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+ */
32+package com.jme3.animation;
33+
34+import com.jme3.audio.AudioNode;
35+import com.jme3.export.InputCapsule;
36+import com.jme3.export.JmeExporter;
37+import com.jme3.export.JmeImporter;
38+import com.jme3.export.OutputCapsule;
39+import com.jme3.scene.Node;
40+import com.jme3.scene.Spatial;
41+import com.jme3.util.TempVars;
42+import java.io.IOException;
43+import java.util.logging.Level;
44+import java.util.logging.Logger;
45+
46+/**
47+ * AudioTrack is a track to add to an existing animation, to paly a sound during
48+ * an animations for example : gun shot, foot step, shout, etc...
49+ *
50+ * usage is
51+ * <pre>
52+ * AnimControl control model.getControl(AnimControl.class);
53+ * AudioTrack track = new AudioTrack(existionAudioNode, control.getAnim("TheAnim").getLength());
54+ * control.getAnim("TheAnim").addTrack(track);
55+ * </pre>
56+ *
57+ * This is mostly intended for short sounds, playInstance will be called on the
58+ * AudioNode at time 0 + startOffset.
59+ *
60+ *
61+ * @author Nehon
62+ */
63+public class AudioTrack implements ClonableTrack {
64+
65+ private static final Logger logger = Logger.getLogger(AudioTrack.class.getName());
66+ private AudioNode audio;
67+ private float startOffset = 0;
68+ private float length = 0;
69+ private boolean initialized = false;
70+ private boolean started = false;
71+ private boolean played = false;
72+
73+ //Animation listener to stop the sound when the animation ends or is changed
74+ private class OnEndListener implements AnimEventListener {
75+
76+ public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
77+ stop();
78+ }
79+
80+ public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {
81+ }
82+ }
83+
84+ /**
85+ * default constructor for serialization only
86+ */
87+ public AudioTrack() {
88+ }
89+
90+ /**
91+ * Creates an AudioTrack
92+ *
93+ * @param audio the AudioNode
94+ * @param length the length of the track (usually the length of the
95+ * animation you want to add the track to)
96+ */
97+ public AudioTrack(AudioNode audio, float length) {
98+ this.audio = audio;
99+ this.length = length;
100+ setUserData(this);
101+ }
102+
103+ /**
104+ * Creates an AudioTrack
105+ *
106+ * @param audio the AudioNode
107+ * @param length the length of the track (usually the length of the
108+ * animation you want to add the track to)
109+ * @param startOffset the time in second when the sound will be played after
110+ * the animation starts (default is 0)
111+ */
112+ public AudioTrack(AudioNode audio, float length, float startOffset) {
113+ this(audio, length);
114+ this.startOffset = startOffset;
115+ }
116+
117+ /**
118+ * Internal use only
119+ *
120+ * @see Track#setTime(float, float, com.jme3.animation.AnimControl,
121+ * com.jme3.animation.AnimChannel, com.jme3.util.TempVars)
122+ */
123+ public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) {
124+
125+ if (time >= length) {
126+ return;
127+ }
128+ if (!initialized) {
129+ control.addListener(new OnEndListener());
130+ initialized = true;
131+ }
132+ if (!started && time >= startOffset) {
133+ started = true;
134+ audio.playInstance();
135+ }
136+ }
137+
138+ //stops the sound
139+ private void stop() {
140+ audio.stop();
141+ started = false;
142+ }
143+
144+ /**
145+ * Retruns the length of the track
146+ *
147+ * @return length of the track
148+ */
149+ public float getLength() {
150+ return length;
151+ }
152+
153+ /**
154+ * Clone this track
155+ *
156+ * @return
157+ */
158+ @Override
159+ public Track clone() {
160+ return new AudioTrack(audio, length, startOffset);
161+ }
162+
163+ /**
164+ * This method clone the Track and search for the cloned counterpart of the
165+ * original audio node in the given cloned spatial. The spatial is assumed
166+ * to be the Spatial holding the AnimControl controling the animation using
167+ * this Track.
168+ *
169+ * @param spatial the Spatial holding the AnimControl
170+ * @return the cloned Track with proper reference
171+ */
172+ public Track cloneForSpatial(Spatial spatial) {
173+ AudioTrack audioTrack = new AudioTrack();
174+ audioTrack.length = this.length;
175+ audioTrack.startOffset = this.startOffset;
176+
177+ //searching for the newly cloned AudioNode
178+ audioTrack.audio = findAudio(spatial);
179+ if (audioTrack.audio == null) {
180+ logger.log(Level.WARNING, "{0} was not found in {1} or is not bound to this track", new Object[]{audio.getName(), spatial.getName()});
181+ audioTrack.audio = audio;
182+ }
183+
184+ //setting user data on the new AudioNode and marking it with a reference to the cloned Track.
185+ setUserData(audioTrack);
186+
187+ return audioTrack;
188+ }
189+
190+ /**
191+ * recursive function responsible for finding the newly cloned AudioNode
192+ *
193+ * @param spat
194+ * @return
195+ */
196+ private AudioNode findAudio(Spatial spat) {
197+ if (spat instanceof AudioNode) {
198+ //spat is an AudioNode
199+ AudioNode em = (AudioNode) spat;
200+ //getting the UserData TrackInfo so check if it should be attached to this Track
201+ TrackInfo t = (TrackInfo) em.getUserData("TrackInfo");
202+ if (t != null && t.getTracks().contains(this)) {
203+ return em;
204+ }
205+ return null;
206+
207+ } else if (spat instanceof Node) {
208+ for (Spatial child : ((Node) spat).getChildren()) {
209+ AudioNode em = findAudio(child);
210+ if (em != null) {
211+ return em;
212+ }
213+ }
214+ }
215+ return null;
216+ }
217+
218+ private void setUserData(AudioTrack audioTrack) {
219+ //fetching the UserData TrackInfo.
220+ TrackInfo data = (TrackInfo) audioTrack.audio.getUserData("TrackInfo");
221+
222+ //if it does not exist, we create it and attach it to the AudioNode.
223+ if (data == null) {
224+ data = new TrackInfo();
225+ audioTrack.audio.setUserData("TrackInfo", data);
226+ }
227+
228+ //adding the given Track to the TrackInfo.
229+ data.addTrack(audioTrack);
230+ }
231+
232+ public void cleanUp() {
233+ TrackInfo t = (TrackInfo) audio.getUserData("TrackInfo");
234+ t.getTracks().remove(this);
235+ if (!t.getTracks().isEmpty()) {
236+ audio.setUserData("TrackInfo", null);
237+ }
238+ }
239+
240+ /**
241+ *
242+ * @return the audio node used by this track
243+ */
244+ public AudioNode getAudio() {
245+ return audio;
246+ }
247+
248+ /**
249+ * sets the audio node to be used for this track
250+ *
251+ * @param audio
252+ */
253+ public void setAudio(AudioNode audio) {
254+ if (this.audio != null) {
255+ TrackInfo data = (TrackInfo) audio.getUserData("TrackInfo");
256+ data.getTracks().remove(this);
257+ }
258+ this.audio = audio;
259+ setUserData(this);
260+ }
261+
262+ /**
263+ *
264+ * @return the start offset of the track
265+ */
266+ public float getStartOffset() {
267+ return startOffset;
268+ }
269+
270+ /**
271+ * set the start offset of the track
272+ *
273+ * @param startOffset
274+ */
275+ public void setStartOffset(float startOffset) {
276+ this.startOffset = startOffset;
277+ }
278+
279+ /**
280+ * Internal use only serialization
281+ *
282+ * @param ex exporter
283+ * @throws IOException exception
284+ */
285+ public void write(JmeExporter ex) throws IOException {
286+ OutputCapsule out = ex.getCapsule(this);
287+ out.write(audio, "audio", null);
288+ out.write(length, "length", 0);
289+ out.write(startOffset, "startOffset", 0);
290+ }
291+
292+ /**
293+ * Internal use only serialization
294+ *
295+ * @param im importer
296+ * @throws IOException Exception
297+ */
298+ public void read(JmeImporter im) throws IOException {
299+ InputCapsule in = im.getCapsule(this);
300+ audio = (AudioNode) in.readSavable("audio", null);
301+ length = in.readFloat("length", length);
302+ startOffset = in.readFloat("startOffset", 0);
303+ }
304+}
--- a/engine/src/core/com/jme3/animation/Bone.java
+++ b/engine/src/core/com/jme3/animation/Bone.java
@@ -1,5 +1,5 @@
11 /*
2- * Copyright (c) 2009-2010 jMonkeyEngine
2+ * Copyright (c) 2009-2012 jMonkeyEngine
33 * All rights reserved.
44 *
55 * Redistribution and use in source and binary forms, with or without
@@ -31,16 +31,8 @@
3131 */
3232 package com.jme3.animation;
3333
34-import com.jme3.export.JmeExporter;
35-import com.jme3.export.JmeImporter;
36-import com.jme3.export.InputCapsule;
37-import com.jme3.export.OutputCapsule;
38-import com.jme3.export.Savable;
39-import com.jme3.math.Matrix3f;
40-import com.jme3.math.Matrix4f;
41-import com.jme3.math.Quaternion;
42-import com.jme3.math.Transform;
43-import com.jme3.math.Vector3f;
34+import com.jme3.export.*;
35+import com.jme3.math.*;
4436 import com.jme3.scene.Node;
4537 import com.jme3.util.TempVars;
4638 import java.io.IOException;
@@ -63,7 +55,6 @@ public final class Bone implements Savable {
6355 * Animation transforms are not applied to this bone when enabled.
6456 */
6557 private boolean userControl = false;
66- private boolean useModelSpaceVectors = false;
6758 /**
6859 * The attachment node.
6960 */
@@ -94,8 +85,16 @@ public final class Bone implements Savable {
9485 private Vector3f worldPos = new Vector3f();
9586 private Quaternion worldRot = new Quaternion();
9687 private Vector3f worldScale = new Vector3f();
97- //used for getCombinedTransform
88+
89+ // Used for getCombinedTransform
9890 private Transform tmpTransform = new Transform();
91+
92+ /**
93+ * Used to handle blending from one animation to another.
94+ * See {@link #blendAnimTransforms(com.jme3.math.Vector3f, com.jme3.math.Quaternion, com.jme3.math.Vector3f, float)}
95+ * on how this variable is used.
96+ */
97+ private transient float currentWeightSum = -1;
9998
10099 /**
101100 * Creates a new bone with the given name.
@@ -332,27 +331,45 @@ public final class Bone implements Savable {
332331 * world transform with this bones' local transform.
333332 */
334333 public final void updateWorldVectors() {
335- if (true || !useModelSpaceVectors) {
336- if (parent != null) {
337- //rotation
338- parent.worldRot.mult(localRot, worldRot);
339-
340- //scale
341- //For scale parent scale is not taken into account!
342- // worldScale.set(localScale);
343- parent.worldScale.mult(localScale, worldScale);
344-
345- //translation
346- //scale and rotation of parent affect bone position
347- parent.worldRot.mult(localPos, worldPos);
348- worldPos.multLocal(parent.worldScale);
349- worldPos.addLocal(parent.worldPos);
334+ if (currentWeightSum == 1f) {
335+ currentWeightSum = -1;
336+ } else if (currentWeightSum != -1f) {
337+ // Apply the weight to the local transform
338+ if (currentWeightSum == 0) {
339+ localRot.set(initialRot);
340+ localPos.set(initialPos);
341+ localScale.set(initialScale);
350342 } else {
351- worldRot.set(localRot);
352- worldPos.set(localPos);
353- worldScale.set(localScale);
343+ float invWeightSum = 1f - currentWeightSum;
344+ localRot.nlerp(initialRot, invWeightSum);
345+ localPos.interpolate(initialPos, invWeightSum);
346+ localScale.interpolate(initialScale, invWeightSum);
354347 }
348+
349+ // Future invocations of transform blend will start over.
350+ currentWeightSum = -1;
351+ }
352+
353+ if (parent != null) {
354+ //rotation
355+ parent.worldRot.mult(localRot, worldRot);
356+
357+ //scale
358+ //For scale parent scale is not taken into account!
359+ // worldScale.set(localScale);
360+ parent.worldScale.mult(localScale, worldScale);
361+
362+ //translation
363+ //scale and rotation of parent affect bone position
364+ parent.worldRot.mult(localPos, worldPos);
365+ worldPos.multLocal(parent.worldScale);
366+ worldPos.addLocal(parent.worldPos);
367+ } else {
368+ worldRot.set(localRot);
369+ worldPos.set(localPos);
370+ worldScale.set(localScale);
355371 }
372+
356373 if (attachNode != null) {
357374 attachNode.setLocalTranslation(worldPos);
358375 attachNode.setLocalRotation(worldRot);
@@ -471,6 +488,12 @@ public final class Bone implements Savable {
471488 // TODO: add scale here ???
472489 worldPos.set(translation);
473490 worldRot.set(rotation);
491+
492+ //if there is an attached Node we need to set it's local transforms too.
493+ if(attachNode != null){
494+ attachNode.setLocalTranslation(translation);
495+ attachNode.setLocalRotation(rotation);
496+ }
474497 }
475498
476499 /**
@@ -489,7 +512,7 @@ public final class Bone implements Savable {
489512 * Attach models and effects to this node to make
490513 * them follow this bone's motions.
491514 */
492- public Node getAttachmentsNode() {
515+ Node getAttachmentsNode() {
493516 if (attachNode == null) {
494517 attachNode = new Node(name + "_attachnode");
495518 attachNode.setUserData("AttachedBone", this);
@@ -526,34 +549,70 @@ public final class Bone implements Savable {
526549 }
527550 }
528551
552+ /**
553+ * Blends the given animation transform onto the bone's local transform.
554+ * <p>
555+ * Subsequent calls of this method stack up, with the final transformation
556+ * of the bone computed at {@link #updateWorldVectors() } which resets
557+ * the stack.
558+ * <p>
559+ * E.g. a single transform blend with weight = 0.5 followed by an
560+ * updateWorldVectors() call will result in final transform = transform * 0.5.
561+ * Two transform blends with weight = 0.5 each will result in the two
562+ * transforms blended together (nlerp) with blend = 0.5.
563+ *
564+ * @param translation The translation to blend in
565+ * @param rotation The rotation to blend in
566+ * @param scale The scale to blend in
567+ * @param weight The weight of the transform to apply. Set to 1.0 to prevent
568+ * any other transform from being applied until updateWorldVectors().
569+ */
529570 void blendAnimTransforms(Vector3f translation, Quaternion rotation, Vector3f scale, float weight) {
530571 if (userControl) {
531572 return;
532573 }
533-
534- TempVars vars = TempVars.get();
535-// assert vars.lock();
536-
537- Vector3f tmpV = vars.vect1;
538- Vector3f tmpV2 = vars.vect2;
539- Quaternion tmpQ = vars.quat1;
540-
541- //location
542- tmpV.set(initialPos).addLocal(translation);
543- localPos.interpolate(tmpV, weight);
544-
545- //rotation
546- tmpQ.set(initialRot).multLocal(rotation);
547- localRot.nlerp(tmpQ, weight);
548-
549- //scale
550- if (scale != null) {
551- tmpV2.set(initialScale).multLocal(scale);
552- localScale.interpolate(tmpV2, weight);
574+
575+ if (weight == 0) {
576+ // Do not apply this transform at all.
577+ return;
553578 }
554579
555-
556- vars.release();
580+ if (currentWeightSum == 1){
581+ return; // More than 2 transforms are being blended
582+ } else if (currentWeightSum == -1 || currentWeightSum == 0) {
583+ // Set the transform fully
584+ localPos.set(initialPos).addLocal(translation);
585+ localRot.set(initialRot).multLocal(rotation);
586+ if (scale != null) {
587+ localScale.set(initialScale).multLocal(scale);
588+ }
589+ // Set the weight. It will be applied in updateWorldVectors().
590+ currentWeightSum = weight;
591+ } else {
592+ // The weight is already set.
593+ // Blend in the new transform.
594+ TempVars vars = TempVars.get();
595+
596+ Vector3f tmpV = vars.vect1;
597+ Vector3f tmpV2 = vars.vect2;
598+ Quaternion tmpQ = vars.quat1;
599+
600+ tmpV.set(initialPos).addLocal(translation);
601+ localPos.interpolate(tmpV, weight);
602+
603+ tmpQ.set(initialRot).multLocal(rotation);
604+ localRot.nlerp(tmpQ, weight);
605+
606+ if (scale != null) {
607+ tmpV2.set(initialScale).multLocal(scale);
608+ localScale.interpolate(tmpV2, weight);
609+ }
610+
611+ // Ensures no new weights will be blended in the future.
612+ currentWeightSum = 1;
613+
614+ vars.release();
615+ }
557616 }
558617
559618 /**
@@ -593,14 +652,6 @@ public final class Bone implements Savable {
593652 return this.toString(0);
594653 }
595654
596- public boolean isUseModelSpaceVectors() {
597- return useModelSpaceVectors;
598- }
599-
600- public void setUseModelSpaceVectors(boolean useModelSpaceVectors) {
601- this.useModelSpaceVectors = useModelSpaceVectors;
602- }
603-
604655 @Override
605656 @SuppressWarnings("unchecked")
606657 public void read(JmeImporter im) throws IOException {
--- a/engine/src/core/com/jme3/animation/BoneAnimation.java
+++ b/engine/src/core/com/jme3/animation/BoneAnimation.java
@@ -1,5 +1,5 @@
11 /*
2- * Copyright (c) 2009-2010 jMonkeyEngine
2+ * Copyright (c) 2009-2012 jMonkeyEngine
33 * All rights reserved.
44 *
55 * Redistribution and use in source and binary forms, with or without
--- a/engine/src/core/com/jme3/animation/BoneTrack.java
+++ b/engine/src/core/com/jme3/animation/BoneTrack.java
@@ -1,5 +1,5 @@
11 /*
2- * Copyright (c) 2009-2010 jMonkeyEngine
2+ * Copyright (c) 2009-2012 jMonkeyEngine
33 * All rights reserved.
44 *
55 * Redistribution and use in source and binary forms, with or without
@@ -31,11 +31,7 @@
3131 */
3232 package com.jme3.animation;
3333
34-import com.jme3.export.JmeExporter;
35-import com.jme3.export.JmeImporter;
36-import com.jme3.export.InputCapsule;
37-import com.jme3.export.OutputCapsule;
38-import com.jme3.export.Savable;
34+import com.jme3.export.*;
3935 import com.jme3.math.Quaternion;
4036 import com.jme3.math.Vector3f;
4137 import com.jme3.util.TempVars;
@@ -186,8 +182,10 @@ public final class BoneTrack implements Track {
186182 * The transforms can be interpolated in some method from the keyframes.
187183 *
188184 * @param time the current time of the animation
189- * @param skeleton the skeleton to which the bone belong
190185 * @param weight the weight of the animation
186+ * @param control
187+ * @param channel
188+ * @param vars
191189 */
192190 public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) {
193191 BitSet affectedBones = channel.getAffectedBones();
@@ -245,11 +243,11 @@ public final class BoneTrack implements Track {
245243 tempS.interpolate(tempS2, blend);
246244 }
247245
248- if (weight != 1f) {
246+// if (weight != 1f) {
249247 target.blendAnimTransforms(tempV, tempQ, scales != null ? tempS : null, weight);
250- } else {
251- target.setAnimTransforms(tempV, tempQ, scales != null ? tempS : null);
252- }
248+// } else {
249+// target.setAnimTransforms(tempV, tempQ, scales != null ? tempS : null);
250+// }
253251 }
254252
255253 /**
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/ClonableTrack.java
@@ -0,0 +1,67 @@
1+/*
2+ * Copyright (c) 2009-2012 jMonkeyEngine
3+ * All rights reserved.
4+ *
5+ * Redistribution and use in source and binary forms, with or without
6+ * modification, are permitted provided that the following conditions are
7+ * met:
8+ *
9+ * * Redistributions of source code must retain the above copyright
10+ * notice, this list of conditions and the following disclaimer.
11+ *
12+ * * Redistributions in binary form must reproduce the above copyright
13+ * notice, this list of conditions and the following disclaimer in the
14+ * documentation and/or other materials provided with the distribution.
15+ *
16+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17+ * may be used to endorse or promote products derived from this software
18+ * without specific prior written permission.
19+ *
20+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+ */
32+package com.jme3.animation;
33+
34+import com.jme3.scene.Spatial;
35+
36+/**
37+ * An interface that allow to clone a Track for a given Spatial.
38+ * The spatial fed to the method is the Spatial holding the AnimControl controling the Animation using this track.
39+ *
40+ * Implement this interface only if you make your own Savable Track and that the track has a direct reference to a Spatial in the scene graph.
41+ * This Spatial is assumed to be a child of the spatial holding the AnimControl.
42+ *
43+ *
44+ * @author Nehon
45+ */
46+public interface ClonableTrack extends Track {
47+
48+ /**
49+ * Allows to clone the track for a given Spatial.
50+ * The spatial fed to the method is the Spatial holding the AnimControl controling the Animation using this track.
51+ * This method will be called during the loading process of a j3o model by the assetManager.
52+ * The assetManager keeps the original model in cache and returns a clone of the model.
53+ *
54+ * This method prupose is to find the cloned reference of the original spatial which it refers to in the cloned model.
55+ *
56+ * See EffectTrack for a proper implementation.
57+ *
58+ * @param spatial the spatial holding the AnimControl
59+ * @return the cloned Track
60+ */
61+ public Track cloneForSpatial(Spatial spatial);
62+
63+ /**
64+ * Method responsible of cleaning UserData on referenced Spatials when the Track is deleted
65+ */
66+ public void cleanUp();
67+}
--- a/engine/src/core/com/jme3/animation/CompactArray.java
+++ b/engine/src/core/com/jme3/animation/CompactArray.java
@@ -1,5 +1,5 @@
11 /*
2- * Copyright (c) 2009-2010 jMonkeyEngine
2+ * Copyright (c) 2009-2012 jMonkeyEngine
33 * All rights reserved.
44 *
55 * Redistribution and use in source and binary forms, with or without
--- a/engine/src/core/com/jme3/animation/CompactQuaternionArray.java
+++ b/engine/src/core/com/jme3/animation/CompactQuaternionArray.java
@@ -1,5 +1,5 @@
11 /*
2- * Copyright (c) 2009-2010 jMonkeyEngine
2+ * Copyright (c) 2009-2012 jMonkeyEngine
33 * All rights reserved.
44 *
55 * Redistribution and use in source and binary forms, with or without
@@ -31,14 +31,9 @@
3131 */
3232 package com.jme3.animation;
3333
34-import java.io.IOException;
35-
36-import com.jme3.export.InputCapsule;
37-import com.jme3.export.JmeExporter;
38-import com.jme3.export.JmeImporter;
39-import com.jme3.export.OutputCapsule;
40-import com.jme3.export.Savable;
34+import com.jme3.export.*;
4135 import com.jme3.math.Quaternion;
36+import java.io.IOException;
4237
4338 /**
4439 * Serialize and compress {@link Quaternion}[] by indexing same values
--- a/engine/src/core/com/jme3/animation/CompactVector3Array.java
+++ b/engine/src/core/com/jme3/animation/CompactVector3Array.java
@@ -1,5 +1,5 @@
11 /*
2- * Copyright (c) 2009-2010 jMonkeyEngine
2+ * Copyright (c) 2009-2012 jMonkeyEngine
33 * All rights reserved.
44 *
55 * Redistribution and use in source and binary forms, with or without
@@ -29,17 +29,11 @@
2929 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
3030 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3131 */
32-
3332 package com.jme3.animation;
3433
35-import java.io.IOException;
36-
37-import com.jme3.export.InputCapsule;
38-import com.jme3.export.JmeExporter;
39-import com.jme3.export.JmeImporter;
40-import com.jme3.export.OutputCapsule;
41-import com.jme3.export.Savable;
34+import com.jme3.export.*;
4235 import com.jme3.math.Vector3f;
36+import java.io.IOException;
4337
4438 /**
4539 * Serialize and compress Vector3f[] by indexing same values
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/EffectTrack.java
@@ -0,0 +1,431 @@
1+/*
2+ * Copyright (c) 2009-2012 jMonkeyEngine
3+ * All rights reserved.
4+ *
5+ * Redistribution and use in source and binary forms, with or without
6+ * modification, are permitted provided that the following conditions are
7+ * met:
8+ *
9+ * * Redistributions of source code must retain the above copyright
10+ * notice, this list of conditions and the following disclaimer.
11+ *
12+ * * Redistributions in binary form must reproduce the above copyright
13+ * notice, this list of conditions and the following disclaimer in the
14+ * documentation and/or other materials provided with the distribution.
15+ *
16+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17+ * may be used to endorse or promote products derived from this software
18+ * without specific prior written permission.
19+ *
20+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+ */
32+package com.jme3.animation;
33+
34+import com.jme3.effect.ParticleEmitter;
35+import com.jme3.export.InputCapsule;
36+import com.jme3.export.JmeExporter;
37+import com.jme3.export.JmeImporter;
38+import com.jme3.export.OutputCapsule;
39+import com.jme3.renderer.RenderManager;
40+import com.jme3.renderer.ViewPort;
41+import com.jme3.scene.Node;
42+import com.jme3.scene.Spatial;
43+import com.jme3.scene.Spatial.CullHint;
44+import com.jme3.scene.control.AbstractControl;
45+import com.jme3.scene.control.Control;
46+import com.jme3.util.TempVars;
47+import java.io.IOException;
48+import java.util.logging.Level;
49+import java.util.logging.Logger;
50+
51+/**
52+ * EffectTrack is a track to add to an existing animation, to emmit particles
53+ * during animations for example : exhausts, dust raised by foot steps, shock
54+ * waves, lightnings etc...
55+ *
56+ * usage is
57+ * <pre>
58+ * AnimControl control model.getControl(AnimControl.class);
59+ * EffectTrack track = new EffectTrack(existingEmmitter, control.getAnim("TheAnim").getLength());
60+ * control.getAnim("TheAnim").addTrack(track);
61+ * </pre>
62+ *
63+ * if the emitter has emmits 0 particles per seconds emmitAllPArticles will be
64+ * called on it at time 0 + startOffset. if it he it has more it will start
65+ * emmit normally at time 0 + startOffset.
66+ *
67+ *
68+ * @author Nehon
69+ */
70+public class EffectTrack implements ClonableTrack {
71+
72+ private static final Logger logger = Logger.getLogger(EffectTrack.class.getName());
73+ private ParticleEmitter emitter;
74+ private float startOffset = 0;
75+ private float particlesPerSeconds = 0;
76+ private float length = 0;
77+ private boolean emitted = false;
78+ private boolean initialized = false;
79+ //control responsible for disable and cull the emitter once all particles are gone
80+ private KillParticleControl killParticles = new KillParticleControl();
81+
82+ public static class KillParticleControl extends AbstractControl {
83+
84+ ParticleEmitter emitter;
85+ boolean stopRequested = false;
86+ boolean remove = false;
87+
88+ public KillParticleControl() {
89+ }
90+
91+ @Override
92+ public void setSpatial(Spatial spatial) {
93+ super.setSpatial(spatial);
94+ if (spatial != null) {
95+ if (spatial instanceof ParticleEmitter) {
96+ emitter = (ParticleEmitter) spatial;
97+ } else {
98+ throw new IllegalArgumentException("KillParticleEmitter can only ba attached to ParticleEmitter");
99+ }
100+ }
101+
102+
103+ }
104+
105+ @Override
106+ protected void controlUpdate(float tpf) {
107+ if (remove) {
108+ emitter.removeControl(this);
109+ return;
110+ }
111+ if (emitter.getNumVisibleParticles() == 0) {
112+ emitter.setCullHint(CullHint.Always);
113+ emitter.setEnabled(false);
114+ emitter.removeControl(this);
115+ stopRequested = false;
116+ }
117+ }
118+
119+ @Override
120+ protected void controlRender(RenderManager rm, ViewPort vp) {
121+ }
122+
123+ @Override
124+ public Control cloneForSpatial(Spatial spatial) {
125+
126+ KillParticleControl c = new KillParticleControl();
127+ //this control should be removed as it shouldn't have been persisted in the first place
128+ //In the quest to find the less hackish solution to achieve this,
129+ //making it remove itself from the spatial in the first update loop when loaded was the less bad.
130+ c.remove = true;
131+ c.setSpatial(spatial);
132+ return c;
133+
134+ }
135+ };
136+
137+ //Anim listener that stops the Emmitter when the animation is finished or changed.
138+ private class OnEndListener implements AnimEventListener {
139+
140+ public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
141+ stop();
142+ }
143+
144+ public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {
145+ }
146+ }
147+
148+ /**
149+ * default constructor only for serialization
150+ */
151+ public EffectTrack() {
152+ }
153+
154+ /**
155+ * Creates and EffectTrack
156+ *
157+ * @param emitter the emmitter of the track
158+ * @param length the length of the track (usually the length of the
159+ * animation you want to add the track to)
160+ */
161+ public EffectTrack(ParticleEmitter emitter, float length) {
162+ this.emitter = emitter;
163+ //saving particles per second value
164+ this.particlesPerSeconds = emitter.getParticlesPerSec();
165+ //setting the emmitter to not emmit.
166+ this.emitter.setParticlesPerSec(0);
167+ this.length = length;
168+ //Marking the emitter with a reference to this track for further use in deserialization.
169+ setUserData(this);
170+
171+ }
172+
173+ /**
174+ * Creates and EffectTrack
175+ *
176+ * @param emitter the emmitter of the track
177+ * @param length the length of the track (usually the length of the
178+ * animation you want to add the track to)
179+ * @param startOffset the time in second when the emitter will be triggerd
180+ * after the animation starts (default is 0)
181+ */
182+ public EffectTrack(ParticleEmitter emitter, float length, float startOffset) {
183+ this(emitter, length);
184+ this.startOffset = startOffset;
185+ }
186+
187+ /**
188+ * Internal use only
189+ *
190+ * @see Track#setTime(float, float, com.jme3.animation.AnimControl,
191+ * com.jme3.animation.AnimChannel, com.jme3.util.TempVars)
192+ */
193+ public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) {
194+
195+ if (time >= length) {
196+ return;
197+ }
198+ //first time adding the Animation listener to stop the track at the end of the animation
199+ if (!initialized) {
200+ control.addListener(new OnEndListener());
201+ initialized = true;
202+ }
203+ //checking fo time to trigger the effect
204+ if (!emitted && time >= startOffset) {
205+ emitted = true;
206+ emitter.setCullHint(CullHint.Dynamic);
207+ emitter.setEnabled(true);
208+ //if the emitter has 0 particles per seconds emmit all particles in one shot
209+ if (particlesPerSeconds == 0) {
210+ emitter.emitAllParticles();
211+ if (!killParticles.stopRequested) {
212+ emitter.addControl(killParticles);
213+ killParticles.stopRequested = true;
214+ }
215+ } else {
216+ //else reset its former particlePerSec value to let it emmit.
217+ emitter.setParticlesPerSec(particlesPerSeconds);
218+ }
219+ }
220+ }
221+
222+ //stops the emmiter to emit.
223+ private void stop() {
224+ emitter.setParticlesPerSec(0);
225+ emitted = false;
226+ if (!killParticles.stopRequested) {
227+ emitter.addControl(killParticles);
228+ killParticles.stopRequested = true;
229+ }
230+
231+ }
232+
233+ /**
234+ * Retruns the length of the track
235+ *
236+ * @return length of the track
237+ */
238+ public float getLength() {
239+ return length;
240+ }
241+
242+ /**
243+ * Clone this track
244+ *
245+ * @return
246+ */
247+ @Override
248+ public Track clone() {
249+ return new EffectTrack(emitter, length, startOffset);
250+ }
251+
252+ /**
253+ * This method clone the Track and search for the cloned counterpart of the
254+ * original emmitter in the given cloned spatial. The spatial is assumed to
255+ * be the Spatial holding the AnimControl controling the animation using
256+ * this Track.
257+ *
258+ * @param spatial the Spatial holding the AnimControl
259+ * @return the cloned Track with proper reference
260+ */
261+ public Track cloneForSpatial(Spatial spatial) {
262+ EffectTrack effectTrack = new EffectTrack();
263+ effectTrack.particlesPerSeconds = this.particlesPerSeconds;
264+ effectTrack.length = this.length;
265+ effectTrack.startOffset = this.startOffset;
266+
267+ //searching for the newly cloned ParticleEmitter
268+ effectTrack.emitter = findEmitter(spatial);
269+ if (effectTrack.emitter == null) {
270+ logger.log(Level.WARNING, "{0} was not found in {1} or is not bound to this track", new Object[]{emitter.getName(), spatial.getName()});
271+ effectTrack.emitter = emitter;
272+ }
273+
274+ removeUserData(this);
275+ //setting user data on the new emmitter and marking it with a reference to the cloned Track.
276+ setUserData(effectTrack);
277+ effectTrack.emitter.setParticlesPerSec(0);
278+ return effectTrack;
279+ }
280+
281+ /**
282+ * recursive function responsible for finding the newly cloned Emitter
283+ *
284+ * @param spat
285+ * @return
286+ */
287+ private ParticleEmitter findEmitter(Spatial spat) {
288+ if (spat instanceof ParticleEmitter) {
289+ //spat is a PArticleEmitter
290+ ParticleEmitter em = (ParticleEmitter) spat;
291+ //getting the UserData TrackInfo so check if it should be attached to this Track
292+ TrackInfo t = (TrackInfo) em.getUserData("TrackInfo");
293+ if (t != null && t.getTracks().contains(this)) {
294+ return em;
295+ }
296+ return null;
297+
298+ } else if (spat instanceof Node) {
299+ for (Spatial child : ((Node) spat).getChildren()) {
300+ ParticleEmitter em = findEmitter(child);
301+ if (em != null) {
302+ return em;
303+ }
304+ }
305+ }
306+ return null;
307+ }
308+
309+ public void cleanUp() {
310+ TrackInfo t = (TrackInfo) emitter.getUserData("TrackInfo");
311+ t.getTracks().remove(this);
312+ if (t.getTracks().isEmpty()) {
313+ emitter.setUserData("TrackInfo", null);
314+ }
315+ }
316+
317+ /**
318+ *
319+ * @return the emitter used by this track
320+ */
321+ public ParticleEmitter getEmitter() {
322+ return emitter;
323+ }
324+
325+ /**
326+ * Sets the Emitter to use in this track
327+ *
328+ * @param emitter
329+ */
330+ public void setEmitter(ParticleEmitter emitter) {
331+ if (this.emitter != null) {
332+ TrackInfo data = (TrackInfo) emitter.getUserData("TrackInfo");
333+ data.getTracks().remove(this);
334+ }
335+ this.emitter = emitter;
336+ //saving particles per second value
337+ this.particlesPerSeconds = emitter.getParticlesPerSec();
338+ //setting the emmitter to not emmit.
339+ this.emitter.setParticlesPerSec(0);
340+ setUserData(this);
341+ }
342+
343+ /**
344+ *
345+ * @return the start offset of the track
346+ */
347+ public float getStartOffset() {
348+ return startOffset;
349+ }
350+
351+ /**
352+ * set the start offset of the track
353+ *
354+ * @param startOffset
355+ */
356+ public void setStartOffset(float startOffset) {
357+ this.startOffset = startOffset;
358+ }
359+
360+ private void setUserData(EffectTrack effectTrack) {
361+ //fetching the UserData TrackInfo.
362+ TrackInfo data = (TrackInfo) effectTrack.emitter.getUserData("TrackInfo");
363+
364+ //if it does not exist, we create it and attach it to the emitter.
365+ if (data == null) {
366+ data = new TrackInfo();
367+ effectTrack.emitter.setUserData("TrackInfo", data);
368+ }
369+
370+ //adding the given Track to the TrackInfo.
371+ data.addTrack(effectTrack);
372+
373+
374+ }
375+
376+ private void removeUserData(EffectTrack effectTrack) {
377+ //fetching the UserData TrackInfo.
378+ TrackInfo data = (TrackInfo) effectTrack.emitter.getUserData("TrackInfo");
379+
380+ //if it does not exist, we create it and attach it to the emitter.
381+ if (data == null) {
382+ return;
383+ }
384+
385+ //removing the given Track to the TrackInfo.
386+ data.getTracks().remove(effectTrack);
387+
388+
389+ }
390+
391+ /**
392+ * Internal use only serialization
393+ *
394+ * @param ex exporter
395+ * @throws IOException exception
396+ */
397+ public void write(JmeExporter ex) throws IOException {
398+ OutputCapsule out = ex.getCapsule(this);
399+ //reseting the particle emission rate on the emitter before saving.
400+ emitter.setParticlesPerSec(particlesPerSeconds);
401+ out.write(emitter, "emitter", null);
402+ out.write(particlesPerSeconds, "particlesPerSeconds", 0);
403+ out.write(length, "length", 0);
404+ out.write(startOffset, "startOffset", 0);
405+ //Setting emission rate to 0 so that this track can go on being used.
406+ emitter.setParticlesPerSec(0);
407+ }
408+
409+ /**
410+ * Internal use only serialization
411+ *
412+ * @param im importer
413+ * @throws IOException Exception
414+ */
415+ public void read(JmeImporter im) throws IOException {
416+ InputCapsule in = im.getCapsule(this);
417+ this.particlesPerSeconds = in.readFloat("particlesPerSeconds", 0);
418+ //reading the emitter even if the track will then reference its cloned counter part if it's loaded with the assetManager.
419+ //This also avoid null pointer exception if the model is not loaded via the AssetManager.
420+ emitter = (ParticleEmitter) in.readSavable("emitter", null);
421+ emitter.setParticlesPerSec(0);
422+ //if the emitter was saved with a KillParticleControl we remove it.
423+// Control c = emitter.getControl(KillParticleControl.class);
424+// if(c!=null){
425+// emitter.removeControl(c);
426+// }
427+ //emitter.removeControl(KillParticleControl.class);
428+ length = in.readFloat("length", length);
429+ startOffset = in.readFloat("startOffset", 0);
430+ }
431+}
--- a/engine/src/core/com/jme3/animation/LoopMode.java
+++ b/engine/src/core/com/jme3/animation/LoopMode.java
@@ -1,5 +1,5 @@
11 /*
2- * Copyright (c) 2009-2010 jMonkeyEngine
2+ * Copyright (c) 2009-2012 jMonkeyEngine
33 * All rights reserved.
44 *
55 * Redistribution and use in source and binary forms, with or without
@@ -29,7 +29,6 @@
2929 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
3030 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3131 */
32-
3332 package com.jme3.animation;
3433
3534 /**
--- a/engine/src/core/com/jme3/animation/Pose.java
+++ b/engine/src/core/com/jme3/animation/Pose.java
@@ -1,5 +1,5 @@
11 /*
2- * Copyright (c) 2009-2010 jMonkeyEngine
2+ * Copyright (c) 2009-2012 jMonkeyEngine
33 * All rights reserved.
44 *
55 * Redistribution and use in source and binary forms, with or without
@@ -29,19 +29,13 @@
2929 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
3030 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3131 */
32-
3332 package com.jme3.animation;
3433
35-import java.io.IOException;
36-import java.nio.FloatBuffer;
37-
38-import com.jme3.export.InputCapsule;
39-import com.jme3.export.JmeExporter;
40-import com.jme3.export.JmeImporter;
41-import com.jme3.export.OutputCapsule;
42-import com.jme3.export.Savable;
34+import com.jme3.export.*;
4335 import com.jme3.math.Vector3f;
4436 import com.jme3.util.BufferUtils;
37+import java.io.IOException;
38+import java.nio.FloatBuffer;
4539
4640 /**
4741 * A pose is a list of offsets that say where a mesh vertices should be for this pose.
@@ -64,6 +58,13 @@ public final class Pose implements Savable, Cloneable {
6458 this.indices = indices;
6559 }
6660
61+ /**
62+ * Serialization-only. Do not use.
63+ */
64+ public Pose()
65+ {
66+ }
67+
6768 public int getTargetMeshIndex(){
6869 return targetMeshIndex;
6970 }
@@ -97,21 +98,22 @@ public final class Pose implements Savable, Cloneable {
9798 * This method creates a clone of the current object.
9899 * @return a clone of the current object
99100 */
101+ @Override
100102 public Pose clone() {
101- try {
102- Pose result = (Pose) super.clone();
103+ try {
104+ Pose result = (Pose) super.clone();
103105 result.indices = this.indices.clone();
104- if(this.offsets!=null) {
105- result.offsets = new Vector3f[this.offsets.length];
106- for(int i=0;i<this.offsets.length;++i) {
107- result.offsets[i] = this.offsets[i].clone();
108- }
106+ if (this.offsets != null) {
107+ result.offsets = new Vector3f[this.offsets.length];
108+ for (int i = 0; i < this.offsets.length; ++i) {
109+ result.offsets[i] = this.offsets[i].clone();
110+ }
109111 }
110- return result;
112+ return result;
111113 } catch (CloneNotSupportedException e) {
112114 throw new AssertionError();
113115 }
114- }
116+ }
115117
116118 public void write(JmeExporter e) throws IOException {
117119 OutputCapsule out = e.getCapsule(this);
@@ -125,7 +127,12 @@ public final class Pose implements Savable, Cloneable {
125127 InputCapsule in = i.getCapsule(this);
126128 name = in.readString("name", "");
127129 targetMeshIndex = in.readInt("meshIndex", -1);
128- offsets = (Vector3f[]) in.readSavableArray("offsets", null);
129130 indices = in.readIntArray("indices", null);
131+
132+ Savable[] readSavableArray = in.readSavableArray("offsets", null);
133+ if (readSavableArray != null) {
134+ offsets = new Vector3f[readSavableArray.length];
135+ System.arraycopy(readSavableArray, 0, offsets, 0, readSavableArray.length);
136+ }
130137 }
131138 }
--- a/engine/src/core/com/jme3/animation/PoseTrack.java
+++ b/engine/src/core/com/jme3/animation/PoseTrack.java
@@ -1,5 +1,5 @@
11 /*
2- * Copyright (c) 2009-2010 jMonkeyEngine
2+ * Copyright (c) 2009-2012 jMonkeyEngine
33 * All rights reserved.
44 *
55 * Redistribution and use in source and binary forms, with or without
@@ -29,14 +29,9 @@
2929 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
3030 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3131 */
32-
3332 package com.jme3.animation;
3433
35-import com.jme3.export.JmeExporter;
36-import com.jme3.export.JmeImporter;
37-import com.jme3.export.InputCapsule;
38-import com.jme3.export.OutputCapsule;
39-import com.jme3.export.Savable;
34+import com.jme3.export.*;
4035 import com.jme3.scene.Mesh;
4136 import com.jme3.scene.VertexBuffer;
4237 import com.jme3.scene.VertexBuffer.Type;
@@ -65,6 +60,13 @@ public final class PoseTrack implements Track {
6560 }
6661
6762 /**
63+ * Serialization-only. Do not use.
64+ */
65+ public PoseFrame()
66+ {
67+ }
68+
69+ /**
6870 * This method creates a clone of the current object.
6971 * @return a clone of the current object
7072 */
@@ -93,8 +95,13 @@ public final class PoseTrack implements Track {
9395
9496 public void read(JmeImporter i) throws IOException {
9597 InputCapsule in = i.getCapsule(this);
96- poses = (Pose[]) in.readSavableArray("poses", null);
9798 weights = in.readFloatArray("weights", null);
99+
100+ Savable[] readSavableArray = in.readSavableArray("poses", null);
101+ if (readSavableArray != null) {
102+ poses = new Pose[readSavableArray.length];
103+ System.arraycopy(readSavableArray, 0, poses, 0, readSavableArray.length);
104+ }
98105 }
99106 }
100107
@@ -104,6 +111,13 @@ public final class PoseTrack implements Track {
104111 this.frames = frames;
105112 }
106113
114+ /**
115+ * Serialization-only. Do not use.
116+ */
117+ public PoseTrack()
118+ {
119+ }
120+
107121 private void applyFrame(Mesh target, int frameIndex, float weight){
108122 PoseFrame frame = frames[frameIndex];
109123 VertexBuffer pb = target.getBuffer(Type.Position);
@@ -184,7 +198,12 @@ public final class PoseTrack implements Track {
184198 public void read(JmeImporter i) throws IOException {
185199 InputCapsule in = i.getCapsule(this);
186200 targetMeshIndex = in.readInt("meshIndex", 0);
187- frames = (PoseFrame[]) in.readSavableArray("frames", null);
188201 times = in.readFloatArray("times", null);
202+
203+ Savable[] readSavableArray = in.readSavableArray("frames", null);
204+ if (readSavableArray != null) {
205+ frames = new PoseFrame[readSavableArray.length];
206+ System.arraycopy(readSavableArray, 0, frames, 0, readSavableArray.length);
207+ }
189208 }
190209 }
--- a/engine/src/core/com/jme3/animation/Skeleton.java
+++ b/engine/src/core/com/jme3/animation/Skeleton.java
@@ -1,5 +1,5 @@
11 /*
2- * Copyright (c) 2009-2010 jMonkeyEngine
2+ * Copyright (c) 2009-2012 jMonkeyEngine
33 * All rights reserved.
44 *
55 * Redistribution and use in source and binary forms, with or without
@@ -31,15 +31,10 @@
3131 */
3232 package com.jme3.animation;
3333
34-import com.jme3.export.JmeExporter;
35-import com.jme3.export.JmeImporter;
36-import com.jme3.export.InputCapsule;
37-import com.jme3.export.OutputCapsule;
38-import com.jme3.export.Savable;
34+import com.jme3.export.*;
3935 import com.jme3.math.Matrix4f;
4036 import com.jme3.util.TempVars;
4137 import java.io.IOException;
42-
4338 import java.util.ArrayList;
4439 import java.util.List;
4540
--- a/engine/src/core/com/jme3/animation/SkeletonControl.java
+++ b/engine/src/core/com/jme3/animation/SkeletonControl.java
@@ -1,55 +1,113 @@
11 /*
2- * To change this template, choose Tools | Templates
3- * and open the template in the editor.
2+ * Copyright (c) 2009-2012 jMonkeyEngine
3+ * All rights reserved.
4+ *
5+ * Redistribution and use in source and binary forms, with or without
6+ * modification, are permitted provided that the following conditions are
7+ * met:
8+ *
9+ * * Redistributions of source code must retain the above copyright
10+ * notice, this list of conditions and the following disclaimer.
11+ *
12+ * * Redistributions in binary form must reproduce the above copyright
13+ * notice, this list of conditions and the following disclaimer in the
14+ * documentation and/or other materials provided with the distribution.
15+ *
16+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17+ * may be used to endorse or promote products derived from this software
18+ * without specific prior written permission.
19+ *
20+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
431 */
532 package com.jme3.animation;
633
7-import com.jme3.export.InputCapsule;
8-import com.jme3.export.JmeExporter;
9-import com.jme3.export.JmeImporter;
10-import com.jme3.export.OutputCapsule;
11-import com.jme3.export.Savable;
34+import com.jme3.export.*;
35+import com.jme3.material.Material;
1236 import com.jme3.math.FastMath;
1337 import com.jme3.math.Matrix4f;
1438 import com.jme3.renderer.RenderManager;
39+import com.jme3.renderer.RendererException;
1540 import com.jme3.renderer.ViewPort;
16-import com.jme3.scene.Geometry;
17-import com.jme3.scene.Mesh;
18-import com.jme3.scene.Node;
19-import com.jme3.scene.Spatial;
20-import com.jme3.scene.UserData;
21-import com.jme3.scene.VertexBuffer;
41+import com.jme3.scene.*;
2242 import com.jme3.scene.VertexBuffer.Type;
2343 import com.jme3.scene.control.AbstractControl;
2444 import com.jme3.scene.control.Control;
45+import com.jme3.shader.VarType;
46+import com.jme3.util.SafeArrayList;
2547 import com.jme3.util.TempVars;
2648 import java.io.IOException;
49+import java.nio.Buffer;
2750 import java.nio.ByteBuffer;
2851 import java.nio.FloatBuffer;
29-import java.util.ArrayList;
52+import java.util.Arrays;
53+import java.util.HashSet;
54+import java.util.Set;
55+import java.util.logging.Level;
56+import java.util.logging.Logger;
3057
3158 /**
32- * The Skeleton control deforms a model according to a skeleton,
33- * It handles the computation of the deformation matrices and performs
34- * the transformations on the mesh
35- *
59+ * The Skeleton control deforms a model according to a skeleton, It handles the
60+ * computation of the deformation matrices and performs the transformations on
61+ * the mesh
62+ *
3663 * @author Rémy Bouquet Based on AnimControl by Kirill Vainer
3764 */
3865 public class SkeletonControl extends AbstractControl implements Cloneable {
3966
4067 /**
41- * The skeleton of the model
68+ * The skeleton of the model.
4269 */
4370 private Skeleton skeleton;
4471 /**
4572 * List of targets which this controller effects.
4673 */
47- private Mesh[] targets;
74+ private SafeArrayList<Mesh> targets = new SafeArrayList<Mesh>(Mesh.class);
4875 /**
49- * Used to track when a mesh was updated. Meshes are only updated
50- * if they are visible in at least one camera.
76+ * Used to track when a mesh was updated. Meshes are only updated if they
77+ * are visible in at least one camera.
5178 */
5279 private boolean wasMeshUpdated = false;
80+
81+ /**
82+ * User wishes to use hardware skinning if available.
83+ */
84+ private transient boolean hwSkinningDesired = false;
85+
86+ /**
87+ * Hardware skinning is currently being used.
88+ */
89+ private transient boolean hwSkinningEnabled = false;
90+
91+ /**
92+ * Hardware skinning was tested on this GPU, results
93+ * are stored in {@link #hwSkinningSupported} variable.
94+ */
95+ private transient boolean hwSkinningTested = false;
96+
97+ /**
98+ * If hardware skinning was {@link #hwSkinningTested tested}, then
99+ * this variable will be set to true if supported, and false if otherwise.
100+ */
101+ private transient boolean hwSkinningSupported = false;
102+
103+ /**
104+ * Bone offset matrices, recreated each frame
105+ */
106+ private transient Matrix4f[] offsetMatrices;
107+ /**
108+ * Material references used for hardware skinning
109+ */
110+ private Set<Material> materials = new HashSet<Material>();
53111
54112 /**
55113 * Serialization only. Do not use.
@@ -57,100 +115,207 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
57115 public SkeletonControl() {
58116 }
59117
118+ private void switchToHardware() {
119+ // Next full 10 bones (e.g. 30 on 24 bones)
120+ int numBones = ((skeleton.getBoneCount() / 10) + 1) * 10;
121+ for (Material m : materials) {
122+ m.setInt("NumberOfBones", numBones);
123+ }
124+ for (Mesh mesh : targets) {
125+ if (mesh.isAnimated()) {
126+ mesh.prepareForAnim(false);
127+ }
128+ }
129+ }
130+
131+ private void switchToSoftware() {
132+ for (Material m : materials) {
133+ if (m.getParam("NumberOfBones") != null) {
134+ m.clearParam("NumberOfBones");
135+ }
136+ }
137+ for (Mesh mesh : targets) {
138+ if (mesh.isAnimated()) {
139+ mesh.prepareForAnim(true);
140+ }
141+ }
142+ }
143+
144+ private boolean testHardwareSupported(RenderManager rm) {
145+ for (Material m : materials) {
146+ // Some of the animated mesh(es) do not support hardware skinning,
147+ // so it is not supported by the model.
148+ if (m.getMaterialDef().getMaterialParam("NumberOfBones") == null) {
149+ Logger.getLogger(SkeletonControl.class.getName()).log(Level.WARNING,
150+ "Not using hardware skinning for {0}, " +
151+ "because material {1} doesn''t support it.",
152+ new Object[]{spatial, m.getMaterialDef().getName()});
153+
154+ return false;
155+ }
156+ }
157+
158+ switchToHardware();
159+
160+ try {
161+ rm.preloadScene(spatial);
162+ return true;
163+ } catch (RendererException e) {
164+ Logger.getLogger(SkeletonControl.class.getName()).log(Level.WARNING, "Could not enable HW skinning due to shader compile error:", e);
165+ return false;
166+ }
167+ }
168+
60169 /**
61- * Creates a skeleton control.
62- * The list of targets will be acquired automatically when
63- * the control is attached to a node.
170+ * Specifies if hardware skinning is preferred. If it is preferred and
171+ * supported by GPU, it shall be enabled, if its not preferred, or not
172+ * supported by GPU, then it shall be disabled.
173+ *
174+ * @see #isHardwareSkinningUsed()
175+ */
176+ public void setHardwareSkinningPreferred(boolean preferred) {
177+ hwSkinningDesired = preferred;
178+ }
179+
180+ /**
181+ * @return True if hardware skinning is preferable to software skinning.
182+ * Set to false by default.
64183 *
184+ * @see #setHardwareSkinningPreferred(boolean)
185+ */
186+ public boolean isHardwareSkinningPreferred() {
187+ return hwSkinningDesired;
188+ }
189+
190+ /**
191+ * @return True is hardware skinning is activated and is currently used, false otherwise.
192+ */
193+ public boolean isHardwareSkinningUsed() {
194+ return hwSkinningEnabled;
195+ }
196+
197+ /**
198+ * Creates a skeleton control. The list of targets will be acquired
199+ * automatically when the control is attached to a node.
200+ *
65201 * @param skeleton the skeleton
66202 */
67203 public SkeletonControl(Skeleton skeleton) {
68204 this.skeleton = skeleton;
69205 }
70-
206+
71207 /**
72208 * Creates a skeleton control.
73- *
209+ *
74210 * @param targets the meshes controlled by the skeleton
75211 * @param skeleton the skeleton
76212 */
77213 @Deprecated
78- SkeletonControl(Mesh[] targets, Skeleton skeleton){
214+ SkeletonControl(Mesh[] targets, Skeleton skeleton) {
79215 this.skeleton = skeleton;
80- this.targets = targets;
81- }
82-
83- private boolean isMeshAnimated(Mesh mesh){
84- return mesh.getBuffer(Type.BindPosePosition) != null;
216+ this.targets = new SafeArrayList<Mesh>(Mesh.class, Arrays.asList(targets));
85217 }
86218
87- private Mesh[] findTargets(Node node){
88- Mesh sharedMesh = null;
89- ArrayList<Mesh> animatedMeshes = new ArrayList<Mesh>();
90-
91- for (Spatial child : node.getChildren()){
92- if (!(child instanceof Geometry)){
93- continue; // could be an attachment node, ignore.
94- }
95-
96- Geometry geom = (Geometry) child;
97-
98- // is this geometry using a shared mesh?
99- Mesh childSharedMesh = geom.getUserData(UserData.JME_SHAREDMESH);
100-
101- if (childSharedMesh != null){
102- // Don't bother with non-animated shared meshes
103- if (isMeshAnimated(childSharedMesh)){
104- // child is using shared mesh,
105- // so animate the shared mesh but ignore child
106- if (sharedMesh == null){
107- sharedMesh = childSharedMesh;
108- }else if (sharedMesh != childSharedMesh){
109- throw new IllegalStateException("Two conflicting shared meshes for " + node);
219+
220+ private void findTargets(Node node) {
221+ Mesh sharedMesh = null;
222+
223+ for (Spatial child : node.getChildren()) {
224+ if (child instanceof Geometry) {
225+ Geometry geom = (Geometry) child;
226+
227+ // is this geometry using a shared mesh?
228+ Mesh childSharedMesh = geom.getUserData(UserData.JME_SHAREDMESH);
229+
230+ if (childSharedMesh != null) {
231+ // Don’t bother with non-animated shared meshes
232+ if (childSharedMesh.isAnimated()) {
233+ // child is using shared mesh,
234+ // so animate the shared mesh but ignore child
235+ if (sharedMesh == null) {
236+ sharedMesh = childSharedMesh;
237+ } else if (sharedMesh != childSharedMesh) {
238+ throw new IllegalStateException("Two conflicting shared meshes for " + node);
239+ }
240+ materials.add(geom.getMaterial());
241+ }
242+ } else {
243+ Mesh mesh = geom.getMesh();
244+ if (mesh.isAnimated()) {
245+ targets.add(mesh);
246+ materials.add(geom.getMaterial());
110247 }
111248 }
112- }else{
113- Mesh mesh = geom.getMesh();
114- if (isMeshAnimated(mesh)){
115- animatedMeshes.add(mesh);
116- }
249+ } else if (child instanceof Node) {
250+ findTargets((Node) child);
117251 }
118252 }
119-
120- if (sharedMesh != null){
121- animatedMeshes.add(sharedMesh);
253+
254+ if (sharedMesh != null) {
255+ targets.add(sharedMesh);
122256 }
123-
124- return animatedMeshes.toArray(new Mesh[animatedMeshes.size()]);
257+
125258 }
126-
259+
127260 @Override
128- public void setSpatial(Spatial spatial){
261+ public void setSpatial(Spatial spatial) {
129262 super.setSpatial(spatial);
130- if (spatial != null){
131- Node node = (Node) spatial;
132- targets = findTargets(node);
133- }else{
134- targets = null;
263+ updateTargetsAndMaterials(spatial);
264+ }
265+
266+ private void controlRenderSoftware() {
267+ resetToBind(); // reset morph meshes to bind pose
268+
269+ offsetMatrices = skeleton.computeSkinningMatrices();
270+
271+ for (Mesh mesh : targets) {
272+ // NOTE: This assumes that code higher up
273+ // Already ensured those targets are animated
274+ // otherwise a crash will happen in skin update
275+ softwareSkinUpdate(mesh, offsetMatrices);
276+ }
277+ }
278+
279+ private void controlRenderHardware() {
280+ offsetMatrices = skeleton.computeSkinningMatrices();
281+ for (Material m : materials) {
282+ m.setParam("BoneMatrices", VarType.Matrix4Array, offsetMatrices);
135283 }
136284 }
285+
137286
138287 @Override
139288 protected void controlRender(RenderManager rm, ViewPort vp) {
140289 if (!wasMeshUpdated) {
141- resetToBind(); // reset morph meshes to bind pose
142-
143- Matrix4f[] offsetMatrices = skeleton.computeSkinningMatrices();
144-
145- // if hardware skinning is supported, the matrices and weight buffer
146- // will be sent by the SkinningShaderLogic object assigned to the shader
147- for (int i = 0; i < targets.length; i++) {
148- // NOTE: This assumes that code higher up
149- // Already ensured those targets are animated
150- // otherwise a crash will happen in skin update
151- //if (isMeshAnimated(targets[i])) {
152- softwareSkinUpdate(targets[i], offsetMatrices);
153- //}
290+ updateTargetsAndMaterials(spatial);
291+
292+ // Prevent illegal cases. These should never happen.
293+ assert hwSkinningTested || (!hwSkinningTested && !hwSkinningSupported && !hwSkinningEnabled);
294+ assert !hwSkinningEnabled || (hwSkinningEnabled && hwSkinningTested && hwSkinningSupported);
295+
296+ if (hwSkinningDesired && !hwSkinningTested) {
297+ hwSkinningTested = true;
298+ hwSkinningSupported = testHardwareSupported(rm);
299+
300+ if (hwSkinningSupported) {
301+ hwSkinningEnabled = true;
302+
303+ Logger.getLogger(SkeletonControl.class.getName()).log(Level.INFO, "Hardware skinning engaged for " + spatial);
304+ } else {
305+ switchToSoftware();
306+ }
307+ } else if (hwSkinningDesired && hwSkinningSupported && !hwSkinningEnabled) {
308+ switchToHardware();
309+ hwSkinningEnabled = true;
310+ } else if (!hwSkinningDesired && hwSkinningEnabled) {
311+ switchToSoftware();
312+ hwSkinningEnabled = false;
313+ }
314+
315+ if (hwSkinningEnabled) {
316+ controlRenderHardware();
317+ } else {
318+ controlRenderSoftware();
154319 }
155320
156321 wasMeshUpdated = true;
@@ -160,14 +325,15 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
160325 @Override
161326 protected void controlUpdate(float tpf) {
162327 wasMeshUpdated = false;
163- }
328+ }
164329
330+ //only do this for software updates
165331 void resetToBind() {
166- for (Mesh mesh : targets){
167- if (isMeshAnimated(mesh)) {
168- VertexBuffer bi = mesh.getBuffer(Type.BoneIndex);
169- ByteBuffer bib = (ByteBuffer) bi.getData();
170- if (!bib.hasArray()) {
332+ for (Mesh mesh : targets) {
333+ if (mesh.isAnimated()) {
334+ Buffer bwBuff = mesh.getBuffer(Type.BoneWeight).getData();
335+ Buffer biBuff = mesh.getBuffer(Type.BoneIndex).getData();
336+ if (!biBuff.hasArray() || !bwBuff.hasArray()) {
171337 mesh.prepareForAnim(true); // prepare for software animation
172338 }
173339 VertexBuffer bindPos = mesh.getBuffer(Type.BindPosePosition);
@@ -182,6 +348,19 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
182348 nb.clear();
183349 bpb.clear();
184350 bnb.clear();
351+
352+ //reseting bind tangents if there is a bind tangent buffer
353+ VertexBuffer bindTangents = mesh.getBuffer(Type.BindPoseTangent);
354+ if (bindTangents != null) {
355+ VertexBuffer tangents = mesh.getBuffer(Type.Tangent);
356+ FloatBuffer tb = (FloatBuffer) tangents.getData();
357+ FloatBuffer btb = (FloatBuffer) bindTangents.getData();
358+ tb.clear();
359+ btb.clear();
360+ tb.put(btb).clear();
361+ }
362+
363+
185364 pb.put(bpb).clear();
186365 nb.put(bnb).clear();
187366 }
@@ -192,13 +371,12 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
192371 Node clonedNode = (Node) spatial;
193372 AnimControl ctrl = spatial.getControl(AnimControl.class);
194373 SkeletonControl clone = new SkeletonControl();
195- clone.setSpatial(clonedNode);
196374
197375 clone.skeleton = ctrl.getSkeleton();
198- // Fix animated targets for the cloned node
199- clone.targets = findTargets(clonedNode);
200376
201- // Fix attachments for the cloned node
377+ clone.setSpatial(clonedNode);
378+
379+ // Fix attachments for the cloned node
202380 for (int i = 0; i < clonedNode.getQuantity(); i++) {
203381 // go through attachment nodes, apply them to correct bone
204382 Spatial child = clonedNode.getChild(i);
@@ -219,9 +397,9 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
219397 }
220398
221399 /**
222- *
400+ *
223401 * @param boneName the name of the bone
224- * @return the node attached to this bone
402+ * @return the node attached to this bone
225403 */
226404 public Node getAttachmentsNode(String boneName) {
227405 Bone b = skeleton.getBone(boneName);
@@ -238,38 +416,162 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
238416
239417 /**
240418 * returns the skeleton of this control
241- * @return
419+ *
420+ * @return
242421 */
243422 public Skeleton getSkeleton() {
244423 return skeleton;
245424 }
246425
247426 /**
248- * sets the skeleton for this control
249- * @param skeleton
427+ * returns a copy of array of the targets meshes of this control
428+ *
429+ * @return
250430 */
251-// public void setSkeleton(Skeleton skeleton) {
252-// this.skeleton = skeleton;
253-// }
431+ public Mesh[] getTargets() {
432+ return targets.toArray(new Mesh[targets.size()]);
433+ }
254434
255435 /**
256- * returns the targets meshes of this control
257- * @return
436+ * Update the mesh according to the given transformation matrices
437+ *
438+ * @param mesh then mesh
439+ * @param offsetMatrices the transformation matrices to apply
258440 */
259- public Mesh[] getTargets() {
260- return targets;
441+ private void softwareSkinUpdate(Mesh mesh, Matrix4f[] offsetMatrices) {
442+
443+ VertexBuffer tb = mesh.getBuffer(Type.Tangent);
444+ if (tb == null) {
445+ //if there are no tangents use the classic skinning
446+ applySkinning(mesh, offsetMatrices);
447+ } else {
448+ //if there are tangents use the skinning with tangents
449+ applySkinningTangents(mesh, offsetMatrices, tb);
450+ }
451+
452+
261453 }
262454
263455 /**
264- * sets the target meshes of this control
265- * @param targets
456+ * Method to apply skinning transforms to a mesh's buffers
457+ *
458+ * @param mesh the mesh
459+ * @param offsetMatrices the offset matices to apply
266460 */
267-// public void setTargets(Mesh[] targets) {
268-// this.targets = targets;
269-// }
461+ private void applySkinning(Mesh mesh, Matrix4f[] offsetMatrices) {
462+ int maxWeightsPerVert = mesh.getMaxNumWeights();
463+ if (maxWeightsPerVert <= 0) {
464+ throw new IllegalStateException("Max weights per vert is incorrectly set!");
465+ }
270466
271- private void softwareSkinUpdate(Mesh mesh, Matrix4f[] offsetMatrices) {
467+ int fourMinusMaxWeights = 4 - maxWeightsPerVert;
468+
469+ // NOTE: This code assumes the vertex buffer is in bind pose
470+ // resetToBind() has been called this frame
471+ VertexBuffer vb = mesh.getBuffer(Type.Position);
472+ FloatBuffer fvb = (FloatBuffer) vb.getData();
473+ fvb.rewind();
474+
475+ VertexBuffer nb = mesh.getBuffer(Type.Normal);
476+ FloatBuffer fnb = (FloatBuffer) nb.getData();
477+ fnb.rewind();
478+
479+ // get boneIndexes and weights for mesh
480+ ByteBuffer ib = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData();
481+ FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData();
482+
483+ ib.rewind();
484+ wb.rewind();
485+
486+ float[] weights = wb.array();
487+ byte[] indices = ib.array();
488+ int idxWeights = 0;
489+
490+ TempVars vars = TempVars.get();
491+
492+ float[] posBuf = vars.skinPositions;
493+ float[] normBuf = vars.skinNormals;
494+
495+ int iterations = (int) FastMath.ceil(fvb.limit() / ((float) posBuf.length));
496+ int bufLength = posBuf.length;
497+ for (int i = iterations - 1; i >= 0; i--) {
498+ // read next set of positions and normals from native buffer
499+ bufLength = Math.min(posBuf.length, fvb.remaining());
500+ fvb.get(posBuf, 0, bufLength);
501+ fnb.get(normBuf, 0, bufLength);
502+ int verts = bufLength / 3;
503+ int idxPositions = 0;
504+
505+ // iterate vertices and apply skinning transform for each effecting bone
506+ for (int vert = verts - 1; vert >= 0; vert--) {
507+ // Skip this vertex if the first weight is zero.
508+ if (weights[idxWeights] == 0) {
509+ idxPositions += 3;
510+ idxWeights += 4;
511+ continue;
512+ }
513+
514+ float nmx = normBuf[idxPositions];
515+ float vtx = posBuf[idxPositions++];
516+ float nmy = normBuf[idxPositions];
517+ float vty = posBuf[idxPositions++];
518+ float nmz = normBuf[idxPositions];
519+ float vtz = posBuf[idxPositions++];
520+
521+ float rx = 0, ry = 0, rz = 0, rnx = 0, rny = 0, rnz = 0;
522+
523+ for (int w = maxWeightsPerVert - 1; w >= 0; w--) {
524+ float weight = weights[idxWeights];
525+ Matrix4f mat = offsetMatrices[indices[idxWeights++] & 0xff];
526+
527+ rx += (mat.m00 * vtx + mat.m01 * vty + mat.m02 * vtz + mat.m03) * weight;
528+ ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight;
529+ rz += (mat.m20 * vtx + mat.m21 * vty + mat.m22 * vtz + mat.m23) * weight;
530+
531+ rnx += (nmx * mat.m00 + nmy * mat.m01 + nmz * mat.m02) * weight;
532+ rny += (nmx * mat.m10 + nmy * mat.m11 + nmz * mat.m12) * weight;
533+ rnz += (nmx * mat.m20 + nmy * mat.m21 + nmz * mat.m22) * weight;
534+ }
535+
536+ idxWeights += fourMinusMaxWeights;
537+
538+ idxPositions -= 3;
539+ normBuf[idxPositions] = rnx;
540+ posBuf[idxPositions++] = rx;
541+ normBuf[idxPositions] = rny;
542+ posBuf[idxPositions++] = ry;
543+ normBuf[idxPositions] = rnz;
544+ posBuf[idxPositions++] = rz;
545+ }
546+
547+ fvb.position(fvb.position() - bufLength);
548+ fvb.put(posBuf, 0, bufLength);
549+ fnb.position(fnb.position() - bufLength);
550+ fnb.put(normBuf, 0, bufLength);
551+ }
552+
553+ vars.release();
554+
555+ vb.updateData(fvb);
556+ nb.updateData(fnb);
557+
558+ }
559+
560+ /**
561+ * Specific method for skinning with tangents to avoid cluttering the
562+ * classic skinning calculation with null checks that would slow down the
563+ * process even if tangents don't have to be computed. Also the iteration
564+ * has additional indexes since tangent has 4 components instead of 3 for
565+ * pos and norm
566+ *
567+ * @param maxWeightsPerVert maximum number of weights per vertex
568+ * @param mesh the mesh
569+ * @param offsetMatrices the offsetMaytrices to apply
570+ * @param tb the tangent vertexBuffer
571+ */
572+ private void applySkinningTangents(Mesh mesh, Matrix4f[] offsetMatrices, VertexBuffer tb) {
272573 int maxWeightsPerVert = mesh.getMaxNumWeights();
574+
273575 if (maxWeightsPerVert <= 0) {
274576 throw new IllegalStateException("Max weights per vert is incorrectly set!");
275577 }
@@ -283,9 +585,15 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
283585 fvb.rewind();
284586
285587 VertexBuffer nb = mesh.getBuffer(Type.Normal);
588+
286589 FloatBuffer fnb = (FloatBuffer) nb.getData();
287590 fnb.rewind();
288591
592+
593+ FloatBuffer ftb = (FloatBuffer) tb.getData();
594+ ftb.rewind();
595+
596+
289597 // get boneIndexes and weights for mesh
290598 ByteBuffer ib = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData();
291599 FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData();
@@ -302,19 +610,33 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
302610
303611 float[] posBuf = vars.skinPositions;
304612 float[] normBuf = vars.skinNormals;
613+ float[] tanBuf = vars.skinTangents;
305614
306- int iterations = (int) FastMath.ceil(fvb.capacity() / ((float) posBuf.length));
307- int bufLength = posBuf.length * 3;
615+ int iterations = (int) FastMath.ceil(fvb.limit() / ((float) posBuf.length));
616+ int bufLength = 0;
617+ int tanLength = 0;
308618 for (int i = iterations - 1; i >= 0; i--) {
309619 // read next set of positions and normals from native buffer
310620 bufLength = Math.min(posBuf.length, fvb.remaining());
621+ tanLength = Math.min(tanBuf.length, ftb.remaining());
311622 fvb.get(posBuf, 0, bufLength);
312623 fnb.get(normBuf, 0, bufLength);
624+ ftb.get(tanBuf, 0, tanLength);
313625 int verts = bufLength / 3;
314626 int idxPositions = 0;
627+ //tangents has their own index because of the 4 components
628+ int idxTangents = 0;
315629
316630 // iterate vertices and apply skinning transform for each effecting bone
317631 for (int vert = verts - 1; vert >= 0; vert--) {
632+ // Skip this vertex if the first weight is zero.
633+ if (weights[idxWeights] == 0) {
634+ idxTangents += 4;
635+ idxPositions += 3;
636+ idxWeights += 4;
637+ continue;
638+ }
639+
318640 float nmx = normBuf[idxPositions];
319641 float vtx = posBuf[idxPositions++];
320642 float nmy = normBuf[idxPositions];
@@ -322,7 +644,14 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
322644 float nmz = normBuf[idxPositions];
323645 float vtz = posBuf[idxPositions++];
324646
325- float rx = 0, ry = 0, rz = 0, rnx = 0, rny = 0, rnz = 0;
647+ float tnx = tanBuf[idxTangents++];
648+ float tny = tanBuf[idxTangents++];
649+ float tnz = tanBuf[idxTangents++];
650+
651+ // skipping the 4th component of the tangent since it doesn't have to be transformed
652+ idxTangents++;
653+
654+ float rx = 0, ry = 0, rz = 0, rnx = 0, rny = 0, rnz = 0, rtx = 0, rty = 0, rtz = 0;
326655
327656 for (int w = maxWeightsPerVert - 1; w >= 0; w--) {
328657 float weight = weights[idxWeights];
@@ -335,50 +664,71 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
335664 rnx += (nmx * mat.m00 + nmy * mat.m01 + nmz * mat.m02) * weight;
336665 rny += (nmx * mat.m10 + nmy * mat.m11 + nmz * mat.m12) * weight;
337666 rnz += (nmx * mat.m20 + nmy * mat.m21 + nmz * mat.m22) * weight;
667+
668+ rtx += (tnx * mat.m00 + tny * mat.m01 + tnz * mat.m02) * weight;
669+ rty += (tnx * mat.m10 + tny * mat.m11 + tnz * mat.m12) * weight;
670+ rtz += (tnx * mat.m20 + tny * mat.m21 + tnz * mat.m22) * weight;
338671 }
339672
340673 idxWeights += fourMinusMaxWeights;
341674
342675 idxPositions -= 3;
676+
343677 normBuf[idxPositions] = rnx;
344678 posBuf[idxPositions++] = rx;
345679 normBuf[idxPositions] = rny;
346680 posBuf[idxPositions++] = ry;
347681 normBuf[idxPositions] = rnz;
348682 posBuf[idxPositions++] = rz;
683+
684+ idxTangents -= 4;
685+
686+ tanBuf[idxTangents++] = rtx;
687+ tanBuf[idxTangents++] = rty;
688+ tanBuf[idxTangents++] = rtz;
689+
690+ //once again skipping the 4th component of the tangent
691+ idxTangents++;
349692 }
350693
351694 fvb.position(fvb.position() - bufLength);
352695 fvb.put(posBuf, 0, bufLength);
353696 fnb.position(fnb.position() - bufLength);
354697 fnb.put(normBuf, 0, bufLength);
698+ ftb.position(ftb.position() - tanLength);
699+ ftb.put(tanBuf, 0, tanLength);
355700 }
356701
357702 vars.release();
358703
359704 vb.updateData(fvb);
360705 nb.updateData(fnb);
706+ tb.updateData(ftb);
707+
361708
362-// mesh.updateBound();
363709 }
364710
365711 @Override
366712 public void write(JmeExporter ex) throws IOException {
367713 super.write(ex);
368714 OutputCapsule oc = ex.getCapsule(this);
369- oc.write(targets, "targets", null);
370715 oc.write(skeleton, "skeleton", null);
716+ //Targets and materials don't need to be saved, they'll be gathered on each frame
371717 }
372718
373719 @Override
374720 public void read(JmeImporter im) throws IOException {
375721 super.read(im);
376722 InputCapsule in = im.getCapsule(this);
377- Savable[] sav = in.readSavableArray("targets", null);
378- if (sav != null) {
379- targets = new Mesh[sav.length];
380- System.arraycopy(sav, 0, targets, 0, sav.length);
381- }
382723 skeleton = (Skeleton) in.readSavable("skeleton", null);
383724 }
725+
726+ private void updateTargetsAndMaterials(Spatial spatial) {
727+ targets.clear();
728+ materials.clear();
729+ if (spatial != null && spatial instanceof Node) {
730+ Node node = (Node) spatial;
731+ findTargets(node);
732+ }
733+ }
384734 }
--- a/engine/src/core/com/jme3/animation/SpatialAnimation.java
+++ b/engine/src/core/com/jme3/animation/SpatialAnimation.java
@@ -1,3 +1,34 @@
1+/*
2+ * Copyright (c) 2009-2012 jMonkeyEngine
3+ * All rights reserved.
4+ *
5+ * Redistribution and use in source and binary forms, with or without
6+ * modification, are permitted provided that the following conditions are
7+ * met:
8+ *
9+ * * Redistributions of source code must retain the above copyright
10+ * notice, this list of conditions and the following disclaimer.
11+ *
12+ * * Redistributions in binary form must reproduce the above copyright
13+ * notice, this list of conditions and the following disclaimer in the
14+ * documentation and/or other materials provided with the distribution.
15+ *
16+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17+ * may be used to endorse or promote products derived from this software
18+ * without specific prior written permission.
19+ *
20+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+ */
132 package com.jme3.animation;
233
334 /**
--- a/engine/src/core/com/jme3/animation/SpatialTrack.java
+++ b/engine/src/core/com/jme3/animation/SpatialTrack.java
@@ -1,7 +1,36 @@
1+/*
2+ * Copyright (c) 2009-2012 jMonkeyEngine
3+ * All rights reserved.
4+ *
5+ * Redistribution and use in source and binary forms, with or without
6+ * modification, are permitted provided that the following conditions are
7+ * met:
8+ *
9+ * * Redistributions of source code must retain the above copyright
10+ * notice, this list of conditions and the following disclaimer.
11+ *
12+ * * Redistributions in binary form must reproduce the above copyright
13+ * notice, this list of conditions and the following disclaimer in the
14+ * documentation and/or other materials provided with the distribution.
15+ *
16+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17+ * may be used to endorse or promote products derived from this software
18+ * without specific prior written permission.
19+ *
20+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+ */
132 package com.jme3.animation;
233
3-import java.io.IOException;
4-
534 import com.jme3.export.InputCapsule;
635 import com.jme3.export.JmeExporter;
736 import com.jme3.export.JmeImporter;
@@ -10,6 +39,8 @@ import com.jme3.math.Quaternion;
1039 import com.jme3.math.Vector3f;
1140 import com.jme3.scene.Spatial;
1241 import com.jme3.util.TempVars;
42+import java.io.IOException;
43+import java.util.Arrays;
1344
1445 /**
1546 * This class represents the track for spatial animation.
@@ -64,8 +95,6 @@ public class SpatialTrack implements Track {
6495 *
6596 * @param time
6697 * the current time of the animation
67- * @param spatial
68- * the spatial that should be animated with this track
6998 */
7099 public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) {
71100 Spatial spatial = control.getSpatial();
@@ -79,14 +108,18 @@ public class SpatialTrack implements Track {
79108
80109 int lastFrame = times.length - 1;
81110 if (time < 0 || lastFrame == 0) {
82- rotations.get(0, tempQ);
83- translations.get(0, tempV);
111+ if (rotations != null)
112+ rotations.get(0, tempQ);
113+ if (translations != null)
114+ translations.get(0, tempV);
84115 if (scales != null) {
85116 scales.get(0, tempS);
86117 }
87118 } else if (time >= times[lastFrame]) {
88- rotations.get(lastFrame, tempQ);
89- translations.get(lastFrame, tempV);
119+ if (rotations != null)
120+ rotations.get(lastFrame, tempQ);
121+ if (translations != null)
122+ translations.get(lastFrame, tempV);
90123 if (scales != null) {
91124 scales.get(lastFrame, tempS);
92125 }
@@ -101,13 +134,17 @@ public class SpatialTrack implements Track {
101134
102135 float blend = (time - times[startFrame]) / (times[endFrame] - times[startFrame]);
103136
104- rotations.get(startFrame, tempQ);
105- translations.get(startFrame, tempV);
137+ if (rotations != null)
138+ rotations.get(startFrame, tempQ);
139+ if (translations != null)
140+ translations.get(startFrame, tempV);
106141 if (scales != null) {
107142 scales.get(startFrame, tempS);
108143 }
109- rotations.get(endFrame, tempQ2);
110- translations.get(endFrame, tempV2);
144+ if (rotations != null)
145+ rotations.get(endFrame, tempQ2);
146+ if (translations != null)
147+ translations.get(endFrame, tempV2);
111148 if (scales != null) {
112149 scales.get(endFrame, tempS2);
113150 }
@@ -116,8 +153,10 @@ public class SpatialTrack implements Track {
116153 tempS.interpolate(tempS2, blend);
117154 }
118155
119- spatial.setLocalTranslation(tempV);
120- spatial.setLocalRotation(tempQ);
156+ if (translations != null)
157+ spatial.setLocalTranslation(tempV);
158+ if (rotations != null)
159+ spatial.setLocalRotation(tempQ);
121160 if (scales != null) {
122161 spatial.setLocalScale(tempS);
123162 }
@@ -141,25 +180,24 @@ public class SpatialTrack implements Track {
141180 throw new RuntimeException("BoneTrack with no keyframes!");
142181 }
143182
144- assert times.length == translations.length
145- && times.length == rotations.length;
146-
147183 this.times = times;
148- this.translations = new CompactVector3Array();
149- this.translations.add(translations);
150- this.translations.freeze();
151- this.rotations = new CompactQuaternionArray();
152- this.rotations.add(rotations);
153- this.rotations.freeze();
154-
184+ if (translations != null) {
185+ assert times.length == translations.length;
186+ this.translations = new CompactVector3Array();
187+ this.translations.add(translations);
188+ this.translations.freeze();
189+ }
190+ if (rotations != null) {
191+ assert times.length == rotations.length;
192+ this.rotations = new CompactQuaternionArray();
193+ this.rotations.add(rotations);
194+ this.rotations.freeze();
195+ }
155196 if (scales != null) {
156197 assert times.length == scales.length;
157-
158198 this.scales = new CompactVector3Array();
159199 this.scales.add(scales);
160200 this.scales.freeze();
161-
162-
163201 }
164202 }
165203
@@ -167,7 +205,7 @@ public class SpatialTrack implements Track {
167205 * @return the array of rotations of this track
168206 */
169207 public Quaternion[] getRotations() {
170- return rotations.toObjectArray();
208+ return rotations == null ? null : rotations.toObjectArray();
171209 }
172210
173211 /**
@@ -188,7 +226,7 @@ public class SpatialTrack implements Track {
188226 * @return the array of translations of this track
189227 */
190228 public Vector3f[] getTranslations() {
191- return translations.toObjectArray();
229+ return translations == null ? null : translations.toObjectArray();
192230 }
193231
194232 /**
@@ -206,21 +244,13 @@ public class SpatialTrack implements Track {
206244 public SpatialTrack clone() {
207245 int tablesLength = times.length;
208246
209- float[] times = this.times.clone();
210- Vector3f[] sourceTranslations = this.getTranslations();
211- Quaternion[] sourceRotations = this.getRotations();
212- Vector3f[] sourceScales = this.getScales();
213-
214- Vector3f[] translations = new Vector3f[tablesLength];
215- Quaternion[] rotations = new Quaternion[tablesLength];
216- Vector3f[] scales = new Vector3f[tablesLength];
217- for (int i = 0; i < tablesLength; ++i) {
218- translations[i] = sourceTranslations[i].clone();
219- rotations[i] = sourceRotations[i].clone();
220- scales[i] = sourceScales != null ? sourceScales[i].clone() : new Vector3f(1.0f, 1.0f, 1.0f);
221- }
247+ float[] timesCopy = this.times.clone();
248+ Vector3f[] translationsCopy = this.getTranslations() == null ? null : Arrays.copyOf(this.getTranslations(), tablesLength);
249+ Quaternion[] rotationsCopy = this.getRotations() == null ? null : Arrays.copyOf(this.getRotations(), tablesLength);
250+ Vector3f[] scalesCopy = this.getScales() == null ? null : Arrays.copyOf(this.getScales(), tablesLength);
251+
222252 //need to use the constructor here because of the final fields used in this class
223- return new SpatialTrack(times, translations, rotations, scales);
253+ return new SpatialTrack(timesCopy, translationsCopy, rotationsCopy, scalesCopy);
224254 }
225255
226256 @Override
--- a/engine/src/core/com/jme3/animation/Track.java
+++ b/engine/src/core/com/jme3/animation/Track.java
@@ -1,5 +1,5 @@
11 /*
2- * Copyright (c) 2009-2010 jMonkeyEngine
2+ * Copyright (c) 2009-2012 jMonkeyEngine
33 * All rights reserved.
44 *
55 * Redistribution and use in source and binary forms, with or without
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/TrackInfo.java
@@ -0,0 +1,75 @@
1+/*
2+ * Copyright (c) 2009-2012 jMonkeyEngine
3+ * All rights reserved.
4+ *
5+ * Redistribution and use in source and binary forms, with or without
6+ * modification, are permitted provided that the following conditions are
7+ * met:
8+ *
9+ * * Redistributions of source code must retain the above copyright
10+ * notice, this list of conditions and the following disclaimer.
11+ *
12+ * * Redistributions in binary form must reproduce the above copyright
13+ * notice, this list of conditions and the following disclaimer in the
14+ * documentation and/or other materials provided with the distribution.
15+ *
16+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17+ * may be used to endorse or promote products derived from this software
18+ * without specific prior written permission.
19+ *
20+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+ */
32+package com.jme3.animation;
33+
34+import com.jme3.export.InputCapsule;
35+import com.jme3.export.JmeExporter;
36+import com.jme3.export.JmeImporter;
37+import com.jme3.export.OutputCapsule;
38+import com.jme3.export.Savable;
39+import java.io.IOException;
40+import java.util.ArrayList;
41+
42+/**
43+ * This class is intended as a UserData added to a Spatial that is referenced by a Track.
44+ * (ParticleEmitter for EffectTrack and AudioNode for AudioTrack)
45+ * It holds the list of tracks that are directly referencing the Spatial.
46+ *
47+ * This is used when loading a Track to find the cloned reference of a Spatial in the cloned model returned by the assetManager.
48+ *
49+ * @author Nehon
50+ */
51+public class TrackInfo implements Savable {
52+
53+ ArrayList<Track> tracks = new ArrayList<Track>();
54+
55+ public TrackInfo() {
56+ }
57+
58+ public void write(JmeExporter ex) throws IOException {
59+ OutputCapsule c = ex.getCapsule(this);
60+ c.writeSavableArrayList(tracks, "tracks", null);
61+ }
62+
63+ public void read(JmeImporter im) throws IOException {
64+ InputCapsule c = im.getCapsule(this);
65+ tracks = c.readSavableArrayList("tracks", null);
66+ }
67+
68+ public ArrayList<Track> getTracks() {
69+ return tracks;
70+ }
71+
72+ public void addTrack(Track track) {
73+ tracks.add(track);
74+ }
75+}
--- a/engine/src/core/com/jme3/animation/package.html
+++ b/engine/src/core/com/jme3/animation/package.html
@@ -64,7 +64,7 @@ the transformations for bones when no animated pose is applied to the skeleton.
6464 All bones <em>must</em> have a bind pose transformation before they can be
6565 animated. To set the bind pose for the skeleton, set the local (relative
6666 to parent) transformations for all the bones using the call
67-{@link com.jme3.animation.Bone#setBindTransforms(com.jme3.math.Vector3f, com.jme3.math.Quaternion) }.
67+{@link com.jme3.animation.Bone#setBindTransforms(com.jme3.math.Vector3f, com.jme3.math.Quaternion, com.jme3.math.Vector3f) }.
6868 Then call {@link com.jme3.animation.Skeleton#updateWorldVectors() } followed by
6969 {@link com.jme3.animation.Skeleton#setBindingPose() }. <br>
7070 <p>