Visual Computing Library
Loading...
Searching...
No Matches
desktop_trackball.h
1/*****************************************************************************
2 * VCLib *
3 * Visual Computing Library *
4 * *
5 * Copyright(C) 2021-2025 *
6 * Visual Computing Lab *
7 * ISTI - Italian National Research Council *
8 * *
9 * All rights reserved. *
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the Mozilla Public License Version 2.0 as published *
13 * by the Mozilla Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 * This program is distributed in the hope that it will be useful, *
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
19 * Mozilla Public License Version 2.0 *
20 * (https://www.mozilla.org/en-US/MPL/2.0/) for more details. *
21 ****************************************************************************/
22
23#ifndef VCL_RENDER_VIEWER_DESKTOP_TRACKBALL_H
24#define VCL_RENDER_VIEWER_DESKTOP_TRACKBALL_H
25
26#include "trackball.h"
27
28#include <vclib/render/input.h>
29#include <vclib/space/core/bit_set.h>
30
31#include <map>
32
33namespace vcl {
34
35template<typename Scalar>
37{
38public:
39 using ScalarType = Scalar;
42
43 inline static const Point3<Scalar> UNIT_X = {1, 0, 0};
44 inline static const Point3<Scalar> UNIT_Y = {0, 1, 0};
45
46private:
47 using MotionType = vcl::TrackBall<Scalar>::MotionType;
48
49 // translation step in camera space
50 static constexpr double DISCRETE_TRANSLATION_STEP = 0.1;
51 static constexpr double DISCRETE_ROTATION_STEP = M_PI_4 / 3.0; // 15 deg
52
53 uint mWidth = 1024;
54 uint mHeight = 768;
55
56 vcl::TrackBall<Scalar> mTrackball;
57
58 Point3<Scalar> mDefaultTrackBallCenter;
59 float mDefaultTrackBallRadius = 1.0;
60
61 // current modifiers state (must be updated using setKeyModifiers)
62 KeyModifiers mCurrentKeyModifiers = {KeyModifier::NO_MODIFIER};
63 // current mouse button (automatically updated)
64 // if dragging holds the current mouse button
65 MouseButton::Enum mCurrentMouseButton = MouseButton::NO_BUTTON;
66
67 std::map<std::pair<MouseButton::Enum, KeyModifiers>, MotionType>
68 mDragMotionMap = {
69 // clang-format off
70 {{MouseButton::LEFT, {KeyModifier::NO_MODIFIER}},
71 TrackBallType::ARC },
72 {{MouseButton::LEFT, {KeyModifier::CONTROL}}, TrackBallType::PAN },
73 {{MouseButton::LEFT, {KeyModifier::ALT}}, TrackBallType::ZMOVE},
74 {{MouseButton::LEFT, {KeyModifier::SHIFT}}, TrackBallType::SCALE},
75 {{MouseButton::MIDDLE, {KeyModifier::NO_MODIFIER}},
76 TrackBallType::PAN },
77 {{MouseButton::MIDDLE, {KeyModifier::CONTROL}},
78 TrackBallType::ROLL },
79 {{MouseButton::LEFT, {KeyModifier::SHIFT, KeyModifier::CONTROL}},
80 TrackBallType::DIR_LIGHT_ARC },
81 // clang-format on
82 };
83
84 using Axis = unsigned char;
85 std::map<std::pair<KeyModifiers, Axis>, MotionType> mScrollAtomicMap = {
86 {{{KeyModifier::NO_MODIFIER}, 1}, TrackBallType::SCALE},
87 {{{KeyModifier::CONTROL}, 1}, TrackBallType::ROLL },
88 {{{KeyModifier::SHIFT}, 1}, TrackBallType::FOV },
89#ifdef __APPLE__
90 {{{KeyModifier::SHIFT}, 0}, TrackBallType::FOV },
91#endif
92 };
93
94 std::map<
95 std::pair<Key::Enum, KeyModifiers>,
96 std::function<void(TrackBallType& t)>>
97 mKeyAtomicMap = {
98 {{Key::R, {KeyModifier::NO_MODIFIER}},
99 [&](TrackBallType& t) {
100 t.reset(
101 mDefaultTrackBallCenter, 1.5 / mDefaultTrackBallRadius);
102 }},
103 {{Key::R, {KeyModifier::CONTROL, KeyModifier::SHIFT}},
104 [&](TrackBallType& t) {
105 t.resetDirectionalLight();
106 }},
107
108 // rotate
109 {{Key::NP_2, {KeyModifier::NO_MODIFIER}},
110 [](TrackBallType& t) {
111 rotate(t, UNIT_X, DISCRETE_ROTATION_STEP);
112 }},
113 {{Key::NP_4, {KeyModifier::NO_MODIFIER}},
114 [](TrackBallType& t) {
115 rotate(t, UNIT_Y, -DISCRETE_ROTATION_STEP);
116 }},
117 {{Key::NP_6, {KeyModifier::NO_MODIFIER}},
118 [](TrackBallType& t) {
119 rotate(t, UNIT_Y, DISCRETE_ROTATION_STEP);
120 }},
121 {{Key::NP_8, {KeyModifier::NO_MODIFIER}},
122 [](TrackBallType& t) {
123 rotate(t, UNIT_X, -DISCRETE_ROTATION_STEP);
124 }},
125
126 // translate
127 {{Key::UP, {KeyModifier::NO_MODIFIER}},
128 [](TrackBallType& t) {
129 translate(t, UNIT_Y * DISCRETE_TRANSLATION_STEP);
130 }},
131 {{Key::DOWN, {KeyModifier::NO_MODIFIER}},
132 [](TrackBallType& t) {
133 translate(t, -UNIT_Y * DISCRETE_TRANSLATION_STEP);
134 }},
135 {{Key::LEFT, {KeyModifier::NO_MODIFIER}},
136 [](TrackBallType& t) {
137 translate(t, -UNIT_X * DISCRETE_TRANSLATION_STEP);
138 }},
139 {{Key::RIGHT, {KeyModifier::NO_MODIFIER}},
140 [](TrackBallType& t) {
141 translate(t, UNIT_X * DISCRETE_TRANSLATION_STEP);
142 }},
143
144 // set view
145 {{Key::NP_1, {KeyModifier::NO_MODIFIER}},
146 [](TrackBallType& t) { // front
147 t.reset();
148 }},
149 {{Key::NP_7, {KeyModifier::NO_MODIFIER}},
150 [](TrackBallType& t) { // top
151 t.reset();
152 rotate(t, UNIT_X, M_PI_2);
153 }},
154 {{Key::NP_3, {KeyModifier::NO_MODIFIER}},
155 [](TrackBallType& t) { // right
156 t.reset();
157 rotate(t, UNIT_Y, -M_PI_2);
158 }},
159 {{Key::NP_1, {KeyModifier::CONTROL}},
160 [](TrackBallType& t) { // back
161 t.reset();
162 rotate(t, UNIT_Y, M_PI);
163 }},
164 {{Key::NP_7, {KeyModifier::CONTROL}},
165 [](TrackBallType& t) { // bottom
166 t.reset();
167 rotate(t, UNIT_X, -M_PI_2);
168 }},
169 {{Key::NP_3, {KeyModifier::CONTROL}},
170 [](TrackBallType& t) { // left
171 t.reset();
172 rotate(t, UNIT_Y, M_PI_2);
173 }},
174 // projection mode
175 {{Key::NP_5, {KeyModifier::NO_MODIFIER}},
176 [](TrackBallType& t) { // reset
177 const auto v =
178 t.projectionMode() ==
182 t.setProjectionMode(v);
183 }},
184
185 // rotate light
186 {{Key::NP_2, {KeyModifier::CONTROL, KeyModifier::SHIFT}},
187 [](TrackBallType& t) {
188 rotateLight(t, UNIT_X, DISCRETE_ROTATION_STEP);
189 }},
190 {{Key::NP_4, {KeyModifier::CONTROL, KeyModifier::SHIFT}},
191 [](TrackBallType& t) {
192 rotateLight(t, UNIT_Y, -DISCRETE_ROTATION_STEP);
193 }},
194 {{Key::NP_6, {KeyModifier::CONTROL, KeyModifier::SHIFT}},
195 [](TrackBallType& t) {
196 rotateLight(t, UNIT_Y, DISCRETE_ROTATION_STEP);
197 }},
198 {{Key::NP_8, {KeyModifier::CONTROL, KeyModifier::SHIFT}},
199 [](TrackBallType& t) {
200 rotateLight(t, UNIT_X, -DISCRETE_ROTATION_STEP);
201 }},
202 };
203
204public:
205 DesktopTrackBall(uint width = 1024, uint height = 768)
206 {
207 resizeViewer(width, height);
208 }
209
210 bool isDragging() const { return mTrackball.isDragging(); }
211
212 MotionType currentMotion() const { return mTrackball.currentMotion(); }
213
214 DirectionalLight<Scalar> light() const { return mTrackball.light(); }
215
216 const Camera<Scalar>& camera() const { return mTrackball.camera(); }
217
218 Matrix44<Scalar> viewMatrix() const { return mTrackball.viewMatrix(); }
219
220 Matrix44<Scalar> lightGizmoMatrix() const
221 {
222 return mTrackball.lightGizmoMatrix();
223 }
224
225 Matrix44<Scalar> gizmoMatrix() const { return mTrackball.gizmoMatrix(); }
226
227 Matrix44<Scalar> projectionMatrix() const
228 {
229 return mTrackball.projectionMatrix();
230 }
231
232 void resetTrackBall()
233 {
234 mTrackball.reset(
235 mDefaultTrackBallCenter, 1.5 / mDefaultTrackBallRadius);
236 }
237
238 void setTrackBall(const Point3<Scalar>& center, Scalar radius)
239 {
240 mDefaultTrackBallCenter = center;
241 mDefaultTrackBallRadius = radius;
242
243 resetTrackBall();
244 }
245
246 void focus(const Point3<Scalar>& center)
247 {
248 mTrackball.applyAtomicMotion(TrackBallType::FOCUS, center);
249 }
250
251 void resizeViewer(uint w, uint h)
252 {
253 mWidth = w;
254 mHeight = h;
255 mTrackball.setScreenSize(w, h);
256 }
257
258 void setKeyModifiers(KeyModifiers keys) { mCurrentKeyModifiers = keys; }
259
260 void moveMouse(int x, int y)
261 {
262 // ugly AF
263 auto it = mDragMotionMap.find(
264 std::make_pair(mCurrentMouseButton, mCurrentKeyModifiers));
265 if (it != mDragMotionMap.end()) {
266 mTrackball.beginDragMotion(it->second);
267 }
268 mTrackball.setMousePosition(x, y);
269 mTrackball.update();
270 }
271
272 void pressMouse(MouseButton::Enum button)
273 {
274 // if dragging, do not update the current mouse button
275 if (mTrackball.isDragging()) {
276 return;
277 }
278
279 mCurrentMouseButton = button;
280
281 auto it =
282 mDragMotionMap.find(std::make_pair(button, mCurrentKeyModifiers));
283 if (it != mDragMotionMap.end()) {
284 mTrackball.beginDragMotion(it->second);
285 // no need to update here, it will be updated in moveMouse
286 // for event driven rendering (e.g., Qt) this can trigger
287 // an unwanted drag motion using the previous mouse position
288 }
289 }
290
291 void releaseMouse(MouseButton::Enum button)
292 {
293 // if dragging, update the current mouse button only if it matches
294 if (mTrackball.isDragging() && mCurrentMouseButton == button) {
295 mCurrentMouseButton = MouseButton::NO_BUTTON;
296 }
297
298 auto it =
299 mDragMotionMap.find(std::make_pair(button, mCurrentKeyModifiers));
300 if (it != mDragMotionMap.end()) {
301 mTrackball.endDragMotion(it->second);
302 mTrackball.update();
303 }
304 }
305
306 void scroll(Scalar pixelDeltaX, Scalar pixelDeltaY)
307 {
308 if (pixelDeltaX == 0 && pixelDeltaY == 0) {
309 return;
310 }
311
312 if (pixelDeltaX != 0) {
313 auto it = mScrollAtomicMap.find({mCurrentKeyModifiers, Axis(0)});
314 if (it != mScrollAtomicMap.end()) {
315 mTrackball.applyAtomicMotion(it->second, pixelDeltaX);
316 }
317 }
318
319 if (pixelDeltaY != 0) {
320 auto it = mScrollAtomicMap.find({mCurrentKeyModifiers, Axis(1)});
321 if (it != mScrollAtomicMap.end()) {
322 mTrackball.applyAtomicMotion(it->second, pixelDeltaY);
323 }
324 }
325 }
326
327 void keyPress(Key::Enum key)
328 {
329 // atomic motions are enabled while dragging
330 auto atomicOp = mKeyAtomicMap.find({key, mCurrentKeyModifiers});
331 if (atomicOp != mKeyAtomicMap.end()) {
332 atomicOp->second(mTrackball);
333 }
334
335 // dragging
336 auto it = mDragMotionMap.find(
337 std::make_pair(mCurrentMouseButton, mCurrentKeyModifiers));
338 if (it != mDragMotionMap.end()) {
339 mTrackball.beginDragMotion(it->second);
340 }
341 else {
342 mTrackball.endDragMotion(currentMotion());
343 mTrackball.update();
344 }
345 }
346
347 void keyRelease(Key::Enum key)
348 {
349 // ugly solution to end drag motion when a key is released
350 if (!mTrackball.isDragging())
351 return;
352
353 // dragging
354 auto it = mDragMotionMap.find(
355 std::make_pair(mCurrentMouseButton, mCurrentKeyModifiers));
356 if (it != mDragMotionMap.end()) {
357 mTrackball.beginDragMotion(it->second);
358 }
359 else {
360 mTrackball.endDragMotion(currentMotion());
361 mTrackball.update();
362 }
363 }
364
365private:
366 static void rotate(
367 TrackBallType& t,
368 const Point3<Scalar>& axis,
369 Scalar angle = M_PI / 6)
370 {
371 using Args = typename TrackBallType::TransformArgs;
372 t.applyAtomicMotion(TrackBallType::ARC, Args(axis, angle));
373 }
374
375 static void rotateLight(
376 TrackBallType& t,
377 const Point3<Scalar>& axis,
378 Scalar angle = M_PI / 6)
379 {
380 using Args = typename TrackBallType::TransformArgs;
381 t.applyAtomicMotion(TrackBallType::DIR_LIGHT_ARC, Args(axis, angle));
382 }
383
384 static void translate(TrackBallType& t, const Point3<Scalar>& translation)
385 {
386 t.applyAtomicMotion(TrackBallType::PAN, translation);
387 }
388};
389
390} // namespace vcl
391
392#endif // VCL_RENDER_VIEWER_DESKTOP_TRACKBALL_H
Definition camera.h:32
Definition desktop_trackball.h:37
A class representing a line segment in n-dimensional space. The class is parameterized by a PointConc...
Definition segment.h:43