// Note, a red-blue 3-D image is termed an "ANAGLYPH"

#define LEFT			0
#define RIGHT			1
#define NUMEYES			2

#define DEGREES_TO_RADIANS	0.017453293
#define ONE_EIGHTY  (180 * DEGREES_TO_RADIANS)	// in radians
#define THREE_SIXTY (360 * DEGREES_TO_RADIANS)	// in radians

struct TriPoint {
	double	x;
	double 	y;
	double	z;
};

struct DuoPoint {
	double	x;
	double 	y;
};

	CPtrList	theEye[NUMEYES];

	TriPoint	aPrime,a1,a2,a3,temp;
	double		aPrimeMag, tempMag;
	double		offX, offY, offZ;
	TriPoint	from[NUMEYES];
	TriPoint	center;
	TriPoint	at,up;

	COLORREF	anaglyphColor[NUMEYES];
#define REDCOLOR		RGB(255,0,0)
#define CYANCOLOR		RGB(0,255,255)

/***************************************************************************/
CStereoStarView::CStereoStarView() : offscreenBitMap(NULL), offscreenDCInit(FALSE), theMainFrame(NULL)
{
	...
	from[LEFT].x = -0.25;
	from[LEFT].y = 0.0;
	from[LEFT].z = 6;
	from[RIGHT].x = 0.25;
	from[RIGHT].y = 0.0;
	from[RIGHT].z = 6;
	at.x = 0.0;
	at.y = 0.0;
	at.z = 0.0;
	up.x = 0.0;
	up.y = 1.0;
	up.z = 0.0;

	center.x = (from[LEFT].x + from[RIGHT].x) / 2 ;
	center.y = (from[LEFT].y + from[RIGHT].y) / 2 ;
	center.z = (from[LEFT].z + from[RIGHT].z) / 2 ;

	delta_x = from[LEFT].x - from[RIGHT].x;
	delta_y = from[LEFT].y - from[RIGHT].y;
	delta_z = from[LEFT].z - from[RIGHT].z;
	separation = sqrt((delta_x * delta_x) + (delta_y * delta_y) + (delta_z * delta_z));

	m_thetaValue = 270.0 * DEGREES_TO_RADIANS;
	m_rhoValue = 6.0;
	m_phiValue = 0.0;
	...
	origin.x = 0.0;
	origin.y = 0.0;
	origin.z = 0.0;
	...
	anaglyphColor[LEFT] = CYANCOLOR;
	anaglyphColor[RIGHT] = REDCOLOR;
	...
}

// =============================
// BEGINNING OF "CALC EYE" SECTION
// CalcEye() is called when a new star list is loaded from disk
// and when user changes the co-ordinates of his point of view.
// Its primary function is to load the 	CPtrList theEye[NUMEYES] variable
// with a series of DuoPoints. 
// Each star's x,y,z co-ordinate is transformed into a flat screen
// perspective view co-ordinates (stored in a DuoPoint) used to plot the 
// star's image on the screen.
// Each star actually has two DuoPoints calculated, one for the left eye's view
// and one for the right eye. 
// The two DuoPoints are inserted into theEye thusly:
//   theEye[LEFT].AddTail(theLeftPoint);
//   theEye[RIGHT].AddTail(theRightPoint);
// =============================

/***************************************************************************/
// Transform all the screen objects, first for left eye, then for right
/***************************************************************************/
void CStereoStarView::CalcEye()
{
	EmptyEyeLists();

	Calc3D(LEFT);
	Calc3D(RIGHT);
}
// end CStereoStarView::CalcEye

/***************************************************************************/
void CStereoStarView::EmptyEyeLists() 
{
	DuoPoint*	thePoint;
	int			eyeCount = theEye[LEFT].GetCount();

	for (int a = 0; a < eyeCount; a++) {
		thePoint = (DuoPoint*)theEye[LEFT].GetAt(theEye[LEFT].FindIndex(a));
		delete thePoint;
		thePoint = (DuoPoint*)theEye[RIGHT].GetAt(theEye[RIGHT].FindIndex(a));
		delete thePoint;
	}
	theEye[LEFT].RemoveAll();
	theEye[RIGHT].RemoveAll();
}

