/*****************************************************************************\
  PMVIS, Partitioned Mesh Visualizer
  Copyright (C) 1999-2009 Bilgehan Uygar Oztekin

  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 3 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, see <http://www.gnu.org/licenses/>.
\*****************************************************************************/

///////////////////////////////////////////////////////////////////////////////
// Filename         pmvis.cpp
// Title            partitioned mesh visualizer
//
// Author           Bilgehan Uygar Oztekin
//
// Starting date    1999/08/26
// Last modified    2009/02/22
// Version number   1.09
//
// License          GNU General Public License Version 3
//                  see http://www.gnu.org/licenses/lgpl-3.0.txt
//
// Platforms        Anything that supports OpenGL/GLUT
//
////////////////////////////////////////////////////////////////////////////////

// You need decent C++ compiler (e.g. GNU gcc/g++), opengl, and glut libraries.
// For debian lenny (assuming that you have g++ and opengl already):
/*

sudo apt-get install freeglut3-dev
g++ -lglut pmvis.cpp -o pmvis

# Assuming that the test files are in the same directory, try
./pmvis -n test.xyz -c test.con -p test.par -o 1 -g quad
# Enjoy!

*/

#define USE_EXCEPTIONS                      // remove to disable exceptions

#include <iostream>
#include <iomanip>
#include <fstream>
#include <cassert>
#include <cmath>
#include <ctime>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <set>
#include <map>
#include <GL/glut.h>

// Not a good idea, but this is an old program, not in the mood to fix all.
using namespace std;

typedef float REAL;                 // REAL and integer types used in the program
typedef unsigned INDEX;

const double EDGE_OFFSET=0.000001;  // modify if contour lines are not properly drawn
const double pi=acos(-1.0),twopi=pi*2,halfpi=asin(1.0);

// Camera control variables

GLfloat eyex, eyey, eyez, centerx, centery, centerz;
GLfloat inca1=pi/24, inca2=pi/24, incl, minl=0, maxl, z=1;
GLfloat a1=0, a2=pi/4, l, near_plane, far_plane, clip_size;

// Help strings

const char aboutstr[]="\n\
  Partitioned Mesh Visualizer, Version 1.08\n\
     (C)1999, Bilgehan Uygar Oztekin\n\n\
";

const char helpstr[]="\
  lb, rb and mb denote left, right and middle mouse buttons respectively.\n\
  lb            -> rotation\n\
  lb and rb     -> y dir: zoom in/out, x dir: adjust clipping cube size\n\
  mb            -> translation\n\
  mb and rb     -> y dir: move clipping cube in/out, x dir: clipping cube size\n\
  ctrl+lb       -> same as mb but translation state is locked until lb release\n\
  shift+lb      -> same as lb and rb but state will be locked until lb release\n\
  ctrl+shift+lb -> same as mb+rb but state will be locked until lb is released\n\
  arrow keys    -> rotation,                pageup/pagedown    -> zoom in/out\n\
  Ctrl and shift combinations are useful if the mouse does not have 3 buttons\n\
\n\
  [n]          [enter*] -> center on partition n (use 0 for the whole object)\n\
  's'[n1]      [enter*] -> select/desellect partition n1 ex:\"s3\" sel/desel 3rd\n\
  's'[n1];[n2] [enter*] -> select partitions in range n1 to n2\n\
  ctrl+[function key]   -> store current selection to function key (F1 to F8)\n\
  [function key]        -> recall previously stored partition selection\n\
  (*)Program will automatically feed enter after a timeout if not pressed\n\
\n\
  't' -> apply current transparency value to selection\n\
  'o' -> make current selection opaque\n\
  'h' -> hide/unhide current selection\n\
  'r' -> randomly shuffle colors\n\
  'c' -> clipping mode on/off\n\
  '+' -> select all,  '-' -> select none,  '*' -> invert selection\
";

void error(const char *str)                     // print error str and terminate
{
    cerr << str << endl;
    exit(0);;
}

// Binary read and write functions using c++ streams

inline void bread(istream &istr,void *buffer,long count=1)
{
    istr.read((char *)buffer,count);
}

/*inline void bwrite(ostream &ostr,void *buffer,long count=1)
{
    ostr.write((char *)buffer,count);
}*/

// This function converts coordinates in hsv color space to rgb color space

void hsv2rgb(float &r,float &g,float &b, float h, float s, float v)
{
    while(h<0) h+=360;
    while(h>360) h-=360;
    h/=60.0;
    int i=(int)floor(h);
    float f=h-i,p=v*(1-s),q=v*(1-s*f),t=v*(1-s*(1-f));
    switch(i){
    case 0:r=v;g=t;b=p;break;
    case 1:r=q;g=v;b=p;break;
    case 2:r=p;g=v;b=t;break;
    case 3:r=p;g=q;b=v;break;
    case 4:r=t;g=p;b=v;break;
    case 5:r=v;g=p;b=q;break;
    }
}

enum elemtype { triangle=1,tetrahedral=2,brick=3,quadrilateral=4 };

inline int ordered(const char *str1, const char *str2, const char *str3)  // returns true if str1<=str2<=str3
{
    return strcmp(str1,str2)<=0 && strcmp(str2,str3)<=0;
}

// enconde and decode are used to store node numbering in an efficient manner (1 byte) using bitwise ops

inline unsigned char encode(INDEX *original, INDEX *permuted, INDEX count)
{
    unsigned char order=0;
    for(int i=count-1;i>=0;i--){
        int j=0;
        for(;j<count&&original[i]!=permuted[j];j++);
        assert(j!=count);
        order|=(unsigned char)(j);
        if(i) order <<=2;
    }
    return order;
}

inline void decode(INDEX *permuted, INDEX *result, unsigned char order, INDEX count)
{
    for(int i=0;i<count;i++){
        result[i]=permuted[order&(unsigned char)3];
        order >>=2;
    }
}

// Instances of this class will be entered in a red-black tree to remove inside elements for efficiency

class entry{
public:
    unsigned char size,order;
    INDEX partition,*data;
    entry(int _size,INDEX _partition,INDEX *init,unsigned char _order):size(_size),partition(_partition),order(_order){
        data=new INDEX[_size];
        assert(data);
        for(int i=0;i<_size;i++) data[i]=init[i];
    }
    ~entry() {assert(data);delete data;}
    entry(const entry& e){
        size=e.size;
        order=e.order;
        partition=e.partition;
        data=new INDEX[size];
        for(int i=0;i<size;i++) data[i]=e.data[i];
    }
};

