diff options
Diffstat (limited to 'tween.scm')
| -rw-r--r-- | tween.scm | 104 |
1 files changed, 72 insertions, 32 deletions
@@ -86,7 +86,9 @@ ease-fn ;; number → number elapsed ;; ms since tween started (includes delay period) done? ;; boolean - callback) ;; (entity → unspecified) or #f; invoked once at completion + callback ;; (entity → unspecified) or #f; invoked once at completion + repeat ;; -1 = infinite, 0 = no more repeats, N = N repeats remaining + yoyo?) ;; swap starts/ends on each repeat cycle ;; ── Public API ──────────────────────────────────────────────────────────── @@ -96,13 +98,15 @@ ;; props: alist of (keyword . target-number), e.g. ((#:x . 200) (#:y . 40)) (define (make-tween entity #!key props (duration 500) (delay 0) (ease 'linear) - (on-complete #f)) + (on-complete #f) (repeat 0) (yoyo? #f)) (unless (and (integer? duration) (> duration 0)) (error "make-tween: duration must be a positive integer (ms)" duration)) (unless (and (integer? delay) (>= delay 0)) (error "make-tween: delay must be a non-negative integer (ms)" delay)) (unless (pair? props) (error "make-tween: props must be a non-empty alist" props)) + (unless (and (integer? repeat) (>= repeat -1)) + (error "make-tween: repeat must be -1 (infinite) or a non-negative integer" repeat)) (let ((ease-fn (ease-resolve ease)) (starts (map (lambda (p) (let ((k (car p))) @@ -117,7 +121,9 @@ ease-fn: ease-fn elapsed: 0 done?: #f - callback: on-complete))) + callback: on-complete + repeat: repeat + yoyo?: yoyo?))) ;; Linear interpolation with eased factor u in [0,1] (define (lerp a b u) @@ -132,37 +138,71 @@ entity ends)) + (define (tw-with-elapsed tw elapsed) + (make-tw starts: (tw-starts tw) ends: (tw-ends tw) + duration: (tw-duration tw) delay: (tw-delay tw) + ease-fn: (tw-ease-fn tw) elapsed: elapsed + done?: #f callback: (tw-callback tw) + repeat: (tw-repeat tw) yoyo?: (tw-yoyo? tw))) + + (define (tw-finish tw elapsed) + (make-tw starts: (tw-starts tw) ends: (tw-ends tw) + duration: (tw-duration tw) delay: (tw-delay tw) + ease-fn: (tw-ease-fn tw) elapsed: elapsed + done?: #t callback: #f + repeat: 0 yoyo?: (tw-yoyo? tw))) + + (define (tw-next-cycle tw overflow) + (let* ((yoyo? (tw-yoyo? tw)) + (starts (tw-starts tw)) + (ends (tw-ends tw))) + (make-tw starts: (if yoyo? ends starts) + ends: (if yoyo? starts ends) + duration: (tw-duration tw) delay: 0 + ease-fn: (tw-ease-fn tw) elapsed: overflow + done?: #f callback: (tw-callback tw) + repeat: (let ((r (tw-repeat tw))) (if (= r -1) -1 (- r 1))) + yoyo?: yoyo?))) + + (define (tween-complete tw entity elapsed) + (let ((final (apply-props entity (tw-starts tw) (tw-ends tw) 1.0))) + (if (zero? (tw-repeat tw)) + (begin + (when (tw-callback tw) ((tw-callback tw) final)) + (values (tw-finish tw elapsed) final)) + (let ((overflow (- (- elapsed (tw-delay tw)) (tw-duration tw)))) + (values (tw-next-cycle tw overflow) final))))) + + (define (tween-interpolate tw entity elapsed) + (let* ((t0 (- elapsed (tw-delay tw))) + (u (min 1.0 (max 0.0 (/ t0 (tw-duration tw))))) + (eased ((tw-ease-fn tw) u)) + (ent2 (apply-props entity (tw-starts tw) (tw-ends tw) eased))) + (if (>= u 1.0) + (tween-complete tw entity elapsed) + (values (tw-with-elapsed tw elapsed) ent2)))) + (define (tween-step tw entity dt) (unless (tw? tw) (error "tween-step: expected tween struct" tw)) (if (tw-done? tw) (values tw entity) - (let* ((elapsed (+ (tw-elapsed tw) dt)) - (delay (tw-delay tw)) - (duration (tw-duration tw)) - (ease-fn (tw-ease-fn tw)) - (starts (tw-starts tw)) - (ends (tw-ends tw))) - (cond ((< elapsed delay) - (values (make-tw starts: starts ends: ends duration: duration - delay: delay ease-fn: ease-fn - elapsed: elapsed done?: #f callback: (tw-callback tw)) - entity)) - (else - (let* ((t0 (- elapsed delay)) - (u-raw (/ t0 duration)) - (u (min 1.0 (max 0.0 u-raw))) - (eased (ease-fn u)) - (ent2 (apply-props entity starts ends eased))) - (if (>= u 1.0) - (let* ((final (apply-props entity starts ends 1.0)) - (cb (tw-callback tw)) - (_ (when cb (cb final))) - (tw2 (make-tw starts: starts ends: ends duration: duration - delay: delay ease-fn: ease-fn - elapsed: elapsed done?: #t callback: #f))) - (values tw2 final)) - (values (make-tw starts: starts ends: ends duration: duration - delay: delay ease-fn: ease-fn - elapsed: elapsed done?: #f callback: (tw-callback tw)) - ent2)))))))) + (let ((elapsed (+ (tw-elapsed tw) dt))) + (if (< elapsed (tw-delay tw)) + (values (tw-with-elapsed tw elapsed) entity) + (tween-interpolate tw entity elapsed))))) + + ;; ── Pipeline step ────────────────────────────────────────────────────────── + ;; Auto-advance #:tween on an entity. Call from update: as part of the + ;; per-entity pipeline, e.g. (step-tweens entity dt). Removes #:tween + ;; when the tween finishes. + + (define-pipeline (step-tweens tweens) (entity dt) + (let ((tw (entity-ref entity #:tween #f))) + (if (not tw) + entity + (receive (tw2 ent2) (tween-step tw entity dt) + (if (tween-finished? tw2) + (entity-set ent2 #:tween #f) + (entity-set ent2 #:tween tw2)))))) + ) ;; end module |
