Unravel Engine C++ Reference
Loading...
Searching...
No Matches
scene_panel.cpp
Go to the documentation of this file.
1#include "scene_panel.h"
2#include "../panel.h"
3#include "../panels_defs.h"
5#include "imgui_widgets/utils.h"
11#include <editor/shortcuts.h>
12
13
18#include <engine/ecs/ecs.h>
25
34#include <imgui/imgui.h>
35#include <imgui/imgui_internal.h>
36#include <imgui_widgets/gizmo.h>
37#include <imgui_widgets/imcoolbar.h>
38#include <seq/seq.h>
39
40#include <algorithm>
42#include <logging/logging.h>
43#include <numeric>
44
45namespace unravel
46{
47namespace
48{
49
50// Forward declarations
51void restore_original_materials(entt::handle entity, const std::vector<asset_handle<material>>& original_materials);
52void apply_material_preview(rtti::context& ctx,
53 entt::handle entity,
54 const std::string& material_path,
55 entt::handle& last_preview_entity,
57 bool& is_previewing);
58void manipulation_gizmos(bool& gizmo_at_center,
59 bool& was_using_gizmo,
60 entt::handle center,
61 entt::handle editor_camera,
62 editing_manager& em);
63void handle_camera_movement(entt::handle camera, math::vec3& move_dir, float& acceleration, bool& is_dragging);
64
65// Material preview state
66struct material_preview_state
67{
68 entt::handle last_preview_entity;
69 std::vector<asset_handle<material>> original_materials;
70 bool is_previewing = false;
72};
73
74// Global preview state
75static material_preview_state g_preview_state;
76
77// Check if a material is being dragged and get its path
78auto check_material_drag(std::string& out_material_path) -> bool
79{
80 for(const auto& type : ex::get_suported_formats<material>())
81 {
82 auto payload = ImGui::GetDragDropPayload();
83 if(payload && payload->IsDataType(type.c_str()))
84 {
85 if(payload->Data)
86 {
87 out_material_path =
88 std::string(reinterpret_cast<const char*>(payload->Data), std::size_t(payload->DataSize));
89 out_material_path = fs::convert_to_protocol(fs::path(out_material_path)).generic_string();
90 return true;
91 }
92 }
93 }
94 return false;
95}
96
97// Handle material preview during drag
98void handle_material_preview(rtti::context& ctx, const camera_component& camera_comp, const std::string& material_path)
99{
100 auto& pick_manager = ctx.get_cached<picking_manager>();
101
102 // Check if the material path changed
103 if(g_preview_state.current_drag_material != material_path)
104 {
105 // Restore previous preview if there was one
106 if(g_preview_state.is_previewing && g_preview_state.last_preview_entity)
107 {
108 restore_original_materials(g_preview_state.last_preview_entity, g_preview_state.original_materials);
109 }
110
111 // Update current material
112 g_preview_state.current_drag_material = material_path;
113 g_preview_state.is_previewing = false;
114 }
115
116 // Query for entity under cursor to show preview
117 // picking_manager handles throttling internally
118 auto cursor_pos = ImGui::GetMousePos();
119 pick_manager.query_pick(math::vec2{cursor_pos.x, cursor_pos.y},
120 camera_comp.get_camera(),
121 [&ctx, material_path](entt::handle entity, const math::vec2& screen_pos)
122 {
123 apply_material_preview(ctx,
124 entity,
125 material_path,
126 g_preview_state.last_preview_entity,
127 g_preview_state.original_materials,
128 g_preview_state.is_previewing);
129 });
130}
131
132// Handle material drop on entity
133void handle_material_drop(rtti::context& ctx, const camera_component& camera_comp, const std::string& material_path)
134{
135 auto cursor_pos = ImGui::GetMousePos();
136 auto& pick_manager = ctx.get_cached<picking_manager>();
137 auto& am = ctx.get_cached<asset_manager>();
138 auto& em = ctx.get_cached<editing_manager>();
139
140 // Load the material asset
141 auto material_asset = am.get_asset<material>(material_path);
142 bool force = true;
143
144 // Use the picking system to query what's under the cursor
145 pick_manager.query_pick(
146 math::vec2{cursor_pos.x, cursor_pos.y},
147 camera_comp.get_camera(),
148 [material_asset, &em](entt::handle entity, const math::vec2& screen_pos)
149 {
150 // Check if entity has a model component
151 if(entity && entity.all_of<model_component>())
152 {
153 // Get current materials to store as old state
154 auto& model_comp = entity.get<model_component>();
155 const auto& current_model = model_comp.get_model();
156 auto old_materials = current_model.get_materials();
157
158 em.push_undo_stack_enabled(true);
159
160 // Create and execute the action
161 em.queue_action<entity_set_materials_action_t>({}, entity, old_materials, material_asset);
162
163 em.pop_undo_stack_enabled();
164 }
165 else if(entity)
166 {
167 APPLOG_WARNING("Cannot apply material to entity without model_component");
168 }
169 },
170 force);
171}
172
173// Factory wrapper for viewport drops: snapshots the current cursor position and camera,
174// runs the user-supplied producer inside an undoable create_entities_action_t, and selects
175// the resulting entity. No parenting action is layered - viewport drops land at scene root.
176void queue_create_at_cursor(rtti::context& ctx,
177 const camera_component& camera_comp,
178 const std::string& action_name,
179 std::function<entt::handle(rtti::context&, scene&, const camera&, const math::vec2&)> producer)
180{
181 auto cursor_pos = ImGui::GetMousePos();
182 auto& em = ctx.get_cached<editing_manager>();
183
184 em.push_undo_stack_enabled(true);
185 em.queue_action<create_entities_action_t>(
186 action_name,
187 [&ctx, camera = camera_comp.get_camera(), cursor_pos, producer = std::move(producer)]() -> entt::handle
188 {
189 auto& em = ctx.get_cached<editing_manager>();
190 auto* target_scene = em.get_active_scene(ctx);
191 if(!target_scene)
192 {
193 return {};
194 }
195
196 auto object = producer(ctx, *target_scene, camera, math::vec2{cursor_pos.x, cursor_pos.y});
197 if(!object)
198 {
199 return {};
200 }
201 em.select(object);
202 return object;
203 });
204 em.pop_undo_stack_enabled();
205}
206
207// Handle mesh drop at cursor position
208void handle_mesh_drop(rtti::context& ctx, const camera_component& camera_comp, const std::string& mesh_path)
209{
210 // Capture the modifier state at drop time; the producer may run on a later frame.
211 const bool align_to_surface = ImGui::IsKeyDown(shortcuts::modifier_drop_align_to_surface);
212 queue_create_at_cursor(ctx, camera_comp, "Drop Mesh",
213 [mesh_path, align_to_surface](rtti::context& ctx, scene& scn, const camera& cam, const math::vec2& cursor) -> entt::handle
214 {
215 return defaults::create_mesh_entity_at(ctx, scn, mesh_path, cam, cursor, align_to_surface);
216 });
217}
218
219// Handle prefab drop at cursor position
220void handle_prefab_drop(rtti::context& ctx, const camera_component& camera_comp, const std::string& prefab_path)
221{
222 // Capture the modifier state at drop time; the producer may run on a later frame.
223 const bool align_to_surface = ImGui::IsKeyDown(shortcuts::modifier_drop_align_to_surface);
224 queue_create_at_cursor(ctx, camera_comp, "Drop Prefab",
225 [prefab_path, align_to_surface](rtti::context& ctx, scene& scn, const camera& cam, const math::vec2& cursor) -> entt::handle
226 {
227 return defaults::create_prefab_at(ctx, scn, prefab_path, cam, cursor, align_to_surface);
228 });
229}
230
231// Reset material preview state
232void reset_preview_state()
233{
234 if(g_preview_state.is_previewing && g_preview_state.last_preview_entity)
235 {
236 restore_original_materials(g_preview_state.last_preview_entity, g_preview_state.original_materials);
237 g_preview_state.is_previewing = false;
238 g_preview_state.last_preview_entity = {};
239 g_preview_state.original_materials.clear();
240 g_preview_state.current_drag_material.clear();
241 }
242}
243
244// ============================================================================
245// Camera Movement Helper Functions
246// ============================================================================
247
248auto calculate_movement_speed(float base_speed, bool speed_boost_active, float multiplier) -> float
249{
250 float movement_speed = base_speed;
251 if(speed_boost_active)
252 {
253 movement_speed *= multiplier;
254 }
255 return movement_speed;
256}
257
258void handle_middle_mouse_panning(entt::handle camera, float movement_speed, float dt)
259{
260 if(!ImGui::IsMouseDown(ImGuiMouseButton_Middle))
261 {
262 return;
263 }
264
265 auto delta_move = ImGui::GetIO().MouseDelta;
266 auto& transform = camera.get<transform_component>();
267
268 if(delta_move.x != 0)
269 {
270 transform.move_by_local({-1 * delta_move.x * movement_speed * dt, 0.0f, 0.0f});
271 }
272 if(delta_move.y != 0)
273 {
274 transform.move_by_local({0.0f, delta_move.y * movement_speed * dt, 0.0f});
275 }
276}
277
278auto collect_movement_input(float& max_hold, bool& is_dragging) -> math::vec3
279{
280 math::vec3 movement_input{0.0f, 0.0f, 0.0f};
281
282 auto is_key_down = [&](ImGuiKey k) -> bool
283 {
284 bool down = ImGui::IsKeyDown(k);
285 if(down)
286 {
287 auto data = ImGui::GetKeyData(ImGui::GetCurrentContext(), k);
288 max_hold = std::max(max_hold, data->DownDuration);
289 }
290 return down;
291 };
292
293 if(is_dragging)
294 {
295 float move_speed = 4.0f;
296 if(is_key_down(shortcuts::camera_forward))
297 {
298 movement_input.z += move_speed;
299 }
300 if(is_key_down(shortcuts::camera_backward))
301 {
302 movement_input.z -= move_speed;
303 }
304 if(is_key_down(shortcuts::camera_right))
305 {
306 movement_input.x += move_speed;
307 }
308 if(is_key_down(shortcuts::camera_left))
309 {
310 movement_input.x -= move_speed;
311 }
312 }
313
314 auto delta_wheel = ImGui::GetIO().MouseWheel;
315 if(delta_wheel != 0)
316 {
317 movement_input.z += 15.0f * delta_wheel;
318 }
319
320 return movement_input;
321}
322
323auto handle_mouse_rotation(entt::handle camera, float rotation_speed, bool is_dragging) -> bool
324{
325 if(!is_dragging)
326 {
327 return false;
328 }
329
330 auto delta_move = ImGui::GetIO().MouseDelta;
331 auto& transform = camera.get<transform_component>();
332
333 if(delta_move.x != 0.0f || delta_move.y != 0.0f)
334 {
335 float dx = delta_move.x * rotation_speed;
336 float dy = delta_move.y * rotation_speed;
337
338 transform.rotate_by_euler_global({0.0f, dx, 0.0f});
339 transform.rotate_by_euler_local({dy, 0.0f, 0.0f});
340 return true;
341 }
342 return false;
343}
344
345void update_movement_acceleration(math::vec3& move_dir, float& acceleration, const math::vec3& input, bool any_input)
346{
347 if(any_input)
348 {
349 if(acceleration < 0.1f)
350 {
351 acceleration = 0.1f;
352 }
353 acceleration *= 1.5f;
354 acceleration = std::min(1.0f, acceleration);
355 move_dir.x = input.x;
356 move_dir.z = input.z;
357 }
358 else if(acceleration > 0.0001f)
359 {
360 acceleration *= 0.85f;
361 }
362}
363
364void apply_movement(entt::handle camera,
365 const math::vec3& move_dir,
366 float movement_speed,
367 float acceleration,
368 float max_hold,
369 float hold_speed,
370 float dt)
371{
372 if(acceleration <= 0.0001f)
373 {
374 return;
375 }
376
377 auto& transform = camera.get<transform_component>();
378
379 if(!math::any(math::epsilonNotEqual(move_dir, math::vec3(0.0f, 0.0f, 0.0f), math::epsilon<float>())))
380 {
381 return;
382 }
383
384 float adjusted_dt = dt;
385 if(math::epsilonNotEqual(move_dir.x, 0.0f, math::epsilon<float>()) ||
386 math::epsilonNotEqual(move_dir.z, 0.0f, math::epsilon<float>()))
387 {
388 adjusted_dt += max_hold * hold_speed;
389 }
390
391 auto length = math::length(move_dir);
392 transform.move_by_local(math::normalize(move_dir) * length * movement_speed * adjusted_dt * acceleration);
393}
394
395void handle_camera_movement(entt::handle camera, math::vec3& move_dir, float& acceleration, bool& is_dragging)
396{
397 if(!ImGui::IsWindowFocused())
398 {
399 return;
400 }
401
402 if(!ImGui::IsWindowHovered() && !is_dragging)
403 {
404 return;
405 }
406
407 // Movement parameters
408 constexpr float base_movement_speed = 2.0f;
409 constexpr float rotation_speed = 0.1f;
410 constexpr float speed_multiplier = 5.0f;
411 constexpr float hold_speed = 0.1f;
412 float fixed_dt = ImMin(0.0333f, ImGui::GetIO().DeltaTime); // Fixed delta time
413
414 bool speed_boost_active = ImGui::IsKeyDown(shortcuts::modifier_camera_speed_boost);
415 float movement_speed = calculate_movement_speed(base_movement_speed, speed_boost_active, speed_multiplier);
416
417 // Handle middle mouse panning
418 handle_middle_mouse_panning(camera, movement_speed, fixed_dt);
419
420 // Handle right mouse dragging
421 is_dragging = ImGui::IsMouseDown(ImGuiMouseButton_Right);
422
423 if(is_dragging)
424 {
425 ImGui::WrapMousePos();
426 if(ImGui::IsWindowHovered())
427 {
428 ImGui::SetMouseCursor(ImGuiMouseCursor_Cross);
429 }
430 }
431
432 // Collect movement input (works for both dragging and non-dragging)
433 float max_hold = 0.0f;
434 math::vec3 movement_input = collect_movement_input(max_hold, is_dragging);
435 bool any_input = math::any(math::epsilonNotEqual(movement_input, math::vec3(0.0f), math::epsilon<float>()));
436
437 // Handle mouse rotation (only when dragging)
438 bool any_rotation = handle_mouse_rotation(camera, rotation_speed, is_dragging);
439
440 // Process camera input with acceleration
441 update_movement_acceleration(move_dir, acceleration, movement_input, any_input);
442
443 if(any_input || any_rotation)
444 {
445 seq::scope::stop_all("camera_focus");
446 }
447
448 if(acceleration > 0.0001f)
449 {
450 // Continue movement with deceleration when not actively inputting
451 apply_movement(camera, move_dir, movement_speed, acceleration, 0.0f, hold_speed, fixed_dt);
452 }
453}
454
455// ============================================================================
456// Gizmo Manipulation Helper Functions
457// ============================================================================
458
459void setup_gizmo_context(const camera_component& camera_comp)
460{
461 auto p = ImGui::GetItemRectMin();
462 auto s = ImGui::GetItemRectSize();
463 const auto& camera = camera_comp.get_camera();
464
465 ImGuizmo::SetDrawlist(ImGui::GetWindowDrawList());
466 ImGuizmo::SetRect(p.x, p.y, s.x, s.y);
467 ImGuizmo::SetOrthographic(camera.get_projection_mode() == projection_mode::orthographic);
468}
469
470void handle_view_manipulator(entt::handle editor_camera, const camera_component& camera_comp)
471{
472 auto p = ImGui::GetItemRectMin();
473 auto s = ImGui::GetItemRectSize();
474 const auto& camera = camera_comp.get_camera();
475 auto& camera_trans = editor_camera.get<transform_component>();
476
477 auto view = camera.get_view().get_matrix();
478 static const ImVec2 view_gizmo_sz(100.0f, 100.0f);
479
480 ImGuizmo::ViewManipulate(value_ptr(view),
481 1.0f,
482 p + ImVec2(s.x - view_gizmo_sz.x, 0.0f),
483 view_gizmo_sz,
484 ImGui::GetColorU32(ImVec4(0.0f, 0.0f, 0.0f, 0.0f)));
485
486 math::transform tr = glm::inverse(view);
487 camera_trans.set_rotation_local(tr.get_rotation());
488}
489
490void handle_gizmo_shortcuts(editing_manager& em)
491{
492 if(!ImGui::IsWindowFocused())
493 {
494 return;
495 }
496 if(ImGui::IsMouseDown(ImGuiMouseButton_Right) || ImGui::IsAnyItemActive() || ImGuizmo::IsUsing())
497 {
498 return;
499 }
500
501 if(ImGui::IsKeyPressed(shortcuts::universal_tool))
502 {
503 em.operation = ImGuizmo::OPERATION::UNIVERSAL;
504 }
505 if(ImGui::IsKeyPressed(shortcuts::move_tool))
506 {
507 em.operation = ImGuizmo::OPERATION::TRANSLATE;
508 }
509 if(ImGui::IsKeyPressed(shortcuts::rotate_tool))
510 {
511 em.operation = ImGuizmo::OPERATION::ROTATE;
512 }
513 if(ImGui::IsKeyPressed(shortcuts::scale_tool))
514 {
515 em.operation = ImGuizmo::OPERATION::SCALE;
516 }
517 if(ImGui::IsKeyPressed(shortcuts::bounds_tool))
518 {
519 em.operation = ImGuizmo::OPERATION::BOUNDS;
520 }
521}
522
523void setup_snap_data(editing_manager& em, float*& snap, float*& bounds_snap, float bounds_snap_data[3])
524{
525 snap = nullptr;
526 bounds_snap = nullptr;
527
528 if(!ImGui::IsKeyDown(shortcuts::modifier_snapping))
529 {
530 return;
531 }
532
533 bounds_snap = bounds_snap_data;
534
535 if(em.operation == ImGuizmo::OPERATION::TRANSLATE)
536 {
537 snap = &em.snap_data.translation_snap[0];
538 }
539 else if(em.operation == ImGuizmo::OPERATION::ROTATE)
540 {
541 snap = &em.snap_data.rotation_degree_snap;
542 }
543 else if(em.operation == ImGuizmo::OPERATION::SCALE)
544 {
545 snap = &em.snap_data.scale_snap;
546 }
547}
548
549auto calculate_center_pivot(const std::vector<entt::handle*>& selections) -> math::vec3
550{
551 math::vec3 pivot{0.0f, 0.0f, 0.0f};
552 size_t points = 0;
553
554 for(const auto& sel : selections)
555 {
556 if(sel && *sel)
557 {
558 auto& sel_transform_comp = sel->get<transform_component>();
559 pivot += sel_transform_comp.get_position_global();
560 points++;
561 }
562 }
563
564 if(points > 0)
565 {
566 pivot /= static_cast<float>(points);
567 }
568
569 return pivot;
570}
571
572void setup_gizmo_pivot(bool gizmo_at_center,
573 entt::handle center,
574 const std::vector<entt::handle*>& selections,
575 entt::handle active_selection)
576{
577 auto& center_transform_comp = center.get<transform_component>();
578 auto& transform_comp = active_selection.get<transform_component>();
579
580 auto trans_global = transform_comp.get_transform_global();
581 center_transform_comp.set_transform_global(trans_global);
582
583 if(gizmo_at_center)
584 {
585 math::vec3 pivot = calculate_center_pivot(selections);
586 center_transform_comp.set_position_global(pivot);
587 }
588}
589
590struct bounds_manipulation_result_t
591{
593 math::vec3 initial_position{};
594
596 math::vec3 new_position{};
597};
598
599auto handle_component_bounds_manipulation(entt::handle active_selection,
600 fsize_t area,
601 const camera_component& camera_comp,
602 editing_manager& em,
603 float* snap,
604 float bounds_snap_data[3],
605 float* bounds_snap) -> hpp::optional<bounds_manipulation_result_t>
606{
607 auto& transform_comp = active_selection.get<transform_component>();
608 const auto& camera = camera_comp.get_camera();
609
610 // Store initial state for undo/redo
611 fsize_t initial_area = area;
612 math::vec3 initial_position = transform_comp.get_position_global();
613
614 // Local-space half-extents = 0.5 in X & Y, zero thickness in Z
615 float bounds[6] = {
616 -0.5f,
617 -0.5f,
618 0.0f, // min x, y, z
619 0.5f,
620 0.5f,
621 0.0f // max x, y, z
622 };
623
624 math::transform model_tr;
625 model_tr.set_position(transform_comp.get_position_global());
626 model_tr.set_rotation(transform_comp.get_rotation_global());
627 model_tr.set_scale(math::vec3(area.width, area.height, 1.0f));
628
629 math::mat4 output = model_tr;
630
631 int movetype = ImGuizmo::Manipulate(camera.get_view(),
632 camera.get_projection(),
633 ImGuizmo::BOUNDS,
634 em.mode,
635 math::value_ptr(output),
636 nullptr,
637 snap,
638 bounds,
639 bounds_snap);
640
641 if(movetype != ImGuizmo::MT_NONE)
642 {
643 math::transform output_tr = output;
644 const auto& scale = output_tr.get_scale();
645 const auto& trans = output_tr.get_translation();
646
647 // Create new area and position
649 math::vec3 new_position = trans;
650 bounds_manipulation_result_t result;
651 result.initial_area = initial_area;
652 result.initial_position = initial_position;
653 result.new_area = new_area;
654 result.new_position = new_position;
655 return result;
656 }
657
658 return hpp::nullopt;
659}
660
661auto handle_text_component_bounds_manipulation(entt::handle active_selection,
662 const camera_component& camera_comp,
663 editing_manager& em,
664 float* snap,
665 float bounds_snap_data[3],
666 float* bounds_snap) -> bool
667{
668 auto text_comp = active_selection.try_get<text_component>();
669 if(!text_comp)
670 {
671 return false;
672 }
673
674 auto area = text_comp->get_area();
675 if(!area.is_valid())
676 {
677 return false;
678 }
679
680 auto result = handle_component_bounds_manipulation(active_selection,
681 area,
682 camera_comp,
683 em,
684 snap,
685 bounds_snap_data,
686 bounds_snap);
687 if(!result)
688 {
689 return false;
690 }
691
692 const auto& initial_area = result->initial_area;
693 const auto& initial_position = result->initial_position;
694 const auto& new_area = result->new_area;
695 const auto& new_position = result->new_position;
696
697 // Create composite action with both text bounds and transform changes
698 auto composite_action = std::make_shared<composite_action_t>();
699
700 // Add text bounds action
701 composite_action->add_sub_action(
702 std::make_shared<entity_set_text_bounds_action_t>(active_selection, initial_area, new_area));
703
704 // Add global transform action for the center entity
705 composite_action->add_sub_action(
706 std::make_shared<transform_move_global_action_t>(active_selection, initial_position, new_position));
707
708 // Execute the composite action
709 em.push_undo_stack_enabled(true);
710 em.do_action("Text Bounds Manipulation", composite_action);
711 em.pop_undo_stack_enabled();
712
713 return true;
714}
715
716auto handle_ui_document_component_bounds_manipulation(entt::handle active_selection,
717 const camera_component& camera_comp,
718 editing_manager& em,
719 float* snap,
720 float bounds_snap_data[3],
721 float* bounds_snap) -> bool
722{
723 auto ui_document_comp = active_selection.try_get<ui_document_component>();
724 if(!ui_document_comp)
725 {
726 return false;
727 }
728
729 if(!ui_document_comp->size.is_valid())
730 {
731 return false;
732 }
733 auto area = ui_document_comp->get_world_space_scale();
734
735 auto result = handle_component_bounds_manipulation(active_selection,
736 fsize_t(area.x, area.y),
737 camera_comp,
738 em,
739 snap,
740 bounds_snap_data,
741 bounds_snap);
742 if(!result)
743 {
744 return false;
745 }
746
747 const auto& initial_area = fsize_t(result->initial_area.width * ui_document_comp->pixels_per_world_unit,
748 result->initial_area.height * ui_document_comp->pixels_per_world_unit);
749 const auto& initial_position = result->initial_position;
750 const auto& new_area = fsize_t(result->new_area.width * ui_document_comp->pixels_per_world_unit,
751 result->new_area.height * ui_document_comp->pixels_per_world_unit);
752 const auto& new_position = result->new_position;
753
754 // Create composite action with both text bounds and transform changes
755 auto composite_action = std::make_shared<composite_action_t>();
756
759 // Add text bounds action
760 composite_action->add_sub_action(
761 std::make_shared<entity_set_ui_document_component_bounds_action_t>(active_selection,
762 initial_area_size,
763 new_area_size));
764
765 // Add global transform action for the center entity
766 composite_action->add_sub_action(
767 std::make_shared<transform_move_global_action_t>(active_selection, initial_position, new_position));
768
769 // Execute the composite action
770 em.push_undo_stack_enabled(true);
771 em.do_action("UI Document Size Manipulation", composite_action);
772 em.pop_undo_stack_enabled();
773
774 return true;
775}
776
777auto handle_inverse_kinematics(entt::handle selection, entt::handle center, editing_manager& em) -> bool
778{
779 // Allow IK when gizmo is being used, but block for other ImGui items (like text inputs, sliders, etc.)
780 bool is_gizmo_active = ImGuizmo::IsUsing();
781 bool is_other_item_active = ImGui::IsAnyItemActive() && !is_gizmo_active;
782
783 if(is_other_item_active)
784 {
785 return false;
786 }
787
788 auto& center_transform_comp = center.get<transform_component>();
789
790 if(ImGui::IsKeyDown(shortcuts::ik_ccd))
791 {
792 return ik_set_position_ccd(selection,
793 center_transform_comp.get_position_global(),
794 math::vec3(0.f),
795 em.ik_data.num_nodes,
796 100);
797 }
798 if(ImGui::IsKeyDown(shortcuts::ik_fabrik))
799 {
800 return ik_set_position_fabrik(selection,
801 center_transform_comp.get_position_global(),
802 math::vec3(0.f),
803 em.ik_data.num_nodes,
804 100);
805 }
806 if(ImGui::IsKeyDown(shortcuts::ik_two_bone))
807 {
808 return ik_set_position_two_bone(selection,
809 center_transform_comp.get_position_global(),
810 center_transform_comp.get_z_axis_global(),
811 1.0f,
812 1.0f);
813 }
814 return false;
815}
816
817void apply_transform_delta_to_selections(const std::vector<entt::handle>& top_level_selections,
818 const std::vector<entt::handle>& original_parents,
819 const math::mat4& center_delta)
820{
821 for(size_t i = 0; i < top_level_selections.size(); ++i)
822 {
823 auto& sel = top_level_selections[i];
824 if(!sel)
825 {
826 continue;
827 }
828
829 auto& sel_transform_comp = sel.get<transform_component>();
830
831 // "old_global" is the entity's transform BEFORE we moved the center.
832 math::mat4 old_global = sel_transform_comp.get_transform_global();
833
834 // Compute the new global by applying the same delta we applied to the center
835 math::mat4 new_global = center_delta * old_global;
836
837 // Convert that new global transform back into local space for the entity's
838 // actual/original parent (which we never physically changed).
839 entt::handle original_parent = original_parents[i];
840 if(original_parent)
841 {
842 const auto& parent_transform = original_parent.get<transform_component>();
843 math::mat4 parent_global = parent_transform.get_transform_global();
844 math::mat4 parent_global_inv = glm::inverse(parent_global);
845
846 math::mat4 new_local = parent_global_inv * new_global;
847 sel_transform_comp.set_transform_local(math::transform(new_local));
848 }
849 else
850 {
851 // If no valid parent, the new local == new global
852 sel_transform_comp.set_transform_local(math::transform(new_global));
853 }
854 }
855}
856
857auto handle_standard_gizmo_manipulation(entt::handle active_selection,
858 entt::handle center,
859 const camera_component& camera_comp,
860 editing_manager& em,
861 float* snap) -> int
862{
863 auto& center_transform_comp = center.get<transform_component>();
864 const auto& camera = camera_comp.get_camera();
865
866 math::mat4 output = center_transform_comp.get_transform_global();
867 math::mat4 output_delta;
868
869 ImGuizmo::AllowAxisFlip(false);
870
871 int movetype = ImGuizmo::Manipulate(camera.get_view(),
872 camera.get_projection(),
873 em.operation,
874 em.mode,
875 math::value_ptr(output),
876 math::value_ptr(output_delta),
877 snap,
878 nullptr,
879 nullptr);
880
881 if(movetype != ImGuizmo::MT_NONE)
882 {
883 math::transform delta = output_delta;
884
885 auto perspective = center_transform_comp.get_perspective_local();
886 auto skew = center_transform_comp.get_skew_local();
887
888 if(ImGuizmo::IsScaleType(movetype))
889 {
890 center_transform_comp.scale_by_local(delta.get_scale());
891 }
892 if(ImGuizmo::IsRotateType(movetype))
893 {
894 center_transform_comp.rotate_by_global(delta.get_rotation());
895 }
896 if(ImGuizmo::IsTranslateType(movetype))
897 {
898 center_transform_comp.move_by_global(delta.get_translation());
899 }
900
901 center_transform_comp.set_skew_local(skew);
902 center_transform_comp.set_perspective_local(perspective);
903 }
904
905 return movetype;
906}
907
908void manipulation_gizmos(bool& gizmo_at_center,
909 bool& was_using_gizmo,
910 entt::handle center,
911 entt::handle editor_camera,
912 editing_manager& em)
913{
914 auto& camera_trans = editor_camera.get<transform_component>();
915 auto& camera_comp = editor_camera.get<camera_component>();
916
917 setup_gizmo_context(camera_comp);
918 handle_view_manipulator(editor_camera, camera_comp);
919 handle_gizmo_shortcuts(em);
920
921 auto active_sel = em.try_get_active_selection_as<entt::handle>();
922 if(!active_sel || !active_sel->valid() || !active_sel->all_of<transform_component>())
923 {
924 return;
925 }
926
927 float bounds_snap_data[3] = {0.1f, 0.1f, 0.0f};
928 float* snap = nullptr;
929 float* bounds_snap = nullptr;
930
931 setup_snap_data(em, snap, bounds_snap, bounds_snap_data);
932
933 auto selections = em.try_get_selections_as<entt::handle>();
934 setup_gizmo_pivot(gizmo_at_center, center, selections, *active_sel);
935
936 // Store initial center transform before any manipulation
937 auto& center_transform_comp = center.get<transform_component>();
938 math::mat4 center_initial_global = center_transform_comp.get_transform_global();
939
940 // Convert pointer vector to value vector for get_top_level_entities
941 std::vector<entt::handle> selection_values;
942 selection_values.reserve(selections.size());
943 for(const auto& sel : selections)
944 {
945 if(sel && *sel)
946 {
947 selection_values.emplace_back(*sel);
948 }
949 }
950
951 auto top_level_selections = transform_component::get_top_level_entities(selection_values);
952
953 std::vector<entt::handle> original_parents;
954 std::vector<math::transform> original_transforms;
955 original_parents.reserve(top_level_selections.size());
956 original_transforms.reserve(top_level_selections.size());
957
958 // Store initial state before any manipulation
959 for(const auto& sel : top_level_selections)
960 {
961 if(sel)
962 {
963 auto& sel_transform_comp = sel.get<transform_component>();
964 original_parents.emplace_back(sel_transform_comp.get_parent());
965 original_transforms.emplace_back(sel_transform_comp.get_transform_local());
966 }
967 }
968
969 bool bounds_changed = false;
970 // Handle text component bounds manipulation for non-rotate/scale operations
971 if(em.operation != ImGuizmo::ROTATE && em.operation != ImGuizmo::SCALE && top_level_selections.size() == 1)
972 {
973 bounds_changed = handle_text_component_bounds_manipulation(*active_sel,
974 camera_comp,
975 em,
976 snap,
977 bounds_snap_data,
978 bounds_snap);
979
980 bounds_changed = handle_ui_document_component_bounds_manipulation(*active_sel,
981 camera_comp,
982 em,
983 snap,
984 bounds_snap_data,
985 bounds_snap);
986 }
987
988 int movetype = ImGuizmo::MT_NONE;
989 // Handle standard gizmo manipulation for non-bounds operations
990 if(em.operation != ImGuizmo::BOUNDS)
991 {
992 movetype = handle_standard_gizmo_manipulation(*active_sel, center, camera_comp, em, snap);
993 }
994
995 // After all manipulations, compute the delta and apply it to all selections
996 math::mat4 center_final_global = center_transform_comp.get_transform_global();
997 math::mat4 center_delta = center_final_global * glm::inverse(center_initial_global);
998
999 auto batch_action = std::make_shared<composite_action_t>();
1000 // Apply transforms and create undoable actions
1001 for(size_t i = 0; i < top_level_selections.size(); ++i)
1002 {
1003 auto& sel = top_level_selections[i];
1004 if(sel)
1005 {
1006 bool ik_keys_down = ImGui::IsKeyDown(shortcuts::ik_ccd) || ImGui::IsKeyDown(shortcuts::ik_fabrik) ||
1007 ImGui::IsKeyDown(shortcuts::ik_two_bone);
1008
1009 if(ik_keys_down)
1010 {
1011 // When IK is active, only the center entity (gizmo target) is moved by the gizmo.
1012 // IK algorithm adjusts parent bones to make the end effector reach the target.
1013 // Do NOT apply any direct transform to the selection - let IK handle it.
1014 handle_inverse_kinematics(sel, center, em);
1015 continue;
1016 }
1017
1018 // Apply transform delta to each selection (normal case when IK is not active)
1019 auto& sel_transform_comp = sel.get<transform_component>();
1020 math::mat4 old_global = sel_transform_comp.get_transform_global();
1021 math::mat4 new_global = center_delta * old_global;
1022
1023 // Convert to local space based on parent
1024 entt::handle original_parent = original_parents[i];
1025 math::transform new_local_transform;
1026
1027 if(original_parent)
1028 {
1029 const auto& parent_transform = original_parent.get<transform_component>();
1030 math::mat4 parent_global = parent_transform.get_transform_global();
1031 math::mat4 parent_global_inv = glm::inverse(parent_global);
1032 math::mat4 new_local = parent_global_inv * new_global;
1033 new_local_transform = math::transform(new_local);
1034 }
1035 else
1036 {
1037 // If no valid parent, the new local == new global
1038 new_local_transform = math::transform(new_global);
1039 }
1040
1041 // Apply the new transform
1042 sel_transform_comp.set_transform_local(new_local_transform);
1043 // Create undoable action if there was a manipulation
1044 if(movetype != ImGuizmo::MT_NONE)
1045 {
1046 // if(ImGui::IsMouseReleased(ImGuiMouseButton_Left))
1047 {
1048 bool position = ImGuizmo::IsTranslateType(movetype);
1049 bool rotation = ImGuizmo::IsRotateType(movetype);
1050 bool scale = ImGuizmo::IsScaleType(movetype);
1051 bool skew = false;
1052
1053 if(top_level_selections.size() > 1)
1054 {
1055 position = true;
1056 }
1057
1058 // batch_action->add_sub_action(std::make_shared<transform_manipulation_action_t>(
1059 // sel,
1060 // original_transforms[i],
1061 // new_local_transform,
1062 // position, rotation, scale, skew));
1063
1064 auto composite_action = std::make_shared<composite_action_t>();
1065
1066 if(position)
1067 {
1068 composite_action->add_sub_action(
1069 std::make_shared<transform_move_action_t>(sel,
1070 original_transforms[i].get_position(),
1071 new_local_transform.get_position()));
1072 }
1073 if(rotation)
1074 {
1075 composite_action->add_sub_action(
1076 std::make_shared<transform_rotate_action_t>(sel,
1077 original_transforms[i].get_rotation(),
1078 new_local_transform.get_rotation()));
1079 }
1080 if(scale)
1081 {
1082 composite_action->add_sub_action(
1083 std::make_shared<transform_scale_action_t>(sel,
1084 original_transforms[i].get_scale(),
1085 new_local_transform.get_scale()));
1086 }
1087 if(skew)
1088 {
1089 composite_action->add_sub_action(
1090 std::make_shared<transform_skew_action_t>(sel,
1091 original_transforms[i].get_skew(),
1092 new_local_transform.get_skew()));
1093 }
1094
1095 batch_action->add_sub_action(composite_action);
1096 }
1097 }
1098 }
1099 }
1100
1101 if(batch_action->sub_actions.size() > 0)
1102 {
1103 em.push_undo_stack_enabled(true);
1104 em.do_action("Transform Manipulation", batch_action);
1105 em.pop_undo_stack_enabled();
1106 }
1107}
1108
1109// Process drag and drop for assets
1110void process_drag_drop_target(rtti::context& ctx, const camera_component& camera_comp)
1111{
1112 if(!ImGui::BeginDragDropTarget())
1113 {
1114 // If we were previewing and drag ended without dropping, restore materials
1115 reset_preview_state();
1116 return;
1117 }
1118
1119 // Set cursor based on whether payload is being accepted
1120 if(ImGui::IsDragDropPayloadBeingAccepted())
1121 {
1122 ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
1123
1124 // Check for material drag and show preview
1125 std::string material_path;
1126 if(check_material_drag(material_path))
1127 {
1128 handle_material_preview(ctx, camera_comp, material_path);
1129 }
1130 }
1131 else
1132 {
1133 ImGui::SetMouseCursor(ImGuiMouseCursor_NotAllowed);
1134 reset_preview_state();
1135 }
1136
1137 // Handle material drop
1138 for(const auto& type : ex::get_suported_formats<material>())
1139 {
1140 auto payload = ImGui::AcceptDragDropPayload(type.c_str());
1141 if(payload != nullptr)
1142 {
1143 std::string absolute_path(reinterpret_cast<const char*>(payload->Data), std::size_t(payload->DataSize));
1144 std::string key = fs::convert_to_protocol(fs::path(absolute_path)).generic_string();
1145
1146 // Clear preview state since we're actually dropping now
1147 reset_preview_state();
1148
1149 handle_material_drop(ctx, camera_comp, key);
1150 }
1151 }
1152
1153 // Handle mesh drop
1154 for(const auto& type : ex::get_suported_formats<mesh>())
1155 {
1156 auto payload = ImGui::AcceptDragDropPayload(type.c_str());
1157 if(payload != nullptr)
1158 {
1159 // Clear preview state
1160 reset_preview_state();
1161
1162 std::string absolute_path(reinterpret_cast<const char*>(payload->Data), std::size_t(payload->DataSize));
1163 std::string key = fs::convert_to_protocol(fs::path(absolute_path)).generic_string();
1164
1165 handle_mesh_drop(ctx, camera_comp, key);
1166 }
1167 }
1168
1169 // Handle prefab drop
1170 for(const auto& type : ex::get_suported_formats<prefab>())
1171 {
1172 auto payload = ImGui::AcceptDragDropPayload(type.c_str());
1173 if(payload != nullptr)
1174 {
1175 // Clear preview state
1176 reset_preview_state();
1177
1178 std::string absolute_path(reinterpret_cast<const char*>(payload->Data), std::size_t(payload->DataSize));
1179 std::string key = fs::convert_to_protocol(fs::path(absolute_path)).generic_string();
1180
1181 handle_prefab_drop(ctx, camera_comp, key);
1182 }
1183 }
1184
1185 ImGui::EndDragDropTarget();
1186}
1187
1188// Helper function to restore original materials
1189void restore_original_materials(entt::handle entity, const std::vector<asset_handle<material>>& original_materials)
1190{
1191 if(!entity || !entity.all_of<model_component>() || original_materials.empty())
1192 return;
1193
1194 auto& model_comp = entity.get<model_component>();
1195 class model model_copy = model_comp.get_model();
1196
1197 // Restore original materials
1198 for(size_t i = 0; i < original_materials.size() && i < model_copy.get_materials().size(); ++i)
1199 {
1200 model_copy.set_material(original_materials[i], i);
1201 }
1202
1203 // Update the model
1204 model_comp.set_model(model_copy);
1205}
1206
1207// Apply material preview to an entity and save original materials for restoration
1208void apply_material_preview(rtti::context& ctx,
1209 entt::handle entity,
1210 const std::string& material_path,
1211 entt::handle& last_preview_entity,
1213 bool& is_previewing)
1214{
1215 // If entity is invalid, restore previous preview if there was one
1216 if(!entity)
1217 {
1219 {
1220 restore_original_materials(last_preview_entity, original_materials);
1221 is_previewing = false;
1223 original_materials.clear();
1224 }
1225 return;
1226 }
1227
1228 // If entity changed, restore previous preview
1230 {
1231 restore_original_materials(last_preview_entity, original_materials);
1232 is_previewing = false;
1233 original_materials.clear();
1234 }
1235
1236 // If entity has model component and is different from last preview
1237 if(entity && entity.all_of<model_component>() && (!is_previewing || entity != last_preview_entity))
1238 {
1239 // Load material for preview
1240 auto& am = ctx.get_cached<asset_manager>();
1241 auto material_asset = am.get_asset<material>(material_path);
1242
1243 // Store original materials for restoration
1244 auto& model_comp = entity.get<model_component>();
1245 auto& model = model_comp.get_model();
1246
1247 // Save original materials if not already previewing this entity
1249 {
1250 original_materials.clear();
1251 for(const auto& mat : model.get_materials())
1252 {
1253 original_materials.push_back(mat);
1254 }
1255 }
1256
1257 // Apply preview material
1258 class model model_copy = model;
1259 for(size_t i = 0; i < model_copy.get_materials().size(); ++i)
1260 {
1261 model_copy.set_material(material_asset, i);
1262 }
1263 model_comp.set_model(model_copy);
1264
1265 // Update preview state
1266 is_previewing = true;
1268 }
1269}
1270
1271} // namespace
1272
1273// ============================================================================
1274// Scene Panel Implementation
1275// ============================================================================
1276
1278 : entity_panel(parent, name)
1279 , fullscreen_name_(get_name() + " (Fullscreen)")
1280{
1281}
1282
1283auto scene_panel::get_window_name() const -> const char*
1284{
1285 return is_fullscreen() ? fullscreen_name_.c_str() : panel_base::get_window_name();
1286}
1287
1289{
1290 ctx.add<gizmo_registry>();
1291 gizmos_.init(ctx);
1292
1293 // create editor camera
1294 defaults::create_camera_entity(ctx, panel_scene_, "Scene Camera");
1295
1296 // create center entity
1297 panel_scene_.create_entity();
1298}
1299
1301{
1302 gizmos_.deinit(ctx);
1303 ctx.remove<gizmo_registry>();
1304}
1305
1306// ============================================================================
1307// Drag Selection Helper Functions
1308// ============================================================================
1309
1310void scene_panel::handle_drag_selection(rtti::context& ctx, const camera& camera, editing_manager& em)
1311{
1312 if(!ImGui::IsAnyItemHovered() && !ImGuizmo::IsOver() && ImGui::IsWindowHovered())
1313 {
1314 if(ImGui::IsMouseClicked(ImGuiMouseButton_Left))
1315 {
1316 drag_start_pos_ = ImGui::GetMousePos();
1317 }
1318 // Check if we should start drag selection
1319 if(ImGui::IsMouseDragging(ImGuiMouseButton_Left))
1320 {
1321 // Only start drag selection if we're not clicking on anything and not over a gizmo
1322 if(!is_drag_selecting_)
1323 {
1324 is_drag_selecting_ = true;
1325 }
1326 }
1327 }
1328
1329 // Update drag selection
1330 if(is_drag_selecting_)
1331 {
1332 drag_current_pos_ = ImGui::GetMousePos();
1333
1334 // End drag selection on mouse release
1335 if(ImGui::IsMouseReleased(ImGuiMouseButton_Left))
1336 {
1337 auto& pick_manager = ctx.get_cached<picking_manager>();
1338 pick_manager.cancel_pick();
1339 is_drag_selecting_ = false;
1340 }
1341 }
1342}
1343
1344void scene_panel::draw_drag_selection_rect(const ImVec2& start_pos, const ImVec2& current_pos)
1345{
1346 if(start_pos.x == current_pos.x && start_pos.y == current_pos.y)
1347 {
1348 return;
1349 }
1350
1351 ImDrawList* draw_list = ImGui::GetWindowDrawList();
1352
1353 // Calculate the rectangle bounds
1354 ImVec2 min_pos(std::min(start_pos.x, current_pos.x), std::min(start_pos.y, current_pos.y));
1355 ImVec2 max_pos(std::max(start_pos.x, current_pos.x), std::max(start_pos.y, current_pos.y));
1356
1357 // Draw the selection rectangle
1358 ImU32 rect_color = ImGui::GetColorU32(ImVec4(0.2f, 0.6f, 1.0f, 0.3f)); // Semi-transparent blue
1359 ImU32 border_color = ImGui::GetColorU32(ImVec4(0.2f, 0.6f, 1.0f, 0.8f)); // Solid blue border
1360
1361 // Fill rectangle
1362 draw_list->AddRectFilled(min_pos, max_pos, rect_color);
1363
1364 // Border
1365 draw_list->AddRect(min_pos, max_pos, border_color, 0.0f, 0, 2.0f);
1366}
1367
1368void scene_panel::handle_prefab_mode_changes(rtti::context& ctx)
1369{
1370 auto& em = ctx.get_cached<editing_manager>();
1371 bool is_prefab_mode = em.is_prefab_mode();
1372
1373 // Detect when we enter prefab mode
1374 if(is_prefab_mode && !was_prefab_mode_)
1375 {
1376 std::array<entt::handle, 1> entities = {em.prefab_entity};
1378 }
1379 // Detect when we exit prefab mode (e.g., due to external factors)
1380 else if(!is_prefab_mode && was_prefab_mode_)
1381 {
1382 // If we're exiting prefab mode and auto-save is enabled, save changes
1383 if(auto_save_prefab_ && em.edited_prefab)
1384 {
1385 em.save_prefab_changes(ctx);
1386 }
1387 }
1388
1389 was_prefab_mode_ = is_prefab_mode;
1390}
1391
1393{
1394 handle_prefab_mode_changes(ctx);
1395
1396 if(!is_visible())
1397 {
1398 return;
1399 }
1400
1401 auto& path = ctx.get_cached<rendering_system>();
1402 path.on_frame_update(panel_scene_, dt);
1403
1404 auto& em = ctx.get_cached<editing_manager>();
1405 if(em.is_prefab_mode())
1406 {
1407 path.on_frame_update(em.prefab_scene, dt);
1408 }
1409}
1410
1412{
1413 auto& path = ctx.get_cached<rendering_system>();
1414 path.on_frame_before_render(panel_scene_, dt);
1415
1416 auto& em = ctx.get_cached<editing_manager>();
1417 if(em.is_prefab_mode())
1418 {
1419 path.on_frame_before_render(em.prefab_scene, dt);
1420 }
1421}
1422
1423auto scene_panel::begin_panel(const char* name, ImGuiWindowFlags flags) -> bool
1424{
1425 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
1426 bool open = panel_base::begin_panel(name, flags);
1427 ImGui::PopStyleVar();
1428 return open;
1429}
1430
1431void scene_panel::draw_scene(rtti::context& ctx, delta_t dt)
1432{
1433 auto& em = ctx.get_cached<editing_manager>();
1434 auto& path = ctx.get_cached<rendering_system>();
1435 auto handle = get_camera();
1436
1437 if(!handle)
1438 {
1439 return;
1440 }
1441
1442 auto& camera_comp = handle.get<camera_component>();
1443
1444 // Use the appropriate scene based on mode
1445 auto target_scene = em.get_active_scene(ctx);
1446
1447 if(target_scene)
1448 {
1449 path.render_scene(handle, camera_comp, *target_scene, dt, false);
1450 gizmos_.on_frame_render(ctx, *target_scene, handle, dd_2d_);
1451 }
1452}
1453
1455{
1456 if(m_skip_frames_ > 0)
1457 {
1458 m_skip_frames_--;
1459 return;
1460 }
1461
1462 if(!is_visible())
1463 {
1464 auto handle = get_camera();
1465 if(handle)
1466 {
1467 auto& camera_comp = handle.get<camera_component>();
1468 camera_comp.get_render_view() = {};
1469 }
1470 return;
1471 }
1472 draw_scene(ctx, dt);
1473}
1474
1476{
1477 // m_skip_frames_ = 100;
1478}
1479
1480auto scene_panel::get_window_flags() const -> ImGuiWindowFlags
1481{
1482 ImGuiWindowFlags flags = ImGuiWindowFlags_MenuBar;
1483 if(is_fullscreen())
1484 {
1485 flags |= ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
1486 ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBringToFrontOnFocus;
1487 }
1488 return flags;
1489}
1490
1491auto scene_panel::get_camera() -> entt::handle
1492{
1493 entt::handle camera_entity;
1494 panel_scene_.registry->view<camera_component>().each(
1495 [&](auto e, auto&& camera_comp)
1496 {
1497 camera_entity = panel_scene_.create_handle(e);
1498 });
1499 return camera_entity;
1500}
1501
1503{
1504 auto camera = get_camera();
1505 if(camera)
1506 {
1507 camera.destroy();
1508 }
1509 defaults::create_camera_entity(ctx, panel_scene_, "Scene Camera");
1510}
1511
1512auto scene_panel::get_center() -> entt::handle
1513{
1514 entt::handle center_entity;
1515
1516 auto view = panel_scene_.registry->view<root_component>(entt::exclude<camera_component>);
1517 view.each(
1518 [&](auto e, auto&& comp)
1519 {
1520 center_entity = panel_scene_.create_handle(e);
1521 });
1522 return center_entity;
1523}
1524
1526{
1527 return auto_save_prefab_;
1528}
1529
1530// ============================================================================
1531// UI Drawing Functions
1532// ============================================================================
1533
1534void scene_panel::draw_prefab_mode_header(rtti::context& ctx)
1535{
1536 auto& em = ctx.get_cached<editing_manager>();
1537
1538 if(!em.is_prefab_mode())
1539 {
1540 return;
1541 }
1542
1543 ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_ButtonActive));
1544 if(ImGui::Button(ICON_MDI_KEYBOARD_RETURN " Back to Scene"))
1545 {
1546 em.exit_prefab_mode(ctx,
1547 auto_save_prefab_ ? editing_manager::save_option::yes
1549 }
1550 ImGui::PopStyleColor();
1551
1552 if(em.edited_prefab)
1553 {
1554 ImGui::SameLine();
1555 ImGui::Text("Editing Prefab: %s", fs::path(em.edited_prefab.id()).filename().string().c_str());
1556
1557 ImGui::SameLine();
1558 if(ImGui::Button("Save"))
1559 {
1560 em.save_prefab_changes(ctx);
1561 }
1562
1563 ImGui::SameLine();
1564 ImGui::Checkbox("Auto Save", &auto_save_prefab_);
1565 ImGui::SetItemTooltipEx("%s", "Automatically save changes when exiting prefab mode");
1566 }
1567
1568 ImGui::Separator();
1569}
1570
1571void scene_panel::draw_transform_tools(editing_manager& em)
1572{
1573 ImGui::SetNextWindowViewportToCurrent();
1574
1575 if(ImGui::MenuItem(ICON_MDI_CURSOR_MOVE, nullptr, em.operation == ImGuizmo::OPERATION::TRANSLATE))
1576 {
1577 em.operation = ImGuizmo::OPERATION::TRANSLATE;
1578 }
1579 ImGui::SetItemTooltipEx("%s", "Translate Tool");
1580 ImGui::SetNextWindowViewportToCurrent();
1581
1582 if(ImGui::MenuItem(ICON_MDI_ROTATE_3D_VARIANT, nullptr, em.operation == ImGuizmo::OPERATION::ROTATE))
1583 {
1584 em.operation = ImGuizmo::OPERATION::ROTATE;
1585 }
1586 ImGui::SetItemTooltipEx("%s", "Rotate Tool");
1587 ImGui::SetNextWindowViewportToCurrent();
1588
1589 if(ImGui::MenuItem(ICON_MDI_RELATIVE_SCALE, nullptr, em.operation == ImGuizmo::OPERATION::SCALE))
1590 {
1591 em.operation = ImGuizmo::OPERATION::SCALE;
1592 em.mode = ImGuizmo::MODE::LOCAL;
1593 }
1594 ImGui::SetItemTooltipEx("%s", "Scale Tool");
1595 ImGui::SetNextWindowViewportToCurrent();
1596
1597 if(ImGui::MenuItem(ICON_MDI_MOVE_RESIZE, nullptr, em.operation == ImGuizmo::OPERATION::UNIVERSAL))
1598 {
1599 em.operation = ImGuizmo::OPERATION::UNIVERSAL;
1600 em.mode = ImGuizmo::MODE::LOCAL;
1601 }
1602 ImGui::SetItemTooltipEx("%s", "Transform Tool");
1603}
1604
1605void scene_panel::draw_gizmo_pivot_mode_menu(bool& gizmo_at_center)
1606{
1607 auto icon = gizmo_at_center ? ICON_MDI_SET_CENTER "Center" ICON_MDI_ARROW_DOWN_BOLD
1609 ImGui::SetNextWindowViewportToCurrent();
1610
1611 if(ImGui::BeginMenu(icon))
1612 {
1613 if(ImGui::MenuItem(ICON_MDI_SET_CENTER "Center", nullptr, gizmo_at_center))
1614 {
1615 gizmo_at_center = true;
1616 }
1617 ImGui::SetItemTooltipEx("%s",
1618 "The tool handle is placed at the center\n"
1619 "of the selections' pivots.");
1620
1621 if(ImGui::MenuItem(ICON_MDI_ROTATE_3D "Pivot", nullptr, !gizmo_at_center))
1622 {
1623 gizmo_at_center = false;
1624 }
1625 ImGui::SetItemTooltipEx("%s",
1626 "The tool handle is placed at the\n"
1627 "active object's pivot point.");
1628
1629 ImGui::EndMenu();
1630 }
1631 ImGui::SetItemTooltipEx("%s", "Tool's Handle Position");
1632}
1633
1634void scene_panel::draw_coordinate_system_menu(editing_manager& em)
1635{
1636 auto icon = em.mode == ImGuizmo::MODE::LOCAL ? ICON_MDI_CUBE "Local" ICON_MDI_ARROW_DOWN_BOLD
1638 ImGui::SetNextWindowViewportToCurrent();
1639
1640 if(ImGui::BeginMenu(icon))
1641 {
1642 if(ImGui::MenuItem(ICON_MDI_CUBE "Local",
1643 ImGui::GetKeyName(shortcuts::toggle_local_global),
1644 em.mode == ImGuizmo::MODE::LOCAL))
1645 {
1646 em.mode = ImGuizmo::MODE::LOCAL;
1647 }
1648 ImGui::SetItemTooltipEx("%s", "Local Coordinate System");
1649
1650 if(ImGui::MenuItem(ICON_MDI_WEB "Global", nullptr, em.mode == ImGuizmo::MODE::WORLD))
1651 {
1652 em.mode = ImGuizmo::MODE::WORLD;
1653 }
1654 ImGui::SetItemTooltipEx("%s", "Global Coordinate System");
1655
1656 ImGui::EndMenu();
1657 }
1658 ImGui::SetItemTooltipEx("%s", "Tool's Coordinate System");
1659}
1660
1661void scene_panel::draw_grid_settings_menu(editing_manager& em)
1662{
1663 ImGui::SetNextWindowViewportToCurrent();
1664
1665 if(ImGui::MenuItem(ICON_MDI_GRID, nullptr, em.show_grid))
1666 {
1667 em.show_grid = !em.show_grid;
1668 }
1669 ImGui::SetItemTooltipEx("%s", "Show/Hide Grid");
1670 ImGui::SetNextWindowViewportToCurrent();
1671
1672 if(ImGui::BeginMenu(ICON_MDI_ARROW_DOWN_BOLD, em.show_grid))
1673 {
1674 ImGui::PushItemWidth(100.0f);
1675
1676 ImGui::TextUnformatted("Grid Visual");
1677 ImGui::LabelText("Plane", "%s", "X Z");
1678 ImGui::KnobSliderScalarT("Opacity", &em.grid_data.opacity, 0.0f, 1.0f);
1679 ImGui::Checkbox("Depth Aware", &em.grid_data.depth_aware);
1680 ImGui::SetItemTooltipEx("%s", "Grid is depth aware.");
1681
1682 ImGui::PopItemWidth();
1683
1684 ImGui::EndMenu();
1685 }
1686 ImGui::SetItemTooltipEx("%s", "Grid Properties");
1687}
1688
1689void scene_panel::draw_gizmos_settings_menu(editing_manager& em)
1690{
1691 ImGui::SetNextWindowViewportToCurrent();
1692
1693 if(ImGui::MenuItem(ICON_MDI_SELECTION_MARKER, nullptr, em.show_icon_gizmos))
1694 {
1695 em.show_icon_gizmos = !em.show_icon_gizmos;
1696 }
1697 ImGui::SetItemTooltipEx("%s", "Show/Hide Gizmos");
1698 ImGui::PushID("Billboard Gizmos");
1699 ImGui::SetNextWindowViewportToCurrent();
1700
1701 if(ImGui::BeginMenu(ICON_MDI_ARROW_DOWN_BOLD, em.show_icon_gizmos))
1702 {
1703 ImGui::PushItemWidth(100.0f);
1704
1705 ImGui::TextUnformatted("Gizmos Visual");
1706 ImGui::KnobSliderScalarT("Opacity", &em.billboard_data.opacity, 0.0f, 1.0f);
1707 ImGui::KnobSliderScalarT("Size", &em.billboard_data.size, 0.1f, 1.0f);
1708
1709 ImGui::Checkbox("Depth Aware", &em.billboard_data.depth_aware);
1710 ImGui::SetItemTooltipEx("%s", "Gizmos are depth aware.");
1711
1712 ImGui::Separator();
1713 ImGui::TextUnformatted("Billboard Filters");
1714 ImGui::Checkbox("Camera", &em.billboard_data.show_camera);
1715 ImGui::Checkbox("Light", &em.billboard_data.show_light);
1716 ImGui::Checkbox("Reflection Probe", &em.billboard_data.show_reflection_probe);
1717 ImGui::Checkbox("Audio Source", &em.billboard_data.show_audio_source);
1718 ImGui::Checkbox("Particle Emitter", &em.billboard_data.show_particle_emitter);
1719
1720 ImGui::Separator();
1721 ImGui::TextUnformatted("Selection Gizmos");
1722 ImGui::Checkbox("Selection Outline", &em.gizmos.show_selection_outline);
1723 ImGui::Checkbox("Selection Wireframe", &em.gizmos.show_selection_wireframe);
1724 ImGui::SetItemTooltipEx("%s", "Draw a vertex-pulling wireframe overlay on top of the selected entity's mesh.");
1725 if(em.gizmos.show_selection_wireframe)
1726 {
1727 ImGui::ColorEdit4("Wireframe Color",
1728 math::value_ptr(em.gizmos.selection_wireframe_color),
1729 ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_AlphaPreviewHalf | ImGuiColorEditFlags_NoInputs);
1730 ImGui::KnobSliderScalarT("Wireframe Thickness",
1731 &em.gizmos.selection_wireframe_thickness,
1732 0.5f,
1733 5.0f,
1734 "%.2f px");
1735 }
1736 ImGui::Checkbox("Camera Gizmos", &em.gizmos.show_camera);
1737 ImGui::Checkbox("Model Gizmos", &em.gizmos.show_model);
1738 ImGui::Checkbox("Light Gizmos", &em.gizmos.show_light);
1739 ImGui::Checkbox("Reflection Probe Gizmos", &em.gizmos.show_reflection_probe);
1740 ImGui::Checkbox("Volume Gizmos", &em.gizmos.show_volume);
1741 ImGui::Checkbox("Text Gizmos", &em.gizmos.show_text);
1742 ImGui::Checkbox("Particle Emitter Gizmos", &em.gizmos.show_particle_emitter);
1743 ImGui::Checkbox("Component Gizmos", &em.gizmos.show_component_gizmos);
1744
1745 ImGui::Separator();
1746 ImGui::TextUnformatted("Model Details");
1747 ImGui::Checkbox("World Bounds & LOD", &em.gizmos.show_model_bounds);
1748 ImGui::Checkbox("World Submesh Bounds & LOD", &em.gizmos.show_model_submesh_bounds);
1749
1750 ImGui::Separator();
1751 ImGui::TextUnformatted("Particle Emitter Details");
1752 ImGui::Checkbox("Bounds", &em.gizmos.show_particle_emitter_bounds);
1753 ImGui::Checkbox("Shape", &em.gizmos.show_particle_emitter_shape);
1754 ImGui::Checkbox("Direction", &em.gizmos.show_particle_emitter_direction);
1755
1756 ImGui::PopItemWidth();
1757
1758 ImGui::EndMenu();
1759 }
1760 ImGui::SetItemTooltipEx("%s", "Gizmos Properties");
1761 ImGui::PopID();
1762}
1763
1764void scene_panel::draw_visualization_menu()
1765{
1766 ImGui::SetNextWindowViewportToCurrent();
1767
1769 {
1770 ImGui::RadioButton("Full", &visualize_passes_, -1);
1771 ImGui::RadioButton("Base Color", &visualize_passes_, 0);
1772 ImGui::RadioButton("Diffuse Color", &visualize_passes_, 1);
1773 ImGui::RadioButton("Specular Color", &visualize_passes_, 2);
1774 ImGui::RadioButton("Radiance", &visualize_passes_, 3);
1775 ImGui::RadioButton("Irradiance", &visualize_passes_, 4);
1776 ImGui::RadioButton("Ambient Occlusion", &visualize_passes_, 5);
1777 ImGui::RadioButton("Normals (World Space)", &visualize_passes_, 6);
1778 ImGui::RadioButton("Roughness", &visualize_passes_, 7);
1779 ImGui::RadioButton("Metalness", &visualize_passes_, 8);
1780 ImGui::RadioButton("Emissive Color", &visualize_passes_, 9);
1781 ImGui::RadioButton("Subsurface Color", &visualize_passes_, 10);
1782 ImGui::RadioButton("Depth", &visualize_passes_, 11);
1783 ImGui::RadioButton("SSIL", &visualize_passes_, 12);
1784 ImGui::RadioButton("Radiance Alpha", &visualize_passes_, 13);
1785 ImGui::RadioButton("Specular Occlusion", &visualize_passes_, 14);
1786
1787 ImGui::EndMenu();
1788 }
1789 ImGui::SetItemTooltipEx("%s", "Visualize Render Passes");
1790}
1791
1792void scene_panel::draw_snapping_menu(editing_manager& em)
1793{
1794 ImGui::SetNextWindowViewportToCurrent();
1795
1796 if(ImGui::BeginMenu(ICON_MDI_GRID_LARGE ICON_MDI_ARROW_DOWN_BOLD))
1797 {
1798 ImGui::PushItemWidth(200.0f);
1799 ImGui::DragVecN("Translation Snap",
1800 ImGuiDataType_Float,
1801 math::value_ptr(em.snap_data.translation_snap),
1802 em.snap_data.translation_snap.length(),
1803 0.5f,
1804 nullptr,
1805 nullptr,
1806 "%.2f");
1807
1808 ImGui::DragFloat("Rotation Degree Snap", &em.snap_data.rotation_degree_snap);
1809 ImGui::DragFloat("Scale Snap", &em.snap_data.scale_snap);
1810 ImGui::PopItemWidth();
1811 ImGui::EndMenu();
1812 }
1813 ImGui::SetItemTooltipEx("%s", "Snapping Properties");
1814}
1815
1816void scene_panel::draw_inverse_kinematics_menu(editing_manager& em)
1817{
1818 ImGui::SetNextWindowViewportToCurrent();
1819
1820 if(ImGui::BeginMenu(ICON_MDI_CRANE ICON_MDI_ARROW_DOWN_BOLD))
1821 {
1822 ImGui::PushItemWidth(200.0f);
1823 ImGui::InputInt("Inverse Kinematic Nodes", &em.ik_data.num_nodes);
1824
1825 ImGui::Separator();
1826 ImGui::TextUnformatted("Inverse Kinematic Shortcuts");
1827 ImGui::Text("CCD: %s", shortcuts::get_shortcut_name(shortcuts::ik_ccd).c_str());
1828 ImGui::Text("Fabrik: %s", shortcuts::get_shortcut_name(shortcuts::ik_fabrik).c_str());
1829 ImGui::Text("Two Bone: %s", shortcuts::get_shortcut_name(shortcuts::ik_two_bone).c_str());
1830 ImGui::PopItemWidth();
1831 ImGui::EndMenu();
1832 }
1833 ImGui::SetItemTooltipEx("%s", "Inverse Kinematic Properties");
1834}
1835
1836void scene_panel::draw_camera_settings_menu(rtti::context& ctx)
1837{
1838 ImGui::SetNextWindowSizeConstraints({}, {400.0f, ImGui::GetContentRegionAvail().y});
1839 ImGui::SetNextWindowViewportToCurrent();
1840
1841 if(ImGui::BeginMenu(ICON_MDI_CAMERA ICON_MDI_ARROW_DOWN_BOLD))
1842 {
1843 if(ImGui::Button("Reset Camera"))
1844 {
1845 reset_camera(ctx);
1846 }
1847
1848 ImGui::SetItemTooltipEx("%s", "Reset the Scene camera.");
1849
1850 entt::meta_any cam = get_camera();
1851 inspect_var(ctx, cam, make_proxy(cam));
1852
1853 ImGui::EndMenu();
1854 }
1855 ImGui::SetItemTooltipEx("%s", "Settings for the Scene view camera.");
1856}
1857
1858void scene_panel::handle_viewport_interaction(rtti::context& ctx, const camera& camera, editing_manager& em)
1859{
1860 bool is_using = ImGuizmo::IsUsing();
1861 bool is_over = ImGuizmo::IsOver();
1862 bool is_entity = em.is_selected_type<entt::handle>();
1863
1864 // Handle drag selection
1865 handle_drag_selection(ctx, camera, em);
1866
1868 {
1869 auto& pick_manager = ctx.get_cached<picking_manager>();
1870 auto bounds = get_drag_selection_bounds();
1871
1872 math::vec2 area = {bounds.second.x - bounds.first.x, bounds.second.y - bounds.first.y};
1873 // Calculate the center of the drag selection area
1874 math::vec2 center = {bounds.first.x + area.x * 0.5f, bounds.first.y + area.y * 0.5f};
1875
1876 pick_manager.request_pick(camera, em.get_select_mode(), center, area);
1877 }
1878
1879 // Only handle single-click selection if we're not drag selecting
1880 if(ImGui::IsItemClicked(ImGuiMouseButton_Left) && !is_using && !is_drag_selecting_)
1881 {
1882 bool is_over_active_gizmo = is_over && is_entity;
1883 if(!is_over_active_gizmo)
1884 {
1885 ImGui::SetWindowFocus();
1886 auto& pick_manager = ctx.get_cached<picking_manager>();
1887 auto pos = ImGui::GetMousePos();
1888
1889 pick_manager.request_pick(camera, em.get_select_mode(), {pos.x, pos.y});
1890 }
1891 }
1892
1893 if(ImGui::IsItemClicked(ImGuiMouseButton_Middle) || ImGui::IsItemClicked(ImGuiMouseButton_Right))
1894 {
1895 ImGui::SetWindowFocus();
1896 ImGui::SetMouseCursor(ImGuiMouseCursor_None);
1897 }
1898
1899 if(ImGui::IsItemReleased(ImGuiMouseButton_Middle) || ImGui::IsItemReleased(ImGuiMouseButton_Right))
1900 {
1901 ImGui::SetMouseCursor(ImGuiMouseCursor_Arrow);
1902 }
1903}
1904
1905void scene_panel::handle_keyboard_shortcuts(editing_manager& em)
1906{
1907 bool is_delete_pressed = ImGui::IsItemKeyPressed(shortcuts::delete_item);
1908 bool is_focus_pressed = ImGui::IsItemKeyPressed(shortcuts::focus_selected);
1909 bool is_duplicate_pressed = ImGui::IsItemCombinationKeyPressed(shortcuts::duplicate_item);
1910
1911 auto selections = em.try_get_selections_as_copy<entt::handle>();
1912
1913 if(is_delete_pressed)
1914 {
1915 delete_entities(selections);
1916 }
1917
1918 if(is_focus_pressed)
1919 {
1920 focus_entities(get_camera(), selections);
1921 }
1922
1923 if(is_duplicate_pressed)
1924 {
1925 duplicate_entities(selections);
1926 }
1927}
1928
1929void scene_panel::setup_camera_viewport(camera_component& camera_comp, const ImVec2& size, const ImVec2& pos)
1930{
1931 if(size.x > 0 && size.y > 0)
1932 {
1933 camera_comp.get_camera().set_viewport_pos({static_cast<int32_t>(pos.x), static_cast<int32_t>(pos.y)});
1934 camera_comp.set_viewport_size({static_cast<std::uint32_t>(size.x), static_cast<std::uint32_t>(size.y)});
1935 }
1936}
1937
1938void scene_panel::draw_scene_viewport(rtti::context& ctx, const ImVec2& size, const ImVec2& pos)
1939{
1940 auto& em = ctx.get_cached<editing_manager>();
1941
1942 auto camera_entity = get_camera();
1943 if(!camera_entity)
1944 {
1945 return;
1946 }
1947 auto& camera_comp = camera_entity.get<camera_component>();
1948 const auto& camera = camera_comp.get_camera();
1949 const auto& rview = camera_comp.get_render_view();
1950 const auto& obuffer = rview.fbo_safe_get("OBUFFER");
1951
1952 ImGui::SetCursorScreenPos(pos);
1953 if(obuffer && obuffer->get_attachment_count() > 0)
1954 {
1955 const auto& tex = obuffer->get_texture(0);
1957 }
1958 else
1959 {
1960 ImGui::Dummy(size);
1961 }
1962
1963 if(em.is_prefab_mode())
1964 {
1965 ImVec2 padding(2.0f, 2.0f);
1966 auto color = ImGui::GetColorU32(ImGuiCol_ButtonActive);
1967 auto min = ImGui::GetItemRectMin() - padding;
1968 auto max = ImGui::GetItemRectMax() + padding;
1969 ImGui::RenderFocusFrame(min, max, color, 4.0f);
1970 }
1971
1972 handle_viewport_interaction(ctx, camera, em);
1973 handle_keyboard_shortcuts(em);
1974
1975 manipulation_gizmos(gizmo_at_center_, was_using_gizmo_, get_center(), camera_entity, em);
1976 handle_camera_movement(camera_entity, move_dir_, acceleration_, is_dragging_);
1977 draw_selected_camera(ctx, camera_entity, size);
1978
1979 // {
1980
1981 // const float& ref_font_scale = ImGui::GetCurrentContext()->FontSizeBase;
1982
1983 // ImGui::ImCoolBarConfig config;
1984 // config.normal_size = 50.0f;
1985 // config.hovered_size = 80.0f;
1986 // config.anchor = ImVec2(0.5f, 1.0f);
1987 // config.anchor_area = ImRect(pos, pos + size);
1988
1989 // if (ImGui::BeginCoolBar("CoolBarMainWin", ImCoolBarFlags_Horizontal, config))
1990 // {
1991 // if (ImGui::CoolBarItemGuard item{"imgui_demo"})
1992 // {
1993 // ImVec2 size(item.ctx.width, 0);
1994 // ImGui::Button("Play", size);
1995 // }
1996
1997 // if (ImGui::CoolBarItemGuard item{"imgui_demo1"})
1998 // {
1999 // ImVec2 size(item.ctx.width, 0);
2000 // ImGui::Button("Pause", size);
2001 // }
2002
2003 // if (ImGui::CoolBarItemGuard item{"imgui_demo2"})
2004 // {
2005 // ImVec2 size(item.ctx.width, 0);
2006 // ImGui::Button("Stop", size);
2007 // }
2008
2009 // if (ImGui::CoolBarItemGuard item{"imgui_demo3"})
2010 // {
2011 // ImVec2 size(item.ctx.width, 0);
2012 // ImGui::Button("Stop & Reset", size);
2013 // }
2014 // ImGui::EndCoolBar();
2015 // }
2016 // }
2017 // Draw drag selection rectangle if active
2018 if(is_drag_selecting_)
2019 {
2020 draw_drag_selection_rect(drag_start_pos_, drag_current_pos_);
2021 }
2022
2023 camera_comp.get_pipeline_data().get_pipeline()->set_debug_pass(visualize_passes_);
2024
2025 auto window = ImGui::GetCurrentWindow();
2026 auto draw_list = window->DrawList;
2027 auto clip_rect = window->ClipRect;
2028 clip_rect.Expand(-ImGui::GetStyle().FramePadding);
2029 draw_list->PushClipRect(clip_rect.Min, clip_rect.Max);
2030
2031 auto callbacks = std::move(dd_2d_.callbacks);
2032 for(const auto& callback : callbacks)
2033 {
2034 callback();
2035 }
2036
2037 draw_list->PopClipRect();
2038}
2039
2040void scene_panel::draw_ui(rtti::context& ctx)
2041{
2042 draw_menubar(ctx);
2043
2044 if(m_skip_frames_ > 0)
2045 {
2046 auto spinner_size = ImGui::GetContentRegionAvail().y * 0.2f;
2047
2048 ImGui::SetCursorPosY(ImGui::GetContentRegionAvail().y * 0.5f - spinner_size * 0.5f);
2049 ImGui::AlignedItem(0.5f,
2050 ImGui::GetContentRegionAvail().x,
2051 spinner_size,
2052 [spinner_size]()
2053 {
2054 ImSpinner::Spinner<ImSpinner::SpinnerTypeT::e_st_eclipse>("spinner",
2055 ImSpinner::Radius{spinner_size * 0.5f},
2056 ImSpinner::Thickness{6.0f},
2057 ImSpinner::Color{ImSpinner::white},
2058 ImSpinner::Speed{6.0f});
2059
2060 });
2061
2062 return;
2063 }
2064
2065 auto camera_entity = get_camera();
2066
2067 bool has_edit_camera = camera_entity && camera_entity.all_of<transform_component, camera_component>();
2068
2069 if(!has_edit_camera)
2070 {
2071 return;
2072 }
2073
2074 auto avail = ImGui::GetContentRegionAvail();
2075 if(avail.x <= 0 || avail.y <= 0)
2076 {
2077 return;
2078 }
2079
2080 // Determine the fitted viewport rectangle for the editor camera based on
2081 // the resolution preset. We always fit the aspect within the available
2082 // area so that picking and gizmos map 1:1 between screen pixels and the
2083 // camera viewport. If no resolution is configured, the panel falls back
2084 // to the full available area.
2085 ImVec2 view_size = avail;
2086 if(const auto* current_res = viewport_resolution::get_resolution(ctx, current_resolution_index_))
2087 {
2088 view_size = viewport_resolution::compute_fitted_size(*current_res, avail);
2089 }
2090
2091 const auto avail_origin = ImGui::GetCursorScreenPos();
2092 const ImVec2 view_pos(avail_origin.x + (avail.x - view_size.x) * 0.5f,
2093 avail_origin.y + (avail.y - view_size.y) * 0.5f);
2094
2095 auto& camera_comp = camera_entity.get<camera_component>();
2096
2097 setup_camera_viewport(camera_comp, view_size, view_pos);
2098 draw_scene_viewport(ctx, view_size, view_pos);
2099 process_drag_drop_target(ctx, camera_comp);
2100
2101 const auto& pstats = camera_comp.get_pipeline_data().get_pipeline()->get_stats();
2102 viewport_stats_overlay::draw(pstats, stats_overlay_state_, "scene");
2103
2104 if(stats_overlay_state_.open_profiler_requested)
2105 {
2106 stats_overlay_state_.open_profiler_requested = false;
2108 }
2109}
2110
2111void scene_panel::draw_menubar(rtti::context& ctx)
2112{
2113 auto& em = ctx.get_cached<editing_manager>();
2114
2115 if(ImGui::BeginMenuBar())
2116 {
2117 // Apply Unity-like styling - more prominent, tab-like appearance
2118 ImGui::PushStyleColor(ImGuiCol_Header, ImGui::GetStyleColorVec4(ImGuiCol_TabSelected));
2119 ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImGui::GetStyleColorVec4(ImGuiCol_TabSelected));
2120 ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImGui::GetStyleColorVec4(ImGuiCol_TabSelectedOverline));
2121
2122 draw_prefab_mode_header(ctx);
2123 viewport_resolution::draw_menu(ctx, current_resolution_index_);
2124 draw_transform_tools(em);
2125 draw_gizmo_pivot_mode_menu(gizmo_at_center_);
2126 draw_coordinate_system_menu(em);
2127 draw_grid_settings_menu(em);
2128 draw_gizmos_settings_menu(em);
2129 draw_visualization_menu();
2130 draw_snapping_menu(em);
2131 draw_inverse_kinematics_menu(em);
2132 draw_camera_settings_menu(ctx);
2133 viewport_stats_overlay::draw_stats_toggle(stats_overlay_state_);
2134
2135 ImGui::PopStyleColor(3);
2136
2137 ImGui::EndMenuBar();
2138 }
2139}
2140
2141void scene_panel::draw_selected_camera(rtti::context& ctx, entt::handle editor_camera, const ImVec2& size)
2142{
2143 auto& em = ctx.get_cached<editing_manager>();
2144
2145 if(auto sel = em.try_get_active_selection_as<entt::handle>())
2146 {
2147 if(sel && sel->valid() && sel->all_of<camera_component>())
2148 {
2149 const auto& selected_camera = sel->get<camera_component>();
2150
2151 auto& game_panel = parent_->get_game_panel();
2152 game_panel.set_visible_force(true);
2153
2154 const auto& camera = selected_camera.get_camera();
2155 const auto& render_view = selected_camera.get_render_view();
2156 const auto& viewport_size = camera.get_viewport_size();
2157 const auto& obuffer = render_view.fbo_safe_get("OBUFFER");
2158
2159 if(!obuffer)
2160 {
2161 return;
2162 }
2163 float factor = std::min(size.x / float(viewport_size.width), size.y / float(viewport_size.height)) / 4.0f;
2164 ImVec2 bounds(viewport_size.width * factor, viewport_size.height * factor);
2165 // Calculate the position to place the image
2166 ImVec2 image_pos =
2167 ImVec2(ImGui::GetWindowSize().x - 20 - bounds.x, ImGui::GetWindowSize().y - 20 - bounds.y);
2168
2169 // Move the cursor to the calculated position
2170 ImGui::SetCursorPos(image_pos);
2171
2172 const auto& tex = obuffer->get_texture(0);
2173 ImGui::Image(ImGui::ToId(tex), bounds);
2174
2175 if(ImGui::IsKeyChordPressed(shortcuts::snap_scene_camera_to_selected_camera))
2176 {
2177 auto& transform = editor_camera.get<transform_component>();
2178 auto& transform_selected = sel->get<transform_component>();
2179 transform_selected.set_transform_global(transform.get_transform_global());
2180 }
2181 }
2182 }
2183}
2184
2185} // namespace unravel
const btCollisionObject * object
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.
void set_scale(const vec3_t &scale) noexcept
Set the scale component.
auto get_scale() const noexcept -> const vec3_t &
Get the scale component.
auto get_translation() const noexcept -> const vec3_t &
Get the translation component.
auto get_rotation() const noexcept -> const quat_t &
Get the rotation component.
auto get_skew() const noexcept -> const vec3_t &
Get the skew component.
void set_rotation(const quat_t &rotation) noexcept
Set the rotation component.
void set_position(const vec3_t &position) noexcept
Set the position component.
Class that contains core camera data, used for rendering and other purposes.
auto get_render_view() -> gfx::render_view &
Gets the render view.
Class representing a camera. Contains functionality for manipulating and updating a camera....
Definition camera.h:62
void focus_entities(entt::handle camera, const std::vector< entt::handle > &entities)
void duplicate_entities(const std::vector< entt::handle > &entities)
imgui_panels * parent_
void delete_entities(const std::vector< entt::handle > &entities)
void on_frame_render(rtti::context &ctx, scene &scn, entt::handle camera_entity, dd_2d_raii &dd_2d)
auto init(rtti::context &ctx) -> bool
auto deinit(rtti::context &ctx) -> bool
auto get_profiler_timeline_panel() -> profiler_timeline_panel &
Definition panel.cpp:236
auto get_game_panel() -> game_panel &
Definition panel.cpp:206
virtual auto get_window_name() const -> const char *
virtual bool begin_panel(const char *name, ImGuiWindowFlags flags)
auto is_fullscreen() const -> bool
Definition panel_base.h:22
auto is_visible() const -> bool
Definition panel_base.h:17
Base class for different rendering paths in the ACE framework.
void on_frame_update(scene &scn, delta_t dt)
Prepares the scene for rendering.
void on_frame_before_render(scene &scn, delta_t dt)
void on_frame_render(rtti::context &ctx, delta_t dt)
void on_frame_update(rtti::context &ctx, delta_t dt)
void init(rtti::context &ctx)
auto get_auto_save_prefab() const -> bool
auto is_drag_selection_active() const -> bool
Definition scene_panel.h:38
auto get_drag_selection_bounds() const -> std::pair< ImVec2, ImVec2 >
Definition scene_panel.h:40
auto get_window_flags() const -> ImGuiWindowFlags override
scene_panel(imgui_panels *parent, const char *name)
void on_frame_before_render(rtti::context &ctx, delta_t dt)
auto get_center() -> entt::handle
void reset_camera(rtti::context &ctx)
Recreate the Scene panel editor camera at the default pose (UI "Reset Camera").
auto get_window_name() const -> const char *override
void deinit(rtti::context &ctx)
auto get_camera() -> entt::handle
static auto get_top_level_entities(const std::vector< entt::handle > &list) -> std::vector< entt::handle >
float y
float x
size< std::uint32_t > usize32_t
std::chrono::duration< float > delta_t
size< float > fsize_t
const char * icon
math::vec3 position
Definition defaults.cpp:52
uint16_t view
std::string name
Definition hub.cpp:33
#define ICON_MDI_ROTATE_3D
#define ICON_MDI_ARROW_DOWN_BOLD
#define ICON_MDI_CAMERA
#define ICON_MDI_CRANE
#define ICON_MDI_SELECTION_MARKER
#define ICON_MDI_GRID_LARGE
#define ICON_MDI_ROTATE_3D_VARIANT
#define ICON_MDI_WEB
#define ICON_MDI_DRAWING_BOX
#define ICON_MDI_GRID
#define ICON_MDI_CURSOR_MOVE
#define ICON_MDI_SET_CENTER
#define ICON_MDI_KEYBOARD_RETURN
#define ICON_MDI_RELATIVE_SCALE
#define ICON_MDI_MOVE_RESIZE
#define ICON_MDI_CUBE
#define APPLOG_WARNING(...)
Definition logging.h:19
texture_job_type type
const aiScene * scene
void Image(gfx::texture_handle _handle, uint8_t _mip, uint8_t _flags, const ImVec2 &_size, const ImVec2 &_uv0=ImVec2(0.0f, 0.0f), const ImVec2 &_uv1=ImVec2(1.0f, 1.0f))
Definition imgui.h:175
ImTextureID ToId(gfx::texture_handle _handle, uint8_t _mip=0, uint8_t _flags=IMGUI_FLAGS_ALPHA_BLEND)
Definition imgui.h:103
auto get_suported_formats() -> const std::vector< std::string > &
path convert_to_protocol(const path &_path)
Oposite of the resolve_protocol this function tries to convert to protocol path from an absolute one.
bgfx::Transform transform
Definition graphics.h:42
transform_t< float > transform
void stop_all(const std::string &scope)
Stops all actions within the specified scope.
Definition seq.cpp:160
constexpr ImGuiKey camera_forward
Definition shortcuts.h:28
constexpr ImGuiKey camera_left
Definition shortcuts.h:30
constexpr ImGuiKey modifier_camera_speed_boost
Definition shortcuts.h:11
constexpr ImGuiKey delete_item
Definition shortcuts.h:35
const ImGuiKeyCombination duplicate_item
Definition shortcuts.h:36
constexpr ImGuiKey camera_right
Definition shortcuts.h:31
constexpr ImGuiKey modifier_drop_align_to_surface
Definition shortcuts.h:14
constexpr ImGuiKeyChord snap_scene_camera_to_selected_camera
Definition shortcuts.h:70
constexpr ImGuiKey ik_ccd
Definition shortcuts.h:60
constexpr ImGuiKey camera_backward
Definition shortcuts.h:29
auto get_shortcut_name(const ImGuiKeyCombination &shortcut) -> std::string
Definition shortcuts.h:76
constexpr ImGuiKey ik_two_bone
Definition shortcuts.h:62
constexpr ImGuiKey ik_fabrik
Definition shortcuts.h:61
constexpr ImGuiKey toggle_local_global
Definition shortcuts.h:69
constexpr ImGuiKey focus_selected
Definition shortcuts.h:66
constexpr ImGuiKey bounds_tool
Definition shortcuts.h:57
constexpr ImGuiKey rotate_tool
Definition shortcuts.h:54
constexpr ImGuiKey modifier_snapping
Definition shortcuts.h:12
constexpr ImGuiKey universal_tool
Definition shortcuts.h:56
constexpr ImGuiKey scale_tool
Definition shortcuts.h:55
constexpr ImGuiKey move_tool
Definition shortcuts.h:53
auto get_resolution(rtti::context &ctx, int index) -> const settings::resolution_settings::resolution *
Get the resolution preset at the given index (clamped against the configured presets)....
auto compute_fitted_size(const settings::resolution_settings::resolution &res, ImVec2 avail_size) -> ImVec2
Compute a viewport size that always fits within avail_size by aspect ratio (ignoring any fixed pixel ...
auto draw_menu(rtti::context &ctx, int &current_index) -> bool
Draw the resolution selection drop-down for the current menu bar. The selection is read and written t...
void draw(const rendering::pipeline_stats &pstats, state &overlay_state, const char *id)
Draw a statistics overlay child window at the top-right corner of the current ImGui window....
void draw_stats_toggle(state &overlay_state)
Draw a right-aligned "Stats" toggle button for the menu bar. Toggles the overlay visibility on click.
auto ik_set_position_two_bone(entt::handle end_effector, const math::vec3 &target, const math::vec3 &pole, float weight, float soften) -> bool
auto ik_set_position_fabrik(entt::handle end_effector, const math::vec3 &target, const math::vec3 &pole, size_t num_bones_in_chain, int max_iterations, float threshold) -> bool
auto ik_set_position_ccd(entt::handle end_effector, const math::vec3 &target, const math::vec3 &pole, size_t num_bones_in_chain, int max_iterations, float threshold) -> bool
auto make_proxy(entt::meta_any &var, const std::string &name) -> meta_any_proxy
Creates a simple proxy for direct variable access.
auto inspect_var(rtti::context &ctx, entt::meta_any &var, const meta_any_proxy &var_proxy, const var_info &info, const entt::meta_custom &custom) -> inspect_result
Main entry point for inspecting any variable with automatic type resolution.
std::vector< math::color > color
std::vector< float > scale
std::vector< math::quat > rotation
entt::handle entity
fsize_t new_area
std::vector< asset_handle< material > > original_materials
bool is_previewing
std::string current_drag_material
math::vec3 initial_position
entt::handle last_preview_entity
fsize_t initial_area
math::vec3 new_position
Thread-safe handle to an asset.
auto get_cached() -> T &
Definition context.hpp:49
auto add(Args &&... args) -> T &
Definition context.hpp:16
void remove()
Definition context.hpp:78
T width
Definition basetypes.hpp:55
T height
Definition basetypes.hpp:56
std::vector< std::function< void()> > callbacks
Definition gizmo.h:16
static auto create_mesh_entity_at(rtti::context &ctx, scene &scn, const std::string &key, const camera &cam, math::vec2 pos, bool align_to_surface=false) -> entt::handle
Creates a mesh entity at a specified position.
Definition defaults.cpp:897
static void focus_camera_on_entities(entt::handle camera, hpp::span< const entt::handle > entities, float duration=0.0f)
Focuses a camera on a specified entity with a timed transition.
static auto create_camera_entity(rtti::context &ctx, scene &scn, const std::string &name) -> entt::handle
Creates a camera entity.
Definition defaults.cpp:999
static auto create_prefab_at(rtti::context &ctx, scene &scn, const std::string &key, const camera &cam, math::vec2 pos, bool align_to_surface=false) -> entt::handle
Creates a prefab entity at a specified position.
Definition defaults.cpp:842
auto is_prefab_mode() const -> bool
auto get_active_scene(rtti::context &ctx) -> scene *
void exit_prefab_mode(rtti::context &ctx, save_option save_changes=save_option::prompt)
void save_prefab_changes(rtti::context &ctx)
asset_handle< prefab > edited_prefab
Root component structure for the ACE framework, serves as the base component.
auto create_entity(const std::string &tag={}, entt::handle parent={}) -> entt::handle
Creates an entity in the scene with an optional tag and parent.
Definition scene.cpp:360
gfx::uniform_handle handle
Definition uniform.cpp:9
float size