From 5b7321ea4b970d7f1e5b321c6dbc14765dbd2163 Mon Sep 17 00:00:00 2001 From: Stanislav Mishchenko Date: Wed, 17 Apr 2024 23:13:25 +0300 Subject: [PATCH] add layer click handlers and coordinates convertion logic --- .../solve/rendering/canvas/OpenGLCanvas.kt | 5 -- .../solve/rendering/canvas/SceneCanvas.kt | 47 ++++++++++++++++++- .../engine/core/input/LayerClickHandler.kt | 10 ++++ .../core/input/LineLayerClickHandler.kt | 34 ++++++++++++++ .../engine/core/input/MouseInputHandler.kt | 5 ++ .../core/input/PointLayerClickHandler.kt | 28 +++++++++++ .../engine/core/renderers/Renderer.kt | 5 +- .../engine/utils/CalculationUtils.kt | 17 +++++++ .../rendering/engine/utils/StructuresUtils.kt | 3 ++ src/main/kotlin/solve/scene/view/SceneView.kt | 19 ++++++-- 10 files changed, 161 insertions(+), 12 deletions(-) create mode 100644 src/main/kotlin/solve/rendering/engine/core/input/LayerClickHandler.kt create mode 100644 src/main/kotlin/solve/rendering/engine/core/input/LineLayerClickHandler.kt create mode 100644 src/main/kotlin/solve/rendering/engine/core/input/MouseInputHandler.kt create mode 100644 src/main/kotlin/solve/rendering/engine/core/input/PointLayerClickHandler.kt create mode 100644 src/main/kotlin/solve/rendering/engine/utils/CalculationUtils.kt diff --git a/src/main/kotlin/solve/rendering/canvas/OpenGLCanvas.kt b/src/main/kotlin/solve/rendering/canvas/OpenGLCanvas.kt index e45e9958..4cb4ad07 100644 --- a/src/main/kotlin/solve/rendering/canvas/OpenGLCanvas.kt +++ b/src/main/kotlin/solve/rendering/canvas/OpenGLCanvas.kt @@ -62,11 +62,6 @@ abstract class OpenGLCanvas { } private fun render(event: GLRenderEvent) { - /*glEnable(GL_BLEND) - glEnable(GL_DEPTH_TEST) - glEnable(GL_MULTISAMPLE) - glDepthFunc(GL_LEQUAL) - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)*/ glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT) onDraw(event.delta.toFloat()) diff --git a/src/main/kotlin/solve/rendering/canvas/SceneCanvas.kt b/src/main/kotlin/solve/rendering/canvas/SceneCanvas.kt index 4bfb3062..100bb2c1 100644 --- a/src/main/kotlin/solve/rendering/canvas/SceneCanvas.kt +++ b/src/main/kotlin/solve/rendering/canvas/SceneCanvas.kt @@ -6,7 +6,9 @@ import solve.rendering.engine.core.renderers.FramesRenderer import solve.rendering.engine.core.renderers.LinesLayerRenderer import solve.rendering.engine.core.renderers.PlanesLayerRenderer import solve.rendering.engine.core.renderers.PointsLayerRenderer +import solve.rendering.engine.core.renderers.Renderer import solve.rendering.engine.utils.minus +import solve.rendering.engine.utils.plus import solve.rendering.engine.utils.times import solve.rendering.engine.utils.toFloatVector import solve.scene.controller.SceneController @@ -30,6 +32,8 @@ class SceneCanvas : OpenGLCanvas() { private var framesSelectionSize = 0 private var framesSize = Vector2i() + private val framesRatio: Float + get() = framesSize.x.toFloat() / framesSize.y private var columnsNumber = 0 private val rowsNumber: Int get() = (framesSelectionSize.toFloat() / columnsNumber.toFloat()).ceilToInt() @@ -99,6 +103,16 @@ class SceneCanvas : OpenGLCanvas() { isDraggingScene = false } + fun interactWithLandmark(screenPoint: Vector2i) { + val frameCoordinate = shaderToFrameVector(calculateWindowTopLeftCornerShaderPosition()) + screenToFrameVector(screenPoint) + //val frameIndex = frameCoordinate.toIntVector() + // Frame local coordinate including spacing area. + val frameLocalCoordinate = Vector2f(frameCoordinate.x % 1, frameCoordinate.y % 1) + // Frame local coordinate excluding spacing area. + frameLocalCoordinate.y *= (1 + Renderer.FramesSpacing) + frameLocalCoordinate.x *= (framesSize.x + Renderer.getSpacingWidth(framesSize)) / framesSize.x + } + fun zoomToPoint(screenPoint: Vector2i, newZoom: Float) { val cameraPoint = fromScreenToCameraPoint(screenPoint) window.camera.zoomToPoint(cameraPoint, newZoom) @@ -125,6 +139,33 @@ class SceneCanvas : OpenGLCanvas() { canvasScene?.update() } + private fun calculateWindowTopLeftCornerShaderPosition() : Vector2f { + return window.camera.position - screenToShaderVector(Vector2i(window.size) / 2f) + } + + // Converts screen vector to shader coordinates. + // One frame excluding spacing area corresponds to a (1, framesRatio) vector. + private fun screenToShaderVector(screenVector: Vector2i) : Vector2f { + return Vector2f(screenVector) / window.camera.scaledZoom + } + + // Converts screen vector to frame coordinates. + // One frame including spacing area corresponds to a (1, 1) vector. + private fun screenToFrameVector(screenVector: Vector2i) : Vector2f { + val shaderVector = screenToShaderVector(screenVector) + val frameVector = shaderToFrameVector(shaderVector) + + return frameVector + } + + private fun shaderToFrameVector(shaderVector: Vector2f) : Vector2f { + val frameVector = Vector2f(shaderVector) + frameVector.x /= (framesSize.x + Renderer.getSpacingWidth(framesSize)) / framesSize.y + frameVector.y /= (1 + Renderer.FramesSpacing) + + return frameVector + } + private fun checkRenderersInitialization() { if (needToReinitializeRenderers) { reinitializeRenderers() @@ -167,8 +208,10 @@ class SceneCanvas : OpenGLCanvas() { val halfScreenSize = (Vector2f(window.width.toFloat(), window.height.toFloat()) / 2f) / window.camera.scaledZoom leftUpperCornerCameraPosition = halfScreenSize - val framesSelectionSize = - Vector2i(columnsNumber * framesSize.x, rowsNumber * framesSize.y).toFloatVector() + val framesSelectionSize = Vector2f( + columnsNumber * framesSize.x + (columnsNumber - 1) * Renderer.getSpacingWidth(framesSize), + rowsNumber * framesSize.y + (rowsNumber - 1) * Renderer.getSpacingWidth(framesSize) + ) val framesSelectionScreenSize = framesSelectionSize * window.camera.zoom / IdentityFramesSizeScale / window.camera.scaledZoom diff --git a/src/main/kotlin/solve/rendering/engine/core/input/LayerClickHandler.kt b/src/main/kotlin/solve/rendering/engine/core/input/LayerClickHandler.kt new file mode 100644 index 00000000..017f01df --- /dev/null +++ b/src/main/kotlin/solve/rendering/engine/core/input/LayerClickHandler.kt @@ -0,0 +1,10 @@ +package solve.rendering.engine.core.input + +import org.joml.Vector2i +import solve.scene.model.Landmark + +abstract class LayerClickHandler { + // Returns the index of the clicked landmark, if there is one. + // Otherwise returns -1. + abstract fun indexOfClickedLandmark(landmarks: List, clickedPixelCoordinate: Vector2i): Int +} diff --git a/src/main/kotlin/solve/rendering/engine/core/input/LineLayerClickHandler.kt b/src/main/kotlin/solve/rendering/engine/core/input/LineLayerClickHandler.kt new file mode 100644 index 00000000..b7050f47 --- /dev/null +++ b/src/main/kotlin/solve/rendering/engine/core/input/LineLayerClickHandler.kt @@ -0,0 +1,34 @@ +package solve.rendering.engine.core.input + +import org.joml.Vector2i +import solve.rendering.engine.utils.pointToSegmentDistance +import solve.rendering.engine.utils.toFloatVector +import solve.rendering.engine.utils.toVector2i +import solve.scene.model.Landmark.Line + +class LineLayerClickHandler : LayerClickHandler() { + override fun indexOfClickedLandmark(landmarks: List, clickedPixelCoordinate: Vector2i): Int { + var minLineDistanceIndex = -1 + var minLineDistance = Float.MAX_VALUE + + landmarks.forEachIndexed { index, line -> + val lineStartCoordinate = line.startCoordinate.toVector2i() + val lineFinishCoordinate = line.finishCoordinate.toVector2i() + val lineDistance = pointToSegmentDistance( + clickedPixelCoordinate.toFloatVector(), + lineStartCoordinate.toFloatVector(), + lineFinishCoordinate.toFloatVector() + ) + if (lineDistance < ClickedLandmarkMaxAllowedDistance && lineDistance < minLineDistance) { + minLineDistanceIndex = index + minLineDistance = lineDistance + } + } + + return minLineDistanceIndex + } + + companion object { + private const val ClickedLandmarkMaxAllowedDistance = 3 + } +} \ No newline at end of file diff --git a/src/main/kotlin/solve/rendering/engine/core/input/MouseInputHandler.kt b/src/main/kotlin/solve/rendering/engine/core/input/MouseInputHandler.kt new file mode 100644 index 00000000..66401183 --- /dev/null +++ b/src/main/kotlin/solve/rendering/engine/core/input/MouseInputHandler.kt @@ -0,0 +1,5 @@ +package solve.rendering.engine.core.input + +object MouseInputHandler { + +} \ No newline at end of file diff --git a/src/main/kotlin/solve/rendering/engine/core/input/PointLayerClickHandler.kt b/src/main/kotlin/solve/rendering/engine/core/input/PointLayerClickHandler.kt new file mode 100644 index 00000000..8b3fa909 --- /dev/null +++ b/src/main/kotlin/solve/rendering/engine/core/input/PointLayerClickHandler.kt @@ -0,0 +1,28 @@ +package solve.rendering.engine.core.input + +import org.joml.Vector2i +import solve.rendering.engine.utils.minus +import solve.rendering.engine.utils.toVector2i +import solve.scene.model.Landmark.Keypoint + +class PointLayerClickHandler : LayerClickHandler() { + override fun indexOfClickedLandmark(landmarks: List, clickedPixelCoordinate: Vector2i): Int { + var minKeypointDistanceIndex = -1 + var minKeypointDistance = Double.MAX_VALUE + + landmarks.forEachIndexed { index, keypoint -> + val keypointCoordinate = keypoint.coordinate.toVector2i() + val keypointDistance = (keypointCoordinate - clickedPixelCoordinate).length() + if (keypointDistance < ClickedLandmarkMaxAllowedDistance && keypointDistance < minKeypointDistance) { + minKeypointDistanceIndex = index + minKeypointDistance = keypointDistance + } + } + + return minKeypointDistanceIndex + } + + companion object { + private const val ClickedLandmarkMaxAllowedDistance = 3 + } +} \ No newline at end of file diff --git a/src/main/kotlin/solve/rendering/engine/core/renderers/Renderer.kt b/src/main/kotlin/solve/rendering/engine/core/renderers/Renderer.kt index 68abf4ba..f0a61041 100644 --- a/src/main/kotlin/solve/rendering/engine/core/renderers/Renderer.kt +++ b/src/main/kotlin/solve/rendering/engine/core/renderers/Renderer.kt @@ -1,6 +1,7 @@ package solve.rendering.engine.core.renderers import org.joml.Vector2f +import org.joml.Vector2i import org.lwjgl.opengl.GL11.GL_UNSIGNED_INT import org.lwjgl.opengl.GL11.glDrawElements import solve.rendering.engine.Window @@ -161,6 +162,8 @@ abstract class Renderer(protected val window: Window) : Comparable { const val FramesSpacingUniformName = "uFramesSpacing" const val DefaultGridWidth = 10 - const val FramesSpacing = 0.02f + const val FramesSpacing = 0.25f + + fun getSpacingWidth(framesSize: Vector2i) = framesSize.y * FramesSpacing } } diff --git a/src/main/kotlin/solve/rendering/engine/utils/CalculationUtils.kt b/src/main/kotlin/solve/rendering/engine/utils/CalculationUtils.kt new file mode 100644 index 00000000..1f0800b8 --- /dev/null +++ b/src/main/kotlin/solve/rendering/engine/utils/CalculationUtils.kt @@ -0,0 +1,17 @@ +package solve.rendering.engine.utils + +import org.joml.Vector2f +import kotlin.math.abs +import kotlin.math.pow +import kotlin.math.sqrt + +fun pointToSegmentDistance( + pointPosition: Vector2f, + segmentStartPosition: Vector2f, + segmentEndPosition: Vector2f +) : Float { + return abs((segmentEndPosition.x - segmentStartPosition.x) * (pointPosition.y - segmentStartPosition.y) - + (pointPosition.x - segmentStartPosition.x) * (segmentEndPosition.y - segmentStartPosition.y)) / + sqrt((segmentEndPosition.x - segmentStartPosition.x).pow(2) + + (segmentEndPosition.y - segmentStartPosition.y).pow(2)) +} diff --git a/src/main/kotlin/solve/rendering/engine/utils/StructuresUtils.kt b/src/main/kotlin/solve/rendering/engine/utils/StructuresUtils.kt index 4aade1f3..50dd7f75 100644 --- a/src/main/kotlin/solve/rendering/engine/utils/StructuresUtils.kt +++ b/src/main/kotlin/solve/rendering/engine/utils/StructuresUtils.kt @@ -7,6 +7,7 @@ import org.joml.Vector3f import org.joml.Vector3i import org.joml.Vector4f import org.joml.Vector4i +import solve.scene.model.Point operator fun Vector2f.plus(otherVector: Vector2f): Vector2f = Vector2f(this).add(otherVector) operator fun Vector3f.plus(otherVector: Vector3f): Vector3f = Vector3f(this).add(otherVector) @@ -74,3 +75,5 @@ fun Vector2f.toList() = listOf(x, y) fun Vector3f.toList() = listOf(x, y, z) fun Vector4f.toList() = listOf(x, y, z, w) + +fun Point.toVector2i() = Vector2i(x.toInt(), y.toInt()) diff --git a/src/main/kotlin/solve/scene/view/SceneView.kt b/src/main/kotlin/solve/scene/view/SceneView.kt index 48596b1a..10da9e2c 100644 --- a/src/main/kotlin/solve/scene/view/SceneView.kt +++ b/src/main/kotlin/solve/scene/view/SceneView.kt @@ -24,6 +24,8 @@ class SceneView : View() { private var mouseScreenPoint = Vector2i() + private var wasMouseDragging = false + var currentAssociationsManager: AssociationsManager? = null private set @@ -79,24 +81,33 @@ class SceneView : View() { mouseScreenPoint = extrudeEventMousePosition(event) } root.setOnMouseDragged { event -> - if (event.button != MouseDragButton) { + if (event.button != MouseInteractButton) { return@setOnMouseDragged } + wasMouseDragging = true mouseScreenPoint = extrudeEventMousePosition(event) canvas.dragTo(mouseScreenPoint) } root.setOnMousePressed { event -> - if (event.button != MouseDragButton) { + if (event.button != MouseInteractButton) { return@setOnMousePressed } canvas.startDragging(mouseScreenPoint) } root.setOnMouseReleased { event -> - if (event.button != MouseDragButton) { + if (event.button != MouseInteractButton) { return@setOnMouseReleased } canvas.stopDragging() } + root.setOnMouseClicked { event -> + if (event.button != MouseInteractButton) { + return@setOnMouseClicked + } + if (!wasMouseDragging) + canvas.interactWithLandmark(mouseScreenPoint) + wasMouseDragging = false + } root.setOnScroll { event -> val scrollDelta = event.deltaY if (scrollDelta > 0) { @@ -108,7 +119,7 @@ class SceneView : View() { } companion object { - private val MouseDragButton = MouseButton.MIDDLE + private val MouseInteractButton = MouseButton.PRIMARY const val framesMargin = 0.0 const val scrollSpeed = 20.0