/*
* Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.scenario.animation.shared;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.beans.value.WritableValue;
General implementation of ClipInterpolator, which covers all use-cases.
/**
* General implementation of ClipInterpolator, which covers all use-cases.
*/
// @@OPT:
// - A binary search in interpolate might make sense.
// - Prepare only first segment when starting timeline and do the rest later =>
// improves startup time?
// - Store 1 / (rightMillis - leftMillis) for each interval and multiply
class GeneralClipInterpolator extends ClipInterpolator {
private KeyFrame[] keyFrames;
private long[] keyFrameTicks;
// List of interpolation-points associated with each target
private InterpolationInterval[][] interval = new InterpolationInterval[0][];
// List of indexes for targets with undefined start value
private int[] undefinedStartValues = new int[0];
// Is internal representation up-to-date?
private boolean invalid = true;
GeneralClipInterpolator(KeyFrame[] keyFrames, long[] keyFrameTicks) {
this.keyFrames = keyFrames;
this.keyFrameTicks = keyFrameTicks;
}
// See comment in ClipInterpolator
@Override
ClipInterpolator setKeyFrames(KeyFrame[] keyFrames, long[] keyFrameTicks) {
if (ClipInterpolator.getRealKeyFrameCount(keyFrames) == 2) {
return ClipInterpolator.create(keyFrames, keyFrameTicks);
}
this.keyFrames = keyFrames;
this.keyFrameTicks = keyFrameTicks;
invalid = true;
return this;
}
@Override
void validate(boolean forceSync) {
if (invalid) {
final Map<WritableValue<?>, KeyValue> lastKeyValues = new HashMap<>();
final int n = keyFrames.length;
int index;
for (index = 0; index < n; index++) {
final KeyFrame keyFrame = keyFrames[index];
if (keyFrameTicks[index] == 0) {
for (final KeyValue keyValue : keyFrame.getValues()) {
lastKeyValues.put(keyValue.getTarget(), keyValue);
}
} else {
break;
}
}
final Map<WritableValue<?>, List<InterpolationInterval>> map = new HashMap<>();
final Set<WritableValue<?>> undefinedValues = new HashSet<>();
// iterate through all keyFrames
for (; index < n; index++) {
final KeyFrame keyFrame = keyFrames[index];
final long ticks = keyFrameTicks[index];
// iterate through all keyValues in this keyFrame
for (final KeyValue rightKeyValue : keyFrame.getValues()) {
final WritableValue<?> target = rightKeyValue.getTarget();
List<InterpolationInterval> list = map.get(target);
final KeyValue leftKeyValue = lastKeyValues.get(target);
if (list == null) {
// first encounter of a particular target, generate a
// new interval list
list = new ArrayList<>();
map.put(target, list);
if (leftKeyValue == null) {
list.add(InterpolationInterval.create(
rightKeyValue, ticks));
undefinedValues.add(target);
} else {
list.add(InterpolationInterval
.create(rightKeyValue, ticks,
leftKeyValue, ticks));
}
} else {
assert leftKeyValue != null;
list.add(InterpolationInterval.create(rightKeyValue,
ticks, leftKeyValue,
ticks - list.get(list.size() - 1).ticks));
}
lastKeyValues.put(target, rightKeyValue);
}
}
// copy everything to arrays
final int targetCount = map.size();
if (interval.length != targetCount) {
interval = new InterpolationInterval[targetCount][];
}
final int undefinedStartValuesCount = undefinedValues.size();
if (undefinedStartValues.length != undefinedStartValuesCount) {
undefinedStartValues = new int[undefinedStartValuesCount];
}
int undefinedStartValuesIndex = 0;
final Iterator<Map.Entry<WritableValue<?>, List<InterpolationInterval>>> iterator = map
.entrySet().iterator();
for (int i = 0; i < targetCount; i++) {
final Map.Entry<WritableValue<?>, List<InterpolationInterval>> entry = iterator
.next();
interval[i] = new InterpolationInterval[entry.getValue().size()];
entry.getValue().toArray(interval[i]);
if (undefinedValues.contains(entry.getKey())) {
undefinedStartValues[undefinedStartValuesIndex++] = i;
}
}
invalid = false;
} else if (forceSync) {
final int n = undefinedStartValues.length;
for (int i = 0; i < n; i++) {
final int index = undefinedStartValues[i];
interval[index][0].recalculateStartValue();
}
}
}
@Override
void interpolate(long ticks) {
final int targetCount = interval.length;
// iterate through all targets
targetLoop: for (int targetIndex = 0; targetIndex < targetCount; targetIndex++) {
InterpolationInterval[] intervalList = interval[targetIndex];
final int intervalCount = intervalList.length;
// leftMillis keeps the timestamp of the left side of the interval
long leftTicks = 0;
// iterate through all intervals except the last one
for (int intervalIndex = 0; intervalIndex < intervalCount - 1; intervalIndex++) {
final InterpolationInterval i = intervalList[intervalIndex];
final long rightTicks = i.ticks;
if (ticks <= rightTicks) {
// we found the current interval
final double frac = (double)(ticks - leftTicks)
/ (rightTicks - leftTicks);
i.interpolate(frac);
continue targetLoop;
}
leftTicks = rightTicks;
}
// we did not find a current interval, use the last one
final InterpolationInterval i = intervalList[intervalCount - 1];
// the last interval may end before the timeline ends, make sure we
// set the end value
final double frac = Math.min(1.0, (double)(ticks - leftTicks)
/ (i.ticks - leftTicks));
i.interpolate(frac);
}
}
}