/***************************************************************************/
// Transform all the screen objects from xyz into persepective screen x,y
/***************************************************************************/
void CStereoStarView::Calc3D(short eyeID)
{
	short		a, b;
	DuoPoint*	thePoint;
	TriPoint	theLoc;
	double		ex,ey;

	Init3DVars(eyeID);

	// Transform all the star locations
	// Fill theEye array
	int numStars = theDoc->GetNumStars();
	for (a = 0; a < numStars; a++)
	{
		theLoc = theDoc->GetLoc(a, coordType);
		TransformPoint(&theLoc, &ex, &ey);
		thePoint = new(DuoPoint);
		thePoint->x = ex;
		thePoint->y = ey;
		theEye[eyeID].AddTail(thePoint);
	}

	// Transform the origin (used for radial lines)

	theLoc.x = origin.x;
	theLoc.y = origin.y;
	theLoc.z = origin.z;

	TransformPoint(&theLoc, &ex, &ey);
	planeCenter[eyeID].x = ex;
	planeCenter[eyeID].y = ey;


	// Transform the cartesian axises
	for (a = 0; a < NUMAXIS; a++)
	{
		for (int b = 0; b < NUMSIGN; b++)
		{
			theLoc = axisCoords[a][b][coordType];
			TransformPoint(&theLoc, &ex, &ey);
			axis[a][b][eyeID].x = ex;
			axis[a][b][eyeID].y = ey;
		}
	}

	// transform the planes
	for (a = 0; a < NUMPLANES; a++)
	{
		for (b = 0; b < NUMCORNERS; b++)
		{
			theLoc = theDoc->plane[a][b][coordType];
			if (theLoc.x == 0.0)	
				theLoc.x += origin.x;
			if (theLoc.y == 0.0)	
				theLoc.y += origin.y;
			if (theLoc.z == 0.0)	
				theLoc.z += origin.z;
			TransformPoint(&theLoc, &ex, &ey);
			plane[a][b][eyeID].x = ex;
			plane[a][b][eyeID].y = ey;
		}
	}

}
// end CStereoStarView::Calc3D

/***************************************************************************/
// Initialize the constants for the 3-D calculations for the current eye
/***************************************************************************/
void CStereoStarView::Init3DVars(short eyeID)
{
	aPrime = Subtract(&at, &from[eyeID]);
	aPrimeMag = Mag(&aPrime);

	a3 = Divide(&aPrime, aPrimeMag);

	temp = Cross(&aPrime, &up);
	tempMag = Mag(&temp);

	a1 = Divide(&temp, tempMag);

	temp = Cross(&aPrime, &up);
	temp = Cross(&temp, &aPrime);
	tempMag = Mag(&temp);

	a2 = Divide(&temp, tempMag);

	offX = -a1.x * from[eyeID].x - a1.y * from[eyeID].y - a1.z * from[eyeID].z;
	offY = -a2.x * from[eyeID].x - a2.y * from[eyeID].y - a2.z * from[eyeID].z;
	offZ = -a3.x * from[eyeID].x - a3.y * from[eyeID].y - a3.z * from[eyeID].z;
}

/***************************************************************************/
// Calculate the distance between pointA and pointB
/***************************************************************************/
double CStereoStarView::TrueDistance(TriPoint *pointA, TriPoint *pointB)
{
	double	delta_x, delta_y, delta_z;

	delta_x = pointA->x - pointB->x;
	delta_y = pointA->y - pointB->y;
	delta_z = pointA->z - pointB->z;
	return sqrt((delta_x * delta_x) + (delta_y * delta_y) + (delta_z * delta_z));
}
// end CStereoStarView::TrueDistance

/***************************************************************************/
// Vector Magnitude
/***************************************************************************/
double CStereoStarView::Mag(TriPoint *v)
{
	return sqrt((v->x * v->x) + (v->y * v->y) + (v->z * v->z));
#ifdef OLD
	return fabs((v->x * v->x) + (v->y * v->y) + (v->z * v->z));
#endif
}
// end CStereoStarView::Mag

/***************************************************************************/
// Vector Subtraction
/***************************************************************************/
TriPoint CStereoStarView::Subtract(TriPoint *v1, TriPoint *v2)
{
	TriPoint	c;

	c.x = v1->x - v2->x;
	c.y = v1->y - v2->y;
	c.z = v1->z - v2->z;
	return c;
}
// end CStereoStarView::Subtract

