• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[vJASS] BezierEasing

Been using this on my profession, and so I wrote this as a library on the map I am making on my spare time.

JASS:
library BezierEasing /* 1.0.0
*************************************************************************************
*
*   Build Cubic Bezier-based Easing functions
*
*   Instead of solving for the point on the cubic bezier curve, BezierEasing
*   solves for output Y where X is the input.
*
*   Useful for adjusting animation rate smoothness
*
*************************************************************************************
*
*   struct BezierEasing extends array
*
*       static method create takes real ax, real ay, real bx, real by returns thistype
*       - points (ax, ay) and (bx, by) are cubic bezier control points on 2D plane.
*       - cx = cubic(0, ax, bx, 1)
*       - cy = cubic(0, ay, by, 1)
*       method operator [] takes real t returns real
*       - real "t" is the given time progression whose value in [0..1] range
*
*************************************************************************************/
    globals
        /*
        *   Adjust precision of epsilon's value
        *   higher precision = lower performance
        *
        *   May cause infinite loop if the
        *   precision is too high.
        */
        private constant real EPSILON = 0.00001
    endglobals
  
    private function Abs takes real a returns real
        if(a < 0) then
            return -a
        endif
        return a
    endfunction
  
    private function Max takes real a, real b returns real
        if(a < b) then
            return b
        endif
        return a
    endfunction
  
    /*
    *   Float Equality Approximation
    *   Accuracy is influenced by EPSILON's value
    */
    private function Equals  takes real a, real b returns boolean
        return Abs(a - b) <= EPSILON*Max(1., Max(Abs(a), Abs(b)))
    endfunction
  
    private function Bezier3 takes real a, real b, real c, real d, real t returns real
        local real x = 1. - t
        return x*x*x*a + 3*x*x*t*b + 3*x*t*t*c + t*t*t*d
    endfunction
  
    private module Init
        private static method onInit takes nothing returns nothing
            call init()
        endmethod
    endmodule
  
    struct BezierEasing extends array
        private static thistype array r
      
        private real x1
        private real y1
        private real x2
        private real y2
      
        static method create takes real ax, real ay, real bx, real by returns thistype
            local thistype this = r[0]
            if(r[this] == 0) then
                set r[0] = this + 1
            else
                set r[0] = r[this]
            endif
            set r[this] = -1
          
            set x1 = ax
            set y1 = ay
            set x2 = bx
            set y2 = by
            return this
        endmethod
      
        method operator [] takes real t returns real
            /*
            *   Perform binary search for the equivalent points on curve
            *   by using the t factor of cubic beziers, where the input
            *   is equal to the bezier point's x, and the output is the
            *   point's y, respectively.
            */
            local real lo = 0.
            local real hi = 1.
            local real mid
            local real tx
            local real ty
          
            local real ax = x1
            local real ay = y1
            local real bx = x2
            local real by = y2
          
            /*
            *   Since bezier points lies within
            *   the [0, 1] bracket, just return
            *   the bound values.
            */
            if(Equals(t, 0.)) then
                return 0.
            elseif(Equals(t, 1.)) then
                return 1.
            endif
          
            /*
            *   Binary Search
            */
            loop
                set mid = (lo + hi)*0.5
                set tx = Bezier3(0, ax, bx, 1, mid)
                set ty = Bezier3(0, ay, by, 1, mid)
              
                if(Equals(t, tx))then
                    return ty
                elseif(t < tx) then
                    set hi = mid
                else
                    set lo = mid
                endif
            endloop
            return 0.
        endmethod
      
        method destroy takes nothing returns nothing
            if(r[this] == -1) then
                set r[this] = r[0]
                set r[0] = this
              
                set x1 = 0.
                set y1 = 0.
                set x2 = 0.
                set y2 = 0.
            endif
        endmethod
      
        private static method init takes nothing returns nothing
            set r[0] = 1
        endmethod
        implement Init
    endstruct
  
    struct BezierEase extends array
        readonly static BezierEasing inQuad
        readonly static BezierEasing outQuad
        readonly static BezierEasing inOutQuad
        readonly static BezierEasing inCubic
        readonly static BezierEasing outCubic
        readonly static BezierEasing inOutCubic
        readonly static BezierEasing inQuart
        readonly static BezierEasing outQuart
        readonly static BezierEasing inOutQuart
        readonly static BezierEasing inQuint
        readonly static BezierEasing outQuint
        readonly static BezierEasing inOutQuint
        readonly static BezierEasing inSine
        readonly static BezierEasing outSine
        readonly static BezierEasing inOutSine
        readonly static BezierEasing inBack
        readonly static BezierEasing outBack
        readonly static BezierEasing inOutBack
        readonly static BezierEasing inCirc
        readonly static BezierEasing outCirc
        readonly static BezierEasing inOutCirc
        readonly static BezierEasing inExpo
        readonly static BezierEasing outExpo
        readonly static BezierEasing inOutExpo
      
        private static method init takes nothing returns nothing
            set inSine = BezierEasing.create(0.47, 0, 0.745, 0.715)
            set outSine = BezierEasing.create(0.39, 0.575, 0.565, 1)
            set inOutSine = BezierEasing.create(0.445, 0.05, 0.55, 0.95)
            set inQuad = BezierEasing.create(0.55, 0.085, 0.68, 0.53)
            set outQuad = BezierEasing.create(0.25, 0.46, 0.45, 0.94)
            set inOutQuad = BezierEasing.create(0.455, 0.03, 0.515, 0.955)
            set inCubic = BezierEasing.create(0.55, 0.055, 0.675, 0.19)
            set outCubic = BezierEasing.create(0.215, 0.61, 0.355, 1)
            set inOutCubic = BezierEasing.create(0.645, 0.045, 0.355, 1)
            set inQuart = BezierEasing.create(0.895, 0.03, 0.685, 0.22)
            set outQuart = BezierEasing.create(0.165, 0.84, 0.44, 1)
            set inOutQuart = BezierEasing.create(0.77, 0, 0.175, 1)
            set inQuint = BezierEasing.create(0.755, 0.05, 0.855, 0.06)
            set outQuint = BezierEasing.create(0.23, 1, 0.32, 1)
            set inOutQuint = BezierEasing.create(0.86, 0, 0.07, 1)
            set inExpo = BezierEasing.create(0.95, 0.05, 0.795, 0.035)
            set outExpo = BezierEasing.create(0.19, 1, 0.22, 1)
            set inOutExpo = BezierEasing.create(1, 0, 0, 1)
            set inCirc = BezierEasing.create(0.6, 0.04, 0.98, 0.335)
            set outCirc = BezierEasing.create(0.075, 0.82, 0.165, 1)
            set inOutCirc = BezierEasing.create(0.785, 0.135, 0.15, 0.86)
            set inBack = BezierEasing.create(0.6, -0.28, 0.735, 0.045)
            set outBack = BezierEasing.create(0.175, 0.885, 0.32, 1.275)
            set inOutBack = BezierEasing.create(0.68, -0.55, 0.265, 1.55)
        endmethod
      
        implement Init
    endstruct
