Browse Source

small fixes

master
eater 2 years ago
parent
commit
eff722ea05
Signed by: eater GPG Key ID: AD2560A0F84F0759
  1. 21
      README.md
  2. 2
      threedom/src/main/kotlin/me/eater/threedom/dom/Document.kt
  3. 12
      threedom/src/main/kotlin/me/eater/threedom/dom/EventTree.kt
  4. 2
      threedom/src/main/kotlin/me/eater/threedom/dom/IDocument.kt
  5. 50
      threedom/src/main/kotlin/me/eater/threedom/dom/INode.kt
  6. 27
      threedom/src/main/kotlin/me/eater/threedom/dom/INodeContainer.kt
  7. 21
      threedom/src/main/kotlin/me/eater/threedom/dom/INodeQueryCapable.kt
  8. 8
      threedom/src/main/kotlin/me/eater/threedom/dom/Node.kt
  9. 8
      threedom/src/main/kotlin/me/eater/threedom/utils/KDTree.kt
  10. 4
      threedom/src/main/kotlin/me/eater/threedom/utils/joml/JOML.kt
  11. 8
      threedom/src/test/kotlin/me/eater/test/threedom/dom/PositionTest.kt

21
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<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}")
}
}
```

2
threedom/src/main/kotlin/me/eater/threedom/dom/Document.kt

@ -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)

12
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) {

2
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<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)

50
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<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 {

27
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<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()

21
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<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<*>>
}

8
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<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()
}

8
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<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)
}

4
threedom/src/main/kotlin/me/eater/threedom/utils/joml/JOML.kt

@ -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())

8
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<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…
Cancel
Save