From 90d9e4685ea6be290c04a22bac4f3ce286ab3987 Mon Sep 17 00:00:00 2001 From: Edwin Jakobs Date: Wed, 26 Feb 2025 22:08:11 +0100 Subject: [PATCH] [orx-noise] Add demos and extensions for uniform simplex sampling --- .../kotlin/simplexrange/ListExtensions.kt | 48 ++++++++++ .../simplexrange/SimplexRangeExtensions.kt | 75 ++++++++++++++- .../simplexrange/DemoSimplexRange2D02.kt | 59 ++++++++++++ .../simplexrange/DemoSimplexUniform01.kt | 91 +++++++++++++++++++ .../simplexrange/DemoSimplexUniform02.kt | 64 +++++++++++++ 5 files changed, 336 insertions(+), 1 deletion(-) create mode 100644 orx-noise/src/commonMain/kotlin/simplexrange/ListExtensions.kt create mode 100644 orx-noise/src/jvmDemo/kotlin/simplexrange/DemoSimplexRange2D02.kt create mode 100644 orx-noise/src/jvmDemo/kotlin/simplexrange/DemoSimplexUniform01.kt create mode 100644 orx-noise/src/jvmDemo/kotlin/simplexrange/DemoSimplexUniform02.kt diff --git a/orx-noise/src/commonMain/kotlin/simplexrange/ListExtensions.kt b/orx-noise/src/commonMain/kotlin/simplexrange/ListExtensions.kt new file mode 100644 index 00000000..3a34a7c5 --- /dev/null +++ b/orx-noise/src/commonMain/kotlin/simplexrange/ListExtensions.kt @@ -0,0 +1,48 @@ +package org.openrndr.extra.noise.simplexrange + +import org.openrndr.extra.math.simplexrange.simplexUpscale +import org.openrndr.math.LinearType +import kotlin.random.Random + +/** + * Generates a random value within the convex hull of the elements in the list using a uniform + * distribution over the simplex formed by the elements, optionally applying bias to the distribution. + * + * @param random The random number generator used to produce random values. Defaults to `Random.Default`. + * @param biasOrder The number of iterations to apply bias adjustments to the weights. Defaults to 0. + * @param biasAmount The magnitude of the bias adjustment applied during each iteration. Defaults to 0.0. + * @return A value of type `T` representing the weighted interpolation of the list elements, + * with weights sampled uniformly or with bias adjustments if specified. + */ +fun > List.uniformSimplex(random: Random = Random.Default, + biasOrder: Int = 0, + biasAmount: Double = 0.0): T { + return when (size) { + 0 -> error("") + 1 -> this[0] + 2 -> { + val x = random.nextDouble() + this[0] * (1.0 - x) + this[1] * x + } + + else -> { + val r = DoubleArray(size - 1) { random.nextDouble() } + val b = simplexUpscale(r) + + if (biasOrder > 0) { + for (i in 0 until biasOrder) { + b[random.nextInt(b.size)] += random.nextDouble(biasAmount) + } + val sum = b.sum() + for (i in 0 until b.size) { + b[i] = b[i] / sum + } + } + var result = this[0] * b[0] + for (i in 1 until size) { + result += this[i] * b[i] + } + result + } + } +} \ No newline at end of file diff --git a/orx-noise/src/commonMain/kotlin/simplexrange/SimplexRangeExtensions.kt b/orx-noise/src/commonMain/kotlin/simplexrange/SimplexRangeExtensions.kt index 091d2922..4dca390c 100644 --- a/orx-noise/src/commonMain/kotlin/simplexrange/SimplexRangeExtensions.kt +++ b/orx-noise/src/commonMain/kotlin/simplexrange/SimplexRangeExtensions.kt @@ -4,6 +4,8 @@ import org.openrndr.math.LinearType import org.openrndr.extra.math.simplexrange.SimplexRange4D import org.openrndr.extra.math.simplexrange.SimplexRange3D import org.openrndr.extra.math.simplexrange.SimplexRange2D +import kotlin.math.pow + import kotlin.random.Random /** @@ -16,6 +18,41 @@ fun > SimplexRange2D.uniform(random: Random): T { return value(random.nextDouble(), random.nextDouble()) } +/** + * Generates a uniformly distributed value within the SimplexRange2D. + * + * @param random the random number generator used to produce random values. + * @return a value of type T sampled uniformly within the 2D simplex range. + */ +fun > SimplexRange2D.uniformPower(exp: Double, random: Random = Random.Default): T { + val b = upscale(random.nextDouble(), random.nextDouble()) + for (i in 0 until b.size) { + b[i] = b[i].pow(exp) + } + val sum = b.sum() + return x0 * b[0] / sum + x1 * b[1] / sum + x2 * b[2] / sum +} + + +/** + * Generates a random point within the simplex represented by the `SimplexRange2D`, + * forming an interpolation of the three control points `x0`, `x1`, `x2` using a weighted + * random combination normalized to sum to 1. + * + * @param random The random number generator used to produce the random weights. + * @return A randomly interpolated point of type `T` within the simplex. + */ +fun > SimplexRange2D.uniformCube(random: Random): T { + val r = DoubleArray(3) { random.nextDouble() } + val sum = r.sum() + if (sum > 0.0) { + for (i in 0 until r.size) { + r[i] /= sum + } + } + return this.x0 * r[0] + x1 * r[1] + x2 * r[2] +} + /** * Generates a uniformly distributed value within the 3D simplex range. * @@ -26,6 +63,24 @@ fun > SimplexRange3D.uniform(random: Random): T { return value(random.nextDouble(), random.nextDouble(), random.nextDouble()) } +/** + * Generates a random point within the simplex represented by the `SimplexRange3D`, + * forming an interpolation of the four control points using a weighted random combination normalized to sum to 1. + * + * @param random The random number generator used to produce the random weights. + * @return A randomly interpolated point of type `T` within the simplex. + */ +fun > SimplexRange3D.uniformCube(random: Random): T { + val r = DoubleArray(4) { random.nextDouble() } + val sum = r.sum() + if (sum > 0.0) { + for (i in 0 until r.size) { + r[i] /= sum + } + } + return this.x0 * r[0] + x1 * r[1] + x2 * r[2] + x3 * r[3] +} + /** * Generates a uniformly distributed value within the 4D simplex range using a random generator. * @@ -34,4 +89,22 @@ fun > SimplexRange3D.uniform(random: Random): T { */ fun > SimplexRange4D.uniform(random: Random): T { return value(random.nextDouble(), random.nextDouble(), random.nextDouble(), random.nextDouble()) -} \ No newline at end of file +} + +/** + * Generates a random point within the simplex represented by the `SimplexRange4D`, + * forming an interpolation of the five control points using a weighted random combination normalized to sum to 1. + * + * @param random The random number generator used to produce the random weights. + * @return A randomly interpolated point of type `T` within the simplex. + */ +fun > SimplexRange4D.uniformCube(random: Random): T { + val r = DoubleArray(5) { random.nextDouble() } + val sum = r.sum() + if (sum > 0.0) { + for (i in 0 until r.size) { + r[i] /= sum + } + } + return this.x0 * r[0] + x1 * r[1] + x2 * r[2] + x3 * r[3] + x4 * r[4] +} diff --git a/orx-noise/src/jvmDemo/kotlin/simplexrange/DemoSimplexRange2D02.kt b/orx-noise/src/jvmDemo/kotlin/simplexrange/DemoSimplexRange2D02.kt new file mode 100644 index 00000000..2b394e76 --- /dev/null +++ b/orx-noise/src/jvmDemo/kotlin/simplexrange/DemoSimplexRange2D02.kt @@ -0,0 +1,59 @@ +package simplexrange + +import org.openrndr.application +import org.openrndr.color.ColorRGBa +import org.openrndr.extra.math.simplexrange.SimplexRange3D +import kotlin.random.Random +import org.openrndr.extra.noise.simplexrange.uniform +import org.openrndr.extra.noise.simplexrange.uniformCube + +/** + * This demo creates a dynamic graphical output utilizing simplex and + * linear interpolation-based color ranges. + * + * Functionalities: + * - Defines a list of base colors converted to LAB color space for smooth interpolation. + * - Constructs a 3D simplex range and a 2D linear range for color sampling. + * - Randomly populates two sections of the screen with rectangles filled with colors + * sampled from simplex and linear ranges respectively. + * - Draws a vertical divider line in the middle of the application window. + */ +fun main() { + application { + configure { + width = 720 + height = 720 + } + program { + extend { + val colors = listOf(ColorRGBa.BLACK, ColorRGBa.RED, ColorRGBa.GREEN, ColorRGBa.BLUE).map { it.toLABa() } + drawer.stroke = null + val sr = SimplexRange3D(colors[0], colors[1], colors[2], colors[3]) + + val r = Random((seconds * 2).toInt()) + + // Draw the simplex sampling on the left + drawer.rectangles { + for (y in 0 until 40) { + for (x in 0 until 20) { + fill = sr.uniform(r).toRGBa() + rectangle(x * width / 40.0, y * height / 40.0, width / 40.0, height / 40.0) + } + } + } + + // Draw the bilinear sampling on the right + drawer.rectangles { + for (y in 0 until 40) { + for (x in 20 until 40) { + fill = sr.uniformCube(r).toRGBa() + rectangle(x * width / 40.0, y * height / 40.0, width / 40.0, height / 40.0) + } + } + } + drawer.stroke = ColorRGBa.BLACK + drawer.lineSegment(drawer.bounds.vertical(0.5)) + } + } + } +} \ No newline at end of file diff --git a/orx-noise/src/jvmDemo/kotlin/simplexrange/DemoSimplexUniform01.kt b/orx-noise/src/jvmDemo/kotlin/simplexrange/DemoSimplexUniform01.kt new file mode 100644 index 00000000..3ee20f9d --- /dev/null +++ b/orx-noise/src/jvmDemo/kotlin/simplexrange/DemoSimplexUniform01.kt @@ -0,0 +1,91 @@ +package simplexrange + +import org.openrndr.application +import org.openrndr.color.ColorRGBa +import org.openrndr.draw.isolated +import kotlin.random.Random +import org.openrndr.extra.noise.simplexrange.uniformSimplex +import org.openrndr.math.Polar +import org.openrndr.math.Vector2 +import kotlin.math.cos +import kotlin.math.floor + +/** + * This demo creates a dynamic graphical output utilizing simplex and + * linear interpolation-based color ranges. + * + * Functionalities: + * - Defines a list of base colors converted to LAB color space for smooth interpolation. + * - Constructs a 3D simplex range and a 2D linear range for color sampling. + * - Randomly populates two sections of the screen with rectangles filled with colors + * sampled from simplex and linear ranges respectively. + * - Draws a vertical divider line in the middle of the application window. + */ +fun main() { + application { + configure { + width = 720 + height = 720 + } + program { + extend { + val positions = (0 until 3).map { Polar((30.0 + it * 120.0), 180.0).cartesian } + val colors = listOf(ColorRGBa.PINK, ColorRGBa.RED, ColorRGBa.BLUE).map { it.toLABa() } + val freq = 1.0 + + drawer.stroke = null + + val power = cos(floor(seconds*freq)/freq) *16.0 + 16.0 + drawer.isolated { + drawer.translate(drawer.bounds.position(0.25,0.25) + Vector2(0.0, 40.0)) + val rp = Random((seconds * freq).toInt()) + val rc = Random((seconds * freq).toInt()) + for (i in 0 until 32 * 32) { + drawer.fill = colors.uniformSimplex(rc, 1, power).toRGBa() + drawer.circle(positions.uniformSimplex(rp, 1, power), 2.0) + } + } + drawer.isolated { + drawer.translate(drawer.bounds.position(0.75,0.75) + Vector2(0.0, 40.0)) + val rp = Random((seconds * freq).toInt()) + val rc = Random((seconds * freq).toInt()) + for (i in 0 until 32 * 32) { + drawer.fill = colors.uniformSimplex(rc, 2, power).toRGBa() + drawer.circle(positions.uniformSimplex(rp, 2, power), 2.0) + } + } + + + drawer.isolated { + drawer.stroke = null + val rc = Random((seconds * freq).toInt()) + drawer.translate(drawer.bounds.position(0.5,0.0) + Vector2(20.0, 20.0)) + + for (i in 0 until 32 * 32) { + val x = i.mod(32) + val y = i / 32 + drawer.fill = colors.uniformSimplex(rc, 1, power).toRGBa() + drawer.rectangle(x * 10.0, y * 10.0, 10.0, 10.0) + } + + } + + drawer.isolated { + drawer.stroke = null + val rc = Random((seconds * freq).toInt()) + + drawer.translate(drawer.bounds.position(0.0,0.5) + Vector2(20.0, 20.0)) + + for (i in 0 until 32 * 32) { + val x = i.mod(32) + val y = i / 32 + drawer.fill = colors.uniformSimplex(rc, 2, power).toRGBa() + drawer.rectangle(x * 10.0, y * 10.0, 10.0, 10.0) + } + + } + + } + } + } +} \ No newline at end of file diff --git a/orx-noise/src/jvmDemo/kotlin/simplexrange/DemoSimplexUniform02.kt b/orx-noise/src/jvmDemo/kotlin/simplexrange/DemoSimplexUniform02.kt new file mode 100644 index 00000000..36704da2 --- /dev/null +++ b/orx-noise/src/jvmDemo/kotlin/simplexrange/DemoSimplexUniform02.kt @@ -0,0 +1,64 @@ +package simplexrange + +import org.openrndr.application +import org.openrndr.color.ColorRGBa +import org.openrndr.draw.isolated +import org.openrndr.extra.math.simplexrange.SimplexRange2D +import org.openrndr.extra.math.simplexrange.SimplexRange3D +import org.openrndr.extra.noise.scatter +import kotlin.random.Random +import org.openrndr.extra.noise.simplexrange.uniformSimplex +import org.openrndr.math.Polar +import org.openrndr.math.Vector2 +import org.openrndr.shape.Rectangle +import kotlin.math.cos +import kotlin.math.floor + +/** + * This demo creates a dynamic graphical output utilizing simplex and + * linear interpolation-based color ranges. + * + * Functionalities: + * - Defines a list of base colors converted to LAB color space for smooth interpolation. + * - Constructs a 3D simplex range and a 2D linear range for color sampling. + * - Randomly populates two sections of the screen with rectangles filled with colors + * sampled from simplex and linear ranges respectively. + * - Draws a vertical divider line in the middle of the application window. + */ +fun main() { + application { + configure { + width = 720 + height = 720 + } + program { + extend { + val positions = (0 until 3).map { Polar((30.0 + it * 120.0), 180.0).cartesian } + val colors = listOf(ColorRGBa.PINK, ColorRGBa.RED, ColorRGBa.BLUE).map { it.toLABa() } + + val positionsr = SimplexRange2D(positions[0], positions[1], positions[2]) + val colorsr = SimplexRange2D(colors[0], colors[1], colors[2]) + val freq = 1.0 + + drawer.stroke = null + + + val r = Random((seconds * freq).toInt()) + val points = Rectangle(0.0, 0.0, 360.0, 360.0).scatter(10.0, random = r) + + drawer.circles(points, 2.0) + + val power = cos(floor(seconds*freq)/freq) *16.0 + 16.0 + drawer.isolated { + drawer.translate(drawer.bounds.position(0.75,0.25) + Vector2(0.0, 40.0)) + + for (point in points) { + drawer.fill = colorsr.value(point.x/360.0, point.y/360.0).toRGBa() + drawer.circle(positionsr.value(point.x/360.0, point.y/360.0), 2.0) + } + } + + } + } + } +} \ No newline at end of file