/* -*- Mode: C; tab-width: 4 -*- */
/* noof --- SGI Diatoms */

#if 0
static const char sccsid[] = "@(#)noof.c	5.04 2002/02/10 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:
 * 10-Feb-2001: From the GLUT demo distribution by Mark Kilgard
 *              <mjk@nvidia.com> ported by Dave Riesz <driesz@cpl.net>
 */

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

#ifdef STANDALONE
# define MODE_noof
# define DEFAULTS			"*delay:		1000   \n"

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

#ifdef MODE_noof

#define MINSIZE         32      /* minimal viewport size */

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

ENTRYPOINT ModeSpecOpt noof_opts =
{0, (XrmOptionDescRec *) NULL, 0, (argtype *) NULL, (OptionStruct *) NULL};

#ifdef USE_MODULES
ModStruct   noof_description =
{"noof", "init_noof", "draw_noof", "release_noof",
 "draw_noof", "init_noof", (char *) NULL, &noof_opts,
 1000, 1, 1, 1, 64, 1.0, "",
 "Shows SGI Diatoms", 0, NULL};

#endif

#define N_SHAPES 7

typedef struct {
#ifdef WIN32
	HGLRC glx_context;
#else
	GLXContext *glx_context;
#endif
	Window      window;
	float ht, wd;
	float pos[N_SHAPES * 3];
	float dir[N_SHAPES * 3];
	float acc[N_SHAPES * 3];
	float col[N_SHAPES * 3];
	float hsv[N_SHAPES * 3];
	float hpr[N_SHAPES * 3];
	float ang[N_SHAPES];
	float spn[N_SHAPES];
	float sca[N_SHAPES];
	float geep[N_SHAPES];
	float peep[N_SHAPES];
	float speedsq[N_SHAPES];
	int blad[N_SHAPES];
	int tko;
} noofstruct;

static noofstruct *noof = (noofstruct *) NULL;

/* For portability... */
#undef fcos
#undef fsin
#define fcos  cos
#define fsin  sin

/* function declarations */
static void oneFrame(noofstruct *np);
static void motionUpdate(noofstruct *np, int t);
static void colorUpdate(noofstruct *np, int i);
static void gravity(noofstruct *np, float fx);
static void drawleaf(noofstruct *np, int l);
static void initshapes(noofstruct *np, int i);

/* new window size or exposure */
static void
reshape_noof(ModeInfo *mi, int width, int height)
{
	noofstruct *np;

	if(noof == NULL) return;
	np = &noof[MI_SCREEN(mi)];

	if(!np->glx_context) return;

#ifdef WIN32
	wglMakeCurrent(hdc, np->glx_context);
#else
	glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(np->glx_context));
#endif

/* from myinit */
	glClearColor(0.0, 0.0, 0.0, 1.0);
	glEnable(GL_LINE_SMOOTH);
	glShadeModel(GL_FLAT);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	glViewport(0, 0, width, height);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	if (width <= height) {
		np->wd = 1.0;
		np->ht = (GLfloat) height / (GLfloat) width;
		glOrtho(0.0, 1.0,
			0.0, 1.0 * (GLfloat) height / (GLfloat) width,
			-16.0, 4.0);
	} else {
		np->wd = (GLfloat) width / (GLfloat) height;
		np->ht = 1.0;
		glOrtho(0.0, 1.0 * (GLfloat) width / (GLfloat) height,
			0.0, 1.0,
			-16.0, 4.0);
	}
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	glClear(GL_COLOR_BUFFER_BIT);
	glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
}

static void
pinit(ModeInfo * mi)
{
	int i;
	noofstruct *np;

	if(noof == NULL) return;
	np = &noof[MI_SCREEN(mi)];

	for (i = 0; i < N_SHAPES; i++)
		initshapes(np, i);
	np->tko = 0;
}

ENTRYPOINT void
init_noof(ModeInfo * mi)
{
	noofstruct *np;

	MI_INIT(mi, noof);
	np = &noof[MI_SCREEN(mi)];

	if ((np->glx_context = init_GL(mi)) != NULL)
	{
		reshape_noof(mi, MI_WIDTH(mi), MI_HEIGHT(mi));
		glDrawBuffer(GL_FRONT);
		pinit(mi);
	}
	else
	{
		MI_CLEARWINDOW(mi);
	}


}

ENTRYPOINT void
draw_noof(ModeInfo * mi)
{
	noofstruct *np;

	if(noof == NULL) return;
	np = &noof[MI_SCREEN(mi)];

	MI_IS_DRAWN(mi) = True;
	if(!np->glx_context) return;
#ifdef WIN32
	wglMakeCurrent(hdc, np->glx_context);
#else
	glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(np->glx_context));
#endif
	oneFrame(np);
}

