﻿<?xml version="1.0" encoding="utf-8"?>

<CustomShader version="5">
    <Parameters>
        <Parameter name="windScale"                               target="windScale"                               group="base"     type="float"  defaultValue="1" minValue = "0.0"  maxValue = "10.0"/>
        <Parameter name="fadeInOutStartEnd"                       target="fadeInOutStartEnd"                       group="base"     type="float4" defaultValue="-5 0 70 75" />
        <Parameter name="plantRotate"                             target="plantRotate"                             group="rotation" type="float4" defaultValue="0.4363 1.57 0.0 0.0" minValue = "-6.0 -6.0 0.0 0.0"  maxValue = "6.0 6.0 1.0 1.0"/>
        <Parameter name="cellSizeTerrainSizeScaleXZScaleY"        target="cellSizeTerrainSizeScaleXZScaleY"        group="base"     type="float4" defaultValue="16 1024 2 255"/>
        <Parameter name="horizontalPosVarAtlasSizeAtlasOffset"    target="horizontalPosVarAtlasSizeAtlasOffset"    group="base"     type="float4"/>
        <Parameter name="texCoordOffsetAndScale"                  target="texCoordOffsetAndScale"                  group="base"     type="float4"/>
        <Parameter name="sizeAndSizeVariance"                     target="sizeAndSizeVariance"                     group="base"     type="float4"/>
        <Parameter name="snowScale"                               target="snowScale"                               group="base"     type="float"  defaultValue="0.6" minValue = "0.0" maxValue = "1.0"/>
        <Parameter name="seasonalTint"                            target="seasonalTint"                            group="seasonal" type="float4" defaultValue="0.294 0.164 0.038 0.35" minValue = "0.0 0.0 0.0 0.0" maxValue = "1.0 1.0 1.0 1.0" />
        <Parameter name="uvOffset"                                target="uvOffset"                                group="uvOffset" type="float"  defaultValue="0.0" minValue = "0" maxValue = "1.0" />
        <!-- X = virtual size (e.g. 4096), Y = unused, Z = layer offset for ground detail, W = layer offset for spray -->
        <Parameter name="terrainDetailMapVirtualSize"             target="terrainDetailMapVirtualSize"             group="base"     type="float4" />
        <Parameter name="terrainDetailShifts"                     target="terrainDetailShifts"                     group="base"     type="float4" />
    </Parameters>
    <UvUsages>
        <UvUsage textureName="baseMap"       uvType="uv0" uvScale="1.0"/>
        <UvUsage textureName="normalMap"     uvType="uv0" uvScale="1.0"/>
        <UvUsage textureName="glossMap"      uvType="uv0" uvScale="1.0"/>
        <UvUsage textureName="windNoise"     uvType="custom"/>
        <UvUsage textureName="alphaMap"      uvType="uv0" uvScale="1.0"/>
        <UvUsage textureName="terrainDetailMap" uvType="worldspace"/>
    </UvUsages>
    <Textures>
        <Texture name = "windNoise"     group="base"     defaultColorProfile = "linearRGB" defaultFilename = "$data/shared/windNoise.png"/>
        <Texture name = "alphaMap"      group="base"     defaultColorProfile = "linearRGB" defaultFilename = "$data/shared/default_alpha.png"/>
        <Texture name="terrainDetailMap" defaultColorProfile="linearRGB" isOptional="true"/>
    </Textures>
    <VertexAttributes>
        <IgnoredVertexAttribute name="position" group="base"/>
        <IgnoredVertexAttribute name="normal"   group="base"/>
        <IgnoredVertexAttribute name="uv0"      group="base"/>
        <IgnoredVertexAttribute name="tangent"  group="base"/>
    </VertexAttributes>
    <Variations>
        <Variation name="SEASONAL" groups="base seasonal">
<![CDATA[
#define SEASONAL
    /*
    Season states:
        cShared3 = 0 = summer
        cShared3 = 1 = autumn
        cShared3 = 2 = winter
        cShared3 = 3 = spring
        cShared3 = 4 = summer
    */
]]>
        </Variation>
        <Variation name = "PLANT_ROTATION" groups="base rotation">
