Skip to content

Commit

Permalink
fix: do not ignore the animation length property
Browse files Browse the repository at this point in the history
  • Loading branch information
yusshu committed Oct 9, 2023
1 parent d1e8aeb commit 3cbf607
Show file tree
Hide file tree
Showing 7 changed files with 46 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,18 @@
public final class Animation implements Examinable {

private final String name;
private final int length;
private final LoopMode loopMode;
private final Map<String, Timeline> timelines;

public Animation(
String name,
int length,
LoopMode loopMode,
Map<String, Timeline> timelines
) {
this.name = Objects.requireNonNull(name, "name");
this.length = length;
this.loopMode = Objects.requireNonNull(loopMode, "loopMode");
this.timelines = Objects.requireNonNull(timelines, "timelines");
}
Expand All @@ -67,6 +70,17 @@ public String name() {
return name;
}

/**
* Returns the animation length, in
* ticks
*
* @return The animation length
* @since 1.0.0
*/
public int length() {
return length;
}

/**
* Returns the animation loop mode,
* which specifies what the animation
Expand Down Expand Up @@ -100,6 +114,7 @@ public Map<String, Timeline> timelines() {
public @NotNull Stream<? extends ExaminableProperty> examinableProperties() {
return Stream.of(
ExaminableProperty.of("name", name),
ExaminableProperty.of("length", length),
ExaminableProperty.of("loopMode", loopMode),
ExaminableProperty.of("timelines", timelines)
);
Expand All @@ -111,13 +126,14 @@ public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
Animation that = (Animation) o;
return name.equals(that.name)
&& length == that.length
&& loopMode == that.loopMode
&& timelines.equals(that.timelines);
}

@Override
public int hashCode() {
return Objects.hash(name, loopMode, timelines);
return Objects.hash(name, length, loopMode, timelines);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,16 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;

final class DynamicTimeline implements Timeline {

private final Map<Channel, List<AnimationEntry>> entries = new HashMap<>();
private final int length;

DynamicTimeline(int length) {
this.length = length;
}

@Override
public void put(int position, Channel channel, Vector3Float value) {
Expand All @@ -47,7 +53,7 @@ public void put(int position, Channel channel, Vector3Float value) {

@Override
public @NotNull Iterator<KeyFrame> iterator() {
return new DynamicKeyFrameIterator(entries);
return new DynamicKeyFrameIterator(entries, length);
}

private static final class AnimationEntry {
Expand Down Expand Up @@ -78,6 +84,8 @@ private static class DynamicKeyFrameIterator
*/
private final Iterator<AnimationEntry>[] iterators;

private final int length;

/**
* Represents the current tick of the iteration,
* it increments in 1 in every next() call when
Expand All @@ -92,8 +100,9 @@ private static class DynamicKeyFrameIterator
private final Vector3Float[] nextValues = new Vector3Float[CHANNEL_COUNT];

@SuppressWarnings({"unchecked"})
public DynamicKeyFrameIterator(Map<Channel, List<AnimationEntry>> entries) {
public DynamicKeyFrameIterator(Map<Channel, List<AnimationEntry>> entries, int length) {
this.iterators = new Iterator[CHANNEL_COUNT];
this.length = length;

for (Channel channel : Channel.values()) {
int index = channel.ordinal();
Expand Down Expand Up @@ -122,6 +131,9 @@ public DynamicKeyFrameIterator(Map<Channel, List<AnimationEntry>> entries) {

@Override
public boolean hasNext() {
if (tick >= length) {
return false;
}
// if there are next frames, there must be non-explored
// entries or currently exploring entries must not be
// finishing
Expand All @@ -138,6 +150,10 @@ public boolean hasNext() {
@Override
public KeyFrame next() {

if (tick >= length) {
throw new NoSuchElementException("No more keyframes in the timeline! (tick >= length)");
}

Vector3Float position = Vector3Float.ZERO;
Vector3Float rotation = Vector3Float.ZERO;
Vector3Float scale = Vector3Float.ONE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ public Vector3Float initialValue() {
* @return A new dynamic timeline instance
* @since 1.0.0
*/
static Timeline dynamic() {
return new DynamicTimeline();
static Timeline dynamic(int length) {
return new DynamicTimeline(length);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public synchronized void queue(Animation animation, int transitionTicks) {
Map<String, Timeline> framesByBone = new HashMap<>();

lastFrames.forEach((boneName, frame) -> {
Timeline keyFrames = framesByBone.computeIfAbsent(boneName, k -> Timeline.dynamic());
Timeline keyFrames = framesByBone.computeIfAbsent(boneName, k -> Timeline.dynamic(animation.length()));
keyFrames.put(0, Timeline.Channel.POSITION, frame.position());
keyFrames.put(0, Timeline.Channel.ROTATION, frame.rotation());
keyFrames.put(0, Timeline.Channel.SCALE, frame.scale());
Expand All @@ -80,7 +80,7 @@ public synchronized void queue(Animation animation, int transitionTicks) {
if (iterator.hasNext()) {
KeyFrame firstFrame = frames.iterator().next();

Timeline keyFrames = framesByBone.computeIfAbsent(boneName, k -> Timeline.dynamic());
Timeline keyFrames = framesByBone.computeIfAbsent(boneName, k -> Timeline.dynamic(animation.length()));
keyFrames.put(transitionTicks, Timeline.Channel.POSITION, firstFrame.position());
keyFrames.put(transitionTicks, Timeline.Channel.ROTATION, firstFrame.rotation());
keyFrames.put(transitionTicks, Timeline.Channel.SCALE, firstFrame.scale());
Expand All @@ -89,6 +89,7 @@ public synchronized void queue(Animation animation, int transitionTicks) {

queue.addFirst(new Animation(
"generated-transition",
transitionTicks,
Animation.LoopMode.HOLD,
framesByBone
));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,13 @@ public void test_hold_loop_mode() {
}

private void queue(Animation.LoopMode loopMode) {
Timeline timeline = Timeline.dynamic();
Timeline timeline = Timeline.dynamic(LENGTH);
timeline.put(0, Timeline.Channel.POSITION, START);
timeline.put(LENGTH, Timeline.Channel.POSITION, END);

Map<String, Timeline> timelines = new HashMap<>();
timelines.put("bone", timeline);
controller.queue(new Animation("test", loopMode, timelines));
controller.queue(new Animation("test", LENGTH, loopMode, timelines));
}

private void consume() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public class TimelineTest {
@Test
@DisplayName("Test that dynamic timeline keyframe linear interpolation works")
public void test_linear_interpolation() {
Timeline timeline = Timeline.dynamic();
Timeline timeline = Timeline.dynamic(DEPTH);

timeline.put(0, Timeline.Channel.POSITION, Vector3Float.ZERO);
timeline.put(DEPTH, Timeline.Channel.POSITION, Vector3Float.ONE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ static void readAnimations(

if (GsonUtil.isNullOrAbsent(animationJson, "animators")) {
// empty animation, no keyframes of any kind
animations.put(name, new Animation(name, loopMode, Collections.emptyMap()));
animations.put(name, new Animation(name, length, loopMode, Collections.emptyMap()));
continue;
}

Expand All @@ -78,7 +78,7 @@ static void readAnimations(
JsonObject animatorJson = animatorEntry.getValue().getAsJsonObject();
String boneName = animatorJson.get("name").getAsString();

Timeline frames = Timeline.dynamic();
Timeline frames = Timeline.dynamic(length);

for (JsonElement keyFrameElement : animatorJson.get("keyframes").getAsJsonArray()) {

Expand Down Expand Up @@ -108,7 +108,7 @@ static void readAnimations(
animators.put(boneName, frames);
}

animations.put(name, new Animation(name, loopMode, animators));
animations.put(name, new Animation(name, length, loopMode, animators));
}
}

Expand Down

0 comments on commit 3cbf607

Please sign in to comment.