1#include "transformation_gizmo.h"
3#include <cgv/math/intersection.h>
10transformation_gizmo::transformation_gizmo() {
11 _boxes.style.illumination_mode = IM_OFF;
12 _boxes.style.map_color_to_material = CM_COLOR_AND_OPACITY;
13 _boxes.style.default_extent = _handle_size;
15 _cones.style.illumination_mode = IM_OFF;
16 _cones.style.map_color_to_material = CM_COLOR_AND_OPACITY;
17 _cones.style.radius = 0.02f;
19 _sphere.style.illumination_mode = IM_OFF;
20 _sphere.style.map_color_to_material = CM_COLOR_AND_OPACITY;
21 _sphere.style.halo_color = { 1.0f };
23 _rectangles.style.illumination_mode = IM_OFF;
24 _rectangles.style.map_color_to_material = CM_COLOR_AND_OPACITY;
27 for(
size_t i = 0; i < _ring_segment_count; ++i) {
28 float t =
static_cast<float>(i) /
static_cast<float>(_ring_segment_count - 1);
30 _ring_points.push_back({ std::cos(t), std::sin(t) });
32 _ring_points.back() = _ring_points.front();
38 success &= _box_renderer.
init(ctx);
39 success &= _cone_renderer.
init(ctx);
40 success &= _rectangle_renderer.
init(ctx);
41 success &= _sphere_renderer.
init(ctx);
43 success &= _boxes.init(ctx);
44 success &= _cones.init(ctx);
45 success &= _rectangles.init(ctx);
46 success &= _sphere.init(ctx);
52 _box_renderer.
clear(ctx);
53 _cone_renderer.
clear(ctx);
54 _rectangle_renderer.
clear(ctx);
55 _sphere_renderer.
clear(ctx);
59 _rectangles.destruct(ctx);
60 _sphere.destruct(ctx);
63transformation_gizmo::Mode transformation_gizmo::get_mode()
const {
67void transformation_gizmo::set_mode(Mode mode) {
69 _interaction_feature = InteractionFeature::kNone;
70 set_geometry_out_of_date();
74vec3 transformation_gizmo::get_scale()
const {
78void transformation_gizmo::set_scale(
const vec3& scale) {
83void transformation_gizmo::create_geometry() {
87 const vec3 vx(1.0f, 0.0f, 0.0f);
88 const vec3 vy(0.0f, 1.0f, 0.0f);
89 const vec3 vz(0.0f, 0.0f, 1.0f);
91 hls red =
rgb(1.0f, 0.0f, 0.0f);
92 hls green =
rgb(0.0f, 1.0f, 0.0f);
93 hls blue =
rgb(0.0f, 0.0f, 1.0f);
95 const float saturation = 0.9f;
96 const float lightness = 0.3f;
99 green.S() = saturation;
100 blue.S() = saturation;
102 green.L() = lightness;
103 blue.L() = lightness;
105 const rgb x_color = red;
106 const rgb y_color = green;
107 const rgb z_color = blue;
115 _mode == Mode::kTranslation ||
116 _mode == Mode::kScale ||
117 _mode == Mode::kModel;
119 bool has_translation =
120 _mode == Mode::kTranslation ||
121 _mode == Mode::kModel;
124 _mode == Mode::kRotation ||
125 _mode == Mode::kModel;
128 _mode == Mode::kScale ||
129 _mode == Mode::kModel;
132 float axis_length = 1.0f;
134 if(has_translation && has_scale)
135 axis_length -= 2.0f * _handle_size;
136 else if(has_translation)
137 axis_length -= 2.0f * _handle_size;
139 axis_length -= _handle_size;
141 vec3 hx = axis_length * vx;
142 vec3 hy = axis_length * vy;
143 vec3 hz = axis_length * vz;
145 _cones.add(v0 + _center_radius * vx, hx);
146 _cones.add(v0 + _center_radius * vy, hy);
147 _cones.add(v0 + _center_radius * vz, hz);
148 _cones.fill_radii(_axis_radius);
149 _cones.add_segment_color({ x_color, 1.0f });
150 _cones.add_segment_color({ y_color, 1.0f });
151 _cones.add_segment_color({ z_color, 1.0f });
153 float arrow_offset = has_scale ? 3.0f * _handle_size : 0.0f;
155 if(has_translation) {
157 _cones.add(hx + arrow_offset * vx, (1.0f + arrow_offset) * vx);
158 _cones.add(hy + arrow_offset * vy, (1.0f + arrow_offset) * vy);
159 _cones.add(hz + arrow_offset * vz, (1.0f + arrow_offset) * vz);
160 _cones.add(0.5f * _handle_size, 0.0f);
161 _cones.add(0.5f * _handle_size, 0.0f);
162 _cones.add(0.5f * _handle_size, 0.0f);
163 _cones.add_segment_color({ x_color, 1.0f });
164 _cones.add_segment_color({ y_color, 1.0f });
165 _cones.add_segment_color({ z_color, 1.0f });
170 float box_offset = axis_length + 0.5f * _handle_size;
172 _boxes.add_position(v0 + box_offset * vx);
173 _boxes.add_position(v0 + box_offset * vy);
174 _boxes.add_position(v0 + box_offset * vz);
175 _boxes.add_color({ x_color, 1.0f });
176 _boxes.add_color({ y_color, 1.0f });
177 _boxes.add_color({ z_color, 1.0f });
183 const auto add_ring = [
this](
auto transform,
const rgba& color) {
184 for(
size_t i = 0; i < _ring_points.size(); ++i)
185 _cones.add(transform(_ring_points[i]), transform(_ring_points[(i + 1) % _ring_points.size()]));
186 _cones.fill_colors(color);
189 add_ring([](
vec2 p) {
return vec3(0.0f, p.
x(), p.
y()); }, { x_color, 1.0f });
190 add_ring([](
vec2 p) {
return vec3(p.
x(), 0.0f, p.
y()); }, { y_color, 1.0f });
191 add_ring([](
vec2 p) {
return vec3(p.
x(), p.
y(), 0.0f); }, { z_color, 1.0f });
192 _cones.fill_radii(_axis_radius);
196 if(_mode == Mode::kTranslation || _mode == Mode::kScale) {
197 _rectangles.add_position(v0 + 0.5f * (vy + vz));
198 _rectangles.add_position(v0 + 0.5f * (vx + vz));
199 _rectangles.add_position(v0 + 0.5f * (vx + vy));
200 _rectangles.fill_extents(
vec2(_plane_size));
202 _rectangles.add_color({ x_color, 0.5f });
203 _rectangles.add_color({ y_color, 0.5f });
204 _rectangles.add_color({ z_color, 0.5f });
206 _rectangles.add_border_color({ x_color, 1.0f });
207 _rectangles.add_border_color({ y_color, 1.0f });
208 _rectangles.add_border_color({ z_color, 1.0f });
210 _rectangles.add_rotation(
quat(vy, cgv::math::deg2rad(90.0f)));
211 _rectangles.add_rotation(
quat(vx, cgv::math::deg2rad(-90.0f)));
212 _rectangles.add_rotation(
quat());
216 _sphere.add(v0, _axis_radius);
217 _sphere.colors.push_back({ 1.0f });
219 _sphere.add(v0, _center_radius);
220 _sphere.colors.push_back({ 0.7f, 0.7f, 0.7f, 0.0f });
226 const auto saturate_color = [](
rgba& color) {
230 color = {
rgb(hls), color.alpha() };
233 int axis_idx = axis_id_to_index(_interaction_axis_id);
234 switch(_interaction_feature) {
235 case InteractionFeature::kAxis:
237 size_t base_idx = 2.0f * axis_idx;
238 if(has_translation && has_scale && _interaction_mode == Mode::kScale || has_translation != has_scale) {
239 saturate_color(_cones.colors[base_idx]);
240 saturate_color(_cones.colors[base_idx + 1]);
243 if(_interaction_mode == Mode::kTranslation) {
244 saturate_color(_cones.colors[base_idx + 6]);
245 saturate_color(_cones.colors[base_idx + 7]);
248 if(_interaction_mode == Mode::kScale)
249 saturate_color(_boxes.colors[axis_idx]);
252 case InteractionFeature::kPlane:
254 size_t base_idx =
static_cast<size_t>(axis_idx) * 2 * _ring_segment_count;
255 if(_mode == Mode::kModel)
258 for(
size_t i = 0; i < 2 * _ring_segment_count; ++i)
259 saturate_color(_cones.colors[base_idx + i]);
261 saturate_color(_rectangles.colors[axis_idx]);
262 saturate_color(_rectangles.border_colors[axis_idx]);
265 case InteractionFeature::kCenter:
266 _sphere.colors.back().alpha() = 0.2f;
273void transformation_gizmo::draw_geometry(
context& ctx) {
275 _rectangle_renderer.set_y_view_angle(get_view()->get_y_view_angle());
276 _sphere_renderer.set_y_view_angle(get_view()->get_y_view_angle());
280 const float size = get_size();
282 _sphere.style.halo_width_in_pixel = -3.0f / size;
283 _sphere.style.blend_width_in_pixel = 1.0f / size;
285 _rectangles.style.border_width_in_pixel = -3.0f / size;
286 _rectangles.style.pixel_blend = 2.0f / size;
288 _sphere.render(ctx, _sphere_renderer);
289 _rectangles.render(ctx, _rectangle_renderer);
290 _cones.render(ctx, _cone_renderer);
291 _boxes.render(ctx, _box_renderer);
294bool transformation_gizmo::intersect_bounding_box(
const cgv::math::ray3& ray) {
298 if(_mode == Mode::kRotation || _mode == Mode::kModel)
301 min -= _center_radius;
303 if(_mode == Mode::kModel)
304 max += 3.0f * _handle_size;
309 vec2 t = std::numeric_limits<float>::max();
310 return cgv::math::ray_box_intersection(ray, min, max, t) != 0;
314 float min_t = std::numeric_limits<float>::max();
316 const auto update_t_if_closer = [
this, &min_t](
float t, Mode transformation, InteractionFeature feature, AxisId axis_id) {
317 if(t >= 0.0f && t < min_t) {
319 _interaction_mode = transformation;
320 _interaction_feature = feature;
321 _interaction_axis_id = axis_id;
325 if(_mode == Mode::kRotation || _mode == Mode::kModel) {
327 size_t start_offset = _mode == Mode::kModel ? 12 : 0;
328 for(
size_t i = start_offset; i < _cones.size(); i += 2) {
329 vec3 pa = _cones.positions[i];
330 vec3 pb = _cones.positions[i + 1];
332 float t = std::numeric_limits<float>::max();
333 if(cgv::math::ray_cylinder_intersection2(ray, pa, pb, 3.0f * _axis_radius, t)) {
334 int axis_idx =
static_cast<int>((i - start_offset) / (2 * _ring_segment_count));
335 update_t_if_closer(t, Mode::kRotation, InteractionFeature::kPlane, index_to_axis_id(axis_idx));
341 std::array<std::pair<vec3, vec3>, 6> cylinders;
342 cylinders.fill({ 0.0f, 0.0f });
343 size_t cylinder_count = 0;
344 float axis_length = 1.0f;
346 if(_mode == Mode::kModel) {
347 axis_length -= _handle_size;
350 if(_mode == Mode::kTranslation || _mode == Mode::kScale || _mode == Mode::kModel) {
352 for(
unsigned i = 0; i < 3; ++i) {
353 cylinders[i].first[i] = _center_radius;
354 cylinders[i].second[i] = axis_length;
358 if(_mode == Mode::kModel) {
360 for(
size_t i = 0; i < 3; ++i) {
361 cylinders[i + 3].first[i] = axis_length + 2.0f * _handle_size;
362 cylinders[i + 3].second[i] = axis_length + 4.0f * _handle_size;
366 for(
size_t i = 0; i < cylinder_count; ++i) {
367 float t = std::numeric_limits<float>::max();
368 if(cgv::math::ray_cylinder_intersection2(ray, cylinders[i].first, cylinders[i].second, _handle_size, t)) {
369 Mode transformation = _mode;
370 if(cylinder_count > 3)
371 transformation = i < 3 ? Mode::kScale : Mode::kTranslation;
373 update_t_if_closer(t, transformation, InteractionFeature::kAxis, index_to_axis_id(
static_cast<int>(i % 3)));
378 if(_mode == Mode::kTranslation || _mode == Mode::kScale) {
379 float t = std::numeric_limits<float>::max();
380 for(
size_t i = 0; i < 3; ++i) {
381 vec3 position = { 0.5f };
383 if(cgv::math::ray_axis_aligned_rectangle_intersection(ray, position, { _plane_size },
static_cast<int>(i), t))
384 update_t_if_closer(t, _mode, InteractionFeature::kPlane, index_to_axis_id(
static_cast<int>(i)));
389 vec2 ts(std::numeric_limits<float>::max());
390 if(cgv::math::ray_sphere_intersection(ray, { 0.0f }, _center_radius, ts)) {
391 Mode transformation = _mode == Mode::kModel ? Mode::kTranslation : _mode;
392 update_t_if_closer(ts.x(), transformation, InteractionFeature::kCenter, AxisId::kX);
395 return min_t > 0.0f && min_t < std::numeric_limits<float>::max();
399 int axis_idx = axis_id_to_index(_interaction_axis_id);
400 vec3 axis = get_axis(axis_idx);
404 _interaction_plane.origin = get_position();
406 const auto get_rotated_axis = [
this](
const vec3& axis) {
408 get_rotation().
rotate(rotated);
412 if(_interaction_mode == Mode::kRotation) {
413 switch(_interaction_feature) {
414 case InteractionFeature::kPlane:
416 _interaction_plane.normal = get_rotated_axis(axis);
418 float threshold_angle = std::cos(cgv::math::deg2rad(75.0f));
420 float incident_angle = dot(_interaction_plane.normal, ray.direction);
421 if(std::abs(incident_angle) < threshold_angle)
422 _interaction_plane.normal = incident_angle < 0.0f ? -view_dir : view_dir;
425 case InteractionFeature::kCenter:
426 _interaction_plane.normal = view_dir;
432 switch(_interaction_feature) {
433 case InteractionFeature::kAxis:
437 _interaction_plane.normal = view_dir;
440 case InteractionFeature::kPlane:
441 _interaction_plane.normal = get_rotated_axis(axis);
443 case InteractionFeature::kCenter:
444 _interaction_plane.normal = view_dir;
452 if(!cgv::math::ray_plane_intersection(ray, _interaction_plane.origin, _interaction_plane.normal, t))
456 _drag_start_position = get_position();
457 _drag_start_scale = _scale;
458 _drag_start_rotation = get_rotation();
461 on_change(GizmoAction::kDragStart, _interaction_mode);
467 int axis_idx = axis_id_to_index(_interaction_axis_id);
468 vec3 axis = get_axis(axis_idx);
470 const vec3 position = get_position();
472 if(!_interaction_plane.valid())
476 if(!cgv::math::ray_plane_intersection(ray, _interaction_plane.origin, _interaction_plane.normal, t) || t < 0.0f) {
478 set_position(_drag_start_position);
479 set_scale(_drag_start_scale);
480 set_rotation(_drag_start_rotation);
482 vec3 start_intersection_position = _drag_start_ray.
position(_drag_start_t);
485 if(get_orientation() == GizmoOrientation::kLocal)
486 _drag_start_rotation.
rotate(axis);
488 switch(_interaction_mode) {
489 case Mode::kTranslation:
494 float start_offset = ray_ray_closest_approach(_drag_start_ray, axis_ray).second;
495 float offset = ray_ray_closest_approach(ray, axis_ray).second;
497 vec3 new_position = _drag_start_position;
498 if(_interaction_feature == InteractionFeature::kAxis)
499 new_position += (offset - start_offset) * axis;
501 new_position += intersection_position - start_intersection_position;
503 set_position(new_position);
509 vec3 new_local_offset = start_intersection_position - _drag_start_position;
510 if(_interaction_feature == InteractionFeature::kAxis)
511 new_local_offset[axis_idx] = (intersection_position - position)[axis_idx];
513 new_local_offset = intersection_position - position;
515 vec3 scale_mult = new_local_offset / (start_intersection_position - _drag_start_position);
517 vec3 new_scale = _drag_start_scale;
518 switch(_interaction_feature) {
519 case InteractionFeature::kAxis:
520 new_scale[axis_idx] *= scale_mult[axis_idx];
522 case InteractionFeature::kPlane:
523 for(
int i = 0; i < 3; ++i) {
525 new_scale[i] *= scale_mult[i];
528 case InteractionFeature::kCenter:
529 new_scale *= length(scale_mult);
535 set_scale(new_scale);
538 case Mode::kRotation:
540 vec3 start_dir = start_intersection_position - _drag_start_position;
541 vec3 end_dir = intersection_position - position;
549 vec3 plane_tangent = normalize(cross(_interaction_plane.normal, start_dir));
551 float cos_theta = dot(start_dir, end_dir);
552 cos_theta = cgv::math::clamp(cos_theta, -1.0f, 1.0f);
553 float angle = std::acos(cos_theta);
556 if(dot(plane_tangent, end_dir) < 0.0f)
557 angle = 2.0f * M_PI - angle;
559 vec3 rotaton_axis = _interaction_feature == InteractionFeature::kCenter ? _interaction_plane.normal : axis;
561 quat new_rotation =
quat(rotaton_axis, angle) * _drag_start_rotation;
563 set_rotation(new_rotation);
572 on_change(GizmoAction::kDrag, _interaction_mode);
579 on_change(GizmoAction::kDragEnd, _interaction_mode);
583 vec3 ba = r1.direction;
584 vec3 oa = r0.origin - r1.origin;
586 float a = dot(ba, ba);
587 float b = dot(r0.direction, ba);
588 float c = dot(oa, ba);
589 float e = dot(oa, r0.direction);
591 vec2 st =
vec2(c - b * e, b * c - a * e) / (a - b * b);
593 return { st.
y(), st.
x() };
T normalize()
normalize the vector using the L2-Norm and return the length
void rotate(vec_type &v) const
rotate vector according to quaternion
This class defines a template for n-dimensional rays with arbitrary data type defined by origin and d...
fvec< T, N > position(float t) const
Returns the position of the ray at the given distance (ray parameter t) from its origin.
virtual void clear(const context &ctx)
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.
void post_redraw()
posts a redraw event to the current context if one is available
bool init(context &ctx)
call init() once before using renderer
virtual bool init(context &ctx)
call init() once before using renderer
virtual void clear(const context &ctx)
the clear function destructs the shader program
const dvec3 & get_view_dir() const
query current view direction
namespace for api independent GPU programming
cgv::math::quaternion< float > quat
declare type of quaternion
cgv::media::color< float, cgv::media::RGB > rgb
declare rgb color type with 32 bit components
cgv::math::fvec< float, 2 > vec2
declare type of 2d single precision floating point vectors
cgv::math::fvec< float, 3 > vec3
declare type of 3d single precision floating point vectors