struct ltentry  // comparison class for the "entry" class. Used to compare keys in STL set
{
    bool operator()(const entry &e1, const entry &e2) const
    {
        assert(e1.size==e2.size);
        if(e1.partition<e2.partition) return true;
        else if(e1.partition>e2.partition) return false;
        else
            for(int i=0;i<e1.size;i++)
                if(e1.data[i]<e2.data[i]) return true;
                else if(e1.data[i]>e2.data[i]) return false;
        return false;
    }
};

int starting_list;          // [starting_list .. starting_list + number of partitions] are valid display lists.
typedef REAL boxtype[6];    // box type in 3d space. minx, miny, minz, maxx, maxy, maxz...
typedef REAL pointtype[3];  // point type, x, y, z...
boxtype *minmax;            // bounding box for each partitions.
boxtype bounding_box;       // bounding box for all partitions.
pointtype *center;          // center for each partition
map<int,int> partitions;    // will be used to map actual partition numbers to internal numbering system.
char *selection;            // will hold selected partitions
typedef char fchars[8];
fchars *backup_selection;
int *mapcolor;

REAL current_transparency=0.8, *transparencies;

void init(int argc, char **argv)        // Read parameters and files, initialize display lists etc.
{
    const char *node_filename="",*con_filename="",*part_filename="",*metis_partitions="";
    char node_tag=0,con_tag=0,part_tag=0,ascii_tag=1,keep_tag=0,metis_tag=0;
    int psize=3,nsize=4,offset=0;
    elemtype type=tetrahedral;

    // Process command line arguments
    int i;
    for(i=2;i<=argc;i++){
        if(ordered("-n",argv[i-1],"-nodes")){
            node_filename=argv[i++];
            node_tag=1;
        } else if(ordered("-c",argv[i-1],"-connectivity")){
            con_filename=argv[i++];
            con_tag=1;
        } else if(ordered("-p",argv[i-1],"-partition")){
            part_filename=argv[i++];
            part_tag=1;
        } else if(ordered("-m",argv[i-1],"-metis")){
            metis_partitions=argv[i++];
            metis_tag=1;
        } else if(ordered("-b",argv[i-1],"-binary")){
            ascii_tag=0;
        } else if(ordered("-k",argv[i-1],"-keep")){
            keep_tag=1;
        } else if(ordered("-o",argv[i-1],"-offset")){
            offset=atoi(argv[i++]);
        } else if(ordered("-g",argv[i-1],"-geometry")){
            if(i==argc){
                cerr << "Element type expected !" << endl;
                exit(0);
            }
            if(ordered("tri",argv[i],"triangle")) type=triangle;
            else if(ordered("quad",argv[i],"quadrilateral")) type=quadrilateral;
            else if(ordered("tet",argv[i],"tetrahedral")) type=tetrahedral;
            else if(ordered("hex",argv[i],"hexahedral")) type=brick;
            else {
                cerr << "Unknown element type :" << argv[i] << endl;
                exit(0);
            }
            switch(type){
            case triangle:      psize=3;nsize=3;break;
            case quadrilateral: psize=4;nsize=4;break;
            case tetrahedral:   psize=3;nsize=4;break;
            case brick:         psize=4;nsize=8;break;
            }
            i++;
        } else {
            cerr << "Invalid Parameter: " << argv[i-1] << endl;
            exit(0);
        }
    }
    if(metis_tag && part_tag){
        cout << "Ignoring Metis directive as partition file is given..." << endl;
        metis_tag=0;
    }
    if(!node_tag || !con_tag){
        cout << "Usage: pmvis parameters\n";
        cout << "Parameters are:\n";
        cout << "  -n[odes]        node_file          -> specifies node file          (required)\n";
        cout << "  -c[onnectivity] connectivity_file  -> specifies connectivity file  (required)\n";
        cout << "  -p[artition]    partition_file     -> partition file               (optional)\n";
        cout << "  -m[etis] number_of_partitions -> use metis to build the partitions (optional)\n";
        cout << "  -b[inary]   -> all files are binary (default: ascii files of floats and ints)\n";
        cout << "  -k[eep]     -> force to keep intermediate elements (default: only boundaries)\n";
        cout << "  -o[ffset] n -> n=0:use C/C++ style, n=1:use Fortran sytle indices (default:0)\n";
        cout << "  -g[eometry] tri[angle] quad[rilateral] tet[rahedral] hex[aherdal]  (def:tets)\n";
        exit(0);
    }

    // Read data, prepare display list(s)
#ifdef USE_EXCEPTIONS
    try
#endif
    {
        ifstream nodefile(node_filename,ios::binary | ios::in);
        if(nodefile.fail()) error("Error opening node file");
        typedef REAL nodetype[3];
        nodetype *nodes;

        // Read node file

        long number_of_nodes=0;
        if(ascii_tag){                                  // ascii
            while(nodefile){
                REAL temp;
                for(int i=0;i<3;i++) nodefile >> temp;
                if(!nodefile) break; else number_of_nodes++;
            }
            nodefile.clear(ios::goodbit);nodefile.seekg(0,ios::beg);
            nodes=new nodetype[number_of_nodes];
            for(long i=0;i<number_of_nodes;i++){
                nodefile >> nodes[i][0] >> nodes[i][1] >> nodes[i][2];
            }
        } else {                                        // binary
            nodefile.seekg(0,ios::end);
            number_of_nodes=nodefile.tellg()/sizeof(REAL)/psize;
            nodefile.clear(ios::goodbit);nodefile.seekg(0,ios::beg);
            nodes=new nodetype[number_of_nodes];
            bread(nodefile,nodes,number_of_nodes*sizeof(nodetype));
        }
        cout << "Number of nodes      : " << number_of_nodes << endl;
        ifstream confile(con_filename,ios::binary | ios::in);
        if(confile.fail()) error("Error opening connectivity file");

        if(metis_tag){
            INDEX elements=0;
            INDEX *nv=new INDEX[nsize];
            {
                ifstream tempcon(con_filename,ios::binary | ios::in);
                while(tempcon){
                    if(ascii_tag){
                        for(int i=0;i<nsize;i++) tempcon >> nv[i];
                    } else {
                        bread(tempcon,nv,nsize*sizeof(INDEX));
                    }
                    if(!tempcon) break; else elements++;
                }
            }
            ofstream nodal("temp",ios::binary | ios::out);
            nodal << elements << " " << type << endl;
            ifstream tempcon(con_filename,ios::binary | ios::in);
            while(tempcon){
                if(ascii_tag){
                    for(int i=0;i<nsize;i++) tempcon >> nv[i];
                } else {
                    bread(tempcon,nv,nsize*sizeof(INDEX));
                }
                if(!tempcon) break;
                for(int i=0;i<nsize;i++) nodal << nv[i] << " ";
                nodal << endl;
            }
            delete nv;
            tempcon.close();
            char temp[64]="partdmesh temp ";
            strcat(temp,metis_partitions);
            system(temp);
            char *partitionfilestr=new char[64];
            strcpy(partitionfilestr,"temp.epart.");
            strcat(partitionfilestr,metis_partitions);
            part_filename=partitionfilestr;
            cout << "\nPartition file created: " << part_filename << endl << endl;
            part_tag=1;
        }

        ifstream partfile;
        if(part_tag){
            partfile.open(part_filename,ios::binary | ios::in);
            if(partfile.fail()) error("Error opening partition file");
        }

        // Read connectivity file and partition file, remove inner elements

        long number_of_elements=0;
        set<entry,ltentry> objects;
        INDEX *nv=new INDEX[nsize];
        INDEX *ov=new INDEX[psize];
        INDEX *pv=new INDEX[psize];
        while(confile){
            INDEX partition=1;
            if(ascii_tag){
                for(int i=0;i<nsize;i++) confile >> nv[i];
                if(part_tag) partfile >> partition;
            } else {
                bread(confile,nv,nsize*sizeof(INDEX));
                if(part_tag)
                    if(metis_tag) partfile >> partition;
                    else partfile.read((char*)&partition,sizeof(INDEX));
            }
            if(!confile) break; else number_of_elements++;
            if(!partfile) error("Premature end of partition file");
            map<int,int>::iterator iter=partitions.find(partition);
            if(iter==partitions.end())
                partitions.insert(map<int,int>::value_type(partition,partitions.size()));
            for(int j=0;j<nsize;j++) nv[j]-=offset;
            switch(type){
                case triangle:
                {
                    pv[0]=nv[0];pv[1]=nv[1];pv[2]=nv[2];
                    sort(pv,pv+psize);
                    entry temp=entry(psize,/*partitions[*/partition/*]*/,pv,encode(nv,pv,psize));
                    if(keep_tag) objects.insert(temp);
                    else {
                        set<entry,ltentry>::iterator it=objects.find(temp);
                        if(it!=objects.end()) objects.erase(temp); else objects.insert(temp);
                    }
                    break;
                }
                case quadrilateral:
                {
                    pv[0]=nv[0];pv[1]=nv[1];pv[2]=nv[2];pv[3]=nv[3];
                    sort(pv,pv+psize);
                    entry temp=entry(psize,/*partitions[*/partition/*]*/,pv,encode(nv,pv,psize));//1.07
                    if(keep_tag) objects.insert(temp);
                    else {
                        set<entry,ltentry>::iterator it=objects.find(temp);
                        if(it!=objects.end()) objects.erase(temp); else objects.insert(temp);
                    }
                    break;
                }
                case tetrahedral:
                {
                    for(int i=0;i<4;i++){
                        switch(i){
                        case 0:pv[0]=nv[0];pv[1]=nv[2];pv[2]=nv[1];break;
                        case 1:pv[0]=nv[0];pv[1]=nv[1];pv[2]=nv[3];break;
                        case 2:pv[0]=nv[2];pv[1]=nv[0];pv[2]=nv[3];break;
                        case 3:pv[0]=nv[1];pv[1]=nv[2];pv[2]=nv[3];break;
                        }
                        ov[0]=pv[0];ov[1]=pv[1];ov[2]=pv[2];
                        sort(pv,pv+psize);
                        entry temp=entry(psize,/*partitions[*/partition/*]*/,pv,encode(ov,pv,psize));//1.07
                        if(keep_tag) objects.insert(temp);
                        else {
                            set<entry,ltentry>::iterator it=objects.find(temp);
                            if(it!=objects.end()) objects.erase(temp); else objects.insert(temp);
                        }
                    }
                    break;
                }
                case brick:
                {
                    for(int i=0;i<6;i++){
                        switch(i){
                        case 0:pv[0]=nv[0];pv[1]=nv[1];pv[2]=nv[2];pv[3]=nv[3];break;
                        case 1:pv[0]=nv[1];pv[1]=nv[5];pv[2]=nv[6];pv[3]=nv[2];break;
                        case 2:pv[0]=nv[5];pv[1]=nv[4];pv[2]=nv[7];pv[3]=nv[6];break;
                        case 3:pv[0]=nv[4];pv[1]=nv[0];pv[2]=nv[3];pv[3]=nv[7];break;
                        case 4:pv[0]=nv[1];pv[1]=nv[0];pv[2]=nv[4];pv[3]=nv[5];break;
                        case 5:pv[0]=nv[2];pv[1]=nv[6];pv[2]=nv[7];pv[3]=nv[3];break;
                        }
                        ov[0]=pv[0];ov[1]=pv[1];ov[2]=pv[2];ov[3]=pv[3];
                        sort(pv,pv+psize);
                        entry temp=entry(psize,/*partitions[*/partition/*]*/,pv,encode(ov,pv,psize));//1.07
                        if(keep_tag) objects.insert(temp);
                        else {
                            set<entry,ltentry>::iterator it=objects.find(temp);
                            if(it!=objects.end()) objects.erase(temp); else objects.insert(temp);
                        }
                    }
                    break;
                }
            }
        }
        delete[] pv;
        delete[] ov;
        delete[] nv;
        cout << "Number of elements   : " << number_of_elements << endl;
        cout << "Number of polygons   : " << objects.size() << endl;
        cout << "Number of partitions : " << partitions.size() << endl;
        if(part_tag) partfile.close();
        confile.close();
        nodefile.close();

        // Reorder partitions so that original partition enumeration order is kept. //1.07
        {
            int count=0;
            for(map<int,int>::iterator it=partitions.begin();it!=partitions.end();it++,count++)
                it->second=count;
        }

        // Prepare display lists

        starting_list=glGenLists(partitions.size());
        char *first=new char[partitions.size()];
        fill(first,first+partitions.size(),1);
        minmax=new boxtype[partitions.size()];
        for(i=0;i<partitions.size();i++){
            glNewList(starting_list+i,GL_COMPILE);
            #define setvertex(n) glVertex3f(nodes[n][0],nodes[n][1],nodes[n][2])
            #define ifmin(minx,x) if(minx>x) minx=x;
            #define ifmax(maxx,x) if(maxx<x) maxx=x;
            for(set<entry,ltentry>::iterator it=objects.begin();it!=objects.end();it++){
                //if(it->partition!=i) continue;                // ver 1.07
                if(partitions[it->partition]!=i) continue;
                if(first[i]){
                    minmax[i][0]=minmax[i][3]=nodes[it->data[0]][0];
                    minmax[i][1]=minmax[i][4]=nodes[it->data[0]][1];
                    minmax[i][2]=minmax[i][5]=nodes[it->data[0]][2];
                    first[i]=0;
                } else {
                    for(int j=0;j<psize;j++){
                        ifmin(minmax[i][0],nodes[it->data[j]][0]);
                        ifmax(minmax[i][3],nodes[it->data[j]][0]);
                        ifmin(minmax[i][1],nodes[it->data[j]][1]);
                        ifmax(minmax[i][4],nodes[it->data[j]][1]);
                        ifmin(minmax[i][2],nodes[it->data[j]][2]);
                        ifmax(minmax[i][5],nodes[it->data[j]][2]);
                    }
                }
                INDEX data[4];
                decode(it->data,data,it->order,psize);      // psize ???
                switch(type){
                    case triangle:case tetrahedral:
                    {
                        glBegin(GL_TRIANGLES);
                            setvertex(data[0]);
                            setvertex(data[1]);
                            setvertex(data[2]);
                        glEnd();
                        break;
                    }
                    case quadrilateral:case brick:
                    {
                        glBegin(GL_QUADS);
                            setvertex(data[0]);
                            setvertex(data[1]);
                            setvertex(data[2]);
                            setvertex(data[3]);
                        glEnd();
                        break;
                    }
                }
            }
            glEndList();
        }
        for(i=0;i<6;i++) bounding_box[i]=minmax[0][i];
        for(i=1;i<partitions.size();i++){
            ifmin(bounding_box[0],minmax[i][0]);
            ifmin(bounding_box[1],minmax[i][1]);
            ifmin(bounding_box[2],minmax[i][2]);
            ifmax(bounding_box[3],minmax[i][3]);
            ifmax(bounding_box[4],minmax[i][4]);
            ifmax(bounding_box[5],minmax[i][5]);
        }
        centerx=(bounding_box[0]+bounding_box[3])/2;
        centery=(bounding_box[1]+bounding_box[4])/2;
        centerz=(bounding_box[2]+bounding_box[5])/2;
        #define max(x,y) (x)>(y)?(x):(y)
        maxl=max((bounding_box[3]-bounding_box[0]),(bounding_box[4]-bounding_box[1]));
        maxl=max(maxl,(bounding_box[5]-bounding_box[2]));
        maxl*=3;
        l=maxl/2;
        incl=maxl/90;
        far_plane=maxl*2;
        near_plane=maxl/20;
        clip_size=maxl/90;
        delete[] first;
        delete[] nodes;
        selection=new char[partitions.size()];
        backup_selection=new char[partitions.size()][8];
        transparencies=new REAL[partitions.size()];
        center=new pointtype[partitions.size()];
        for(i=0;i<partitions.size();i++){
            for(int j=0;j<8;j++) backup_selection[i][j]=0;
            selection[i]=0;
            transparencies[i]=1;
            center[i][0]=(minmax[i][0]+minmax[i][3])/2;
            center[i][1]=(minmax[i][1]+minmax[i][4])/2;
            center[i][2]=(minmax[i][2]+minmax[i][5])/2;
        }
    }
#ifdef USE_EXCEPTIONS
    catch(char *str){
        error(str);
    } catch(...) {
        error("Unhandled exception");
    }
#endif
    glEnable(GL_DEPTH_TEST);glDepthFunc(GL_LEQUAL);
    glEnable(GL_LINE_SMOOTH);glHint(GL_LINE_SMOOTH_HINT,GL_NICEST);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    eyex=l*cos(a2)*cos(a1)+centerx;
    eyey=l*cos(a2)*sin(a1)+centery;
    eyez=l*sin(a2)+centerz;
    mapcolor=new int[partitions.size()];
    for(i=0;i<partitions.size();i++) mapcolor[i]=i;
    cout << "\nUse right click on view window to open the menu.\n" << endl;
    glutPostRedisplay();
}

