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.ColorTable;
044import com.kitfox.svg.xml.StyleAttribute;
045import com.kitfox.svg.xml.XMLParseUtil;
046import java.awt.Color;
047import java.awt.geom.AffineTransform;
048import java.awt.geom.GeneralPath;
049import java.awt.geom.PathIterator;
050import org.xml.sax.Attributes;
051import org.xml.sax.SAXException;
052
053
054/**
055 * Animate is a really annoying morphic tag that could represent a real value,
056 * a color or a path
057 *
058 * @author Mark McKay
059 * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
060 */
061public class Animate extends AnimateBase implements AnimateColorIface
062{
063    public static final String TAG_NAME = "animate";
064    
065//    StyleAttribute retAttrib = new StyleAttribute
066    public static final int DT_REAL = 0;
067    public static final int DT_COLOR = 1;
068    public static final int DT_PATH = 2;
069    int dataType = DT_REAL;
070    
071    protected double fromValue = Double.NaN;
072    protected double toValue = Double.NaN;
073    protected double byValue = Double.NaN;
074    protected double[] valuesValue;
075    
076    protected Color fromColor = null;
077    protected Color toColor = null;
078
079    protected GeneralPath fromPath = null;
080    protected GeneralPath toPath = null;
081
082    /** Creates a new instance of Animate */
083    public Animate()
084    {
085    }
086
087    public String getTagName()
088    {
089        return TAG_NAME;
090    }
091
092    public int getDataType()
093    {
094        return dataType;
095    }
096    
097    public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent) throws SAXException
098    {
099                //Load style string
100        super.loaderStartElement(helper, attrs, parent);
101
102        String strn = attrs.getValue("from");
103        if (strn != null)
104        {
105            if (XMLParseUtil.isDouble(strn))
106            {
107                fromValue = XMLParseUtil.parseDouble(strn); 
108            } 
109//            else if (attrs.getValue("attributeName").equals("d"))
110//            {
111//                fromPath = this.buildPath(strn, GeneralPath.WIND_EVEN_ODD);
112//                dataType = DT_PATH;
113//            }
114            else
115            {
116                fromColor = ColorTable.parseColor(strn); 
117                if (fromColor == null)
118                {
119                    //Try path
120                    fromPath = this.buildPath(strn, GeneralPath.WIND_EVEN_ODD);
121                    dataType = DT_PATH;
122                }
123                else dataType = DT_COLOR;
124            }
125        }
126
127        strn = attrs.getValue("to");
128        if (strn != null)
129        {
130            if (XMLParseUtil.isDouble(strn))
131            {
132                toValue = XMLParseUtil.parseDouble(strn); 
133            } 
134            else
135            {
136                toColor = ColorTable.parseColor(strn); 
137                if (toColor == null)
138                {
139                    //Try path
140                    toPath = this.buildPath(strn, GeneralPath.WIND_EVEN_ODD);
141                    dataType = DT_PATH;
142                }
143                else dataType = DT_COLOR;
144            }
145        }
146
147        strn = attrs.getValue("by");
148        try 
149        {
150            if (strn != null) byValue = XMLParseUtil.parseDouble(strn); 
151        } catch (Exception e) {}
152
153        strn = attrs.getValue("values");
154        try 
155        {
156            if (strn != null) valuesValue = XMLParseUtil.parseDoubleList(strn); 
157        } catch (Exception e) {}
158    }
159    
160    /**
161     * Evaluates this animation element for the passed interpolation time.  Interp
162     * must be on [0..1].
163     */
164    public double eval(double interp)
165    {
166        boolean fromExists = !Double.isNaN(fromValue);
167        boolean toExists = !Double.isNaN(toValue);
168        boolean byExists = !Double.isNaN(byValue);
169        boolean valuesExists = valuesValue != null;
170        
171        if (valuesExists)
172        {
173            double sp = interp * valuesValue.length;
174            int ip = (int)sp;
175            double fp = sp - ip;
176            
177            int i0 = ip;
178            int i1 = ip + 1;
179            
180            if (i0 < 0) return valuesValue[0];
181            if (i1 >= valuesValue.length) return valuesValue[valuesValue.length - 1];
182            return valuesValue[i0] * (1 - fp) + valuesValue[i1] * fp;
183        }
184        else if (fromExists && toExists)
185        {
186            return toValue * interp + fromValue * (1.0 - interp);
187        }
188        else if (fromExists && byExists)
189        {
190            return fromValue + byValue * interp;
191        }
192        else if (toExists && byExists)
193        {
194            return toValue - byValue * (1.0 - interp);
195        }
196        else if (byExists)
197        {
198            return byValue * interp;
199        }
200  
201        //Should not reach this line
202        throw new RuntimeException("Animate tag could not be evalutated - insufficient arguements");
203    }
204
205    public Color evalColor(double interp)
206    {
207        if (fromColor == null && toColor != null)
208        {
209            float[] toCol = new float[3];
210            toColor.getColorComponents(toCol);
211            return new Color(toCol[0] * (float)interp, 
212                toCol[1] * (float)interp, 
213                toCol[2] * (float)interp);
214        }
215        else if (fromColor != null && toColor != null)
216        {
217            float nInterp = 1 - (float)interp;
218            
219            float[] fromCol = new float[3];
220            float[] toCol = new float[3];
221            fromColor.getColorComponents(fromCol);
222            toColor.getColorComponents(toCol);
223            return new Color(fromCol[0] * nInterp + toCol[0] * (float)interp, 
224                fromCol[1] * nInterp + toCol[1] * (float)interp, 
225                fromCol[2] * nInterp + toCol[2] * (float)interp);
226        }
227        
228        throw new RuntimeException("Animate tag could not be evalutated - insufficient arguements");
229    }
230
231    public GeneralPath evalPath(double interp)
232    {
233        if (fromPath == null && toPath != null)
234        {
235            PathIterator itTo = toPath.getPathIterator(new AffineTransform());
236            
237            GeneralPath midPath = new GeneralPath();
238            float[] coordsTo = new float[6];
239            
240            for (; !itTo.isDone(); itTo.next())
241            {
242                int segTo = itTo.currentSegment(coordsTo);
243                
244                switch (segTo)
245                {
246                    case PathIterator.SEG_CLOSE:
247                        midPath.closePath();
248                        break;
249                    case PathIterator.SEG_CUBICTO:
250                        midPath.curveTo(
251                                (float)(coordsTo[0] * interp), 
252                                (float)(coordsTo[1] * interp), 
253                                (float)(coordsTo[2] * interp), 
254                                (float)(coordsTo[3] * interp), 
255                                (float)(coordsTo[4] * interp), 
256                                (float)(coordsTo[5] * interp)
257                                );
258                        break;
259                    case PathIterator.SEG_LINETO:
260                        midPath.lineTo(
261                                (float)(coordsTo[0] * interp), 
262                                (float)(coordsTo[1] * interp)
263                                );
264                        break;
265                    case PathIterator.SEG_MOVETO:
266                        midPath.moveTo(
267                                (float)(coordsTo[0] * interp), 
268                                (float)(coordsTo[1] * interp)
269                                );
270                        break;
271                    case PathIterator.SEG_QUADTO:
272                        midPath.quadTo(
273                                (float)(coordsTo[0] * interp), 
274                                (float)(coordsTo[1] * interp), 
275                                (float)(coordsTo[2] * interp), 
276                                (float)(coordsTo[3] * interp)
277                                );
278                        break;
279                }
280            }
281            
282            return midPath;
283        }
284        else if (toPath != null)
285        {
286            PathIterator itFrom = fromPath.getPathIterator(new AffineTransform());
287            PathIterator itTo = toPath.getPathIterator(new AffineTransform());
288            
289            GeneralPath midPath = new GeneralPath();
290            float[] coordsFrom = new float[6];
291            float[] coordsTo = new float[6];
292            
293            for (; !itFrom.isDone(); itFrom.next())
294            {
295                int segFrom = itFrom.currentSegment(coordsFrom);
296                int segTo = itTo.currentSegment(coordsTo);
297                
298                if (segFrom != segTo)
299                {
300                    throw new RuntimeException("Path shape mismatch");
301                }
302                
303                switch (segFrom)
304                {
305                    case PathIterator.SEG_CLOSE:
306                        midPath.closePath();
307                        break;
308                    case PathIterator.SEG_CUBICTO:
309                        midPath.curveTo(
310                                (float)(coordsFrom[0] * (1 - interp) + coordsTo[0] * interp), 
311                                (float)(coordsFrom[1] * (1 - interp) + coordsTo[1] * interp), 
312                                (float)(coordsFrom[2] * (1 - interp) + coordsTo[2] * interp), 
313                                (float)(coordsFrom[3] * (1 - interp) + coordsTo[3] * interp), 
314                                (float)(coordsFrom[4] * (1 - interp) + coordsTo[4] * interp), 
315                                (float)(coordsFrom[5] * (1 - interp) + coordsTo[5] * interp)
316                                );
317                        break;
318                    case PathIterator.SEG_LINETO:
319                        midPath.lineTo(
320                                (float)(coordsFrom[0] * (1 - interp) + coordsTo[0] * interp), 
321                                (float)(coordsFrom[1] * (1 - interp) + coordsTo[1] * interp)
322                                );
323                        break;
324                    case PathIterator.SEG_MOVETO:
325                        midPath.moveTo(
326                                (float)(coordsFrom[0] * (1 - interp) + coordsTo[0] * interp), 
327                                (float)(coordsFrom[1] * (1 - interp) + coordsTo[1] * interp)
328                                );
329                        break;
330                    case PathIterator.SEG_QUADTO:
331                        midPath.quadTo(
332                                (float)(coordsFrom[0] * (1 - interp) + coordsTo[0] * interp), 
333                                (float)(coordsFrom[1] * (1 - interp) + coordsTo[1] * interp), 
334                                (float)(coordsFrom[2] * (1 - interp) + coordsTo[2] * interp), 
335                                (float)(coordsFrom[3] * (1 - interp) + coordsTo[3] * interp)
336                                );
337                        break;
338                }
339            }
340            
341            return midPath;
342        }
343        
344        throw new RuntimeException("Animate tag could not be evalutated - insufficient arguements");
345    }
346    
347    /**
348     * If this element is being accumulated, detemine the delta to accumulate by
349     */
350    public double repeatSkipSize(int reps)
351    {
352        boolean fromExists = !Double.isNaN(fromValue);
353        boolean toExists = !Double.isNaN(toValue);
354        boolean byExists = !Double.isNaN(byValue);
355        
356        if (fromExists && toExists)
357        {
358            return (toValue - fromValue) * reps;
359        }
360        else if (fromExists && byExists)
361        {
362            return (fromValue + byValue) * reps;
363        }
364        else if (toExists && byExists)
365        {
366            return toValue * reps;
367        }
368        else if (byExists)
369        {
370            return byValue * reps;
371        }
372
373        //Should not reach this line
374        return 0;
375    }
376
377    protected void rebuild(AnimTimeParser animTimeParser) throws SVGException
378    {
379        super.rebuild(animTimeParser);
380
381        StyleAttribute sty = new StyleAttribute();
382
383        if (getPres(sty.setName("from")))
384        {
385            String strn = sty.getStringValue();
386            if (XMLParseUtil.isDouble(strn))
387            {
388                fromValue = XMLParseUtil.parseDouble(strn);
389            }
390            else
391            {
392                fromColor = ColorTable.parseColor(strn);
393                if (fromColor == null)
394                {
395                    //Try path
396                    fromPath = this.buildPath(strn, GeneralPath.WIND_EVEN_ODD);
397                    dataType = DT_PATH;
398                }
399                else dataType = DT_COLOR;
400            }
401        }
402
403        if (getPres(sty.setName("to")))
404        {
405            String strn = sty.getStringValue();
406            if (XMLParseUtil.isDouble(strn))
407            {
408                toValue = XMLParseUtil.parseDouble(strn);
409            }
410            else
411            {
412                toColor = ColorTable.parseColor(strn);
413                if (toColor == null)
414                {
415                    //Try path
416                    toPath = this.buildPath(strn, GeneralPath.WIND_EVEN_ODD);
417                    dataType = DT_PATH;
418                }
419                else dataType = DT_COLOR;
420            }
421        }
422
423        if (getPres(sty.setName("by")))
424        {
425            String strn = sty.getStringValue();
426            if (strn != null) byValue = XMLParseUtil.parseDouble(strn);
427        }
428
429        if (getPres(sty.setName("values")))
430        {
431            String strn = sty.getStringValue();
432            if (strn != null) valuesValue = XMLParseUtil.parseDoubleList(strn);
433        }
434    }
435    
436}