/* -*- Mode: C; tab-width: 4 -*- */
/* Sierpinski3D --- 3D sierpinski gasket */

#if 0
static const char sccsid[] = "@(#)sierpinski3D.c	00.01 99/11/04 xlockmore";
#endif

/*-
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in
 * supporting documentation.
 *
 * This file is provided AS IS with no warranties of any kind.  The author
 * shall have no liability with respect to the infringement of copyrights,
 * trade secrets or any patents by this file or any part thereof.  In no
 * event will the author be liable for any lost revenue or profits or
 * other special, indirect and consequential damages.
 *
 * Revision History:
 *
 * 06-Apr-2001  ported from Xscreensaver by Rolf Groppe <rolf@groppe.de>
 *
 * 1999: written by Tim Robinson <the_luggage@bigfoot.com>
 *       a 3-D representation of the Sierpinski gasket fractal.
 *
 * 10-Dec-99  jwz   rewrote to draw a set of tetrahedrons instead of a
 *                  random scattering of points.
 */

#ifdef VMS
#include <types.h>
#endif

#ifdef STANDALONE
# define MODE_sierpinski3d
# define DEFAULTS					"*count:		1       \n"			\
									"*cycles:		9999    \n"			\
									"*delay:		15000   \n"			\
									"*showFPS:      False   \n"			\
									"*wireframe:	False	\n"

#define free_gasket 0
#define gasket_handle_event 0
# include "xlockmore.h"		/* from the xscreensaver distribution */
#else  /* !STANDALONE */
# include "xlock.h"			/* from the xlockmore distribution */
#include "visgl.h"
#include "color.h"
#endif /* !STANDALONE */

#ifdef MODE_sierpinski3d

#undef countof
#define countof(x) (sizeof((x))/sizeof((*x)))

static int max_depth;
static int speed;
static int intensity;
static float intens_factor;

static XrmOptionDescRec opts[] = {
  {(char *) "-maxdepth", (char *) ".sierpinski3d.maxdepth", XrmoptionSepArg, (caddr_t) 0 },
  {(char *) "-speed", (char *) ".sierpinski3d.speed",    XrmoptionSepArg, (caddr_t) 0 },
  {(char *) "-intensity",(char *) ".sierpinski3d.intensity", XrmoptionSepArg, (caddr_t) 0}
};

static argtype vars[] = {
  {(void *) &max_depth, (char *) "maxdepth", (char *) "MaxDepth",
     (char *) "5", t_Int},
  {(void *) &speed, (char *) "speed", (char *) "Speed",
     (char *) "150", t_Int},
  {(void *) &intensity, (char *) "intensity", (char *) "Intensity",
     (char *) "2185", t_Int}
};

static OptionStruct desc[] = {
  {(char *) "-maxdepth", (char *) "maximum depth"},
  {(char *) "-speed", (char *) "speed"},
  {(char *) "-intensity", (char *) "intensity"}
};

ENTRYPOINT ModeSpecOpt gasket_opts =
{countof(opts), opts, countof(vars), vars, desc};

#ifdef USE_MODULES
ModStruct sierpinski3d_description =
{"sierpinski3d", "init_gasket", "draw_gasket", "release_gasket",
 "draw_gasket", "init_gasket", (char *) NULL, &gasket_opts,
 15000, 1, 2, 1, 64, 1.0, "",
 "Shows GL's Sierpinski gasket", 0, NULL};

#endif

#define FLOATRAND(a) (((double)LRAND() / (double)MAXRAND) * a)

typedef struct{
  GLfloat x;
  GLfloat y;
  GLfloat z;
} GL_VECTOR;

typedef struct {
#ifdef WIN32
    HGLRC glx_context;
#else
    GLXContext *glx_context;
#endif
  Window      window;
  GLfloat rotx, roty, rotz;	   /* current object rotation */
  GLfloat dx, dy, dz;		   /* current rotational velocity */
  GLfloat ddx, ddy, ddz;	   /* current rotational acceleration */
  GLfloat d_max;			   /* max velocity */

  GLfloat     angle;
  GLuint      gasket1;

  int current_depth;

  int ncolors;
  XColor *colors;
  int ccolor;

} gasketstruct;

static gasketstruct *gasket = (gasketstruct *) NULL;

#include <GL/glu.h>

/* static GLuint limit; */


/* Computing normal vectors (thanks to Nat Friedman <ndf@mit.edu>)
 */

