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