Added orx-no-clear

This commit is contained in:
edwin
2018-11-06 15:44:49 +01:00
parent 07598fdbbe
commit 7cec4bddc8
6 changed files with 495 additions and 429 deletions

View File

@@ -1,27 +1,27 @@
# ORX (OPENRNDR EXTRA) # ORX (OPENRNDR EXTRA)
A growing library of assorted data structures, algorithms and utilities. A growing library of assorted data structures, algorithms and utilities.
- orx-kdtree, a kd-tree implementation for fast nearest point searches - `orx-integral-image`, a CPU-based implementation for integral images (summed area tables)
- orx-jumpflood, a filter/shader based implementation of the jump flood algorithm for finding fast approximate (directional) distance fields - `orx-jumpflood`, a filter/shader based implementation of the jump flood algorithm for finding fast approximate (directional) distance fields
- orx-integral-image, a CPU-based implementation for integral images (summed area tables) - `orx-kdtree`, a kd-tree implementation for fast nearest point searches
- `orx-no-clear`, a simple extension that provides drawing without clearing the background
## Usage ## Usage
ORX is build against OPENRNDR 0.3.28, make sure you use this version in your project. ORX is build against OPENRNDR 0.3.28, make sure you use this version in your project.
Easiest way to add ORX to your project is through the use of Jitpack Easiest way to add ORX to your project is through the use of Jitpack
Add repository: Add repository:
``` ```
repositories { repositories {
maven { url 'https://jitpack.io' } maven { url 'https://jitpack.io' }
} }
``` ```
Add dependency: Add dependency:
``` ```
dependencies { dependencies {
compile 'com.github.openrndr.orx:<orx-artifact>:v0.0.8' compile 'com.github.openrndr.orx:<orx-artifact>:v0.0.9'
} }
``` ```

View File

@@ -1,48 +1,47 @@
plugins { plugins {
id 'org.jetbrains.kotlin.jvm' version '1.3.0' id 'org.jetbrains.kotlin.jvm' version '1.3.0'
} }
allprojects { allprojects {
group 'org.openrndr.extra' group 'org.openrndr.extra'
version '0.0.8' version '0.0.9'
} }
repositories { repositories {
mavenCentral() mavenCentral()
} }
ext { ext {
openrndrVersion = "0.3.28" openrndrVersion = "0.3.28"
} }
subprojects {
subprojects {
apply plugin: 'kotlin'
apply plugin: 'kotlin' apply plugin: 'maven'
apply plugin: 'maven' repositories {
repositories { mavenLocal()
mavenLocal() mavenCentral()
mavenCentral() maven {
maven { url = "https://dl.bintray.com/openrndr/openrndr"
url = "https://dl.bintray.com/openrndr/openrndr" }
} }
}
dependencies {
dependencies { compile "org.openrndr:openrndr-core:$openrndrVersion"
compile "org.openrndr:openrndr-core:$openrndrVersion" compile "org.openrndr:openrndr-filter:$openrndrVersion"
compile "org.openrndr:openrndr-filter:$openrndrVersion" compile group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: '1.0.0'
compile group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: '0.27.0' }
}
}
}
dependencies {
dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8" }
}
compileKotlin {
compileKotlin { kotlinOptions.jvmTarget = "1.8"
kotlinOptions.jvmTarget = "1.8" }
} compileTestKotlin {
compileTestKotlin { kotlinOptions.jvmTarget = "1.8"
kotlinOptions.jvmTarget = "1.8"
} }

View File