ENTRYPOINT void
release_noof(ModeInfo * mi)
{
	if (noof != NULL) {
		int screen;

		for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++) {
			noofstruct *np = &noof[screen];

			if (np->glx_context) {
#ifdef WIN32
				np->glx_context = NULL;
#else
				np->glx_context = (GLXContext *) NULL;
#endif
			}
		}
		free(noof);
		noof = (noofstruct *) NULL;
	}
	FreeAllGL(mi);
}

/******************************************************************************/

/* --- shape parameters def'n --- */

static void
initshapes(noofstruct *np, int i)
{
  int k;
  float f;

  /* random init of pos, dir, color */
  for (k = i * 3; k <= i * 3 + 2; k++) {
    f = ((float) LRAND() / (float) MAXRAND);
    np->pos[k] = f;
    f = ((float) LRAND() / (float) MAXRAND);
    f = (f - 0.5) * 0.05;
    np->dir[k] = f;
    f = ((float) LRAND() / (float) MAXRAND);
    f = (f - 0.5) * 0.0002;
    np->acc[k] = f;
    f = ((float) LRAND() / (float) MAXRAND);
    np->col[k] = f;
  }

  np->speedsq[i] = np->dir[i * 3] * np->dir[i * 3] + np->dir[i * 3 + 1] * np->dir[i * 3 + 1];
  f = ((float) LRAND() / (float) MAXRAND);
  np->blad[i] = 2 + (int) (f * 17.0);
  f = ((float) LRAND() / (float) MAXRAND);
  np->ang[i] = f;
  f = ((float) LRAND() / (float) MAXRAND);
  np->spn[i] = (f - 0.5) * 40.0 / (10 + np->blad[i]);
  f = ((float) LRAND() / (float) MAXRAND);
  np->sca[i] = (f * 0.1 + 0.08);
  np->dir[i * 3] *= np->sca[i];
  np->dir[i * 3 + 1] *= np->sca[i];

  f = ((float) LRAND() / (float) MAXRAND);
  np->hsv[i * 3] = f * 360.0;

  f = ((float) LRAND() / (float) MAXRAND);
  np->hsv[i * 3 + 1] = f * 0.6 + 0.4;

  f = ((float) LRAND() / (float) MAXRAND);
  np->hsv[i * 3 + 2] = f * 0.7 + 0.3;

  f = ((float) LRAND() / (float) MAXRAND);
  np->hpr[i * 3] = f * 0.005 * 360.0;
  f = ((float) LRAND() / (float) MAXRAND);
  np->hpr[i * 3 + 1] = f * 0.03;
  f = ((float) LRAND() / (float) MAXRAND);
  np->hpr[i * 3 + 2] = f * 0.02;

  np->geep[i] = 0;
  f = ((float) LRAND() / (float) MAXRAND);
  np->peep[i] = 0.01 + f * 0.2;
}

static float bladeratio[] =
{
  /* nblades = 2..7 */
  0.0, 0.0, 3.00000, 1.73205, 1.00000, 0.72654, 0.57735, 0.48157,
  /* 8..13 */
  0.41421, 0.36397, 0.19076, 0.29363, 0.26795, 0.24648,
  /* 14..19 */
  0.22824, 0.21256, 0.19891, 0.18693, 0.17633, 0.16687,
};

