// Curve class, used to represent all flexible objects
//

#include <stdlib.h>
#include <math.h>
#include "globals.h"
#include "curve.h"
#include "opengl.h"
#include "matrix.h"
#include "vector.h"

#define LC_CURVE_SAVE_VERSION 1 // LeoCAD 0.73
#define LC_CURVE_POINT_SAVE_VERSION 1 // LeoCAD 0.73

GLuint CurvePoint::m_nArrowList = 0;
GLuint CurvePoint::m_nSphereList = 0;

static LC_OBJECT_KEY_INFO curve_point_key_info[LC_CURVE_POINT_KEY_COUNT] =
{
  { "Control Point Position", 3, LC_CURVE_POINT_KEY_POSITION },
  { "Control Point Direction 1", 3, LC_CURVE_POINT_KEY_DIRECTION1 },
  { "Control Point Direction 2", 3, LC_CURVE_POINT_KEY_DIRECTION2 },
  { "Control Point Angle", 1, LC_CURVE_POINT_KEY_ANGLE }
};

// =============================================================================
// CurvePoint class

CurvePoint::CurvePoint (Curve *pParent)
  : Object (LC_OBJECT_CURVE_POINT)
{
  m_pParent = pParent;

  /*
    FIXME
  strcpy (m_strName, pParent->GetName ());
  m_strName[LC_OBJECT_NAME_LEN-8] = '\0';
  strcat (m_strName, ".Target");
  */

  Initialize ();
}

CurvePoint::CurvePoint (Curve *pParent, const float *pos, const float *dir)
  : Object (LC_OBJECT_CURVE_POINT)
{
  m_pParent = pParent;

  /*
    FIXME
  strcpy (m_strName, pParent->GetName ());
  m_strName[LC_OBJECT_NAME_LEN-8] = '\0';
  strcat (m_strName, ".Target");
  */

  Initialize ();

  float angle[1] = { 0 };

  ChangeKey (1, false, true, pos, LC_CURVE_POINT_KEY_POSITION);
  ChangeKey (1, false, true, dir, LC_CURVE_POINT_KEY_DIRECTION1);
  ChangeKey (1, false, true, dir, LC_CURVE_POINT_KEY_DIRECTION2);
  ChangeKey (1, false, true, angle, LC_CURVE_POINT_KEY_ANGLE);
  ChangeKey (1, true, true, pos, LC_CURVE_POINT_KEY_POSITION);
  ChangeKey (1, true, true, dir, LC_CURVE_POINT_KEY_DIRECTION1);
  ChangeKey (1, true, true, dir, LC_CURVE_POINT_KEY_DIRECTION2);
  ChangeKey (1, true, true, angle, LC_CURVE_POINT_KEY_ANGLE);

  UpdatePosition (1, false);
}

void CurvePoint::Initialize ()
{
  if (m_nSphereList == 0)
  {
    m_nSphereList = glGenLists (1);
    glNewList (m_nSphereList, GL_COMPILE);

    float radius = 0.2f;
    int slices = 6, stacks = 6;
    float rho, drho, theta, dtheta;
    float x, y, z;
    int i, j, imin, imax;
    drho = 3.1415926536f/(float)stacks;
    dtheta = 2.0f*3.1415926536f/(float)slices;

    // draw +Z end as a triangle fan
    glBegin(GL_TRIANGLE_FAN);
    glVertex3f(0.0, 0.0, radius);
    for (j = 0; j <= slices; j++) 
    {
      theta = (j == slices) ? 0.0f : j * dtheta;
      x = (float)(-sin(theta) * sin(drho));
      y = (float)(cos(theta) * sin(drho));
      z = (float)(cos(drho));
      glVertex3f(x*radius, y*radius, z*radius);
    }
    glEnd();

    imin = 1;
    imax = stacks-1;

    for (i = imin; i < imax; i++)
    {
      rho = i * drho;
      glBegin(GL_QUAD_STRIP);
      for (j = 0; j <= slices; j++)
      {
	theta = (j == slices) ? 0.0f : j * dtheta;
	x = (float)(-sin(theta) * sin(rho));
	y = (float)(cos(theta) * sin(rho));
	z = (float)(cos(rho));
	glVertex3f(x*radius, y*radius, z*radius);
	x = (float)(-sin(theta) * sin(rho+drho));
	y = (float)(cos(theta) * sin(rho+drho));
	z = (float)(cos(rho+drho));
	glVertex3f(x*radius, y*radius, z*radius);
      }
      glEnd();
    }

    // draw -Z end as a triangle fan
    glBegin(GL_TRIANGLE_FAN);
    glVertex3f(0.0, 0.0, -radius);
    rho = 3.1415926536f - drho;
    for (j = slices; j >= 0; j--)
    {
      theta = (j==slices) ? 0.0f : j * dtheta;
      x = (float)(-sin(theta) * sin(rho));
      y = (float)(cos(theta) * sin(rho));
      z = (float)(cos(rho));
      glVertex3f(x*radius, y*radius, z*radius);
    }
    glEnd();
    glEndList();
  }

  m_nState = LC_CURVE_POINT_CONTINUOUS;

  float *values[] = { m_fPos, m_fDir1, m_fDir2, &m_fAngle };
  RegisterKeys (values, curve_point_key_info, LC_CURVE_POINT_KEY_COUNT);
}

