diff options
Diffstat (limited to 'dev/MinGfx/src/unicam.h')
-rw-r--r-- | dev/MinGfx/src/unicam.h | 256 |
1 files changed, 256 insertions, 0 deletions
diff --git a/dev/MinGfx/src/unicam.h b/dev/MinGfx/src/unicam.h new file mode 100644 index 0000000..999232b --- /dev/null +++ b/dev/MinGfx/src/unicam.h @@ -0,0 +1,256 @@ +/* + This file is part of the MinGfx Project. + + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + + Original Author(s) of this File: + Dan Keefe, 2018, University of Minnesota + + Author(s) of Significant Updates/Modifications to the File: + ... + */ + +#ifndef SRC_UNICAM_H_ +#define SRC_UNICAM_H_ + +#include "quick_shapes.h" +#include "point2.h" +#include "point3.h" +#include "vector2.h" +#include "vector3.h" + + +namespace mingfx { + + +/** This implements a user interface for controlling the camera with the mouse. + It is a special interface inspired by the "Unicam" technique developed by + Zeleznik et al. + + The key feature is that this interface makes it possible to control camera pan, + dolly, and rotation with only a single mouse button. That is quite useful + because it leaves the other mouse buttons free for pointing, sketching, or + other interaction techniques. + + The only downside of this technique is that it can take some time to learn. In + order to enjoy it, you will need to read these brief instructions on how to Pan, + Dolly, Rotate, and Spin: + + - Pan: Click and drag horizontally with the mouse. Once you make an initial + horizontal movement you can than pan up and down as well, but the key to entering + pan mode is to start with a horizontal movement. + + - Dolly: Click and drag vertically with the mouse. The initial movement must + be vertical. If you click on some object in the scene, then the speed of dollying + is set so that the object will come all the up to the camera lens if you drag + the mouse to the bottom of the screen. + + - Rotate: Start with a quick click and release to set the center of rotation. + This is most useful if you click on some object in the scene. You will see a + black dot appear to mark the center of rotation. If you click on the background + then a center of rotation will be selected for you. It will be a point straight + ahead and at a depth 4.0 units away. The depth can be adjusted for your application + with set_default_depth(). Once your center of rotation is established, move + your mouse away a bit and then click and drag to do a trackball rotatation of + the scene around this point. Come to a stop before letting go of the mouse + button in order to avoid entering the spin state! + + - Spin: For some fun, try "throwing" the scene so that it continues to rotate + even after you let go. To do this, start a rotation and then let go of the + mouse button while your mouse is still moving. To stop spinning just click and + release the mouse once to "catch" the scene. + + + Example usage: +~~~ +// Create a global or member variable in your MyGraphicsApp class: +UniCam unicam_; + + +void MyGraphicsApp::OnLeftMouseDown(const Point2 &pos) { + Point2 mouse_xy = PixelsToNormalizedDeviceCoords(pos); + float mouse_z = ReadZValueAtPixel(pos); + unicam_.OnButtonDown(mouse_xy, mouse_z); +} + +void MyGraphicsApp::OnLeftMouseDrag(const Point2 &pos, const Vector2 &delta) { + Point2 mouse_xy = PixelsToNormalizedDeviceCoords(pos); + unicam_.OnDrag(mouse_xy); +} + +void MyGraphicsApp::OnLeftMouseUp(const Point2 &pos) { + Point2 mouse_xy = PixelsToNormalizedDeviceCoords(pos); + unicam_.OnButtonUp(mouse_xy); +} + +void MyGraphicsApp::InitOpenGL() { + projMatrix_ = Matrix4::perspective(30, aspect_ratio(), 1, 20); + unicam_.set_view_matrix(Matrix4::lookAt(Point3(2.5,1,2.5), Point3(0,0,0), Vector3(0,1,0));); +} + +void MyGraphicsApp::DrawOpenGL() { + // draw your scene using the view matrix from UniCam + Matrix4 proj_matrix = Matrix4::Perspective(60, aspect_ratio(), 0.001, 10);; + Matrix4 view_matrix = uniCam.view_matrix(); + Matrix4 model_matrix = Matrix4::RotateY(to_radians(45.0)); + quickShapes.DrawCube(model_matrix, view_matirx, proj_matrix, Color(1,1,1)); + + // tell unicam to draw itself (i.e., the small sphere that marks the center of + // rotation when in rotation mode) + unicam_.Draw(proj_matrix); +} +~~~ +*/ +class UniCam { +public: + + /// Creates a UniCam object with an initial view matrix = identity. + UniCam(); + + /// Creates a UniCam object with the supplied initial view matrix. + UniCam(const Matrix4 &initialViewMatrix); + + virtual ~UniCam(); + + + // To make the interaction work, the following set of functions need to be + // called from your GraphicsApp or whatever main application class you use + // to receive user input events and a draw callback. + + /// Attach this to whatever mouse button you wish, for example, call this + /// from within GraphicsApp::OnRightMouseDown(). If your mousePos is reported + /// in pixels, you will need to convert it to normalized device coordinates + /// before passing it on to this routine. The depth buffer value for the + /// pixel under the mouse is also needed. If you are using GraphicsApp, you + /// can access both of these as follows: + /// ~~~ + /// Point2 mouse_xy = PixelsToNormalizedDeviceCoords(mouse_in_pixels); + /// float mouse_z = ReadZValueAtPixel(mouse_in_pixels); + /// uniCam.OnButtonDown(mouse_xy, mouse_z); + /// ~~~ + void OnButtonDown(const Point2 &normalizedMousePos, float mouseZ); + + /// Attach this to the corresponding mouse move event, for example, call this + /// from within GraphicsApp::OnRightMouseDrag(). If your mousePos is reported + /// in pixels, you will need to convert it to normalized device coordinates + /// before passing it on to this routine. Within GraphicsApp, use: + /// ~~~ + /// Point2 mouse_xy = PixelsToNormalizedDeviceCoords(mouse_in_pixels); + /// uniCam.OnDrag(mouse_xy); + /// ~~~ + void OnDrag(const Point2 &normalizedMousePos); + + /// Attach this to the corresponding button up event, for example, call this + /// from within GraphicsApp::OnRightMouseUp(). If your mousePos is reported + /// in pixels, you will need to convert it to normalized device coordinates + /// before passing it on to this routine. Within GraphicsApp, use: + /// ~~~ + /// Point2 mouse_xy = PixelsToNormalizedDeviceCoords(mouse_in_pixels); + /// uniCam.OnButtonUp(mouse_xy); + /// ~~~ + void OnButtonUp(const Point2 &normalizedMousePos); + + /// Attach this to a callback that can be used to control animation. Within + /// GraphicsApp::UpdateSimulation(), use: + /// ~~~ + /// uniCam.AdvanceAnimation(dt); + /// ~~~ + void AdvanceAnimation(double dt); + + /// Finally, attach this to your draw callback routine. Within + /// GraphicsApp::DrawUsingOpenGL(), use: + /// ~~~ + /// uniCam.Draw(projMatrix); + /// ~~~ + void Draw(const Matrix4 &projectionMatrix); + + + /// Access the camera view matrix created by the UniCam interactions via + /// this method and use it to draw the geometry in your scence. + /// For example, within GraphicsApp::DrawUsingOpenGL(), you might have: + /// ~~~ + /// Matrix4 P = Matrix4::Perspective(30, aspect_ratio(), 1, 20); + /// Matrix4 V = unicam.view_matrix(); + /// Matrix4 M = Matrix4::RotateY(GfxMath::ToRadians(45.0)); + /// quick_shapes.DrawCube(M, V, P, Color(1,1,1)); + /// ~~~ + Matrix4 view_matrix(); + + + /// Returns the "eye" point (i.e., focal point) of the camera in world + /// space coordinates. + Point3 eye(); + + /// Returns the look direction (i.e., -Z axis of the camera matrix) in world + /// space coordinates. + Vector3 look(); + + + // ------------- + + /// This is not required, but you may use this if you wish to set an initial + /// view matrix or reset the view matrix + void set_view_matrix(Matrix4 viewMatrix); + + /// This sets the depth of the center of rotation for the case when the user's + /// click does not intersect any geometry. It defaults to 4 units, but the + /// right value to use depends very much on the current scene. For example, + /// you could set a very good value by calculating the current centroid of + /// your scene and the finding the depth of this point (the distance along + /// the look vector) relative to the camera. + void set_default_depth(float d); + + +private: + + void recalc_angular_vel(); + + enum class UniCamState { + START, + PAN_DOLLY_ROT_DECISION, + PAN_DOLLY_DECISION, + ROT_WAIT_FOR_SECOND_CLICK, + PAN, + DOLLY, + ROT, + SPINNING + }; + UniCamState state_; + + Point2 mouseLast_; + double elapsedTime_; + + Point2 initialClickPos_; + bool hitGeometry_; + Point3 hitPoint_; + + bool rotInitialized_; + Point3 rotLastIPoint_; + float boundingSphereRad_; + Point3 boundingSphereCtr_; + double rotLastTime_; + std::vector<std::pair<double, double>> rotAngularVelBuffer_; + double rotAngularVel_; + Vector3 rotAxis_; + + bool dollyInitialized_; + float dollyFactor_; + float defaultDepth_; + + bool showIcon_; + QuickShapes quickShapes_; + + Matrix4 V_; + Matrix4 Vstart_; + + // saved from the last draw call in order to unproject the mouse pos + Matrix4 Pdraw_; +}; + + +} // end namespace + +#endif + + |