/* * 3-D Helixpaint. Inspired by Electropaint. * * gcc -Wall `pkg-config --libs --cflags gtkglext-1.0` -o helix helix.c */ #include #include #include #include #include #include typedef struct _HelixIndexZ HelixIndexZ; typedef struct _HelixAttribs HelixAttribs; typedef struct _HelixElement HelixElement; typedef struct _HelixWindow HelixWindow; struct _HelixIndexZ { gdouble z_depth; gint index; }; struct _HelixAttribs { gdouble armlen; gdouble height; gdouble size; gdouble angle; gdouble roll; gdouble wave; gdouble wag; gint vertices; }; static HelixAttribs attr_zero = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0 }; struct _HelixElement { gdouble z_depth; /* for proper alpha blending */ gdouble transform[16]; /* state for drawing */ HelixAttribs current; /* state at next tick */ HelixAttribs absolute; HelixAttribs relative; }; struct _HelixWindow { GtkWidget *window; GtkWidget *drawing_area; GTimer *timer; guint timeout_id; gboolean visibility; gint frames; gdouble fps; gdouble ticks; gdouble tick_fraction; gdouble view_rot_x; gdouble view_rot_y; gdouble view_rot_z; gdouble angle; HelixElement *master_element; gint nelements; HelixElement *elements; }; static void helix_elements_configure (HelixWindow *hwin) { gint i; hwin->master_element->absolute = attr_zero; hwin->master_element->relative = attr_zero; hwin->master_element->absolute.vertices = 4; for (i = 0; i < hwin->nelements; i++) { HelixElement *elem = &hwin->elements[i]; elem->absolute = attr_zero; elem->relative = attr_zero; elem->absolute.vertices = 4; } } static void helix_elements_propagate (HelixWindow *hwin) { HelixElement *elem, *prev; static gint count = 0; gdouble time = hwin->ticks + hwin->tick_fraction; gint i; for (i = 0; i < hwin->nelements; i++) { elem = &hwin->elements[i]; if (i < hwin->nelements - 1) prev = &hwin->elements[i+1]; else prev = hwin->master_element; /* copy behaviour from previous element */ elem->absolute = prev->absolute; elem->relative = prev->relative; } /* setup master element */ count = (count + 1) % 250; elem = hwin->master_element; if (count < 200) { if (elem->relative.angle > -20) elem->relative.angle -= 1; } else { if (elem->relative.angle < 0) elem->relative.angle += 1; } elem->absolute.size = 0.06 + sin (time/G_PI * 2) * 0.01; elem->absolute.armlen = 0.4; elem->absolute.roll = 90; elem->absolute.wave = 90 + sin (time/G_PI*3) * 20; elem->relative.wave = 90; elem->relative.armlen = 0.0075; // + sin (time/G_PI) * 0.05; // elem->relative.wag = 10 * sin (time/G_PI * 1.05); elem->relative.height = 1.5 / hwin->nelements; } static void helix_attribs_mix (gdouble prev_factor, HelixAttribs *prev, gdouble this_factor, HelixAttribs *this, HelixAttribs *accu) { accu->armlen = prev_factor * prev->armlen + this_factor * this->armlen; accu->angle = prev_factor * prev->angle + this_factor * this->angle; accu->wag = prev_factor * prev->wag + this_factor * this->wag; accu->height = prev_factor * prev->height + this_factor * this->height; accu->wave = prev_factor * prev->wave + this_factor * this->wave; accu->size = prev_factor * prev->size + this_factor * this->size; accu->roll = prev_factor * prev->roll + this_factor * this->roll; accu->vertices = (gint) (prev_factor * prev->vertices + this_factor * this->vertices); } static int helix_z_order_compare (const void *a, const void *b) { HelixIndexZ *a1 = (HelixIndexZ *) a; HelixIndexZ *b1 = (HelixIndexZ *) b; if (a1->z_depth < b1->z_depth) return -1; else if (a1->z_depth > b1->z_depth) return 1; else return 0; } /* draw a frame */ static gboolean helix_frame_draw (GtkWidget *widget, GdkEventExpose *event, gpointer data) { HelixWindow *hwin = (HelixWindow *) data; GdkGLContext *glcontext = gtk_widget_get_gl_context (widget); GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (widget); GLfloat color [4] = { 0.0, 0.0, 0.0, 0.5 }; GLfloat specular [4] = { 0.5, 0.5, 0.5, 1.0 }; GLfloat shininess [1] = { 70.0 }; gint i, j; static gdouble total_height = 0; static gboolean cleared = 0; HelixIndexZ *ordering_z = g_new0 (HelixIndexZ, hwin->nelements); HelixAttribs accu_rel = attr_zero; /*** OpenGL BEGIN ***/ if (!gdk_gl_drawable_gl_begin (gldrawable, glcontext)) return FALSE; if (!cleared && (cleared = 1)) glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); #if 0 /* fade old picture */ glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color); glRectf (-5, -5, 5, 5); glClear (GL_DEPTH_BUFFER_BIT); #else glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); #endif glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, specular); glMaterialfv (GL_FRONT_AND_BACK, GL_SHININESS, shininess); glPushMatrix (); glRotatef (hwin->view_rot_x, 1.0, 0.0, 0.0); glRotatef (hwin->view_rot_y, 0.0, 1.0, 0.0); glRotatef (hwin->view_rot_z, 0.0, 0.0, 1.0); glTranslatef (0.0, 0.0, - total_height / 2.0); /* * calculate the current state of the elements * and store their calculated transformation matrix */ for (j = 0; j < hwin->nelements - 1; j++) { HelixElement *elem = &hwin->elements[j]; HelixElement *prev = &hwin->elements[j+1]; HelixAttribs curr_rel; gdouble dt = hwin->tick_fraction; /* interpolate the relative and absolute attributes */ helix_attribs_mix (1.0 - dt, &elem->relative, dt, &prev->relative, &curr_rel); helix_attribs_mix (1.0 - dt, &elem->absolute, dt, &prev->absolute, &elem->current); /* accumulate the current relative attributes */ helix_attribs_mix (1.0, &accu_rel, 1.0, &curr_rel, &accu_rel); /* * the used values are the accumulated relative plus * the current absolute values. */ helix_attribs_mix (1.0, &elem->current, 1.0, &accu_rel, &elem->current); glPushMatrix (); glRotated (elem->current.angle, 0.0, 0.0, 1.0); glTranslated (0.0, elem->current.armlen, elem->current.height); glRotated (elem->current.roll, 0.0, 1.0, 0.0); glRotated (elem->current.wave, 1.0, 0.0, 0.0); /* wag = 0: flat side towards the center */ glRotated (elem->current.wag + 180.0 / elem->current.vertices, 0.0, 0.0, 1.0); glScaled (elem->current.size, elem->current.size, elem->current.size); /* * calculate the depth of the center of a element in world coords. * the center is (0,0,0,1) (homogenous), thus * M * (0,0,0,1) = (m[12], m[13], m[14], m[15]) * and unhomogenizing gives z = m[14]/m[15]. */ glGetDoublev (GL_MODELVIEW_MATRIX, elem->transform); elem->z_depth = elem->transform[14] / elem->transform[15]; ordering_z[j].index = j; ordering_z[j].z_depth = elem->z_depth; glPopMatrix (); } /* * sort the indicies of the elements according to their distance * to the viewer */ qsort (ordering_z, hwin->nelements - 1, sizeof (HelixIndexZ), helix_z_order_compare); /* * finally draw them to the screen in the correct order. */ for (j = 0; j < hwin->nelements - 1; j++) { HelixElement *elem = &hwin->elements[ordering_z[j].index]; gdouble depth; glLoadMatrixd (elem->transform); /* insane normalisation */ depth = - (elem->z_depth + 9) / 2; #if 1 color[0] = 1 - depth; color[1] = 0.0; color[2] = depth; color[3] = 1.0; #else color[0] = j * 0.7 / hwin->nelements; color[1] = 0.0; color[2] = 1.0 - j * 1.0 / hwin->nelements; color[3] = 1.0; #endif glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color); glBegin(GL_POLYGON); /* clockwise to make the normal point into the correct direction */ for (i = elem->current.vertices - 1; i >= 0; i--) { glVertex3f (sin (2.0 * G_PI * i / elem->current.vertices), cos (2.0 * G_PI * i / elem->current.vertices), 0.0); } glEnd (); } glPopMatrix (); if (gdk_gl_drawable_is_double_buffered (gldrawable)) gdk_gl_drawable_swap_buffers (gldrawable); else glFlush (); gdk_gl_drawable_gl_end (gldrawable); /*** OpenGL END ***/ total_height = accu_rel.height; #if 0 /* interaction with gdk drawing functions? */ gdk_draw_line (GTK_WIDGET (hwin->drawing_area)->window, GTK_WIDGET (hwin->drawing_area)->style->white_gc, 0, 0, 100, 100); #endif g_free (ordering_z); return TRUE; } static gboolean helix_frame_next (HelixWindow *hwin) { gdouble ticks; ticks = 8 * g_timer_elapsed (hwin->timer, NULL); if (ticks - hwin->ticks > 1.0) { helix_elements_propagate (hwin); hwin->ticks = rint (ticks); } hwin->tick_fraction = ticks - hwin->ticks; if (hwin->visibility) gtk_widget_queue_draw (hwin->drawing_area); return TRUE; } /* new window size or exposure */ static gboolean helix_configure_event (GtkWidget *widget, GdkEventConfigure *event, gpointer data) { GdkGLContext *glcontext = gtk_widget_get_gl_context (widget); GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (widget); GLfloat h = (GLfloat) (widget->allocation.height) / (GLfloat) (widget->allocation.width); /*** OpenGL BEGIN ***/ if (!gdk_gl_drawable_gl_begin (gldrawable, glcontext)) return FALSE; glViewport (0, 0, widget->allocation.width, widget->allocation.height); glMatrixMode (GL_PROJECTION); glLoadIdentity (); glFrustum (MIN (-.5, -.5 / h), MAX (.5, .5 / h), MIN (-.5, -.5 * h), MAX (.5, .5 * h), 5.0, 60.0); glMatrixMode (GL_MODELVIEW); glLoadIdentity (); glTranslatef (0.0, 0.0, -10); gdk_gl_drawable_gl_end (gldrawable); /*** OpenGL END ***/ return TRUE; } static gboolean helix_visibility_update (GtkWidget *widget, GdkEventAny *event, gpointer data) { HelixWindow *hwin = (HelixWindow *) data; switch (event->type) { case GDK_MAP: hwin->visibility = TRUE; break; case GDK_UNMAP: hwin->visibility = FALSE; break; case GDK_VISIBILITY_NOTIFY: hwin->visibility = ((GdkEventVisibility *) event)->state != GDK_VISIBILITY_FULLY_OBSCURED; break; default: g_printerr ("Unknown event in helix_visibility_update!\n"); hwin->visibility = TRUE; break; } return TRUE; } /* change view angle, exit upon ESC */ static gboolean helix_key_press_event (GtkWidget *widget, GdkEventKey *event, gpointer data) { static gboolean fullscreen = FALSE; HelixWindow *hwin = (HelixWindow *) data; switch (event->keyval) { case GDK_z: hwin->view_rot_z += 5.0; break; case GDK_Z: hwin->view_rot_z -= 5.0; break; case GDK_Up: hwin->view_rot_x += 5.0; break; case GDK_Down: hwin->view_rot_x -= 5.0; break; case GDK_Left: hwin->view_rot_y += 5.0; break; case GDK_Right: hwin->view_rot_y -= 5.0; break; case GDK_F11: case GDK_f: case GDK_F: if (fullscreen) gtk_window_unfullscreen (GTK_WINDOW (hwin->window)); else gtk_window_fullscreen (GTK_WINDOW (hwin->window)); fullscreen = !fullscreen; break; case GDK_Escape: case GDK_q: case GDK_Q: gtk_main_quit (); break; default: return FALSE; } return TRUE; } static void helix_init_gl (GtkWidget *widget, gpointer data) { GdkGLContext *glcontext = gtk_widget_get_gl_context (widget); GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (widget); static GLfloat pos[4] = {5.0, 5.0, 15.0, 0.0}; static GLfloat white[4] = {1.0, 1.0, 1.0, 1.0}; /*** OpenGL BEGIN ***/ if (!gdk_gl_drawable_gl_begin (gldrawable, glcontext)) return; glLightfv (GL_LIGHT0, GL_POSITION, pos); glLightfv (GL_LIGHT0, GL_DIFFUSE, white); glLightfv (GL_LIGHT0, GL_SPECULAR, white); glEnable (GL_LIGHTING); glEnable (GL_LIGHT0); glEnable (GL_DEPTH_TEST); glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable (GL_BLEND); glEnable (GL_NORMALIZE); glLightModeli (GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE); #if 0 g_printerr ("\n"); g_printerr ("GL_RENDERER = %s\n", (char *) glGetString (GL_RENDERER)); g_printerr ("GL_VERSION = %s\n", (char *) glGetString (GL_VERSION)); g_printerr ("GL_VENDOR = %s\n", (char *) glGetString (GL_VENDOR)); g_printerr ("GL_EXTENSIONS = %s\n", (char *) glGetString (GL_EXTENSIONS)); g_printerr ("\n"); #endif gdk_gl_drawable_gl_end (gldrawable); /*** OpenGL END ***/ } HelixWindow * helix_setup_ui () { GdkGLConfig *glconfig; GtkWidget *vbox; HelixWindow *hwin; hwin = g_new0 (HelixWindow, 1); hwin->view_rot_x = -50; /* create timer */ if (hwin->timer == NULL) hwin->timer = g_timer_new (); g_timer_start (hwin->timer); hwin->fps = 50; hwin->nelements = 54; hwin->elements = g_new0 (HelixElement, hwin->nelements); hwin->master_element = g_new0 (HelixElement, 1); helix_elements_configure (hwin); /* * Configure OpenGL-capable visual. */ /* Try double-buffered visual */ glconfig = gdk_gl_config_new_by_mode (GDK_GL_MODE_RGB | GDK_GL_MODE_DEPTH | GDK_GL_MODE_DOUBLE); if (glconfig == NULL) { g_printerr ("*** Cannot find the double-buffered visual.\n"); g_printerr ("*** Trying single-buffered visual.\n"); /* Try single-buffered visual */ glconfig = gdk_gl_config_new_by_mode (GDK_GL_MODE_RGB | GDK_GL_MODE_DEPTH); if (glconfig == NULL) { g_printerr ("*** No appropriate OpenGL-capable visual found.\n"); exit (1); } } /* * Top-level window. */ hwin->window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (hwin->window), "Helix"); /* Get automatically redrawn if any of their children changed allocation. */ gtk_container_set_reallocate_redraws (GTK_CONTAINER (hwin->window), TRUE); g_signal_connect (G_OBJECT (hwin->window), "delete_event", G_CALLBACK (gtk_main_quit), NULL); /* * VBox. */ vbox = gtk_vbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (hwin->window), vbox); gtk_widget_show (vbox); /* * Drawing area for drawing OpenGL scene. */ hwin->drawing_area = gtk_drawing_area_new (); gtk_widget_set_size_request (hwin->drawing_area, 600, 600); /* Set OpenGL-capability to the widget. */ gtk_widget_set_gl_capability (hwin->drawing_area, glconfig, NULL, TRUE, GDK_GL_RGBA_TYPE); gtk_widget_add_events (hwin->drawing_area, GDK_VISIBILITY_NOTIFY_MASK); g_signal_connect_after (G_OBJECT (hwin->drawing_area), "realize", G_CALLBACK (helix_init_gl), hwin); g_signal_connect (G_OBJECT (hwin->drawing_area), "configure_event", G_CALLBACK (helix_configure_event), NULL); g_signal_connect (G_OBJECT (hwin->drawing_area), "expose_event", G_CALLBACK (helix_frame_draw), hwin); g_signal_connect (G_OBJECT (hwin->drawing_area), "map_event", G_CALLBACK (helix_visibility_update), hwin); g_signal_connect (G_OBJECT (hwin->drawing_area), "unmap_event", G_CALLBACK (helix_visibility_update), hwin); g_signal_connect (G_OBJECT (hwin->drawing_area), "visibility_notify_event", G_CALLBACK (helix_visibility_update), hwin); g_signal_connect (G_OBJECT (hwin->window), "key_press_event", G_CALLBACK (helix_key_press_event), hwin); gtk_box_pack_start (GTK_BOX (vbox), hwin->drawing_area, TRUE, TRUE, 0); gtk_widget_show (hwin->drawing_area); /* * Show window. */ gtk_widget_show (hwin->window); /* * Timeout function to maintain framerate */ hwin->timeout_id = g_timeout_add_full (GDK_PRIORITY_REDRAW, 1000 / hwin->fps, (GSourceFunc) helix_frame_next, hwin, NULL); return hwin; } int main (int argc, char *argv[]) { HelixWindow *hwin; gtk_init (&argc, &argv); gtk_gl_init (&argc, &argv); hwin = helix_setup_ui (); gtk_main (); return 0; }