1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
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
|