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 {
1207 std::array<entt::handle, 1> entities = {em.prefab_entity};
1209 }
1210 // Detect when we exit prefab mode (e.g., due to external factors)
1211 else if(!is_prefab_mode && was_prefab_mode_)
1212 {
1213 // If we're exiting prefab mode and auto-save is enabled, save changes
1214 if(auto_save_prefab_ && em.edited_prefab)
1215 {
1216 em.save_prefab_changes(ctx);
1217 }
1218 }
1219
1220 was_prefab_mode_ = is_prefab_mode;
1221}
1222
1224{
1225 handle_prefab_mode_changes(ctx);
1226
1227 if(!is_visible_)
1228 {
1229 return;
1230 }
1231
1232 auto& path = ctx.get_cached<rendering_system>();
1233 path.on_frame_update(panel_scene_, dt);
1234
1235 auto& em = ctx.get_cached<editing_manager>();
1236 if(em.is_prefab_mode())
1237 {
1238 path.on_frame_update(em.prefab_scene, dt);
1239 }
1240}
1241
1243{
1244 auto& path = ctx.get_cached<rendering_system>();
1245 path.on_frame_before_render(panel_scene_, dt);
1246
1247 auto& em = ctx.get_cached<editing_manager>();
1248 if(em.is_prefab_mode())
1249 {
1250 path.on_frame_before_render(em.prefab_scene, dt);
1251 }
1252}
1253
1254void scene_panel::draw_scene(rtti::context& ctx, delta_t dt)
1255{
1256 auto& em = ctx.get_cached<editing_manager>();
1257 auto& path = ctx.get_cached<rendering_system>();
1258 auto handle = get_camera();
1259 auto& camera_comp = handle.get<camera_component>();
1260
1261 // Use the appropriate scene based on mode
1262 auto target_scene = em.get_active_scene(ctx);
1263
1264 if(target_scene)
1265 {
1266 path.render_scene(handle, camera_comp, *target_scene, dt);
1267 gizmos_.on_frame_render(ctx, *target_scene, handle);
1268 }
1269}
1270
1272{
1273 if(!is_visible_)
1274 {
1275 return;
1276 }
1277 draw_scene(ctx, dt);
1278}
1279
1281{
1283
1284 if(ImGui::Begin(name, nullptr, ImGuiWindowFlags_MenuBar))
1285 {
1286 is_focused_ = ImGui::IsWindowFocused();
1287 set_visible(true);
1288 draw_ui(ctx);
1289 }
1290 else
1291 {
1292 set_visible(false);
1293 }
1294 ImGui::End();
1295}
1296
1297auto scene_panel::get_camera() -> entt::handle
1298{
1299 entt::handle camera_entity;
1300 panel_scene_.registry->view<camera_component>().each([&](auto e, auto&& camera_comp)
1301 {
1302 camera_entity = panel_scene_.create_handle(e);
1303 });
1304 return camera_entity;
1305}
1306
1307auto scene_panel::get_center() -> entt::handle
1308{
1309 entt::handle center_entity;
1310
1311 auto view = panel_scene_.registry->view<root_component>(entt::exclude<camera_component>);
1312 view.each([&](auto e, auto&& comp)
1313 {
1314 center_entity = panel_scene_.create_handle(e);
1315 });
1316 return center_entity;
1317}
1318
1320{
1321 is_visible_ = visible;
1322}
1323
1324auto scene_panel::is_focused() const -> bool
1325{
1326 return is_focused_;
1327}
1328
1330{
1331 return auto_save_prefab_;
1332}
1333
1334
1335// ============================================================================
1336// UI Drawing Functions
1337// ============================================================================
1338
1339void scene_panel::draw_prefab_mode_header(rtti::context& ctx)
1340{
1341 auto& em = ctx.get_cached<editing_manager>();
1342
1343 if(!em.is_prefab_mode())
1344 {
1345 return;
1346 }
1347
1348 ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_ButtonActive));
1349 if(ImGui::Button(ICON_MDI_KEYBOARD_RETURN " Back to Scene"))
1350 {
1351 em.exit_prefab_mode(ctx,
1352 auto_save_prefab_ ? editing_manager::save_option::yes
1354 }
1355 ImGui::PopStyleColor();
1356
1357 if(em.edited_prefab)
1358 {
1359 ImGui::SameLine();
1360 ImGui::Text("Editing Prefab: %s", fs::path(em.edited_prefab.id()).filename().string().c_str());
1361
1362 ImGui::SameLine();
1363 if(ImGui::Button("Save"))
1364 {
1365 em.save_prefab_changes(ctx);
1366 }
1367
1368 ImGui::SameLine();
1369 ImGui::Checkbox("Auto Save", &auto_save_prefab_);
1370 ImGui::SetItemTooltipEx("%s", "Automatically save changes when exiting prefab mode");
1371 }
1372
1373 ImGui::Separator();
1374}
1375
1376void scene_panel::draw_transform_tools(editing_manager& em)
1377{
1378 ImGui::SetNextWindowViewportToCurrent();
1379
1380 if(ImGui::MenuItem(ICON_MDI_CURSOR_MOVE, nullptr, em.operation == ImGuizmo::OPERATION::TRANSLATE))
1381 {
1382 em.operation = ImGuizmo::OPERATION::TRANSLATE;
1383 }
1384 ImGui::SetItemTooltipEx("%s", "Translate Tool");
1385 ImGui::SetNextWindowViewportToCurrent();
1386
1387 if(ImGui::MenuItem(ICON_MDI_ROTATE_3D_VARIANT, nullptr, em.operation == ImGuizmo::OPERATION::ROTATE))
1388 {
1389 em.operation = ImGuizmo::OPERATION::ROTATE;
1390 }
1391 ImGui::SetItemTooltipEx("%s", "Rotate Tool");
1392 ImGui::SetNextWindowViewportToCurrent();
1393
1394 if(ImGui::MenuItem(ICON_MDI_RELATIVE_SCALE, nullptr, em.operation == ImGuizmo::OPERATION::SCALE))
1395 {
1396 em.operation = ImGuizmo::OPERATION::SCALE;
1397 em.mode = ImGuizmo::MODE::LOCAL;
1398 }
1399 ImGui::SetItemTooltipEx("%s", "Scale Tool");
1400 ImGui::SetNextWindowViewportToCurrent();
1401
1402 if(ImGui::MenuItem(ICON_MDI_MOVE_RESIZE, nullptr, em.operation == ImGuizmo::OPERATION::UNIVERSAL))
1403 {
1404 em.operation = ImGuizmo::OPERATION::UNIVERSAL;
1405 em.mode = ImGuizmo::MODE::LOCAL;
1406 }
1407 ImGui::SetItemTooltipEx("%s", "Transform Tool");
1408}
1409
1410void scene_panel::draw_gizmo_pivot_mode_menu(bool& gizmo_at_center)
1411{
1412 auto icon = gizmo_at_center ? ICON_MDI_SET_CENTER "Center" ICON_MDI_ARROW_DOWN_BOLD
1414 ImGui::SetNextWindowViewportToCurrent();
1415
1416 if(ImGui::BeginMenu(icon))
1417 {
1418 if(ImGui::MenuItem(ICON_MDI_SET_CENTER "Center", nullptr, gizmo_at_center))
1419 {
1420 gizmo_at_center = true;
1421 }
1422 ImGui::SetItemTooltipEx("%s",
1423 "The tool handle is placed at the center\n"
1424 "of the selections' pivots.");
1425
1426 if(ImGui::MenuItem(ICON_MDI_ROTATE_3D "Pivot", nullptr, !gizmo_at_center))
1427 {
1428 gizmo_at_center = false;
1429 }
1430 ImGui::SetItemTooltipEx("%s",
1431 "The tool handle is placed at the\n"
1432 "active object's pivot point.");
1433
1434 ImGui::EndMenu();
1435 }
1436 ImGui::SetItemTooltipEx("%s", "Tool's Handle Position");
1437}
1438
1439void scene_panel::draw_coordinate_system_menu(editing_manager& em)
1440{
1441 auto icon = em.mode == ImGuizmo::MODE::LOCAL ? ICON_MDI_CUBE "Local" ICON_MDI_ARROW_DOWN_BOLD
1443 ImGui::SetNextWindowViewportToCurrent();
1444
1445 if(ImGui::BeginMenu(icon))
1446 {
1447 if(ImGui::MenuItem(ICON_MDI_CUBE "Local",
1448 ImGui::GetKeyName(shortcuts::toggle_local_global),
1449 em.mode == ImGuizmo::MODE::LOCAL))
1450 {
1451 em.mode = ImGuizmo::MODE::LOCAL;
1452 }
1453 ImGui::SetItemTooltipEx("%s", "Local Coordinate System");
1454
1455 if(ImGui::MenuItem(ICON_MDI_WEB "Global", nullptr, em.mode == ImGuizmo::MODE::WORLD))
1456 {
1457 em.mode = ImGuizmo::MODE::WORLD;
1458 }
1459 ImGui::SetItemTooltipEx("%s", "Global Coordinate System");
1460
1461 ImGui::EndMenu();
1462 }
1463 ImGui::SetItemTooltipEx("%s", "Tool's Coordinate System");
1464}
1465
1466void scene_panel::draw_grid_settings_menu(editing_manager& em)
1467{
1468 ImGui::SetNextWindowViewportToCurrent();
1469
1470 if(ImGui::MenuItem(ICON_MDI_GRID, nullptr, em.show_grid))
1471 {
1472 em.show_grid = !em.show_grid;
1473 }
1474 ImGui::SetItemTooltipEx("%s", "Show/Hide Grid");
1475 ImGui::SetNextWindowViewportToCurrent();
1476
1477 if(ImGui::BeginMenu(ICON_MDI_ARROW_DOWN_BOLD, em.show_grid))
1478 {
1479 ImGui::PushItemWidth(100.0f);
1480
1481 ImGui::TextUnformatted("Grid Visual");
1482 ImGui::LabelText("Plane", "%s", "X Z");
1483 ImGui::SliderFloat("Opacity", &em.grid_data.opacity, 0.0f, 1.0f);
1484 ImGui::Checkbox("Depth Aware", &em.grid_data.depth_aware);
1485 ImGui::SetItemTooltipEx("%s", "Grid is depth aware.");
1486
1487 ImGui::PopItemWidth();
1488
1489 ImGui::EndMenu();
1490 }
1491 ImGui::SetItemTooltipEx("%s", "Grid Properties");
1492}
1493
1494void scene_panel::draw_gizmos_settings_menu(editing_manager& em)
1495{
1496 ImGui::SetNextWindowViewportToCurrent();
1497
1498 if(ImGui::MenuItem(ICON_MDI_SELECTION_MARKER, nullptr, em.show_icon_gizmos))
1499 {
1500 em.show_icon_gizmos = !em.show_icon_gizmos;
1501 }
1502 ImGui::SetItemTooltipEx("%s", "Show/Hide Gizmos");
1503 ImGui::PushID("Billboard Gizmos");
1504 ImGui::SetNextWindowViewportToCurrent();
1505
1506 if(ImGui::BeginMenu(ICON_MDI_ARROW_DOWN_BOLD, em.show_icon_gizmos))
1507 {
1508 ImGui::PushItemWidth(100.0f);
1509
1510 ImGui::TextUnformatted("Gizmos Visual");
1511 ImGui::SliderFloat("Opacity", &em.billboard_data.opacity, 0.0f, 1.0f);
1512 ImGui::SliderFloat("Size", &em.billboard_data.size, 0.1f, 1.0f);
1513
1514 ImGui::Checkbox("Depth Aware", &em.billboard_data.depth_aware);
1515 ImGui::SetItemTooltipEx("%s", "Gizmos are depth aware.");
1516
1517 ImGui::PopItemWidth();
1518
1519 ImGui::EndMenu();
1520 }
1521 ImGui::SetItemTooltipEx("%s", "Gizmos Properties");
1522 ImGui::PopID();
1523}
1524
1525void scene_panel::draw_visualization_menu()
1526{
1527 ImGui::SetNextWindowViewportToCurrent();
1528
1530 {
1531 ImGui::RadioButton("Full", &visualize_passes_, -1);
1532 ImGui::RadioButton("Base Color", &visualize_passes_, 0);
1533 ImGui::RadioButton("Diffuse Color", &visualize_passes_, 1);
1534 ImGui::RadioButton("Specular Color", &visualize_passes_, 2);
1535 ImGui::RadioButton("Indirect Specular Color", &visualize_passes_, 3);
1536 ImGui::RadioButton("Ambient Occlusion", &visualize_passes_, 4);
1537 ImGui::RadioButton("Normals (World Space)", &visualize_passes_, 5);
1538 ImGui::RadioButton("Roughness", &visualize_passes_, 6);
1539 ImGui::RadioButton("Metalness", &visualize_passes_, 7);
1540 ImGui::RadioButton("Emissive Color", &visualize_passes_, 8);
1541 ImGui::RadioButton("Subsurface Color", &visualize_passes_, 9);
1542 ImGui::RadioButton("Depth", &visualize_passes_, 10);
1543
1544 ImGui::EndMenu();
1545 }
1546 ImGui::SetItemTooltipEx("%s", "Visualize Render Passes");
1547}
1548
1549void scene_panel::draw_snapping_menu(editing_manager& em)
1550{
1551 ImGui::SetNextWindowViewportToCurrent();
1552
1553 if(ImGui::BeginMenu(ICON_MDI_GRID_LARGE ICON_MDI_ARROW_DOWN_BOLD))
1554 {
1555 ImGui::PushItemWidth(200.0f);
1556 ImGui::DragVecN("Translation Snap",
1557 ImGuiDataType_Float,
1558 math::value_ptr(em.snap_data.translation_snap),
1559 em.snap_data.translation_snap.length(),
1560 0.5f,
1561 nullptr,
1562 nullptr,
1563 "%.2f");
1564
1565 ImGui::DragFloat("Rotation Degree Snap", &em.snap_data.rotation_degree_snap);
1566 ImGui::DragFloat("Scale Snap", &em.snap_data.scale_snap);
1567 ImGui::PopItemWidth();
1568 ImGui::EndMenu();
1569 }
1570 ImGui::SetItemTooltipEx("%s", "Snapping Properties");
1571}
1572
1573void scene_panel::draw_inverse_kinematics_menu(editing_manager& em)
1574{
1575 ImGui::SetNextWindowViewportToCurrent();
1576
1577 if(ImGui::BeginMenu(ICON_MDI_CRANE ICON_MDI_ARROW_DOWN_BOLD))
1578 {
1579 ImGui::PushItemWidth(200.0f);
1580 ImGui::InputInt("Inverse Kinematic Nodes", &em.ik_data.num_nodes);
1581
1582 ImGui::PopItemWidth();
1583 ImGui::EndMenu();
1584 }
1585 ImGui::SetItemTooltipEx("%s", "Inverse Kinematic Properties");
1586}
1587
1588void scene_panel::draw_camera_settings_menu(rtti::context& ctx)
1589{
1590
1591
1592 ImGui::SetNextWindowSizeConstraints({}, {400.0f, ImGui::GetContentRegionAvail().y});
1593 ImGui::SetNextWindowViewportToCurrent();
1594
1595 if(ImGui::BeginMenu(ICON_MDI_CAMERA ICON_MDI_ARROW_DOWN_BOLD))
1596 {
1597 if(ImGui::Button("Scene Camera"))
1598 {
1599 get_camera().destroy();
1600 defaults::create_camera_entity(ctx, panel_scene_, "Scene Camera");
1601
1602 }
1603
1604 ImGui::SetItemTooltipEx("%s", "Reset the Scene camera.");
1605
1606
1607
1608 entt::meta_any cam = get_camera();
1609 inspect_var(ctx, cam, make_proxy(cam));
1610
1611 ImGui::EndMenu();
1612 }
1613 ImGui::SetItemTooltipEx("%s", "Settings for the Scene view camera.");
1614}
1615
1616void scene_panel::handle_viewport_interaction(rtti::context& ctx, const camera& camera, editing_manager& em)
1617{
1618 bool is_using = ImGuizmo::IsUsing();
1619 bool is_over = ImGuizmo::IsOver();
1620 bool is_entity = em.is_selected_type<entt::handle>();
1621
1622 // Handle drag selection
1623 handle_drag_selection(ctx, camera, em);
1624
1626 {
1627 auto& pick_manager = ctx.get_cached<picking_manager>();
1628 auto bounds = get_drag_selection_bounds();
1629
1630 math::vec2 area = {bounds.second.x - bounds.first.x, bounds.second.y - bounds.first.y};
1631 // Calculate the center of the drag selection area
1632 math::vec2 center = {
1633 bounds.first.x + area.x * 0.5f,
1634 bounds.first.y + area.y * 0.5f
1635 };
1636
1637 pick_manager.request_pick(camera, em.get_select_mode(), center, area);
1638 }
1639
1640 // Only handle single-click selection if we're not drag selecting
1641 if(ImGui::IsItemClicked(ImGuiMouseButton_Left) && !is_using && !is_drag_selecting_)
1642 {
1643 bool is_over_active_gizmo = is_over && is_entity;
1644 if(!is_over_active_gizmo)
1645 {
1646 ImGui::SetWindowFocus();
1647 auto& pick_manager = ctx.get_cached<picking_manager>();
1648 auto pos = ImGui::GetMousePos();
1649
1650 pick_manager.request_pick(camera, em.get_select_mode(), {pos.x, pos.y});
1651 }
1652 }
1653
1654 if(ImGui::IsItemClicked(ImGuiMouseButton_Middle) || ImGui::IsItemClicked(ImGuiMouseButton_Right))
1655 {
1656 ImGui::SetWindowFocus();
1657 ImGui::SetMouseCursor(ImGuiMouseCursor_None);
1658 }
1659
1660 if(ImGui::IsItemReleased(ImGuiMouseButton_Middle) || ImGui::IsItemReleased(ImGuiMouseButton_Right))
1661 {
1662 ImGui::SetMouseCursor(ImGuiMouseCursor_Arrow);
1663 }
1664}
1665
1666void scene_panel::handle_keyboard_shortcuts(editing_manager& em)
1667{
1668 bool is_delete_pressed = ImGui::IsItemKeyPressed(shortcuts::delete_item);
1669 bool is_focus_pressed = ImGui::IsItemKeyPressed(shortcuts::focus_selected);
1670 bool is_duplicate_pressed = ImGui::IsItemCombinationKeyPressed(shortcuts::duplicate_item);
1671
1672 auto selections = em.try_get_selections_as_copy<entt::handle>();
1673
1674 if(is_delete_pressed)
1675 {
1676 delete_entities(selections);
1677 }
1678
1679 if(is_focus_pressed)
1680 {
1681 focus_entities(get_camera(), selections);
1682 }
1683
1684 if(is_duplicate_pressed)
1685 {
1686 duplicate_entities(selections);
1687 }
1688}
1689
1690void scene_panel::setup_camera_viewport(camera_component& camera_comp, const ImVec2& size, const ImVec2& pos)
1691{
1692 if(size.x > 0 && size.y > 0)
1693 {
1694 camera_comp.get_camera().set_viewport_pos(
1695 {static_cast<std::uint32_t>(pos.x), static_cast<std::uint32_t>(pos.y)});
1696 camera_comp.set_viewport_size({static_cast<std::uint32_t>(size.x), static_cast<std::uint32_t>(size.y)});
1697 }
1698}
1699
1700void scene_panel::draw_scene_viewport(rtti::context& ctx, const ImVec2& size)
1701{
1702 auto& em = ctx.get_cached<editing_manager>();
1703
1704
1705 auto pos = ImGui::GetCursorScreenPos();
1706 auto camera_entity = get_camera();
1707 if(!camera_entity)
1708 {
1709 return;
1710 }
1711 auto& camera_comp = camera_entity.get<camera_component>();
1712 const auto& camera = camera_comp.get_camera();
1713 const auto& rview = camera_comp.get_render_view();
1714 const auto& obuffer = rview.fbo_safe_get("OBUFFER");
1715
1716
1717 if(obuffer && obuffer->get_attachment_count() > 0)
1718 {
1719 const auto& tex = obuffer->get_texture(0);
1721 }
1722 else
1723 {
1724 ImGui::Text("No render view");
1725 }
1726
1727 if(em.is_prefab_mode())
1728 {
1729 ImVec2 padding(2.0f, 2.0f);
1730 auto color = ImGui::GetColorU32(ImGuiCol_ButtonActive);
1731 auto min = ImGui::GetItemRectMin() - padding;
1732 auto max = ImGui::GetItemRectMax() + padding;
1733 ImGui::RenderFocusFrame(min, max, color, 4.0f);
1734 }
1735
1736 handle_viewport_interaction(ctx, camera, em);
1737 handle_keyboard_shortcuts(em);
1738
1739 manipulation_gizmos(gizmo_at_center_, was_using_gizmo_, get_center(), camera_entity, em);
1740 handle_camera_movement(camera_entity, move_dir_, acceleration_, is_dragging_);
1741 draw_selected_camera(ctx, camera_entity, size);
1742
1743 // {
1744
1745 // const float& ref_font_scale = ImGui::GetCurrentContext()->FontSizeBase;
1746
1747 // ImGui::ImCoolBarConfig config;
1748 // config.normal_size = 50.0f;
1749 // config.hovered_size = 80.0f;
1750 // config.anchor = ImVec2(0.5f, 1.0f);
1751 // config.anchor_area = ImRect(pos, pos + size);
1752
1753 // if (ImGui::BeginCoolBar("CoolBarMainWin", ImCoolBarFlags_Horizontal, config))
1754 // {
1755 // if (ImGui::CoolBarItemGuard item{"imgui_demo"})
1756 // {
1757 // ImVec2 size(item.ctx.width, 0);
1758 // ImGui::Button("Play", size);
1759 // }
1760
1761 // if (ImGui::CoolBarItemGuard item{"imgui_demo1"})
1762 // {
1763 // ImVec2 size(item.ctx.width, 0);
1764 // ImGui::Button("Pause", size);
1765 // }
1766
1767 // if (ImGui::CoolBarItemGuard item{"imgui_demo2"})
1768 // {
1769 // ImVec2 size(item.ctx.width, 0);
1770 // ImGui::Button("Stop", size);
1771 // }
1772
1773 // if (ImGui::CoolBarItemGuard item{"imgui_demo3"})
1774 // {
1775 // ImVec2 size(item.ctx.width, 0);
1776 // ImGui::Button("Stop & Reset", size);
1777 // }
1778 // ImGui::EndCoolBar();
1779 // }
1780 // }
1781 // Draw drag selection rectangle if active
1782 if(is_drag_selecting_)
1783 {
1784 draw_drag_selection_rect(drag_start_pos_, drag_current_pos_);
1785 }
1786
1787 camera_comp.get_pipeline_data().get_pipeline()->set_debug_pass(visualize_passes_);
1788}
1789
1790void scene_panel::draw_ui(rtti::context& ctx)
1791{
1792 draw_menubar(ctx);
1793
1794 auto& em = ctx.get_cached<editing_manager>();
1795 auto camera_entity = get_camera();
1796
1797 bool has_edit_camera = camera_entity && camera_entity.all_of<transform_component, camera_component>();
1798
1799 if(!has_edit_camera)
1800 {
1801 return;
1802 }
1803
1804 auto size = ImGui::GetContentRegionAvail();
1805 if(size.x > 0 && size.y > 0)
1806 {
1807
1808 auto pos = ImGui::GetCursorScreenPos();
1809 auto& camera_comp = camera_entity.get<camera_component>();
1810
1811 setup_camera_viewport(camera_comp, size, pos);
1812 draw_scene_viewport(ctx, size);
1813 process_drag_drop_target(ctx, camera_comp);
1814 }
1815}
1816
1817void scene_panel::draw_framerate_display()
1818{
1820 auto& io = ImGui::GetIO();
1821
1822 auto fps_size = ImGui::CalcTextSize(fmt::format("{:.1f}", io.Framerate).c_str()).x;
1823 ImGui::PopFont();
1824
1825 ImGui::SameLine();
1826
1827 ImGui::AlignedItem(1.0f,
1828 ImGui::GetContentRegionAvail().x,
1829 fps_size,
1830 [&]()
1831 {
1833 ImGui::Text("%.1f", io.Framerate);
1834 ImGui::PopFont();
1835 });
1836}
1837
1838void scene_panel::draw_menubar(rtti::context& ctx)
1839{
1840 auto& em = ctx.get_cached<editing_manager>();
1841
1842 if(ImGui::BeginMenuBar())
1843 {
1844 draw_prefab_mode_header(ctx);
1845 draw_transform_tools(em);
1846 draw_gizmo_pivot_mode_menu(gizmo_at_center_);
1847 draw_coordinate_system_menu(em);
1848 draw_grid_settings_menu(em);
1849 draw_gizmos_settings_menu(em);
1850 draw_visualization_menu();
1851 draw_snapping_menu(em);
1852 draw_inverse_kinematics_menu(em);
1853 draw_camera_settings_menu(ctx);
1854 draw_framerate_display();
1855
1856 ImGui::EndMenuBar();
1857 }
1858}
1859
1860void scene_panel::draw_selected_camera(rtti::context& ctx, entt::handle editor_camera, const ImVec2& size)
1861{
1862 auto& em = ctx.get_cached<editing_manager>();
1863
1864 if(auto sel = em.try_get_active_selection_as<entt::handle>())
1865 {
1866 if(sel && sel->valid() && sel->all_of<camera_component>())
1867 {
1868 const auto& selected_camera = sel->get<camera_component>();
1869
1870 auto& game_panel = parent_->get_game_panel();
1871 game_panel.set_visible_force(true);
1872
1873 const auto& camera = selected_camera.get_camera();
1874 const auto& render_view = selected_camera.get_render_view();
1875 const auto& viewport_size = camera.get_viewport_size();
1876 const auto& obuffer = render_view.fbo_safe_get("OBUFFER");
1877
1878 if(!obuffer)
1879 {
1880 return;
1881 }
1882 float factor = std::min(size.x / float(viewport_size.width), size.y / float(viewport_size.height)) / 4.0f;
1883 ImVec2 bounds(viewport_size.width * factor, viewport_size.height * factor);
1884 // Calculate the position to place the image
1885 ImVec2 image_pos =
1886 ImVec2(ImGui::GetWindowSize().x - 20 - bounds.x, ImGui::GetWindowSize().y - 20 - bounds.y);
1887
1888 // Move the cursor to the calculated position
1889 ImGui::SetCursorPos(image_pos);
1890
1891 const auto& tex = obuffer->get_texture(0);
1892 ImGui::Image(ImGui::ToId(tex), bounds);
1893
1894 if(ImGui::IsKeyChordPressed(shortcuts::snap_scene_camera_to_selected_camera))
1895 {
1896 auto& transform = editor_camera.get<transform_component>();
1897 auto& transform_selected = sel->get<transform_component>();
1898 transform_selected.set_transform_global(transform.get_transform_global());
1899 }
1900 }
1901 }
1902}
1903
1904} // 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:174
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, hpp::span< const entt::handle > entities)
Focuses a camera on a specified entity.
Definition defaults.cpp:822
static auto create_camera_entity(rtti::context &ctx, scene &scn, const std::string &name) -> entt::handle
Creates a camera entity.
Definition defaults.cpp:592
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:228
gfx::uniform_handle handle
Definition uniform.cpp:9