#include <iostream.h>
#include <iomanip.h>
#include <gl/glut.h>
#include <math.h>

// General constants

const double PI = 3.14159263;
const double MINDIST = 0.25;		// Hide points closer to ball than this
const double SCALE = 0.5;			// View extends SCALE * MAXPOINTS from origin
const double BALLRAD = 0.1;			// Radius of balls

// Constants for line view.
const double BRIGHT = 0.3;
const double RAD = 10.0;			// Radius of sphere of starting points.
double SEGLEN = 0.1;				// Length of line segment
const int MAXLEN = 500;				// Max # steps on path.
const int STEPS = 5;				// Steps on sphere of starting points.

// Constants for vector view.
double VECLEN = 0.2;				// Length of vectors
const int MAXPOINTS = 10;			// (2 * (MAXPOINTS + 1))^3 will be plotted

// First ball is at origin, second is at (a, b, c).
double a = 1.0;
double b = 0.0;
double c = 0.0;

// Rotation angles for viewing.
double ax = 0.0;
double ay = 0.0;
double az = 0.0;

// Current mode.
enum { LINES, VECTORS } mode = LINES;

// Current window dimensions.
int window_width = 600;
int window_height = 400;


// Utility functions.

// Distance from (x,y,z) to (0,0,0).
double dist (double x, double y, double z)
{
	return sqrt(x*x + y*y + z*z);
}

// Compute (x^2 + y^2 + z^2)^(3/2)
double tht (double x, double y, double z)
{
	double mag = x*x + y*y + z*z;
	return sqrt(mag * mag * mag);
}


// OpenGL display function.
void display () 
{
	glClear(GL_COLOR_BUFFER_BIT);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glTranslatef(0.0, 0.0, -3.0);

	// Apply rotations.
	glRotatef(ax, 1.0, 0.0, 0.0);
	glRotatef(ay, 0.0, 1.0, 0.0);
	glRotatef(az, 0.0, 0.0, 1.0);

	// Draw axes.
	glBegin(GL_LINES);
	glColor3f(1.0, 0.0, 0.0);
	glVertex3f(0.0, 0.0, 0.0);
	glVertex3f(1.0, 0.0, 0.0);

	glColor3f(0.0, 1.0, 0.0);
	glVertex3f(0.0, 0.0, 0.0);
	glVertex3f(0.0, 1.0, 0.0);

	glColor3f(0.0, 0.0, 1.0);
	glVertex3f(0.0, 0.0, 0.0);
	glVertex3f(0.0, 0.0, 1.0);
	glEnd();

	int i, j, k;
	glColor3f(0.8, 0.8, 0.5);
	switch (mode)
	{

	// Vector view

	case VECTORS:
		glBegin(GL_LINES);
		for (i = -MAXPOINTS; i <= MAXPOINTS; i++)
		{
			double x = double(i) * SCALE;
			for (j = -MAXPOINTS; j <= MAXPOINTS; j++)
			{
				double y = double(j) * SCALE;
				for (k = -MAXPOINTS; k <= MAXPOINTS; k++)
				{
					double z = double(k) * SCALE;
					double D1 = tht(x, y, z);
					double D2 = tht(x-a, y-b, z-c);
					if (D1 > MINDIST && D2 > MINDIST)
					{
						// Compute vector field.
						double vx = - x/D1 - (x-a)/D2;
						double vy = - y/D1 - (y-b)/D2;
						double vz = - z/D1 - (z-c)/D2;

						// Draw a short line through this point showing
						// direction and strength of field..
						glVertex3f(x - VECLEN * vx, y - VECLEN * vy, z - VECLEN * vz);
						glVertex3f(x + VECLEN * vx, y + VECLEN * vy, z + VECLEN * vz);
					}
				}
			}
		}
		glEnd();
		break;
	
	// Line view

	case LINES:
		for (i = 0; i <= 2 * STEPS; i++)
		{
			double theta = (PI * i) / STEPS;
			for (j = 0; j <= STEPS; j++)
			{
				double phi = (PI * j) / STEPS;	// 0 <= phi <= PI

				// (x,y,z) is a point on the enclosing sphere.
				double x = RAD * cos(theta) * sin(phi);
				double y = RAD * sin(theta) * sin(phi);
				double z = RAD * cos(phi);
				glBegin(GL_LINE_STRIP);
				int n = 0;

				// Move in the direction of the field at (x,y,z).
				while (true)
				{
					glVertex3f(x, y, z);

					// Stop when limit or ball reached.
					if (n++ > MAXLEN || dist(x,y,z) <= BALLRAD || dist(x-a,y-a,z-a) <= BALLRAD)
						break;
					double D1 = tht(x, y, z);
					double D2 = tht(x - a, y - b, z - c);
					double dx = - x/D1 - (x-a)/D2;
					double dy = - y/D1 - (y-b)/D2;
					double dz = - z/D1 - (z-c)/D2;
					double len = dist(dx, dy, dz) / SEGLEN;

					// Set brightness according to field strength.
					glColor3f(BRIGHT * len, BRIGHT * len, BRIGHT * len);

					// Move to next point.
					x += dx / len;
					y += dy / len;
					z += dz / len;
				}
				glEnd();
			}
		}
		break;
	}

	// Draw balls.
	glColor3f(1.0, 0.0, 0.0);
	glutSolidSphere(BALLRAD, 10,10);
	glPushMatrix();
	glTranslatef(a, b, c);
	glColor3f(0.0, 1.0, 0.0);
	glutSolidSphere(BALLRAD, 10,10);
	glPopMatrix();

	glutSwapBuffers();
	glFlush();
}


