float _BladeWidth, _SegmentsMinusOne, _BendStrength;

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

struct g2f {
    float4 pos : SV_POSITION;
#if !defined(IS_IN_SHADOW_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;
}

#if defined(IS_IN_SHADOW_PASS)
[MaxVertexCount(24)]
#else
[MaxVertexCount(24)]
#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);

    float halfWidth = _BladeWidth/2;

    g2f o;

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

    int segmentsMinusOne = max(1, (int)(_SegmentsMinusOne) - ((cameraDistance) / 8.0) + N21(basePos));

    for (int i = 0; i < segmentsMinusOne; i++) {
        // in "blade space"
        const float lowerThickness = 0.8;
        const float segmentWidth = halfWidth * pow(sin(lowerThickness * 3.141 * ((int)_SegmentsMinusOne - i) / (int)_SegmentsMinusOne), .8);
        const float segmentHeightNormalized = i / _SegmentsMinusOne;
        // float nextSegmentHeightNormalized = (i+1) / _SegmentsMinusOne;

        // 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);

#if !defined(IS_IN_SHADOW_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);
#if !defined(IS_IN_SHADOW_PASS)
        o.uv = float2(0, segmentHeightNormalized);
        TRANSFER_SHADOW(o)
#endif
        triStream.Append(o);

        o.pos = UnityObjectToClipPos(vertRight);
#if !defined(IS_IN_SHADOW_PASS)
        o.uv = float2(1, segmentHeightNormalized);
        TRANSFER_SHADOW(o)
#endif
        triStream.Append(o);

        previousSegmentCenter = segmentCenterSnapshot;
    }

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

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

    const fixed shadow = SHADOW_ATTENUATION(i);

    float3 lightFinal = float3(i.uv.yyy);
    lightFinal *= max(.35, shadow);

    fixed4 albedo = fixed4(0.3,1.0,0.3,1.0);

    fixed3 color = fixed3(lightFinal * albedo);
    return fixed4(color, 1);
#endif
}