@@ -1,353 +1,352 @@
package org.openrndr.extra.kdtree package org.openrndr.extra.kdtree
import kotlinx.coroutines.experimental.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.experimental.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.experimental.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.experimental.runBlocking import kotlinx.coroutines.runBlocking
import org.openrndr.math.IntVector2 import org.openrndr.math.IntVector2
import org.openrndr.math.Vector2 import org.openrndr.math.Vector2
import org.openrndr.math.Vector3 import org.openrndr.math.Vector3
import org.openrndr.math.Vector4 import org.openrndr.math.Vector4
import java.util.* import java.util.*
import java.util.concurrent.atomic.AtomicInteger import kotlin.IllegalStateException
import kotlin.IllegalStateException
/** built-in mapper for [Vector2] */
/** built-in mapper for [Vector2] */ fun vector2Mapper(v: Vector2, dimension: Int): Double {
fun vector2Mapper(v: Vector2, dimension: Int): Double { return when (dimension) {
return when (dimension) { 0 -> v.x
0 -> v.x else -> v.y
else -> v.y }
} }
}
fun intVector2Mapper(v: IntVector2, dimension: Int): Double {
fun intVector2Mapper(v: IntVector2, dimension: Int): Double { return when (dimension) {
return when (dimension) { 0 -> v.x.toDouble()
0 -> v.x.toDouble() else -> v.y.toDouble()
else -> v.y.toDouble() }
} }
}
/** built-in mapper for [Vector3] */
/** built-in mapper for [Vector3] */ fun vector3Mapper(v: Vector3, dimension: Int): Double {
fun vector3Mapper(v: Vector3, dimension: Int): Double { return when (dimension) {
return when (dimension) { 0 -> v.x
0 -> v.x 1 -> v.y
1 -> v.y else -> v.y
else -> v.y }
} }
}
/** built-in mapper for [Vector4] */
/** built-in mapper for [Vector4] */ fun vector4Mapper(v: Vector4, dimension: Int): Double {
fun vector4Mapper(v: Vector4, dimension: Int): Double { return when (dimension) {
return when (dimension) { 0 -> v.x
0 -> v.x 1 -> v.y
1 -> v.y 2 -> v.z
2 -> v.z else -> v.w
else -> v.w }
} }
}
class KDTreeNode<T> {
class KDTreeNode<T> { var parent: KDTreeNode<T>? = null
var parent: KDTreeNode<T>? = null var median: Double = 0.0
var median: Double = 0.0 var dimension: Int = 0
var dimension: Int = 0 var children: Array<KDTreeNode<T>?> = arrayOfNulls(2)
var children: Array<KDTreeNode<T>?> = arrayOfNulls(2) var item: T? = null
var item: T? = null
internal val isLeaf: Boolean
internal val isLeaf: Boolean get() = children[0] == null && children[1] == null
get() = children[0] == null && children[1] == null
override fun toString(): String {
override fun toString(): String { return "KDTreeNode{" +
return "KDTreeNode{" + "median=" + median +
"median=" + median + ", item=" + item +
", item=" + item + ", dimension=" + dimension +
", dimension=" + dimension + ", children=" + Arrays.toString(children) +
", children=" + Arrays.toString(children) +
"} " + super.toString()
"} " + super.toString() }
} }
}
fun <T> insertItem(root: KDTreeNode<T>, item: T, mapper: (T, Int) -> Double): KDTreeNode<T> {
fun <T> insertItem(root: KDTreeNode<T>, item: T, mapper: (T, Int) -> Double): KDTreeNode<T> { return if (root.isLeaf) {
return if (root.isLeaf) { root.item = item
root.item = item root
root } else {
} else { if (mapper(item, root.dimension) < root.median) {
if (mapper(item, root.dimension) < root.median) { insertItem(root.children[0] ?: throw IllegalStateException("left is null"), item, mapper)
insertItem(root.children[0] ?: throw IllegalStateException("left is null"), item, mapper) } else {
} else { insertItem(root.children[1] ?: throw IllegalStateException("right is null"), item, mapper)
insertItem(root.children[1] ?: throw IllegalStateException("right is null"), item, mapper) }
} }
} }
}
fun <T> buildKDTree(items: MutableList<T>, dimensions: Int, mapper: (T, Int) -> Double): KDTreeNode<T> {
fun <T> buildKDTree(items: MutableList<T>, dimensions: Int, mapper: (T, Int) -> Double): KDTreeNode<T> { val root = KDTreeNode<T>()
val root = KDTreeNode<T>()
val start = System.currentTimeMillis()
val start = System.currentTimeMillis() fun <T> buildTreeTask(scope: CoroutineScope, node: KDTreeNode<T>, items: MutableList<T>, dimensions: Int, levels: Int, mapper: (T, Int) -> Double): KDTreeNode<T> {
fun <T> buildTreeTask(scope: CoroutineScope, node: KDTreeNode<T>, items: MutableList<T>, dimensions: Int, levels: Int, mapper: (T, Int) -> Double): KDTreeNode<T> {
if (items.size > 0) {
if (items.size > 0) { val dimension = levels % dimensions
val dimension = levels % dimensions val values = ArrayList<T>()
val values = ArrayList<T>() for (item in items) {
for (item in items) { values.add(item)
values.add(item) }
}
node.dimension = dimension
node.dimension = dimension val median = selectNth(items, items.size / 2) { mapper(it, dimension) }
val median = selectNth(items, items.size / 2) { mapper(it, dimension) }
val leftItems = mutableListOf<T>()
val leftItems = mutableListOf<T>() val rightItems = mutableListOf<T>()
val rightItems = mutableListOf<T>()
node.median = mapper(median, dimension)
node.median = mapper(median, dimension) node.item = median
node.item = median for (item in items) {
for (item in items) { if (item === median) {
if (item === median) { continue
continue }
} if (mapper(item, dimension) < node.median) {
if (mapper(item, dimension) < node.median) { leftItems.add(item)
leftItems.add(item) } else {
} else { rightItems.add(item)
rightItems.add(item) }
} }
}
// validate split
// validate split if (leftItems.size + rightItems.size + 1 != items.size) {
if (leftItems.size + rightItems.size + 1 != items.size) { throw IllegalStateException("left: ${leftItems.size}, right: ${rightItems.size}, items: ${items.size}")
throw IllegalStateException("left: ${leftItems.size}, right: ${rightItems.size}, items: ${items.size}") }
}
if (leftItems.size > 0) {
if (leftItems.size > 0) { node.children[0] = KDTreeNode()
node.children[0] = KDTreeNode() node.children[0]?.let {
node.children[0]?.let { it.parent = node
it.parent = node
scope.launch {
scope.launch { buildTreeTask(scope, it, leftItems, dimensions, levels + 1, mapper)
buildTreeTask(scope, it, leftItems, dimensions, levels + 1, mapper) }
} }
} }
} if (rightItems.size > 0) {
if (rightItems.size > 0) { node.children[1] = KDTreeNode()
node.children[1] = KDTreeNode() node.children[1]?.let {
node.children[1]?.let { it.parent = node
it.parent = node scope.launch {
scope.launch { buildTreeTask(scope, it, rightItems, dimensions, levels + 1, mapper)
buildTreeTask(scope, it, rightItems, dimensions, levels + 1, mapper) }
} }
} }
} }
} return node
return node }
}
val job = GlobalScope.launch {
val job = GlobalScope.launch { buildTreeTask(this, root, items, dimensions, 0, mapper)
buildTreeTask(this, root, items, dimensions, 0, mapper) }
} runBlocking {
runBlocking { job.join()
job.join() }
} println("building took ${System.currentTimeMillis()-start}ms")
println("building took ${System.currentTimeMillis()-start}ms") return root
return root }
}
private fun <T> sqrDistance(left: T, right: T, dimensions: Int, mapper: (T, Int) -> Double): Double {
private fun <T> sqrDistance(left: T, right: T, dimensions: Int, mapper: (T, Int) -> Double): Double { var distance = 0.0
var distance = 0.0
for (i in 0 until dimensions) {
for (i in 0 until dimensions) { val d = mapper(left, i) - mapper(right, i)
val d = mapper(left, i) - mapper(right, i) distance += d * d
distance += d * d }
} return distance
return distance }
}
fun <T> findAllNodes(root: KDTreeNode<T>): List<KDTreeNode<T>> {
fun <T> findAllNodes(root: KDTreeNode<T>): List<KDTreeNode<T>> { val stack = Stack<KDTreeNode<T>>()
val stack = Stack<KDTreeNode<T>>() val all = ArrayList<KDTreeNode<T>>()
val all = ArrayList<KDTreeNode<T>>() stack.empty()
stack.empty() stack.push(root)
stack.push(root) while (!stack.isEmpty()) {
while (!stack.isEmpty()) { val node = stack.pop()
val node = stack.pop() // if (node.item != null /*&& !visited.contains(node.children[1])*/) {
// if (node.item != null /*&& !visited.contains(node.children[1])*/) { all.add(node)
all.add(node) // }
// }
if (node.children[0] != null /*&&!visited.contains(node.children[0])*/) {
if (node.children[0] != null /*&&!visited.contains(node.children[0])*/) { stack.push(node.children[0])
stack.push(node.children[0]) }
} if (node.children[1] != null) {
if (node.children[1] != null) { stack.push(node.children[1])
stack.push(node.children[1]) }
} }
} return all
return all }
}
fun <T> findNearest(root: KDTreeNode<T>, item: T, dimensions: Int, mapper: (T, Int) -> Double): T? {
fun <T> findNearest(root: KDTreeNode<T>, item: T, dimensions: Int, mapper: (T, Int) -> Double): T? { var nearest = java.lang.Double.POSITIVE_INFINITY
var nearest = java.lang.Double.POSITIVE_INFINITY var nearestArg: KDTreeNode<T>? = null
var nearestArg: KDTreeNode<T>? = null
fun nearest(node: KDTreeNode<T>?, item: T) {
fun nearest(node: KDTreeNode<T>?, item: T) { if (node != null) {
if (node != null) {
if (node.item == null) {
if (node.item == null) { println(node)
println(node) }
}
val route: Int = if (mapper(item, node.dimension) < node.median) {
val route: Int = if (mapper(item, node.dimension) < node.median) { nearest(node.children[0], item)
nearest(node.children[0], item) 0
0 } else {
} else { nearest(node.children[1], item)
nearest(node.children[1], item) 1
1 }
}
val distance = sqrDistance(item, node.item
val distance = sqrDistance(item, node.item ?: throw IllegalStateException("item is null"), dimensions, mapper)
?: throw IllegalStateException("item is null"), dimensions, mapper) if (distance < nearest) {
if (distance < nearest) { nearest = distance
nearest = distance nearestArg = node
nearestArg = node }
}
val d = Math.abs(node.median - mapper(item, node.dimension))
val d = Math.abs(node.median - mapper(item, node.dimension)) if (d * d < nearest) {
if (d * d < nearest) { nearest(node.children[1 - route], item)
nearest(node.children[1 - route], item) }
} }
} }
} nearest(root, item)
nearest(root, item) return nearestArg?.item
return nearestArg?.item }
}
fun <T> insert(root: KDTreeNode<T>, item: T, dimensions: Int, mapper: (T, Int) -> Double): KDTreeNode<T> {
fun <T> insert(root: KDTreeNode<T>, item: T, dimensions: Int, mapper: (T, Int) -> Double): KDTreeNode<T> { val stack = Stack<KDTreeNode<T>>()
val stack = Stack<KDTreeNode<T>>() stack.push(root)
stack.push(root)
dive@ while (true) {
dive@ while (true) {
val node = stack.peek()
val node = stack.peek()
val value = mapper(item, node.dimension)
val value = mapper(item, node.dimension)
if (value < node.median) {
if (value < node.median) { if (node.children[0] != null) {
if (node.children[0] != null) { stack.push(node.children[0])
stack.push(node.children[0]) } else {
} else { // sit here
// sit here node.children[0] = KDTreeNode()
node.children[0] = KDTreeNode() node.children[0]?.item = item
node.children[0]?.item = item node.children[0]?.dimension = (node.dimension + 1) % dimensions
node.children[0]?.dimension = (node.dimension + 1) % dimensions node.children[0]?.median = mapper(item, (node.dimension + 1) % dimensions)
node.children[0]?.median = mapper(item, (node.dimension + 1) % dimensions) node.children[0]?.parent = node
node.children[0]?.parent = node return node.children[0] ?: throw IllegalStateException("child is null")
return node.children[0] ?: throw IllegalStateException("child is null") }
} } else {
} else { if (node.children[1] != null) {
if (node.children[1] != null) { stack.push(node.children[1])
stack.push(node.children[1]) } else {
} else { // sit here
// sit here node.children[1] = KDTreeNode()
node.children[1] = KDTreeNode() node.children[1]?.item = item
node.children[1]?.item = item node.children[1]?.dimension = (node.dimension + 1) % dimensions
node.children[1]?.dimension = (node.dimension + 1) % dimensions node.children[1]?.median = mapper(item, (node.dimension + 1) % dimensions)
node.children[1]?.median = mapper(item, (node.dimension + 1) % dimensions) node.children[1]?.parent = node
node.children[1]?.parent = node return node.children[1] ?: throw IllegalStateException("child is null")
return node.children[1] ?: throw IllegalStateException("child is null")
}
} }
} }
} }
}
fun <T> remove(toRemove: KDTreeNode<T>, mapper: (T, Int) -> Double): KDTreeNode<T>? {
fun <T> remove(toRemove: KDTreeNode<T>, mapper: (T, Int) -> Double): KDTreeNode<T>? { // trivial case
// trivial case if (toRemove.isLeaf) {
if (toRemove.isLeaf) { val p = toRemove.parent
val p = toRemove.parent if (p != null) {
if (p != null) { when {
when { p.children[0] === toRemove -> p.children[0] = null
p.children[0] === toRemove -> p.children[0] = null p.children[1] === toRemove -> p.children[1] = null
p.children[1] === toRemove -> p.children[1] = null else -> {
else -> { // broken!
// broken! }
} }
} } else {
} else { toRemove.item = null
toRemove.item = null }
} } else {
} else { val stack = Stack<KDTreeNode<T>>()
val stack = Stack<KDTreeNode<T>>()
var branch = 0
var branch = 0
if (toRemove.children[0] != null) {
if (toRemove.children[0] != null) { stack.push(toRemove.children[0])
stack.push(toRemove.children[0]) branch = 0
branch = 0 } else {
} else { stack.push(toRemove.children[1])
stack.push(toRemove.children[1]) branch = 1
branch = 1 }
}
var minValue: Double = java.lang.Double.POSITIVE_INFINITY
var minValue: Double = java.lang.Double.POSITIVE_INFINITY var maxValue: Double = java.lang.Double.NEGATIVE_INFINITY
var maxValue: Double = java.lang.Double.NEGATIVE_INFINITY var minArg: KDTreeNode<T>? = null
var minArg: KDTreeNode<T>? = null var maxArg: KDTreeNode<T>? = null
var maxArg: KDTreeNode<T>? = null
while (!stack.isEmpty()) {
while (!stack.isEmpty()) { val node = stack.pop() ?: throw RuntimeException("null on stack")
val node = stack.pop() ?: throw RuntimeException("null on stack")
val value = mapper(node.item ?: throw IllegalStateException("item is null"), toRemove.dimension)
val value = mapper(node.item ?: throw IllegalStateException("item is null"), toRemove.dimension)
if (value < minValue) {
if (value < minValue) { minValue = value
minValue = value minArg = node
minArg = node }
}
if (value > maxValue) {
if (value > maxValue) { maxValue = value
maxValue = value maxArg = node
maxArg = node }
}
if (node.dimension != toRemove.dimension) {
if (node.dimension != toRemove.dimension) { if (node.children[0] != null) {
if (node.children[0] != null) { stack.push(node.children[0])
stack.push(node.children[0]) }
} if (node.children[1] != null) {
if (node.children[1] != null) { stack.push(node.children[1])
stack.push(node.children[1]) }
} } else {
} else { if (branch == 1) {
if (branch == 1) { if (node.children[0] != null) {
if (node.children[0] != null) { stack.push(node.children[0])
stack.push(node.children[0]) } else {
} else { if (node.children[1] != null) {
if (node.children[1] != null) { stack.push(node.children[1])
stack.push(node.children[1]) }
} }
} }
} if (branch == 0) {
if (branch == 0) { if (node.children[1] != null) {
if (node.children[1] != null) { stack.push(node.children[1])
stack.push(node.children[1]) } else {
} else { if (node.children[0] != null) {
if (node.children[0] != null) { stack.push(node.children[0])
stack.push(node.children[0]) }
} }
} }
} }
} }
}
if (branch == 1) {
if (branch == 1) { toRemove.item = minArg?.item
toRemove.item = minArg?.item toRemove.median = mapper(minArg?.item ?: throw IllegalStateException("minArg is null"), toRemove.dimension)
toRemove.median = mapper(minArg?.item ?: throw IllegalStateException("minArg is null"), toRemove.dimension) remove(minArg, mapper)
remove(minArg, mapper) }
} if (branch == 0) {
if (branch == 0) { toRemove.item = maxArg?.item
toRemove.item = maxArg?.item toRemove.median = mapper(maxArg?.item ?: throw IllegalStateException("maxArg is null"), toRemove.dimension)
toRemove.median = mapper(maxArg?.item ?: throw IllegalStateException("maxArg is null"), toRemove.dimension) remove(maxArg, mapper)
remove(maxArg, mapper) }
} }
} return null
return null }
}