int xpos=0,ypos=0,preview=0,previewmode=1,wireframe=1,polygon=1,clip_mode=0;
int current_partition=0,number_to_read=0,reading_number=0,blink=0,labelmode=1;
int mainmenu, override=0, hide_selected=0, blending=0, forcesort=1;
REAL timeout=0.5, blink_timeout=0.3, explosion_factor=0.8;
clock_t start;

template<class REAL>inline REAL sqr(REAL x)
{
    return x*x;
}

void reshape(int w, int h)                      // reshape callback
{
    glViewport(0,0, (GLsizei) w, (GLsizei) h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60.0, (GLdouble)w/(GLdouble)h, near_plane, far_plane);
    glMatrixMode(GL_MODELVIEW);
}

void set_clip_planes(void)                      // set the six clipping planes
{
    GLdouble clipplane[4]={0,0,0,0};
    REAL size=clip_size/2;

    clipplane[0]=-1;clipplane[3]=centerx+size;glClipPlane(GL_CLIP_PLANE0,clipplane);
    clipplane[0]=1;clipplane[3]=-centerx+size;glClipPlane(GL_CLIP_PLANE1,clipplane);

    clipplane[0]=0;
    clipplane[1]=-1;clipplane[3]=centery+size;glClipPlane(GL_CLIP_PLANE2,clipplane);
    clipplane[1]=1;clipplane[3]=-centery+size;glClipPlane(GL_CLIP_PLANE3,clipplane);

    clipplane[1]=0;
    clipplane[2]=-1;clipplane[3]=centerz+size;glClipPlane(GL_CLIP_PLANE4,clipplane);
    clipplane[2]=1;clipplane[3]=-centerz+size;glClipPlane(GL_CLIP_PLANE5,clipplane);
}

