001: /*
002:
003: Licensed to the Apache Software Foundation (ASF) under one or more
004: contributor license agreements. See the NOTICE file distributed with
005: this work for additional information regarding copyright ownership.
006: The ASF licenses this file to You under the Apache License, Version 2.0
007: (the "License"); you may not use this file except in compliance with
008: the License. You may obtain a copy of the License at
009:
010: http://www.apache.org/licenses/LICENSE-2.0
011:
012: Unless required by applicable law or agreed to in writing, software
013: distributed under the License is distributed on an "AS IS" BASIS,
014: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: See the License for the specific language governing permissions and
016: limitations under the License.
017:
018: */
019: package org.apache.batik.anim;
020:
021: import java.awt.geom.Point2D;
022:
023: import org.apache.batik.anim.timing.TimedElement;
024: import org.apache.batik.anim.values.AnimatableAngleValue;
025: import org.apache.batik.anim.values.AnimatableMotionPointValue;
026: import org.apache.batik.anim.values.AnimatableValue;
027: import org.apache.batik.dom.anim.AnimatableElement;
028: import org.apache.batik.ext.awt.geom.Cubic;
029: import org.apache.batik.ext.awt.geom.ExtendedGeneralPath;
030: import org.apache.batik.ext.awt.geom.ExtendedPathIterator;
031: import org.apache.batik.ext.awt.geom.PathLength;
032: import org.apache.batik.util.SMILConstants;
033:
034: /**
035: * An animation class for 'animateMotion' animations.
036: *
037: * @author <a href="mailto:cam%40mcc%2eid%2eau">Cameron McCormack</a>
038: * @version $Id: MotionAnimation.java 491178 2006-12-30 06:18:34Z cam $
039: */
040: public class MotionAnimation extends InterpolatingAnimation {
041:
042: /**
043: * The path that describes the motion.
044: */
045: protected ExtendedGeneralPath path;
046:
047: /**
048: * The path length calculation object.
049: */
050: protected PathLength pathLength;
051:
052: /**
053: * The points defining the distance along the path that the
054: * keyTimes apply.
055: */
056: protected float[] keyPoints;
057:
058: /**
059: * Whether automatic rotation should be performed.
060: */
061: protected boolean rotateAuto;
062:
063: /**
064: * Whether the automatic rotation should be reversed.
065: */
066: protected boolean rotateAutoReverse;
067:
068: /**
069: * The angle of rotation (in radians) to use when automatic rotation is
070: * not being used.
071: */
072: protected float rotateAngle;
073:
074: /**
075: * Creates a new MotionAnimation.
076: */
077: public MotionAnimation(TimedElement timedElement,
078: AnimatableElement animatableElement, int calcMode,
079: float[] keyTimes, float[] keySplines, boolean additive,
080: boolean cumulative, AnimatableValue[] values,
081: AnimatableValue from, AnimatableValue to,
082: AnimatableValue by, ExtendedGeneralPath path,
083: float[] keyPoints, boolean rotateAuto,
084: boolean rotateAutoReverse, float rotateAngle,
085: short rotateAngleUnit) {
086: super (timedElement, animatableElement, calcMode, keyTimes,
087: keySplines, additive, cumulative);
088: this .rotateAuto = rotateAuto;
089: this .rotateAutoReverse = rotateAutoReverse;
090: this .rotateAngle = AnimatableAngleValue.rad(rotateAngle,
091: rotateAngleUnit);
092:
093: if (path == null) {
094: path = new ExtendedGeneralPath();
095: if (values == null || values.length == 0) {
096: if (from != null) {
097: AnimatableMotionPointValue fromPt = (AnimatableMotionPointValue) from;
098: float x = fromPt.getX();
099: float y = fromPt.getY();
100: path.moveTo(x, y);
101: if (to != null) {
102: AnimatableMotionPointValue toPt = (AnimatableMotionPointValue) to;
103: path.lineTo(toPt.getX(), toPt.getY());
104: } else if (by != null) {
105: AnimatableMotionPointValue byPt = (AnimatableMotionPointValue) by;
106: path.lineTo(x + byPt.getX(), y + byPt.getY());
107: } else {
108: throw timedElement.createException(
109: "values.to.by.path.missing",
110: new Object[] { null });
111: }
112: } else {
113: if (to != null) {
114: AnimatableMotionPointValue unPt = (AnimatableMotionPointValue) animatableElement
115: .getUnderlyingValue();
116: AnimatableMotionPointValue toPt = (AnimatableMotionPointValue) to;
117: path.moveTo(unPt.getX(), unPt.getY());
118: path.lineTo(toPt.getX(), toPt.getY());
119: this .cumulative = false;
120: } else if (by != null) {
121: AnimatableMotionPointValue byPt = (AnimatableMotionPointValue) by;
122: path.moveTo(0, 0);
123: path.lineTo(byPt.getX(), byPt.getY());
124: this .additive = true;
125: } else {
126: throw timedElement.createException(
127: "values.to.by.path.missing",
128: new Object[] { null });
129: }
130: }
131: } else {
132: AnimatableMotionPointValue pt = (AnimatableMotionPointValue) values[0];
133: path.moveTo(pt.getX(), pt.getY());
134: for (int i = 1; i < values.length; i++) {
135: pt = (AnimatableMotionPointValue) values[i];
136: path.lineTo(pt.getX(), pt.getY());
137: }
138: }
139: }
140: this .path = path;
141: pathLength = new PathLength(path);
142: int segments = 0;
143: ExtendedPathIterator epi = path.getExtendedPathIterator();
144: while (!epi.isDone()) {
145: int type = epi.currentSegment();
146: if (type != ExtendedPathIterator.SEG_MOVETO) {
147: segments++;
148: }
149: epi.next();
150: }
151:
152: int count = keyPoints == null ? segments + 1 : keyPoints.length;
153: float totalLength = pathLength.lengthOfPath();
154: if (this .keyTimes != null && calcMode != CALC_MODE_PACED) {
155: if (this .keyTimes.length != count) {
156: throw timedElement
157: .createException(
158: "attribute.malformed",
159: new Object[] {
160: null,
161: SMILConstants.SMIL_KEY_TIMES_ATTRIBUTE });
162: }
163: } else {
164: if (calcMode == CALC_MODE_LINEAR
165: || calcMode == CALC_MODE_SPLINE) {
166: this .keyTimes = new float[count];
167: for (int i = 0; i < count; i++) {
168: this .keyTimes[i] = (float) i / (count - 1);
169: }
170: } else if (calcMode == CALC_MODE_DISCRETE) {
171: this .keyTimes = new float[count];
172: for (int i = 0; i < count; i++) {
173: this .keyTimes[i] = (float) i / count;
174: }
175: } else { // CALC_MODE_PACED
176: // This corrects the keyTimes to be paced, so from now on
177: // it can be considered the same as CALC_MODE_LINEAR.
178: epi = path.getExtendedPathIterator();
179: this .keyTimes = new float[count];
180: int j = 0;
181: for (int i = 0; i < count - 1; i++) {
182: while (epi.currentSegment() == ExtendedPathIterator.SEG_MOVETO) {
183: j++;
184: epi.next();
185: }
186: this .keyTimes[i] = pathLength.getLengthAtSegment(j)
187: / totalLength;
188: j++;
189: epi.next();
190: }
191: this .keyTimes[count - 1] = 1f;
192: }
193: }
194:
195: if (keyPoints != null) {
196: if (keyPoints.length != this .keyTimes.length) {
197: throw timedElement
198: .createException(
199: "attribute.malformed",
200: new Object[] {
201: null,
202: SMILConstants.SMIL_KEY_POINTS_ATTRIBUTE });
203: }
204: } else {
205: epi = path.getExtendedPathIterator();
206: keyPoints = new float[count];
207: int j = 0;
208: for (int i = 0; i < count - 1; i++) {
209: while (epi.currentSegment() == ExtendedPathIterator.SEG_MOVETO) {
210: j++;
211: epi.next();
212: }
213: keyPoints[i] = pathLength.getLengthAtSegment(j)
214: / totalLength;
215: j++;
216: epi.next();
217: }
218: keyPoints[count - 1] = 1f;
219: }
220: this .keyPoints = keyPoints;
221: }
222:
223: /**
224: * Called when the element is sampled at the given unit time. This updates
225: * the {@link #value} of the animation if active.
226: */
227: protected void sampledAtUnitTime(float unitTime, int repeatIteration) {
228: AnimatableValue value, accumulation;
229: float interpolation = 0;
230: if (unitTime != 1) {
231: int keyTimeIndex = 0;
232: while (keyTimeIndex < keyTimes.length - 1
233: && unitTime >= keyTimes[keyTimeIndex + 1]) {
234: keyTimeIndex++;
235: }
236: if (keyTimeIndex == keyTimes.length - 1
237: && calcMode == CALC_MODE_DISCRETE) {
238: keyTimeIndex = keyTimes.length - 2;
239: interpolation = 1;
240: } else {
241: if (calcMode == CALC_MODE_LINEAR
242: || calcMode == CALC_MODE_PACED
243: || calcMode == CALC_MODE_SPLINE) {
244: interpolation = (unitTime - keyTimes[keyTimeIndex])
245: / (keyTimes[keyTimeIndex + 1] - keyTimes[keyTimeIndex]);
246: if (calcMode == CALC_MODE_SPLINE && unitTime != 0) {
247: // XXX This could be done better, e.g. with
248: // Newton-Raphson.
249: Cubic c = keySplineCubics[keyTimeIndex];
250: float tolerance = 0.001f;
251: float min = 0;
252: float max = 1;
253: Point2D.Double p;
254: for (;;) {
255: float t = (min + max) / 2;
256: p = c.eval(t);
257: double x = p.getX();
258: if (Math.abs(x - interpolation) < tolerance) {
259: break;
260: }
261: if (x < interpolation) {
262: min = t;
263: } else {
264: max = t;
265: }
266: }
267: interpolation = (float) p.getY();
268: }
269: }
270: }
271: float point = keyPoints[keyTimeIndex];
272: if (interpolation != 0) {
273: point += interpolation
274: * (keyPoints[keyTimeIndex + 1] - keyPoints[keyTimeIndex]);
275: }
276: point *= pathLength.lengthOfPath();
277: Point2D p = pathLength.pointAtLength(point);
278: float ang;
279: if (rotateAuto) {
280: ang = pathLength.angleAtLength(point);
281: if (rotateAutoReverse) {
282: ang += Math.PI;
283: }
284: } else {
285: ang = rotateAngle;
286: }
287: value = new AnimatableMotionPointValue(null, (float) p
288: .getX(), (float) p.getY(), ang);
289: } else {
290: Point2D p = pathLength.pointAtLength(pathLength
291: .lengthOfPath());
292: float ang;
293: if (rotateAuto) {
294: ang = pathLength.angleAtLength(pathLength
295: .lengthOfPath());
296: if (rotateAutoReverse) {
297: ang += Math.PI;
298: }
299: } else {
300: ang = rotateAngle;
301: }
302: value = new AnimatableMotionPointValue(null, (float) p
303: .getX(), (float) p.getY(), ang);
304: }
305: if (cumulative) {
306: Point2D p = pathLength.pointAtLength(pathLength
307: .lengthOfPath());
308: float ang;
309: if (rotateAuto) {
310: ang = pathLength.angleAtLength(pathLength
311: .lengthOfPath());
312: if (rotateAutoReverse) {
313: ang += Math.PI;
314: }
315: } else {
316: ang = rotateAngle;
317: }
318: accumulation = new AnimatableMotionPointValue(null,
319: (float) p.getX(), (float) p.getY(), ang);
320: } else {
321: accumulation = null;
322: }
323:
324: this.value = value.interpolate(this.value, null, interpolation,
325: accumulation, repeatIteration);
326: if (this.value.hasChanged()) {
327: markDirty();
328: }
329: }
330: }
|