<![CDATA[
/*
TODO:
    - lower the rotation based on real size of the plants

foliage model have to be oriented to +X
plantRotate.x - random
                0.0   == 0 deg
                0.262 == 15 deg
                0.524 == 30 deg
                0.785 == 45 deg
                0.960 == 55 deg
                6.283 == 360 deg
plantRotate.y - rotation value,
                0     == +X
                1.570 == -Z
                3.142 == -X
                4.712 == +Z
*/
#define PLANT_ROTATION
]]>
        </Variation>
        <Variation name="PLANT_ROWS" groups="base">
<![CDATA[
/*
    Use In.dmRotation to rotate the plants on 90 degrees
    foliage model have to be oriented to +X
    0..N (terrainDetailShifts.y >> 8) low-level data
*/
#define PLANT_ROWS
]]>
        </Variation>
        <Variation name="BILLBOARD" groups="base">
<![CDATA[
#define BILLBOARD_180
]]>
        </Variation>
        <Variation name="BILLBOARD_SEASONAL" groups="base seasonal">
<![CDATA[
#define BILLBOARD_180
#define SEASONAL
]]>
        </Variation>
        <Variation name="BILLBOARD_UV_OFFSET" groups="base uvOffset">
<![CDATA[
#define BILLBOARD_180
#define UV_OFFSET
]]>
        </Variation>

    </Variations>
    
    <LodLevel startDistance="0" alphaForceMode="use">
        <CodeInjections>
            <CodeInjection position = "SHARED_START">
<![CDATA[
#if defined(FOLIAGE)
    #define SOFTWARE_INSTANCING 1
#endif
]]>
            </CodeInjection>
            <CodeInjection position = "VS_INPUT">
<![CDATA[
    float3 terrainNormal;
    float3 blockPosition;
    float2 widthHeightScale;
    float rotAngle;
#if defined(SOFTWARE_INSTANCING)
    float atlasIndex;
#endif
    float3 blockPositionWorld;
    float3 gWindDirection;
    float gPreviousWindNoise;
    float gCurrentWindNoise;
    uint dmDetailIndex;
    uint dmRotation; 
    uint dmSprayIndex;
]]>
            </CodeInjection>
            <CodeInjection position = "SAMPLERS">
<![CDATA[
    sampler2D windNoise;
    sampler2D alphaMap;
    usampler2D terrainDetailMap;
]]>
            </CodeInjection>
            <CodeInjection position="CONFIG_DEFINES">
<![CDATA[
#if GPU_PROFILE <= GPU_PROFILE_HIGH
    #define NUM_SHADOW_MAP_SAMPLES 1
#endif
#if defined(FOLIAGE)
    #define DISTANCE_BLENDING
#endif
]]>
            </CodeInjection>
            <CodeInjection position="LIB_FUNCTION_VS">