int starting_angle=0;

void uint2str(unsigned num, char *str)          // converts unsigned int to string
{
    int i=0;
    while(num!=0){
        str[i++]='0'+(num%10);
	num/=10;
    }
    for(int j=0;j<i/2;j++){
        char temp=str[j];
	str[j]=str[i-j-1];
	str[i-j-1]=temp;
    }
    str[i]='\0';
}

void display(void)                              // display callback
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
    gluLookAt(eyex, eyey, eyez, centerx, centery, centerz, 0.0, 0.0, z);
    if(previewmode && preview){
        glPushMatrix();
        glTranslatef(centerx,centery,centerz);
        glutSolidCube(clip_size*0.99);
        glPopMatrix();
    }
    if(clip_mode) set_clip_planes();
    multimap<REAL,INDEX> backtofront;
    for(int c=0;c<=blending;c++){
        multimap<REAL,INDEX>::iterator it=backtofront.begin();
        for(int j=0;j<partitions.size();j++){
            int i=j;
            if(blending&&forcesort&&!blink&&polygon&&!(preview&&previewmode)){
                if(!c&&transparencies[j]!=1.0){
                    backtofront.insert(multimap<REAL,INDEX>::value_type(-sqr(eyex-center[j][0])-sqr(eyey-center[j][1])-sqr(eyez-center[j][2]),j));
                    continue;
                }
                if(c) if(transparencies[j]!=1.0){ i=it->second;it++;} else continue;
            }
            if(blink&&current_partition!=i || hide_selected&&selection[i]) continue;
            glPushMatrix();
            float cx=center[i][0];
            float cy=center[i][1];
            float cz=center[i][2];
            glTranslatef(cx,cy,cz);
            glScalef(explosion_factor,explosion_factor,explosion_factor);
            glTranslatef(-cx,-cy,-cz);
            float r,g,b,a=transparencies[i];
            float angle=float(mapcolor[i])/float(partitions.size())*360.0+starting_angle,s;
            if(selection[i]) s=0.0;else s=1.0;
            if(wireframe!=2 || (preview&&previewmode)){
                hsv2rgb(r,g,b,angle,s,1.0);glColor4f(r,g,b,a);
            } else glColor4f(0,0,0,a);
            if(preview && previewmode){
                glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
                glBegin(GL_QUAD_STRIP);
                    glVertex3f(minmax[i][0],minmax[i][1],minmax[i][2]);
                    glVertex3f(minmax[i][3],minmax[i][1],minmax[i][2]);
                    glVertex3f(minmax[i][0],minmax[i][1],minmax[i][5]);
                    glVertex3f(minmax[i][3],minmax[i][1],minmax[i][5]);
                    glVertex3f(minmax[i][0],minmax[i][4],minmax[i][5]);
                    glVertex3f(minmax[i][3],minmax[i][4],minmax[i][5]);
                    glVertex3f(minmax[i][0],minmax[i][4],minmax[i][2]);
                    glVertex3f(minmax[i][3],minmax[i][4],minmax[i][2]);
                glEnd();
                glBegin(GL_QUAD_STRIP);
                    glVertex3f(minmax[i][0],minmax[i][1],minmax[i][5]);
                    glVertex3f(minmax[i][0],minmax[i][4],minmax[i][5]);
                    glVertex3f(minmax[i][0],minmax[i][1],minmax[i][2]);
                    glVertex3f(minmax[i][0],minmax[i][4],minmax[i][2]);
                    glVertex3f(minmax[i][3],minmax[i][1],minmax[i][2]);
                    glVertex3f(minmax[i][3],minmax[i][4],minmax[i][2]);
                    glVertex3f(minmax[i][3],minmax[i][1],minmax[i][5]);
                    glVertex3f(minmax[i][3],minmax[i][4],minmax[i][5]);
                glEnd();
            } else {
                if(polygon){
                    glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
                    glDepthRange(EDGE_OFFSET,1.0);
                    glCallList(i+starting_list);
                }
                if(wireframe){
                    if(polygon && (wireframe!=2 || (preview&&previewmode))) hsv2rgb(r,g,b,angle,s,0.7);
                    else hsv2rgb(r,g,b,angle,s,0.9);
                    glColor4f(r,g,b,a);
                    glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
                    glDepthRange(0.0, 1.0-EDGE_OFFSET);
                    glCallList(i+starting_list);
                }
            }
            glColor3f(1.0,1.0,1.0);
            if(labelmode==2||(labelmode==1&&(preview&&previewmode||blink))){
                glDepthRange(EDGE_OFFSET*2,1.0);
                glRasterPos3f(cx,cy,cz);
                char str[16];uint2str(i+1,str);
                for(char *p=str;*p;p++) glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18,*p);
            }
            glPopMatrix();
        }
        if(!(blending&&!blink&&polygon&&forcesort&&!(preview&&previewmode))) continue;
        glDepthMask(0);
    }
    glDepthMask(1);
    glutSwapBuffers();
    if(blink){
        glutSetCursor(GLUT_CURSOR_WAIT);
        for(clock_t start=clock();(clock()-start)/CLOCKS_PER_SEC<blink_timeout;);
        blink=0;glutPostRedisplay();
        glutSetCursor(GLUT_CURSOR_LEFT_ARROW);
    }
}

