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.regex.Matcher;
050import java.util.regex.Pattern;
051import org.xml.sax.Attributes;
052import org.xml.sax.SAXException;
053
054
055/**
056 * @author Mark McKay
057 * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
058 */
059public class AnimateMotion extends AnimateXform
060{
061    public static final String TAG_NAME = "animateMotion";
062    
063    static final Matcher matchPoint = Pattern.compile("\\s*(\\d+)[^\\d]+(\\d+)\\s*").matcher("");
064    
065//    protected double fromValue;
066//    protected double toValue;
067    private GeneralPath path;
068    private int rotateType = RT_ANGLE;
069    private double rotate;  //Static angle to rotate by
070    
071    public static final int RT_ANGLE = 0;  //Rotate by constant 'rotate' degrees
072    public static final int RT_AUTO = 1;  //Rotate to reflect tangent of position on path
073    
074    final ArrayList<Bezier> bezierSegs = new ArrayList<Bezier>();
075    double curveLength;
076    
077    /** Creates a new instance of Animate */
078    public AnimateMotion()
079    {
080    }
081
082    @Override
083    public String getTagName()
084    {
085        return TAG_NAME;
086    }
087
088    @Override
089    public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent) throws SAXException
090    {
091                //Load style string
092        super.loaderStartElement(helper, attrs, parent);
093        
094        //Motion element implies animating the transform element
095        if (attribName == null) 
096        {
097            attribName = "transform";
098            attribType = AT_AUTO;
099            setAdditiveType(AD_SUM);
100        }
101        
102
103        String path = attrs.getValue("path");
104        if (path != null)
105        {
106            this.path = buildPath(path, GeneralPath.WIND_NON_ZERO);
107        }
108        
109        //Now parse rotation style
110        String rotate = attrs.getValue("rotate");
111        if (rotate != null)
112        {
113            if (rotate.equals("auto"))
114            {
115                this.rotateType = RT_AUTO;
116            }
117            else
118            {
119                try { this.rotate = Math.toRadians(Float.parseFloat(rotate)); } catch (Exception e) {}
120            }
121        }
122
123        //Determine path
124        String from = attrs.getValue("from");
125        String to = attrs.getValue("to");
126
127        buildPath(from, to);
128    }
129    
130    protected static void setPoint(Point2D.Float pt, String x, String y)
131    {
132        try { pt.x = Float.parseFloat(x); } catch (Exception e) {};
133        
134        try { pt.y = Float.parseFloat(y); } catch (Exception e) {};
135    }
136
137    private void buildPath(String from, String to)
138    {
139        if (from != null && to != null)
140        {
141            Point2D.Float ptFrom = new Point2D.Float(), ptTo = new Point2D.Float();
142
143            matchPoint.reset(from);
144            if (matchPoint.matches())
145            {
146                setPoint(ptFrom, matchPoint.group(1), matchPoint.group(2));
147            }
148
149            matchPoint.reset(to);
150            if (matchPoint.matches())
151            {
152                setPoint(ptFrom, matchPoint.group(1), matchPoint.group(2));
153            }
154
155            if (ptFrom != null && ptTo != null)
156            {
157                path = new GeneralPath();
158                path.moveTo(ptFrom.x, ptFrom.y);
159                path.lineTo(ptTo.x, ptTo.y);
160            }
161        }
162
163        paramaterizePath();
164    }
165    
166    private void paramaterizePath()
167    {
168        bezierSegs.clear();
169        curveLength = 0;
170        
171        double[] coords = new double[6];
172        double sx = 0, sy = 0;
173        
174        for (PathIterator pathIt = path.getPathIterator(new AffineTransform()); !pathIt.isDone(); pathIt.next())
175        {
176            Bezier bezier = null;
177                    
178            int segType = pathIt.currentSegment(coords);
179            
180            switch (segType)
181            {
182                case PathIterator.SEG_LINETO: 
183                {
184                    bezier = new Bezier(sx, sy, coords, 1);
185                    sx = coords[0];
186                    sy = coords[1];
187                    break;
188                }
189                case PathIterator.SEG_QUADTO:
190                {
191                    bezier = new Bezier(sx, sy, coords, 2);
192                    sx = coords[2];
193                    sy = coords[3];
194                    break;
195                }
196                case PathIterator.SEG_CUBICTO:
197                {
198                    bezier = new Bezier(sx, sy, coords, 3);
199                    sx = coords[4];
200                    sy = coords[5];
201                    break;
202                }
203                case PathIterator.SEG_MOVETO:
204                {
205                    sx = coords[0];
206                    sy = coords[1];
207                    break;
208                }
209                case PathIterator.SEG_CLOSE:
210                    //Do nothing
211                    break;
212                
213            }
214
215            if (bezier != null)
216            {
217                bezierSegs.add(bezier);
218                curveLength += bezier.getLength();
219            }
220        }
221    }
222    
223    /**
224     * Evaluates this animation element for the passed interpolation time.  Interp
225     * must be on [0..1].
226     */
227    @Override
228    public AffineTransform eval(AffineTransform xform, double interp)
229    {
230        Point2D.Double point = new Point2D.Double();
231        
232        if (interp >= 1)
233        {
234            Bezier last = (Bezier)bezierSegs.get(bezierSegs.size() - 1);
235            last.getFinalPoint(point);
236            xform.setToTranslation(point.x, point.y);
237            return xform;
238        }
239        
240        double curLength = curveLength * interp;
241        for (Bezier bez : bezierSegs) {
242            double bezLength = bez.getLength();
243            if (curLength < bezLength)
244            {
245                double param = curLength / bezLength;
246                bez.eval(param, point);
247                break;
248            }
249            
250            curLength -= bezLength;
251        }
252        
253        xform.setToTranslation(point.x, point.y);
254        
255        return xform;
256    }
257    
258
259    @Override
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}