/* biof --- 3D Bioform */

#if !defined( lint ) && !defined( SABER )
static const char sccsid[] = "@(#)biof.c	5.15 2005/02/15 xlockmore";
#endif

/* Copyright (c) E. Lassauge, 2005. */

/*
 * 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.
 *
 * The original code is from the really slick screensaver series
 * (see below the original header)
 *
 * Eric Lassauge  (Feb-15-2005) <lassauge AT users.sourceforge.net>
 *
 * REVISION HISTORY:
 *
 * - Eric Lassauge  (Feb-15-2005) <lassauge AT users.sourceforge.net>
 *   Ported the rss_glx version to xlockmore (added random color + wireframe + fps)
 */

/*
 * Copyright (C) 2002  <hk@dgmr.nl>
 * Ported to Linux by Tugrul Galatali <tugrul@galatali.com>
 *
 * BioF is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as 
 * published by the Free Software Foundation.
 *
 * BioF is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifdef STANDALONE
# define MODE_biof
# define PROGCLASS      "BioF"
# define HACK_INIT      init_biof
# define HACK_DRAW      draw_biof
# define HACK_RESHAPE   reshape_biof
# define biof_opts  xlockmore_opts
# define DEFAULTS       "*delay:       25000 \n" \
                         "*count:          4 \n" \
                         "*showFPS:    False \n" \
                         "*cycles:       100 \n" \
                         "*size:        6000 \n" \
                         "*preset:         5 \n" 

# include "xlockmore.h"         /* from the xscreensaver distribution */
# include <stdio.h>
# include <stdlib.h>
# include <math.h>
#else /* !STANDALONE */
# include "xlock.h"             /* from the xlockmore distribution */
# include "visgl.h"
#endif /* !STANDALONE */

#ifdef MODE_biof
// BioF screen saver

#include <stdlib.h>
#include <GL/gl.h>
#include <GL/glu.h>

#define TRIANGLES 1
#define SPHERES 2
#define BIGSPHERES 3
#define POINTS 4
#define LIGHTMAP 5

//#define COLOR	0.9, 0.75, 0.5
//#define COLOR_RAND(i)	((float) (NRAND(i)) / 100.0)
// Try to keep the same 'weight' on the components:
#define COLOR_RAND(i)	((float) ( (2*i - 100) + NRAND(100 - i) ) / 100.0)
static float C_RED;
static float C_GREEN;
static float C_BLUE;
#define COLOR	C_RED, C_GREEN, C_BLUE

static void handle_opts (ModeInfo * mi);

static XrmOptionDescRec opts[] =
{
        {(char *) "-preset",    (char *) ".biof.preset",    XrmoptionSepArg, (caddr_t)NULL},
        {(char *) "-triangles",    (char *) ".biof.preset",    XrmoptionNoArg, (caddr_t)"1"},
        {(char *) "-spheres",    (char *) ".biof.preset",    XrmoptionNoArg, (caddr_t)"2"},
        {(char *) "-bigspheres",    (char *) ".biof.preset",    XrmoptionNoArg, (caddr_t)"3"},
        {(char *) "-lightmap",    (char *) ".biof.preset",    XrmoptionNoArg, (caddr_t)"5"},
        {(char *) "-lines",    (char *) ".biof.lines",    XrmoptionSepArg, (caddr_t)NULL},
        {(char *) "-points",    (char *) ".biof.points",    XrmoptionSepArg, (caddr_t)NULL},
        {(char *) "-offangle",    (char *) ".biof.offangle",    XrmoptionNoArg, (caddr_t)"True"},
        {(char *) "+offangle",    (char *) ".biof.offangle",    XrmoptionNoArg, (caddr_t)"False"},
};

static int var_preset;
static int var_offangle;
static int var_lines;
static int var_points;

static argtype vars[] =
{
        {(void *) & var_preset, (char *) "preset",    (char *) "Preset",    (char *) "0",    t_Int},
        {(void *) & var_lines, (char *) "lines",    (char *) "Lines",    (char *) "0",    t_Int},
        {(void *) & var_points, (char *) "points",    (char *) "Points",    (char *) "0",    t_Int},
        {(void *) & var_offangle, (char *) "offangle",    (char *) "OffAngle",    (char *) "False",    t_Bool},
};