CurvePoint::~CurvePoint ()
{
}

void CurvePoint::MinIntersectDist (LC_CLICKLINE* pLine)
{
  float dist = (float)BoundingBoxIntersectDist (pLine);

  if (dist < pLine->mindist)
  {
    pLine->mindist = dist;
    pLine->pClosest = this;

    m_nLastHit = 1;
  }

  m_nLastHit = 0;
  // FIXME: check arrows
}

void CurvePoint::UpdatePosition (unsigned short nTime, bool bAnimation)
{
  CalculateKeys (nTime, bAnimation);
}

bool CurvePoint::FileLoad (File& file)
{
  // FIXME
  return true;
}

void CurvePoint::FileSave (File& file) const
{
  // FIXME
}

void CurvePoint::Move (unsigned short nTime, bool bAnimation, bool bAddKey, float dx, float dy, float dz)
{
  /*
  if (m_nState & LC_CURVE_POINT_ARROW1_FOCUS)
  if (m_nState & LC_CURVE_POINT_ARROW2_FOCUS)
  if (m_nState & LC_CURVE_POINT_CONTINUOUS)
    ;
  */
}

void CurvePoint::Select (bool bSelecting, bool bFocus, bool bMultiple)
{
  // FIXME: select arrows, use m_nLastHit

  if (bSelecting == true)
  {
    m_nState |= LC_CURVE_POINT_SELECTED;

    if (bFocus == true)
    {
      m_nState |= LC_CURVE_POINT_FOCUSED;

      m_pParent->DeselectOtherPoints (this, bMultiple);
    }
  }
  else
  {
    if (bFocus == true)
      m_nState &= ~(LC_CURVE_POINT_SELECTED|LC_CURVE_POINT_FOCUSED);
    else
      m_nState &= ~(LC_CURVE_POINT_SELECTED);
  }
}

void CurvePoint::Render (LC_RENDER_INFO* pInfo)
{
  if (m_nState & LC_CURVE_POINT_FOCUSED)
    glColor3ubv (FlatColorArray[LC_COL_FOCUSED]);
  else if (m_nState & LC_CURVE_POINT_SELECTED)
    glColor3ubv (FlatColorArray[LC_COL_SELECTED]);
  else
    glColor3f(0.5f, 0.8f, 0.5f); // FIXME: same as camera color, add to FlatColorArray
      //      glColor3ub (0, 0, 0); // FIXME: inverse of background
      // FIXME: add a new color to the array and change the names from LC_COL to LC_COLOR ?

  glPushMatrix ();
  glTranslatef (m_fPos[0], m_fPos[1], m_fPos[2]);
  glCallList (m_nSphereList);

  // FIXME: create and use arrow display list
  //      if (m_pPoints[i].m_nFlags & LC_CURVE_POINT_FOCUSED)
      {
	glBegin (GL_LINES);
        glVertex3f ( m_fDir1[0]/5,  m_fDir1[1]/5,  m_fDir1[2]/5);
        glVertex3f (-m_fDir2[0]/5, -m_fDir2[1]/5, -m_fDir2[2]/5);
	glEnd ();
      }

  glPopMatrix ();
}

// =============================================================================
// Curve class

Curve::Curve ()
  : Object (LC_OBJECT_CURVE)
{
  Initialize ();
}

