-
Notifications
You must be signed in to change notification settings - Fork 21
Explaining Math
In this article I am attempting to give an explanation to what is going on. Some basics on linear algebra would be of help, but not assumed. Some idea of vectors would be definitely useful.
A position in the element we are trying to control, can be represented by two points (x, y). This is the amount of distance on moves (0,0) left-top corner of the image. But first we need to do this, set the origin of all transformation as (0,0):
elem.style['transform-origin'] = "0 0"
We will call this u=(x,y) a vector. CSS lets us do linear transformations, like
v = Au + b
If you would like to explicitly write down, it means something like:
v.x = a.xx u.x + a.xy u.y + b.x
v.y = a.yx u.x + a.yy u.y + b.y
Every linear operator A, can be defined in terms of their images on basic vectors ex=(1,0), ey=(0,1). So A = (a.x, a.y) means, a.x = (a.xx, a.yx) the columns in the previous example. Since this a convenient notation (called column-major), we will use it. Now we can write succinctly,
Au = a.x u.x + a.y + u.y
v = Au + b
Now there are two fundamental quantities we are interested in, first dot-product (aka inner product) which to some extent expresses how "close" two vectors are, and another wedge product (aka exterior product) which to expresses to some extent, how far two vectors are. We expect these two to be two linear functions, that are distributive on both sides. Geometrically one represents the product of projection of a vector on second, and length of second, and other represents the signed-area of the parallelogram composed of both.
dot(ex, ex) = 1
dot(ey, ey) = 1
dot(ex, ey) = 0
dot(ey, ex) = 0
dot(u, v) = dot(u.x ex + u.y ey, v.x ex + v.y ey) = u.x v.x + u.y e.y
wedge(ex, ex) = 0
wedge(ey, ey) = 0
wedge(ex, ey) = 1
wedge(ey, ex) = -1
wedge(u, v) = wedge(u.x ex + u.y ey, v.x ex + v.y ey) = u.x v.y - u.y e.x
Note that by Pythagorean theorem square of length of a vector is just dot(a, a). (if you think hard enough you will realize this is actually a proof for the same, but I am not taking that rigour)
Now let us concentrate on rotations, what should A be if we are simply rotating by (1,0) to some (c, s) whose length is 1. It is simple, ex=(1,0) becomes (c,s) by definition, and ey=(0, 1) becomes (-s, c). Because we want something for which dot goes to zero, and wedge is 1, just like what would happen when ex and ey are operated. Just because coordinate system changes dot and wedge shouldn't change unless you are scaling.
dot ( (c, s), (-s,c) ) = -cs + sc = 0
wedge( (c, s), (-s, c) ) = c^2 + s^2 = dot( (c,s), (c,s) ) = 1
Now we know our rotation matrix takes the form, because we know its images on ex and ey.
rotate(c, s) = [ (c, s), (-s, c) ]
Now if we were to scale this, by some length, we would get some number multiplied by rotate(c,s) matrix. So if our objective is to rotate some a=(ax, ay) to b=(bx, by) then it is clear first we have to scale down to unit-length and then apply rotation matrix. Since both a and b need not be of unit-length, we have normalize them to get correct c, and s values.
|a| = sqrt(dot(a, a))
|b| = sqrt(dot(a, a))
c = dot(a, b) / (|a||b|)
s = wedge(a, b) / (|a||b|)
Final rotation + scaling matrix, it should have a scale given by |b| / |a|, so:
rotscal, b) = |b|/|a| [ (c, s), (-s, c) ]
rotscal(a, b) = 1/|a||a| [ (dot, wedge), (-wedge, dot) ]
rotscal(a, b) = 1/(dot(a,a)) (dot, wedge), (-wedge, dot) ]
rotscal(a, b) = 1/(dot(a,a)) rotate(dot, wedge)
So we end up getting a formula that has absolutely no sqrt in it! This is no magic, but because similarity transforms must be rational, but we will not get into it for now.
Now if we have two fingers sliding from (s1, s2) to (d1, d2), what should be the rotscal matrix?
R = rotscal(d1 - s1, d2 - s2)
What about the vector part of the translation? When s1 should finally reach d1, so we need an offset of:
b = d1 - R s1
Now I think you can read the code and understand what is going on. Now in code I've used index 0 for x, and index 1 for y.