├── .gitignore ├── LICENSE ├── README.md ├── demo ├── easing-demo.lisp └── package-demo.lisp ├── easing-demo.asd ├── easing-test.asd ├── easing.asd ├── src ├── easing-single-float.lisp ├── easing.lisp └── package.lisp └── t ├── easing-test.lisp └── package-test.lisp /.gitignore: -------------------------------------------------------------------------------- 1 | #* 2 | *.DS_Store 3 | *.dx64fsl 4 | *.falsl 5 | *.fas 6 | *.fasl 7 | *.lx64fsl 8 | *.~lock* 9 | *~ 10 | .#* 11 | \#*\# 12 | scratchpad.lisp -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Danilo Vidovic (vydd) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Easing 2 | 3 | This library includes commonly used easing functions (defined by Robert Penner), implemented in Common Lisp. Included with the library is an example program (implemented using [Sketch](https://github.com/vydd/sketch)). 4 | 5 | ![Easing](http://i.imgur.com/JVSj2zn.png) 6 | 7 | ## Functions 8 | 9 | linear in-sine out-sine in-out-sine in-cubic out-cubic in-out-cubic in-quad out-quad in-out-quad in-quart out-quart in-out-quart in-quint out-quint in-out-quint in-exp out-exp in-out-exp in-circ out-circ in-out-circ in-elastic out-elastic in-out-elastic in-back out-back in-out-back in-bounce out-bounce in-out-bounce 10 | -------------------------------------------------------------------------------- /demo/easing-demo.lisp: -------------------------------------------------------------------------------- 1 | ;;;; easing-demo.lisp 2 | 3 | (in-package #:easing-demo) 4 | 5 | (ease:defeasing custom (x) 6 | (if (<= x 0.5) 7 | (/ (1+ (truncate (* x 10))) 10) 8 | (/ (1+ (truncate (* x 4))) 4))) 9 | 10 | (defparameter *easings* 11 | '(in-custom ease:linear 12 | ease:in-sine ease:out-sine ease:in-out-sine 13 | ease:in-cubic ease:out-cubic ease:in-out-cubic 14 | ease:in-quad ease:out-quad ease:in-out-quad 15 | ease:in-quart ease:out-quart ease:in-out-quart 16 | ease:in-quint ease:out-quint ease:in-out-quint 17 | ease:in-exp ease:out-exp ease:in-out-exp 18 | ease:in-circ ease:out-circ ease:in-out-circ 19 | ease:in-elastic ease:out-elastic ease:in-out-elastic 20 | ease:in-back ease:out-back ease:in-out-back 21 | ease:in-bounce ease:out-bounce ease:in-out-bounce)) 22 | 23 | (defsketch demo 24 | ((title "Easing demo") 25 | (width 1200) 26 | (height 600) 27 | (y-axis :up) 28 | (rows 4) 29 | (cols 8) 30 | (frames 0)) 31 | (background (gray 0.8)) 32 | (setf frames (mod (1+ frames) 100)) 33 | (with-pen (make-pen :stroke (gray 0)) 34 | (loop for fn in *easings* 35 | for x = 0 then (mod (1+ x) cols) 36 | for y = 0 then (+ y (if (zerop x) 1 0)) 37 | do (progn 38 | (with-identity-matrix 39 | (translate (* x (/ width cols)) (* y (/ height rows))) 40 | (with-font (make-font :size 12) 41 | (text (symbol-name fn) 14 10)) 42 | (scale (/ width cols) (/ height rows)) 43 | (translate 0.1 0.2) 44 | (scale 0.8 0.6) 45 | (draw-easing fn frames)))))) 46 | 47 | (defun draw-easing (fn frames) 48 | (with-pen (make-pen :fill (gray 1 0.8) :stroke (gray 0 0.2) :weight 0.01) 49 | (rect 0 0 1 1)) 50 | (with-pen (make-pen :stroke +black+ :weight 0.01) 51 | (apply #'polyline (loop for i upto 0.99 by 0.01 append (list i (funcall fn i))))) 52 | (with-pen (make-pen :fill +red+) 53 | (circle 1.0 (funcall fn (/ frames 100)) 0.04))) 54 | 55 | (defun demo () 56 | (make-instance 'demo)) 57 | -------------------------------------------------------------------------------- /demo/package-demo.lisp: -------------------------------------------------------------------------------- 1 | ;;;; package-demo.lisp 2 | 3 | (defpackage #:easing-demo 4 | (:use #:cl #:sketch) 5 | (:export :demo)) 6 | -------------------------------------------------------------------------------- /easing-demo.asd: -------------------------------------------------------------------------------- 1 | ;;;; easing-demo.asd 2 | 3 | (asdf:defsystem #:easing-demo 4 | :description "Easing functions demo." 5 | :author "Danilo Vidovic (vydd)" 6 | :license "MIT" 7 | :depends-on (#:easing #:sketch) 8 | :pathname "demo" 9 | :serial t 10 | :components ((:file "package-demo") 11 | (:file "easing-demo"))) 12 | -------------------------------------------------------------------------------- /easing-test.asd: -------------------------------------------------------------------------------- 1 | ;;;; easing-test.asd 2 | 3 | (asdf:defsystem #:easing-test 4 | :description "Easing functions test." 5 | :author "Danilo Vidovic (vydd)" 6 | :license "MIT" 7 | :depends-on (#:easing #:fiveam) 8 | :pathname "t" 9 | :serial t 10 | :components ((:file "package-test") 11 | (:file "easing-test"))) 12 | -------------------------------------------------------------------------------- /easing.asd: -------------------------------------------------------------------------------- 1 | ;;;; easing.asd 2 | 3 | (asdf:defsystem #:easing 4 | :description "Easing functions." 5 | :author "Danilo Vidovic (vydd)" 6 | :license "MIT" 7 | :depends-on (#:alexandria) 8 | :pathname "src" 9 | :serial t 10 | :components ((:file "package") 11 | (:file "easing") 12 | (:file "easing-single-float"))) 13 | -------------------------------------------------------------------------------- /src/easing-single-float.lisp: -------------------------------------------------------------------------------- 1 | ;;;; easing.lisp 2 | 3 | (in-package #:easing-f) 4 | 5 | (eval-when (:compile-toplevel :load-toplevel :execute) 6 | (defconstant pi-sf 3.141592653589793s0)) 7 | 8 | (defmacro defeasing-f (name args &body body) 9 | (let ((x (or (car args) 'x)) 10 | (arg-names (remove-if (lambda (x) (char= (aref (symbol-name x) 0) #\&)) 11 | (mapcar (lambda (x) (if (listp x) (first x) x)) 12 | args))) 13 | (declaim-args (mapcar (lambda (x) 14 | (if (and (symbolp x) 15 | (char= (aref (symbol-name x) 0) #\&)) 16 | x 17 | 'single-float)) 18 | args)) 19 | (ease-in (alexandria:symbolicate 'in- name)) 20 | (ease-out (alexandria:symbolicate 'out- name)) 21 | (ease-in-out (alexandria:symbolicate 'in-out- name))) 22 | `(progn 23 | ;; in 24 | (declaim (inline ,ease-in) 25 | (ftype (function ,declaim-args single-float) ,ease-in)) 26 | (defun ,ease-in ,args 27 | (declare (optimize (speed 3) (safety 0) (debug 0)) 28 | (single-float ,@arg-names) 29 | (inline + - / *)) 30 | (cond ((>= 0s0 ,x) 0s0) 31 | ((<= 1s0 ,x) 1s0) 32 | (t ,@body))) 33 | ;; out 34 | (declaim (inline ,ease-out) 35 | (ftype (function ,declaim-args single-float) ,ease-out)) 36 | (defun ,ease-out ,args 37 | (declare (optimize (speed 3) (safety 0) (debug 0)) 38 | (single-float ,@arg-names) 39 | (inline + - / *)) 40 | (cond ((>= 0s0 ,x) 0s0) 41 | ((<= 1s0 ,x) 1s0) 42 | (t (let ((,x (- 1s0 ,x))) 43 | (the single-float 44 | (+ 1s0 (- ,@body))))))) 45 | ;; in-out 46 | (declaim (inline ,ease-in-out) 47 | (ftype (function ,declaim-args single-float) ,ease-in-out)) 48 | (defun ,ease-in-out ,args 49 | (declare (optimize (speed 3) (safety 0) (debug 0)) 50 | (single-float ,@arg-names) 51 | (inline + - / *)) 52 | (cond ((>= 0s0 ,x) 0s0) 53 | ((<= 1s0 ,x) 1s0) 54 | (t (the single-float 55 | (if (<= ,x 0.5s0) 56 | (let ((,x (* 2s0 ,x))) 57 | (/ ,@body 2s0)) 58 | (let ((,x (- 1s0 (* 2s0 (- ,x 0.5s0))))) 59 | (+ 0.5s0 (/ (+ 1s0 (- ,@body)) 60 | 2s0))))))))))) 61 | 62 | ;; The float versions 63 | 64 | (declaim (inline linear) 65 | (ftype (function (single-float) single-float) linear)) 66 | (defun linear (x) 67 | (declare (optimize (speed 3) (safety 0) (debug 0)) 68 | (single-float x)) 69 | x) 70 | 71 | (defeasing-f sine (x) 72 | (- 1s0 (cos (* x (/ pi-sf 2s0))))) 73 | 74 | (defeasing-f quad (x) 75 | (* x x)) 76 | 77 | (defeasing-f cubic (x) 78 | (* x x x)) 79 | 80 | (defeasing-f quart (x) 81 | (expt x 4s0)) 82 | 83 | (defeasing-f quint (x) 84 | (expt x 5s0)) 85 | 86 | (defeasing-f exp (x) 87 | (expt 2s0 (* 10s0 (- x 1s0)))) 88 | 89 | (defeasing-f circ (x) 90 | (- (- (the single-float (sqrt (- 1s0 (* x x)))) 1s0))) 91 | 92 | (defeasing-f elastic (x &optional (p 0.3s0) (s 0s0 set-s)) 93 | (let ((s (if set-s s (* (asin 1s0) (* p #.(/ 1s0 (* 2s0 pi-sf))))))) 94 | (- (* (expt 2 (* 10 (- x 1s0))) (sin (/ (* (- (- x 1s0) s) (* 2 pi-sf)) p)))))) 95 | 96 | (defeasing-f back (x &optional (s 1.70158s0)) 97 | (* x x (- (* (+ 1s0 s) x) s))) 98 | 99 | (defeasing-f bounce (x &optional (c1 7.5625)) 100 | (let ((x (- 1s0 x))) 101 | (- 1s0 (cond ((< x (/ 1s0 2.75)) (* c1 x x)) 102 | ((< x (/ 2s0 2.75s0)) (let ((x (- x (/ 1.5s0 2.75s0)))) 103 | (+ 0.75s0 (* c1 x x)))) 104 | ((< x (/ 2.5s0 2.75s0)) (let ((x (- x (/ 2.25 2.75)))) 105 | (+ 0.9375s0 (* c1 x x)))) 106 | (t (let ((x (- x (/ 2.625s0 2.75s0)))) 107 | (+ 0.984375s0 (* c1 x x)))))))) 108 | -------------------------------------------------------------------------------- /src/easing.lisp: -------------------------------------------------------------------------------- 1 | ;;;; easing.lisp 2 | 3 | (in-package #:easing) 4 | 5 | (defmacro defeasing (name args &body body) 6 | (let ((x (or (car args) 'x))) 7 | `(progn 8 | (defun ,(alexandria:symbolicate 'in- name) ,args 9 | (cond ((>= 0 ,x) 0) 10 | ((<= 1 ,x) 1) 11 | (t ,@body))) 12 | (defun ,(alexandria:symbolicate 'out- name) ,args 13 | (cond ((>= 0 ,x) 0) 14 | ((<= 1 ,x) 1) 15 | (t (let ((,x (- 1 ,x))) 16 | (1+ (- ,@body)))))) 17 | (defun ,(alexandria:symbolicate 'in-out- name) ,args 18 | (cond ((>= 0 ,x) 0) 19 | ((<= 1 ,x) 1) 20 | (t (if (<= ,x 0.5) 21 | (let ((,x (* 2 ,x))) 22 | (/ ,@body 2)) 23 | (let ((,x (- 1 (* 2 (- ,x 0.5))))) 24 | (+ 0.5 (/ (+ 1 (- ,@body)) 2)))))))))) 25 | 26 | ;; Default easings are Robert Penner's easing functions 27 | 28 | (defun linear (x) x) 29 | 30 | (defeasing sine (x) 31 | (- 1 (cos (* x (/ PI 2))))) 32 | 33 | (defeasing quad (x) 34 | (* x x)) 35 | 36 | (defeasing cubic (x) 37 | (* x x x)) 38 | 39 | (defeasing quart (x) 40 | (expt x 4)) 41 | 42 | (defeasing quint (x) 43 | (expt x 5)) 44 | 45 | (defeasing exp (x) 46 | (expt 2 (* 10 (- x 1)))) 47 | 48 | (defeasing circ (x) 49 | (- (- (sqrt (- 1 (* x x))) 1))) 50 | 51 | (defeasing elastic (x &optional (p 0.3) (s nil)) 52 | (let ((s (or s (* (asin 1) (/ p (* 2 PI)))))) 53 | (- (* (expt 2 (* 10 (- x 1))) (sin (/ (* (- (- x 1) s) (* 2 PI)) p)))))) 54 | 55 | (defeasing back (x &optional (s 1.70158)) 56 | (* x x (- (* (+ 1 s) x) s))) 57 | 58 | (defeasing bounce (x &optional (c1 7.5625)) 59 | (let ((x (- 1 x))) 60 | (- 1 (cond ((< x (/ 1 2.75)) (* c1 x x)) 61 | ((< x (/ 2 2.75)) (let ((x (- x (/ 1.5 2.75)))) 62 | (+ 0.75 (* c1 x x)))) 63 | ((< x (/ 2.5 2.75)) (let ((x (- x (/ 2.25 2.75)))) 64 | (+ 0.9375 (* c1 x x)))) 65 | (t (let ((x (- x (/ 2.625 2.75)))) 66 | (+ 0.984375 (* c1 x x)))))))) 67 | -------------------------------------------------------------------------------- /src/package.lisp: -------------------------------------------------------------------------------- 1 | ;;;; package.lisp 2 | 3 | (defpackage #:easing 4 | (:use #:cl) 5 | (:nicknames :ease) 6 | (:export :defeasing :linear 7 | :in-sine :out-sine :in-out-sine 8 | :in-cubic :out-cubic :in-out-cubic 9 | :in-quad :out-quad :in-out-quad 10 | :in-quart :out-quart :in-out-quart 11 | :in-quint :out-quint :in-out-quint 12 | :in-exp :out-exp :in-out-exp 13 | :in-circ :out-circ :in-out-circ 14 | :in-elastic :out-elastic :in-out-elastic 15 | :in-back :out-back :in-out-back 16 | :in-bounce :out-bounce :in-out-bounce)) 17 | 18 | (defpackage #:easing-f 19 | (:use #:cl) 20 | (:nicknames :ease-f) 21 | (:export :defeasing :linear 22 | :in-sine :out-sine :in-out-sine 23 | :in-cubic :out-cubic :in-out-cubic 24 | :in-quad :out-quad :in-out-quad 25 | :in-quart :out-quart :in-out-quart 26 | :in-quint :out-quint :in-out-quint 27 | :in-exp :out-exp :in-out-exp 28 | :in-circ :out-circ :in-out-circ 29 | :in-elastic :out-elastic :in-out-elastic 30 | :in-back :out-back :in-out-back 31 | :in-bounce :out-bounce :in-out-bounce)) 32 | -------------------------------------------------------------------------------- /t/easing-test.lisp: -------------------------------------------------------------------------------- 1 | ;;;; easing-test.lisp 2 | 3 | (in-package #:easing-test) 4 | 5 | (eval-when (:compile-toplevel :load-toplevel :execute) 6 | (defparameter *easings* 7 | '(ease:linear 8 | ease:in-sine ease:out-sine ease:in-out-sine 9 | ease:in-cubic ease:out-cubic ease:in-out-cubic 10 | ease:in-quad ease:out-quad ease:in-out-quad 11 | ease:in-quart ease:out-quart ease:in-out-quart 12 | ease:in-quint ease:out-quint ease:in-out-quint 13 | ease:in-exp ease:out-exp ease:in-out-exp 14 | ease:in-circ ease:out-circ ease:in-out-circ 15 | ease:in-elastic ease:out-elastic ease:in-out-elastic 16 | ease:in-back ease:out-back ease:in-out-back 17 | ease:in-bounce ease:out-bounce ease:in-out-bounce))) 18 | 19 | (def-suite easing) 20 | (in-suite easing) 21 | 22 | (test defeasing 23 | (is-true 24 | (= (ease:in-circ 0.3) 25 | (- 1 (ease:out-circ 0.7)) 26 | (* 2 (ease:in-out-circ 0.15))))) 27 | 28 | (defun generate-function-tests () 29 | (mapcar (lambda (f) 30 | `(test ,(alexandria:symbolicate f) 31 | (is-true (= (,f 0.12) ,(funcall f 0.12))))) 32 | *easings*)) 33 | 34 | ;;; The following code can be generated by executing 35 | ;;; (generate-function-tests) 36 | 37 | (test in-sine (is-true (= (easing:in-sine 0.12) 0.017712748481835572d0))) 38 | (test out-sine (is-true (= (easing:out-sine 0.12) 0.18738132194319423d0))) 39 | (test in-out-sine (is-true (= (easing:in-out-sine 0.12) 0.03511175550489043d0))) 40 | (test in-cubic (is-true (= (easing:in-cubic 0.12) 0.0017279999))) 41 | (test out-cubic (is-true (= (easing:out-cubic 0.12) 0.318528))) 42 | (test in-out-cubic (is-true (= (easing:in-out-cubic 0.12) 0.0069119995))) 43 | (test in-quad (is-true (= (easing:in-quad 0.12) 0.0144))) 44 | (test out-quad (is-true (= (easing:out-quad 0.12) 0.2256))) 45 | (test in-out-quad (is-true (= (easing:in-out-quad 0.12) 0.0288))) 46 | (test in-quart (is-true (= (easing:in-quart 0.12) 2.0735999e-4))) 47 | (test out-quart (is-true (= (easing:out-quart 0.12) 0.40030468))) 48 | (test in-out-quart (is-true (= (easing:in-out-quart 0.12) 0.0016588799))) 49 | (test in-quint (is-true (= (easing:in-quint 0.12) 2.4883198e-5))) 50 | (test out-quint (is-true (= (easing:out-quint 0.12) 0.4722681))) 51 | (test in-out-quint (is-true (= (easing:in-out-quint 0.12) 3.9813117e-4))) 52 | (test in-exp (is-true (= (easing:in-exp 0.12) 0.0022435512))) 53 | (test out-exp (is-true (= (easing:out-exp 0.12) 0.56472474))) 54 | (test in-out-exp (is-true (= (easing:in-out-exp 0.12) 0.002577164))) 55 | (test in-circ (is-true (= (easing:in-circ 0.12) 0.0072261095))) 56 | (test out-circ (is-true (= (easing:out-circ 0.12) 0.47497368))) 57 | (test in-out-circ (is-true (= (easing:in-out-circ 0.12) 0.014613569))) 58 | (test in-elastic (is-true (= (easing:in-elastic 0.12) 0.0020495852816176195d0))) 59 | (test out-elastic (is-true (= (easing:out-elastic 0.12) 1.3521450910311152d0))) 60 | (test in-out-elastic (is-true (= (easing:in-out-elastic 0.12) -0.002520847183846066d0))) 61 | (test in-back (is-true (= (easing:in-back 0.12) -0.01983442))) 62 | (test out-back (is-true (= (easing:out-back 0.12) 0.4766525))) 63 | (test in-out-back (is-true (= (easing:in-out-back 0.12) -0.030332182))) 64 | (test in-bounce (is-true (= (easing:in-bounce 0.12) 0.033599973))) 65 | (test out-bounce (is-true (= (easing:out-bounce 0.12) 0.10890001))) 66 | (test in-out-bounce (is-true (= (easing:in-out-bounce 0.12) 0.018449992))) 67 | 68 | (def-suite easing-f) 69 | (in-suite easing-f) 70 | 71 | (eval-when (:compile-toplevel :load-toplevel :execute) 72 | (defun generate-optimization-tests-list () 73 | (mapcar (lambda (f) 74 | `(test ,(alexandria:symbolicate 'optimized- f) 75 | (is-true (= (truncate 76 | (* 10000 (,(intern (string (alexandria:symbolicate f)) :ease-f) 77 | 0.3s0))) 78 | (truncate 79 | (* 10000 (,(intern (string (alexandria:symbolicate f)) :ease) 80 | 0.3s0))))))) 81 | *easings*))) 82 | 83 | (defmacro generate-optimization-tests () 84 | `(progn 85 | ,@(generate-optimization-tests-list))) 86 | 87 | (generate-optimization-tests) 88 | 89 | (defun run-tests () 90 | (run! 'easing) 91 | (run! 'easing-f)) 92 | -------------------------------------------------------------------------------- /t/package-test.lisp: -------------------------------------------------------------------------------- 1 | ;;;; package-test.lisp 2 | 3 | (defpackage #:easing-test 4 | (:use #:cl #:fiveam) 5 | (:export :run-tests)) 6 | --------------------------------------------------------------------------------