[orx-marching-squares] Add orx-marching-squares module

This commit is contained in:
Edwin Jakobs
2023-02-02 18:24:07 +01:00
parent 4422f114ba
commit 5038fd0fc3
9 changed files with 265 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
# orx-marching-squares
Tools for extracting contours from functions
## How to use it?
`orx-marching-squares` provides the `findContours()` function
```kotlin
fun f(v: Vector2) = v.distanceTo(drawer.bounds.center) - 200.0
val segments = findContours(::f, drawer.bounds, 16.0)
drawer.lineSegments(segments)
```
With a small adjustment to the given function one can use `findContours` to find iso contours. The trick is to add a cosine over the distance function.
```kotlin
fun f(v: Vector2) = cos((v.distanceTo(drawer.bounds.center) / 100.0) * 2 * PI)
val segments = findContours(::f, drawer.bounds.offsetEdges(32.0), 16.0)
drawer.lineSegments(segments)
```

View File

@@ -0,0 +1,23 @@
plugins {
org.openrndr.extra.convention.`kotlin-multiplatform`
}
kotlin {
sourceSets {
@Suppress("UNUSED_VARIABLE")
val commonMain by getting {
dependencies {
api(libs.openrndr.math)
api(libs.openrndr.shape)
}
}
@Suppress("UNUSED_VARIABLE")
val jvmDemo by getting {
dependencies {
implementation(project(":orx-shapes"))
implementation(project(":orx-noise"))
}
}
}
}

View File

@@ -0,0 +1,107 @@
package org.openrndr.extra.marchingsquares
import org.openrndr.math.IntVector2
import org.openrndr.math.Vector2
import org.openrndr.shape.LineSegment
import org.openrndr.shape.Rectangle
import kotlin.math.max
import kotlin.math.min
fun findContours(
f: (Vector2) -> Double,
area: Rectangle,
cellSize: Double,
useInterpolation: Boolean = true
): List<LineSegment> {
val segments = mutableListOf<LineSegment>()
val values = mutableMapOf<IntVector2, Double>()
for (y in 0 until (area.width / cellSize).toInt()) {
for (x in 0 until (area.width / cellSize).toInt()) {
values[IntVector2(x, y)] = f(Vector2(x * cellSize + area.x, y * cellSize + area.y))
}
}
val zero = 0.0
for (y in 0 until (area.width / cellSize).toInt()) {
for (x in 0 until (area.width / cellSize).toInt()) {
val v00 = (values[IntVector2(x, y)] ?: zero)
val v10 = (values[IntVector2(x + 1, y)] ?: zero)
val v01 = (values[IntVector2(x, y + 1)] ?: zero)
val v11 = (values[IntVector2(x + 1, y + 1)] ?: zero)
val p00 = Vector2(x.toDouble(), y.toDouble()) * cellSize + area.corner
val p10 = Vector2((x + 1).toDouble(), y.toDouble()) * cellSize + area.corner
val p01 = Vector2(x.toDouble(), (y + 1).toDouble()) * cellSize + area.corner
val p11 = Vector2((x + 1).toDouble(), (y + 1).toDouble()) * cellSize + area.corner
val index = (if (v00 >= 0.0) 1 else 0) +
(if (v10 >= 0.0) 2 else 0) +
(if (v01 >= 0.0) 4 else 0) +
(if (v11 >= 0.0) 8 else 0)
fun blend(v1: Double, v2: Double): Double {
if (useInterpolation) {
require(v1 == v1 && v2 == v2)
val f1 = min(v1, v2)
val f2 = max(v1, v2)
val v = (-f1) / (f2 - f1)
require(v == v)
require(v in 0.0..1.0)
return if (f1 == v1) {
v
} else {
1.0 - v
}
} else {
return 0.5
}
}
fun emitLine(
p00: Vector2, p01: Vector2, v00: Double, v01: Double,
p10: Vector2, p11: Vector2, v10: Double, v11: Double
) {
val r0 = blend(v00, v01)
val r1 = blend(v10, v11)
val l0 = LineSegment(p00.mix(p01, r0), p10.mix(p11, r1))
segments.add(l0)
}
when (index) {
0, 15 -> {}
1, 15 xor 1 -> {
emitLine(p00, p01, v00, v01, p00, p10, v00, v10)
}
2, 15 xor 2 -> {
emitLine(p00, p10, v00, v10, p10, p11, v10, v11)
}
3, 15 xor 3 -> {
emitLine(p00, p01, v00, v01, p10, p11, v10, v11)
}
4, 15 xor 4 -> {
emitLine(p00, p01, v00, v01, p01, p11, v01, v11)
}
5, 15 xor 5 -> {
emitLine(p00, p10, v00, v10, p01, p11, v01, v11)
}
6, 15 xor 6 -> {
emitLine(p00, p01, v00, v01, p00, p10, v00, v10)
emitLine(p01, p11, v01, v11, p10, p11, v10, v11)
}
7, 15 xor 7 -> {
emitLine(p01, p11, v01, v11, p10, p11, v10, v11)
}
}
}
}
return segments
}