int select_range=0, range1, range2;

void autoselect(void)               // will timeout and autoselect current keyboard entry
{
    if(!override && ((clock()-start)/CLOCKS_PER_SEC<timeout)) return;
    override=reading_number=0;
    glutSetCursor(GLUT_CURSOR_LEFT_ARROW);
    if(select_range==1){
        select_range=0;
        if(number_to_read==0) return;
        selection[number_to_read-1]=1-selection[number_to_read-1];
    } else if(select_range==2){
        range2=number_to_read;
        if(range2==0) range2=partitions.size();
        if(range1==0) range1=1;
        for(int i=range1-1;i<range2&&i<partitions.size();i++)
            selection[i]=1;
        select_range=0;
    } else {
        current_partition=number_to_read;
        if(current_partition==0){
            centerx=(bounding_box[0]+bounding_box[3])/2;
            centery=(bounding_box[1]+bounding_box[4])/2;
            centerz=(bounding_box[2]+bounding_box[5])/2;
        } else if(--current_partition>=0 && current_partition<partitions.size()){
            centerx=(minmax[current_partition][0]+minmax[current_partition][3])/2;
            centery=(minmax[current_partition][1]+minmax[current_partition][4])/2;
            centerz=(minmax[current_partition][2]+minmax[current_partition][5])/2;
            blink=1;preview=0;
        }
    }
    glutIdleFunc(NULL);
    glutPostRedisplay();
}

