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>
8
11#include <engine/events.h>
17namespace unravel
18{
19
20namespace
21{
22auto to_bx(const math::vec3& data) -> bx::Vec3
23{
24 return {data.x, data.y, data.z};
25}
26
27auto from_bx(const bx::Vec3& data) -> math::vec3
28{
29 return {data.x, data.y, data.z};
30}
31
32} // namespace
33
34constexpr int picking_manager::tex_id_dim;
39
41{
42 auto& em = ctx.get_cached<editing_manager>();
43
44 // Get the appropriate scene based on edit mode
45 scene* target_scene = em.get_active_scene(ctx);
46
47 if (!target_scene)
48 {
49 return;
50 }
51
52 if(pick_area_.x > 0.0f && pick_area_.y > 0.0f && pick_camera_)
53 {
54 const auto& pick_camera = *pick_camera_;
55
56 target_scene->registry->view<transform_component, model_component, active_component>().each(
57 [&](auto e, auto&& transform_comp, auto&& model_comp, auto&& active)
58 {
59 auto& model = model_comp.get_model();
60 if(!model.is_valid())
61 {
62 return;
63 }
64
65 const auto& world_transform = transform_comp.get_transform_global();
66
67 auto lod = model.get_lod(0);
68 if(!lod)
69 {
70 return;
71 }
72
73 const auto& mesh = lod.get();
74 const auto& bounds = mesh->get_bounds();
75
76 // Test the bounding box of the mesh
77 if(!pick_camera.test_obb(bounds, world_transform))
78 {
79 auto ue = target_scene->create_handle(e);
80 em.unselect(ue);
81 return;
82 }
83
84 // After OBB test, check if the screen bounds are in the rect area
85 // Project the bounding box corners to screen space and check if any are in the selection area
86 const auto& bbox = bounds;
87 math::vec3 corners[8] = {
88 {bbox.min.x, bbox.min.y, bbox.min.z},
89 {bbox.max.x, bbox.min.y, bbox.min.z},
90 {bbox.min.x, bbox.max.y, bbox.min.z},
91 {bbox.max.x, bbox.max.y, bbox.min.z},
92 {bbox.min.x, bbox.min.y, bbox.max.z},
93 {bbox.max.x, bbox.min.y, bbox.max.z},
94 {bbox.min.x, bbox.max.y, bbox.max.z},
95 {bbox.max.x, bbox.max.y, bbox.max.z}
96 };
97
98 bool in_selection_area = true;
99 for(int i = 0; i < 8; ++i)
100 {
101 bool corner_in_selection_area = false;
102 // Transform corner to world space
103 math::vec3 world_corner = world_transform.transform_coord(corners[i]);
104
105 // Project to screen space
106 math::vec3 screen_pos = pick_camera.world_to_viewport(world_corner);
107
108 // Check if this corner is within the selection rectangle
109 if(screen_pos.x >= pick_position_.x - pick_area_.x * 0.5f &&
110 screen_pos.x <= pick_position_.x + pick_area_.x * 0.5f &&
111 screen_pos.y >= pick_position_.y - pick_area_.y * 0.5f &&
112 screen_pos.y <= pick_position_.y + pick_area_.y * 0.5f)
113 {
114 corner_in_selection_area = true;
115 }
116
117 in_selection_area &= corner_in_selection_area;
118 }
119
120
121 if(!in_selection_area)
122 {
123 auto ue = target_scene->create_handle(e);
124 em.unselect(ue);
125 return;
126 }
127
128 auto id = ENTT_ID_TYPE(e);
129
130 process_pick_result(ctx, target_scene, id);
131 });
132
133 pick_camera_.reset();
134 pick_position_ = {};
135 pick_area_ = {};
136
137 return;
138 }
139
140 const auto render_frame = gfx::get_render_frame();
141
142 if(pick_camera_)
143 {
144 const auto& pick_camera = *pick_camera_;
145
146 const auto& pick_view = pick_camera.get_view();
147 const auto& pick_proj = pick_camera.get_projection();
148
149 gfx::render_pass pass("picking_buffer_pass");
150 // ID buffer clears to black, which represents clicking on nothing (background)
151 pass.clear(BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0x000000ff, 1.0f, 0);
152 pass.set_view_proj(pick_view, pick_proj);
153 pass.bind(surface_.get());
154
155 bool anything_picked = false;
156 target_scene->registry->view<transform_component, model_component, active_component>().each(
157 [&](auto e, auto&& transform_comp, auto&& model_comp, auto&& active)
158 {
159 auto& model = model_comp.get_model();
160 if(!model.is_valid())
161 {
162 return;
163 }
164
165 const auto& world_transform = transform_comp.get_transform_global();
166
167 auto lod = model.get_lod(0);
168 if(!lod)
169 {
170 return;
171 }
172
173 const auto& mesh = lod.get();
174 const auto& bounds = mesh->get_bounds();
175
176 // Test the bounding box of the mesh
177 if(!pick_camera.test_obb(bounds, world_transform))
178 return;
179
180 auto id = ENTT_ID_TYPE(e);
181 std::uint32_t rr = (id) & 0xff;
182 std::uint32_t gg = (id >> 8) & 0xff;
183 std::uint32_t bb = (id >> 16) & 0xff;
184 std::uint32_t aa = (id >> 24) & 0xff;
185
186 math::vec4 color_id = {rr / 255.0f, gg / 255.0f, bb / 255.0f, aa / 255.0f};
187
188 anything_picked = true;
189 const auto& submesh_transforms = model_comp.get_submesh_transforms();
190 const auto& bone_transforms = model_comp.get_bone_transforms();
191 const auto& skinning_transforms = model_comp.get_skinning_transforms();
192
193 model::submit_callbacks callbacks;
194 callbacks.setup_begin = [&](const model::submit_callbacks::params& submit_params)
195 {
196 auto& prog = submit_params.skinned ? program_skinned_ : program_;
197
198 prog->begin();
199 };
200 callbacks.setup_params_per_instance = [&](const model::submit_callbacks::params& submit_params)
201 {
202 auto& prog = submit_params.skinned ? program_skinned_ : program_;
203
204 prog->set_uniform("u_id", math::value_ptr(color_id));
205 };
206 callbacks.setup_params_per_submesh =
207 [&](const model::submit_callbacks::params& submit_params, const material& mat)
208 {
209 auto& prog = submit_params.skinned ? program_skinned_ : program_;
210
211 gfx::set_state(mat.get_render_states());
212 gfx::submit(pass.id, prog->native_handle(), 0, submit_params.preserve_state);
213 };
214 callbacks.setup_end = [&](const model::submit_callbacks::params& submit_params)
215 {
216 auto& prog = submit_params.skinned ? program_skinned_ : program_;
217
218 prog->end();
219 };
220
221 model.submit(world_transform, submesh_transforms, bone_transforms, skinning_transforms, 0, callbacks);
222 });
223
224 gfx::discard();
225
226 if(program_gizmos_)
227 {
228 gfx::dd_raii dd(pass.id);
229
230 target_scene->registry->view<transform_component, text_component, active_component>().each(
231 [&](auto e, auto&& transform_comp, auto&& text_comp, auto&& active)
232 {
233 if(!text_comp.can_be_rendered())
234 {
235 return;
236 }
237 const auto& world_transform = transform_comp.get_transform_global();
238 auto bbox = text_comp.get_bounds();
239
240 if(!pick_camera.test_obb(bbox, world_transform))
241 {
242 return;
243 }
244
245 auto id = ENTT_ID_TYPE(e);
246 math::color color(id);
247
248 dd.encoder.setColor(color);
249 dd.encoder.setState(true, true, false, true, false);
250
251 dd.encoder.pushTransform((const float*)world_transform);
252 bx::Aabb aabb;
253 aabb.min = to_bx(bbox.min);
254 aabb.max = to_bx(bbox.max);
255 dd.encoder.draw(aabb);
257 });
258
259 if(em.show_icon_gizmos)
260 {
261 program_gizmos_->begin();
262 dd.encoder.pushProgram(program_gizmos_->native_handle());
263
264 auto& scn = *target_scene;
265 hpp::for_each_type<camera_component,
269 [&](auto tag)
270 {
271 using type_t = typename std::decay_t<decltype(tag)>::type;
272
273 scn.registry->view<type_t>().each(
274 [&](auto e, auto&& comp)
275 {
276 auto entity = scn.create_handle(e);
277
278 auto& tm = ctx.get_cached<thumbnail_manager>();
279
280 anything_picked = true;
281
282 auto id = ENTT_ID_TYPE(e);
283 math::color color(id);
284
285 dd.encoder.setColor(color);
286 dd.encoder.setState(true, true, false, true);
287 auto& transform_comp = entity.template get<transform_component>();
288 const auto& world_transform = transform_comp.get_transform_global();
289
290 auto icon = tm.get_gizmo_icon(entity);
291 if(icon)
292 {
293 if(!pick_camera.test_billboard(em.billboard_data.size, world_transform))
294 return; // completely outside → skip draw
295
297 icon->native_handle(),
298 to_bx(world_transform.get_position()),
299 to_bx(pick_camera.get_position()),
300 to_bx(pick_camera.z_unit_axis()),
301 em.billboard_data.size);
302 }
303 });
304 });
305
306
307 dd.encoder.popProgram();
308 program_gizmos_->end();
309 }
310
311 }
312
313 pick_camera_.reset();
314 start_readback_ = anything_picked;
315
316 if(!anything_picked && !pick_callback_)
317 {
318 em.unselect();
319 }
320 }
321
322 // If the user previously clicked, and we're done reading data from GPU, look at ID buffer on CPU
323 // Whatever mesh has the most pixels in the ID buffer is the one the user clicked on.
324 if((reading_ == 0u) && start_readback_)
325 {
326 bool blit_support = gfx::is_supported(BGFX_CAPS_TEXTURE_BLIT);
327
328 if(blit_support == false)
329 {
330 APPLOG_WARNING("Texture blitting is not supported. Picking will not work");
331 start_readback_ = false;
332 return;
333 }
334
335 gfx::render_pass pass("picking_buffer_blit_pass");
336 pass.touch();
337 // Blit and read
338 gfx::blit(pass.id, blit_tex_->native_handle(), 0, 0, surface_->get_texture()->native_handle());
339 reading_ = gfx::read_texture(blit_tex_->native_handle(), blit_data_.data());
340 start_readback_ = false;
341 }
342
343 if(reading_ && reading_ <= render_frame)
344 {
345 reading_ = 0;
346 std::map<std::uint32_t, std::uint32_t> ids; // This contains all the IDs found in the buffer
347 std::uint32_t max_amount = 0;
348 for(std::uint8_t* x = &blit_data_.front(); x < &blit_data_.back();)
349 {
350 std::uint8_t rr = *x++;
351 std::uint8_t gg = *x++;
352 std::uint8_t bb = *x++;
353 std::uint8_t aa = *x++;
354
355 // Skip background
356 // if(0 == (rr | gg | bb | aa))
357 // {
358 // continue;
359 // }
360
361 auto hash_key = static_cast<std::uint32_t>(rr + (gg << 8) + (bb << 16) + (aa << 24));
362 std::uint32_t amount = 1;
363 auto map_iter = ids.find(hash_key);
364 if(map_iter != ids.end())
365 {
366 amount = map_iter->second + 1;
367 }
368
369 // Amount of times this ID (color) has been clicked on in buffer
370 ids[hash_key] = amount;
371 max_amount = max_amount > amount ? max_amount : amount;
372 }
373
374 ENTT_ID_TYPE id_key = 0;
375 if(max_amount != 0u)
376 {
377 for(auto& pair : ids)
378 {
379 if(pair.second == max_amount)
380 {
381 id_key = pair.first;
382 process_pick_result(ctx, target_scene, id_key);
383 break;
384 }
385 }
386 }
387 else
388 {
389 // If nothing was picked, still call the process_pick_result with id_key = 0
390 // This will create an invalid handle that will be passed to the callback
391 if(pick_callback_)
392 {
393 process_pick_result(ctx, target_scene, id_key);
394 }
395 else
396 {
397 em.unselect();
398 }
399 }
400
401 // Clear the callback after processing
402 pick_callback_ = {};
403 }
404}
405
406void picking_manager::process_pick_result(rtti::context& ctx, scene* target_scene, ENTT_ID_TYPE id_key)
407{
408 // Create entity handle (may be invalid if id_key is 0)
409 auto entity = entt::entity(id_key);
410 entt::handle picked_entity;
411
412 // Only try to create a handle if the entity ID is valid
413 if (id_key != 0)
414 {
415 picked_entity = target_scene->create_handle(entity);
416 }
417
418 if (pick_callback_)
419 {
420 // Call the custom callback with either a valid entity or an invalid handle
421 // Do this because the callback can reassign the pick_callback_ variable
422 auto callback = pick_callback_;
423 callback(picked_entity, pick_position_);
424 }
425 else
426 {
427 // Use the traditional selection mechanism
428 auto& em = ctx.get_cached<editing_manager>();
429 if (picked_entity)
430 {
431 em.select(picked_entity, pick_mode_);
432 }
433 else
434 {
435 em.unselect();
436 }
437 }
438}
439
443
447
449{
450 auto& ev = ctx.get_cached<events>();
451 ev.on_frame_render.connect(sentinel_, 850, this, &picking_manager::on_frame_render);
452
453 auto& am = ctx.get_cached<asset_manager>();
454
455 // Set up ID buffer, which has a color target and depth buffer
456 auto picking_rt =
457 std::make_shared<gfx::texture>(tex_id_dim,
458 tex_id_dim,
459 false,
460 1,
461 gfx::texture_format::RGBA8,
462 0 | BGFX_TEXTURE_RT | BGFX_SAMPLER_MIN_POINT | BGFX_SAMPLER_MAG_POINT |
463 BGFX_SAMPLER_MIP_POINT | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP);
464
465 auto picking_rt_depth =
466 std::make_shared<gfx::texture>(tex_id_dim,
467 tex_id_dim,
468 false,
469 1,
470 gfx::texture_format::D24S8,
471 0 | BGFX_TEXTURE_RT | BGFX_SAMPLER_MIN_POINT | BGFX_SAMPLER_MAG_POINT |
472 BGFX_SAMPLER_MIP_POINT | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP);
473
474 std::vector<std::shared_ptr<gfx::texture>> textures{picking_rt, picking_rt_depth};
475 surface_ = std::make_shared<gfx::frame_buffer>(textures);
476
477 // CPU texture for blitting to and reading ID buffer so we can see what was clicked on.
478 // Impossible to read directly from a render target, you *must* blit to a CPU texture
479 // first. Algorithm Overview: Render on GPU -> Blit to CPU texture -> Read from CPU
480 // texture.
481 blit_tex_ = std::make_shared<gfx::texture>(
482 tex_id_dim,
483 tex_id_dim,
484 false,
485 1,
486 gfx::texture_format::RGBA8,
487 0 | BGFX_TEXTURE_BLIT_DST | BGFX_TEXTURE_READ_BACK | BGFX_SAMPLER_MIN_POINT | BGFX_SAMPLER_MAG_POINT |
488 BGFX_SAMPLER_MIP_POINT | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP);
489
490 auto vs = am.get_asset<gfx::shader>("editor:/data/shaders/vs_picking_id.sc");
491 auto vs_skinned = am.get_asset<gfx::shader>("editor:/data/shaders/vs_picking_id_skinned.sc");
492 auto fs = am.get_asset<gfx::shader>("editor:/data/shaders/fs_picking_id.sc");
493
494 program_ = std::make_unique<gpu_program>(vs, fs);
495 program_skinned_ = std::make_unique<gpu_program>(vs_skinned, fs);
496
497 auto vs_gizmos = am.get_asset<gfx::shader>("editor:/data/shaders/vs_picking_debugdraw_fill_texture.sc");
498 auto fs_gizmos = am.get_asset<gfx::shader>("editor:/data/shaders/fs_picking_debugdraw_fill_texture.sc");
499 program_gizmos_ = std::make_unique<gpu_program>(vs_gizmos, fs_gizmos);
500
501 return true;
502}
503
505{
506 return true;
507}
508
509void picking_manager::setup_pick_camera(const camera& cam, math::vec2 pos, math::vec2 area)
510{
511 camera pick_camera;
512
513 if(area.x > 0.0f && area.y > 0.0f)
514 {
515 // Area picking: copy the passed camera and adjust for the selection area
516 pick_camera = cam; // Copy the passed camera
517
518 }
519 else
520 {
521 // Single point picking (existing logic)
522 const auto near_clip = cam.get_near_clip();
523 const auto far_clip = cam.get_far_clip();
524 const auto& frustum = cam.get_frustum();
525
526 math::vec3 pick_eye;
527 math::vec3 pick_at;
528 math::vec3 pick_up = cam.y_unit_axis();
529
530 if(!cam.viewport_to_world(pos, frustum.planes[math::volume_plane::near_plane], pick_eye, true))
531 return;
532
533 if(!cam.viewport_to_world(pos, frustum.planes[math::volume_plane::far_plane], pick_at, true))
534 return;
535
536 pick_camera.set_aspect_ratio(1.0f);
537 pick_camera.set_fov(1.0f);
538 pick_camera.set_near_clip(near_clip);
539 pick_camera.set_far_clip(far_clip);
540 pick_camera.look_at(pick_eye, pick_at, pick_up);
541 }
542
543 pick_camera_ = pick_camera;
544 pick_position_ = pos;
545 pick_area_ = area;
546 reading_ = 0;
547 start_readback_ = true;
548}
549
551{
552 pick_camera_.reset();
553 pick_position_ = {};
554 pick_area_ = {};
555 reading_ = 0;
556 start_readback_ = false;
557}
558
559void picking_manager::request_pick(const camera& cam, editing_manager::select_mode mode, math::vec2 pos, math::vec2 area)
560{
561 setup_pick_camera(cam, pos, area);
562 pick_mode_ = mode;
563
564 if(area.x > 0.0f && area.y > 0.0f)
565 {
567 }
568
569 pick_callback_ = {}; // Clear any existing callback
570}
571
572void picking_manager::query_pick(math::vec2 pos, const camera& cam, pick_callback callback, bool force)
573{
574 // If already picking, ignore this request
575 if (!force && is_picking())
576 {
577 return;
578 }
579
580 // Set up the pick operation
581 setup_pick_camera(cam, pos);
582 pick_callback_ = callback;
583}
584
585auto picking_manager::is_picking() const -> bool
586{
587 return pick_camera_.has_value() || reading_ != 0;
588}
589
590auto picking_manager::get_pick_texture() const -> const std::shared_ptr<gfx::texture>&
591{
592 return blit_tex_;
593}
594
595} // namespace unravel
manifold_type type
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:35
auto get_far_clip() const -> float
Retrieves the distance from the camera to the far clip plane.
Definition camera.cpp:67
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:523
void set_fov(float degrees)
Sets the field of view angle of this camera (perspective only).
Definition camera.cpp:77
void look_at(const math::vec3 &eye, const math::vec3 &at)
Sets the camera to look at a specified target.
Definition camera.cpp:326
void set_far_clip(float distance)
Sets the far plane distance.
Definition camera.cpp:125
auto y_unit_axis() const -> math::vec3
Retrieves the y-axis unit vector of the camera's local coordinate system.
Definition camera.cpp:355
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:165
void set_near_clip(float distance)
Sets the near plane distance.
Definition camera.cpp:105
auto get_frustum() const -> const math::frustum &
Retrieves the current camera object frustum.
Definition camera.cpp:365
auto get_near_clip() const -> float
Retrieves the distance from the camera to the near clip plane.
Definition camera.cpp:62
Class that contains core light data, used for rendering and other purposes.
Base class for materials used in rendering.
Definition material.h:32
Main class representing a 3D mesh with support for different LODs, submeshes, and skinning.
Definition mesh.h:310
auto get_bounds() const -> const math::bbox &
Gets the local bounding box for this mesh.
Definition mesh.cpp:1507
Class that contains core data for meshes.
Structure describing a LOD group (set of meshes), LOD transitions, and their materials.
Definition model.h:42
auto get_lod(uint32_t lod) const -> asset_handle< mesh >
Gets the LOD (Level of Detail) mesh for the specified level.
Definition model.cpp:14
auto is_valid() const -> bool
Checks if the model is valid.
Definition model.cpp:9
void submit(const math::mat4 &world_transform, const pose_mat4 &submesh_transforms, const pose_mat4 &bone_transforms, const std::vector< pose_mat4 > &skinning_matrices, unsigned int lod, const submit_callbacks &callbacks) const
Submits the model for rendering.
Definition model.cpp:193
void query_pick(math::vec2 pos, const camera &cam, pick_callback callback, bool force=false)
std::function< void(entt::handle entity, const math::vec2 &screen_pos)> pick_callback
auto init(rtti::context &ctx) -> bool
void request_pick(const camera &cam, editing_manager::select_mode mode, math::vec2 pos, math::vec2 area={})
static constexpr int tex_id_dim
auto is_picking() const -> bool
void on_frame_render(rtti::context &ctx, delta_t dt)
void on_frame_pick(rtti::context &ctx, delta_t dt)
auto get_pick_texture() const -> const std::shared_ptr< gfx::texture > &
auto deinit(rtti::context &ctx) -> bool
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.
std::chrono::duration< float > delta_t
std::string tag
Definition hub.cpp:26
const char * icon
#define APPLOG_WARNING(...)
Definition logging.h:19
Definition cache.hpp:11
void submit(view_id _id, program_handle _handle, int32_t _depth, bool _preserveState)
Definition graphics.cpp:899
uint32_t read_texture(texture_handle _handle, void *_data, uint8_t _mip)
Definition graphics.cpp:590
void set_state(uint64_t _state, uint32_t _rgba)
Definition graphics.cpp:763
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)
Definition graphics.cpp:973
void discard(uint8_t _flags)
Definition graphics.cpp:968
uint32_t get_render_frame()
auto to_bx(const glm::vec3 &data) -> bx::Vec3
Definition gizmos.cpp:7
entt::entity entity
float x
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
Definition render_pass.h:98
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
auto get_cached() -> T &
Definition context.hpp:49
hpp::event< void(rtti::context &, delta_t)> on_frame_render
Definition events.h:19
Parameters for the submit callbacks.
Definition model.h:132
bool skinned
Indicates if the model is skinned.
Definition model.h:134
Callbacks for submitting the model for rendering.
Definition model.h:126
std::function< void(const params &info, const material &)> setup_params_per_submesh
Callback for setting up per submesh.
Definition model.h:143
std::function< void(const params &info)> setup_begin
Callback for setup begin.
Definition model.h:139
std::function< void(const params &info)> setup_params_per_instance
Callback for setting up per instance.
Definition model.h:141
std::function< void(const params &info)> setup_end
Callback for setup end.
Definition model.h:145
Represents a scene in the ACE framework, managing entities and their relationships.
Definition scene.h:21
auto create_handle(entt::entity e) -> entt::handle
Creates an entity in the scene.
Definition scene.cpp:304