Curve::Curve (PieceInfo *pInfo, const float *pos, unsigned char color)
  : Object (LC_OBJECT_CURVE)
{
	/*
  Initialize ();

  // FIXME: set the curve type and length based on the PieceInfo
  m_fLength = 5;

  m_nCurveType = LC_CURVE_TYPE_HOSE;
  m_nColor = color;

  float dir[3] = { 0, 20, 0 }, pos2[3] = { pos[0] + m_fLength, pos[1], pos[2] };

  m_fUp0[0] = 0;
  m_fUp0[1] = 0;
  m_fUp0[2] = 1;

  CurvePoint *pt;
  pt = new CurvePoint (this, pos, dir);
  m_Points.Add (pt);

  dir[1] = 0;
  dir[2] = -5;

  pt = new CurvePoint (this, pos2, dir);
  m_Points.Add (pt);

  pos2[0] += 5;
  dir[2] = 5;

  pt = new CurvePoint (this, pos2, dir);
  m_Points.Add (pt);

  UpdatePosition (1, false);
*/
}

Curve::~Curve ()
{
	/*
  for (int i = 0; i < m_Points.GetSize (); i++)
    delete m_Points[i];
  glDeleteLists (m_nDisplayList, 1);
	*/
}

void Curve::Initialize ()
{
  m_nCurveType = (LC_CURVE_TYPE)0;
  m_nState = 0;
  m_nColor = 0;
  m_nDisplayList = glGenLists (1);
}

bool Curve::FileLoad (File& file)
{
  // FIXME
  return true;
}

void Curve::FileSave (File& file) const
{
  // FIXME
}

void Curve::MinIntersectDist (LC_CLICKLINE* pLine)
{
  // FIXME
}

void Curve::UpdatePosition (unsigned short nTime, bool bAnimation)
{
  for (int i = 0; i < m_Points.GetSize (); i++)
    m_Points[i]->UpdatePosition (nTime, bAnimation);

  glNewList (m_nDisplayList, GL_COMPILE);

  switch (m_nCurveType)
  {
  case LC_CURVE_TYPE_HOSE:
    TesselateHose ();
    break;
  }

  glEndList ();
}

void Curve::Move (unsigned short nTime, bool bAnimation, bool bAddKey, float dx, float dy, float dz)
{
  for (int i = 0; i < m_Points.GetSize (); i++)
    m_Points[i]->Move (nTime, bAnimation, bAddKey, dx, dy, dz);
}

void Curve::Select (bool bSelecting, bool bFocus, bool bMultiple)
{
  if (bSelecting == true)
  {
    if (bFocus == true)
    {
      m_nState |= (LC_CURVE_SELECTED|LC_CURVE_FOCUSED);

      for (int i = 0; i < m_Points.GetSize (); i++)
        m_Points[i]->Select (false, true, bMultiple);
    }
    else
      m_nState |= LC_CURVE_SELECTED;
  }
  else
  {
    if (bFocus == true)
      m_nState &= ~(LC_CURVE_SELECTED|LC_CURVE_FOCUSED);
    else
      m_nState &= ~(LC_CURVE_SELECTED);

    for (int i = 0; i < m_Points.GetSize (); i++)
      m_Points[i]->Select (false, bFocus, bMultiple);
  }
}

void Curve::DeselectOtherPoints (CurvePoint *pSender, bool bFocusOnly)
{
  CurvePoint *pt;

  for (int i = 0; i < m_Points.GetSize (); i++)
  {
    pt = m_Points[i];
    if (pt != pSender)
      pt->Select (false, bFocusOnly, true);
  }
}

