Skip to content

Commit 4d4fa09

Browse files
committed
fixed scene ray picking once again
1 parent 9c975b6 commit 4d4fa09

13 files changed

Lines changed: 197 additions & 30 deletions

File tree

kool-core/src/commonMain/kotlin/de/fabmax/kool/scene/Camera.kt

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package de.fabmax.kool.scene
22

3-
import de.fabmax.kool.KoolContext
43
import de.fabmax.kool.input.Pointer
54
import de.fabmax.kool.math.*
65
import de.fabmax.kool.pipeline.RenderPass
@@ -106,13 +105,13 @@ abstract class Camera(name: String = "camera") : Node(name) {
106105

107106
abstract fun updateProjectionMatrix(updateEvent: RenderPass.UpdateEvent)
108107

109-
fun computePickRay(pickRay: RayF, ptr: Pointer, viewport: Viewport, ctx: KoolContext): Boolean {
110-
return ptr.isValid && computePickRay(pickRay, ptr.x.toFloat(), ptr.y.toFloat(), viewport, ctx)
108+
fun computePickRay(pickRay: RayF, ptr: Pointer, viewport: Viewport): Boolean {
109+
return ptr.isValid && computePickRay(pickRay, ptr.x.toFloat(), ptr.y.toFloat(), viewport)
111110
}
112111

113-
fun computePickRay(pickRay: RayF, screenX: Float, screenY: Float, viewport: Viewport, ctx: KoolContext): Boolean {
114-
var valid = unProjectScreen(tmpVec3.set(screenX, screenY, 0f), viewport, ctx, pickRay.origin)
115-
valid = valid && unProjectScreen(tmpVec3.set(screenX, screenY, 1f), viewport, ctx, pickRay.direction)
112+
fun computePickRay(pickRay: RayF, screenX: Float, screenY: Float, viewport: Viewport): Boolean {
113+
var valid = unProjectScreen(tmpVec3.set(screenX, screenY, 0f), viewport, pickRay.origin)
114+
valid = valid && unProjectScreen(tmpVec3.set(screenX, screenY, 1f), viewport, pickRay.direction)
116115

117116
if (valid) {
118117
pickRay.direction.subtract(pickRay.origin)
@@ -122,13 +121,13 @@ abstract class Camera(name: String = "camera") : Node(name) {
122121
return valid
123122
}
124123

125-
fun initRayTes(rayTest: RayTest, ptr: Pointer, viewport: Viewport, ctx: KoolContext): Boolean {
126-
return ptr.isValid && initRayTes(rayTest, ptr.x.toFloat(), ptr.y.toFloat(), viewport, ctx)
124+
fun initRayTes(rayTest: RayTest, ptr: Pointer, viewport: Viewport): Boolean {
125+
return ptr.isValid && initRayTes(rayTest, ptr.x.toFloat(), ptr.y.toFloat(), viewport)
127126
}
128127

129-
fun initRayTes(rayTest: RayTest, screenX: Float, screenY: Float, viewport: Viewport, ctx: KoolContext): Boolean {
128+
fun initRayTes(rayTest: RayTest, screenX: Float, screenY: Float, viewport: Viewport): Boolean {
130129
rayTest.clear()
131-
return computePickRay(rayTest.ray, screenX, screenY, viewport, ctx)
130+
return computePickRay(rayTest.ray, screenX, screenY, viewport)
132131
}
133132

134133
abstract fun computeFrustumPlane(z: Float, result: FrustumPlane)
@@ -164,16 +163,16 @@ abstract class Camera(name: String = "camera") : Node(name) {
164163
return projectOk
165164
}
166165

167-
fun projectScreen(world: Vec3f, viewport: Viewport, ctx: KoolContext, result: MutableVec3f): Boolean {
166+
fun projectScreen(world: Vec3f, viewport: Viewport, result: MutableVec3f): Boolean {
168167
val projectOk = projectViewport(world, viewport, result)
169168
result.x += viewport.x
170-
result.y += ctx.windowHeight - viewport.y - viewport.height
169+
result.y += viewport.y
171170
return projectOk
172171
}
173172

174-
fun unProjectScreen(screen: Vec3f, viewport: Viewport, ctx: KoolContext, result: MutableVec3f): Boolean {
173+
fun unProjectScreen(screen: Vec3f, viewport: Viewport, result: MutableVec3f): Boolean {
175174
val x = screen.x - viewport.x
176-
val y = (ctx.windowHeight - screen.y) - viewport.y
175+
val y = viewport.y + viewport.height - screen.y
177176

178177
tmpVec4.set(2f * x / viewport.width - 1f, 2f * y / viewport.height - 1f, 2f * screen.z - 1f, 1f)
179178
invViewProj.transform(tmpVec4)

kool-core/src/commonMain/kotlin/de/fabmax/kool/scene/Node.kt

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ open class Node(name: String? = null) : BaseReleasable() {
7373
/**
7474
* Inverse of this node's model matrix (single-precision).
7575
*/
76-
val invModelMatF: Mat4d get() = modelMats.lazyInvModelMatD.get()
76+
val invModelMatF: Mat4f get() = modelMats.lazyInvModelMatF.get()
7777

7878
/**
7979
* Inverse of this node's model matrix (double-precision).
@@ -127,8 +127,9 @@ open class Node(name: String? = null) : BaseReleasable() {
127127

128128
bounds.clear()
129129
for (i in mutChildren.indices) {
130-
mutChildren[i].update(updateEvent)
131-
bounds.add(mutChildren[i].bounds)
130+
val child = mutChildren[i]
131+
child.update(updateEvent)
132+
child.addBoundsToParentBounds(bounds)
132133
}
133134
addContentToBoundingBox(bounds)
134135

@@ -138,6 +139,26 @@ open class Node(name: String? = null) : BaseReleasable() {
138139
globalRadius = globalCenter.distance(globalExtentMut)
139140
}
140141

142+
private fun addBoundsToParentBounds(parentBounds: BoundingBoxF) {
143+
if (!bounds.isEmpty) {
144+
val minX = bounds.min.x
145+
val minY = bounds.min.y
146+
val minZ = bounds.min.z
147+
val maxX = bounds.max.x
148+
val maxY = bounds.max.y
149+
val maxZ = bounds.max.z
150+
151+
parentBounds.add(transform.transform(tmpTransformVec.set(minX, minY, minZ), 1f))
152+
parentBounds.add(transform.transform(tmpTransformVec.set(minX, minY, maxZ), 1f))
153+
parentBounds.add(transform.transform(tmpTransformVec.set(minX, maxY, minZ), 1f))
154+
parentBounds.add(transform.transform(tmpTransformVec.set(minX, maxY, maxZ), 1f))
155+
parentBounds.add(transform.transform(tmpTransformVec.set(maxX, minY, minZ), 1f))
156+
parentBounds.add(transform.transform(tmpTransformVec.set(maxX, minY, maxZ), 1f))
157+
parentBounds.add(transform.transform(tmpTransformVec.set(maxX, maxY, minZ), 1f))
158+
parentBounds.add(transform.transform(tmpTransformVec.set(maxX, maxY, maxZ), 1f))
159+
}
160+
}
161+
141162
protected open fun addContentToBoundingBox(localBounds: BoundingBoxF) { }
142163

143164
fun updateModelMat() {
@@ -207,9 +228,9 @@ open class Node(name: String? = null) : BaseReleasable() {
207228
if (test.isIntersectingBoundingSphere(this)) {
208229
// bounding sphere hit -> transform ray to local coordinates and do further testing
209230
val localRay = if (transform.isDoublePrecision) {
210-
test.getRayTransformed(transform.invMatrixD)
231+
test.getRayTransformed(invModelMatD)
211232
} else {
212-
test.getRayTransformed(transform.invMatrixF)
233+
test.getRayTransformed(invModelMatF)
213234
}
214235

215236
val dLocal = bounds.hitDistanceSqr(localRay)

kool-core/src/commonMain/kotlin/de/fabmax/kool/scene/OrbitInputTransform.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ class CameraOrthogonalPan : PanBase() {
298298
override fun computePanPoint(result: MutableVec3f, view: RenderPass.View, ptrPos: Vec2d, ctx: KoolContext): Boolean {
299299
panPlane.p.set(view.camera.globalLookAt)
300300
panPlane.n.set(view.camera.globalLookDir)
301-
return view.camera.computePickRay(pointerRay, ptrPos.x.toFloat(), ptrPos.y.toFloat(), view.viewport, ctx) &&
301+
return view.camera.computePickRay(pointerRay, ptrPos.x.toFloat(), ptrPos.y.toFloat(), view.viewport) &&
302302
panPlane.intersectionPoint(pointerRay, result)
303303
}
304304
}
@@ -313,7 +313,7 @@ class FixedPlanePan(planeNormal: Vec3f) : PanBase() {
313313

314314
override fun computePanPoint(result: MutableVec3f, view: RenderPass.View, ptrPos: Vec2d, ctx: KoolContext): Boolean {
315315
panPlane.p.set(view.camera.globalLookAt)
316-
return view.camera.computePickRay(pointerRay, ptrPos.x.toFloat(), ptrPos.y.toFloat(), view.viewport, ctx) &&
316+
return view.camera.computePickRay(pointerRay, ptrPos.x.toFloat(), ptrPos.y.toFloat(), view.viewport) &&
317317
panPlane.intersectionPoint(pointerRay, result)
318318
}
319319
}

kool-core/src/commonMain/kotlin/de/fabmax/kool/scene/Scene.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,8 @@ open class Scene(name: String? = null) : Node(name) {
124124
logD { "Released scene \"$name\"" }
125125
}
126126

127-
fun computePickRay(pointer: Pointer, ctx: KoolContext, result: RayF): Boolean {
128-
return camera.computePickRay(result, pointer, mainRenderPass.viewport, ctx)
127+
fun computePickRay(pointer: Pointer, result: RayF): Boolean {
128+
return camera.computePickRay(result, pointer, mainRenderPass.viewport)
129129
}
130130

131131
companion object {

kool-core/src/commonMain/kotlin/de/fabmax/kool/util/Gizmo.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ class Gizmo : Node(), InputStack.PointerListener {
389389
val ptr = pointerState.primaryPointer
390390
val scene = findParentOfType<Scene>() ?: return
391391
val cam = scene.camera
392-
if (!scene.computePickRay(ptr, ctx, pickRay)) {
392+
if (!scene.computePickRay(ptr, pickRay)) {
393393
return
394394
}
395395

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package de.fabmax.kool.mock
2+
3+
import de.fabmax.kool.scene.Node
4+
5+
object Mock {
6+
7+
val testCtx = TestKoolContext()
8+
9+
val testRenderPass = MockRenderPass()
10+
11+
fun mockDraw(node: Node) {
12+
testRenderPass.apply {
13+
mockView.drawQueue.reset(false)
14+
node.update(updateEvent)
15+
node.collectDrawCommands(updateEvent)
16+
}
17+
}
18+
19+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package de.fabmax.kool.mock
2+
3+
import de.fabmax.kool.pipeline.RenderPass
4+
import de.fabmax.kool.scene.Node
5+
import de.fabmax.kool.scene.PerspectiveCamera
6+
7+
class MockRenderPass(
8+
override val width: Int = Mock.testCtx.windowWidth,
9+
override val height: Int = Mock.testCtx.windowHeight,
10+
override val depth: Int = 1,
11+
override val isReverseDepth: Boolean = false
12+
) : RenderPass("mock-render-pass") {
13+
14+
val mockView = View("mock-view", Node(), PerspectiveCamera(), arrayOf())
15+
override val views = listOf(mockView)
16+
17+
val updateEvent = views[0].makeUpdateEvent(Mock.testCtx)
18+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package de.fabmax.kool.mock
2+
3+
import de.fabmax.kool.KoolContext
4+
import de.fabmax.kool.pipeline.backend.RenderBackend
5+
6+
class TestKoolContext : KoolContext() {
7+
8+
override val isJavascript: Boolean = false
9+
override val isJvm: Boolean = true
10+
override val windowWidth: Int = 1600
11+
override val windowHeight: Int = 900
12+
override var isFullscreen: Boolean = false
13+
14+
override val backend: RenderBackend
15+
get() = TODO("Not yet implemented")
16+
17+
override fun run() {
18+
throw IllegalStateException("TextKoolContext cannot be run")
19+
}
20+
21+
override fun openUrl(url: String, sameWindow: Boolean) {
22+
println("open url: $url")
23+
}
24+
25+
override fun getSysInfos(): List<String> = emptyList()
26+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package de.fabmax.kool.scene
2+
3+
import de.fabmax.kool.math.RayTest
4+
import de.fabmax.kool.math.Vec3f
5+
import de.fabmax.kool.mock.Mock
6+
import org.junit.Test
7+
8+
class RayPickTest {
9+
@Test
10+
fun pickNodeNoTransform() {
11+
val rayTest = RayTest()
12+
rayTest.ray.origin.set(0f, 0f, 10f)
13+
rayTest.ray.direction.set(0f, 0f, -1f)
14+
15+
val unitCube = ColorMesh().apply {
16+
generate {
17+
cube { }
18+
}
19+
}
20+
Mock.mockDraw(unitCube)
21+
22+
unitCube.rayTest(rayTest)
23+
assert(rayTest.isHit)
24+
assert(rayTest.hitNode == unitCube)
25+
assert(rayTest.hitPositionGlobal.isFuzzyEqual(Vec3f(0f, 0f, 0.5f))) {
26+
"Expected hit position: (0.5, 0.0, 0.0) but is ${rayTest.hitPositionGlobal}"
27+
}
28+
}
29+
30+
@Test
31+
fun pickNodeTranslated() {
32+
val rayTest = RayTest()
33+
rayTest.ray.origin.set(1f, 0f, 10f)
34+
rayTest.ray.direction.set(0f, 0f, -1f)
35+
36+
val unitCube = ColorMesh().apply {
37+
generate {
38+
cube { }
39+
}
40+
transform.translate(1f, 0f, 0f)
41+
}
42+
Mock.mockDraw(unitCube)
43+
44+
unitCube.rayTest(rayTest)
45+
assert(rayTest.isHit)
46+
assert(rayTest.hitNode == unitCube)
47+
assert(rayTest.hitPositionGlobal.isFuzzyEqual(Vec3f(1f, 0f, 0.5f))) {
48+
"Expected hit position: (0.5, 0.0, 0.0) but is ${rayTest.hitPositionGlobal}"
49+
}
50+
}
51+
52+
@Test
53+
fun pickNodeNested() {
54+
val rayTest = RayTest()
55+
rayTest.ray.origin.set(2f, 0f, 10f)
56+
rayTest.ray.direction.set(0f, 0f, -1f)
57+
58+
val unitCube1 = ColorMesh().apply {
59+
generate {
60+
cube { }
61+
}
62+
transform.translate(1f, 0f, 0f)
63+
}
64+
val unitCube2 = ColorMesh().apply {
65+
generate {
66+
cube { }
67+
}
68+
transform.translate(1f, 0f, 0f)
69+
}
70+
71+
unitCube1.addNode(unitCube2)
72+
Mock.mockDraw(unitCube1)
73+
74+
println("1 center: ${unitCube1.globalCenter}, r = ${unitCube1.globalRadius}")
75+
println("2 center: ${unitCube2.globalCenter}, r = ${unitCube2.globalRadius}")
76+
77+
unitCube1.rayTest(rayTest)
78+
assert(rayTest.isHit)
79+
assert(rayTest.hitNode == unitCube2)
80+
assert(rayTest.hitPositionGlobal.isFuzzyEqual(Vec3f(2f, 0f, 0.5f))) {
81+
"Expected hit position: (0.5, 0.0, 0.0) but is ${rayTest.hitPositionGlobal}"
82+
}
83+
}
84+
}

kool-demo/src/commonMain/kotlin/de/fabmax/kool/demo/fluidsim/FluidDemo.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ class FluidDemo : DemoScene("Fluid Simulation") {
271271
val linePos = float2Var(inPos.xy)
272272
val drawState = texture2d("drawState")
273273

274-
repeat(iterations) { iter ->
274+
repeat(iterations) {
275275
linePos += sampleTexture(drawState, linePos).xy * 0.01f.const
276276
}
277277

0 commit comments

Comments
 (0)