using NativeEngine; using Sandbox.Rendering; namespace Sandbox; /// /// A scene world that contains s. See Utility.CreateSceneWorld. /// /// You may also want a to manually render the scene world. /// public sealed partial class SceneWorld : IHandle { [SkipHotload] internal static HashSet All = new HashSet(); internal HashSet InternalSceneObjects { get; set; } = new(); internal HashSet InternalSceneMaps { get; set; } = new(); internal HashSet InternalSkyboxWorlds { get; set; } = new(); /// /// List of scene objects belonging to this scene world. /// public IReadOnlyCollection SceneObjects { get { lock ( InternalSceneObjects ) { return InternalSceneObjects .Where( x => x.IsValid() ) .ToArray(); // Ha Ha I bet no-one ever notices this } } } /// /// Controls gradient fog settings. /// public GradientFogSetup GradientFog; /// /// Sets the ambient lighting color /// public Color AmbientLightColor = Color.Transparent; /// /// Sets the clear color, if nothing else is drawn, this is the color you will see /// [System.Obsolete( "Use SceneCamera.BackgroundColor" )] public Color ClearColor = Color.Black; #region IHandle // // A pointer to the actual native object // internal ISceneWorld native; // // IHandle implementation // void IHandle.HandleInit( IntPtr ptr ) => OnNativeInit( ptr ); void IHandle.HandleDestroy() => OnNativeDestroy(); bool IHandle.HandleValid() => !native.IsNull; #endregion /// /// If a world is transient, it means it was created by game code, and should /// be deleted at the end of the game session. If they're non transient then /// they were created in the menu, or by the engine code and will be released /// properly by that code. /// internal bool IsTransient { get; set; } internal SceneWorld( HandleCreationData _ ) { } public SceneWorld() { IsTransient = true; using ( var h = IHandle.MakeNextHandle( this ) ) { CSceneSystem.CreateWorld( "World Debug Name" ); } } /// /// Delete this scene world. You shouldn't access it anymore. /// public void Delete() { if ( !IsTransient ) return; if ( !native.IsValid ) return; foreach ( var map in InternalSceneMaps.ToArray() ) { RemoveSceneMap( map ); } InternalSceneMaps.Clear(); CSceneSystem.DestroyWorld( this ); native = IntPtr.Zero; } internal void OnNativeInit( ISceneWorld ptr ) { All.Add( this ); native = ptr; } internal void OnNativeDestroy() { native = IntPtr.Zero; All.Remove( this ); } /// /// Deleted objects are actually deleted at the end of each frame. Call this /// to actually delete pending deletes right now instead of waiting. /// public void DeletePendingObjects() { if ( !native.IsValid ) return; native.DeleteEndOfFrameObjects(); } /// /// This finishes any loads and actually spawns the world sceneobjects /// internal void UpdateObjectsForRendering( Vector3 eyePos = default, float farPlane = 5000.0f ) { if ( InternalSceneMaps is null ) return; if ( !InternalSceneMaps.Any() ) return; foreach ( var worldGroup in InternalSceneMaps.Select( x => x.WorldGroup ).Distinct() ) { g_pWorldRendererMgr.UpdateObjectsForRendering( worldGroup, eyePos, 1.0f, farPlane ); } // If we have a 3D skybox, update its objects too foreach ( var world in InternalSkyboxWorlds ) { world?.SkyboxWorld.UpdateObjectsForRendering( world.Origin, farPlane ); } } /// /// Add a scenemap to this world /// internal void AddSceneMap( SceneMap sceneMap ) { lock ( InternalSceneMaps ) { if ( !InternalSceneMaps.Add( sceneMap ) ) { Log.Warning( "Couldn't add sceneMap" ); } // If the PVS data is valid - then apply it to this world // We need to be careful that the pvs doesn't get deleted // because this will be a hanging pointer. // I think it would be better to wrap it in a c# object and // only delete when it gets garbage collected if ( sceneMap.PVS.IsValid ) { //Log.Info( $"{this} - SET PVS from {sceneMap}" ); native.SetPVS( sceneMap.PVS ); } } } internal void RemoveSceneMap( SceneMap sceneMap ) { lock ( InternalSceneMaps ) { if ( !InternalSceneMaps.Remove( sceneMap ) ) { Log.Warning( "Couldn't remove sceneMap" ); } if ( native.GetPVS() == sceneMap.PVS ) { native.SetPVS( default ); } } } }