float _BladeWidth, _BendStrength;
int _NumSegments_LOD_0, _NumSegments_LOD_1, _NumSegments_LOD_2;

struct MeshData {
    float4 vertex : POSITION;
    float3 tipOffset : TEXCOORD0;
};
struct v2g {
    float4 vertex : SV_POSITION;
    float3 tipOffset : TEXCOORD0;
};

struct g2f {
    float4 pos : SV_POSITION;
#ifdef IS_IN_BASE_PASS
    float2 uv : TEXCOORD0; // bottom left 0.0 top right 1.1 (values converge - 0 and 1 meet at the tip)
    SHADOW_COORDS(1) // put shadows data into TEXCOORD1
#endif
};

v2g vert (MeshData v) {
    v2g o;
    o.vertex = v.vertex;
    o.tipOffset = v.tipOffset;
    return o;
}

const int ns = 11;

#if _LOD_LOD_0
[MaxVertexCount(4 + ( 11 - 1 ) * 2)]
#elif _LOD_LOD_1
[MaxVertexCount(4 + (  5 - 1 ) * 2)]
#elif _LOD_LOD_2
[MaxVertexCount(4)]
#endif
void geom(point v2g IN[1], inout TriangleStream<g2f> triStream) {
    const float3 basePos = IN[0].vertex.xyz;
    const float3 tipPosBladeSpace = IN[0].tipOffset;
    const float3 tipPosObjectSpace = basePos + tipPosBladeSpace;

    // const float4 basePosClipSpace = UnityObjectToClipPos(float4(basePos, 1));
    // const float4 tipPosClipSpace = UnityObjectToClipPos(float4(tipPosObjectSpace, 1));
    // if (!isInFrustum(basePosClipSpace, 0.1) && !isInFrustum(tipPosClipSpace, 0.1)) return;

    const float3 basePosWS = mul(unity_ObjectToWorld, float4(basePos.xyz, 1.0)).xyz;
    const float cameraDistance = distance(_WorldSpaceCameraPos, basePosWS);

    // randomly cull grass blades
//     float randoVal = N21(basePos);
// #if _LOD_LOD_0
//     if (randoVal * 10.0 > max(1, 2 / (cameraDistance / 20))) {
//         return;
//     }
// #elif _LOD_LOD_1
//     if (randoVal * 10.0 > max(1, 5 / (cameraDistance / 20))) {
//         return;
//     }
// #endif
    
    float halfWidth = _BladeWidth/2;

    g2f o;

    float3 previousSegmentCenter = basePos - float3(0,-1,0); // point beneath the basePos

    // int numSegments = max(1, (int)(11) - ((cameraDistance) / 8.0) + N21(basePos));

#if _LOD_LOD_0
    int numSegments = 11;
#elif _LOD_LOD_1
    int numSegments = 5;
#elif _LOD_LOD_2
    int numSegments = 1;
#endif
    for (int i = 0; i < numSegments; i++) {
        const float lowerThickness = 0.8;
        
#if _LOD_LOD_2
        {
            const float segmentWidth = halfWidth * pow(sin(lowerThickness * 3.141), .8);
            const float3 widthOffset = getWidthOffset(segmentWidth, tipPosBladeSpace.xz) * max(1, cameraDistance / 8);
            float3 segmentCenterBase = basePos;
            float3 segmentCenterTip = basePos + tipPosBladeSpace;
            const float3 vertLeftBase = segmentCenterBase + widthOffset;
            const float3 vertRightBase = segmentCenterBase - widthOffset;
            const float3 vertLeftTip = segmentCenterTip + widthOffset * 0.4;
            const float3 vertRightTip = segmentCenterTip - widthOffset * 0.4;
        
            o.pos = UnityObjectToClipPos(vertLeftBase);
            #ifdef IS_IN_BASE_PASS
            o.uv = float2(0, 0);
            TRANSFER_SHADOW(o)
    #endif
            triStream.Append(o);

            o.pos = UnityObjectToClipPos(vertRightBase);
            #ifdef IS_IN_BASE_PASS
            o.uv = float2(1, 0);
            TRANSFER_SHADOW(o)
    #endif
            triStream.Append(o);

            o.pos = UnityObjectToClipPos(vertLeftTip);
            #ifdef IS_IN_BASE_PASS
            o.uv = float2(0, 1);
            TRANSFER_SHADOW(o)
    #endif
            triStream.Append(o);

            o.pos = UnityObjectToClipPos(vertRightTip);
            #ifdef IS_IN_BASE_PASS
            o.uv = float2(1, 1);
            TRANSFER_SHADOW(o)
    #endif
            triStream.Append(o);
        }
        continue;
#endif

        // in "blade space"
        const float segmentWidth = halfWidth * pow(sin(lowerThickness * 3.141 * (numSegments - i) / numSegments), .8);
        const float segmentHeightNormalized = i / (float)numSegments;
        // float nextSegmentHeightNormalized = (i+1) / (float)numSegments;

        // generate new verts
        const float3 widthOffset = getWidthOffset(segmentWidth, tipPosBladeSpace.xz) * max(1, cameraDistance / 8);
        float3 segmentCenter = basePos + bendParabula(tipPosBladeSpace, segmentHeightNormalized, _BendStrength);
        const float3 segmentCenterSnapshot = segmentCenter;
        
        const float3 vertLeft = segmentCenter + widthOffset;
        const float3 vertRight = segmentCenter - widthOffset;
        // const float3 nextSegmentCenter = basePos + bendParabula(tipPos, nextSegmentHeight01);

#ifdef IS_IN_BASE_PASS
        // calculate normal
        const float3 surfTangent1 = previousSegmentCenter - segmentCenter;
        const float3 surfTangent2 = previousSegmentCenter - vertLeft;
        const float3 normal = normalize(cross(surfTangent1, surfTangent2)); // TODO remove normalize
        float3 normalWS = UnityObjectToWorldNormal(normal);

        const float3 camVec = normalize(segmentCenter - _WorldSpaceCameraPos.xyz);

        // at which side of the blade are we looking?
        const bool lookingFromAbove = dot(camVec, normalWS) > 0;
        if (lookingFromAbove) {
            normalWS = -normalWS;
        }
#endif
        o.pos = UnityObjectToClipPos(vertLeft);
#ifdef IS_IN_BASE_PASS
        o.uv = float2(0, segmentHeightNormalized);
        TRANSFER_SHADOW(o)
#endif
        triStream.Append(o);

        o.pos = UnityObjectToClipPos(vertRight);
#ifdef IS_IN_BASE_PASS
        o.uv = float2(1, segmentHeightNormalized);
        TRANSFER_SHADOW(o)
#endif
        triStream.Append(o);

        previousSegmentCenter = segmentCenterSnapshot;
    }

    o.pos = UnityObjectToClipPos(basePos + tipPosBladeSpace * float3(_BendStrength,1,_BendStrength));
#ifdef IS_IN_BASE_PASS
    o.uv = float2(.5, 1);
    TRANSFER_SHADOW(o)
#endif
    triStream.Append(o);
}

fixed4 frag(g2f i) : SV_Target{
#ifdef IS_IN_SHADOW_PASS
    return 0;
#else
    float atten = 1.0;

    const fixed shadow = SHADOW_ATTENUATION(i);

    float3 light = float3(i.uv.yyy);
    light *= max(.35, shadow);
#if _LOD_LOD_2
    light *= 0.75;
#endif

#if _LOD_LOD_0
    float3 albedo = float3(0.3,1.0,0.3);
#elif _LOD_LOD_1
    float3 albedo = float3(1.0,0.7,0.3);
    albedo = float3(0.3,1.0,0.3);
#elif _LOD_LOD_2
    float3 albedo = float3(1.0,0.3,0.1);
    albedo = float3(0.3,1.0,0.3);
#endif

    return float4(light * albedo, 1);
#endif
}
