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