Main repository of MikuMikuStudio
Revision | ccbbcb888cf9386b780fe82f788ca3b68bb3cc69 (tree) |
---|---|
Time | 2013-07-07 03:10:38 |
Author | kobayasi <kobayasi@pscn...> |
Commiter | kobayasi |
copy from jme10694
@@ -1,5 +1,5 @@ | ||
1 | 1 | /* |
2 | - * Copyright (c) 2009-2010 jMonkeyEngine | |
2 | + * Copyright (c) 2009-2012 jMonkeyEngine | |
3 | 3 | * All rights reserved. |
4 | 4 | * |
5 | 5 | * Redistribution and use in source and binary forms, with or without |
@@ -29,7 +29,6 @@ | ||
29 | 29 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
30 | 30 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
31 | 31 | */ |
32 | - | |
33 | 32 | package com.jme3.animation; |
34 | 33 | |
35 | 34 | import com.jme3.math.FastMath; |
@@ -61,38 +60,69 @@ public final class AnimChannel { | ||
61 | 60 | private float speed; |
62 | 61 | private float timeBlendFrom; |
63 | 62 | private float speedBlendFrom; |
63 | + private boolean notified=false; | |
64 | 64 | |
65 | 65 | private LoopMode loopMode, loopModeBlendFrom; |
66 | 66 | |
67 | 67 | private float blendAmount = 1f; |
68 | 68 | private float blendRate = 0; |
69 | - | |
69 | + | |
70 | 70 | 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 | + } | |
73 | 74 | |
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; | |
93 | 97 | } |
94 | - | |
95 | 98 | 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; | |
96 | 126 | } |
97 | 127 | |
98 | 128 | AnimChannel(AnimControl control){ |
@@ -206,7 +236,7 @@ public final class AnimChannel { | ||
206 | 236 | */ |
207 | 237 | public void setAnim(String name, float blendTime){ |
208 | 238 | if (name == null) |
209 | - throw new NullPointerException(); | |
239 | + throw new IllegalArgumentException("name cannot be null"); | |
210 | 240 | |
211 | 241 | if (blendTime < 0f) |
212 | 242 | throw new IllegalArgumentException("blendTime cannot be less than zero"); |
@@ -225,12 +255,15 @@ public final class AnimChannel { | ||
225 | 255 | loopModeBlendFrom = loopMode; |
226 | 256 | blendAmount = 0f; |
227 | 257 | blendRate = 1f / blendTime; |
258 | + }else{ | |
259 | + blendFrom = null; | |
228 | 260 | } |
229 | 261 | |
230 | 262 | animation = anim; |
231 | 263 | time = 0; |
232 | 264 | speed = 1f; |
233 | 265 | loopMode = LoopMode.Loop; |
266 | + notified = false; | |
234 | 267 | } |
235 | 268 | |
236 | 269 | /** |
@@ -316,14 +349,33 @@ public final class AnimChannel { | ||
316 | 349 | BitSet getAffectedBones(){ |
317 | 350 | return affectedBones; |
318 | 351 | } |
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 | + } | |
319 | 368 | |
320 | 369 | void update(float tpf, TempVars vars) { |
321 | 370 | if (animation == null) |
322 | 371 | return; |
323 | 372 | |
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); | |
325 | 377 | blendFrom.setTime(timeBlendFrom, 1f - blendAmount, control, this, vars); |
326 | - //blendFrom.setTime(timeBlendFrom, control.skeleton, 1f - blendAmount, affectedBones); | |
378 | + | |
327 | 379 | timeBlendFrom += tpf * speedBlendFrom; |
328 | 380 | timeBlendFrom = clampWrapTime(timeBlendFrom, |
329 | 381 | blendFrom.getLength(), |
@@ -339,25 +391,28 @@ public final class AnimChannel { | ||
339 | 391 | blendFrom = null; |
340 | 392 | } |
341 | 393 | } |
342 | - | |
394 | + | |
343 | 395 | animation.setTime(time, blendAmount, control, this, vars); |
344 | - //animation.setTime(time, control.skeleton, blendAmount, affectedBones); | |
345 | 396 | time += tpf * speed; |
346 | 397 | |
347 | 398 | 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 | + } | |
351 | 406 | control.notifyAnimCycleDone(this, animation.getName()); |
352 | 407 | } |
353 | 408 | } |
354 | 409 | |
355 | 410 | time = clampWrapTime(time, animation.getLength(), loopMode); |
356 | 411 | if (time < 0){ |
412 | + // Negative time indicates that speed should be inverted | |
413 | + // (for cycle loop mode only) | |
357 | 414 | time = -time; |
358 | 415 | speed = -speed; |
359 | 416 | } |
360 | - | |
361 | - | |
362 | 417 | } |
363 | 418 | } |
@@ -1,5 +1,5 @@ | ||
1 | 1 | /* |
2 | - * Copyright (c) 2009-2010 jMonkeyEngine | |
2 | + * Copyright (c) 2009-2012 jMonkeyEngine | |
3 | 3 | * All rights reserved. |
4 | 4 | * |
5 | 5 | * Redistribution and use in source and binary forms, with or without |
@@ -31,11 +31,7 @@ | ||
31 | 31 | */ |
32 | 32 | package com.jme3.animation; |
33 | 33 | |
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.*; | |
39 | 35 | import com.jme3.renderer.RenderManager; |
40 | 36 | import com.jme3.renderer.ViewPort; |
41 | 37 | import com.jme3.scene.Mesh; |
@@ -47,6 +43,7 @@ import java.io.IOException; | ||
47 | 43 | import java.util.ArrayList; |
48 | 44 | import java.util.Collection; |
49 | 45 | import java.util.HashMap; |
46 | +import java.util.Map.Entry; | |
50 | 47 | |
51 | 48 | /** |
52 | 49 | * <code>AnimControl</code> is a Spatial control that allows manipulation |
@@ -74,21 +71,17 @@ public final class AnimControl extends AbstractControl implements Cloneable { | ||
74 | 71 | * Skeleton object must contain corresponding data for the targets' weight buffers. |
75 | 72 | */ |
76 | 73 | Skeleton skeleton; |
77 | - | |
78 | 74 | /** only used for backward compatibility */ |
79 | 75 | @Deprecated |
80 | 76 | private SkeletonControl skeletonControl; |
81 | - | |
82 | 77 | /** |
83 | 78 | * List of animations |
84 | 79 | */ |
85 | - HashMap<String, Animation> animationMap; | |
86 | - | |
80 | + HashMap<String, Animation> animationMap = new HashMap<String, Animation>(); | |
87 | 81 | /** |
88 | 82 | * Animation channels |
89 | 83 | */ |
90 | 84 | private transient ArrayList<AnimChannel> channels = new ArrayList<AnimChannel>(); |
91 | - | |
92 | 85 | /** |
93 | 86 | * Animation event listeners |
94 | 87 | */ |
@@ -120,13 +113,16 @@ public final class AnimControl extends AbstractControl implements Cloneable { | ||
120 | 113 | AnimControl clone = (AnimControl) super.clone(); |
121 | 114 | clone.spatial = spatial; |
122 | 115 | clone.channels = new ArrayList<AnimChannel>(); |
123 | - | |
124 | - if (skeleton != null){ | |
116 | + clone.listeners = new ArrayList<AnimEventListener>(); | |
117 | + | |
118 | + if (skeleton != null) { | |
125 | 119 | clone.skeleton = new Skeleton(skeleton); |
126 | 120 | } |
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 | + } | |
130 | 126 | |
131 | 127 | return clone; |
132 | 128 | } catch (CloneNotSupportedException ex) { |
@@ -215,6 +211,11 @@ public final class AnimControl extends AbstractControl implements Cloneable { | ||
215 | 211 | * @see AnimControl#createChannel() |
216 | 212 | */ |
217 | 213 | public void clearChannels() { |
214 | + for (AnimChannel animChannel : channels) { | |
215 | + for (AnimEventListener list : listeners) { | |
216 | + list.onAnimCycleDone(this, animChannel, animChannel.getAnimationName()); | |
217 | + } | |
218 | + } | |
218 | 219 | channels.clear(); |
219 | 220 | } |
220 | 221 |
@@ -276,7 +277,7 @@ public final class AnimControl extends AbstractControl implements Cloneable { | ||
276 | 277 | */ |
277 | 278 | @Override |
278 | 279 | public void setSpatial(Spatial spatial) { |
279 | - if (spatial == null && skeletonControl != null){ | |
280 | + if (spatial == null && skeletonControl != null) { | |
280 | 281 | this.spatial.removeControl(skeletonControl); |
281 | 282 | } |
282 | 283 |
@@ -316,13 +317,13 @@ public final class AnimControl extends AbstractControl implements Cloneable { | ||
316 | 317 | |
317 | 318 | return a.getLength(); |
318 | 319 | } |
319 | - | |
320 | + | |
320 | 321 | /** |
321 | 322 | * Internal use only. |
322 | 323 | */ |
323 | 324 | @Override |
324 | 325 | protected void controlUpdate(float tpf) { |
325 | - if (skeleton != null){ | |
326 | + if (skeleton != null) { | |
326 | 327 | skeleton.reset(); // reset skeleton to bind pose |
327 | 328 | } |
328 | 329 |
@@ -332,7 +333,7 @@ public final class AnimControl extends AbstractControl implements Cloneable { | ||
332 | 333 | } |
333 | 334 | vars.release(); |
334 | 335 | |
335 | - if (skeleton != null){ | |
336 | + if (skeleton != null) { | |
336 | 337 | skeleton.updateWorldVectors(); |
337 | 338 | } |
338 | 339 | } |
@@ -357,12 +358,15 @@ public final class AnimControl extends AbstractControl implements Cloneable { | ||
357 | 358 | super.read(im); |
358 | 359 | InputCapsule in = im.getCapsule(this); |
359 | 360 | 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 | + } | |
361 | 365 | |
362 | - if (im.getFormatVersion() == 0){ | |
366 | + if (im.getFormatVersion() == 0) { | |
363 | 367 | // Changed for backward compatibility with j3o files generated |
364 | 368 | // before the AnimControl/SkeletonControl split. |
365 | - | |
369 | + | |
366 | 370 | // If we find a target mesh array the AnimControl creates the |
367 | 371 | // SkeletonControl for old files and add it to the spatial. |
368 | 372 | // When backward compatibility won't be needed anymore this can deleted |
@@ -1,5 +1,5 @@ | ||
1 | 1 | /* |
2 | - * Copyright (c) 2009-2010 jMonkeyEngine | |
2 | + * Copyright (c) 2009-2012 jMonkeyEngine | |
3 | 3 | * All rights reserved. |
4 | 4 | * |
5 | 5 | * Redistribution and use in source and binary forms, with or without |
@@ -29,7 +29,6 @@ | ||
29 | 29 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
30 | 30 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
31 | 31 | */ |
32 | - | |
33 | 32 | package com.jme3.animation; |
34 | 33 | |
35 | 34 | /** |
@@ -1,5 +1,5 @@ | ||
1 | 1 | /* |
2 | - * Copyright (c) 2009-2011 jMonkeyEngine | |
2 | + * Copyright (c) 2009-2012 jMonkeyEngine | |
3 | 3 | * All rights reserved. |
4 | 4 | * |
5 | 5 | * Redistribution and use in source and binary forms, with or without |
@@ -31,14 +31,11 @@ | ||
31 | 31 | */ |
32 | 32 | package com.jme3.animation; |
33 | 33 | |
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; | |
41 | 37 | import com.jme3.util.TempVars; |
38 | +import java.io.IOException; | |
42 | 39 | |
43 | 40 | /** |
44 | 41 | * The animation class updates the animation target with the tracks of a given type. |
@@ -46,27 +43,26 @@ import com.jme3.util.TempVars; | ||
46 | 43 | * @author Kirill Vainer, Marcin Roguski (Kaelthas) |
47 | 44 | */ |
48 | 45 | public class Animation implements Savable, Cloneable { |
49 | - | |
46 | + | |
50 | 47 | /** |
51 | 48 | * The name of the animation. |
52 | 49 | */ |
53 | 50 | private String name; |
54 | - | |
55 | 51 | /** |
56 | 52 | * The length of the animation. |
57 | 53 | */ |
58 | 54 | private float length; |
59 | - | |
60 | 55 | /** |
61 | 56 | * The tracks of the animation. |
62 | 57 | */ |
63 | - private Track[] tracks; | |
64 | - | |
58 | + private SafeArrayList<Track> tracks = new SafeArrayList<Track>(Track.class); | |
59 | + | |
65 | 60 | /** |
66 | 61 | * Serialization-only. Do not use. |
67 | 62 | */ |
68 | - public Animation() {} | |
69 | - | |
63 | + public Animation() { | |
64 | + } | |
65 | + | |
70 | 66 | /** |
71 | 67 | * Creates a new <code>Animation</code> with the given name and length. |
72 | 68 | * |
@@ -77,24 +73,24 @@ public class Animation implements Savable, Cloneable { | ||
77 | 73 | this.name = name; |
78 | 74 | this.length = length; |
79 | 75 | } |
80 | - | |
76 | + | |
81 | 77 | /** |
82 | 78 | * The name of the bone animation |
83 | 79 | * @return name of the bone animation |
84 | 80 | */ |
85 | 81 | public String getName() { |
86 | - return name; | |
82 | + return name; | |
87 | 83 | } |
88 | - | |
84 | + | |
89 | 85 | /** |
90 | 86 | * Returns the length in seconds of this animation |
91 | 87 | * |
92 | 88 | * @return the length in seconds of this animation |
93 | 89 | */ |
94 | 90 | public float getLength() { |
95 | - return length; | |
91 | + return length; | |
96 | 92 | } |
97 | - | |
93 | + | |
98 | 94 | /** |
99 | 95 | * This method sets the current time of the animation. |
100 | 96 | * This method behaves differently for every known track type. |
@@ -106,77 +102,87 @@ public class Animation implements Savable, Cloneable { | ||
106 | 102 | * @param channel the animation channel |
107 | 103 | */ |
108 | 104 | void setTime(float time, float blendAmount, AnimControl control, AnimChannel channel, TempVars vars) { |
109 | - if (tracks == null) | |
105 | + if (tracks == null) { | |
110 | 106 | return; |
111 | - | |
112 | - for (int i = 0; i < tracks.length; i++){ | |
113 | - tracks[i].setTime(time, blendAmount, control, channel, vars); | |
114 | 107 | } |
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); | |
144 | 111 | } |
145 | - */ | |
146 | 112 | } |
147 | - | |
113 | + | |
148 | 114 | /** |
149 | 115 | * 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. | |
153 | 116 | * |
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 | |
155 | 128 | */ |
156 | - public void setTracks(Track[] tracks){ | |
157 | - this.tracks = tracks; | |
129 | + public void addTrack(Track track) { | |
130 | + tracks.add(track); | |
158 | 131 | } |
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 | + | |
160 | 144 | /** |
161 | 145 | * Returns the tracks set in {@link #setTracks(com.jme3.animation.Track[]) }. |
162 | 146 | * |
163 | 147 | * @return the tracks set previously |
164 | 148 | */ |
165 | 149 | public Track[] getTracks() { |
166 | - return tracks; | |
150 | + return tracks.getArray(); | |
167 | 151 | } |
168 | - | |
152 | + | |
169 | 153 | /** |
170 | 154 | * This method creates a clone of the current object. |
171 | 155 | * @return a clone of the current object |
172 | 156 | */ |
173 | - @Override | |
174 | - public Animation clone() { | |
157 | + @Override | |
158 | + public Animation clone() { | |
175 | 159 | try { |
176 | 160 | 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 | + } | |
180 | 186 | } |
181 | 187 | return result; |
182 | 188 | } catch (CloneNotSupportedException e) { |
@@ -188,13 +194,13 @@ public class Animation implements Savable, Cloneable { | ||
188 | 194 | public String toString() { |
189 | 195 | return getClass().getSimpleName() + "[name=" + name + ", length=" + length + ']'; |
190 | 196 | } |
191 | - | |
192 | - @Override | |
197 | + | |
198 | + @Override | |
193 | 199 | public void write(JmeExporter ex) throws IOException { |
194 | 200 | OutputCapsule out = ex.getCapsule(this); |
195 | 201 | out.write(name, "name", null); |
196 | 202 | out.write(length, "length", 0f); |
197 | - out.write(tracks, "tracks", null); | |
203 | + out.write(tracks.getArray(), "tracks", null); | |
198 | 204 | } |
199 | 205 | |
200 | 206 | @Override |
@@ -202,9 +208,17 @@ public class Animation implements Savable, Cloneable { | ||
202 | 208 | InputCapsule in = im.getCapsule(this); |
203 | 209 | name = in.readString("name", null); |
204 | 210 | length = in.readFloat("length", 0f); |
205 | - | |
211 | + | |
206 | 212 | 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 | + } | |
209 | 223 | } |
210 | 224 | } |
@@ -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 | +} |
@@ -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 | +} |
@@ -1,5 +1,5 @@ | ||
1 | 1 | /* |
2 | - * Copyright (c) 2009-2010 jMonkeyEngine | |
2 | + * Copyright (c) 2009-2012 jMonkeyEngine | |
3 | 3 | * All rights reserved. |
4 | 4 | * |
5 | 5 | * Redistribution and use in source and binary forms, with or without |
@@ -31,16 +31,8 @@ | ||
31 | 31 | */ |
32 | 32 | package com.jme3.animation; |
33 | 33 | |
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.*; | |
44 | 36 | import com.jme3.scene.Node; |
45 | 37 | import com.jme3.util.TempVars; |
46 | 38 | import java.io.IOException; |
@@ -63,7 +55,6 @@ public final class Bone implements Savable { | ||
63 | 55 | * Animation transforms are not applied to this bone when enabled. |
64 | 56 | */ |
65 | 57 | private boolean userControl = false; |
66 | - private boolean useModelSpaceVectors = false; | |
67 | 58 | /** |
68 | 59 | * The attachment node. |
69 | 60 | */ |
@@ -94,8 +85,16 @@ public final class Bone implements Savable { | ||
94 | 85 | private Vector3f worldPos = new Vector3f(); |
95 | 86 | private Quaternion worldRot = new Quaternion(); |
96 | 87 | private Vector3f worldScale = new Vector3f(); |
97 | - //used for getCombinedTransform | |
88 | + | |
89 | + // Used for getCombinedTransform | |
98 | 90 | 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; | |
99 | 98 | |
100 | 99 | /** |
101 | 100 | * Creates a new bone with the given name. |
@@ -332,27 +331,45 @@ public final class Bone implements Savable { | ||
332 | 331 | * world transform with this bones' local transform. |
333 | 332 | */ |
334 | 333 | 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); | |
350 | 342 | } 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); | |
354 | 347 | } |
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); | |
355 | 371 | } |
372 | + | |
356 | 373 | if (attachNode != null) { |
357 | 374 | attachNode.setLocalTranslation(worldPos); |
358 | 375 | attachNode.setLocalRotation(worldRot); |
@@ -471,6 +488,12 @@ public final class Bone implements Savable { | ||
471 | 488 | // TODO: add scale here ??? |
472 | 489 | worldPos.set(translation); |
473 | 490 | 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 | + } | |
474 | 497 | } |
475 | 498 | |
476 | 499 | /** |
@@ -489,7 +512,7 @@ public final class Bone implements Savable { | ||
489 | 512 | * Attach models and effects to this node to make |
490 | 513 | * them follow this bone's motions. |
491 | 514 | */ |
492 | - public Node getAttachmentsNode() { | |
515 | + Node getAttachmentsNode() { | |
493 | 516 | if (attachNode == null) { |
494 | 517 | attachNode = new Node(name + "_attachnode"); |
495 | 518 | attachNode.setUserData("AttachedBone", this); |
@@ -526,34 +549,70 @@ public final class Bone implements Savable { | ||
526 | 549 | } |
527 | 550 | } |
528 | 551 | |
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 | + */ | |
529 | 570 | void blendAnimTransforms(Vector3f translation, Quaternion rotation, Vector3f scale, float weight) { |
530 | 571 | if (userControl) { |
531 | 572 | return; |
532 | 573 | } |
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; | |
553 | 578 | } |
554 | 579 | |
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 | + } | |
557 | 616 | } |
558 | 617 | |
559 | 618 | /** |
@@ -593,14 +652,6 @@ public final class Bone implements Savable { | ||
593 | 652 | return this.toString(0); |
594 | 653 | } |
595 | 654 | |
596 | - public boolean isUseModelSpaceVectors() { | |
597 | - return useModelSpaceVectors; | |
598 | - } | |
599 | - | |
600 | - public void setUseModelSpaceVectors(boolean useModelSpaceVectors) { | |
601 | - this.useModelSpaceVectors = useModelSpaceVectors; | |
602 | - } | |
603 | - | |
604 | 655 | @Override |
605 | 656 | @SuppressWarnings("unchecked") |
606 | 657 | public void read(JmeImporter im) throws IOException { |
@@ -1,5 +1,5 @@ | ||
1 | 1 | /* |
2 | - * Copyright (c) 2009-2010 jMonkeyEngine | |
2 | + * Copyright (c) 2009-2012 jMonkeyEngine | |
3 | 3 | * All rights reserved. |
4 | 4 | * |
5 | 5 | * Redistribution and use in source and binary forms, with or without |
@@ -1,5 +1,5 @@ | ||
1 | 1 | /* |
2 | - * Copyright (c) 2009-2010 jMonkeyEngine | |
2 | + * Copyright (c) 2009-2012 jMonkeyEngine | |
3 | 3 | * All rights reserved. |
4 | 4 | * |
5 | 5 | * Redistribution and use in source and binary forms, with or without |
@@ -31,11 +31,7 @@ | ||
31 | 31 | */ |
32 | 32 | package com.jme3.animation; |
33 | 33 | |
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.*; | |
39 | 35 | import com.jme3.math.Quaternion; |
40 | 36 | import com.jme3.math.Vector3f; |
41 | 37 | import com.jme3.util.TempVars; |
@@ -186,8 +182,10 @@ public final class BoneTrack implements Track { | ||
186 | 182 | * The transforms can be interpolated in some method from the keyframes. |
187 | 183 | * |
188 | 184 | * @param time the current time of the animation |
189 | - * @param skeleton the skeleton to which the bone belong | |
190 | 185 | * @param weight the weight of the animation |
186 | + * @param control | |
187 | + * @param channel | |
188 | + * @param vars | |
191 | 189 | */ |
192 | 190 | public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) { |
193 | 191 | BitSet affectedBones = channel.getAffectedBones(); |
@@ -245,11 +243,11 @@ public final class BoneTrack implements Track { | ||
245 | 243 | tempS.interpolate(tempS2, blend); |
246 | 244 | } |
247 | 245 | |
248 | - if (weight != 1f) { | |
246 | +// if (weight != 1f) { | |
249 | 247 | 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 | +// } | |
253 | 251 | } |
254 | 252 | |
255 | 253 | /** |
@@ -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 | +} |
@@ -1,5 +1,5 @@ | ||
1 | 1 | /* |
2 | - * Copyright (c) 2009-2010 jMonkeyEngine | |
2 | + * Copyright (c) 2009-2012 jMonkeyEngine | |
3 | 3 | * All rights reserved. |
4 | 4 | * |
5 | 5 | * Redistribution and use in source and binary forms, with or without |
@@ -1,5 +1,5 @@ | ||
1 | 1 | /* |
2 | - * Copyright (c) 2009-2010 jMonkeyEngine | |
2 | + * Copyright (c) 2009-2012 jMonkeyEngine | |
3 | 3 | * All rights reserved. |
4 | 4 | * |
5 | 5 | * Redistribution and use in source and binary forms, with or without |
@@ -31,14 +31,9 @@ | ||
31 | 31 | */ |
32 | 32 | package com.jme3.animation; |
33 | 33 | |
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.*; | |
41 | 35 | import com.jme3.math.Quaternion; |
36 | +import java.io.IOException; | |
42 | 37 | |
43 | 38 | /** |
44 | 39 | * Serialize and compress {@link Quaternion}[] by indexing same values |
@@ -1,5 +1,5 @@ | ||
1 | 1 | /* |
2 | - * Copyright (c) 2009-2010 jMonkeyEngine | |
2 | + * Copyright (c) 2009-2012 jMonkeyEngine | |
3 | 3 | * All rights reserved. |
4 | 4 | * |
5 | 5 | * Redistribution and use in source and binary forms, with or without |
@@ -29,17 +29,11 @@ | ||
29 | 29 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
30 | 30 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
31 | 31 | */ |
32 | - | |
33 | 32 | package com.jme3.animation; |
34 | 33 | |
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.*; | |
42 | 35 | import com.jme3.math.Vector3f; |
36 | +import java.io.IOException; | |
43 | 37 | |
44 | 38 | /** |
45 | 39 | * Serialize and compress Vector3f[] by indexing same values |
@@ -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 | +} |
@@ -1,5 +1,5 @@ | ||
1 | 1 | /* |
2 | - * Copyright (c) 2009-2010 jMonkeyEngine | |
2 | + * Copyright (c) 2009-2012 jMonkeyEngine | |
3 | 3 | * All rights reserved. |
4 | 4 | * |
5 | 5 | * Redistribution and use in source and binary forms, with or without |
@@ -29,7 +29,6 @@ | ||
29 | 29 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
30 | 30 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
31 | 31 | */ |
32 | - | |
33 | 32 | package com.jme3.animation; |
34 | 33 | |
35 | 34 | /** |
@@ -1,5 +1,5 @@ | ||
1 | 1 | /* |
2 | - * Copyright (c) 2009-2010 jMonkeyEngine | |
2 | + * Copyright (c) 2009-2012 jMonkeyEngine | |
3 | 3 | * All rights reserved. |
4 | 4 | * |
5 | 5 | * Redistribution and use in source and binary forms, with or without |
@@ -29,19 +29,13 @@ | ||
29 | 29 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
30 | 30 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
31 | 31 | */ |
32 | - | |
33 | 32 | package com.jme3.animation; |
34 | 33 | |
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.*; | |
43 | 35 | import com.jme3.math.Vector3f; |
44 | 36 | import com.jme3.util.BufferUtils; |
37 | +import java.io.IOException; | |
38 | +import java.nio.FloatBuffer; | |
45 | 39 | |
46 | 40 | /** |
47 | 41 | * 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 { | ||
64 | 58 | this.indices = indices; |
65 | 59 | } |
66 | 60 | |
61 | + /** | |
62 | + * Serialization-only. Do not use. | |
63 | + */ | |
64 | + public Pose() | |
65 | + { | |
66 | + } | |
67 | + | |
67 | 68 | public int getTargetMeshIndex(){ |
68 | 69 | return targetMeshIndex; |
69 | 70 | } |
@@ -97,21 +98,22 @@ public final class Pose implements Savable, Cloneable { | ||
97 | 98 | * This method creates a clone of the current object. |
98 | 99 | * @return a clone of the current object |
99 | 100 | */ |
101 | + @Override | |
100 | 102 | public Pose clone() { |
101 | - try { | |
102 | - Pose result = (Pose) super.clone(); | |
103 | + try { | |
104 | + Pose result = (Pose) super.clone(); | |
103 | 105 | 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 | + } | |
109 | 111 | } |
110 | - return result; | |
112 | + return result; | |
111 | 113 | } catch (CloneNotSupportedException e) { |
112 | 114 | throw new AssertionError(); |
113 | 115 | } |
114 | - } | |
116 | + } | |
115 | 117 | |
116 | 118 | public void write(JmeExporter e) throws IOException { |
117 | 119 | OutputCapsule out = e.getCapsule(this); |
@@ -125,7 +127,12 @@ public final class Pose implements Savable, Cloneable { | ||
125 | 127 | InputCapsule in = i.getCapsule(this); |
126 | 128 | name = in.readString("name", ""); |
127 | 129 | targetMeshIndex = in.readInt("meshIndex", -1); |
128 | - offsets = (Vector3f[]) in.readSavableArray("offsets", null); | |
129 | 130 | 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 | + } | |
130 | 137 | } |
131 | 138 | } |
@@ -1,5 +1,5 @@ | ||
1 | 1 | /* |
2 | - * Copyright (c) 2009-2010 jMonkeyEngine | |
2 | + * Copyright (c) 2009-2012 jMonkeyEngine | |
3 | 3 | * All rights reserved. |
4 | 4 | * |
5 | 5 | * Redistribution and use in source and binary forms, with or without |
@@ -29,14 +29,9 @@ | ||
29 | 29 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
30 | 30 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
31 | 31 | */ |
32 | - | |
33 | 32 | package com.jme3.animation; |
34 | 33 | |
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.*; | |
40 | 35 | import com.jme3.scene.Mesh; |
41 | 36 | import com.jme3.scene.VertexBuffer; |
42 | 37 | import com.jme3.scene.VertexBuffer.Type; |
@@ -65,6 +60,13 @@ public final class PoseTrack implements Track { | ||
65 | 60 | } |
66 | 61 | |
67 | 62 | /** |
63 | + * Serialization-only. Do not use. | |
64 | + */ | |
65 | + public PoseFrame() | |
66 | + { | |
67 | + } | |
68 | + | |
69 | + /** | |
68 | 70 | * This method creates a clone of the current object. |
69 | 71 | * @return a clone of the current object |
70 | 72 | */ |
@@ -93,8 +95,13 @@ public final class PoseTrack implements Track { | ||
93 | 95 | |
94 | 96 | public void read(JmeImporter i) throws IOException { |
95 | 97 | InputCapsule in = i.getCapsule(this); |
96 | - poses = (Pose[]) in.readSavableArray("poses", null); | |
97 | 98 | 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 | + } | |
98 | 105 | } |
99 | 106 | } |
100 | 107 |
@@ -104,6 +111,13 @@ public final class PoseTrack implements Track { | ||
104 | 111 | this.frames = frames; |
105 | 112 | } |
106 | 113 | |
114 | + /** | |
115 | + * Serialization-only. Do not use. | |
116 | + */ | |
117 | + public PoseTrack() | |
118 | + { | |
119 | + } | |
120 | + | |
107 | 121 | private void applyFrame(Mesh target, int frameIndex, float weight){ |
108 | 122 | PoseFrame frame = frames[frameIndex]; |
109 | 123 | VertexBuffer pb = target.getBuffer(Type.Position); |
@@ -184,7 +198,12 @@ public final class PoseTrack implements Track { | ||
184 | 198 | public void read(JmeImporter i) throws IOException { |
185 | 199 | InputCapsule in = i.getCapsule(this); |
186 | 200 | targetMeshIndex = in.readInt("meshIndex", 0); |
187 | - frames = (PoseFrame[]) in.readSavableArray("frames", null); | |
188 | 201 | 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 | + } | |
189 | 208 | } |
190 | 209 | } |
@@ -1,5 +1,5 @@ | ||
1 | 1 | /* |
2 | - * Copyright (c) 2009-2010 jMonkeyEngine | |
2 | + * Copyright (c) 2009-2012 jMonkeyEngine | |
3 | 3 | * All rights reserved. |
4 | 4 | * |
5 | 5 | * Redistribution and use in source and binary forms, with or without |
@@ -31,15 +31,10 @@ | ||
31 | 31 | */ |
32 | 32 | package com.jme3.animation; |
33 | 33 | |
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.*; | |
39 | 35 | import com.jme3.math.Matrix4f; |
40 | 36 | import com.jme3.util.TempVars; |
41 | 37 | import java.io.IOException; |
42 | - | |
43 | 38 | import java.util.ArrayList; |
44 | 39 | import java.util.List; |
45 | 40 |
@@ -1,55 +1,113 @@ | ||
1 | 1 | /* |
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. | |
4 | 31 | */ |
5 | 32 | package com.jme3.animation; |
6 | 33 | |
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; | |
12 | 36 | import com.jme3.math.FastMath; |
13 | 37 | import com.jme3.math.Matrix4f; |
14 | 38 | import com.jme3.renderer.RenderManager; |
39 | +import com.jme3.renderer.RendererException; | |
15 | 40 | 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.*; | |
22 | 42 | import com.jme3.scene.VertexBuffer.Type; |
23 | 43 | import com.jme3.scene.control.AbstractControl; |
24 | 44 | import com.jme3.scene.control.Control; |
45 | +import com.jme3.shader.VarType; | |
46 | +import com.jme3.util.SafeArrayList; | |
25 | 47 | import com.jme3.util.TempVars; |
26 | 48 | import java.io.IOException; |
49 | +import java.nio.Buffer; | |
27 | 50 | import java.nio.ByteBuffer; |
28 | 51 | 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; | |
30 | 57 | |
31 | 58 | /** |
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 | + * | |
36 | 63 | * @author Rémy Bouquet Based on AnimControl by Kirill Vainer |
37 | 64 | */ |
38 | 65 | public class SkeletonControl extends AbstractControl implements Cloneable { |
39 | 66 | |
40 | 67 | /** |
41 | - * The skeleton of the model | |
68 | + * The skeleton of the model. | |
42 | 69 | */ |
43 | 70 | private Skeleton skeleton; |
44 | 71 | /** |
45 | 72 | * List of targets which this controller effects. |
46 | 73 | */ |
47 | - private Mesh[] targets; | |
74 | + private SafeArrayList<Mesh> targets = new SafeArrayList<Mesh>(Mesh.class); | |
48 | 75 | /** |
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. | |
51 | 78 | */ |
52 | 79 | 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>(); | |
53 | 111 | |
54 | 112 | /** |
55 | 113 | * Serialization only. Do not use. |
@@ -57,100 +115,207 @@ public class SkeletonControl extends AbstractControl implements Cloneable { | ||
57 | 115 | public SkeletonControl() { |
58 | 116 | } |
59 | 117 | |
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 | + | |
60 | 169 | /** |
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. | |
64 | 183 | * |
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 | + * | |
65 | 201 | * @param skeleton the skeleton |
66 | 202 | */ |
67 | 203 | public SkeletonControl(Skeleton skeleton) { |
68 | 204 | this.skeleton = skeleton; |
69 | 205 | } |
70 | - | |
206 | + | |
71 | 207 | /** |
72 | 208 | * Creates a skeleton control. |
73 | - * | |
209 | + * | |
74 | 210 | * @param targets the meshes controlled by the skeleton |
75 | 211 | * @param skeleton the skeleton |
76 | 212 | */ |
77 | 213 | @Deprecated |
78 | - SkeletonControl(Mesh[] targets, Skeleton skeleton){ | |
214 | + SkeletonControl(Mesh[] targets, Skeleton skeleton) { | |
79 | 215 | 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)); | |
85 | 217 | } |
86 | 218 | |
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()); | |
110 | 247 | } |
111 | 248 | } |
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); | |
117 | 251 | } |
118 | 252 | } |
119 | - | |
120 | - if (sharedMesh != null){ | |
121 | - animatedMeshes.add(sharedMesh); | |
253 | + | |
254 | + if (sharedMesh != null) { | |
255 | + targets.add(sharedMesh); | |
122 | 256 | } |
123 | - | |
124 | - return animatedMeshes.toArray(new Mesh[animatedMeshes.size()]); | |
257 | + | |
125 | 258 | } |
126 | - | |
259 | + | |
127 | 260 | @Override |
128 | - public void setSpatial(Spatial spatial){ | |
261 | + public void setSpatial(Spatial spatial) { | |
129 | 262 | 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); | |
135 | 283 | } |
136 | 284 | } |
285 | + | |
137 | 286 | |
138 | 287 | @Override |
139 | 288 | protected void controlRender(RenderManager rm, ViewPort vp) { |
140 | 289 | 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(); | |
154 | 319 | } |
155 | 320 | |
156 | 321 | wasMeshUpdated = true; |
@@ -160,14 +325,15 @@ public class SkeletonControl extends AbstractControl implements Cloneable { | ||
160 | 325 | @Override |
161 | 326 | protected void controlUpdate(float tpf) { |
162 | 327 | wasMeshUpdated = false; |
163 | - } | |
328 | + } | |
164 | 329 | |
330 | + //only do this for software updates | |
165 | 331 | 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()) { | |
171 | 337 | mesh.prepareForAnim(true); // prepare for software animation |
172 | 338 | } |
173 | 339 | VertexBuffer bindPos = mesh.getBuffer(Type.BindPosePosition); |
@@ -182,6 +348,19 @@ public class SkeletonControl extends AbstractControl implements Cloneable { | ||
182 | 348 | nb.clear(); |
183 | 349 | bpb.clear(); |
184 | 350 | 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 | + | |
185 | 364 | pb.put(bpb).clear(); |
186 | 365 | nb.put(bnb).clear(); |
187 | 366 | } |
@@ -192,13 +371,12 @@ public class SkeletonControl extends AbstractControl implements Cloneable { | ||
192 | 371 | Node clonedNode = (Node) spatial; |
193 | 372 | AnimControl ctrl = spatial.getControl(AnimControl.class); |
194 | 373 | SkeletonControl clone = new SkeletonControl(); |
195 | - clone.setSpatial(clonedNode); | |
196 | 374 | |
197 | 375 | clone.skeleton = ctrl.getSkeleton(); |
198 | - // Fix animated targets for the cloned node | |
199 | - clone.targets = findTargets(clonedNode); | |
200 | 376 | |
201 | - // Fix attachments for the cloned node | |
377 | + clone.setSpatial(clonedNode); | |
378 | + | |
379 | + // Fix attachments for the cloned node | |
202 | 380 | for (int i = 0; i < clonedNode.getQuantity(); i++) { |
203 | 381 | // go through attachment nodes, apply them to correct bone |
204 | 382 | Spatial child = clonedNode.getChild(i); |
@@ -219,9 +397,9 @@ public class SkeletonControl extends AbstractControl implements Cloneable { | ||
219 | 397 | } |
220 | 398 | |
221 | 399 | /** |
222 | - * | |
400 | + * | |
223 | 401 | * @param boneName the name of the bone |
224 | - * @return the node attached to this bone | |
402 | + * @return the node attached to this bone | |
225 | 403 | */ |
226 | 404 | public Node getAttachmentsNode(String boneName) { |
227 | 405 | Bone b = skeleton.getBone(boneName); |
@@ -238,38 +416,162 @@ public class SkeletonControl extends AbstractControl implements Cloneable { | ||
238 | 416 | |
239 | 417 | /** |
240 | 418 | * returns the skeleton of this control |
241 | - * @return | |
419 | + * | |
420 | + * @return | |
242 | 421 | */ |
243 | 422 | public Skeleton getSkeleton() { |
244 | 423 | return skeleton; |
245 | 424 | } |
246 | 425 | |
247 | 426 | /** |
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 | |
250 | 430 | */ |
251 | -// public void setSkeleton(Skeleton skeleton) { | |
252 | -// this.skeleton = skeleton; | |
253 | -// } | |
431 | + public Mesh[] getTargets() { | |
432 | + return targets.toArray(new Mesh[targets.size()]); | |
433 | + } | |
254 | 434 | |
255 | 435 | /** |
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 | |
258 | 440 | */ |
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 | + | |
261 | 453 | } |
262 | 454 | |
263 | 455 | /** |
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 | |
266 | 460 | */ |
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 | + } | |
270 | 466 | |
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) { | |
272 | 573 | int maxWeightsPerVert = mesh.getMaxNumWeights(); |
574 | + | |
273 | 575 | if (maxWeightsPerVert <= 0) { |
274 | 576 | throw new IllegalStateException("Max weights per vert is incorrectly set!"); |
275 | 577 | } |
@@ -283,9 +585,15 @@ public class SkeletonControl extends AbstractControl implements Cloneable { | ||
283 | 585 | fvb.rewind(); |
284 | 586 | |
285 | 587 | VertexBuffer nb = mesh.getBuffer(Type.Normal); |
588 | + | |
286 | 589 | FloatBuffer fnb = (FloatBuffer) nb.getData(); |
287 | 590 | fnb.rewind(); |
288 | 591 | |
592 | + | |
593 | + FloatBuffer ftb = (FloatBuffer) tb.getData(); | |
594 | + ftb.rewind(); | |
595 | + | |
596 | + | |
289 | 597 | // get boneIndexes and weights for mesh |
290 | 598 | ByteBuffer ib = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData(); |
291 | 599 | FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData(); |
@@ -302,19 +610,33 @@ public class SkeletonControl extends AbstractControl implements Cloneable { | ||
302 | 610 | |
303 | 611 | float[] posBuf = vars.skinPositions; |
304 | 612 | float[] normBuf = vars.skinNormals; |
613 | + float[] tanBuf = vars.skinTangents; | |
305 | 614 | |
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; | |
308 | 618 | for (int i = iterations - 1; i >= 0; i--) { |
309 | 619 | // read next set of positions and normals from native buffer |
310 | 620 | bufLength = Math.min(posBuf.length, fvb.remaining()); |
621 | + tanLength = Math.min(tanBuf.length, ftb.remaining()); | |
311 | 622 | fvb.get(posBuf, 0, bufLength); |
312 | 623 | fnb.get(normBuf, 0, bufLength); |
624 | + ftb.get(tanBuf, 0, tanLength); | |
313 | 625 | int verts = bufLength / 3; |
314 | 626 | int idxPositions = 0; |
627 | + //tangents has their own index because of the 4 components | |
628 | + int idxTangents = 0; | |
315 | 629 | |
316 | 630 | // iterate vertices and apply skinning transform for each effecting bone |
317 | 631 | 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 | + | |
318 | 640 | float nmx = normBuf[idxPositions]; |
319 | 641 | float vtx = posBuf[idxPositions++]; |
320 | 642 | float nmy = normBuf[idxPositions]; |
@@ -322,7 +644,14 @@ public class SkeletonControl extends AbstractControl implements Cloneable { | ||
322 | 644 | float nmz = normBuf[idxPositions]; |
323 | 645 | float vtz = posBuf[idxPositions++]; |
324 | 646 | |
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; | |
326 | 655 | |
327 | 656 | for (int w = maxWeightsPerVert - 1; w >= 0; w--) { |
328 | 657 | float weight = weights[idxWeights]; |
@@ -335,50 +664,71 @@ public class SkeletonControl extends AbstractControl implements Cloneable { | ||
335 | 664 | rnx += (nmx * mat.m00 + nmy * mat.m01 + nmz * mat.m02) * weight; |
336 | 665 | rny += (nmx * mat.m10 + nmy * mat.m11 + nmz * mat.m12) * weight; |
337 | 666 | 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; | |
338 | 671 | } |
339 | 672 | |
340 | 673 | idxWeights += fourMinusMaxWeights; |
341 | 674 | |
342 | 675 | idxPositions -= 3; |
676 | + | |
343 | 677 | normBuf[idxPositions] = rnx; |
344 | 678 | posBuf[idxPositions++] = rx; |
345 | 679 | normBuf[idxPositions] = rny; |
346 | 680 | posBuf[idxPositions++] = ry; |
347 | 681 | normBuf[idxPositions] = rnz; |
348 | 682 | 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++; | |
349 | 692 | } |
350 | 693 | |
351 | 694 | fvb.position(fvb.position() - bufLength); |
352 | 695 | fvb.put(posBuf, 0, bufLength); |
353 | 696 | fnb.position(fnb.position() - bufLength); |
354 | 697 | fnb.put(normBuf, 0, bufLength); |
698 | + ftb.position(ftb.position() - tanLength); | |
699 | + ftb.put(tanBuf, 0, tanLength); | |
355 | 700 | } |
356 | 701 | |
357 | 702 | vars.release(); |
358 | 703 | |
359 | 704 | vb.updateData(fvb); |
360 | 705 | nb.updateData(fnb); |
706 | + tb.updateData(ftb); | |
707 | + | |
361 | 708 | |
362 | -// mesh.updateBound(); | |
363 | 709 | } |
364 | 710 | |
365 | 711 | @Override |
366 | 712 | public void write(JmeExporter ex) throws IOException { |
367 | 713 | super.write(ex); |
368 | 714 | OutputCapsule oc = ex.getCapsule(this); |
369 | - oc.write(targets, "targets", null); | |
370 | 715 | oc.write(skeleton, "skeleton", null); |
716 | + //Targets and materials don't need to be saved, they'll be gathered on each frame | |
371 | 717 | } |
372 | 718 | |
373 | 719 | @Override |
374 | 720 | public void read(JmeImporter im) throws IOException { |
375 | 721 | super.read(im); |
376 | 722 | 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 | - } | |
382 | 723 | skeleton = (Skeleton) in.readSavable("skeleton", null); |
383 | 724 | } |
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 | + } | |
384 | 734 | } |
@@ -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 | + */ | |
1 | 32 | package com.jme3.animation; |
2 | 33 | |
3 | 34 | /** |
@@ -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 | + */ | |
1 | 32 | package com.jme3.animation; |
2 | 33 | |
3 | -import java.io.IOException; | |
4 | - | |
5 | 34 | import com.jme3.export.InputCapsule; |
6 | 35 | import com.jme3.export.JmeExporter; |
7 | 36 | import com.jme3.export.JmeImporter; |
@@ -10,6 +39,8 @@ import com.jme3.math.Quaternion; | ||
10 | 39 | import com.jme3.math.Vector3f; |
11 | 40 | import com.jme3.scene.Spatial; |
12 | 41 | import com.jme3.util.TempVars; |
42 | +import java.io.IOException; | |
43 | +import java.util.Arrays; | |
13 | 44 | |
14 | 45 | /** |
15 | 46 | * This class represents the track for spatial animation. |
@@ -64,8 +95,6 @@ public class SpatialTrack implements Track { | ||
64 | 95 | * |
65 | 96 | * @param time |
66 | 97 | * the current time of the animation |
67 | - * @param spatial | |
68 | - * the spatial that should be animated with this track | |
69 | 98 | */ |
70 | 99 | public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) { |
71 | 100 | Spatial spatial = control.getSpatial(); |
@@ -79,14 +108,18 @@ public class SpatialTrack implements Track { | ||
79 | 108 | |
80 | 109 | int lastFrame = times.length - 1; |
81 | 110 | 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); | |
84 | 115 | if (scales != null) { |
85 | 116 | scales.get(0, tempS); |
86 | 117 | } |
87 | 118 | } 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); | |
90 | 123 | if (scales != null) { |
91 | 124 | scales.get(lastFrame, tempS); |
92 | 125 | } |
@@ -101,13 +134,17 @@ public class SpatialTrack implements Track { | ||
101 | 134 | |
102 | 135 | float blend = (time - times[startFrame]) / (times[endFrame] - times[startFrame]); |
103 | 136 | |
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); | |
106 | 141 | if (scales != null) { |
107 | 142 | scales.get(startFrame, tempS); |
108 | 143 | } |
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); | |
111 | 148 | if (scales != null) { |
112 | 149 | scales.get(endFrame, tempS2); |
113 | 150 | } |
@@ -116,8 +153,10 @@ public class SpatialTrack implements Track { | ||
116 | 153 | tempS.interpolate(tempS2, blend); |
117 | 154 | } |
118 | 155 | |
119 | - spatial.setLocalTranslation(tempV); | |
120 | - spatial.setLocalRotation(tempQ); | |
156 | + if (translations != null) | |
157 | + spatial.setLocalTranslation(tempV); | |
158 | + if (rotations != null) | |
159 | + spatial.setLocalRotation(tempQ); | |
121 | 160 | if (scales != null) { |
122 | 161 | spatial.setLocalScale(tempS); |
123 | 162 | } |
@@ -141,25 +180,24 @@ public class SpatialTrack implements Track { | ||
141 | 180 | throw new RuntimeException("BoneTrack with no keyframes!"); |
142 | 181 | } |
143 | 182 | |
144 | - assert times.length == translations.length | |
145 | - && times.length == rotations.length; | |
146 | - | |
147 | 183 | 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 | + } | |
155 | 196 | if (scales != null) { |
156 | 197 | assert times.length == scales.length; |
157 | - | |
158 | 198 | this.scales = new CompactVector3Array(); |
159 | 199 | this.scales.add(scales); |
160 | 200 | this.scales.freeze(); |
161 | - | |
162 | - | |
163 | 201 | } |
164 | 202 | } |
165 | 203 |
@@ -167,7 +205,7 @@ public class SpatialTrack implements Track { | ||
167 | 205 | * @return the array of rotations of this track |
168 | 206 | */ |
169 | 207 | public Quaternion[] getRotations() { |
170 | - return rotations.toObjectArray(); | |
208 | + return rotations == null ? null : rotations.toObjectArray(); | |
171 | 209 | } |
172 | 210 | |
173 | 211 | /** |
@@ -188,7 +226,7 @@ public class SpatialTrack implements Track { | ||
188 | 226 | * @return the array of translations of this track |
189 | 227 | */ |
190 | 228 | public Vector3f[] getTranslations() { |
191 | - return translations.toObjectArray(); | |
229 | + return translations == null ? null : translations.toObjectArray(); | |
192 | 230 | } |
193 | 231 | |
194 | 232 | /** |
@@ -206,21 +244,13 @@ public class SpatialTrack implements Track { | ||
206 | 244 | public SpatialTrack clone() { |
207 | 245 | int tablesLength = times.length; |
208 | 246 | |
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 | + | |
222 | 252 | //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); | |
224 | 254 | } |
225 | 255 | |
226 | 256 | @Override |
@@ -1,5 +1,5 @@ | ||
1 | 1 | /* |
2 | - * Copyright (c) 2009-2010 jMonkeyEngine | |
2 | + * Copyright (c) 2009-2012 jMonkeyEngine | |
3 | 3 | * All rights reserved. |
4 | 4 | * |
5 | 5 | * Redistribution and use in source and binary forms, with or without |
@@ -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 | +} |
@@ -64,7 +64,7 @@ the transformations for bones when no animated pose is applied to the skeleton. | ||
64 | 64 | All bones <em>must</em> have a bind pose transformation before they can be |
65 | 65 | animated. To set the bind pose for the skeleton, set the local (relative |
66 | 66 | 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) }. | |
68 | 68 | Then call {@link com.jme3.animation.Skeleton#updateWorldVectors() } followed by |
69 | 69 | {@link com.jme3.animation.Skeleton#setBindingPose() }. <br> |
70 | 70 | <p> |