static OptionStruct desc[] =
{
        {(char *) "-preset num",        (char *) "preset to use [1-5]"},
        {(char *) "-triangles",         (char *) "preset 1"},
        {(char *) "-spheres",           (char *) "preset 2"},
        {(char *) "-bigspheres",        (char *) "preset 3"},
        {(char *) "-lightmap",          (char *) "preset 5"},
        {(char *) "-lines num",         (char *) "use num lines"},
        {(char *) "-points num",        (char *) "use num points"},
        {(char *) "+/-offangle",        (char *) "whether to use OffAngle"},
};

ModeSpecOpt biof_opts =
{sizeof opts / sizeof opts[0], opts, sizeof vars / sizeof vars[0], vars, desc};

#ifdef USE_MODULES
ModStruct   cage_description =
{"cage", "init_biof", "draw_biof", "release_biof",
 "draw_biof", "init_biof", (char *) NULL, &cage_opts,
 10000, 800, 1, 0, 1.0, "",
 "Shows the Impossible Cage, an Escher-like GL scene", 0, NULL};

#endif

typedef struct {
#ifdef WIN32
	HGLRC       glx_context;
#else
	GLXContext *glx_context;
#endif
        GLint       WinH, WinW;
	int	    wire;

	int NRPOINTS, NRLINES;
	int dGeometry;
	int dOffAngle;
	double time;

	GLUquadricObj *shapes;
} biofstruct;

static biofstruct *biof = (biofstruct *) NULL;

static void Horn (ModeInfo * mi, int ribs, double bend, double stack, double twist, double twisttrans, double grow)
{
	biofstruct *bp = &biof[MI_SCREEN(mi)];
	int i;

	for (i = 0; i < ribs; i++) {
		glPushMatrix ();
		glRotated (i * bend / ribs, 1, 0, 0);
		glTranslated (0, 0, i * stack / ribs);
		glTranslated (twisttrans, 0, 0);
		glRotated (i * twist / ribs, 0, 0, 1);
		glTranslated (-twisttrans, 0, 0);

		switch (bp->dGeometry) {
		case TRIANGLES:
			glBegin (bp->wire?GL_LINE_LOOP:GL_TRIANGLES);
			glColor4d (COLOR, 1.0);
			glVertex3d (2.0, 2.0, 0.0);
			glColor4d (COLOR, 0.0);
			glVertex3d (0.0, 2.0, 0.0);
			glVertex3d (2.0, 0.0, 0.0);
			glEnd ();

			break;

		case BIGSPHERES:
			if (!bp->wire)
			{
				gluQuadricDrawStyle (bp->shapes, GLU_FILL);
				gluQuadricNormals (bp->shapes, GLU_SMOOTH);
			}
			else
			{
				gluQuadricDrawStyle (bp->shapes, GLU_LINE);
				gluQuadricNormals (bp->shapes, GLU_NONE);
			}
			gluSphere (bp->shapes, 7 * exp (i / ribs * log (grow)), 20, 10);

			break;

		case SPHERES:
			if (!bp->wire)
			{
				gluQuadricDrawStyle (bp->shapes, GLU_FILL);
				gluQuadricNormals (bp->shapes, GLU_SMOOTH);
			}
			else
			{
				gluQuadricDrawStyle (bp->shapes, GLU_LINE);
				gluQuadricNormals (bp->shapes, GLU_NONE);
			}
			//gluSphere(bp->shapes, power(grow,i/ribs), 5, 5);
			gluSphere (bp->shapes, exp (i / ribs * log (grow)), 6, 4);

			break;

		case LIGHTMAP:
		case POINTS:
			glBegin (GL_POINTS);
			glVertex3d (0, 0, 0);
			glEnd ();

			break;
		}

		glPopMatrix ();
	}
}

void reshape_biof (ModeInfo * mi, int width, int height)
{
	biofstruct *bp = &biof[MI_SCREEN(mi)];

	glViewport (0, 0, bp->WinW = (GLint) width, bp->WinH = (GLint) height);
	glMatrixMode (GL_PROJECTION);
	glLoadIdentity ();
	gluPerspective (30, (GLdouble) width / (GLdouble) height, 100, 300);
}