void keyboard(unsigned char key, int x, int y)  // keyboard callback
{
    if(key>='0' && key <='9'){
        if(!reading_number){
            reading_number=1;number_to_read=0;
        }
        number_to_read=number_to_read*10+key-'0';
        start=clock();glutIdleFunc(autoselect);
        glutSetCursor(GLUT_CURSOR_HELP);
        glutPostRedisplay();
    } else {
        switch(key){
        case '+':{for(int i=0;i<partitions.size();i++) selection[i]=1;glutPostRedisplay();break;}
        case '-':{for(int i=0;i<partitions.size();i++) selection[i]=0;glutPostRedisplay();break;}
        case '*':{for(int i=0;i<partitions.size();i++) selection[i]=1-selection[i];glutPostRedisplay();break;}
        case 's':case 'S':select_range=1;number_to_read=0;start=clock();glutIdleFunc(autoselect);glutSetCursor(GLUT_CURSOR_HELP);;break;
        case 'h':case 'H':hide_selected=1-hide_selected;glutPostRedisplay();break;
        case 'c':case 'C':{
            if(!clip_mode){
                clip_mode=1;
                glEnable(GL_CLIP_PLANE0);
                glEnable(GL_CLIP_PLANE1);
                glEnable(GL_CLIP_PLANE2);
                glEnable(GL_CLIP_PLANE3);
                glEnable(GL_CLIP_PLANE4);
                glEnable(GL_CLIP_PLANE5);
            } else {
                clip_mode=0;
                glDisable(GL_CLIP_PLANE0);
                glDisable(GL_CLIP_PLANE1);
                glDisable(GL_CLIP_PLANE2);
                glDisable(GL_CLIP_PLANE3);
                glDisable(GL_CLIP_PLANE4);
                glDisable(GL_CLIP_PLANE5);
            }
            glutPostRedisplay();
            break;
        }
        case 't':case 'T':{
            for(int i=0;i<partitions.size();i++)
                if(selection[i]) transparencies[i]=current_transparency;
            blending=1;glEnable(GL_BLEND);
            glutPostRedisplay();
            break;
        }
        case 'r':case 'R':{
            random_shuffle(mapcolor,mapcolor+partitions.size());
            starting_angle=int(clock())%360;
            glutPostRedisplay();
            break;
        }
        case 'o':case 'O':{
            for(int i=0;i<partitions.size();i++)
                if(selection[i]) transparencies[i]=1.0;
            glutPostRedisplay();
            break;
        }
        case ':':case ';':range1=number_to_read;select_range=2;number_to_read=0;start=clock();break;
        case '\r':case '\n':if(reading_number) override=1;break;
        case 'x':case 'X':case 'q':case 'Q':exit(0);
        }
    }
}

#define update_a1(inc) {if(a2>halfpi&&a2<3*halfpi) a1-=inc; else a1+=inc;if(a1<0)a1+=twopi;if(a1>twopi)a1-=twopi;}
#define update_a2(inc) {a2+=inc;if(a2<0)a2+=twopi;if(a2>twopi)a2-=twopi;if(a2>halfpi&&a2<3*halfpi) z=-1; else z=1;}
#define update_l(inc)  {l+=inc;if(l<minl)l=minl;if(l>maxl)l=maxl;}

void end_keyboard_preview(void)
{
    if((clock()-start)/CLOCKS_PER_SEC<timeout) return;
    reading_number=preview=0;
    glutIdleFunc(NULL);
    glutPostRedisplay();
}

void functionkeys(int key)
{
    if(glutGetModifiers()&&GLUT_ACTIVE_CTRL){
        for(int i=0;i<partitions.size();i++) backup_selection[i][key]=selection[i];
    } else {
        for(int i=0;i<partitions.size();i++) selection[i]=backup_selection[i][key];
        glutPostRedisplay();
    }
}

void special_keyboard(int key, int x, int y)    // handle arrow keys, pgup, pgdn
{
    switch(key){
    case GLUT_KEY_LEFT      :   update_a1( inca1);break;    // move camera left
    case GLUT_KEY_RIGHT     :   update_a1(-inca1);break;    // move camera right
    case GLUT_KEY_UP        :   update_a2(-inca2);break;    // move camera up
    case GLUT_KEY_DOWN      :   update_a2(+inca2);break;    // move camera down
    case GLUT_KEY_PAGE_UP   :   update_l(-incl);break;      // zoom in
    case GLUT_KEY_PAGE_DOWN :   update_l(+incl);break;      // zoom out
    case GLUT_KEY_F1        :   functionkeys(0);return;
    case GLUT_KEY_F2        :   functionkeys(1);return;
    case GLUT_KEY_F3        :   functionkeys(2);return;
    case GLUT_KEY_F4        :   functionkeys(3);return;
    case GLUT_KEY_F5        :   functionkeys(4);return;
    case GLUT_KEY_F6        :   functionkeys(5);return;
    case GLUT_KEY_F7        :   functionkeys(6);return;
    case GLUT_KEY_F8        :   functionkeys(7);return;
    default:return;
    }
    preview=1;
    start=clock();
    glutIdleFunc(end_keyboard_preview);
    eyex=l*cos(a2)*cos(a1)+centerx;
    eyey=l*cos(a2)*sin(a1)+centery;
    eyez=l*sin(a2)+centerz;
    glutPostRedisplay();
}

int zoom_mode=0,translate_mode=0;

void mousefunc(int button, int state, int x, int y)     // handle mouse button presses
{
    xpos=x;ypos=y;
    if(button==GLUT_MIDDLE_BUTTON){
        if(state==GLUT_DOWN){
            preview=1;
            translate_mode=1;
            glutDetachMenu(GLUT_RIGHT_BUTTON);
            glutSetCursor(GLUT_CURSOR_INFO);
        } else {
            translate_mode=0;
            preview=0;glutSetMenu(mainmenu);
            glutAttachMenu(GLUT_RIGHT_BUTTON);
            glutSetCursor(GLUT_CURSOR_LEFT_ARROW);
        }
    }
    if(button==GLUT_LEFT_BUTTON){
        if(state==GLUT_DOWN){
            preview=1;
            int mod=glutGetModifiers();
            if(mod & GLUT_ACTIVE_CTRL) translate_mode=1;
            if(mod & GLUT_ACTIVE_SHIFT) zoom_mode=1;
            glutDetachMenu(GLUT_RIGHT_BUTTON);
            switch((translate_mode << 1) | zoom_mode){
            case 0:glutSetCursor(GLUT_CURSOR_CYCLE);break;
            case 1:glutSetCursor(GLUT_CURSOR_UP_DOWN);break;
            case 2:glutSetCursor(GLUT_CURSOR_INFO);break;
            case 3:glutSetCursor(GLUT_CURSOR_UP_DOWN);break;
            }
        } else if(state==GLUT_UP){
            preview=zoom_mode=translate_mode=0;
            glutSetMenu(mainmenu);glutAttachMenu(GLUT_RIGHT_BUTTON);
            glutSetCursor(GLUT_CURSOR_LEFT_ARROW);
        }
    } else if(button==GLUT_RIGHT_BUTTON){
        if(state==GLUT_DOWN){
            zoom_mode=1;
            glutSetCursor(GLUT_CURSOR_UP_DOWN);
        } else {
           zoom_mode=0;
           if(preview)
               if(translate_mode) glutSetCursor(GLUT_CURSOR_INFO);
               else glutSetCursor(GLUT_CURSOR_CYCLE);
           else glutSetCursor(GLUT_CURSOR_LEFT_ARROW);
        }
    }
    glutPostRedisplay();
}

