//import <GLUP/current_profile/fragment_shader_preamble.h>
//import <GLUPGLSL/state.h>
//import <GLUP/stdglup.h>
//import <GLUP/current_profile/toggles.h>
//import <GLUP/current_profile/primitive.h>
//import <GLUP/fragment_shader_utils.h>
//import <GLUP/fragment_ray_tracing.h>

in VertexData {
    vec4 color;
    vec4 tex_coord;
    vec3 center_world_space;
    float radius;
} FragmentIn;

void main(void) {

    vec3 C = FragmentIn.center_world_space;
    float r = FragmentIn.radius;

    if(glupIsEnabled(GLUP_CLIPPING)) {
        if(
	    glup_primitive == GLUP_POINTS ||
	    GLUP.clipping_mode == GLUP_CLIP_WHOLE_CELLS
	) {
            if(dot(vec4(C,1.0),GLUP.world_clip_plane) < 0.0) {
                discard;
            }
        } else if(GLUP.clipping_mode == GLUP_CLIP_STRADDLING_CELLS) {
            float dist =
                abs(dot(vec4(C,1.0),GLUP.world_clip_plane)) /
                length(GLUP.world_clip_plane.xyz) ;
            if(dist > r) {
                discard;
            }
        }
    }

    Ray R = glup_primary_ray();
    vec3 M,N;

    if(
	glup_primitive != GLUP_POINTS &&
        glupIsEnabled(GLUP_CLIPPING) &&
        GLUP.clipping_mode == GLUP_CLIP_SLICE_CELLS
    ) {
        N = GLUP.world_clip_plane.xyz;
        float w = GLUP.world_clip_plane.w;
        float t = -(w + dot(N,R.O)) / dot(N,R.V);
        M = R.O + t*R.V;
        if(dot(M-C,M-C) > r*r) {
            discard;
        }
    } else {
	// High-precision ray-sphere intersection
	// See Ray Tracing Gems, Chapter 7,
	// Precision Improvements for Ray-Sphere Intersection,
	// E. Haines, J. Gunther, T. Akenine-Moller
        vec3 D = R.O-C;
        float a = dot(R.V,R.V);
	float b_prime = -dot(D,R.V);
	vec3  H = D + (b_prime/a) * R.V;
	float delta = r*r - dot(H,H);
        if(delta < 0.0) {
            discard;
        }
	// Original article: q = b_prime + sign(b_prime)*sqrt(a*delta)
	// Don't know why they do that, here we know we want t1
	float q = b_prime - sqrt(a*delta);
	float t = q/a;

	/*
        // Equivalent lower-precision version:
        float a = dot(R.V,R.V);
        float b = 2.0*dot(R.V,D);
        float c = dot(D,D)-r*r;
        float delta = b*b-4.0*a*c;
        if(delta < 0.0) {
            discard;
        }
        float t = (-b-sqrt(delta))/(2.0*a);
	*/

        M = R.O + t*R.V;
        N = M-C;
    }

    if(
	glup_primitive != GLUP_POINTS &&
        glupIsEnabled(GLUP_CLIPPING) &&
        GLUP.clipping_mode == GLUP_CLIP_STANDARD
    ) {
        if(dot(vec4(M,1.0),GLUP.world_clip_plane) < 0.0) {
            discard;
        }
    }

    glup_update_depth(M);

    if(glupIsEnabled(GLUP_PRIMITIVE_FILTERING)) {
        glup_primitive_filter(gl_PrimitiveID);
    }

    if(glupIsEnabled(GLUP_PICKING)) {
        glup_FragColor = glup_picking(gl_PrimitiveID);
        return;
    }

    vec4 result;
    if(glupIsEnabled(GLUP_VERTEX_COLORS)) {
        result = FragmentIn.color;
    } else {
        result = GLUP.front_color;
    }
    if(glupIsEnabled(GLUP_TEXTURING)) {
        result = glup_texturing(result, FragmentIn.tex_coord);
    }
    if(glupIsEnabled(GLUP_LIGHTING)) {
        N = normalize(GLUP.normal_matrix*N);
        if(
	    glup_primitive != GLUP_POINTS &&
            glupIsEnabled(GLUP_CLIPPING) &&
            GLUP.clipping_mode == GLUP_CLIP_SLICE_CELLS &&
            N.z < 0.0
        ) {
            N = -N;
        }
        result = glup_lighting(result, N);
    }
    glup_FragColor = result;
    glup_alpha_discard();
}
