[orx-marching-squares] Add orx-marching-squares module
This commit is contained in:
21
orx-marching-squares/README.md
Normal file
21
orx-marching-squares/README.md
Normal 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)
|
||||
```
|
||||
23
orx-marching-squares/build.gradle.kts
Normal file
23
orx-marching-squares/build.gradle.kts
Normal 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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
107
orx-marching-squares/src/commonMain/kotlin/MarchingSquares.kt
Normal file
107
orx-marching-squares/src/commonMain/kotlin/MarchingSquares.kt
Normal 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
|
||||
}
|
||||
22
orx-marching-squares/src/jvmDemo/kotlin/FindContours01.kt
Normal file
22
orx-marching-squares/src/jvmDemo/kotlin/FindContours01.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
24
orx-marching-squares/src/jvmDemo/kotlin/FindContours02.kt
Normal file
24
orx-marching-squares/src/jvmDemo/kotlin/FindContours02.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
30
orx-marching-squares/src/jvmDemo/kotlin/FindContours03.kt
Normal file
30
orx-marching-squares/src/jvmDemo/kotlin/FindContours03.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
36
orx-marching-squares/src/jvmDemo/kotlin/FindContours04.kt
Normal file
36
orx-marching-squares/src/jvmDemo/kotlin/FindContours04.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user