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    }