Skip to content

Commit e70aaf3

Browse files
committed
[orx-fcurve] Add fcurve subproject
1 parent 82cf4e0 commit e70aaf3

File tree

10 files changed

+987
-0
lines changed

10 files changed

+987
-0
lines changed

orx-fcurve/README.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# orx-fcurve
2+
3+
FCurves are 1 dimensional function curves constructed from 2D bezier functions.
4+
5+
The language to express Fcurves is similar to SVG's path language.
6+
7+
| Command | Description |
8+
|-----------------------|-------------------------------------------------------------|
9+
| `m/M y` | move the pen only in the y-direction |
10+
| `h/H x` | draw a horizontal line |
11+
| `l/L x y` | line to (x, y) |
12+
| `q/Q x0 y0 x y` | quadratic bezier to (x,y) and control-point (x0, y0) |
13+
| `c/C x0 y0 x1 y1 x y` | cubic bezier to (x,y) and control-points (x0, y0), (x1, y1) |
14+
| `t/T x y` | quadratic smooth to (x, y) |
15+
| `s/S x1 y1 x y` | cubic smooth to (x,y) and control point (x1, y1) |
16+
17+
## Example Fcurves
18+
19+
`M0 l5,10 q4,-10` or `M0 l5 10 q4 -10`
20+
21+
`M0 h10 c3,10,5,-10,8,0.5 L5,5`
22+
23+
New lines are allowed, which can help in formatting the Fcurve
24+
```
25+
M0 h10
26+
c3,10,5,-10,8,0.5
27+
L5,5
28+
```
29+
30+
# EFCurves
31+
32+
EFCurves are Fcurves with an additional preprocessing step in which scalar expressions are evaluated.
33+
34+
## Comments
35+
36+
EFCurves add support for comments using the `#` character.
37+
38+
`M0 h10 c3,10,5,-10,8,0.5 # L5,5`
39+
40+
41+
```
42+
M0 h10 # setup the initial y value and hold it for 10 units.
43+
c3,10,5,-10,8,0.5 # relative cubic bezier curve
44+
# and a final line-to
45+
L5,5
46+
```
47+
48+
## Expressions
49+
50+
For example: `M0 L_3 * 4_,4` evaluates to `M0 L12,4`.
51+
52+
`orx-expression-evaluator` is used to evaluate the expressions, please refer to its
53+
documentation for details on the expression language used.
54+
55+
## Repetitions
56+
57+
EFCurves add support for repetitions. Repetitions are expanded by replacing
58+
occurrences of `|<text-to-repeat>|[<number-of-repetitions>]` with `number-of-repetitions` copies
59+
of `text-to-repeat`.
60+
61+
For example:
62+
* `M0 |h1 m1|[3]` expands to `M0 h1 m1 h1 m1 h1 m1`
63+
* `M0 |h1 m1|[0]` expands to `M0`
64+
65+
### Nested repetitions
66+
67+
Repetitions can be nested.
68+
69+
For example `|M0 |h1 m1|[3]|[2]` expands to `M0 h1 m1 h1 m1 h1 m1 M0 h1 m1 h1 m1 h1 m1`.
70+
71+
### Interaction between repetitions and expressions
72+
73+
`M0 |H_it + 1_ m1][3]` expands to `M0 H1 m1 H2 m1 H3 m1`
74+
75+
76+
77+
`M0 |H_index + 1_ m_it_]{1.2, 1.3, 1.4}` expands to `M0 H1 m1.2 H2 m1.3 H3 m1.4`
78+
79+
80+
81+
# References
82+
* https://x.com/ruby0x1/status/1258252352672247814
83+
* https://blender.stackexchange.com/questions/52403/what-is-the-mathematical-basis-for-f-curves/52468#52468
84+
* https://pomax.github.io/bezierinfo/#yforx

