using Editor.MapDoc; using NativeEngine; using NativeHammer; using System; namespace Editor.MapEditor; /// /// MapViews are owned by the MapViewMgr. They display the MapViewMgr's mapdoc. /// /// The MapView provides either a 2d or 3d view of the provided map doc. The rendering mode /// may be swapped between various 2d and 3d modes dynamically. In addition to basic display /// functionality the view also provides movement implementation for moving a camera within a 3d view /// or panning a 2d view. /// public partial class MapView : IHandle { #region IHandle // // A pointer to the actual native object // internal CMapView native; // // IHandle implementation // void IHandle.HandleInit( IntPtr ptr ) => OnNativeInit( ptr ); void IHandle.HandleDestroy() => OnNativeDestroy(); bool IHandle.HandleValid() => !native.IsNull; #endregion internal MapView() { } internal MapView( HandleCreationData _ ) { } internal virtual void OnNativeInit( CMapView ptr ) { native = ptr; } internal virtual void OnNativeDestroy() { native = IntPtr.Zero; } /// /// Read-only SceneCamera set automatically during rendering /// public SceneCamera SceneCamera { get; init; } = new SceneCamera( "MapView" ); public Gizmo.Instance GizmoInstance { get; set; } = new(); public MapDocument MapDoc => native.GetMapDoc(); /// /// /// public Vector2 MousePosition { get { native.GetMousePosition( out Vector2 mousePosition ); return mousePosition; } } /// /// Builds a ray from the mouse cursor /// public void BuildRay( out Vector3 startRay, out Vector3 endRay ) { native.GetCamera().BuildRay( MousePosition, out startRay, out endRay ); } /// /// Called for each MapView right before rendering begins, this is where you'd give it the scene to render. /// internal void PreRender( ISceneView sceneView ) { // Can / do we, I doubt we have a RenderContext? // using ( new Graphics.Scope( ref setup ) ) CToolCamera toolCamera = native.GetCamera(); // Readonly camera, we could probably hook this up with good reason SceneCamera.Position = toolCamera.GetOrigin(); SceneCamera.Angles = toolCamera.GetAngles(); SceneCamera.FieldOfView = toolCamera.GetCameraFOV(); SceneCamera.Size = new Vector2( toolCamera.GetWidth(), toolCamera.GetHeight() ); SceneCamera.Worlds.Add( GizmoInstance.World ); GizmoInstance.Input.Camera = SceneCamera; GizmoInstance.Input.CursorPosition = MousePosition; GizmoInstance.Input.CursorRay = SceneCamera.GetRay( MousePosition ); GizmoInstance.Input.LeftMouse = Application.MouseButtons.Contains( MouseButtons.Left ); GizmoInstance.Input.Modifiers = Application.KeyboardModifiers; GizmoInstance.Input.IsHovered = native.IsActive(); // Map current hammer manipulation mode to our gizmos GizmoInstance.Settings.EditMode = native.GetManipulationMode() switch { ManipulationMode_t.MANIPULATION_MODE_SELECT => "select", ManipulationMode_t.MANIPULATION_MODE_TRANSLATE => "position", // maybe should be translate ManipulationMode_t.MANIPULATION_MODE_ROTATE => "rotate", ManipulationMode_t.MANIPULATION_MODE_SCALE => "scale", ManipulationMode_t.MANIPULATION_MODE_PIVOT => "pivot", _ => "invalid" }; var scene = MapDoc?.World?.Scene; if ( scene == null ) return; using var sceneScope = scene.Push(); { GizmoInstance.Selection.Clear(); foreach ( var go in Selection.All.OfType() ) { GizmoInstance.Selection.Add( go.GameObject ); } } scene.EditorTick( RealTime.Now, RealTime.Delta ); var hash = GizmoInstance.Selection.GetHashCode(); // Draw gizmos using ( GizmoInstance.Push() ) { if ( GizmoInstance.Input.IsHovered ) { UpdateHovered(); } scene.EditorDraw(); EditorEvent.Run( "hammer.rendermapview", this ); DrawSelection(); MapViewDropTarget.CurrentDropTarget?.DrawGizmos( this ); // Something changed if ( hash != GizmoInstance.Selection.GetHashCode() ) { if ( !GizmoInstance.Input.Modifiers.HasFlag( KeyboardModifiers.Shift ) ) Selection.Clear(); // I bet this is expensive iterating the entire world foreach ( var node in MapDoc.World.Children.OfType().Where( x => GizmoInstance.Selection.OfType().Contains( x.GameObject ) ) ) { Selection.Add( node ); } // MapDoc.World.EditorSession } } // I forget what this is even doing // I think it lets gizmos stomp native traces, cool native.UpdateManagedGizmoState( GizmoInstance.current.HoveredPath != null, GizmoInstance.current.HitDistance ); // Add all our scene worlds to the render list sceneView.AddWorldToRenderList( scene.SceneWorld ); sceneView.AddWorldToRenderList( scene.DebugSceneWorld ); foreach ( var world in SceneCamera.Worlds.Where( x => x.IsValid() ) ) { sceneView.AddWorldToRenderList( world ); } } void UpdateHovered() { var scene = MapDoc?.World?.Scene; if ( scene is null ) return; var tr = scene.Trace.Ray( Gizmo.CurrentRay, Gizmo.RayDepth ) .UseRenderMeshes( true ) .UsePhysicsWorld( false ) .Run(); // We want to also trace native here float n = native.HitDistanceAtMouse(); if ( tr.Distance > n ) return; if ( tr.Hit && tr.Component.IsValid() ) { using ( Gizmo.ObjectScope( tr.GameObject, tr.GameObject.WorldTransform ) ) { Gizmo.Hitbox.DepthBias = 1; Gizmo.Hitbox.TrySetHovered( tr.Distance ); if ( tr.Component is ModelRenderer mr && mr.Model is not null ) // TODO Is this selected { Gizmo.Draw.Color = Gizmo.Colors.Active.WithAlpha( MathF.Sin( RealTime.Now * 20.0f ).Remap( -1, 1, 0.3f, 0.8f ) ); Gizmo.Draw.LineBBox( mr.Model.Bounds ); } } } } void DrawSelection() { var scene = MapDoc?.World.Scene; if ( scene is null ) return; foreach ( var modelRenderer in GizmoInstance.Selection.OfType().SelectMany( x => x.Components.GetAll() ) ) { if ( modelRenderer.Model is null ) continue; using ( Gizmo.ObjectScope( modelRenderer.GameObject, default ) ) { Gizmo.Transform = modelRenderer.GameObject.WorldTransform; Gizmo.Draw.Color = Gizmo.Colors.Selected; Gizmo.Draw.LineBBox( modelRenderer.Model.Bounds ); } } } // There is access to the Qt IMapViewWindow via GetMapViewWindow() could cast to Widget ( Would be ever want that? ) } internal static class MapViewRender { internal static void OnPreRender( MapView view, ISceneView sceneView ) => view.PreRender( sceneView ); /// /// Called from CMapView::ObjectsAt giving managed an opportunity to add a trace to the list. /// These then get sorted by distance and selected. /// internal static bool TraceManagedGizmos( MapView view, Vector2 mousePos, ref NativeHammer.HitInfo_t hitInfo ) { var frame = view.GizmoInstance.current; if ( string.IsNullOrEmpty( frame.HoveredPath ) ) { return false; } hitInfo.m_flTraceT = frame.HitDistance; return true; } }