NathanSweet b882fb8c51 Support for multiple mixing without dipping.
The multiple mixing setting has been removed. Multiple mixing is always done and dipping is avoided for adjacent track entries.

This is pretty close to complete. Mixing `a -> b` where both key property `x` avoids dipping. If the mix is interrupted by `c`, the dipping is properly mixed out. However, if `c` *also* keys `x`, a dip is seen.

This is good test JSON data:
http://n4te.com/x/1948-6b1G.txt
The problem can be seen by doing `m1 -> m1-dup` then interrupting the mix with `m1`.

Related issues: #621, #815, #899, #900
2017-05-16 04:35:54 +02:00

959 lines
30 KiB
Java

/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF
* USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
* IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
package com.esotericsoftware.spine;
import java.util.concurrent.atomic.AtomicInteger;
import com.badlogic.gdx.Files.FileType;
import com.badlogic.gdx.backends.lwjgl.LwjglFileHandle;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Pool;
import com.esotericsoftware.spine.AnimationState.AnimationStateListener;
import com.esotericsoftware.spine.AnimationState.TrackEntry;
import com.esotericsoftware.spine.attachments.AttachmentLoader;
import com.esotericsoftware.spine.attachments.BoundingBoxAttachment;
import com.esotericsoftware.spine.attachments.ClippingAttachment;
import com.esotericsoftware.spine.attachments.MeshAttachment;
import com.esotericsoftware.spine.attachments.PathAttachment;
import com.esotericsoftware.spine.attachments.PointAttachment;
import com.esotericsoftware.spine.attachments.RegionAttachment;
public class AnimationStateTests {
final SkeletonJson json = new SkeletonJson(new AttachmentLoader() {
public RegionAttachment newRegionAttachment (Skin skin, String name, String path) {
return null;
}
public MeshAttachment newMeshAttachment (Skin skin, String name, String path) {
return null;
}
public BoundingBoxAttachment newBoundingBoxAttachment (Skin skin, String name) {
return null;
}
public ClippingAttachment newClippingAttachment (Skin skin, String name) {
return null;
}
public PathAttachment newPathAttachment (Skin skin, String name) {
return null;
}
public PointAttachment newPointAttachment (Skin skin, String name) {
return null;
}
});
final AnimationStateListener stateListener = new AnimationStateListener() {
public void start (TrackEntry entry) {
add(actual("start", entry));
}
public void interrupt (TrackEntry entry) {
add(actual("interrupt", entry));
}
public void end (TrackEntry entry) {
add(actual("end", entry));
}
public void dispose (TrackEntry entry) {
add(actual("dispose", entry));
}
public void complete (TrackEntry entry) {
add(actual("complete", entry));
}
public void event (TrackEntry entry, Event event) {
add(actual("event " + event.getString(), entry));
}
private void add (Result result) {
while (expected.size > actual.size) {
Result note = expected.get(actual.size);
if (!note.note) break;
actual.add(note);
log(note.name);
}
String message = result.toString();
if (actual.size >= expected.size) {
message += "FAIL: <none>";
fail = true;
} else if (!expected.get(actual.size).equals(result)) {
message += "FAIL: " + expected.get(actual.size);
fail = true;
} else
message += "PASS";
log(message);
actual.add(result);
}
};
final SkeletonData skeletonData;
final Array<Result> actual = new Array();
final Array<Result> expected = new Array();
AnimationStateData stateData;
AnimationState state;
int entryCount;
float time = 0;
boolean fail;
int test;
AnimationStateTests () {
skeletonData = json.readSkeletonData(new LwjglFileHandle("test/test.json", FileType.Internal));
TrackEntry entry;
setup("0.1 time step", // 1
expect(0, "start", 0, 0), //
expect(0, "event 0", 0, 0), //
expect(0, "event 14", 0.5f, 0.5f), //
expect(0, "event 30", 1, 1), //
expect(0, "complete", 1, 1), //
expect(0, "end", 1, 1.1f), //
expect(0, "dispose", 1, 1.1f) //
);
state.setAnimation(0, "events0", false).setTrackEnd(1);
run(0.1f, 1000, null);
setup("1/60 time step, dispose queued", // 2
expect(0, "start", 0, 0), //
expect(0, "interrupt", 0, 0), //
expect(0, "end", 0, 0), //
expect(0, "dispose", 0, 0), //
expect(1, "dispose", 0, 0), //
expect(0, "dispose", 0, 0), //
expect(1, "dispose", 0, 0), //
note("First 2 set/addAnimation calls are done."),
expect(0, "start", 0, 0), //
expect(0, "event 0", 0, 0), //
expect(0, "event 14", 0.483f, 0.483f), //
expect(0, "event 30", 1, 1), //
expect(0, "complete", 1, 1), //
expect(0, "end", 1, 1.017f), //
expect(0, "dispose", 1, 1.017f) //
);
state.setAnimation(0, "events0", false);
state.addAnimation(0, "events1", false, 0);
state.addAnimation(0, "events0", false, 0);
state.addAnimation(0, "events1", false, 0);
state.setAnimation(0, "events0", false).setTrackEnd(1);
run(1 / 60f, 1000, null);
setup("30 time step", // 3
expect(0, "start", 0, 0), //
expect(0, "event 0", 0, 0), //
expect(0, "event 14", 30, 30), //
expect(0, "event 30", 30, 30), //
expect(0, "complete", 30, 30), //
expect(0, "end", 30, 60), //
expect(0, "dispose", 30, 60) //
);
state.setAnimation(0, "events0", false).setTrackEnd(1);
run(30, 1000, null);
setup("1 time step", // 4
expect(0, "start", 0, 0), //
expect(0, "event 0", 0, 0), //
expect(0, "event 14", 1, 1), //
expect(0, "event 30", 1, 1), //
expect(0, "complete", 1, 1), //
expect(0, "end", 1, 2), //
expect(0, "dispose", 1, 2) //
);
state.setAnimation(0, "events0", false).setTrackEnd(1);
run(1, 1.01f, null);
setup("interrupt", // 5
expect(0, "start", 0, 0), //
expect(0, "event 0", 0, 0), //
expect(0, "event 14", 0.5f, 0.5f), //
expect(0, "event 30", 1, 1), //
expect(0, "complete", 1, 1), //
expect(0, "interrupt", 1.1f, 1.1f), //
expect(1, "start", 0.1f, 1.1f), //
expect(1, "event 0", 0.1f, 1.1f), //
expect(0, "end", 1.1f, 1.2f), //
expect(0, "dispose", 1.1f, 1.2f), //
expect(1, "event 14", 0.5f, 1.5f), //
expect(1, "event 30", 1, 2), //
expect(1, "complete", 1, 2), //
expect(1, "interrupt", 1.1f, 2.1f), //
expect(0, "start", 0.1f, 2.1f), //
expect(0, "event 0", 0.1f, 2.1f), //
expect(1, "end", 1.1f, 2.2f), //
expect(1, "dispose", 1.1f, 2.2f), //
expect(0, "event 14", 0.5f, 2.5f), //
expect(0, "event 30", 1, 3), //
expect(0, "complete", 1, 3), //
expect(0, "end", 1, 3.1f), //
expect(0, "dispose", 1, 3.1f) //
);
state.setAnimation(0, "events0", false);
state.addAnimation(0, "events1", false, 0);
state.addAnimation(0, "events0", false, 0).setTrackEnd(1);
run(0.1f, 4f, null);
setup("interrupt with delay", // 6
expect(0, "start", 0, 0), //
expect(0, "event 0", 0, 0), //
expect(0, "event 14", 0.5f, 0.5f), //
expect(0, "interrupt", 0.6f, 0.6f), //
expect(1, "start", 0.1f, 0.6f), //
expect(1, "event 0", 0.1f, 0.6f), //
expect(0, "end", 0.6f, 0.7f), //
expect(0, "dispose", 0.6f, 0.7f), //
expect(1, "event 14", 0.5f, 1.0f), //
expect(1, "event 30", 1, 1.5f), //
expect(1, "complete", 1, 1.5f), //
expect(1, "end", 1, 1.6f), //
expect(1, "dispose", 1, 1.6f) //
);
state.setAnimation(0, "events0", false);
state.addAnimation(0, "events1", false, 0.5f).setTrackEnd(1);
run(0.1f, 1000, null);
setup("interrupt with delay and mix time", // 7
expect(0, "start", 0, 0), //
expect(0, "event 0", 0, 0), //
expect(0, "event 14", 0.5f, 0.5f), //
expect(0, "interrupt", 1, 1), //
expect(1, "start", 0.1f, 1), //
expect(0, "complete", 1, 1), //
expect(1, "event 0", 0.1f, 1), //
expect(1, "event 14", 0.5f, 1.4f), //
expect(0, "end", 1.6f, 1.7f), //
expect(0, "dispose", 1.6f, 1.7f), //
expect(1, "event 30", 1, 1.9f), //
expect(1, "complete", 1, 1.9f), //
expect(1, "end", 1, 2), //
expect(1, "dispose", 1, 2) //
);
stateData.setMix("events0", "events1", 0.7f);
state.setAnimation(0, "events0", true);
state.addAnimation(0, "events1", false, 0.9f).setTrackEnd(1);
run(0.1f, 1000, null);
setup("animation 0 events do not fire during mix", // 8
expect(0, "start", 0, 0), //
expect(0, "event 0", 0, 0), //
expect(0, "interrupt", 0.5f, 0.5f), //
expect(1, "start", 0.1f, 0.5f), //
expect(1, "event 0", 0.1f, 0.5f), //
expect(1, "event 14", 0.5f, 0.9f), //
expect(0, "complete", 1, 1), //
expect(0, "end", 1.1f, 1.2f), //
expect(0, "dispose", 1.1f, 1.2f), //
expect(1, "event 30", 1, 1.4f), //
expect(1, "complete", 1, 1.4f), //
expect(1, "end", 1, 1.5f), //
expect(1, "dispose", 1, 1.5f) //
);
stateData.setDefaultMix(0.7f);
state.setAnimation(0, "events0", false);
state.addAnimation(0, "events1", false, 0.4f).setTrackEnd(1);
run(0.1f, 1000, null);
setup("event threshold, some animation 0 events fire during mix", // 9
expect(0, "start", 0, 0), //
expect(0, "event 0", 0, 0), //
expect(0, "interrupt", 0.5f, 0.5f), //
expect(1, "start", 0.1f, 0.5f), //
expect(0, "event 14", 0.5f, 0.5f), //
expect(1, "event 0", 0.1f, 0.5f), //
expect(1, "event 14", 0.5f, 0.9f), //
expect(0, "complete", 1, 1), //
expect(0, "end", 1.1f, 1.2f), //
expect(0, "dispose", 1.1f, 1.2f), //
expect(1, "event 30", 1, 1.4f), //
expect(1, "complete", 1, 1.4f), //
expect(1, "end", 1, 1.5f), //
expect(1, "dispose", 1, 1.5f) //
);
stateData.setMix("events0", "events1", 0.7f);
state.setAnimation(0, "events0", false).setEventThreshold(0.5f);
state.addAnimation(0, "events1", false, 0.4f).setTrackEnd(1);
run(0.1f, 1000, null);
setup("event threshold, all animation 0 events fire during mix", // 10
expect(0, "start", 0, 0), //
expect(0, "event 0", 0, 0), //
expect(0, "event 14", 0.5f, 0.5f), //
expect(0, "interrupt", 0.9f, 0.9f), //
expect(1, "start", 0.1f, 0.9f), //
expect(1, "event 0", 0.1f, 0.9f), //
expect(0, "event 30", 1, 1), //
expect(0, "complete", 1, 1), //
expect(0, "event 0", 1, 1), //
expect(1, "event 14", 0.5f, 1.3f), //
expect(0, "end", 1.5f, 1.6f), //
expect(0, "dispose", 1.5f, 1.6f), //
expect(1, "event 30", 1, 1.8f), //
expect(1, "complete", 1, 1.8f), //
expect(1, "end", 1, 1.9f), //
expect(1, "dispose", 1, 1.9f) //
);
state.setAnimation(0, "events0", true).setEventThreshold(1);
entry = state.addAnimation(0, "events1", false, 0.8f);
entry.setMixDuration(0.7f);
entry.setTrackEnd(1);
run(0.1f, 1000, null);
setup("looping", // 11
expect(0, "start", 0, 0), //
expect(0, "event 0", 0, 0), //
expect(0, "event 14", 0.5f, 0.5f), //
expect(0, "event 30", 1, 1), //
expect(0, "complete", 1, 1), //
expect(0, "event 0", 1, 1), //
expect(0, "event 14", 1.5f, 1.5f), //
expect(0, "event 30", 2, 2), //
expect(0, "complete", 2, 2), //
expect(0, "event 0", 2, 2), //
expect(0, "event 14", 2.5f, 2.5f), //
expect(0, "event 30", 3, 3), //
expect(0, "complete", 3, 3), //
expect(0, "event 0", 3, 3), //
expect(0, "event 14", 3.5f, 3.5f), //
expect(0, "event 30", 4, 4), //
expect(0, "complete", 4, 4), //
expect(0, "event 0", 4, 4), //
expect(0, "end", 4.1f, 4.1f), //
expect(0, "dispose", 4.1f, 4.1f) //
);
state.setAnimation(0, "events0", true);
run(0.1f, 4, null);
setup("not looping, track end past animation 0 duration", // 12
expect(0, "start", 0, 0), //
expect(0, "event 0", 0, 0), //
expect(0, "event 14", 0.5f, 0.5f), //
expect(0, "event 30", 1, 1), //
expect(0, "complete", 1, 1), //
expect(0, "interrupt", 2.1f, 2.1f), //
expect(1, "start", 0.1f, 2.1f), //
expect(1, "event 0", 0.1f, 2.1f), //
expect(0, "end", 2.1f, 2.2f), //
expect(0, "dispose", 2.1f, 2.2f), //
expect(1, "event 14", 0.5f, 2.5f), //
expect(1, "event 30", 1, 3), //
expect(1, "complete", 1, 3), //
expect(1, "end", 1, 3.1f), //
expect(1, "dispose", 1, 3.1f) //
);
state.setAnimation(0, "events0", false);
state.addAnimation(0, "events1", false, 2).setTrackEnd(1);
run(0.1f, 4f, null);
setup("interrupt animation after first loop complete", // 13
expect(0, "start", 0, 0), //
expect(0, "event 0", 0, 0), //
expect(0, "event 14", 0.5f, 0.5f), //
expect(0, "event 30", 1, 1), //
expect(0, "complete", 1, 1), //
expect(0, "event 0", 1, 1), //
expect(0, "event 14", 1.5f, 1.5f), //
expect(0, "event 30", 2, 2), //
expect(0, "complete", 2, 2), //
expect(0, "event 0", 2, 2), //
expect(0, "interrupt", 2.1f, 2.1f), //
expect(1, "start", 0.1f, 2.1f), //
expect(1, "event 0", 0.1f, 2.1f), //
expect(0, "end", 2.1f, 2.2f), //
expect(0, "dispose", 2.1f, 2.2f), //
expect(1, "event 14", 0.5f, 2.5f), //
expect(1, "event 30", 1, 3), //
expect(1, "complete", 1, 3), //
expect(1, "end", 1, 3.1f), //
expect(1, "dispose", 1, 3.1f) //
);
state.setAnimation(0, "events0", true);
run(0.1f, 6, new TestListener() {
public void frame (float time) {
if (MathUtils.isEqual(time, 1.4f)) state.addAnimation(0, "events1", false, 0).setTrackEnd(1);
}
});
setup("add animation on empty track", // 14
expect(0, "start", 0, 0), //
expect(0, "event 0", 0, 0), //
expect(0, "event 14", 0.5f, 0.5f), //
expect(0, "event 30", 1, 1), //
expect(0, "complete", 1, 1), //
expect(0, "end", 1, 1.1f), //
expect(0, "dispose", 1, 1.1f) //
);
state.addAnimation(0, "events0", false, 0).setTrackEnd(1);
run(0.1f, 1.9f, null);
setup("end time beyond non-looping animation duration", // 15
expect(0, "start", 0, 0), //
expect(0, "event 0", 0, 0), //
expect(0, "event 14", 0.5f, 0.5f), //
expect(0, "event 30", 1, 1), //
expect(0, "complete", 1, 1), //
expect(0, "end", 9f, 9.1f), //
expect(0, "dispose", 9f, 9.1f) //
);
state.setAnimation(0, "events0", false).setTrackEnd(9);
run(0.1f, 10, null);
setup("looping with animation start", // 16
expect(0, "start", 0, 0), //
expect(0, "event 30", 0.4f, 0.4f), //
expect(0, "complete", 0.4f, 0.4f), //
expect(0, "event 30", 0.8f, 0.8f), //
expect(0, "complete", 0.8f, 0.8f), //
expect(0, "event 30", 1.2f, 1.2f), //
expect(0, "complete", 1.2f, 1.2f), //
expect(0, "end", 1.4f, 1.4f), //
expect(0, "dispose", 1.4f, 1.4f) //
);
entry = state.setAnimation(0, "events0", true);
entry.setAnimationLast(0.6f);
entry.setAnimationStart(0.6f);
run(0.1f, 1.4f, null);
setup("looping with animation start and end", // 17
expect(0, "start", 0, 0), //
expect(0, "event 14", 0.3f, 0.3f), //
expect(0, "complete", 0.6f, 0.6f), //
expect(0, "event 14", 0.9f, 0.9f), //
expect(0, "complete", 1.2f, 1.2f), //
expect(0, "event 14", 1.5f, 1.5f), //
expect(0, "end", 1.8f, 1.8f), //
expect(0, "dispose", 1.8f, 1.8f) //
);
entry = state.setAnimation(0, "events0", true);
entry.setAnimationStart(0.2f);
entry.setAnimationLast(0.2f);
entry.setAnimationEnd(0.8f);
run(0.1f, 1.8f, null);
setup("non-looping with animation start and end", // 18
expect(0, "start", 0, 0), //
expect(0, "event 14", 0.3f, 0.3f), //
expect(0, "complete", 0.6f, 0.6f), //
expect(0, "end", 1, 1.1f), //
expect(0, "dispose", 1, 1.1f) //
);
entry = state.setAnimation(0, "events0", false);
entry.setAnimationStart(0.2f);
entry.setAnimationLast(0.2f);
entry.setAnimationEnd(0.8f);
entry.setTrackEnd(1);
run(0.1f, 1.8f, null);
setup("mix out looping with animation start and end", // 19
expect(0, "start", 0, 0), //
expect(0, "event 14", 0.3f, 0.3f), //
expect(0, "complete", 0.6f, 0.6f), //
expect(0, "interrupt", 0.8f, 0.8f), //
expect(1, "start", 0.1f, 0.8f), //
expect(1, "event 0", 0.1f, 0.8f), //
expect(0, "event 14", 0.9f, 0.9f), //
expect(0, "complete", 1.2f, 1.2f), //
expect(1, "event 14", 0.5f, 1.2f), //
expect(0, "end", 1.4f, 1.5f), //
expect(0, "dispose", 1.4f, 1.5f), //
expect(1, "event 30", 1, 1.7f), //
expect(1, "complete", 1, 1.7f), //
expect(1, "end", 1, 1.8f), //
expect(1, "dispose", 1, 1.8f) //
);
entry = state.setAnimation(0, "events0", true);
entry.setAnimationStart(0.2f);
entry.setAnimationLast(0.2f);
entry.setAnimationEnd(0.8f);
entry.setEventThreshold(1);
entry = state.addAnimation(0, "events1", false, 0.7f);
entry.setMixDuration(0.7f);
entry.setTrackEnd(1);
run(0.1f, 20, null);
setup("setAnimation with track entry mix", // 20
expect(0, "start", 0, 0), //
expect(0, "event 0", 0, 0), //
expect(0, "event 14", 0.5f, 0.5f), //
expect(0, "event 30", 1, 1), //
expect(0, "complete", 1, 1), //
expect(0, "event 0", 1, 1), //
expect(0, "interrupt", 1, 1), //
expect(1, "start", 0, 1), //
expect(1, "event 0", 0.1f, 1.1f), //
expect(1, "event 14", 0.5f, 1.5f), //
expect(0, "end", 1.7f, 1.8f), //
expect(0, "dispose", 1.7f, 1.8f), //
expect(1, "event 30", 1, 2), //
expect(1, "complete", 1, 2), //
expect(1, "end", 1, 2.1f), //
expect(1, "dispose", 1, 2.1f) //
);
state.setAnimation(0, "events0", true);
run(0.1f, 1000, new TestListener() {
public void frame (float time) {
if (MathUtils.isEqual(time, 1f)) {
TrackEntry entry = state.setAnimation(0, "events1", false);
entry.setMixDuration(0.7f);
entry.setTrackEnd(1);
}
}
});
setup("setAnimation twice", // 21
expect(0, "start", 0, 0), //
expect(0, "interrupt", 0, 0), //
expect(0, "end", 0, 0), //
expect(0, "dispose", 0, 0), //
expect(1, "start", 0, 0), //
expect(1, "event 0", 0, 0), //
expect(1, "event 14", 0.5f, 0.5f), //
note("First 2 setAnimation calls are done."),
expect(1, "interrupt", 0.8f, 0.8f), //
expect(0, "start", 0, 0.8f), //
expect(0, "interrupt", 0, 0.8f), //
expect(0, "end", 0, 0.8f), //
expect(0, "dispose", 0, 0.8f), //
expect(2, "start", 0, 0.8f), //
expect(2, "event 0", 0.1f, 0.9f), //
expect(1, "end", 0.9f, 1), //
expect(1, "dispose", 0.9f, 1), //
expect(2, "event 14", 0.5f, 1.3f), //
expect(2, "event 30", 1, 1.8f), //
expect(2, "complete", 1, 1.8f), //
expect(2, "end", 1, 1.9f), //
expect(2, "dispose", 1, 1.9f) //
);
state.setAnimation(0, "events0", false); // First should be ignored.
state.setAnimation(0, "events1", false);
run(0.1f, 1000, new TestListener() {
public void frame (float time) {
if (MathUtils.isEqual(time, 0.8f)) {
state.setAnimation(0, "events0", false); // First should be ignored.
state.setAnimation(0, "events2", false).setTrackEnd(1);
}
}
});
setup("setAnimation twice with multiple mixing", // 22
expect(0, "start", 0, 0), //
expect(0, "interrupt", 0, 0), //
expect(0, "end", 0, 0), //
expect(0, "dispose", 0, 0), //
expect(1, "start", 0, 0), //
expect(1, "event 0", 0, 0), //
note("First 2 setAnimation calls are done."),
expect(1, "interrupt", 0.2f, 0.2f), //
expect(0, "start", 0, 0.2f), //
expect(0, "interrupt", 0, 0.2f), //
expect(0, "end", 0, 0.2f), //
expect(0, "dispose", 0, 0.2f), //
expect(2, "start", 0, 0.2f), //
expect(2, "event 0", 0.1f, 0.3f), //
note("Second 2 setAnimation calls are done."),
expect(2, "interrupt", 0.2f, 0.4f), //
expect(1, "start", 0, 0.4f), //
expect(1, "interrupt", 0, 0.4f), //
expect(1, "end", 0, 0.4f), //
expect(1, "dispose", 0, 0.4f), //
expect(0, "start", 0, 0.4f), //
expect(0, "event 0", 0.1f, 0.5f), //
expect(0, "event 14", 0.5f, 0.9f), //
expect(2, "end", 0.8f, 1.1f), //
expect(2, "dispose", 0.8f, 1.1f), //
expect(1, "end", 0.8f, 1.1f), //
expect(1, "dispose", 0.8f, 1.1f), //
expect(0, "event 30", 1, 1.4f), //
expect(0, "complete", 1, 1.4f), //
expect(0, "end", 1, 1.5f), //
expect(0, "dispose", 1, 1.5f) //
);
stateData.setDefaultMix(0.6f);
state.setAnimation(0, "events0", false); // First should be ignored.
state.setAnimation(0, "events1", false);
run(0.1f, 1000, new TestListener() {
public void frame (float time) {
if (MathUtils.isEqual(time, 0.2f)) {
state.setAnimation(0, "events0", false); // First should be ignored.
state.setAnimation(0, "events2", false);
}
if (MathUtils.isEqual(time, 0.4f)) {
state.setAnimation(0, "events1", false); // First should be ignored.
state.setAnimation(0, "events0", false).setTrackEnd(1);
}
}
});
setup("addAnimation with delay on empty track", // 23
expect(0, "start", 0, 0), //
expect(0, "event 0", 0, 5), //
expect(0, "event 14", 0.5f, 5.5f), //
expect(0, "event 30", 1, 6), //
expect(0, "complete", 1, 6), //
expect(0, "end", 1, 6.1f), //
expect(0, "dispose", 1, 6.1f) //
);
state.addAnimation(0, "events0", false, 5).setTrackEnd(1);
run(0.1f, 10, null);
setup("setAnimation during AnimationStateListener"); // 24
state.addListener(new AnimationStateListener() {
public void start (TrackEntry entry) {
if (entry.getAnimation().getName().equals("events0")) state.setAnimation(1, "events1", false);
}
public void interrupt (TrackEntry entry) {
state.addAnimation(3, "events1", false, 0);
}
public void end (TrackEntry entry) {
if (entry.getAnimation().getName().equals("events0")) state.setAnimation(0, "events1", false);
}
public void dispose (TrackEntry entry) {
if (entry.getAnimation().getName().equals("events0")) state.setAnimation(1, "events1", false);
}
public void complete (TrackEntry entry) {
if (entry.getAnimation().getName().equals("events0")) state.setAnimation(1, "events1", false);
}
public void event (TrackEntry entry, Event event) {
if (entry.getTrackIndex() != 2) state.setAnimation(2, "events1", false);
}
});
state.addAnimation(0, "events0", false, 0);
state.addAnimation(0, "events1", false, 0);
state.setAnimation(1, "events1", false).setTrackEnd(1);
run(0.1f, 10, null);
setup("clearTrack", // 25
expect(0, "start", 0, 0), //
expect(0, "event 0", 0, 0), //
expect(0, "event 14", 0.5f, 0.5f), //
expect(0, "end", 0.7f, 0.7f), //
expect(0, "dispose", 0.7f, 0.7f) //
);
state.addAnimation(0, "events0", false, 0).setTrackEnd(1);
run(0.1f, 10, new TestListener() {
public void frame (float time) {
if (MathUtils.isEqual(time, 0.7f)) state.clearTrack(0);
}
});
setup("setEmptyAnimation", // 26
expect(0, "start", 0, 0), //
expect(0, "event 0", 0, 0), //
expect(0, "event 14", 0.5f, 0.5f), //
expect(0, "interrupt", 0.7f, 0.7f), //
expect(-1, "start", 0, 0.7f), //
expect(-1, "complete", 0.1f, 0.8f), //
expect(0, "end", 0.8f, 0.9f), //
expect(0, "dispose", 0.8f, 0.9f), //
expect(-1, "end", 0.2f, 1), //
expect(-1, "dispose", 0.2f, 1) //
);
state.addAnimation(0, "events0", false, 0).setTrackEnd(1);
run(0.1f, 10, new TestListener() {
public void frame (float time) {
if (MathUtils.isEqual(time, 0.7f)) state.setEmptyAnimation(0, 0);
}
});
setup("TrackEntry listener"); // 27
final AtomicInteger counter = new AtomicInteger();
state.addAnimation(0, "events0", false, 0).setListener(new AnimationStateListener() {
public void start (TrackEntry entry) {
counter.addAndGet(1 << 1);
}
public void interrupt (TrackEntry entry) {
counter.addAndGet(1 << 5);
}
public void end (TrackEntry entry) {
counter.addAndGet(1 << 9);
}
public void dispose (TrackEntry entry) {
counter.addAndGet(1 << 13);
}
public void complete (TrackEntry entry) {
counter.addAndGet(1 << 17);
}
public void event (TrackEntry entry, Event event) {
counter.addAndGet(1 << 21);
}
});
state.addAnimation(0, "events0", false, 0);
state.addAnimation(0, "events1", false, 0);
state.setAnimation(1, "events1", false).setTrackEnd(1);
run(0.1f, 10, null);
if (counter.get() != 15082016) {
log("TEST 28 FAILED! " + counter);
System.exit(0);
}
System.out.println("AnimationState tests passed.");
}
void setup (String description, Result... expectedArray) {
test++;
expected.addAll(expectedArray);
stateData = new AnimationStateData(skeletonData);
state = new AnimationState(stateData);
state.trackEntryPool = new Pool<TrackEntry>() {
public TrackEntry obtain () {
TrackEntry entry = super.obtain();
entryCount++;
// System.out.println("+1: " + entryCount + " " + entry.hashCode());
return entry;
}
protected TrackEntry newObject () {
return new TrackEntry();
}
public void free (TrackEntry entry) {
entryCount--;
// System.out.println("-1: " + entryCount + " " + entry.hashCode());
super.free(entry);
}
};
time = 0;
fail = false;
log(test + ": " + description);
if (expectedArray.length > 0) {
state.addListener(stateListener);
log(String.format("%-3s%-12s%-7s%-7s%-7s", "#", "EVENT", "TRACK", "TOTAL", "RESULT"));
}
}
void run (float incr, float endTime, TestListener listener) {
Skeleton skeleton = new Skeleton(skeletonData);
state.apply(skeleton);
while (time < endTime) {
time += incr;
skeleton.update(incr);
state.update(incr);
// Reduce float discrepancies for tests.
for (TrackEntry entry : state.getTracks()) {
if (entry == null) continue;
entry.trackTime = round(entry.trackTime, 6);
entry.delay = round(entry.delay, 3);
if (entry.mixingFrom != null) entry.mixingFrom.trackTime = round(entry.mixingFrom.trackTime, 6);
}
state.apply(skeleton);
// Apply multiple times to ensure no side effects.
if (expected.size > 0) state.removeListener(stateListener);
state.apply(skeleton);
state.apply(skeleton);
if (expected.size > 0) state.addListener(stateListener);
if (listener != null) listener.frame(time);
}
state.clearTracks();
// Expecting more than actual is a failure.
for (int i = actual.size, n = expected.size; i < n; i++) {
log(String.format("%-29s", "<none>") + "FAIL: " + expected.get(i));
fail = true;
}
// Check all allocated entries were freed.
if (!fail) {
if (entryCount != 0) {
log("FAIL: Pool balance: " + entryCount);
fail = true;
}
}
actual.clear();
expected.clear();
log("");
if (fail) {
log("TEST " + test + " FAILED!");
System.exit(0);
}
}
Result expect (int animationIndex, String name, float trackTime, float totalTime) {
Result result = new Result();
result.name = name;
result.animationIndex = animationIndex;
result.trackTime = trackTime;
result.totalTime = totalTime;
return result;
}
Result actual (String name, TrackEntry entry) {
Result result = new Result();
result.name = name;
result.animationIndex = skeletonData.getAnimations().indexOf(entry.animation, true);
result.trackTime = Math.round(entry.trackTime * 1000) / 1000f;
result.totalTime = Math.round(time * 1000) / 1000f;
return result;
}
Result note (String message) {
Result result = new Result();
result.name = message;
result.note = true;
return result;
}
void log (String message) {
System.out.println(message);
}
class Result {
String name;
int animationIndex;
float trackTime, totalTime;
boolean note;
public int hashCode () {
int result = 31 + animationIndex;
result = 31 * result + name.hashCode();
result = 31 * result + Float.floatToIntBits(totalTime);
result = 31 * result + Float.floatToIntBits(trackTime);
return result;
}
public boolean equals (Object obj) {
Result other = (Result)obj;
if (animationIndex != other.animationIndex) return false;
if (!name.equals(other.name)) return false;
if (!MathUtils.isEqual(totalTime, other.totalTime)) return false;
if (!MathUtils.isEqual(trackTime, other.trackTime)) return false;
return true;
}
public String toString () {
return String.format("%-3s%-12s%-7s%-7s", "" + animationIndex, name, roundTime(trackTime), roundTime(totalTime));
}
}
static float round (float value, int decimals) {
float shift = (float)Math.pow(10, decimals);
return Math.round(value * shift) / shift;
}
static String roundTime (float value) {
String text = Float.toString(round(value, 3));
return text.endsWith(".0") ? text.substring(0, text.length() - 2) : text;
}
static interface TestListener {
void frame (float time);
}
static public void main (String[] args) throws Exception {
new AnimationStateTests();
}
}