orx-fcurve/build.gradle.kts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
plugins {
2+
org.openrndr.extra.convention.`kotlin-multiplatform`
3+
id(libs.plugins.kotlin.serialization.get().pluginId)
4+
}
5+
6+
kotlin {
7+
sourceSets {
8+
@Suppress("UNUSED_VARIABLE")
9+
val commonMain by getting {
10+
dependencies {
11+
implementation(project(":orx-parameters"))
12+
implementation(project(":orx-expression-evaluator"))
13+
implementation(libs.openrndr.application)
14+
implementation(libs.openrndr.draw)
15+
implementation(libs.openrndr.filter)
16+
implementation(libs.kotlin.reflect)
17+
implementation(libs.kotlin.serialization.core)
18+
}
19+
}
20+
21+
@Suppress("UNUSED_VARIABLE")
22+
val jvmDemo by getting {
23+
dependencies {
24+
implementation(project(":orx-fcurve"))
25+
implementation(project(":orx-noise"))
26+
}
27+
}
28+
}
29+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package org.openrndr.extra.fcurve
2+
3+
import org.openrndr.extra.expressions.evaluateExpression
4+
5+
/**
6+
* expand efcurve to fcurve
7+
* @param ef an efcurve string
8+
* @param constants a map of constants that is passed to [evaluateExpression]
9+
*/
10+
fun efcurve(ef: String, constants: Map<String, Double> = emptyMap()): String {
11+
val expression = Regex("_([^_]+)_")
12+
val repetition = Regex("\\|([^|]+)\\|\\[([^\\[\\]]+)]")
13+
val list = Regex("\\|([^|]+)\\|\\{([^\\[\\]]+)}")
14+
15+
/**
16+
* perform comment substitution
17+
* (?m) enables multiline mode
18+
*/
19+
var curve = Regex("(?m)(#.*)$").replace(ef, "")
20+
21+
/**
22+
* Allow for nested repetitions and lists
23+
*/
24+
do {
25+
val referenceCurve = curve
26+
27+
/**
28+
* perform list expansion |text|{items}
29+
*/
30+
curve = list.replace(curve) { occ ->
31+
val listText = expression.replace(occ.groupValues[2]) { exp ->
32+
val expressionText = exp.groupValues[1]
33+
evaluateExpression(expressionText, constants)?.toString()
34+
?: error("parse error in repetition count expression '$expressionText'")
35+
}
36+
val listTokens = listText.split(Regex("[,;][\t\n ]*|[\t\n ]+"))
37+
val listItems = listTokens.filter { it.isNotEmpty() }
38+
.map { it.trim().toDoubleOrNull() ?: error("'$it' is not a number in $listTokens") }
39+
40+
listItems.mapIndexed { index, value ->
41+
expression.replace(occ.groupValues[1]) { exp ->
42+
val expressionText = exp.groupValues[1]
43+
evaluateExpression(
44+
exp.groupValues[1],
45+
constants + mapOf("index" to index.toDouble(), "it" to value)
46+
)?.toString() ?: error("parse error in repeated expression '$expressionText'")
47+
}
48+
}.joinToString(" ")
49+
}
50+
51+
/**
52+
* perform repetition expansion |text|[repeat-count]
53+
*/
54+
curve = repetition.replace(curve) { occ ->
55+
val repetitions = expression.replace(occ.groupValues[2]) { exp ->
56+
val expressionText = exp.groupValues[1]
57+
evaluateExpression(expressionText, constants)?.toInt()?.toString()
58+
?: error("parse error in repetition count expression '$expressionText'")
59+
}.toInt()
60+
List(repetitions) { repetition ->
61+
expression.replace(occ.groupValues[1]) { exp ->
62+
val expressionText = exp.groupValues[1]
63+
evaluateExpression(exp.groupValues[1], constants + mapOf("it" to repetition.toDouble()))?.toString()
64+
?: error("parse error in repeated expression '$expressionText'")
65+
}
66+
}.joinToString(" ")
67+
}
68+
} while (curve != referenceCurve)
69+
70+
/**
71+
* evaluate expression in expansion
72+
*/
73+
return (expression.replace(curve) { ef ->
74+
evaluateExpression(ef.groupValues[1], constants)?.toString() ?: error("parse error in '$curve")
75+
})
76+
}
77+
78+
fun main() {
79+
efcurve("""M1 |h5 m3|{
80+
|10.3 # toch wel handig zo'n comment
81+
|11.2
82+
|14.5
83+
|}
84+
""".trimMargin())
85+
86+
println(efcurve("|M0 |h4 m3|[2]|[5]"))
87+
88+
println(efcurve("""M0|h4 m_it_|{|_cos(it * PI * 0.5)_ |[4]}"""))
89+
}

0 commit comments

Comments
 (0)