void init_biof (ModeInfo * mi)
{
	float glfLightPosition[4] = { 100.0, 100.0, 100.0, 0.0 };
	float glfFog[4] = { 0.0, 0.0, 0.3, 1.0 };
	//float DiffuseLightColor[4] = { 1, 0.8, 0.4, 1.0 };
	float DiffuseLightColor[4] = { 1.0, 1.0, 1.0, 1.0 };
	int i, j;
	double x, y, r, d;
	unsigned char texbuf[64][64];
	int        screen = MI_SCREEN(mi);
        biofstruct *bp;

        if (biof == NULL) {
                if ((biof = (biofstruct *) calloc(MI_NUM_SCREENS(mi),
                                           sizeof (biofstruct))) == NULL)
                        return;
        }
        bp = &biof[screen];
	bp->wire = MI_IS_WIREFRAME(mi);
	C_RED   = COLOR_RAND(90);
	C_GREEN = COLOR_RAND(75);
	C_BLUE  = COLOR_RAND(50);
	DiffuseLightColor[1] = COLOR_RAND(80);
	DiffuseLightColor[2] = COLOR_RAND(40);

	handle_opts(mi);
	if (MI_IS_DEBUG(mi)) {
                (void) fprintf(stderr,
                               "%s:\n\tpreset=%d\n\toffangle=%d\n\tlines=%d\n\tpoints=%d\n\twireframe=%s\n",
                               MI_NAME(mi),
                               bp->dGeometry,
                               bp->dOffAngle,
                               bp->NRLINES,
                               bp->NRPOINTS,
                               bp->wire ? "yes" : "no"
                        );
        }

        if ((bp->glx_context = init_GL(mi)) != NULL) {

		glEnable (GL_DEPTH_TEST);

		if (bp->wire)
		{
			glDisable (GL_BLEND);
		}
		else
		{
			glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
			glEnable (GL_BLEND);
		}

		if ((bp->dGeometry == SPHERES) || (bp->dGeometry == BIGSPHERES)) {
			if (bp->wire)
			{
				glDisable (GL_CULL_FACE);
				glDisable (GL_LIGHTING);
				glDisable (GL_LIGHT0);
			}
			else
			{
				glEnable (GL_CULL_FACE);
				glCullFace (GL_BACK);

				glLightfv (GL_LIGHT0, GL_POSITION, glfLightPosition);
				glEnable (GL_LIGHTING);
				glEnable (GL_LIGHT0);

				glLightfv (GL_LIGHT0, GL_DIFFUSE, DiffuseLightColor);
			}

			bp->shapes = gluNewQuadric ();
		} else if (bp->dGeometry == POINTS) {
			if (bp->wire)
			{
				glDisable (GL_POINT_SMOOTH);
				glPointSize (3.0);
			}
			else
			{
				glHint (GL_POINT_SMOOTH_HINT, GL_NICEST);
				glPointSize (3.0);
				glEnable (GL_POINT_SMOOTH);

				glFogi (GL_FOG_MODE, GL_LINEAR);
				glHint (GL_FOG_HINT, GL_NICEST);
				glFogfv (GL_FOG_COLOR, glfFog);
				glFogf (GL_FOG_START, 150);
				glFogf (GL_FOG_END, 250);
				glEnable (GL_FOG);
			}
		} else if (bp->dGeometry == LIGHTMAP) { /* Can't be wired ! */
			r = 32;

			memset ((void *)&texbuf, 0, 4096);

			for (i = 0; i < 64; i++) {
				for (j = 0; j < 64; j++) {
					x = abs (i - 32);
					y = abs (j - 32);
					d = sqrt (x * x + y * y);

					if (d < r) {
						d = 1 - (d / r);
						texbuf[i][j] = (char)(255.0f * d * d);
					}
				}
			}

			gluBuild2DMipmaps (GL_TEXTURE_2D, 1, 64, 64, GL_LUMINANCE, GL_UNSIGNED_BYTE, texbuf);

			glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
			glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		}

		reshape_biof (mi,MI_WIDTH(mi), MI_HEIGHT(mi));
        } else {
                MI_CLEARWINDOW(mi);
        }

}

