Unravel Engine C++ Reference
Loading...
Searching...
No Matches
picking_manager.cpp
Go to the documentation of this file.
1#include "picking_manager.h"
2#include "thumbnail_manager.h"
3
6#include <graphics/texture.h>
7#include <logging/logging.h>
9
12#include <engine/events.h>
21
22#include <algorithm>
23#include <unordered_set>
24namespace unravel
25{
26
27namespace
28{
29auto to_bx(const math::vec3& data) -> bx::Vec3
30{
31 return {data.x, data.y, data.z};
32}
33
34auto from_bx(const bx::Vec3& data) -> math::vec3
35{
36 return {data.x, data.y, data.z};
37}
38
39} // namespace
40
41// Helper functions for different picking types
42namespace
43{
44// Type 1: Position-only picking
45bool is_position_in_selection_area(const math::vec3& world_position,
46 const camera& pick_camera,
47 const math::vec2& pick_position,
48 const math::vec2& pick_area)
49{
50 // Project position to screen space
51 math::vec3 screen_pos = pick_camera.world_to_viewport(world_position);
52
53 // Check if position is within the selection rectangle
54 return (
55 screen_pos.x >= pick_position.x - pick_area.x * 0.5f && screen_pos.x <= pick_position.x + pick_area.x * 0.5f &&
56 screen_pos.y >= pick_position.y - pick_area.y * 0.5f && screen_pos.y <= pick_position.y + pick_area.y * 0.5f);
57}
58
59// Type 3: Global bounds (already world-space)
60bool are_corners_in_selection_area(hpp::span<const math::vec3> corners,
61 const camera& pick_camera,
62 const math::vec2& pick_position,
63 const math::vec2& pick_area)
64{
65 // Check if all corners are in selection area
66 bool all_corners_in_selection = true;
67 for(int i = 0; i < 8; ++i)
68 {
69 // Project to screen space (corners are already in world space)
70 math::vec3 screen_pos = pick_camera.world_to_viewport(corners[i]);
71
72 // Check if this corner is within the selection rectangle
73 bool corner_in_selection = (screen_pos.x >= pick_position.x - pick_area.x * 0.5f &&
74 screen_pos.x <= pick_position.x + pick_area.x * 0.5f &&
75 screen_pos.y >= pick_position.y - pick_area.y * 0.5f &&
76 screen_pos.y <= pick_position.y + pick_area.y * 0.5f);
77
78 all_corners_in_selection &= corner_in_selection;
79 }
80
81 return all_corners_in_selection;
82}
83
84// Type 2: Local bounds with transform (existing logic)
85bool are_local_bounds_in_selection_area(const math::bbox& local_bounds,
86 const math::transform& world_transform,
87 const camera& pick_camera,
88 const math::vec2& pick_position,
89 const math::vec2& pick_area)
90{
91 // Generate bounding box corners
92 math::vec3 corners[8] = {
93 world_transform.transform_coord({local_bounds.min.x, local_bounds.min.y, local_bounds.min.z}),
94 world_transform.transform_coord({local_bounds.max.x, local_bounds.min.y, local_bounds.min.z}),
95 world_transform.transform_coord({local_bounds.min.x, local_bounds.max.y, local_bounds.min.z}),
96 world_transform.transform_coord({local_bounds.max.x, local_bounds.max.y, local_bounds.min.z}),
97 world_transform.transform_coord({local_bounds.min.x, local_bounds.min.y, local_bounds.max.z}),
98 world_transform.transform_coord({local_bounds.max.x, local_bounds.min.y, local_bounds.max.z}),
99 world_transform.transform_coord({local_bounds.min.x, local_bounds.max.y, local_bounds.max.z}),
100 world_transform.transform_coord({local_bounds.max.x, local_bounds.max.y, local_bounds.max.z})};
101
102 return are_corners_in_selection_area(corners, pick_camera, pick_position, pick_area);
103}
104
105// Type 3: Global bounds (already world-space)
106bool are_global_bounds_in_selection_area(const math::bbox& world_bounds,
107 const camera& pick_camera,
108 const math::vec2& pick_position,
109 const math::vec2& pick_area)
110{
111 // Generate bounding box corners (already in world space)
112 math::vec3 corners[8] = {{world_bounds.min.x, world_bounds.min.y, world_bounds.min.z},
113 {world_bounds.max.x, world_bounds.min.y, world_bounds.min.z},
114 {world_bounds.min.x, world_bounds.max.y, world_bounds.min.z},
115 {world_bounds.max.x, world_bounds.max.y, world_bounds.min.z},
116 {world_bounds.min.x, world_bounds.min.y, world_bounds.max.z},
117 {world_bounds.max.x, world_bounds.min.y, world_bounds.max.z},
118 {world_bounds.min.x, world_bounds.max.y, world_bounds.max.z},
119 {world_bounds.max.x, world_bounds.max.y, world_bounds.max.z}};
120
121 return are_corners_in_selection_area(corners, pick_camera, pick_position, pick_area);
122}
123} // namespace
124
125namespace
126{
133auto find_model_owner(entt::registry& registry, entt::handle armature_entity) -> entt::handle
134{
135 if(!armature_entity)
136 {
137 return {};
138 }
139 auto view = registry.view<model_component>();
140 for(auto e : view)
141 {
142 entt::handle model_entity(registry, e);
143 auto& model_comp = view.get<model_component>(e);
144 const auto& armature_entities = model_comp.get_armature_entities();
145 for(const auto& arm_ent : armature_entities)
146 {
147 if(arm_ent == armature_entity)
148 {
149 return model_entity;
150 }
151 }
152 }
153 return {};
154}
155
163auto are_in_same_model(entt::registry& registry, entt::handle entity1, entt::handle entity2) -> bool
164{
165 if(!entity1 || !entity2)
166 {
167 return false;
168 }
169 auto model_owner1 = find_model_owner(registry, entity1);
170 auto model_owner2 = find_model_owner(registry, entity2);
171 if(!model_owner1 || !model_owner2)
172 {
173 return false;
174 }
175 return model_owner1 == model_owner2;
176}
177
189auto get_logical_top_level_entity(entt::registry& registry, entt::handle entity) -> entt::handle
190{
191 if(!entity)
192 {
193 return {};
194 }
195 entt::handle starting_model_owner = {};
196 bool is_starting_in_armature = false;
197 auto starting_model = find_model_owner(registry, entity);
198 if(starting_model)
199 {
200 starting_model_owner = starting_model;
201 is_starting_in_armature = true;
202 }
203 struct marker_info
204 {
205 entt::handle entity;
206 int depth;
207 };
208 hpp::optional<marker_info> prefab_marker;
209 hpp::optional<marker_info> animation_marker;
210 hpp::optional<marker_info> model_root_marker;
211 entt::handle last_in_same_model = entity;
212 auto current = entity;
213 int depth = 0;
214 while(current)
215 {
216 if(current.try_get<prefab_component>())
217 {
218 if(!prefab_marker || prefab_marker->depth > depth)
219 {
220 prefab_marker = {current, depth};
221 }
222 }
223 if(current.try_get<animation_component>())
224 {
225 if(!animation_marker || animation_marker->depth > depth)
226 {
227 animation_marker = {current, depth};
228 }
229 }
230 auto* transform = current.try_get<transform_component>();
231 if(!transform)
232 {
233 break;
234 }
235 auto parent = transform->get_parent();
236 if(!parent)
237 {
238 if(is_starting_in_armature)
239 {
240 auto current_model = find_model_owner(registry, current);
241 if(current_model == starting_model_owner)
242 {
243 if(!model_root_marker || model_root_marker->depth > depth)
244 {
245 model_root_marker = {current, depth};
246 }
247 }
248 }
249 else
250 {
251 if(!model_root_marker || model_root_marker->depth > depth)
252 {
253 model_root_marker = {current, depth};
254 }
255 }
256 break;
257 }
258 if(is_starting_in_armature)
259 {
260 if(are_in_same_model(registry, current, parent))
261 {
262 last_in_same_model = current;
263 }
264 else
265 {
266 if(!model_root_marker || model_root_marker->depth > depth)
267 {
268 model_root_marker = {last_in_same_model, depth};
269 }
270 is_starting_in_armature = false;
271 }
272 }
273 current = parent;
274 depth++;
275 }
276 if(prefab_marker)
277 {
278 return prefab_marker->entity;
279 }
280 if(animation_marker)
281 {
282 return animation_marker->entity;
283 }
284 if(model_root_marker)
285 {
286 return model_root_marker->entity;
287 }
288 return entity;
289}
290
297auto is_ancestor_of(entt::handle potential_ancestor, entt::handle child) -> bool
298{
299 if(!child || !potential_ancestor)
300 {
301 return false;
302 }
303 auto* transform = child.try_get<transform_component>();
304 if(!transform)
305 {
306 return false;
307 }
308 entt::handle current = transform->get_parent();
309 while(current)
310 {
311 if(current == potential_ancestor)
312 {
313 return true;
314 }
315 auto* current_transform = current.try_get<transform_component>();
316 if(!current_transform)
317 {
318 break;
319 }
320 current = current_transform->get_parent();
321 }
322 return false;
323}
324
332auto should_skip_selection_for_additive_pick(const editing_manager& em,
334 const math::vec2& pick_area,
335 entt::handle picked_entity) -> bool
336{
337 bool is_area_picking = pick_area.x > 0.0f && pick_area.y > 0.0f;
338
339
340 if(!is_area_picking)
341 {
342 return false;
343 }
344 if(!picked_entity)
345 {
346 return false;
347 }
349 {
350 return false;
351 }
352 auto selections = em.get_selections();
353 for(const auto& selected_obj : selections)
354 {
355 if(selected_obj.type() == entt::resolve<entt::handle>())
356 {
357 auto selected_entity = selected_obj.cast<const entt::handle&>();
358 if(is_ancestor_of(selected_entity, picked_entity))
359 {
360 return true;
361 }
362 }
363 }
364 return false;
365}
366
371auto filter_area_selection_roots(std::vector<entt::handle>& candidates) -> void
372{
373 candidates.erase(
374 std::remove_if(candidates.begin(),
375 candidates.end(),
376 [&](const entt::handle& entity)
377 {
378 for(const auto& other : candidates)
379 {
380 if(other != entity && is_ancestor_of(other, entity))
381 {
382 return true;
383 }
384 }
385 return false;
386 }),
387 candidates.end());
388}
389
390auto resolve_area_selection_candidates(entt::registry& registry, std::vector<entt::handle> candidates)
391 -> std::vector<entt::handle>
392{
393 std::vector<entt::handle> resolved;
394 resolved.reserve(candidates.size());
395
396 std::unordered_set<entt::entity> seen;
397 for(const auto& candidate : candidates)
398 {
399 if(!candidate)
400 {
401 continue;
402 }
403
404 auto logical = get_logical_top_level_entity(registry, candidate);
405 if(!logical)
406 {
407 continue;
408 }
409
410 const auto entity_id = logical.entity();
411 if(seen.insert(entity_id).second)
412 {
413 resolved.push_back(logical);
414 }
415 }
416
417 filter_area_selection_roots(resolved);
418 return resolved;
419}
420
421} // namespace
422
423constexpr int picking_manager::tex_id_dim;
424void picking_manager::on_frame_render(rtti::context& ctx, delta_t dt)
425{
426 on_frame_pick(ctx, dt);
427}
428
429void picking_manager::on_frame_pick(rtti::context& ctx, delta_t dt)
430{
431 APP_SCOPE_PERF("On Frame Pick");
432 auto& em = ctx.get_cached<editing_manager>();
433
434 // Get the appropriate scene based on edit mode
435 scene* target_scene = em.get_active_scene(ctx);
436
437 if(!target_scene)
438 {
439 return;
440 }
441
442 if(pick_area_.x > 0.0f && pick_area_.y > 0.0f && pick_camera_)
443 {
444 const auto& pick_camera = *pick_camera_;
445
446 std::vector<entt::handle> in_area_candidates;
447 in_area_candidates.reserve(64);
448
449 target_scene->registry->view<transform_component, active_component>().each(
450 [&](auto e, auto&& transform_comp, auto&&)
451 {
452 const auto& world_transform = transform_comp.get_transform_global();
453 const auto& world_position = world_transform.get_position();
454
455 if(!pick_camera.get_frustum().test_point(world_position))
456 {
457 return;
458 }
459
460 if(!is_position_in_selection_area(world_position, pick_camera, pick_position_, pick_area_))
461 {
462 return;
463 }
464
465 in_area_candidates.push_back(target_scene->create_handle(e));
466 });
467
468 const auto area_selection =
469 resolve_area_selection_candidates(*target_scene->registry, std::move(in_area_candidates));
470
471 target_scene->registry->view<transform_component, active_component>().each(
472 [&](auto e, auto&&, auto&&)
473 {
474 auto handle = target_scene->create_handle(e);
475 const bool in_area_selection =
476 std::find(area_selection.begin(), area_selection.end(), handle) != area_selection.end();
477
478 if(in_area_selection)
479 {
480 process_pick_result(ctx, target_scene, ENTT_ID_TYPE(e));
481 return;
482 }
483
484 if(std::find(picked_entities_.begin(), picked_entities_.end(), handle) == picked_entities_.end())
485 {
486 em.unselect(handle);
487 }
488 });
489
490 pick_camera_.reset();
491 original_camera_.reset();
492 pick_position_ = {};
493 return;
494 }
495
496 const auto render_frame = gfx::get_render_frame();
497
498 picked_entities_.clear();
499 if(pick_camera_ && original_camera_)
500 {
501 const auto& pick_camera = *pick_camera_;
502 const auto& original_camera = *original_camera_;
503
504 const auto& pick_view = pick_camera.get_view();
505 const auto& pick_proj = pick_camera.get_projection();
506
507 gfx::render_pass pass("Picking/Buffer Pass");
508 // ID buffer clears to black, which represents clicking on nothing (background)
509 pass.clear(BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0x000000ff, 1.0f, 0);
510 pass.set_view_proj(pick_view, pick_proj);
511 pass.bind(surface_.get());
512
513 bool anything_picked = false;
514
515 // Regular picking (render-to-texture) supports:
516 // Type 2: Model components - rendered with actual geometry
517 // Type 1 & 3: Other components - handled via gizmo icons (see gizmo section below)
518 target_scene->registry->view<transform_component, model_component, active_component>().each(
519 [&](auto e, auto&& transform_comp, auto&& model_comp, auto&& active)
520 {
521 auto& model = model_comp.get_model();
522 if(!model.is_valid())
523 {
524 return;
525 }
526
527 const auto& world_transform = transform_comp.get_transform_global();
528
529 auto& current_lod_data = model_comp.get_lod_data_for_camera(&original_camera, gfx::get_render_frame());
530
531 // Test against the pose-aware world AABB (tracks node/bone animation),
532 // never the bind-pose mesh bounds.
533 if(!pick_camera.get_frustum().test_aabb(model_comp.get_world_bounds()))
534 {
535 return;
536 }
537
538 auto id = ENTT_ID_TYPE(e);
539 std::uint32_t rr = (id) & 0xff;
540 std::uint32_t gg = (id >> 8) & 0xff;
541 std::uint32_t bb = (id >> 16) & 0xff;
542 std::uint32_t aa = (id >> 24) & 0xff;
543
544 math::vec4 color_id = {rr / 255.0f, gg / 255.0f, bb / 255.0f, aa / 255.0f};
545
546 anything_picked = true;
547 const auto& submesh_transforms = model_comp.get_submesh_transforms();
548 const auto& bone_transforms = model_comp.get_bone_transforms();
549 const auto& skinning_transforms = model_comp.get_skinning_transforms();
550
551 model::submit_callbacks callbacks;
552 callbacks.setup_begin = [&](const model::submit_callbacks::params& submit_params)
553 {
554 auto& prog = submit_params.skinned ? program_skinned_ : program_;
555
556 prog->begin();
557 };
558 callbacks.setup_params_per_instance = [&](const model::submit_callbacks::params& submit_params)
559 {
560 auto& prog = submit_params.skinned ? program_skinned_ : program_;
561
562 prog->set_uniform("u_id", math::value_ptr(color_id));
563 };
564 callbacks.setup_params_per_submesh =
565 [&](const model::submit_callbacks::params& submit_params, const material& mat)
566 {
567 auto& prog = submit_params.skinned ? program_skinned_ : program_;
568
569 gfx::set_state(mat.get_render_states());
570 gfx::submit(pass.id, prog->native_handle(), 0, submit_params.preserve_state);
571 };
572 callbacks.setup_end = [&](const model::submit_callbacks::params& submit_params)
573 {
574 auto& prog = submit_params.skinned ? program_skinned_ : program_;
575
576 prog->end();
577 };
578
579 model.submit(world_transform, submesh_transforms, bone_transforms, skinning_transforms, current_lod_data.current_lod_index, callbacks);
580 });
581
582 gfx::discard();
583
584 if(program_gizmos_)
585 {
586 gfx::dd_raii dd(pass.id);
587
588 target_scene->registry->view<transform_component, text_component, active_component>().each(
589 [&](auto e, auto&& transform_comp, auto&& text_comp, auto&& active)
590 {
591 if(!text_comp.can_be_rendered())
592 {
593 return;
594 }
595 const auto& world_transform = transform_comp.get_transform_global();
596 auto bbox = text_comp.get_bounds();
597
598 if(!pick_camera.test_obb(bbox, world_transform))
599 {
600 return;
601 }
602
603 auto id = ENTT_ID_TYPE(e);
604 math::color color(id);
605
607 dd.encoder.setState(true, true, false, true, false);
608
609 dd.encoder.pushTransform((const float*)world_transform);
610 bx::Aabb aabb;
611 aabb.min = to_bx(bbox.min);
612 aabb.max = to_bx(bbox.max);
613 dd.encoder.draw(aabb);
615 });
616
617 target_scene->registry->view<transform_component, ui_document_component, active_component>().each(
618 [&](auto e, auto&& transform_comp, auto&& ui_document_comp, auto&& active)
619 {
620 const auto& world_transform = transform_comp.get_transform_global();
621 auto size = ui_document_comp.get_world_space_scale();
622 math::bbox bbox;
623 bbox.min = math::vec3(-size.x * 0.5f, -size.y * 0.5f, 0.0f);
624 bbox.max = math::vec3(size.x * 0.5f, size.y * 0.5f, 0.0f);
625
626 if(!pick_camera.test_obb(bbox, world_transform))
627 {
628 return;
629 }
630
631 auto id = ENTT_ID_TYPE(e);
632 math::color color(id);
633
635 dd.encoder.setState(true, true, false, true, false);
636
637 dd.encoder.pushTransform((const float*)world_transform);
638 bx::Aabb aabb;
639 aabb.min = to_bx(bbox.min);
640 aabb.max = to_bx(bbox.max);
641 dd.encoder.draw(aabb);
643 });
644
645 if(em.show_icon_gizmos)
646 {
647 program_gizmos_->begin();
648 dd.encoder.pushProgram(program_gizmos_->native_handle());
649
650 auto& scn = *target_scene;
651 hpp::for_each_type<camera_component,
656 [&](auto tag)
657 {
658 using type_t = typename std::decay_t<decltype(tag)>::type;
659
660 scn.registry->view<type_t>().each(
661 [&](auto e, auto&& comp)
662 {
663
664 auto entity = scn.create_handle(e);
665
666 auto& tm = ctx.get_cached<thumbnail_manager>();
667
668 anything_picked = true;
669
670 auto id = ENTT_ID_TYPE(e);
671 math::color color(id);
672
674 dd.encoder.setState(true, true, false, true);
675 auto& transform_comp = entity.template get<transform_component>();
676 const auto& world_transform = transform_comp.get_transform_global();
677
678 if constexpr(std::is_same<type_t, particle_emitter_component>())
679 {
680 if(!em.billboard_data.show_particle_emitter)
681 {
682 return;
683 }
684 }
685
686 if constexpr(std::is_same<type_t, audio_source_component>())
687 {
688 if(!em.billboard_data.show_audio_source)
689 {
690 return;
691 }
692 }
693
694 if constexpr(std::is_same<type_t, reflection_probe_component>())
695 {
696 if(!em.billboard_data.show_reflection_probe)
697 {
698 return;
699 }
700 }
701
702
703 if constexpr(std::is_same<type_t, light_component>())
704 {
705 if(!em.billboard_data.show_light)
706 {
707 return;
708 }
709 }
710
711 if constexpr(std::is_same<type_t, camera_component>())
712 {
713 if(!em.billboard_data.show_camera)
714 {
715 return;
716 }
717 }
718
719
720
721 auto icon = tm.get_gizmo_icon(entity);
722 if(icon)
723 {
724 if(!pick_camera.test_billboard(em.billboard_data.size, world_transform))
725 return; // completely outside → skip draw
726
728 icon->native_handle(),
729 to_bx(world_transform.get_position()),
730 to_bx(pick_camera.get_position()),
731 to_bx(pick_camera.z_unit_axis()),
732 em.billboard_data.size);
733 }
734 });
735 });
736
737 dd.encoder.popProgram();
738 program_gizmos_->end();
739 }
740 }
741
742 pick_camera_.reset();
743 original_camera_.reset();
744 start_readback_ = anything_picked;
745
746 if(!anything_picked && !pick_callback_)
747 {
748 em.unselect();
749 }
750 }
751
752 // If the user previously clicked, and we're done reading data from GPU, look at ID buffer on CPU
753 // Whatever mesh has the most pixels in the ID buffer is the one the user clicked on.
754 if((reading_ == 0u) && start_readback_)
755 {
756 bool blit_support = gfx::is_supported(BGFX_CAPS_TEXTURE_BLIT);
757
758 if(blit_support == false)
759 {
760 APPLOG_WARNING("Texture blitting is not supported. Picking will not work");
761 start_readback_ = false;
762 return;
763 }
764
765 gfx::render_pass pass("Picking/Buffer Blit Pass");
766 pass.touch();
767 // Blit and read
768 gfx::blit(pass.id, blit_tex_->native_handle(), 0, 0, surface_->get_texture()->native_handle());
769 reading_ = gfx::read_texture(blit_tex_->native_handle(), blit_data_.data());
770 start_readback_ = false;
771 }
772
773 if(reading_ && reading_ <= render_frame)
774 {
775 reading_ = 0;
776 std::map<std::uint32_t, std::uint32_t> ids; // This contains all the IDs found in the buffer
777 std::uint32_t max_amount = 0;
778 for(std::uint8_t* x = &blit_data_.front(); x < &blit_data_.back();)
779 {
780 std::uint8_t rr = *x++;
781 std::uint8_t gg = *x++;
782 std::uint8_t bb = *x++;
783 std::uint8_t aa = *x++;
784
785 // Skip background
786 // if(0 == (rr | gg | bb | aa))
787 // {
788 // continue;
789 // }
790
791 auto hash_key = static_cast<std::uint32_t>(rr + (gg << 8) + (bb << 16) + (aa << 24));
792 std::uint32_t amount = 1;
793 auto map_iter = ids.find(hash_key);
794 if(map_iter != ids.end())
795 {
796 amount = map_iter->second + 1;
797 }
798
799 // Amount of times this ID (color) has been clicked on in buffer
800 ids[hash_key] = amount;
801 max_amount = max_amount > amount ? max_amount : amount;
802 }
803
804 ENTT_ID_TYPE id_key = 0;
805 if(max_amount != 0u)
806 {
807 for(auto& pair : ids)
808 {
809 if(pair.second == max_amount)
810 {
811 id_key = pair.first;
812 process_pick_result(ctx, target_scene, id_key);
813 break;
814 }
815 }
816 }
817 else
818 {
819 // If nothing was picked, still call the process_pick_result with id_key = 0
820 // This will create an invalid handle that will be passed to the callback
821 if(pick_callback_)
822 {
823 process_pick_result(ctx, target_scene, id_key);
824 }
825 else
826 {
827 em.unselect();
828 }
829 }
830
831 // Clear the callback after processing
832 pick_callback_ = {};
833 }
834}
835
836void picking_manager::process_pick_result(rtti::context& ctx, scene* target_scene, ENTT_ID_TYPE id_key)
837{
838 // Create entity handle (may be invalid if id_key is 0)
839 auto entity = entt::entity(id_key);
840 entt::handle picked_entity;
841 auto& em = ctx.get_cached<editing_manager>();
842
843 // Only try to create a handle if the entity ID is valid
844 if(id_key != 0)
845 {
846 picked_entity = target_scene->create_handle(entity);
847 if(picked_entity)
848 {
849 auto logical_pick = get_logical_top_level_entity(*target_scene->registry, picked_entity);
850
851 const bool is_area_picking = pick_area_.x > 0.0f && pick_area_.y > 0.0f;
852 if(is_area_picking || !em.is_selected(logical_pick))
853 {
854 picked_entity = logical_pick;
855 }
856 }
857 }
858
859 if(pick_callback_)
860 {
861 // Call the custom callback with either a valid entity or an invalid handle
862 // Do this because the callback can reassign the pick_callback_ variable
863 auto callback = pick_callback_;
864 callback(picked_entity, pick_position_);
865 }
866 else
867 {
868 // Use the traditional selection mechanism
869 if(picked_entity)
870 {
871 if(!should_skip_selection_for_additive_pick(em, pick_mode_, pick_area_, picked_entity))
872 {
873 em.select(picked_entity, pick_mode_);
874 }
875 }
876 else
877 {
878 em.unselect();
879 }
880 }
881}
882
883picking_manager::picking_manager()
884{
885}
886
887picking_manager::~picking_manager()
888{
889}
890
891auto picking_manager::init(rtti::context& ctx) -> bool
892{
893 auto& ev = ctx.get_cached<events>();
894 ev.on_frame_render.connect(sentinel_, 850, this, &picking_manager::on_frame_render);
895
896 auto& am = ctx.get_cached<asset_manager>();
897
898 // Set up ID buffer, which has a color target and depth buffer
899 auto picking_rt =
900 std::make_shared<gfx::texture>(tex_id_dim,
901 tex_id_dim,
902 false,
903 1,
904 gfx::texture_format::RGBA8,
905 0 | BGFX_TEXTURE_RT | BGFX_SAMPLER_MIN_POINT | BGFX_SAMPLER_MAG_POINT |
906 BGFX_SAMPLER_MIP_POINT | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP);
907
908 auto picking_rt_depth =
909 std::make_shared<gfx::texture>(tex_id_dim,
910 tex_id_dim,
911 false,
912 1,
913 gfx::texture_format::D24S8,
914 0 | BGFX_TEXTURE_RT | BGFX_SAMPLER_MIN_POINT | BGFX_SAMPLER_MAG_POINT |
915 BGFX_SAMPLER_MIP_POINT | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP);
916
917 std::vector<std::shared_ptr<gfx::texture>> textures{picking_rt, picking_rt_depth};
918 surface_ = std::make_shared<gfx::frame_buffer>(textures);
919
920 // CPU texture for blitting to and reading ID buffer so we can see what was clicked on.
921 // Impossible to read directly from a render target, you *must* blit to a CPU texture
922 // first. Algorithm Overview: Render on GPU -> Blit to CPU texture -> Read from CPU
923 // texture.
924 blit_tex_ = std::make_shared<gfx::texture>(
925 tex_id_dim,
926 tex_id_dim,
927 false,
928 1,
929 gfx::texture_format::RGBA8,
930 0 | BGFX_TEXTURE_BLIT_DST | BGFX_TEXTURE_READ_BACK | BGFX_SAMPLER_MIN_POINT | BGFX_SAMPLER_MAG_POINT |
931 BGFX_SAMPLER_MIP_POINT | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP);
932
933 auto vs = am.get_asset<gfx::shader>("editor:/data/shaders/vs_picking_id.sc");
934 auto vs_skinned = am.get_asset<gfx::shader>("editor:/data/shaders/vs_picking_id_skinned.sc");
935 auto fs = am.get_asset<gfx::shader>("editor:/data/shaders/fs_picking_id.sc");
936
937 program_ = std::make_unique<gpu_program>(vs, fs);
938 program_skinned_ = std::make_unique<gpu_program>(vs_skinned, fs);
939
940 auto vs_gizmos = am.get_asset<gfx::shader>("editor:/data/shaders/vs_picking_debugdraw_fill_texture.sc");
941 auto fs_gizmos = am.get_asset<gfx::shader>("editor:/data/shaders/fs_picking_debugdraw_fill_texture.sc");
942 program_gizmos_ = std::make_unique<gpu_program>(vs_gizmos, fs_gizmos);
943
944 return true;
945}
946
947auto picking_manager::deinit(rtti::context& ctx) -> bool
948{
949 return true;
950}
951
952void picking_manager::setup_pick_camera(const camera& cam, math::vec2 pos, math::vec2 area)
953{
954 camera pick_camera;
955 if(area.x > 0.0f && area.y > 0.0f)
956 {
957 // Area picking: copy the passed camera and adjust for the selection area
958 pick_camera = cam; // Copy the passed camera
959 }
960 else
961 {
962 // Single point picking (existing logic)
963 const auto near_clip = cam.get_near_clip();
964 const auto far_clip = cam.get_far_clip();
965 const auto& frustum = cam.get_frustum();
966
967 math::vec3 pick_eye;
968 math::vec3 pick_at;
969 math::vec3 pick_up = cam.y_unit_axis();
970
971 if(!cam.viewport_to_world(pos, frustum.planes[math::volume_plane::near_plane], pick_eye, true))
972 return;
973
974 if(!cam.viewport_to_world(pos, frustum.planes[math::volume_plane::far_plane], pick_at, true))
975 return;
976
977 pick_camera.set_aspect_ratio(1.0f);
978 pick_camera.set_fov(1.0f);
979 pick_camera.set_near_clip(near_clip);
980 pick_camera.set_far_clip(far_clip);
981 pick_camera.look_at(pick_eye, pick_at, pick_up);
982 }
983 original_camera_ = cam;
984 pick_camera_ = pick_camera;
985 pick_position_ = pos;
986 pick_area_ = area;
987 reading_ = 0;
988 start_readback_ = true;
989}
990
991void picking_manager::cancel_pick()
992{
993 pick_camera_.reset();
994 original_camera_.reset();
995 pick_position_ = {};
996 pick_area_ = {};
997 reading_ = 0;
998 start_readback_ = false;
999 picked_entities_.clear();
1000}
1001
1002void picking_manager::request_pick(const camera& cam,
1004 math::vec2 pos,
1005 math::vec2 area)
1006{
1007 bool was_area_picking = pick_area_.x > 0.0f && pick_area_.y > 0.0f;
1008 bool is_area_picking = area.x > 0.0f && area.y > 0.0f;
1009
1010 setup_pick_camera(cam, pos, area);
1011 pick_mode_ = mode;
1012
1013 if(is_area_picking)
1014 {
1015 if(!was_area_picking)
1016 {
1017 if(mode == editing_manager::select_mode::normal)
1018 {
1019 picked_entities_.clear();
1020 }
1021 else
1022 {
1023 auto& ctx = engine::context();
1024 auto& em = ctx.get_cached<editing_manager>();
1025 picked_entities_ = em.try_get_selections_as_copy<entt::handle>();
1026 }
1027 }
1028
1029
1030 pick_mode_ = editing_manager::select_mode::shift;
1031 }
1032
1033 pick_callback_ = {}; // Clear any existing callback
1034}
1035
1036void picking_manager::query_pick(math::vec2 pos, const camera& cam, pick_callback callback, bool force)
1037{
1038 // If already picking, ignore this request
1039 if(!force && is_picking())
1040 {
1041 return;
1042 }
1043
1044 // Set up the pick operation
1045 setup_pick_camera(cam, pos);
1046 pick_callback_ = callback;
1047}
1048
1049auto picking_manager::is_picking() const -> bool
1050{
1051 return pick_camera_.has_value() || reading_ != 0;
1052}
1053
1054auto picking_manager::get_pick_texture() const -> const std::shared_ptr<gfx::texture>&
1055{
1056 return blit_tex_;
1057}
1058
1059} // namespace unravel
General purpose transformation class designed to maintain each component of the transformation separa...
Definition transform.hpp:27
auto get_position() const noexcept -> const vec3_t &
Get the position component.
auto transform_coord(const vec2_t &v) const noexcept -> vec2_t
Transform a 2D coordinate.
Manages assets, including loading, unloading, and storage.
Class that contains core data for audio sources.
Class that contains core camera data, used for rendering and other purposes.
Class representing a camera. Contains functionality for manipulating and updating a camera....
Definition camera.h:62
auto get_far_clip() const -> float
Retrieves the distance from the camera to the far clip plane.
Definition camera.cpp:69
auto viewport_to_world(const math::vec2 &point, const math::plane &plane, math::vec3 &position_out, bool clip) const -> bool
Converts a screen position into a world space position on the specified plane.
Definition camera.cpp:530
void set_fov(float degrees)
Sets the field of view angle of this camera (perspective only).
Definition camera.cpp:79
void look_at(const math::vec3 &eye, const math::vec3 &at)
Sets the camera to look at a specified target.
Definition camera.cpp:333
void set_far_clip(float distance)
Sets the far plane distance.
Definition camera.cpp:127
auto y_unit_axis() const -> math::vec3
Retrieves the y-axis unit vector of the camera's local coordinate system.
Definition camera.cpp:362
void set_aspect_ratio(float aspect, bool locked=false)
Sets the aspect ratio to be used for generating the horizontal FOV angle (perspective only).
Definition camera.cpp:167
void set_near_clip(float distance)
Sets the near plane distance.
Definition camera.cpp:107
auto get_frustum() const -> const math::frustum &
Retrieves the current camera object frustum.
Definition camera.cpp:372
auto get_near_clip() const -> float
Retrieves the distance from the camera to the near clip plane.
Definition camera.cpp:64
Class that contains core light data, used for rendering and other purposes.
Base class for materials used in rendering.
Definition material.h:44
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 is_valid() const -> bool
Checks if the model is valid.
Definition model.cpp:174
void submit(const math::mat4 &world_transform, const submesh_pose_mat4 &submesh_transforms, const pose_mat4 &bone_transforms, const std::vector< pose_mat4 > &skinning_transforms, unsigned int lod, const submit_callbacks &callbacks, const math::frustum *frustum=nullptr, const camera *view=nullptr, const model_submit_extras &extras={}) const
Submits the model for rendering.
Definition model.cpp:720
Component that wraps the soa particle system emitter.
std::function< void(entt::handle entity, const math::vec2 &screen_pos)> pick_callback
Class that contains core reflection probe data, used for rendering and other purposes.
Component that handles transformations (position, rotation, scale, etc.) in the ACE framework.
float x
std::chrono::duration< float > delta_t
const char * icon
const char * id
uint16_t view
std::string tag
Definition hub.cpp:32
#define APPLOG_WARNING(...)
Definition logging.h:19
texture_job_type type
Definition cache.hpp:11
uint32_t read_texture(texture_handle _handle, void *_data, uint16_t _layer, uint8_t _mip)
Definition graphics.cpp:759
void submit(view_id _id, program_handle _handle, int32_t _depth, bool _preserveState)
void set_state(uint64_t _state, uint32_t _rgba)
Definition graphics.cpp:937
auto is_supported(uint64_t flag) -> bool
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
void blit(view_id _id, texture_handle _dst, uint16_t _dstX, uint16_t _dstY, texture_handle _src, uint16_t _srcX, uint16_t _srcY, uint16_t _width, uint16_t _height)
void discard(uint8_t _flags)
bgfx::Transform transform
Definition graphics.h:42
uint32_t get_render_frame()
Hash specialization for batch_key to enable use in std::unordered_map.
auto to_bx(const glm::vec3 &data) -> bx::Vec3
Definition gizmos.cpp:12
std::vector< math::color > color
#define APP_SCOPE_PERF(name_literal)
Create a scoped performance timer that records to the timeline profiler. Only accepts string literals...
Definition profiler.h:675
entt::handle entity
void draw(const bx::Aabb &_aabb)
void setColor(uint32_t _abgr)
void setState(bool _depthTest, bool _depthWrite, bool _clockwise, bool _alphaWrite=false, bool _alphaBlend=true)
void pushTransform(const void *_mtx)
void pushProgram(bgfx::ProgramHandle _handle)
DebugDrawEncoder encoder
Definition debugdraw.h:15
void set_view_proj(const float *v, const float *p)
gfx::view_id id
void clear(uint16_t _flags, uint32_t _rgba=0x000000ff, float _depth=1.0f, uint8_t _stencil=0) const
void touch() const
void bind(const frame_buffer *fb=nullptr) const
Storage for box vector values and wraps up common functionality.
Definition bbox.h:21
vec3 max
The maximum vector value of the bounding box.
Definition bbox.h:311
vec3 min
The minimum vector value of the bounding box.
Definition bbox.h:306
auto get_cached() -> T &
Definition context.hpp:49
auto try_get_selections_as_copy() const -> std::vector< T >
hpp::event< void(rtti::context &, delta_t)> on_frame_render
Definition events.h:19
Parameters for the submit callbacks.
Definition model.h:551
bool skinned
Indicates if the model is skinned.
Definition model.h:553
Callbacks for submitting the model for rendering.
Definition model.h:545
std::function< void(const params &info, const material &)> setup_params_per_submesh
Callback for setting up per submesh.
Definition model.h:562
std::function< void(const params &info)> setup_begin
Callback for setup begin.
Definition model.h:558
std::function< void(const params &info)> setup_params_per_instance
Callback for setting up per instance.
Definition model.h:560
std::function< void(const params &info)> setup_end
Callback for setup end.
Definition model.h:564
Represents a scene in the ACE framework, managing entities and their relationships.
Definition scene.h:70
std::unique_ptr< entt::registry > registry
The registry that manages all entities in the scene.
Definition scene.h:187
auto create_handle(entt::entity e) -> entt::handle
Creates an entity in the scene.
Definition scene.cpp:428
Component that holds a reference to a UI document for RmlUi rendering.
gfx::uniform_handle handle
Definition uniform.cpp:9
bool seen