001/*
002 * SVG Salamander
003 * Copyright (c) 2004, Mark McKay
004 * All rights reserved.
005 *
006 * Redistribution and use in source and binary forms, with or 
007 * without modification, are permitted provided that the following
008 * conditions are met:
009 *
010 *   - Redistributions of source code must retain the above 
011 *     copyright notice, this list of conditions and the following
012 *     disclaimer.
013 *   - Redistributions in binary form must reproduce the above
014 *     copyright notice, this list of conditions and the following
015 *     disclaimer in the documentation and/or other materials 
016 *     provided with the distribution.
017 *
018 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
019 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
020 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
021 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
022 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
023 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
025 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
026 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
027 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
028 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
029 * OF THE POSSIBILITY OF SUCH DAMAGE. 
030 * 
031 * Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
032 * projects can be found at http://www.kitfox.com
033 *
034 * Created on August 15, 2004, 2:51 AM
035 */
036
037package com.kitfox.svg.animation;
038
039import com.kitfox.svg.SVGElement;
040import com.kitfox.svg.SVGException;
041import com.kitfox.svg.SVGLoaderHelper;
042import com.kitfox.svg.animation.parser.AnimTimeParser;
043import com.kitfox.svg.xml.StyleAttribute;
044import java.awt.geom.AffineTransform;
045import java.awt.geom.GeneralPath;
046import java.awt.geom.PathIterator;
047import java.awt.geom.Point2D;
048import java.util.ArrayList;
049import java.util.Iterator;
050import java.util.regex.Matcher;
051import java.util.regex.Pattern;
052import org.xml.sax.Attributes;
053import org.xml.sax.SAXException;
054
055
056/**
057 * @author Mark McKay
058 * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
059 */
060public class AnimateMotion extends AnimateXform
061{
062    public static final String TAG_NAME = "animateMotion";
063    
064    static final Matcher matchPoint = Pattern.compile("\\s*(\\d+)[^\\d]+(\\d+)\\s*").matcher("");
065    
066//    protected double fromValue;
067//    protected double toValue;
068    private GeneralPath path;
069    private int rotateType = RT_ANGLE;
070    private double rotate;  //Static angle to rotate by
071    
072    public static final int RT_ANGLE = 0;  //Rotate by constant 'rotate' degrees
073    public static final int RT_AUTO = 1;  //Rotate to reflect tangent of position on path
074    
075    final ArrayList bezierSegs = new ArrayList();
076    double curveLength;
077    
078    /** Creates a new instance of Animate */
079    public AnimateMotion()
080    {
081    }
082
083    public String getTagName()
084    {
085        return TAG_NAME;
086    }
087
088    public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent) throws SAXException
089    {
090                //Load style string
091        super.loaderStartElement(helper, attrs, parent);
092        
093        //Motion element implies animating the transform element
094        if (attribName == null) 
095        {
096            attribName = "transform";
097            attribType = AT_AUTO;
098            setAdditiveType(AD_SUM);
099        }
100        
101
102        String path = attrs.getValue("path");
103        if (path != null)
104        {
105            this.path = buildPath(path, GeneralPath.WIND_NON_ZERO);
106        }
107        
108        //Now parse rotation style
109        String rotate = attrs.getValue("rotate");
110        if (rotate != null)
111        {
112            if (rotate.equals("auto"))
113            {
114                this.rotateType = RT_AUTO;
115            }
116            else
117            {
118                try { this.rotate = Math.toRadians(Float.parseFloat(rotate)); } catch (Exception e) {}
119            }
120        }
121
122        //Determine path
123        String from = attrs.getValue("from");
124        String to = attrs.getValue("to");
125
126        buildPath(from, to);
127    }
128    
129    protected static void setPoint(Point2D.Float pt, String x, String y)
130    {
131        try { pt.x = Float.parseFloat(x); } catch (Exception e) {};
132        
133        try { pt.y = Float.parseFloat(y); } catch (Exception e) {};
134    }
135
136    private void buildPath(String from, String to)
137    {
138        if (from != null && to != null)
139        {
140            Point2D.Float ptFrom = new Point2D.Float(), ptTo = new Point2D.Float();
141
142            matchPoint.reset(from);
143            if (matchPoint.matches())
144            {
145                setPoint(ptFrom, matchPoint.group(1), matchPoint.group(2));
146            }
147
148            matchPoint.reset(to);
149            if (matchPoint.matches())
150            {
151                setPoint(ptFrom, matchPoint.group(1), matchPoint.group(2));
152            }
153
154            if (ptFrom != null && ptTo != null)
155            {
156                path = new GeneralPath();
157                path.moveTo(ptFrom.x, ptFrom.y);
158                path.lineTo(ptTo.x, ptTo.y);
159            }
160        }
161
162        paramaterizePath();
163    }
164    
165    private void paramaterizePath()
166    {
167        bezierSegs.clear();
168        curveLength = 0;
169        
170        double[] coords = new double[6];
171        double sx = 0, sy = 0;
172        
173        for (PathIterator pathIt = path.getPathIterator(new AffineTransform()); !pathIt.isDone(); pathIt.next())
174        {
175            Bezier bezier = null;
176                    
177            int segType = pathIt.currentSegment(coords);
178            
179            switch (segType)
180            {
181                case PathIterator.SEG_LINETO: 
182                {
183                    bezier = new Bezier(sx, sy, coords, 1);
184                    sx = coords[0];
185                    sy = coords[1];
186                    break;
187                }
188                case PathIterator.SEG_QUADTO:
189                {
190                    bezier = new Bezier(sx, sy, coords, 2);
191                    sx = coords[2];
192                    sy = coords[3];
193                    break;
194                }
195                case PathIterator.SEG_CUBICTO:
196                {
197                    bezier = new Bezier(sx, sy, coords, 3);
198                    sx = coords[4];
199                    sy = coords[5];
200                    break;
201                }
202                case PathIterator.SEG_MOVETO:
203                {
204                    sx = coords[0];
205                    sy = coords[1];
206                    break;
207                }
208                case PathIterator.SEG_CLOSE:
209                    //Do nothing
210                    break;
211                
212            }
213
214            if (bezier != null)
215            {
216                bezierSegs.add(bezier);
217                curveLength += bezier.getLength();
218            }
219        }
220    }
221    
222    /**
223     * Evaluates this animation element for the passed interpolation time.  Interp
224     * must be on [0..1].
225     */
226    public AffineTransform eval(AffineTransform xform, double interp)
227    {
228        Point2D.Double point = new Point2D.Double();
229        
230        if (interp >= 1)
231        {
232            Bezier last = (Bezier)bezierSegs.get(bezierSegs.size() - 1);
233            last.getFinalPoint(point);
234            xform.setToTranslation(point.x, point.y);
235            return xform;
236        }
237        
238        double curLength = curveLength * interp;
239        for (Iterator it = bezierSegs.iterator(); it.hasNext();)
240        {
241            Bezier bez = (Bezier)it.next();
242            
243            double bezLength = bez.getLength();
244            if (curLength < bezLength)
245            {
246                double param = curLength / bezLength;
247                bez.eval(param, point);
248                break;
249            }
250            
251            curLength -= bezLength;
252        }
253        
254        xform.setToTranslation(point.x, point.y);
255        
256        return xform;
257    }
258    
259
260    protected void rebuild(AnimTimeParser animTimeParser) throws SVGException
261    {
262        super.rebuild(animTimeParser);
263
264        StyleAttribute sty = new StyleAttribute();
265
266        if (getPres(sty.setName("path")))
267        {
268            String strn = sty.getStringValue();
269            this.path = buildPath(strn, GeneralPath.WIND_NON_ZERO);
270        }
271
272        if (getPres(sty.setName("rotate")))
273        {
274            String strn = sty.getStringValue();
275            if (strn.equals("auto"))
276            {
277                this.rotateType = RT_AUTO;
278            }
279            else
280            {
281                try { this.rotate = Math.toRadians(Float.parseFloat(strn)); } catch (Exception e) {}
282            }
283        }
284
285        String from = null;
286        if (getPres(sty.setName("from")))
287        {
288            from = sty.getStringValue();
289        }
290
291        String to = null;
292        if (getPres(sty.setName("to")))
293        {
294            to = sty.getStringValue();
295        }
296        
297        buildPath(from, to);
298    }
299
300    /**
301     * @return the path
302     */
303    public GeneralPath getPath()
304    {
305        return path;
306    }
307
308    /**
309     * @param path the path to set
310     */
311    public void setPath(GeneralPath path)
312    {
313        this.path = path;
314    }
315
316    /**
317     * @return the rotateType
318     */
319    public int getRotateType()
320    {
321        return rotateType;
322    }
323
324    /**
325     * @param rotateType the rotateType to set
326     */
327    public void setRotateType(int rotateType)
328    {
329        this.rotateType = rotateType;
330    }
331
332    /**
333     * @return the rotate
334     */
335    public double getRotate()
336    {
337        return rotate;
338    }
339
340    /**
341     * @param rotate the rotate to set
342     */
343    public void setRotate(double rotate)
344    {
345        this.rotate = rotate;
346    }
347}