/***************************************************************************/
// Cross Product
/***************************************************************************/
TriPoint CStereoStarView::Cross(TriPoint *v1, TriPoint *v2)
{
	TriPoint	c;

	c.x = v1->y * v2->z - v2->y * v1->z;
	c.y = v1->z * v2->x - v2->z * v1->x;
	c.z = v1->x * v2->y - v2->x * v1->y;
	return c;
}
// end CStereoStarView::Cross

/***************************************************************************/
// Vector division
/***************************************************************************/
TriPoint CStereoStarView::Divide(TriPoint *v, double num)
{
	TriPoint	result;

	result.x = v->x;
	result.y = v->y;
	result.z = v->z;

	if (num != 0)
	{
		result.x = v->x / num;
		result.y = v->y / num;
		result.z = v->z / num;
	}
	return result;
}
// end CStereoStarView::Divide

/***************************************************************************/
// convert degrees to radians
/***************************************************************************/
double CStereoStarView::MakeRadians(double degrees)
{
	return degrees * 0.017453293;
}
// end CStereoStarView::MakeRadians

/***************************************************************************/
// Input the 3-dimensional point in theLoc
// Output the screen x,y positions in perspective in ex,ey
/***************************************************************************/
void CStereoStarView::TransformPoint(TriPoint* theLoc, double* ex, double* ey)
{
	TriPoint	eyeC;
	TriPoint	originShiftLoc;

	originShiftLoc.x = theLoc->x - origin.x;
	originShiftLoc.y = theLoc->y - origin.y;
	originShiftLoc.z = theLoc->z - origin.z;

	eyeC.x =
		(originShiftLoc.x * a1.x) +
		(originShiftLoc.y * a1.y) +
		(originShiftLoc.z * a1.z) +
		offX;
	eyeC.y =
		(originShiftLoc.x * a2.x) +
		(originShiftLoc.y * a2.y) +
		(originShiftLoc.z * a2.z) +
		offY;
	eyeC.z =
		(originShiftLoc.x * a3.x) +
		(originShiftLoc.y * a3.y) +
		(originShiftLoc.z * a3.z) +
		offZ;
	*ex = eyeC.x * dval;
	if (eyeC.z != 0)	*ex = *ex / eyeC.z;
	*ey = eyeC.y * dval;
	if (eyeC.z != 0)	*ey = *ey / eyeC.z;
}

// =============================
// END OF "CALC EYE" SECTION
// =============================

// =============================
// BEGINNING OF "DRAW VIEW" SECTION
// Whenever the user loads a new star list or rotates the view,
// a routine is called to update the offscreen bitmap.
// The CView's draw method merely querys the scroll bars and
// bit blits the appropriate section of the offscreen bitmap
// to the screen.
// In the process of updating the offscreen bitmap,
// the stars are drawn on the offscreen bitmap by 
// a routine called PlotStars, in part below:
// =============================

/***************************************************************************/
void CStereoStarView::PlotStar(CPaintDC* dc)
{
	double		plotX,plotY;
	CRgn		theClip;
	DuoPoint*	eye;
	int			oldMode;
	COLORREF	starColor, nameColor, coordColor;
	TriPoint	theLoc;
	int			numStars = theDoc->GetNumStars();

	oldMode = dc->SetROP2(R2_MASKPEN);

	for (int a = 0; a < NUMEYES; a++)
	{
		for (int starNum = 0; starNum < numStars; starNum++)
		{
			eye = (DuoPoint*)theEye[a].GetAt(theEye[a].FindIndex(starNum));
			// scale it by the user set scaling factor (magnify or scrink image)
			plotX = ScaleX(eye->x, a);
			plotY = ScaleY(eye->y);

			starColor = anaglyphColor[a];
			nameColor = anaglyphColor[a];
			coordColor = anaglyphColor[a];

			DrawStar(dc, starNum, plotX, plotY, starColor);
			DrawName(dc, starNum, plotX, plotY, nameColor, coordColor);
		}
	}

	dc->SetROP2(oldMode);
}

/***************************************************************************/
double CStereoStarView::ScaleX(double locX, int eyeID)
{
	return (locX * scaleFactor[scaleMode]) + scx[eyeID][viewMode];
}

/***************************************************************************/
double CStereoStarView::ScaleY(double locY)
{
	return SY - ((locY * scaleFactor[scaleMode]) + SCY);
}

