/* Code for doing nice, perspective-correct, antialiased
   point rendering in OpenGL.
   
   To use this, you just:
   	- Set up your modelview matrix to take you to camera coordinates
	- Call nice_points_begin
	- Draw some points (e.g., with glBegin(GL_POINTS); )
	- Call nice_points_end

  Internally, this code uses OpenGL programmable shaders to make
  perspective-correct smooth points.  You specify the points' 
  camera-coordinates size in "point_size" (post-modelview matrix units).  
  "hfov" is the camera horizontal field of view, in radians.
*/
bool nice_points_begin(float point_size=1.0,vec3 cameraLoc=vec3(0.0), float hfov=1.0)
{
	glEnable(GL_POINT_SMOOTH);
	if (!GLEW_ARB_shading_language_100) 
	{ // No programmable shaders: fallback path is fixed-screen-size points
		glPointSize(2.0);
		return true;
	}
	
	// Perspective-correct point rendering with a GLSL shader
	static GLuint splatTex=0;
	const int splatFrac=4; /* amount of transparent blank space to add outside splat */
	if (splatTex==0) {
		/* Make a new splat texture: a central dark spot
		  with fuzzy edges.  This is a greyscale texture.
		*/
		const int w=32*splatFrac, h=32*splatFrac;
		unsigned char arr[w*h];
		for (int y=0;y<h;y++)
		for (int x=0;x<w;x++) {
			double dx=x*splatFrac/(double)w-0.5;
			double dy=y*splatFrac/(double)h-0.5;
			double r=sqrt(dx*dx+dy*dy);
			double out=1.0;
			double r_min=0.3, r_max=0.48;
			float rf=(r-r_min)/(r_max-r_min);
			if (rf<0) out=1.0;
			else if (rf>1) out=0.0;
			else out=1.0-rf*rf; /* quadratic dropoff */
			arr[y*w+x]=(unsigned char)(255*out);
		}
		
		glGenTextures(1,&splatTex);
		glBindTexture(GL_TEXTURE_2D,splatTex);
		gluBuild2DMipmaps(GL_TEXTURE_2D,GL_LUMINANCE8,
			w,h,GL_LUMINANCE,GL_UNSIGNED_BYTE,arr);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	}
	/* This is GLSL code, that runs on the graphics card itself */
	static GLhandleARB p=makeProgramObject(
	"/* GLSL Vertex shader, to compute point color and size */\n"
	"uniform vec3 cameraLoc; // camera location, in post-ModelView coordinates\n"
	"uniform float diameter; // overall point diameter (camera Z units)\n"
	"uniform float brightness; // overall exposure control--onscreen brightness/particle\n"
	"varying float coverage; // fraction of pixels covered by this point\n"
	"varying vec4 color; //vertex colors \n"
	"void main(void) {\n"
	"	vec4 cameraCoord=gl_ModelViewMatrix*gl_Vertex;\n"
	"	gl_Position=gl_ProjectionMatrix*cameraCoord;\n"
	"	color=cameraCoord; /* energy is spread over screen area */ \n"
	"	float d=distance(cameraLoc,vec3(cameraCoord)); // camera at origin\n"
	"	gl_PointSize=diameter/d; // <- point diameter in pixels (drops like sqrt(1/r^2))\n"
	"	const float maxSize=1.9;\n"
	"	coverage=1.0;\n"
	"	if (gl_PointSize<maxSize) { // can't draw sub-pixel points  \n"
	"		// color=vec4(0,1,0,1); // debugging: make small points green \n"
	"		color*=(1.0/(maxSize*maxSize))*gl_PointSize*gl_PointSize; \n"
	"		color.a+=2.0/256.0; // don't let points get *too* transparent!\n"
	"		gl_PointSize=maxSize;\n"
	"	}\n"
	"}\n"
	,
	"/* GLSL Fragment shader, to write actual pixels */\n"
	"varying float coverage; // fraction of pixels covered by this point\n"
	"varying vec4 color;\n"
	"uniform sampler2D splatTex;\n"
	"void main(void) {\n"
	"	coverage*=texture2D(splatTex,vec2(0.25*gl_TexCoord[0])).r;\n"
	"	gl_FragColor=fract(color); // vec4(1,0,0,1);\n"
	"	gl_FragColor.a=coverage; // alpha-blending\n"
	"}\n"
	);
#if 1
	glUseProgramObjectARB(p);
	setUniform(p,"cameraLoc",cameraLoc);
	setUniform(p,"brightness",50*0.1f); 
	setUniform(p,"diameter",point_size/hfov*glutGet(GLUT_WINDOW_WIDTH)); /* diameter in screen pixels */
	glBindTexture(GL_TEXTURE_2D,splatTex);
	setUniform(p,"splatTex",0); /* splat texture is bound to texture unit 0 */
	
	/* GL Point Sprites (magically makes texture coordinates in fragment program) */
	glEnable(GL_POINT_SPRITE_ARB);
	glTexEnvi(GL_POINT_SPRITE_ARB,GL_COORD_REPLACE_ARB,GL_TRUE);
	
	glPointSize(1.0); /* set default point size */
	/* Make gl_PointSize work from GLSL */
	glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
#endif
	glEnable(GL_ALPHA_TEST); /* too agressive--loses too many points */
	glAlphaFunc(GL_GREATER,0.0); /* pass if you're over zero */
	//glDisable(GL_DEPTH_TEST); /* don't do Z buffer (screws up overlaps, esp. w/blending) */
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); /* non-premultiplied blending (30ms/frame) */
	//glBlendFunc(GL_ONE,GL_ONE_MINUS_SRC_ALPHA); /* premultiplied blending (still 30ms/frame) */
	//glBlendFunc(GL_ONE,GL_ONE); /* pure additive blending (25ms/frame) */
	//glBlendEquation(GL_MAX); /* "max" blending, which Quinn likes */
	
	return false; 
}

void nice_points_end() {
	glUseProgramObjectARB(0);
}