<![CDATA[
float getPlantRotation(in VS_INPUT In, ObjectParameters& object)
{
#if defined(PLANT_ROTATION)
    if (getDefaultTexCoords(In, object).x > 1) {
        float mRand = lerp(-object.plantRotate.x, object.plantRotate.x, In.rotAngle);
        return mRand + object.plantRotate.y;
    }
#endif
#if defined(PLANT_ROWS)
    {
        int maxRotation = floatBitsToInt(object.terrainDetailShifts.y) >> 8;
        int halfRot = (maxRotation+1) >> 1;
        return abs((int)In.dmRotation - halfRot) * 2 < halfRot ? 0.0 : (3.1415926 / 2.0);
    }
#endif
    return In.rotAngle*3.1415926; // In.blendWeights.w*3.1415926;
}

float fitRange(float mIn,float mOldMin,float mOldMax ,float mNewMin ,float mNewMax ){
    return (mNewMin + (mIn - mOldMin) * (mNewMax  - mNewMin ) / (mOldMax  - mOldMin));
}

void getWindNoise(float3 mWindDirection, float3 mFullWorldPosition, float previousTimeInSec, float currentTimeInSec, float mUVScale, float mSpeed, inout float previousWindNoise, inout float currentWindNoise)
{
    float3 mWind1 = normalize(mWindDirection);
    float3 mWind2 = float3(mWind1.z, 0.0, -mWind1.x);
    float mDot1 = dot(mWind1,mFullWorldPosition);
    float mDot2 = dot(mWind2,mFullWorldPosition);
    float mV = mUVScale * mDot2;

    float mU_previous = mUVScale * mDot1 - mSpeed * previousTimeInSec;
    float mU_current = mUVScale * mDot1 - mSpeed * currentTimeInSec;

    previousWindNoise = tex2Dlod(windNoise,float2(mU_previous,mV),0).x;
    currentWindNoise = tex2Dlod(windNoise,float2(mU_current,mV),0).x;
}

float3 getRotatedVector(float3 vector, float angle, float3 normalizedDir){
    float sinRot, cosRot;
    sincos(angle, sinRot, cosRot);

    float3 flatDir = normalizedDir;
    float3 flatPerp = float3(flatDir.z, 0, -flatDir.x);
    float3 flatUp = float3(0, 1, 0);
    float offsetInFlatDir = dot(vector, flatDir);
    float offsetInFlatPerp = dot(vector, flatPerp);
    float offsetInFlatUp = dot(vector, flatUp);

    float newOffsetInFlatDir = offsetInFlatDir * cosRot + offsetInFlatUp * sinRot;
    float newOffsetInFlatUp = offsetInFlatUp * cosRot - offsetInFlatDir * sinRot;

    return flatDir * newOffsetInFlatDir + flatPerp * offsetInFlatPerp + flatUp * newOffsetInFlatUp;
}

void getDetailInfo(ObjectParameters& object, float2 uvCoords, out uint detailIndex, out uint rotation, out uint sprayType)
{
    // Move by half a pixel because a pixel defines the center value of a quad
    float2 stepped = max(uvCoords * object.terrainDetailMapVirtualSize.x - 0.5, 0);
    uint uvMask = tex2DSize(terrainDetailMap, 0) - 1;

    uint3 tex = utex2DFetch(terrainDetailMap, uint2(stepped) & uvMask, 0).xyz;
    detailIndex = tex.x;
    rotation = tex.y;
    sprayType = tex.z;
}
]]>
            </CodeInjection>
            <CodeInjection position="VS_OUTPUT">
<![CDATA[
    float distanceBlendInverseAlphaAttr : TEXCOORDn;
#if defined(BILLBOARD_180)
    float2 texCoords2: TEXCOORDn;
    float texCoords2Weight: TEXCOORDn;
#endif
    float windColor : TEXCOORDn;
]]>
            </CodeInjection>
            <CodeInjection position="FILL_VERTEX_INPUT_VS">
