[orx-fcurve] Add mfcurve, FCurve.min, FCurve.max, DemoFCurveSheet01.kt
This commit is contained in:
@@ -23,7 +23,7 @@ This is an example of a flat horizontal FCurve:
|
|||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
// set the initial value to 0.5, hold that value for 1 seconds
|
// set the initial value to 0.5, hold that value for 1 seconds
|
||||||
val sizeCurve = fcurve("M0.5 H1")
|
val sizeCurve = fcurve("M0.5 h1")
|
||||||
```
|
```
|
||||||
|
|
||||||
Two horizontal segments at different heights:
|
Two horizontal segments at different heights:
|
||||||
@@ -36,12 +36,6 @@ val sizeCurve = fcurve("M0.4 h0.5 M0.6 h0.5")
|
|||||||
Note that `x` values are relative, except for `H` where `x` is absolute.
|
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.
|
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
|
### Line
|
||||||
|
|
||||||
@@ -103,11 +97,11 @@ commands require the presence of a previous segment, otherwise the program will
|
|||||||
```kotlin
|
```kotlin
|
||||||
// Hold the value 0.5 during 0.2 seconds
|
// 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
|
// 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")
|
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
|
// Hold the value 0.5 during 0.2 seconds
|
||||||
// then draw a smooth with 4 repetitions where we move up slowly and down quickly
|
// 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")
|
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 FCurve methods
|
||||||
|
|||||||
@@ -3,6 +3,34 @@ package org.openrndr.extra.fcurve
|
|||||||
import org.openrndr.extra.expressions.FunctionExtensions
|
import org.openrndr.extra.expressions.FunctionExtensions
|
||||||
import org.openrndr.extra.expressions.evaluateExpression
|
import org.openrndr.extra.expressions.evaluateExpression
|
||||||
|
|
||||||
|
/**
|
||||||
|
* expand mfcurve to fcurve
|
||||||
|
*/
|
||||||
|
fun mfcurve(
|
||||||
|
mf: String,
|
||||||
|
constants: Map<String, Double> = emptyMap(),
|
||||||
|
functions: FunctionExtensions = FunctionExtensions.EMPTY
|
||||||
|
): String {
|
||||||
|
/**
|
||||||
|
* perform comment substitution
|
||||||
|
*/
|
||||||
|
val stripped = Regex("(#.*)$", RegexOption.MULTILINE).replace(mf, "")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* detect modifier
|
||||||
|
*/
|
||||||
|
val parts = stripped.split("|")
|
||||||
|
|
||||||
|
val efcurve = parts.getOrElse(0) { "" }
|
||||||
|
val modifier = parts.getOrNull(1)
|
||||||
|
|
||||||
|
var fcurve = efcurve(efcurve, constants, functions)
|
||||||
|
if (modifier != null) {
|
||||||
|
fcurve = modifyFCurve(fcurve, modifier, constants, functions)
|
||||||
|
}
|
||||||
|
return fcurve
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* expand efcurve to fcurve
|
* expand efcurve to fcurve
|
||||||
* @param ef an efcurve string
|
* @param ef an efcurve string
|
||||||
@@ -12,7 +40,6 @@ fun efcurve(
|
|||||||
ef: String,
|
ef: String,
|
||||||
constants: Map<String, Double> = emptyMap(),
|
constants: Map<String, Double> = emptyMap(),
|
||||||
functions: FunctionExtensions = FunctionExtensions.EMPTY
|
functions: FunctionExtensions = FunctionExtensions.EMPTY
|
||||||
|
|
||||||
): String {
|
): String {
|
||||||
// IntelliJ falsely reports a redundant escape character. the escape character is required when running the regular
|
// IntelliJ falsely reports a redundant escape character. the escape character is required when running the regular
|
||||||
// expression on a javascript target. Removing the escape character will result in a `Lone quantifier brackets`
|
// expression on a javascript target. Removing the escape character will result in a `Lone quantifier brackets`
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import org.openrndr.math.Vector2
|
|||||||
import org.openrndr.math.transforms.buildTransform
|
import org.openrndr.math.transforms.buildTransform
|
||||||
import org.openrndr.shape.Segment2D
|
import org.openrndr.shape.Segment2D
|
||||||
import org.openrndr.shape.ShapeContour
|
import org.openrndr.shape.ShapeContour
|
||||||
|
import org.openrndr.shape.bounds
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -91,6 +92,19 @@ data class FCurve(val segments: List<Segment2D>) {
|
|||||||
return FCurve(segments.map { it.reverse.transform(t) })
|
return FCurve(segments.map { it.reverse.transform(t) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val bounds by lazy {
|
||||||
|
segments.map { it.bounds }.bounds
|
||||||
|
}
|
||||||
|
val min: Double
|
||||||
|
get() {
|
||||||
|
if (segments.isEmpty()) return 0.0 else return bounds.position(0.0, 0.0).y
|
||||||
|
}
|
||||||
|
|
||||||
|
val max: Double
|
||||||
|
get() {
|
||||||
|
if (segments.isEmpty()) return 0.0 else return bounds.position(1.0, 1.0).y
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change the duration of the Fcurve
|
* Change the duration of the Fcurve
|
||||||
*/
|
*/
|
||||||
@@ -146,9 +160,8 @@ data class FCurve(val segments: List<Segment2D>) {
|
|||||||
0.0
|
0.0
|
||||||
} else {
|
} else {
|
||||||
segments.first().start.x
|
segments.first().start.x
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The unitless end position of the Fcurve
|
* The unitless end position of the Fcurve
|
||||||
@@ -208,24 +221,28 @@ data class FCurve(val segments: List<Segment2D>) {
|
|||||||
/**
|
/**
|
||||||
* Return a list of contours that can be used to visualize the Fcurve
|
* Return a list of contours that can be used to visualize the Fcurve
|
||||||
*/
|
*/
|
||||||
fun contours(scale: Vector2 = Vector2.ONE): List<ShapeContour> {
|
fun contours(scale: Vector2 = Vector2(1.0, -1.0), offset: Vector2 = Vector2.ZERO): List<ShapeContour> {
|
||||||
var active = mutableListOf<Segment2D>()
|
var active = mutableListOf<Segment2D>()
|
||||||
val result = mutableListOf<ShapeContour>()
|
val result = mutableListOf<ShapeContour>()
|
||||||
|
|
||||||
for (segment in segments) {
|
for (segment in segments) {
|
||||||
if (active.isEmpty()) {
|
|
||||||
active.add(segment.transform(buildTransform {
|
val tsegment = segment.transform(
|
||||||
|
buildTransform {
|
||||||
|
translate(offset)
|
||||||
scale(scale.x, scale.y)
|
scale(scale.x, scale.y)
|
||||||
}))
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (active.isEmpty()) {
|
||||||
|
active.add(tsegment)
|
||||||
} else {
|
} else {
|
||||||
val dy = abs(active.last().end.y - segment.start.y)
|
val dy = abs(active.last().end.y - tsegment.start.y)
|
||||||
if (dy > 1E-3) {
|
if (dy > 1E-3) {
|
||||||
result.add(ShapeContour.fromSegments(active, false))
|
result.add(ShapeContour.fromSegments(active, false))
|
||||||
active = mutableListOf()
|
active = mutableListOf()
|
||||||
}
|
}
|
||||||
active.add(segment.transform(buildTransform {
|
active.add(tsegment)
|
||||||
scale(scale.x, scale.y)
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (active.isNotEmpty()) {
|
if (active.isNotEmpty()) {
|
||||||
@@ -293,21 +310,28 @@ class FCurveBuilder {
|
|||||||
|
|
||||||
fun continueTo(x: Double, y: Double, relative: Boolean = false) {
|
fun continueTo(x: Double, y: Double, relative: Boolean = false) {
|
||||||
val r = if (relative) 1.0 else 0.0
|
val r = if (relative) 1.0 else 0.0
|
||||||
val lastSegment = segments.last()
|
|
||||||
val lastDuration = lastSegment.end.x - lastSegment.start.x
|
if (segments.isNotEmpty()) {
|
||||||
val outTangent = segments.last().cubic.control.last()
|
val lastSegment = segments.last()
|
||||||
val outPos = lastSegment.end
|
val lastDuration = lastSegment.end.x - lastSegment.start.x
|
||||||
val dx = outPos.x - outTangent.x
|
val outTangent = if (segments.last().linear) lastSegment.end else segments.last().control.last()
|
||||||
val dy = outPos.y - outTangent.y
|
val outPos = lastSegment.end
|
||||||
val ts = x / lastDuration
|
val d = outPos - outTangent
|
||||||
segments.add(
|
//val dn = d.normalized
|
||||||
Segment2D(
|
val ts = 1.0// x / lastDuration
|
||||||
cursor,
|
segments.add(
|
||||||
Vector2(cursor.x + dx * ts, cursor.y + dy),
|
Segment2D(
|
||||||
Vector2(cursor.x + x * 0.66, cursor.y * r + y),
|
cursor,
|
||||||
Vector2(cursor.x + x, cursor.y * r + y)
|
cursor + d * ts,
|
||||||
).scaleTangents()
|
Vector2(cursor.x + x, cursor.y * r + y)
|
||||||
)
|
).scaleTangents()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
segments.add(
|
||||||
|
Segment2D(cursor,
|
||||||
|
Vector2(cursor.x + x, cursor.y * r + y)).quadratic
|
||||||
|
)
|
||||||
|
}
|
||||||
cursor = Vector2(cursor.x + x, cursor.y * r + y)
|
cursor = Vector2(cursor.x + x, cursor.y * r + y)
|
||||||
path += "${if (relative) "t" else "T"}$x,$y"
|
path += "${if (relative) "t" else "T"}$x,$y"
|
||||||
}
|
}
|
||||||
@@ -334,7 +358,7 @@ class FCurveBuilder {
|
|||||||
if (relative) {
|
if (relative) {
|
||||||
lineTo(x, cursor.y)
|
lineTo(x, cursor.y)
|
||||||
} else {
|
} else {
|
||||||
require(segments.isEmpty()) { "absolute hold (H $x) is only allowed when used as first command"}
|
require(segments.isEmpty()) { "absolute hold (H $x) is only allowed when used as first command" }
|
||||||
cursor = cursor.copy(x = x)
|
cursor = cursor.copy(x = x)
|
||||||
}
|
}
|
||||||
path += "h$x"
|
path += "h$x"
|
||||||
@@ -361,11 +385,12 @@ fun fcurve(builder: FCurveBuilder.() -> Unit): FCurve {
|
|||||||
/**
|
/**
|
||||||
* Split an Fcurve string in to command parts
|
* Split an Fcurve string in to command parts
|
||||||
*/
|
*/
|
||||||
private fun fCurveCommands(d: String): List<String> {
|
fun fCurveCommands(d: String): List<String> {
|
||||||
val svgCommands = "mMlLqQsStTcChH"
|
val svgCommands = "mMlLqQsStTcChH"
|
||||||
val number = "0-9.\\-E%"
|
val number = "0-9.\\-E%"
|
||||||
|
|
||||||
return d.split(Regex("(?:[\t ,]|\r?\n)+|(?<=[$svgCommands])(?=[$number])|(?<=[$number])(?=[$svgCommands])")).filter { it.isNotBlank() }
|
return d.split(Regex("(?:[\t ,]|\r?\n)+|(?<=[$svgCommands])(?=[$number])|(?<=[$number])(?=[$svgCommands])"))
|
||||||
|
.filter { it.isNotBlank() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun evaluateFCurveCommands(parts: List<String>): FCurve {
|
private fun evaluateFCurveCommands(parts: List<String>): FCurve {
|
||||||
@@ -418,8 +443,8 @@ private fun evaluateFCurveCommands(parts: List<String>): FCurve {
|
|||||||
*/
|
*/
|
||||||
"l", "L" -> {
|
"l", "L" -> {
|
||||||
val isRelative = command.first().isLowerCase()
|
val isRelative = command.first().isLowerCase()
|
||||||
val x = popNumberOrPercentageOf { dx() }
|
val x = popNumber()
|
||||||
val y = popNumberOrPercentageOf { cursor.y }
|
val y = popNumber()
|
||||||
lineTo(x, y, isRelative)
|
lineTo(x, y, isRelative)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -432,8 +457,8 @@ private fun evaluateFCurveCommands(parts: List<String>): FCurve {
|
|||||||
val tcy0 = popToken()
|
val tcy0 = popToken()
|
||||||
val tcx1 = popToken()
|
val tcx1 = popToken()
|
||||||
val tcy1 = popToken()
|
val tcy1 = popToken()
|
||||||
val x = popNumberOrPercentageOf { dx() }
|
val x = popNumber()
|
||||||
val y = popNumberOrPercentageOf { cursor.y }
|
val y = popNumber()
|
||||||
val x0 = tcx0.numberOrPercentageOf { x }
|
val x0 = tcx0.numberOrPercentageOf { x }
|
||||||
val y0 = tcy0.numberOrFactorOf { factor ->
|
val y0 = tcy0.numberOrFactorOf { factor ->
|
||||||
if (relative) y * factor else cursor.y * (1.0 - factor).coerceAtLeast(0.0) + y * factor
|
if (relative) y * factor else cursor.y * (1.0 - factor).coerceAtLeast(0.0) + y * factor
|
||||||
@@ -476,8 +501,8 @@ private fun evaluateFCurveCommands(parts: List<String>): FCurve {
|
|||||||
val relative = command.first().isLowerCase()
|
val relative = command.first().isLowerCase()
|
||||||
val tcx0 = popToken()
|
val tcx0 = popToken()
|
||||||
val tcy0 = popToken()
|
val tcy0 = popToken()
|
||||||
val x = popNumberOrPercentageOf { dx() }
|
val x = popNumber()
|
||||||
val y = popNumberOrPercentageOf { cursor.y }
|
val y = popNumber()
|
||||||
val x1 = tcx0.numberOrPercentageOf { x }
|
val x1 = tcx0.numberOrPercentageOf { x }
|
||||||
val y1 = tcy0.numberOrPercentageOf { y }
|
val y1 = tcy0.numberOrPercentageOf { y }
|
||||||
continueTo(x1, y1, x, y, relative)
|
continueTo(x1, y1, x, y, relative)
|
||||||
@@ -488,8 +513,8 @@ private fun evaluateFCurveCommands(parts: List<String>): FCurve {
|
|||||||
*/
|
*/
|
||||||
"t", "T" -> {
|
"t", "T" -> {
|
||||||
val isRelative = command.first().isLowerCase()
|
val isRelative = command.first().isLowerCase()
|
||||||
val x = popNumberOrPercentageOf { dx() }
|
val x = popNumber()
|
||||||
val y = popNumberOrPercentageOf { cursor.y }
|
val y = popNumber()
|
||||||
continueTo(x, y, isRelative)
|
continueTo(x, y, isRelative)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
200
orx-fcurve/src/commonMain/kotlin/FCurveModifier.kt
Normal file
200
orx-fcurve/src/commonMain/kotlin/FCurveModifier.kt
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
package org.openrndr.extra.fcurve
|
||||||
|
|
||||||
|
import org.openrndr.extra.expressions.FunctionExtensions
|
||||||
|
import org.openrndr.extra.expressions.compileFunction1
|
||||||
|
import org.openrndr.math.Vector2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modify an [fcurve] string using a [modifiers] string
|
||||||
|
*/
|
||||||
|
fun modifyFCurve(
|
||||||
|
fcurve: String,
|
||||||
|
modifiers: String,
|
||||||
|
constants: Map<String, Double> = emptyMap(),
|
||||||
|
functions: FunctionExtensions = FunctionExtensions.EMPTY
|
||||||
|
): String {
|
||||||
|
val parts = fCurveCommands(fcurve)
|
||||||
|
val mparts = parts.reversed().toMutableList()
|
||||||
|
|
||||||
|
@Suppress("RegExpRedundantEscape")
|
||||||
|
val modifier = Regex("([xy])=\\{([^{}]+)\\}")
|
||||||
|
|
||||||
|
val modifierExpressions = modifier.findAll(modifiers).map { it.groupValues[1] to it.groupValues[2] }.toMap()
|
||||||
|
|
||||||
|
val xModifierExpression = modifierExpressions["x"]
|
||||||
|
val xModifier =
|
||||||
|
if (xModifierExpression != null) compileFunction1(xModifierExpression, "x", constants, functions) else {
|
||||||
|
{ x: Double -> x }
|
||||||
|
}
|
||||||
|
|
||||||
|
val yModifierExpression = modifierExpressions["y"]
|
||||||
|
val yModifier =
|
||||||
|
if (yModifierExpression != null) compileFunction1(yModifierExpression, "y", constants, functions) else {
|
||||||
|
{ y: Double -> y }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun popToken(): String = mparts.removeLast()
|
||||||
|
fun popNumber(): Double = mparts.removeLast().toDoubleOrNull() ?: error("not a number")
|
||||||
|
|
||||||
|
fun String.numberOrFactorOf(percentageOf: (Double) -> Double): Double {
|
||||||
|
return if (endsWith("%")) {
|
||||||
|
val f = (dropLast(1).toDoubleOrNull() ?: error("'$this' is not a percentage")) / 100.0
|
||||||
|
percentageOf(f)
|
||||||
|
} else {
|
||||||
|
toDoubleOrNull() ?: error("'$this' is not a number")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.numberOrPercentageOf(percentageOf: () -> Double): Double {
|
||||||
|
return numberOrFactorOf { f -> f * percentageOf() }
|
||||||
|
}
|
||||||
|
|
||||||
|
var cursor = Vector2.ZERO
|
||||||
|
var modified = ""
|
||||||
|
fun emit(command: String, vararg ops: Double, relative: Boolean, x: Double, y: Double) {
|
||||||
|
modified = modified + " " + command + " " + ops.joinToString(" ")
|
||||||
|
cursor = if (relative) {
|
||||||
|
Vector2(x + cursor.x, y + cursor.y)
|
||||||
|
} else {
|
||||||
|
Vector2(x + cursor.x, y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (mparts.isNotEmpty()) {
|
||||||
|
val command = mparts.removeLast()
|
||||||
|
|
||||||
|
when (command) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle move cursor command
|
||||||
|
*/
|
||||||
|
"m", "M" -> {
|
||||||
|
val relative = command.first().isLowerCase()
|
||||||
|
val rf = if (relative) 1.0 else 0.0
|
||||||
|
val y = popNumber() + rf * cursor.y
|
||||||
|
emit("M", yModifier(y), relative = false, x = 0.0, y = y)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle line command
|
||||||
|
*/
|
||||||
|
"l", "L" -> {
|
||||||
|
val relative = command.first().isLowerCase()
|
||||||
|
val rf = if (relative) 1.0 else 0.0
|
||||||
|
val x = popNumber()
|
||||||
|
val y = popNumber() + rf * cursor.y
|
||||||
|
|
||||||
|
emit("L", xModifier(x), yModifier(y), relative = false, x = x, y = y)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle cubic bezier command
|
||||||
|
*/
|
||||||
|
"c", "C" -> {
|
||||||
|
val relative = command.first().isLowerCase()
|
||||||
|
val rf = if (relative) 1.0 else 0.0
|
||||||
|
|
||||||
|
val tcx0 = popToken()
|
||||||
|
val tcy0 = popToken()
|
||||||
|
val tcx1 = popToken()
|
||||||
|
val tcy1 = popToken()
|
||||||
|
val x = popNumber()
|
||||||
|
val y = popNumber()
|
||||||
|
val ay = y + cursor.y * rf
|
||||||
|
val x0 = tcx0.numberOrPercentageOf { x }
|
||||||
|
val y0 = tcy0.numberOrFactorOf { factor ->
|
||||||
|
if (relative) y * factor else cursor.y * (1.0 - factor).coerceAtLeast(0.0) + y * factor
|
||||||
|
} + cursor.y * rf
|
||||||
|
val x1 = tcx1.numberOrPercentageOf { x }
|
||||||
|
val y1 = tcy1.numberOrFactorOf { factor ->
|
||||||
|
if (relative) y * factor else cursor.y * (1.0 - factor).coerceAtLeast(0.0) + y * factor
|
||||||
|
} + cursor.y * rf
|
||||||
|
emit(
|
||||||
|
"C",
|
||||||
|
xModifier(x0),
|
||||||
|
yModifier(y0),
|
||||||
|
xModifier(x1),
|
||||||
|
yModifier(y1),
|
||||||
|
xModifier(x),
|
||||||
|
xModifier(ay),
|
||||||
|
relative = false,
|
||||||
|
x = x,
|
||||||
|
y = ay
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle quadratic bezier command
|
||||||
|
*/
|
||||||
|
"q", "Q" -> {
|
||||||
|
val relative = command.first().isLowerCase()
|
||||||
|
val rf = if (relative) 1.0 else 0.0
|
||||||
|
val tcx0 = popToken()
|
||||||
|
val tcy0 = popToken()
|
||||||
|
val x = popNumber()
|
||||||
|
val y = popNumber()
|
||||||
|
val ay = y + cursor.y * rf
|
||||||
|
val x0 = tcx0.numberOrPercentageOf { x }
|
||||||
|
val y0 = tcy0.numberOrFactorOf { factor ->
|
||||||
|
if (relative) y * factor else cursor.y * (1.0 - factor).coerceAtLeast(0.0) + y * factor
|
||||||
|
} + rf * cursor.y
|
||||||
|
emit("Q", xModifier(x0), yModifier(y0), xModifier(x), yModifier(ay), relative = false, x = x, y = ay)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle horizontal line (or hold) command
|
||||||
|
*/
|
||||||
|
"h", "H" -> {
|
||||||
|
if (command == "H") {
|
||||||
|
val x = popNumber()
|
||||||
|
emit(command, xModifier(x), relative = false, x = x, y = cursor.y)
|
||||||
|
cursor = Vector2(x, cursor.y)
|
||||||
|
} else {
|
||||||
|
val x = popNumber()
|
||||||
|
emit(command, xModifier(x), relative = false, x = x, y = cursor.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle cubic smooth to command
|
||||||
|
*/
|
||||||
|
"s", "S" -> {
|
||||||
|
val relative = command.first().isLowerCase()
|
||||||
|
val rf = if (relative) 1.0 else 0.0
|
||||||
|
val tcx0 = popToken()
|
||||||
|
val tcy0 = popToken()
|
||||||
|
val x = popNumber()
|
||||||
|
val y = popNumber()
|
||||||
|
val ay = y + cursor.y * rf
|
||||||
|
val x1 = tcx0.numberOrPercentageOf { x }
|
||||||
|
val y1 = tcy0.numberOrFactorOf { factor ->
|
||||||
|
if (relative) y * factor else cursor.y * (1.0 - factor).coerceAtLeast(0.0) + y * factor
|
||||||
|
} + rf * cursor.y
|
||||||
|
emit("S", xModifier(x1), yModifier(y1), xModifier(x), yModifier(ay), relative = false, x = x, y = ay)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle quadratic smooth to command
|
||||||
|
*/
|
||||||
|
"t", "T" -> {
|
||||||
|
val relative = command.first().isLowerCase()
|
||||||
|
val rf = if (relative) 1.0 else 0.0
|
||||||
|
val x = popNumber()
|
||||||
|
val y = popNumber() + cursor.y * rf
|
||||||
|
emit("T", xModifier(x), yModifier(y), relative = false, x = x, y = y)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> error("unknown command: $command in ${parts}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return modified
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val f = "l 10 10 h 4 t 20.0 20.0 s 5% 50% 30.0 30.0"
|
||||||
|
println(modifyFCurve(f, "x={sqrt(x)} y={y * 2.0}"))
|
||||||
|
|
||||||
|
val mf = "l 10 10 h 4 t 20.0 20.0 s 5% 50% 30.0 30.0 | x={2.0 * x} y={-3.0 * y}"
|
||||||
|
val f2 = mfcurve(mf)
|
||||||
|
println(f2)
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import org.openrndr.application
|
import org.openrndr.application
|
||||||
import org.openrndr.color.ColorRGBa
|
import org.openrndr.color.ColorRGBa
|
||||||
import org.openrndr.extra.fcurve.fcurve
|
import org.openrndr.extra.fcurve.fcurve
|
||||||
import org.openrndr.extra.parameters.ParameterType
|
|
||||||
import org.openrndr.math.Vector2
|
import org.openrndr.math.Vector2
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
@@ -16,12 +15,11 @@ fun main() {
|
|||||||
val yposCurve = fcurve("M360 h5")
|
val yposCurve = fcurve("M360 h5")
|
||||||
val ypos = yposCurve.sampler()
|
val ypos = yposCurve.sampler()
|
||||||
|
|
||||||
|
|
||||||
extend {
|
extend {
|
||||||
drawer.circle(xpos(seconds.mod(5.0)), ypos(seconds.mod(5.0)), 100.0)
|
drawer.circle(xpos(seconds.mod(5.0)), ypos(seconds.mod(5.0)), 100.0)
|
||||||
drawer.stroke = ColorRGBa.PINK
|
drawer.stroke = ColorRGBa.PINK
|
||||||
drawer.contours(xposCurve.contours(Vector2(720.0 / 5.0, 1.0)))
|
drawer.contours(xposCurve.contours(Vector2(720.0 / 5.0, -1.0), Vector2(0.0, height * 1.0)))
|
||||||
drawer.contours(yposCurve.contours(Vector2(720.0 / 5.0, 1.0)))
|
drawer.contours(yposCurve.contours(Vector2(720.0 / 5.0, -1.0), Vector2(0.0, height * 1.0)))
|
||||||
drawer.translate(seconds.mod(5.0)*(720.0/5.0), 0.0)
|
drawer.translate(seconds.mod(5.0)*(720.0/5.0), 0.0)
|
||||||
drawer.lineSegment(0.0, 0.0, 0.0, 720.0)
|
drawer.lineSegment(0.0, 0.0, 0.0, 720.0)
|
||||||
}
|
}
|
||||||
|
|||||||
54
orx-fcurve/src/jvmDemo/kotlin/DemoFCurveSheet01.kt
Normal file
54
orx-fcurve/src/jvmDemo/kotlin/DemoFCurveSheet01.kt
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import org.openrndr.application
|
||||||
|
import org.openrndr.color.ColorRGBa
|
||||||
|
import org.openrndr.draw.loadFont
|
||||||
|
import org.openrndr.extra.fcurve.efcurve
|
||||||
|
import org.openrndr.extra.fcurve.fcurve
|
||||||
|
import org.openrndr.math.Vector2
|
||||||
|
|
||||||
|
fun main() = application {
|
||||||
|
configure {
|
||||||
|
width = 720
|
||||||
|
height = 720
|
||||||
|
}
|
||||||
|
program {
|
||||||
|
val fcurveTexts = listOf(
|
||||||
|
//"(l 35.0 25.0 h {175-35})[4]", // linear steps
|
||||||
|
"(c 33% 0% 67% 67% 35.0 25.0 h {175-35})[4]", // ease-in steps
|
||||||
|
"(c 50% 50% 50% 100% 35.0 25.0 h {175-35})[4]", // ease-out steps
|
||||||
|
"(c 50% 0% 50% 100% 35.0 25.0 h {175-35})[4]", // ease-in-out steps
|
||||||
|
"(c 95% 0% 100% 100% 35.0 25.0 h {175-35})[4]", // arc-in steps
|
||||||
|
"(c 0% 0% 5% 100% 35.0 25.0 h {175-35})[4]", // arc-out steps
|
||||||
|
"(c 95% 0% 100% 100% 17.5 12.5 c 0% 0% 5% 100% 17.5 12.5 h {175-35})[4]", // arc-out steps
|
||||||
|
)
|
||||||
|
|
||||||
|
val fcurves = fcurveTexts.map { fcurve(efcurve(it)) }
|
||||||
|
|
||||||
|
extend {
|
||||||
|
drawer.clear(ColorRGBa.WHITE)
|
||||||
|
|
||||||
|
drawer.fontMap = loadFont("demo-data/fonts/IBMPlexMono-Regular.ttf", 16.0)
|
||||||
|
drawer.translate(10.0, 20.0)
|
||||||
|
|
||||||
|
drawer.stroke = ColorRGBa.PINK
|
||||||
|
drawer.lineSegment(mouse.position.x - 10.0, 0.0, mouse.position.x - 10.0, height * 1.0)
|
||||||
|
|
||||||
|
fun color(i: Int): ColorRGBa =
|
||||||
|
ColorRGBa.BLUE.toHSVa().shiftHue(i * 30.0).saturate(0.5).shade(0.9).toRGBa()
|
||||||
|
|
||||||
|
for (i in fcurveTexts.indices) {
|
||||||
|
drawer.fill = color(i)
|
||||||
|
drawer.text(fcurveTexts[i], 0.0, 120.0)
|
||||||
|
|
||||||
|
drawer.stroke = color(i).opacify(0.25)
|
||||||
|
drawer.lineSegment(0.0, 100.0, width - 20.0, 100.0)
|
||||||
|
|
||||||
|
drawer.stroke = color(i)
|
||||||
|
val y = 100.0 - fcurves[i].value(mouse.position.x - 10.0)
|
||||||
|
drawer.contours(fcurves[i].contours(offset = Vector2(0.0, 100.0)))
|
||||||
|
drawer.circle(mouse.position.x - 10.0, y, 10.0)
|
||||||
|
|
||||||
|
drawer.translate(0.0, 110.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user