From 3993085197ac11c530dec3e38f531eecb73982dc Mon Sep 17 00:00:00 2001 From: Jonathan Ellis Date: Tue, 18 Jan 2022 12:36:28 -0600 Subject: [PATCH] Add quadtree nearestToPoint, and Readwrite locking (#217) --- .../src/commonMain/kotlin/IQuadtree.kt | 45 ++++++++++++++++ .../src/commonMain/kotlin/Quadtree.kt | 52 +++++++++++++++++-- .../src/jvmMain/kotlin/ReadwriteQuadtree.kt | 44 ++++++++++++++++ 3 files changed, 136 insertions(+), 5 deletions(-) create mode 100644 orx-quadtree/src/commonMain/kotlin/IQuadtree.kt create mode 100644 orx-quadtree/src/jvmMain/kotlin/ReadwriteQuadtree.kt diff --git a/orx-quadtree/src/commonMain/kotlin/IQuadtree.kt b/orx-quadtree/src/commonMain/kotlin/IQuadtree.kt new file mode 100644 index 00000000..f7e2a94a --- /dev/null +++ b/orx-quadtree/src/commonMain/kotlin/IQuadtree.kt @@ -0,0 +1,45 @@ +package org.openrndr.extra.quadtree + +import org.openrndr.math.Vector2 + +interface IQuadtree { + /** + * Clears the whole tree + */ + fun clear() + + /** + * Finds the nearest and neighbouring objects within a radius + * (needs to have a different name so there is no ambiguity when the generic object type is Vector2) + * + * @param element + * @param radius + * @return + */ + fun nearestToPoint(point: Vector2, radius: Double): QuadtreeQuery? + + /** + * Finds the nearest and neighbouring points within a radius + * + * @param element + * @param radius + * @return + */ + fun nearest(element: T, radius: Double): QuadtreeQuery? + + /** + * Inserts the element in the appropriate node + * + * @param element + * @return + */ + fun insert(element: T): Boolean + + /** + * Finds which node the element is within (but not necessarily belonging to) + * + * @param element + * @return + */ + fun findNode(element: T): Quadtree? +} \ No newline at end of file diff --git a/orx-quadtree/src/commonMain/kotlin/Quadtree.kt b/orx-quadtree/src/commonMain/kotlin/Quadtree.kt index 985a08ad..8b63227b 100644 --- a/orx-quadtree/src/commonMain/kotlin/Quadtree.kt +++ b/orx-quadtree/src/commonMain/kotlin/Quadtree.kt @@ -16,7 +16,7 @@ data class QuadtreeQuery(val nearest: T, val neighbours: List, val quads: * @property maxObjects maximum number of objects per node * @property mapper */ -class Quadtree(val bounds: Rectangle, val maxObjects: Int = 10, val mapper: ((T) -> Vector2)) { +class Quadtree(val bounds: Rectangle, val maxObjects: Int = 10, val mapper: ((T) -> Vector2)) : IQuadtree { /** * The 4 nodes of the tree */ @@ -30,7 +30,7 @@ class Quadtree(val bounds: Rectangle, val maxObjects: Int = 10, val mapper: ( /** * Clears the whole tree */ - fun clear() { + override fun clear() { objects.clear() for (i in nodes.indices) { @@ -42,6 +42,48 @@ class Quadtree(val bounds: Rectangle, val maxObjects: Int = 10, val mapper: ( } } + /** + * Finds the nearest and neighbouring objects within a radius + * (needs to have a different name so there is no ambiguity when the generic object type is Vector2) + * + * @param point + * @param radius + * @return + */ + override fun nearestToPoint(point: Vector2, radius: Double): QuadtreeQuery? { + if (!bounds.contains(point)) return null + + val r2 = radius * radius + + val scaledBounds = Rectangle.fromCenter(point, radius * 2) + val intersected: List> = intersect(scaledBounds) ?: return null + + var minDist = Double.MAX_VALUE + val nearestObjects = mutableListOf() + var nearestObject: T? = null + + for (interNode in intersected) { + for (obj in interNode.objects) { + val p = mapper(obj) + + val dist = p.squaredDistanceTo(point) + + if (dist < r2) { + nearestObjects.add(obj) + + if (dist < minDist) { + minDist = dist + nearestObject = obj + } + } + } + } + + if (nearestObject == null) return null + + return QuadtreeQuery(nearestObject, nearestObjects, intersected) + } + /** * Finds the nearest and neighbouring points within a radius * @@ -49,7 +91,7 @@ class Quadtree(val bounds: Rectangle, val maxObjects: Int = 10, val mapper: ( * @param radius * @return */ - fun nearest(element: T, radius: Double): QuadtreeQuery? { + override fun nearest(element: T, radius: Double): QuadtreeQuery? { val point = mapper(element) if (!bounds.contains(point)) return null @@ -92,7 +134,7 @@ class Quadtree(val bounds: Rectangle, val maxObjects: Int = 10, val mapper: ( * @param element * @return */ - fun insert(element: T): Boolean { + override fun insert(element: T): Boolean { // only* the root needs to check this if (depth == 0) { if (!bounds.contains(mapper(element))) return false @@ -128,7 +170,7 @@ class Quadtree(val bounds: Rectangle, val maxObjects: Int = 10, val mapper: ( * @param element * @return */ - fun findNode(element: T): Quadtree? { + override fun findNode(element: T): Quadtree? { val v = mapper(element) if (!bounds.contains(v)) return null diff --git a/orx-quadtree/src/jvmMain/kotlin/ReadwriteQuadtree.kt b/orx-quadtree/src/jvmMain/kotlin/ReadwriteQuadtree.kt new file mode 100644 index 00000000..5417d3b5 --- /dev/null +++ b/orx-quadtree/src/jvmMain/kotlin/ReadwriteQuadtree.kt @@ -0,0 +1,44 @@ +package org.openrndr.extra.quadtree + +import org.openrndr.math.Vector2 +import java.util.concurrent.locks.ReentrantReadWriteLock +import kotlin.concurrent.read +import kotlin.concurrent.write + +/** + * Wraps a quadtree with a ReentrantReadWriteLock, which allows multiple concurrent + * readers or one writer at a time. + */ +class ReadwriteQuadtree(val qt: Quadtree) : IQuadtree { + val lock = ReentrantReadWriteLock() + + override fun clear() { + lock.write { + qt.clear() + } + } + + override fun nearestToPoint(point: Vector2, radius: Double): QuadtreeQuery? { + lock.read { + return qt.nearestToPoint(point, radius) + } + } + + override fun nearest(element: T, radius: Double): QuadtreeQuery? { + lock.read { + return qt.nearest(element, radius) + } + } + + override fun insert(element: T): Boolean { + lock.write { + return qt.insert(element) + } + } + + override fun findNode(element: T): Quadtree? { + lock.read { + return qt.findNode(element) + } + } +} \ No newline at end of file