/*
 *  $Id: scars.c 29521 2026-02-23 10:24:50Z yeti-dn $
 *  Copyright (C) 2003-2026 David Necas (Yeti), Petr Klapetek.
 *  E-mail: yeti@gwyddion.net, klapetek@gwyddion.net.
 *
 *  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 Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include "config.h"
#include <glib/gi18n-lib.h>
#include <gtk/gtk.h>
#include <epoxy/gl.h>
#include <gwy.h>
#include "../preview.h"

#define RUN_MODES GWY_RUN_INTERACTIVE

enum {
    DDDVIEW_SIZE = 300,
    NTB_ROTATE = 0,
    NTB_SCALE = 1,
    NTB_COLOR = 2,
    ZSCALE_MIN = 0,
    ZSCALE_MAX = 100,
};

enum {
    PARAM_THETA,
    PARAM_PHI,
    PARAM_PSI,
    PARAM_BG_COLOR,
    PARAM_ZSCALE,
    PARAM_IMAGE_OVERLAY,
    PARAM_IMAGE_MASK,
};

typedef struct {
    GwyParams *params;
    GwyField *field;
    GwyNield *mask;
} ModuleArgs;

typedef struct {
    ModuleArgs *args;
    GtkWidget *dialog;
    GtkWidget *notebook;
    GwyParamTable *table_rotate;
    GwyParamTable *table_scale;
    GwyParamTable *table_color;
    GtkWidget *glarea;
    GwyGradient *gradient;
    GwyField *overlay;
    GwyField *mask;

    /* model-view-projection matrix */
    float mvp[16];

    /* GL objects */
    guint vao;
    guint program;
    guint mvp_location;
    guint position_index;
    guint color_index;

    gint ntriangles;

    gint mouse_begin_x;
    gint mouse_begin_y;
    GwyRGBA bg_color;
    gdouble zrange;
} ModuleGUI;

/* position and color information for each vertex */
typedef struct vertex_info {
    float position[3];
    float color[3];
} vertex_info;

static const GwyRGBA black = { 0.0, 0.0, 0.0, 1.0 };
static const GwyRGBA white = { 1.0, 1.0, 1.0, 1.0 };

static gboolean         module_register     (void);
static GwyParamDef*     define_module_params(void);
static void             ddd                 (GwyFile *data,
                                             GwyRunModeFlags mode);
static GwyDialogOutcome run_gui             (ModuleArgs *args,
                                             GwyFile *data,
                                             gint id);
static void             param_changed       (ModuleGUI *gui,
                                             gint id);
static void             preview             (gpointer user_data);

static void             sanitize_params     (GwyParams *params);

static void             glarea_realize      (GtkWidget *widget,
                                             ModuleGUI *gui);
static void             glarea_unrealize    (GtkWidget *widget,
                                             ModuleGUI *gui);
static gboolean         glarea_render       (GtkGLArea *area,
                                             GdkGLContext *context,
                                             ModuleGUI *gui);
static void             init_buffers        (guint position_index,
                                             guint color_index,
                                             guint *vao_out,
                                             vertex_info *vdata,
                                             gint vsize);
static gboolean         init_shaders        (guint *program_out,
                                             guint *mvp_location_out,
                                             guint *position_location_out,
                                             guint *color_location_out,
                                             GError **error);
static void             init_mvp            (float *res,
                                             float zscale);
static void             compute_mvp         (float *res,
                                             float phi,
                                             float theta,
                                             float psi,
                                             float zscale);
static gboolean         mouse_moved         (GtkWidget *widget,
                                             GdkEvent *event,
                                             ModuleGUI *gui);
static gboolean         button_clicked      (GtkWidget *widget,
                                             GdkEventButton *event,
                                             ModuleGUI *gui);
static gboolean         mask_filter         (GwyFile *data,
                                             gint id,
                                             gpointer user_data);
static gboolean         overlay_filter      (GwyFile *data,
                                             gint id,
                                             gpointer user_data);



static GwyModuleInfo module_info = {
    GWY_MODULE_ABI_VERSION,
    &module_register,
    N_("Simple 3D viewer"),
    "Petr Klapetek <klapetek@gwyddion.net>",
    "1.0",
    "David Nečas (Yeti) & Petr Klapetek",
    "2004",
};

