mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-01-04 04:18:27 -05:00
765 lines
20 KiB
C#
765 lines
20 KiB
C#
|
|
using Sandbox.Rendering;
|
|
using System.Drawing;
|
|
using System.Text.Json.Serialization;
|
|
|
|
namespace Sandbox;
|
|
|
|
/// <summary>
|
|
/// Every scene should have at least one Camera.
|
|
/// </summary>
|
|
[Expose]
|
|
[Title( "Camera" )]
|
|
[Category( "Camera" )]
|
|
[Icon( "videocam" )]
|
|
[EditorHandle( "materials/gizmo/camera.png" )]
|
|
public sealed partial class CameraComponent : Component, Component.ExecuteInEditor, Component.ISceneStage
|
|
{
|
|
SceneCamera sceneCamera;
|
|
|
|
/// <summary>
|
|
/// The clear flags for this camera.
|
|
/// </summary>
|
|
[Property]
|
|
public ClearFlags ClearFlags { get; set; } = ClearFlags.Color | ClearFlags.Stencil | ClearFlags.Depth;
|
|
|
|
/// <summary>
|
|
/// The background color of this camera's view if there's no 2D Sky in the scene.
|
|
/// </summary>
|
|
[Property]
|
|
public Color BackgroundColor { get; set; } = "#557685";
|
|
|
|
bool _isMainCamera = true;
|
|
int _priority = 1;
|
|
|
|
/// <summary>
|
|
/// Returns true if this is the main game camera.
|
|
/// </summary>
|
|
[Property]
|
|
public bool IsMainCamera
|
|
{
|
|
get => _isMainCamera;
|
|
set
|
|
{
|
|
if ( _isMainCamera == value )
|
|
return;
|
|
|
|
_isMainCamera = value;
|
|
Scene.UpdateMainCamera();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The axis to use for the field of view.
|
|
/// </summary>
|
|
[Property, Title( "FOV Axis" ), HideIf( nameof( Orthographic ), true )]
|
|
public Axis FovAxis { get; set; } = Axis.Horizontal;
|
|
|
|
/// <summary>
|
|
/// The field of view of this camera.
|
|
/// </summary>
|
|
[Property, Range( 1, 179 ), HideIf( nameof( Orthographic ), true )]
|
|
public float FieldOfView { get; set; } = 60;
|
|
|
|
/// <summary>
|
|
/// The camera's near clip plane distance. This is the closest distance this camera will be able to render.
|
|
/// A good value for this is about 5. Below 5 and particularly below 1 you're going to start to see
|
|
/// a lot of artifacts like z-fighting.
|
|
/// </summary>
|
|
[Property, Range( 1, 1000 ), Step( 1 )]
|
|
public float ZNear { get; set; } = 10;
|
|
|
|
/// <summary>
|
|
/// The camera's far clip plane distance. This is the furthest distance this camera will be able to render.
|
|
/// This value totally depends on the game you're making. Shorter the better, sensible ranges would be
|
|
/// between about 1000 and 30000, but if you want it to be further out you can balance that out by making
|
|
/// ZNear larger.
|
|
/// </summary>
|
|
[Property, Range( 1, 100000 ), Step( 1 )]
|
|
public float ZFar { get; set; } = 10000;
|
|
|
|
/// <summary>
|
|
/// The priority of this camera. Dictates which camera gets rendered on top of another. Higher means it'll be rendered on top.
|
|
/// </summary>
|
|
[Property, Range( 1, 16 )]
|
|
public int Priority
|
|
{
|
|
get => _priority;
|
|
set
|
|
{
|
|
if ( _priority == value ) return;
|
|
|
|
_priority = value;
|
|
Scene.UpdateMainCamera();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether or not to use orthographic projection instead of perspective.
|
|
/// </summary>
|
|
[Property]
|
|
public bool Orthographic { get; set; }
|
|
|
|
/// <summary>
|
|
/// The orthographic size for this camera while <see cref="Orthographic"/> is set to true.
|
|
/// </summary>
|
|
[Property]
|
|
public float OrthographicHeight { get; set; } = 1204;
|
|
|
|
/// <summary>
|
|
/// The HMD eye that this camera is targeting.
|
|
/// Use <see cref="StereoTargetEye.None"/> for the user's monitor (i.e. the companion window).
|
|
/// </summary>
|
|
[Property]
|
|
public StereoTargetEye TargetEye { get; set; } = StereoTargetEye.None;
|
|
|
|
/// <summary>
|
|
/// A list of tags that will be checked to include specific game objects when rendering this camera.
|
|
/// If none are set, it will include everything.
|
|
/// </summary>
|
|
[Property]
|
|
public TagSet RenderTags { get; set; } = new();
|
|
|
|
/// <summary>
|
|
/// A list of tags that will be checked to exclude specific game objects when rendering this camera.
|
|
/// </summary>
|
|
[Property]
|
|
public TagSet RenderExcludeTags { get; set; } = new();
|
|
|
|
/// <summary>
|
|
/// The size of the camera represented on the screen. Normalized between 0 and 1.
|
|
/// </summary>
|
|
[Property, Range( 0, 1, slider: false )]
|
|
public Vector4 Viewport { get; set; } = new( 0.0f, 0.0f, 1.0f, 1.0f ); // todo this should use a rect
|
|
|
|
/// <summary>
|
|
/// If specified, this camera will render to this RenderTexture instead of the screen.
|
|
/// This can then be used in other stuff like materials.
|
|
/// </summary>
|
|
[Property]
|
|
public RenderTextureAsset RenderTexture;
|
|
|
|
/// <summary>
|
|
/// Since we are rendering to a texture, better to generate mipmaps for it.
|
|
/// </summary>
|
|
private CommandList _renderTextureMipGenCommandList
|
|
{
|
|
get
|
|
{
|
|
var cmd = new CommandList( "Generate MipMaps For RenderTexture" );
|
|
|
|
if ( RenderTexture != null && RenderTexture.Texture != null && RenderTexture.Texture.Mips > 1 )
|
|
cmd.GenerateMipMaps( RenderTexture.Texture );
|
|
|
|
return cmd;
|
|
}
|
|
}
|
|
|
|
private Texture _renderTarget;
|
|
|
|
/// <summary>
|
|
/// The texture to draw this camera to.
|
|
/// Requires <see cref="Texture.CreateRenderTarget()"/>
|
|
/// </summary>
|
|
[JsonIgnore, Hide]
|
|
public Texture RenderTarget
|
|
{
|
|
get => RenderTexture?.Texture ?? _renderTarget;
|
|
set
|
|
{
|
|
if ( _renderTarget == value )
|
|
return;
|
|
|
|
if ( value is not null )
|
|
{
|
|
if ( !value.Desc.m_nFlags.HasFlag( NativeEngine.RuntimeTextureSpecificationFlags.TSPEC_RENDER_TARGET ) )
|
|
throw new Exception( $"{nameof( RenderTarget )} texture needs to be a render target" );
|
|
}
|
|
|
|
_renderTarget = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Render this camera using a different render mode
|
|
/// </summary>
|
|
[JsonIgnore, Hide]
|
|
public SceneCameraDebugMode DebugMode { get; set; }
|
|
|
|
/// <summary>
|
|
/// Render this camera using a wireframe view.
|
|
/// </summary>
|
|
[JsonIgnore, Hide]
|
|
public bool WireframeMode { get; set; }
|
|
|
|
/// <summary>
|
|
/// Accessor for getting this Camera Component's SceneCamera
|
|
/// </summary>
|
|
internal SceneCamera SceneCamera
|
|
{
|
|
get => sceneCamera;
|
|
}
|
|
|
|
public override void Reset()
|
|
{
|
|
base.Reset();
|
|
|
|
RenderTags = new();
|
|
RenderExcludeTags = new();
|
|
Viewport = new( 0.0f, 0.0f, 1.0f, 1.0f );
|
|
ClearFlags = ClearFlags.Color | ClearFlags.Stencil | ClearFlags.Depth;
|
|
}
|
|
|
|
private void EnsureSceneCameraCreated()
|
|
{
|
|
if ( sceneCamera is not null ) return;
|
|
|
|
// scene should be scoped in!
|
|
Assert.NotNull( Scene );
|
|
if ( Game.IsPlaying )
|
|
{
|
|
// Only assert active scene in play mode, since we want to create cameras anywhere in the editor
|
|
Assert.AreEqual( Game.ActiveScene, Scene, "Camera scene is not active scene" );
|
|
}
|
|
|
|
// Use the GameObject name so we can identify the camera when debugging
|
|
sceneCamera = new( GameObject.Name );
|
|
|
|
sceneCamera.OnRenderStageHook = ExecuteCommandLists;
|
|
}
|
|
|
|
protected override void OnAwake()
|
|
{
|
|
EnsureSceneCameraCreated();
|
|
Scene.Cameras.Add( this );
|
|
}
|
|
|
|
protected override void OnDestroy()
|
|
{
|
|
Scene.Cameras.Remove( this );
|
|
sceneCamera?.Dispose();
|
|
sceneCamera = null;
|
|
}
|
|
|
|
protected override void DrawGizmos()
|
|
{
|
|
if ( sceneCamera is null )
|
|
return;
|
|
|
|
using var scope = Gizmo.Scope( $"{GetHashCode()}" );
|
|
|
|
Gizmo.Transform = global::Transform.Zero;
|
|
|
|
UpdateSceneCameraTransform( sceneCamera );
|
|
|
|
var frustum = sceneCamera.GetFrustum( new Rect( 0f, 0f, 1920f, 1080f ), new Vector2( 1920, 1080 ) );
|
|
|
|
Gizmo.Draw.Color = Color.White.WithAlpha( 0.4f );
|
|
Gizmo.Draw.LineFrustum( frustum );
|
|
}
|
|
|
|
internal void UpdateSceneCameraTransform( SceneCamera camera )
|
|
{
|
|
camera.Position = WorldPosition;
|
|
camera.Rotation = WorldRotation;
|
|
camera.ZNear = ZNear;
|
|
camera.ZFar = ZFar;
|
|
camera.Rect = new Rect( Viewport.x, Viewport.y, Viewport.z, Viewport.w );
|
|
camera.Size = ScreenRect.Size;
|
|
camera.Ortho = Orthographic;
|
|
camera.OrthoHeight = OrthographicHeight;
|
|
|
|
if ( FovAxis == Axis.Vertical )
|
|
camera.FieldOfView = Screen.CreateVerticalFieldOfView( FieldOfView );
|
|
else
|
|
camera.FieldOfView = FieldOfView;
|
|
}
|
|
|
|
internal void UpdateSceneCameraStereo( SceneCamera camera )
|
|
{
|
|
camera.TargetEye = TargetEye;
|
|
camera.WantsStereoSubmit = !Scene.IsEditor;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update a SceneCamera with the settings from this component
|
|
/// </summary>
|
|
public void UpdateSceneCamera( SceneCamera camera, bool includeTags = true )
|
|
{
|
|
if ( Scene is null )
|
|
{
|
|
Log.Warning( $"Trying to update camera from {this} but has no scene" );
|
|
return;
|
|
}
|
|
|
|
UpdateSceneCameraTransform( camera );
|
|
|
|
camera.World = Scene.SceneWorld;
|
|
camera.ClearFlags = ClearFlags;
|
|
camera.Worlds.Clear();
|
|
camera.Worlds.Add( Scene.DebugSceneWorld );
|
|
|
|
// Also render any active gizmos
|
|
if ( Gizmo.Active is not null )
|
|
camera.Worlds.Add( Gizmo.Active.World );
|
|
|
|
CopyPostProcessing( camera );
|
|
|
|
// A camera (such as the editor viewport) might not want to take render tags into account
|
|
// from an existing camera source
|
|
if ( includeTags )
|
|
{
|
|
camera.RenderTags.SetFrom( RenderTags );
|
|
camera.ExcludeTags.SetFrom( RenderExcludeTags );
|
|
}
|
|
|
|
// Merge our global Scene attributes into the camera
|
|
Scene.RenderAttributes.MergeTo( camera.Attributes );
|
|
|
|
camera.DebugMode = DebugMode;
|
|
camera.WireframeMode = WireframeMode;
|
|
camera.EnablePostProcessing = EnablePostProcessing;
|
|
camera.BackgroundColor = ClearFlags.Contains( ClearFlags.Color ) ? BackgroundColor : Color.Transparent;
|
|
}
|
|
|
|
internal void CopyPostProcessing( SceneCamera camera )
|
|
{
|
|
if ( Scene is null )
|
|
{
|
|
Log.Warning( $"Trying to update camera from {this} but has no scene" );
|
|
return;
|
|
}
|
|
|
|
// defaults - let components override
|
|
camera.CubemapFog.Enabled = false;
|
|
camera.Bloom.Enabled = false;
|
|
|
|
AutoExposure.Apply( camera );
|
|
|
|
// Don't set the background colour if we don't clear color
|
|
// Also don't hook into render overlays, nor volumetric fog stuff.
|
|
if ( ClearFlags.Contains( ClearFlags.Color ) )
|
|
{
|
|
camera.VolumetricFog.Enabled = Scene.GetAllComponents<VolumetricFogVolume>().Count() > 0;
|
|
camera.VolumetricFog.DrawDistance = 4096;
|
|
camera.VolumetricFog.FadeInStart = 64;
|
|
camera.VolumetricFog.FadeInEnd = 256;
|
|
camera.VolumetricFog.IndirectStrength = 1.0f;
|
|
camera.VolumetricFog.Anisotropy = 1;
|
|
camera.VolumetricFog.Scattering = 1.0f;
|
|
camera.VolumetricFog.BakedIndirectTexture = Scene.GetAllComponents<VolumetricFogController>().FirstOrDefault()?.BakedFogTexture;
|
|
}
|
|
|
|
#pragma warning disable CS0612
|
|
GameObject.RunEvent<ISceneCameraSetup>( x => x.SetupCamera( this, camera ) );
|
|
#pragma warning restore CS0612
|
|
|
|
//
|
|
// Child camera executes command lists from this camera
|
|
//
|
|
camera.OnRenderStageHook = ExecuteCommandLists;
|
|
|
|
//
|
|
// Hack because I don't want this to have to be on a camera. This
|
|
// is hidden from users, so we'll figure out how to square it later
|
|
//
|
|
foreach ( var cubemapFog in Scene.GetAllComponents<CubemapFog>() )
|
|
{
|
|
if ( cubemapFog.Tags.HasAny( RenderExcludeTags ) )
|
|
continue;
|
|
|
|
cubemapFog.SetupCamera( this, camera );
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update the SceneCamera UI with the settings from this component
|
|
/// </summary>
|
|
private void UpdateSceneCameraUI( SceneCamera camera )
|
|
{
|
|
if ( Scene is null )
|
|
return;
|
|
|
|
camera.OnRenderUI = () => OnCameraRenderUI( camera );
|
|
}
|
|
|
|
[Obsolete( "Use CommandList" )]
|
|
public IDisposable AddHookAfterOpaque( string debugName, int order, Action<SceneCamera> renderEffect ) => null;
|
|
|
|
/// <summary>
|
|
/// Obsolete 09/06/2025
|
|
/// </summary>
|
|
[Obsolete( "Use CommandList" )]
|
|
public IDisposable AddHookAfterTransparent( string debugName, int order, Action<SceneCamera> renderEffect ) => null;
|
|
|
|
/// <summary>
|
|
/// Obsolete 09/06/2025
|
|
/// </summary>
|
|
[Obsolete( "Use CommandList" )]
|
|
public IDisposable AddHookBeforeOverlay( string debugName, int order, Action<SceneCamera> renderEffect ) => null;
|
|
|
|
/// <summary>
|
|
/// Obsolete 02/10/2025
|
|
/// </summary>
|
|
[Obsolete( "Use CommandList" )]
|
|
public IDisposable AddHookAfterUI( string debugName, int order, Action<SceneCamera> renderEffect ) => null;
|
|
|
|
private void OnCameraRenderUI( SceneCamera camera )
|
|
{
|
|
if ( Scene is null )
|
|
return;
|
|
|
|
foreach ( var c in Scene.GetAll<ScreenPanel>().OrderBy( x => x.ZIndex ) )
|
|
{
|
|
if ( !c.Active ) continue;
|
|
var target = c.TargetCamera ?? (IsMainCamera ? this : null);
|
|
if ( target != this ) continue;
|
|
if ( RenderExcludeTags.HasAny( c.GameObject.Tags ) ) continue;
|
|
|
|
c.Render();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Obsolete 02/10/2025
|
|
/// </summary>
|
|
[Obsolete]
|
|
public interface ISceneCameraSetup
|
|
{
|
|
void SetupCamera( CameraComponent camera, SceneCamera sceneCamera );
|
|
}
|
|
|
|
internal bool IsSceneEditorCamera;
|
|
|
|
internal void InitializeRendering()
|
|
{
|
|
using ( Scene.Push() )
|
|
{
|
|
EnsureSceneCameraCreated();
|
|
|
|
if ( IsSceneEditorCamera )
|
|
{
|
|
UpdateSceneCamera( sceneCamera );
|
|
Scene.Camera?.CopyPostProcessing( sceneCamera );
|
|
}
|
|
else
|
|
{
|
|
UpdateSceneCamera( sceneCamera );
|
|
}
|
|
|
|
UpdateSceneCameraUI( sceneCamera );
|
|
UpdateSceneCameraStereo( sceneCamera );
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This should only be called when creating render lists!!
|
|
/// </summary>
|
|
internal void AddToRenderList( SwapChainHandle_t swapChain, Vector2? size )
|
|
{
|
|
if ( !Active )
|
|
return;
|
|
|
|
// if width and height are too small, skip it
|
|
if ( Viewport.z <= 0 ) return;
|
|
if ( Viewport.w <= 0 ) return;
|
|
|
|
using ( Scene.Push() )
|
|
{
|
|
InitializeRendering();
|
|
|
|
if ( RenderTarget is not null && RenderTarget.native.IsValid )
|
|
{
|
|
SceneCamera.RenderToTexture( RenderTarget, size ?? CustomSize, default );
|
|
}
|
|
else
|
|
{
|
|
SceneCamera.AddToRenderList( swapChain, size ?? CustomSize );
|
|
}
|
|
}
|
|
}
|
|
|
|
public Vector2 PointToScreenNormal( in Vector3 worldPosition )
|
|
{
|
|
EnsureSceneCameraCreated();
|
|
UpdateSceneCameraTransform( sceneCamera );
|
|
|
|
return sceneCamera.ToScreenNormal( worldPosition );
|
|
}
|
|
|
|
public Vector2 PointToScreenPixels( in Vector3 worldPosition )
|
|
{
|
|
var sr = ScreenRect;
|
|
var v = PointToScreenNormal( worldPosition );
|
|
return new Vector2( v.x, v.y ) * sr.Size;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The size of the viewport, in screen coordinates
|
|
/// </summary>
|
|
public Rect ScreenRect
|
|
{
|
|
get
|
|
{
|
|
var ss = CustomSize ?? Screen.Size;
|
|
return new Rect( ss.x * Viewport.x, ss.y * Viewport.y, ss.x * Viewport.z, ss.y * Viewport.w );
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Given a BBox in world space, will return the screen space rect that totally contains the box.
|
|
/// </summary>
|
|
public Rect BBoxToScreenPixels( BBox bounds, out bool isBehind )
|
|
{
|
|
Vector2[] corners = new Vector2[8];
|
|
Vector3 min = bounds.Mins;
|
|
Vector3 max = bounds.Maxs;
|
|
|
|
isBehind = true;
|
|
|
|
corners[0] = PointToScreenPixels( new Vector3( min.x, min.y, min.z ) );
|
|
corners[1] = PointToScreenPixels( new Vector3( max.x, min.y, min.z ) );
|
|
corners[2] = PointToScreenPixels( new Vector3( min.x, max.y, min.z ) );
|
|
corners[3] = PointToScreenPixels( new Vector3( min.x, min.y, max.z ) );
|
|
corners[4] = PointToScreenPixels( new Vector3( max.x, max.y, min.z ) );
|
|
corners[5] = PointToScreenPixels( new Vector3( min.x, max.y, max.z ) );
|
|
corners[6] = PointToScreenPixels( new Vector3( max.x, min.y, max.z ) );
|
|
corners[7] = PointToScreenPixels( new Vector3( max.x, max.y, max.z ) );
|
|
|
|
Vector2 minScreen = corners[0];
|
|
Vector2 maxScreen = corners[0];
|
|
|
|
for ( int i = 1; i < 8; i++ )
|
|
{
|
|
minScreen = Vector2.Min( minScreen, corners[i] );
|
|
maxScreen = Vector2.Max( maxScreen, corners[i] );
|
|
}
|
|
|
|
var ss = CustomSize ?? Screen.Size;
|
|
|
|
// off screen
|
|
if ( maxScreen.x < 0 ) isBehind = true;
|
|
if ( maxScreen.y < 0 ) isBehind = true;
|
|
if ( minScreen.x > ss.x ) isBehind = true;
|
|
if ( minScreen.y > ss.y ) isBehind = true;
|
|
|
|
return Rect.FromPoints( minScreen, maxScreen );
|
|
}
|
|
|
|
public Vector2 PointToScreenPixels( Vector3 worldPosition, out bool isBehind )
|
|
{
|
|
EnsureSceneCameraCreated();
|
|
UpdateSceneCameraTransform( sceneCamera );
|
|
|
|
var sr = ScreenRect;
|
|
var v = sceneCamera.ToScreenWithDirection( worldPosition );
|
|
isBehind = v.z <= 0.0f;
|
|
return new Vector2( v.x, v.y ) * sr.Size;
|
|
}
|
|
|
|
public Vector2 PointToScreenNormal( Vector3 worldPosition, out bool isBehind )
|
|
{
|
|
EnsureSceneCameraCreated();
|
|
UpdateSceneCameraTransform( sceneCamera );
|
|
|
|
var v = sceneCamera.ToScreenWithDirection( worldPosition );
|
|
isBehind = v.z <= 0.0f;
|
|
return new Vector2( v.x, v.y );
|
|
}
|
|
|
|
public Ray ScreenPixelToRay( Vector2 pixelPosition )
|
|
{
|
|
EnsureSceneCameraCreated();
|
|
UpdateSceneCameraTransform( sceneCamera );
|
|
|
|
return sceneCamera.GetRay( pixelPosition, ScreenRect.Size );
|
|
}
|
|
|
|
public Ray ScreenNormalToRay( Vector3 normalPosition )
|
|
{
|
|
var pixelPosition = new Vector3(
|
|
normalPosition.x * ScreenRect.Size.x,
|
|
normalPosition.y * ScreenRect.Size.y,
|
|
normalPosition.z );
|
|
|
|
return ScreenPixelToRay( pixelPosition );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert from screen coords to world coords on the near frustum plane.
|
|
/// </summary>
|
|
public Vector3 ScreenToWorld( Vector2 screen )
|
|
{
|
|
EnsureSceneCameraCreated();
|
|
UpdateSceneCameraTransform( sceneCamera );
|
|
|
|
return sceneCamera.ToWorld( screen );
|
|
}
|
|
|
|
protected override void OnEnabled()
|
|
{
|
|
Scene?.UpdateMainCamera();
|
|
|
|
AddCommandList( _hudCommandList, Rendering.Stage.AfterPostProcess, 5000 );
|
|
AddCommandList( _overlayCommandList, Rendering.Stage.AfterUI, 5000 );
|
|
|
|
if ( RenderTexture is not null )
|
|
AddCommandList( _renderTextureMipGenCommandList, Rendering.Stage.AfterPostProcess, 10000 );
|
|
}
|
|
|
|
protected override void OnDisabled()
|
|
{
|
|
Scene?.UpdateMainCamera();
|
|
|
|
RemoveCommandList( _hudCommandList );
|
|
RemoveCommandList( _overlayCommandList );
|
|
RemoveCommandList( _renderTextureMipGenCommandList );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the view frustum of the current screen rect.
|
|
/// </summary>
|
|
public Frustum GetFrustum()
|
|
{
|
|
UpdateSceneCameraTransform( sceneCamera );
|
|
return sceneCamera.GetFrustum( ScreenRect );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Given a pixel rect return a frustum on the current camera.
|
|
/// </summary>
|
|
public Frustum GetFrustum( Rect screenRect )
|
|
{
|
|
// Note: Updating every time might cause issues if too much pressure is put on this function. Keep an eye on it.
|
|
UpdateSceneCameraTransform( sceneCamera );
|
|
return sceneCamera.GetFrustum( screenRect );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Given a pixel rect return a frustum on the current camera. Pass in 1 to ScreenSize to use normalized screen coords.
|
|
/// </summary>
|
|
public Frustum GetFrustum( Rect screenRect, Vector3 screenSize )
|
|
{
|
|
// Same as above
|
|
UpdateSceneCameraTransform( sceneCamera );
|
|
return sceneCamera.GetFrustum( screenRect, screenSize );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Render scene to a texture from this camera's point of view
|
|
/// </summary>
|
|
public bool RenderToTexture( Texture target, in ViewSetup config = default )
|
|
{
|
|
if ( target is null || target.native.IsNull )
|
|
return false;
|
|
|
|
if ( !Graphics.IsActive )
|
|
{
|
|
Scene.PreCameraRender();
|
|
InitializeRendering();
|
|
}
|
|
|
|
using var setup = new CameraRenderer( $"{GameObject.Name}.RenderToTexture", sceneCamera._cameraId );
|
|
|
|
lock ( this )
|
|
{
|
|
setup.Configure( sceneCamera, config );
|
|
|
|
//
|
|
// Adds the views to the scene system
|
|
//
|
|
setup.Native.RenderToTexture( target.native, Graphics.SceneView );
|
|
setup.Native.ClearSceneWorlds();
|
|
}
|
|
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Allows specifying a custom projection matrix for this camera
|
|
/// </summary>
|
|
public Matrix? CustomProjectionMatrix
|
|
{
|
|
get => SceneCamera.CustomProjectionMatrix;
|
|
set => SceneCamera.CustomProjectionMatrix = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Allows specifying a custom aspect ratio for this camera.
|
|
/// By default the camera size is screen size or render target size.
|
|
/// </summary>
|
|
[JsonIgnore, Hide]
|
|
public Vector2? CustomSize { get; set; }
|
|
|
|
/// <summary>
|
|
/// Get frustum projection matrix.
|
|
/// </summary>
|
|
public Matrix ProjectionMatrix => sceneCamera.ProjectionMatrix;
|
|
|
|
/// <summary>
|
|
/// Calculates a projection matrix with an oblique clip-plane defined in world space.
|
|
/// </summary>
|
|
public Matrix CalculateObliqueMatrix( Plane clipPlane )
|
|
{
|
|
var tx = WorldTransform;
|
|
var normal = tx.Rotation.Inverse * clipPlane.Normal;
|
|
normal = new Vector3( normal.y, -normal.z, normal.x ).Normal;
|
|
|
|
System.Numerics.Matrix4x4 m = ProjectionMatrix;
|
|
|
|
Vector4 q = default;
|
|
|
|
q.x = (MathF.Sign( normal.x ) - m.M13) / m.M11;
|
|
q.y = (MathF.Sign( normal.y ) - m.M23) / m.M22;
|
|
q.z = 1f;
|
|
q.w = (1f - m.M33) / m.M34;
|
|
|
|
var plane = new Vector4( normal, Vector3.Dot( tx.Position - clipPlane.Position, clipPlane.Normal ) );
|
|
var c = plane * (1.0f / System.Numerics.Vector4.Dot( plane, q ));
|
|
|
|
m.M31 = -c.x;
|
|
m.M32 = -c.y;
|
|
m.M33 = -c.z;
|
|
m.M34 = c.w;
|
|
|
|
return m;
|
|
}
|
|
|
|
public enum Axis
|
|
{
|
|
/// <summary>
|
|
/// Fits the view within the x-axis.
|
|
/// </summary>
|
|
[Icon( "panorama_horizontal" )]
|
|
Horizontal,
|
|
|
|
/// <summary>
|
|
/// Fits the view within the y-axis.
|
|
/// </summary>
|
|
[Icon( "panorama_vertical" )]
|
|
Vertical
|
|
}
|
|
|
|
/// <summary>
|
|
/// Render this camera to the target bitmap.
|
|
/// </summary>
|
|
public void RenderToBitmap( Bitmap targetBitmap )
|
|
{
|
|
if ( targetBitmap == null || targetBitmap.Width <= 1 || targetBitmap.Height <= 1 )
|
|
return;
|
|
|
|
if ( !this.IsValid() )
|
|
return;
|
|
|
|
using ( Scene.Push() )
|
|
{
|
|
Scene.PreCameraRender();
|
|
InitializeRendering();
|
|
SceneCamera.OnPreRender( targetBitmap.Size );
|
|
|
|
SceneCamera.RenderToBitmap( targetBitmap );
|
|
}
|
|
}
|
|
}
|