This commit is contained in:
parent
f96da63987
commit
eff722ea05
11 changed files with 148 additions and 15 deletions
21
README.md
Normal file
21
README.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
# 3DOM
|
||||
|
||||
A SceneGraph written as if it's a DOM
|
||||
|
||||
```kt
|
||||
fun main() {
|
||||
val doc = Document()
|
||||
val node = doc.createNode<PlainNode>()
|
||||
node.model { setTranslation(10, 30, 3) }
|
||||
|
||||
doc.on<DOMTreeUpdate.Insert> { (ev) ->
|
||||
println("Node has been added at ${ev.absolute.translation}")
|
||||
}
|
||||
|
||||
doc.addNode(node)
|
||||
|
||||
doc.inRange(Vector3d(0, 0, 0), 50).forEach {
|
||||
println("Node found at ${it.absolute.translation}")
|
||||
}
|
||||
}
|
||||
```
|
|
@ -63,7 +63,7 @@ class Document : IDocument {
|
|||
private val byId: MutableMap<String, Long> = mutableMapOf()
|
||||
private val byClass: MutableMap<String, MutableSet<Long>> = mutableMapOf()
|
||||
|
||||
fun updateKDTreeForNode(node: INode<*>) {
|
||||
private fun updateKDTreeForNode(node: INode<*>) {
|
||||
node.updateAbsolute()
|
||||
kdTree.update(node)
|
||||
|
||||
|
|
|
@ -14,15 +14,24 @@ class EventTree(block: EventTree.() -> Unit = {}) {
|
|||
block(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an event listener for event [eventName] on top level, top level event listeners will be executed before all other event listeners
|
||||
*/
|
||||
fun addTopLevelEventListener(eventName: String, handler: (Event<*>) -> Unit) {
|
||||
this.topLevel.getOrPut(eventName, ::mutableSetOf).add(handler)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an event listener for event [eventName] on [target]
|
||||
*/
|
||||
fun addEventListener(eventName: String, target: INode<*>, handler: (Event<*>) -> Unit) {
|
||||
this.listeners.getOrPut(eventName, ::mutableMapOf).getOrPut(target.nodeId, ::mutableSetOf).add(handler)
|
||||
this.nodes.getOrPut(target.nodeId, ::mutableSetOf).add(eventName)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove node and all it's event listeners from event tree
|
||||
*/
|
||||
fun removeNode(node: INode<*>) {
|
||||
val events = this.nodes.remove(node.nodeId) ?: return
|
||||
for (event in events) {
|
||||
|
@ -30,6 +39,9 @@ class EventTree(block: EventTree.() -> Unit = {}) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger event [eventName] on [target]
|
||||
*/
|
||||
fun trigger(eventName: String, target: INode<*>, event: Event<*>) {
|
||||
this.topLevel[eventName]?.let {
|
||||
for (handler in it) {
|
||||
|
|
|
@ -14,8 +14,6 @@ interface IDocument : EventDispatcher, INodeContainer {
|
|||
fun findAt(x: Number, y: Number, z: Number) = findAt(Vector3d(x, y, z))
|
||||
fun findAt(vec: Vector3dc): Collection<INode<*>>
|
||||
fun rebalance()
|
||||
fun findInRegion(pointA: Vector3dc, pointB: Vector3dc): Sequence<INode<*>>
|
||||
fun findInRange(origin: Vector3dc, range: Number): Sequence<INode<*>>
|
||||
}
|
||||
|
||||
inline fun <reified T : INode<T>> IDocument.createNode() = createNode(T::class)
|
||||
|
|
|
@ -6,19 +6,64 @@ import org.joml.Matrix4dc
|
|||
import java.util.concurrent.atomic.AtomicLong
|
||||
|
||||
interface INode<T : INode<T>> : Comparable<INode<*>>, INodeContainer {
|
||||
/**
|
||||
* The ID of this node
|
||||
*/
|
||||
var id: String?
|
||||
|
||||
/**
|
||||
* Set with all class names assigned to this node
|
||||
*/
|
||||
val classList: MutableSet<String>
|
||||
|
||||
/**
|
||||
* Internal ID of this node, should be unique inside document
|
||||
*/
|
||||
val nodeId: Long
|
||||
|
||||
/**
|
||||
* Parent of this node
|
||||
*/
|
||||
val parentNode: INode<*>?
|
||||
|
||||
/**
|
||||
* Document this node belongs to
|
||||
*/
|
||||
val document: IDocument?
|
||||
|
||||
/**
|
||||
* Absolute matrix relative to document
|
||||
*/
|
||||
val absolute: Matrix4dc
|
||||
get() = (parentNode?.absolute ?: Matrix4d()) * model
|
||||
|
||||
/**
|
||||
* model matrix relative to parent node
|
||||
*/
|
||||
var model: Matrix4dc
|
||||
|
||||
/**
|
||||
* Clone this node
|
||||
*
|
||||
* @param deep also clone all child nodes
|
||||
*/
|
||||
fun clone(deep: Boolean): T
|
||||
|
||||
/**
|
||||
* Update the parent of this node, for consistency purposes.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
fun updateParent(refNode: INode<*>?): Boolean
|
||||
|
||||
/**
|
||||
* Detach this node from the owner document
|
||||
*/
|
||||
fun detachFromDocument()
|
||||
|
||||
/**
|
||||
* Check if [node] is parent or grand-parent of this node
|
||||
*/
|
||||
fun hasParent(node: INode<*>): Boolean {
|
||||
var current: INode<*>? = parentNode;
|
||||
|
||||
|
@ -34,6 +79,11 @@ interface INode<T : INode<T>> : Comparable<INode<*>>, INodeContainer {
|
|||
}
|
||||
|
||||
override fun compareTo(other: INode<*>): Int = this.nodeId.compareTo(other.nodeId)
|
||||
|
||||
/**
|
||||
* Update the absolute position of this node
|
||||
* @internal
|
||||
*/
|
||||
fun updateAbsolute()
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -1,14 +1,41 @@
|
|||
package me.eater.threedom.dom
|
||||
|
||||
interface INodeContainer : INodeQueryCapable {
|
||||
/**
|
||||
* Add node to this node container
|
||||
*/
|
||||
fun addNode(newNode: INode<*>)
|
||||
|
||||
/**
|
||||
* Remove node from this node container
|
||||
*/
|
||||
fun removeNode(refNode: INode<*>)
|
||||
|
||||
/**
|
||||
* Remove all nodes from this node container
|
||||
*/
|
||||
fun removeAll()
|
||||
|
||||
/**
|
||||
* Replace [newNode] with [refNode], if [newNode] is not part of this node, [refNode] will not be added
|
||||
*/
|
||||
fun replaceNode(newNode: INode<*>, refNode: INode<*>): Boolean
|
||||
|
||||
/**
|
||||
* Check if [refNode] is a child from this node
|
||||
*/
|
||||
fun hasChild(refNode: INode<*>): Boolean
|
||||
|
||||
/**
|
||||
* Return a sequence of (direct) child nodes
|
||||
*/
|
||||
fun sequence(): Sequence<INode<*>>
|
||||
|
||||
operator fun iterator(): Iterator<INode<*>>
|
||||
|
||||
/**
|
||||
* Return a recursive iterator which will iterate over all children, and their children etc.
|
||||
*/
|
||||
fun recursiveIterator(): Iterator<INode<*>> = sequence<INode<*>> {
|
||||
val iterators = mutableListOf<Iterator<INode<*>>>()
|
||||
var current: Iterator<INode<*>>? = iterator()
|
||||
|
|
|
@ -1,10 +1,25 @@
|
|||
package me.eater.threedom.dom
|
||||
|
||||
import me.eater.threedom.dom.query.NodeQuery
|
||||
import org.joml.Vector3dc
|
||||
|
||||
interface INodeQueryCapable {
|
||||
/**
|
||||
* Get node inside this node by [id]
|
||||
*/
|
||||
fun getNodeById(id: String): INode<*>?
|
||||
|
||||
/**
|
||||
* Get all nodes inside this node with the class [className]
|
||||
*/
|
||||
fun getNodesByClassName(className: String): Sequence<INode<*>>
|
||||
fun find(query: NodeQuery): Sequence<INode<*>> = emptySequence()
|
||||
fun findOne(query: NodeQuery): INode<*>? = find(query).firstOrNull()
|
||||
|
||||
/**
|
||||
* find all nodes inside this node in region between [pointA] and [pointB]
|
||||
*/
|
||||
fun findInRegion(pointA: Vector3dc, pointB: Vector3dc): Sequence<INode<*>>
|
||||
|
||||
/**
|
||||
* find all nodes inside this node in [range] of [origin]
|
||||
*/
|
||||
fun findInRange(origin: Vector3dc, range: Number): Sequence<INode<*>>
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import me.eater.threedom.utils.joml.mutable
|
|||
import me.eater.threedom.utils.joml.times
|
||||
import org.joml.Matrix4d
|
||||
import org.joml.Matrix4dc
|
||||
import org.joml.Vector3dc
|
||||
|
||||
abstract class Node<T : INode<T>>(document: IDocument?) : INode<T>, EventListener {
|
||||
override var document: IDocument? = document
|
||||
|
@ -197,4 +198,11 @@ abstract class Node<T : INode<T>>(document: IDocument?) : INode<T>, EventListene
|
|||
override fun updateAbsolute() {
|
||||
this.absolute = (this.parentNode?.absolute ?: Matrix4d()) * model
|
||||
}
|
||||
|
||||
override fun findInRegion(pointA: Vector3dc, pointB: Vector3dc): Sequence<INode<*>> =
|
||||
document?.findInRegion(pointA, pointB)?.filter { it.hasParent(this) } ?: emptySequence()
|
||||
|
||||
|
||||
override fun findInRange(origin: Vector3dc, range: Number): Sequence<INode<*>> =
|
||||
document?.findInRange(origin, range)?.filter { it.hasParent(this) } ?: emptySequence()
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import me.eater.threedom.dom.IDocument
|
|||
import me.eater.threedom.dom.INode
|
||||
import me.eater.threedom.utils.joml.Vector3d
|
||||
import me.eater.threedom.utils.joml.compareTo
|
||||
import me.eater.threedom.utils.joml.getTranslation
|
||||
import me.eater.threedom.utils.joml.translation
|
||||
import org.joml.Vector3dc
|
||||
|
||||
class KDTree(private val document: IDocument, private var root: Node = Node(Vector3d(0, 0, 0))) {
|
||||
|
@ -102,7 +102,7 @@ class KDTree(private val document: IDocument, private var root: Node = Node(Vect
|
|||
companion object {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun create(nodes: Collection<INode<*>>, depth: Long = 0): Node =
|
||||
create(nodes.groupBy({ it.absolute.getTranslation() }) { it.nodeId } as Map<Vector3dc, Collection<Long>>,
|
||||
create(nodes.groupBy({ it.absolute.translation }) { it.nodeId } as Map<Vector3dc, Collection<Long>>,
|
||||
depth)
|
||||
|
||||
fun create(nodes: Map<Vector3dc, Collection<Long>>, depth: Long = 0): Node {
|
||||
|
@ -139,13 +139,13 @@ class KDTree(private val document: IDocument, private var root: Node = Node(Vect
|
|||
constructor(document: IDocument, nodes: Collection<INode<*>>) : this(document, Node.create(nodes))
|
||||
|
||||
fun add(node: INode<*>) {
|
||||
val vec = node.absolute.getTranslation()
|
||||
val vec = node.absolute.translation
|
||||
nodeLocMap[node.nodeId] = vec
|
||||
root.add(vec, node.nodeId)
|
||||
}
|
||||
|
||||
fun remove(node: INode<*>) {
|
||||
root.remove(node.absolute.getTranslation(), node.nodeId)
|
||||
root.remove(node.absolute.translation, node.nodeId)
|
||||
nodeLocMap.remove(node.nodeId)
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,9 @@ import org.joml.Vector3dc
|
|||
fun <T : Number> Matrix4dc.setTranslation(x: T, y: T, z: T): Matrix4d =
|
||||
Matrix4d(this).setTranslation(x.toDouble(), y.toDouble(), z.toDouble())
|
||||
|
||||
fun Matrix4dc.getTranslation(): Vector3d = getTranslation(Vector3d())
|
||||
val Matrix4dc.translation: Vector3d
|
||||
get() = getTranslation(Vector3d())
|
||||
|
||||
fun Matrix4dc.mutable(): Matrix4d = if (this is Matrix4d) this else Matrix4d(this)
|
||||
|
||||
operator fun Matrix4dc.times(rhs: Matrix4dc) = mul(rhs, Matrix4d())
|
||||
|
|
|
@ -10,7 +10,7 @@ import me.eater.threedom.dom.IDocument
|
|||
import me.eater.threedom.dom.PlainNode
|
||||
import me.eater.threedom.dom.createNode
|
||||
import me.eater.threedom.utils.joml.Vector3d
|
||||
import me.eater.threedom.utils.joml.getTranslation
|
||||
import me.eater.threedom.utils.joml.translation
|
||||
import me.eater.threedom.utils.joml.setTranslation
|
||||
|
||||
class PositionTest : StringSpec({
|
||||
|
@ -19,13 +19,13 @@ class PositionTest : StringSpec({
|
|||
val node = doc.createNode<PlainNode>()
|
||||
doc.addNode(node)
|
||||
node.model { setTranslation(10, 0, 10) }
|
||||
node.absolute.getTranslation() shouldBe Vector3d(10, 0, 10)
|
||||
node.absolute.translation shouldBe Vector3d(10, 0, 10)
|
||||
val nodeTwo = doc.createNode<PlainNode>()
|
||||
node.addNode(nodeTwo)
|
||||
nodeTwo.model { setTranslation(-10, 20, 0) }
|
||||
nodeTwo.absolute.getTranslation() shouldBe Vector3d(0, 20, 10)
|
||||
nodeTwo.absolute.translation shouldBe Vector3d(0, 20, 10)
|
||||
doc.addNode(nodeTwo)
|
||||
nodeTwo.absolute.getTranslation() shouldBe Vector3d(-10, 20, 0)
|
||||
nodeTwo.absolute.translation shouldBe Vector3d(-10, 20, 0)
|
||||
}
|
||||
|
||||
"ensure position search tree works" {
|
||||
|
|
Loading…
Reference in a new issue