using Godot; using System; using Rokojori.Extensions; namespace Rokojori { /** Which scalar a mask texture's pixel is reduced to. Int values are switched on in TextureStack.gdshaderinc - do not renumber. */ public enum TextureStackMaskChannel { Red = 0, Green = 1, Blue = 2, Alpha = 3, Luminance = 4 } /** One mask on a TextureStackLayer. A mask reduces its texture to a single 0-1 value and multiplies it into the layer's effective opacity. Several masks on one layer multiply together, which is how the outline's masked-decal example is authored without any nesting: a circle-gradient texture and a noise texture as two masks on one layer. texture takes any Texture2D, so a TextureStackTexture2D goes straight in and a mask can itself be a composited stack. The transform is applied around the centre of the layer's placement rect and is independent of the layer's own content UV transform. Its UV space is rect-local: on a full-texture layer that is the whole texture, on a decal it is the decal, which is what makes a mask follow a stamp. Levels remap the sampled value before invert: everything at or below levelsMin becomes 0, everything at or above levelsMax becomes 1. Leave at 0/1 for no remapping. This is the "one noise texture, many hardnesses" control. Mask count per layer is capped by the blit source ceiling - see TextureStackLayer.MaxMasks. Every value is a property that emits changed, so editing a mask recomposites the stack that reads it - see TextureStackChangeSource. */ [Tool] [GlobalClass] public partial class TextureStackMask:Resource { Texture2D _texture; [Export] public Texture2D texture { get => _texture; set { _texture = value; TextureStackNotify.Changed( this ); } } TextureSettings _textureSettings; /** Sampling and colour space for the mask texture. Empty means Linear sampling and no conversion - see TextureSettings. This is the slot that matters most in the whole system: a mask is usually authored small, a gradient or a 64px noise, and stretched across the whole output, so Nearest here is the difference between a smooth falloff and visible banding. */ [Export] public TextureSettings textureSettings { get => _textureSettings; set { _textureSettings = value; TextureStackNotify.Changed( this ); } } TextureStackMaskChannel _channel = TextureStackMaskChannel.Red; [Export] public TextureStackMaskChannel channel { get => _channel; set { _channel = value; TextureStackNotify.Changed( this ); } } bool _invert = false; [Export] public bool invert { get => _invert; set { _invert = value; TextureStackNotify.Changed( this ); } } [ExportGroup( "Transform" )] Vector2 _offset = Vector2.Zero; [Export] public Vector2 offset { get => _offset; set { _offset = value; TextureStackNotify.Changed( this ); } } Vector2 _scale = Vector2.One; [Export] public Vector2 scale { get => _scale; set { _scale = value; TextureStackNotify.Changed( this ); } } float _rotationDegrees = 0; [Export] public float rotationDegrees { get => _rotationDegrees; set { _rotationDegrees = value; TextureStackNotify.Changed( this ); } } bool _repeat = false; /** Off: the mask is transparent (value 0) outside its own 0-1 range, so a scaled-down mask hides everything around itself. On: it tiles. */ [Export] public bool repeat { get => _repeat; set { _repeat = value; TextureStackNotify.Changed( this ); } } [ExportGroup( "Levels" )] float _levelsMin = 0; [Export( PropertyHint.Range, "0,1" )] public float levelsMin { get => _levelsMin; set { _levelsMin = value; TextureStackNotify.Changed( this ); } } float _levelsMax = 1; [Export( PropertyHint.Range, "0,1" )] public float levelsMax { get => _levelsMax; set { _levelsMax = value; TextureStackNotify.Changed( this ); } } public bool isUsable => texture != null; /** offset.xy / scale.xy, as the composite shader expects it. */ public Vector4 packedTransform { get { var s = new Vector2( Mathf.IsZeroApprox( scale.X ) ? 0.0001f : scale.X, Mathf.IsZeroApprox( scale.Y ) ? 0.0001f : scale.Y ); return new Vector4( offset.X, offset.Y, s.X, s.Y ); } } /** rotation in radians / repeat as 0-1 / levelsMin / levelsMax. */ public Vector4 packedParameters { get { var min = levelsMin; var max = levelsMax; // A collapsed or inverted range would divide by zero in the shader. if ( max - min < 0.0001f ) { max = min + 0.0001f; } return new Vector4( Mathf.DegToRad( rotationDegrees ), repeat ? 1 : 0, min, max ); } } } }