Editor/SuperShotContext.cs

Editor utility that provides context for the SuperShot capture system. It stores and resolves editor freecam transforms, FOV and clipping planes, exposes the active scene and view mode, computes effective capture source, desired aspect and maps shot resolution enums to concrete pixel sizes.

File Access
using System;
using Sandbox;

namespace Editor.SuperShot;

public static class SuperShotContext
{
	public static Transform FreecamTransform { get; private set; }
	public static float FreecamFov { get; private set; } = 70f;
	public static float FreecamZNear { get; private set; } = 1f;
	public static float FreecamZFar { get; private set; } = 100000f;

	static RealTimeSince _sinceFreecamUpdate = 1000f;

	// Last resolved freecam, so a capture taken while the scene view isn't "current" still mirrors it.
	static Transform _lastGoodTransform;
	static float _lastGoodFov;
	static float _lastGoodZNear = 1f;
	static float _lastGoodZFar = 100000f;
	static bool _hasLastGood;

	public static bool HasFreecam => _sinceFreecamUpdate < 0.5f;

	public static void UpdateFreecam( Transform transform, float fov, float zNear, float zFar )
	{
		FreecamTransform = transform;
		FreecamFov = fov;
		FreecamZNear = MathX.Clamp( zNear, 1f, 1000f );
		FreecamZFar = zFar;
		_sinceFreecamUpdate = 0f;

		Remember( transform, FreecamFov, FreecamZNear, FreecamZFar );
	}

	static void Remember( Transform transform, float fov, float zNear, float zFar )
	{
		_lastGoodTransform = transform;
		_lastGoodFov = fov;
		_lastGoodZNear = zNear;
		_lastGoodZFar = zFar;
		_hasLastGood = true;
	}

	public static bool TryResolveFreecam( out Transform transform, out float fov, out float zNear, out float zFar )
	{
		var viewport = SceneViewWidget.Current?.LastSelectedViewportWidget;
		if ( viewport?.State is not null )
		{
			transform = new Transform( viewport.State.CameraPosition, viewport.State.CameraRotation );
			fov = EditorPreferences.CameraFieldOfView;
			zNear = MathX.Clamp( EditorPreferences.CameraZNear, 1f, 1000f );
			zFar = EditorPreferences.CameraZFar;
			Remember( transform, fov, zNear, zFar );
			return true;
		}

		if ( HasFreecam )
		{
			transform = FreecamTransform;
			fov = FreecamFov;
			zNear = FreecamZNear;
			zFar = FreecamZFar;
			Remember( transform, fov, zNear, zFar );
			return true;
		}

		if ( _hasLastGood )
		{
			transform = _lastGoodTransform;
			fov = _lastGoodFov;
			zNear = _lastGoodZNear;
			zFar = _lastGoodZFar;
			return true;
		}

		transform = default;
		fov = FreecamFov;
		zNear = FreecamZNear;
		zFar = FreecamZFar;
		return false;
	}

	public static Scene ActiveScene => SceneEditorSession.Active?.Scene;

	// True while playing and still following the game camera (not ejected to the free-fly editor camera).
	public static bool IsAttachedGameView
		=> SceneViewWidget.Current?.CurrentView == SceneViewWidget.ViewMode.Game;

	// Attached-play uses the live game camera the player sees; ejected/not-playing honours Shot Preferences.
	public static CaptureSource EffectiveSource( CaptureSettings settings )
		=> IsAttachedGameView ? CaptureSource.SceneMainCamera : settings.Source;

	public static float DesiredAspect { get; set; } = 16f / 9f;

	public static (int width, int height) ResolveSize( CaptureSettings s )
	{
		return s.Resolution switch
		{
			ShotResolution.OneK => (1024, 576),
			ShotResolution.HD720 => (1280, 720),
			ShotResolution.HD1080 => (1920, 1080),
			ShotResolution.QHD1440 => (2560, 1440),
			ShotResolution.UHD4K => (3840, 2160),
			ShotResolution.UHD5K => (5120, 2880),
			ShotResolution.UHD8K => (7680, 4320),
			ShotResolution.Square1080 => (1080, 1080),
			ShotResolution.Square4K => (3840, 3840),
			ShotResolution.Portrait => (1080, 1920),
			ShotResolution.Ultrawide1440 => (3440, 1440),
			ShotResolution.Ultrawide1080 => (2560, 1080),
			ShotResolution.Cinematic => (3840, 1646),
			ShotResolution.PackageSquare => (512, 512),
			ShotResolution.PackageWide => (910, 512),
			ShotResolution.PackageTall => (512, 910),
			ShotResolution.Custom => (Math.Max( 64, s.CustomWidth ), Math.Max( 64, s.CustomHeight )),
			_ => (1920, 1080)
		};
	}
}