/***************************************************************************/
void CStereoStarView::ChangeScreenDimensions(short newWidth, short newHeight)
{
	SX = newWidth;
	SY = newHeight;
	SCX = (SX/2);
	SCY = (SY/2);
	scx[LEFT][ANAGLYPH] = SCX;
	scx[RIGHT][ANAGLYPH] = SCX;
}

/***************************************************************************/
void CStereoStarView::DrawStar(CPaintDC* dc, short starNum, double plotX, double plotY, COLORREF theColor)
{
	CPen*		oldPen;
	int			plotXI = (int)plotX;
	int			plotYI = (int)plotY;

	CPen	thePen(PS_SOLID,0, theColor);
	oldPen = (CPen*)dc->SelectObject(&thePen);
	dc->MoveTo(plotXI, plotYI-5);
	dc->LineTo(plotXI, plotYI+6);
	dc->MoveTo(plotXI-5, plotYI);
	dc->LineTo(plotXI+6, plotYI);
	dc->Arc(plotXI-3,plotYI+4, plotXI+4, plotYI-3, plotXI+4, plotYI+4, plotXI+4, plotYI+4);
	dc->SelectObject(oldPen);
}
// end CStereoStarView::DrawStar

// =============================
// END OF "DRAW VIEW" SECTION
// =============================

// =============================
// BEGINNING OF "USER ROTATE VIEW" SECTION
// The user can do a variety of things to the view.
// The Spin Theta control makes the view spin horizonatally around
// the co-ordinate origin (actually it makes the eye viewpoints orbit
// around the origin)
// The Spin Phi control makes the view rotate vertically.
// The Spin Rho control moves the view closer or farther away from
// the eye viewpoint. Note that close up one gets a "fisheye" view
// The Spin Scale control magnifies and shrinks the view, which has
// a slighty different effect from the Spin Rho.
// The Spin View Angle control directly makes a fishey or narrow view,
// squeezing the angle selected into the width of the viewscreen.
// The Spin Eye Sep control alters the spacing of the virtual eyes
// that are viewing the image. The farther apart, the more pronouced
// the 3-D effect, up to a point where the user's eyes cannot cope with 
// the distortion.
// 
// After any of these operation, CalcEye is called to update theEye vector
// and UpdateOffscreenBitmap is invoked to refresh the offscreen bitmap.
//
// The controls are implemented as MFC style spinbuttons
// 
// =============================

/***************************************************************************/
void CStereoStarView::OnSpinTheta(CCmdUI* pCmdUI) 
{
	if (m_thetaValue != theMainFrame->m_Spin_Theta_Ctrl.GetPos() * DEGREES_TO_RADIANS)
	{
		m_thetaValue = theMainFrame->m_Spin_Theta_Ctrl.GetPos() * DEGREES_TO_RADIANS;
		PolarToRectangular();
		CalcEye();
		UpdateOffscreenBitmap();
		InvalidateRect(theClientRect, TRUE);
	}
}

/***************************************************************************/
void CStereoStarView::OnSpinPhi(CCmdUI* pCmdUI) 
{
	if (m_phiValue != theMainFrame->m_Spin_Phi_Ctrl.GetPos() * DEGREES_TO_RADIANS)
	{
		m_phiValue = theMainFrame->m_Spin_Phi_Ctrl.GetPos() * DEGREES_TO_RADIANS;
		PolarToRectangular();
		CalcEye();
		UpdateOffscreenBitmap();
		InvalidateRect(theClientRect, TRUE);
	}
}

/***************************************************************************/
void CStereoStarView::OnSpinRho(CCmdUI* pCmdUI) 
{
	if (m_rhoValue != theMainFrame->m_Spin_Rho_Ctrl.GetPos())
	{
		m_rhoValue = theMainFrame->m_Spin_Rho_Ctrl.GetPos();
		PolarToRectangular();
		CalcEye();
		UpdateOffscreenBitmap();
		InvalidateRect(theClientRect, TRUE);
	}
}

/***************************************************************************/
void CStereoStarView::OnSpinScale(CCmdUI* pCmdUI) 
{
	if (scaleFactor[scaleMode] != theMainFrame->m_Spin_Scale_Ctrl.GetPos())
	{
		scaleFactor[scaleMode] = theMainFrame->m_Spin_Scale_Ctrl.GetPos();
		PolarToRectangular();
		CalcEye();
		UpdateOffscreenBitmap();
		InvalidateRect(theClientRect, TRUE);
	}
}