void release_biof (ModeInfo * mi)
{
	int         screen;

        if (biof != NULL) {
                for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++) {
                        biofstruct *bp = &biof[screen];
			if ((bp->dGeometry == SPHERES) || (bp->dGeometry == BIGSPHERES))
				gluDeleteQuadric(bp->shapes);
                }
                (void)free((void *)biof);
                biof = (biofstruct *) NULL;
        }
        FreeAllGL(mi);
}

void draw_biof (ModeInfo * mi)
{
#ifndef WIN32
	struct timeval now;
#endif
	double dt = 0;
	biofstruct *bp = &biof[MI_SCREEN(mi)];
	Display    *display = MI_DISPLAY(mi);
        Window      window = MI_WINDOW(mi);

        MI_IS_DRAWN(mi) = True;

        if (!bp->glx_context)
                return;
#ifdef WIN32
	wglMakeCurrent(hdc, bp->glx_context);
#else
	glXMakeCurrent(display, window, *(bp->glx_context));
#endif
#ifdef WIN32
	bp->time += MI_DELAY(mi) / 100000.0;
	dt = bp->time;
#else
	GETTIMEOFDAY (&now);
	dt = (now.tv_sec * 1000000 + now.tv_usec) * 0.000001;
#endif
	if (bp->dGeometry == LIGHTMAP) {
		int i, j, offset;
		float *fb_buffer;
		double x, y, w;

		if ((fb_buffer = (float *) malloc(bp->NRPOINTS *
				bp->NRLINES * 3 * sizeof (float))) == NULL) {
			return;
		}
		glDisable (GL_DEPTH_TEST);
		glFeedbackBuffer (bp->NRPOINTS * bp->NRLINES * 3, GL_2D, fb_buffer);
		glRenderMode (GL_FEEDBACK);

		glMatrixMode (GL_MODELVIEW);
		glPushMatrix ();
		glLoadIdentity ();

		gluLookAt (200, 0, 0, 0, 0, 0, 0, 0, 1);

		glRotated (90, 0, 1, 0);
		glRotated (10 * dt, 0, 0, 1);

		glNewList (1, GL_COMPILE);
		Horn (mi, bp->NRPOINTS, 315 * sin (dt * 0.237), 45 /* + 20 * sin(dt * 0.133) */ , 1500 * sin (dt * 0.213), 7.5 + 2.5 * sin (dt * 0.173), 1);
		glEndList ();

		if (bp->dOffAngle)
			glRotated (45, 1, 1, 0);

		for (i = 0; i < bp->NRLINES; i++) {
			glPushMatrix ();

			glRotated (360 * i / bp->NRLINES, 0, 0, 1);
			glRotated (45, 1, 0, 0);
			glTranslated (0, 0, 2);

			glCallList (1);

			glPopMatrix ();
		}

		glPopMatrix ();

		glRenderMode (GL_RENDER);

		glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

		glMatrixMode (GL_PROJECTION);
		glPushMatrix ();
		glLoadIdentity ();

		glMatrixMode (GL_MODELVIEW);
		glPushMatrix ();
		glLoadIdentity ();

		glScalef (2.0 / bp->WinW, 2.0 / bp->WinH, 1.0);
		glTranslatef (-0.5 * bp->WinW, -0.5 * bp->WinH, 0.0);

		glClear (GL_COLOR_BUFFER_BIT);

		glEnable (GL_TEXTURE_2D);
		glBlendFunc (GL_ONE, GL_ONE);
		glEnable (GL_BLEND);

		glBegin (GL_QUADS);

		w = bp->WinW >> 7;

		for (j = 0; j < bp->NRPOINTS; j++) {
			glColor3f (0.25 - 0.15 * j / bp->NRPOINTS, 0.15 + 0.10 * j / bp->NRPOINTS, 0.06 + 0.14 * j / bp->NRPOINTS);
			for (i = 0; i < bp->NRLINES; i++) {
				offset = 3 * (i * bp->NRPOINTS + j);
				x = fb_buffer[offset + 1];
				y = fb_buffer[offset + 2];

				glTexCoord2f (0.0, 0.0);
				glVertex2f (x - w, y - w);

				glTexCoord2f (1.0, 0.0);
				glVertex2f (x + w, y - w);

				glTexCoord2f (1.0, 1.0);
				glVertex2f (x + w, y + w);

				glTexCoord2f (0.0, 1.0);
				glVertex2f (x - w, y + w);
			}
		}
		if (fb_buffer != NULL)
			free(fb_buffer);
		glEnd ();

		glMatrixMode (GL_MODELVIEW);
		glPopMatrix ();
		glMatrixMode (GL_PROJECTION);
		glPopMatrix ();
	} else {
		int i;

		glClear (GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);

		glMatrixMode (GL_MODELVIEW);
		glLoadIdentity ();
		gluLookAt (200, 0, 0, 0, 0, 0, 0, 0, 1);

		glPushMatrix ();
		glRotated (90, 0, 1, 0);
		glRotated (10 * dt, 0, 0, 1);

		glColor3d (COLOR);

		glNewList (1, GL_COMPILE);
		Horn (mi, bp->NRPOINTS, 315 * sin (dt * 0.237), 50 + 20 * sin (dt * 0.133), 1500 * sin (dt * 0.213), 7.5 + 2.5 * sin (dt * 0.173), 0.6 + 0.1 * sin (dt * 0.317));
		glEndList ();

		if (bp->dOffAngle)
			glRotated (45, 1, 1, 0);

		for (i = 0; i < bp->NRLINES; i++) {
			glPushMatrix ();
			glRotated (360 * i / bp->NRLINES, 0, 0, 1);
			glRotated (45, 1, 0, 0);
			glTranslated (0, 0, 2);
			glCallList (1);
			glPopMatrix ();
		}

		glPopMatrix ();
	}

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

static void setDefaults (ModeInfo *mi, int use_preset)
{
	biofstruct *bp = &biof[MI_SCREEN(mi)];
	bp->dGeometry = use_preset;

	switch (use_preset) {
	case TRIANGLES:
		bp->NRPOINTS = 80;
		bp->NRLINES = 32;

		break;
	case SPHERES:
		bp->NRPOINTS = 80;
		bp->NRLINES = 30;

		break;
	case BIGSPHERES:
		bp->NRPOINTS = 20;
		bp->NRLINES = 5;

		break;
	case POINTS:
		bp->NRPOINTS = 250;
		bp->NRLINES = 30;

		break;
	case LIGHTMAP:
		bp->NRPOINTS = 150;
		bp->NRLINES = 20;

		break;
	}
}

static void handle_opts (ModeInfo * mi)
{
	biofstruct *bp = &biof[MI_SCREEN(mi)];
	int change_flag = 0;

	setDefaults(mi,LIGHTMAP);
	bp->dOffAngle = 0;

	if ( (var_preset > 0) && (var_preset < 6))
	{
		change_flag = 1;
		setDefaults (mi, var_preset);
	}
	if (var_lines > 0)
	{
		change_flag = 1;
		bp->NRLINES = var_lines;
	}
	if (var_points > 0)
	{
		change_flag = 1;
		bp->NRPOINTS = var_points;
	}
	if (var_offangle)
	{
		change_flag = 1;
		bp->dOffAngle = 1;
	}

	if (!change_flag) {
		bp->dGeometry = NRAND (5) + 1;
		bp->dOffAngle = NRAND (2);

		switch (bp->dGeometry) {
		case TRIANGLES:
		case SPHERES:
			bp->NRLINES = NRAND (7) + 2;
			bp->NRPOINTS = NRAND (96) + 32;
			break;

		case BIGSPHERES:
			bp->NRLINES = NRAND (7) + 4;
			bp->NRPOINTS = NRAND (32) + 4;
			break;

		case POINTS:
		case LIGHTMAP:
			bp->NRLINES = NRAND (32) + 4;
			bp->NRPOINTS = NRAND (64) + 64;
		}
	}
}
#endif
