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.regex.Matcher; 050import java.util.regex.Pattern; 051import org.xml.sax.Attributes; 052import org.xml.sax.SAXException; 053 054 055/** 056 * @author Mark McKay 057 * @author <a href="mailto:mark@kitfox.com">Mark McKay</a> 058 */ 059public class AnimateMotion extends AnimateXform 060{ 061 public static final String TAG_NAME = "animateMotion"; 062 063 static final Matcher matchPoint = Pattern.compile("\\s*(\\d+)[^\\d]+(\\d+)\\s*").matcher(""); 064 065// protected double fromValue; 066// protected double toValue; 067 private GeneralPath path; 068 private int rotateType = RT_ANGLE; 069 private double rotate; //Static angle to rotate by 070 071 public static final int RT_ANGLE = 0; //Rotate by constant 'rotate' degrees 072 public static final int RT_AUTO = 1; //Rotate to reflect tangent of position on path 073 074 final ArrayList<Bezier> bezierSegs = new ArrayList<Bezier>(); 075 double curveLength; 076 077 /** Creates a new instance of Animate */ 078 public AnimateMotion() 079 { 080 } 081 082 @Override 083 public String getTagName() 084 { 085 return TAG_NAME; 086 } 087 088 @Override 089 public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent) throws SAXException 090 { 091 //Load style string 092 super.loaderStartElement(helper, attrs, parent); 093 094 //Motion element implies animating the transform element 095 if (attribName == null) 096 { 097 attribName = "transform"; 098 attribType = AT_AUTO; 099 setAdditiveType(AD_SUM); 100 } 101 102 103 String path = attrs.getValue("path"); 104 if (path != null) 105 { 106 this.path = buildPath(path, GeneralPath.WIND_NON_ZERO); 107 } 108 109 //Now parse rotation style 110 String rotate = attrs.getValue("rotate"); 111 if (rotate != null) 112 { 113 if (rotate.equals("auto")) 114 { 115 this.rotateType = RT_AUTO; 116 } 117 else 118 { 119 try { this.rotate = Math.toRadians(Float.parseFloat(rotate)); } catch (Exception e) {} 120 } 121 } 122 123 //Determine path 124 String from = attrs.getValue("from"); 125 String to = attrs.getValue("to"); 126 127 buildPath(from, to); 128 } 129 130 protected static void setPoint(Point2D.Float pt, String x, String y) 131 { 132 try { pt.x = Float.parseFloat(x); } catch (Exception e) {}; 133 134 try { pt.y = Float.parseFloat(y); } catch (Exception e) {}; 135 } 136 137 private void buildPath(String from, String to) 138 { 139 if (from != null && to != null) 140 { 141 Point2D.Float ptFrom = new Point2D.Float(), ptTo = new Point2D.Float(); 142 143 matchPoint.reset(from); 144 if (matchPoint.matches()) 145 { 146 setPoint(ptFrom, matchPoint.group(1), matchPoint.group(2)); 147 } 148 149 matchPoint.reset(to); 150 if (matchPoint.matches()) 151 { 152 setPoint(ptFrom, matchPoint.group(1), matchPoint.group(2)); 153 } 154 155 if (ptFrom != null && ptTo != null) 156 { 157 path = new GeneralPath(); 158 path.moveTo(ptFrom.x, ptFrom.y); 159 path.lineTo(ptTo.x, ptTo.y); 160 } 161 } 162 163 paramaterizePath(); 164 } 165 166 private void paramaterizePath() 167 { 168 bezierSegs.clear(); 169 curveLength = 0; 170 171 double[] coords = new double[6]; 172 double sx = 0, sy = 0; 173 174 for (PathIterator pathIt = path.getPathIterator(new AffineTransform()); !pathIt.isDone(); pathIt.next()) 175 { 176 Bezier bezier = null; 177 178 int segType = pathIt.currentSegment(coords); 179 180 switch (segType) 181 { 182 case PathIterator.SEG_LINETO: 183 { 184 bezier = new Bezier(sx, sy, coords, 1); 185 sx = coords[0]; 186 sy = coords[1]; 187 break; 188 } 189 case PathIterator.SEG_QUADTO: 190 { 191 bezier = new Bezier(sx, sy, coords, 2); 192 sx = coords[2]; 193 sy = coords[3]; 194 break; 195 } 196 case PathIterator.SEG_CUBICTO: 197 { 198 bezier = new Bezier(sx, sy, coords, 3); 199 sx = coords[4]; 200 sy = coords[5]; 201 break; 202 } 203 case PathIterator.SEG_MOVETO: 204 { 205 sx = coords[0]; 206 sy = coords[1]; 207 break; 208 } 209 case PathIterator.SEG_CLOSE: 210 //Do nothing 211 break; 212 213 } 214 215 if (bezier != null) 216 { 217 bezierSegs.add(bezier); 218 curveLength += bezier.getLength(); 219 } 220 } 221 } 222 223 /** 224 * Evaluates this animation element for the passed interpolation time. Interp 225 * must be on [0..1]. 226 */ 227 @Override 228 public AffineTransform eval(AffineTransform xform, double interp) 229 { 230 Point2D.Double point = new Point2D.Double(); 231 232 if (interp >= 1) 233 { 234 Bezier last = (Bezier)bezierSegs.get(bezierSegs.size() - 1); 235 last.getFinalPoint(point); 236 xform.setToTranslation(point.x, point.y); 237 return xform; 238 } 239 240 double curLength = curveLength * interp; 241 for (Bezier bez : bezierSegs) { 242 double bezLength = bez.getLength(); 243 if (curLength < bezLength) 244 { 245 double param = curLength / bezLength; 246 bez.eval(param, point); 247 break; 248 } 249 250 curLength -= bezLength; 251 } 252 253 xform.setToTranslation(point.x, point.y); 254 255 return xform; 256 } 257 258 259 @Override 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 300 /** 301 * @return the path 302 */ 303 public GeneralPath getPath() 304 { 305 return path; 306 } 307 308 /** 309 * @param path the path to set 310 */ 311 public void setPath(GeneralPath path) 312 { 313 this.path = path; 314 } 315 316 /** 317 * @return the rotateType 318 */ 319 public int getRotateType() 320 { 321 return rotateType; 322 } 323 324 /** 325 * @param rotateType the rotateType to set 326 */ 327 public void setRotateType(int rotateType) 328 { 329 this.rotateType = rotateType; 330 } 331 332 /** 333 * @return the rotate 334 */ 335 public double getRotate() 336 { 337 return rotate; 338 } 339 340 /** 341 * @param rotate the rotate to set 342 */ 343 public void setRotate(double rotate) 344 { 345 this.rotate = rotate; 346 } 347}