GWY_MODULE_QUERY2(module_info, ddd)

static gboolean
module_register(void)
{
    gwy_image_func_register("ddd",
                            &ddd,
                            N_("/Basic Operations/3D view..."),
                            GWY_ICON_MARK_SCARS,
                            RUN_MODES,
                            GWY_MENU_FLAG_IMAGE,
                            N_("Mark horizontal scars (strokes)"));

    return TRUE;
}

static GwyParamDef*
define_module_params(void)
{
    static GwyParamDef *paramdef = NULL;

    if (paramdef)
        return paramdef;

    paramdef = gwy_param_def_new();
    gwy_param_def_set_function_name(paramdef, "ddd");
    gwy_param_def_add_double(paramdef, PARAM_THETA, "theta", _("Theta"), 0.0, 360, 30);
    gwy_param_def_add_double(paramdef, PARAM_PHI, "phi", _("Phi"), 0.0, 360, 30);
    gwy_param_def_add_double(paramdef, PARAM_PSI, "psi", _("Psi"), 0.0, 360, 60);
    gwy_param_def_add_color(paramdef, PARAM_BG_COLOR, "background_color", _("_Background color"), TRUE, white);
    gwy_param_def_add_double(paramdef, PARAM_ZSCALE, "zscale", _("Z scale"), ZSCALE_MIN, ZSCALE_MAX, 1);
    gwy_param_def_add_image_id(paramdef, PARAM_IMAGE_OVERLAY, "overlay", _("Overlay"));
    gwy_param_def_add_image_id(paramdef, PARAM_IMAGE_MASK, "mask", _("Mask"));

    return paramdef;
}

static void
ddd(GwyFile *data, GwyRunModeFlags mode)
{
    ModuleArgs args;
    GwyDialogOutcome outcome = GWY_DIALOG_PROCEED;
    GQuark mquark;
    gint id;

    g_return_if_fail(mode & RUN_MODES);
    gwy_data_browser_get_current(GWY_APP_FIELD, &args.field,
                                 GWY_APP_MASK_FIELD_KEY, &mquark,
                                 GWY_APP_MASK_FIELD, &args.mask,
                                 GWY_APP_FIELD_ID, &id,
                                 0);
    g_return_if_fail(args.field && mquark);

    args.params = gwy_params_new_from_settings(define_module_params());
    sanitize_params(args.params);
    if (mode == GWY_RUN_INTERACTIVE) {
        outcome = run_gui(&args, data, id);
        gwy_params_save_to_settings(args.params);
        if (outcome == GWY_DIALOG_CANCEL)
            goto end;
    }
end:
    g_object_unref(args.params);
}

