Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3D GUI updates #792

Draft
wants to merge 20 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
95e4a8d
OpenVRHMD: set initialized to false before shutting down VR to preven…
smlpt Nov 18, 2024
488d8db
Fix volume centering with anisotropic values
smlpt Dec 17, 2024
5f9c349
Porting VR UI elements from microscenery
smlpt Jan 14, 2025
0b7ebc5
Stealing VR behavior changes from Jans branch
smlpt Jan 14, 2025
05c0692
Added VR3DGui adapted from microscenery
smlpt Jan 14, 2025
2fed0a4
Added Switch adapted from microscenery
smlpt Jan 14, 2025
38632eb
Button: added option for passing color and pressedColor
smlpt Jan 14, 2025
6cdc028
Button: allow buttons to be triggered by touch only
smlpt Jan 16, 2025
e6f9298
VRTouch: add option to interact via a custom node instead of controll…
smlpt Jan 16, 2025
e23e515
WheelMenu: make compatible with new signature of SimplePressable
smlpt Jan 16, 2025
36d70bc
Add horizontal centering for Columns
smlpt Jan 16, 2025
1017fc2
Unbreak some behavior API to make it compatible with the tests
smlpt Jan 16, 2025
c0d9e09
Button: add depressDelay parameter
smlpt Jan 30, 2025
26ba645
DefaultNode: optimize code readability in getMaximumBoundingBox
smlpt Jan 30, 2025
169fce4
VRTouch: only interact with visible nodes. Remove redundant onTouch call
smlpt Jan 31, 2025
9e5dedc
Button: Fix depressDelay logic
smlpt Jan 31, 2025
49e610d
Add includeChildren as flag to generateBoundingBox and as attribute t…
smlpt Feb 3, 2025
5c6a275
DefaultGeometry: consider child scale transform for generating boundi…
smlpt Feb 3, 2025
7ca74b7
TextBox: refresh bounding box after updating text
smlpt Feb 3, 2025
e84507a
OrientedBoundingBox: always test intersection with bounding sphere fi…
smlpt Feb 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/main/kotlin/graphics/scenery/BoundingGrid.kt
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ open class BoundingGrid : Mesh("Bounding Grid") {
/** Slack around transparent objects, 2% by default. */
var slack = 0.02f

/** Whether to consider the node's children in bounding box generation. */
var includeChildren = true

/** The [Node] this bounding grid is attached to. Set to null to remove. */
var node: Node? = null
set(value) {
Expand Down Expand Up @@ -130,7 +133,11 @@ open class BoundingGrid : Mesh("Bounding Grid") {

protected fun updateFromNode() {
node?.let { node ->
val maxBoundingBox = node.getMaximumBoundingBox()
val maxBoundingBox = if (includeChildren) {
node.getMaximumBoundingBox()
} else {
node.generateBoundingBox(false) ?: OrientedBoundingBox(node, Vector3f(0f), Vector3f(0f))
}
nodeBoundingBoxHash = maxBoundingBox.hashCode()


Expand Down
19 changes: 13 additions & 6 deletions src/main/kotlin/graphics/scenery/DefaultNode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -103,17 +103,24 @@ open class DefaultNode(name: String = "Node") : Node, Networkable {
}

override fun getMaximumBoundingBox(): OrientedBoundingBox {
if(boundingBox == null && children.size == 0) {
return OrientedBoundingBox(this,0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)
if (boundingBox == null && children.size == 0) {
return OrientedBoundingBox(this, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)
}

if(children.none { it !is BoundingGrid }) {
return OrientedBoundingBox(this,boundingBox?.min ?: Vector3f(0.0f, 0.0f, 0.0f), boundingBox?.max ?: Vector3f(0.0f, 0.0f, 0.0f))
if (children.none { it !is BoundingGrid }) {
return OrientedBoundingBox(
this,
boundingBox?.min ?: Vector3f(0.0f, 0.0f, 0.0f),
boundingBox?.max ?: Vector3f(0.0f, 0.0f, 0.0f)
)
}

return children
.filter { it !is BoundingGrid }.map { it.getMaximumBoundingBox().translate(it.spatialOrNull()?.position ?: Vector3f(0f, 0f, 0f)) }
.fold(boundingBox ?: OrientedBoundingBox(this, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f), { lhs, rhs -> lhs.expand(lhs, rhs) })
.filter { it !is BoundingGrid }
.map { it.getMaximumBoundingBox().translate(it.spatialOrNull()?.position ?: Vector3f(0f, 0f, 0f)) }
.fold(
boundingBox ?: OrientedBoundingBox(this, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)
) { lhs, rhs -> lhs.expand(lhs, rhs) }
}

override fun runRecursive(func: (Node) -> Unit) {
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/graphics/scenery/InstancedNode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ open class InstancedNode(template: Node, name: String = "InstancedNode") : Defau
return node
}

override fun generateBoundingBox(): OrientedBoundingBox? {
override fun generateBoundingBox(includeChildren: Boolean): OrientedBoundingBox? {
//TODO? generate joint boundingbox of all instances, set bounding box
return template?.generateBoundingBox()
}
Expand All @@ -73,7 +73,7 @@ open class InstancedNode(template: Node, name: String = "InstancedNode") : Defau
addSpatial()
}

override fun generateBoundingBox(): OrientedBoundingBox? {
override fun generateBoundingBox(includeChildren: Boolean): OrientedBoundingBox? {
return instancedParent.template?.generateBoundingBox()
}
}
Expand Down
12 changes: 9 additions & 3 deletions src/main/kotlin/graphics/scenery/Node.kt
Original file line number Diff line number Diff line change
Expand Up @@ -180,14 +180,20 @@ interface Node : Networkable {
* geometry information into consideration if this Node implements [Geometry].
* In case a bounding box cannot be determined, the function will return null.
*/
fun generateBoundingBox(): OrientedBoundingBox? {
fun generateBoundingBox(includeChildren: Boolean = true): OrientedBoundingBox? {
val geometry = geometryOrNull()
if(geometry == null) {
logger.warn("$name: Assuming 3rd party BB generation")
return boundingBox
} else {
boundingBox = geometry.generateBoundingBox(children)
return boundingBox
if (includeChildren) {
boundingBox = geometry.generateBoundingBox(children)
return boundingBox
} else {
boundingBox = geometry.generateBoundingBox(listOf())
return boundingBox
}

}
}

Expand Down
10 changes: 6 additions & 4 deletions src/main/kotlin/graphics/scenery/OrientedBoundingBox.kt
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,14 @@ open class OrientedBoundingBox(val n: Node, val min: Vector3f, val max: Vector3f
* Checks this [OrientedBoundingBox] for intersection with [other], and returns
* true if the bounding boxes do intersect.
*
* If [precise] is true, the intersection test will be performed using oriented bounding boxes (OBBs),
* otherwise, a faster, but less precise bounding sphere test is performed.
* If [precise] is true, the intersection test will still test with the less precise bounding sphere test first,
* and if it returns true a more precise test will be performed using oriented bounding boxes (OBBs).
*/
@JvmOverloads
fun intersects(other: OrientedBoundingBox, precise: Boolean = false): Boolean {
return if(precise) {
val approxResult =
other.getBoundingSphere().radius + getBoundingSphere().radius > (other.getBoundingSphere().origin - getBoundingSphere().origin).length()
return if(precise && approxResult) {
Intersectionf.testObOb(
this.center,
this.n.spatialOrNull()!!.localX,
Expand All @@ -90,7 +92,7 @@ open class OrientedBoundingBox(val n: Node, val min: Vector3f, val max: Vector3f
other.halfSize
)
} else {
other.getBoundingSphere().radius + getBoundingSphere().radius > (other.getBoundingSphere().origin - getBoundingSphere().origin).length()
return approxResult
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import graphics.scenery.BufferUtils
import graphics.scenery.Node
import graphics.scenery.OrientedBoundingBox
import graphics.scenery.geometry.GeometryType
import graphics.scenery.utils.extensions.times
import graphics.scenery.utils.lazyLogger
import org.joml.Vector3f
import java.nio.FloatBuffer
Expand Down Expand Up @@ -58,10 +59,11 @@ open class DefaultGeometry(private var node: Node): Geometry {
boundingBoxCoords[3] = maxOf(boundingBoxCoords[3], vertex[1])
boundingBoxCoords[5] = maxOf(boundingBoxCoords[5], vertex[2])
}
val scale = node.spatialOrNull()?.scale ?: Vector3f(1f)
logger.debug("$node.name: Calculated bounding box with ${boundingBoxCoords.joinToString(", ")}")
return OrientedBoundingBox(
node, Vector3f(boundingBoxCoords[0], boundingBoxCoords[2], boundingBoxCoords[4]),
Vector3f(boundingBoxCoords[1], boundingBoxCoords[3], boundingBoxCoords[5])
node, Vector3f(boundingBoxCoords[0], boundingBoxCoords[2], boundingBoxCoords[4]).times(scale),
Vector3f(boundingBoxCoords[1], boundingBoxCoords[3], boundingBoxCoords[5]).times(scale)
)
}
}
Expand Down
1 change: 1 addition & 0 deletions src/main/kotlin/graphics/scenery/controls/OpenVRHMD.kt
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ open class OpenVRHMD(val seated: Boolean = false, val useCompositor: Boolean = t
* Runs the OpenVR shutdown hooks
*/
fun close() {
initialized = false
VR_ShutdownInternal()
}

Expand Down
73 changes: 49 additions & 24 deletions src/main/kotlin/graphics/scenery/controls/behaviours/VRGrab.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import graphics.scenery.utils.extensions.plus
import org.joml.Quaternionf
import org.joml.Vector3f
import org.scijava.ui.behaviour.DragBehaviour
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Future


/**
Expand All @@ -25,38 +27,48 @@ import org.scijava.ui.behaviour.DragBehaviour
*
* @param targets Only nodes in this list may be dragged. They must have a [Grabable] attribute.
* @param multiTarget If this is true all targets which collide with [controllerHitbox] will be dragged otherwise only one.
* @param holdToDrag if true user has to hold the button pressed to continue dragging, otherwise the target will stick to the controller.
*
* @author Jan Tiemann
*/
open class VRGrab(
protected val name: String,
protected val controllerHitbox: Node,
val name: String,
var controllerHitbox: Spatial,
protected val targets: () -> List<Node>,
protected val multiTarget: Boolean = false,
protected val holdToDrag: Boolean = true,
protected val onGrab: ((Node) -> Unit)? = null,
protected val onDrag: ((Node) -> Unit)? = null,
protected val onRelease: ((Node) -> Unit)? = null
) : DragBehaviour, Enablable{

override var enabled: Boolean = true

protected val controllerSpatial: Spatial = controllerHitbox.spatialOrNull()
?: throw IllegalArgumentException("controller hitbox needs a spatial attribute")

protected var selected = emptyList<Node>()

protected var lastPos = Vector3f()
protected var lastRotation = Quaternionf()

private val dragFunction = {this.drag(-42,0)}

init {
if (!holdToDrag && multiTarget) throw IllegalArgumentException("holdToDrag cant be false if multiTarget is true.")
}

/**
* Called on the first frame this behavior is triggered.
*
* @param x invalid - residue from parent behavior. Use [controllerSpatial] instead.
* @param y invalid - residue from parent behavior. Use [controllerSpatial] instead.
* @param x invalid - residue from parent behavior. Use [controllerHitbox] instead.
* @param y invalid - residue from parent behavior. Use [controllerHitbox] instead.
*/
override fun init(x: Int, y: Int) {
if (!enabled) return
selected = targets().filter { box -> controllerHitbox.spatialOrNull()?.intersects(box) ?: false }
if (!holdToDrag && selected.isNotEmpty()){
releaseDragging()
return
}

selected = targets().filter { box -> controllerHitbox.intersects(box, true) }
if (!multiTarget) {
selected = selected.take(1)
}
Expand All @@ -66,26 +78,30 @@ open class VRGrab(
selected.forEach {
onGrab?.invoke(it)
it.getAttributeOrNull(Grabable::class.java)?.onGrab?.invoke()
if (!holdToDrag){
it.update += dragFunction
}
}
lastPos = controllerSpatial.worldPosition()
lastRotation = controllerSpatial.worldRotation()
lastPos = controllerHitbox.worldPosition()
lastRotation = controllerHitbox.worldRotation()
}

/**
* Called on every frame this behavior is triggered.
*
* @param x invalid - residue from parent behavior. Use [controllerSpatial] instead.
* @param y invalid - residue from parent behavior. Use [controllerSpatial] instead.
* @param x invalid - residue from parent behavior. Use [controllerHitbox] instead.
* @param y invalid - residue from parent behavior. Use [controllerHitbox] instead.
*/
override fun drag(x: Int, y: Int) {
if (!enabled) return
val newPos = controllerHitbox.spatialOrNull()?.worldPosition() ?: Vector3f()
if (!holdToDrag && x != -42) return //magic number
val newPos = controllerHitbox.worldPosition()
val diffTranslation = newPos - lastPos
val diffRotation = Quaternionf(controllerSpatial.worldRotation()).mul(lastRotation.conjugate())
val diffRotation = Quaternionf(controllerHitbox.worldRotation()).mul(lastRotation.conjugate())

selected.forEach { node ->
node.getAttributeOrNull(Grabable::class.java)?.let { grabable ->
val target = (grabable.target ?: node)
val target = (grabable.target() ?: node)
target.spatialOrNull()?.let { spatial ->
// apply parent world rotation to diff if available
val translationWorld = target.parent?.spatialOrNull()?.worldRotation()?.let { q -> diffTranslation.rotate(q) }
Expand Down Expand Up @@ -114,21 +130,28 @@ open class VRGrab(
}
}

lastPos = controllerSpatial.worldPosition()
lastRotation = controllerSpatial.worldRotation()
lastPos = controllerHitbox.worldPosition()
lastRotation = controllerHitbox.worldRotation()
}

/**
* Called on the last frame this behavior is triggered.
*
* @param x invalid - residue from parent behavior. Use [controllerSpatial] instead.
* @param y invalid - residue from parent behavior. Use [controllerSpatial] instead.
* @param x invalid - residue from parent behavior. Use [controllerHitbox] instead.
* @param y invalid - residue from parent behavior. Use [controllerHitbox] instead.
*/
override fun end(x: Int, y: Int) {
if (!enabled) return
if (holdToDrag) {
releaseDragging()
}
}

private fun releaseDragging() {
selected.forEach {
onRelease?.invoke(it)
it.getAttributeOrNull(Grabable::class.java)?.onRelease?.invoke()
it.update -= dragFunction
}
selected = emptyList()
}
Expand All @@ -147,21 +170,23 @@ open class VRGrab(
hmd: OpenVRHMD,
button: List<OpenVRHMD.OpenVRButton>,
controllerSide: List<TrackerRole>,
holdToDrag: Boolean = true,
onGrab: ((Node, TrackedDevice) -> Unit)? = { _, device -> (hmd as? OpenVRHMD)?.vibrate(device) },
onDrag: ((Node, TrackedDevice) -> Unit)? = null,
onRelease: ((Node, TrackedDevice) -> Unit)? = null
) : List<VRGrab>{
val future = mutableListOf<VRGrab>()
) : Future<VRGrab> {
val future = CompletableFuture<VRGrab>()
hmd.events.onDeviceConnect.add { _, device, _ ->
if (device.type == TrackedDeviceType.Controller) {
device.model?.let { controller ->
if (controllerSide.contains(device.role)) {
val name = "VRGrab:${hmd.trackingSystemName}:${device.role}:$button"
val grabBehaviour = VRGrab(
name,
controller.children.first(),
(controller.children.firstOrNull { it.name == "collider"}?: controller.children.first()).spatialOrNull() ?: throw IllegalArgumentException("Need collider spatial for VRGrab."),
{ scene.discover(scene, { n -> n.getAttributeOrNull(Grabable::class.java) != null }) },
false,
holdToDrag,
{ n -> onGrab?.invoke(n, device) },
{ n -> onDrag?.invoke(n, device) },
{ n -> onRelease?.invoke(n, device) }
Expand All @@ -171,7 +196,7 @@ open class VRGrab(
button.forEach {
hmd.addKeyBinding(name, device.role, it)
}
future.add(grabBehaviour)
future.complete(grabBehaviour)
}
}
}
Expand All @@ -195,5 +220,5 @@ open class Grabable(
val onDrag: (() -> Unit)? = null,
val onRelease: (() -> Unit)? = null,
val lockRotation: Boolean = false,
val target: Node? = null
var target: () -> Node? = {null}
)
Loading
Loading