endlibrary

Sample:
JASS:
library Tester requires BezierEasing
  
    private struct T
        private static constant real TIMEOUT = 0.031250000
        private static constant real DURATION = 2.0
        private static constant real SPEED = 500.
      
        private static constant timer timer = CreateTimer()
        private thistype next
        private thistype prev
      
        private real t
        private real dx
        private real dy
        private real x
        private real y
        private unit u
      
        /*
        *   For the custom Easing function, Easing InOut Back
        */
        private static BezierEasing IOBack
      
        private static method update takes nothing returns nothing
            local thistype this = thistype(0).next
            local real tmp
            loop
                exitwhen 0 == this
                if(t > 0.) then
                    /*
                    *   Since easing functions have results within the [0, 1] bracket
                    *   we need to divide t to the max cap as a scaled progress.
                    *
                    *   subtract from 1 since t is in regression
                    */
                    set tmp = IOBack[1 - t/DURATION]
                    /*
                    *   Apply progress of distance
                    */
                    call SetUnitX(u, x + dx*tmp)
                    call SetUnitY(u, y + dy*tmp)
                  
                    set t = t - TIMEOUT
                else
                    set next.prev = prev
                    set prev.next = next
                    set dx = 0.
                    set dy = 0.
                    set t = 0.
                  
                    if(thistype(0).next == 0) then
                        call PauseTimer(timer)
                    endif
                  
                    call deallocate()
                endif
                set this = next
            endloop
        endmethod
      
        private static method E takes nothing returns boolean
            local thistype this = allocate()
            local unit tu = GetFilterUnit()
            local real angle = GetUnitFacing(tu)*bj_DEGTORAD
            /*
            *   Original unit coords
            *   Used for setting the polar offset
            */
            set x = GetUnitX(tu)
            set y = GetUnitY(tu)
            /*
            *   Direction Vectors
            */
            set dx = Cos(angle)*SPEED
            set dy = Sin(angle)*SPEED
            set u = tu
            set t = DURATION
          
            if(thistype(0).next == 0) then
                call TimerStart(timer, TIMEOUT, true, function thistype.update)
            endif
          
            set next = 0
            set prev = thistype(0).prev
            set thistype(0).prev.next = this
            set thistype(0).prev = this
            return false
        endmethod
        private static method init takes nothing returns nothing
            call TriggerRegisterPlayerUnitEvent(CreateTrigger(), Player(1), EVENT_PLAYER_UNIT_SELECTED, function thistype.E)
          
            /*
            *   Create the easing function
            *   The values provided creates
            *   the known easeInOutBack function
            */
            set IOBack = BezierEasing.create(0.68, -0.55, 0.265, 1.55)
        endmethod
        implement Init
    endstruct
endlibrary

If you want to visualize your bezier easings, you can use this web tool:
cubic-bezier.com


This library also includes preset BezierEasing functions provided by Easing Functions Cheat Sheet
 
Last edited:
A nice system to see. :)

I can at least suggest that some of the variables in the demo be retained in an add-on library. That way, one will not have to unnecessarily have to rediscover the magic numbers in the whole thing.
There is a struct I included in the script that allows you to access preset instances, but I forgot to include its documentation in the comment header.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
While I would prefer more automation to make the work less difficult on the user for implementation, this is an interesting system and provides a potentially-much-smoother user experience for, at the very least, the movement system contained in the demo script.

A demo map, in this instance, would be even better, but it is straightforward enough to reproduce. I just don't see many people building their own versions of this, and most likely they would end up just using your example code with small modifications.

If this were programmed in Lua, it would allow the user-facing code to be significantly shorter.

Nevertheless, this is probably as good as vJass can get, as it would otherwise be glaringly inefficient.

No more unnapproved anniversaries for this resource, Almia. This is now approved.
 
Thanks @Bribe, I never really expected this to be approved (I just lost hope lmao).

Anyways, about the Lua thinng, there has been some libraries for Lua when it comes to easing, however the formulas are hardcoded (so optimized) but you cannot make your own bezier graph. The API I provided here is closer to CSS animations.
 
Top