// -*- C++ -*- /* * Gnome Chemistry Utils * gcu/glview.cc * * Copyright (C) 2006 Jean Bréfort * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 * USA */ #include "config.h" #include #include #include #include #include #include #include #include #include #include static GdkGLConfig *glconfig = NULL; double DefaultPsi = 70.; double DefaultTheta = 10.; double DefaultPhi = -90.; using namespace gcu; // Callbacks static bool on_init(GtkWidget *widget, GLView* View) { View->Init (); return true; } bool on_reshape(GtkWidget *widget, GdkEventConfigure *event, GLView* View) { View->Reshape (); return true; } static bool on_draw(GtkWidget *widget, GdkEventExpose *event, GLView* View) { /* Draw only last expose. */ if (event->count > 0) return TRUE; View->Draw (); return true; } static bool on_motion(GtkWidget *widget, GdkEventMotion *event, GLView* View) { View->OnMotion (event); return true; } static bool on_pressed(GtkWidget *widget, GdkEventButton *event, GLView* View) { return View->OnPressed (event); } // GLView implementation GLView::GLView (GLDocument* pDoc) { m_bInit = false; m_Doc = pDoc; m_nGLList = 0; m_Red = m_Green = m_Blue = 0.; m_Alpha = 1.; m_Angle = 10.; SetRotation (DefaultPsi, DefaultTheta, DefaultPhi); /* Create new OpenGL widget. */ if (glconfig == NULL) { /* Check if OpenGL is supported. */ if (!gdk_gl_query_extension()) { g_print("\n*** OpenGL is not supported.\n"); exit(1); } /* Configure OpenGL-capable visual. */ /* Try double-buffered visual */ glconfig = gdk_gl_config_new_by_mode (GdkGLConfigMode (GDK_GL_MODE_RGB | GDK_GL_MODE_DEPTH | GDK_GL_MODE_DOUBLE)); if (glconfig == NULL) { g_print("*** Cannot find the double-buffered visual.\n"); exit(1); } } /* create new OpenGL widget */ m_pWidget = GTK_WIDGET(gtk_drawing_area_new()); /* Set OpenGL-capability to the widget. */ gtk_widget_set_gl_capability(m_pWidget, glconfig, NULL, TRUE, GDK_GL_RGBA_TYPE); gtk_widget_set_events(GTK_WIDGET(m_pWidget), GDK_EXPOSURE_MASK| GDK_BUTTON_MOTION_MASK| GDK_POINTER_MOTION_HINT_MASK| GDK_BUTTON_PRESS_MASK); // Connect signal handlers // Do initialization when widget has been realized. g_signal_connect (G_OBJECT (m_pWidget), "realize", G_CALLBACK (on_init), this); // When window is resized viewport needs to be resized also. g_signal_connect (G_OBJECT (m_pWidget), "configure_event", G_CALLBACK (on_reshape), this); // Redraw image when exposed. g_signal_connect (G_OBJECT (m_pWidget), "expose_event", G_CALLBACK (on_draw), this); // When moving mouse g_signal_connect (G_OBJECT (m_pWidget), "motion_notify_event", G_CALLBACK (on_motion), this); // When a mouse button is pressed g_signal_connect (G_OBJECT (m_pWidget), "button_press_event", G_CALLBACK (on_pressed), this); gtk_widget_show (GTK_WIDGET (m_pWidget)); } GLView::~GLView () { } void GLView::Init () { GdkGLContext *glcontext = gtk_widget_get_gl_context (m_pWidget); GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (m_pWidget); if (gdk_gl_drawable_gl_begin (gldrawable, glcontext)) { glEnable (GL_LIGHTING); glEnable (GL_LIGHT0); glEnable (GL_DEPTH_TEST); glEnable (GL_CULL_FACE); glEnable (GL_COLOR_MATERIAL); float shiny = 25.0, spec[4] = {1.0, 1.0, 1.0, 1.0}; glMaterialfv (GL_FRONT_AND_BACK, GL_SHININESS, &shiny); glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, spec); glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); m_bInit = true; gdk_gl_drawable_gl_end (gldrawable); Update (); } } void GLView::Reshape () { if (!m_bInit) return; float fAspect; GdkGLContext *glcontext = gtk_widget_get_gl_context (m_pWidget); GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (m_pWidget); if (gdk_gl_drawable_gl_begin (gldrawable, glcontext)) { if (m_pWidget->allocation.height) { fAspect = (GLfloat) m_pWidget->allocation.width / (GLfloat) m_pWidget->allocation.height; if (fAspect == 0.0) fAspect = 1.0; } else // don't divide by zero, not that we should ever run into that... fAspect = 1.0f; double x = m_Doc->GetMaxDist (); if (x == 0) x = 1; glViewport (0,0, m_pWidget->allocation.width, m_pWidget->allocation.height); if (fAspect > 1.0) { m_Height = x * (1 - tan (m_Angle / 360 * M_PI)); m_Width = m_Height * fAspect; } else { m_Width = x * (1 - tan (m_Angle / 360 * M_PI)); m_Height = m_Width / fAspect; } glMatrixMode (GL_PROJECTION); glLoadIdentity(); if (m_Angle > 0.) { m_Radius = (float) (x / sin (m_Angle / 360 * M_PI)) ; m_Near = m_Radius - x; m_Far = m_Radius + x; glFrustum (- m_Width, m_Width, - m_Height, m_Height, m_Near, m_Far); } else { m_Radius = 2 * x; m_Near = m_Radius - x; m_Far = m_Radius + x; glOrtho (- m_Width, m_Width, - m_Height, m_Height, m_Near, m_Far); } glMatrixMode (GL_MODELVIEW); glLoadIdentity (); glTranslatef (0, 0, -m_Radius); gdk_gl_drawable_gl_end (gldrawable); } } void GLView::Draw () { if (!m_bInit) return; GdkGLContext *glcontext = gtk_widget_get_gl_context (m_pWidget); GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (m_pWidget); if (gdk_gl_drawable_gl_begin (gldrawable, glcontext)) { glClearColor (m_Red, m_Green, m_Blue, m_Alpha); glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if (m_nGLList) { glPushMatrix (); glRotated (m_Psi, 0.0, 1.0, 0.0); glRotated (m_Theta, 0.0, 0.0, 1.0); glRotated (m_Phi, 0.0, 1.0, 0.0); glEnable (GL_BLEND); glCallList (m_nGLList); glDisable (GL_BLEND); glPopMatrix (); } gdk_gl_drawable_gl_end (gldrawable); /* Swap backbuffer to front */ gdk_gl_drawable_swap_buffers (gldrawable); } } void GLView::Update() { if (!m_bInit) return; GdkGLContext *glcontext = gtk_widget_get_gl_context (m_pWidget); GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (m_pWidget); if (gdk_gl_drawable_gl_begin (gldrawable, glcontext)) { if (m_nGLList) glDeleteLists(m_nGLList,1); m_nGLList = glGenLists(1); glNewList (m_nGLList, GL_COMPILE); m_Doc->Draw (); glEndList (); gdk_gl_drawable_gl_end (gldrawable); } Reshape (); Draw (); } void GLView::SetRotation(double psi, double theta, double phi) { m_Psi = psi; m_Theta = theta; m_Phi = phi; Matrix m (m_Psi / 180 * M_PI, m_Theta / 180 * M_PI, m_Phi / 180 * M_PI, euler); m_Euler = m; } bool GLView::OnPressed (GdkEventButton *event) { if (event->button == 1) { // beginning of drag, reset mouse position m_Lastx = event->x; m_Lasty = event->y; return true; } return false; } bool GLView::OnMotion(GdkEventMotion *event) { gint x, y; GdkModifierType state; if (event->is_hint) gdk_window_get_pointer (event->window, &x, &y, &state); else { x = (gint) event->x; y = (gint) event->y; state = (GdkModifierType) event->state; } if (state & GDK_BUTTON1_MASK) { if ((x == m_Lastx) && (y == m_Lasty)) return false; m_Doc->SetDirty (true); Rotate (x - m_Lastx, y - m_Lasty); m_Lastx = x; m_Lasty = y; gtk_widget_queue_draw_area(m_pWidget, 0, 0, m_pWidget->allocation.width, m_pWidget->allocation.height); } return true; } void GLView::Rotate (gdouble x, gdouble y) { gdouble z = sqrt (x*x + y*y); Matrix Mat (0, (y > 0) ? - acos (x/z) : acos (x/z), z * M_PI / 900., rotation); m_Euler = Mat * m_Euler; m_Euler.Euler (m_Psi, m_Theta, m_Phi); m_Psi /= M_PI / 180.; m_Theta /= M_PI / 180.; m_Phi /= M_PI / 180.; } void GLView::Print (GnomePrintContext *pc, gdouble width, gdouble height) { int Width = m_pWidget->allocation.width; int Height = m_pWidget->allocation.height; if (Width > width) { Height = (int) (Height * width / Width); Width = (int) width; } if (Height > height) { Width = (int) (Width * height / Height); Height = (int) height; } double matrix [6] = {Width, 0, 0, Height, (width - Width) / 2, (height - Height )/ 2}; int w= (int) (Width * 300. / 72.), h = (int) (Height * 300. / 72.); GdkPixbuf *pixbuf = BuildPixbuf (w, h); if (pixbuf) { gnome_print_gsave (pc); gnome_print_concat (pc, matrix); gnome_print_rgbimage (pc, (const guchar*) gdk_pixbuf_get_pixels (pixbuf), w, h, gdk_pixbuf_get_rowstride (pixbuf)); gnome_print_grestore (pc); g_object_unref (pixbuf); } } static gboolean do_save_image (const gchar *buf, gsize count, GError **error, gpointer data) { GnomeVFSHandle *handle = (GnomeVFSHandle*) data; GnomeVFSFileSize written = 0; GnomeVFSResult res; while (count) { res = gnome_vfs_write (handle, buf, count, &written); if (res != GNOME_VFS_OK) { g_set_error (error, g_quark_from_static_string ("gchemutils"), res, gnome_vfs_result_to_string (res)); return false; } count -= written; } return true; } void GLView::SaveAsImage (string const &filename, char const *type, map& options, unsigned width, unsigned height) { if (width == 0 || height == 0) return; GdkPixbuf *pixbuf = BuildPixbuf (width, height); if (pixbuf) { char const **keys = g_new0 (char const*, options.size () + 1); char const **values = g_new0 (char const*, options.size ()); GError *error = NULL; map::iterator i, iend = options.end (); int j = 0; for (i = options.begin (); i != iend; i++) { keys[j] = (*i).first.c_str (); values[j++] = (*i).second.c_str (); } GnomeVFSHandle *handle = NULL; if (gnome_vfs_create (&handle, filename.c_str (), GNOME_VFS_OPEN_WRITE, true, 0644) == GNOME_VFS_OK) { gdk_pixbuf_save_to_callbackv (pixbuf, do_save_image, handle, type, (char**) keys, (char**) values, &error); if (error) { fprintf (stderr, _("Unable to save image file: %s\n"), error->message); g_error_free (error); } gnome_vfs_close (handle); // hope there will be no error there } g_free (keys); g_free (values); g_object_unref (pixbuf); } } GdkPixbuf *GLView::BuildPixbuf (unsigned width, unsigned height) { GdkGLConfig *glconfig = gdk_gl_config_new_by_mode ( GdkGLConfigMode (GDK_GL_MODE_RGBA | GDK_GL_MODE_DEPTH)); GdkPixmap *pixmap = gdk_pixmap_new (NULL, width, height, 24); GdkGLPixmap *gl_pixmap = gdk_pixmap_set_gl_capability (pixmap, glconfig, NULL ); GdkGLDrawable * drawable = gdk_pixmap_get_gl_drawable (pixmap); GdkGLContext * context = gdk_gl_context_new (drawable, NULL, TRUE, GDK_GL_RGBA_TYPE); double aspect = (GLfloat) width / height; double x = m_Doc->GetMaxDist (), w, h; if (x == 0) x = 1; if (aspect > 1.0) { h = x * (1 - tan (m_Angle / 360 * M_PI)); w = h * aspect; } else { w = x * (1 - tan (m_Angle / 360 * M_PI)); h = w / aspect; } GdkPixbuf *pixbuf = NULL; if (gdk_gl_drawable_gl_begin (drawable, context)) { glEnable (GL_LIGHTING); glEnable (GL_LIGHT0); glEnable (GL_DEPTH_TEST); glEnable (GL_CULL_FACE); glEnable (GL_COLOR_MATERIAL); float shiny = 25.0, spec[4] = {1.0, 1.0, 1.0, 1.0}; glMaterialfv (GL_FRONT_AND_BACK, GL_SHININESS, &shiny); glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, spec); glViewport (0, 0, width, height); glMatrixMode (GL_PROJECTION); glLoadIdentity (); GLfloat radius, near, far; if (m_Angle > 0.) { radius = (float) (x / sin (m_Angle / 360 * M_PI)) ; near = radius - x; far = radius + x; glFrustum (- w, w, - h, h, near, far); } else { radius = 2 * x; near = radius - x; far = radius + x; glOrtho (- w, w, - h, h, near, far); } glMatrixMode (GL_MODELVIEW); glLoadIdentity (); glTranslatef (0, 0, -m_Radius); glClearColor (m_Red, m_Green, m_Blue, m_Alpha); glClearDepth (1.0); glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if (m_nGLList) { glPushMatrix (); glRotated (m_Psi, 0.0, 1.0, 0.0); glRotated (m_Theta, 0.0, 0.0, 1.0); glRotated (m_Phi, 0.0, 1.0, 0.0); glEnable (GL_BLEND); GetDoc ()->Draw(); glDisable (GL_BLEND); glPopMatrix (); } glFlush (); gdk_gl_drawable_gl_end (drawable); pixbuf = gdk_pixbuf_get_from_drawable (NULL, (GdkDrawable*) pixmap, NULL, 0, 0, 0, 0, -1, -1); } else if (m_bInit) { unsigned hstep, vstep; double dxStep, dyStep; unsigned char *tmp, *dest, *src, *dst; unsigned LineWidth, s = sizeof(int); if (m_pWidget->allocation.width & (s - 1)) LineWidth = ((~(s - 1)) & (m_pWidget->allocation.width * 3)) + s; else LineWidth = m_pWidget->allocation.width * 3; unsigned size = LineWidth * m_pWidget->allocation.height; int i, j; hstep = m_pWidget->allocation.width; vstep = m_pWidget->allocation.height; tmp = new unsigned char[size]; if (!tmp) goto osmesa; pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, false, 8, (int) width, (int) height); dest = gdk_pixbuf_get_pixels (pixbuf); int n, m, imax, jmax, rowstride = gdk_pixbuf_get_rowstride (pixbuf); imax = width / hstep; jmax = height / vstep; dxStep = ((double) hstep) / width * 2; dyStep = ((double) vstep) / height * 2; for (j = 0; j <= jmax; j++) { for (i = 0; i <= imax; i++) { GdkGLContext *glcontext = gtk_widget_get_gl_context(m_pWidget); GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable(m_pWidget); if (gdk_gl_drawable_gl_begin (gldrawable, glcontext)) { glMatrixMode (GL_PROJECTION); glLoadIdentity (); if (m_Angle > 0.) glFrustum (w * ( -1 + i * dxStep), w * ( -1 + (i + 1)* dxStep), h * ( 1 - (j + 1)* dyStep), h * ( 1 - j* dyStep), m_Near , m_Far); else glOrtho (w * ( -1 + i * dxStep), w * ( -1 + (i + 1)* dxStep), h * ( 1 - (j + 1)* dyStep), h * ( 1 - j* dyStep), m_Near , m_Far); glMatrixMode (GL_MODELVIEW); glLoadIdentity (); glTranslatef (0, 0, - m_Radius); glClearColor (m_Red, m_Green, m_Blue, m_Alpha); glClearDepth (1.0); glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if (m_nGLList) { glPushMatrix (); glRotated (m_Psi, 0.0, 1.0, 0.0); glRotated (m_Theta, 0.0, 0.0, 1.0); glRotated (m_Phi, 0.0, 1.0, 0.0); glEnable (GL_BLEND); GetDoc ()->Draw(); glDisable (GL_BLEND); glPopMatrix (); } glFlush (); gdk_gl_drawable_gl_end (gldrawable); glPixelStorei (GL_PACK_ALIGNMENT, s); glReadBuffer (GL_BACK_LEFT); glReadPixels (0, 0, m_pWidget->allocation.width, m_pWidget->allocation.height, GL_RGB, GL_UNSIGNED_BYTE, tmp); // copy the data to the pixbuf. // linesize m = (i < imax)? hstep * 3: (width - imax * hstep) * 3; src = tmp + (vstep - 1) * LineWidth; dst = dest + j * vstep * rowstride + i * hstep * 3; for (n = 0; n < (int) ((j < jmax)? vstep: height - jmax * vstep); n++) { memcpy (dst, src, m); src -= LineWidth; dst += rowstride; } } else { g_object_unref (pixbuf); pixbuf = NULL; goto osmesa; } } } delete [] tmp; } else { osmesa: g_warning ("Off-screen rendering not supported in this context"); // TODO: implement rendering using an exterbal program and osmesa } gdk_gl_context_destroy (context); gdk_gl_pixmap_destroy (gl_pixmap); // destroying pixmap gives a CRITICAL and destroying glconfig leeds to a crash. Update (); return pixbuf; }