Visual Computing Library  devel
Loading...
Searching...
No Matches
trackball_event_drawer.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_DRAWERS_TRACKBALL_EVENT_DRAWER_H
24#define VCL_RENDER_DRAWERS_TRACKBALL_EVENT_DRAWER_H
25
26#include "event_drawer.h"
27
28#include <vclib/render/input.h>
29#include <vclib/render/viewer/trackball.h>
30#include <vclib/space/core/bit_set.h>
31
32#include <map>
33
34namespace vcl {
35
36template<typename Scalar, typename DerivedRenderApp>
37class TrackBallEventDrawerT : public EventDrawer<DerivedRenderApp>
38{
39public:
40 using ScalarType = Scalar;
44
45 inline static const Point3<Scalar> UNIT_X = {1, 0, 0};
46 inline static const Point3<Scalar> UNIT_Y = {0, 1, 0};
47
48private:
50
51 using MotionType = vcl::TrackBall<Scalar>::MotionType;
52
53 // translation step in camera space
54 static constexpr double DISCRETE_TRANSLATION_STEP = 0.1;
55 static constexpr double DISCRETE_ROTATION_STEP = M_PI_4 / 3.0; // 15 deg
56
57 uint mWidth = 1024;
58 uint mHeight = 768;
59
60 vcl::TrackBall<Scalar> mTrackball;
61
62 Point3<Scalar> mDefaultTrackBallCenter;
63 float mDefaultTrackBallRadius = 1.0;
64
65 // current modifiers state (must be updated using setKeyModifiers)
66 KeyModifiers mCurrentKeyModifiers = {KeyModifier::NO_MODIFIER};
67 // current mouse button (automatically updated)
68 // if dragging holds the current mouse button
69 MouseButton::Enum mCurrentMouseButton = MouseButton::NO_BUTTON;
70
71 std::map<std::pair<MouseButton::Enum, KeyModifiers>, MotionType>
72 mDragMotionMap = {
73 // clang-format off
74 {{MouseButton::LEFT, {KeyModifier::NO_MODIFIER}},
75 TrackBallType::ARC },
76 {{MouseButton::LEFT, {KeyModifier::CONTROL}}, TrackBallType::PAN },
77 {{MouseButton::LEFT, {KeyModifier::ALT}}, TrackBallType::ZMOVE},
78 {{MouseButton::LEFT, {KeyModifier::SHIFT}}, TrackBallType::SCALE},
79 {{MouseButton::MIDDLE, {KeyModifier::NO_MODIFIER}},
80 TrackBallType::PAN },
81 {{MouseButton::MIDDLE, {KeyModifier::CONTROL}},
82 TrackBallType::ROLL },
83 {{MouseButton::LEFT, {KeyModifier::SHIFT, KeyModifier::CONTROL}},
84 TrackBallType::DIR_LIGHT_ARC },
85 // clang-format on
86 };
87
88 using Axis = unsigned char;
89 std::map<std::pair<KeyModifiers, Axis>, MotionType> mScrollAtomicMap = {
90 {{{KeyModifier::NO_MODIFIER}, 1}, TrackBallType::SCALE},
91 {{{KeyModifier::CONTROL}, 1}, TrackBallType::ROLL },
92 {{{KeyModifier::SHIFT}, 1}, TrackBallType::FOV },
93#ifdef __APPLE__
94 {{{KeyModifier::SHIFT}, 0}, TrackBallType::FOV },
95#endif
96 };
97
98 std::map<
99 std::pair<Key::Enum, KeyModifiers>,
100 std::function<void(TrackBallType& t)>>
101 mKeyAtomicMap = {
102 {{Key::R, {KeyModifier::NO_MODIFIER}},
103 [&](TrackBallType& t) {
104 t.reset(
105 mDefaultTrackBallCenter, 1.5 / mDefaultTrackBallRadius);
106 }},
107 {{Key::R, {KeyModifier::CONTROL, KeyModifier::SHIFT}},
108 [&](TrackBallType& t) {
109 t.resetDirectionalLight();
110 }},
111
112 // rotate
113 {{Key::NP_2, {KeyModifier::NO_MODIFIER}},
114 [](TrackBallType& t) {
115 rotate(t, UNIT_X, DISCRETE_ROTATION_STEP);
116 }},
117 {{Key::NP_4, {KeyModifier::NO_MODIFIER}},
118 [](TrackBallType& t) {
119 rotate(t, UNIT_Y, -DISCRETE_ROTATION_STEP);
120 }},
121 {{Key::NP_6, {KeyModifier::NO_MODIFIER}},
122 [](TrackBallType& t) {
123 rotate(t, UNIT_Y, DISCRETE_ROTATION_STEP);
124 }},
125 {{Key::NP_8, {KeyModifier::NO_MODIFIER}},
126 [](TrackBallType& t) {
127 rotate(t, UNIT_X, -DISCRETE_ROTATION_STEP);
128 }},
129
130 // translate
131 {{Key::UP, {KeyModifier::NO_MODIFIER}},
132 [](TrackBallType& t) {
133 translate(t, UNIT_Y * DISCRETE_TRANSLATION_STEP);
134 }},
135 {{Key::DOWN, {KeyModifier::NO_MODIFIER}},
136 [](TrackBallType& t) {
137 translate(t, -UNIT_Y * DISCRETE_TRANSLATION_STEP);
138 }},
139 {{Key::LEFT, {KeyModifier::NO_MODIFIER}},
140 [](TrackBallType& t) {
141 translate(t, -UNIT_X * DISCRETE_TRANSLATION_STEP);
142 }},
143 {{Key::RIGHT, {KeyModifier::NO_MODIFIER}},
144 [](TrackBallType& t) {
145 translate(t, UNIT_X * DISCRETE_TRANSLATION_STEP);
146 }},
147
148 // set view
149 {{Key::NP_1, {KeyModifier::NO_MODIFIER}},
150 [](TrackBallType& t) { // front
151 t.reset();
152 }},
153 {{Key::NP_7, {KeyModifier::NO_MODIFIER}},
154 [](TrackBallType& t) { // top
155 t.reset();
156 rotate(t, UNIT_X, M_PI_2);
157 }},
158 {{Key::NP_3, {KeyModifier::NO_MODIFIER}},
159 [](TrackBallType& t) { // right
160 t.reset();
161 rotate(t, UNIT_Y, -M_PI_2);
162 }},
163 {{Key::NP_1, {KeyModifier::CONTROL}},
164 [](TrackBallType& t) { // back
165 t.reset();
166 rotate(t, UNIT_Y, M_PI);
167 }},
168 {{Key::NP_7, {KeyModifier::CONTROL}},
169 [](TrackBallType& t) { // bottom
170 t.reset();
171 rotate(t, UNIT_X, -M_PI_2);
172 }},
173 {{Key::NP_3, {KeyModifier::CONTROL}},
174 [](TrackBallType& t) { // left
175 t.reset();
176 rotate(t, UNIT_Y, M_PI_2);
177 }},
178 // projection mode
179 {{Key::NP_5, {KeyModifier::NO_MODIFIER}},
180 [](TrackBallType& t) { // reset
181 const auto v =
182 t.projectionMode() ==
186 t.setProjectionMode(v);
187 }},
188
189 // rotate light
190 {{Key::NP_2, {KeyModifier::CONTROL, KeyModifier::SHIFT}},
191 [](TrackBallType& t) {
192 rotateLight(t, UNIT_X, DISCRETE_ROTATION_STEP);
193 }},
194 {{Key::NP_4, {KeyModifier::CONTROL, KeyModifier::SHIFT}},
195 [](TrackBallType& t) {
196 rotateLight(t, UNIT_Y, -DISCRETE_ROTATION_STEP);
197 }},
198 {{Key::NP_6, {KeyModifier::CONTROL, KeyModifier::SHIFT}},
199 [](TrackBallType& t) {
200 rotateLight(t, UNIT_Y, DISCRETE_ROTATION_STEP);
201 }},
202 {{Key::NP_8, {KeyModifier::CONTROL, KeyModifier::SHIFT}},
203 [](TrackBallType& t) {
204 rotateLight(t, UNIT_X, -DISCRETE_ROTATION_STEP);
205 }},
206 };
207
208public:
209 TrackBallEventDrawerT(uint width = 1024, uint height = 768) :
210 Base(width, height)
211 {
212 resizeViewer(width, height);
213 }
214
215 const Camera<Scalar>& camera() const { return mTrackball.camera(); }
216
217 Matrix44<Scalar> viewMatrix() const { return mTrackball.viewMatrix(); }
218
219 Matrix44<Scalar> projectionMatrix() const
220 {
221 return mTrackball.projectionMatrix();
222 }
223
224 void reset()
225 {
226 mTrackball.reset(
227 mDefaultTrackBallCenter, 1.5 / mDefaultTrackBallRadius);
228 }
229
230 void focus(const Point3<Scalar>& center)
231 {
232 mTrackball.applyAtomicMotion(TrackBallType::FOCUS, center);
233 }
234
235 void fitScene(const Point3<Scalar>& center, Scalar radius)
236 {
237 mDefaultTrackBallCenter = center;
238 mDefaultTrackBallRadius = radius;
239
240 reset();
241 }
242
243 DirectionalLight<Scalar> light() const { return mTrackball.light(); }
244
245 Matrix44<Scalar> lightGizmoMatrix() const
246 {
247 return mTrackball.lightGizmoMatrix();
248 }
249
250 Matrix44<Scalar> gizmoMatrix() const { return mTrackball.gizmoMatrix(); }
251
252 // events
253
254 void onResize(unsigned int width, unsigned int height) override
255 {
256 resizeViewer(width, height);
257 }
258
259 void onKeyPress(Key::Enum key, const KeyModifiers& modifiers) override
260 {
261 setKeyModifiers(modifiers);
262 keyPress(key);
263 }
264
265 void onKeyRelease(Key::Enum key, const KeyModifiers& modifiers) override
266 {
267 setKeyModifiers(modifiers);
268 keyRelease(key);
269 }
270
271 void onMouseMove(double x, double y, const KeyModifiers& modifiers) override
272 {
273 setKeyModifiers(modifiers);
274 moveMouse(x, y);
275 }
276
277 void onMousePress(
278 MouseButton::Enum button,
279 double x,
280 double y,
281 const KeyModifiers& modifiers) override
282 {
283 setKeyModifiers(modifiers);
284 moveMouse(x, y);
285 pressMouse(button);
286 }
287
288 void onMouseRelease(
289 MouseButton::Enum button,
290 double x,
291 double y,
292 const KeyModifiers& modifiers) override
293 {
294 setKeyModifiers(modifiers);
295 moveMouse(x, y);
296 releaseMouse(button);
297 }
298
299 void onMouseScroll(double dx, double dy, const KeyModifiers& modifiers)
300 override
301 {
302 setKeyModifiers(modifiers);
303 scroll(dx, dy);
304 }
305
306protected:
307 bool isDragging() const { return mTrackball.isDragging(); }
308
309 MotionType currentMotion() const { return mTrackball.currentMotion(); }
310
311private:
312 void resizeViewer(uint w, uint h)
313 {
314 mWidth = w;
315 mHeight = h;
316 mTrackball.setScreenSize(w, h);
317 }
318
319 void setKeyModifiers(KeyModifiers keys) { mCurrentKeyModifiers = keys; }
320
321 void moveMouse(int x, int y)
322 {
323 // ugly AF
324 auto it = mDragMotionMap.find(
325 std::make_pair(mCurrentMouseButton, mCurrentKeyModifiers));
326 if (it != mDragMotionMap.end()) {
327 mTrackball.beginDragMotion(it->second);
328 }
329 mTrackball.setMousePosition(x, y);
330 mTrackball.update();
331 }
332
333 void pressMouse(MouseButton::Enum button)
334 {
335 // if dragging, do not update the current mouse button
336 if (mTrackball.isDragging()) {
337 return;
338 }
339
340 mCurrentMouseButton = button;
341
342 auto it =
343 mDragMotionMap.find(std::make_pair(button, mCurrentKeyModifiers));
344 if (it != mDragMotionMap.end()) {
345 mTrackball.beginDragMotion(it->second);
346 // no need to update here, it will be updated in moveMouse
347 // for event driven rendering (e.g., Qt) this can trigger
348 // an unwanted drag motion using the previous mouse position
349 }
350 }
351
352 void releaseMouse(MouseButton::Enum button)
353 {
354 // if dragging, update the current mouse button only if it matches
355 if (mTrackball.isDragging() && mCurrentMouseButton == button) {
356 mCurrentMouseButton = MouseButton::NO_BUTTON;
357 }
358
359 auto it =
360 mDragMotionMap.find(std::make_pair(button, mCurrentKeyModifiers));
361 if (it != mDragMotionMap.end()) {
362 mTrackball.endDragMotion(it->second);
363 mTrackball.update();
364 }
365 }
366
367 void scroll(Scalar pixelDeltaX, Scalar pixelDeltaY)
368 {
369 if (pixelDeltaX == 0 && pixelDeltaY == 0) {
370 return;
371 }
372
373 if (pixelDeltaX != 0) {
374 auto it = mScrollAtomicMap.find({mCurrentKeyModifiers, Axis(0)});
375 if (it != mScrollAtomicMap.end()) {
376 mTrackball.applyAtomicMotion(it->second, pixelDeltaX);
377 }
378 }
379
380 if (pixelDeltaY != 0) {
381 auto it = mScrollAtomicMap.find({mCurrentKeyModifiers, Axis(1)});
382 if (it != mScrollAtomicMap.end()) {
383 mTrackball.applyAtomicMotion(it->second, pixelDeltaY);
384 }
385 }
386 }
387
388 void keyPress(Key::Enum key)
389 {
390 // atomic motions are enabled while dragging
391 auto atomicOp = mKeyAtomicMap.find({key, mCurrentKeyModifiers});
392 if (atomicOp != mKeyAtomicMap.end()) {
393 atomicOp->second(mTrackball);
394 }
395
396 // dragging
397 auto it = mDragMotionMap.find(
398 std::make_pair(mCurrentMouseButton, mCurrentKeyModifiers));
399 if (it != mDragMotionMap.end()) {
400 mTrackball.beginDragMotion(it->second);
401 }
402 else {
403 mTrackball.endDragMotion(currentMotion());
404 mTrackball.update();
405 }
406 }
407
408 void keyRelease(Key::Enum key)
409 {
410 // ugly solution to end drag motion when a key is released
411 if (!mTrackball.isDragging())
412 return;
413
414 // dragging
415 auto it = mDragMotionMap.find(
416 std::make_pair(mCurrentMouseButton, mCurrentKeyModifiers));
417 if (it != mDragMotionMap.end()) {
418 mTrackball.beginDragMotion(it->second);
419 }
420 else {
421 mTrackball.endDragMotion(currentMotion());
422 mTrackball.update();
423 }
424 }
425
426 static void rotate(
427 TrackBallType& t,
428 const Point3<Scalar>& axis,
429 Scalar angle = M_PI / 6)
430 {
431 using Args = typename TrackBallType::TransformArgs;
432 t.applyAtomicMotion(TrackBallType::ARC, Args(axis, angle));
433 }
434
435 static void rotateLight(
436 TrackBallType& t,
437 const Point3<Scalar>& axis,
438 Scalar angle = M_PI / 6)
439 {
440 using Args = typename TrackBallType::TransformArgs;
441 t.applyAtomicMotion(TrackBallType::DIR_LIGHT_ARC, Args(axis, angle));
442 }
443
444 static void translate(TrackBallType& t, const Point3<Scalar>& translation)
445 {
446 t.applyAtomicMotion(TrackBallType::PAN, translation);
447 }
448};
449
450template<typename DerivedRenderApp>
452
453} // namespace vcl
454
455#endif // VCL_RENDER_DRAWERS_TRACKBALL_EVENT_DRAWER_H
A class representing a box in N-dimensional space.
Definition box.h:46
Definition camera.h:32
The EventDrawer class is a base class for drawers that can handle events.
Definition event_drawer.h:44
Definition trackball_event_drawer.h:38