<![CDATA[
#if defined(FOLIAGE)
    #if defined(SOFTWARE_INSTANCING)
        fillSoftInstancedFoliageVertexInput(In.vertexId, In, vertexTextureBuffer);
        getFoliageInstanceData(In.instanceId, instanceDataBuffer, object.horizontalPosVarAtlasSizeAtlasOffset.y, object.sizeAndSizeVariance, In.blockPosition, In.terrainNormal, In.rotAngle, In.widthHeightScale, In.atlasIndex);
        In.texCoords2 = object.texCoordOffsetAndScale;
    #else
        fillFoliageVertexInput(vertexAttributes, In, object);
        In.rotAngle = vertexAttributes.blendWeights.w;
        In.terrainNormal = vertexAttributes.texCoords1.xyz - 0.5;
        In.widthHeightScale = vertexAttributes.color.xy * 8;
        In.blockPosition = vertexAttributes.blendWeights.xyz;
        In.blockPosition.y += 0.5;
    #endif
    float3 blockPos = In.blockPosition.xyz;
    blockPos *= object.cellSizeTerrainSizeScaleXZScaleY.xwx;
    float3 worldBlockPos = mul(object.modelMatrix, float4(blockPos, 1)).xyz;
    In.blockPositionWorld = worldBlockPos;
#else
    // Regular mesh
    In.rotAngle = 0.0;
    In.terrainNormal = float3(0,1,0);
    In.widthHeightScale = 1.0;
    In.blockPosition = 0;
    In.blockPositionWorld = float3(object.modelMatrix[0][3], object.modelMatrix[1][3], object.modelMatrix[2][3]);
#endif
    // ------
    float3 mFullWorldPosition = In.blockPositionWorld + float3(invViewMatrix[0][3], invViewMatrix[1][3], invViewMatrix[2][3]);
    In.gWindDirection = float3(0.0,0.0,0.0);
    In.gWindDirection.xz = normalize(float2(cShared1, cShared2));
    In.gWindDirection.y = 0;
    In.gPreviousWindNoise = 0;
    In.gCurrentWindNoise = 0;
    getWindNoise(In.gWindDirection, mFullWorldPosition, cPrevTime_s, cTime_s, 0.03, 0.2, In.gPreviousWindNoise, In.gCurrentWindNoise);

    float terrainSizeWorld = object.cellSizeTerrainSizeScaleXZScaleY.y * object.cellSizeTerrainSizeScaleXZScaleY.z;
    float2 blockPositionTerrainUV = saturate((mFullWorldPosition.xz + terrainSizeWorld * 0.5) / terrainSizeWorld);
    getDetailInfo(object, blockPositionTerrainUV, In.dmDetailIndex, In.dmRotation, In.dmSprayIndex);
]]>
            </CodeInjection>
            <CodeInjection position="GET_NORMAL_VS">
<![CDATA[
#if defined(FOLIAGE)
    float3 normal = In.normal.xyz; // get mesh normal
    #if !defined(BILLBOARD_180)
        float plantRotation = getPlantRotation(In, object);
        normal = rotateFoliageVector(In, object, normal, plantRotation);
        normal = rotateVectorToTerrainNormal(In, object, normal, In.terrainNormal);
    #endif
    return normal;
#endif
]]>
            </CodeInjection>
            <CodeInjection position="GET_TANGENT_VS">
<![CDATA[
#if defined(FOLIAGE)
    float3 tangent = In.tangent.xyz;
    #if !defined(BILLBOARD_180)
        tangent = rotateFoliageVector(In, object, tangent, In.rotAngle);
        tangent = rotateVectorToTerrainNormal(In, object, tangent, In.terrainNormal);
    #endif
    return tangent;
#endif
]]>
            </CodeInjection>
            <CodeInjection position="GET_BITANGENT_SIDE_VS">
<![CDATA[
#if defined(BILLBOARD_180)
    return 1;
#endif
]]>
            </CodeInjection>
            <CodeInjection position="GET_DEFAULT_TEXCOORDS_VS">
<![CDATA[
#if defined(FOLIAGE)
    #if defined(SOFTWARE_INSTANCING)
        return getFoliageTexCoords(In, object, In.texCoords2.xy, In.texCoords2.zw, In.atlasIndex, object.horizontalPosVarAtlasSizeAtlasOffset.zw);
    #else
        return getFoliageTexCoords(In, object, In.texCoords2.zw, In.texCoords2.xy, 0, 0);
    #endif
#endif
]]>
            </CodeInjection>
            <CodeInjection position="OBJECT_PARAMETERS">
<![CDATA[
    float  windScale;
    float4 fadeInOutStartEnd;
    float4 cellSizeTerrainSizeScaleXZScaleY;
    float4 seasonalTint;
    float4 terrainDetailMapVirtualSize;
    float4 terrainDetailShifts;
#if defined(SOFTWARE_INSTANCING)
    float4 horizontalPosVarAtlasSizeAtlasOffset;
    float4 texCoordOffsetAndScale;
    float4 sizeAndSizeVariance;
#endif
#if defined( PLANT_ROTATION )
    float4 plantRotate;
#endif
#if defined( UV_OFFSET )
    float uvOffset;
#endif
    float snowScale;
]]>
            </CodeInjection>
            <CodeInjection position="GET_POSITION_VS">
