Skip to content
Permalink
Browse files

Merge pull request #244 from scenerygraphics/pupil-update

* PupilTracker: changes to reflect new datum format since Pupil 1.10
* Camera: allow overriding of width, height and fov
* PupilEyeTracker: improve calibration routine
* Node: add orientBetweenPoints function
* Node: when creating bounding box, check for both capacity and remaining in vertex buffers
* Volume: add functions for sampling from volumetric data
* bumps ClearGL to 2.2.6
* add MaybeIntersects helper class
* Volume: move positioning and scaling code from shader to class
* add VolumeSamplingExample
* Renderer: add image requests via screenshotRequest()
  • Loading branch information...
skalarproduktraum committed May 3, 2019
2 parents 674a534 + 8215ef0 commit 817c9888e8064e3a9a061e00f0d07c703d10b03f
Showing with 721 additions and 126 deletions.
  1. +1 −1 pom.xml
  2. +12 −2 src/main/kotlin/graphics/scenery/BoundingGrid.kt
  3. +48 −13 src/main/kotlin/graphics/scenery/Camera.kt
  4. +10 −0 src/main/kotlin/graphics/scenery/Cylinder.kt
  5. +50 −0 src/main/kotlin/graphics/scenery/DetachedHeadCamera.kt
  6. +5 −2 src/main/kotlin/graphics/scenery/Line.kt
  7. +39 −10 src/main/kotlin/graphics/scenery/Node.kt
  8. +3 −2 src/main/kotlin/graphics/scenery/Scene.kt
  9. +8 −0 src/main/kotlin/graphics/scenery/backends/RenderedImage.kt
  10. +34 −0 src/main/kotlin/graphics/scenery/backends/Renderer.kt
  11. +19 −3 src/main/kotlin/graphics/scenery/backends/opengl/OpenGLRenderer.kt
  12. +24 −8 src/main/kotlin/graphics/scenery/backends/vulkan/VulkanRenderer.kt
  13. +4 −1 src/main/kotlin/graphics/scenery/controls/OpenVRHMD.kt
  14. +64 −13 src/main/kotlin/graphics/scenery/controls/PupilEyeTracker.kt
  15. +20 −0 src/main/kotlin/graphics/scenery/utils/MaybeIntersects.kt
  16. +7 −0 src/main/kotlin/graphics/scenery/volumes/TransferFunction.kt
  17. +133 −23 src/main/kotlin/graphics/scenery/volumes/Volume.kt
  18. BIN src/main/resources/graphics/scenery/backends/shaders/FXAA.frag.spv
  19. BIN src/main/resources/graphics/scenery/backends/shaders/Line.geom.spv
  20. +3 −22 src/main/resources/graphics/scenery/backends/shaders/Volume.vert
  21. BIN src/main/resources/graphics/scenery/backends/shaders/Volume.vert.spv
  22. +41 −25 src/test/tests/graphics/scenery/tests/examples/advanced/EyeTrackingExample.kt
  23. +4 −1 src/test/tests/graphics/scenery/tests/examples/advanced/ProceduralVolumeExample.kt
  24. +192 −0 src/test/tests/graphics/scenery/tests/examples/advanced/VolumeSamplingExample.kt
@@ -109,7 +109,7 @@
<dokka.version>0.9.18</dokka.version>
<dokka.skip>true</dokka.skip>

