// -*- C++ -*-
/*
* Gnome Chemistry Utils
* gcu/glview.cc
*
* Copyright (C) 2006 Jean Bréfort <jean.brefort@normalesup.org>
*
* 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 <gcu/application.h>
#include <gcu/gldocument.h>
#include <gcu/glview.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <gtk/gtkgl.h>
#include <libgnomevfs/gnome-vfs-ops.h>
#include <glib/gi18n-lib.h>
#include <cmath>
#include <cstring>
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<string, string>& 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<string, string>::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;
}
syntax highlighted by Code2HTML, v. 0.9.1