/*
void Curve::TesselateHose ()
{
  float x, y, z, t, t2, t3, cx[4], cy[4], cz[4];
  const float *p1, *p2, *r1, *r2, *u1, *u2;

  glEnableClientState (GL_VERTEX_ARRAY);

  for (unsigned int i = 0; i < m_Points.GetSize () - 1; i++)
  {
    p1 = m_Points[i]->GetPosition ();
    p2 = m_Points[i+1]->GetPosition ();
    r1 = m_Points[i]->GetDirection1 ();
    r2 = m_Points[i+1]->GetDirection2 ();
    u1 = m_Points[i]->GetUpVector ();
    u2 = m_Points[i+1]->GetUpVector ();

    cx[0] = 2*p1[0] - 2*p2[0] + r1[0] + r2[0];
    cx[1] = -3*p1[0] + 3*p2[0] - 2*r1[0] - r2[0];
    cx[2] = r1[0];
    cx[3] = p1[0];

    cy[0] = 2*p1[1] - 2*p2[1] + r1[1] + r2[1];
    cy[1] = -3*p1[1] + 3*p2[1] - 2*r1[1] - r2[1];
    cy[2] = r1[1];
    cy[3] = p1[1];

    cz[0] = 2*p1[2] - 2*p2[2] + r1[2] + r2[2];
    cz[1] = -3*p1[2] + 3*p2[2] - 2*r1[2] - r2[2];
    cz[2] = r1[2];
    cz[3] = p1[2];

    int steps1 = 16, steps2 = 6, j, k;
    float* verts = (float*)malloc ((steps1+1) * steps2 * 3 * sizeof (float));
    float a, b, c;
    float ux, uy, uz;

    for (t = 0, j = 0; j <= steps1; j++, t += 1.0f/steps1)
    {
      t2 = t*t;
      t3 = t2*t;

      // position
      x = cx[0]*t3 + cx[1]*t2 + cx[2]*t + cx[3];
      y = cy[0]*t3 + cy[1]*t2 + cy[2]*t + cy[3];
      z = cz[0]*t3 + cz[1]*t2 + cz[2]*t + cz[3];

      // tangent
      a = 3*cx[0]*t2 + 2*cx[1]*t + cx[2];
      b = 3*cy[0]*t2 + 2*cy[1]*t + cy[2];
      c = 3*cz[0]*t2 + 2*cz[1]*t + cz[2];

      // gradient
      ux = 6*cx[0]*t + 2*cx[1];
      uy = 6*cy[0]*t + 2*cy[1];
      uz = 6*cz[0]*t + 2*cz[1];

      Vector side, front (a, b, c);
      Vector up (ux, uy, uz);
      side.Cross (front, up);
      up.Cross (side, front);
      up.Normalize ();
      front.Normalize ();
      side.Normalize ();

      float f[16];
#define M(row,col)  f[col*4+row]
      M(0,0) = side[0]; M(0,1) = up[0]; M(0,2) = front[0]; M(0,3) = x;
      M(1,0) = side[1]; M(1,1) = up[1]; M(1,2) = front[1]; M(1,3) = y;
      M(2,0) = side[2]; M(2,1) = up[2]; M(2,2) = front[2]; M(2,3) = z;
      M(3,0) = 0.0;     M(3,1) = 0.0;   M(3,2) = 0.0;      M(3,3) = 1.0;
#undef M

      float v[3];
      Matrix m;
      m.FromFloat (f);

      for (int k = 0; k < steps2; k++)
      {
	float *o = &verts[(j*steps2+k)*3];
	v[0] = cos (2.0 * M_PI * k / steps2) * 0.15f;
	v[1] = sin (2.0 * M_PI * k / steps2) * 0.15f;
	v[2] = 0;
	m.TransformPoint (o, v);
        glVertex3fv (o);
      }
    }

    GLuint *index = (GLuint*)malloc (2 * (steps2+1) * sizeof (GLuint));
    glVertexPointer (3, GL_FLOAT, 0, verts);
    for (j = 0; j < steps1; j++)
    {
      for (k = 0; k < steps2; k++)
      {
	index[k*2] = j*steps2+k;
	index[k*2+1] = (j+1)*steps2+k;
      }
      index[k*2] = index[0];
      index[k*2+1] = index[1];
      glDrawElements (GL_TRIANGLE_STRIP, 2*(steps2+1), GL_UNSIGNED_INT, index);
    }

    free (index);
    free (verts);
  }
}
*/