#define clip_scaling_factor 1.005

void motionfunc(int x, int y)               // handle mouse motion events (drag)
{
    if(!preview) return;
    if(translate_mode){
        REAL tx=eyex-centerx,ty=eyey-centery,tz=eyez-centerz;
        REAL hx=ty,hy=-tx,hz=0;
        REAL vx=ty*hz-tz*hy,vy=tz*hx-tx*hz,vz=tx*hy-ty*hx;
        REAL lenv=sqrt(vx*vx+vy*vy+vz*vz);
        REAL lenh=sqrt(hx*hx+hy*hy+hz*hz);
        if(zoom_mode){
            if(abs(x-xpos)>4*abs(y-ypos)){
                clip_size=clip_size*exp(log(clip_scaling_factor)*(x-xpos));
                if(clip_size<maxl/100) clip_size=maxl/100;
                else if(clip_size>maxl/4) clip_size=maxl/4;
                set_clip_planes();
            } else {
                REAL lent=sqrt(tx*tx+ty*ty+tz*tz);
                centerx-=(ypos-y)*incl*tx/lent/9;
                centery-=(ypos-y)*incl*ty/lent/9;
                centerz-=(ypos-y)*incl*tz/lent/9;
                eyex-=(ypos-y)*incl*tx/lent/9;
                eyey-=(ypos-y)*incl*ty/lent/9;
                eyez-=(ypos-y)*incl*tz/lent/9;
            }
        } else {
            centerx-=(ypos-y)*incl*vx/lenv/18;
            centery-=(ypos-y)*incl*vy/lenv/18;
            centerz-=(ypos-y)*incl*vz/lenv/18;
            centerx+=(xpos-x)*incl*hx/lenh/18;
            centery+=(xpos-x)*incl*hy/lenh/18;
            centerz+=(xpos-x)*incl*hz/lenh/18;
        }
    } else if(zoom_mode){
        if(abs(x-xpos)>4*abs(y-ypos)){
            if(x-xpos<0) clip_size/=1.04; else clip_size*=1.04;
            if(clip_size<maxl/100) clip_size=maxl/100;
            else if(clip_size>maxl/4) clip_size=maxl/4;
            set_clip_planes();
        } else update_l(-incl*(ypos-y)/9);
    } else {
        update_a1(pi*(xpos-x)/180);
        update_a2(-pi*(ypos-y)/180);
    }
    xpos=x;ypos=y;
    eyex=l*cos(a2)*cos(a1)+centerx;
    eyey=l*cos(a2)*sin(a1)+centery;
    eyez=l*sin(a2)+centerz;
    glutPostRedisplay();
}

enum menuitems { help, about, quit };

void menu(int item)
{
    switch(item){
    case quit:exit(0);
    case help:cout << helpstr << endl;break;
    case about:cout << aboutstr << endl;
    };
}

void model(int item)
{
    switch(item){
    case 0:wireframe=1;polygon=0;break;     // wireframe only
    case 1:wireframe=0;polygon=1;break;     // polygon only
    case 2:wireframe=polygon=1;break;       // outlined polygons
    case 3:wireframe=2;polygon=1;break;     // black polygons (wireframe with hidden lines)
    }
    glutPostRedisplay();
}

void lines(int item)
{
    switch(item){
    case 0:glDisable(GL_LINE_SMOOTH);glutPostRedisplay();return;    // no antialiasing
    case 1:glHint(GL_LINE_SMOOTH_HINT,GL_FASTEST);break;            // fastest method, low quality
    case 2:glHint(GL_LINE_SMOOTH_HINT,GL_DONT_CARE);break;          // don't care, leave to driver
    case 3:glHint(GL_LINE_SMOOTH_HINT,GL_NICEST);                   // best method, slow
    }
    glEnable(GL_LINE_SMOOTH);glutPostRedisplay();
}

void labels(int item)
{
    labelmode=item;
    glutPostRedisplay();
}

void culling(int item)
{
    switch(item){
    case 0:glDisable(GL_CULL_FACE);break;
    case 1:glCullFace(GL_FRONT);glEnable(GL_CULL_FACE);break;
    case 2:glCullFace(GL_BACK);glEnable(GL_CULL_FACE);break;
    }
    glutPostRedisplay();
}

void preview_selection(int item)
{
    previewmode=item;
}

void explosion(int item)
{
    explosion_factor=float(item)/10;
    glutPostRedisplay();
}

void transparency(int item)
{
    switch(item){
    case -1:glDisable(GL_BLEND);blending=0;break;
    case -2:glEnable(GL_BLEND);blending=1;break;
    case -3:glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);forcesort=1;break;
    case -4:glBlendFunc(GL_SRC_ALPHA, GL_ONE);forcesort=0;break;
    }
    if(item>=0){
        current_transparency=float(item)/10;
        blending=1;glEnable(GL_BLEND);
        for(int i=0;i<partitions.size();i++)
            if(selection[i]) transparencies[i]=current_transparency;
    }
    glutPostRedisplay();
}

void clipping(int item)
{
    if(item){
        clip_mode=1;
        glEnable(GL_CLIP_PLANE0);
        glEnable(GL_CLIP_PLANE1);
        glEnable(GL_CLIP_PLANE2);
        glEnable(GL_CLIP_PLANE3);
        glEnable(GL_CLIP_PLANE4);
        glEnable(GL_CLIP_PLANE5);
    } else {
        clip_mode=0;
        glDisable(GL_CLIP_PLANE0);
        glDisable(GL_CLIP_PLANE1);
        glDisable(GL_CLIP_PLANE2);
        glDisable(GL_CLIP_PLANE3);
        glDisable(GL_CLIP_PLANE4);
        glDisable(GL_CLIP_PLANE5);
    }
    glutPostRedisplay();
}