static void
drawleaf(noofstruct *np, int l)
{

  int b, blades;
  float x, y;
  float wobble;

  blades = np->blad[l];

  y = 0.10 * fsin(np->geep[l] * M_PI / 180.0) + 0.099 * fsin(np->geep[l] * 5.12 * M_PI / 180.0);
  if (y < 0)
    y = -y;
  x = 0.15 * fcos(np->geep[l] * M_PI / 180.0) + 0.149 * fcos(np->geep[l] * 5.12 * M_PI / 180.0);
  if (x < 0.0)
    x = 0.0 - x;
  if (y < 0.001 && x > 0.000002 && ((np->tko & 0x1) == 0)) {
    initshapes(np, l);      /* let it become reborn as something
                           else */
    np->tko++;
    return;
  } {
    float w1 = fsin(np->geep[l] * 15.3 * M_PI / 180.0);
    wobble = 3.0 + 2.00 * fsin(np->geep[l] * 0.4 * M_PI / 180.0) + 3.94261 * w1;
  }

  /**
  if(blades == 2) if (y > 3.000*x) y = x*3.000;
  if(blades == 3) if (y > 1.732*x) y = x*1.732;
  if(blades == 4) if (y >       x) y = x;
  if(blades == 5) if (y > 0.726*x) y = x*0.726;
  if(blades == 6) if (y > 0.577*x) y = x*0.577;
  if(blades == 7) if (y > 0.481*x) y = x*0.481;
  if(blades == 8) if (y > 0.414*x) y = x*0.414;
  */
  if (y > x * bladeratio[blades])
    y = x * bladeratio[blades];

  for (b = 0; b < blades; b++) {
    glPushMatrix();
    glTranslatef(np->pos[l * 3], np->pos[l * 3 + 1], np->pos[l * 3 + 2]);
    glRotatef(np->ang[l] + b * (360.0 / blades), 0.0, 0.0, 1.0);
    glScalef(wobble * np->sca[l], wobble * np->sca[l], wobble * np->sca[l]);
    /**
    if(np->tko & 0x40000) glColor3f(np->col[l*3], np->col[l*3+1], np->col[l*3+2]);
    else
    */
    glColor4ub(0, 0, 0, 0x60);

    /* constrain geep cooridinates here XXX */
    glEnable(GL_BLEND);

    glBegin(GL_TRIANGLE_STRIP);
    glVertex2f(x * np->sca[l], 0.0);
    glVertex2f(x, y);
    glVertex2f(x, -y);  /* C */
    glVertex2f(0.3, 0.0);  /* D */
    glEnd();

    /**
    if(np->tko++ & 0x40000) glColor3f(0,0,0);
    else
    */
    glColor3f(np->col[l * 3], np->col[l * 3 + 1], np->col[l * 3 + 2]);
    glBegin(GL_LINE_LOOP);
    glVertex2f(x * np->sca[l], 0.0);
    glVertex2f(x, y);
    glVertex2f(0.3, 0.0);  /* D */
    glVertex2f(x, -y);  /* C */
    glEnd();
    glDisable(GL_BLEND);

    glPopMatrix();
  }
}

static void
motionUpdate(noofstruct *np, int t)
{
  if (np->pos[t * 3] < -np->sca[t] * np->wd && np->dir[t * 3] < 0.0) {
    np->dir[t * 3] = -np->dir[t * 3];
  /**
  np->acc[t*3+1] += 0.8*np->acc[t*3];
  np->acc[t*3] = -0.8*np->acc[t*3];
  */
  } else if (np->pos[t * 3] > (1 + np->sca[t]) * np->wd && np->dir[t * 3] > 0.0) {
    np->dir[t * 3] = -np->dir[t * 3];
    /**
    np->acc[t*3+1] += 0.8*np->acc[t*3];
    np->acc[t*3] = -0.8*np->acc[t*3];
    */
  } else if (np->pos[t * 3 + 1] < -np->sca[t] * np->ht && np->dir[t * 3 + 1] < 0.0) {
    np->dir[t * 3 + 1] = -np->dir[t * 3 + 1];
    /**
    np->acc[t*3] += 0.8*np->acc[t*3+1];
    np->acc[t*3+1] = -0.8*np->acc[t*3+1];
    */
  } else if (np->pos[t * 3 + 1] > (1 + np->sca[t]) * np->ht && np->dir[t * 3 + 1] > 0.0) {
    np->dir[t * 3 + 1] = -np->dir[t * 3 + 1];
    /**
    np->acc[t*3] += 0.8*np->acc[t*3+1];
    np->acc[t*3+1] = -0.8*np->acc[t*3+1];
    */
  }

  np->pos[t * 3] += np->dir[t * 3];
  np->pos[t * 3 + 1] += np->dir[t * 3 + 1];
  /**
  np->dir[t*3]   += np->acc[t*3];
  np->dir[t*3+1] += np->acc[t*3+1];
  */
  np->ang[t] += np->spn[t];
  np->geep[t] += np->peep[t];
  if (np->geep[t] > 360 * 5.0)
    np->geep[t] -= 360 * 5.0;
  if (np->ang[t] < 0.0) {
    np->ang[t] += 360.0;
  }
  if (np->ang[t] > 360.0) {
    np->ang[t] -= 360.0;
  }
}

