| /* |
| |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
| */ |
| package org.apache.batik.anim; |
| |
| import java.awt.geom.Point2D; |
| |
| import org.apache.batik.anim.dom.AnimatableElement; |
| import org.apache.batik.anim.timing.TimedElement; |
| import org.apache.batik.anim.values.AnimatableValue; |
| import org.apache.batik.ext.awt.geom.Cubic; |
| import org.apache.batik.util.SMILConstants; |
| |
| /** |
| * An animation class for 'animate' animations. |
| * |
| * @author <a href="mailto:cam%40mcc%2eid%2eau">Cameron McCormack</a> |
| * @version $Id$ |
| */ |
| public class SimpleAnimation extends InterpolatingAnimation { |
| |
| /** |
| * Values between which to interpolate. |
| */ |
| protected AnimatableValue[] values; |
| |
| /** |
| * Starting value of the animation. |
| */ |
| protected AnimatableValue from; |
| |
| /** |
| * Ending value of the animation. |
| */ |
| protected AnimatableValue to; |
| |
| /** |
| * Relative offset value for the animation. |
| */ |
| protected AnimatableValue by; |
| |
| /** |
| * Creates a new SimpleAnimation. |
| */ |
| public SimpleAnimation(TimedElement timedElement, |
| AnimatableElement animatableElement, |
| int calcMode, |
| float[] keyTimes, |
| float[] keySplines, |
| boolean additive, |
| boolean cumulative, |
| AnimatableValue[] values, |
| AnimatableValue from, |
| AnimatableValue to, |
| AnimatableValue by) { |
| super(timedElement, animatableElement, calcMode, keyTimes, keySplines, |
| additive, cumulative); |
| this.from = from; |
| this.to = to; |
| this.by = by; |
| |
| if (values == null) { |
| if (from != null) { |
| values = new AnimatableValue[2]; |
| values[0] = from; |
| if (to != null) { |
| values[1] = to; |
| } else if (by != null) { |
| values[1] = from.interpolate(null, null, 0f, by, 1); |
| } else { |
| throw timedElement.createException |
| ("values.to.by.missing", new Object[] { null }); |
| } |
| } else { |
| if (to != null) { |
| values = new AnimatableValue[2]; |
| values[0] = animatableElement.getUnderlyingValue(); |
| values[1] = to; |
| this.cumulative = false; |
| toAnimation = true; |
| } else if (by != null) { |
| this.additive = true; |
| values = new AnimatableValue[2]; |
| values[0] = by.getZeroValue(); |
| values[1] = by; |
| } else { |
| throw timedElement.createException |
| ("values.to.by.missing", new Object[] { null }); |
| } |
| } |
| } |
| this.values = values; |
| |
| if (this.keyTimes != null && calcMode != CALC_MODE_PACED) { |
| if (this.keyTimes.length != values.length) { |
| throw timedElement.createException |
| ("attribute.malformed", |
| new Object[] { null, |
| SMILConstants.SMIL_KEY_TIMES_ATTRIBUTE }); |
| } |
| } else { |
| if (calcMode == CALC_MODE_LINEAR || calcMode == CALC_MODE_SPLINE |
| || calcMode == CALC_MODE_PACED && !values[0].canPace()) { |
| int count = values.length == 1 ? 2 : values.length; |
| this.keyTimes = new float[count]; |
| for (int i = 0; i < count; i++) { |
| this.keyTimes[i] = (float) i / (count - 1); |
| } |
| } else if (calcMode == CALC_MODE_DISCRETE) { |
| int count = values.length; |
| this.keyTimes = new float[count]; |
| for (int i = 0; i < count; i++) { |
| this.keyTimes[i] = (float) i / count; |
| } |
| } else { // CALC_MODE_PACED |
| // This corrects the keyTimes to be paced, so from now on |
| // it can be considered the same as CALC_MODE_LINEAR. |
| int count = values.length; |
| float[] cumulativeDistances = new float[count]; |
| cumulativeDistances[0] = 0; |
| for (int i = 1; i < count; i++) { |
| cumulativeDistances[i] = cumulativeDistances[i - 1] |
| + values[i - 1].distanceTo(values[i]); |
| } |
| float totalLength = cumulativeDistances[count - 1]; |
| this.keyTimes = new float[count]; |
| this.keyTimes[0] = 0; |
| for (int i = 1; i < count - 1; i++) { |
| this.keyTimes[i] = cumulativeDistances[i] / totalLength; |
| } |
| this.keyTimes[count - 1] = 1; |
| } |
| } |
| |
| if (calcMode == CALC_MODE_SPLINE |
| && keySplines.length != (this.keyTimes.length - 1) * 4) { |
| throw timedElement.createException |
| ("attribute.malformed", |
| new Object[] { null, |
| SMILConstants.SMIL_KEY_SPLINES_ATTRIBUTE }); |
| } |
| } |
| |
| /** |
| * Called when the element is sampled at the given unit time. This updates |
| * the {@link #value} of the animation if active. |
| */ |
| protected void sampledAtUnitTime(float unitTime, int repeatIteration) { |
| AnimatableValue value, accumulation, nextValue; |
| float interpolation = 0; |
| if (unitTime != 1) { |
| int keyTimeIndex = 0; |
| while (keyTimeIndex < keyTimes.length - 1 |
| && unitTime >= keyTimes[keyTimeIndex + 1]) { |
| keyTimeIndex++; |
| } |
| value = values[keyTimeIndex]; |
| if (calcMode == CALC_MODE_LINEAR |
| || calcMode == CALC_MODE_PACED |
| || calcMode == CALC_MODE_SPLINE) { |
| nextValue = values[keyTimeIndex + 1]; |
| interpolation = (unitTime - keyTimes[keyTimeIndex]) |
| / (keyTimes[keyTimeIndex + 1] - keyTimes[keyTimeIndex]); |
| if (calcMode == CALC_MODE_SPLINE && unitTime != 0) { |
| // XXX This could be done better, e.g. with |
| // Newton-Raphson. |
| Cubic c = keySplineCubics[keyTimeIndex]; |
| float tolerance = 0.001f; |
| float min = 0; |
| float max = 1; |
| Point2D.Double p; |
| for (;;) { |
| float t = (min + max) / 2; |
| p = c.eval(t); |
| double x = p.getX(); |
| if (Math.abs(x - interpolation) < tolerance) { |
| break; |
| } |
| if (x < interpolation) { |
| min = t; |
| } else { |
| max = t; |
| } |
| } |
| interpolation = (float) p.getY(); |
| } |
| } else { |
| nextValue = null; |
| } |
| } else { |
| value = values[values.length - 1]; |
| nextValue = null; |
| } |
| if (cumulative) { |
| accumulation = values[values.length - 1]; |
| } else { |
| accumulation = null; |
| } |
| |
| this.value = value.interpolate(this.value, nextValue, interpolation, |
| accumulation, repeatIteration); |
| if (this.value.hasChanged()) { |
| markDirty(); |
| } |
| } |
| } |