Skip to content

Instantly share code, notes, and snippets.

@devongovett
Created August 4, 2011 20:15
Show Gist options
  • Save devongovett/1126129 to your computer and use it in GitHub Desktop.
Save devongovett/1126129 to your computer and use it in GitHub Desktop.
WebKitCSSTransform for all browsers
class CSSTransform
constructor: ->
@m11 = @m22 = @m33 = @m44 = 1
@m12 = @m13 = @m14 = 0
@m21 = @m23 = @m24 = 0
@m31 = @m32 = @m34 = 0
@m41 = @m42 = @m43 = 0
if arguments.length
@setMatrix arguments...
setMatrix: (v...) ->
if v.length is 6
[@m11, @m12, @m21, @m22, @m41, @m42] = v
else
[@m11, @m12, @m13, @m14,
@m21, @m22, @m23, @m24,
@m31, @m32, @m33, @m34,
@m41, @m42, @m43, @m44] = v
multiply = (a, b) ->
c = new CSTransform
c.m11 = a.m11 * b.m11 + a.m12 * b.m21 + a.m13 * b.m31 + a.m14 * b.m41
c.m12 = a.m11 * b.m12 + a.m12 * b.m22 + a.m13 * b.m32 + a.m14 * b.m42
c.m13 = a.m11 * b.m13 + a.m12 * b.m23 + a.m13 * b.m33 + a.m14 * b.m43
c.m14 = a.m11 * b.m14 + a.m12 * b.m24 + a.m13 * b.m34 + a.m14 * b.m44
c.m21 = a.m21 * b.m11 + a.m22 * b.m21 + a.m23 * b.m31 + a.m24 * b.m41
c.m22 = a.m21 * b.m12 + a.m22 * b.m22 + a.m23 * b.m32 + a.m24 * b.m42
c.m23 = a.m21 * b.m13 + a.m22 * b.m23 + a.m23 * b.m33 + a.m24 * b.m43
c.m24 = a.m21 * b.m14 + a.m22 * b.m24 + a.m23 * b.m34 + a.m24 * b.m44
c.m31 = a.m31 * b.m11 + a.m32 * b.m21 + a.m33 * b.m31 + a.m34 * b.m41
c.m32 = a.m31 * b.m12 + a.m32 * b.m22 + a.m33 * b.m32 + a.m34 * b.m42
c.m33 = a.m31 * b.m13 + a.m32 * b.m23 + a.m33 * b.m33 + a.m34 * b.m43
c.m34 = a.m31 * b.m14 + a.m32 * b.m24 + a.m33 * b.m34 + a.m34 * b.m44
c.m41 = a.m41 * b.m11 + a.m42 * b.m21 + a.m43 * b.m31 + a.m44 * b.m41
c.m42 = a.m41 * b.m12 + a.m42 * b.m22 + a.m43 * b.m32 + a.m44 * b.m42
c.m43 = a.m41 * b.m13 + a.m42 * b.m23 + a.m43 * b.m33 + a.m44 * b.m43
c.m44 = a.m41 * b.m14 + a.m42 * b.m24 + a.m43 * b.m34 + a.m44 * b.m44
return c
v3Length = (a, b, c) ->
Math.sqrt (a * a) + (b * b) + (c * c)
v3Scale = (a, b, c, desiredLength) ->
len = v3Length(a, b, c)
if len isnt 0
l = desiredLength / len
return [a * l, b * l, c * l]
return [a, b, c]
v3Dot = (a1, b1, c1, a2, b2, c2) ->
(a1 * a2) + (b1 * b2) + (c1 * c2)
v3Combine = (a1, b1, c1, a2, b2, c2, ascl, bscl) ->
a = (ascl * a1) + (bscl * a2)
b = (ascl * b1) + (bscl * b2)
c = (ascl * c1) + (bscl * c2)
return [a, b, c]
v3Cross = (a1, b1, c1, a2, b2, c2) ->
a = (b1 * c2) - (c1 * b2)
b = (c1 * a2) - (a1 * c2)
c = (a1 * b2) - (b1 * a2)
return [a, b, c]
decompose: ->
# get translation
result =
translateX: @m41
translateY: @m42
translateZ: @m43
# scale and skew
{m11, m12, m13, m21, m22, m23, m31, m32, m33} = this
# Compute X scale factor and normalize first row.
result.scaleX = v3Length(m11, m12, m13)
[m11, m12, m13] = v3Scale(m11, m12, m13, 1.0)
# Compute XY shear factor and make 2nd row orthogonal to 1st.
result.skewXY = v3Dot(m11, m12, m13, m21, m22, m23)
[m21, m22, m23] = v3Combine(m21, m22, m23, m11, m12, m13, 1.0, -result.skewXY)
# Now, compute Y scale and normalize 2nd row.
result.scaleY = v3Length(m21, m22, m23)
[m21, m22, m23] = v3Scale(m21, m22, m23, 1.0)
result.skewXY /= result.scaleY
# Compute XZ and YZ shears, orthogonalize 3rd row.
result.skewXZ = v3Dot(m11, m12, m13, m31, m32, m33)
[m31, m32, m33] = v3Combine(m31, m32, m33, m11, m12, m13, 1.0, -result.skewXZ)
result.skewYZ = v3Dot(m21, m22, m23, m31, m32, m33)
[m31, m32, m33] = v3Combine(m31, m32, m33, m11, m12, m13, 1.0, -result.skewYZ)
# Next, get Z scale and normalize 3rd row.
result.scaleZ = v3Length(m31, m32, m33)
[m31, m32, m33] = v3Scale(m31, m32, m33, 1.0)
result.skewXZ /= result.scaleZ
result.skewYZ /= result.scaleZ
# At this point, the matrix is orthonormal.
# Check for a coordinate system flip. If the determinant
# is -1, then negate the matrix and the scaling factors.
[a, b, c] = v3Cross(m21, m22, m23, m31, m32, m33)
if v3Dot(m11, m12, m13, a, b, c) < 0
result.scale *= -1
[m11, m12, m13, m21, m22, m23, m31, m32, m33] = (e * -1 for e in [m11, m12, m13, m21, m22, m23, m31, m32, m33])
# Now, get the rotations out
t = m11 + m21 + m31 + 1.0
if t > 1e-4
s = 0.5 / Math.sqrt(t)
w = 0.25 / s
x = (m31 - m23) * s
y = (m13 - m31) * s
z = (m21 - m12) * s
else if m11 > m22 and m11 > m33
s = Math.sqrt(1.0 + m11 - m22 - m33) * 2.0
x = 0.25 * s
y = (m12 + m21) / s
z = (m13 + m31) / s
w = (m32 - m23) / s
else if m22 > m33
s = Math.sqrt(1.0 + m22 - m11 - m33) * 2.0
x = (m12 + m21) / s
y = 0.25 * s
z = (m23 + m32) / s
w = (m13 - m31) / s
else
s = Math.sqrt(1.0 + m33 - m11 - m22) * 2.0
x = (m13 + m31) / s
y = (m23 + m32) / s
z = 0.25 * s
w = (m21 - m12) / s
result.quaternionX = x
result.quaternionY = y
result.quaternionZ = z
result.quaternionW = w
return result
valueAt = (from, to, progress) ->
if from isnt to
return from + (to - from) * progress
return from
blend: (progress, from, to) ->
from = from.decompose()
to = to.decompose()
from.scaleX = valueAt from.scaleX, to.scaleX, progress
from.scaleY = valueAt from.scaleY, to.scaleY, progress
from.scaleZ = valueAt from.scaleZ, to.scaleZ, progress
from.skewXY = valueAt from.skewXY, to.skewXY, progress
from.skewXZ = valueAt from.skewXZ, to.skewXZ, progress
from.skewYZ = valueAt from.skewYZ, to.skewYZ, progress
from.translateX = valueAt from.translateX, to.translateX, progress
from.translateY = valueAt from.translateY, to.translateY, progress
from.translateZ = valueAt from.translateZ, to.translateZ, progress
#slerp(&fromDecomp.quaternionX, &toDecomp.quaternionX, progress);
translate: (x = 0, y = 0, z = 0) ->
t = new CSTransform
t.m41 = x
t.m42 = y
t.m43 = z
return multiply(this, t)
scale: (sx = 1, sy = sx, sz = 1) ->
t = new CSTransform
t.m11 = sx
t.m22 = sy
t.m33 = sz
return multiply(this, t)
radians = (angle) ->
Math.PI * (angle / 180)
rotate: (rx, ry, rz) ->
rx = 0 if isNaN(rx)
if isNaN(ry) and isNaN(rz)
rz = rx
rx = ry = 0
ry = 0 if isNaN(ry)
rz = 0 if isNaN(rz)
rx = radians(rx)
ry = radians(ry)
rz = radians(rz)
tx = new CSTransform
ty = new CSTransform
tz = new CSTransform
rz /= 2
sinA = Math.sin(rz)
cosA = Math.cos(rz)
sinA2 = sinA * sinA
# Matrices are identity outside the assigned values
tz.m11 = tz.m22 = 1 - 2 * sinA2
tz.m12 = tz.m21 = 2 * sinA * cosA
tz.m21 *= -1
ry /= 2
sinA = Math.sin(ry)
cosA = Math.cos(ry)
sinA2 = sinA * sinA
ty.m11 = ty.m33 = 1 - 2 * sinA2
ty.m13 = ty.m31 = 2 * sinA * cosA
ty.m13 *= -1
rx /= 2
sinA = Math.sin(rx)
cosA = Math.cos(rx)
sinA2 = sinA * sinA
tx.m22 = tx.m33 = 1 - 2 * sinA2
tx.m23 = tx.m32 = 2 * sinA * cosA
tx.m32 *= -1
return multiply(this, multiply(tx, multiply(tz, ty)))
skew: (sx = 0, sy = 0) ->
sx = radians(sx)
sy = radians(sy)
t = new CSTransform(1, Math.tan(sy), Math.tan(sx), 1, 0, 0)
return multiply(this, t)
isAffine: ->
@m13 is 0 and @m14 is 0 and
@m23 is 0 and @m24 is 0 and
@m31 is 0 and @m32 is 0 and
@m33 is 1 and @m34 is 0 and
@m43 is 0 and @m44 is 1
cssString: ->
if @isAffine()
prefix = 'matrix('
points = [@m11, @m12, @m21, @m22, @m41, @m42]
else
prefix = 'matrix3d('
points = [@m11, @m12, @m13, @m14,
@m21, @m22, @m23, @m24,
@m31, @m32, @m33, @m34,
@m41, @m42, @m43, @m44]
points = (point.toFixed(6) for point in points)
return prefix + points.join(', ') + ')'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment