Files
sbox-public/engine/Sandbox.Engine/Systems/SceneSystem/SceneObject.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

553 lines
14 KiB
C#

using NativeEngine;
namespace Sandbox;
/// <summary>
/// A model scene object that can be rendered within a <see cref="SceneWorld"/>.
/// </summary>
[Expose]
public partial class SceneObject : IHandle
{
#region IHandle
//
// A pointer to the actual native object
//
internal CSceneObject native;
//
// IHandle implementation
//
void IHandle.HandleInit( IntPtr ptr ) => OnNativeInit( ptr );
void IHandle.HandleDestroy() => OnNativeDestroy();
bool IHandle.HandleValid() => !native.IsNull;
#endregion
RenderAttributes _attributes;
public RenderAttributes Attributes
{
get
{
if ( native.IsNull ) return null;
// FIXME: What really sucks with this is it allocates CSceneObject::m_pExtraData even if we're only reading
return _attributes ??= new RenderAttributes( native.GetAttributesPtrForModify() );
}
}
/// <summary>
/// The scene world this object belongs to.
/// </summary>
public SceneWorld World { get; internal set; }
internal SceneObject()
{
Tags = new Internal.SceneObjectTags( this );
Flags = new SceneObjectFlagAccessor( this );
}
internal SceneObject( HandleCreationData _ ) : this()
{
}
public SceneObject( SceneWorld sceneWorld, Model model ) : this( sceneWorld, model, Transform.Zero )
{
}
public SceneObject( SceneWorld sceneWorld, Model model, Transform transform ) : this()
{
Assert.IsValid( sceneWorld );
if ( !model.HasRenderMeshes() ) model = Model.Error;
using ( var h = IHandle.MakeNextHandle( this ) )
{
var flags = Rendering.SceneObjectFlags.CastShadows | Rendering.SceneObjectFlags.IsLoaded;
var typeFlags = ESceneObjectTypeFlags.NONE;
MeshSystem.CreateSceneObject( model.native, transform, null, flags, typeFlags, sceneWorld, 0x1 );
Transform = transform;
}
}
public SceneObject( SceneWorld sceneWorld, string modelName, Transform transform ) : this( sceneWorld, Model.Load( modelName ), transform )
{
}
public SceneObject( SceneWorld sceneWorld, string modelName ) : this( sceneWorld, modelName, Transform.Zero )
{
}
/// <summary>
/// Delete this scene object. You shouldn't access it anymore.
/// </summary>
public void Delete()
{
if ( native.IsValid )
{
CSceneSystem.DeleteSceneObjectAtFrameEnd( this );
native = IntPtr.Zero; // avoid double delete etc
}
}
internal virtual void OnNativeInit( CSceneObject ptr )
{
native = ptr;
World = native.GetWorld();
Assert.NotNull( World );
lock ( World.InternalSceneObjects )
{
World.InternalSceneObjects.Add( this );
}
//Log.Info( $"Created SceneObject: {GetType().Name}" );
}
internal virtual void OnNativeDestroy()
{
lock ( World.InternalSceneObjects )
{
World.InternalSceneObjects.Remove( this );
}
native = IntPtr.Zero;
if ( _attributes != null )
{
_attributes.Set( default );
_attributes = null;
}
//Log.Info( $"Destroyed SceneObject: {GetType().Name}" );
}
Transform _transform;
/// <summary>
/// Transform of this scene object, relative to its <see cref="Parent"/>, or <see cref="SceneWorld"/> if parent is not set.
/// </summary>
public Transform Transform
{
get => _transform;
set
{
if ( _transform == value )
return;
_transform = value;
native.SetTransform( value );
OnTransformChanged( value );
}
}
/// <summary>
/// Rotation of this scene object, relative to its <see cref="Parent"/>, or <see cref="SceneWorld"/> if parent is not set.
/// </summary>
public Rotation Rotation
{
get => Transform.Rotation;
set
{
if ( Rotation == value )
return;
Transform = Transform.WithRotation( value );
}
}
/// <summary>
/// Position of this scene object, relative to its <see cref="Parent"/>, or <see cref="SceneWorld"/> if parent is not set.
/// </summary>
[Property( "origin" )]
public Vector3 Position
{
get => Transform.Position;
set
{
if ( Position == value )
return;
Transform = Transform.WithPosition( value );
}
}
// We could do better here, sometimes sceneobjects are set to infinite bounds by design
private BBox GetSafeBounds()
{
var bounds = native.GetBounds();
var volume = bounds.Volume;
if ( !float.IsNaN( volume ) && !float.IsInfinity( volume ) )
return bounds;
return BBox.FromPositionAndSize( Position );
}
/// <summary>
/// Set or get the axis aligned bounding box for this object.
/// </summary>
public BBox Bounds
{
get => GetSafeBounds();
set => native.SetBounds( value );
}
/// <summary>
/// Set the axis aligned bounding box by transforming by this objects transform.
/// </summary>
public BBox LocalBounds
{
[Obsolete( "LocalBounds.get is incorrect and should not be used. It does not reverse the full transform." )]
get => Bounds.Translate( -Position );
set => Bounds = value.Transform( Transform );
}
/// <summary>
/// Whether this scene object should render or not.
/// </summary>
public bool RenderingEnabled
{
get => native.IsRenderingEnabled();
set => native.SetRenderingEnabled( value );
}
/// <summary>
/// Color tint of this scene object.
/// </summary>
public Color ColorTint
{
get => native.GetTintRGBA();
set => native.SetTintRGBA( value );
}
/// <summary>
/// Clipping plane for this scene object. Requires <see cref="ClipPlaneEnabled"/> to be <c>true</c>.
/// </summary>
[Obsolete]
public Plane ClipPlane;
/// <summary>
/// Whether or not to use the clipping plane defined in <see cref="ClipPlane"/>.
/// </summary>
[Obsolete]
public bool ClipPlaneEnabled;
/// <summary>
/// Movement parent of this scene object, if any.
/// </summary>
public SceneObject Parent => native.GetParent();
/// <summary>
/// Add a named child scene object to this one. The child scene object will have its parent set.
/// </summary>
/// <remarks>
/// The name can be used to look up children by name, but it is not bound. (SceneObject_FindChild)
/// </remarks>
public void AddChild( string name, SceneObject child )
{
if ( !child.IsValid() )
return;
native.AddChildObject( name, child, 0x02 );
}
/// <summary>
/// Unlink given scene object as a child from this one. The child scene object will have its parent set to null. It will not be deleted.
/// </summary>
public void RemoveChild( SceneObject child )
{
if ( !child.IsValid() )
return;
native.RemoveChild( child );
}
/// <summary>
/// The model this scene object will render.
/// </summary>
public Model Model
{
get
{
return Model.FromNative( native.GetModelHandle() );
}
set
{
if ( value == Model )
return;
var model = value;
if ( !model.HasRenderMeshes() ) model = Model.Error;
MeshSystem.ChangeModel( this, model.native );
OnModelChanged();
}
}
internal virtual void OnModelChanged()
{
_materialOverride = null;
}
/// <summary>
/// State of all bodygroups of this object's model. You might be looking for <see cref="SceneModel.SetBodyGroup"/>.
/// </summary>
public ulong MeshGroupMask
{
get => native.GetCurrentMeshGroupMask();
set => native.ResetMeshGroups( value );
}
/// <summary>
/// Override current LOD level, -1 to disable.
/// </summary>
internal int LodOverride
{
set => native.SetLOD( value );
}
Material _materialOverride;
/// <summary>
/// Override all materials on this object's <see cref="Model"/>.
/// </summary>
public void SetMaterialOverride( Material material )
{
if ( _materialOverride == material )
return;
_materialOverride = material;
if ( material != null && material.native.IsValid )
{
native.SetMaterialOverrideForMeshInstances( material.native );
return;
}
native.SetMaterialOverrideForMeshInstances( default );
}
/// <summary>
/// Clear all material replacements.
/// </summary>
public void ClearMaterialOverride()
{
native.ClearMaterialOverrideList();
_materialOverride = default;
}
/// <summary>
/// Replaces all materials of the model that have the given <b>User Material Attribute</b> set to <b>"1"</b>, with given material.
///
/// <para>The system checks both the models' default material group materials and the materials of the active material group.</para>
/// </summary>
/// <param name="material">Material to replace with.</param>
/// <param name="attributeName">Name of the <b>User Material Attribute</b> to test on each material of the model. They are set in the Material Editor's <b>Attributes</b> tab.</param>
/// <param name="attributeValue">Value of the attribute to test for.</param>
public void SetMaterialOverride( Material material, string attributeName, int attributeValue = 1 )
{
native.SetMaterialOverride( material?.native ?? IntPtr.Zero, attributeName, attributeValue );
}
/// <summary>
/// Set material group to replace materials of the model as set up in ModelDoc.
/// </summary>
public void SetMaterialGroup( string name )
{
native.SetMaterialGroup( name );
}
internal virtual void OnTransformChanged( in Transform tx )
{
}
/// <summary>
/// This object is not batchable by material for some reason ( example: has dynamic attributes that affect rendering )
/// </summary>
public bool Batchable
{
get => !native.IsNotBatchable();
set => native.SetBatchable( value );
}
Component _component;
/// <summary>
/// For storing and retrieving the GameObject this SceneObject belongs to
/// </summary>
internal GameObject GameObject { get; set; }
/// <summary>
/// The component that created this object
/// </summary>
internal Component Component
{
get => _component;
set
{
_component = value;
GameObject = _component?.GameObject;
}
}
[Obsolete( "Use Component property" )]
public void SetComponentSource( Component c )
{
Component = c;
}
[Obsolete( "Use GameObject property" )]
public GameObject GetGameObject() => GameObject;
/// <summary>
/// Updates flags like transparent/opaque based on object's material, this is usually called automatically.
/// But some procedural workflows (mesh editor) may want to call this manually.
/// </summary>
internal void UpdateFlagsBasedOnMaterial()
{
native.UpdateFlagsBasedOnMaterial();
}
/// <summary>
/// Access to various advanced scene object flags.
/// </summary>
public SceneObjectFlagAccessor Flags { get; internal set; }
public class SceneObjectFlagAccessor
{
SceneObject Object;
internal SceneObjectFlagAccessor( SceneObject obj )
{
Object = obj;
}
internal bool HasFlag( Rendering.SceneObjectFlags f )
{
return Object.native.HasFlags( f );
}
internal void SetFlag( Rendering.SceneObjectFlags f, bool val )
{
Object.native.ChangeFlags( val ? f : Rendering.SceneObjectFlags.None, f );
}
/// <summary>
/// Whether this scene object should cast shadows.
/// </summary>
public bool CastShadows
{
get => HasFlag( Rendering.SceneObjectFlags.CastShadows );
set => SetFlag( Rendering.SceneObjectFlags.CastShadows, value );
}
public bool IsOpaque
{
get => HasFlag( Rendering.SceneObjectFlags.IsOpaque );
set => SetFlag( Rendering.SceneObjectFlags.IsOpaque, value );
}
public bool IsTranslucent
{
get => HasFlag( Rendering.SceneObjectFlags.IsTranslucent );
set => SetFlag( Rendering.SceneObjectFlags.IsTranslucent, value );
}
[Obsolete( "SceneObject.IsDecal is obsolete" )]
public bool IsDecal
{
get => HasFlag( Rendering.SceneObjectFlags.IsDecal );
set => SetFlag( Rendering.SceneObjectFlags.IsDecal, value );
}
public bool OverlayLayer
{
get => HasFlag( Rendering.SceneObjectFlags.GameOverlayLayer );
set => SetFlag( Rendering.SceneObjectFlags.GameOverlayLayer, value );
}
/// <summary>
/// Don't render in the opaque/translucent game passes. This is useful when you
/// want to only render in the Bloom layer, rather than additionally to it.
/// </summary>
public bool ExcludeGameLayer
{
get => HasFlag( Rendering.SceneObjectFlags.ExcludeGameLayer );
set => SetFlag( Rendering.SceneObjectFlags.ExcludeGameLayer, value );
}
public bool ViewModelLayer
{
get => HasFlag( Rendering.SceneObjectFlags.ViewModelLayer );
set => SetFlag( Rendering.SceneObjectFlags.ViewModelLayer, value );
}
public bool SkyBoxLayer
{
get => HasFlag( Rendering.SceneObjectFlags.Skybox3DLayer );
set => SetFlag( Rendering.SceneObjectFlags.Skybox3DLayer, value );
}
public bool NeedsLightProbe
{
get => HasFlag( Rendering.SceneObjectFlags.NeedsLightProbe );
set => SetFlag( Rendering.SceneObjectFlags.NeedsLightProbe, value );
}
/// <summary>
/// True if this object needs cubemap information
/// </summary>
public bool NeedsEnvironmentMap
{
get => HasFlag( Rendering.SceneObjectFlags.EnvironmentMapped );
set => SetFlag( Rendering.SceneObjectFlags.EnvironmentMapped, value );
}
/// <summary>
/// Automatically sets the "FrameBufferCopyTexture" attribute within the material.
/// This does the same thing as Render.CopyFrameBuffer(); except automatically if
/// the pass allows for it.
/// </summary>
public bool WantsFrameBufferCopy
{
get => HasFlag( Rendering.SceneObjectFlags.WantsFrameBufferCopyTexture );
set => SetFlag( Rendering.SceneObjectFlags.WantsFrameBufferCopyTexture, value );
}
/// <summary>
/// Draw this in cubemaps
/// </summary>
public bool IncludeInCubemap
{
get => !HasFlag( Rendering.SceneObjectFlags.HideInCubemaps );
set => SetFlag( Rendering.SceneObjectFlags.HideInCubemaps, !value );
}
internal bool WantsExecuteBefore
{
get => HasFlag( Rendering.SceneObjectFlags.ExecuteBefore );
set => SetFlag( Rendering.SceneObjectFlags.ExecuteBefore, value );
}
internal bool WantsExecuteAfter
{
get => HasFlag( Rendering.SceneObjectFlags.ExecuteAfter );
set => SetFlag( Rendering.SceneObjectFlags.ExecuteAfter, value );
}
public bool WantsPrePass
{
get => HasFlag( Rendering.SceneObjectFlags.NoZPrepass ) == false;
set => SetFlag( Rendering.SceneObjectFlags.NoZPrepass, !value );
}
}
public sealed override int GetHashCode() => base.GetHashCode();
public sealed override bool Equals( object obj ) => base.Equals( obj );
}