#T#PoiStochasticProperties
[HideInInspector] m_start_Stochastic ("Stochastic Sampling", Float) = 0
[KeywordEnum(Deliot Heitz, Hextile, None)] _StochasticMode ("Sampling Mode", Float) = 0
[HideInInspector] g_start_deliot ("Deliot Heitz--{condition_show:{type:PROPERTY_BOOL,data:_StochasticMode==0}}", Float) = 0
	_StochasticDeliotHeitzDensity ("Detiling Density", Range(0.1, 10)) = 1
[HideInInspector] g_end_deliot ("Deliot Heitz", Float) = 0
[HideInInspector] g_start_hextile ("Hextile--{condition_show:{type:PROPERTY_BOOL,data:_StochasticMode==1}}", Float) = 0
	_StochasticHexGridDensity ("Hex Grid Density", Range(0.1, 10)) = 1
	_StochasticHexRotationStrength ("Rotation Strength", Range(0, 2)) = 0
	_StochasticHexFallOffContrast("Falloff Contrast", Range(0.01, 0.99)) = 0.6
	_StochasticHexFallOffPower("Falloff Power", Range(0, 20)) = 7
[HideInInspector] g_end_hextile ("Hextile", Float) = 0
[HideInInspector] m_end_Stochastic ("Stochastic Sampling", Float) = 0

#T#PoiStochasticKeywords
#pragma shader_feature_local _STOCHASTICMODE_DELIOT_HEITZ _STOCHASTICMODE_HEXTILE _STOCHASTICMODE_NONE

#T#PoiStochasticVariables
float _StochasticDeliotHeitzDensity;
float _StochasticHexGridDensity;
float _StochasticHexRotationStrength;
float _StochasticHexFallOffContrast;
float _StochasticHexFallOffPower;

#T#PoiStochasticFunction
#if defined(_STOCHASTICMODE_DELIOT_HEITZ)
	#define POI2D_SAMPLER_STOCHASTIC(tex, texSampler, uv, useStochastic) (useStochastic ? DeliotHeitzSampleTexture(tex, sampler##texSampler, uv) : POI2D_SAMPLER(tex, texSampler, uv))
	#define POI2D_SAMPLER_PAN_STOCHASTIC(tex, texSampler, uv, pan, useStochastic) (useStochastic ? DeliotHeitzSampleTexture(tex, sampler##texSampler, POI_PAN_UV(uv, pan)) : POI2D_SAMPLER_PAN(tex, texSampler, uv, pan))
	#define POI2D_SAMPLER_PANGRAD_STOCHASTIC(tex, texSampler, uv, pan, dx, dy, useStochastic) (useStochastic ? DeliotHeitzSampleTexture(tex, sampler##texSampler, POI_PAN_UV(uv, pan), dx, dy) : POI2D_SAMPLER_PANGRAD(tex, texSampler, uv, pan, dx, dy))
#endif
#if defined(_STOCHASTICMODE_HEXTILE)
	#define POI2D_SAMPLER_STOCHASTIC(tex, texSampler, uv, useStochastic) (useStochastic ? HextileSampleTexture(tex, sampler##texSampler, uv, false) : POI2D_SAMPLER(tex, texSampler, uv))
	#define POI2D_SAMPLER_PAN_STOCHASTIC(tex, texSampler, uv, pan, useStochastic) (useStochastic ? HextileSampleTexture(tex, sampler##texSampler, POI_PAN_UV(uv, pan), false) : POI2D_SAMPLER_PAN(tex, texSampler, uv, pan))
	#define POI2D_SAMPLER_PANGRAD_STOCHASTIC(tex, texSampler, uv, pan, dx, dy, useStochastic) (useStochastic ? HextileSampleTexture(tex, sampler##texSampler, POI_PAN_UV(uv, pan), false, dx, dy) : POI2D_SAMPLER_PANGRAD(tex, texSampler, uv, pan, dx, dy))
#endif

