import { abs, add, ceil, clamp, dFdx, dFdy, exp2, float, floor, Fn, fract, length, log2, max, min, mul, sin, sub, vec2, vec3 } from '../../tsl/TSLBase.js';

/**
 * See: https://casual-effects.com/research/Wyman2017Hashed/index.html
 */

const ALPHA_HASH_SCALE = 0.05; // Derived from trials only, and may be changed.

const hash2D = /*@__PURE__*/ Fn( ( [ value ] ) => {

	return fract( mul( 1.0e4, sin( mul( 17.0, value.x ).add( mul( 0.1, value.y ) ) ) ).mul( add( 0.1, abs( sin( mul( 13.0, value.y ).add( value.x ) ) ) ) ) );

} );

const hash3D = /*@__PURE__*/ Fn( ( [ value ] ) => {

	return hash2D( vec2( hash2D( value.xy ), value.z ) );

} );

const getAlphaHashThreshold = /*@__PURE__*/ Fn( ( [ position ] ) => {

	// Find the discretized derivatives of our coordinates
	const maxDeriv = max(
		length( dFdx( position.xyz ) ),
		length( dFdy( position.xyz ) )
	).toVar( 'maxDeriv' );

	const pixScale = float( 1 ).div( float( ALPHA_HASH_SCALE ).mul( maxDeriv ) ).toVar( 'pixScale' );

	// Find two nearest log-discretized noise scales
	const pixScales = vec2(
		exp2( floor( log2( pixScale ) ) ),
		exp2( ceil( log2( pixScale ) ) )
	).toVar( 'pixScales' );

	// Compute alpha thresholds at our two noise scales
	const alpha = vec2(
		hash3D( floor( pixScales.x.mul( position.xyz ) ) ),
		hash3D( floor( pixScales.y.mul( position.xyz ) ) ),
	).toVar( 'alpha' );

	// Factor to interpolate lerp with
	const lerpFactor = fract( log2( pixScale ) ).toVar( 'lerpFactor' );

	// Interpolate alpha threshold from noise at two scales
	const x = add( mul( lerpFactor.oneMinus(), alpha.x ), mul( lerpFactor, alpha.y ) ).toVar( 'x' );

	// Pass into CDF to compute uniformly distrib threshold
	const a = min( lerpFactor, lerpFactor.oneMinus() ).toVar( 'a' );
	const cases = vec3(
		x.mul( x ).div( mul( 2.0, a ).mul( sub( 1.0, a ) ) ),
		x.sub( mul( 0.5, a ) ).div( sub( 1.0, a ) ),
		sub( 1.0, sub( 1.0, x ).mul( sub( 1.0, x ) ).div( mul( 2.0, a ).mul( sub( 1.0, a ) ) ) ) ).toVar( 'cases' );

	// Find our final, uniformly distributed alpha threshold (ατ)
	const threshold = x.lessThan( a.oneMinus() ).select( x.lessThan( a ).select( cases.x, cases.y ), cases.z );

	// Avoids ατ == 0. Could also do ατ =1-ατ
	return clamp( threshold, 1.0e-6, 1.0 );

} );

export default getAlphaHashThreshold;