View File

@@ -0,0 +1,22 @@
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.extra.marchingsquares.findContours
import org.openrndr.math.Vector2
fun main() {
application {
configure {
width = 720
height = 720
}
program {
extend {
drawer.clear(ColorRGBa.BLACK)
drawer.stroke = ColorRGBa.PINK
fun f(v: Vector2) = v.distanceTo(drawer.bounds.center) - 200.0
val segments = findContours(::f, drawer.bounds, 16.0)
drawer.lineSegments(segments)
}
}
}
}

View File

@@ -0,0 +1,24 @@
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.extra.marchingsquares.findContours
import org.openrndr.math.Vector2
import kotlin.math.PI
import kotlin.math.cos
fun main() {
application {
configure {
width = 720
height = 720
}
program {
extend {
drawer.clear(ColorRGBa.BLACK)
drawer.stroke = ColorRGBa.PINK
fun f(v: Vector2) = cos((v.distanceTo(drawer.bounds.center) / 100.0) * 2 * PI)
val segments = findContours(::f, drawer.bounds.offsetEdges(32.0), 16.0)
drawer.lineSegments(segments)
}
}
}
}

View File

@@ -0,0 +1,30 @@
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.extra.marchingsquares.findContours
import org.openrndr.math.Vector2
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.sin
fun main() {
application {
configure {
width = 720
height = 720
}
program {
extend {
drawer.clear(ColorRGBa.BLACK)
drawer.stroke = ColorRGBa.PINK
fun f(v: Vector2): Double {
val p = v + Vector2(cos(v.y * 0.1 + seconds) * 40.0, sin(v.x * 0.1 + seconds) * 40.0)
return cos((p.distanceTo(drawer.bounds.center) / 720.0) * 6 * PI)
}
val segments = findContours(::f, drawer.bounds.offsetEdges(32.0), 4.0)
drawer.lineSegments(segments)
}
}
}
}

View File

@@ -0,0 +1,36 @@
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.grayscale
import org.openrndr.draw.loadImage
import org.openrndr.extra.marchingsquares.findContours
import org.openrndr.math.Vector2
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.sin
fun main() {
application {
configure {
width = 640
height = 480
}
program {
val image = loadImage("demo-data/images/image-001.png")
image.shadow.download()
extend {
drawer.clear(ColorRGBa.BLACK)
drawer.stroke = ColorRGBa.BLACK
fun f(v: Vector2): Double {
val iv = v.toInt()
val d = if (iv.x >= 0 && iv.y >= 0 && iv.x < image.width && iv.y < image.height) image.shadow[iv.x, iv.y].luminance else 0.0
return cos(d * PI * 8.0 + seconds)
}
val segments = findContours(::f, drawer.bounds.offsetEdges(32.0), 4.0)
drawer.drawStyle.colorMatrix = grayscale()
drawer.image(image)
drawer.lineSegments(segments)
}
}
}
}