/* noof, Copyright (c) 2004-2018 Bill Torzewski <billt@worksitez.com>
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation.  No representations are made about the suitability of this
 * software for any purpose.  It is provided "as is" without express or 
 * implied warranty.
 *
 * Originally a demo included with GLUT;
 * (Apparently this was called "diatoms" on Irix.)
 * ported to raw GL and xscreensaver by jwz, 12-Feb-2004.
 */

#define DEFAULTS	"*delay:	10000       \n" \
			"*showFPS:      False       \n" \
			"*fpsSolid:     True        \n" \
			"*doubleBuffer: False       \n" \
			"*suppressRotationAnimation: True\n" \

# define release_noof 0
# define noof_handle_event 0
#include "xlockmore.h"
#include "pow2.h"

#ifdef USE_GL /* whole file */

#define N_SHAPES 7

ENTRYPOINT ModeSpecOpt noof_opts = {0, NULL, 0, NULL, NULL};

typedef struct {
  GLXContext *glx_context;

  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];

  float ht, wd;

  int tko;

  GLuint screenshot_texture, tex_w, tex_h;

} noof_configuration;

static noof_configuration *bps = NULL;


static void
initshapes(noof_configuration *bp, int i)
{
  int k;
  float f;

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

  bp->speedsq[i] = bp->dir[i * 3] * bp->dir[i * 3] + bp->dir[i * 3 + 1] * bp->dir[i * 3 + 1];
  f = random() / (double) RAND_MAX;
  bp->blad[i] = 2 + (int) (f * 17.0);
  f = random() / (double) RAND_MAX;
  bp->ang[i] = f;
  f = random() / (double) RAND_MAX;
  bp->spn[i] = (f - 0.5) * 40.0 / (10 + bp->blad[i]);
  f = random() / (double) RAND_MAX;
  bp->sca[i] = (f * 0.1 + 0.08);
  bp->dir[i * 3] *= bp->sca[i];
  bp->dir[i * 3 + 1] *= bp->sca[i];

  f = random() / (double) RAND_MAX;
  bp->hsv[i * 3] = f * 360.0;

  f = random() / (double) RAND_MAX;
  bp->hsv[i * 3 + 1] = f * 0.6 + 0.4;

  f = random() / (double) RAND_MAX;
  bp->hsv[i * 3 + 2] = f * 0.7 + 0.3;

  f = random() / (double) RAND_MAX;
  bp->hpr[i * 3] = f * 0.005 * 360.0;
  f = random() / (double) RAND_MAX;
  bp->hpr[i * 3 + 1] = f * 0.03;
  f = random() / (double) RAND_MAX;
  bp->hpr[i * 3 + 2] = f * 0.02;

  bp->geep[i] = 0;
  f = random() / (double) RAND_MAX;
  bp->peep[i] = 0.01 + f * 0.2;
}