#ifndef POI2D_SAMPLER_STOCHASTIC
	#define POI2D_SAMPLER_STOCHASTIC(tex, texSampler, uv, useStochastic) (POI2D_SAMPLER(tex, texSampler, uv))
#endif
#ifndef POI2D_SAMPLER_PAN_STOCHASTIC
	#define POI2D_SAMPLER_PAN_STOCHASTIC(tex, texSampler, uv, pan, useStochastic) (POI2D_SAMPLER_PAN(tex, texSampler, uv, pan))
#endif
#ifndef POI2D_SAMPLER_PANGRAD_STOCHASTIC
	#define POI2D_SAMPLER_PANGRAD_STOCHASTIC(tex, texSampler, uv, pan, dx, dy, useStochastic) (POI2D_SAMPLER_PANGRAD(tex, texSampler, uv, pan, dx, dy))
#endif

// When using, properties won't properly lock at optimize time; needs macro evaluation implemented
// #define POI2D_SAMPLER_STOCHASTIC_INLINED(tex, texSampler) (POI2D_SAMPLER_STOCHASTIC(tex, texSampler, poiUV(poiMesh.uv[tex##UV], tex##_ST), tex##Stochastic))
// #define POI2D_SAMPLER_PAN_STOCHASTIC_INLINED(tex, texSampler) (POI2D_SAMPLER_PAN_STOCHASTIC(tex, texSampler, poiUV(poiMesh.uv[tex##UV], tex##_ST), tex##Pan, tex##Stochastic))

// #define POI2D_MAINTEX_SAMPLER_STOCHASTIC_INLINED(tex) (POI2D_SAMPLER_STOCHASTIC(tex, _MainTex, poiUV(poiMesh.uv[tex##UV], tex##_ST), tex##Stochastic))
// #define POI2D_MAINTEX_SAMPLER_PAN_STOCHASTIC_INLINED(tex) (POI2D_SAMPLER_PAN_STOCHASTIC(tex, _MainTex, poiUV(poiMesh.uv[tex##UV], tex##_ST), tex##Pan, tex##Stochastic))

// Deliot, Heitz 2019 - Fast, but non-histogram-preserving (ends up looking a bit blurry and lower contrast)
// https://eheitzresearch.wordpress.com/738-2/

// Classic Magic Numbers fracsin
#if !defined(_STOCHASTICMODE_NONE)
float2 StochasticHash2D2D (float2 s)
{
	return frac(sin(glsl_mod(float2(dot(s, float2(127.1,311.7)), dot(s, float2(269.5,183.3))), 3.14159)) * 43758.5453);
}
#endif

#if defined(_STOCHASTICMODE_DELIOT_HEITZ)
// UV Offsets and blend weights
// UVBW[0...2].xy = UV Offsets
// UVBW[0...2].z = Blend Weights
float3x3 DeliotHeitzStochasticUVBW(float2 uv)
{
	// UV transformed into triangular grid space with UV scaled by approximation of 2*sqrt(3)
	const float2x2 stochasticSkewedGrid = float2x2(1.0, -0.57735027, 0.0, 1.15470054);
	float2 skewUV = mul(stochasticSkewedGrid, uv * 3.4641 * _StochasticDeliotHeitzDensity);
	
	// Vertex IDs and barycentric coords
	float2 vxID = floor(skewUV);
	float3 bary = float3(frac(skewUV), 0);
	bary.z = 1.0 - bary.x - bary.y;

	float3x3 pos = float3x3(
		float3(vxID, 				bary.z),
		float3(vxID + float2(0, 1), bary.y),
		float3(vxID + float2(1, 0), bary.x) 
	);

	float3x3 neg = float3x3(
		float3(vxID + float2(1, 1), 	 -bary.z),
		float3(vxID + float2(1, 0), 1.0 - bary.y),
		float3(vxID + float2(0, 1), 1.0 - bary.x) 
	);

	return (bary.z > 0) ? pos : neg;
}

