23#include <unordered_set>
29auto to_bx(
const math::vec3& data) -> bx::Vec3
31 return {data.x, data.y, data.z};
34auto from_bx(
const bx::Vec3& data) -> math::vec3
36 return {data.x, data.y, data.z};
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)
51 math::vec3 screen_pos = pick_camera.world_to_viewport(world_position);
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);
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)
66 bool all_corners_in_selection =
true;
67 for(
int i = 0;
i < 8; ++
i)
70 math::vec3 screen_pos = pick_camera.world_to_viewport(corners[i]);
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);
78 all_corners_in_selection &= corner_in_selection;
81 return all_corners_in_selection;
85bool are_local_bounds_in_selection_area(
const math::bbox& local_bounds,
87 const camera& pick_camera,
88 const math::vec2& pick_position,
89 const math::vec2& pick_area)
92 math::vec3 corners[8] = {
94 world_transform.
transform_coord({local_bounds.max.x, local_bounds.min.y, local_bounds.min.z}),
96 world_transform.
transform_coord({local_bounds.max.x, local_bounds.max.y, local_bounds.min.z}),
98 world_transform.
transform_coord({local_bounds.max.x, local_bounds.min.y, local_bounds.max.z}),
100 world_transform.
transform_coord({local_bounds.max.x, local_bounds.max.y, local_bounds.max.z})};
102 return are_corners_in_selection_area(corners, pick_camera, pick_position, pick_area);
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)
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}};
121 return are_corners_in_selection_area(corners, pick_camera, pick_position, pick_area);
133auto find_model_owner(entt::registry& registry, entt::handle armature_entity) -> entt::handle
139 auto view = registry.view<model_component>();
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)
147 if(arm_ent == armature_entity)
163auto are_in_same_model(entt::registry& registry, entt::handle entity1, entt::handle entity2) ->
bool
165 if(!entity1 || !entity2)
169 auto model_owner1 = find_model_owner(registry, entity1);
170 auto model_owner2 = find_model_owner(registry, entity2);
171 if(!model_owner1 || !model_owner2)
175 return model_owner1 == model_owner2;
189auto get_logical_top_level_entity(entt::registry& registry, entt::handle
entity) -> entt::handle
195 entt::handle starting_model_owner = {};
196 bool is_starting_in_armature =
false;
197 auto starting_model = find_model_owner(registry,
entity);
200 starting_model_owner = starting_model;
201 is_starting_in_armature =
true;
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;
216 if(current.try_get<prefab_component>())
218 if(!prefab_marker || prefab_marker->depth > depth)
220 prefab_marker = {current, depth};
223 if(current.try_get<animation_component>())
225 if(!animation_marker || animation_marker->depth > depth)
227 animation_marker = {current, depth};
230 auto*
transform = current.try_get<transform_component>();
238 if(is_starting_in_armature)
240 auto current_model = find_model_owner(registry, current);
241 if(current_model == starting_model_owner)
243 if(!model_root_marker || model_root_marker->depth > depth)
245 model_root_marker = {current, depth};
251 if(!model_root_marker || model_root_marker->depth > depth)
253 model_root_marker = {current, depth};
258 if(is_starting_in_armature)
260 if(are_in_same_model(registry, current, parent))
262 last_in_same_model = current;
266 if(!model_root_marker || model_root_marker->depth > depth)
268 model_root_marker = {last_in_same_model, depth};
270 is_starting_in_armature =
false;
278 return prefab_marker->entity;
282 return animation_marker->entity;
284 if(model_root_marker)
286 return model_root_marker->entity;
297auto is_ancestor_of(entt::handle potential_ancestor, entt::handle child) ->
bool
299 if(!child || !potential_ancestor)
303 auto*
transform = child.try_get<transform_component>();
308 entt::handle current =
transform->get_parent();
311 if(current == potential_ancestor)
315 auto* current_transform = current.try_get<transform_component>();
316 if(!current_transform)
320 current = current_transform->get_parent();
332auto should_skip_selection_for_additive_pick(
const editing_manager& em,
334 const math::vec2& pick_area,
335 entt::handle picked_entity) ->
bool
337 bool is_area_picking = pick_area.x > 0.0f && pick_area.y > 0.0f;
352 auto selections = em.get_selections();
353 for(
const auto& selected_obj : selections)
355 if(selected_obj.type() == entt::resolve<entt::handle>())
357 auto selected_entity = selected_obj.cast<
const entt::handle&>();
358 if(is_ancestor_of(selected_entity, picked_entity))
371auto filter_area_selection_roots(std::vector<entt::handle>& candidates) ->
void
374 std::remove_if(candidates.begin(),
376 [&](
const entt::handle&
entity)
378 for(const auto& other : candidates)
380 if(other != entity && is_ancestor_of(other, entity))
390auto resolve_area_selection_candidates(entt::registry& registry, std::vector<entt::handle> candidates)
391 -> std::vector<entt::handle>
393 std::vector<entt::handle> resolved;
394 resolved.reserve(candidates.size());
396 std::unordered_set<entt::entity>
seen;
397 for(
const auto& candidate : candidates)
404 auto logical = get_logical_top_level_entity(registry, candidate);
410 const auto entity_id = logical.entity();
411 if(
seen.insert(entity_id).second)
413 resolved.push_back(logical);
417 filter_area_selection_roots(resolved);
423constexpr int picking_manager::tex_id_dim;
426 on_frame_pick(ctx, dt);
435 scene* target_scene = em.get_active_scene(ctx);
442 if(pick_area_.x > 0.0f && pick_area_.y > 0.0f && pick_camera_)
444 const auto& pick_camera = *pick_camera_;
446 std::vector<entt::handle> in_area_candidates;
447 in_area_candidates.reserve(64);
450 [&](
auto e,
auto&& transform_comp,
auto&&)
452 const auto& world_transform = transform_comp.get_transform_global();
453 const auto& world_position = world_transform.
get_position();
455 if(!pick_camera.get_frustum().test_point(world_position))
460 if(!is_position_in_selection_area(world_position, pick_camera, pick_position_, pick_area_))
465 in_area_candidates.push_back(target_scene->create_handle(e));
468 const auto area_selection =
469 resolve_area_selection_candidates(*target_scene->registry, std::move(in_area_candidates));
472 [&](
auto e,
auto&&,
auto&&)
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();
478 if(in_area_selection)
480 process_pick_result(ctx, target_scene, ENTT_ID_TYPE(e));
484 if(std::find(picked_entities_.begin(), picked_entities_.end(),
handle) == picked_entities_.end())
490 pick_camera_.reset();
491 original_camera_.reset();
498 picked_entities_.clear();
499 if(pick_camera_ && original_camera_)
501 const auto& pick_camera = *pick_camera_;
502 const auto& original_camera = *original_camera_;
504 const auto& pick_view = pick_camera.get_view();
505 const auto& pick_proj = pick_camera.get_projection();
509 pass.
clear(BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0x000000ff, 1.0f, 0);
511 pass.
bind(surface_.get());
513 bool anything_picked =
false;
519 [&](
auto e,
auto&& transform_comp,
auto&& model_comp,
auto&& active)
521 auto&
model = model_comp.get_model();
527 const auto& world_transform = transform_comp.get_transform_global();
529 auto& current_lod_data = model_comp.get_lod_data_for_camera(&original_camera,
gfx::get_render_frame());
533 if(!pick_camera.get_frustum().test_aabb(model_comp.get_world_bounds()))
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;
544 math::vec4 color_id = {rr / 255.0f, gg / 255.0f, bb / 255.0f, aa / 255.0f};
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();
554 auto& prog = submit_params.skinned ? program_skinned_ : program_;
560 auto& prog = submit_params.skinned ? program_skinned_ : program_;
562 prog->set_uniform(
"u_id", math::value_ptr(color_id));
567 auto& prog = submit_params.
skinned ? program_skinned_ : program_;
574 auto& prog = submit_params.
skinned ? program_skinned_ : program_;
579 model.
submit(world_transform, submesh_transforms, bone_transforms, skinning_transforms, current_lod_data.current_lod_index, callbacks);
589 [&](
auto e,
auto&& transform_comp,
auto&& text_comp,
auto&& active)
591 if(!text_comp.can_be_rendered())
595 const auto& world_transform = transform_comp.get_transform_global();
596 auto bbox = text_comp.get_bounds();
598 if(!pick_camera.test_obb(bbox, world_transform))
603 auto id = ENTT_ID_TYPE(e);
611 aabb.min =
to_bx(bbox.min);
612 aabb.max =
to_bx(bbox.max);
618 [&](
auto e,
auto&& transform_comp,
auto&& ui_document_comp,
auto&& active)
620 const auto& world_transform = transform_comp.get_transform_global();
621 auto size = ui_document_comp.get_world_space_scale();
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);
626 if(!pick_camera.test_obb(bbox, world_transform))
631 auto id = ENTT_ID_TYPE(e);
645 if(em.show_icon_gizmos)
647 program_gizmos_->begin();
650 auto& scn = *target_scene;
658 using type_t =
typename std::decay_t<
decltype(
tag)>
::type;
660 scn.registry->view<type_t>().each(
661 [&](
auto e,
auto&& comp)
664 auto entity = scn.create_handle(e);
668 anything_picked =
true;
670 auto id = ENTT_ID_TYPE(e);
675 auto& transform_comp =
entity.template get<transform_component>();
676 const auto& world_transform = transform_comp.get_transform_global();
678 if constexpr(std::is_same<type_t, particle_emitter_component>())
680 if(!em.billboard_data.show_particle_emitter)
686 if constexpr(std::is_same<type_t, audio_source_component>())
688 if(!em.billboard_data.show_audio_source)
694 if constexpr(std::is_same<type_t, reflection_probe_component>())
696 if(!em.billboard_data.show_reflection_probe)
703 if constexpr(std::is_same<type_t, light_component>())
705 if(!em.billboard_data.show_light)
711 if constexpr(std::is_same<type_t, camera_component>())
713 if(!em.billboard_data.show_camera)
724 if(!pick_camera.test_billboard(em.billboard_data.size, world_transform))
728 icon->native_handle(),
730 to_bx(pick_camera.get_position()),
731 to_bx(pick_camera.z_unit_axis()),
732 em.billboard_data.size);
738 program_gizmos_->end();
742 pick_camera_.reset();
743 original_camera_.reset();
744 start_readback_ = anything_picked;
746 if(!anything_picked && !pick_callback_)
754 if((reading_ == 0
u) && start_readback_)
758 if(blit_support ==
false)
760 APPLOG_WARNING(
"Texture blitting is not supported. Picking will not work");
761 start_readback_ =
false;
768 gfx::blit(pass.
id, blit_tex_->native_handle(), 0, 0, surface_->get_texture()->native_handle());
770 start_readback_ =
false;
773 if(reading_ && reading_ <= render_frame)
776 std::map<std::uint32_t, std::uint32_t> ids;
777 std::uint32_t max_amount = 0;
778 for(std::uint8_t*
x = &blit_data_.front();
x < &blit_data_.back();)
780 std::uint8_t rr = *
x++;
781 std::uint8_t gg = *
x++;
782 std::uint8_t bb = *
x++;
783 std::uint8_t aa = *
x++;
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())
796 amount = map_iter->second + 1;
800 ids[hash_key] = amount;
801 max_amount = max_amount > amount ? max_amount : amount;
804 ENTT_ID_TYPE id_key = 0;
807 for(
auto& pair : ids)
809 if(pair.second == max_amount)
812 process_pick_result(ctx, target_scene, id_key);
823 process_pick_result(ctx, target_scene, id_key);
836void picking_manager::process_pick_result(
rtti::context& ctx,
scene* target_scene, ENTT_ID_TYPE id_key)
839 auto entity = entt::entity(id_key);
840 entt::handle picked_entity;
849 auto logical_pick = get_logical_top_level_entity(*target_scene->
registry, picked_entity);
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))
854 picked_entity = logical_pick;
863 auto callback = pick_callback_;
864 callback(picked_entity, pick_position_);
871 if(!should_skip_selection_for_additive_pick(em, pick_mode_, pick_area_, picked_entity))
873 em.select(picked_entity, pick_mode_);
883picking_manager::picking_manager()
887picking_manager::~picking_manager()
894 ev.
on_frame_render.connect(sentinel_, 850,
this, &picking_manager::on_frame_render);
900 std::make_shared<gfx::texture>(tex_id_dim,
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);
908 auto picking_rt_depth =
909 std::make_shared<gfx::texture>(tex_id_dim,
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);
917 std::vector<std::shared_ptr<gfx::texture>> textures{picking_rt, picking_rt_depth};
918 surface_ = std::make_shared<gfx::frame_buffer>(textures);
924 blit_tex_ = std::make_shared<gfx::texture>(
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);
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");
937 program_ = std::make_unique<gpu_program>(vs,
fs);
938 program_skinned_ = std::make_unique<gpu_program>(vs_skinned,
fs);
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);
952void picking_manager::setup_pick_camera(
const camera& cam, math::vec2 pos, math::vec2 area)
955 if(area.x > 0.0f && area.y > 0.0f)
981 pick_camera.
look_at(pick_eye, pick_at, pick_up);
983 original_camera_ = cam;
984 pick_camera_ = pick_camera;
985 pick_position_ = pos;
988 start_readback_ =
true;
991void picking_manager::cancel_pick()
993 pick_camera_.reset();
994 original_camera_.reset();
998 start_readback_ =
false;
999 picked_entities_.clear();
1002void picking_manager::request_pick(
const camera& cam,
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;
1010 setup_pick_camera(cam, pos, area);
1015 if(!was_area_picking)
1017 if(mode == editing_manager::select_mode::normal)
1019 picked_entities_.clear();
1023 auto& ctx = engine::context();
1030 pick_mode_ = editing_manager::select_mode::shift;
1033 pick_callback_ = {};
1039 if(!
force && is_picking())
1045 setup_pick_camera(cam, pos);
1046 pick_callback_ = callback;
1049auto picking_manager::is_picking() const ->
bool
1051 return pick_camera_.has_value() || reading_ != 0;
1054auto picking_manager::get_pick_texture() const -> const
std::shared_ptr<
gfx::texture>&
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....
auto get_far_clip() const -> float
Retrieves the distance from the camera to the far clip plane.
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.
void set_fov(float degrees)
Sets the field of view angle of this camera (perspective only).
void look_at(const math::vec3 &eye, const math::vec3 &at)
Sets the camera to look at a specified target.
void set_far_clip(float distance)
Sets the far plane distance.
auto y_unit_axis() const -> math::vec3
Retrieves the y-axis unit vector of the camera's local coordinate system.
void set_aspect_ratio(float aspect, bool locked=false)
Sets the aspect ratio to be used for generating the horizontal FOV angle (perspective only).
void set_near_clip(float distance)
Sets the near plane distance.
auto get_frustum() const -> const math::frustum &
Retrieves the current camera object frustum.
auto get_near_clip() const -> float
Retrieves the distance from the camera to the near clip plane.
Class that contains core light data, used for rendering and other purposes.
Base class for materials used in rendering.
Class that contains core data for meshes.
Structure describing a LOD group (set of meshes), LOD transitions, and their materials.
auto is_valid() const -> bool
Checks if the model is valid.
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.
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.
std::chrono::duration< float > delta_t
#define APPLOG_WARNING(...)
uint32_t read_texture(texture_handle _handle, void *_data, uint16_t _layer, uint8_t _mip)
void submit(view_id _id, program_handle _handle, int32_t _depth, bool _preserveState)
void set_state(uint64_t _state, uint32_t _rgba)
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)
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
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
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...
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)
void set_view_proj(const float *v, const float *p)
void clear(uint16_t _flags, uint32_t _rgba=0x000000ff, float _depth=1.0f, uint8_t _stencil=0) const
void bind(const frame_buffer *fb=nullptr) const
Storage for box vector values and wraps up common functionality.
vec3 max
The maximum vector value of the bounding box.
vec3 min
The minimum vector value of the bounding box.
auto try_get_selections_as_copy() const -> std::vector< T >
hpp::event< void(rtti::context &, delta_t)> on_frame_render
Parameters for the submit callbacks.
bool skinned
Indicates if the model is skinned.
Callbacks for submitting the model for rendering.
std::function< void(const params &info, const material &)> setup_params_per_submesh
Callback for setting up per submesh.
std::function< void(const params &info)> setup_begin
Callback for setup begin.
std::function< void(const params &info)> setup_params_per_instance
Callback for setting up per instance.
std::function< void(const params &info)> setup_end
Callback for setup end.
Represents a scene in the ACE framework, managing entities and their relationships.
std::unique_ptr< entt::registry > registry
The registry that manages all entities in the scene.
auto create_handle(entt::entity e) -> entt::handle
Creates an entity in the scene.
Component that holds a reference to a UI document for RmlUi rendering.