Files
orx/orx-math/src/jvmDemo/kotlin/rbf/RbfInterpolation01.kt
2025-10-06 14:02:47 +02:00

131 lines
5.2 KiB
Kotlin

package rbf
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.isolated
import org.openrndr.draw.shadeStyle
import org.openrndr.extra.color.spaces.OKHSV
import org.openrndr.extra.color.spaces.OKLab
import org.openrndr.extra.color.tools.shadeLuminosity
import org.openrndr.extra.color.tools.shiftHue
import org.openrndr.extra.math.rbf.Rbf2DInterpolator
import org.openrndr.extra.math.rbf.rbfGaussian
import org.openrndr.extra.noise.uniform
import org.openrndr.extra.shaderphrases.noise.fhash12Phrase
import org.openrndr.extra.shaderphrases.rbf.rbfGaussianPhrase
import org.openrndr.math.Vector3
import kotlin.collections.indices
import kotlin.collections.map
import kotlin.collections.toTypedArray
import kotlin.random.Random
import kotlin.ranges.until
import kotlin.text.trimIndent
import kotlin.text.trimMargin
/**
* Demonstrates using a two-dimensional Radial Basis Function (RBF) interpolator
* with the user provided 2D input points, their corresponding values (colors in this demo),
* a smoothing factor, and a radial basis function kernel.
*
* The program chooses 14 random points in the window area leaving a 100 pixels
* margin around the borders and assigns a randomized color to each point.
*
* Next it creates the interpolator using those points and colors, a smoothing factor
* and the RBF function used for interpolation. This function takes a squared distance
* as input and returns a scalar value representing the influence of points at that distance.
*
* A ShadeStyle implementing the RBF interpolation is created next, used to render
* the background gradient interpolating all points and their colors.
*
* After rendering the background, the original points and their colors are
* drawn as circles for reference.
*
* Finally, the current mouse position is used for sampling a color
* from the interpolator and displayed for comparison. Notice that even if
* the fill color is flat, it may look like a gradient due to the changing
* colors in the surrounding pixels.
*/
fun main() {
application {
configure {
width = 720
height = 720
}
program {
val r = Random(0)
val points = drawer.bounds.offsetEdges(-100.0).uniform(14, r)
val colors = points.map {
ColorRGBa.PINK
.shiftHue<OKHSV>(Double.uniform(-180.0, 180.0, r))
.shadeLuminosity<OKLab>(Double.uniform(0.4, 1.0, r))
.toLinear()
}
// Here the `scale` and `smoothing` values are hand-tuned
val scale = 0.04 / 5.0
val interpolator = Rbf2DInterpolator(
points,
colors.map { doubleArrayOf(it.r, it.g, it.b) }.toTypedArray<DoubleArray>(),
smoothing = 0.09,
rbf = rbfGaussian(scale)
)
/**
* Shader style that implements RBF interpolation in the fragment shader.
* Uses a Gaussian RBF function to interpolate colors between given points.
* Includes custom distance calculation and color interpolation functions.
*/
val ss = shadeStyle {
fragmentPreamble = """
|$fhash12Phrase
|$rbfGaussianPhrase
|float squaredDistance(vec2 p, vec2 q) {
| vec2 d = p - q;
| return dot(d, d);
|}
|vec3 rbfInterpolate(vec2 p) {
| vec3 c = p_mean;
| for (int i = 0; i < p_weights_SIZE; ++i) {
| float r = rbfGaussian(squaredDistance(p_points[i], p), $scale);
| c += p_weights[i].rgb * r;
| }
| return c;
|}
""".trimMargin()
fragmentTransform = """
x_fill.rgb = rbfInterpolate(c_boundsPosition.xy * vec2(720.0, 720.0));
""".trimIndent()
val weights = (0 until points.size).map {
Vector3(interpolator.weights[it][0], interpolator.weights[it][1], interpolator.weights[it][2])
}.toTypedArray()
parameter("weights", weights)
parameter("points", points.toTypedArray())
parameter("mean", Vector3(interpolator.mean[0], interpolator.mean[1], interpolator.mean[2]))
}
extend {
// draw the interpolated colors
drawer.isolated {
drawer.shadeStyle = ss
drawer.rectangle(drawer.bounds)
}
// draw the original points and colors for reference
drawer.circles {
for (i in points.indices) {
fill = colors[i]
circle(points[i], 10.0)
}
}
// compute color on CPU for comparison
drawer.fill = interpolator.interpolate(mouse.position).let {
ColorRGBa(it[0], it[1], it[2], 1.0)
}
drawer.circle(mouse.position, 30.0)
}
}
}
}