rokojori_action_library/Runtime/Procedural/Textures/TextureStack/TextureStackMask.cs

191 lines
5.0 KiB
C#

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 );
}
}
}
}