<cleargl.version>2.2.5</cleargl.version>
<cleargl.version>2.2.6</cleargl.version>
<slf4j.version>1.7.25</slf4j.version>
<lwjgl.version>3.2.1</lwjgl.version>
<spirvcrossj.version>0.5.2-1.1.101.0</spirvcrossj.version>
@@ -35,6 +35,9 @@ open class BoundingGrid : Mesh("Bounding Grid") {
@ShaderProperty
var ticksOnly: Int = 1

/** Slack around transparent objects, 10mm in world space by default. */
var slack = 0.01f

/** The [Node] this bounding grid is attached to. Set to null to remove. */
var node: Node? = null
set(value) {
@@ -96,8 +99,15 @@ open class BoundingGrid : Mesh("Bounding Grid") {
val maxBoundingBox = node.getMaximumBoundingBox()
nodeBoundingBoxHash = maxBoundingBox.hashCode()

val min = maxBoundingBox.min
val max = maxBoundingBox.max

var min = maxBoundingBox.min
var max = maxBoundingBox.max

if(node.material.blending.transparent) {
val slack = GLVector(slack, slack, slack)
min = min - slack
max = max + slack
}

val b = Box(max - min)

@@ -37,7 +37,7 @@ open class Camera : Node("Camera") {
/** Right vector of the camera */
var right: GLVector = GLVector(1.0f, 0.0f, 0.0f)
/** FOV of the camera **/
var fov: Float = 70.0f
open var fov: Float = 70.0f
/** Z buffer near plane */
var nearPlaneDistance = 0.05f
/** Z buffer far plane location */
@@ -47,9 +47,9 @@ open class Camera : Node("Camera") {
/** Projection the camera uses */
var projectionType: ProjectionType = ProjectionType.Undefined
/** Width of the projection */
var width: Float = 0.0f
open var width: Float = 0.0f
/** Height of the projection */
var height: Float = 0.0f
open var height: Float = 0.0f
/** View-space coordinate system e.g. for frustum culling. */
var viewSpaceTripod: Camera.Tripod
protected set
@@ -198,20 +198,55 @@ open class Camera : Node("Camera") {
* If the vector is 2D, [nearPlaneDistance] is assumed for the Z value, otherwise
* the Z value from the vector is taken.
*/
@JvmOverloads fun viewportToWorld(vector: GLVector, offset: Float = 0.01f): GLVector {
val unproject = projection.clone()
unproject.mult(getTransformation())
unproject.invert()

var clipSpace = unproject.mult(when (vector.dimension) {
1 -> GLVector(vector.x(), 1.0f, nearPlaneDistance + offset, 1.0f)
2 -> GLVector(vector.x(), vector.y(), nearPlaneDistance + offset, 1.0f)
@JvmOverloads fun viewportToWorld(vector: GLVector, offset: Float = 0.01f, normalized: Boolean = false): GLVector {
val pv = projection.clone()
pv.mult(getTransformation())
val ipv = pv.inverse

var worldSpace = ipv.mult(when (vector.dimension) {
1 -> GLVector(vector.x(), 1.0f, 0.0f, 1.0f)
2 -> GLVector(vector.x(), vector.y(), 0.0f, 1.0f)
3 -> GLVector(vector.x(), vector.y(), vector.z(), 1.0f)
else -> vector
})

clipSpace = clipSpace.times(1.0f/clipSpace.w())
return clipSpace.xyz()
worldSpace = worldSpace.times(1.0f/worldSpace.w())
// worldSpace.set(2, offset)
return worldSpace.xyz()
/*
var x = vector.x()
var y = vector.y()

if(normalized) {
x *= width
y *= height
}

val pos = if(this is DetachedHeadCamera) {
position + headPosition
} else {
position
}

val view = (target - pos).normalize()
var h = view.cross(up).normalize()
var v = h.cross(view)

val fov = fov * Math.PI / 180.0f
val lengthV = Math.tan(fov / 2.0).toFloat() * nearPlaneDistance
val lengthH = lengthV * (width / height)

v *= lengthV
h *= lengthH

val posX = (x - width / 2.0f) / (width / 2.0f)
val posY = -1.0f * (y - height / 2.0f) / (height / 2.0f)

val worldPos = pos + view * nearPlaneDistance + h * posX + v * posY
val worldDir = (worldPos - pos).normalized

return worldPos + worldDir * offset
*/
}

/**
@@ -1,8 +1,10 @@
package graphics.scenery

import cleargl.GLVector
import java.nio.FloatBuffer
import java.nio.IntBuffer
import java.util.*
import kotlin.math.PI

/**
* Constructs a cylinder with the given [radius] and number of [segments].
@@ -74,4 +76,12 @@ class Cylinder(var radius: Float, var height: Float, var segments: Int) : Node("
boundingBox = generateBoundingBox()
}

companion object {
@JvmStatic fun betweenPoints(p1: GLVector, p2: GLVector, radius: Float = 0.02f, height: Float = 1.0f, segments: Int = 16): Cylinder {
val cylinder = Cylinder(radius, height, segments)
cylinder.orientBetweenPoints(p1, p2, rescale = true, reposition = true)
return cylinder
}
}

}
@@ -6,6 +6,8 @@ import com.jogamp.opengl.math.Quaternion
import graphics.scenery.backends.Display
import graphics.scenery.controls.TrackerInput
import java.io.Serializable
import kotlin.math.PI
import kotlin.math.atan
import kotlin.reflect.KProperty

/**
@@ -27,6 +29,54 @@ class DetachedHeadCamera(@Transient var tracker: TrackerInput? = null) : Camera(
field = value
}

override var width: Float = 0.0f
get() = if(tracker != null && tracker is Display) {
(tracker as? Display)?.getRenderTargetSize()?.x() ?: super.width
} else {
super.width
}
set(value) {
super.width = value
field = value
}

override var height: Float = 0.0f
get() = if(tracker != null && tracker is Display) {
(tracker as? Display)?.getRenderTargetSize()?.y() ?: super.width
} else {
super.width
}
set(value) {
super.height = value
field = value
}

override var fov: Float = 70.0f
get() = if(tracker != null && tracker is Display) {
val proj = (tracker as? Display)?.getEyeProjection(0, nearPlaneDistance, farPlaneDistance)
if(proj != null) {
atan(1.0f / proj.get(1, 1)) * 2.0f * 180.0f / PI.toFloat()
} else {
super.fov
}
} else {
super.fov
}
set(value) {
super.fov = value
field = value
}

// override var position: GLVector = GLVector(0.0f, 0.0f, 0.0f)
// get() = if(tracker != null) {
// field + headPosition
// } else {
// field
// }
// set(value) {
// field = value
// }

/**
* Delegate class for getting a head rotation from a [TrackerInput].
*/
@@ -69,8 +69,6 @@ class Line @JvmOverloads constructor(var capacity: Int = 50, transparent: Boolea
}

protected fun activateTransparency(transparent: Boolean) {
logger.info("transparency changed to ${transparent}")

if(transparent) {
val newMaterial = ShaderMaterial.fromFiles(
"${this::class.java.simpleName}.vert",
@@ -109,6 +107,11 @@ class Line @JvmOverloads constructor(var capacity: Int = 50, transparent: Boolea
* @param p The vector containing the vertex data
*/
fun addPoint(p: GLVector) {
if(p.dimension != 3) {
logger.error("Cannot add position with dimension ${p.dimension} to line.")
return
}

if(vertices.limit() + 3 > vertices.capacity()) {
val newVertices = BufferUtils.allocateFloat(vertices.capacity() + 3*capacity)
vertices.position(0)
@@ -5,6 +5,7 @@ import cleargl.GLVector
import com.jogamp.opengl.math.Quaternion
import graphics.scenery.backends.Renderer
import graphics.scenery.utils.LazyLogger
import graphics.scenery.utils.MaybeIntersects
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import java.io.Serializable
@@ -15,6 +16,7 @@ import java.util.concurrent.locks.ReentrantLock
import java.util.function.Consumer
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
import kotlin.math.PI
import kotlin.math.max
import kotlin.math.min
import kotlin.properties.Delegates
@@ -343,7 +345,7 @@ open class Node(open var name: String = "Node") : Renderable, Serializable {
* This method composes the [model] matrices of the node from its
* [position], [scale] and [rotation].
*/
fun composeModel() {
open fun composeModel() {
@Suppress("SENSELESS_COMPARISON")
if(position != null && rotation != null && scale != null) {
model.setIdentity()
@@ -364,7 +366,7 @@ open class Node(open var name: String = "Node") : Renderable, Serializable {
val vertexBufferView = vertices.asReadOnlyBuffer()
val boundingBoxCoords = floatArrayOf(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)

if (vertexBufferView.capacity() == 0) {
if (vertexBufferView.capacity() == 0 || vertexBufferView.remaining() == 0) {
boundingBox = if(!children.none()) {
getMaximumBoundingBox()
} else {
@@ -543,6 +545,28 @@ open class Node(open var name: String = "Node") : Renderable, Serializable {
return this.scale
}

/**
* Orients the Node between points [p1] and [p2], and optionally
* [rescale]s and [reposition]s it.
*/
@JvmOverloads fun orientBetweenPoints(p1: GLVector, p2: GLVector, rescale: Boolean = false, reposition: Boolean = false): Quaternion {
val direction = p2 - p1
this.rotation = this.rotation
.setLookAt(direction.normalized.toFloatArray(),
floatArrayOf(0.0f, 1.0f, 0.0f),
FloatArray(3), FloatArray(3), FloatArray(3))
.rotateByAngleX(PI.toFloat()/2.0f)
if(rescale) {
this.scale = GLVector(1.0f, direction.magnitude(), 1.0f)
}

if(reposition) {
this.position = p1.clone()
}

return this.rotation
}

private fun expand(lhs: OrientedBoundingBox, rhs: OrientedBoundingBox): OrientedBoundingBox {
return OrientedBoundingBox(
min(lhs.min.x(), rhs.min.x()),
@@ -626,9 +650,9 @@ open class Node(open var name: String = "Node") : Renderable, Serializable {
* Returns a Pair of Boolean and Float, indicating whether an intersection is possible,
* and at which distance.
*
* Code adapted from zachamarz, http://gamedev.stackexchange.com/a/18459
* Code adapted from [zachamarz](http://gamedev.stackexchange.com/a/18459).
*/
fun intersectAABB(origin: GLVector, dir: GLVector): Pair<Boolean, Float> {
fun intersectAABB(origin: GLVector, dir: GLVector): MaybeIntersects {
val bbmin = getMaximumBoundingBox().min.xyzw()
val bbmax = getMaximumBoundingBox().max.xyzw()

@@ -637,10 +661,10 @@ open class Node(open var name: String = "Node") : Renderable, Serializable {

// skip if inside the bounding box
if(origin.isInside(min, max)) {
return false to 0.0f
return MaybeIntersects.NoIntersection()
}

val invDir = GLVector(1 / dir.x(), 1 / dir.y(), 1 / dir.z())
val invDir = GLVector(1 / (dir.x() + Float.MIN_VALUE), 1 / (dir.y() + Float.MIN_VALUE), 1 / (dir.z() + Float.MIN_VALUE))

val t1 = (min.x() - origin.x()) * invDir.x()
val t2 = (max.x() - origin.x()) * invDir.x()
@@ -654,16 +678,21 @@ open class Node(open var name: String = "Node") : Renderable, Serializable {

// we are in front of the AABB
if (tmax < 0) {
return false to tmax
return MaybeIntersects.NoIntersection()
}

// we have missed the AABB
if (tmin > tmax) {
return false to tmax
return MaybeIntersects.NoIntersection()
}

// we have a match!
return true to tmin
// we have a match! calculate entry and exit points
val entry = origin + dir * tmin
val exit = origin + dir * tmax
val localEntry = world.inverse.mult(entry.xyzw())
val localExit = world.inverse.mult(exit.xyzw())

return MaybeIntersects.Intersection(tmin, entry, exit, localEntry.xyz(), localExit.xyz())
}

private fun GLVector.isInside(min: GLVector, max: GLVector): Boolean {
@@ -1,6 +1,7 @@
package graphics.scenery

import cleargl.GLVector
import graphics.scenery.utils.MaybeIntersects
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import java.util.*
@@ -203,9 +204,9 @@ open class Scene : Node("RootNode") {
}).map {
Pair(it, it.intersectAABB(position, direction))
}.filter {
it.second.first && it.second.second > 0.0f
it.second is MaybeIntersects.Intersection && (it.second as MaybeIntersects.Intersection).distance > 0.0f
}.map {
RaycastResult(it.first, it.second.second)
RaycastResult(it.first, (it.second as MaybeIntersects.Intersection).distance)
}.sortedBy {
it.distance
}
@@ -0,0 +1,8 @@
package graphics.scenery.backends

import java.nio.ByteBuffer

sealed class RenderedImage(open var width: Int, open var height: Int, open var data: ByteArray? = null) {
data class RenderedRGBImage(override var width: Int, override var height: Int, override var data: ByteArray? = null) : RenderedImage(width, height, data)
data class RenderedRGBAImage(override var width: Int, override var height: Int, override var data: ByteArray? = null) : RenderedImage(width, height, data)
}
Oops, something went wrong.

0 comments on commit 817c988

Please sign in to comment.
You can’t perform that action at this time.