From eff722ea056ce8b04f50b2b45f69b870acd72af8 Mon Sep 17 00:00:00 2001 From: eater <=@eater.me> Date: Sun, 17 May 2020 18:17:54 +0200 Subject: [PATCH] small fixes --- README.md | 21 ++++++++ .../kotlin/me/eater/threedom/dom/Document.kt | 2 +- .../kotlin/me/eater/threedom/dom/EventTree.kt | 12 +++++ .../kotlin/me/eater/threedom/dom/IDocument.kt | 2 - .../kotlin/me/eater/threedom/dom/INode.kt | 50 +++++++++++++++++++ .../me/eater/threedom/dom/INodeContainer.kt | 27 ++++++++++ .../eater/threedom/dom/INodeQueryCapable.kt | 21 ++++++-- .../main/kotlin/me/eater/threedom/dom/Node.kt | 8 +++ .../kotlin/me/eater/threedom/utils/KDTree.kt | 8 +-- .../me/eater/threedom/utils/joml/JOML.kt | 4 +- .../eater/test/threedom/dom/PositionTest.kt | 8 +-- 11 files changed, 148 insertions(+), 15 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..6edb75c --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# 3DOM + +A SceneGraph written as if it's a DOM + +```kt +fun main() { + val doc = Document() + val node = doc.createNode() + node.model { setTranslation(10, 30, 3) } + + doc.on { (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}") + } +} +``` diff --git a/threedom/src/main/kotlin/me/eater/threedom/dom/Document.kt b/threedom/src/main/kotlin/me/eater/threedom/dom/Document.kt index 6691ccb..eb1528a 100644 --- a/threedom/src/main/kotlin/me/eater/threedom/dom/Document.kt +++ b/threedom/src/main/kotlin/me/eater/threedom/dom/Document.kt @@ -63,7 +63,7 @@ class Document : IDocument { private val byId: MutableMap = mutableMapOf() private val byClass: MutableMap> = mutableMapOf() - fun updateKDTreeForNode(node: INode<*>) { + private fun updateKDTreeForNode(node: INode<*>) { node.updateAbsolute() kdTree.update(node) diff --git a/threedom/src/main/kotlin/me/eater/threedom/dom/EventTree.kt b/threedom/src/main/kotlin/me/eater/threedom/dom/EventTree.kt index 4f61ef4..4e6d5a7 100644 --- a/threedom/src/main/kotlin/me/eater/threedom/dom/EventTree.kt +++ b/threedom/src/main/kotlin/me/eater/threedom/dom/EventTree.kt @@ -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) { diff --git a/threedom/src/main/kotlin/me/eater/threedom/dom/IDocument.kt b/threedom/src/main/kotlin/me/eater/threedom/dom/IDocument.kt index 7158ad5..1f8c0b0 100644 --- a/threedom/src/main/kotlin/me/eater/threedom/dom/IDocument.kt +++ b/threedom/src/main/kotlin/me/eater/threedom/dom/IDocument.kt @@ -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> fun rebalance() - fun findInRegion(pointA: Vector3dc, pointB: Vector3dc): Sequence> - fun findInRange(origin: Vector3dc, range: Number): Sequence> } inline fun > IDocument.createNode() = createNode(T::class) diff --git a/threedom/src/main/kotlin/me/eater/threedom/dom/INode.kt b/threedom/src/main/kotlin/me/eater/threedom/dom/INode.kt index 60f9c80..5580435 100644 --- a/threedom/src/main/kotlin/me/eater/threedom/dom/INode.kt +++ b/threedom/src/main/kotlin/me/eater/threedom/dom/INode.kt @@ -6,19 +6,64 @@ import org.joml.Matrix4dc import java.util.concurrent.atomic.AtomicLong interface INode> : Comparable>, INodeContainer { + /** + * The ID of this node + */ var id: String? + + /** + * Set with all class names assigned to this node + */ val classList: MutableSet + + /** + * 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> : Comparable>, INodeContainer { } override fun compareTo(other: INode<*>): Int = this.nodeId.compareTo(other.nodeId) + + /** + * Update the absolute position of this node + * @internal + */ fun updateAbsolute() companion object { diff --git a/threedom/src/main/kotlin/me/eater/threedom/dom/INodeContainer.kt b/threedom/src/main/kotlin/me/eater/threedom/dom/INodeContainer.kt index e801482..d407f3e 100644 --- a/threedom/src/main/kotlin/me/eater/threedom/dom/INodeContainer.kt +++ b/threedom/src/main/kotlin/me/eater/threedom/dom/INodeContainer.kt @@ -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> operator fun iterator(): Iterator> + + /** + * Return a recursive iterator which will iterate over all children, and their children etc. + */ fun recursiveIterator(): Iterator> = sequence> { val iterators = mutableListOf>>() var current: Iterator>? = iterator() diff --git a/threedom/src/main/kotlin/me/eater/threedom/dom/INodeQueryCapable.kt b/threedom/src/main/kotlin/me/eater/threedom/dom/INodeQueryCapable.kt index c7c87dc..22a0420 100644 --- a/threedom/src/main/kotlin/me/eater/threedom/dom/INodeQueryCapable.kt +++ b/threedom/src/main/kotlin/me/eater/threedom/dom/INodeQueryCapable.kt @@ -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> - fun find(query: NodeQuery): Sequence> = 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> + + /** + * find all nodes inside this node in [range] of [origin] + */ + fun findInRange(origin: Vector3dc, range: Number): Sequence> } diff --git a/threedom/src/main/kotlin/me/eater/threedom/dom/Node.kt b/threedom/src/main/kotlin/me/eater/threedom/dom/Node.kt index e9be264..5cad9c0 100644 --- a/threedom/src/main/kotlin/me/eater/threedom/dom/Node.kt +++ b/threedom/src/main/kotlin/me/eater/threedom/dom/Node.kt @@ -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>(document: IDocument?) : INode, EventListener { override var document: IDocument? = document @@ -197,4 +198,11 @@ abstract class Node>(document: IDocument?) : INode, EventListene override fun updateAbsolute() { this.absolute = (this.parentNode?.absolute ?: Matrix4d()) * model } + + override fun findInRegion(pointA: Vector3dc, pointB: Vector3dc): Sequence> = + document?.findInRegion(pointA, pointB)?.filter { it.hasParent(this) } ?: emptySequence() + + + override fun findInRange(origin: Vector3dc, range: Number): Sequence> = + document?.findInRange(origin, range)?.filter { it.hasParent(this) } ?: emptySequence() } diff --git a/threedom/src/main/kotlin/me/eater/threedom/utils/KDTree.kt b/threedom/src/main/kotlin/me/eater/threedom/utils/KDTree.kt index 6371490..9a13290 100644 --- a/threedom/src/main/kotlin/me/eater/threedom/utils/KDTree.kt +++ b/threedom/src/main/kotlin/me/eater/threedom/utils/KDTree.kt @@ -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>, depth: Long = 0): Node = - create(nodes.groupBy({ it.absolute.getTranslation() }) { it.nodeId } as Map>, + create(nodes.groupBy({ it.absolute.translation }) { it.nodeId } as Map>, depth) fun create(nodes: Map>, 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>) : 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) } diff --git a/threedom/src/main/kotlin/me/eater/threedom/utils/joml/JOML.kt b/threedom/src/main/kotlin/me/eater/threedom/utils/joml/JOML.kt index 0784a1c..b08c35c 100644 --- a/threedom/src/main/kotlin/me/eater/threedom/utils/joml/JOML.kt +++ b/threedom/src/main/kotlin/me/eater/threedom/utils/joml/JOML.kt @@ -8,7 +8,9 @@ import org.joml.Vector3dc fun 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()) diff --git a/threedom/src/test/kotlin/me/eater/test/threedom/dom/PositionTest.kt b/threedom/src/test/kotlin/me/eater/test/threedom/dom/PositionTest.kt index 27751cd..6f7b48d 100644 --- a/threedom/src/test/kotlin/me/eater/test/threedom/dom/PositionTest.kt +++ b/threedom/src/test/kotlin/me/eater/test/threedom/dom/PositionTest.kt @@ -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() 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() 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" {