float4 DeliotHeitzSampleTexture(Texture2D tex, SamplerState texSampler, float2 uv, float2 dx, float2 dy)
{
	// UVBW[0...2].xy = UV Offsets
	// UVBW[0...2].z = Blend Weights
	float3x3 UVBW = DeliotHeitzStochasticUVBW(uv);

	//blend samples with calculated weights
	return 	mul(tex.SampleGrad(texSampler, uv + StochasticHash2D2D(UVBW[0].xy), dx, dy), UVBW[0].z) +
			mul(tex.SampleGrad(texSampler, uv + StochasticHash2D2D(UVBW[1].xy), dx, dy), UVBW[1].z) +
			mul(tex.SampleGrad(texSampler, uv + StochasticHash2D2D(UVBW[2].xy), dx, dy), UVBW[2].z) ;
}

float4 DeliotHeitzSampleTexture(Texture2D tex, SamplerState texSampler, float2 uv)
{
	float2 dx = ddx(uv), dy = ddy(uv);
	return DeliotHeitzSampleTexture(tex, texSampler, uv, dx, dy);
}
#endif // defined(_STOCHASTICMODE_DELIOT_HEITZ)

#if defined(_STOCHASTICMODE_HEXTILE)
// HexTiling: Slower, but histogram-preserving
// SPDX-License-Idenfitier: MIT
// Copyright (c) 2022 mmikk
// https://github.com/mmikk/hextile-demo
float2 HextileMakeCenUV(float2 vertex)
{
	// 0.288675 ~= 1/(2*sqrt(3))
	const float2x2 stochasticInverseSkewedGrid = float2x2(1.0, 0.5, 0.0, 1.0/1.15470054);
	return mul(stochasticInverseSkewedGrid, vertex) * 0.288675;
}

float2x2 HextileLoadRot2x2(float2 idx, float rotStrength)
{
	float angle = abs(idx.x * idx.y) + abs(idx.x + idx.y) + PI;

	// remap to +/-pi
	angle = glsl_mod(angle, 2 * PI);
	if(angle < 0)  angle += 2 * PI;
	if(angle > PI) angle -= 2 * PI;

	angle *= rotStrength;

	float cs = cos(angle), si = sin(angle);
	return float2x2(cs, -si, si, cs);
}

// UV Offsets and base blend weights
// UVBWR[0...2].xy = UV Offsets
// UVBWR[0...2].zw = rotation costh/sinth -> reconstruct rotation matrix with float2x2(UVBWR[n].z, -UVBWR[n].w, UVBWR[n].w, UVBWR[n].z)
// UVBWR[3].xyz = Blend Weights (w unused) - needs luminance weighting
float4x4 HextileUVBWR(float2 uv)
{
	// Create Triangle Grid
	// Skew input space into simplex triangle grid (3.4641 ~= 2*sqrt(3))
	const float2x2 stochasticSkewedGrid = float2x2(1.0, -0.57735027, 0.0, 1.15470054);
	float2 skewedCoord = mul(stochasticSkewedGrid, uv * 3.4641 * _StochasticHexGridDensity);
	
	float2 baseId = float2(floor(skewedCoord));
	float3 temp = float3(frac(skewedCoord), 0);
	temp.z = 1 - temp.x - temp.y;

	float s = step(0.0, -temp.z);
	float s2 = 2 * s - 1;

	float3 weights = float3(-temp.z * s2, s - temp.y * s2, s - temp.x * s2);

	float2 vertex0 = baseId + float2(s, s);
	float2 vertex1 = baseId + float2(s, 1 - s);
	float2 vertex2 = baseId + float2(1 - s, s);

	float2 cen0 = HextileMakeCenUV(vertex0), cen1 = HextileMakeCenUV(vertex1), cen2 = HextileMakeCenUV(vertex2);
	float2x2 rot0 = float2x2(1, 0, 0, 1), rot1 = float2x2(1, 0, 0, 1), rot2 = float2x2(1, 0, 0, 1);

	if(_StochasticHexRotationStrength > 0)
	{
		rot0 = HextileLoadRot2x2(vertex0, _StochasticHexRotationStrength);
		rot1 = HextileLoadRot2x2(vertex1, _StochasticHexRotationStrength);
		rot2 = HextileLoadRot2x2(vertex2, _StochasticHexRotationStrength);
	}

	return float4x4(
		float4(mul(uv - cen0, rot0) + cen0 + StochasticHash2D2D(vertex0), rot0[0].x, -rot0[0].y),
		float4(mul(uv - cen1, rot1) + cen1 + StochasticHash2D2D(vertex1), rot1[0].x, -rot1[0].y),
		float4(mul(uv - cen2, rot2) + cen2 + StochasticHash2D2D(vertex2), rot2[0].x, -rot2[0].y),
		float4(weights, 0)
	);
}