static GwyDialogOutcome
run_gui(ModuleArgs *args, GwyFile *data, gint id)
{
    GtkWidget *hbox;
    GwyDialog *dialog;
    GwyParamTable *table;
    ModuleGUI gui;
    const gchar *name = NULL;

    gwy_clear1(gui);
    gui.args = args;
    gui.ntriangles = 0;
    gui.mouse_begin_x = 0;
    gui.mouse_begin_y = 0;
    gui.zrange = 1;

    name = gwy_file_get_palette(data, GWY_FILE_IMAGE, id);
    gui.gradient = gwy_gradients_get_gradient(name);

    gui.dialog = gwy_dialog_new(_("3D"));
    dialog = GWY_DIALOG(gui.dialog);
    gwy_dialog_add_stock_buttons(dialog, GWY_STOCK_OK, 0);
    hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
    gwy_dialog_add_content(dialog, hbox, TRUE, TRUE, 0);
 
     gui.glarea = gtk_gl_area_new();
    g_signal_connect(gui.glarea, "realize", G_CALLBACK(glarea_realize), &gui);
    g_signal_connect(gui.glarea, "unrealize", G_CALLBACK(glarea_unrealize), &gui);
    g_signal_connect(gui.glarea, "render", G_CALLBACK(glarea_render), &gui);

    gtk_widget_set_size_request(gui.glarea, DDDVIEW_SIZE, DDDVIEW_SIZE);	
    gtk_box_pack_start(GTK_BOX(hbox), gui.glarea, TRUE, TRUE, 0);

    gui.notebook = gtk_notebook_new();
    gtk_box_pack_start(GTK_BOX(hbox), gui.notebook, TRUE, TRUE, 0);

    table = gui.table_rotate = gwy_param_table_new(args->params);

    gwy_param_table_append_slider(table, PARAM_THETA);
    gwy_param_table_set_unitstr(table, PARAM_THETA, _("deg"));
    gwy_param_table_append_slider(table, PARAM_PHI);
    gwy_param_table_set_unitstr(table, PARAM_PHI, _("deg"));
    gwy_param_table_append_slider(table, PARAM_PSI);
    gwy_param_table_set_unitstr(table, PARAM_PSI, _("deg"));

    gtk_notebook_append_page(GTK_NOTEBOOK(gui.notebook), gwy_param_table_widget(table), gtk_label_new(_("Angles")));
    g_signal_connect_swapped(table, "param-changed", G_CALLBACK(param_changed), &gui);

    table = gui.table_scale = gwy_param_table_new(args->params);

    gwy_param_table_append_slider(table, PARAM_ZSCALE);

    gtk_notebook_append_page(GTK_NOTEBOOK(gui.notebook), gwy_param_table_widget(table), gtk_label_new(_("Scales")));
    g_signal_connect_swapped(table, "param-changed", G_CALLBACK(param_changed), &gui);

    table = gui.table_color = gwy_param_table_new(args->params);
    gwy_param_table_append_image_id(table, PARAM_IMAGE_OVERLAY);
    gwy_param_table_data_id_set_filter(table, PARAM_IMAGE_OVERLAY, overlay_filter, args->field, NULL);

    gwy_param_table_append_image_id(table, PARAM_IMAGE_MASK);
    gwy_param_table_data_id_set_filter(table, PARAM_IMAGE_MASK, mask_filter, args->field, NULL);

    gwy_param_table_append_color(table, PARAM_BG_COLOR);
    gwy_param_table_color_add_preset(table, PARAM_BG_COLOR, black, _("Black"));
    gwy_param_table_color_add_preset(table, PARAM_BG_COLOR, white, _("White"));

    gtk_notebook_append_page(GTK_NOTEBOOK(gui.notebook), gwy_param_table_widget(table), gtk_label_new(_("Colors")));
    g_signal_connect_swapped(table, "param-changed", G_CALLBACK(param_changed), &gui);

    gwy_dialog_set_preview_func(dialog, GWY_PREVIEW_IMMEDIATE, preview, &gui, NULL);

    gtk_widget_add_events(GTK_WIDGET(gui.glarea),
                             gtk_widget_get_events(gui.glarea)
                             | GDK_POINTER_MOTION_MASK
                             | GDK_POINTER_MOTION_HINT_MASK
                             | GDK_BUTTON1_MOTION_MASK
                             | GDK_BUTTON_PRESS_MASK
                             | GDK_BUTTON_RELEASE_MASK
                             | GDK_VISIBILITY_NOTIFY_MASK);

    g_signal_connect(GTK_GL_AREA(gui.glarea), "motion-notify-event", 
                     G_CALLBACK(mouse_moved), &gui);
    g_signal_connect(GTK_GL_AREA(gui.glarea), "button-press-event", 
                     G_CALLBACK(button_clicked), &gui);


    return gwy_dialog_run(dialog);
}

static gboolean
mask_filter(GwyFile *data, gint id, gpointer user_data)
{       
    GwyNield *othermask;
    GwyField *field = (GwyField*)user_data;
    
    if (!(othermask = gwy_file_get_image_mask(data, id)))
        return FALSE;
    return !gwy_nield_is_incompatible_with_field(othermask, field, GWY_DATA_MISMATCH_RES);
}

static gboolean
overlay_filter(GwyFile *data, gint id, gpointer user_data)
{   
    GwyField *otherfield, *field = (GwyField*)user_data;
        
    if (!(otherfield = gwy_file_get_image(data, id)))
        return FALSE;
    //if (otherfield == field) //we want to use our data as well for overlay
    //    return FALSE;
    return !gwy_field_is_incompatible(field, otherfield,
                                      GWY_DATA_MISMATCH_RES | GWY_DATA_MISMATCH_REAL | GWY_DATA_MISMATCH_LATERAL);
}  

