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 Matrix44<Scalar> viewMatrix() const { return mTrackball.viewMatrix(); }
216
217 Matrix44<Scalar> projectionMatrix() const
218 {
219 return mTrackball.projectionMatrix();
220 }
221
222 void reset()
223 {
224 mTrackball.reset(
225 mDefaultTrackBallCenter, 1.5 / mDefaultTrackBallRadius);
226 }
227
228 void focus(const Point3<Scalar>& center)
229 {
230 mTrackball.applyAtomicMotion(TrackBallType::FOCUS, center);
231 }
232
233 void fitScene(const Point3<Scalar>& center, Scalar radius)
234 {
235 mDefaultTrackBallCenter = center;
236 mDefaultTrackBallRadius = radius;
237
238 reset();
239 }
240
241 void fitView(const Point3<Scalar>& center)
242 {
243 mTrackball.adaptCurrentViewToCenter(center);
244 }
245
246 Camera<Scalar> camera() const { return mTrackball.camera(); }
247
248 void setCamera(const Camera<Scalar>& cam) { mTrackball.setCamera(cam); }
249
250 DirectionalLight<Scalar> light() const { return mTrackball.light(); }
251
252 Matrix44<Scalar> lightGizmoMatrix() const
253 {
254 return mTrackball.lightGizmoMatrix();
255 }
256
257 Matrix44<Scalar> gizmoMatrix() const { return mTrackball.gizmoMatrix(); }
258
259 // events
260
261 void onResize(unsigned int width, unsigned int height) override
262 {
263 resizeViewer(width, height);
264 }
265
266 void onKeyPress(Key::Enum key, const KeyModifiers& modifiers) override
267 {
268 setKeyModifiers(modifiers);
269 keyPress(key);
270 }
271
272 void onKeyRelease(Key::Enum key, const KeyModifiers& modifiers) override
273 {
274 setKeyModifiers(modifiers);
275 keyRelease(key);
276 }
277
278 void onMouseMove(double x, double y, const KeyModifiers& modifiers) override
279 {
280 setKeyModifiers(modifiers);
281 moveMouse(x, y);
282 }
283
284 void onMousePress(
285 MouseButton::Enum button,
286 double x,
287 double y,
288 const KeyModifiers& modifiers) override
289 {
290 setKeyModifiers(modifiers);
291 moveMouse(x, y);
292 pressMouse(button);
293 }
294
295 void onMouseRelease(
296 MouseButton::Enum button,
297 double x,
298 double y,
299 const KeyModifiers& modifiers) override
300 {
301 setKeyModifiers(modifiers);
302 moveMouse(x, y);
303 releaseMouse(button);
304 }
305
306 void onMouseScroll(double dx, double dy, const KeyModifiers& modifiers)
307 override
308 {
309 setKeyModifiers(modifiers);
310 scroll(dx, dy);
311 }
312
313protected:
314 bool isDragging() const { return mTrackball.isDragging(); }
315
316 MotionType currentMotion() const { return mTrackball.currentMotion(); }
317
318private:
319 void resizeViewer(uint w, uint h)
320 {
321 mWidth = w;
322 mHeight = h;
323 mTrackball.setScreenSize(w, h);
324 }
325
326 void setKeyModifiers(KeyModifiers keys) { mCurrentKeyModifiers = keys; }
327
328 void moveMouse(int x, int y)
329 {
330 // ugly AF
331 auto it = mDragMotionMap.find(
332 std::make_pair(mCurrentMouseButton, mCurrentKeyModifiers));
333 if (it != mDragMotionMap.end()) {
334 mTrackball.beginDragMotion(it->second);
335 }
336 mTrackball.setMousePosition(x, y);
337 mTrackball.update();
338 }
339
340 void pressMouse(MouseButton::Enum button)
341 {
342 // if dragging, do not update the current mouse button
343 if (mTrackball.isDragging()) {
344 return;
345 }
346
347 mCurrentMouseButton = button;
348
349 auto it =
350 mDragMotionMap.find(std::make_pair(button, mCurrentKeyModifiers));
351 if (it != mDragMotionMap.end()) {
352 mTrackball.beginDragMotion(it->second);
353 // no need to update here, it will be updated in moveMouse
354 // for event driven rendering (e.g., Qt) this can trigger
355 // an unwanted drag motion using the previous mouse position
356 }
357 }
358
359 void releaseMouse(MouseButton::Enum button)
360 {
361 // if dragging, update the current mouse button only if it matches
362 if (mTrackball.isDragging() && mCurrentMouseButton == button) {
363 mCurrentMouseButton = MouseButton::NO_BUTTON;
364 }
365
366 auto it =
367 mDragMotionMap.find(std::make_pair(button, mCurrentKeyModifiers));
368 if (it != mDragMotionMap.end()) {
369 mTrackball.endDragMotion(it->second);
370 mTrackball.update();
371 }
372 }
373
374 void scroll(Scalar pixelDeltaX, Scalar pixelDeltaY)
375 {
376 if (pixelDeltaX == 0 && pixelDeltaY == 0) {
377 return;
378 }
379
380 if (pixelDeltaX != 0) {
381 auto it = mScrollAtomicMap.find({mCurrentKeyModifiers, Axis(0)});
382 if (it != mScrollAtomicMap.end()) {
383 mTrackball.applyAtomicMotion(it->second, pixelDeltaX);
384 }
385 }
386
387 if (pixelDeltaY != 0) {
388 auto it = mScrollAtomicMap.find({mCurrentKeyModifiers, Axis(1)});
389 if (it != mScrollAtomicMap.end()) {
390 mTrackball.applyAtomicMotion(it->second, pixelDeltaY);
391 }
392 }
393 }
394
395 void keyPress(Key::Enum key)
396 {
397 // atomic motions are enabled while dragging
398 auto atomicOp = mKeyAtomicMap.find({key, mCurrentKeyModifiers});
399 if (atomicOp != mKeyAtomicMap.end()) {
400 atomicOp->second(mTrackball);
401 }
402
403 // dragging
404 auto it = mDragMotionMap.find(
405 std::make_pair(mCurrentMouseButton, mCurrentKeyModifiers));
406 if (it != mDragMotionMap.end()) {
407 mTrackball.beginDragMotion(it->second);
408 }
409 else {
410 mTrackball.endDragMotion(currentMotion());
411 mTrackball.update();
412 }
413 }
414
415 void keyRelease(Key::Enum key)
416 {
417 // ugly solution to end drag motion when a key is released
418 if (!mTrackball.isDragging())
419 return;
420
421 // dragging
422 auto it = mDragMotionMap.find(
423 std::make_pair(mCurrentMouseButton, mCurrentKeyModifiers));
424 if (it != mDragMotionMap.end()) {
425 mTrackball.beginDragMotion(it->second);
426 }
427 else {
428 mTrackball.endDragMotion(currentMotion());
429 mTrackball.update();
430 }
431 }
432
433 static void rotate(
434 TrackBallType& t,
435 const Point3<Scalar>& axis,
436 Scalar angle = M_PI / 6)
437 {
438 using Args = typename TrackBallType::TransformArgs;
439 t.applyAtomicMotion(TrackBallType::ARC, Args(axis, angle));
440 }
441
442 static void rotateLight(
443 TrackBallType& t,
444 const Point3<Scalar>& axis,
445 Scalar angle = M_PI / 6)
446 {
447 using Args = typename TrackBallType::TransformArgs;
448 t.applyAtomicMotion(TrackBallType::DIR_LIGHT_ARC, Args(axis, angle));
449 }
450
451 static void translate(TrackBallType& t, const Point3<Scalar>& translation)
452 {
453 t.applyAtomicMotion(TrackBallType::PAN, translation);
454 }
455};
456
457template<typename DerivedRenderApp>
459
460} // namespace vcl
461
462#endif // VCL_RENDER_DRAWERS_TRACKBALL_EVENT_DRAWER_H
A class representing a box in N-dimensional space.
Definition box.h:46
A Pinhole camera model.
Definition camera.h:35
The EventDrawer class is a base class for drawers that can handle events.
Definition event_drawer.h:44
Definition trackball_event_drawer.h:38