static void
colorUpdate(noofstruct *np, int i)
{
  if (np->hsv[i * 3 + 1] <= 0.5 && np->hpr[i * 3 + 1] < 0.0)
    np->hpr[i * 3 + 1] = -np->hpr[i * 3 + 1];  /* adjust s */
  if (np->hsv[i * 3 + 1] >= 1.0 && np->hpr[i * 3 + 1] > 0.0)
    np->hpr[i * 3 + 1] = -np->hpr[i * 3 + 1];  /* adjust s */
  if (np->hsv[i * 3 + 2] <= 0.4 && np->hpr[i * 3 + 2] < 0.0)
    np->hpr[i * 3 + 2] = -np->hpr[i * 3 + 2];  /* adjust s */
  if (np->hsv[i * 3 + 2] >= 1.0 && np->hpr[i * 3 + 2] > 0.0)
    np->hpr[i * 3 + 2] = -np->hpr[i * 3 + 2];  /* adjust s */

  np->hsv[i * 3] += np->hpr[i * 3];
  np->hsv[i * 3 + 1] += np->hpr[i * 3 + 1];
  np->hsv[i * 3 + 2] += np->hpr[i * 3 + 2];

  /* --- hsv -> rgb --- */
#define H(hhh) hhh[i*3  ]
#define S(hhh) hhh[i*3+1]
#define V(hhh) hhh[i*3+2]

#define R(hhh) hhh[i*3  ]
#define G(hhh) hhh[i*3+1]
#define B(hhh) hhh[i*3+2]

  if (V(np->hsv) < 0.0)
    V(np->hsv) = 0.0;
  if (V(np->hsv) > 1.0)
    V(np->hsv) = 1.0;
  if (S(np->hsv) <= 0.0) {
    R(np->col) = V(np->hsv);
    G(np->col) = V(np->hsv);
    B(np->col) = V(np->hsv);
  } else {
    float f, h, p, q, t, v;
    int hi;

    while (H(np->hsv) < 0.0)
      H(np->hsv) += 360.0;
    while (H(np->hsv) >= 360.0)
      H(np->hsv) -= 360.0;

    if (S(np->hsv) < 0.0)
      S(np->hsv) = 0.0;
    if (S(np->hsv) > 1.0)
      S(np->hsv) = 1.0;

    h = H(np->hsv) / 60.0;
    hi = (int) (h);
    f = h - hi;
    v = V(np->hsv);
    p = V(np->hsv) * (1 - S(np->hsv));
    q = V(np->hsv) * (1 - S(np->hsv) * f);
    t = V(np->hsv) * (1 - S(np->hsv) * (1 - f));

    if (hi <= 0) {
      R(np->col) = v;
      G(np->col) = t;
      B(np->col) = p;
    } else if (hi == 1) {
      R(np->col) = q;
      G(np->col) = v;
      B(np->col) = p;
    } else if (hi == 2) {
      R(np->col) = p;
      G(np->col) = v;
      B(np->col) = t;
    } else if (hi == 3) {
      R(np->col) = p;
      G(np->col) = q;
      B(np->col) = v;
    } else if (hi == 4) {
      R(np->col) = t;
      G(np->col) = p;
      B(np->col) = v;
    } else {
      R(np->col) = v;
      G(np->col) = p;
      B(np->col) = q;
    }
  }
}

static void
gravity(noofstruct *np, float fx)
{
  int a, b;

  for (a = 0; a < N_SHAPES; a++) {
    for (b = 0; b < a; b++) {
      float t, d2;

      t = np->pos[b * 3] - np->pos[a * 3];
      d2 = t * t;
      t = np->pos[b * 3 + 1] - np->pos[a * 3 + 1];
      d2 += t * t;
      if (d2 < 0.000001)
        d2 = 0.00001;
      if (d2 < 0.1) {

        float v0, v1, z;
        v0 = np->pos[b * 3] - np->pos[a * 3];
        v1 = np->pos[b * 3 + 1] - np->pos[a * 3 + 1];

        z = 0.00000001 * fx / (d2);

        np->dir[a * 3] += v0 * z * np->sca[b];
        np->dir[b * 3] += -v0 * z * np->sca[a];
        np->dir[a * 3 + 1] += v1 * z * np->sca[b];
        np->dir[b * 3 + 1] += -v1 * z * np->sca[a];

      }
    }
    /** apply brakes
    if(np->dir[a*3]*np->dir[a*3] + np->dir[a*3+1]*np->dir[a*3+1]
      > 0.0001) {
      np->dir[a*3] *= 0.9;
      np->dir[a*3+1] *= 0.9;
    }
    */
  }
}

static void
oneFrame(noofstruct *np)
{
  int i;

  /**
  if((LRAND() & 0xff) == 0x34){
    glClear(GL_COLOR_BUFFER_BIT);
  }

  if((np->tko & 0x1f) == 0x1f){
    glEnable(GL_BLEND);
    glColor4f(0.0, 0.0, 0.0, 0.09);
    glRectf(0.0, 0.0, np->wd, np->ht);
    glDisable(GL_BLEND);
#ifdef __sgi
    sginap(0);
#endif
  }
  */
  gravity(np, -2.0);
  for (i = 0; i < N_SHAPES; i++) {
    motionUpdate(np, i);
#ifdef __sgi
    sginap(0);
#endif
    colorUpdate(np, i);
#ifdef __sgi
    sginap(0);
#endif
    drawleaf(np, i);
#ifdef __sgi
    sginap(0);
#endif

  }
  glFlush();
}

XSCREENSAVER_MODULE ("Noof", noof)

#endif /* MODE_noof */