<![CDATA[
    float3 position = In.position.xyz;  // get mesh XYZ
    position *= In.widthHeightScale.xyx;  // scale by widthScale & heightScale
    float relativeHeight = saturate(position.y);
#if defined(FOLIAGE)
    #if !defined(BILLBOARD_180)
        float plantRotation = getPlantRotation(In, object);
        position = rotateFoliageVector(In, object, position, plantRotation);
    #endif
    float2 curDir = readFoliageAnimationTexture(In.blockPositionWorld);
    float bend = length(curDir);
    if (relativeHeight*bend > 0) {
        curDir /= bend;
        float squish = 1 - bend;
        float3 bentPosition = getRotatedVector(position, bend*1.570796, float3(curDir.x, 0, curDir.y));
        position = lerp(position, bentPosition, min(relativeHeight*3.0, 1)); // slowly start bending up to 33cm, then go all in
    }
#else
    float bend = 0.0;
#endif

#if !defined(DEPTH_VIS) // avoid shadow artefacts by not animating in the shadow map
    if (relativeHeight*cShared0 > 0.001) {
        // New wind animation (noise texture based rotation)
        float pi = 3.14159265359;
        float angleMin = -20.0 * (pi/180.0) * object.windScale;
        float angleMax =  25.0 * (pi/180.0) * object.windScale;
        float mAngle = cShared0 * fitRange(In.gCurrentWindNoise,0,1,angleMin,angleMax) * saturate(1-bend*2);
        float3 windedPosition = getRotatedVector(position, mAngle, In.gWindDirection);
        position = lerp(position, windedPosition, relativeHeight);
    }
#endif

#if !defined(BILLBOARD_180)
    position += In.blockPosition.xyz*object.cellSizeTerrainSizeScaleXZScaleY.xwx;   // add blockPositionX,Y,Z
#endif
    return position.xyz;
]]>
            </CodeInjection>
            <CodeInjection position="GET_PREV_POSITION_VS">
<![CDATA[
    float3 position = In.position.xyz;     // get mesh XYZ
    position *= In.widthHeightScale.xyx;  // scale by widthScale & heightScale
    float relativeHeight = saturate(position.y);

#if defined(FOLIAGE)
    #if !defined(BILLBOARD_180)
        float plantRotation = getPlantRotation(In, object);
        position = rotateFoliageVector(In, object, position, plantRotation);
    #endif

    float2 curDir = readPreviousFoliageAnimationTexture(In.blockPositionWorld);
    float bend = length(curDir);
    if (relativeHeight*bend > 0) {
        curDir /= bend;
        float squish = 1 - bend;
        float3 bentPosition = getRotatedVector(position, bend*1.570796, float3(curDir.x, 0, curDir.y));
        position = lerp(position, bentPosition, min(relativeHeight*3.0, 1)); // slowly start bending up to 33cm, then go all in
    }
#else
    float bend = 0.0;
#endif

#if !defined(DEPTH_VIS) // avoid shadow artefacts by not animating in the shadow map
    if (relativeHeight*cShared0 > 0.001) {
        // New wind animation (noise texture based rotation)
        float pi = 3.14159265359;
        float angleMin = -20.0 * (pi/180.0) * object.windScale;
        float angleMax =  25.0 * (pi/180.0) * object.windScale;
        float mAngle = cShared0 * fitRange(In.gPreviousWindNoise,0,1,angleMin,angleMax) * saturate(1-bend*2);
        float3 windedPosition = getRotatedVector(position, mAngle, In.gWindDirection);
        position = lerp(position, windedPosition, relativeHeight);
    }
#endif

#if defined(FOLIAGE)
    #if !defined(BILLBOARD_180)
        position += In.blockPosition.xyz*object.cellSizeTerrainSizeScaleXZScaleY.xwx;   // add blockPositionX,Y,Z
    #endif
#endif
    return position.xyz;
]]>
            </CodeInjection>
            <CodeInjection position="POST_GET_WORLD_POSE_VS">
