mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-01-02 19:38:24 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
345 lines
8.8 KiB
C#
345 lines
8.8 KiB
C#
using Sandbox.Rendering;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
namespace Sandbox;
|
|
|
|
/// <summary>
|
|
/// The Decal component projects textures onto model's opaque or transparent surfaces.
|
|
/// They inherit and modify the PBR properties of the surface they're projected on.
|
|
/// </summary>
|
|
[Expose]
|
|
[Title( "Decal" )]
|
|
[Category( "Rendering" )]
|
|
[Icon( "lens_blur" )]
|
|
[EditorHandle( "materials/gizmo/decal.png" )]
|
|
[HelpUrl( "https://sbox.game/dev/doc/scene/components/reference/decals/" )]
|
|
public sealed partial class Decal : Component, Component.ExecuteInEditor, Component.ITemporaryEffect
|
|
{
|
|
[Property, WideMode]
|
|
public List<DecalDefinition> Decals { get; set; } = [];
|
|
|
|
[Obsolete] public Texture ColorTexture { get; set; }
|
|
[Obsolete] public Texture NormalTexture { get; set; }
|
|
[Obsolete] public Texture RMOTexture { get; set; }
|
|
|
|
float _startTime;
|
|
DecalSceneObject _sceneObject;
|
|
private uint _sequenceId = 0;
|
|
int _seed = 0;
|
|
DecalDefinition _def;
|
|
bool _isActive;
|
|
|
|
/// <summary>
|
|
/// How long should this decal live for?
|
|
/// </summary>
|
|
[Property, Header( "Life" )]
|
|
public ParticleFloat LifeTime { get; set; } = 0;
|
|
|
|
/// <summary>
|
|
/// If true then the decal will repeat itself forever
|
|
/// </summary>
|
|
[Property]
|
|
public bool Looped { get; set; }
|
|
|
|
/// <summary>
|
|
/// If true then this decal will automatically get removed when maxdecals are exceeded. This is good for
|
|
/// things like bullect impacts, where you want to keep them around for as long as possible but also
|
|
/// don't want to have an unlimited amount of them hanging around.
|
|
///
|
|
/// Note that while the component will be destroyed, you probably want a TemporaryEffect component on the
|
|
/// GameObject to make sure it all gets fully deleted.
|
|
/// </summary>
|
|
[Property]
|
|
public bool Transient { get; set; }
|
|
|
|
/// <summary>
|
|
/// A 2D size of the decal in world units.
|
|
/// </summary>
|
|
[Property, Header( "Dimensions" )]
|
|
public Vector2 Size { get; set; } = 1;
|
|
|
|
/// <summary>
|
|
/// Scale the width and height by this value
|
|
/// </summary>
|
|
[Property]
|
|
public ParticleFloat Scale { get; set; } = 1;
|
|
|
|
/// <summary>
|
|
/// Scale the width and height by this value
|
|
/// </summary>
|
|
[Property]
|
|
public ParticleFloat Rotation { get; set; } = new ParticleFloat( 0, 360 );
|
|
|
|
/// <summary>
|
|
/// The depth of the decal in world units. This is how far the decal extends into the surface it is projected onto.
|
|
/// </summary>
|
|
[Property]
|
|
public float Depth { get; set; } = 8;
|
|
|
|
/// <summary>
|
|
/// How long should this decal live for?
|
|
/// </summary>
|
|
[Property, Header( "Properties" )]
|
|
public ParticleFloat Parallax { get; set; } = 1;
|
|
|
|
/// <summary>
|
|
/// Tints the color of the decal's albedo and can be used to adjust the overall opacity of the decal.
|
|
/// </summary>
|
|
[Property]
|
|
public ParticleGradient ColorTint { get; set; } = Color.White;
|
|
|
|
/// <summary>
|
|
/// Controls the opacity of the decal's color texture without reducing the impact of the normal or rmo texture.
|
|
/// Set to 0 to create a normal/rmo only decal masked by the color textures alpha.
|
|
/// </summary>
|
|
[Property, Range( 0, 1 )]
|
|
public ParticleFloat ColorMix { get; set; } = 1.0f;
|
|
|
|
/// <summary>
|
|
/// Attenuation angle controls how much the decal fades at an angle.
|
|
/// At 0 it does not fade at all. Up to 1 it fades the most.
|
|
/// </summary>
|
|
[Property, Range( 0, 1 )]
|
|
public float AttenuationAngle { get; set; } = 1.0f;
|
|
|
|
private uint _sortLayer;
|
|
|
|
/// <summary>
|
|
/// Determines the order the decal gets rendered in, the higher the layer the more priority it has.
|
|
/// Decals on the same layer get automatically sorted by their GameObject ID.
|
|
/// </summary>
|
|
[Property, Header( "Sorting" )]
|
|
public uint SortLayer
|
|
{
|
|
get => _sortLayer;
|
|
set
|
|
{
|
|
if ( _sortLayer == value ) return;
|
|
_sortLayer = value;
|
|
UpdateSortLayer();
|
|
}
|
|
}
|
|
|
|
[Title( "Sheet" )]
|
|
[Property, FeatureEnabled( "SheetSequence", Icon = "apps" )]
|
|
public bool SheetSequence { get; set; }
|
|
|
|
/// <summary>
|
|
/// Which sequence to use
|
|
/// </summary>
|
|
[Property, Feature( "SheetSequence" ), Range( 0, 255 )]
|
|
public uint SequenceId
|
|
{
|
|
get => _sequenceId;
|
|
set
|
|
{
|
|
if ( _sequenceId == value )
|
|
return;
|
|
|
|
_sequenceId = value;
|
|
|
|
UpdateSequence();
|
|
}
|
|
}
|
|
|
|
protected override void OnEnabled()
|
|
{
|
|
Assert.IsNull( _sceneObject );
|
|
|
|
_sceneObject = new DecalSceneObject( Scene.SceneWorld );
|
|
|
|
_seed = Random.Shared.Int( 10000 );
|
|
_startTime = Time.Now;
|
|
_def = Random.Shared.FromList( Decals );
|
|
_isActive = true;
|
|
|
|
UpdateSceneObject();
|
|
UpdateToDelta( 0 );
|
|
|
|
if ( Transient )
|
|
{
|
|
Scene.Get<DecalGameSystem>()?.AddTransient( this );
|
|
}
|
|
}
|
|
|
|
protected override void OnDisabled()
|
|
{
|
|
_sceneObject?.Delete();
|
|
_sceneObject = null;
|
|
|
|
if ( Transient )
|
|
{
|
|
Scene.Get<DecalGameSystem>()?.RemoveTransient( this );
|
|
}
|
|
}
|
|
|
|
bool ITemporaryEffect.IsActive => _isActive;
|
|
|
|
void ITemporaryEffect.DisableLooping()
|
|
{
|
|
Looped = false;
|
|
}
|
|
|
|
void UpdateCurrentDefinition()
|
|
{
|
|
_def = default;
|
|
|
|
// We don't know when the contents of Decals changes, so we
|
|
// re-select every time here, based on the seed.
|
|
if ( Decals is not null && Decals.Count > 0 )
|
|
{
|
|
var id = (int)(Rand( 349 ) * 64);
|
|
_def = Decals[id % Decals.Count];
|
|
}
|
|
}
|
|
|
|
void UpdateSceneObject()
|
|
{
|
|
if ( !_sceneObject.IsValid() )
|
|
return;
|
|
|
|
UpdateCurrentDefinition();
|
|
|
|
if ( _def is null )
|
|
{
|
|
_sceneObject.RenderingEnabled = false;
|
|
return;
|
|
}
|
|
|
|
// _sceneObject.ExclusionBitMask = ExclusionLayer;
|
|
|
|
_sceneObject.Tags.SetFrom( GameObject.Tags );
|
|
|
|
UpdateSortLayer();
|
|
UpdateSequence();
|
|
}
|
|
|
|
void UpdateSortLayer()
|
|
{
|
|
if ( !_sceneObject.IsValid() ) return;
|
|
|
|
// 24 bits gameobject id / 8 bits user sort layer
|
|
// this way you get automatic sorting with a user layer override
|
|
var bytes = GameObject.Id.ToByteArray();
|
|
_sceneObject.SortOrder = ((uint)(SortLayer & 0xFF) << 24) | (uint)(bytes[0] | (bytes[1] << 8) | (bytes[2] << 16));
|
|
}
|
|
|
|
protected override void OnPreRender()
|
|
{
|
|
if ( !_sceneObject.IsValid() )
|
|
return;
|
|
|
|
var lt = LifeTime.Evaluate( 0.0f, Rand( 2531 ) );
|
|
|
|
if ( lt <= 0 )
|
|
{
|
|
UpdateToDelta( 0 );
|
|
return;
|
|
}
|
|
|
|
float d = (Time.Now - _startTime) / lt;
|
|
|
|
if ( d >= 1 && !Looped && !Scene.IsEditor )
|
|
{
|
|
_isActive = false;
|
|
d = 1;
|
|
}
|
|
|
|
UpdateToDelta( d % 1.0f );
|
|
}
|
|
|
|
Vector3 GetDecalVolume( float delta )
|
|
{
|
|
if ( _def is null )
|
|
return 1;
|
|
|
|
var scale = Scale.Evaluate( delta, Rand( 238 ) );
|
|
|
|
Vector3 size = new Vector3( Depth, Size.x, Size.y );
|
|
size.y *= _def.Width * scale;
|
|
size.z *= _def.Height * scale;
|
|
return size;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the world bounds of this decal
|
|
/// </summary>
|
|
public BBox WorldBounds => BBox.FromPositionAndSize( Transform.World.Position, GetDecalVolume( 0.5f ) * Transform.World.Scale );
|
|
|
|
void UpdateToDelta( float delta )
|
|
{
|
|
UpdateCurrentDefinition();
|
|
|
|
if ( _def is null )
|
|
{
|
|
_sceneObject.RenderingEnabled = false;
|
|
return;
|
|
}
|
|
|
|
var rotation = new Angles( 0, 0, Rotation.Evaluate( delta, Rand( 512 ) ) );
|
|
var size = GetDecalVolume( delta );
|
|
var tx = Transform.World.WithScale( Transform.World.Scale * size );
|
|
tx.Rotation = tx.Rotation * rotation;
|
|
|
|
_sceneObject.RenderingEnabled = _isActive;
|
|
_sceneObject.Color = _def.Tint * ColorTint.Evaluate( delta, Rand( 238 ) );
|
|
_sceneObject.ColorMix = _def.ColorMix * ColorMix.Evaluate( delta, Rand( 324 ) );
|
|
_sceneObject.AttenuationAngle = AttenuationAngle;
|
|
_sceneObject.ParallaxStrength = Parallax.Evaluate( delta, Rand( 245 ) ) * _def.ParallaxStrength * 0.25f;
|
|
_sceneObject.SamplerIndex = SamplerState.GetBindlessIndex( new SamplerState { AddressModeU = TextureAddressMode.Clamp, AddressModeV = TextureAddressMode.Clamp, Filter = _def.FilterMode } );
|
|
_sceneObject.Transform = tx;
|
|
|
|
_sceneObject.ColorTexture = _def.ColorTexture;
|
|
_sceneObject.NormalTexture = _def.NormalTexture;
|
|
_sceneObject.RMOTexture = _def.RoughMetalOcclusionTexture;
|
|
_sceneObject.HeightTexture = _def.HeightTexture;
|
|
_sceneObject.EmissionTexture = _def.EmissiveTexture;
|
|
_sceneObject.EmissionEnergy = _def.EmissionEnergy;
|
|
}
|
|
|
|
private void UpdateSequence()
|
|
{
|
|
if ( !_sceneObject.IsValid() )
|
|
return;
|
|
|
|
_sceneObject.SequenceIndex = SheetSequence ? SequenceId % 255 : 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tags have been updated - lets update our scene object tags
|
|
/// </summary>
|
|
protected override void OnTagsChanged()
|
|
{
|
|
if ( !_sceneObject.IsValid() )
|
|
return;
|
|
|
|
_sceneObject.Tags.SetFrom( GameObject.Tags );
|
|
}
|
|
|
|
protected override void DrawGizmos()
|
|
{
|
|
if ( !Gizmo.IsSelected )
|
|
{
|
|
using ( Gizmo.Scope() )
|
|
{
|
|
Gizmo.Transform = Gizmo.Transform.WithScale( 1 );
|
|
Gizmo.Draw.Arrow( Vector3.Zero, Vector3.Forward * 8.0f, 2, 1 );
|
|
}
|
|
}
|
|
else if ( _def is not null )
|
|
{
|
|
var size = GetDecalVolume( 0.5f );
|
|
|
|
var box = BBox.FromPositionAndSize( Vector3.Zero, size );
|
|
Gizmo.Draw.LineBBox( box );
|
|
}
|
|
}
|
|
|
|
[MethodImpl( MethodImplOptions.AggressiveInlining )]
|
|
internal float Rand( int seed = 0, [CallerLineNumber] int line = 0 )
|
|
{
|
|
int i = _seed + (line * 20) + seed;
|
|
return Game.Random.FloatDeterministic( i );
|
|
}
|
|
}
|