/***************************************************************************/
void CStereoStarView::OnSpinViewAngle(CCmdUI* pCmdUI) 
{
	double	newSeparation = theMainFrame->m_Spin_EyeSep_Ctrl.GetPos() / 10.0;

	if (separation != newSeparation)
	{
		separation = newSeparation;
		PolarToRectangular();
		CalcEye();
		UpdateOffscreenBitmap();
		InvalidateRect(theClientRect, TRUE);
	}
}

/***************************************************************************/
void CStereoStarView::OnSpinEyeSep(CCmdUI* pCmdUI) 
{
	if (viewAngle != theMainFrame->m_Spin_ViewAngle_Ctrl.GetPos())
	{
		viewAngle = theMainFrame->m_Spin_ViewAngle_Ctrl.GetPos();
		theta = MakeRadians(viewAngle);
		dval = cos(theta / 2) / sin(theta / 2);
		CalcEye();
		UpdateOffscreenBitmap();
		InvalidateRect(theClientRect, TRUE);
	}
}

/***************************************************************************/
void CStereoStarView::PolarToRectangular()
{
	short	quadrant;
	CString	theText;
	double	hyp;

	// convert Theta,Phi,Rho into X,Y,Z and store in dialog edit controls
	center.x = m_rhoValue * sin(m_phiValue) * cos(m_thetaValue);
	center.y = m_rhoValue * sin(m_phiValue) * sin(m_thetaValue);
	center.z = m_rhoValue * cos(m_phiValue);

	if (center.x >= 0)
	{
		if (center.y >= 0)	quadrant = 1;	// QII  | QI
		else				quadrant = 4;	// -,+  | +,+
	}										// -----+-----
	else									// QIII | QIV 
	{										// -,-  | +,- 
		if (center.y >= 0)	quadrant = 2;
		else				quadrant = 3;
	}

	// convert the "between-the-eyes" point and the separation into left & right eye co-ords
	switch (quadrant)
	{
	case 1:
		from[LEFT].x  = center.x + (sin(m_thetaValue) * separation);
		from[LEFT].y  = center.y - (cos(m_thetaValue) * separation);
		from[RIGHT].x = center.x - (sin(m_thetaValue) * separation);
		from[RIGHT].y = center.y + (cos(m_thetaValue) * separation);
		break;
	case 2:
		from[LEFT].x  = center.x + (sin(ONE_EIGHTY - m_thetaValue) * separation);
		from[LEFT].y  = center.y + (cos(ONE_EIGHTY - m_thetaValue) * separation);
		from[RIGHT].x = center.x - (sin(ONE_EIGHTY - m_thetaValue) * separation);
		from[RIGHT].y = center.y - (cos(ONE_EIGHTY - m_thetaValue) * separation);
		break;
	case 3:
		from[LEFT].x  = center.x - (sin(m_thetaValue - ONE_EIGHTY) * separation);
		from[LEFT].y  = center.y + (cos(m_thetaValue - ONE_EIGHTY) * separation);
		from[RIGHT].x = center.x + (sin(m_thetaValue - ONE_EIGHTY) * separation);
		from[RIGHT].y = center.y - (cos(m_thetaValue - ONE_EIGHTY) * separation);
		break;
	case 4:
		from[LEFT].x  = center.x - (sin(THREE_SIXTY - m_thetaValue) * separation);
		from[LEFT].y  = center.y - (cos(THREE_SIXTY - m_thetaValue) * separation);
		from[RIGHT].x = center.x + (sin(THREE_SIXTY - m_thetaValue) * separation);
		from[RIGHT].y = center.y + (cos(THREE_SIXTY - m_thetaValue) * separation);
		break;
	}
	from[LEFT].z = center.z;
	from[RIGHT].z = center.z;

	// Look through the centerpoint at a point about 100x away
	at.x = ( -center.x ) * 100;
	at.y = ( -center.y ) * 100;
	at.z = ( -center.z ) * 100;

	// Do something arcane to approximate the "up" value
	up.z = sin(m_phiValue) * 10;
	hyp = cos(m_phiValue) * 10;
	up.x = -(cos(m_thetaValue) * hyp);
	up.y = -(sin(m_thetaValue) * hyp);
}

