using System; using ImGui; using GlitchyEngine.Renderer; using GlitchyEngine.Math; using GlitchyEngine; using GlitchyEngine.World; using ImGuizmo; using GlitchyEngine.Events; namespace GlitchyEditor.EditWindows { class SceneViewportWindow : EditorWindow { public const String s_WindowTitle = "Scene"; private RenderTargetGroup _renderTarget ~ _?.ReleaseRef(); public Event> ViewportSizeChangedEvent ~ _.Dispose(); public Event> EntityClickedEvent ~ _.Dispose(); public RenderTargetGroup RenderTarget { get => _renderTarget; set { if(_renderTarget == value) return; SetReference!(_renderTarget, value); } } public this(Editor editor) { _editor = editor; } private ImGui.Vec2 oldViewportSize = .(100, 100); private bool viewPortChanged; public uint32 SelectedEntityId; public bool SelectionChanged; public Vector2 ViewportSize => (Vector2)oldViewportSize; protected override void InternalShow() { ImGui.PushStyleVar(.WindowPadding, ImGui.Vec2(1, 1)); defer ImGui.PopStyleVar(); if(!ImGui.Begin(s_WindowTitle, &_open, .NoScrollbar | .MenuBar)) { ImGui.End(); return; } ShowMenuBar(); if(ImGui.IsWindowHovered() && Input.IsMouseButtonPressing(.RightButton)) { var currentWindow = ImGui.GetCurrentWindow(); ImGui.FocusWindow(currentWindow); } _hasFocus = ImGui.IsWindowFocused(); if (_hasFocus && !_editor.CurrentCamera.InUse) { if (Input.IsKeyPressing(.Q)) _gizmoType = .TRANSLATE; if (Input.IsKeyPressing(.W)) _gizmoType = .ROTATE; if (Input.IsKeyPressing(.E)) _gizmoType = .SCALE; if (Input.IsKeyPressing(.G)) _gizmoMode = .WORLD; if (Input.IsKeyPressing(.L)) _gizmoMode = .LOCAL; } var viewportSize = ImGui.GetContentRegionAvail(); if (_editor.CurrentCamera.[Friend]BindMouse) WrapMouseInViewport(); if(_renderTarget != null) { ImGui.Image(_renderTarget.GetViewBinding(0), viewportSize); //ImGui.Image(_editor.CurrentCamera.RenderTarget.GetViewBinding(0), viewportSize); //ImGui.Image(_editor.CurrentScene.[Friend]_compositeTarget.GetViewBinding(0), viewportSize); } if (ImGui.BeginDragDropTarget()) { ImGui.Payload* payload = ImGui.AcceptDragDropPayload("CONTENT_BROWSER_ITEM"); if (payload != null) { Log.EngineLogger.Warning(""); StringView path = .((char8*)payload.Data, (int)payload.DataSize); _editor.RequestOpenScene(this, path); } ImGui.EndDragDropTarget(); } bool gizmoUsed = DrawImGuizmo(viewportSize); MousePicking(viewportSize, gizmoUsed); ImGui.End(); if(oldViewportSize != viewportSize) { ViewportSizeChangedEvent.Invoke(this, (Vector2)viewportSize); viewPortChanged = true; oldViewportSize = viewportSize; } } /// Wraps the mouse, so that it always stays in the viewport. private void WrapMouseInViewport() { var mousePos = (Vector2)ImGui.GetMousePos(); var winPos = (Vector2)ImGui.GetWindowPos(); var winSize = (Vector2)ImGui.GetWindowSize(); Vector2 newMousePos = mousePos; if (mousePos.X < winPos.X) { newMousePos.X = winPos.X + winSize.X - 1; } else if (mousePos.X > winPos.X + winSize.X) { newMousePos.X = winPos.X + 1; } if (mousePos.Y < winPos.Y) { newMousePos.Y = winPos.Y + winSize.Y - 1; } else if (mousePos.Y > winPos.Y + winSize.Y) { newMousePos.Y = winPos.Y + 1; } if (newMousePos != mousePos) { Input.SetMousePosition((Int2)newMousePos); _editor.CurrentCamera.[Friend]MouseCooldown = 2; } } private void MousePicking(ImGui.Vec2 viewportSize, bool gizmoUsed) { Vector2 relativeMouse = (Vector2)ImGui.GetMousePos() - (Vector2)ImGui.GetItemRectMin(); int rtWidth = _editor.CurrentScene.[Friend]_compositeTarget.Width; int rtHeight = _editor.CurrentScene.[Friend]_compositeTarget.Height; if (Input.IsMouseButtonPressing(.LeftButton) && ImGui.IsWindowHovered() && !gizmoUsed && !_editor.CurrentCamera.InUse && relativeMouse.X >= 0 && relativeMouse.Y >= 0 && relativeMouse.X < viewportSize.x && relativeMouse.Y < viewportSize.y && relativeMouse.X < rtWidth && relativeMouse.Y < rtHeight) { uint32 id = uint32.MaxValue; _editor.CurrentScene.[Friend]_compositeTarget.GetData(&id, 1, (.)relativeMouse.X, (.)relativeMouse.Y, 1, 1); SelectionChanged = true; SelectedEntityId = id; EntityClickedEvent(this, id); } else { SelectionChanged = false; } } private ImGuizmo.OPERATION _gizmoType = .TRANSLATE; private ImGuizmo.MODE _gizmoMode = .LOCAL; private float _snap = 0.5f; private float _angleSnap = 45.0f; private bool _doSnap = false; private void ShowMenuBar() { if(ImGui.BeginMenuBar()) { if (ImGui.RadioButton("Position", _gizmoType.HasFlag(.TRANSLATE))) { if (Input.IsKeyPressed(Key.Control)) _gizmoType ^= .TRANSLATE; else _gizmoType = .TRANSLATE; } if (ImGui.RadioButton("Rotation", _gizmoType.HasFlag(.ROTATE))) { if (Input.IsKeyPressed(Key.Control)) _gizmoType ^= .ROTATE; else _gizmoType = .ROTATE; } if (ImGui.RadioButton("Scale", _gizmoType.HasFlag(.SCALE))) { if (Input.IsKeyPressed(Key.Control)) _gizmoType ^= .SCALE; else _gizmoType = .SCALE; } if (ImGui.RadioButton("All", _gizmoType == .TRANSLATE | .ROTATE | .SCALE)) _gizmoType = .TRANSLATE | .ROTATE | .SCALE; // If we scale, the mode must be local otherwise we could skew the matrix. if (_gizmoType.HasFlag(.SCALE)) _gizmoMode = .LOCAL; if (ImGui.MenuItem(_gizmoMode == .WORLD ? "Global" : "Local", null, true, !_gizmoType.HasFlag(.SCALE))) { if (_gizmoMode == .WORLD) _gizmoMode = .LOCAL; else _gizmoMode = .WORLD; } _doSnap = Input.IsKeyPressed(.Shift); ImGui.PushItemWidth(100); if (_gizmoType.HasFlag(.ROTATE)) { ImGui.DragFloat("Angle Snap", &_angleSnap, 1.0f, 0.0f, 180.0f); } if (_gizmoType.HasFlag(.TRANSLATE) || _gizmoType.HasFlag(.SCALE)) { ImGui.DragFloat("Snap", &_snap, 0.1f, 0.0f, float.MaxValue); } ImGui.PopItemWidth(); ImGui.EndMenuBar(); } } private bool DrawImGuizmo(ImGui.Vec2 viewportSize) { ImGuizmo.SetOrthographic(false); ImGuizmo.SetDrawlist(); var topLeft = ImGui.GetWindowPos(); var cntMin = ImGui.GetWindowContentRegionMin(); topLeft.x += cntMin.x; topLeft.y += cntMin.y; ImGuizmo.SetRect(topLeft.x, topLeft.y, viewportSize.x, viewportSize.y); var view = _editor.CurrentCamera.View; var projection = _editor.CurrentCamera.Projection; if(_editor.EntityHierarchyWindow.SelectedEntities.Count == 0) return false; var entity = _editor.EntityHierarchyWindow.SelectedEntities.Back; var transformCmp = entity.GetComponent(); var worldTransform = transformCmp.WorldTransform; Matrix parentView = .Identity; if (transformCmp.Parent != .InvalidEntity) { var parentTransformCmp = Entity(transformCmp.Parent, entity.Scene).GetComponent(); parentView = parentTransformCmp.WorldTransform.Invert(); } Vector3 snap = .(_snap); if (_gizmoType.HasFlag(.ROTATE)) snap = .(_angleSnap); if (ImGuizmo.Manipulate((.)&view, (.)&projection, _gizmoType, _gizmoMode, (.)&worldTransform, null, _doSnap ? (.)&snap : null)) { // TODO: Fix when parent is scaled // Seems to work fine for parent rotation and translation but scaled parent ruins everything // (probably because scaling a rotated matrix results in a skewed matrix, but unity can do it, so should we) transformCmp.LocalTransform = parentView * worldTransform; } return ImGuizmo.IsUsing(); } } }