diff --git a/orx-camera/src/commonMain/kotlin/Camera2D.kt b/orx-camera/src/commonMain/kotlin/Camera2D.kt index b5431577..365c22e5 100644 --- a/orx-camera/src/commonMain/kotlin/Camera2D.kt +++ b/orx-camera/src/commonMain/kotlin/Camera2D.kt @@ -13,7 +13,7 @@ import org.openrndr.math.Vector2 import org.openrndr.math.transforms.buildTransform /** - * The [Camera2D] extension enables panning, rotating and zooming the view + * The [Camera2D] extension enables panning, rotating, and zooming the view * with the mouse: * - left click and drag to **pan** * - right click and drag to **rotate** @@ -59,6 +59,17 @@ class Camera2D : Extension, ChangeEvents { } } + /** + * 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`, + * indicating that the camera's state has been modified and needs to be updated or rendered accordingly. + */ + fun defaults() { + view = Matrix44.IDENTITY + rotationCenter = Vector2.ZERO + dirty = true + } + /** * Configures the mouse interaction events for controlling the camera view and handling * transformations such as translation, rotation, and scaling via mouse inputs. @@ -71,8 +82,7 @@ class Camera2D : Extension, ChangeEvents { rotationCenter = it.position if (it.button == MouseButton.CENTER) { - view = Matrix44.IDENTITY - dirty = true + defaults() } } mouse.dragged.listen { diff --git a/orx-camera/src/commonMain/kotlin/OrbitalCamera.kt b/orx-camera/src/commonMain/kotlin/OrbitalCamera.kt index 8ae4c4c2..fb3647fc 100644 --- a/orx-camera/src/commonMain/kotlin/OrbitalCamera.kt +++ b/orx-camera/src/commonMain/kotlin/OrbitalCamera.kt @@ -65,6 +65,24 @@ class OrbitalCamera( var orthoNear = -1000.0 var orthoFar = 1000.0 + // Defaults + val eyeDefault = eye.copy() + val lookAtDefault = lookAt.copy() + val fovDefault = fov + val magnitudeDefault = magnitude + + /** + * Reinitialize the camera to its initial state. + * + * @param instant whether the rotation is applied immediately; if false, it interpolates over time (default is false) + */ + fun defaults(instant: Boolean = false) { + panTo(lookAtDefault, instant) + rotateTo(eyeDefault, instant) + zoomTo(fovDefault, instant) + scaleTo(magnitudeDefault, instant) + } + /** * Sets the view for the orbital camera by updating the look-at position, spherical coordinates, * and field of view (FOV). This method initializes both the target and current states of these properties. diff --git a/orx-camera/src/jvmDemo/kotlin/DemoCamera2D02.kt b/orx-camera/src/jvmDemo/kotlin/DemoCamera2D02.kt index e39eb4bc..7291d65b 100644 --- a/orx-camera/src/jvmDemo/kotlin/DemoCamera2D02.kt +++ b/orx-camera/src/jvmDemo/kotlin/DemoCamera2D02.kt @@ -21,8 +21,8 @@ fun main() = application { colorBuffer() depthBuffer() } - // Create camera and apply an initial transformation - // so the origin is no longer in the top left corner. + // Create a camera and apply an initial transformation + // so the origin is no longer in the top-left corner. val cam = Camera2D() cam.view *= transform { translate(width * 0.5, height * 1.0) diff --git a/orx-camera/src/jvmDemo/kotlin/DemoCamera2DManual01.kt b/orx-camera/src/jvmDemo/kotlin/DemoCamera2DManual01.kt index a4a9211d..b668dad4 100644 --- a/orx-camera/src/jvmDemo/kotlin/DemoCamera2DManual01.kt +++ b/orx-camera/src/jvmDemo/kotlin/DemoCamera2DManual01.kt @@ -1,26 +1,17 @@ -import org.openrndr.PresentationMode import org.openrndr.application import org.openrndr.color.ColorRGBa -import org.openrndr.draw.isolatedWithTarget -import org.openrndr.draw.loadFont -import org.openrndr.draw.renderTarget -import org.openrndr.extra.camera.Camera2D import org.openrndr.extra.camera.Camera2DManual -import org.openrndr.math.Vector2 -import org.openrndr.math.transforms.transform -import org.openrndr.shape.Rectangle -import kotlin.math.sin /** * Demonstrate the use of `Camera2DManual` for manual camera control. * * The application is configured with a 720x720 window size. Within the program, a custom camera (`Camera2DManual`) - * is initialized and used to create isolated drawing scopes. The `isolated` method is utilized to overlay different + * is initialized and used to create isolated drawing scopes. The `isolated` method is used to overlay different * drawing operations while maintaining individual camera states, ensuring proper transformations for specific elements. * - * A pink circle is drawn at the center of the canvas with varying radii using isolated and non-isolated camera states. - * The outermost and innermost circles are affected by the `Camera2DManual` isolated scope, while the middle circle - * is outside of the camera's isolated scope, creating a layered visual effect. + * Three circles are drawn on the canvas: a small pink one, a medium white one and a large pink one. + * Only the pink ones are affected by the interactive `Camera2DManual`, while the middle white circle is outside + * the camera's isolated scope. */ fun main() = application { configure { diff --git a/orx-camera/src/jvmDemo/kotlin/DemoCamera2DManual02.kt b/orx-camera/src/jvmDemo/kotlin/DemoCamera2DManual02.kt new file mode 100644 index 00000000..72f7ea76 --- /dev/null +++ b/orx-camera/src/jvmDemo/kotlin/DemoCamera2DManual02.kt @@ -0,0 +1,69 @@ +import org.openrndr.application +import org.openrndr.color.ColorRGBa +import org.openrndr.extra.camera.Camera2DManual +import org.openrndr.extra.noise.shapes.uniform +import org.openrndr.extra.noise.uniform +import org.openrndr.math.Vector2 +import org.openrndr.math.transforms.transform +import org.openrndr.shape.Rectangle +import org.openrndr.shape.contains + +/** + * Demonstrate the use of `Camera2DManual` to independently translate, scale and rotate one contour + * in a collection. + * + * When the mouse is clicked, the active contour is transformed using the camera view matrix, + * then the camera is reset to its default state and whatever shape is under the mouse becomes + * the new active contour. + * + * As the mouse is dragged or its wheel scrolled, the camera is updated, affecting + * how the active contour is rendered. + */ +fun main() = application { + configure { + width = 720 + height = 720 + } + + program { + val camera = Camera2DManual() + // Create a mutable list of rectangular contours with random transformations + // applied to them. This is the initial state for the contours. + val contours = MutableList(16) { + Rectangle.fromCenter(Vector2.ZERO, 85.0).contour.transform( + transform { + translate(drawer.bounds.uniform()) + scale(Double.uniform(0.5, 2.0)) + rotate(Double.uniform(0.0, 360.0)) + } + ) + } + + var activeContour = -1 + extend { + // Draw all contours. The active contour is drawn in pink and + // affected by the camera's transformations. + contours.forEachIndexed { i, c -> + if (i == activeContour) { + camera.isolated { + drawer.fill = ColorRGBa.PINK + drawer.contour(c) + } + } else { + drawer.fill = ColorRGBa.GRAY + drawer.contour(c) + } + } + } + mouse.buttonDown.listen { + // Apply the camera view matrix to the active contour + if (activeContour >= 0) contours[activeContour] = contours[activeContour].transform(camera.view) + + // Reset the camera to its default state + camera.defaults() + + // Make the contour under the mouse the active contour + activeContour = contours.indexOfLast { mouse.position in it } + } + } +} diff --git a/orx-camera/src/jvmDemo/kotlin/DemoOrbitalCamera01.kt b/orx-camera/src/jvmDemo/kotlin/DemoOrbitalCamera01.kt index 43f08ff1..c30ac9fb 100644 --- a/orx-camera/src/jvmDemo/kotlin/DemoOrbitalCamera01.kt +++ b/orx-camera/src/jvmDemo/kotlin/DemoOrbitalCamera01.kt @@ -54,5 +54,10 @@ fun main() = application { drawer.circle(0.0, 0.0, 50.0) } } + keyboard.keyDown.listen { + if (it.name == "r") { + camera.defaults() + } + } } } \ No newline at end of file