typedef struct vector {
  GLfloat x, y, z;
} vector;

typedef struct plane {
  vector p1, p2, p3;
} plane;

static void
vector_set(vector *v, GLfloat x, GLfloat y, GLfloat z)
{
  v->x = x;
  v->y = y;
  v->z = z;
}

static void
vector_cross(vector v1, vector v2, vector *v3)
{
  v3->x = (v1.y * v2.z) - (v1.z * v2.y);
  v3->y = (v1.z * v2.x) - (v1.x * v2.z);
  v3->z = (v1.x * v2.y) - (v1.y * v2.x);
}

static void
vector_subtract(vector v1, vector v2, vector *res)
{
  res->x = v1.x - v2.x;
  res->y = v1.y - v2.y;
  res->z = v1.z - v2.z;
}

static void
plane_normal(plane p, vector *n)
{
  vector v1, v2;
  vector_subtract(p.p1, p.p2, &v1);
  vector_subtract(p.p1, p.p3, &v2);
  vector_cross(v2, v1, n);
}

static void
do_normal(GLfloat x1, GLfloat y1, GLfloat z1,
	  GLfloat x2, GLfloat y2, GLfloat z2,
	  GLfloat x3, GLfloat y3, GLfloat z3)
{
  plane plane;
  vector n;
  vector_set(&plane.p1, x1, y1, z1);
  vector_set(&plane.p2, x2, y2, z2);
  vector_set(&plane.p3, x3, y3, z3);
  plane_normal(plane, &n);
  n.x = -n.x; n.y = -n.y; n.z = -n.z;

  glNormal3f(n.x, n.y, n.z);

#ifdef DEBUG
  /* Draw a line in the direction of this face's normal. */
  {
    GLfloat ax = n.x > 0 ? n.x : -n.x;
    GLfloat ay = n.y > 0 ? n.y : -n.y;
    GLfloat az = n.z > 0 ? n.z : -n.z;
    GLfloat mx = (x1 + x2 + x3) / 3;
    GLfloat my = (y1 + y2 + y3) / 3;
    GLfloat mz = (z1 + z2 + z3) / 3;
    GLfloat xx, yy, zz;

    GLfloat max = ax > ay ? ax : ay;
    if (az > max) max = az;
    max *= 2;
    xx = n.x / max;
    yy = n.y / max;
    zz = n.z / max;

    glBegin(GL_LINE_LOOP);
    glVertex3f(mx, my, mz);
    glVertex3f(mx+xx, my+yy, mz+zz);
    glEnd();
  }
#endif /* DEBUG */
}



static void
triangle (GLfloat x1, GLfloat y1, GLfloat z1,
          GLfloat x2, GLfloat y2, GLfloat z2,
          GLfloat x3, GLfloat y3, GLfloat z3,
          Bool wireframe_p)
{
  if (wireframe_p)
    glBegin (GL_LINE_LOOP);
  else
    {
      do_normal (x1, y1, z1,  x2, y2, z2,  x3, y3, z3);
      glBegin (GL_TRIANGLES);
    }
  glVertex3f (x1, y1, z1);
  glVertex3f (x2, y2, z2);
  glVertex3f (x3, y3, z3);
  glEnd();
}

