Files
sbox-public/engine/Sandbox.Tools/MapEditor/MapView.cs
s&box team 71f266059a Open source release
This commit imports the C# engine code and game files, excluding C++ source code.

[Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
2025-11-24 09:05:18 +00:00

248 lines
6.9 KiB
C#

using Editor.MapDoc;
using NativeEngine;
using NativeHammer;
using System;
namespace Editor.MapEditor;
/// <summary>
/// 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.
/// </summary>
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;
}
/// <summary>
/// Read-only SceneCamera set automatically during rendering
/// </summary>
public SceneCamera SceneCamera { get; init; } = new SceneCamera( "MapView" );
public Gizmo.Instance GizmoInstance { get; set; } = new();
public MapDocument MapDoc => native.GetMapDoc();
/// <summary>
///
/// </summary>
public Vector2 MousePosition
{
get
{
native.GetMousePosition( out Vector2 mousePosition );
return mousePosition;
}
}
/// <summary>
/// Builds a ray from the mouse cursor
/// </summary>
public void BuildRay( out Vector3 startRay, out Vector3 endRay )
{
native.GetCamera().BuildRay( MousePosition, out startRay, out endRay );
}
/// <summary>
/// Called for each MapView right before rendering begins, this is where you'd give it the scene to render.
/// </summary>
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<MapGameObject>() )
{
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<MapGameObject>().Where( x => GizmoInstance.Selection.OfType<GameObject>().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<GameObject>().SelectMany( x => x.Components.GetAll<ModelRenderer>() ) )
{
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 );
/// <summary>
/// Called from CMapView::ObjectsAt giving managed an opportunity to add a trace to the list.
/// These then get sorted by distance and selected.
/// </summary>
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;
}
}