diff --git a/orx-fcurve/README.md b/orx-fcurve/README.md index 96b0ceef..d3b1e103 100644 --- a/orx-fcurve/README.md +++ b/orx-fcurve/README.md @@ -1,10 +1,10 @@ # 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. - -`x` values usually represent duration in seconds. +The language to express FCurves is similar to SVG's path language. | Relative command | Absolute command | Description | |---------------------|-----------------------|-------------------------------------------------------------| @@ -16,26 +16,147 @@ The language to express Fcurves is similar to SVG's path language. | `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) | -## Example Fcurves +## Examples -`M0 l5,10 q4,-10` or `M0 l5 10 q4 -10` +This is an example of a flat horizontal FCurve: -`M0 h10 c3,10,5,-10,8,0.5 L5,5` +```kotlin +// set the initial value to 0.5, hold that value for 1 seconds +val sizeCurve = fcurve("M0.5 H1") +``` -New lines and commas are optional. They can help with readability. +Two horizontal segments at different heights: + +```kotlin +// 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. + +The last example can be written with absolute times as: + +```kotlin +// hold value 0.4 until time 0.5, then hold value 0.6 until time 1.0 +val sizeCurve = fcurve("M0.4 H0.5 M0.6 H1.0") +``` + +### Line + +We can interpolate from height 0.2 to 0.8 in 2 seconds like this: + +```kotlin +// 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()`: + +```kotlin +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: + +```kotlin +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. + +```kotlin +// 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 -c3,10,5,-10,8,0.5 -L5,5 +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. + +```kotlin +// 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: + +```kotlin +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 -EFCurves are Fcurves with an additional preprocessing step in which scalar expressions are evaluated. +Extended Fcurves have a additional preprocessing step in which scalar expressions are evaluated. ## Comments -EFCurves add support for comments using the `#` character. +EFCurves support comments using the `#` character. `M0 h10 c3,10,5,-10,8,0.5 # L5,5` @@ -49,10 +170,10 @@ L5,5 ## Expressions -For example: `M0 L_3 * 4_,4` evaluates to `M0 L12,4`. +Expressions wrapped in underscore characters (`_`) are evaluated using `orx-expression-evaluator`. +Please refer to its [documentation](https://github.com/openrndr/orx/tree/master/orx-expression-evaluator) for details on the expression language used. -`orx-expression-evaluator` is used to evaluate the expressions, 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 @@ -74,16 +195,14 @@ For example `|M0 |h1 m1|[3]|[2]` expands to `M0 h1 m1 h1 m1 h1 m1 M0 h1 m1 h1 m1 `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 * https://x.com/ruby0x1/status/1258252352672247814 * https://blender.stackexchange.com/questions/52403/what-is-the-mathematical-basis-for-f-curves/52468#52468 * https://pomax.github.io/bezierinfo/#yforx + ## Demos ### DemoFCurve01