static void
four_tetras (GL_VECTOR *outer, Bool wireframe_p, int countdown)
{
  if (countdown <= 0)
    {
      triangle (outer[0].x, outer[0].y, outer[0].z,
                outer[1].x, outer[1].y, outer[1].z,
                outer[2].x, outer[2].y, outer[2].z,
                wireframe_p);
      triangle (outer[0].x, outer[0].y, outer[0].z,
                outer[3].x, outer[3].y, outer[3].z,
                outer[1].x, outer[1].y, outer[1].z,
                wireframe_p);
      triangle (outer[0].x, outer[0].y, outer[0].z,
                outer[2].x, outer[2].y, outer[2].z,
                outer[3].x, outer[3].y, outer[3].z,
                wireframe_p);
      triangle (outer[1].x, outer[1].y, outer[1].z,
                outer[3].x, outer[3].y, outer[3].z,
                outer[2].x, outer[2].y, outer[2].z,
                wireframe_p);
    }
  else
    {
#     define M01 0
#     define M02 1
#     define M03 2
#     define M12 3
#     define M13 4
#     define M23 5
      GL_VECTOR inner[M23+1];
      GL_VECTOR corner[4];

      inner[M01].x = (outer[0].x + outer[1].x) / 2.0;
      inner[M01].y = (outer[0].y + outer[1].y) / 2.0;
      inner[M01].z = (outer[0].z + outer[1].z) / 2.0;

      inner[M02].x = (outer[0].x + outer[2].x) / 2.0;
      inner[M02].y = (outer[0].y + outer[2].y) / 2.0;
      inner[M02].z = (outer[0].z + outer[2].z) / 2.0;

      inner[M03].x = (outer[0].x + outer[3].x) / 2.0;
      inner[M03].y = (outer[0].y + outer[3].y) / 2.0;
      inner[M03].z = (outer[0].z + outer[3].z) / 2.0;

      inner[M12].x = (outer[1].x + outer[2].x) / 2.0;
      inner[M12].y = (outer[1].y + outer[2].y) / 2.0;
      inner[M12].z = (outer[1].z + outer[2].z) / 2.0;

      inner[M13].x = (outer[1].x + outer[3].x) / 2.0;
      inner[M13].y = (outer[1].y + outer[3].y) / 2.0;
      inner[M13].z = (outer[1].z + outer[3].z) / 2.0;

      inner[M23].x = (outer[2].x + outer[3].x) / 2.0;
      inner[M23].y = (outer[2].y + outer[3].y) / 2.0;
      inner[M23].z = (outer[2].z + outer[3].z) / 2.0;

      countdown--;

      corner[0] = outer[0];
      corner[1] = inner[M01];
      corner[2] = inner[M02];
      corner[3] = inner[M03];
      four_tetras (corner, wireframe_p, countdown);

      corner[0] = inner[M01];
      corner[1] = outer[1];
      corner[2] = inner[M12];
      corner[3] = inner[M13];
      four_tetras (corner, wireframe_p, countdown);

      corner[0] = inner[M02];
      corner[1] = inner[M12];
      corner[2] = outer[2];
      corner[3] = inner[M23];
      four_tetras (corner, wireframe_p, countdown);

      corner[0] = inner[M03];
      corner[1] = inner[M13];
      corner[2] = inner[M23];
      corner[3] = outer[3];
      four_tetras (corner, wireframe_p, countdown);
    }
}


static void
compile_gasket(ModeInfo *mi)
{
  Bool wireframe_p = MI_IS_WIREFRAME(mi);
  gasketstruct *gp = &gasket[MI_SCREEN(mi)];

  GL_VECTOR   vertex[5];

  /* define vertices */
  vertex[0].x =  0.5;
  vertex[0].y = -(1.0/3.0)*sqrt((2.0/3.0));
  vertex[0].z = -sqrt(3.0)/6.0;

  vertex[1].x = -0.5;
  vertex[1].y = -(1.0/3.0)*sqrt((2.0/3.0));
  vertex[1].z = -sqrt(3.0)/6.0;

  vertex[2].x = 0.0;
  vertex[2].y = (2.0/3.0)*sqrt((2.0/3.0));
  vertex[2].z = -sqrt(3.0)/6.0;

  vertex[3].x = 0.0;
  vertex[3].y = 0.0;
  vertex[3].z = sqrt(3.0)/3.0;

  vertex[4].x = 0.0;
  vertex[4].y = 0.0;
  vertex[4].z = 0.0;

  four_tetras (vertex, wireframe_p,
               (gp->current_depth < 0
                ? -gp->current_depth : gp->current_depth));
}