static void
param_changed(ModuleGUI *gui, gint id)
{
    ModuleArgs *args = gui->args;
    GwyParams *params = gui->args->params;

    if (id < 0 || id == PARAM_BG_COLOR) {
        gui->bg_color = gwy_params_get_color(params, PARAM_BG_COLOR);
    }

    if (id < 0 || (id == PARAM_THETA || id == PARAM_PHI
                   || id == PARAM_PSI || id == PARAM_ZSCALE)) {
       gdouble theta = gwy_params_get_double(args->params, PARAM_THETA);
       gdouble phi = gwy_params_get_double(args->params, PARAM_PHI);
       gdouble psi = gwy_params_get_double(args->params, PARAM_PSI);
       gdouble zscale = gwy_params_get_double(args->params, PARAM_ZSCALE);

       gdouble realzscale = zscale*gui->zrange/gwy_field_get_xreal(args->field);

       compute_mvp(gui->mvp, theta, phi, psi, realzscale);
    }

    if (id < 0 || (id == PARAM_IMAGE_OVERLAY)) 
       gui->overlay = gwy_params_get_image(args->params, PARAM_IMAGE_OVERLAY);

    gtk_widget_queue_draw(gui->glarea);
}

static void
set_vortex(gdouble *data, gint col, gint row, gint xres, gint yres, 
           gdouble min, gdouble zshift, gdouble zscale,
           GwyGradient *gradient, vertex_info *vdata)
{
    GwyRGBA color;

    vdata->position[0] = (gdouble)(col - xres/2)/xres;  
    vdata->position[1] = (gdouble)(row - yres/2)/yres; 
    vdata->position[2] = (data[row*xres + col]-zshift)/zscale;
    gwy_gradient_get_color(gradient, (data[row*xres + col]-min)/zscale, &color);
    vdata->color[0] = color.r;
    vdata->color[1] = color.g;
    vdata->color[2] = color.b;
}

static void
glarea_realize(GtkWidget *widget, ModuleGUI *gui)
{
    ModuleArgs *args = gui->args;
    GdkGLContext *context;
    GError *error = NULL;
    vertex_info *vdata;
    gint ntriangles, i, vsize;
    gint xres, yres, col, row;
    gdouble *data, zshift, zrange, min, max;
    gdouble zscale = gwy_params_get_double(gui->args->params, PARAM_ZSCALE);
    gdouble realzscale;

    //FIXME: Documentation says that it is automatically called before emitting the GtkGLArea::render signal
    gtk_gl_area_make_current(GTK_GL_AREA(widget));

    if (gtk_gl_area_get_error(GTK_GL_AREA (widget)) != NULL)
        return;

    context = gtk_gl_area_get_context(GTK_GL_AREA(widget));

    if (!init_shaders(&gui->program,
                      &gui->mvp_location,
                      &gui->position_index,
                      &gui->color_index,
                      &error)) {
        gtk_gl_area_set_error(GTK_GL_AREA(gui->glarea), error);
        g_error_free(error);
    }

    xres = gwy_field_get_xres(args->field);
    yres = gwy_field_get_yres(args->field);
    data = gwy_field_get_data(args->field);
    zshift = gwy_field_mean(args->field);
    gwy_field_min_max(args->field, &min, &max);
    zrange = max - min;
    gui->zrange = zrange;

    realzscale = zscale*zrange/gwy_field_get_xreal(args->field);   
    compute_mvp(gui->mvp,
                gwy_params_get_double(gui->args->params, PARAM_PHI),
                gwy_params_get_double(gui->args->params, PARAM_THETA),
                gwy_params_get_double(gui->args->params, PARAM_PSI),
                realzscale);

    gui->ntriangles = 2*(xres-1)*(yres-1);

    vsize = 3*gui->ntriangles;
    vdata = g_new(vertex_info, vsize);

    i = 0;
    for (col = 0; col < (xres-1); col++) {
        for (row = 0; row < (yres-1); row++) {

            set_vortex(data, col, row, xres, yres, 
                       min, zshift, zrange, gui->gradient, vdata + i);
            i++;

            set_vortex(data, col + 1, row, xres, yres, 
                       min, zshift, zrange, gui->gradient, vdata + i);
            i++;

            set_vortex(data, col, row + 1, xres, yres, 
                       min, zshift, zrange, gui->gradient, vdata + i);
            i++;

            set_vortex(data, col, row + 1, xres, yres, 
                       min, zshift, zrange, gui->gradient, vdata + i);
            i++;

            set_vortex(data, col + 1, row, xres, yres, 
                       min, zshift, zrange, gui->gradient, vdata + i);
            i++;

            set_vortex(data, col + 1, row + 1, xres, yres, 
                       min, zshift, zrange, gui->gradient, vdata + i);
            i++;
         }
    }

    init_buffers(gui->position_index, gui->color_index, &gui->vao, vdata, vsize);

    g_free(vdata);
}