// Mouse controls position of second ball.
void mouse_movement (int x, int y) 
{
	ay = 90.0 * (double(x) / double(window_width) - 0.5);
	ax = - 90.0 * (double(y) / double(window_height) - 0.5);
	glutPostRedisplay();
}

// Allow user to reshape/resize window.
void reshape (int w, int h) 
{
	window_width = w;
	window_height = h;
	glViewport(0, 0, window_width, window_height);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(90.0, GLfloat(window_width)/GLfloat(window_height), 1.0, 10.0);
	glutPostRedisplay();
}


void help ()
{
	cout << "Demonstration of a vector field.  The red and green balls generate a" << endl;
	cout << "field (electrostatic or gravitational).  The yellow lines show the" << endl;
	cout << "direction of magnitude of the vector field created by the balls at" << endl;
	cout << "various points in 3D space.  The red ball is at the origin.  The axes" << endl;
	cout << "are shown as red (X), green (Y), and blue (Z) lines." << endl;
	cout << "Horizontal mouse movement rotates view about Y axis." << endl;
	cout << "Vertical mouse movement rotates view about X axis." << endl; 
	cout << "Pressing:" << endl;
	cout << "    x, y, or z  moves the green ball along the corresponding axis." << endl;
	cout << "    X, Y, or Z  moves the green ball in the opposite direction." << endl;
	cout << "    l           selects line view." << endl;
	cout << "    v           selects vector view." << endl;
	cout << "    h           displays theis message." << endl;
	cout << "    ESC         quites." << endl;
}


// Respond to keyboard events.
void keyboard (unsigned char key, int x, int y) 
{
	switch (key) 
	{
	case 'h':
		help();
		break;
	case 'l':
		mode = LINES;
		break;
	case 'v':
		mode = VECTORS;
		break;
	case 'x':
		a += 0.1;
		break;
	case 'y':
		b += 0.1;
		break;
	case 'z':
		c += 0.1;
		break;
	case 'X':
		a -= 0.1;
		break;
	case 'Y':
		b -= 0.1;
		break;
	case 'Z':
		c -= 0.1;
		break;
	case 27:
		exit(0);
	}
	glutPostRedisplay();

}


int main (int argc, char *argv[]) 
{
	glutInit(&argc, argv);
	glutInitDisplayMode (GLUT_RGB | GLUT_DOUBLE);
	glutInitWindowSize(window_width, window_height);
	glutCreateWindow("Potential");
	glutDisplayFunc(display);
	glutMotionFunc(mouse_movement);
	glutReshapeFunc(reshape);
	glutKeyboardFunc(keyboard);
	help();
	glutMainLoop();
	return 0;
}


