Class: WhirledPeas::Animator::Easing
- Inherits:
-
Object
- Object
- WhirledPeas::Animator::Easing
- Defined in:
- lib/whirled_peas/animator/easing.rb
Constant Summary collapse
- EASING =
Implementations of available ease-in functions. Ease-out and ease-in-out can all be derived from ease-in.
{ bezier: proc do |value| value /= 2 2 * value * value * (3 - 2 * value) end, linear: proc { |value| value }, parametric: proc do |value| value /= 2 squared = value * value 2 * squared / (2 * (squared - value) + 1) end, quadratic: proc do |value| value * value end }
- EFFECTS =
%i[in out in_out]
- INVERSE_MAX_ITERATIONS =
10
- INVERSE_DELTA =
0.0001
- INVERSE_EPSILON =
0.00001
Instance Method Summary collapse
- #ease(value) ⇒ Object
-
#initialize(easing = :linear, effect = :in_out) ⇒ Easing
constructor
A new instance of Easing.
- #invert(target) ⇒ Object
Constructor Details
#initialize(easing = :linear, effect = :in_out) ⇒ Easing
Returns a new instance of Easing.
28 29 30 31 32 33 34 35 36 37 38 39 |
# File 'lib/whirled_peas/animator/easing.rb', line 28 def initialize(easing=:linear, effect=:in_out) unless EASING.key?(easing) raise ArgumentError, "Invalid easing function: #{easing}, expecting one of #{EASING.keys.join(', ')}" end unless EFFECTS.include?(effect) raise ArgumentError, "Invalid effect: #{effect}, expecting one of #{EFFECTS.join(', ')}" end @easing = easing @effect = effect end |
Instance Method Details
#ease(value) ⇒ Object
41 42 43 44 45 46 47 48 49 50 |
# File 'lib/whirled_peas/animator/easing.rb', line 41 def ease(value) case effect when :in ease_in(value) when :out ease_out(value) else ease_in_out(value) end end |
#invert(target) ⇒ Object
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/whirled_peas/animator/easing.rb', line 52 def invert(target) ease_fn = case effect when :in proc { |v| ease_in(v) } when :out proc { |v| ease_out(v) } else proc { |v| ease_in_out(v) } end # Use Newton's method(!!) to find the inverse values of the easing function for the # specified target. Make an initial guess that is equal to the target and see how # far off the eased value is from the target. If we are close enough (as dictated by # INVERSE_EPSILON constant), then we return our guess. If we aren't close enough, then # find the slope of the eased line (approximated with a small step of INVERSE_DELTA # along the x-axis). The intersection of the slope and target will give us the value # of our next guess. # # Since most easing functions only vary slightly from the identity line (y = x), we # can typically get the eased guess within epsilon of the target in a few iterations, # however only iterate at most INVERSE_MAX_ITERATIONS times. # # ┃ ...... # ┃ ... # target -┃------------+ ... # ┃ /.|.. # ┃ ../. | # ┃ ... | | # ┃... | | # ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━ # | | # guess next guess # # IMPORTANT: This method only works well for monotonic easing functions # Pick the target as the first guess. For targets of 0 and 1 (and 0.5 for ease_in_out), # this guess will be the exact value that yields the target. For other values, the # eased guess will generally be close to the target. guess = target INVERSE_MAX_ITERATIONS.times do |i| eased_guess = ease_fn.call(guess) error = eased_guess - target break if error.abs < INVERSE_EPSILON next_eased_guess = ease_fn.call(guess + INVERSE_DELTA) delta_eased = next_eased_guess - eased_guess guess -= INVERSE_DELTA * error / delta_eased end guess end |