void console(int item)
{
    glutSetCursor(GLUT_CURSOR_WAIT);
    switch(item){
    case 0:{
            cout << "Center's current coordinates are " << centerx << " " << centery << " " << centerz << endl;
            cout << "Enter new coordinates (x y z) : ";
            cin >> centerx >> centery >> centerz;
            cout << endl;
            break;
        }
    case 1:{
            cout << "Eye's current coordinates are " << eyex << " " << eyey << " " << eyez << endl;
            cout << "Enter new coordinates (x y z) : ";
            cin >> eyex >> eyey >> eyez;
            cout << endl;
            break;
            }
    case 2:{
            cout << "Current clipping cube size is " << clip_size << endl;
            cout << "Enter new size : ";
            cin >> clip_size;
            cout << endl;
            break;
        }
    case 3:{
            cout << "Current transparency factor is " << current_transparency << endl;
            cout << "Enter new factor (0.0 completely transparent 1.0 completely opaque) : ";
            cin >> current_transparency;
            cout << endl;
            blending=1;glEnable(GL_BLEND);
            for(int i=0;i<partitions.size();i++)
                if(selection[i]) transparencies[i]=current_transparency;
            break;
        }
    case 4:{
            cout << "Current explosion factor is " << explosion_factor << endl;
            cout << "Enter new factor (must be between 0.0 and 1.0 for regular applications) : ";
            cin >> explosion_factor;
            cout << endl;
            break;
        }
    }
    glutSetCursor(GLUT_CURSOR_LEFT_ARROW);
    glutPostRedisplay();
}

void colormap(int item)
{
    switch(item){
    case 0:{
        starting_angle=0;
        for(int i=0;i<partitions.size();i++) mapcolor[i]=i;
        break;
    }
    case 1:
        random_shuffle(mapcolor,mapcolor+partitions.size());
        starting_angle=int(clock())%360;
        break;
    }
    glutPostRedisplay();
}

void initmenu(void)                 // build right click menu
{
    int modelmenu=glutCreateMenu(model);
    glutAddMenuEntry("Wireframe",0);
    glutAddMenuEntry("Black polygons",3);
    glutAddMenuEntry("Polygons only",1);
    glutAddMenuEntry("Outlined polygons",2);

    int linesmenu=glutCreateMenu(lines);
    glutAddMenuEntry("Antialiasing off",0);
    glutAddMenuEntry("Low quality",1);
    glutAddMenuEntry("Don't care",2);
    glutAddMenuEntry("Best quality",3);

    int labelmenu=glutCreateMenu(labels);
    glutAddMenuEntry("Labels off",0);
    glutAddMenuEntry("Preview mode only",1);
    glutAddMenuEntry("All modes",2);

    int previewmenu=glutCreateMenu(preview_selection);
    glutAddMenuEntry("Off",0);
    glutAddMenuEntry("On",1);

    int blendmenu=glutCreateMenu(transparency);
    glutAddMenuEntry("One minus source alpha",-3);
    glutAddMenuEntry("One",-4);

    int transmenu=glutCreateMenu(transparency);
    glutAddMenuEntry("Disable",-1);
    glutAddMenuEntry("Enable",-2);
    glutAddSubMenu("Function",blendmenu);
    glutAddMenuEntry("0.0 (Hidden)",0);
    glutAddMenuEntry("0.1",1);
    glutAddMenuEntry("0.2",2);
    glutAddMenuEntry("0.3",3);
    glutAddMenuEntry("0.4",4);
    glutAddMenuEntry("0.5",5);
    glutAddMenuEntry("0.6",6);
    glutAddMenuEntry("0.7",7);
    glutAddMenuEntry("0.8",8);
    glutAddMenuEntry("0.9",9);
    glutAddMenuEntry("1.0 (Opaque)",10);

    int explosionmenu=glutCreateMenu(explosion);
    glutAddMenuEntry("0.2",2);
    glutAddMenuEntry("0.3",3);
    glutAddMenuEntry("0.4",4);
    glutAddMenuEntry("0.5",5);
    glutAddMenuEntry("0.6",6);
    glutAddMenuEntry("0.7",7);
    glutAddMenuEntry("0.8",8);
    glutAddMenuEntry("0.9",9);
    glutAddMenuEntry("1.0",10);

    int cullmenu=glutCreateMenu(culling);
    glutAddMenuEntry("Culing off",0);
    glutAddMenuEntry("Front face",1);
    glutAddMenuEntry("Back face",2);

    int clipmenu=glutCreateMenu(clipping);
    glutAddMenuEntry("Clipping off",0);
    glutAddMenuEntry("Clipping on",1);

    int colormenu=glutCreateMenu(colormap);
    glutAddMenuEntry("Default",0);
    glutAddMenuEntry("Random",1);

    int consolemenu=glutCreateMenu(console);
    glutAddMenuEntry("Center position",0);
    glutAddMenuEntry("Camera position",1);
    glutAddMenuEntry("Clipping cube size",2);
    glutAddMenuEntry("Transparency factor",3);
    glutAddMenuEntry("Explosion factor",4);

    mainmenu=glutCreateMenu(menu);
    glutAddSubMenu("Model",modelmenu);
    glutAddSubMenu("Lines",linesmenu);
    glutAddSubMenu("Labels",labelmenu);
    glutAddSubMenu("Preview",previewmenu);
    glutAddSubMenu("Culling",cullmenu);
    glutAddSubMenu("Clipping",clipmenu);
    glutAddSubMenu("Explosion",explosionmenu);
    glutAddSubMenu("Transparency",transmenu);
    glutAddSubMenu("Colormap",colormenu);
    glutAddSubMenu("Console Input",consolemenu);
    glutAddMenuEntry("Help",help);
    glutAddMenuEntry("About",about);
    glutAddMenuEntry("Quit",quit);
    glutAttachMenu(GLUT_RIGHT_BUTTON);
}

int main(int argc, char **argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
    glutInitWindowSize(800, 800);
    glutInitWindowPosition(0,0);
    glutCreateWindow("Partitioned Mesh Visualizer");

    init(argc, argv);
    initmenu();

    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutKeyboardFunc(keyboard);
    glutSpecialFunc(special_keyboard);
    glutMouseFunc(mousefunc);
    glutMotionFunc(motionfunc);

    glutMainLoop();
}