float4 HextileSampleTexture(Texture2D tex, SamplerState texSampler, float2 uv, bool isNormalMap, float2 dUVdx, float2 dUVdy)
{
	// For some reason doing this instead of just calculating it directly prevents it from \
	// breaking after a certain number of textures use it. I don't understand why yet
	float4x4 UVBWR = HextileUVBWR(uv);

	// 2D Rotation Matrices for dUVdx/dy
	// Not sure if this constant folds during compiling when rot is locked at 0, so force it
	float2x2 rot0 = float2x2(1, 0, 0, 1), rot1 = float2x2(1, 0, 0, 1), rot2 = float2x2(1, 0, 0, 1);

	if(_StochasticHexRotationStrength > 0)
	{
		rot0 = float2x2(UVBWR[0].z, -UVBWR[0].w, UVBWR[0].w, UVBWR[0].z);
		rot1 = float2x2(UVBWR[1].z, -UVBWR[1].w, UVBWR[1].w, UVBWR[1].z);
		rot2 = float2x2(UVBWR[2].z, -UVBWR[2].w, UVBWR[2].w, UVBWR[2].z);
	}

	// Weights
	float3 W = UVBWR[3].xyz;

	// Sample texture
	// float3x4 c = float3x4(
	// 	tex.SampleGrad(texSampler, UVBWR[0].xy, mul(dUVdx, rot0), mul(dUVdy, rot0)),
	// 	tex.SampleGrad(texSampler, UVBWR[1].xy, mul(dUVdx, rot1), mul(dUVdy, rot1)),
	// 	tex.SampleGrad(texSampler, UVBWR[2].xy, mul(dUVdx, rot2), mul(dUVdy, rot2))
	// );

	float4 c0 = tex.SampleGrad(texSampler, UVBWR[0].xy, mul(dUVdx, rot0), mul(dUVdy, rot0));
	float4 c1 = tex.SampleGrad(texSampler, UVBWR[1].xy, mul(dUVdx, rot1), mul(dUVdy, rot1));
	float4 c2 = tex.SampleGrad(texSampler, UVBWR[2].xy, mul(dUVdx, rot2), mul(dUVdy, rot2));

	// Blend samples using luminance
	// This is technically incorrect for normal maps, but produces very similar
	// results to blending using normal map gradients (steepness)
	const float3 Lw = float3(0.299, 0.587, 0.114);
	float3 Dw = float3(dot(c0.xyz, Lw), dot(c1.xyz, Lw), dot(c2.xyz, Lw));

	Dw = lerp(1.0, Dw, _StochasticHexFallOffContrast);
	W = Dw * pow(W, _StochasticHexFallOffPower);
	// In the original hextiling there's a Gain3 step here, but it seems to slow things down \
	// and cause the UVs to break, so I've omitted it. Looks fine without

	W /= (W.x + W.y + W.z);
	return W.x * c0 + W.y * c1 + W.z * c2;
}

float4 HextileSampleTexture(Texture2D tex, SamplerState texSampler, float2 uv, bool isNormalMap)
{
	return HextileSampleTexture(tex, texSampler, uv, isNormalMap, ddx(uv), ddy(uv));
}
#endif // defined(_STOCHASTICMODE_HEXTILE)