<![CDATA[

#if !defined(BILLBOARD_180)
    prevWorldPosition = mul(object.prevModelMatrix, float4(prevPosition, 1));
#else
    float mUVShift;
    float mUVShift2;
    {
        float3 upVector = float3(object.modelMatrix[0][1], object.modelMatrix[1][1], object.modelMatrix[2][1]);
        float3 camToPos = float3(object.modelMatrix[0][3], object.modelMatrix[1][3], object.modelMatrix[2][3]);

        float3 blockPosCC = In.blockPosition.xyz;
        blockPosCC *= object.cellSizeTerrainSizeScaleXZScaleY.xwx;
        float3 worldBlockPosCCC = mul((float3x3)object.modelMatrix, blockPosCC);

        camToPos += worldBlockPosCCC;

        // Apply some random camera alignment offset  to make the circle around the camera less obvious
        float3 camToPosOrient = camToPos;
        camToPosOrient.xz += (frac(sin(worldBlockPosCCC.xz*0.412) * 12.0) - 0.5) * 0.2*length(camToPos);
        {
            float plantRotation = getPlantRotation(In, object);
            float3 plantDir = rotateFoliageVector(In, object, worldNormal, plantRotation);

            float3 mProjectCamToPos      = normalize(camToPosOrient -upVector*dot(upVector,camToPosOrient));
            float3 mProjectedWorldNormal = normalize(plantDir -upVector*dot(upVector,plantDir));

            float3 camCross     = cross( mProjectCamToPos, mProjectedWorldNormal );
            float mEyeAngle     = acos(clamp(-dot( mProjectCamToPos, mProjectedWorldNormal), -1.0, 1.0));
            float mUVShiftTileF = 8.0 * 0.5 * mEyeAngle / 3.1415926535897932384626433832795;
            if ( camCross.y > 0 ){
                mUVShiftTileF *= -1;
            }
            float mUVShiftTile1 = floor(mUVShiftTileF);
            mUVShift            = mUVShiftTile1/8.0;
            Out.texCoords2Weight = saturate(2*(mUVShiftTileF-mUVShiftTile1) - 0.5);
            mUVShift2           = mUVShift + 1.0/8.0;
        }

        float3 sideVector = normalize(cross(camToPosOrient, upVector));

        float3 dirVector = cross(sideVector, upVector);
        // Roate 20deg towards the top (we generate the lods as if we view them from above)
        //upVector = -sin(0.349)*dirVector + cos(0.349)*upVector;
        worldPosition = camToPos + sideVector*(position.x) + upVector*position.y;
        worldTangent.xyz = sideVector;
        worldBitangent  = upVector;
        worldNormal.xyz = cross(sideVector, upVector);

        prevWorldPosition = worldPosition;
    }
    // compute previous world pose.. not worth to compute.. so we set prevWorldPosition = worldPosition
#endif
]]>
            </CodeInjection>
            <CodeInjection position="POST_SET_TEXCOORDS_VS">
<![CDATA[
#if defined(BILLBOARD_180)
    Out.texCoords2 = Out.defaultTexCoords;
    Out.texCoords2.x += mUVShift2;
    Out.defaultTexCoords.x += mUVShift;

    #if defined( UV_OFFSET )
        Out.texCoords2.x += object.uvOffset;
        Out.defaultTexCoords.x += object.uvOffset;
    #endif
#endif
]]>
            </CodeInjection>
            <CodeInjection position="VS_END">