static void
glarea_unrealize(GtkWidget *widget, ModuleGUI *gui)
{
    //FIXME, more deallocation should be here
    gtk_gl_area_make_current(GTK_GL_AREA(widget));

    if (gtk_gl_area_get_error(GTK_GL_AREA(widget)) != NULL)
       return;

    glDeleteProgram(gui->program);
}

static void
init_buffers(guint position_index, guint color_index, guint *vao_out, vertex_info *vdata, gint vsize)
{
    guint vao, buffer;

    /* create a VAO to store the other buffers */
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);

    /* VBO that holds the vertex data */
    glGenBuffers(1, &buffer);
    glBindBuffer(GL_ARRAY_BUFFER, buffer);
    glBufferData(GL_ARRAY_BUFFER, vsize*sizeof(vertex_info), vdata, GL_STATIC_DRAW);
//    glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_data), vertex_data, GL_STATIC_DRAW);

    /* enable and set the position attribute */
    glEnableVertexAttribArray(position_index);
    glVertexAttribPointer(position_index, 3, GL_FLOAT, GL_FALSE,
                          sizeof(struct vertex_info),
                          (GLvoid *)(G_STRUCT_OFFSET(struct vertex_info, position)));

    /* enable and set the color attribute */
    glEnableVertexAttribArray(color_index);
    glVertexAttribPointer(color_index, 3, GL_FLOAT, GL_FALSE,
                          sizeof (struct vertex_info),
                          (GLvoid *)(G_STRUCT_OFFSET(struct vertex_info, color)));

    /* reset the state; we will re-enable the VAO when needed */
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    /* the VBO is referenced by the VAO */
    glDeleteBuffers(1, &buffer);

    if (vao_out != NULL)
        *vao_out = vao;
}

/* Create and compile a shader */
static guint
create_shader(int shader_type, const char *source, GError **error, guint *shader_out)
{
    guint shader = glCreateShader(shader_type);
    glShaderSource(shader, 1, &source, NULL);
    glCompileShader(shader);
    int status;

    glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
    if (status == GL_FALSE) {
        int log_len;
        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_len);
  
        char *buffer = g_malloc(log_len + 1);
        glGetShaderInfoLog(shader, log_len, NULL, buffer);

        printf("Error in shader compilation: %s\n", buffer);
        g_free(buffer);

        glDeleteShader(shader);
        shader = 0;
    }
 
    if (shader_out != NULL)
        *shader_out = shader;
  
    return shader != 0;
}


