/* mirrorblob Copyright (c) 2003 Jon Dowdall */ /* * 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: * 23-Sep-2003: jon.dowdall@bigpond.com Created module "blob" * 19-Oct-2003: jon.dowdall@bigpond.com Added texturing * 21-Oct-2003: Renamed to mirrorblob * 10-Feb-2004: jon.dowdall@bigpond.com Added motion blur * 28-Jan-2006: jon.dowdall@bigpond.com Big clean up and bug fixes * 13-Apr-2009: jon.dowdall@gmail.com Fixed Mac version * * The mirrorblob screensaver draws a pulsing blob on the screen. Options * include adding a background (via screen_to_texture), texturing the blob, * making the blob semi-transparent and varying the resolution of the blob * tessellation. * * The blob was inspired by a lavalamp is in no way a simulation. The code is * just an attempt to generate some eye-candy. * * Much of xmirrorblob code framework is taken from the pulsar module by David * Konerding and the glslideshow by Mike Oliphant and Jamie Zawinski. * */ #include #ifdef STANDALONE #define DEFAULTS "*delay: " DEF_DELAY "\n" \ "*showFPS: " DEF_FPS "\n" \ "*useSHM: True \n" \ "*desktopGrabber: xscreensaver-getimage -no-desktop %s\n" \ "*grabDesktopImages: True \n" \ "*chooseRandomImages: True \n" \ "*suppressRotationAnimation: True\n" \ # define release_mirrorblob 0 # include "xlockmore.h" #else /* !STANDALONE */ # include "xlock.h" /* from the xlockmore distribution */ #endif /* !STANDALONE */ #ifdef USE_GL /* whole file */ #define DEF_DELAY "10000" #define DEF_FPS "False" #define DEF_WIRE "False" #define DEF_BLEND "1.0" #define DEF_FOG "False" #define DEF_ANTIALIAS "False" #define DEF_WALLS "False" #define DEF_COLOUR "False" #define DEF_ASYNC "True" #define DEF_TEXTURE "True" #define DEF_OFFSET_TEXTURE "False" #define DEF_PAINT_BACKGROUND "True" #define DEF_RESOLUTION "30" #define DEF_BUMPS "10" #define DEF_MOTION_BLUR "0.0" #define DEF_INCREMENTAL "0" #define DEF_HOLD_TIME "30.0" #define DEF_FADE_TIME "5.0" #define DEF_ZOOM "1.0" #ifdef HAVE_XMU # ifndef VMS # include #else /* VMS */ # include # endif /* VMS */ #endif #include "gltrackball.h" #include "grab-ximage.h" #undef countof #define countof(x) (sizeof((x)) / sizeof((*x))) #define PI 3.1415926535897 /* Options from command line */ static GLfloat blend; static Bool wireframe; static Bool do_fog; static Bool do_antialias; static Bool do_walls; static Bool do_texture; static Bool do_paint_background; static Bool do_colour; static Bool offset_texture; static int resolution; static int bumps; static float motion_blur; static float fade_time; static float hold_time; static float zoom; /* Internal parameters based on supplied options */ static Bool culling; static Bool load_textures; static XrmOptionDescRec opts[] = { {"-wire", ".blob.wire", XrmoptionNoArg, "true" }, {"+wire", ".blob.wire", XrmoptionNoArg, "false" }, {"-blend", ".blob.blend", XrmoptionSepArg, 0 }, {"-fog", ".blob.fog", XrmoptionNoArg, "true" }, {"+fog", ".blob.fog", XrmoptionNoArg, "false" }, {"-antialias", ".blob.antialias", XrmoptionNoArg, "true" }, {"+antialias", ".blob.antialias", XrmoptionNoArg, "false" }, {"-walls", ".blob.walls", XrmoptionNoArg, "true" }, {"+walls", ".blob.walls", XrmoptionNoArg, "false" }, {"-texture", ".blob.texture", XrmoptionNoArg, "true" }, {"+texture", ".blob.texture", XrmoptionNoArg, "false" }, {"-colour", ".blob.colour", XrmoptionNoArg, "true" }, {"+colour", ".blob.colour", XrmoptionNoArg, "false" }, {"-offset-texture", ".blob.offsetTexture", XrmoptionNoArg, "true" }, {"+offset-texture", ".blob.offsetTexture", XrmoptionNoArg, "false" }, {"-paint-background", ".blob.paintBackground", XrmoptionNoArg, "true" }, {"+paint-background", ".blob.paintBackground", XrmoptionNoArg, "false" }, {"-resolution", ".blob.resolution", XrmoptionSepArg, NULL }, {"-bumps", ".blob.bumps", XrmoptionSepArg, NULL }, {"-motion-blur", ".blob.motionBlur", XrmoptionSepArg, 0 }, {"-fade-time", ".blob.fadeTime", XrmoptionSepArg, 0 }, {"-hold-time", ".blob.holdTime", XrmoptionSepArg, 0 }, {"-zoom", ".blob.zoom", XrmoptionSepArg, 0 }, }; static argtype vars[] = { {&wireframe, "wire", "Wire", DEF_WIRE, t_Bool}, {&blend, "blend", "Blend", DEF_BLEND, t_Float}, {&do_fog, "fog", "Fog", DEF_FOG, t_Bool}, {&do_antialias, "antialias", "Antialias", DEF_ANTIALIAS, t_Bool}, {&do_walls, "walls", "Walls", DEF_WALLS, t_Bool}, {&do_texture, "texture", "Texture", DEF_TEXTURE, t_Bool}, {&do_colour, "colour", "Colour", DEF_COLOUR, t_Bool}, {&offset_texture, "offsetTexture","OffsetTexture", DEF_OFFSET_TEXTURE, t_Bool}, {&do_paint_background,"paintBackground","PaintBackground", DEF_PAINT_BACKGROUND, t_Bool}, {&resolution, "resolution", "Resolution", DEF_RESOLUTION, t_Int}, {&bumps, "bumps", "Bump", DEF_BUMPS, t_Int}, {&motion_blur, "motionBlur", "MotionBlur", DEF_MOTION_BLUR, t_Float}, {&fade_time, "fadeTime", "FadeTime", DEF_FADE_TIME, t_Float}, {&hold_time, "holdTime", "HoldTime", DEF_HOLD_TIME, t_Float}, {&zoom, "zoom", "Zoom", DEF_ZOOM, t_Float}, }; static OptionStruct desc[] = { {"-/+ wire", "whether to do use wireframe instead of filled (faster)"}, {"-/+ blend", "whether to do enable blending (slower)"}, {"-/+ fog", "whether to do enable fog (slower)"}, {"-/+ antialias", "whether to do enable antialiased lines (slower)"}, {"-/+ walls", "whether to add walls to the blob space (slower)"}, {"-/+ texture", "whether to add a texture to the blob (slower)"}, {"-/+ colour", "whether to colour the blob"}, {"-/+ offset_texture", "whether to offset texture co-ordinates"}, {"-/+ paint_background", "whether to display a background texture (slower)"}, {"-resolution", "Resolution of blob tesselation"}, {"-bumps", "Number of bumps used to disturb blob"}, {"-motion_blur", "Fade blob images (higher number = faster fade)"}, {"-fade_time", "Number of frames to transistion to next image"}, {"-hold_time", "Number of frames before next image"}, }; ENTRYPOINT ModeSpecOpt mirrorblob_opts = {countof(opts), opts, countof(vars), vars, desc}; #ifdef USE_MODULES ModStruct mirrorblob_description = {"mirrorblob", "init_mirrorblob", "draw_mirrorblob", NULL, "draw_mirrorblob", "init_mirrorblob", "free_mirrorblob", &mirrorblob_opts, 1000, 1, 2, 1, 4, 1.0, "", "OpenGL mirrorblob", 0, NULL}; #endif /***************************************************************************** * Types used in blob code *****************************************************************************/ typedef struct { GLfloat x, y; } Vector2D; typedef struct { GLfloat x, y, z; } Vector3D; typedef struct { GLfloat w; GLfloat x; GLfloat y; GLfloat z; } Quaternion; typedef struct { GLubyte red, green, blue, alpha; } Colour; typedef struct { Vector3D initial_position; Vector3D position; Vector3D normal; } Node_Data; typedef struct { int node1, node2, node3; Vector3D normal; double length1, length2, length3; } Face_Data; /* Structure to hold data about bumps used to distortion sphere */ typedef struct { double cx, cy, cpower, csize; double ax, ay, power, size; double mx, my, mpower, msize; double vx, vy, vpower, vsize; Vector3D pos; } Bump_Data; /* Vertices of a tetrahedron */ #define sqrt_3 0.5773502692 /*#undef sqrt_3 #define sqrt_3 1.0*/ #define PPP { sqrt_3, sqrt_3, sqrt_3 } /* +X, +Y, +Z */ #define MMP { -sqrt_3, -sqrt_3, sqrt_3 } /* -X, -Y, +Z */ #define MPM { -sqrt_3, sqrt_3, -sqrt_3 } /* -X, +Y, -Z */ #define PMM { sqrt_3, -sqrt_3, -sqrt_3 } /* +X, -Y, -Z */ /* Structure describing a tetrahedron */ static Vector3D tetrahedron[4][3] = { {PPP, MMP, MPM}, {PMM, MPM, MMP}, {PPP, MPM, PMM}, {PPP, PMM, MMP} }; /***************************************************************************** * Static blob data *****************************************************************************/ static const Vector3D zero_vector = { 0.0, 0.0, 0.0 }; /* Use 2 textures to allow a gradual fade between images */ #define NUM_TEXTURES 2 #define BUMP_ARRAY_SIZE 1024 typedef enum { INITIALISING, HOLDING, LOADING, TRANSITIONING } Frame_State; /* structure for holding the mirrorblob data */ typedef struct { int screen_width, screen_height; GLXContext *glx_context; Window window; XColor fg, bg; /* Parameters controlling the position of the blob as a whole */ Vector3D blob_center; Vector3D blob_anchor; Vector3D blob_velocity; Vector3D blob_force; /* Count of the total number of nodes and faces used to tesselate the blob */ int num_nodes; int num_faces; Node_Data *nodes; Face_Data *faces; Vector3D *dots; Vector3D *normals; Colour *colours; Vector2D *tex_coords; /* Pointer to the bump function results */ double *bump_shape, *wall_shape; Bump_Data *bump_data; /* Use 2 textures to allow a gradual fade between images */ int current_texture; /* Ratio of used texture size to total texture size */ GLfloat tex_width[NUM_TEXTURES], tex_height[NUM_TEXTURES]; GLuint textures[NUM_TEXTURES]; Frame_State state; double state_start_time; int colour_cycle; Bool mipmap_p; Bool waiting_for_image_p; Bool first_image_p; trackball_state *trackball; int button_down; } mirrorblobstruct; static mirrorblobstruct *Mirrorblob = NULL; /****************************************************************************** * * Returns the current time in seconds as a double. Shamelessly borrowed from * glslideshow. * */ static double double_time (void) { struct timeval now; # ifdef GETTIMEOFDAY_TWO_ARGS struct timezone tzp; gettimeofday(&now, &tzp); # else gettimeofday(&now); # endif return (now.tv_sec + ((double) now.tv_usec * 0.000001)); } /****************************************************************************** * * Change to the projection matrix and set our viewing volume. * */ static void reset_projection(int width, int height) { glMatrixMode (GL_PROJECTION); glLoadIdentity (); gluPerspective (60.0, 1.0, 1.0, 1024.0 ); glMatrixMode (GL_MODELVIEW); glLoadIdentity (); } /****************************************************************************** * * Calculate the dot product of two vectors u and v * Dot product u.v = |u||v|cos(theta) * Where theta = angle between u and v */ static inline double dot (const Vector3D u, const Vector3D v) { return (u.x * v.x) + (u.y * v.y) + (u.z * v.z); } /****************************************************************************** * * Calculate the cross product of two vectors. * Gives a vector perpendicular to u and v with magnitude |u||v|sin(theta) * Where theta = angle between u and v */ static inline Vector3D cross (const Vector3D u, const Vector3D v) { Vector3D result; result.x = (u.y * v.z - u.z * v.y); result.y = (u.z * v.x - u.x * v.z); result.z = (u.x * v.y - u.y * v.x); return result; } /****************************************************************************** * * Add vector v to vector u */ static inline void add (Vector3D *u, const Vector3D v) { u->x = u->x + v.x; u->y = u->y + v.y; u->z = u->z + v.z; } /****************************************************************************** * * Subtract vector v from vector u */ static inline Vector3D subtract (const Vector3D u, const Vector3D v) { Vector3D result; result.x = u.x - v.x; result.y = u.y - v.y; result.z = u.z - v.z; return result; } /****************************************************************************** * * multiply vector v by scalar s */ static inline Vector3D scale (const Vector3D v, const double s) { Vector3D result; result.x = v.x * s; result.y = v.y * s; result.z = v.z * s; return result; } /****************************************************************************** * * normalise vector v */ static inline Vector3D normalise (const Vector3D v) { Vector3D result; double magnitude; magnitude = sqrt (dot(v, v)); if (magnitude > 1e-300) { result = scale (v, 1.0 / magnitude); } else { printf("zero\n"); result = zero_vector; } return result; } /****************************************************************************** * * Calculate the transform matrix for the given quaternion */ static void quaternion_transform (Quaternion q, GLfloat * transform) { GLfloat x, y, z, w; x = q.x; y = q.y; z = q.z; w = q.w; transform[0] = (w * w) + (x * x) - (y * y) - (z * z); transform[1] = (2.0 * x * y) + (2.0 * w * z); transform[2] = (2.0 * x * z) - (2.0 * w * y); transform[3] = 0.0; transform[4] = (2.0 * x * y) - (2.0 * w * z); transform[5] = (w * w) - (x * x) + (y * y) - (z * z); transform[6] = (2.0 * y * z) + (2.0 * w * x); transform[7] = 0.0; transform[8] = (2.0 * x * z) + (2.0 * w * y); transform[9] = (2.0 * y * z) - (2.0 * w * x); transform[10] = (w * w) - (x * x) - (y * y) + (z * z); transform[11] = 0.0; transform[12] = 0.0; transform[13] = 0.0; transform[14] = 0.0; transform[15] = (w * w) + (x * x) + (y * y) + (z * z); } /****************************************************************************** * * Apply a matrix transform to the given vector */ static inline Vector3D vector_transform (Vector3D u, GLfloat * t) { Vector3D result; result.x = (u.x * t[0] + u.y * t[4] + u.z * t[8] + 1.0 * t[12]); result.y = (u.x * t[1] + u.y * t[5] + u.z * t[9] + 1.0 * t[13]); result.z = (u.x * t[2] + u.y * t[6] + u.z * t[10] + 1.0 * t[14]); return result; } /****************************************************************************** * * Return a node that is on an arc between node1 and node2, where distance * is the proportion of the distance from node1 to the total arc. */ static Vector3D partial (Vector3D node1, Vector3D node2, double distance) { Vector3D result; Vector3D rotation_axis; GLfloat transformation[16]; double angle; Quaternion rotation; rotation_axis = normalise (cross (node1, node2)); angle = acos (dot (node1, node2)) * distance; rotation.x = rotation_axis.x * sin (angle / 2.0); rotation.y = rotation_axis.y * sin (angle / 2.0); rotation.z = rotation_axis.z * sin (angle / 2.0); rotation.w = cos (angle / 2.0); quaternion_transform (rotation, transformation); result = vector_transform (node1, transformation); return result; } /**************************************************************************** * * Callback indicating a texture has loaded */ static void image_loaded_cb (const char *filename, XRectangle *geometry, int image_width, int image_height, int texture_width, int texture_height, void *closure) { mirrorblobstruct *mp = (mirrorblobstruct *) closure; GLint texid = -1; int texture_index = -1; int i; glGetIntegerv (GL_TEXTURE_BINDING_2D, &texid); if (texid < 0) abort(); for (i = 0; i < NUM_TEXTURES; i++) { if (mp->textures[i] == texid) { texture_index = i; break; } } if (texture_index < 0) abort(); mp->tex_width [texture_index] = (GLfloat) image_width / texture_width; mp->tex_height[texture_index] = -(GLfloat) image_height / texture_height; glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (mp->mipmap_p ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR)); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); mp->waiting_for_image_p = False; mp->first_image_p = True; } /* Load a new file into a texture */ static void grab_texture(ModeInfo *mi, int texture_index) { mirrorblobstruct *mp = &Mirrorblob[MI_SCREEN(mi)]; { int w = (MI_WIDTH(mi) / 2) - 1; int h = (MI_HEIGHT(mi) / 2) - 1; if (w <= 10) w = 10; if (h <= 10) h = 10; mp->waiting_for_image_p = True; mp->mipmap_p = True; load_texture_async (mi->xgwa.screen, mi->window, *mp->glx_context, w, h, mp->mipmap_p, mp->textures[texture_index], image_loaded_cb, mp); } } /****************************************************************************** * * Generate internal parameters based on supplied options the parameters to * ensure they are consistant. */ static void set_parameters(void) { # ifdef HAVE_JWZGLES /* #### glPolygonMode other than GL_FILL unimplemented */ wireframe = 0; # endif /* In wire frame mode do not draw a texture */ if (wireframe) { do_texture = False; blend = 1.0; } /* Need to load textures if either the blob or the backgound has an image */ if (do_texture || do_paint_background) { load_textures = True; } else { load_textures = False; } /* If theres no texture don't calculate co-ordinates. */ if (!do_texture) { offset_texture = False; } culling = True; } /****************************************************************************** * * Initialise the openGL state data. */ static void initialize_gl(ModeInfo *mi, GLsizei width, GLsizei height) { mirrorblobstruct *gp = &Mirrorblob[MI_SCREEN(mi)]; /* Lighting values */ GLfloat ambientLight[] = { 0.2f, 0.2f, 0.2f, 1.0f }; GLfloat lightPos0[] = {500.0f, 100.0f, 200.0f, 1.0f }; GLfloat whiteLight0[] = { 0.0f, 0.0f, 0.0f, 1.0f }; GLfloat sourceLight0[] = { 0.6f, 0.6f, 0.6f, 1.0f }; GLfloat specularLight0[] = { 0.8f, 0.8f, 0.9f, 1.0f }; GLfloat lightPos1[] = {-50.0f, -100.0f, 2500.0f, 1.0f }; GLfloat whiteLight1[] = { 0.0f, 0.0f, 0.0f, 1.0f }; GLfloat sourceLight1[] = { 0.6f, 0.6f, 0.6f, 1.0f }; GLfloat specularLight1[] = { 0.7f, 0.7f, 0.7f, 1.0f }; GLfloat specref[] = { 1.0f, 1.0f, 1.0f, 1.0f }; GLfloat fogColor[4] = { 0.4, 0.4, 0.5, 0.1 }; /* Set the internal parameters based on the configuration settings */ set_parameters(); /* Set the viewport to the width and heigh of the window */ glViewport (0, 0, width, height ); if (do_antialias) { blend = 1.0; glEnable(GL_LINE_SMOOTH); glEnable(GL_POLYGON_SMOOTH); } /* The blend function is used for trasitioning between two images even when * blend is not selected. */ glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); if (do_fog) { glEnable(GL_FOG); glFogfv(GL_FOG_COLOR, fogColor); glFogf(GL_FOG_DENSITY, 0.50); glFogf(GL_FOG_START, 15.0); glFogf(GL_FOG_END, 30.0); } /* Set the shading model to smooth (Gouraud shading). */ glShadeModel (GL_SMOOTH); glLightModelfv (GL_LIGHT_MODEL_AMBIENT, ambientLight); glLightfv (GL_LIGHT0, GL_AMBIENT, whiteLight0); glLightfv (GL_LIGHT0, GL_DIFFUSE, sourceLight0); glLightfv (GL_LIGHT0, GL_SPECULAR, specularLight0); glLightfv (GL_LIGHT0, GL_POSITION, lightPos0); glEnable (GL_LIGHT0); glLightfv (GL_LIGHT1, GL_AMBIENT, whiteLight1); glLightfv (GL_LIGHT1, GL_DIFFUSE, sourceLight1); glLightfv (GL_LIGHT1, GL_SPECULAR, specularLight1); glLightfv (GL_LIGHT1, GL_POSITION, lightPos1); glEnable (GL_LIGHT1); glEnable (GL_LIGHTING); /* Enable color tracking */ glEnable (GL_COLOR_MATERIAL); /* Set Material properties to follow glColor values */ glColorMaterial (GL_FRONT, GL_AMBIENT_AND_DIFFUSE); /* Set all materials to have specular reflectivity */ glMaterialfv (GL_FRONT, GL_SPECULAR, specref); glMateriali (GL_FRONT, GL_SHININESS, 32); /* Let GL implementation scale normal vectors. */ glEnable (GL_NORMALIZE); /* Enable Arrays */ if (load_textures) { glLightModeli (GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR); glEnable (GL_TEXTURE_2D); gp->current_texture = 0; glGenTextures(NUM_TEXTURES, gp->textures); grab_texture(mi, gp->current_texture); glMatrixMode (GL_TEXTURE); glRotated (180.0, 1.0, 0.0, 0.0); glMatrixMode (GL_MODELVIEW); } /* Clear the buffer since this is not done during a draw with motion blur */ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } /****************************************************************************** * * Initialise the openGL state data. */ static void set_blob_gl_state(GLfloat alpha) { if (do_antialias) { glEnable(GL_LINE_SMOOTH); glEnable(GL_POLYGON_SMOOTH); } if (wireframe) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); } else { glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } /* The blend function is used for trasitioning between two images even when * blend is not selected. */ glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); /* Culling. */ if (culling) { glCullFace (GL_BACK); glEnable (GL_CULL_FACE); glFrontFace (GL_CCW); } else { glDisable (GL_CULL_FACE); } if (blend < 1.0) { glEnable (GL_BLEND); glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); /* Set the default blob colour to off-white. */ glColor4f (0.9, 0.9, 1.0, alpha); } else { glDisable(GL_BLEND); glColor4f (0.9, 0.9, 1.0, 1.0); } glEnable(GL_DEPTH_TEST); glEnable(GL_LIGHTING); } /****************************************************************************** * * Initialise the data required to draw the blob allocating the memory as * necessary. * * Return 0 on success. */ static int initialise_blob(mirrorblobstruct *gp, int width, int height, int bump_array_size) { /* Loop variables */ int i, u, v, node, side, face, base, base2 = 0; int nodes_on_edge = resolution; Vector3D node1, node2, result; if (nodes_on_edge < 2) return -1; gp->num_nodes = 2 * nodes_on_edge * nodes_on_edge - 4 * nodes_on_edge + 4; gp->num_faces = 4 * (nodes_on_edge - 1) * (nodes_on_edge - 1); gp->nodes = (Node_Data *) malloc (gp->num_nodes * sizeof (Node_Data)); if (!gp->nodes) { fprintf (stderr, "Couldn't allocate gp->nodes buffer\n"); return -1; } gp->faces = (Face_Data *) malloc (gp->num_faces * sizeof (Face_Data)); if (!gp->faces) { fprintf (stderr, "Couldn't allocate faces data buffer\n"); return -1; } gp->bump_data = (Bump_Data *) malloc (bumps * sizeof (Bump_Data)); if (!gp->bump_data) { fprintf(stderr, "Couldn't allocate bump data buffer\n"); return -1; } gp->bump_shape = (double *)malloc(bump_array_size * sizeof(double)); if (!gp->bump_shape) { fprintf(stderr, "Couldn't allocate bump buffer\n"); return -1; } gp->wall_shape = (double *)malloc(bump_array_size * sizeof(double)); if (!gp->wall_shape) { fprintf(stderr, "Couldn't allocate wall bump buffer\n"); return -1; } gp->dots = (Vector3D *)malloc(gp->num_nodes * sizeof(Vector3D)); if (!gp->dots) { fprintf(stderr, "Couldn't allocate nodes buffer\n"); return -1; } gp->normals = (Vector3D *)malloc(gp->num_nodes * sizeof(Vector3D)); if (!gp->normals) { fprintf(stderr, "Couldn't allocate normals buffer\n"); return -1; } gp->colours = (Colour *)malloc(gp->num_nodes * sizeof(Colour)); if (!gp->colours) { fprintf(stderr, "Couldn't allocate colours buffer\n"); return -1; } gp->tex_coords = (Vector2D *)malloc(gp->num_nodes * sizeof(Vector2D)); if (!gp->tex_coords) { fprintf(stderr, "Couldn't allocate gp->tex_coords buffer\n"); return -1; } /* Initialise bump data */ for (i = 0; i < bumps; i++) { gp->bump_data[i].ax = 2.0 * (((double)random() / (double)RAND_MAX) - 0.5); gp->bump_data[i].ay = 2.0 * (((double)random() / (double)RAND_MAX) - 0.5); gp->bump_data[i].power = (5.0 / pow(bumps, 0.75)) * (((double)random() / (double)RAND_MAX) - 0.5); gp->bump_data[i].size = 0.1 + 0.5 * (((double)random() / (double)RAND_MAX)); gp->bump_data[i].pos.x = 1.5 * sin(PI * gp->bump_data[i].ay) * cos(PI * gp->bump_data[i].ax); gp->bump_data[i].pos.y = 1.5 * cos(PI * gp->bump_data[i].ay); gp->bump_data[i].pos.z = 1.5 * sin(PI * gp->bump_data[i].ay) * sin(PI * gp->bump_data[i].ax); gp->bump_data[i].cx = 2.0 * (((double)random() / (double)RAND_MAX) - 0.5); gp->bump_data[i].cy = 2.0 * (((double)random() / (double)RAND_MAX) - 0.5); gp->bump_data[i].cpower = (5.0 / pow(bumps, 0.75)) * (((double)random() / (double)RAND_MAX) - 0.5); gp->bump_data[i].csize = 0.35; /*0.1 + 0.25 * (((double)random() / (double)RAND_MAX));*/ gp->bump_data[i].vx = 0.0; gp->bump_data[i].vy = 0.0; gp->bump_data[i].vpower = 0.0; gp->bump_data[i].vsize = 0.0; gp->bump_data[i].mx = 0.003 * ((double)random() / (double)RAND_MAX); gp->bump_data[i].my = 0.003 * ((double)random() / (double)RAND_MAX); gp->bump_data[i].mpower = 0.003 * ((double)random() / (double)RAND_MAX); gp->bump_data[i].msize = 0.003 * ((double)random() / (double)RAND_MAX); } /* Initialise lookup table of bump strength */ for (i = 0; i < bump_array_size; i++) { double xd, xd2; xd = i / (double)bump_array_size; xd2 = 48.0 * xd * xd; gp->bump_shape[i] = 0.1 / (xd2 + 0.1); xd2 = 40.0 * xd * xd * xd * xd; gp->wall_shape[i] = 0.4 / (xd2 + 0.1); } node = 0; face = 0; for (side = 0; side < 4; side++) { base = node; if (side == 2) { base2 = node; } /* * The start and end of the for loops below are modified based on the * side of the tetrahedron that is being calculated to avoid duplication * of the gp->nodes that are on the edges of the tetrahedron. */ for (u = (side > 1); u < (nodes_on_edge - (side > 0)); u++) { node1 = partial (normalise (tetrahedron[side][0]), normalise (tetrahedron[side][1]), u / (double) (nodes_on_edge - 1)); node2 = partial (normalise (tetrahedron[side][0]), normalise (tetrahedron[side][2]), u / (double) (nodes_on_edge - 1)); for (v = (side > 1); v <= (u - (side > 2)); v++) { if (u > 0) result = partial (node1, node2, v / (double) u); else result = node1; gp->nodes[node].position = normalise (result); gp->nodes[node].initial_position = gp->nodes[node].position; gp->nodes[node].normal = zero_vector; node++; } } /* * Determine which nodes make up each face. The complexity is caused * by having to determine the correct nodes for the edges of the * tetrahedron since the common nodes on the edges are only calculated * once (see above). */ for (u = 0; u < (nodes_on_edge - 1); u++) { for (v = 0; v <= u; v++) { { if (side < 2) { gp->faces[face].node1 = base + ((u * (u + 1)) / 2) + v; gp->faces[face].node2 = base + ((u + 1) * (u + 2)) / 2 + v + 1; gp->faces[face].node3 = base + ((u + 1) * (u + 2)) / 2 + v; if ((side == 1) && (u == (nodes_on_edge - 2))) { gp->faces[face].node3 = ((u + 1) * (u + 2)) / 2 + nodes_on_edge - v - 1; gp->faces[face].node2 = ((u + 1) * (u + 2)) / 2 + nodes_on_edge - v - 2; } } else if (side < 3) { gp->faces[face].node1 = base + (((u - 1) * u) / 2) + v - 1; gp->faces[face].node2 = base + ((u) * (u + 1)) / 2 + v; gp->faces[face].node3 = base + ((u) * (u + 1)) / 2 + v - 1; if (u == (nodes_on_edge - 2)) { int n = nodes_on_edge - v - 1; gp->faces[face].node2 = ((nodes_on_edge * (nodes_on_edge + 1)) / 2) + ((n - 1) * (n + 0)) / 2; gp->faces[face].node3 = ((nodes_on_edge * (nodes_on_edge + 1)) / 2) + ((n + 0) * (n + 1)) / 2; } if (v == 0) { gp->faces[face].node1 = (((u + 1) * (u + 2)) / 2) - 1; gp->faces[face].node3 = (((u + 2) * (u + 3)) / 2) - 1; } } else { gp->faces[face].node1 = base + (((u - 2) * (u - 1)) / 2) + v - 1; gp->faces[face].node2 = base + ((u - 1) * u) / 2 + v; gp->faces[face].node3 = base + ((u - 1) * u) / 2 + v - 1; if (v == 0) { gp->faces[face].node1 = base2 + ((u * (u + 1)) / 2) - 1; gp->faces[face].node3 = base2 + ((u + 1) * (u + 2)) / 2 - 1; } if (u == (nodes_on_edge - 2)) { gp->faces[face].node3 = ((nodes_on_edge * (nodes_on_edge + 1)) / 2) + ((v + 1) * (v + 2)) / 2 - 1; gp->faces[face].node2 = ((nodes_on_edge * (nodes_on_edge + 1)) / 2) + ((v + 2) * (v + 3)) / 2 - 1; } if (v == u) { gp->faces[face].node1 = (u * (u + 1)) / 2; gp->faces[face].node2 = ((u + 1) * (u + 2)) / 2; } } face++; } if (v < u) { if (side < 2) { gp->faces[face].node1 = base + ((u * (u + 1)) / 2) + v; gp->faces[face].node2 = base + ((u * (u + 1)) / 2) + v + 1; gp->faces[face].node3 = base + (((u + 1) * (u + 2)) / 2) + v + 1; if ((side == 1) && (u == (nodes_on_edge - 2))) { gp->faces[face].node3 = ((u + 1) * (u + 2)) / 2 + nodes_on_edge - v - 2; } } else if (side < 3) { gp->faces[face].node1 = base + ((u * (u - 1)) / 2) + v - 1; gp->faces[face].node2 = base + ((u * (u - 1)) / 2) + v; gp->faces[face].node3 = base + ((u * (u + 1)) / 2) + v; if (u == (nodes_on_edge - 2)) { int n = nodes_on_edge - v - 1; gp->faces[face].node3 = ((nodes_on_edge * (nodes_on_edge + 1)) / 2) + ((n + 0) * (n - 1)) / 2; } if (v == 0) { gp->faces[face].node1 = (((u + 1) * (u + 2)) / 2) - 1; } } else { gp->faces[face].node1 = base + (((u - 2) * (u - 1)) / 2) + v - 1; gp->faces[face].node2 = base + (((u - 2) * (u - 1)) / 2) + v; gp->faces[face].node3 = base + (((u - 1) * u) / 2) + v; if (v == 0) { gp->faces[face].node1 = base2 + (u * (u + 1)) / 2 - 1; } if (u == (nodes_on_edge - 2)) { gp->faces[face].node3 = ((nodes_on_edge * (nodes_on_edge + 1)) / 2) + ((v + 2) * (v + 3)) / 2 - 1; } if (v == (u - 1)) { gp->faces[face].node2 = (u * (u + 1)) / 2; } } face++; } } } } return 0; } /****************************************************************************** * * Return the magnitude of the given vector */ #if 0 static inline double length (Vector3D u) { return sqrt (u.x * u.x + u.y * u.y + u.z * u.z); } #endif /****************************************************************************** * * Calculate the blob shape. */ static void calc_blob(mirrorblobstruct *gp, int width, int height, int bump_array_size, float limit, double fade) { /* Loop variables */ int i, index, face; /* position of a node */ Vector3D node; Vector3D offset; Vector3D bump_vector; int dist; /* Update position and strength of bumps used to distort the blob */ for (i = 0; i < bumps; i++) { gp->bump_data[i].vx += gp->bump_data[i].mx*(gp->bump_data[i].cx - gp->bump_data[i].ax); gp->bump_data[i].vy += gp->bump_data[i].my*(gp->bump_data[i].cy - gp->bump_data[i].ay); gp->bump_data[i].vpower += gp->bump_data[i].mpower * (gp->bump_data[i].cpower - gp->bump_data[i].power); gp->bump_data[i].vsize += gp->bump_data[i].msize * (gp->bump_data[i].csize - gp->bump_data[i].size); gp->bump_data[i].ax += 0.1 * gp->bump_data[i].vx; gp->bump_data[i].ay += 0.1 * gp->bump_data[i].vy; gp->bump_data[i].power += 0.1 * gp->bump_data[i].vpower; gp->bump_data[i].size += 0.1 * gp->bump_data[i].vsize; gp->bump_data[i].pos.x = 1.0 * sin(PI * gp->bump_data[i].ay) * cos(PI * gp->bump_data[i].ax); gp->bump_data[i].pos.y = 1.0 * cos(PI * gp->bump_data[i].ay); gp->bump_data[i].pos.z = 1.0 * sin(PI * gp->bump_data[i].ay) * sin(PI * gp->bump_data[i].ax); } /* Update calculate new position for each vertex based on an offset from * the initial position */ gp->blob_force = zero_vector; for (index = 0; index < gp->num_nodes; ++index) { node = gp->nodes[index].initial_position; gp->nodes[index].normal = node; offset = zero_vector; for ( i = 0; i < bumps; i++) { bump_vector = subtract(gp->bump_data[i].pos, node); dist = bump_array_size * dot(bump_vector, bump_vector) * gp->bump_data[i].size; if (dist < bump_array_size) { add(&offset, scale(node, gp->bump_data[i].power * gp->bump_shape[dist])); add(&gp->blob_force, scale(node, gp->bump_data[i].power * gp->bump_shape[dist])); } } add(&node, offset); node = scale(node, zoom); add(&node, gp->blob_center); if (do_walls) { if (node.z < -limit) node.z = -limit; if (node.z > limit) node.z = limit; dist = bump_array_size * (node.z + limit) * (node.z + limit) * 0.5; if (dist < bump_array_size) { node.x += (node.x - gp->blob_center.x) * gp->wall_shape[dist]; node.y += (node.y - gp->blob_center.y) * gp->wall_shape[dist]; gp->blob_force.z += (node.z + limit); } else { dist = bump_array_size * (node.z - limit) * (node.z - limit) * 0.5; if (dist < bump_array_size) { node.x += (node.x - gp->blob_center.x) * gp->wall_shape[dist]; node.y += (node.y - gp->blob_center.y) * gp->wall_shape[dist]; gp->blob_force.z -= (node.z - limit); } if (node.y < -limit) node.y = -limit; if (node.y > limit) node.y = limit; dist = bump_array_size * (node.y + limit) * (node.y + limit) * 0.5; if (dist < bump_array_size) { node.x += (node.x - gp->blob_center.x) * gp->wall_shape[dist]; node.z += (node.z - gp->blob_center.z) * gp->wall_shape[dist]; gp->blob_force.y += (node.y + limit); } else { dist = bump_array_size * (node.y - limit) * (node.y - limit) * 0.5; if (dist < bump_array_size) { node.x += (node.x - gp->blob_center.x) * gp->wall_shape[dist]; node.z += (node.z - gp->blob_center.z) * gp->wall_shape[dist]; gp->blob_force.y -= (node.y - limit); } } if (node.x < -limit) node.x = -limit; if (node.x > limit) node.x = limit; dist = bump_array_size * (node.x + limit) * (node.x + limit) * 0.5; if (dist < bump_array_size) { node.y += (node.y - gp->blob_center.y) * gp->wall_shape[dist]; node.z += (node.z - gp->blob_center.z) * gp->wall_shape[dist]; gp->blob_force.x += (node.x + limit); } else { dist = bump_array_size * (node.x - limit) * (node.x - limit) * 0.5; if (dist < bump_array_size) { node.y += (node.y - gp->blob_center.y) * gp->wall_shape[dist]; node.z += (node.z - gp->blob_center.z) * gp->wall_shape[dist]; gp->blob_force.x -= (node.x - limit); } } if (node.y < -limit) node.y = -limit; if (node.y > limit) node.y = limit; } } gp->dots[index] = node; } /* Determine the normal for each face */ for (face = 0; face < gp->num_faces; face++) { /* Use pointers to indexed nodes to help readability */ int index1 = gp->faces[face].node1; int index2 = gp->faces[face].node2; int index3 = gp->faces[face].node3; gp->faces[face].normal = cross(subtract(gp->dots[index2], gp->dots[index1]), subtract(gp->dots[index3], gp->dots[index1])); /* Add the normal for the face onto the normal for the verticies of the face */ add(&gp->nodes[index1].normal, gp->faces[face].normal); add(&gp->nodes[index2].normal, gp->faces[face].normal); add(&gp->nodes[index3].normal, gp->faces[face].normal); } /* Use the normal to set the colour and texture */ if (do_colour || do_texture) { for (index = 0; index < gp->num_nodes; ++index) { gp->normals[index] = normalise(gp->nodes[index].normal); if (do_colour) { gp->colours[index].red = (int)(255.0 * fabs(gp->normals[index].x)); gp->colours[index].green = (int)(255.0 * fabs(gp->normals[index].y)); gp->colours[index].blue = (int)(255.0 * fabs(gp->normals[index].z)); gp->colours[index].alpha = (int)(255.0 * fade); } if (do_texture) { if (offset_texture) { const float cube_size = 100.0; Vector3D eye = {0.0, 0.0, 50.0}; Vector3D eye_r = normalise(subtract(gp->dots[index], eye)); Vector3D reference = subtract(eye_r, scale(gp->normals[index], 2.0 * dot(eye_r, gp->normals[index]))); double x = 0.0; double y = 0.0; double n, n_min = 10000.0, sign = 1.0; if (fabs(reference.z) > 1e-9) { n = (cube_size - gp->dots[index].z) / reference.z; if (n < 0.0) { n = (-cube_size - gp->dots[index].z) / reference.z; sign = 3.0; } if (n > 0.0) { x = sign * (gp->dots[index].x + n * reference.x); y = sign * (gp->dots[index].y + n * reference.y); n_min = n; } } if (fabs(reference.x) > 1e-9) { n = (cube_size - gp->dots[index].x) / reference.x; sign = 1.0; if (n < 0.0) { n = (-cube_size - gp->dots[index].x) / reference.x; sign = -1.0; } if ((n > 0.0) && (n < n_min)) { x = sign * (2.0 * cube_size - (gp->dots[index].z + n * reference.z)); y = sign * x * (gp->dots[index].y + n * reference.y) / cube_size; n_min = n; } } if (fabs(reference.y) > 1e-9) { n = (cube_size - gp->dots[index].y) / reference.y; sign = 1.0; if (n < 0.0) { n = (-cube_size - gp->dots[index].y) / reference.y; sign = -1.0; } if ((n > 0.0) && (n < n_min)) { y = sign * (2.0 * cube_size -( gp->dots[index].z + n * reference.z)); x = sign * y * (gp->dots[index].x + n * reference.x) / cube_size; } } gp->tex_coords[index].x = 0.5 + x / (cube_size * 6.0); gp->tex_coords[index].y = 0.5 - y / (cube_size * 6.0); } else { gp->tex_coords[index].x = 0.5 * (1.0 + asin(gp->normals[index].x) / (0.5 * PI)); gp->tex_coords[index].y = -0.5 * (1.0 + asin(gp->normals[index].y) / (0.5 * PI)); } /* Adjust the texture co-ordinates to from range 0..1 to * 0..width or 0..height as appropriate */ gp->tex_coords[index].x *= gp->tex_width[gp->current_texture]; gp->tex_coords[index].y *= gp->tex_height[gp->current_texture]; } } } /* Update the center of the whole blob */ add(&gp->blob_velocity, scale (subtract (gp->blob_anchor, gp->blob_center), 1.0 / 80.0)); add(&gp->blob_velocity, scale (gp->blob_force, 0.01 / gp->num_nodes)); add(&gp->blob_center, scale(gp->blob_velocity, 0.5)); gp->blob_velocity = scale(gp->blob_velocity, 0.999); } static void draw_vertex(mirrorblobstruct *gp, int index) { if (do_colour) { glColor3ub(gp->colours[index].red, gp->colours[index].green, gp->colours[index].blue); } if (load_textures) { glTexCoord2fv(&gp->tex_coords[index].x); } glNormal3fv(&gp->normals[index].x); glVertex3fv(&gp->dots[index].x); } /****************************************************************************** * * Draw the blob shape. * */ static void draw_blob (mirrorblobstruct *gp) { int face; glMatrixMode(GL_MODELVIEW); glLoadIdentity(); /* glRotatef(current_device_rotation(), 0, 0, 1); */ /* Move down the z-axis. */ glTranslatef (0.0, 0.0, -4.0); gltrackball_rotate (gp->trackball); /* glColor4ub (255, 0, 0, 128); */ glBegin(GL_TRIANGLES); for (face = 0; face < gp->num_faces; face++) { draw_vertex(gp, gp->faces[face].node1); draw_vertex(gp, gp->faces[face].node2); draw_vertex(gp, gp->faces[face].node3); } glEnd(); #if 0 glBegin(GL_LINES); for (face = 0; face < gp->num_faces; face++) { if (gp->normals[gp->faces[face].node1].z > 0.0) { Vector3D end = gp->dots[gp->faces[face].node1]; glVertex3dv(&end); add(&end, scale(gp->normals[gp->faces[face].node1], 0.25)); glVertex3dv(&end); } } glEnd(); #endif glLoadIdentity(); } /****************************************************************************** * * Draw the background image simply map a texture onto a full screen quad. */ static void draw_background (ModeInfo *mi) { mirrorblobstruct *gp = &Mirrorblob[MI_SCREEN(mi)]; GLfloat rot = current_device_rotation(); glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); glEnable (GL_TEXTURE_2D); glDisable(GL_LIGHTING); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); /* Reset the projection matrix to make it easier to get the size of the quad * correct */ glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glRotatef (-rot, 0, 0, 1); /* if ((rot > 45 && rot < 135) || (rot < -45 && rot > -135)) { GLfloat s = MI_WIDTH(mi) / (GLfloat) MI_HEIGHT(mi); glScalef (s, 1/s, 1); } */ glOrtho(0.0, MI_WIDTH(mi), MI_HEIGHT(mi), 0.0, -1000.0, 1000.0); glBegin (GL_QUADS); glTexCoord2f (0.0, 0.0); glVertex2i (0, 0); glTexCoord2f (0.0, gp->tex_height[gp->current_texture]); glVertex2i (0, MI_HEIGHT(mi)); glTexCoord2f (gp->tex_width[gp->current_texture], gp->tex_height[gp->current_texture]); glVertex2i (MI_WIDTH(mi), MI_HEIGHT(mi)); glTexCoord2f (gp->tex_width[gp->current_texture], 0.0); glVertex2i (MI_WIDTH(mi), 0); glEnd(); glPopMatrix (); glMatrixMode (GL_MODELVIEW); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); } /****************************************************************************** * * Update the scene. */ static GLvoid draw_scene(ModeInfo * mi) { mirrorblobstruct *gp = &Mirrorblob[MI_SCREEN(mi)]; double fade = 0.0; double current_time; check_gl_error ("draw_scene"); mi->polygon_count = 0; glColor4f (1.0, 1.0, 1.0, 1.0); current_time = double_time(); switch (gp->state) { case INITIALISING: glColor4f (0.0, 0.0, 0.0, 1.0); fade = 1.0; break; case TRANSITIONING: fade = 1.0 - (current_time - gp->state_start_time) / fade_time; break; case LOADING: /* FALL-THROUGH */ case HOLDING: fade = 1.0; break; } /* Set the correct texture, when transitioning this ensures that the first draw * is the original texture (which has the new texture drawn over it with decreasing * transparency) */ if (load_textures) { glBindTexture(GL_TEXTURE_2D, gp->textures[gp->current_texture]); } glDisable (GL_DEPTH_TEST); if (do_paint_background) { glEnable (GL_TEXTURE_2D); if (motion_blur > 0.0) { glClear(GL_DEPTH_BUFFER_BIT); glEnable (GL_BLEND); glColor4f (1.0, 1.0, 1.0, motion_blur); } else { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } draw_background (mi); mi->polygon_count++; /* When transitioning between two images paint the new image over the old * image with a varying alpha value to get a smooth fade. */ if (gp->state == TRANSITIONING) { glEnable (GL_BLEND); /* Select the texture to transition to */ glBindTexture (GL_TEXTURE_2D, gp->textures[1 - gp->current_texture]); glColor4f (1.0, 1.0, 1.0, 1.0 - fade); draw_background (mi); mi->polygon_count++; /* Select the original texture to draw the blob */ glBindTexture (GL_TEXTURE_2D, gp->textures[gp->current_texture]); } /* Clear the depth buffer bit so the backgound is behind the blob */ glClear(GL_DEPTH_BUFFER_BIT); } else if (motion_blur > 0.0) { glEnable (GL_BLEND); glColor4f (0.0, 0.0, 0.0, motion_blur); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glTranslatef (0.0, 0.0, -4.0); glRectd (-10.0, -10.0, 10.0, 10.0); if (wireframe) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); } glClear(GL_DEPTH_BUFFER_BIT); } else { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } if (!do_texture) { fade = 1.0; glDisable (GL_TEXTURE_2D); } calc_blob(gp, MI_WIDTH(mi), MI_HEIGHT(mi), BUMP_ARRAY_SIZE, 2.5, fade * blend); set_blob_gl_state(fade * blend); if (blend < 1.0) { /* Disable the colour chanels so that only the depth buffer is updated */ glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); draw_blob(gp); mi->polygon_count += gp->num_faces; glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); } glDepthFunc(GL_LEQUAL); draw_blob(gp); mi->polygon_count += gp->num_faces; /* While transitioning between images draw a second blob with a modified * alpha value. */ if (load_textures && (hold_time > 0)) { switch (gp->state) { case INITIALISING: if (!gp->waiting_for_image_p) { gp->state = HOLDING; } break; case HOLDING: if ((current_time - gp->state_start_time) > hold_time) { grab_texture(mi, 1 - gp->current_texture); gp->state = LOADING; } break; case LOADING: /* Once the image has loaded move to the TRANSITIONING STATE */ if (!gp->waiting_for_image_p) { gp->state = TRANSITIONING; /* Get the time again rather than using the current time so * that the time taken by the grab_texture function is not part * of the fade time */ gp->state_start_time = double_time(); } break; case TRANSITIONING: /* If the blob is textured draw over existing blob to fade between * images */ if (do_texture) { /* Select the texture to transition to */ glBindTexture (GL_TEXTURE_2D, gp->textures[1 - gp->current_texture]); glEnable (GL_BLEND); /* If colour is enabled update the alpha data in the buffer and * use that in the blending since the alpha of the incomming * verticies will not be correct */ if (do_colour) { glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE); glClearColor(0.0, 0.0, 0.0, (1.0 - fade) * blend); glClear(GL_COLOR_BUFFER_BIT); glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); glBlendFunc(GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA); } else { glColor4f (0.9, 0.9, 1.0, (1.0 - fade) * blend); } draw_blob (gp); mi->polygon_count += gp->num_faces; if (do_colour) { /* Restore the 'standard' blend functions. */ glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } } if ((current_time - gp->state_start_time) > fade_time) { gp->state = HOLDING; gp->state_start_time = current_time; gp->current_texture = 1 - gp->current_texture; } break; } } } /****************************************************************************** * * XMirrorblob screen update entry */ ENTRYPOINT void draw_mirrorblob(ModeInfo * mi) { mirrorblobstruct *gp = &Mirrorblob[MI_SCREEN(mi)]; Display *display = MI_DISPLAY(mi); Window window = MI_WINDOW(mi); if (!gp->glx_context) return; /* Wait for the first image; for subsequent images, load them in the background while animating. */ if (gp->waiting_for_image_p && gp->first_image_p) return; glXMakeCurrent(display, window, *gp->glx_context); draw_scene(mi); if (mi->fps_p) do_fps (mi); glFinish(); glXSwapBuffers(display, window); } /****************************************************************************** * * XMirrorblob screen resize entry */ ENTRYPOINT void reshape_mirrorblob(ModeInfo *mi, int width, int height) { glViewport( 0, 0, MI_WIDTH(mi), MI_HEIGHT(mi) ); reset_projection(width, height); } /**************************************************************************** * * Handle Mouse events */ ENTRYPOINT Bool mirrorblob_handle_event (ModeInfo * mi, XEvent * event) { mirrorblobstruct *gp = &Mirrorblob[MI_SCREEN (mi)]; if (event->xany.type == ButtonPress && event->xbutton.button == Button4) { zoom *= 1.1; return True; } else if (event->xany.type == ButtonPress && event->xbutton.button == Button5) { zoom *= 0.9; return True; } else if (gltrackball_event_handler (event, gp->trackball, MI_WIDTH (mi), MI_HEIGHT (mi), &gp->button_down)) { return True; } else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event)) { gp->state_start_time = 0; gp->state = HOLDING; return True; } return False; } /****************************************************************************** * * XMirrorblob initialise entry */ ENTRYPOINT void init_mirrorblob(ModeInfo * mi) { int screen = MI_SCREEN(mi); mirrorblobstruct *gp; MI_INIT(mi, Mirrorblob); gp = &Mirrorblob[screen]; gp->window = MI_WINDOW(mi); if ((gp->glx_context = init_GL(mi)) != NULL) { reshape_mirrorblob(mi, MI_WIDTH(mi), MI_HEIGHT(mi)); initialize_gl(mi, MI_WIDTH(mi), MI_HEIGHT(mi)); } else { MI_CLEARWINDOW(mi); } gp->trackball = gltrackball_init(False); initialise_blob(gp, MI_WIDTH(mi), MI_HEIGHT(mi), BUMP_ARRAY_SIZE); gp->state = INITIALISING; gp->state_start_time = double_time(); gp->first_image_p = True; } /****************************************************************************** * * XMirrorblob cleanup entry */ ENTRYPOINT void free_mirrorblob(ModeInfo * mi) { mirrorblobstruct *gp = &Mirrorblob[MI_SCREEN(mi)]; int i; if (!gp->glx_context) return; glXMakeCurrent (MI_DISPLAY(mi), MI_WINDOW(mi), *gp->glx_context); if (gp->nodes) free(gp->nodes); if (gp->faces) free(gp->faces); if (gp->bump_data) free(gp->bump_data); if (gp->colours) free(gp->colours); if (gp->tex_coords) free(gp->tex_coords); if (gp->dots) free(gp->dots); if (gp->normals) free(gp->normals); if (gp->wall_shape) free(gp->wall_shape); if (gp->bump_shape) free(gp->bump_shape); if (gp->trackball) gltrackball_free (gp->trackball); for (i = 0; i < NUM_TEXTURES; i++) if (gp->textures[i]) glDeleteTextures(1, &gp->textures[i]); } XSCREENSAVER_MODULE ("MirrorBlob", mirrorblob) #endif