using NativeEngine; namespace Sandbox; /// /// A model scene object that can be rendered within a . /// [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() ); } } /// /// The scene world this object belongs to. /// 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 ) { } /// /// Delete this scene object. You shouldn't access it anymore. /// 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; /// /// Transform of this scene object, relative to its , or if parent is not set. /// public Transform Transform { get => _transform; set { if ( _transform == value ) return; _transform = value; native.SetTransform( value ); OnTransformChanged( value ); } } /// /// Rotation of this scene object, relative to its , or if parent is not set. /// public Rotation Rotation { get => Transform.Rotation; set { if ( Rotation == value ) return; Transform = Transform.WithRotation( value ); } } /// /// Position of this scene object, relative to its , or if parent is not set. /// [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 ); } /// /// Set or get the axis aligned bounding box for this object. /// public BBox Bounds { get => GetSafeBounds(); set => native.SetBounds( value ); } /// /// Set the axis aligned bounding box by transforming by this objects transform. /// 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 ); } /// /// Whether this scene object should render or not. /// public bool RenderingEnabled { get => native.IsRenderingEnabled(); set => native.SetRenderingEnabled( value ); } /// /// Color tint of this scene object. /// public Color ColorTint { get => native.GetTintRGBA(); set => native.SetTintRGBA( value ); } /// /// Clipping plane for this scene object. Requires to be true. /// [Obsolete] public Plane ClipPlane; /// /// Whether or not to use the clipping plane defined in . /// [Obsolete] public bool ClipPlaneEnabled; /// /// Movement parent of this scene object, if any. /// public SceneObject Parent => native.GetParent(); /// /// Add a named child scene object to this one. The child scene object will have its parent set. /// /// /// The name can be used to look up children by name, but it is not bound. (SceneObject_FindChild) /// public void AddChild( string name, SceneObject child ) { if ( !child.IsValid() ) return; native.AddChildObject( name, child, 0x02 ); } /// /// 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. /// public void RemoveChild( SceneObject child ) { if ( !child.IsValid() ) return; native.RemoveChild( child ); } /// /// The model this scene object will render. /// 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; } /// /// State of all bodygroups of this object's model. You might be looking for . /// public ulong MeshGroupMask { get => native.GetCurrentMeshGroupMask(); set => native.ResetMeshGroups( value ); } /// /// Override current LOD level, -1 to disable. /// internal int LodOverride { set => native.SetLOD( value ); } Material _materialOverride; /// /// Override all materials on this object's . /// 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 ); } /// /// Clear all material replacements. /// public void ClearMaterialOverride() { native.ClearMaterialOverrideList(); _materialOverride = default; } /// /// Replaces all materials of the model that have the given User Material Attribute set to "1", with given material. /// /// The system checks both the models' default material group materials and the materials of the active material group. /// /// Material to replace with. /// Name of the User Material Attribute to test on each material of the model. They are set in the Material Editor's Attributes tab. /// Value of the attribute to test for. public void SetMaterialOverride( Material material, string attributeName, int attributeValue = 1 ) { native.SetMaterialOverride( material?.native ?? IntPtr.Zero, attributeName, attributeValue ); } /// /// Set material group to replace materials of the model as set up in ModelDoc. /// public void SetMaterialGroup( string name ) { native.SetMaterialGroup( name ); } internal virtual void OnTransformChanged( in Transform tx ) { } /// /// This object is not batchable by material for some reason ( example: has dynamic attributes that affect rendering ) /// public bool Batchable { get => !native.IsNotBatchable(); set => native.SetBatchable( value ); } Component _component; /// /// For storing and retrieving the GameObject this SceneObject belongs to /// internal GameObject GameObject { get; set; } /// /// The component that created this object /// 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; /// /// 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. /// internal void UpdateFlagsBasedOnMaterial() { native.UpdateFlagsBasedOnMaterial(); } /// /// Access to various advanced scene object flags. /// 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 ); } /// /// Whether this scene object should cast shadows. /// 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 ); } /// /// 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. /// 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 ); } /// /// True if this object needs cubemap information /// public bool NeedsEnvironmentMap { get => HasFlag( Rendering.SceneObjectFlags.EnvironmentMapped ); set => SetFlag( Rendering.SceneObjectFlags.EnvironmentMapped, value ); } /// /// Automatically sets the "FrameBufferCopyTexture" attribute within the material. /// This does the same thing as Render.CopyFrameBuffer(); except automatically if /// the pass allows for it. /// public bool WantsFrameBufferCopy { get => HasFlag( Rendering.SceneObjectFlags.WantsFrameBufferCopyTexture ); set => SetFlag( Rendering.SceneObjectFlags.WantsFrameBufferCopyTexture, value ); } /// /// Draw this in cubemaps /// 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 ); }