static void
draw(ModeInfo *mi)
{
  Bool wireframe_p = MI_IS_WIREFRAME(mi);
  gasketstruct *gp = &gasket[MI_SCREEN(mi)];
  static int tick = 0;

  static GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
  static float white[]  = {1.0, 1.0, 1.0, 1.0};
  static float color[]  = {0.0, 0.0, 0.0, 1.0};

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  if (!wireframe_p)
    {
      glColor4fv (white);

      glLightfv(GL_LIGHT0, GL_POSITION,  pos);

      color[0] = gp->colors[gp->ccolor].red  * intens_factor;
      color[1] = gp->colors[gp->ccolor].green * intens_factor;
      color[2] = gp->colors[gp->ccolor].blue * intens_factor;
      gp->ccolor++;
      if (gp->ccolor >= gp->ncolors) gp->ccolor = 0;

      glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);

      glShadeModel(GL_SMOOTH);

      glEnable(GL_LIGHTING);
      glEnable(GL_LIGHT0);
    }

  glEnable(GL_DEPTH_TEST);
  glEnable(GL_NORMALIZE);
  glEnable(GL_CULL_FACE);

  glPushMatrix();

  {
    static int frame = 0;
    GLfloat x, y, z;

#   define SINOID(SCALE,SIZE) \
      ((((1 + sin((frame * (SCALE)) / 2 * M_PI)) / 2.0) * (SIZE)) - (SIZE)/2)
    x = SINOID(0.0071, 8.0);
    y = SINOID(0.0053, 6.0);
    z = SINOID(0.0037, 15.0);
    frame++;
    glTranslatef(x, y, z);

    x = gp->rotx;
    y = gp->roty;
    z = gp->rotz;
    if (x < 0) x = 1 - (x + 1);
    if (y < 0) y = 1 - (y + 1);
    if (z < 0) z = 1 - (z + 1);
    glRotatef(x * 360, 1.0, 0.0, 0.0);
    glRotatef(y * 360, 0.0, 1.0, 0.0);
    glRotatef(z * 360, 0.0, 0.0, 1.0);
  }

  glScalef( 8.0, 8.0, 8.0 );
  glCallList(gp->gasket1);

  glPopMatrix();


  if (tick++ >= speed)
    {

      tick = 0;
      if (gp->current_depth >= max_depth)
        gp->current_depth = -max_depth;
      gp->current_depth++;

      glDeleteLists (gp->gasket1, 1);
      glNewList (gp->gasket1, GL_COMPILE);
      compile_gasket (mi);
      glEndList();

    }
}

static void
pinit(ModeInfo *mi)
{
  gasketstruct *gp = &gasket[MI_SCREEN(mi)];

  /* draw the gasket */
  gp->gasket1 = glGenLists(1);
  gp->current_depth = 1;       /* start out at level 1, not 0 */
  glNewList(gp->gasket1, GL_COMPILE);
    compile_gasket(mi);
  glEndList();
}



/* lifted from lament.c */
#define RAND(n) ((long) ((LRAND() & 0x7fffffff) % ((long) (n))))
#ifndef RANDSIGN
#define RANDSIGN() ((LRAND() & 1) ? 1 : -1)
#endif

static void
rotate(GLfloat *pos, GLfloat *v, GLfloat *dv, GLfloat max_v, Bool verbose)
{
  double ppos = *pos;

  /* tick position */
  if (ppos < 0)
    ppos = -(ppos + *v);
  else
    ppos += *v;

  if (ppos > 1.0)
    ppos -= 1.0;
  else if (ppos < 0)
    ppos += 1.0;

  if ((ppos < 0.0) || (ppos > 1.0)) {
    if (verbose) {
      (void) fprintf(stderr, "Weirdness in rotate()\n");
      (void) fprintf(stderr, "ppos = %g\n", ppos);
    }
    return;
  }
  *pos = (*pos > 0 ? ppos : -ppos);

  /* accelerate */
  *v += *dv;

  /* clamp velocity */
  if (*v > max_v || *v < -max_v)
    {
      *dv = -*dv;
    }
  /* If it stops, start it going in the other direction. */
  else if (*v < 0)
    {
      if (NRAND(4) != 0)
	{
	  *v = 0;

	  /* keep going in the same direction */
	  if (LRAND() & 1)
	    *dv = 0;
	  else if (*dv < 0)
	    *dv = -*dv;
	}
      else
	{
	  /* reverse gears */
	  *v = -*v;
	  *dv = -*dv;
	  *pos = -*pos;
	}
    }

  /* Alter direction of rotational acceleration randomly. */
  if (!(NRAND(120)))
    *dv = -*dv;

  /* Change acceleration very occasionally. */
  if (!(NRAND(200)))
    {
      if (*dv == 0)
	*dv = 0.00001;
      else if (LRAND() & 1)
	*dv *= 1.2;
      else
	*dv *= 0.8;
    }
}

