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"
4#include "imgui_widgets/utils.h"
10#include <editor/shortcuts.h>
11
16#include <engine/ecs/ecs.h>
23
26
31#include <seq/seq.h>
32#include <imgui/imgui.h>
33#include <imgui/imgui_internal.h>
34#include <imgui_widgets/gizmo.h>
35#include <imgui_widgets/imcoolbar.h>
36
37
38#include <algorithm>
40#include <logging/logging.h>
41#include <numeric>
42
43namespace unravel
44{
45namespace
46{
47
48// Forward declarations
49void restore_original_materials(entt::handle entity, const std::vector<asset_handle<material>>& original_materials);
50void apply_material_preview(rtti::context& ctx, entt::handle entity, const std::string& material_path,
52 bool& is_previewing);
53void manipulation_gizmos(bool& gizmo_at_center, bool& was_using_gizmo, entt::handle center, entt::handle editor_camera, editing_manager& em);
54void handle_camera_movement(entt::handle camera, math::vec3& move_dir, float& acceleration, bool& is_dragging);
55
56// Material preview state
57struct material_preview_state
58{
59 entt::handle last_preview_entity;
60 std::vector<asset_handle<material>> original_materials;
61 bool is_previewing = false;
63};
64
65// Global preview state
66static material_preview_state g_preview_state;
67
68// Check if a material is being dragged and get its path
69auto check_material_drag(std::string& out_material_path) -> bool
70{
71 for(const auto& type : ex::get_suported_formats<material>())
72 {
73 auto payload = ImGui::GetDragDropPayload();
74 if(payload && payload->IsDataType(type.c_str()))
75 {
76 if(payload->Data)
77 {
78 out_material_path = std::string(reinterpret_cast<const char*>(payload->Data), std::size_t(payload->DataSize));
79 out_material_path = fs::convert_to_protocol(fs::path(out_material_path)).generic_string();
80 return true;
81 }
82 }
83 }
84 return false;
85}
86
87// Handle material preview during drag
88void handle_material_preview(rtti::context& ctx, const camera_component& camera_comp, const std::string& material_path)
89{
90 auto& pick_manager = ctx.get_cached<picking_manager>();
91
92 // Check if the material path changed
93 if(g_preview_state.current_drag_material != material_path)
94 {
95 // Restore previous preview if there was one
96 if(g_preview_state.is_previewing && g_preview_state.last_preview_entity)
97 {
98 restore_original_materials(g_preview_state.last_preview_entity, g_preview_state.original_materials);
99 }
100
101 // Update current material
102 g_preview_state.current_drag_material = material_path;
103 g_preview_state.is_previewing = false;
104 }
105
106 // Query for entity under cursor to show preview
107 // picking_manager handles throttling internally
108 auto cursor_pos = ImGui::GetMousePos();
109 pick_manager.query_pick(
110 math::vec2{cursor_pos.x, cursor_pos.y},
111 camera_comp.get_camera(),
112 [&ctx, material_path](entt::handle entity, const math::vec2& screen_pos) {
113 apply_material_preview(ctx, entity, material_path, g_preview_state.last_preview_entity,
114 g_preview_state.original_materials, g_preview_state.is_previewing);
115 }
116 );
117}
118
119// Handle material drop on entity
120void handle_material_drop(rtti::context& ctx, const camera_component& camera_comp, const std::string& material_path)
121{
122 auto cursor_pos = ImGui::GetMousePos();
123 auto& pick_manager = ctx.get_cached<picking_manager>();
124 auto& am = ctx.get_cached<asset_manager>();
125 auto& em = ctx.get_cached<editing_manager>();
126
127 // Load the material asset
128 auto material_asset = am.get_asset<material>(material_path);
129 bool force = true;
130
131 // Use the picking system to query what's under the cursor
132 pick_manager.query_pick(
133 math::vec2{cursor_pos.x, cursor_pos.y},
134 camera_comp.get_camera(),
135 [material_asset, &em](entt::handle entity, const math::vec2& screen_pos) {
136 // Check if entity has a model component
137 if(entity && entity.all_of<model_component>())
138 {
139 // Get current materials to store as old state
140 auto& model_comp = entity.get<model_component>();
141 const auto& current_model = model_comp.get_model();
142 auto old_materials = current_model.get_materials();
143
144 em.push_undo_stack_enabled(true);
145
146 // Create and execute the action
147 em.queue_action<entity_set_materials_action_t>({}, entity, old_materials, material_asset);
148
149 em.pop_undo_stack_enabled();
150 }
151 else if(entity)
152 {
153 APPLOG_WARNING("Cannot apply material to entity without model_component");
154 }
155 }, force
156 );
157}
158
159// Handle mesh drop at cursor position
160void handle_mesh_drop(rtti::context& ctx, const camera_component& camera_comp, const std::string& mesh_path)
161{
162 auto cursor_pos = ImGui::GetMousePos();
163 auto& em = ctx.get_cached<editing_manager>();
164
165 em.queue_action("Drop Mesh",
166 [&ctx, camera = camera_comp.get_camera(), mesh_path, cursor_pos]()
167 {
168 auto& em = ctx.get_cached<editing_manager>();
169 auto target_scene = em.get_active_scene(ctx);
170
171 auto object = defaults::create_mesh_entity_at(ctx,
172 *target_scene,
173 mesh_path,
174 camera,
175 math::vec2{cursor_pos.x, cursor_pos.y});
176 em.select(object);
177 });
178}
179
180// Handle prefab drop at cursor position
181void handle_prefab_drop(rtti::context& ctx, const camera_component& camera_comp, const std::string& prefab_path)
182{
183 auto cursor_pos = ImGui::GetMousePos();
184 auto& em = ctx.get_cached<editing_manager>();
185
186 em.queue_action("Drop Prefab",
187 [&ctx, camera = camera_comp.get_camera(), prefab_path, cursor_pos]()
188 {
189 auto& em = ctx.get_cached<editing_manager>();
190 auto target_scene = em.get_active_scene(ctx);
191
192 auto object = defaults::create_prefab_at(ctx,
193 *target_scene,
194 prefab_path,
195 camera,
196 math::vec2{cursor_pos.x, cursor_pos.y});
197 em.select(object);
198 });
199}
200
201// Reset material preview state
202void reset_preview_state()
203{
204 if(g_preview_state.is_previewing && g_preview_state.last_preview_entity)
205 {
206 restore_original_materials(g_preview_state.last_preview_entity, g_preview_state.original_materials);
207 g_preview_state.is_previewing = false;
208 g_preview_state.last_preview_entity = {};
209 g_preview_state.original_materials.clear();
210 g_preview_state.current_drag_material.clear();
211 }
212}
213
214// ============================================================================
215// Camera Movement Helper Functions
216// ============================================================================
217
218auto calculate_movement_speed(float base_speed, bool speed_boost_active, float multiplier) -> float
219{
220 float movement_speed = base_speed;
221 if(speed_boost_active)
222 {
223 movement_speed *= multiplier;
224 }
225 return movement_speed;
226}
227
228void handle_middle_mouse_panning(entt::handle camera, float movement_speed, float dt)
229{
230 if(!ImGui::IsMouseDown(ImGuiMouseButton_Middle))
231 {
232 return;
233 }
234
235 auto delta_move = ImGui::GetIO().MouseDelta;
236 auto& transform = camera.get<transform_component>();
237
238 if(delta_move.x != 0)
239 {
240 transform.move_by_local({-1 * delta_move.x * movement_speed * dt, 0.0f, 0.0f});
241 }
242 if(delta_move.y != 0)
243 {
244 transform.move_by_local({0.0f, delta_move.y * movement_speed * dt, 0.0f});
245 }
246}
247
248auto collect_movement_input(float& max_hold, bool& is_dragging) -> math::vec3
249{
250 math::vec3 movement_input{0.0f, 0.0f, 0.0f};
251
252 auto is_key_down = [&](ImGuiKey k) -> bool
253 {
254 bool down = ImGui::IsKeyDown(k);
255 if(down)
256 {
257 auto data = ImGui::GetKeyData(ImGui::GetCurrentContext(), k);
258 max_hold = std::max(max_hold, data->DownDuration);
259 }
260 return down;
261 };
262
263 if(is_dragging)
264 {
265 float move_speed = 4.0f;
266 if(is_key_down(shortcuts::camera_forward))
267 {
268 movement_input.z += move_speed;
269 }
270 if(is_key_down(shortcuts::camera_backward))
271 {
272 movement_input.z -= move_speed;
273 }
274 if(is_key_down(shortcuts::camera_right))
275 {
276 movement_input.x += move_speed;
277 }
278 if(is_key_down(shortcuts::camera_left))
279 {
280 movement_input.x -= move_speed;
281 }
282
283 }
284
285
286 auto delta_wheel = ImGui::GetIO().MouseWheel;
287 if(delta_wheel != 0)
288 {
289 movement_input.z += 15.0f * delta_wheel;
290 }
291
292 return movement_input;
293}
294
295auto handle_mouse_rotation(entt::handle camera, float rotation_speed, bool is_dragging)-> bool
296{
297 if(!is_dragging)
298 {
299 return false;
300 }
301
302 auto delta_move = ImGui::GetIO().MouseDelta;
303 auto& transform = camera.get<transform_component>();
304
305 if(delta_move.x != 0.0f || delta_move.y != 0.0f)
306 {
307 float dx = delta_move.x * rotation_speed;
308 float dy = delta_move.y * rotation_speed;
309
310 transform.rotate_by_euler_global({0.0f, dx, 0.0f});
311 transform.rotate_by_euler_local({dy, 0.0f, 0.0f});
312 return true;
313 }
314 return false;
315}
316
317void update_movement_acceleration(math::vec3& move_dir, float& acceleration, const math::vec3& input, bool any_input)
318{
319 if(any_input)
320 {
321 if(acceleration < 0.1f)
322 {
323 acceleration = 0.1f;
324 }
325 acceleration *= 1.5f;
326 acceleration = std::min(1.0f, acceleration);
327 move_dir.x = input.x;
328 move_dir.z = input.z;
329 }
330 else if(acceleration > 0.0001f)
331 {
332 acceleration *= 0.85f;
333 }
334}
335
336void apply_movement(entt::handle camera,
337 const math::vec3& move_dir,
338 float movement_speed,
339 float acceleration,
340 float max_hold,
341 float hold_speed,
342 float dt)
343{
344 if(acceleration <= 0.0001f)
345 {
346 return;
347 }
348
349 auto& transform = camera.get<transform_component>();
350
351 if(!math::any(math::epsilonNotEqual(move_dir, math::vec3(0.0f, 0.0f, 0.0f), math::epsilon<float>())))
352 {
353 return;
354 }
355
356 float adjusted_dt = dt;
357 if(math::epsilonNotEqual(move_dir.x, 0.0f, math::epsilon<float>()) ||
358 math::epsilonNotEqual(move_dir.z, 0.0f, math::epsilon<float>()))
359 {
360 adjusted_dt += max_hold * hold_speed;
361 }
362
363 auto length = math::length(move_dir);
364 transform.move_by_local(math::normalize(move_dir) * length * movement_speed * adjusted_dt * acceleration);
365
366
367}
368
369
370void handle_camera_movement(entt::handle camera, math::vec3& move_dir, float& acceleration, bool& is_dragging)
371{
372 if(!ImGui::IsWindowFocused())
373 {
374 return;
375 }
376
377 if(!ImGui::IsWindowHovered() && !is_dragging)
378 {
379 return;
380 }
381
382 // Movement parameters
383 constexpr float base_movement_speed = 2.0f;
384 constexpr float rotation_speed = 0.1f;
385 constexpr float speed_multiplier = 5.0f;
386 constexpr float hold_speed = 0.1f;
387 constexpr float fixed_dt = 0.0166f; // Fixed delta time
388
389 bool speed_boost_active = ImGui::IsKeyDown(shortcuts::modifier_camera_speed_boost);
390 float movement_speed = calculate_movement_speed(base_movement_speed, speed_boost_active, speed_multiplier);
391
392 // Handle middle mouse panning
393 handle_middle_mouse_panning(camera, movement_speed, fixed_dt);
394
395
396 // Handle right mouse dragging
397 is_dragging = ImGui::IsMouseDown(ImGuiMouseButton_Right);
398
399 if(is_dragging)
400 {
401 ImGui::WrapMousePos();
402 if(ImGui::IsWindowHovered())
403 {
404 ImGui::SetMouseCursor(ImGuiMouseCursor_Cross);
405 }
406 }
407
408 // Collect movement input (works for both dragging and non-dragging)
409 float max_hold = 0.0f;
410 math::vec3 movement_input = collect_movement_input(max_hold, is_dragging);
411 bool any_input = math::any(math::epsilonNotEqual(movement_input, math::vec3(0.0f), math::epsilon<float>()));
412
413 // Handle mouse rotation (only when dragging)
414 bool any_rotation = handle_mouse_rotation(camera, rotation_speed, is_dragging);
415
416 // Process camera input with acceleration
417 update_movement_acceleration(move_dir, acceleration, movement_input, any_input);
418
419 if(any_input || any_rotation)
420 {
421 seq::scope::stop_all("camera_focus");
422 }
423
424 if(acceleration > 0.0001f)
425 {
426 // Continue movement with deceleration when not actively inputting
427 apply_movement(camera, move_dir, movement_speed, acceleration, 0.0f, hold_speed, fixed_dt);
428 }
429}
430
431// ============================================================================
432// Gizmo Manipulation Helper Functions
433// ============================================================================
434
435void setup_gizmo_context(const camera_component& camera_comp)
436{
437 auto p = ImGui::GetItemRectMin();
438 auto s = ImGui::GetItemRectSize();
439 const auto& camera = camera_comp.get_camera();
440
441 ImGuizmo::SetDrawlist(ImGui::GetWindowDrawList());
442 ImGuizmo::SetRect(p.x, p.y, s.x, s.y);
443 ImGuizmo::SetOrthographic(camera.get_projection_mode() == projection_mode::orthographic);
444}
445
446void handle_view_manipulator(entt::handle editor_camera, const camera_component& camera_comp)
447{
448 auto p = ImGui::GetItemRectMin();
449 auto s = ImGui::GetItemRectSize();
450 const auto& camera = camera_comp.get_camera();
451 auto& camera_trans = editor_camera.get<transform_component>();
452
453 auto view = camera.get_view().get_matrix();
454 static const ImVec2 view_gizmo_sz(100.0f, 100.0f);
455
456 ImGuizmo::ViewManipulate(value_ptr(view),
457 1.0f,
458 p + ImVec2(s.x - view_gizmo_sz.x, 0.0f),
459 view_gizmo_sz,
460 ImGui::GetColorU32(ImVec4(0.0f, 0.0f, 0.0f, 0.0f)));
461
462 math::transform tr = glm::inverse(view);
463 camera_trans.set_rotation_local(tr.get_rotation());
464}
465
466void handle_gizmo_shortcuts(editing_manager& em)
467{
468 if(ImGui::IsMouseDown(ImGuiMouseButton_Right) || ImGui::IsAnyItemActive() || ImGuizmo::IsUsing())
469 {
470 return;
471 }
472
473 if(ImGui::IsKeyPressed(shortcuts::universal_tool))
474 {
475 em.operation = ImGuizmo::OPERATION::UNIVERSAL;
476 }
477 if(ImGui::IsKeyPressed(shortcuts::move_tool))
478 {
479 em.operation = ImGuizmo::OPERATION::TRANSLATE;
480 }
481 if(ImGui::IsKeyPressed(shortcuts::rotate_tool))
482 {
483 em.operation = ImGuizmo::OPERATION::ROTATE;
484 }
485 if(ImGui::IsKeyPressed(shortcuts::scale_tool))
486 {
487 em.operation = ImGuizmo::OPERATION::SCALE;
488 }
489 if(ImGui::IsKeyPressed(shortcuts::bounds_tool))
490 {
491 em.operation = ImGuizmo::OPERATION::BOUNDS;
492 }
493}
494
495void setup_snap_data(editing_manager& em, float*& snap, float*& bounds_snap, float bounds_snap_data[3])
496{
497 snap = nullptr;
498 bounds_snap = nullptr;
499
500 if(!ImGui::IsKeyDown(shortcuts::modifier_snapping))
501 {
502 return;
503 }
504
505 bounds_snap = bounds_snap_data;
506
507 if(em.operation == ImGuizmo::OPERATION::TRANSLATE)
508 {
509 snap = &em.snap_data.translation_snap[0];
510 }
511 else if(em.operation == ImGuizmo::OPERATION::ROTATE)
512 {
513 snap = &em.snap_data.rotation_degree_snap;
514 }
515 else if(em.operation == ImGuizmo::OPERATION::SCALE)
516 {
517 snap = &em.snap_data.scale_snap;
518 }
519}
520
521auto calculate_center_pivot(const std::vector<entt::handle*>& selections) -> math::vec3
522{
523 math::vec3 pivot{0.0f, 0.0f, 0.0f};
524 size_t points = 0;
525
526 for(const auto& sel : selections)
527 {
528 if(sel && *sel)
529 {
530 auto& sel_transform_comp = sel->get<transform_component>();
531 pivot += sel_transform_comp.get_position_global();
532 points++;
533 }
534 }
535
536 if(points > 0)
537 {
538 pivot /= static_cast<float>(points);
539 }
540
541 return pivot;
542}
543
544void setup_gizmo_pivot(bool gizmo_at_center,
545 entt::handle center,
546 const std::vector<entt::handle*>& selections,
547 entt::handle active_selection)
548{
549 auto& center_transform_comp = center.get<transform_component>();
550 auto& transform_comp = active_selection.get<transform_component>();
551
552 auto trans_global = transform_comp.get_transform_global();
553 center_transform_comp.set_transform_global(trans_global);
554
555 if(gizmo_at_center)
556 {
557 math::vec3 pivot = calculate_center_pivot(selections);
558 center_transform_comp.set_position_global(pivot);
559 }
560}
561
562auto handle_text_component_bounds_manipulation(entt::handle active_selection,
563 const camera_component& camera_comp,
564 editing_manager& em,
565 float* snap,
566 float bounds_snap_data[3],
567 float* bounds_snap) -> bool
568{
569 auto text_comp = active_selection.try_get<text_component>();
570 if(!text_comp)
571 {
572 return false;
573 }
574
575 auto area = text_comp->get_area();
576 if(!area.is_valid())
577 {
578 return false;
579 }
580
581 auto& transform_comp = active_selection.get<transform_component>();
582 const auto& camera = camera_comp.get_camera();
583
584 // Store initial state for undo/redo
585 fsize_t initial_area = area;
586 math::vec3 initial_position = transform_comp.get_position_global();
587
588 // Local-space half-extents = 0.5 in X & Y, zero thickness in Z
589 float bounds[6] = {
590 -0.5f,
591 -0.5f,
592 0.0f, // min x, y, z
593 0.5f,
594 0.5f,
595 0.0f // max x, y, z
596 };
597
598 math::transform model_tr;
599 model_tr.set_position(transform_comp.get_position_global());
600 model_tr.set_rotation(transform_comp.get_rotation_global());
601 model_tr.set_scale(math::vec3(area.width, area.height, 1.0f));
602
603 math::mat4 output = model_tr;
604
605 int movetype = ImGuizmo::Manipulate(camera.get_view(),
606 camera.get_projection(),
607 ImGuizmo::BOUNDS,
608 em.mode,
609 math::value_ptr(output),
610 nullptr,
611 snap,
612 bounds,
613 bounds_snap);
614
615 if(movetype != ImGuizmo::MT_NONE)
616 {
617 math::transform output_tr = output;
618 const auto& scale = output_tr.get_scale();
619 const auto& trans = output_tr.get_translation();
620
621 // Create new area and position
622 fsize_t new_area{scale.x, scale.y};
623 math::vec3 new_position = trans;
624
625 // Create composite action with both text bounds and transform changes
626 auto composite_action = std::make_shared<composite_action_t>();
627
628 // Add text bounds action
629 composite_action->add_sub_action(std::make_shared<entity_set_text_bounds_action_t>(
630 active_selection, initial_area, new_area));
631
632 // Add global transform action for the center entity
633 composite_action->add_sub_action(std::make_shared<transform_move_global_action_t>(
634 active_selection, initial_position, new_position));
635
636 // Execute the composite action
637 em.push_undo_stack_enabled(true);
638 em.do_action("Text Bounds Manipulation", composite_action);
639 em.pop_undo_stack_enabled();
640
641 return true;
642 }
643
644 return false;
645}
646
647void handle_inverse_kinematics(entt::handle selection, entt::handle center, editing_manager& em)
648{
649 if(ImGui::IsAnyItemActive())
650 {
651 return;
652 }
653
654 auto& center_transform_comp = center.get<transform_component>();
655
656 if(ImGui::IsKeyDown(shortcuts::ik_ccd))
657 {
658 ik_set_position_ccd(selection, center_transform_comp.get_position_global(), em.ik_data.num_nodes);
659 }
660 else if(ImGui::IsKeyDown(shortcuts::ik_fabrik))
661 {
662 ik_set_position_fabrik(selection, center_transform_comp.get_position_global(), em.ik_data.num_nodes);
663 }
664}
665
666void apply_transform_delta_to_selections(const std::vector<entt::handle>& top_level_selections,
667 const std::vector<entt::handle>& original_parents,
668 const math::mat4& center_delta)
669{
670 for(size_t i = 0; i < top_level_selections.size(); ++i)
671 {
672 auto& sel = top_level_selections[i];
673 if(!sel)
674 {
675 continue;
676 }
677
678 auto& sel_transform_comp = sel.get<transform_component>();
679
680 // "old_global" is the entity's transform BEFORE we moved the center.
681 math::mat4 old_global = sel_transform_comp.get_transform_global();
682
683 // Compute the new global by applying the same delta we applied to the center
684 math::mat4 new_global = center_delta * old_global;
685
686 // Convert that new global transform back into local space for the entity's
687 // actual/original parent (which we never physically changed).
688 entt::handle original_parent = original_parents[i];
689 if(original_parent)
690 {
691 const auto& parent_transform = original_parent.get<transform_component>();
692 math::mat4 parent_global = parent_transform.get_transform_global();
693 math::mat4 parent_global_inv = glm::inverse(parent_global);
694
695 math::mat4 new_local = parent_global_inv * new_global;
696 sel_transform_comp.set_transform_local(math::transform(new_local));
697 }
698 else
699 {
700 // If no valid parent, the new local == new global
701 sel_transform_comp.set_transform_local(math::transform(new_global));
702 }
703 }
704}
705
706auto handle_standard_gizmo_manipulation(entt::handle active_selection,
707 entt::handle center,
708 const camera_component& camera_comp,
709 editing_manager& em,
710 float* snap) -> int
711{
712 auto& center_transform_comp = center.get<transform_component>();
713 const auto& camera = camera_comp.get_camera();
714
715 math::mat4 output = center_transform_comp.get_transform_global();
716 math::mat4 output_delta;
717
718 ImGuizmo::AllowAxisFlip(false);
719
720 int movetype = ImGuizmo::Manipulate(camera.get_view(),
721 camera.get_projection(),
722 em.operation,
723 em.mode,
724 math::value_ptr(output),
725 math::value_ptr(output_delta),
726 snap,
727 nullptr,
728 nullptr);
729
730 if(movetype != ImGuizmo::MT_NONE)
731 {
732 math::transform delta = output_delta;
733
734 auto perspective = center_transform_comp.get_perspective_local();
735 auto skew = center_transform_comp.get_skew_local();
736
737 if(ImGuizmo::IsScaleType(movetype))
738 {
739 center_transform_comp.scale_by_local(delta.get_scale());
740 }
741 if(ImGuizmo::IsRotateType(movetype))
742 {
743 center_transform_comp.rotate_by_global(delta.get_rotation());
744 }
745 if(ImGuizmo::IsTranslateType(movetype))
746 {
747 center_transform_comp.move_by_global(delta.get_translation());
748 }
749
750 center_transform_comp.set_skew_local(skew);
751 center_transform_comp.set_perspective_local(perspective);
752 }
753
754 return movetype;
755}
756
757void manipulation_gizmos(bool& gizmo_at_center, bool& was_using_gizmo, entt::handle center, entt::handle editor_camera, editing_manager& em)
758{
759 auto& camera_trans = editor_camera.get<transform_component>();
760 auto& camera_comp = editor_camera.get<camera_component>();
761
762 setup_gizmo_context(camera_comp);
763 handle_view_manipulator(editor_camera, camera_comp);
764 handle_gizmo_shortcuts(em);
765
766 auto active_sel = em.try_get_active_selection_as<entt::handle>();
767 if(!active_sel || !active_sel->valid() || !active_sel->all_of<transform_component>())
768 {
769 return;
770 }
771
772 float bounds_snap_data[3] = {0.1f, 0.1f, 0.0f};
773 float* snap = nullptr;
774 float* bounds_snap = nullptr;
775
776 setup_snap_data(em, snap, bounds_snap, bounds_snap_data);
777
778 auto selections = em.try_get_selections_as<entt::handle>();
779 setup_gizmo_pivot(gizmo_at_center, center, selections, *active_sel);
780
781 // Store initial center transform before any manipulation
782 auto& center_transform_comp = center.get<transform_component>();
783 math::mat4 center_initial_global = center_transform_comp.get_transform_global();
784
785 // Convert pointer vector to value vector for get_top_level_entities
786 std::vector<entt::handle> selection_values;
787 selection_values.reserve(selections.size());
788 for(const auto& sel : selections)
789 {
790 if(sel && *sel)
791 {
792 selection_values.emplace_back(*sel);
793 }
794 }
795
796 auto top_level_selections = transform_component::get_top_level_entities(selection_values);
797
798 std::vector<entt::handle> original_parents;
799 std::vector<math::transform> original_transforms;
800 original_parents.reserve(top_level_selections.size());
801 original_transforms.reserve(top_level_selections.size());
802
803 // Store initial state before any manipulation
804 for(const auto& sel : top_level_selections)
805 {
806 if(sel)
807 {
808 auto& sel_transform_comp = sel.get<transform_component>();
809 original_parents.emplace_back(sel_transform_comp.get_parent());
810 original_transforms.emplace_back(sel_transform_comp.get_transform_local());
811 }
812 }
813
814 bool bounds_changed = false;
815 // Handle text component bounds manipulation for non-rotate/scale operations
816 if(em.operation != ImGuizmo::ROTATE && em.operation != ImGuizmo::SCALE && top_level_selections.size() == 1)
817 {
818 bounds_changed = handle_text_component_bounds_manipulation(*active_sel,
819 camera_comp,
820 em,
821 snap,
822 bounds_snap_data,
823 bounds_snap);
824 }
825
826 int movetype = ImGuizmo::MT_NONE;
827 // Handle standard gizmo manipulation for non-bounds operations
828 if(em.operation != ImGuizmo::BOUNDS)
829 {
830 movetype = handle_standard_gizmo_manipulation(*active_sel, center, camera_comp, em, snap);
831 }
832
833 // After all manipulations, compute the delta and apply it to all selections
834 math::mat4 center_final_global = center_transform_comp.get_transform_global();
835 math::mat4 center_delta = center_final_global * glm::inverse(center_initial_global);
836
837
838 auto batch_action = std::make_shared<composite_action_t>();
839 // Apply transforms and create undoable actions
840 for(size_t i = 0; i < top_level_selections.size(); ++i)
841 {
842 auto& sel = top_level_selections[i];
843 if(sel)
844 {
845 handle_inverse_kinematics(sel, center, em);
846 if(ImGui::IsKeyDown(shortcuts::ik_ccd) || ImGui::IsKeyDown(shortcuts::ik_fabrik))
847 {
848 // Skip standard transform if using IK
849 continue;
850 }
851
852 // Apply transform delta to each selection
853 auto& sel_transform_comp = sel.get<transform_component>();
854 math::mat4 old_global = sel_transform_comp.get_transform_global();
855 math::mat4 new_global = center_delta * old_global;
856
857 // Convert to local space based on parent
858 entt::handle original_parent = original_parents[i];
859 math::transform new_local_transform;
860
861 if(original_parent)
862 {
863 const auto& parent_transform = original_parent.get<transform_component>();
864 math::mat4 parent_global = parent_transform.get_transform_global();
865 math::mat4 parent_global_inv = glm::inverse(parent_global);
866 math::mat4 new_local = parent_global_inv * new_global;
867 new_local_transform = math::transform(new_local);
868 }
869 else
870 {
871 // If no valid parent, the new local == new global
872 new_local_transform = math::transform(new_global);
873 }
874
875 // Apply the new transform
876 sel_transform_comp.set_transform_local(new_local_transform);
877
878 // Create undoable action if there was a manipulation
879 if(movetype != ImGuizmo::MT_NONE)
880 {
881 // if(ImGui::IsMouseReleased(ImGuiMouseButton_Left))
882 {
883 bool position = ImGuizmo::IsTranslateType(movetype);
884 bool rotation = ImGuizmo::IsRotateType(movetype);
885 bool scale = ImGuizmo::IsScaleType(movetype);
886 bool skew = false;
887
888 if(top_level_selections.size() > 1)
889 {
890 position = true;
891 }
892
893 // batch_action->add_sub_action(std::make_shared<transform_manipulation_action_t>(
894 // sel,
895 // original_transforms[i],
896 // new_local_transform,
897 // position, rotation, scale, skew));
898
899 auto composite_action = std::make_shared<composite_action_t>();
900
901
902 if(position)
903 {
904 composite_action->add_sub_action(std::make_shared<transform_move_action_t>(
905 sel,
906 original_transforms[i].get_position(),
907 new_local_transform.get_position()));
908 }
909 if(rotation)
910 {
911 composite_action->add_sub_action(std::make_shared<transform_rotate_action_t>(
912 sel,
913 original_transforms[i].get_rotation(),
914 new_local_transform.get_rotation()));
915 }
916 if(scale)
917 {
918 composite_action->add_sub_action(std::make_shared<transform_scale_action_t>(
919 sel,
920 original_transforms[i].get_scale(),
921 new_local_transform.get_scale()));
922 }
923 if(skew)
924 {
925 composite_action->add_sub_action(std::make_shared<transform_skew_action_t>(
926 sel,
927 original_transforms[i].get_skew(),
928 new_local_transform.get_skew()));
929 }
930
931
932 batch_action->add_sub_action(composite_action);
933 }
934
935 }
936 }
937 }
938
939 if(batch_action->sub_actions.size() > 0)
940 {
941 em.push_undo_stack_enabled(true);
942 em.do_action("Transform Manipulation", batch_action);
943 em.pop_undo_stack_enabled();
944 }
945}
946
947// Process drag and drop for assets
948void process_drag_drop_target(rtti::context& ctx, const camera_component& camera_comp)
949{
950 if(!ImGui::BeginDragDropTarget())
951 {
952 // If we were previewing and drag ended without dropping, restore materials
953 reset_preview_state();
954 return;
955 }
956
957 // Set cursor based on whether payload is being accepted
958 if(ImGui::IsDragDropPayloadBeingAccepted())
959 {
960 ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
961
962 // Check for material drag and show preview
963 std::string material_path;
964 if(check_material_drag(material_path))
965 {
966 handle_material_preview(ctx, camera_comp, material_path);
967 }
968 }
969 else
970 {
971 ImGui::SetMouseCursor(ImGuiMouseCursor_NotAllowed);
972 reset_preview_state();
973 }
974
975 // Handle material drop
976 for(const auto& type : ex::get_suported_formats<material>())
977 {
978 auto payload = ImGui::AcceptDragDropPayload(type.c_str());
979 if(payload != nullptr)
980 {
981 std::string absolute_path(reinterpret_cast<const char*>(payload->Data), std::size_t(payload->DataSize));
982 std::string key = fs::convert_to_protocol(fs::path(absolute_path)).generic_string();
983
984 // Clear preview state since we're actually dropping now
985 reset_preview_state();
986
987 handle_material_drop(ctx, camera_comp, key);
988 }
989 }
990
991 // Handle mesh drop
992 for(const auto& type : ex::get_suported_formats<mesh>())
993 {
994 auto payload = ImGui::AcceptDragDropPayload(type.c_str());
995 if(payload != nullptr)
996 {
997 // Clear preview state
998 reset_preview_state();
999
1000 std::string absolute_path(reinterpret_cast<const char*>(payload->Data), std::size_t(payload->DataSize));
1001 std::string key = fs::convert_to_protocol(fs::path(absolute_path)).generic_string();
1002
1003 handle_mesh_drop(ctx, camera_comp, key);
1004 }
1005 }
1006
1007 // Handle prefab drop
1008 for(const auto& type : ex::get_suported_formats<prefab>())
1009 {
1010 auto payload = ImGui::AcceptDragDropPayload(type.c_str());
1011 if(payload != nullptr)
1012 {
1013 // Clear preview state
1014 reset_preview_state();
1015
1016 std::string absolute_path(reinterpret_cast<const char*>(payload->Data), std::size_t(payload->DataSize));
1017 std::string key = fs::convert_to_protocol(fs::path(absolute_path)).generic_string();
1018
1019 handle_prefab_drop(ctx, camera_comp, key);
1020 }
1021 }
1022
1023 ImGui::EndDragDropTarget();
1024}
1025
1026// Helper function to restore original materials
1027void restore_original_materials(entt::handle entity, const std::vector<asset_handle<material>>& original_materials)
1028{
1029 if(!entity || !entity.all_of<model_component>() || original_materials.empty())
1030 return;
1031
1032 auto& model_comp = entity.get<model_component>();
1033 class model model_copy = model_comp.get_model();
1034
1035 // Restore original materials
1036 for(size_t i = 0; i < original_materials.size() && i < model_copy.get_materials().size(); ++i)
1037 {
1038 model_copy.set_material(original_materials[i], i);
1039 }
1040
1041 // Update the model
1042 model_comp.set_model(model_copy);
1043}
1044
1045// Apply material preview to an entity and save original materials for restoration
1046void apply_material_preview(rtti::context& ctx, entt::handle entity, const std::string& material_path,
1048 bool& is_previewing)
1049{
1050 // If entity is invalid, restore previous preview if there was one
1051 if (!entity)
1052 {
1054 {
1055 restore_original_materials(last_preview_entity, original_materials);
1056 is_previewing = false;
1058 original_materials.clear();
1059 }
1060 return;
1061 }
1062
1063 // If entity changed, restore previous preview
1065 {
1066 restore_original_materials(last_preview_entity, original_materials);
1067 is_previewing = false;
1068 original_materials.clear();
1069 }
1070
1071 // If entity has model component and is different from last preview
1072 if(entity && entity.all_of<model_component>() && (!is_previewing || entity != last_preview_entity))
1073 {
1074 // Load material for preview
1075 auto& am = ctx.get_cached<asset_manager>();
1076 auto material_asset = am.get_asset<material>(material_path);
1077
1078 // Store original materials for restoration
1079 auto& model_comp = entity.get<model_component>();
1080 auto& model = model_comp.get_model();
1081
1082 // Save original materials if not already previewing this entity
1084 {
1085 original_materials.clear();
1086 for(const auto& mat : model.get_materials())
1087 {
1088 original_materials.push_back(mat);
1089 }
1090 }
1091
1092 // Apply preview material
1093 class model model_copy = model;
1094 for(size_t i = 0; i < model_copy.get_materials().size(); ++i)
1095 {
1096 model_copy.set_material(material_asset, i);
1097 }
1098 model_comp.set_model(model_copy);
1099
1100 // Update preview state
1101 is_previewing = true;
1103 }
1104}
1105
1106} // namespace
1107
1108// ============================================================================
1109// Scene Panel Implementation
1110// ============================================================================
1111
1112scene_panel::scene_panel(imgui_panels* parent) : entity_panel(parent)
1113{
1114}
1115
1117{
1118 ctx.add<gizmo_registry>();
1119 gizmos_.init(ctx);
1120
1121 //create editor camera
1122 defaults::create_camera_entity(ctx, panel_scene_, "Scene Camera");
1123
1124 //create center entity
1125 panel_scene_.create_entity();
1126}
1127
1129{
1130 gizmos_.deinit(ctx);
1131 ctx.remove<gizmo_registry>();
1132}
1133
1134
1135// ============================================================================
1136// Drag Selection Helper Functions
1137// ============================================================================
1138
1139void scene_panel::handle_drag_selection(rtti::context& ctx, const camera& camera, editing_manager& em)
1140{
1141 if(!ImGui::IsAnyItemHovered() && !ImGuizmo::IsOver() && ImGui::IsWindowHovered())
1142 {
1143 if(ImGui::IsMouseClicked(ImGuiMouseButton_Left))
1144 {
1145 drag_start_pos_ = ImGui::GetMousePos();
1146 }
1147 // Check if we should start drag selection
1148 if(ImGui::IsMouseDragging(ImGuiMouseButton_Left))
1149 {
1150 // Only start drag selection if we're not clicking on anything and not over a gizmo
1151 if(!is_drag_selecting_)
1152 {
1153 is_drag_selecting_ = true;
1154 }
1155 }
1156 }
1157
1158
1159 // Update drag selection
1160 if(is_drag_selecting_)
1161 {
1162 drag_current_pos_ = ImGui::GetMousePos();
1163
1164 // End drag selection on mouse release
1165 if(ImGui::IsMouseReleased(ImGuiMouseButton_Left))
1166 {
1167 auto& pick_manager = ctx.get_cached<picking_manager>();
1168 pick_manager.cancel_pick();
1169 is_drag_selecting_ = false;
1170 }
1171 }
1172}
1173
1174void scene_panel::draw_drag_selection_rect(const ImVec2& start_pos, const ImVec2& current_pos)
1175{
1176 if(start_pos.x == current_pos.x && start_pos.y == current_pos.y)
1177 {
1178 return;
1179 }
1180
1181 ImDrawList* draw_list = ImGui::GetWindowDrawList();
1182
1183 // Calculate the rectangle bounds
1184 ImVec2 min_pos(std::min(start_pos.x, current_pos.x), std::min(start_pos.y, current_pos.y));
1185 ImVec2 max_pos(std::max(start_pos.x, current_pos.x), std::max(start_pos.y, current_pos.y));
1186
1187 // Draw the selection rectangle
1188 ImU32 rect_color = ImGui::GetColorU32(ImVec4(0.2f, 0.6f, 1.0f, 0.3f)); // Semi-transparent blue
1189 ImU32 border_color = ImGui::GetColorU32(ImVec4(0.2f, 0.6f, 1.0f, 0.8f)); // Solid blue border
1190
1191 // Fill rectangle
1192 draw_list->AddRectFilled(min_pos, max_pos, rect_color);
1193
1194 // Border
1195 draw_list->AddRect(min_pos, max_pos, border_color, 0.0f, 0, 2.0f);
1196}
1197
1198
1199void scene_panel::handle_prefab_mode_changes(rtti::context& ctx)
1200{
1201 auto& em = ctx.get_cached<editing_manager>();
1202 bool is_prefab_mode = em.is_prefab_mode();
1203
1204 // Detect when we enter prefab mode
1205 if(is_prefab_mode && !was_prefab_mode_)
1206 {
1208 }
1209 // Detect when we exit prefab mode (e.g., due to external factors)
1210 else if(!is_prefab_mode && was_prefab_mode_)
1211 {
1212 // If we're exiting prefab mode and auto-save is enabled, save changes
1213 if(auto_save_prefab_ && em.edited_prefab)
1214 {
1215 em.save_prefab_changes(ctx);
1216 }
1217 }
1218
1219 was_prefab_mode_ = is_prefab_mode;
1220}
1221
1223{
1224 handle_prefab_mode_changes(ctx);
1225
1226 if(!is_visible_)
1227 {
1228 return;
1229 }
1230
1231 auto& path = ctx.get_cached<rendering_system>();
1232 path.on_frame_update(panel_scene_, dt);
1233
1234 auto& em = ctx.get_cached<editing_manager>();
1235 if(em.is_prefab_mode())
1236 {
1237 path.on_frame_update(em.prefab_scene, dt);
1238 }
1239}
1240
1242{
1243 auto& path = ctx.get_cached<rendering_system>();
1244 path.on_frame_before_render(panel_scene_, dt);
1245
1246 auto& em = ctx.get_cached<editing_manager>();
1247 if(em.is_prefab_mode())
1248 {
1249 path.on_frame_before_render(em.prefab_scene, dt);
1250 }
1251}
1252
1253void scene_panel::draw_scene(rtti::context& ctx, delta_t dt)
1254{
1255 auto& em = ctx.get_cached<editing_manager>();
1256 auto& path = ctx.get_cached<rendering_system>();
1257 auto handle = get_camera();
1258 auto& camera_comp = handle.get<camera_component>();
1259
1260 // Use the appropriate scene based on mode
1261 auto target_scene = em.get_active_scene(ctx);
1262
1263 if(target_scene)
1264 {
1265 path.render_scene(handle, camera_comp, *target_scene, dt);
1266 gizmos_.on_frame_render(ctx, *target_scene, handle);
1267 }
1268}
1269
1271{
1272 if(!is_visible_)
1273 {
1274 return;
1275 }
1276 draw_scene(ctx, dt);
1277}
1278
1280{
1282
1283 if(ImGui::Begin(name, nullptr, ImGuiWindowFlags_MenuBar))
1284 {
1285 is_focused_ = ImGui::IsWindowFocused();
1286 set_visible(true);
1287 draw_ui(ctx);
1288 }
1289 else
1290 {
1291 set_visible(false);
1292 }
1293 ImGui::End();
1294}
1295
1296auto scene_panel::get_camera() -> entt::handle
1297{
1298 entt::handle camera_entity;
1299 panel_scene_.registry->view<camera_component>().each([&](auto e, auto&& camera_comp)
1300 {
1301 camera_entity = panel_scene_.create_handle(e);
1302 });
1303 return camera_entity;
1304}
1305
1306auto scene_panel::get_center() -> entt::handle
1307{
1308 entt::handle center_entity;
1309
1310 auto view = panel_scene_.registry->view<root_component>(entt::exclude<camera_component>);
1311 view.each([&](auto e, auto&& comp)
1312 {
1313 center_entity = panel_scene_.create_handle(e);
1314 });
1315 return center_entity;
1316}
1317
1319{
1320 is_visible_ = visible;
1321}
1322
1323auto scene_panel::is_focused() const -> bool
1324{
1325 return is_focused_;
1326}
1327
1329{
1330 return auto_save_prefab_;
1331}
1332
1333
1334// ============================================================================
1335// UI Drawing Functions
1336// ============================================================================
1337
1338void scene_panel::draw_prefab_mode_header(rtti::context& ctx)
1339{
1340 auto& em = ctx.get_cached<editing_manager>();
1341
1342 if(!em.is_prefab_mode())
1343 {
1344 return;
1345 }
1346
1347 ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_ButtonActive));
1348 if(ImGui::Button(ICON_MDI_KEYBOARD_RETURN " Back to Scene"))
1349 {
1350 em.exit_prefab_mode(ctx,
1351 auto_save_prefab_ ? editing_manager::save_option::yes
1353 }
1354 ImGui::PopStyleColor();
1355
1356 if(em.edited_prefab)
1357 {
1358 ImGui::SameLine();
1359 ImGui::Text("Editing Prefab: %s", fs::path(em.edited_prefab.id()).filename().string().c_str());
1360
1361 ImGui::SameLine();
1362 if(ImGui::Button("Save"))
1363 {
1364 em.save_prefab_changes(ctx);
1365 }
1366
1367 ImGui::SameLine();
1368 ImGui::Checkbox("Auto Save", &auto_save_prefab_);
1369 ImGui::SetItemTooltipEx("%s", "Automatically save changes when exiting prefab mode");
1370 }
1371
1372 ImGui::Separator();
1373}
1374
1375void scene_panel::draw_transform_tools(editing_manager& em)
1376{
1377 ImGui::SetNextWindowViewportToCurrent();
1378
1379 if(ImGui::MenuItem(ICON_MDI_CURSOR_MOVE, nullptr, em.operation == ImGuizmo::OPERATION::TRANSLATE))
1380 {
1381 em.operation = ImGuizmo::OPERATION::TRANSLATE;
1382 }
1383 ImGui::SetItemTooltipEx("%s", "Translate Tool");
1384 ImGui::SetNextWindowViewportToCurrent();
1385
1386 if(ImGui::MenuItem(ICON_MDI_ROTATE_3D_VARIANT, nullptr, em.operation == ImGuizmo::OPERATION::ROTATE))
1387 {
1388 em.operation = ImGuizmo::OPERATION::ROTATE;
1389 }
1390 ImGui::SetItemTooltipEx("%s", "Rotate Tool");
1391 ImGui::SetNextWindowViewportToCurrent();
1392
1393 if(ImGui::MenuItem(ICON_MDI_RELATIVE_SCALE, nullptr, em.operation == ImGuizmo::OPERATION::SCALE))
1394 {
1395 em.operation = ImGuizmo::OPERATION::SCALE;
1396 em.mode = ImGuizmo::MODE::LOCAL;
1397 }
1398 ImGui::SetItemTooltipEx("%s", "Scale Tool");
1399 ImGui::SetNextWindowViewportToCurrent();
1400
1401 if(ImGui::MenuItem(ICON_MDI_MOVE_RESIZE, nullptr, em.operation == ImGuizmo::OPERATION::UNIVERSAL))
1402 {
1403 em.operation = ImGuizmo::OPERATION::UNIVERSAL;
1404 em.mode = ImGuizmo::MODE::LOCAL;
1405 }
1406 ImGui::SetItemTooltipEx("%s", "Transform Tool");
1407}
1408
1409void scene_panel::draw_gizmo_pivot_mode_menu(bool& gizmo_at_center)
1410{
1411 auto icon = gizmo_at_center ? ICON_MDI_SET_CENTER "Center" ICON_MDI_ARROW_DOWN_BOLD
1413 ImGui::SetNextWindowViewportToCurrent();
1414
1415 if(ImGui::BeginMenu(icon))
1416 {
1417 if(ImGui::MenuItem(ICON_MDI_SET_CENTER "Center", nullptr, gizmo_at_center))
1418 {
1419 gizmo_at_center = true;
1420 }
1421 ImGui::SetItemTooltipEx("%s",
1422 "The tool handle is placed at the center\n"
1423 "of the selections' pivots.");
1424
1425 if(ImGui::MenuItem(ICON_MDI_ROTATE_3D "Pivot", nullptr, !gizmo_at_center))
1426 {
1427 gizmo_at_center = false;
1428 }
1429 ImGui::SetItemTooltipEx("%s",
1430 "The tool handle is placed at the\n"
1431 "active object's pivot point.");
1432
1433 ImGui::EndMenu();
1434 }
1435 ImGui::SetItemTooltipEx("%s", "Tool's Handle Position");
1436}
1437
1438void scene_panel::draw_coordinate_system_menu(editing_manager& em)
1439{
1440 auto icon = em.mode == ImGuizmo::MODE::LOCAL ? ICON_MDI_CUBE "Local" ICON_MDI_ARROW_DOWN_BOLD
1442 ImGui::SetNextWindowViewportToCurrent();
1443
1444 if(ImGui::BeginMenu(icon))
1445 {
1446 if(ImGui::MenuItem(ICON_MDI_CUBE "Local",
1447 ImGui::GetKeyName(shortcuts::toggle_local_global),
1448 em.mode == ImGuizmo::MODE::LOCAL))
1449 {
1450 em.mode = ImGuizmo::MODE::LOCAL;
1451 }
1452 ImGui::SetItemTooltipEx("%s", "Local Coordinate System");
1453
1454 if(ImGui::MenuItem(ICON_MDI_WEB "Global", nullptr, em.mode == ImGuizmo::MODE::WORLD))
1455 {
1456 em.mode = ImGuizmo::MODE::WORLD;
1457 }
1458 ImGui::SetItemTooltipEx("%s", "Global Coordinate System");
1459
1460 ImGui::EndMenu();
1461 }
1462 ImGui::SetItemTooltipEx("%s", "Tool's Coordinate System");
1463}
1464
1465void scene_panel::draw_grid_settings_menu(editing_manager& em)
1466{
1467 ImGui::SetNextWindowViewportToCurrent();
1468
1469 if(ImGui::MenuItem(ICON_MDI_GRID, nullptr, em.show_grid))
1470 {
1471 em.show_grid = !em.show_grid;
1472 }
1473 ImGui::SetItemTooltipEx("%s", "Show/Hide Grid");
1474 ImGui::SetNextWindowViewportToCurrent();
1475
1476 if(ImGui::BeginMenu(ICON_MDI_ARROW_DOWN_BOLD, em.show_grid))
1477 {
1478 ImGui::PushItemWidth(100.0f);
1479
1480 ImGui::TextUnformatted("Grid Visual");
1481 ImGui::LabelText("Plane", "%s", "X Z");
1482 ImGui::SliderFloat("Opacity", &em.grid_data.opacity, 0.0f, 1.0f);
1483 ImGui::Checkbox("Depth Aware", &em.grid_data.depth_aware);
1484 ImGui::SetItemTooltipEx("%s", "Grid is depth aware.");
1485
1486 ImGui::PopItemWidth();
1487
1488 ImGui::EndMenu();
1489 }
1490 ImGui::SetItemTooltipEx("%s", "Grid Properties");
1491}
1492
1493void scene_panel::draw_gizmos_settings_menu(editing_manager& em)
1494{
1495 ImGui::SetNextWindowViewportToCurrent();
1496
1497 if(ImGui::MenuItem(ICON_MDI_SELECTION_MARKER, nullptr, em.show_icon_gizmos))
1498 {
1499 em.show_icon_gizmos = !em.show_icon_gizmos;
1500 }
1501 ImGui::SetItemTooltipEx("%s", "Show/Hide Gizmos");
1502 ImGui::PushID("Billboard Gizmos");
1503 ImGui::SetNextWindowViewportToCurrent();
1504
1505 if(ImGui::BeginMenu(ICON_MDI_ARROW_DOWN_BOLD, em.show_icon_gizmos))
1506 {
1507 ImGui::PushItemWidth(100.0f);
1508
1509 ImGui::TextUnformatted("Gizmos Visual");
1510 ImGui::SliderFloat("Opacity", &em.billboard_data.opacity, 0.0f, 1.0f);
1511 ImGui::SliderFloat("Size", &em.billboard_data.size, 0.1f, 1.0f);
1512
1513 ImGui::Checkbox("Depth Aware", &em.billboard_data.depth_aware);
1514 ImGui::SetItemTooltipEx("%s", "Gizmos are depth aware.");
1515
1516 ImGui::PopItemWidth();
1517
1518 ImGui::EndMenu();
1519 }
1520 ImGui::SetItemTooltipEx("%s", "Gizmos Properties");
1521 ImGui::PopID();
1522}
1523
1524void scene_panel::draw_visualization_menu()
1525{
1526 ImGui::SetNextWindowViewportToCurrent();
1527
1529 {
1530 ImGui::RadioButton("Full", &visualize_passes_, -1);
1531 ImGui::RadioButton("Base Color", &visualize_passes_, 0);
1532 ImGui::RadioButton("Diffuse Color", &visualize_passes_, 1);
1533 ImGui::RadioButton("Specular Color", &visualize_passes_, 2);
1534 ImGui::RadioButton("Indirect Specular Color", &visualize_passes_, 3);
1535 ImGui::RadioButton("Ambient Occlusion", &visualize_passes_, 4);
1536 ImGui::RadioButton("Normals (World Space)", &visualize_passes_, 5);
1537 ImGui::RadioButton("Roughness", &visualize_passes_, 6);
1538 ImGui::RadioButton("Metalness", &visualize_passes_, 7);
1539 ImGui::RadioButton("Emissive Color", &visualize_passes_, 8);
1540 ImGui::RadioButton("Subsurface Color", &visualize_passes_, 9);
1541 ImGui::RadioButton("Depth", &visualize_passes_, 10);
1542
1543 ImGui::EndMenu();
1544 }
1545 ImGui::SetItemTooltipEx("%s", "Visualize Render Passes");
1546}
1547
1548void scene_panel::draw_snapping_menu(editing_manager& em)
1549{
1550 ImGui::SetNextWindowViewportToCurrent();
1551
1552 if(ImGui::BeginMenu(ICON_MDI_GRID_LARGE ICON_MDI_ARROW_DOWN_BOLD))
1553 {
1554 ImGui::PushItemWidth(200.0f);
1555 ImGui::DragVecN("Translation Snap",
1556 ImGuiDataType_Float,
1557 math::value_ptr(em.snap_data.translation_snap),
1558 em.snap_data.translation_snap.length(),
1559 0.5f,
1560 nullptr,
1561 nullptr,
1562 "%.2f");
1563
1564 ImGui::DragFloat("Rotation Degree Snap", &em.snap_data.rotation_degree_snap);
1565 ImGui::DragFloat("Scale Snap", &em.snap_data.scale_snap);
1566 ImGui::PopItemWidth();
1567 ImGui::EndMenu();
1568 }
1569 ImGui::SetItemTooltipEx("%s", "Snapping Properties");
1570}
1571
1572void scene_panel::draw_inverse_kinematics_menu(editing_manager& em)
1573{
1574 ImGui::SetNextWindowViewportToCurrent();
1575
1576 if(ImGui::BeginMenu(ICON_MDI_CRANE ICON_MDI_ARROW_DOWN_BOLD))
1577 {
1578 ImGui::PushItemWidth(200.0f);
1579 ImGui::InputInt("Inverse Kinematic Nodes", &em.ik_data.num_nodes);
1580
1581 ImGui::PopItemWidth();
1582 ImGui::EndMenu();
1583 }
1584 ImGui::SetItemTooltipEx("%s", "Inverse Kinematic Properties");
1585}
1586
1587void scene_panel::draw_camera_settings_menu(rtti::context& ctx)
1588{
1589
1590
1591 ImGui::SetNextWindowSizeConstraints({}, {400.0f, ImGui::GetContentRegionAvail().y});
1592 ImGui::SetNextWindowViewportToCurrent();
1593
1594 if(ImGui::BeginMenu(ICON_MDI_CAMERA ICON_MDI_ARROW_DOWN_BOLD))
1595 {
1596 if(ImGui::Button("Scene Camera"))
1597 {
1598 get_camera().destroy();
1599 defaults::create_camera_entity(ctx, panel_scene_, "Scene Camera");
1600
1601 }
1602
1603 ImGui::SetItemTooltipEx("%s", "Reset the Scene camera.");
1604
1605
1606
1607 entt::meta_any cam = get_camera();
1608 inspect_var(ctx, cam, make_proxy(cam));
1609
1610 ImGui::EndMenu();
1611 }
1612 ImGui::SetItemTooltipEx("%s", "Settings for the Scene view camera.");
1613}
1614
1615void scene_panel::handle_viewport_interaction(rtti::context& ctx, const camera& camera, editing_manager& em)
1616{
1617 bool is_using = ImGuizmo::IsUsing();
1618 bool is_over = ImGuizmo::IsOver();
1619 bool is_entity = em.is_selected_type<entt::handle>();
1620
1621 // Handle drag selection
1622 handle_drag_selection(ctx, camera, em);
1623
1625 {
1626 auto& pick_manager = ctx.get_cached<picking_manager>();
1627 auto bounds = get_drag_selection_bounds();
1628
1629 math::vec2 area = {bounds.second.x - bounds.first.x, bounds.second.y - bounds.first.y};
1630 // Calculate the center of the drag selection area
1631 math::vec2 center = {
1632 bounds.first.x + area.x * 0.5f,
1633 bounds.first.y + area.y * 0.5f
1634 };
1635
1636 pick_manager.request_pick(camera, em.get_select_mode(), center, area);
1637 }
1638
1639 // Only handle single-click selection if we're not drag selecting
1640 if(ImGui::IsItemClicked(ImGuiMouseButton_Left) && !is_using && !is_drag_selecting_)
1641 {
1642 bool is_over_active_gizmo = is_over && is_entity;
1643 if(!is_over_active_gizmo)
1644 {
1645 ImGui::SetWindowFocus();
1646 auto& pick_manager = ctx.get_cached<picking_manager>();
1647 auto pos = ImGui::GetMousePos();
1648
1649 pick_manager.request_pick(camera, em.get_select_mode(), {pos.x, pos.y});
1650 }
1651 }
1652
1653 if(ImGui::IsItemClicked(ImGuiMouseButton_Middle) || ImGui::IsItemClicked(ImGuiMouseButton_Right))
1654 {
1655 ImGui::SetWindowFocus();
1656 ImGui::SetMouseCursor(ImGuiMouseCursor_None);
1657 }
1658
1659 if(ImGui::IsItemReleased(ImGuiMouseButton_Middle) || ImGui::IsItemReleased(ImGuiMouseButton_Right))
1660 {
1661 ImGui::SetMouseCursor(ImGuiMouseCursor_Arrow);
1662 }
1663}
1664
1665void scene_panel::handle_keyboard_shortcuts(editing_manager& em)
1666{
1667 bool is_delete_pressed = ImGui::IsItemKeyPressed(shortcuts::delete_item);
1668 bool is_focus_pressed = ImGui::IsItemKeyPressed(shortcuts::focus_selected);
1669 bool is_duplicate_pressed = ImGui::IsItemCombinationKeyPressed(shortcuts::duplicate_item);
1670
1671 auto selections = em.try_get_selections_as_copy<entt::handle>();
1672
1673 if(is_delete_pressed)
1674 {
1675 delete_entities(selections);
1676 }
1677
1678 if(is_focus_pressed)
1679 {
1680 focus_entities(get_camera(), selections);
1681 }
1682
1683 if(is_duplicate_pressed)
1684 {
1685 duplicate_entities(selections);
1686 }
1687}
1688
1689void scene_panel::setup_camera_viewport(camera_component& camera_comp, const ImVec2& size, const ImVec2& pos)
1690{
1691 if(size.x > 0 && size.y > 0)
1692 {
1693 camera_comp.get_camera().set_viewport_pos(
1694 {static_cast<std::uint32_t>(pos.x), static_cast<std::uint32_t>(pos.y)});
1695 camera_comp.set_viewport_size({static_cast<std::uint32_t>(size.x), static_cast<std::uint32_t>(size.y)});
1696 }
1697}
1698
1699void scene_panel::draw_scene_viewport(rtti::context& ctx, const ImVec2& size)
1700{
1701 auto& em = ctx.get_cached<editing_manager>();
1702
1703
1704 auto camera_entity = get_camera();
1705 if(!camera_entity)
1706 {
1707 return;
1708 }
1709 auto& camera_comp = camera_entity.get<camera_component>();
1710 const auto& camera = camera_comp.get_camera();
1711 const auto& rview = camera_comp.get_render_view();
1712 const auto& obuffer = rview.fbo_safe_get("OBUFFER");
1713
1714
1715 if(obuffer && obuffer->get_attachment_count() > 0)
1716 {
1717 const auto& tex = obuffer->get_texture(0);
1719 }
1720 else
1721 {
1722 ImGui::Text("No render view");
1723 }
1724
1725 if(em.is_prefab_mode())
1726 {
1727 ImVec2 padding(2.0f, 2.0f);
1728 auto color = ImGui::GetColorU32(ImGuiCol_ButtonActive);
1729 auto min = ImGui::GetItemRectMin() - padding;
1730 auto max = ImGui::GetItemRectMax() + padding;
1731 ImGui::RenderFocusFrame(min, max, color, 4.0f);
1732 }
1733
1734 handle_viewport_interaction(ctx, camera, em);
1735 handle_keyboard_shortcuts(em);
1736
1737 manipulation_gizmos(gizmo_at_center_, was_using_gizmo_, get_center(), camera_entity, em);
1738 handle_camera_movement(camera_entity, move_dir_, acceleration_, is_dragging_);
1739 draw_selected_camera(ctx, camera_entity, size);
1740
1741 // {
1742
1743 // const float& ref_font_scale = ImGui::GetCurrentContext()->FontSizeBase;
1744
1745 // ImGui::ImCoolBarConfig config;
1746 // config.normal_size = 35.0f;
1747 // config.hovered_size = 145.0f;
1748 // config.anchor = ImVec2(0.0f, 0.5f);
1749 // config.anchor_area = ImRect(ImGui::GetWindowPos(), ImGui::GetWindowPos() + ImGui::GetWindowSize());
1750
1751 // if (ImGui::BeginCoolBar("CoolBarMainWin", ImCoolBarFlags_Vertical, config))
1752 // {
1753 // if (ImGui::CoolBarItemGuard item{"imgui_demo"})
1754 // {
1755 // ImVec2 size(item.ctx.width, 0);
1756 // ImGui::Button("Test", size);
1757 // }
1758
1759 // if (ImGui::CoolBarItemGuard item{"imgui_demo1"})
1760 // {
1761 // ImVec2 size(item.ctx.width, 0);
1762 // ImGui::Button("Test", size);
1763 // }
1764
1765 // if (ImGui::CoolBarItemGuard item{"imgui_demo2"})
1766 // {
1767 // ImVec2 size(item.ctx.width, 0);
1768 // ImGui::Button("Test", size);
1769 // }
1770
1771 // if (ImGui::CoolBarItemGuard item{"imgui_demo3"})
1772 // {
1773 // ImVec2 size(item.ctx.width, 0);
1774 // ImGui::Button("Test", size);
1775 // }
1776 // ImGui::EndCoolBar();
1777 // }
1778 // }
1779 // Draw drag selection rectangle if active
1780 if(is_drag_selecting_)
1781 {
1782 draw_drag_selection_rect(drag_start_pos_, drag_current_pos_);
1783 }
1784
1785 camera_comp.get_pipeline_data().get_pipeline()->set_debug_pass(visualize_passes_);
1786}
1787
1788void scene_panel::draw_ui(rtti::context& ctx)
1789{
1790 draw_menubar(ctx);
1791
1792 auto& em = ctx.get_cached<editing_manager>();
1793 auto camera_entity = get_camera();
1794
1795 bool has_edit_camera = camera_entity && camera_entity.all_of<transform_component, camera_component>();
1796
1797 if(!has_edit_camera)
1798 {
1799 return;
1800 }
1801
1802 auto size = ImGui::GetContentRegionAvail();
1803 if(size.x > 0 && size.y > 0)
1804 {
1805
1806 auto pos = ImGui::GetCursorScreenPos();
1807 auto& camera_comp = camera_entity.get<camera_component>();
1808
1809 setup_camera_viewport(camera_comp, size, pos);
1810 draw_scene_viewport(ctx, size);
1811 process_drag_drop_target(ctx, camera_comp);
1812 }
1813}
1814
1815void scene_panel::draw_framerate_display()
1816{
1818 auto& io = ImGui::GetIO();
1819
1820 auto fps_size = ImGui::CalcTextSize(fmt::format("{:.1f}", io.Framerate).c_str()).x;
1821 ImGui::PopFont();
1822
1823 ImGui::SameLine();
1824
1825 ImGui::AlignedItem(1.0f,
1826 ImGui::GetContentRegionAvail().x,
1827 fps_size,
1828 [&]()
1829 {
1831 ImGui::Text("%.1f", io.Framerate);
1832 ImGui::PopFont();
1833 });
1834}
1835
1836void scene_panel::draw_menubar(rtti::context& ctx)
1837{
1838 auto& em = ctx.get_cached<editing_manager>();
1839
1840 if(ImGui::BeginMenuBar())
1841 {
1842 draw_prefab_mode_header(ctx);
1843 draw_transform_tools(em);
1844 draw_gizmo_pivot_mode_menu(gizmo_at_center_);
1845 draw_coordinate_system_menu(em);
1846 draw_grid_settings_menu(em);
1847 draw_gizmos_settings_menu(em);
1848 draw_visualization_menu();
1849 draw_snapping_menu(em);
1850 draw_inverse_kinematics_menu(em);
1851 draw_camera_settings_menu(ctx);
1852 draw_framerate_display();
1853
1854 ImGui::EndMenuBar();
1855 }
1856}
1857
1858void scene_panel::draw_selected_camera(rtti::context& ctx, entt::handle editor_camera, const ImVec2& size)
1859{
1860 auto& em = ctx.get_cached<editing_manager>();
1861
1862 if(auto sel = em.try_get_active_selection_as<entt::handle>())
1863 {
1864 if(sel && sel->valid() && sel->all_of<camera_component>())
1865 {
1866 const auto& selected_camera = sel->get<camera_component>();
1867
1868 auto& game_panel = parent_->get_game_panel();
1869 game_panel.set_visible_force(true);
1870
1871 const auto& camera = selected_camera.get_camera();
1872 const auto& render_view = selected_camera.get_render_view();
1873 const auto& viewport_size = camera.get_viewport_size();
1874 const auto& obuffer = render_view.fbo_safe_get("OBUFFER");
1875
1876 if(!obuffer)
1877 {
1878 return;
1879 }
1880 float factor = std::min(size.x / float(viewport_size.width), size.y / float(viewport_size.height)) / 4.0f;
1881 ImVec2 bounds(viewport_size.width * factor, viewport_size.height * factor);
1882 // Calculate the position to place the image
1883 ImVec2 image_pos =
1884 ImVec2(ImGui::GetWindowSize().x - 20 - bounds.x, ImGui::GetWindowSize().y - 20 - bounds.y);
1885
1886 // Move the cursor to the calculated position
1887 ImGui::SetCursorPos(image_pos);
1888
1889 const auto& tex = obuffer->get_texture(0);
1890 ImGui::Image(ImGui::ToId(tex), bounds);
1891
1892 if(ImGui::IsKeyChordPressed(shortcuts::snap_scene_camera_to_selected_camera))
1893 {
1894 auto& transform = editor_camera.get<transform_component>();
1895 auto& transform_selected = sel->get<transform_component>();
1896 transform_selected.set_transform_global(transform.get_transform_global());
1897 }
1898 }
1899 }
1900}
1901
1902} // namespace unravel
manifold_type type
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.
Class representing a camera. Contains functionality for manipulating and updating a camera....
Definition camera.h:35
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)
auto init(rtti::context &ctx) -> bool
void on_frame_render(rtti::context &ctx, scene &scn, entt::handle camera_entity)
auto deinit(rtti::context &ctx) -> bool
auto get_game_panel() -> game_panel &
Definition panel.cpp:161
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)
auto is_focused() const -> bool
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:32
void set_visible(bool visible)
auto get_drag_selection_bounds() const -> std::pair< ImVec2, ImVec2 >
Definition scene_panel.h:34
void on_frame_before_render(rtti::context &ctx, delta_t dt)
auto get_center() -> entt::handle
void deinit(rtti::context &ctx)
auto get_camera() -> entt::handle
std::chrono::duration< float > delta_t
float scale
Definition hub.cpp:25
std::string name
Definition hub.cpp:27
#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
const char * icon
#define APPLOG_WARNING(...)
Definition logging.h:19
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:167
void PushFont(Font::Enum _font)
Definition imgui.cpp:617
ImTextureID ToId(gfx::texture_handle _handle, uint8_t _mip=0, uint8_t _flags=IMGUI_FLAGS_ALPHA_BLEND)
Definition imgui.h:102
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:40
transform_t< float > transform
void stop_all(const std::string &scope)
Stops all actions within the specified scope.
Definition seq.cpp:154
constexpr ImGuiKey delete_item
Definition shortcuts.h:33
const ImGuiKeyCombination duplicate_item
Definition shortcuts.h:34
constexpr ImGuiKeyChord snap_scene_camera_to_selected_camera
Definition shortcuts.h:66
constexpr ImGuiKey toggle_local_global
Definition shortcuts.h:65
constexpr ImGuiKey focus_selected
Definition shortcuts.h:62
auto ik_set_position_ccd(entt::handle end_effector, const math::vec3 &target, size_t num_bones_in_chain, float threshold, int max_iterations) -> bool
auto ik_set_position_fabrik(entt::handle end_effector, const math::vec3 &target, size_t num_bones_in_chain, float threshold, int max_iterations) -> 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< asset_handle< material > > original_materials
bool is_previewing
std::string current_drag_material
entt::handle last_preview_entity
entt::entity entity
float x
Represents a handle to an asset, providing access and management functions.
auto get_cached() -> T &
Definition context.hpp:49
auto add(Args &&... args) -> T &
Definition context.hpp:16
void remove()
Definition context.hpp:78
static void focus_camera_on_entities(entt::handle camera, const std::vector< entt::handle > &entities)
Focuses a camera on a specified entity.
Definition defaults.cpp:783
static auto create_camera_entity(rtti::context &ctx, scene &scn, const std::string &name) -> entt::handle
Creates a camera entity.
Definition defaults.cpp:581
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:225
gfx::uniform_handle handle
Definition uniform.cpp:9