small fixes
continuous-integration/drone/push Build is passing Details

master
eater 5 years ago
parent f96da63987
commit eff722ea05
Signed by: eater
GPG Key ID: AD2560A0F84F0759

@ -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 byId: MutableMap<String, Long> = mutableMapOf()
private val byClass: MutableMap<String, MutableSet<Long>> = mutableMapOf() private val byClass: MutableMap<String, MutableSet<Long>> = mutableMapOf()
fun updateKDTreeForNode(node: INode<*>) { private fun updateKDTreeForNode(node: INode<*>) {
node.updateAbsolute() node.updateAbsolute()
kdTree.update(node) kdTree.update(node)

@ -14,15 +14,24 @@ class EventTree(block: EventTree.() -> Unit = {}) {
block(this) 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) { fun addTopLevelEventListener(eventName: String, handler: (Event<*>) -> Unit) {
this.topLevel.getOrPut(eventName, ::mutableSetOf).add(handler) 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) { fun addEventListener(eventName: String, target: INode<*>, handler: (Event<*>) -> Unit) {
this.listeners.getOrPut(eventName, ::mutableMapOf).getOrPut(target.nodeId, ::mutableSetOf).add(handler) this.listeners.getOrPut(eventName, ::mutableMapOf).getOrPut(target.nodeId, ::mutableSetOf).add(handler)
this.nodes.getOrPut(target.nodeId, ::mutableSetOf).add(eventName) this.nodes.getOrPut(target.nodeId, ::mutableSetOf).add(eventName)
} }
/**
* Remove node and all it's event listeners from event tree
*/
fun removeNode(node: INode<*>) { fun removeNode(node: INode<*>) {
val events = this.nodes.remove(node.nodeId) ?: return val events = this.nodes.remove(node.nodeId) ?: return
for (event in events) { 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<*>) { fun trigger(eventName: String, target: INode<*>, event: Event<*>) {
this.topLevel[eventName]?.let { this.topLevel[eventName]?.let {
for (handler in it) { 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(x: Number, y: Number, z: Number) = findAt(Vector3d(x, y, z))
fun findAt(vec: Vector3dc): Collection<INode<*>> fun findAt(vec: Vector3dc): Collection<INode<*>>
fun rebalance() 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) 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 import java.util.concurrent.atomic.AtomicLong
interface INode<T : INode<T>> : Comparable<INode<*>>, INodeContainer { interface INode<T : INode<T>> : Comparable<INode<*>>, INodeContainer {
/**
* The ID of this node
*/
var id: String? var id: String?
/**
* Set with all class names assigned to this node
*/
val classList: MutableSet<String> val classList: MutableSet<String>
/**
* Internal ID of this node, should be unique inside document
*/
val nodeId: Long val nodeId: Long
/**
* Parent of this node
*/
val parentNode: INode<*>? val parentNode: INode<*>?
/**
* Document this node belongs to
*/
val document: IDocument? val document: IDocument?
/**
* Absolute matrix relative to document
*/
val absolute: Matrix4dc val absolute: Matrix4dc
get() = (parentNode?.absolute ?: Matrix4d()) * model get() = (parentNode?.absolute ?: Matrix4d()) * model
/**
* model matrix relative to parent node
*/
var model: Matrix4dc var model: Matrix4dc
/**
* Clone this node
*
* @param deep also clone all child nodes
*/
fun clone(deep: Boolean): T fun clone(deep: Boolean): T
/**
* Update the parent of this node, for consistency purposes.
*
* @internal
*/
fun updateParent(refNode: INode<*>?): Boolean fun updateParent(refNode: INode<*>?): Boolean
/**
* Detach this node from the owner document
*/
fun detachFromDocument() fun detachFromDocument()
/**
* Check if [node] is parent or grand-parent of this node
*/
fun hasParent(node: INode<*>): Boolean { fun hasParent(node: INode<*>): Boolean {
var current: INode<*>? = parentNode; 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) override fun compareTo(other: INode<*>): Int = this.nodeId.compareTo(other.nodeId)
/**
* Update the absolute position of this node
* @internal
*/
fun updateAbsolute() fun updateAbsolute()
companion object { companion object {

@ -1,14 +1,41 @@
package me.eater.threedom.dom package me.eater.threedom.dom
interface INodeContainer : INodeQueryCapable { interface INodeContainer : INodeQueryCapable {
/**
* Add node to this node container
*/
fun addNode(newNode: INode<*>) fun addNode(newNode: INode<*>)
/**
* Remove node from this node container
*/
fun removeNode(refNode: INode<*>) fun removeNode(refNode: INode<*>)
/**
* Remove all nodes from this node container
*/
fun removeAll() 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 fun replaceNode(newNode: INode<*>, refNode: INode<*>): Boolean
/**
* Check if [refNode] is a child from this node
*/
fun hasChild(refNode: INode<*>): Boolean fun hasChild(refNode: INode<*>): Boolean
/**
* Return a sequence of (direct) child nodes
*/
fun sequence(): Sequence<INode<*>> fun sequence(): Sequence<INode<*>>
operator fun iterator(): Iterator<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<*>> { fun recursiveIterator(): Iterator<INode<*>> = sequence<INode<*>> {
val iterators = mutableListOf<Iterator<INode<*>>>() val iterators = mutableListOf<Iterator<INode<*>>>()
var current: Iterator<INode<*>>? = iterator() var current: Iterator<INode<*>>? = iterator()

@ -1,10 +1,25 @@
package me.eater.threedom.dom package me.eater.threedom.dom
import me.eater.threedom.dom.query.NodeQuery import org.joml.Vector3dc
interface INodeQueryCapable { interface INodeQueryCapable {
/**
* Get node inside this node by [id]
*/
fun getNodeById(id: String): INode<*>? fun getNodeById(id: String): INode<*>?
/**
* Get all nodes inside this node with the class [className]
*/
fun getNodesByClassName(className: String): Sequence<INode<*>> 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 me.eater.threedom.utils.joml.times
import org.joml.Matrix4d import org.joml.Matrix4d
import org.joml.Matrix4dc import org.joml.Matrix4dc
import org.joml.Vector3dc
abstract class Node<T : INode<T>>(document: IDocument?) : INode<T>, EventListener { abstract class Node<T : INode<T>>(document: IDocument?) : INode<T>, EventListener {
override var document: IDocument? = document override var document: IDocument? = document
@ -197,4 +198,11 @@ abstract class Node<T : INode<T>>(document: IDocument?) : INode<T>, EventListene
override fun updateAbsolute() { override fun updateAbsolute() {
this.absolute = (this.parentNode?.absolute ?: Matrix4d()) * model 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.dom.INode
import me.eater.threedom.utils.joml.Vector3d import me.eater.threedom.utils.joml.Vector3d
import me.eater.threedom.utils.joml.compareTo 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 import org.joml.Vector3dc
class KDTree(private val document: IDocument, private var root: Node = Node(Vector3d(0, 0, 0))) { 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 { companion object {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
fun create(nodes: Collection<INode<*>>, depth: Long = 0): Node = 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) depth)
fun create(nodes: Map<Vector3dc, Collection<Long>>, depth: Long = 0): Node { 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)) constructor(document: IDocument, nodes: Collection<INode<*>>) : this(document, Node.create(nodes))
fun add(node: INode<*>) { fun add(node: INode<*>) {
val vec = node.absolute.getTranslation() val vec = node.absolute.translation
nodeLocMap[node.nodeId] = vec nodeLocMap[node.nodeId] = vec
root.add(vec, node.nodeId) root.add(vec, node.nodeId)
} }
fun remove(node: INode<*>) { fun remove(node: INode<*>) {
root.remove(node.absolute.getTranslation(), node.nodeId) root.remove(node.absolute.translation, node.nodeId)
nodeLocMap.remove(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 = fun <T : Number> Matrix4dc.setTranslation(x: T, y: T, z: T): Matrix4d =
Matrix4d(this).setTranslation(x.toDouble(), y.toDouble(), z.toDouble()) 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) fun Matrix4dc.mutable(): Matrix4d = if (this is Matrix4d) this else Matrix4d(this)
operator fun Matrix4dc.times(rhs: Matrix4dc) = mul(rhs, Matrix4d()) 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.PlainNode
import me.eater.threedom.dom.createNode import me.eater.threedom.dom.createNode
import me.eater.threedom.utils.joml.Vector3d 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 import me.eater.threedom.utils.joml.setTranslation
class PositionTest : StringSpec({ class PositionTest : StringSpec({
@ -19,13 +19,13 @@ class PositionTest : StringSpec({
val node = doc.createNode<PlainNode>() val node = doc.createNode<PlainNode>()
doc.addNode(node) doc.addNode(node)
node.model { setTranslation(10, 0, 10) } 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>() val nodeTwo = doc.createNode<PlainNode>()
node.addNode(nodeTwo) node.addNode(nodeTwo)
nodeTwo.model { setTranslation(-10, 20, 0) } 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) doc.addNode(nodeTwo)
nodeTwo.absolute.getTranslation() shouldBe Vector3d(-10, 20, 0) nodeTwo.absolute.translation shouldBe Vector3d(-10, 20, 0)
} }
"ensure position search tree works" { "ensure position search tree works" {

Loading…
Cancel
Save