/* Initialize the shaders and link them into a program */
static gboolean
init_shaders(guint *program_out, guint *mvp_location_out,
             guint *position_location_out, guint *color_location_out, GError **error)
{
    GBytes *source;
    guint program = 0;
    guint mvp_location = 0;
    guint vertex = 0, fragment = 0;
    guint position_location = 0;
    guint color_location = 0;
    int status = 0;

    const char *vertexShaderSource = "#version 130\n"
      "in vec3 position;\n"
      "in vec3 color;\n"
      "uniform mat4 mvp;\n"
      "smooth out vec4 vertexColor;\n"
      "void main() {\n"
      "gl_Position = mvp * vec4(position, 1.0);\n"
      "vertexColor = vec4(color, 1.0);\n"
      "}\0";

    create_shader(GL_VERTEX_SHADER, vertexShaderSource, error, &vertex);
    if (vertex == 0) {
        printf("vertex shader not created\n");
        goto out;
    }

    const char *fragmentShaderSource = "#version 130\n"
      "smooth in vec4 vertexColor;\n"
      "out vec4 outputColor;\n"
      "void main() {\n"
      " outputColor = vertexColor;\n"
      "}\0";

    create_shader(GL_FRAGMENT_SHADER, fragmentShaderSource, error, &fragment);
 
    if (fragment == 0) {
        printf("fragment shader not created\n");
        goto out;
    }

    program = glCreateProgram();
    glAttachShader(program, vertex);
    glAttachShader(program, fragment);
    glLinkProgram(program);

    glGetProgramiv(program, GL_LINK_STATUS, &status);
    if (status == GL_FALSE) {
        int log_len;
        char *buffer;

        glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_len);

        buffer = g_malloc(log_len + 1);
        glGetProgramInfoLog(program, log_len, NULL, buffer);

        g_warning("Linking failure:\n%s", buffer);

        g_free(buffer);

        glDeleteProgram(program);
        program = 0;

        goto out;
    }

    /* get the location of the "mvp" uniform */
    mvp_location = glGetUniformLocation(program, "mvp");

    /* get the location of the "position" and "color" attributes */
    position_location = glGetAttribLocation(program, "position");
    color_location = glGetAttribLocation(program, "color");

    /* the individual shaders can be detached and destroyed */
    glDetachShader(program, vertex);
    glDetachShader(program, fragment);

    out:
    if (vertex != 0)
        glDeleteShader (vertex);
    if (fragment != 0)
        glDeleteShader (fragment);

    if (program_out != NULL)
        *program_out = program;
    if (mvp_location_out != NULL)
        *mvp_location_out = mvp_location;
    if (position_location_out != NULL)
        *position_location_out = position_location;
    if (color_location_out != NULL)
        *color_location_out = color_location;

    return program != 0;
}

static void
mmult(float *m1, float *m2, float *res)
{
    int i, j, k;

    for (i = 0; i < 4; i++) {
        for (j = 0; j < 4; j++) {
            res[j*4 + i] = 0;

            for (k = 0; k < 4; k++) {
                res[j*4 + i] += m1[k*4 + i] * m2[j*4 + k];
            }
        }
    }
}

static void
init_mvp (float *res, float zscale)
{
    /* initialize a matrix as an identity matrix */
    res[0] = 1.f; res[4] = 0.f;  res[8] = 0.f; res[12] = 0.f;
    res[1] = 0.f; res[5] = 1.f;  res[9] = 0.f; res[13] = 0.f;
    res[2] = 0.f; res[6] = 0.f; res[10] = zscale; res[14] = 0.f;
    res[3] = 0.f; res[7] = 0.f; res[11] = 0.f; res[15] = 1.f;
}


static void    
compute_mvp (float *res, float phi, float theta, float psi, float zscale)
{ 
    float ms[16];
    float mr[16];
    float x = phi * (G_PI / 180.f);
    float y = theta * (G_PI / 180.f);
    float z = psi * (G_PI / 180.f);
    float c1 = cosf (x), s1 = sinf (x);
    float c2 = cosf (y), s2 = sinf (y);
    float c3 = cosf (z), s3 = sinf (z);
    float c3c2 = c3 * c2;
    float s3c1 = s3 * c1;
    float c3s2s1 = c3 * s2 * s1;
    float s3s1 = s3 * s1;
    float c3s2c1 = c3 * s2 * c1;
    float s3c2 = s3 * c2;
    float c3c1 = c3 * c1;
    float s3s2s1 = s3 * s2 * s1;
    float c3s1 = c3 * s1;
    float s3s2c1 = s3 * s2 * c1;
    float c2s1 = c2 * s1;
    float c2c1 = c2 * c1;

    init_mvp(ms, zscale);

    mr[0] = c3c2;  mr[4] = s3c1 + c3s2s1;  mr[8] = s3s1 - c3s2c1; mr[12] = 0.f;
    mr[1] = -s3c2; mr[5] = c3c1 - s3s2s1;  mr[9] = c3s1 + s3s2c1; mr[13] = 0.f;
    mr[2] = s2;    mr[6] = -c2s1;         mr[10] = c2c1;          mr[14] = 0.f;
    mr[3] = 0.f;   mr[7] = 0.f;           mr[11] = 0.f;           mr[15] = 1.f;

    mmult(mr, ms, res);
}