21
orx-no-clear/README.md Normal file
View File

@@ -0,0 +1,21 @@
# orx-no-clear
A simple OPENRNDR Extension that provides the classical drawing-without-clearing-the-screen functionality that
OPENRNDR does not support natively.
#### Usage
```
class NoClearProgram: Program() {
override fun setup() {
backgroundColor = ColorRGBa.PINK
extend(NoClear())
}
override fun draw() {
drawer.circle(Math.cos(seconds) * width / 2.0 + width / 2.0, Math.sin(seconds * 0.24) * height / 2.0 + height / 2.0, 20.0)
}
}
```

View File

@@ -0,0 +1,45 @@
import org.openrndr.Extension
import org.openrndr.Program
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.Drawer
import org.openrndr.draw.RenderTarget
import org.openrndr.draw.isolated
import org.openrndr.draw.renderTarget
class NoClear : Extension {
override var enabled: Boolean = true
private var renderTarget: RenderTarget? = null
override fun beforeDraw(drawer: Drawer, program: Program) {
if (renderTarget == null || renderTarget?.width != program.width || renderTarget?.height != program.height) {
renderTarget?.let {
it.colorBuffer(0).destroy()
it.detachColorBuffers()
it.destroy()
}
renderTarget = renderTarget(program.width, program.height) {
colorBuffer()
depthBuffer()
}
renderTarget?.let {
drawer.withTarget(it) {
background(program.backgroundColor ?: ColorRGBa.TRANSPARENT)
}
}
}
renderTarget?.bind()
}
override fun afterDraw(drawer: Drawer, program: Program) {
renderTarget?.unbind()
renderTarget?.let {
drawer.isolated {
drawer.ortho()
drawer.image(it.colorBuffer(0))
}
}
}
}

View File

@@ -1,5 +1,7 @@
rootProject.name = 'orx' rootProject.name = 'orx'
include 'orx-jumpflood', include 'orx-integral-image',
'orx-jumpflood',
'orx-kdtree', 'orx-kdtree',
'orx-integral-image' 'orx-no-clear'