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 java.awt.geom.AffineTransform; 045import java.awt.geom.GeneralPath; 046import java.awt.geom.PathIterator; 047import java.awt.geom.Point2D; 048import java.util.ArrayList; 049import java.util.Iterator; 050import java.util.regex.Matcher; 051import java.util.regex.Pattern; 052import org.xml.sax.Attributes; 053import org.xml.sax.SAXException; 054 055 056/** 057 * @author Mark McKay 058 * @author <a href="mailto:mark@kitfox.com">Mark McKay</a> 059 */ 060public class AnimateMotion extends AnimateXform 061{ 062 public static final String TAG_NAME = "animateMotion"; 063 064 static final Matcher matchPoint = Pattern.compile("\\s*(\\d+)[^\\d]+(\\d+)\\s*").matcher(""); 065 066// protected double fromValue; 067// protected double toValue; 068 GeneralPath path; 069 int rotateType = RT_ANGLE; 070 double rotate; //Static angle to rotate by 071 072 public static final int RT_ANGLE = 0; //Rotate by constant 'rotate' degrees 073 public static final int RT_AUTO = 1; //Rotate to reflect tangent of position on path 074 075 final ArrayList bezierSegs = new ArrayList(); 076 double curveLength; 077 078 /** Creates a new instance of Animate */ 079 public AnimateMotion() 080 { 081 } 082 083 public String getTagName() 084 { 085 return TAG_NAME; 086 } 087 088 public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent) throws SAXException 089 { 090 //Load style string 091 super.loaderStartElement(helper, attrs, parent); 092 093 //Motion element implies animating the transform element 094 if (attribName == null) 095 { 096 attribName = "transform"; 097 attribType = AT_AUTO; 098 additiveType = AD_SUM; 099 } 100 101 102 String path = attrs.getValue("path"); 103 if (path != null) 104 { 105 this.path = buildPath(path, GeneralPath.WIND_NON_ZERO); 106 } 107 108 //Now parse rotation style 109 String rotate = attrs.getValue("rotate"); 110 if (rotate != null) 111 { 112 if (rotate.equals("auto")) 113 { 114 this.rotateType = RT_AUTO; 115 } 116 else 117 { 118 try { this.rotate = Math.toRadians(Float.parseFloat(rotate)); } catch (Exception e) {} 119 } 120 } 121 122 //Determine path 123 String from = attrs.getValue("from"); 124 String to = attrs.getValue("to"); 125 126 buildPath(from, to); 127 } 128 129 protected static void setPoint(Point2D.Float pt, String x, String y) 130 { 131 try { pt.x = Float.parseFloat(x); } catch (Exception e) {}; 132 133 try { pt.y = Float.parseFloat(y); } catch (Exception e) {}; 134 } 135 136 private void buildPath(String from, String to) 137 { 138 if (from != null && to != null) 139 { 140 Point2D.Float ptFrom = new Point2D.Float(), ptTo = new Point2D.Float(); 141 142 matchPoint.reset(from); 143 if (matchPoint.matches()) 144 { 145 setPoint(ptFrom, matchPoint.group(1), matchPoint.group(2)); 146 } 147 148 matchPoint.reset(to); 149 if (matchPoint.matches()) 150 { 151 setPoint(ptFrom, matchPoint.group(1), matchPoint.group(2)); 152 } 153 154 if (ptFrom != null && ptTo != null) 155 { 156 path = new GeneralPath(); 157 path.moveTo(ptFrom.x, ptFrom.y); 158 path.lineTo(ptTo.x, ptTo.y); 159 } 160 } 161 162 paramaterizePath(); 163 } 164 165 private void paramaterizePath() 166 { 167 bezierSegs.clear(); 168 curveLength = 0; 169 170 double[] coords = new double[6]; 171 double sx = 0, sy = 0; 172 173 for (PathIterator pathIt = path.getPathIterator(new AffineTransform()); !pathIt.isDone(); pathIt.next()) 174 { 175 Bezier bezier = null; 176 177 int segType = pathIt.currentSegment(coords); 178 179 switch (segType) 180 { 181 case PathIterator.SEG_LINETO: 182 { 183 bezier = new Bezier(sx, sy, coords, 1); 184 sx = coords[0]; 185 sy = coords[1]; 186 break; 187 } 188 case PathIterator.SEG_QUADTO: 189 { 190 bezier = new Bezier(sx, sy, coords, 2); 191 sx = coords[2]; 192 sy = coords[3]; 193 break; 194 } 195 case PathIterator.SEG_CUBICTO: 196 { 197 bezier = new Bezier(sx, sy, coords, 3); 198 sx = coords[4]; 199 sy = coords[5]; 200 break; 201 } 202 case PathIterator.SEG_MOVETO: 203 { 204 sx = coords[0]; 205 sy = coords[1]; 206 break; 207 } 208 case PathIterator.SEG_CLOSE: 209 //Do nothing 210 break; 211 212 } 213 214 if (bezier != null) 215 { 216 bezierSegs.add(bezier); 217 curveLength += bezier.getLength(); 218 } 219 } 220 } 221 222 /** 223 * Evaluates this animation element for the passed interpolation time. Interp 224 * must be on [0..1]. 225 */ 226 public AffineTransform eval(AffineTransform xform, double interp) 227 { 228 Point2D.Double point = new Point2D.Double(); 229 230 if (interp >= 1) 231 { 232 Bezier last = (Bezier)bezierSegs.get(bezierSegs.size() - 1); 233 last.getFinalPoint(point); 234 xform.setToTranslation(point.x, point.y); 235 return xform; 236 } 237 238 double curLength = curveLength * interp; 239 for (Iterator it = bezierSegs.iterator(); it.hasNext();) 240 { 241 Bezier bez = (Bezier)it.next(); 242 243 double bezLength = bez.getLength(); 244 if (curLength < bezLength) 245 { 246 double param = curLength / bezLength; 247 bez.eval(param, point); 248 break; 249 } 250 251 curLength -= bezLength; 252 } 253 254 xform.setToTranslation(point.x, point.y); 255 256 return xform; 257 } 258 259 260 protected void rebuild(AnimTimeParser animTimeParser) throws SVGException 261 { 262 super.rebuild(animTimeParser); 263 264 StyleAttribute sty = new StyleAttribute(); 265 266 if (getPres(sty.setName("path"))) 267 { 268 String strn = sty.getStringValue(); 269 this.path = buildPath(strn, GeneralPath.WIND_NON_ZERO); 270 } 271 272 if (getPres(sty.setName("rotate"))) 273 { 274 String strn = sty.getStringValue(); 275 if (strn.equals("auto")) 276 { 277 this.rotateType = RT_AUTO; 278 } 279 else 280 { 281 try { this.rotate = Math.toRadians(Float.parseFloat(strn)); } catch (Exception e) {} 282 } 283 } 284 285 String from = null; 286 if (getPres(sty.setName("from"))) 287 { 288 from = sty.getStringValue(); 289 } 290 291 String to = null; 292 if (getPres(sty.setName("to"))) 293 { 294 to = sty.getStringValue(); 295 } 296 297 buildPath(from, to); 298 } 299}