[orx-camera] Introduce customizable camera defaults and control functions
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
package org.openrndr.extra.camera
|
package org.openrndr.extra.camera
|
||||||
|
|
||||||
import org.openrndr.Extension
|
import org.openrndr.Extension
|
||||||
|
import org.openrndr.KeyEvents
|
||||||
import org.openrndr.MouseButton
|
import org.openrndr.MouseButton
|
||||||
import org.openrndr.MouseEvents
|
import org.openrndr.MouseEvents
|
||||||
import org.openrndr.Program
|
import org.openrndr.Program
|
||||||
@@ -26,8 +27,35 @@ class Camera2D : Extension, ChangeEvents {
|
|||||||
|
|
||||||
private lateinit var program: Program
|
private lateinit var program: Program
|
||||||
private var controlInitialized = false
|
private var controlInitialized = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the 4x4 transformation matrix of the camera view in a 2D drawing environment.
|
||||||
|
* This matrix is used to apply custom transformations such as translation, rotation,
|
||||||
|
* or scaling to the viewport. By default, it is set to the identity matrix.
|
||||||
|
*
|
||||||
|
* When modified, the `dirty` flag is automatically set to `true` to indicate
|
||||||
|
* that the view matrix has been updated and subsequent transformations might
|
||||||
|
* need recalculation or application.
|
||||||
|
*/
|
||||||
var view = Matrix44.IDENTITY
|
var view = Matrix44.IDENTITY
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
dirty = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the center of rotation for the camera in 2D space.
|
||||||
|
*
|
||||||
|
* Changes to this property will mark the camera's state as dirty, necessitating
|
||||||
|
* a re-calculation of the view transformation.
|
||||||
|
*
|
||||||
|
* Default value is [Vector2.ZERO].
|
||||||
|
*/
|
||||||
var rotationCenter = Vector2.ZERO
|
var rotationCenter = Vector2.ZERO
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
dirty = true
|
||||||
|
}
|
||||||
|
|
||||||
override val changed = Event<Unit>()
|
override val changed = Event<Unit>()
|
||||||
|
|
||||||
@@ -61,13 +89,90 @@ class Camera2D : Extension, ChangeEvents {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Reinitialize the camera to its default state, where no transformations
|
* Reinitialize the camera to its default state, where no transformations
|
||||||
* (such as rotation, translation, or scaling) are applied. It also sets the `dirty` flag to `true`,
|
* (such as rotation, translation, or scaling) are applied.
|
||||||
* indicating that the camera's state has been modified and needs to be updated or rendered accordingly.
|
|
||||||
*/
|
*/
|
||||||
fun defaults() {
|
var defaults = {
|
||||||
view = Matrix44.IDENTITY
|
view = Matrix44.IDENTITY
|
||||||
rotationCenter = Vector2.ZERO
|
rotationCenter = Vector2.ZERO
|
||||||
dirty = true
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies a panning transformation to the camera view. The method modifies the current view
|
||||||
|
* by translating it based on the provided displacement vector, effectively shifting the
|
||||||
|
* camera's view in the scene.
|
||||||
|
*
|
||||||
|
* @param displacement the vector by which the camera view is translated.
|
||||||
|
*/
|
||||||
|
fun pan(displacement: Vector2) {
|
||||||
|
view = buildTransform {
|
||||||
|
translate(displacement)
|
||||||
|
} * view
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotates the camera view by a specified angle around its rotation center.
|
||||||
|
*
|
||||||
|
* @param angle the angle in degrees by which the view is rotated.
|
||||||
|
*/
|
||||||
|
fun rotate(angle: Double) {
|
||||||
|
view = buildTransform {
|
||||||
|
translate(rotationCenter)
|
||||||
|
rotate(angle)
|
||||||
|
translate(-rotationCenter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies a zoom transformation to the camera view. The transformation is centered
|
||||||
|
* around the specified point while adjusting the zoom level by the given factor.
|
||||||
|
*
|
||||||
|
* @param center The point in space around which the zoom transformation is centered.
|
||||||
|
* @param factor The zoom factor, where values greater than 1.0 zoom in and values less than 1.0 zoom out.
|
||||||
|
*/
|
||||||
|
fun zoom(center: Vector2, factor: Double) {
|
||||||
|
view = buildTransform {
|
||||||
|
translate(center)
|
||||||
|
scale(factor)
|
||||||
|
translate(-center)
|
||||||
|
} * view
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up and applies mouse and keyboard controls for interacting with the camera.
|
||||||
|
* This variable provides event-driven logic to handle user input for panning, rotation, and zooming.
|
||||||
|
*
|
||||||
|
* - Mouse button interactions are used to configure the center of rotation and reset the view.
|
||||||
|
* - Mouse drag events control panning and rotation with the left and right mouse buttons respectively.
|
||||||
|
* - Mouse scrolling adjusts the zoom level based on the scroll direction and position.
|
||||||
|
*
|
||||||
|
* @param mouse an instance of `MouseEvents` providing data for mouse interactions,
|
||||||
|
* such as button presses, movement, and scrolling.
|
||||||
|
* @param keyboard an instance of `KeyEvents` providing the framework for handling keyboard inputs,
|
||||||
|
* though currently unused in this implementation.
|
||||||
|
*/
|
||||||
|
var controls = { mouse: MouseEvents, keyboard: KeyEvents ->
|
||||||
|
mouse.buttonDown.listen {
|
||||||
|
rotationCenter = it.position
|
||||||
|
if (it.button == MouseButton.CENTER) {
|
||||||
|
defaults()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mouse.dragged.listen {
|
||||||
|
if (!it.propagationCancelled) {
|
||||||
|
when (it.button) {
|
||||||
|
MouseButton.LEFT -> pan(it.dragDisplacement)
|
||||||
|
MouseButton.RIGHT -> rotate(it.dragDisplacement.x + it.dragDisplacement.y)
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mouse.scrolled.listen {
|
||||||
|
if (!it.propagationCancelled) {
|
||||||
|
val scaleFactor = 1.0 - it.rotation.y * 0.03
|
||||||
|
zoom(it.position, scaleFactor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,42 +182,9 @@ class Camera2D : Extension, ChangeEvents {
|
|||||||
* @param mouse the MouseEvents instance that provides mouse interaction data, including
|
* @param mouse the MouseEvents instance that provides mouse interaction data, including
|
||||||
* button presses, dragging, and scrolling events.
|
* button presses, dragging, and scrolling events.
|
||||||
*/
|
*/
|
||||||
fun setupMouseEvents(mouse: MouseEvents) {
|
fun setupControls(mouse: MouseEvents, keyboard: KeyEvents) {
|
||||||
mouse.buttonDown.listen {
|
if (!controlInitialized) {
|
||||||
rotationCenter = it.position
|
controls(mouse, keyboard)
|
||||||
|
|
||||||
if (it.button == MouseButton.CENTER) {
|
|
||||||
defaults()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mouse.dragged.listen {
|
|
||||||
if (!it.propagationCancelled) {
|
|
||||||
when (it.button) {
|
|
||||||
MouseButton.LEFT -> view = buildTransform {
|
|
||||||
translate(it.dragDisplacement)
|
|
||||||
} * view
|
|
||||||
|
|
||||||
MouseButton.RIGHT -> view = buildTransform {
|
|
||||||
translate(rotationCenter)
|
|
||||||
rotate(it.dragDisplacement.x + it.dragDisplacement.y)
|
|
||||||
translate(-rotationCenter)
|
|
||||||
} * view
|
|
||||||
|
|
||||||
else -> Unit
|
|
||||||
}
|
|
||||||
dirty = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mouse.scrolled.listen {
|
|
||||||
if (!it.propagationCancelled) {
|
|
||||||
val scaleFactor = 1.0 - it.rotation.y * 0.03
|
|
||||||
view = buildTransform {
|
|
||||||
translate(it.position)
|
|
||||||
scale(scaleFactor)
|
|
||||||
translate(-it.position)
|
|
||||||
} * view
|
|
||||||
dirty = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
controlInitialized = true
|
controlInitialized = true
|
||||||
}
|
}
|
||||||
@@ -120,8 +192,9 @@ class Camera2D : Extension, ChangeEvents {
|
|||||||
override fun setup(program: Program) {
|
override fun setup(program: Program) {
|
||||||
this.program = program
|
this.program = program
|
||||||
if (!controlInitialized) {
|
if (!controlInitialized) {
|
||||||
setupMouseEvents(program.mouse)
|
setupControls(program.mouse, program.keyboard)
|
||||||
}
|
}
|
||||||
|
defaults()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun beforeDraw(drawer: Drawer, program: Program) {
|
override fun beforeDraw(drawer: Drawer, program: Program) {
|
||||||
@@ -137,12 +210,19 @@ class Camera2D : Extension, ChangeEvents {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance of the Camera2D extension suitable for manual application.
|
* Creates and sets up a custom-configured Camera2D instance within a Program.
|
||||||
*
|
*
|
||||||
* @return a configured Camera2D instance ready to be used with the calling Program.
|
* This function initializes a new Camera2D, applies the provided configuration block,
|
||||||
|
* and sets it up with the current Program context for interactive 2D transformations
|
||||||
|
* such as panning, rotating, and zooming.
|
||||||
|
*
|
||||||
|
* @param configure an optional configuration block where you can set up the Camera2D
|
||||||
|
* instance (e.g., setting view or rotation center). The default is an empty block.
|
||||||
|
* @return the configured Camera2D instance.
|
||||||
*/
|
*/
|
||||||
fun Program.Camera2DManual(): Camera2D {
|
fun Program.Camera2DManual(configure: Camera2D.() -> Unit = { }): Camera2D {
|
||||||
val camera = Camera2D()
|
val camera = Camera2D()
|
||||||
|
camera.configure()
|
||||||
camera.setup(this)
|
camera.setup(this)
|
||||||
return camera
|
return camera
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user