Skip to content

Latest commit

 

History

History

orx-fcurve

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 

orx-fcurve

FCurves are 1 dimensional function curves constructed from 2D bezier functions. They are often used to control a property over time. x values don't have any units, but they often represent a duration in seconds.

The language to express FCurves is similar to SVG's path language.

Relative command Absolute command Description
m y M y move the pen only in the y-direction
h x hold a value to draw a horizontal line
H x shift curve in time by x. Can only be used as first command.
l x,y L x,y line to (x, y)
q x0,y0,x,y Q x0,y0,x,y quadratic bezier to (x,y) and control-point (x0, y0)
c x0,y0,x1,y1,x,y C x0,y0,x1,y1,x,y cubic bezier to (x,y) and control-points (x0, y0), (x1, y1)
t x,y T x,y quadratic smooth to (x, y)
s x1,y1,x,y S x1,y1,x,y cubic smooth to (x,y) and control point (x1, y1)

Examples

This is an example of a flat horizontal FCurve:

// set the initial value to 0.5, hold that value for 1 seconds
val sizeCurve = fcurve("M0.5 h1")

Two horizontal segments at different heights:

// hold value 0.4 for half second, then hold value 0.6 for half second
val sizeCurve = fcurve("M0.4 h0.5 M0.6 h0.5")

Note that x values are relative, except for H where x is absolute. For y values, lower case commands are relative and upper case commands are absolute.

Line

We can interpolate from height 0.2 to 0.8 in 2 seconds like this:

// set initial value to 0.2, then interpolate linearly to value 0.8 over 2 seconds
val sizeCurve = fcurve("M0.2 L2,0.8")

Easily visualize the curves by calling the .contours() method. It will convert the curve into a list of ShapeContour instances which are easy to draw using drawer.contours():

val sizeCurve = fcurve("M0.2 L2,0.8")
drawer.contours(sizeCurve.contours())

Drawing scale

Note that the bounding box of this last curve will have a width of 2.0 pixels and a height under 1.0 pixel. In other words, almost invisible at its original scale. Since this is a common situation the .contours() method accepts a Vector2 scale argument to control the rendering size:

val sizeCurve = fcurve("M0.2 L2,0.8")
drawer.contours(sizeCurve.contours(Vector2(drawer.width / sizeCurve.duration, drawer.height.toDouble())))

Quadratic and Cubic curves

The Q and C commands (and their lowercase counterparts) allow us to draw quadratic (one control point) and cubic (two control points) curves.

// A quadratic curve that starts at zero, with a control point at 1,0 and ending at 1,1.
// That's a curve that stays near the 0.0 value and quickly raises to 1.0 at the end.
val easeOutCurve = fcurve("M0.0 Q1.0,0.0,1.0,1.0")

// A cubic s-shaped curve spending more time at both ends with a quick transition between them in the middle. 
val easeInOutCurve = fcurve("M0.0 C1,0,0,1,1,1")

Note that new lines, white space and commas are optional. They can help with readability:

M0 h10
c 3,10 5,-10 8,0.5
L 5,5

Smooth curves

The T and S commands (and their lowercase counterparts) allow us to create smooth curves, where one control point is automatically calculated to maintain the curve direction. The smooth curve commands require the presence of a previous segment, otherwise the program will not run.

// Hold the value 0.5 during 0.2 seconds
// then draw a smooth curve down to 0.5, up to 0.7 down to 0.3 and up to 0.7
val smoothCurveT = fcurve("M0.5 h0.2 T0.2,0.3 T0.2,0.7 T0.2,0.3 T0.2,0.7")

// Hold the value 0.5 during 0.2 seconds
// then draw a smooth with 4 repetitions where we move up slowly and down quickly
val smoothCurveS = fcurve("M0.5 h0.2 S0.2,0.0,0.2,0.5 S0.2,0.0,0.2,0.5 S0.2,0.0,0.2,0.5 S0.2,0.0,0.2,0.5")

Useful FCurve methods

Useful methods provided by FCurve:

  • smoothCurveS.reverse() returns a new reversed FCurve.
  • smoothCurveS.changeSpeed(0.5) returns a new FCurve scaled horizontally.
  • smoothCurveS.duration returns the duration of the FCurve.

Sampler

Drawing FCurves is useful for debugging, but their typical use is for animation. The FCurve sampler allows us to query values for the given time value like this:

fun main() {
    application {
        program {
            val xCurve = fcurve(
                """
                M320 H0.4 
                S2,0, 2,320 
                S2,0, 2,320 
                S2,0, 2,320 
                S2,0, 2,320 
                T0.6,320
                """.trimIndent()
            ).sampler() // <--
            extend {
                drawer.circle(
                    xCurve(seconds % 9.0), 
                    height * 0.5, 
                    20.0
                )
            }
        }
    }
}

In this example we used % 9.0 to loop the time between 0.0 and 9.0, repeating the animation over and over.

EFCurves

Extended Fcurves have an additional preprocessing step in which scalar expressions are evaluated.

Comments

EFCurves support comments using the # character.

M0 h10 c3,10,5,-10,8,0.5 # L5,5

M0 h10               # setup the initial y value and hold it for 10 units.
c3,10,5,-10,8,0.5    # relative cubic bezier curve
# and a final line-to
L5,5

Expressions

Expressions within curly brackets are evaluated using orx-expression-evaluator. Please refer to its documentation for details on the expression language used.

For example: M0 L{3 * 4},4 evaluates to M0 L12,4.

Repetitions

EFCurves add support for repetitions. Repetitions are expanded by replacing occurrences of (<text-to-repeat>)[<number-of-repetitions>] with number-of-repetitions copies of text-to-repeat.

For example:

  • M0 (h1 m1)[3] expands to M0 h1 m1 h1 m1 h1 m1
  • M0 (h1 m1)[0] expands to M0

Nested repetitions

Repetitions can be nested.

For example (M0 (h1 m1)[3])[2] expands to M0 h1 m1 h1 m1 h1 m1 M0 h1 m1 h1 m1 h1 m1.

Interaction between repetitions and expressions

M0 (H{it + 1} m1)[3] expands to M0 H1 m1 H2 m1 H3 m1

M0 (H{index + 1} m{it}){1.2, 1.3, 1.4} expands to M0 H1 m1.2 H2 m1.3 H3 m1.4

References

Demos

DemoFCurve01

source code

DemoFCurve01Kt

DemoFCurve02

source code

DemoFCurve02Kt

DemoFCurveSheet01

source code

DemoFCurveSheet01Kt

DemoMultiFCurve01

source code

DemoMultiFCurve01Kt