Index
Lesson 2 -- 2D Transformation & Animation
In this lesson, we will learn about basic geometrical transformations, like translate
, rotate
and scale
. We will also talk a little bit about its more general form -- transform matrix.
Then, we will apply these transformations with the simplest animation technique in canvas
as practice.
translate
Like the picture says, translation is moving every point in a same way. Every point $(x_0, y_0)$ is mapped to $(x_1, y_1)$ such that $x_1 = x_0 + \delta_x$, $y_1 = y_0 + \delta_y$.
So actually, the representation points (like circle center, which is not essential a real point), can also be translated in the same way.
So what retain are the size, shape, as well as direction.
rotate
Rotation is about around which point you rotate a shape, and to what extent. This is the invariant in transformation. Formally, we define the rotation by a axis point and a radian degree, so for every point $(x, y)$ in the original shape, we first translate it to the coordinate where origin is axis, so we got $(x - a_x, y - a_y)$, with it, we transform it to polar coordinate, , so the new point should only differ in $\theta' = \theta + \delta_\theta$, and we reversely transformed $p'$ to the original coordinate.
Actually, rotation can be various. In the 3D world, you can rotate along a line as well.
scale
scaling is simpler to understand -- just differ in size. But wait a minute, how? well, the intuition is to multiply every component of coordinate of every point by a factor. However, will the shape keep? Or, how to define the equivalence between shapes?
Unfortunately, this is a paradox:
In geometry, two subsets of a Euclidean space have the same shape if one can be transformed to the other by a combination of translations, rotations (together also called rigid transformations), and uniform scalings.
So, we first define the scaling, then we define the equivalence.
Transform matrix
So, do we have some way to uniform all those different sorts of transformation?
Yes, we have. The answer is matrix.
As long as we represent the shape as collection of point, and point as a vector of coordinates, and all we want is map a point to another point, so a matrix operation is intuitive.
For translate,
And the point must be extended to $p = (p_x, p_y, 1)$.
For rotation, let's consider the case when axis is origin
For scaling, it is
So, ideally, using a $3 \times 3$ matrix, we can represent any primitive transformation, and by the associative property of matrix multiplication, we can get complex transformation by simple multiplying.
Animation
The idea is simple: Let draw()
to be a function which will be called every X seconds. And we do a step-based transformation every time. It can be simply $Time \rightarrow Picture$ or $(Picture, \delta_{Time}) \rightarrow Picture'$.
Upon that, we got following program:
module Main where
import Prelude
import Control.Monad.Eff.Console
import DOM.Timer
import Graphics.Canvas (getCanvasElementById, getContext2D)
import Graphics.Canvas.Free
import Math hiding (log)
import Data.Maybe
import Control.Monad
import Data.Int (toNumber)
main = do
mcanvas <- getCanvasElementById "canvas"
case mcanvas of
Nothing -> do
log "failed to load canvas element"
Just canvas -> do
let s0 = 400 -- The length of segment
let r = 100.0 -- original radius of the line
repeatEvery 100 0 $ \s -> do -- every 100 ms, `s` is current step in integer
context <- getContext2D canvas
runGraphics context $ do
clear -- clear out the screen
setFillStyle "#000000" -- use black
-- formula 1
let s' = s `mod` s0
let x = if s' * 2 > s0 then s0 - s' else s'
-- formula 2
let theta = (toNumber s' / toNumber s0) * Math.pi
let r' = r - ((toNumber s') / 10.0)
let lineX = (toNumber x) - (Math.cos theta) * r'
let lineY = (Math.sin theta) * r' + 10.0
line (toNumber x) 10.0 lineX lineY
-- formula 3
let size' = (toNumber s') / 50.0
at (toNumber x) 10.0 $ circle (2.0 + size')
-- looper
repeatEvery t s f = do
_ <- timeout t $ do
f s
repeatEvery t (s + 5) f
return unit
at x y gfx = do
save
translate x y
gfx
restore
circle size = do
beginPath
arc { x: 0.0, y: 0.0, r: size, start: 0.0, end: Math.pi * 2.0 }
fill
clear = do
setFillStyle "#FFFFFF"
rect {x: 0.0, y: 0.0, h: 400.0, w: 400.0 }
fill
line x0 y0 x1 y1 = do
beginPath
moveTo x0 y0
lineTo x1 y1
stroke
There are three important formulas, determining the system state:
Formula 1
The changing of $s'$ with $s$ is like
Thus, The changing of $x$ with $s$ is
Formula 2
This does two things: fix one end to the circle, and another end rotate around the circle with a $\theta$ changing with $s$. And also, the length of $r$ will change.
Formula 3
Two things: scaling and translating.