# koenbok/Framer

Koen Bok committed Mar 12, 2013
1 parent a379acc commit 250cb04cc9b94fe9c525e0116bee2e39441871a1
Showing with 341 additions and 0 deletions.
1. +94 −0 src/curves/bezier.coffee
2. +123 −0 src/curves/spring.coffee
3. +124 −0 src/primitives/matrix.coffee
 @@ -0,0 +1,94 @@ # WebKit implementation found on http://stackoverflow.com/a/11697909 class UnitBezier epsilon: 1e-6 # Precision constructor: (p1x, p1y, p2x, p2y) -> # pre-calculate the polynomial coefficients # First and last control points are implied to be (0,0) and (1.0, 1.0) @cx = 3.0 * p1x @bx = 3.0 * (p2x - p1x) - @cx @ax = 1.0 - @cx - @bx @cy = 3.0 * p1y @by = 3.0 * (p2y - p1y) - @cy @ay = 1.0 - @cy - @by sampleCurveX: (t) -> ((@ax * t + @bx) * t + @cx) * t sampleCurveY: (t) -> ((@ay * t + @by) * t + @cy) * t sampleCurveDerivativeX: (t) -> (3.0 * @ax * t + 2.0 * @bx) * t + @cx solveCurveX: (x) -> # First try a few iterations of Newton's method -- normally very fast. t2 = x i = 0 while i < 8 x2 = @sampleCurveX(t2) - x return t2 if Math.abs(x2) < @epsilon d2 = @sampleCurveDerivativeX(t2) break if Math.abs(d2) < @epsilon t2 = t2 - x2 / d2 i++ # No solution found - use bi-section t0 = 0.0 t1 = 1.0 t2 = x return t0 if t2 < t0 return t1 if t2 > t1 while t0 < t1 x2 = @sampleCurveX(t2) return t2 if Math.abs(x2 - x) < @epsilon if x > x2 t0 = t2 else t1 = t2 t2 = (t1 - t0) * .5 + t0 # Give up t2 solve: (x) -> @sampleCurveY @solveCurveX(x) BezierCurve = (a, b, c, d, time, fps) -> # console.log "bezier.BezierCurve", a, b, c, d, time, fps curve = new UnitBezier a, b, c, d values = [] steps = (time / 1000) * fps if steps > 1000 throw Error "Bezier: too many values" for step in [0..steps] values.push curve.solve(step/steps) * 100 values defaults = {} defaults.Linear = (time, fps) -> BezierCurve 0, 0, 1, 1, time, fps defaults.Ease = (time, fps) -> BezierCurve .25, .1, .25, 1, time, fps defaults.EaseIn = (time, fps) -> BezierCurve .42, 0, 1, 1, time, fps defaults.EaseOut = (time, fps) -> BezierCurve 0, 0, .58, 1, time, fps defaults.EaseInOut = (time, fps) -> BezierCurve .42, 0, .58, 1, time, fps exports.defaults = defaults exports.BezierCurve = BezierCurve
 @@ -0,0 +1,123 @@ defaults = tension: 80 friction: 8 velocity: 0 speed: 1/60.0 tolerance: 0.01 springAccelerationForState = (state) -> return - state.tension * state.x - state.friction * state.v springEvaluateState = (initialState) -> output = {} output.dx = initialState.v output.dv = springAccelerationForState initialState return output springEvaluateStateWithDerivative = (initialState, dt, derivative) -> state = {} state.x = initialState.x + derivative.dx * dt state.v = initialState.v + derivative.dv * dt state.tension = initialState.tension state.friction = initialState.friction output = {} output.dx = state.v output.dv = springAccelerationForState state return output springIntegrateState = (state, speed) -> a = springEvaluateState state b = springEvaluateStateWithDerivative state, speed * 0.5, a c = springEvaluateStateWithDerivative state, speed * 0.5, b d = springEvaluateStateWithDerivative state, speed, c dxdt = 1.0/6.0 * (a.dx + 2.0 * (b.dx + c.dx) + d.dx) dvdt = 1.0/6.0 * (a.dv + 2.0 * (b.dv + c.dv) + d.dv) state.x = state.x + dxdt * speed state.v = state.v + dvdt * speed return state class Spring constructor: (args) -> args = args or {} @velocity = args.velocity or defaults.velocity @tension = args.tension or defaults.tension @friction = args.friction or defaults.friction @speed = args.speed or defaults.speed @tolerance = args.tolerance or defaults.tolerance @reset() reset: => @startValue = 0 @currentValue = @startValue @endValue = 100 @moving = true next: => targetValue = @currentValue stateBefore = {} stateAfter = {} # Calculate previous state stateBefore.x = targetValue - @endValue stateBefore.v = @velocity stateBefore.tension = @tension stateBefore.friction = @friction # Calculate new state stateAfter = springIntegrateState stateBefore, @speed @currentValue = @endValue + stateAfter.x finalVelocity = stateAfter.v netFloat = stateAfter.x net1DVelocity = stateAfter.v # See if we reached the end state netValueIsLow = Math.abs(netFloat) < @tolerance netVelocityIsLow = Math.abs(net1DVelocity) < @tolerance stopSpring = netValueIsLow and netVelocityIsLow @moving = !stopSpring if stopSpring finalVelocity = 0 @currentValue = @endValue @velocity = finalVelocity return @currentValue all: -> @reset() count = 0 while @moving if count > 1000 throw Error "Spring: too many values" count++ @next() time: -> @all().length * @speed SpringCurve = (tension, friction, velocity, fps) -> # console.log "spring.SpringCurve", tension, friction, velocity, fps spring = new Spring(tension:tension, friction:friction, velocity:velocity, speed:1/fps) spring.all() exports.SpringCurve = SpringCurve
 @@ -0,0 +1,124 @@ _ = require "underscore" WebKitCSSMatrix::cssValues = -> r = (v) -> v.toFixed 5 values = " matrix3d( #{r @m11}, #{r @m12}, #{r @m13}, #{r @m14}, #{r @m21}, #{r @m22}, #{r @m23}, #{r @m24}, #{r @m31}, #{r @m32}, #{r @m33}, #{r @m34}, #{r @m41}, #{r @m42}, #{r @m43}, #{r @m44})" class Matrix constructor: (matrix) -> if matrix instanceof WebKitCSSMatrix @from matrix @define "x", get: -> @_x or 0 set: (value) -> @_x = value @define "y", get: -> @_y or 0 set: (value) -> @_y = value @define "z", get: -> @_z or 0 set: (value) -> @_z = value @define "scaleX", get: -> @_scaleX or 1 set: (value) -> @_scaleX = value @define "scaleY", get: -> @_scaleY or 1 set: (value) -> @_scaleY = value @define "scaleZ", get: -> @_scaleZ or 1 set: (value) -> @_scaleZ = value @define "scale", get: -> (@_scaleX + @_scaleY) / 2.0 set: (value) -> @_scaleX = value @_scaleY = value @define "rotateX", get: -> @_rotateX or 0 set: (value) -> @_rotateX = value @define "rotateY", get: -> @_rotateY or 0 set: (value) -> @_rotateY = value @define "rotateZ", get: -> @_rotateZ or 0 set: (value) -> @_rotateZ = value @define "rotate", get: -> @rotateZ set: (value) -> @rotateZ = value decompose: (m) -> result = {} result.translation = x: m.m41 y: m.m42 z: m.m43 result.scale = x: Math.sqrt(m.m11*m.m11 + m.m12*m.m12 + m.m13*m.m13) y: Math.sqrt(m.m21*m.m21 + m.m22*m.m22 + m.m23*m.m23) z: Math.sqrt(m.m31*m.m31 + m.m32*m.m32 + m.m33*m.m33) # http://blog.bwhiting.co.uk/?p=26 # Todo: There is still a bug here, where it sometimes rotates in reverse result.rotation = x: -Math.atan2(m.m32/result.scale.z, m.m33/result.scale.z) y: Math.asin(m.m31/result.scale.z) z: -Math.atan2(m.m21/result.scale.y, m.m11/result.scale.x) return result from: (matrix) -> v = @decompose matrix @x = v.translation.x @y = v.translation.y @scaleX = v.scale.x @scaleY = v.scale.y @scaleZ = v.scale.z @rotateX = v.rotation.x / Math.PI * 180 @rotateY = v.rotation.y / Math.PI * 180 @rotateZ = v.rotation.z / Math.PI * 180 matrix: -> m = new WebKitCSSMatrix() m = m.translate @_x, @_y, @_z m = m.rotate @_rotateX, 0, 0 m = m.rotate 0, @_rotateY, 0 m = m.rotate 0, 0, @_rotateZ m = m.scale @scaleX, @scaleY, @scaleZ return m set: (view) -> view._matrix = @ exports.Matrix = Matrix