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    private double[][] values;
064    private double[] keyTimes;
065
066    public static final int AT_REPLACE = 0;
067    public static final int AT_SUM = 1;
068
069    private 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    private int xformType = TR_INVALID;
079
080    /** Creates a new instance of Animate */
081    public AnimateTransform()
082    {
083    }
084
085    @Override
086    public String getTagName()
087    {
088        return TAG_NAME;
089    }
090
091    @Override
092    public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent) throws SAXException
093    {
094                //Load style string
095        super.loaderStartElement(helper, attrs, parent);
096
097        //Type of matrix of transform.  Should be one of the known names used to
098        // define matrix transforms
099        // valid types: translate, scale, rotate, skewX, skewY
100        // 'matrix' not valid for animation
101        String type = attrs.getValue("type").toLowerCase();
102        if (type.equals("translate")) xformType = TR_TRANSLATE;
103        if (type.equals("rotate")) xformType = TR_ROTATE;
104        if (type.equals("scale")) xformType = TR_SCALE;
105        if (type.equals("skewx")) xformType = TR_SKEWX;
106        if (type.equals("skewy")) xformType = TR_SKEWY;
107
108        String fromStrn = attrs.getValue("from");
109        String toStrn = attrs.getValue("to");
110        if (fromStrn != null && toStrn != null)
111        {
112            //fromValue = parseSingleTransform(type + "(" + strn + ")");
113            double[] fromValue = XMLParseUtil.parseDoubleList(fromStrn);
114            fromValue = validate(fromValue);
115
116    //        toValue = parseSingleTransform(type + "(" + strn + ")");
117            double[] toValue = XMLParseUtil.parseDoubleList(toStrn);
118            toValue = validate(toValue);
119            
120            values = new double[][]{fromValue, toValue};
121            keyTimes = new double[]{0, 1};
122        }
123
124        String keyTimeStrn = attrs.getValue("keyTimes");
125        String valuesStrn = attrs.getValue("values");
126        if (keyTimeStrn != null && valuesStrn != null)
127        {
128            keyTimes = XMLParseUtil.parseDoubleList(keyTimeStrn);
129            
130            String[] valueList = Pattern.compile(";").split(valuesStrn);
131            values = new double[valueList.length][];
132            for (int i = 0; i < valueList.length; i++)
133            {
134                double[] list = XMLParseUtil.parseDoubleList(valueList[i]);
135                values[i] = validate(list);
136            }
137        }
138        
139        //Check our additive state
140        String additive = attrs.getValue("additive");
141        if (additive != null)
142        {
143            if (additive.equals("sum")) this.additive = AT_SUM;
144        }
145    }
146
147    /**
148     * Check list size against current xform type and ensure list
149     * is expanded to a standard list size
150     */
151    private double[] validate(double[] paramList)
152    {
153        switch (xformType)
154        {
155            case TR_SCALE:
156            {
157                if (paramList == null)
158                {
159                    paramList = new double[]{1, 1};
160                }
161                else if (paramList.length == 1)
162                {
163                    paramList = new double[]{paramList[0], paramList[0]};
164                    
165//                    double[] tmp = paramList;
166//                    paramList = new double[2];
167//                    paramList[0] = paramList[1] = tmp[0];
168                }
169            }
170        }
171
172        return paramList;
173    }
174
175    /**
176     * Evaluates this animation element for the passed interpolation time.  Interp
177     * must be on [0..1].
178     */
179    @Override
180    public AffineTransform eval(AffineTransform xform, double interp)
181    {
182        int idx = 0;
183        for (; idx < keyTimes.length - 1; idx++)
184        {
185            if (interp >= keyTimes[idx])
186            {
187                idx--;
188                if (idx < 0) idx = 0;
189                break;
190            }
191        }
192        
193        double spanStartTime = keyTimes[idx];
194        double spanEndTime = keyTimes[idx + 1];
195//        double span = spanStartTime - spanEndTime;
196        
197        interp = (interp - spanStartTime) / (spanEndTime - spanStartTime);
198        double[] fromValue = values[idx];
199        double[] toValue = values[idx + 1];
200        
201        switch (xformType)
202        {
203            case TR_TRANSLATE:
204            {
205                double x0 = fromValue.length >= 1 ? fromValue[0] : 0;
206                double x1 = toValue.length >= 1 ? toValue[0] : 0;
207                double y0 = fromValue.length >= 2 ? fromValue[1] : 0;
208                double y1 = toValue.length >= 2 ? toValue[1] : 0;
209                
210                double x = lerp(x0, x1, interp);
211                double y = lerp(y0, y1, interp);
212                
213                xform.setToTranslation(x, y);
214                break;
215            }
216            case TR_ROTATE:
217            {
218                double x1 = fromValue.length == 3 ? fromValue[1] : 0;
219                double y1 = fromValue.length == 3 ? fromValue[2] : 0;
220                double x2 = toValue.length == 3 ? toValue[1] : 0;
221                double y2 = toValue.length == 3 ? toValue[2] : 0;
222                
223                double theta = lerp(fromValue[0], toValue[0], interp);
224                double x = lerp(x1, x2, interp);
225                double y = lerp(y1, y2, interp);
226                xform.setToRotation(Math.toRadians(theta), x, y);
227                break;
228            }
229            case TR_SCALE:
230            {
231                double x0 = fromValue.length >= 1 ? fromValue[0] : 1;
232                double x1 = toValue.length >= 1 ? toValue[0] : 1;
233                double y0 = fromValue.length >= 2 ? fromValue[1] : 1;
234                double y1 = toValue.length >= 2 ? toValue[1] : 1;
235                
236                double x = lerp(x0, x1, interp);
237                double y = lerp(y0, y1, interp);
238                xform.setToScale(x, y);
239                break;
240            }
241            case TR_SKEWX:
242            {
243                double x = lerp(fromValue[0], toValue[0], interp);
244                xform.setToShear(Math.toRadians(x), 0.0);
245                break;
246            }
247            case TR_SKEWY:
248            {
249                double y = lerp(fromValue[0], toValue[0], interp);
250                xform.setToShear(0.0, Math.toRadians(y));
251                break;
252            }
253            default:
254                xform.setToIdentity();
255                break;
256        }
257
258        return xform;
259    }
260
261    @Override
262    protected void rebuild(AnimTimeParser animTimeParser) throws SVGException
263    {
264        super.rebuild(animTimeParser);
265
266        StyleAttribute sty = new StyleAttribute();
267
268        if (getPres(sty.setName("type")))
269        {
270            String strn = sty.getStringValue().toLowerCase();
271            if (strn.equals("translate")) xformType = TR_TRANSLATE;
272            if (strn.equals("rotate")) xformType = TR_ROTATE;
273            if (strn.equals("scale")) xformType = TR_SCALE;
274            if (strn.equals("skewx")) xformType = TR_SKEWX;
275            if (strn.equals("skewy")) xformType = TR_SKEWY;
276        }
277
278        String fromStrn = null;
279        if (getPres(sty.setName("from")))
280        {
281            fromStrn = sty.getStringValue();
282        }
283
284        String toStrn = null;
285        if (getPres(sty.setName("to")))
286        {
287            toStrn = sty.getStringValue();
288        }
289
290        if (fromStrn != null && toStrn != null)
291        {
292            double[] fromValue = XMLParseUtil.parseDoubleList(fromStrn);
293            fromValue = validate(fromValue);
294
295            double[] toValue = XMLParseUtil.parseDoubleList(toStrn);
296            toValue = validate(toValue);
297
298            values = new double[][]{fromValue, toValue};
299        }
300
301        String keyTimeStrn = null;
302        if (getPres(sty.setName("keyTimes")))
303        {
304            keyTimeStrn = sty.getStringValue();
305        }
306
307        String valuesStrn = null;
308        if (getPres(sty.setName("values")))
309        {
310            valuesStrn = sty.getStringValue();
311        }
312
313        if (keyTimeStrn != null && valuesStrn != null)
314        {
315            keyTimes = XMLParseUtil.parseDoubleList(keyTimeStrn);
316
317            String[] valueList = Pattern.compile(";").split(valuesStrn);
318            values = new double[valueList.length][];
319            for (int i = 0; i < valueList.length; i++)
320            {
321                double[] list = XMLParseUtil.parseDoubleList(valueList[i]);
322                values[i] = validate(list);
323            }
324        }
325
326        //Check our additive state
327
328        if (getPres(sty.setName("additive")))
329        {
330            String strn = sty.getStringValue().toLowerCase();
331            if (strn.equals("sum")) this.additive = AT_SUM;
332        }
333    }
334
335    /**
336     * @return the values
337     */
338    public double[][] getValues()
339    {
340        return values;
341    }
342
343    /**
344     * @param values the values to set
345     */
346    public void setValues(double[][] values)
347    {
348        this.values = values;
349    }
350
351    /**
352     * @return the keyTimes
353     */
354    public double[] getKeyTimes()
355    {
356        return keyTimes;
357    }
358
359    /**
360     * @param keyTimes the keyTimes to set
361     */
362    public void setKeyTimes(double[] keyTimes)
363    {
364        this.keyTimes = keyTimes;
365    }
366
367    /**
368     * @return the additive
369     */
370    public int getAdditive()
371    {
372        return additive;
373    }
374
375    /**
376     * @param additive the additive to set
377     */
378    public void setAdditive(int additive)
379    {
380        this.additive = additive;
381    }
382
383    /**
384     * @return the xformType
385     */
386    public int getXformType()
387    {
388        return xformType;
389    }
390
391    /**
392     * @param xformType the xformType to set
393     */
394    public void setXformType(int xformType)
395    {
396        this.xformType = xformType;
397    }
398}