using Godot;
using System;
using Rokojori.Extensions;
namespace Rokojori
{
/** One ground texture in a ControlMapTerrain's set.
Its position in the terrain's `textures` array is its
index, and that index is what a control-map texel stores. So reordering
the array repaints the terrain - worth knowing before dragging entries
around.
Channel packing. Two textures per entry, not four:
albedo RGB + height in alpha, and normal RGB + roughness in alpha. The
packing is not an optimization here, it is the only affordable shape - a
fragment reads up to 8 of these ( 4 control taps, base and overlay each ),
so every extra sampler is 8 more fetches. Height has to be in alpha
because the blend weighting needs it at the same time as the albedo, and
roughness rides along with the normal because nothing needs it alone.
*/
[Tool]
[GlobalClass]
public partial class ControlMapTexture:Resource
{
Texture2D _albedo;
/** RGB albedo, alpha = height. The height drives the blend weighting, so an
entry with a flat alpha of 1 crossfades while one with real height
interlocks with its neighbours. A texture with no alpha channel at all
becomes opaque on import and therefore crossfades, which is the sane
fallback rather than a special case.
*/
[Export]
public Texture2D albedo
{
get => _albedo;
set { _albedo = value; EmitChanged(); }
}
Texture2D _normal;
/** RGB tangent-space normal, alpha = roughness. Left empty, the entry gets
a flat normal and full roughness.
*/
[Export]
public Texture2D normal
{
get => _normal;
set { _normal = value; EmitChanged(); }
}
float _metersPerTile = 4;
/** How many world meters one tile of this texture covers, before the
per-texel scale multiplies it. This is the per-texture setting that
matters most and the reason the parameter strip exists at all - gravel
and a cliff face cannot share a tile size.
*/
[Export]
public float metersPerTile
{
get => _metersPerTile;
set { _metersPerTile = Mathf.Max( 0.01f, value ); EmitChanged(); }
}
float _detile = 0;
/** Random rotation per control texel, 0 to 1 of a full turn, on top of the
painted rotation.
A texture with any visible structure reads as a grid at tile scale once
it covers a large area; rotating each texel's copy breaks that up. Costs
nothing - it is one hash and the rotation was already being applied -
but it fights with a texture that has a direction of its own, so it is
per texture rather than global.
*/
[Export( PropertyHint.Range, "0,1" )]
public float detile
{
get => _detile;
set { _detile = Mathf.Clamp( value, 0, 1 ); EmitChanged(); }
}
float _normalStrength = 1;
/** Scales the normal map's XY before it is normalized. */
[Export]
public float normalStrength
{
get => _normalStrength;
set { _normalStrength = value; EmitChanged(); }
}
float _roughness = 1;
/** Multiplies the roughness read from the normal map's alpha. */
[Export]
public float roughness
{
get => _roughness;
set { _roughness = value; EmitChanged(); }
}
/** The entry as the parameter strip's texel, in the order the shader's
ControlMap_textureParams unpacks it.
*/
public Color ToParams()
{
return new Color( metersPerTile, detile, normalStrength, roughness );
}
}
}