static void
draw_triangle (ModuleGUI *gui)
{
    float mvp[16];

    /* Use our shaders */
    glUseProgram(gui->program);

    /* Update the "mvp" matrix we use in the shader */
    glUniformMatrix4fv(gui->mvp_location, 1, GL_FALSE, &(gui->mvp[0]));

    /* use the buffers in the VAO */
    glBindVertexArray(gui->vao);

    /* draw the three vertices as a triangle */
    glDrawArrays(GL_TRIANGLES, 0, 3*gui->ntriangles);

    /* we finished using the buffers and program */
    glBindVertexArray(0);
    glUseProgram(0);
}


static gboolean
glarea_render(GtkGLArea *area,
        GdkGLContext *context, ModuleGUI *gui)
{
    ModuleArgs *args = gui->args;
  
    /* Clear the viewport */
    glClearColor(gui->bg_color.r, gui->bg_color.g, gui->bg_color.b, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);

    /* Draw our object */
    draw_triangle(gui);

    /* Flush the contents of the pipeline */
    glFlush();

  return TRUE;
}


static void
preview(gpointer user_data)
{
    printf("preview\n");
}

static gboolean
button_clicked(G_GNUC_UNUSED GtkWidget *widget, GdkEventButton *event, ModuleGUI *gui)
{

    if (event->button == 1) {
        //printf("left button clicked\n");
        gui->mouse_begin_x = event->x;
        gui->mouse_begin_y = event->y;    
    }

    return TRUE;
}


static gboolean
mouse_moved(GtkWidget *widget, GdkEvent *event, ModuleGUI *gui)
{
    gint ex, ey, xs, ys;
    gdouble zs, theta, phi, psi;
    GdkModifierType state;

    ex = ((GdkEventMotion*)event)->x;
    ey = ((GdkEventMotion*)event)->y;
    state = ((GdkEventMotion*)event)->state;

    if (state & GDK_BUTTON1_MASK) {
        if (gtk_notebook_get_current_page(GTK_NOTEBOOK(gui->notebook)) == NTB_ROTATE) {
           xs = ex - gui->mouse_begin_x;
           ys = ey - gui->mouse_begin_y;
           gui->mouse_begin_x = ex;
           gui->mouse_begin_y = ey;

           //FIXME, the angles are wrong, all should be set at once
           theta = gwy_params_get_double(gui->args->params, PARAM_THETA) - ys/10.0;
           if (theta < 0)
               theta = 360;
           if (theta > 360)
               theta = 0;
           gwy_param_table_set_double(gui->table_rotate, PARAM_THETA, theta);
 
           psi = gwy_params_get_double(gui->args->params, PARAM_PSI) - xs/10.0;
           if (psi < 0)
               psi = 360;
           if (psi > 360)
               psi = 0;
           gwy_param_table_set_double(gui->table_rotate, PARAM_PSI, psi);
      }
 

        if (gtk_notebook_get_current_page(GTK_NOTEBOOK(gui->notebook)) == NTB_SCALE) {
           ys = ey - gui->mouse_begin_y;
           gui->mouse_begin_y = ey;
           zs = gwy_params_get_double(gui->args->params, PARAM_ZSCALE) - ys/50.0;
           if (zs < ZSCALE_MIN)
               zs = ZSCALE_MIN;
           if (zs > ZSCALE_MAX)
               zs = ZSCALE_MAX;
           gwy_param_table_set_double(gui->table_scale, PARAM_ZSCALE, zs);
      }
    }

    return TRUE;
}


static void
sanitize_params(GwyParams *params)
{
    gdouble theta = gwy_params_get_double(params, PARAM_THETA);
    gdouble phi = gwy_params_get_double(params, PARAM_PHI);
    gdouble psi = gwy_params_get_double(params, PARAM_PSI);
     
    if (theta<0)
        theta = 0;
    if (theta>360)
        theta = 360;

    if (phi<0)
        phi = 0;
    if (phi>360)
        phi = 360;

    if (psi<0)
        psi = 0;
    if (psi>360)
        psi = 360;
}

/* vim: set cin columns=120 tw=118 et ts=4 sw=4 cino=>1s,e0,n0,f0,{0,}0,^0,\:1s,=0,g1s,h0,t0,+1s,c3,(0,u0 : */
