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 com.kitfox.svg.xml.XMLParseUtil;
035    import java.awt.geom.AffineTransform;
036    import java.util.regex.Pattern;
037    import org.xml.sax.Attributes;
038    import org.xml.sax.SAXException;
039    
040    
041    /**
042     * @author Mark McKay
043     * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
044     */
045    public class AnimateTransform extends AnimateXform
046    {
047    //    protected AffineTransform fromValue;
048    //    protected AffineTransform toValue;
049    //    protected double[] fromValue;  //Transform parameters
050    //    protected double[] toValue;
051        protected double[][] values;
052        protected double[] keyTimes;
053    
054        public static final int AT_REPLACE = 0;
055        public static final int AT_SUM = 1;
056    
057        protected int additive = AT_REPLACE;
058    
059        public static final int TR_TRANSLATE = 0;
060        public static final int TR_ROTATE = 1;
061        public static final int TR_SCALE = 2;
062        public static final int TR_SKEWY = 3;
063        public static final int TR_SKEWX = 4;
064        public static final int TR_INVALID = 5;
065    
066        protected int xformType = TR_INVALID;
067    
068        /** Creates a new instance of Animate */
069        public AnimateTransform()
070        {
071        }
072    
073        public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent) throws SAXException
074        {
075                    //Load style string
076            super.loaderStartElement(helper, attrs, parent);
077    
078            //Type of matrix of transform.  Should be one of the known names used to
079            // define matrix transforms
080            // valid types: translate, scale, rotate, skewX, skewY
081            // 'matrix' not valid for animation
082            String type = attrs.getValue("type").toLowerCase();
083            if (type.equals("translate")) xformType = TR_TRANSLATE;
084            if (type.equals("rotate")) xformType = TR_ROTATE;
085            if (type.equals("scale")) xformType = TR_SCALE;
086            if (type.equals("skewx")) xformType = TR_SKEWX;
087            if (type.equals("skewy")) xformType = TR_SKEWY;
088    
089            String fromStrn = attrs.getValue("from");
090            String toStrn = attrs.getValue("to");
091            if (fromStrn != null && toStrn != null)
092            {
093                //fromValue = parseSingleTransform(type + "(" + strn + ")");
094                double[] fromValue = XMLParseUtil.parseDoubleList(fromStrn);
095                fromValue = validate(fromValue);
096    
097        //        toValue = parseSingleTransform(type + "(" + strn + ")");
098                double[] toValue = XMLParseUtil.parseDoubleList(toStrn);
099                toValue = validate(toValue);
100                
101                values = new double[][]{fromValue, toValue};
102                keyTimes = new double[]{0, 1};
103            }
104    
105            String keyTimeStrn = attrs.getValue("keyTimes");
106            String valuesStrn = attrs.getValue("values");
107            if (keyTimeStrn != null && valuesStrn != null)
108            {
109                keyTimes = XMLParseUtil.parseDoubleList(keyTimeStrn);
110                
111                String[] valueList = Pattern.compile(";").split(valuesStrn);
112                values = new double[valueList.length][];
113                for (int i = 0; i < valueList.length; i++)
114                {
115                    double[] list = XMLParseUtil.parseDoubleList(valueList[i]);
116                    values[i] = validate(list);
117                }
118            }
119            
120            //Check our additive state
121            String additive = attrs.getValue("additive");
122            if (additive != null)
123            {
124                if (additive.equals("sum")) this.additive = AT_SUM;
125            }
126        }
127    
128        /**
129         * Check list size against current xform type and ensure list
130         * is expanded to a standard list size
131         */
132        private double[] validate(double[] paramList)
133        {
134            switch (xformType)
135            {
136                case TR_SCALE:
137                {
138                    if (paramList == null)
139                    {
140                        paramList = new double[]{1, 1};
141                    }
142                    else if (paramList.length == 1)
143                    {
144                        paramList = new double[]{paramList[0], paramList[0]};
145                        
146    //                    double[] tmp = paramList;
147    //                    paramList = new double[2];
148    //                    paramList[0] = paramList[1] = tmp[0];
149                    }
150                }
151            }
152    
153            return paramList;
154        }
155    
156        /**
157         * Evaluates this animation element for the passed interpolation time.  Interp
158         * must be on [0..1].
159         */
160        public AffineTransform eval(AffineTransform xform, double interp)
161        {
162            int idx = 0;
163            for (; idx < keyTimes.length - 1; idx++)
164            {
165                if (interp >= keyTimes[idx])
166                {
167                    idx--;
168                    if (idx < 0) idx = 0;
169                    break;
170                }
171            }
172            
173            double spanStartTime = keyTimes[idx];
174            double spanEndTime = keyTimes[idx + 1];
175    //        double span = spanStartTime - spanEndTime;
176            
177            interp = (interp - spanStartTime) / (spanEndTime - spanStartTime);
178            double[] fromValue = values[idx];
179            double[] toValue = values[idx + 1];
180            
181            switch (xformType)
182            {
183                case TR_TRANSLATE:
184                {
185                    double x = (1.0 - interp) * fromValue[0] + interp * toValue[0];
186                    double y = (1.0 - interp) * fromValue[1] + interp * toValue[1];
187                    xform.setToTranslation(x, y);
188                    break;
189                }
190                case TR_ROTATE:
191                {
192                    double x1 = fromValue.length == 3 ? fromValue[1] : 0;
193                    double y1 = fromValue.length == 3 ? fromValue[2] : 0;
194                    double x2 = toValue.length == 3 ? toValue[1] : 0;
195                    double y2 = toValue.length == 3 ? toValue[2] : 0;
196                    
197                    double theta = (1.0 - interp) * fromValue[0] + interp * toValue[0];
198                    double x = (1.0 - interp) * x1 + interp * x2;
199                    double y = (1.0 - interp) * y1 + interp * y2;
200                    xform.setToRotation(Math.toRadians(theta), x, y);
201                    break;
202                }
203                case TR_SCALE:
204                {
205                    double x = (1.0 - interp) * fromValue[0] + interp * toValue[0];
206                    double y = (1.0 - interp) * fromValue[1] + interp * toValue[1];
207                    xform.setToScale(x, y);
208                    break;
209                }
210                case TR_SKEWX:
211                {
212                    double x = (1.0 - interp) * fromValue[0] + interp * toValue[0];
213                    xform.setToShear(Math.toRadians(x), 0.0);
214                    break;
215                }
216                case TR_SKEWY:
217                {
218                    double y = (1.0 - interp) * fromValue[0] + interp * toValue[0];
219                    xform.setToShear(0.0, Math.toRadians(y));
220                    break;
221                }
222                default:
223                    xform.setToIdentity();
224                    break;
225            }
226    
227            return xform;
228        }
229    
230        protected void rebuild(AnimTimeParser animTimeParser) throws SVGException
231        {
232            super.rebuild(animTimeParser);
233    
234            StyleAttribute sty = new StyleAttribute();
235    
236            if (getPres(sty.setName("type")))
237            {
238                String strn = sty.getStringValue().toLowerCase();
239                if (strn.equals("translate")) xformType = TR_TRANSLATE;
240                if (strn.equals("rotate")) xformType = TR_ROTATE;
241                if (strn.equals("scale")) xformType = TR_SCALE;
242                if (strn.equals("skewx")) xformType = TR_SKEWX;
243                if (strn.equals("skewy")) xformType = TR_SKEWY;
244            }
245    
246            String fromStrn = null;
247            if (getPres(sty.setName("from")))
248            {
249                fromStrn = sty.getStringValue();
250            }
251    
252            String toStrn = null;
253            if (getPres(sty.setName("to")))
254            {
255                toStrn = sty.getStringValue();
256            }
257    
258            if (fromStrn != null && toStrn != null)
259            {
260                double[] fromValue = XMLParseUtil.parseDoubleList(fromStrn);
261                fromValue = validate(fromValue);
262    
263                double[] toValue = XMLParseUtil.parseDoubleList(toStrn);
264                toValue = validate(toValue);
265    
266                values = new double[][]{fromValue, toValue};
267            }
268    
269            String keyTimeStrn = null;
270            if (getPres(sty.setName("keyTimes")))
271            {
272                keyTimeStrn = sty.getStringValue();
273            }
274    
275            String valuesStrn = null;
276            if (getPres(sty.setName("values")))
277            {
278                valuesStrn = sty.getStringValue();
279            }
280    
281            if (keyTimeStrn != null && valuesStrn != null)
282            {
283                keyTimes = XMLParseUtil.parseDoubleList(keyTimeStrn);
284    
285                String[] valueList = Pattern.compile(";").split(valuesStrn);
286                values = new double[valueList.length][];
287                for (int i = 0; i < valueList.length; i++)
288                {
289                    double[] list = XMLParseUtil.parseDoubleList(valueList[i]);
290                    values[i] = validate(list);
291                }
292            }
293    
294            //Check our additive state
295    
296            if (getPres(sty.setName("additive")))
297            {
298                String strn = sty.getStringValue().toLowerCase();
299                if (strn.equals("sum")) this.additive = AT_SUM;
300            }
301        }
302    }