<![CDATA[
// full screen debug code...
/*#if defined(SOFTWARE_INSTANCING)
    Out.position.x = (float)((In.vertexId%3) % 2) * 4.0 - 1.0;
    Out.position.y = (float)((In.vertexId%3) / 2) * 4.0 - 1.0;
    Out.position.z = -0.5;
    Out.position.w = 1.0;
    return Out;
#endif
*/
#if defined(FOLIAGE)
    #if defined(DEPTH_VIS)
        float viewDirLength = length(lodViewpoint.xz - In.blockPositionWorld.xz);
        float positionOnFadeIn = (viewDirLength - object.fadeInOutStartEnd.x) / (object.fadeInOutStartEnd.y - object.fadeInOutStartEnd.x);
        float positionOnFadeOut = (viewDirLength - (object.fadeInOutStartEnd.z-6)) / (object.fadeInOutStartEnd.w - (object.fadeInOutStartEnd.z-6));
    #else
        float viewDirLength = length(In.blockPositionWorld.xz);
        float positionOnFadeIn = (viewDirLength - object.fadeInOutStartEnd.x) / (object.fadeInOutStartEnd.y - object.fadeInOutStartEnd.x);
        float positionOnFadeOut = (viewDirLength - object.fadeInOutStartEnd.z) / (object.fadeInOutStartEnd.w - object.fadeInOutStartEnd.z);
    #endif
    float alphaFadeIn = saturate(positionOnFadeIn);
    float alphaFadeOut = 1 - saturate(positionOnFadeOut);
    Out.distanceBlendInverseAlphaAttr = alphaFadeIn - alphaFadeOut;
#endif
    // pass wind color to the pixel shader
    Out.windColor = fitRange(In.gCurrentWindNoise,0,1,0.9,1.1);
]]>
            </CodeInjection>
            <CodeInjection position = "LIB_FUNCTION_FS">
<![CDATA[
// simplifed smoothstep
// remaps the values of x between a and b to the range [0, 1], and clamps values outside this range
float linearstepFS(float a, float b, float x){
    return saturate((x - a)/(b - a));
}
float fitRangeFS(float mIn,float mOldMin,float mOldMax ,float mNewMin ,float mNewMax ){
    return (mNewMin + (mIn - mOldMin) * (mNewMax  - mNewMin ) / (mOldMax  - mOldMin));
}
// Season states:
// cShared3 = 0 = summer
// cShared3 = 1 = autumn
// cShared3 = 2 = winter
// cShared3 = 3 = spring
// cShared3 = 4 = summer
// cShared4 = 0 .. 1 = snow control
float getSnowMask(ObjectParameters& object, float3 mNormal, float mBackfaceScale){
    float NdotY = mNormal.y; // dot(mNormal, float3(0.0, 1.0, 0.0));
    NdotY = max(NdotY * mBackfaceScale, NdotY * (1.0-mBackfaceScale)) + mBackfaceScale;
    float mScale = object.snowScale*min(cShared4,1);
    float mPow = fitRangeFS(mScale,0,1,50,1);
    float pi = 3.14159265359;
    mScale = sin(0.5*pi*mScale); // keep high values close to 1
    float mSnowMask = saturate(mScale*pow(NdotY,mPow));
    return mSnowMask;
}
]]>
            </CodeInjection>
            <CodeInjection position = "FS_GLOBALS">
<![CDATA[
    float  gSnowMask;
    bool   gIsSnowing;
    float3 gSnowDiffuse;
    float3 gSnowSpecular;
    float  gSeason;
]]>
            </CodeInjection>
            <CodeInjection position = "START_FS">
<![CDATA[
    globals.gSnowDiffuse  = float3(0.7300,0.7668,0.8356);
    globals.gSnowSpecular = float3(0.1922,0.8706,0.0000);
    globals.gSnowMask = 0.0;
    globals.gIsSnowing = cShared4 > 0.0 && object.snowScale > 0.0;
    globals.gSeason    = cShared3;
]]>
            </CodeInjection>
            <CodeInjection position="GET_DISTANCE_BLEND_INVERSE_ALPHA_FS">
<![CDATA[
#if defined(FOLIAGE)
    return In.vs.distanceBlendInverseAlphaAttr;
#endif
]]>
            </CodeInjection>
            <CodeInjection position="POST_DIFFUSE_COLOR_FS">