void Curve::TesselateHose ()
{
  float x, y, z, t, t2, t3, cx[4], cy[4], cz[4];
  const float *p1, *p2, *r1, *r2;

  float u[3] = { m_fUp0[0], m_fUp0[1], m_fUp0[2] };
  int steps1 = 16, steps2 = 6, j, k;
  float* verts = (float*)malloc ((steps1+1) * steps2 * 3 * sizeof (float));
  float a, b, c;

  glEnableClientState (GL_VERTEX_ARRAY);
  glVertexPointer (3, GL_FLOAT, 0, verts);

  for (int i = 0; i < m_Points.GetSize () - 1; i++)
  {
    float a1, a2, angle_step; // axial rotation

    p1 = m_Points[i]->GetPosition ();
    p2 = m_Points[i+1]->GetPosition ();
    r1 = m_Points[i]->GetDirection1 ();
    r2 = m_Points[i+1]->GetDirection2 ();
    a1 = m_Points[i]->GetAngle ();
    a2 = m_Points[i+1]->GetAngle ();

    angle_step = (a2 - a1) / steps1;
    if (fabs (angle_step) < 0.01f)
      angle_step = 0;

    cx[0] = 2*p1[0] - 2*p2[0] + r1[0] + r2[0];
    cx[1] = -3*p1[0] + 3*p2[0] - 2*r1[0] - r2[0];
    cx[2] = r1[0];
    cx[3] = p1[0];

    cy[0] = 2*p1[1] - 2*p2[1] + r1[1] + r2[1];
    cy[1] = -3*p1[1] + 3*p2[1] - 2*r1[1] - r2[1];
    cy[2] = r1[1];
    cy[3] = p1[1];

    cz[0] = 2*p1[2] - 2*p2[2] + r1[2] + r2[2];
    cz[1] = -3*p1[2] + 3*p2[2] - 2*r1[2] - r2[2];
    cz[2] = r1[2];
    cz[3] = p1[2];

    for (t = 0, j = 0; j <= steps1; j++, t += 1.0f/steps1)
    {
      t2 = t*t;
      t3 = t2*t;

      // position
      x = cx[0]*t3 + cx[1]*t2 + cx[2]*t + cx[3];
      y = cy[0]*t3 + cy[1]*t2 + cy[2]*t + cy[3];
      z = cz[0]*t3 + cz[1]*t2 + cz[2]*t + cz[3];

      // tangent
      a = 3*cx[0]*t2 + 2*cx[1]*t + cx[2];
      b = 3*cy[0]*t2 + 2*cy[1]*t + cy[2];
      c = 3*cz[0]*t2 + 2*cz[1]*t + cz[2];

      Vector side, front (a, b, c);
      Vector up (u);
      side.Cross (front, up);
      up.Cross (side, front);
      up.Normalize ();
      front.Normalize ();
      side.Normalize ();

      if (angle_step != 0)
      {
        Matrix rot;
        rot.FromAxisAngle (front, angle_step);
        rot.TransformPoint (u, up);
      }
      else
        up.ToFloat (u);

      float f[16];
#define M(row,col)  f[col*4+row]
      M(0,0) = side[0]; M(0,1) = up[0]; M(0,2) = front[0]; M(0,3) = x;
      M(1,0) = side[1]; M(1,1) = up[1]; M(1,2) = front[1]; M(1,3) = y;
      M(2,0) = side[2]; M(2,1) = up[2]; M(2,2) = front[2]; M(2,3) = z;
      M(3,0) = 0.0;     M(3,1) = 0.0;   M(3,2) = 0.0;      M(3,3) = 1.0;
#undef M

      float v[3];
      Matrix m;
      m.FromFloat (f);

      for (int k = 0; k < steps2; k++)
      {
	float *o = &verts[(j*steps2+k)*3];
	v[0] = (float)(cos (2.0 * M_PI * k / steps2) * 0.15);
	v[1] = (float)(sin (2.0 * M_PI * k / steps2) * 0.15);
	v[2] = 0;
	m.TransformPoint (o, v);
      }
    }

    GLuint *index = (GLuint*)malloc (2 * (steps2+1) * sizeof (GLuint));
    for (j = 0; j < steps1; j++)
    {
      for (k = 0; k < steps2; k++)
      {
	index[k*2] = j*steps2+k;
	index[k*2+1] = (j+1)*steps2+k;
      }
      index[k*2] = index[0];
      index[k*2+1] = index[1];
      glDrawElements (GL_TRIANGLE_STRIP, 2*(steps2+1), GL_UNSIGNED_INT, index);
    }

    free (index);
  }
  free (verts);
}