/* new window size or exposure */
static void
reshape_gasket(ModeInfo *mi, int width, int height)
{
  GLfloat h = (GLfloat) height / (GLfloat) width;

  glViewport(0, 0, (GLint) width, (GLint) height);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();

  gluPerspective( 30.0, 1/h, 1.0, 100.0 );
  gluLookAt( 0.0, 0.0, 15.0,
             0.0, 0.0, 0.0,
             0.0, 1.0, 0.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glTranslatef(0.0, 0.0, -15.0);

  glClear(GL_COLOR_BUFFER_BIT);
}

ENTRYPOINT void
init_gasket(ModeInfo *mi)
{
  int           screen = MI_SCREEN(mi);
  gasketstruct *gp;

  MI_INIT(mi, gasket);
  gp = &gasket[screen];

  gp->window = MI_WINDOW(mi);

  gp->rotx = FLOATRAND(1.0) * RANDSIGN();
  gp->roty = FLOATRAND(1.0) * RANDSIGN();
  gp->rotz = FLOATRAND(1.0) * RANDSIGN();

  /* bell curve from 0-1.5 degrees, avg 0.75 */
  gp->dx = (FLOATRAND(1) + FLOATRAND(1) + FLOATRAND(1)) / (360*2);
  gp->dy = (FLOATRAND(1) + FLOATRAND(1) + FLOATRAND(1)) / (360*2);
  gp->dz = (FLOATRAND(1) + FLOATRAND(1) + FLOATRAND(1)) / (360*2);

  gp->d_max = gp->dx * 2;

  gp->ddx = 0.00006 + FLOATRAND(0.00003);
  gp->ddy = 0.00006 + FLOATRAND(0.00003);
  gp->ddz = 0.00006 + FLOATRAND(0.00003);

  gp->ddx = 0.00001;
  gp->ddy = 0.00001;
  gp->ddz = 0.00001;

  intens_factor = intensity / 65536000.0;
  gp->ncolors = 255;
  gp->colors = (XColor *) calloc(gp->ncolors, sizeof(XColor));
  make_smooth_colormap (
 #ifdef STANDALONE
    MI_SCREENPTR(mi), MI_VISUAL(mi), MI_WIN_COLORMAP(mi),
    gp->colors, &gp->ncolors,
    False, (Bool *) NULL, False
#else
    mi, None,
    gp->colors, &gp->ncolors,
    False, (Bool *) NULL
#endif
  );

  if ((gp->glx_context = init_GL(mi)) != NULL)
  {
    reshape_gasket(mi, MI_WIDTH(mi), MI_HEIGHT(mi));
    pinit(mi);
  }
  else
  {
    MI_CLEARWINDOW(mi);
  }
}

ENTRYPOINT void
draw_gasket(ModeInfo * mi)
{
  gasketstruct *gp = &gasket[MI_SCREEN(mi)];
  Display      *display = MI_DISPLAY(mi);
  Window        window = MI_WINDOW(mi);
  int           angle_incr = 1;

  if (!gp->glx_context) return;

  glDrawBuffer(GL_BACK);

  if (max_depth > 10)
    max_depth = 10;

  MI_IS_DRAWN(mi) = True;
#ifdef WIN32
  wglMakeCurrent(hdc, gp->glx_context);
#else
  glXMakeCurrent(display, window, *(gp->glx_context));
#endif
  draw(mi);

  /* rotate */
  gp->angle = (int) (gp->angle + angle_incr) % 360;

  rotate(&gp->rotx, &gp->dx, &gp->ddx, gp->d_max, MI_IS_VERBOSE(mi));
  rotate(&gp->roty, &gp->dy, &gp->ddy, gp->d_max, MI_IS_VERBOSE(mi));
  rotate(&gp->rotz, &gp->dz, &gp->ddz, gp->d_max, MI_IS_VERBOSE(mi));

  /* if (mi->fps_p) do_fps (mi); */

  if (MI_IS_FPS(mi)) do_fps (mi);
  glFinish();
  glXSwapBuffers(display, window);
}

ENTRYPOINT void
release_gasket(ModeInfo * mi)
{
  if (gasket != NULL)
  {
    int         screen;

    for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
    {
      gasketstruct *gp = &gasket[screen];

      if (gp->colors != NULL) {
	XFree((caddr_t) gp->colors);
	gp->colors = (XColor *) NULL;
      }
      if (gp->glx_context)
      {
	/* Display lists MUST be freed while their glXContext is current. */
#ifdef WIN32
	wglMakeCurrent(hdc, gp->glx_context);
#else
        glXMakeCurrent(MI_DISPLAY(mi), gp->window, *(gp->glx_context));
#endif

        if (glIsList(gp->gasket1)) glDeleteLists(gp->gasket1, 1);
      }
    }
    free(gasket);
    gasket = (gasketstruct *) NULL;
  }
  FreeAllGL(mi);
}

XSCREENSAVER_MODULE ("Sierpinski3d", gasket)

#endif /* MODE_sierpinski3d */
