cgv
Loading...
Searching...
No Matches
transformation_gizmo.cxx
1#include "transformation_gizmo.h"
2
3#include <cgv/math/intersection.h>
4#include <cgv/math/constants.h>
5
6using namespace cgv::render;
7
8namespace cgv {
9namespace app {
10
11transformation_gizmo::transformation_gizmo() {
12 _boxes.style.illumination_mode = IM_OFF;
13 _boxes.style.map_color_to_material = CM_COLOR_AND_OPACITY;
14 _boxes.style.default_extent = _handle_size;
15
16 _cones.style.illumination_mode = IM_OFF;
17 _cones.style.map_color_to_material = CM_COLOR_AND_OPACITY;
18 _cones.style.radius = 0.02f;
19
20 _sphere.style.illumination_mode = IM_OFF;
21 _sphere.style.map_color_to_material = CM_COLOR_AND_OPACITY;
22 _sphere.style.halo_color = { 1.0f };
23
24 _rectangles.style.illumination_mode = IM_OFF;
25 _rectangles.style.map_color_to_material = CM_COLOR_AND_OPACITY;
26
27 // calculate ring points for rotation handles
28 for(size_t i = 0; i < _ring_segment_count; ++i) {
29 float t = static_cast<float>(i) / static_cast<float>(_ring_segment_count - 1);
30 t *= static_cast<float>(2.0 * cgv::math::constants::pi);
31 _ring_points.push_back({ std::cos(t), std::sin(t) });
32 }
33 _ring_points.back() = _ring_points.front();
34}
35
37 bool success = true;
38
39 success &= _box_renderer.init(ctx);
40 success &= _cone_renderer.init(ctx);
41 success &= _rectangle_renderer.init(ctx);
42 success &= _sphere_renderer.init(ctx);
43
44 success &= _boxes.init(ctx);
45 success &= _cones.init(ctx);
46 success &= _rectangles.init(ctx);
47 success &= _sphere.init(ctx);
48
49 return success;
50}
51
53 _box_renderer.clear(ctx);
54 _cone_renderer.clear(ctx);
55 _rectangle_renderer.clear(ctx);
56 _sphere_renderer.clear(ctx);
57
58 _boxes.destruct(ctx);
59 _cones.destruct(ctx);
60 _rectangles.destruct(ctx);
61 _sphere.destruct(ctx);
62}
63
64transformation_gizmo::Mode transformation_gizmo::get_mode() const {
65 return _mode;
66}
67
68void transformation_gizmo::set_mode(Mode mode) {
69 _mode = mode;
70 _interaction_feature = InteractionFeature::kNone;
71 set_geometry_out_of_date();
73}
74
75vec3 transformation_gizmo::get_scale() const {
76 return _scale;
77}
78
79void transformation_gizmo::set_scale(const vec3& scale) {
80 _scale = scale;
82}
83
84void transformation_gizmo::create_geometry() {
86
87 const vec3 v0(0.0f);
88 const vec3 vx(1.0f, 0.0f, 0.0f);
89 const vec3 vy(0.0f, 1.0f, 0.0f);
90 const vec3 vz(0.0f, 0.0f, 1.0f);
91
92 hls red = rgb(1.0f, 0.0f, 0.0f);
93 hls green = rgb(0.0f, 1.0f, 0.0f);
94 hls blue = rgb(0.0f, 0.0f, 1.0f);
95
96 const float saturation = 0.9f;
97 const float lightness = 0.3f;
98
99 red.S() = saturation;
100 green.S() = saturation;
101 blue.S() = saturation;
102 red.L() = lightness;
103 green.L() = lightness;
104 blue.L() = lightness;
105
106 const rgb x_color = red;
107 const rgb y_color = green;
108 const rgb z_color = blue;
109
110 _boxes.clear();
111 _cones.clear();
112 _rectangles.clear();
113 _sphere.clear();
114
115 bool use_axes =
116 _mode == Mode::kTranslation ||
117 _mode == Mode::kScale ||
118 _mode == Mode::kModel;
119
120 bool has_translation =
121 _mode == Mode::kTranslation ||
122 _mode == Mode::kModel;
123
124 bool has_rotation =
125 _mode == Mode::kRotation ||
126 _mode == Mode::kModel;
127
128 bool has_scale =
129 _mode == Mode::kScale ||
130 _mode == Mode::kModel;
131
132 if(use_axes) {
133 float axis_length = 1.0f;
134
135 if(has_translation && has_scale)
136 axis_length -= 2.0f * _handle_size;
137 else if(has_translation)
138 axis_length -= 2.0f * _handle_size;
139 else if(has_scale)
140 axis_length -= _handle_size;
141
142 vec3 hx = axis_length * vx;
143 vec3 hy = axis_length * vy;
144 vec3 hz = axis_length * vz;
145
146 _cones.add(v0 + _center_radius * vx, hx);
147 _cones.add(v0 + _center_radius * vy, hy);
148 _cones.add(v0 + _center_radius * vz, hz);
149 _cones.fill_radii(_axis_radius);
150 _cones.add_segment_color({ x_color, 1.0f });
151 _cones.add_segment_color({ y_color, 1.0f });
152 _cones.add_segment_color({ z_color, 1.0f });
153
154 float arrow_offset = has_scale ? 3.0f * _handle_size : 0.0f;
155
156 if(has_translation) {
157 // create axis handles as arrow tips
158 _cones.add(hx + arrow_offset * vx, (1.0f + arrow_offset) * vx);
159 _cones.add(hy + arrow_offset * vy, (1.0f + arrow_offset) * vy);
160 _cones.add(hz + arrow_offset * vz, (1.0f + arrow_offset) * vz);
161 _cones.add(0.5f * _handle_size, 0.0f);
162 _cones.add(0.5f * _handle_size, 0.0f);
163 _cones.add(0.5f * _handle_size, 0.0f);
164 _cones.add_segment_color({ x_color, 1.0f });
165 _cones.add_segment_color({ y_color, 1.0f });
166 _cones.add_segment_color({ z_color, 1.0f });
167 }
168
169 if(has_scale) {
170 // create axis handles as boxes
171 float box_offset = axis_length + 0.5f * _handle_size;
172
173 _boxes.add_position(v0 + box_offset * vx);
174 _boxes.add_position(v0 + box_offset * vy);
175 _boxes.add_position(v0 + box_offset * vz);
176 _boxes.add_color({ x_color, 1.0f });
177 _boxes.add_color({ y_color, 1.0f });
178 _boxes.add_color({ z_color, 1.0f });
179 }
180 }
181
182 if(has_rotation) {
183 // create rings
184 const auto add_ring = [this](auto transform, const rgba& color) {
185 for(size_t i = 0; i < _ring_points.size(); ++i)
186 _cones.add(transform(_ring_points[i]), transform(_ring_points[(i + 1) % _ring_points.size()]));
187 _cones.fill_colors(color);
188 };
189
190 add_ring([](vec2 p) { return vec3(0.0f, p.x(), p.y()); }, { x_color, 1.0f }); // yz plane
191 add_ring([](vec2 p) { return vec3(p.x(), 0.0f, p.y()); }, { y_color, 1.0f }); // xz plane
192 add_ring([](vec2 p) { return vec3(p.x(), p.y(), 0.0f); }, { z_color, 1.0f }); // xy plane
193 _cones.fill_radii(_axis_radius);
194 }
195
196 // create plane handles
197 if(_mode == Mode::kTranslation || _mode == Mode::kScale) {
198 _rectangles.add_position(v0 + 0.5f * (vy + vz));
199 _rectangles.add_position(v0 + 0.5f * (vx + vz));
200 _rectangles.add_position(v0 + 0.5f * (vx + vy));
201 _rectangles.fill_extents(vec2(_plane_size));
202
203 _rectangles.add_color({ x_color, 0.5f });
204 _rectangles.add_color({ y_color, 0.5f });
205 _rectangles.add_color({ z_color, 0.5f });
206
207 _rectangles.add_border_color({ x_color, 1.0f });
208 _rectangles.add_border_color({ y_color, 1.0f });
209 _rectangles.add_border_color({ z_color, 1.0f });
210
211 _rectangles.add_rotation(quat(vy, cgv::math::deg2rad(90.0f)));
212 _rectangles.add_rotation(quat(vx, cgv::math::deg2rad(-90.0f)));
213 _rectangles.add_rotation(quat());
214 }
215
216 // create center sphere
217 _sphere.add(v0, _axis_radius);
218 _sphere.colors.push_back({ 1.0f });
219
220 _sphere.add(v0, _center_radius);
221 _sphere.colors.push_back({ 0.7f, 0.7f, 0.7f, 0.0f });
222
223 if(!is_hovered())
224 return;
225
226 // set colors based on hover state
227 const auto saturate_color = [](rgba& color) {
229 hls.S() = 0.95f;
230 hls.L() = 0.52f;
231 color = { rgb(hls), color.alpha() };
232 };
233
234 int axis_idx = axis_id_to_index(_interaction_axis_id);
235 switch(_interaction_feature) {
236 case InteractionFeature::kAxis:
237 if(use_axes) {
238 size_t base_idx = 2 * static_cast<size_t>(axis_idx);
239 if(has_translation && has_scale && _interaction_mode == Mode::kScale || has_translation != has_scale) {
240 saturate_color(_cones.colors[base_idx]);
241 saturate_color(_cones.colors[base_idx + 1]);
242 }
243
244 if(_interaction_mode == Mode::kTranslation) {
245 saturate_color(_cones.colors[base_idx + 6]);
246 saturate_color(_cones.colors[base_idx + 7]);
247 }
248
249 if(_interaction_mode == Mode::kScale)
250 saturate_color(_boxes.colors[axis_idx]);
251 }
252 break;
253 case InteractionFeature::kPlane:
254 if(has_rotation) {
255 size_t base_idx = static_cast<size_t>(axis_idx) * 2 * _ring_segment_count;
256 if(_mode == Mode::kModel)
257 base_idx += 12;
258
259 for(size_t i = 0; i < 2 * _ring_segment_count; ++i)
260 saturate_color(_cones.colors[base_idx + i]);
261 } else {
262 saturate_color(_rectangles.colors[axis_idx]);
263 saturate_color(_rectangles.border_colors[axis_idx]);
264 }
265 break;
266 case InteractionFeature::kCenter:
267 _sphere.colors.back().alpha() = 0.2f;
268 break;
269 default:
270 break;
271 }
272}
273
274void transformation_gizmo::draw_geometry(context& ctx) {
275 // Pass the y view angle to the renderers so pixel measurements are computed correctly
276 _rectangle_renderer.set_y_view_angle(static_cast<float>(get_view()->get_y_view_angle()));
277 _sphere_renderer.set_y_view_angle(static_cast<float>(get_view()->get_y_view_angle()));
278
279 // Since we use scaling in the modelview matrix we also need to adjust the pixel measures
280 // in the render styles based on the scale of the gizmo.
281 const float size = get_size();
282
283 _sphere.style.halo_width_in_pixel = -3.0f / size;
284 _sphere.style.blend_width_in_pixel = 1.0f / size;
285
286 _rectangles.style.border_width_in_pixel = -3.0f / size;
287 _rectangles.style.pixel_blend = 2.0f / size;
288
289 _sphere.render(ctx, _sphere_renderer);
290 _rectangles.render(ctx, _rectangle_renderer);
291 _cones.render(ctx, _cone_renderer);
292 _boxes.render(ctx, _box_renderer);
293}
294
295bool transformation_gizmo::intersect_bounding_box(const cgv::math::ray3& ray) {
296 vec3 min = { 0.0f };
297 vec3 max = { 1.0f };
298
299 if(_mode == Mode::kRotation || _mode == Mode::kModel)
300 min = -1.0f;
301 else
302 min -= _center_radius;
303
304 if(_mode == Mode::kModel)
305 max += 3.0f * _handle_size;
306
307 min -= 0.1f;
308 max += 0.1f;
309
310 vec2 t = std::numeric_limits<float>::max();
311 return cgv::math::ray_box_intersection(ray, min, max, t) != 0;
312}
313
314bool transformation_gizmo::intersect(const cgv::math::ray3& ray) {
315 float min_t = std::numeric_limits<float>::max();
316
317 const auto update_t_if_closer = [this, &min_t](float t, Mode transformation, InteractionFeature feature, AxisId axis_id) {
318 if(t >= 0.0f && t < min_t) {
319 min_t = t;
320 _interaction_mode = transformation;
321 _interaction_feature = feature;
322 _interaction_axis_id = axis_id;
323 }
324 };
325
326 if(_mode == Mode::kRotation || _mode == Mode::kModel) {
327 // test rings for planes
328 size_t start_offset = _mode == Mode::kModel ? 12 : 0;
329 for(size_t i = start_offset; i < _cones.size(); i += 2) {
330 vec3 pa = _cones.positions[i];
331 vec3 pb = _cones.positions[i + 1];
332
333 float t = std::numeric_limits<float>::max();
334 if(cgv::math::ray_cylinder_intersection2(ray, pa, pb, 3.0f * _axis_radius, t)) {
335 int axis_idx = static_cast<int>((i - start_offset) / (2 * _ring_segment_count));
336 update_t_if_closer(t, Mode::kRotation, InteractionFeature::kPlane, index_to_axis_id(axis_idx));
337 }
338 }
339 }
340
341
342 std::array<std::pair<vec3, vec3>, 6> cylinders;
343 cylinders.fill({ 0.0f, 0.0f });
344 size_t cylinder_count = 0;
345 float axis_length = 1.0f;
346
347 if(_mode == Mode::kModel) {
348 axis_length -= _handle_size;
349 }
350
351 if(_mode == Mode::kTranslation || _mode == Mode::kScale || _mode == Mode::kModel) {
352 cylinder_count += 3;
353 for(unsigned i = 0; i < 3; ++i) {
354 cylinders[i].first[i] = _center_radius;
355 cylinders[i].second[i] = axis_length;
356 }
357 }
358
359 if(_mode == Mode::kModel) {
360 cylinder_count += 3;
361 for(int i = 0; i < 3; ++i) {
362 cylinders[i + 3].first[i] = axis_length + 2.0f * _handle_size;
363 cylinders[i + 3].second[i] = axis_length + 4.0f * _handle_size;
364 }
365 }
366
367 for(size_t i = 0; i < cylinder_count; ++i) {
368 float t = std::numeric_limits<float>::max();
369 if(cgv::math::ray_cylinder_intersection2(ray, cylinders[i].first, cylinders[i].second, _handle_size, t)) {
370 Mode transformation = _mode;
371 if(cylinder_count > 3)
372 transformation = i < 3 ? Mode::kScale : Mode::kTranslation;
373
374 update_t_if_closer(t, transformation, InteractionFeature::kAxis, index_to_axis_id(static_cast<int>(i % 3)));
375 }
376 }
377
378 // test rectangles for planes
379 if(_mode == Mode::kTranslation || _mode == Mode::kScale) {
380 float t = std::numeric_limits<float>::max();
381 for(int i = 0; i < 3; ++i) {
382 vec3 position = { 0.5f };
383 position[i] = 0.0f;
384 if(cgv::math::ray_axis_aligned_rectangle_intersection(ray, position, { _plane_size }, static_cast<int>(i), t))
385 update_t_if_closer(t, _mode, InteractionFeature::kPlane, index_to_axis_id(static_cast<int>(i)));
386 }
387 }
388
389 // test sphere for center
390 vec2 ts(std::numeric_limits<float>::max());
391 if(cgv::math::ray_sphere_intersection(ray, { 0.0f }, _center_radius, ts)) {
392 Mode transformation = _mode == Mode::kModel ? Mode::kTranslation : _mode;
393 update_t_if_closer(ts.x(), transformation, InteractionFeature::kCenter, AxisId::kX);
394 }
395
396 return min_t > 0.0f && min_t < std::numeric_limits<float>::max();
397}
398
399bool transformation_gizmo::start_drag(const cgv::math::ray3& ray) {
400 int axis_idx = axis_id_to_index(_interaction_axis_id);
401 vec3 axis = get_axis(axis_idx);
402
403 const vec3 view_dir = get_view()->get_view_dir();
404
405 _interaction_plane.origin = get_position();
406
407 const auto get_rotated_axis = [this](const vec3& axis) {
408 vec3 rotated = axis;
409 get_rotation().rotate(rotated);
410 return rotated;
411 };
412
413 if(_interaction_mode == Mode::kRotation) {
414 switch(_interaction_feature) {
415 case InteractionFeature::kPlane:
416 {
417 _interaction_plane.normal = get_rotated_axis(axis);
418 // if the ray direction is close to parallel to the plane, choose the screen aligned plane instead
419 float threshold_angle = std::cos(cgv::math::deg2rad(75.0f));
420
421 float incident_angle = dot(_interaction_plane.normal, ray.direction);
422 if(std::abs(incident_angle) < threshold_angle)
423 _interaction_plane.normal = incident_angle < 0.0f ? -view_dir : view_dir;
424 break;
425 }
426 case InteractionFeature::kCenter:
427 _interaction_plane.normal = view_dir;
428 break;
429 default:
430 return false;
431 }
432 } else {
433 switch(_interaction_feature) {
434 case InteractionFeature::kAxis:
435 {
436 // the plane is not actually needed for axis interaction, so we just chose one that will
437 // always produce an intersection with the mouse ray
438 _interaction_plane.normal = view_dir;
439 break;
440 }
441 case InteractionFeature::kPlane:
442 _interaction_plane.normal = get_rotated_axis(axis);
443 break;
444 case InteractionFeature::kCenter:
445 _interaction_plane.normal = view_dir;
446 break;
447 default:
448 return false;
449 }
450 }
451
452 float t = -1.0f;
453 if(!cgv::math::ray_plane_intersection(ray, _interaction_plane.origin, _interaction_plane.normal, t))
454 return false;
455
456 _drag_start_t = t;
457 _drag_start_position = get_position();
458 _drag_start_scale = _scale;
459 _drag_start_rotation = get_rotation();
460
461 if(on_change)
462 on_change(GizmoAction::kDragStart, _interaction_mode);
463
464 return true;
465}
466
467bool transformation_gizmo::drag(const cgv::math::ray3& ray) {
468 int axis_idx = axis_id_to_index(_interaction_axis_id);
469 vec3 axis = get_axis(axis_idx);
470
471 const vec3 position = get_position();
472
473 if(!_interaction_plane.valid())
474 return false;
475
476 float t = -1.0f;
477 if(!cgv::math::ray_plane_intersection(ray, _interaction_plane.origin, _interaction_plane.normal, t) || t < 0.0f) {
478 // restore drag start transforms if no valid intersection with the interaction plane is found
479 set_position(_drag_start_position);
480 set_scale(_drag_start_scale);
481 set_rotation(_drag_start_rotation);
482 } else {
483 vec3 start_intersection_position = _drag_start_ray.position(_drag_start_t);
484 vec3 intersection_position = ray.position(t);
485
486 if(get_orientation() == GizmoOrientation::kLocal)
487 _drag_start_rotation.rotate(axis);
488
489 switch(_interaction_mode) {
490 case Mode::kTranslation:
491 {
492 // TODO: FIXME: This is not optimal and only works well when the mouse position is pointing close to the manipulated axis.
493 cgv::math::ray3 axis_ray = { _drag_start_position, axis };
494
495 float start_offset = ray_ray_closest_approach(_drag_start_ray, axis_ray).second;
496 float offset = ray_ray_closest_approach(ray, axis_ray).second;
497
498 vec3 new_position = _drag_start_position;
499 if(_interaction_feature == InteractionFeature::kAxis)
500 new_position += (offset - start_offset) * axis;
501 else
502 new_position += intersection_position - start_intersection_position;// _local_offset;
503
504 set_position(new_position);
505 break;
506 }
507 case Mode::kScale:
508 {
509 // TODO: FIXME: Scaling does not work corectly for rotated coordinate frames (local gizmo orientation).
510 vec3 new_local_offset = start_intersection_position - _drag_start_position;
511 if(_interaction_feature == InteractionFeature::kAxis)
512 new_local_offset[axis_idx] = (intersection_position - position)[axis_idx];
513 else
514 new_local_offset = intersection_position - position;
515
516 vec3 scale_mult = new_local_offset / (start_intersection_position - _drag_start_position);
517
518 vec3 new_scale = _drag_start_scale;
519 switch(_interaction_feature) {
520 case InteractionFeature::kAxis:
521 new_scale[axis_idx] *= scale_mult[axis_idx];
522 break;
523 case InteractionFeature::kPlane:
524 for(int i = 0; i < 3; ++i) {
525 if(i != axis_idx)
526 new_scale[i] *= scale_mult[i];
527 }
528 break;
529 case InteractionFeature::kCenter:
530 new_scale *= length(scale_mult);
531 break;
532 default:
533 break;
534 }
535
536 set_scale(new_scale);
537 break;
538 }
539 case Mode::kRotation:
540 {
541 vec3 start_dir = start_intersection_position - _drag_start_position;
542 vec3 end_dir = intersection_position - position;
543
544 // check length and skip if too short
545 if(start_dir.normalize() < 0.01f)
546 return true;
547 if(end_dir.normalize() < 0.01f)
548 return true;
549
550 vec3 plane_tangent = normalize(cross(_interaction_plane.normal, start_dir));
551
552 float cos_theta = dot(start_dir, end_dir);
553 cos_theta = cgv::math::clamp(cos_theta, -1.0f, 1.0f);
554 float angle = std::acos(cos_theta);
555
556 // check for side and bring angle in range [0,2pi];
557 if(dot(plane_tangent, end_dir) < 0.0f)
558 angle = static_cast<float>(2.0 * cgv::math::constants::pi) - angle;
559
560 vec3 rotaton_axis = _interaction_feature == InteractionFeature::kCenter ? _interaction_plane.normal : axis;
561
562 quat new_rotation = quat(rotaton_axis, angle) * _drag_start_rotation;
563
564 set_rotation(new_rotation);
565 break;
566 }
567 default:
568 return false;
569 }
570 }
571
572 if(on_change)
573 on_change(GizmoAction::kDrag, _interaction_mode);
574
575 return true;
576}
577
578void transformation_gizmo::end_drag(const cgv::math::ray3& ray) {
579 if(on_change)
580 on_change(GizmoAction::kDragEnd, _interaction_mode);
581}
582
583std::pair<float, float> transformation_gizmo::ray_ray_closest_approach(const cgv::math::ray3& r0, const cgv::math::ray3& r1) const {
584 vec3 ba = r1.direction;
585 vec3 oa = r0.origin - r1.origin;
586
587 float a = dot(ba, ba);
588 float b = dot(r0.direction, ba);
589 float c = dot(oa, ba);
590 float e = dot(oa, r0.direction);
591
592 vec2 st = vec2(c - b * e, b * c - a * e) / (a - b * b);
593
594 return { st.y(), st.x() };
595}
596
597}
598}
void clear(cgv::render::context &) override
clear all objects living in the context like textures or display lists
bool init(cgv::render::context &) override
this method is called after creation or recreation of the context, return whether all necessary funct...
T & y()
return second component
Definition fvec.h:140
T normalize()
normalize the vector using the L2-Norm and return the length
Definition fvec.h:293
T & x()
return first component
Definition fvec.h:136
void rotate(vec_type &v) const
rotate vector according to quaternion
Definition quaternion.h:198
represent a color with components of given type and color and alpha model as specified.
Definition color.h:575
void clear(const context &ctx) override
the clear function destructs the shader program and resets the texture pointers
base class for all drawables, which is independent of the used rendering API.
Definition context.h:672
void post_redraw()
posts a redraw event to the current context if one is available
Definition drawable.cxx:43
bool init(context &ctx) override
call init() once before using renderer
virtual bool init(context &ctx)
call init() once before using renderer
Definition renderer.cxx:172
virtual void clear(const context &ctx)
the clear function destructs the shader program
Definition renderer.cxx:348
const dvec3 & get_view_dir() const
query current view direction
Definition view.cxx:58
namespace for api independent GPU programming
the cgv namespace
Definition print.h:11
cgv::math::quaternion< float > quat
declare type of quaternion
Definition quaternion.h:370
cgv::media::color< float, cgv::media::RGB > rgb
declare rgb color type with 32 bit components
Definition color.h:891
cgv::math::fvec< float, 2 > vec2
declare type of 2d single precision floating point vectors
Definition fvec.h:659
cgv::math::fvec< float, 3 > vec3
declare type of 3d single precision floating point vectors
Definition fvec.h:661
Struct template for fixed n-dimensional rays with arbitrary data type defined by origin and direction...
Definition fray.h:11
fvec< T, N > position(float t) const
Return the position of the ray at the given distance (ray parameter t) from its origin.
Definition fray.h:25
T S() const
convert color to HLS and return S component
Definition color.h:449
T L() const
convert color to HLS and return L component
Definition color.h:447