void Curve::Render (LC_RENDER_INFO* pInfo)
{
	if ((m_nState & LC_CURVE_HIDDEN) != 0)
		return;

	// FIXME: create a "set color" function in LC_RENDER_INFO
	if (pInfo->lighting)
		glColor4ubv (ColorArray[m_nColor]);
	else
		glColor3ubv (FlatColorArray[m_nColor]);

	if (m_nColor > 13 && m_nColor < 22) // FIXME: use a #define
	{
		if (!pInfo->transparent)
		{
			pInfo->transparent = true;
			glEnable (GL_BLEND);
			glDepthMask (GL_FALSE);
			glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		}
	}
	else
	{
		if (pInfo->transparent)
		{
			pInfo->transparent = false;
			glDepthMask (GL_TRUE);
			glDisable (GL_BLEND);
		}
	}

	//  glPolygonMode (GL_FRONT_AND_BACK, GL_LINE);
	glCallList (m_nDisplayList);

	//  if (m_nState & LC_CURVE_SELECTED)
	{
		// turn off transparency to draw the control points
		if (pInfo->transparent)
		{
			pInfo->transparent = false;
			if (pInfo->transparent)
			{
				glDepthMask (GL_TRUE);
				glDisable (GL_BLEND);
			}
		}

		for (int i = 0; i < m_Points.GetSize (); i++)
			m_Points[i]->Render (pInfo);
	}

	/*
	if (IsSelected ())
	{

	for (int i = 0; i < m_nNumPoints; i++)
	{
	if (m_pPoints[i].m_nFlags & LC_CURVE_POINT_FOCUSED)
	glColor3ubv (FlatColorArray[LC_COL_FOCUSED]);
	else if (m_pPoints[i].m_nFlags & LC_CURVE_POINT_SELECTED)
	glColor3ubv (FlatColorArray[LC_COL_SELECTED]);
	else
	glColor3f(0.5f, 0.8f, 0.5f); // FIXME: same as camera color, add to FlatColorArray
	//      glColor3ub (0, 0, 0); // FIXME: inverse of background
	// FIXME: add a new color to the array and change the names from LC_COL to LC_COLOR ?

	glPushMatrix ();
	//    RenderSegment (m_pPoints[i].m_fPos, m_pPoints[i+1].m_fPos, m_pSegments[i].m_fR1, m_pSegments[i].m_fR2);
	glTranslatef (m_pPoints[i].m_fPos[0], m_pPoints[i].m_fPos[1], m_pPoints[i].m_fPos[2]);
	glCallList (m_nSphereList);

	if (m_pPoints[i].m_nFlags & LC_CURVE_POINT_FOCUSED)
	{
	glBegin (GL_LINES);
	if (i < m_nNumSegments)
	{
	glVertex3fv (m_pSegments[i].m_fR1);
	glVertex3f (0, 0, 0);
	}
	else if (i > 0)
	{
	glVertex3f (-m_pSegments[i-1].m_fR2[0], -m_pSegments[i-1].m_fR2[1], -m_pSegments[i-1].m_fR2[2]);
	glVertex3f (0, 0, 0);
	}
	glEnd ();
	}

	glPopMatrix ();
	}
	}
	*/


	/*
	if (m_nFlags & LC_CURVE_LOOP)
	{
	i = m_nNumPoints - 1;
	RenderSegment (m_pPoints[0].pos, m_pPoints[i].pos, m_pPoints[0].normal, m_pPoints[i].normal);
	}
	*/
}






#if 0

#define LC_CURVE_POINT_RADIUS 0.2f

// =============================================================================
// Static functions