<![CDATA[
#if defined(ALBEDO_MAP)
    float4 combinedDiffuseAlpha = float4(diffuseColor.rgb,1.0);
    #if defined(BILLBOARD_180)
        float4 daSource,daTarget;
        daSource.rgb = tex2D(baseMap,  ALBEDO_MAP_TEXCOORDS).rgb;
        daSource.a   = tex2D(alphaMap, ALBEDO_MAP_TEXCOORDS).r;
        daTarget.rgb = tex2D(baseMap,  In.vs.texCoords2).rgb;
        daTarget.a   = tex2D(alphaMap, In.vs.texCoords2).r;
        combinedDiffuseAlpha = lerp(daSource, daTarget, In.vs.texCoords2Weight);
        diffuseColor = combinedDiffuseAlpha.rgb;
    #else
        combinedDiffuseAlpha.a = tex2D(alphaMap, ALBEDO_MAP_TEXCOORDS).r;
    #endif
#endif
    #if defined(SEASONAL)
        if (globals.gSeason == 1 || globals.gSeason == 2) {
            diffuseColor.rgb = lerp(diffuseColor, object.seasonalTint.rgb, object.seasonalTint.a);
    }
    #endif
    { // Snow mixing code
        if (globals.gIsSnowing){
            // pixelShader normal computed before this point, right after PARALLAX_MAPPING_FS
            globals.gSnowMask = getSnowMask(object,normal,-0.05);
            diffuseColor.rgb = lerp( diffuseColor.rgb, globals.gSnowDiffuse.rgb, globals.gSnowMask );
        }
    }
    // make diffuse brighter or darker based on wind
    diffuseColor *= In.vs.windColor;
]]>
            </CodeInjection>
            <CodeInjection position="POST_GLOSS_COLOR_FS">
<![CDATA[
#if defined(GLOSS_MAP)
    // Roughness map layout is: RGB = smoothness, ambientOcclusion, metalness
    #if defined(BILLBOARD_180)
        float3 pbrSource = tex2D(glossMap, GLOSS_MAP_TEXCOORDS).rgb;
        float3 pbrTarget = tex2D(glossMap, In.vs.texCoords2).rgb;
        float3 pbrMix    = lerp(pbrSource, pbrTarget, In.vs.texCoords2Weight);
        roughness = 1-pbrMix.x;
        bakedAO = pbrMix.y;
        metalness = pbrMix.z;
    #endif
#endif
    { // Snow mixing code
        if (globals.gIsSnowing){
            float3 mPbrMapValues = lerp( float3(1-roughness,bakedAO,metalness), globals.gSnowSpecular, globals.gSnowMask );
            roughness = 1-mPbrMapValues.x;
            bakedAO   = mPbrMapValues.y;
            metalness = mPbrMapValues.z;
        }
    }
]]>
            </CodeInjection>
            <CodeInjection position="ALPHA_FS">
<![CDATA[
#if defined(ALBEDO_MAP) && (defined(ALPHA_BLENDED) || defined(ALPHA_TESTED))
    alpha = combinedDiffuseAlpha.w;
#endif
]]>
            </CodeInjection>
            <CodeInjection position="GET_UNNORMALIZED_TANGENT_SPACE_NORMAL_FS">
<![CDATA[
#if defined(BILLBOARD_180)
    #if defined(NORMAL_MAP)
        return lerp(tex2D(normalMap, NORMAL_MAP_TEXCOORDS).xyz, tex2D(normalMap, In.vs.texCoords2).xyz, In.vs.texCoords2Weight) - 0.5;
    #endif
#endif
]]>
            </CodeInjection>
            <CodeInjection position="GET_TANGENT_SPACE_NORMAL_FS">
<![CDATA[
#if defined(BILLBOARD_180)
    #if defined(NORMAL_MAP)
        return normalize(getUnnormalizedTangentSpaceNormal(In, globals, object));
    #endif
#endif
]]>
            </CodeInjection>
            <CodeInjection position="FINAL_POS_FS">
<![CDATA[
#if defined( DEBUG_SHOW_PARALLAX )
    oColor.rgb  = (float3)globals.gSnowMask;
#endif
]]>
            </CodeInjection>
        </CodeInjections>
    </LodLevel>
</CustomShader>
