Unravel Engine C++ Reference
Loading...
Searching...
No Matches
gizmo_entity.cpp
Go to the documentation of this file.
1#include "gizmo_entity.h"
2#include "gizmos.h"
4#include "glm/ext.hpp"
5#include "imgui/imgui_internal.h"
6
8
14
18#include <hpp/type_name.hpp>
19#include <hpp/utility.hpp>
20
21#include <array>
22
23namespace unravel
24{
25namespace
26{
27auto vec3_to_bx(const math::vec3& data) -> bx::Vec3
28{
29 return {data.x, data.y, data.z};
30}
31
32auto vec3_from_bx(const bx::Vec3& data) -> math::vec3
33{
34 return {data.x, data.y, data.z};
35}
36
37} // namespace
38
39void gizmo_entity::draw(rtti::context& ctx, entt::meta_any& var, const camera& cam, gfx::dd_raii& dd1, dd_2d_raii& dd_2d)
40{
41 auto e = var.cast<entt::handle>();
42
43 if(!e || !e.all_of<transform_component>())
44 return;
45
46 auto& em = ctx.get_cached<editing_manager>();
47 auto& transform_comp = e.get<transform_component>();
48 const auto& world_transform = transform_comp.get_transform_global();
49
50 gfx::dd_raii dd(dd1.view);
51 if(e.all_of<camera_component>() && em.gizmos.show_camera)
52 {
53 auto& selected_camera_comp = e.get<camera_component>();
54 auto& selected_camera = selected_camera_comp.get_camera();
55 const auto view_proj = selected_camera.get_view_projection();
56 const auto bounds = selected_camera.get_local_bounding_box();
58 dd.encoder.setColor(0xffffffff);
59 dd.encoder.setWireframe(true);
61 {
62 dd.encoder.drawFrustum(&view_proj);
63
64 }
65 else
66 {
67 bx::Aabb aabb;
68 aabb.min = vec3_to_bx(bounds.min);
69 aabb.max = vec3_to_bx(bounds.max);
70 dd.encoder.pushTransform((const float*)world_transform);
71 dd.encoder.draw(aabb);
73
74 }
75 }
76
77 if(e.all_of<light_component>() && em.gizmos.show_light)
78 {
79 const auto& light_comp = e.get<light_component>();
80 const auto& light = light_comp.get_light();
81
83 {
84 auto adjacent = light.spot_data.get_range();
85 {
86 auto tan_angle = math::tan(math::radians(light.spot_data.get_outer_angle() * 0.5f));
87 // oposite = tan * adjacent
88 auto oposite = tan_angle * adjacent;
90 dd.encoder.setColor(0xff00ff00);
91 dd.encoder.setWireframe(true);
92 dd.encoder.setLod(3);
93 math::vec3 from = transform_comp.get_position_global();
94 math::vec3 to = from + transform_comp.get_z_axis_local() * adjacent;
95 dd.encoder.drawCone(vec3_to_bx(to), vec3_to_bx(from), oposite);
96 }
97 {
98 auto tan_angle = math::tan(math::radians(light.spot_data.get_inner_angle() * 0.5f));
99 // oposite = tan * adjacent
100 auto oposite = tan_angle * adjacent;
102 dd.encoder.setColor(0xff00ffff);
103 dd.encoder.setWireframe(true);
104 dd.encoder.setLod(3);
105 math::vec3 from = transform_comp.get_position_global();
106 math::vec3 to = from + transform_comp.get_z_axis_local() * adjacent;
107 dd.encoder.drawCone(vec3_to_bx(to), vec3_to_bx(from), oposite);
108 }
109 }
110 else if(light.type == light_type::point)
111 {
112 auto radius = light.point_data.range;
114 dd.encoder.setColor(0xff00ff00);
115 dd.encoder.setWireframe(true);
116 math::vec3 center = transform_comp.get_position_global();
117 dd.encoder.drawCircle(Axis::X, center.x, center.y, center.z, radius);
118 dd.encoder.drawCircle(Axis::Y, center.x, center.y, center.z, radius);
119 dd.encoder.drawCircle(Axis::Z, center.x, center.y, center.z, radius);
120 }
122 {
124 dd.encoder.setLod(255);
125 dd.encoder.setColor(0xff00ff00);
126 dd.encoder.setWireframe(true);
127 math::vec3 from1 = transform_comp.get_position_global();
128 math::vec3 to1 = from1 + transform_comp.get_z_axis_local() * 1.0f;
129
130 bx::Cylinder cylinder = {vec3_to_bx(from1), vec3_to_bx(to1), 0.1f};
131
132 dd.encoder.draw(cylinder);
133 math::vec3 from2 = to1;
134 math::vec3 to2 = from2 + transform_comp.get_z_axis_local() * 0.5f;
135
136 bx::Cone cone = {vec3_to_bx(from2), vec3_to_bx(to2), 0.25f};
137 dd.encoder.draw(cone);
138 }
139 }
140
141 if(e.all_of<reflection_probe_component>() && em.gizmos.show_reflection_probe)
142 {
143 const auto& probe_comp = e.get<reflection_probe_component>();
144 const auto& probe = probe_comp.get_probe();
145 if(probe.type == probe_type::box)
146 {
147
149 dd.encoder.setColor(0xff00ff00);
150 dd.encoder.setWireframe(true);
151 dd.encoder.pushTransform((const float*)world_transform);
152 bx::Aabb aabb;
153 aabb.min = vec3_to_bx(-probe.box_data.extents);
154 aabb.max = vec3_to_bx(probe.box_data.extents);
155 dd.encoder.draw(aabb);
157
158 }
159 else
160 {
161
162 auto radius = probe.get_face_extents(0, world_transform);
163 auto transform = world_transform;
164 transform.reset_scale();
166 dd.encoder.setColor(0xff00ff00);
167 dd.encoder.setWireframe(true);
168 dd.encoder.pushTransform((const float*)transform);
169 math::vec3 center{};
170 dd.encoder.drawCircle(Axis::X, center.x, center.y, center.z, radius);
171 dd.encoder.drawCircle(Axis::Y, center.x, center.y, center.z, radius);
172 dd.encoder.drawCircle(Axis::Z, center.x, center.y, center.z, radius);
173
174 }
175 }
176
177 if(e.all_of<volume_component>() && em.gizmos.show_volume)
178 {
179 const auto& volume_comp = e.get<volume_component>();
180 if(volume_comp.mode == volume_mode::local)
181 {
182 const auto& volume = volume_comp.get_local_bounds();
184 dd.encoder.pushTransform((const float*)world_transform);
185 dd.encoder.setColor(0x11ffff00);
186 dd.encoder.setWireframe(false);
187
188 dd.encoder.setState(true, false, true);
189
190 {
191
192 bx::Aabb aabb;
193 aabb.min = vec3_to_bx(volume.min);
194 aabb.max = vec3_to_bx(volume.max);
195 dd.encoder.draw(aabb);
196 }
197 {
198 dd.encoder.setColor(0xff00ff00);
199
200 dd.encoder.setWireframe(true);
201 bx::Aabb aabb;
202 aabb.min = vec3_to_bx(volume.min);
203 aabb.max = vec3_to_bx(volume.max);
204 dd.encoder.draw(aabb);
205 }
206 {
207 dd.encoder.setWireframe(false);
208 dd.encoder.setColor(0x1100ff00);
209
210 bx::Aabb aabb;
211 aabb.min = vec3_to_bx(volume.min - volume_comp.blend_distance);
212 aabb.max = vec3_to_bx(volume.max + volume_comp.blend_distance);
213 dd.encoder.draw(aabb);
214 }
215
216 dd.encoder.setState(false, false, true);
217
219 }
220 }
221
222 if(e.all_of<model_component>() && em.gizmos.show_model)
223 {
224 const auto& frustum = cam.get_frustum();
225 auto& model_comp = e.get<model_component>();
226 const auto& model = model_comp.get_model();
227 if(!model.is_valid())
228 {
229 return;
230 }
231
232 // world bounds
233
234 auto& current_lod_data = model_comp.get_lod_data_for_camera(&cam, gfx::get_render_frame());
235
236 current_lod_data.calculate_screen_rect(cam);
237
238
239 //lods - gate on the pose-aware world AABB so the overlay tracks animated geometry.
240 if(em.gizmos.show_model_bounds && frustum.test_aabb(model_comp.get_world_bounds()))
241 {
242 static constexpr std::array<uint32_t, 5> lod_colors_abgr = {
243 0xff00ff00, // LOD 0: green
244 0xff00ffff, // LOD 1: yellow
245 0xff0080ff, // LOD 2: orange
246 0xff0000ff, // LOD 3: red
247 0xffff00ff, // LOD 4+: magenta
248 };
249 static constexpr std::array<ImU32, 5> lod_colors_im = {
250 IM_COL32(0, 255, 0, 255),
251 IM_COL32(255, 255, 0, 255),
252 IM_COL32(255, 128, 0, 255),
253 IM_COL32(255, 0, 0, 255),
254 IM_COL32(255, 0, 255, 255),
255 };
256
257 const auto color_index = std::min<size_t>(current_lod_data.current_lod_index, lod_colors_abgr.size() - 1);
258
259 auto world_bounds = model_comp.get_world_bounds();
260
261 {
263 dd.encoder.setColor(lod_colors_abgr[color_index]);
264 dd.encoder.setWireframe(true);
265 bx::Aabb aabb;
266 aabb.min = vec3_to_bx(world_bounds.min);
267 aabb.max = vec3_to_bx(world_bounds.max);
268 dd.encoder.draw(aabb);
269 }
270
271 // dd_2d.callbacks.push_back([color_index, rect = current_lod_data.rect]()
272 // {
273 // const auto label_color = lod_colors_im[color_index];
274
275 // auto window = ImGui::GetCurrentWindow();
276 // auto draw_list = window->DrawList;
277 // if(rect.width() > 0 && rect.height() > 0)
278 // {
279
280 // draw_list->AddRect(ImVec2(rect.left, rect.top), ImVec2(rect.right, rect.bottom), label_color);
281 // auto draw_aligned_text = [&](ImVec2 pos, float align,const std::string& text)
282 // {
283 // auto text_size = ImGui::CalcTextSize(text.c_str());
284 // pos.x += (rect.width() - text_size.x) * align;
285 // draw_list->AddText(pos, label_color, text.c_str());
286 // };
287 // draw_aligned_text(ImVec2(rect.left, rect.bottom), 0.5f, fmt::format("LOD: {}", current_lod_data.current_lod_index));
288 // }
289 // });
290
291 // Per-submesh LOD: driven by the same cached world-space AABBs (per-instance and
292 // skinned) and the same selection logic as the render path, so the displayed LOD
293 // is exactly what gets submitted. Boxes/labels are color-coded by LOD.
294 if(em.gizmos.show_model_submesh_bounds)
295 {
296
297 const auto& proxies = model_comp.get_render_proxies();
298 const uint32_t base_lod = current_lod_data.current_lod_index;
299
300 const auto lod = model.get_lod(base_lod);
301 if(!lod)
302 {
303 return;
304 }
305 const auto& mesh = lod.get();
306
307 auto draw_submesh_lod = [&](uint32_t submesh_index, const math::bbox& bounds) -> void
308 {
309 if(!bounds.is_populated() || !frustum.test_aabb(bounds))
310 {
311 return;
312 }
313
314 const uint32_t effective_lod =
315 model.calculate_submesh_lod_from_world_bounds(*mesh, submesh_index, base_lod, bounds, cam);
316 const auto color_index = std::min<size_t>(effective_lod, lod_colors_abgr.size() - 1);
317
319 dd.encoder.setColor(lod_colors_abgr[color_index]);
320 dd.encoder.setWireframe(true);
321 bx::Aabb aabb;
322 aabb.min = vec3_to_bx(bounds.min);
323 aabb.max = vec3_to_bx(bounds.max);
324 dd.encoder.draw(aabb);
325
326 // Bounds passed the frustum test, so the center projects in front of
327 // the camera and the viewport position is valid.
328 const auto screen_pos = cam.world_to_viewport(bounds.get_center());
329 const auto label_color = lod_colors_im[color_index];
330 dd_2d.callbacks.push_back(
331 [screen_pos, effective_lod, label_color]() -> void
332 {
333 auto window = ImGui::GetCurrentWindow();
334 auto draw_list = window->DrawList;
335 const auto text = fmt::format("LOD: {}", effective_lod);
336 const auto text_size = ImGui::CalcTextSize(text.c_str());
337 const ImVec2 pos(screen_pos.x - text_size.x * 0.5f,
338 screen_pos.y - text_size.y * 0.5f);
339 draw_list->AddText(pos, label_color, text.c_str());
340 });
341 };
342
343 // Both sources can coexist on one model (rigid node-attached submeshes use
344 // instance bounds, skinned submeshes use skinned bounds); unpopulated records
345 // are skipped inside draw_submesh_lod.
346 for(size_t submesh_index = 0; submesh_index < proxies.instance_bounds.size(); ++submesh_index)
347 {
348 for(const auto& bounds : proxies.instance_bounds[submesh_index])
349 {
350 draw_submesh_lod(static_cast<uint32_t>(submesh_index), bounds);
351 }
352 }
353 for(size_t submesh_index = 0; submesh_index < proxies.skinned_bounds.size(); ++submesh_index)
354 {
355 draw_submesh_lod(static_cast<uint32_t>(submesh_index), proxies.skinned_bounds[submesh_index]);
356 }
357 }
358 }
359
360
361 }
362
363 if(e.all_of<text_component>() && em.gizmos.show_text)
364 {
365 const auto& frustum = cam.get_frustum();
366 const auto& text_comp = e.get<text_component>();
367 auto bbox = text_comp.get_bounds();
368 if(frustum.test_obb(bbox, world_transform))
369 {
371 dd.encoder.setColor(0xff00ffff);
372 dd.encoder.setWireframe(true);
373 dd.encoder.pushTransform((const float*)world_transform);
374 bx::Aabb aabb;
375 aabb.min = vec3_to_bx(bbox.min);
376 aabb.max = vec3_to_bx(bbox.max);
377 dd.encoder.draw(aabb);
379 }
380 }
381
382 if(e.all_of<particle_emitter_component>() && em.gizmos.show_particle_emitter)
383 {
384 const auto& frustum = cam.get_frustum();
385 const auto& particle_emitter_comp = e.get<particle_emitter_component>();
386
387 // Draw world bounds
388 if(em.gizmos.show_particle_emitter_bounds)
389 {
390 const auto& bounds = particle_emitter_comp.get_world_bounds();
391 if(frustum.test_aabb(bounds))
392 {
394 dd.encoder.setColor(0xff00ffff);
395 dd.encoder.setWireframe(true);
396 bx::Aabb aabb;
397 aabb.min = vec3_to_bx(bounds.min);
398 aabb.max = vec3_to_bx(bounds.max);
399 dd.encoder.draw(aabb);
400 }
401 }
402
403 // Draw emission shape
404 if(em.gizmos.show_particle_emitter_shape)
405 {
407 dd.encoder.setColor(0xffff8000); // Orange color for emission shape
408 dd.encoder.setWireframe(true);
409 dd.encoder.pushTransform((const float*)world_transform);
410
411 const auto shape = particle_emitter_comp.get_shape();
412 const auto scale = particle_emitter_comp.get_emission_shape_scale();
413 const auto position = particle_emitter_comp.get_emission_shape_position();
414 const auto direction = particle_emitter_comp.get_direction();
415 const float shape_size = 1.0f; // Base size for visualization
416
417 switch(shape)
418 {
420 {
421 auto transform = math::translate(math::mat4(1.0f), position) * math::scale(math::mat4(1.0f), scale);
422 dd.encoder.pushTransform(math::value_ptr(transform));
423
424 // Draw a wireframe sphere
425 math::vec3 center{0.0f, 0.0f, 0.0f};
426 dd.encoder.drawCircle(Axis::X, center.x, center.y, center.z, shape_size);
427 dd.encoder.drawCircle(Axis::Y, center.x, center.y, center.z, shape_size);
428 dd.encoder.drawCircle(Axis::Z, center.x, center.y, center.z, shape_size);
429
431 break;
432 }
434 {
435 auto transform = math::translate(math::mat4(1.0f), position) * math::scale(math::mat4(1.0f), scale);
436 dd.encoder.pushTransform(math::value_ptr(transform));
437
438 // Draw hemisphere (half sphere facing up)
439 math::vec3 center{0.0f, 0.0f, 0.0f};
440
441 // Draw the base circle (full circle at Y=0)
442 dd.encoder.drawCircle(Axis::Y, center.x, center.y, center.z, shape_size);
443
444 // Draw vertical arcs to form the hemisphere dome
445 // Each arc goes from one side of the base circle, over the top, to the other side
446 const int num_arcs = 8; // Number of vertical arcs around the hemisphere
447 for(int i = 0; i < num_arcs; ++i)
448 {
449 const float angle = (3.14159265f * static_cast<float>(i)) / static_cast<float>(num_arcs);
450 const float cos_a = math::cos(angle);
451 const float sin_a = math::sin(angle);
452
453 // Position each arc at a different point around the base circle
454 // and draw a 180-degree arc that goes up and over
455 const float x_pos = cos_a * shape_size;
456 const float z_pos = sin_a * shape_size;
457
458 // Draw arc in the plane that contains the Y axis and the radial direction
459 // We need to use moveTo/lineTo to manually create the hemisphere arcs
460 const int arc_segments = 16;
461 bool first_point = true;
462
463 for(int j = 0; j <= arc_segments; ++j)
464 {
465 const float arc_angle = (3.14159265f * static_cast<float>(j)) / static_cast<float>(arc_segments);
466 const float y = shape_size * math::sin(arc_angle);
467 const float radius_at_height = shape_size * math::cos(arc_angle);
468
469 const float x = cos_a * radius_at_height;
470 const float z = sin_a * radius_at_height;
471
472 if(first_point)
473 {
474 dd.encoder.moveTo(x, y, z);
475 first_point = false;
476 }
477 else
478 {
479 dd.encoder.lineTo(x, y, z);
480 }
481 }
482 }
483
484 // Draw horizontal circles at different heights to show the hemisphere shape
485 const int num_horizontal_circles = 2;
486 for(int i = 1; i <= num_horizontal_circles; ++i)
487 {
488 const float height_ratio = static_cast<float>(i) / static_cast<float>(num_horizontal_circles + 1);
489 const float y_pos = shape_size * height_ratio;
490 const float radius_at_height = shape_size * math::sqrt(1.0f - height_ratio * height_ratio);
491
492 // Draw circles at different heights
493 dd.encoder.drawCircle(Axis::Y, center.x, y_pos, center.z, radius_at_height);
494 }
495
497 break;
498 }
500 {
501 auto transform = math::translate(math::mat4(1.0f), position) * math::scale(math::mat4(1.0f), scale);
502 dd.encoder.pushTransform(math::value_ptr(transform));
503
504 // Draw a circle in the XZ plane
505 math::vec3 center{0.0f, 0.0f, 0.0f};
506 dd.encoder.drawCircle(Axis::Y, center.x, center.y, center.z, shape_size);
507
509 break;
510 }
512 {
513 // Draw a box
514 const float half_size = shape_size;
515 bx::Aabb box_aabb;
516 box_aabb.min = {-half_size * scale.x + position.x, -half_size * scale.y + position.y, -half_size * scale.z + position.z};
517 box_aabb.max = {half_size * scale.x + position.x, half_size * scale.y + position.y, half_size * scale.z + position.z};
518 dd.encoder.draw(box_aabb);
519 break;
520 }
522 {
523 // Draw a rectangle in the XZ plane
524 const float half_size = shape_size;
525 bx::Aabb rect_aabb;
526 rect_aabb.min = {-half_size * scale.x + position.x, -0.01f, -half_size * scale.z + position.z};
527 rect_aabb.max = {half_size * scale.x + position.x, 0.01f, half_size * scale.z + position.z};
528 dd.encoder.draw(rect_aabb);
529 break;
530 }
531 default:
532 break;
533 }
534
535 if(em.gizmos.show_particle_emitter_direction)
536 {
537 // Draw direction indicators
538 dd.encoder.setColor(0xff00ff00); // Green color for direction
539 const float arrow_length = shape_size * 0.5f;
540 const float arrow_head_size = arrow_length * 0.2f;
541
542 switch(direction)
543 {
545 {
546 // Draw upward arrows
547 dd.encoder.moveTo(0.0f, 0.0f, 0.0f);
548 dd.encoder.lineTo(0.0f, arrow_length, 0.0f);
549 // Arrow head
550 dd.encoder.moveTo(0.0f, arrow_length, 0.0f);
551 dd.encoder.lineTo(-arrow_head_size, arrow_length - arrow_head_size, 0.0f);
552 dd.encoder.moveTo(0.0f, arrow_length, 0.0f);
553 dd.encoder.lineTo(arrow_head_size, arrow_length - arrow_head_size, 0.0f);
554 dd.encoder.moveTo(0.0f, arrow_length, 0.0f);
555 dd.encoder.lineTo(0.0f, arrow_length - arrow_head_size, -arrow_head_size);
556 dd.encoder.moveTo(0.0f, arrow_length, 0.0f);
557 dd.encoder.lineTo(0.0f, arrow_length - arrow_head_size, arrow_head_size);
558 break;
559 }
561 {
562 // Draw multiple outward arrows
563 const int num_arrows = 6;
564 for(int i = 0; i < num_arrows; ++i)
565 {
566 const float angle =
567 (2.0f * 3.14159265f * static_cast<float>(i)) / static_cast<float>(num_arrows);
568 const float cos_a = math::cos(angle);
569 const float sin_a = math::sin(angle);
570
571 // Arrow shaft
572 dd.encoder.moveTo(cos_a * shape_size * 0.3f, 0.0f, sin_a * shape_size * 0.3f);
573 dd.encoder.lineTo(cos_a * arrow_length, 0.0f, sin_a * arrow_length);
574
575 // Arrow head
576 const float head_x = cos_a * arrow_length;
577 const float head_z = sin_a * arrow_length;
578 const float back_x = cos_a * (arrow_length - arrow_head_size);
579 const float back_z = sin_a * (arrow_length - arrow_head_size);
580
581 dd.encoder.moveTo(head_x, 0.0f, head_z);
582 dd.encoder.lineTo(back_x - sin_a * arrow_head_size * 0.5f,
583 0.0f,
584 back_z + cos_a * arrow_head_size * 0.5f);
585 dd.encoder.moveTo(head_x, 0.0f, head_z);
586 dd.encoder.lineTo(back_x + sin_a * arrow_head_size * 0.5f,
587 0.0f,
588 back_z - cos_a * arrow_head_size * 0.5f);
589 dd.encoder.moveTo(head_x, 0.0f, head_z);
590 dd.encoder.lineTo(back_x, arrow_head_size * 0.5f, back_z);
591 }
592 break;
593 }
595 {
596 // Draw multiple inward arrows
597 const int num_arrows = 6;
598 for(int i = 0; i < num_arrows; ++i)
599 {
600 const float angle =
601 (2.0f * 3.14159265f * static_cast<float>(i)) / static_cast<float>(num_arrows);
602 const float cos_a = math::cos(angle);
603 const float sin_a = math::sin(angle);
604
605 // Arrow shaft
606 dd.encoder.moveTo(cos_a * shape_size * 0.3f, 0.0f, sin_a * shape_size * 0.3f);
607 dd.encoder.lineTo(cos_a * arrow_length, 0.0f, sin_a * arrow_length);
608
609 // Arrow head
610 const float head_x = cos_a * arrow_length;
611 const float head_z = sin_a * arrow_length;
612 const float back_x = cos_a * (arrow_length - arrow_head_size);
613 const float back_z = sin_a * (arrow_length - arrow_head_size);
614
615 dd.encoder.moveTo(head_x, 0.0f, head_z);
616 dd.encoder.lineTo(back_x - sin_a * arrow_head_size * 0.5f,
617 0.0f,
618 back_z + cos_a * arrow_head_size * 0.5f);
619 dd.encoder.moveTo(head_x, 0.0f, head_z);
620 dd.encoder.lineTo(back_x + sin_a * arrow_head_size * 0.5f,
621 0.0f,
622 back_z - cos_a * arrow_head_size * 0.5f);
623 dd.encoder.moveTo(head_x, 0.0f, head_z);
624 dd.encoder.lineTo(back_x, arrow_head_size * 0.5f, back_z);
625 }
626 break;
627 }
628 default:
629 break;
630 }
631 }
632
634 }
635 }
636
637 if(em.gizmos.show_component_gizmos)
638 {
639 hpp::for_each_tuple_type<all_inspectable_components>(
640 [&](auto index)
641 {
642 using ctype = std::tuple_element_t<decltype(index)::value, all_inspectable_components>;
643 auto component = e.try_get<ctype>();
644 if(!component)
645 {
646 return;
647 }
648 auto var_comp = entt::forward_as_meta(*component);
649 ::unravel::draw_gizmo_var(ctx, var_comp, cam, dd, dd_2d);
650 });
651 }
652}
653
654
655
656void gizmo_entity::draw_billboard(rtti::context& ctx, entt::meta_any& var, const camera& cam, gfx::dd_raii& dd)
657{
658 auto e = var.cast<entt::handle>();
659
660 if(!e || !e.all_of<transform_component>())
661 return;
662
663
664 auto& tm = ctx.get_cached<thumbnail_manager>();
665
666 auto& em = ctx.get_cached<editing_manager>();
667
668 auto& transform_comp = e.get<transform_component>();
669 const auto& world_transform = transform_comp.get_transform_global();
670
671 constexpr float MIN_VISIBLE_DISTANCE = 1.0f;
672 constexpr float MIN_FADE_RANGE = 0.5f;
673
674 constexpr float MAX_VISIBLE_DISTANCE = 50.0f;
675 constexpr float MAX_FADE_RANGE = 25.0f;
676
677 auto dist = math::distance(world_transform.get_position(), cam.get_position());
678
679 // Calculate distance-based alpha: full visibility in range (MIN_VISIBLE_DISTANCE - MAX_VISIBLE_DISTANCE), fade outside
680 float distance_alpha = 1.0f;
681 if(dist < MIN_VISIBLE_DISTANCE)
682 {
683 // Fade from 0 to 1 as distance goes from (MIN_VISIBLE_DISTANCE - MIN_FADE_RANGE) to MIN_VISIBLE_DISTANCE
684 float fade_start = MIN_VISIBLE_DISTANCE - MIN_FADE_RANGE;
685 distance_alpha = math::clamp((dist - fade_start) / MIN_FADE_RANGE, 0.0f, 1.0f);
686 }
687 else if(dist > MAX_VISIBLE_DISTANCE)
688 {
689 // Fade from 1 to 0 as distance goes from MAX_VISIBLE_DISTANCE to (MAX_VISIBLE_DISTANCE + MAX_FADE_RANGE)
690 distance_alpha = math::clamp(1.0f - (dist - MAX_VISIBLE_DISTANCE) / MAX_FADE_RANGE, 0.0f, 1.0f);
691 }
692 // else: dist is in range [MIN_VISIBLE_DISTANCE, MAX_VISIBLE_DISTANCE], keep distance_alpha = 1.0f
693
694 auto alpha = em.billboard_data.opacity * distance_alpha;
695
696 // Early return if completely transparent
697 if(alpha <= 0.0f)
698 return;
699
700 auto col = math::color::white();
701
702 float tint = 1.0f;
703 if(!transform_comp.is_active_global())
704 {
705 tint *= 0.5f;
706 }
707
708 auto icon = tm.get_gizmo_icon(e);
709
710 if(e.all_of<light_component>())
711 {
712 const auto& light_comp = e.get<light_component>();
713 const auto& light = light_comp.get_light();
714 col = light.color;
715 }
716
717 if(!cam.test_billboard(em.billboard_data.size, world_transform))
718 return; // completely outside → skip draw
719
720 if(icon)
721 {
722 dd.encoder.setState(em.billboard_data.depth_aware, false, true);
723 col.value.a = alpha;
724
725 col.value *= tint;
726 dd.encoder.setColor(col);
727
729 icon->native_handle(),
730 vec3_to_bx(world_transform.get_position()),
731 vec3_to_bx(cam.get_position()),
732 vec3_to_bx(cam.z_unit_axis()),
733 em.billboard_data.size);
734 dd.encoder.setColor(0xffffffff);
735
736 dd.encoder.setState(true, true, true);
737 }
738}
739} // namespace unravel
Class that contains core camera data, used for rendering and other purposes.
auto get_projection_mode() const -> projection_mode
Gets the projection mode.
auto get_camera() -> camera &
Gets the camera object.
Class representing a camera. Contains functionality for manipulating and updating a camera....
Definition camera.h:62
auto test_billboard(float size, const math::transform &t) const -> bool
Definition camera.cpp:460
auto z_unit_axis() const -> math::vec3
Retrieves the z-axis unit vector of the camera's local coordinate system.
Definition camera.cpp:367
auto get_position() const -> const math::vec3 &
Retrieves the current position of the camera.
Definition camera.cpp:353
auto world_to_viewport(const math::vec3 &pos) const -> math::vec3
Transforms a point from world space into screen space.
Definition camera.cpp:473
auto get_frustum() const -> const math::frustum &
Retrieves the current camera object frustum.
Definition camera.cpp:372
Class that contains core light data, used for rendering and other purposes.
Main class representing a 3D mesh with support for different LODs, submeshes, and skinning.
Definition mesh.h:323
Class that contains core data for meshes.
Structure describing a LOD group (set of meshes), LOD transitions, and their materials.
Definition model.h:275
auto get_lod(uint32_t lod) const -> asset_handle< mesh >
Gets the LOD (Level of Detail) mesh for the specified level.
Definition model.cpp:179
auto calculate_submesh_lod_from_world_bounds(const mesh &m, uint32_t submesh_index, uint32_t base_lod, const math::bbox &world_bounds, const camera &cam) const -> uint32_t
Selects a LOD for a submesh from an already-known world-space AABB.
Definition model.cpp:552
auto is_valid() const -> bool
Checks if the model is valid.
Definition model.cpp:174
Component that wraps the soa particle system emitter.
Class that contains core reflection probe data, used for rendering and other purposes.
auto get_probe() const -> const reflection_probe &
Gets the reflection probe object.
auto get_bounds() const -> math::bbox
Gets the bounding box of the text.
Component that handles transformations (position, rotation, scale, etc.) in the ACE framework.
Spatial volume that applies post-processing effects when the camera is inside. Effect settings come f...
float y
float x
float z
const char * icon
math::vec3 position
Definition defaults.cpp:52
uint16_t index
void draw_billboard(DebugDrawEncoder &dd, bgfx::TextureHandle icon_texture, const bx::Vec3 &icon_center, const bx::Vec3 &camera_pos, const bx::Vec3 &camera_look_dir, float half_size)
Definition debugdraw.cpp:25
uint32_t get_render_frame()
void draw_gizmo_var(rtti::context &ctx, entt::meta_any &var, const camera &cam, gfx::dd_raii &dd, dd_2d_raii &dd_2d)
Definition gizmos.cpp:41
@ box
Box type reflection probe.
std::tuple< id_component, tag_component, layer_component, prefab_component, prefab_id_component, transform_component, test_component, model_component, animation_component, bone_component, submesh_component, camera_component, volume_component, auto_exposure_component, tonemapping_component, assao_component, bloom_component, fxaa_component, taa_component, ssr_component, ssil_component, light_component, skylight_component, reflection_probe_component, physics_component, character_controller_component, audio_source_component, audio_listener_component, text_component, particle_emitter_component, ui_document_component > all_inspectable_components
std::vector< float > scale
@ X
Definition debugdraw.h:17
@ Y
Definition debugdraw.h:18
@ Z
Definition debugdraw.h:19
void draw(const bx::Aabb &_aabb)
void lineTo(float _x, float _y, float _z=0.0f)
void drawCone(const bx::Vec3 &_from, const bx::Vec3 &_to, float _radius)
void setColor(uint32_t _abgr)
void setWireframe(bool _wireframe)
void setState(bool _depthTest, bool _depthWrite, bool _clockwise, bool _alphaWrite=false, bool _alphaBlend=true)
void pushTransform(const void *_mtx)
void drawFrustum(const void *_viewProj)
void moveTo(float _x, float _y, float _z=0.0f)
void drawCircle(const bx::Vec3 &_normal, const bx::Vec3 &_center, float _radius, float _weight=0.0f)
void setLod(uint8_t _lod)
DebugDrawEncoder encoder
Definition debugdraw.h:15
view_id view
Definition debugdraw.h:16
Storage for box vector values and wraps up common functionality.
Definition bbox.h:21
static color white()
Definition color.h:13
auto get_cached() -> T &
Definition context.hpp:49
std::vector< std::function< void()> > callbacks
Definition gizmo.h:16
void draw_billboard(rtti::context &ctx, entt::meta_any &var, const camera &cam, gfx::dd_raii &dd) override
void draw(rtti::context &ctx, entt::meta_any &var, const camera &cam, gfx::dd_raii &dd, dd_2d_raii &dd_2d) override
float range
The range of the point light.
Definition light.h:162
float get_range() const
Gets the range of the spot light.
Definition light.h:106
float get_outer_angle() const
Gets the outer angle of the spot light.
Definition light.h:121
float get_inner_angle() const
Gets the inner angle of the spot light.
Definition light.h:136
Struct representing a light.
Definition light.h:87
light_type type
The type of the light.
Definition light.h:89
math::color color
The color of the light.
Definition light.h:207
point point_data
Data specific to point lights.
Definition light.h:203
spot spot_data
Data specific to spot lights.
Definition light.h:201