SVG metric transformations

Shape preserving transformations are much important for the transformation of rigid bodies. Those transformations are exclusively composed by translations and rotations; s. Transformation of coordinates (Projective; Affine; Metric)

Assume, we want to move the blue triangle located at (x1=100, y1=100) to the red location (x2=250, y2=200) and rotate it by a = -30. To achive this, we have to

  1. translate the (blue) rectangle's reference point by (-x1, -y1) back to the coordinate systems origin in the upper left corner.
  2. rotate about the origin by angle a.
  3. translate the reference point to the new (red) location at (x2, y2).
So we can simply use (s. SVG 1.0 Specification)
     transform = "translate(x2, y2) rotate(a) translate(-x1, -y1)"
or   transform = "translate(x2-x1, y2-y1) rotate(a, x1, x2)"
for our element's SVG transform attribute. Thus the following SVG code snippet will reproduce the upper image:
<?xml version="1.0"?> 
<svg width="400" height="300" onload="OnLoadEvent(evt)">
   <path style="fill:blue;stroke:black;" d="M75,50 125,50 100,100 z"/>
   <path style="fill:red; stroke:black;" d="M75,50 125,50 100,100 z" 
                          transform="translate(250 200) 
                                     rotate(-30) 
                                     translate(-100 -100)"/> 
</svg>
Try it

As we solved our problem, we can stop here .. BUT .. I recommend for performance reasons to use transformation matrices whenever possible. Reformulating the concatenation of translations and rotations as matrices, we yield

   [ 1 0 x2]   [cos(a) -sin(a) 0]   [ 1 0 -x1]   [cos(a) -sin(a) -x1cos(a) + y1sin(a) + x2]
   [ 0 1 y2] * [sin(a)  cos(a) 0] * [ 0 1 -y1] = [sin(a)  cos(a) -x1sin(a) - y1cos(a) + y2]
   [ 0 0 1 ]   [  0       0    1]   [ 0 0  1 ]   [  0       0               1             ]
So we can rewrite our element's SVG transform attribute as
transform = "matrix(cos(a), sin(a), -sin(a), cos(a), -x1cos(a)+y1sin(a)+x2, -x1sin(a)-y1cos(a)+y2)"
and again reproduce the upper image now via:
<?xml version="1.0"?> 
<svg width="400" height="300" onload="OnLoadEvent(evt)">
   <path style="fill:blue;stroke:black;" d="M75,50 125,50 100,100 z"/>
   <path style="fill:red; stroke:black;" d="M75,50 125,50 100,100 z" 
                          transform="matrix(0.866 -0.5 0.5 0.866 113.4 163.4)"/>
</svg>
Try it

To illustrate the ease of use, we want to create a simple animation of a rolling ball.

<?xml version="1.0"?> 
<svg width="400" height="300" onload="OnLoadEvent(evt)">
   <rect style="fill:none;stroke:black;" x="0" y="0" width="400" height="300" />
   <g id="ball">  <!-- roller  -->
      <circle style="fill:magenta;stroke:black;" cx="30" cy="140" r="20" />
      <path style="fill:none;stroke:black;" d="M25,135 35,145 M35,135 25,145"/>
   </g>
   <path style="fill:none;stroke:gray;stroke-width:6" d="M5,163 395,163" />  <!-- ground -->
<script><![CDATA[
// === the svg elements ======================================
var ball = null, x1=30, y1=140, r=20, psi=0, xend=370;  // globals ..

// --- animation function ------------------------------------
function Animate() 
{
   var sp = Math.sin(psi), cp = Math.cos(psi),
       x2 = x1 + r*psi, y2 = y1,
       matrix = "matrix(" + cp +"," + sp + "," + (-sp) + "," + cp + ","
                          + (-x1*cp+y1*sp+x2) + "," 
                          + (-x1*sp-y1*cp+y2) + ")";

   ball.setAttribute("transform", matrix);
   psi += Math.PI/45;
   if (x2 < xend)
      window.setTimeout("window.Animate()", 1);
   return true;
}
// == event handler ============================================
function OnLoadEvent(event)
{
   ball = event.getTarget().getOwnerDocument().getElementById("ball");
   window.Animate = Animate;
   window.Animate();
}
]]></script> 
</svg>
Try it

Now we understand transformations a bit better. We also know that this last animation example can be implemented simpler using SMIL animation.


© 2000 MecXpert