diff --git a/src/Ease.elm b/src/Ease.elm index 557563d..c99a75d 100644 --- a/src/Ease.elm +++ b/src/Ease.elm @@ -73,28 +73,87 @@ linear = {-| A cubic bezier function using 4 parameters: x and y position of first control point, and x and y position of second control point. -See [here](http://greweb.me/glsl-transition/example/ "glsl-transitions") for examples or [here](http://cubic-bezier.com/ "tester") to test. +See [here](http://cubic-bezier.com/ "tester") to test. -} bezier : Float -> Float -> Float -> Float -> Easing -bezier x1 y1 x2 y2 time = +bezier x1 y1 x2 y2 = let - lerp from to v = - from + (to - from) * v + f = + bezierPoint x1 y1 x2 y2 + + numSteps = + -- performance/quality tradeoff, 8 steps should be good enough in most cases + 8 + in + \time -> + bezierHelper numSteps f time ( 0, 1 ) - pair interpolate ( a0, b0 ) ( a1, b1 ) v = - ( interpolate a0 a1 v, interpolate b0 b1 v ) - casteljau ps = - case ps of - [ ( x, y ) ] -> - y +{-| Use binary search to find such `tMid` that the `x` coordinate of `f tMid` is as close to `t` as +possible, then return the `y` coordinate. +-} +bezierHelper : Int -> (Float -> ( Float, Float )) -> Float -> ( Float, Float ) -> Float +bezierHelper steps f t ( tMin, tMax ) = + let + tMid = + (tMin + tMax) / 2 - xs -> - List.map2 (\x y -> pair lerp x y time) xs (Maybe.withDefault [] (List.tail xs)) - |> casteljau + ( x, y ) = + f tMid in - casteljau [ ( 0, 0 ), ( x1, y1 ), ( x2, y2 ), ( 1, 1 ) ] + if steps == 0 then + y + + else + let + newRange = + if x < t then + ( tMid, tMax ) + + else + ( tMin, tMid ) + in + bezierHelper (steps - 1) f t newRange + + +{-| Calculates the (x, y) coordinates of a point of a [ (0, 0), a, b, (1, 1) ] cubic bezier for +given time in <0, 1> range. + +Based on [this gif on wikipedia](https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Higher-order_curves) + +-} +bezierPoint : Float -> Float -> Float -> Float -> Float -> ( Float, Float ) +bezierPoint xa ya xb yb time = + let + q0 = + interpolate2d ( 0, 0 ) ( xa, ya ) time + + q1 = + interpolate2d ( xa, ya ) ( xb, yb ) time + + q2 = + interpolate2d ( xb, yb ) ( 1, 1 ) time + + r0 = + interpolate2d q0 q1 time + + r1 = + interpolate2d q1 q2 time + + b = + interpolate2d r0 r1 time + in + b + + +{-| Return a point on line segment (a, b) for given t in <0, 1> range +-} +interpolate2d : ( Float, Float ) -> ( Float, Float ) -> Float -> ( Float, Float ) +interpolate2d ( xa, ya ) ( xb, yb ) t = + ( xa + t * (xb - xa) + , ya + t * (yb - ya) + ) {-| -} diff --git a/test/Test.elm b/test/Test.elm index 2a5363e..acb9f77 100644 --- a/test/Test.elm +++ b/test/Test.elm @@ -100,8 +100,8 @@ main : Svg a main = Svg.svg [ SvgAttributes.width "850" - , SvgAttributes.height "650" - , SvgAttributes.viewBox "-10 -60 840 590" + , SvgAttributes.height "760" + , SvgAttributes.viewBox "-10 -60 840 700" , SvgAttributes.style "display:block;margin:auto;" ] (title :: List.indexedMap plot easingFunctions) @@ -138,4 +138,10 @@ easingFunctions = , ( inBounce, "inBounce" ) , ( outBounce, "outBounce" ) , ( inOutBounce, "inOutBounce" ) + , ( bezier 1 0 0 1, "bezier 1" ) + , ( bezier 1 0 0 0, "bezier 2" ) + , ( bezier 0 0 0 1, "bezier 3" ) + , ( bezier 0 1 1 0, "bezier 4" ) + , ( bezier 1 1 1 0, "bezier 5" ) + , ( bezier 1 1 0 1, "bezier 6" ) ] diff --git a/test/elm.json b/test/elm.json index c6c76d9..282157e 100644 --- a/test/elm.json +++ b/test/elm.json @@ -4,7 +4,7 @@ ".", "../src" ], - "elm-version": "0.19.0", + "elm-version": "0.19.1", "dependencies": { "direct": { "elm/core": "1.0.0",