001 /*
002 * Animate.java
003 *
004 * The Salamander Project - 2D and 3D graphics libraries in Java
005 * Copyright (C) 2004 Mark McKay
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this library; if not, write to the Free Software
019 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020 *
021 * Mark McKay can be contacted at mark@kitfox.com. Salamander and other
022 * projects can be found at http://www.kitfox.com
023 *
024 * Created on August 15, 2004, 2:51 AM
025 */
026
027 package com.kitfox.svg.animation;
028
029 import com.kitfox.svg.SVGElement;
030 import com.kitfox.svg.SVGException;
031 import com.kitfox.svg.SVGLoaderHelper;
032 import com.kitfox.svg.animation.parser.AnimTimeParser;
033 import com.kitfox.svg.xml.StyleAttribute;
034 import java.awt.geom.AffineTransform;
035 import java.awt.geom.GeneralPath;
036 import java.awt.geom.PathIterator;
037 import java.awt.geom.Point2D;
038 import java.util.ArrayList;
039 import java.util.Iterator;
040 import java.util.regex.Matcher;
041 import java.util.regex.Pattern;
042 import org.xml.sax.Attributes;
043 import org.xml.sax.SAXException;
044
045
046 /**
047 * @author Mark McKay
048 * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
049 */
050 public class AnimateMotion extends AnimateXform
051 {
052 static final Matcher matchPoint = Pattern.compile("\\s*(\\d+)[^\\d]+(\\d+)\\s*").matcher("");
053
054 // protected double fromValue;
055 // protected double toValue;
056 GeneralPath path;
057 int rotateType = RT_ANGLE;
058 double rotate; //Static angle to rotate by
059
060 public static final int RT_ANGLE = 0; //Rotate by constant 'rotate' degrees
061 public static final int RT_AUTO = 1; //Rotate to reflect tangent of position on path
062
063 final ArrayList bezierSegs = new ArrayList();
064 double curveLength;
065
066 /** Creates a new instance of Animate */
067 public AnimateMotion()
068 {
069 }
070
071 public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent) throws SAXException
072 {
073 //Load style string
074 super.loaderStartElement(helper, attrs, parent);
075
076 //Motion element implies animating the transform element
077 if (attribName == null)
078 {
079 attribName = "transform";
080 attribType = AT_AUTO;
081 additiveType = AD_SUM;
082 }
083
084
085 String path = attrs.getValue("path");
086 if (path != null)
087 {
088 this.path = buildPath(path, GeneralPath.WIND_NON_ZERO);
089 }
090
091 //Now parse rotation style
092 String rotate = attrs.getValue("rotate");
093 if (rotate != null)
094 {
095 if (rotate.equals("auto"))
096 {
097 this.rotateType = RT_AUTO;
098 }
099 else
100 {
101 try { this.rotate = Math.toRadians(Float.parseFloat(rotate)); } catch (Exception e) {}
102 }
103 }
104
105 //Determine path
106 String from = attrs.getValue("from");
107 String to = attrs.getValue("to");
108
109 buildPath(from, to);
110 }
111
112 protected static void setPoint(Point2D.Float pt, String x, String y)
113 {
114 try { pt.x = Float.parseFloat(x); } catch (Exception e) {};
115
116 try { pt.y = Float.parseFloat(y); } catch (Exception e) {};
117 }
118
119 private void buildPath(String from, String to)
120 {
121 if (from != null && to != null)
122 {
123 Point2D.Float ptFrom = new Point2D.Float(), ptTo = new Point2D.Float();
124
125 matchPoint.reset(from);
126 if (matchPoint.matches())
127 {
128 setPoint(ptFrom, matchPoint.group(1), matchPoint.group(2));
129 }
130
131 matchPoint.reset(to);
132 if (matchPoint.matches())
133 {
134 setPoint(ptFrom, matchPoint.group(1), matchPoint.group(2));
135 }
136
137 if (ptFrom != null && ptTo != null)
138 {
139 path = new GeneralPath();
140 path.moveTo(ptFrom.x, ptFrom.y);
141 path.lineTo(ptTo.x, ptTo.y);
142 }
143 }
144
145 paramaterizePath();
146 }
147
148 private void paramaterizePath()
149 {
150 bezierSegs.clear();
151 curveLength = 0;
152
153 double[] coords = new double[6];
154 double sx = 0, sy = 0;
155
156 for (PathIterator pathIt = path.getPathIterator(new AffineTransform()); !pathIt.isDone(); pathIt.next())
157 {
158 Bezier bezier = null;
159
160 int segType = pathIt.currentSegment(coords);
161
162 switch (segType)
163 {
164 case PathIterator.SEG_LINETO:
165 {
166 bezier = new Bezier(sx, sy, coords, 1);
167 sx = coords[0];
168 sy = coords[1];
169 break;
170 }
171 case PathIterator.SEG_QUADTO:
172 {
173 bezier = new Bezier(sx, sy, coords, 2);
174 sx = coords[2];
175 sy = coords[3];
176 break;
177 }
178 case PathIterator.SEG_CUBICTO:
179 {
180 bezier = new Bezier(sx, sy, coords, 3);
181 sx = coords[4];
182 sy = coords[5];
183 break;
184 }
185 case PathIterator.SEG_MOVETO:
186 {
187 sx = coords[0];
188 sy = coords[1];
189 break;
190 }
191 case PathIterator.SEG_CLOSE:
192 //Do nothing
193 break;
194
195 }
196
197 if (bezier != null)
198 {
199 bezierSegs.add(bezier);
200 curveLength += bezier.getLength();
201 }
202 }
203 }
204
205 /**
206 * Evaluates this animation element for the passed interpolation time. Interp
207 * must be on [0..1].
208 */
209 public AffineTransform eval(AffineTransform xform, double interp)
210 {
211 Point2D.Double point = new Point2D.Double();
212
213 if (interp >= 1)
214 {
215 Bezier last = (Bezier)bezierSegs.get(bezierSegs.size() - 1);
216 last.getFinalPoint(point);
217 xform.setToTranslation(point.x, point.y);
218 return xform;
219 }
220
221 double curLength = curveLength * interp;
222 for (Iterator it = bezierSegs.iterator(); it.hasNext();)
223 {
224 Bezier bez = (Bezier)it.next();
225
226 double bezLength = bez.getLength();
227 if (curLength < bezLength)
228 {
229 double param = curLength / bezLength;
230 bez.eval(param, point);
231 break;
232 }
233
234 curLength -= bezLength;
235 }
236
237 xform.setToTranslation(point.x, point.y);
238
239 return xform;
240 }
241
242
243 protected void rebuild(AnimTimeParser animTimeParser) throws SVGException
244 {
245 super.rebuild(animTimeParser);
246
247 StyleAttribute sty = new StyleAttribute();
248
249 if (getPres(sty.setName("path")))
250 {
251 String strn = sty.getStringValue();
252 this.path = buildPath(strn, GeneralPath.WIND_NON_ZERO);
253 }
254
255 if (getPres(sty.setName("rotate")))
256 {
257 String strn = sty.getStringValue();
258 if (strn.equals("auto"))
259 {
260 this.rotateType = RT_AUTO;
261 }
262 else
263 {
264 try { this.rotate = Math.toRadians(Float.parseFloat(strn)); } catch (Exception e) {}
265 }
266 }
267
268 String from = null;
269 if (getPres(sty.setName("from")))
270 {
271 from = sty.getStringValue();
272 }
273
274 String to = null;
275 if (getPres(sty.setName("to")))
276 {
277 to = sty.getStringValue();
278 }
279
280 buildPath(from, to);
281 }
282 }