#include <math.h>
/*
// TODO: optimize
static void RenderSegment (float p1[3], float p2[3], float r1[3], float r2[3])
{
  float x, y, z, t, t2, t3, cx[4], cy[4], cz[4];
  int i;

  cx[0] = 2*p1[0] - 2*p2[0] + r1[0] + r2[0];
  cx[1] = -3*p1[0] + 3*p2[0] - 2*r1[0] - r2[0];
  cx[2] = r1[0];
  cx[3] = p1[0];

  cy[0] = 2*p1[1] - 2*p2[1] + r1[1] + r2[1];
  cy[1] = -3*p1[1] + 3*p2[1] - 2*r1[1] - r2[1];
  cy[2] = r1[1];
  cy[3] = p1[1];

  cz[0] = 2*p1[2] - 2*p2[2] + r1[2] + r2[2];
  cz[1] = -3*p1[2] + 3*p2[2] - 2*r1[2] - r2[2];
  cz[2] = r1[2];
  cz[3] = p1[2];

  glColor3f (1,0,0);

  glBegin (GL_LINE_STRIP);
  glVertex3f (cx[3], cy[3], cz[3]);

  for (t = 0, i = 0; i < 10; i++)
  {
    t += 0.1f;
    t2 = t*t;
    t3 = t2*t;

    x = cx[0]*t3 + cx[1]*t2 + cx[2]*t + cx[3];
    y = cy[0]*t3 + cy[1]*t2 + cy[2]*t + cy[3];
    z = cz[0]*t3 + cz[1]*t2 + cz[2]*t + cz[3];
    glVertex3f (x, y, z);
  }

  glEnd ();


  glColor3f (0,0,0);

  float a, b, c;
  for (t = 0, i = 0; i <= 10; i++, t += 0.1f)
  {
    t2 = t*t;
    t3 = t2*t;

    x = cx[0]*t3 + cx[1]*t2 + cx[2]*t + cx[3];
    y = cy[0]*t3 + cy[1]*t2 + cy[2]*t + cy[3];
    z = cz[0]*t3 + cz[1]*t2 + cz[2]*t + cz[3];

    a = 3*cx[0]*t2 + 2*cx[1]*t + cx[2];
    b = 3*cy[0]*t2 + 2*cy[1]*t + cy[2];
    c = 3*cz[0]*t2 + 2*cz[1]*t + cz[2];

    Vector v1 (0, 0, 1);
    Vector v2 (a, b, c);
    Vector v3;
    v3.Cross (v1, v2);
    a = v1.Angle (v2);
    float v[3];
    v3.ToFloat (v);
    Matrix m;
    m.FromAxisAngle (v, a);

    glBegin (GL_LINE_LOOP);
    for (int j = 0; j < 16; j++)
    {
      float o[3];
      v[0] = cos (2.0 * M_PI * j / 16) * 0.15f;
      v[1] = sin (2.0 * M_PI * j / 16) * 0.15f;
      v[2] = 0;
      m.TransformPoint (o, v);
      glVertex3f (o[0]+x, o[1]+y, o[2]+z);
    }

    glEnd ();
  }

}
*/

// =============================================================================
// Curve class

void Curve::MinIntersectDist (LC_CLICKLINE* pLine)
{
  // FIXME: Curve segments and tangent arrows

  for (int i = 0; i < m_nNumPoints; i++)
  {
    double dist = pLine->PointDistance (m_pPoints[i].m_fPos);

    if ((dist < pLine->mindist) && (dist < LC_CURVE_POINT_RADIUS))
    {
      pLine->mindist = dist;
      pLine->pClosest = this;
      pLine->pParam = &m_pPoints[i];
    }
  }
}

void Curve::SetSelection (bool bSelect, void *pParam)
{
  Object::SetSelection (bSelect, pParam);

  if (pParam != NULL)
  {
    for (int i = 0; i < m_nNumPoints; i++)
      if (&m_pPoints[i] == pParam)
      {
	if (bSelect)
	  m_pPoints[i].m_nState |= LC_CURVE_POINT_SELECTED;
	else
	  m_pPoints[i].m_nState &= ~(LC_CURVE_POINT_SELECTED | LC_CURVE_POINT_FOCUSED);
      }
  }
  else
  {
    for (int i = 0; i < m_nNumPoints; i++)
      if (bSelect)
	m_pPoints[i].m_nState |= LC_CURVE_POINT_SELECTED;
      else
	m_pPoints[i].m_nState &= ~(LC_CURVE_POINT_SELECTED | LC_CURVE_POINT_FOCUSED);
  }
}

void Curve::SetFocus (bool bFocus, void *pParam)
{
  Object::SetFocus (bFocus, pParam);

  if (pParam != NULL)
  {
    for (int i = 0; i < m_nNumPoints; i++)
      if (&m_pPoints[i] == pParam)
      {
	if (bFocus)
	  m_pPoints[i].m_nState |= (LC_CURVE_POINT_SELECTED | LC_CURVE_POINT_FOCUSED);
	else
	  m_pPoints[i].m_nState &= ~LC_CURVE_POINT_FOCUSED;
      }
      else
      {
	m_pPoints[i].m_nState &= ~LC_CURVE_POINT_FOCUSED;
      }
  }
}


#endif