static const 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 int
drawleaf(noof_configuration *bp, int l)
{
  int polys = 0;
  int b, blades;
  float x, y;
  float wobble;

  blades = bp->blad[l];

  y = 0.10 * sin(bp->geep[l] * M_PI / 180.0) + 0.099 * sin(bp->geep[l] * 5.12 * M_PI / 180.0);
  if (y < 0)
    y = -y;
  x = 0.15 * cos(bp->geep[l] * M_PI / 180.0) + 0.149 * cos(bp->geep[l] * 5.12 * M_PI / 180.0);
  if (x < 0.0)
    x = 0.0 - x;
  if (y < 0.001 && x > 0.000002 && ((bp->tko & 0x1) == 0)) {
    initshapes(bp, l);      /* let it become reborn as something
                           else */
    bp->tko++;
    return polys;
  } {
    float w1 = sin(bp->geep[l] * 15.3 * M_PI / 180.0);
    wobble = 3.0 + 2.00 * sin(bp->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(bp->pos[l * 3], bp->pos[l * 3 + 1], bp->pos[l * 3 + 2]);
    glRotatef(bp->ang[l] + b * (360.0 / blades), 0.0, 0.0, 1.0);
    glScalef(wobble * bp->sca[l], wobble * bp->sca[l], wobble * bp->sca[l]);
    /**
    if(tko & 0x40000) glColor3f(col[l*3], col[l*3+1], col[l*3+2]); 
    else
    */
    glColor4ub(0, 0, 0, 0x60);

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

    glBegin(GL_TRIANGLE_STRIP);
    glVertex2f(x * bp->sca[l], 0.0);
    glVertex2f(x, y);
    glVertex2f(x, -y);  /* C */
    glVertex2f(0.3, 0.0);  /* D */
    polys += 2;
    glEnd();

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

    glPopMatrix();
  }
  return polys;
}

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

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

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

  bp->hsv[i * 3] += bp->hpr[i * 3];
  bp->hsv[i * 3 + 1] += bp->hpr[i * 3 + 1];
  bp->hsv[i * 3 + 2] += bp->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(bp->hsv) < 0.0)
    V(bp->hsv) = 0.0;
  if (V(bp->hsv) > 1.0)
    V(bp->hsv) = 1.0;
  if (S(bp->hsv) <= 0.0) {
    R(bp->col) = V(bp->hsv);
    G(bp->col) = V(bp->hsv);
    B(bp->col) = V(bp->hsv);
  } else {
    float f, h, p, q, t, v;
    int hi;

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

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

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

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

static void
gravity(noof_configuration *bp, float fx)
{
  int a, b;

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

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

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

        z = 0.00000001 * fx / (d2);

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

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

ENTRYPOINT void
draw_noof (ModeInfo *mi)
{
  int i;
  noof_configuration *bp = &bps[MI_SCREEN(mi)];

  if (!bp->glx_context)
    return;
  glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *bp->glx_context);

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  /* In the olden days, one could just render directly into the front buffer,
     or fail to clear the back buffer and assume that one's bits were still
     there. Not so on modern devices, particularly mobile.  So to achieve
     the effect of frame N+1 accumulating atop frame N, we must save and
     restore a screenshot of frame N.
   */
  if (bp->screenshot_texture)
    {
      GLfloat tw = MI_WIDTH(mi)  / (GLfloat) bp->tex_w;
      GLfloat th = MI_HEIGHT(mi) / (GLfloat) bp->tex_h;
      glDisable (GL_BLEND);
      glEnable (GL_TEXTURE_2D);
      glBindTexture (GL_TEXTURE_2D, bp->screenshot_texture);
      glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
      glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
      glColor3f (1, 1, 1);
      glBegin (GL_QUADS);
      glTexCoord2f (0,  0);  glVertex3f (0, 0, 0);
      glTexCoord2f (tw, 0);  glVertex3f (bp->wd, 0, 0);
      glTexCoord2f (tw, th); glVertex3f (bp->wd, bp->ht, 0);
      glTexCoord2f (0,  th); glVertex3f (0, bp->ht, 0);
      glEnd();
      glDisable (GL_TEXTURE_2D);
      glClear (GL_DEPTH_BUFFER_BIT);
    }

  mi->polygon_count = 0;

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

  if((tko & 0x1f) == 0x1f){
    glEnable(GL_BLEND);
    glColor4f(0.0, 0.0, 0.0, 0.09);
    glRectf(0.0, 0.0, wd, ht);
    glDisable(GL_BLEND);
#ifdef __sgi
    sginap(0);
#endif
  }
  */

  gravity(bp, -2.0);
  for (i = 0; i < N_SHAPES; i++) {
    motionUpdate(bp, i);
    colorUpdate(bp, i);
      mi->polygon_count += drawleaf(bp, i);
  }

  if (bp->screenshot_texture)	/* Store a screenshot into the texture. */
    {
      glDisable (GL_BLEND);
      glEnable (GL_TEXTURE_2D);
      glBindTexture (GL_TEXTURE_2D, bp->screenshot_texture);
      glCopyTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, 0, 0,
                           MI_WIDTH(mi), MI_HEIGHT(mi));
      check_gl_error("screenshot texture");
      glDisable (GL_TEXTURE_2D);
    }

  if (mi->fps_p) do_fps (mi);
  glFinish();

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


ENTRYPOINT void
reshape_noof(ModeInfo *mi, int w, int h)
{
  noof_configuration *bp = &bps[MI_SCREEN(mi)];
  char *s;
  glViewport(0, 0, w, h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  if (w <= h) {
    bp->wd = 1.0;
    bp->ht = (GLfloat) h / (GLfloat) w;
    glOrtho(0.0, 1.0,
      0.0, 1.0 * (GLfloat) h / (GLfloat) w,
      -16.0, 4.0);
  } else {
    bp->wd = (GLfloat) w / (GLfloat) h;
    bp->ht = 1.0;
    glOrtho(0.0, 1.0 * (GLfloat) w / (GLfloat) h,
      0.0, 1.0,
      -16.0, 4.0);
  }
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  if (!bp->screenshot_texture)
    glGenTextures (1, &bp->screenshot_texture);

  glEnable (GL_TEXTURE_2D);
  glBindTexture (GL_TEXTURE_2D, bp->screenshot_texture);

  bp->tex_w = to_pow2 (MI_WIDTH(mi));
  bp->tex_h = to_pow2 (MI_HEIGHT(mi));
  s = calloc (4, bp->tex_w * bp->tex_h);  /* init with black */
  glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, bp->tex_w, bp->tex_h, 0,
                GL_RGBA, GL_UNSIGNED_BYTE, s);
  check_gl_error ("texture generation");
  free (s);
  glDisable (GL_TEXTURE_2D);
  glClear (GL_COLOR_BUFFER_BIT);
}

ENTRYPOINT void 
init_noof (ModeInfo *mi)
{
  int i;
  noof_configuration *bp;

  MI_INIT (mi, bps);

  bp = &bps[MI_SCREEN(mi)];

  bp->glx_context = init_GL(mi);

  glEnable(GL_LINE_SMOOTH);
  glShadeModel(GL_FLAT);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  for (i = 0; i < N_SHAPES; i++)
    initshapes(bp, i);

  reshape_noof (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
}


ENTRYPOINT void 
free_noof (ModeInfo *mi)
{
  noof_configuration *bp = &bps[MI_SCREEN(mi)];
  if (!bp->glx_context) return;
  glXMakeCurrent (MI_DISPLAY(mi), MI_WINDOW(mi), *bp->glx_context);
  if (bp->screenshot_texture) glDeleteTextures (1, &bp->screenshot_texture);
}


XSCREENSAVER_MODULE ("Noof", noof)

#endif /* USE_GL */
