191 lines
5.0 KiB
C#
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 );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|