summaryrefslogblamecommitdiffstats
path: root/hacks/glx/hypertorus.c
blob: 8adf5d71d6e7194d9ae99498476aa2156021c2cd (plain) (tree)
1
2
3
4
5
6
7
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639


                                                          
                                                                        

      
                                                                     



















                                                                          





                                                                         


   

































                                                                      













                                    


                                    









                                               
                                          








                                                









                                                                        


                                                     
                                                     
 







                                                                        
                       









                                                                
                        








                        
                          




























                                                                         
                                                                      

                                                                        

                                                                













                                                                          











                                                                                  


                                        






                                                                               
 


                                           





                                             

                             





                                          
                    


















                                                     




                                                           



















































































































































                                                                        



































































































































                                                                       






































































                                                                    















































                                                                    
                                                              

        

                   
 



                                                               
 





























































                                                                     

                                       
                 
      
                 


 






                                                                             
 





                                                                         







                                                                         






                                                   





































































                                                                
 


                                                







                                                                        
                     
   
                                  
     










                                                              
     
                                       
     










                                                                          

     
                          
   















                                                                          
   
















                                                             
                






                                               
                                           


            





                                                                     





























































                                                      



            



                                     



               








                                                                             
 




















                                                                         
                                               
            
 

                       
 

                                                
 
                                                                        
 








































































































































































































































                                                                          




                                   

                         








                                                               



                                            


                                     








                                                               
   
                                           
   


                            
                        
                                              
   



















































































































































































































































































































                                                                             



























                                               












                             

   


                                                                              
      

                                                                              





                                                                       


                            

                                              



















































































                                                                               
































































                                                                       




                                              













































                                                                           


























                                                                               
                                                    
 

                                    
                                                   



















                                               
                                                                  



                        
 






                                                                   












                                             

 
 


                                              
/* hypertorus --- Shows a hypertorus that rotates in 4d */

#if 0
static const char sccsid[] = "@(#)hypertorus.c  1.2 05/09/28 xlockmore";
#endif

/* Copyright (c) 2003-2021 Carsten Steger <carsten@mirsanmir.org>. */

/*
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in
 * supporting documentation.
 *
 * This file is provided AS IS with no warranties of any kind.  The author
 * shall have no liability with respect to the infringement of copyrights,
 * trade secrets or any patents by this file or any part thereof.  In no
 * event will the author be liable for any lost revenue or profits or
 * other special, indirect and consequential damages.
 *
 * REVISION HISTORY:
 * C. Steger - 03/05/18: Initial version
 * C. Steger - 05/09/28: Added the spirals appearance mode
 *                       and trackball support
 * C. Steger - 07/01/23: Improved 4d trackball support
 * C. Steger - 09/08/22: Removed check-config.pl warnings
 * C. Steger - 20/01/11: Added the changing colors mode
 * C. Steger - 20/05/18: Added per-fragment shading
 * C. Steger - 20/07/26: Make the shader code work under maxOS
 * C. Steger - 20/11/19: Remove all unnecessary global variables
 * C. Steger - 20/12/06: Moved all GLSL support code into glsl-utils.[hc]
 * C. Steger - 20/12/30: Make the shader code work under iOS
 */

/*
 * This program shows the Clifford torus as it rotates in 4d.  The
 * Clifford torus is a torus lies on the "surface" of the hypersphere
 * in 4d.  The program projects the 4d torus to 3d using either a
 * perspective or an orthographic projection.  Of the two
 * alternatives, the perspective projection looks much more appealing.
 * In orthographic projections the torus degenerates into a doubly
 * covered cylinder for some angles.  The projected 3d torus can then
 * be projected to the screen either perspectively or
 * orthographically.
 *
 * There are three display modes for the torus: mesh (wireframe),
 * solid, or transparent.  Furthermore, the appearance of the torus
 * can be as a solid object or as a set of see-through bands or
 * see-through spirals.  Finally, the colors with with the torus is
 * drawn can be set to one-sided, two-sided, or to a color wheel.  The
 * colors can be static or changing dynamically.  In one-sided color
 * mode, the torus is drawn with the same color on the inside and the
 * outside.  In two-sided color mode, the torus is drawn with red on
 * the outside and green on the inside if static colors are used.  If
 * changing colors are used, dynamically varying complementary colors
 * are used for the two sides.  This mode enables you to see that the
 * 3d projection of the torus turns inside-out as it rotates in 4d.
 * The color wheel mode draws the torus with a fully saturated color
 * wheel.  If changing colors are used, the colors of the color wheel
 * are varying dynamically.  The color wheel mode gives a very nice
 * effect when combined with the see-through bands or see-through
 * spirals mode.
 *
 * Finally, the rotation speed for each of the six planes around which
 * the torus rotates can be chosen.
 *
 * This program is inspired by Thomas Banchoff's book "Beyond the
 * Third Dimension: Geometry, Computer Graphics, and Higher
 * Dimensions", Scientific American Library, 1990.
 */

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

#define DISP_WIREFRAME             0
#define DISP_SURFACE               1
#define DISP_TRANSPARENT           2

#define APPEARANCE_SOLID           0
#define APPEARANCE_BANDS           1
#define APPEARANCE_SPIRALS         2

#define COLORS_ONESIDED            0
#define COLORS_TWOSIDED            1
#define COLORS_COLORWHEEL          2

#define DISP_3D_PERSPECTIVE        0
#define DISP_3D_ORTHOGRAPHIC       1

#define DISP_4D_PERSPECTIVE        0
#define DISP_4D_ORTHOGRAPHIC       1

#define DEF_DISPLAY_MODE           "surface"
#define DEF_APPEARANCE             "bands"
#define DEF_COLORS                 "colorwheel"
#define DEF_CHANGE_COLORS          "False"
#define DEF_PROJECTION_3D          "perspective"
#define DEF_PROJECTION_4D          "perspective"
#define DEF_SPEEDWX                "1.1"
#define DEF_SPEEDWY                "1.3"
#define DEF_SPEEDWZ                "1.5"
#define DEF_SPEEDXY                "1.7"
#define DEF_SPEEDXZ                "1.9"
#define DEF_SPEEDYZ                "2.1"


/* For some strange reason, the color buffer must be initialized
   and used on macOS. Otherwise one- and two-sided lighting will
   not work. */
#if (defined(HAVE_COCOA) || defined(__APPLE__)) && !defined(HAVE_IPHONE)
#define VERTEXATTRIBARRAY_WORKAROUND
#endif



#ifdef STANDALONE
# define DEFAULTS           "*delay:      25000 \n" \
                            "*showFPS:    False \n" \
                            "*prefersGLSL: True \n" \

# define release_hypertorus 0
# include "xlockmore.h"         /* from the xscreensaver distribution */
#else  /* !STANDALONE */
# include "xlock.h"             /* from the xlockmore distribution */
#endif /* !STANDALONE */

#ifdef USE_GL

#include "glsl-utils.h"
#include "gltrackball.h"


#ifdef USE_MODULES
ModStruct   hypertorus_description =
{"hypertorus", "init_hypertorus", "draw_hypertorus", NULL,
 "draw_hypertorus", "change_hypertorus", NULL, &hypertorus_opts,
 25000, 1, 1, 1, 1.0, 4, "",
 "Shows a hypertorus rotating in 4d", 0, NULL};

#endif /* USE_MODULES */


static char *mode;
static int display_mode;
static char *appear;
static int appearance;
static int num_spirals;
static char *color_mode;
static int colors;
static Bool change_colors;
static char *proj_3d;
static int projection_3d;
static char *proj_4d;
static int projection_4d;
static float speed_wx;
static float speed_wy;
static float speed_wz;
static float speed_xy;
static float speed_xz;
static float speed_yz;

static const float offset4d[4] = {  0.0,  0.0,  0.0,  2.0 };
static const float offset3d[4] = {  0.0,  0.0, -2.0,  0.0 };


static XrmOptionDescRec opts[] =
{
  {"-mode",            ".displayMode",  XrmoptionSepArg, 0 },
  {"-wireframe",       ".displayMode",  XrmoptionNoArg,  "wireframe" },
  {"-surface",         ".displayMode",  XrmoptionNoArg,  "surface" },
  {"-transparent",     ".displayMode",  XrmoptionNoArg,  "transparent" },
  {"-appearance",      ".appearance",   XrmoptionSepArg, 0 },
  {"-solid",           ".appearance",   XrmoptionNoArg,  "solid" },
  {"-bands",           ".appearance",   XrmoptionNoArg,  "bands" },
  {"-spirals-1",       ".appearance",   XrmoptionNoArg,  "spirals-1" },
  {"-spirals-2",       ".appearance",   XrmoptionNoArg,  "spirals-2" },
  {"-spirals-4",       ".appearance",   XrmoptionNoArg,  "spirals-4" },
  {"-spirals-8",       ".appearance",   XrmoptionNoArg,  "spirals-8" },
  {"-spirals-16",      ".appearance",   XrmoptionNoArg,  "spirals-16" },
  {"-onesided",        ".colors",       XrmoptionNoArg,  "onesided" },
  {"-twosided",        ".colors",       XrmoptionNoArg,  "twosided" },
  {"-colorwheel",      ".colors",       XrmoptionNoArg,  "colorwheel" },
  {"-change-colors",   ".changeColors", XrmoptionNoArg,  "on"},
  {"+change-colors",   ".changeColors", XrmoptionNoArg,  "off"},
  {"-perspective-3d",  ".projection3d", XrmoptionNoArg,  "perspective" },
  {"-orthographic-3d", ".projection3d", XrmoptionNoArg,  "orthographic" },
  {"-perspective-4d",  ".projection4d", XrmoptionNoArg,  "perspective" },
  {"-orthographic-4d", ".projection4d", XrmoptionNoArg,  "orthographic" },
  {"-speed-wx",        ".speedwx",      XrmoptionSepArg, 0 },
  {"-speed-wy",        ".speedwy",      XrmoptionSepArg, 0 },
  {"-speed-wz",        ".speedwz",      XrmoptionSepArg, 0 },
  {"-speed-xy",        ".speedxy",      XrmoptionSepArg, 0 },
  {"-speed-xz",        ".speedxz",      XrmoptionSepArg, 0 },
  {"-speed-yz",        ".speedyz",      XrmoptionSepArg, 0 }
};

static argtype vars[] =
{
  { &mode,          "displayMode",  "DisplayMode",  DEF_DISPLAY_MODE,  t_String },
  { &appear,        "appearance",   "Appearance",   DEF_APPEARANCE,    t_String },
  { &color_mode,    "colors",       "Colors",       DEF_COLORS,        t_String },
  { &change_colors, "changeColors", "ChangeColors", DEF_CHANGE_COLORS, t_Bool },
  { &proj_3d,       "projection3d", "Projection3d", DEF_PROJECTION_3D, t_String },
  { &proj_4d,       "projection4d", "Projection4d", DEF_PROJECTION_4D, t_String },
  { &speed_wx,      "speedwx",      "Speedwx",      DEF_SPEEDWX,       t_Float},
  { &speed_wy,      "speedwy",      "Speedwy",      DEF_SPEEDWY,       t_Float},
  { &speed_wz,      "speedwz",      "Speedwz",      DEF_SPEEDWZ,       t_Float},
  { &speed_xy,      "speedxy",      "Speedxy",      DEF_SPEEDXY,       t_Float},
  { &speed_xz,      "speedxz",      "Speedxz",      DEF_SPEEDXZ,       t_Float},
  { &speed_yz,      "speedyz",      "Speedyz",      DEF_SPEEDYZ,       t_Float}
};

ENTRYPOINT ModeSpecOpt hypertorus_opts =
{sizeof opts / sizeof opts[0], opts, sizeof vars / sizeof vars[0], vars, NULL};


/* Color change speeds */
#define DRHO    0.7
#define DSIGMA  1.1
#define DTAU    1.7

/* Number of subdivisions of the surface */
#define NUMU 64
#define NUMV 64

typedef struct {
  GLint      WindH, WindW;
  GLXContext *glx_context;
  /* 4D rotation angles */
  float alpha, beta, delta, zeta, eta, theta;
  /* Color rotation angles */
  float rho, sigma, tau;
  /* Aspect ratio of the current window */
  float aspect;
  /* Trackball states */
  trackball_state *trackballs[2];
  int current_trackball;
  Bool button_pressed;
  float speed_scale;
#ifdef HAVE_GLSL
  GLfloat uv[(NUMU+1)*(NUMV+1)][2];
  GLfloat col[(NUMU+1)*(NUMV+1)][4];
  GLuint indices[4*NUMU*NUMV];
  Bool use_shaders, buffers_initialized;
  GLuint shader_program;
  GLint vertex_uv_index, color_index;
  GLint mat_rot_index, mat_p_index, bool_persp_index;
  GLint off4d_index, off3d_index, draw_lines_index;
  GLint glbl_ambient_index, lt_ambient_index;
  GLint lt_diffuse_index, lt_specular_index;
  GLint lt_direction_index, lt_halfvect_index;
  GLint front_ambient_index, back_ambient_index;
  GLint front_diffuse_index, back_diffuse_index;
  GLint specular_index, shininess_index;
  GLuint vertex_uv_buffer;
  GLuint color_buffer, indices_buffer;
  GLint ni, ne, nt;
#endif /* HAVE_GLSL */
} hypertorusstruct;

static hypertorusstruct *hyper = (hypertorusstruct *) NULL;



#ifdef HAVE_GLSL

/* The GLSL versions that correspond to different versions of OpenGL. */
static const GLchar *shader_version_2_1 =
  "#version 120\n";
static const GLchar *shader_version_3_0 =
  "#version 130\n";
static const GLchar *shader_version_3_0_es =
  "#version 300 es\n"
  "precision highp float;\n"
  "precision highp int;\n";

/* The vertex shader code is composed of code fragments that depend on
   the OpenGL version and code fragments that are version-independent.
   They are concatenated by glsl_CompileAndLinkShaders in the function
   init_glsl(). */
static const GLchar *vertex_shader_attribs_2_1 =
  "attribute vec2 VertexUV;\n"
  "attribute vec4 VertexColor;\n"
  "\n"
  "varying vec3 Normal;\n"
  "varying vec4 Color;\n"
  "\n";
static const GLchar *vertex_shader_attribs_3_0 =
  "in vec2 VertexUV;\n"
  "in vec4 VertexColor;\n"
  "\n"
  "out vec3 Normal;\n"
  "out vec4 Color;\n"
  "\n";
static const GLchar *vertex_shader_main =
  "uniform mat4 MatRot4D;\n"
  "uniform mat4 MatProj;\n"
  "uniform bool BoolPersp;\n"
  "uniform vec4 Offset4D;\n"
  "uniform vec4 Offset3D;\n"
  "\n"
  "void main (void)\n"
  "{\n"
  "  vec3 p, pu, pv;\n"
  "  float su = sin(VertexUV.x)\n;"
  "  float cu = cos(VertexUV.x)\n;"
  "  float sv = sin(VertexUV.y)\n;"
  "  float cv = cos(VertexUV.y)\n;"
  "  vec4 xx = vec4(cu,su,cv,sv);"
  "  vec4 xxu = vec4(-su,cu,0.0,0.0);"
  "  vec4 xxv = vec4(0.0,0.0,-sv,cv);"
  "  vec4 x = MatRot4D*xx+Offset4D;\n"
  "  vec4 xu = MatRot4D*xxu;\n"
  "  vec4 xv = MatRot4D*xxv;\n"
  "  if (BoolPersp)\n"
  "  {\n"
  "    vec3 r = x.xyz;\n"
  "    float s = x.w;\n"
  "    float t = s*s;\n"
  "    p = r/s+Offset3D.xyz;\n"
  "    pu = (s*xu.xyz-r*xu.w)/t;\n"
  "    pv = (s*xv.xyz-r*xv.w)/t;\n"
  "  }\n"
  "  else\n"
  "  {\n"
  "    p = x.xyz/1.5f+Offset3D.xyz;\n"
  "    pu = xu.xyz;\n"
  "    pv = xv.xyz;\n"
  "  }\n"
  "  vec4 Position = vec4(p,1.0);\n"
  "  Normal = normalize(cross(pu,pv));\n"
  "  gl_Position = MatProj*Position;\n"
  "  Color = VertexColor;\n"
  "}\n";

/* The fragment shader code is composed of code fragments that depend on
   the OpenGL version and code fragments that are version-independent.
   They are concatenated by glsl_CompileAndLinkShaders in the function
   init_glsl_glsl(). */
static const GLchar *fragment_shader_attribs_2_1 =
  "varying vec3 Normal;\n"
  "varying vec4 Color;\n"
  "\n";
static const GLchar *fragment_shader_attribs_3_0 =
  "in vec3 Normal;\n"
  "in vec4 Color;\n"
  "\n"
  "out vec4 FragColor;\n"
  "\n";
static const GLchar *fragment_shader_main =
  "uniform bool DrawLines;\n"
  "uniform vec4 LtGlblAmbient;\n"
  "uniform vec4 LtAmbient, LtDiffuse, LtSpecular;\n"
  "uniform vec3 LtDirection, LtHalfVector;\n"
  "uniform vec4 MatFrontAmbient, MatBackAmbient;\n"
  "uniform vec4 MatFrontDiffuse, MatBackDiffuse;\n"
  "uniform vec4 MatSpecular;\n"
  "uniform float MatShininess;\n"
  "\n"
  "void main (void)\n"
  "{\n"
  "  vec4 color;\n"
  "  if (DrawLines)\n"
  "  {\n"
  "    color = Color;\n"
  "  }\n"
  "  else\n"
  "  {\n"
  "    vec3 normalDirection;\n"
  "    vec4 ambientColor, diffuseColor, sceneColor;\n"
  "    vec4 ambientLighting, diffuseReflection, specularReflection;\n"
  "    float ndotl, ndoth, pf;\n"
  "    \n"
  "    if (gl_FrontFacing)\n"
  "    {\n"
  "      normalDirection = normalize(Normal);\n"
  "      sceneColor = Color*MatFrontAmbient*LtGlblAmbient;\n"
  "      ambientColor = Color*MatFrontAmbient;\n"
  "      diffuseColor = Color*MatFrontDiffuse;\n"
  "    }\n"
  "    else\n"
  "    {\n"
  "      normalDirection = -normalize(Normal);\n"
  "      sceneColor = Color*MatBackAmbient*LtGlblAmbient;\n"
  "      ambientColor = Color*MatBackAmbient;\n"
  "      diffuseColor = Color*MatBackDiffuse;\n"
  "    }\n"
  "    \n"
  "    ndotl = max(0.0,dot(normalDirection,LtDirection));\n"
  "    ndoth = max(0.0,dot(normalDirection,LtHalfVector));\n"
  "    if (ndotl == 0.0)\n"
  "      pf = 0.0;\n"
  "    else\n"
  "      pf = pow(ndoth,MatShininess);\n"
  "    ambientLighting = ambientColor*LtAmbient;\n"
  "    diffuseReflection = LtDiffuse*diffuseColor*ndotl;\n"
  "    specularReflection = LtSpecular*MatSpecular*pf;\n"
  "    color = (sceneColor+ambientLighting+diffuseReflection+\n"
  "             specularReflection);\n"
  "  }\n";
static const GLchar *fragment_shader_out_2_1 =
  "  gl_FragColor = clamp(color,0.0,1.0);\n"
  "}\n";
static const GLchar *fragment_shader_out_3_0 =
  "  FragColor = clamp(color,0.0,1.0);\n"
  "}\n";

#endif /* HAVE_GLSL */



/* Add a rotation around the wx-plane to the matrix m. */
static void rotatewx(float m[4][4], float phi)
{
  float c, s, u, v;
  int i;

  phi *= M_PI/180.0;
  c = cos(phi);
  s = sin(phi);
  for (i=0; i<4; i++)
  {
    u = m[i][1];
    v = m[i][2];
    m[i][1] = c*u+s*v;
    m[i][2] = -s*u+c*v;
  }
}


/* Add a rotation around the wy-plane to the matrix m. */
static void rotatewy(float m[4][4], float phi)
{
  float c, s, u, v;
  int i;

  phi *= M_PI/180.0;
  c = cos(phi);
  s = sin(phi);
  for (i=0; i<4; i++)
  {
    u = m[i][0];
    v = m[i][2];
    m[i][0] = c*u-s*v;
    m[i][2] = s*u+c*v;
  }
}


/* Add a rotation around the wz-plane to the matrix m. */
static void rotatewz(float m[4][4], float phi)
{
  float c, s, u, v;
  int i;

  phi *= M_PI/180.0;
  c = cos(phi);
  s = sin(phi);
  for (i=0; i<4; i++)
  {
    u = m[i][0];
    v = m[i][1];
    m[i][0] = c*u+s*v;
    m[i][1] = -s*u+c*v;
  }
}


/* Add a rotation around the xy-plane to the matrix m. */
static void rotatexy(float m[4][4], float phi)
{
  float c, s, u, v;
  int i;

  phi *= M_PI/180.0;
  c = cos(phi);
  s = sin(phi);
  for (i=0; i<4; i++)
  {
    u = m[i][2];
    v = m[i][3];
    m[i][2] = c*u+s*v;
    m[i][3] = -s*u+c*v;
  }
}


/* Add a rotation around the xz-plane to the matrix m. */
static void rotatexz(float m[4][4], float phi)
{
  float c, s, u, v;
  int i;

  phi *= M_PI/180.0;
  c = cos(phi);
  s = sin(phi);
  for (i=0; i<4; i++)
  {
    u = m[i][1];
    v = m[i][3];
    m[i][1] = c*u-s*v;
    m[i][3] = s*u+c*v;
  }
}


/* Add a rotation around the yz-plane to the matrix m. */
static void rotateyz(float m[4][4], float phi)
{
  float c, s, u, v;
  int i;

  phi *= M_PI/180.0;
  c = cos(phi);
  s = sin(phi);
  for (i=0; i<4; i++)
  {
    u = m[i][0];
    v = m[i][3];
    m[i][0] = c*u-s*v;
    m[i][3] = s*u+c*v;
  }
}


/* Compute the rotation matrix m from the rotation angles. */
static void rotateall(float al, float be, float de, float ze, float et,
                      float th, float m[4][4])
{
  int i, j;

  for (i=0; i<4; i++)
    for (j=0; j<4; j++)
      m[i][j] = (i==j);
  rotatewx(m,al);
  rotatewy(m,be);
  rotatewz(m,de);
  rotatexz(m,et);
  rotatexy(m,ze);
  rotateyz(m,th);
}


/* Add a rotation around the x-axis to the matrix m. */
static void rotatex(float m[3][3], float phi)
{
  float c, s, u, v;
  int i;

  phi *= M_PI/180.0;
  c = cos(phi);
  s = sin(phi);
  for (i=0; i<3; i++)
  {
    u = m[i][1];
    v = m[i][2];
    m[i][1] = c*u+s*v;
    m[i][2] = -s*u+c*v;
  }
}


/* Add a rotation around the y-axis to the matrix m. */
static void rotatey(float m[3][3], float phi)
{
  float c, s, u, v;
  int i;

  phi *= M_PI/180.0;
  c = cos(phi);
  s = sin(phi);
  for (i=0; i<3; i++)
  {
    u = m[i][0];
    v = m[i][2];
    m[i][0] = c*u-s*v;
    m[i][2] = s*u+c*v;
  }
}


/* Add a rotation around the z-axis to the matrix m. */
static void rotatez(float m[3][3], float phi)
{
  float c, s, u, v;
  int i;

  phi *= M_PI/180.0;
  c = cos(phi);
  s = sin(phi);
  for (i=0; i<3; i++)
  {
    u = m[i][0];
    v = m[i][1];
    m[i][0] = c*u+s*v;
    m[i][1] = -s*u+c*v;
  }
}


/* Compute the 3d rotation matrix m from the 3d rotation angles. */
static void rotateall3d(float al, float be, float de, float m[3][3])
{
  int i, j;

  for (i=0; i<3; i++)
    for (j=0; j<3; j++)
      m[i][j] = (i==j);
  rotatex(m,al);
  rotatey(m,be);
  rotatez(m,de);
}


/* Multiply two rotation matrices: o=m*n. */
static void mult_rotmat(float m[4][4], float n[4][4], float o[4][4])
{
  int i, j, k;

  for (i=0; i<4; i++)
  {
    for (j=0; j<4; j++)
    {
      o[i][j] = 0.0;
      for (k=0; k<4; k++)
        o[i][j] += m[i][k]*n[k][j];
    }
  }
}


/* Compute a 4D rotation matrix from two unit quaternions. */
static void quats_to_rotmat(float p[4], float q[4], float m[4][4])
{
  double al, be, de, ze, et, th;
  double r00, r01, r02, r12, r22;

  r00 = 1.0-2.0*(p[1]*p[1]+p[2]*p[2]);
  r01 = 2.0*(p[0]*p[1]+p[2]*p[3]);
  r02 = 2.0*(p[2]*p[0]-p[1]*p[3]);
  r12 = 2.0*(p[1]*p[2]+p[0]*p[3]);
  r22 = 1.0-2.0*(p[1]*p[1]+p[0]*p[0]);

  al = atan2(-r12,r22)*180.0/M_PI;
  be = atan2(r02,sqrt(r00*r00+r01*r01))*180.0/M_PI;
  de = atan2(-r01,r00)*180.0/M_PI;

  r00 = 1.0-2.0*(q[1]*q[1]+q[2]*q[2]);
  r01 = 2.0*(q[0]*q[1]+q[2]*q[3]);
  r02 = 2.0*(q[2]*q[0]-q[1]*q[3]);
  r12 = 2.0*(q[1]*q[2]+q[0]*q[3]);
  r22 = 1.0-2.0*(q[1]*q[1]+q[0]*q[0]);

  et = atan2(-r12,r22)*180.0/M_PI;
  th = atan2(r02,sqrt(r00*r00+r01*r01))*180.0/M_PI;
  ze = atan2(-r01,r00)*180.0/M_PI;

  rotateall(al,be,de,ze,et,-th,m);
}


/* Compute a fully saturated and bright color based on an angle. */
static void color(double angle, float mat[3][3], float col[4])
{
  int s;
  double t, ca, sa;
  float m;

  if (!change_colors)
  {
    if (colors == COLORS_ONESIDED || colors == COLORS_TWOSIDED)
      return;

    if (angle >= 0.0)
      angle = fmod(angle,2*M_PI);
    else
      angle = fmod(angle,-2*M_PI);
    s = floor(angle/(M_PI/3));
    t = angle/(M_PI/3)-s;
    if (s >= 6)
      s = 0;
    switch (s)
    {
      case 0:
        col[0] = 1.0;
        col[1] = t;
        col[2] = 0.0;
        break;
      case 1:
        col[0] = 1.0-t;
        col[1] = 1.0;
        col[2] = 0.0;
        break;
      case 2:
        col[0] = 0.0;
        col[1] = 1.0;
        col[2] = t;
        break;
      case 3:
        col[0] = 0.0;
        col[1] = 1.0-t;
        col[2] = 1.0;
        break;
      case 4:
        col[0] = t;
        col[1] = 0.0;
        col[2] = 1.0;
        break;
      case 5:
        col[0] = 1.0;
        col[1] = 0.0;
        col[2] = 1.0-t;
        break;
    }
  }
  else /* change_colors */
  {
    if (colors == COLORS_ONESIDED || colors == COLORS_TWOSIDED)
    {
      col[0] = mat[0][2];
      col[1] = mat[1][2];
      col[2] = mat[2][2];
    }
    else
    {
      ca = cos(angle);
      sa = sin(angle);
      col[0] = ca*mat[0][0]+sa*mat[0][1];
      col[1] = ca*mat[1][0]+sa*mat[1][1];
      col[2] = ca*mat[2][0]+sa*mat[2][1];
    }
    m = 0.5f/fmaxf(fmaxf(fabsf(col[0]),fabsf(col[1])),fabsf(col[2]));
    col[0] = m*col[0]+0.5f;
    col[1] = m*col[1]+0.5f;
    col[2] = m*col[2]+0.5f;
  }
  if (display_mode == DISP_TRANSPARENT)
    col[3] = 0.7;
  else
    col[3] = 1.0;
}


/* Draw a hypertorus projected into 3D using OpenGL's fixed
   functionality.  Note that the spirals appearance will only work
   correctly if numu and numv are set to 64 or any higher power of 2.
   Similarly, the banded appearance will only work correctly if numu
   and numv are divisible by 4. */
static int hypertorus_ff(ModeInfo *mi, double umin, double umax, double vmin,
                         double vmax, int numu, int numv)
{
  static const GLfloat light_model_ambient[]    = { 0.2, 0.2, 0.2, 1.0 };
  static const GLfloat light_ambient[]          = { 0.0, 0.0, 0.0, 1.0 };
  static const GLfloat light_diffuse[]          = { 1.0, 1.0, 1.0, 1.0 };
  static const GLfloat light_specular[]         = { 1.0, 1.0, 1.0, 1.0 };
  static const GLfloat light_position[]         = { 1.0, 1.0, 1.0, 0.0 };
  static const GLfloat mat_specular[]           = { 1.0, 1.0, 1.0, 1.0 };
  static const GLfloat mat_diff_red[]           = { 1.0, 0.0, 0.0, 1.0 };
  static const GLfloat mat_diff_green[]         = { 0.0, 1.0, 0.0, 1.0 };
  static const GLfloat mat_diff_oneside[]       = { 0.9, 0.4, 0.3, 1.0 };
  static const GLfloat mat_diff_trans_red[]     = { 1.0, 0.0, 0.0, 0.7 };
  static const GLfloat mat_diff_trans_green[]   = { 0.0, 1.0, 0.0, 0.7 };
  static const GLfloat mat_diff_trans_oneside[] = { 0.9, 0.4, 0.3, 0.7 };
  float mat_diff_dyn[4], mat_diff_dyn_compl[4];
  float p[3], pu[3], pv[3], n[3], mat[4][4], matc[3][3], col[4];
  int i, j, k, l, m, b, skew;
  double u, v, ur, vr;
  double cu, su, cv, sv;
  double xx[4], xxu[4], xxv[4], x[4], xu[4], xv[4];
  double r, s, t;
  float q1[4], q2[4], r1[4][4], r2[4][4];
  hypertorusstruct *hp = &hyper[MI_SCREEN(mi)];
  int polys;

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  if (projection_3d == DISP_3D_ORTHOGRAPHIC)
  {
    if (hp->aspect >= 1.0)
      glOrtho(-hp->aspect,hp->aspect,-1.0,1.0,0.1,10.0);
    else
      glOrtho(-1.0,1.0,-1.0/hp->aspect,1.0/hp->aspect,0.1,10.0);
  }
  else
  {
    gluPerspective(60.0,hp->aspect,0.1,10.0);
  }
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  if (display_mode == DISP_SURFACE)
  {
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);
    glShadeModel(GL_SMOOTH);
    glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
    glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT,light_model_ambient);
    glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER,GL_FALSE);
    glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL,GL_SINGLE_COLOR);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glLightfv(GL_LIGHT0,GL_AMBIENT,light_ambient);
    glLightfv(GL_LIGHT0,GL_DIFFUSE,light_diffuse);
    glLightfv(GL_LIGHT0,GL_SPECULAR,light_specular);
    glLightfv(GL_LIGHT0,GL_POSITION,light_position);
    glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,mat_specular);
    glMaterialf(GL_FRONT_AND_BACK,GL_SHININESS,50.0);
    glDepthMask(GL_TRUE);
    glDisable(GL_BLEND);
  }
  else if (display_mode == DISP_TRANSPARENT)
  {
    glDisable(GL_DEPTH_TEST);
    glShadeModel(GL_SMOOTH);
    glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
    glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT,light_model_ambient);
    glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER,GL_FALSE);
    glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL,GL_SINGLE_COLOR);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glLightfv(GL_LIGHT0,GL_AMBIENT,light_ambient);
    glLightfv(GL_LIGHT0,GL_DIFFUSE,light_diffuse);
    glLightfv(GL_LIGHT0,GL_SPECULAR,light_specular);
    glLightfv(GL_LIGHT0,GL_POSITION,light_position);
    glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,mat_specular);
    glMaterialf(GL_FRONT_AND_BACK,GL_SHININESS,50.0);
    glDepthMask(GL_FALSE);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA,GL_ONE);
  }
  else  /* display_mode == DISP_WIREFRAME */
  {
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);
    glShadeModel(GL_FLAT);
    glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
    glDisable(GL_LIGHTING);
    glDisable(GL_LIGHT0);
    glDisable(GL_BLEND);
  }

  if (change_colors)
    rotateall3d(hp->rho,hp->sigma,hp->tau,matc);

  rotateall(hp->alpha,hp->beta,hp->delta,hp->zeta,hp->eta,hp->theta,r1);

  gltrackball_get_quaternion(hp->trackballs[0],q1);
  gltrackball_get_quaternion(hp->trackballs[1],q2);
  quats_to_rotmat(q1,q2,r2);

  mult_rotmat(r2,r1,mat);

  if (!change_colors)
  {
    if (colors == COLORS_ONESIDED)
    {
      glColor3fv(mat_diff_oneside);
      if (display_mode == DISP_TRANSPARENT)
      {
        glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE,
                     mat_diff_trans_oneside);
      }
      else
      {
        glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE,
                     mat_diff_oneside);
      }
    }
    else if (colors == COLORS_TWOSIDED)
    {
      glColor3fv(mat_diff_red);
      if (display_mode == DISP_TRANSPARENT)
      {
        glMaterialfv(GL_FRONT,GL_AMBIENT_AND_DIFFUSE,mat_diff_trans_red);
        glMaterialfv(GL_BACK,GL_AMBIENT_AND_DIFFUSE,mat_diff_trans_green);
      }
      else
      {
        glMaterialfv(GL_FRONT,GL_AMBIENT_AND_DIFFUSE,mat_diff_red);
        glMaterialfv(GL_BACK,GL_AMBIENT_AND_DIFFUSE,mat_diff_green);
      }
    }
  }
  else /* change_colors */
  {
    color(0.0,matc,mat_diff_dyn);
    if (colors == COLORS_ONESIDED)
    {
      glColor3fv(mat_diff_dyn);
      glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE,mat_diff_dyn);
    }
    else if (colors == COLORS_TWOSIDED)
    {
      mat_diff_dyn_compl[0] = 1.0f-mat_diff_dyn[0];
      mat_diff_dyn_compl[1] = 1.0f-mat_diff_dyn[1];
      mat_diff_dyn_compl[2] = 1.0f-mat_diff_dyn[2];
      mat_diff_dyn_compl[3] = mat_diff_dyn[3];
      glColor3fv(mat_diff_dyn);
      glMaterialfv(GL_FRONT,GL_AMBIENT_AND_DIFFUSE,mat_diff_dyn);
      glMaterialfv(GL_BACK,GL_AMBIENT_AND_DIFFUSE,mat_diff_dyn_compl);
    }
  }

  skew = num_spirals;
  ur = umax-umin;
  vr = vmax-vmin;
  for (i=0; i<numu; i++)
  {
    if ((appearance == APPEARANCE_BANDS ||
         appearance == APPEARANCE_SPIRALS) && ((i & 3) >= 2))
      continue;
    if (display_mode == DISP_WIREFRAME)
      glBegin(GL_QUAD_STRIP);
    else
      glBegin(GL_TRIANGLE_STRIP);
    for (j=0; j<=numv; j++)
    {
      for (k=0; k<=1; k++)
      {
        l = i+k;
        m = j;
        u = ur*l/numu+umin;
        v = vr*m/numv+vmin;
        if (appearance == APPEARANCE_SPIRALS)
        {
          u += 4.0*skew/numv*v;
          b = ((i/4)&(skew-1))*(numu/(4*skew));
          color(ur*4*b/numu+umin,matc,col);
        }
        else
        {
          color(u,matc,col);
        }
        if (colors == COLORS_COLORWHEEL)
        {
          glColor3fv(col);
          glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE,col);
        }
        cu = cos(u);
        su = sin(u);
        cv = cos(v);
        sv = sin(v);
        xx[0] = cu;
        xx[1] = su;
        xx[2] = cv;
        xx[3] = sv;
        xxu[0] = -su;
        xxu[1] = cu;
        xxu[2] = 0.0;
        xxu[3] = 0.0;
        xxv[0] = 0.0;
        xxv[1] = 0.0;
        xxv[2] = -sv;
        xxv[3] = cv;
        for (l=0; l<4; l++)
        {
          r = 0.0;
          s = 0.0;
          t = 0.0;
          for (m=0; m<4; m++)
          {
            r += mat[l][m]*xx[m];
            s += mat[l][m]*xxu[m];
            t += mat[l][m]*xxv[m];
          }
          x[l] = r;
          xu[l] = s;
          xv[l] = t;
        }
        if (projection_4d == DISP_4D_ORTHOGRAPHIC)
        {
          for (l=0; l<3; l++)
          {
            p[l] = (x[l]+offset4d[l])/1.5+offset3d[l];
            pu[l] = xu[l];
            pv[l] = xv[l];
          }
        }
        else
        {
          s = x[3]+offset4d[3];
          t = s*s;
          for (l=0; l<3; l++)
          {
            r = x[l]+offset4d[l];
            p[l] = r/s+offset3d[l];
            pu[l] = (xu[l]*s-r*xu[3])/t;
            pv[l] = (xv[l]*s-r*xv[3])/t;
          }
        }
        n[0] = pu[1]*pv[2]-pu[2]*pv[1];
        n[1] = pu[2]*pv[0]-pu[0]*pv[2];
        n[2] = pu[0]*pv[1]-pu[1]*pv[0];
        t = sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);
        n[0] /= t;
        n[1] /= t;
        n[2] /= t;
        glNormal3fv(n);
        glVertex3fv(p);
      }
    }
    glEnd();
  }

  polys = 2*numu*numv;
  if (appearance != APPEARANCE_SOLID)
    polys /= 2;
  return polys;
}


#ifdef HAVE_GLSL

/* Draw a hypertorus projected into 3D using OpenGL's programmable
   functionality.  Note that the spirals appearance will only work
   correctly if numu and numv are set to 64 or any higher power of 2.
   Similarly, the banded appearance will only work correctly if numu
   and numv are divisible by 4. */
static int hypertorus_pf(ModeInfo *mi, double umin, double umax, double vmin,
                         double vmax, int numu, int numv)
{
  static const GLfloat light_model_ambient[]    = { 0.2, 0.2, 0.2, 1.0 };
  static const GLfloat light_ambient[]          = { 0.0, 0.0, 0.0, 1.0 };
  static const GLfloat light_diffuse[]          = { 1.0, 1.0, 1.0, 1.0 };
  static const GLfloat light_specular[]         = { 1.0, 1.0, 1.0, 1.0 };
  static const GLfloat light_position[]         = { 1.0, 1.0, 1.0, 0.0 };
  static const GLfloat mat_specular[]           = { 1.0, 1.0, 1.0, 1.0 };
  static const GLfloat mat_diff_red[]           = { 1.0, 0.0, 0.0, 1.0 };
  static const GLfloat mat_diff_green[]         = { 0.0, 1.0, 0.0, 1.0 };
  static const GLfloat mat_diff_oneside[]       = { 0.9, 0.4, 0.3, 1.0 };
  static const GLfloat mat_diff_trans_red[]     = { 1.0, 0.0, 0.0, 0.7 };
  static const GLfloat mat_diff_trans_green[]   = { 0.0, 1.0, 0.0, 0.7 };
  static const GLfloat mat_diff_trans_oneside[] = { 0.9, 0.4, 0.3, 0.7 };
  static const GLfloat mat_diff_white[]         = { 1.0, 1.0, 1.0, 1.0 };
  GLfloat light_direction[3], half_vector[3], len;
  GLfloat p_mat[16];
  float mat_diff_dyn[4], mat_diff_dyn_compl[4];
  float mat[4][4], matc[3][3];
  int i, j, k, l, m, o, b, skew;
  double u, v, ur, vr;
  float q1[4], q2[4], r1[4][4], r2[4][4];
  GLsizeiptr index_offset;
  hypertorusstruct *hp = &hyper[MI_SCREEN(mi)];
  int polys;

  if (!hp->use_shaders)
    return 0;

  if (change_colors)
    rotateall3d(hp->rho,hp->sigma,hp->tau,matc);

  rotateall(hp->alpha,hp->beta,hp->delta,hp->zeta,hp->eta,hp->theta,r1);

  gltrackball_get_quaternion(hp->trackballs[0],q1);
  gltrackball_get_quaternion(hp->trackballs[1],q2);
  quats_to_rotmat(q1,q2,r2);

  mult_rotmat(r2,r1,mat);

  if (!hp->buffers_initialized)
  {
    /* The u and v values need to be computed once (or each time the value
       of appearance changes, once we support that). */
    skew = num_spirals;
    ur = umax-umin;
    vr = vmax-vmin;
    for (i=0; i<=numu; i++)
    {
      for (j=0; j<=numv; j++)
      {
        u = ur*i/numu+umin;
        v = vr*j/numv+vmin;
        o = i*(numv+1)+j;
        if (appearance == APPEARANCE_SPIRALS)
          u += 4.0*skew/numv*v;
        hp->uv[o][0] = u;
        hp->uv[o][1] = v;
      }
    }
    glBindBuffer(GL_ARRAY_BUFFER,hp->vertex_uv_buffer);
    glBufferData(GL_ARRAY_BUFFER,2*(NUMU+1)*(NUMV+1)*sizeof(GLfloat),
                 hp->uv,GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER,0);

    if (!change_colors && colors == COLORS_COLORWHEEL)
    {
      for (i=0; i<=numu; i++)
      {
        for (j=0; j<=numv; j++)
        {
          u = ur*i/numu+umin;
          v = vr*j/numv+vmin;
          o = i*(numv+1)+j;
          if (appearance == APPEARANCE_SPIRALS)
          {
            u += 4.0*skew/numv*v;
            b = ((i/4)&(skew-1))*(numu/(4*skew));
            color(ur*4*b/numu+umin,matc,&hp->col[o][0]);
          }
          else
          {
            color(u,matc,&hp->col[o][0]);
          }
        }
      }
      glBindBuffer(GL_ARRAY_BUFFER,hp->color_buffer);
      glBufferData(GL_ARRAY_BUFFER,4*(NUMU+1)*(NUMV+1)*sizeof(GLfloat),
                   hp->col,GL_STATIC_DRAW);
      glBindBuffer(GL_ARRAY_BUFFER,0);
    }
#ifdef VERTEXATTRIBARRAY_WORKAROUND
    if (colors != COLORS_COLORWHEEL)
    {
      for (i=0; i<=numu; i++)
      {
        for (j=0; j<=numv; j++)
        {
          o = i*(numv+1)+j;
          if (display_mode == DISP_WIREFRAME)
          {
            if (colors == COLORS_ONESIDED)
            {
              for (k=0; k<4; k++)
                hp->col[o][k] = mat_diff_oneside[k];
            }
            else if (colors == COLORS_TWOSIDED)
            {
              for (k=0; k<4; k++)
                hp->col[o][k] = mat_diff_red[k];
            }
          }
          else
          {
            for (k=0; k<4; k++)
              hp->col[o][k] = mat_diff_white[k];
          }
        }
      }
      glBindBuffer(GL_ARRAY_BUFFER,hp->color_buffer);
      glBufferData(GL_ARRAY_BUFFER,4*(NUMU+1)*(NUMV+1)*sizeof(GLfloat),
                   hp->col,GL_STATIC_DRAW);
      glBindBuffer(GL_ARRAY_BUFFER,0);
    }
#endif /* VERTEXATTRIBARRAY_WORKAROUND */

    /* The indices only need to be computed once (or each time the value
       of appearance changes, once we support that). */
    hp->ne = 0;
    hp->ni = 0;
    hp->nt = 0;
    if (display_mode != DISP_WIREFRAME)
    {
      for (i=0; i<numu; i++)
      {
        if ((appearance == APPEARANCE_BANDS ||
             appearance == APPEARANCE_SPIRALS) && ((i & 3) >= 2))
          continue;
        for (j=0; j<=numv; j++)
        {
          for (k=0; k<=1; k++)
          {
            l = i+k;
            m = j;
            o = l*(numv+1)+m;
            hp->indices[hp->ni++] = o;
          }
        }
        hp->ne++;
      }
      hp->nt = 2*(numv+1);
    }
    else /* display_mode == DISP_WIREFRAME */
    {
      for (i=0; i<numu; i++)
      {
        if ((appearance == APPEARANCE_BANDS ||
             appearance == APPEARANCE_SPIRALS) && ((i & 3) > 2))
          continue;
        if ((appearance == APPEARANCE_BANDS ||
             appearance == APPEARANCE_SPIRALS) && ((i & 3) == 2))
        {
          for (j=0; j<numv; j++)
          {
            hp->indices[hp->ni++] = i*(numv+1)+j;
            hp->indices[hp->ni++] = i*(numv+1)+j+1;
          }
          continue;
        }
        for (j=0; j<numv; j++)
        {
          hp->indices[hp->ni++] = i*(numv+1)+j;
          hp->indices[hp->ni++] = i*(numv+1)+j+1;
          hp->indices[hp->ni++] = i*(numv+1)+j;
          hp->indices[hp->ni++] = (i+1)*(numv+1)+j;
        }
      }
      hp->ne = 1;
    }
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,hp->indices_buffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER,hp->ni*sizeof(GLuint),
                 hp->indices,GL_STATIC_DRAW);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0);
    hp->buffers_initialized = True;
  }

  if (change_colors && colors == COLORS_COLORWHEEL)
  {
    skew = num_spirals;
    ur = umax-umin;
    vr = vmax-vmin;
    for (i=0; i<=numu; i++)
    {
      for (j=0; j<=numv; j++)
      {
        u = ur*i/numu+umin;
        v = vr*j/numv+vmin;
        o = i*(numv+1)+j;
        if (appearance == APPEARANCE_SPIRALS)
        {
          u += 4.0*skew/numv*v;
          b = ((i/4)&(skew-1))*(numu/(4*skew));
          color(ur*4*b/numu+umin,matc,&hp->col[o][0]);
        }
        else
        {
          color(u,matc,&hp->col[o][0]);
        }
      }
    }
  }
#ifdef VERTEXATTRIBARRAY_WORKAROUND
  else if (change_colors && colors != COLORS_COLORWHEEL)
  {
    if (display_mode == DISP_WIREFRAME)
    {
      color(0.0,matc,mat_diff_dyn);
      for (i=0; i<=numu; i++)
      {
        for (j=0; j<=numv; j++)
        {
          o = i*(numv+1)+j;
          for (k=0; k<4; k++)
            hp->col[o][k] = mat_diff_dyn[k];
        }
      }
    }
  }
#endif /* VERTEXATTRIBARRAY_WORKAROUND */

  glUseProgram(hp->shader_program);

  glsl_Identity(p_mat);
  if (projection_3d == DISP_3D_ORTHOGRAPHIC)
  {
    if (hp->aspect >= 1.0)
      glsl_Orthographic(p_mat,-hp->aspect,hp->aspect,-1.0,1.0,
                        0.1,10.0);
    else
      glsl_Orthographic(p_mat,-1.0,1.0,-1.0/hp->aspect,1.0/hp->aspect,
                        0.1,10.0);
  }
  else
  {
    glsl_Perspective(p_mat,60.0f,hp->aspect,0.1f,10.0f);
  }
  glUniformMatrix4fv(hp->mat_rot_index,1,GL_TRUE,(GLfloat *)mat);
  glUniformMatrix4fv(hp->mat_p_index,1,GL_FALSE,p_mat);
  glUniform1i(hp->bool_persp_index,projection_4d == DISP_4D_PERSPECTIVE);
  glUniform4fv(hp->off4d_index,1,offset4d);
  glUniform4fv(hp->off3d_index,1,offset3d);

  len = sqrtf(light_position[0]*light_position[0]+
              light_position[1]*light_position[1]+
              light_position[2]*light_position[2]);
  light_direction[0] = light_position[0]/len;
  light_direction[1] = light_position[1]/len;
  light_direction[2] = light_position[2]/len;
  half_vector[0] = light_direction[0];
  half_vector[1] = light_direction[1];
  half_vector[2] = light_direction[2]+1.0f;
  len = sqrtf(half_vector[0]*half_vector[0]+
              half_vector[1]*half_vector[1]+
              half_vector[2]*half_vector[2]);
  half_vector[0] /= len;
  half_vector[1] /= len;
  half_vector[2] /= len;

  if (display_mode == DISP_SURFACE)
  {
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);
    glDepthMask(GL_TRUE);
    glDisable(GL_BLEND);
    glUniform4fv(hp->glbl_ambient_index,1,light_model_ambient);
    glUniform4fv(hp->lt_ambient_index,1,light_ambient);
    glUniform4fv(hp->lt_diffuse_index,1,light_diffuse);
    glUniform4fv(hp->lt_specular_index,1,light_specular);
    glUniform3fv(hp->lt_direction_index,1,light_direction);
    glUniform3fv(hp->lt_halfvect_index,1,half_vector);
    glUniform4fv(hp->specular_index,1,mat_specular);
    glUniform1f(hp->shininess_index,50.0f);
    glUniform1i(hp->draw_lines_index,GL_FALSE);
  }
  else if (display_mode == DISP_TRANSPARENT)
  {
    glDisable(GL_DEPTH_TEST);
    glDepthMask(GL_FALSE);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA,GL_ONE);
    glUniform4fv(hp->glbl_ambient_index,1,light_model_ambient);
    glUniform4fv(hp->lt_ambient_index,1,light_ambient);
    glUniform4fv(hp->lt_diffuse_index,1,light_diffuse);
    glUniform4fv(hp->lt_specular_index,1,light_specular);
    glUniform3fv(hp->lt_direction_index,1,light_direction);
    glUniform3fv(hp->lt_halfvect_index,1,half_vector);
    glUniform4fv(hp->specular_index,1,mat_specular);
    glUniform1f(hp->shininess_index,50.0f);
    glUniform1i(hp->draw_lines_index,GL_FALSE);
  }
  else /* display_mode == DISP_WIREFRAME */
  {
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);
    glDepthMask(GL_TRUE);
    glDisable(GL_BLEND);
    glUniform1i(hp->draw_lines_index,GL_TRUE);
  }

  glUniform4fv(hp->front_ambient_index,1,mat_diff_white);
  glUniform4fv(hp->front_diffuse_index,1,mat_diff_white);
  glUniform4fv(hp->back_ambient_index,1,mat_diff_white);
  glUniform4fv(hp->back_diffuse_index,1,mat_diff_white);
  glVertexAttrib4f(hp->color_index,1.0f,1.0f,1.0f,1.0f);
  if (!change_colors)
  {
    if (colors == COLORS_ONESIDED)
    {
      if (display_mode == DISP_TRANSPARENT)
      {
        glUniform4fv(hp->front_ambient_index,1,mat_diff_trans_oneside);
        glUniform4fv(hp->front_diffuse_index,1,mat_diff_trans_oneside);
        glUniform4fv(hp->back_ambient_index,1,mat_diff_trans_oneside);
        glUniform4fv(hp->back_diffuse_index,1,mat_diff_trans_oneside);
      }
      else if (display_mode == DISP_SURFACE)
      {
        glUniform4fv(hp->front_ambient_index,1,mat_diff_oneside);
        glUniform4fv(hp->front_diffuse_index,1,mat_diff_oneside);
        glUniform4fv(hp->back_ambient_index,1,mat_diff_oneside);
        glUniform4fv(hp->back_diffuse_index,1,mat_diff_oneside);
      }
      else /* display_mode == DISP_WIREFRAME */
      {
        glVertexAttrib4fv(hp->color_index,mat_diff_oneside);
      }
    }
    else if (colors == COLORS_TWOSIDED)
    {
      if (display_mode == DISP_TRANSPARENT)
      {
        glUniform4fv(hp->front_ambient_index,1,mat_diff_trans_red);
        glUniform4fv(hp->front_diffuse_index,1,mat_diff_trans_red);
        glUniform4fv(hp->back_ambient_index,1,mat_diff_trans_green);
        glUniform4fv(hp->back_diffuse_index,1,mat_diff_trans_green);
      }
      else if (display_mode == DISP_SURFACE)
      {
        glUniform4fv(hp->front_ambient_index,1,mat_diff_red);
        glUniform4fv(hp->front_diffuse_index,1,mat_diff_red);
        glUniform4fv(hp->back_ambient_index,1,mat_diff_green);
        glUniform4fv(hp->back_diffuse_index,1,mat_diff_green);
      }
      else /* display_mode == DISP_WIREFRAME */
      {
        glVertexAttrib4fv(hp->color_index,mat_diff_red);
      }
    }
  }
  else /* change_colors */
  {
    color(0.0,matc,mat_diff_dyn);
    if (colors == COLORS_ONESIDED)
    {
      if (display_mode == DISP_TRANSPARENT || display_mode == DISP_SURFACE)
      {
        glUniform4fv(hp->front_ambient_index,1,mat_diff_dyn);
        glUniform4fv(hp->front_diffuse_index,1,mat_diff_dyn);
        glUniform4fv(hp->back_ambient_index,1,mat_diff_dyn);
        glUniform4fv(hp->back_diffuse_index,1,mat_diff_dyn);
      }
      else /* display_mode == DISP_WIREFRAME */
      {
        glVertexAttrib4fv(hp->color_index,mat_diff_dyn);
      }
    }
    else if (colors == COLORS_TWOSIDED)
    {
      if (display_mode == DISP_TRANSPARENT || display_mode == DISP_SURFACE)
      {
        mat_diff_dyn_compl[0] = 1.0f-mat_diff_dyn[0];
        mat_diff_dyn_compl[1] = 1.0f-mat_diff_dyn[1];
        mat_diff_dyn_compl[2] = 1.0f-mat_diff_dyn[2];
        mat_diff_dyn_compl[3] = mat_diff_dyn[3];
        glUniform4fv(hp->front_ambient_index,1,mat_diff_dyn);
        glUniform4fv(hp->front_diffuse_index,1,mat_diff_dyn);
        glUniform4fv(hp->back_ambient_index,1,mat_diff_dyn_compl);
        glUniform4fv(hp->back_diffuse_index,1,mat_diff_dyn_compl);
      }
      else /* display_mode == DISP_WIREFRAME */
      {
        glVertexAttrib4fv(hp->color_index,mat_diff_dyn);
      }
    }
  }

  glEnableVertexAttribArray(hp->vertex_uv_index);
  glBindBuffer(GL_ARRAY_BUFFER,hp->vertex_uv_buffer);
  glVertexAttribPointer(hp->vertex_uv_index,2,GL_FLOAT,GL_FALSE,0,0);

  if (colors == COLORS_COLORWHEEL)
  {
    glEnableVertexAttribArray(hp->color_index);
    glBindBuffer(GL_ARRAY_BUFFER,hp->color_buffer);
    if (change_colors)
      glBufferData(GL_ARRAY_BUFFER,4*(NUMU+1)*(NUMV+1)*sizeof(GLfloat),
                   hp->col,GL_STREAM_DRAW);
    glVertexAttribPointer(hp->color_index,4,GL_FLOAT,GL_FALSE,0,0);
  }
#ifdef VERTEXATTRIBARRAY_WORKAROUND
  else
  {
    glEnableVertexAttribArray(hp->color_index);
    glBindBuffer(GL_ARRAY_BUFFER,hp->color_buffer);
    if (change_colors)
      glBufferData(GL_ARRAY_BUFFER,4*(NUMU+1)*(NUMV+1)*sizeof(GLfloat),
                   hp->col,GL_STREAM_DRAW);
    glVertexAttribPointer(hp->color_index,4,GL_FLOAT,GL_FALSE,0,0);
  }
#endif /* VERTEXATTRIBARRAY_WORKAROUND */

  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,hp->indices_buffer);

  if (display_mode != DISP_WIREFRAME)
  {
    for (i=0; i<hp->ne; i++)
    {
      index_offset = hp->nt*i*sizeof(GLuint);
      glDrawElements(GL_TRIANGLE_STRIP,hp->nt,GL_UNSIGNED_INT,
                     (const void *)index_offset);
    }
  }
  else /* display_mode == DISP_WIREFRAME */
  {
    glLineWidth(1.0f);
    index_offset = 0;
    glDrawElements(GL_LINES,hp->ni,GL_UNSIGNED_INT,
                   (const void *)index_offset);
  }

  glDisableVertexAttribArray(hp->vertex_uv_index);
  if (colors == COLORS_COLORWHEEL)
    glDisableVertexAttribArray(hp->color_index);
#ifdef VERTEXATTRIBARRAY_WORKAROUND
  else
    glDisableVertexAttribArray(hp->color_index);
#endif /* VERTEXATTRIBARRAY_WORKAROUND */
  glBindBuffer(GL_ARRAY_BUFFER,0);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0);

  glUseProgram(0);

  polys = 2*numu*numv;
  if (appearance != APPEARANCE_SOLID)
    polys /= 2;
  return polys;
}


static void init_glsl(ModeInfo *mi)
{
  hypertorusstruct *hp = &hyper[MI_SCREEN(mi)];
  GLint gl_major, gl_minor, glsl_major, glsl_minor;
  GLboolean gl_gles3;
  const GLchar *vertex_shader_source[3];
  const GLchar *fragment_shader_source[4];

  /* Determine whether to use shaders to render the hypertorus. */
  hp->use_shaders = False;
  hp->buffers_initialized = False;
  hp->shader_program = 0;
  hp->ni = 0;
  hp->ne = 0;
  hp->nt = 0;

  if (!glsl_GetGlAndGlslVersions(&gl_major,&gl_minor,&glsl_major,&glsl_minor,
                                 &gl_gles3))
    return;
  if (!gl_gles3)
  {
    if (gl_major < 3 ||
        (glsl_major < 1 || (glsl_major == 1 && glsl_minor < 30)))
    {
      if ((gl_major < 2 || (gl_major == 2 && gl_minor < 1)) ||
          (glsl_major < 1 || (glsl_major == 1 && glsl_minor < 20)))
        return;
      /* We have at least OpenGL 2.1 and at least GLSL 1.20. */
      vertex_shader_source[0] = shader_version_2_1;
      vertex_shader_source[1] = vertex_shader_attribs_2_1;
      vertex_shader_source[2] = vertex_shader_main;
      fragment_shader_source[0] = shader_version_2_1;
      fragment_shader_source[1] = fragment_shader_attribs_2_1;
      fragment_shader_source[2] = fragment_shader_main;
      fragment_shader_source[3] = fragment_shader_out_2_1;
    }
    else
    {
      /* We have at least OpenGL 3.0 and at least GLSL 1.30. */
      vertex_shader_source[0] = shader_version_3_0;
      vertex_shader_source[1] = vertex_shader_attribs_3_0;
      vertex_shader_source[2] = vertex_shader_main;
      fragment_shader_source[0] = shader_version_3_0;
      fragment_shader_source[1] = fragment_shader_attribs_3_0;
      fragment_shader_source[2] = fragment_shader_main;
      fragment_shader_source[3] = fragment_shader_out_3_0;
    }
  }
  else /* gl_gles3 */
  {
    if (gl_major < 3 || glsl_major < 3)
      return;
    /* We have at least OpenGL ES 3.0 and at least GLSL ES 3.0. */
    vertex_shader_source[0] = shader_version_3_0_es;
    vertex_shader_source[1] = vertex_shader_attribs_3_0;
    vertex_shader_source[2] = vertex_shader_main;
    fragment_shader_source[0] = shader_version_3_0_es;
    fragment_shader_source[1] = fragment_shader_attribs_3_0;
    fragment_shader_source[2] = fragment_shader_main;
    fragment_shader_source[3] = fragment_shader_out_3_0;
  }
  if (!glsl_CompileAndLinkShaders(3,vertex_shader_source,
                                  4,fragment_shader_source,
                                  &hp->shader_program))
    return;
  hp->vertex_uv_index = glGetAttribLocation(hp->shader_program,"VertexUV");
  hp->color_index = glGetAttribLocation(hp->shader_program,"VertexColor");
  if (hp->vertex_uv_index == -1 || hp->color_index == -1)
  {
    glDeleteProgram(hp->shader_program);
    return;
  }
  hp->mat_rot_index = glGetUniformLocation(hp->shader_program,
                                           "MatRot4D");
  hp->mat_p_index = glGetUniformLocation(hp->shader_program,
                                         "MatProj");
  hp->bool_persp_index = glGetUniformLocation(hp->shader_program,
                                              "BoolPersp");
  hp->off4d_index = glGetUniformLocation(hp->shader_program,
                                         "Offset4D");
  hp->off3d_index = glGetUniformLocation(hp->shader_program,
                                         "Offset3D");
  hp->draw_lines_index = glGetUniformLocation(hp->shader_program,
                                              "DrawLines");
  hp->glbl_ambient_index = glGetUniformLocation(hp->shader_program,
                                                "LtGlblAmbient");
  hp->lt_ambient_index = glGetUniformLocation(hp->shader_program,
                                              "LtAmbient");
  hp->lt_diffuse_index = glGetUniformLocation(hp->shader_program,
                                              "LtDiffuse");
  hp->lt_specular_index = glGetUniformLocation(hp->shader_program,
                                               "LtSpecular");
  hp->lt_direction_index = glGetUniformLocation(hp->shader_program,
                                                "LtDirection");
  hp->lt_halfvect_index = glGetUniformLocation(hp->shader_program,
                                               "LtHalfVector");
  hp->front_ambient_index = glGetUniformLocation(hp->shader_program,
                                                 "MatFrontAmbient");
  hp->back_ambient_index = glGetUniformLocation(hp->shader_program,
                                                "MatBackAmbient");
  hp->front_diffuse_index = glGetUniformLocation(hp->shader_program,
                                                 "MatFrontDiffuse");
  hp->back_diffuse_index = glGetUniformLocation(hp->shader_program,
                                                "MatBackDiffuse");
  hp->specular_index = glGetUniformLocation(hp->shader_program,
                                            "MatSpecular");
  hp->shininess_index = glGetUniformLocation(hp->shader_program,
                                             "MatShininess");
  if (hp->mat_rot_index == -1 || hp->mat_p_index == -1 ||
      hp->bool_persp_index == -1 || hp->off4d_index  == -1 ||
      hp->off3d_index  == -1 || hp->draw_lines_index == -1 ||
      hp->glbl_ambient_index == -1 || hp->lt_ambient_index == -1 ||
      hp->lt_diffuse_index == -1 || hp->lt_specular_index == -1 ||
      hp->lt_direction_index == -1 || hp->lt_halfvect_index == -1 ||
      hp->specular_index == -1 || hp->shininess_index == -1 ||
      hp->front_ambient_index == -1 || hp->back_ambient_index == -1 ||
      hp->front_diffuse_index == -1 || hp->back_diffuse_index == -1)
  {
    glDeleteProgram(hp->shader_program);
    return;
  }

  glGenBuffers(1,&hp->vertex_uv_buffer);
  glGenBuffers(1,&hp->color_buffer);
  glGenBuffers(1,&hp->indices_buffer);

  hp->use_shaders = True;
}

#endif /* HAVE_GLSL */


static void init(ModeInfo *mi)
{
  hypertorusstruct *hp = &hyper[MI_SCREEN(mi)];

  hp->alpha = 0.0;
  hp->beta = 0.0;
  hp->delta = 0.0;
  hp->zeta = 0.0;
  hp->eta = 0.0;
  hp->theta = 0.0;

  hp->rho = frand(360.0);
  hp->sigma = frand(360.0);
  hp->tau = frand(360.0);

#ifdef HAVE_GLSL
  init_glsl(mi);
#endif /* HAVE_GLSL */

#ifdef HAVE_ANDROID
  /* glPolygonMode(...,GL_LINE) is not supported for an OpenGL ES 1.1
     context. */
  if (!hp->use_shaders && display_mode == DISP_WIREFRAME)
    display_mode = DISP_SURFACE;
#endif /* HAVE_ANDROID */
}


/* Redisplay the hypertorus. */
static void display_hypertorus(ModeInfo *mi)
{
  hypertorusstruct *hp = &hyper[MI_SCREEN(mi)];

  if (!hp->button_pressed)
  {
    hp->alpha += speed_wx * hp->speed_scale;
    if (hp->alpha >= 360.0)
      hp->alpha -= 360.0;
    hp->beta += speed_wy * hp->speed_scale;
    if (hp->beta >= 360.0)
      hp->beta -= 360.0;
    hp->delta += speed_wz * hp->speed_scale;
    if (hp->delta >= 360.0)
      hp->delta -= 360.0;
    hp->zeta += speed_xy * hp->speed_scale;
    if (hp->zeta >= 360.0)
      hp->zeta -= 360.0;
    hp->eta += speed_xz * hp->speed_scale;
    if (hp->eta >= 360.0)
      hp->eta -= 360.0;
    hp->theta += speed_yz * hp->speed_scale;
    if (hp->theta >= 360.0)
      hp->theta -= 360.0;

    if (change_colors)
    {
      hp->rho += DRHO;
      if (hp->rho >= 360.0)
        hp->rho -= 360.0;
      hp->sigma += DSIGMA;
      if (hp->sigma >= 360.0)
        hp->sigma -= 360.0;
      hp->tau += DTAU;
      if (hp->tau >= 360.0)
        hp->tau -= 360.0;
    }
  }

#ifdef HAVE_GLSL
  if (hp->use_shaders)
    mi->polygon_count = hypertorus_pf(mi,0.0,2.0*M_PI,0.0,2.0*M_PI,NUMU,NUMV);
  else
#endif /* HAVE_GLSL */
    mi->polygon_count = hypertorus_ff(mi,0.0,2.0*M_PI,0.0,2.0*M_PI,NUMU,NUMV);
}


ENTRYPOINT void reshape_hypertorus(ModeInfo *mi, int width, int height)
{
  hypertorusstruct *hp = &hyper[MI_SCREEN(mi)];

  hp->WindW = (GLint)width;
  hp->WindH = (GLint)height;
  glViewport(0,0,width,height);
  hp->aspect = (GLfloat)width/(GLfloat)height;
}


ENTRYPOINT Bool hypertorus_handle_event(ModeInfo *mi, XEvent *event)
{
  hypertorusstruct *hp = &hyper[MI_SCREEN(mi)];
  KeySym  sym = 0;
  char c = 0;

  if (event->xany.type == KeyPress || event->xany.type == KeyRelease)
    XLookupString (&event->xkey, &c, 1, &sym, 0);

  if (event->xany.type == ButtonPress &&
      event->xbutton.button == Button1)
  {
    hp->button_pressed = True;
    gltrackball_start(hp->trackballs[hp->current_trackball],
                      event->xbutton.x, event->xbutton.y,
                      MI_WIDTH(mi), MI_HEIGHT(mi));
    return True;
  }
  else if (event->xany.type == ButtonRelease &&
           event->xbutton.button == Button1)
  {
    hp->button_pressed = False;
    return True;
  }
  else if (event->xany.type == KeyPress)
  {
    if (sym == XK_Shift_L || sym == XK_Shift_R)
    {
      hp->current_trackball = 1;
      if (hp->button_pressed)
        gltrackball_start(hp->trackballs[hp->current_trackball],
                          event->xbutton.x, event->xbutton.y,
                          MI_WIDTH(mi), MI_HEIGHT(mi));
      return True;
    }
  }
  else if (event->xany.type == KeyRelease)
  {
    if (sym == XK_Shift_L || sym == XK_Shift_R)
    {
      hp->current_trackball = 0;
      if (hp->button_pressed)
        gltrackball_start(hp->trackballs[hp->current_trackball],
                          event->xbutton.x, event->xbutton.y,
                          MI_WIDTH(mi), MI_HEIGHT(mi));
      return True;
    }
  }
  else if (event->xany.type == MotionNotify && hp->button_pressed)
  {
    gltrackball_track(hp->trackballs[hp->current_trackball],
                      event->xmotion.x, event->xmotion.y,
                      MI_WIDTH(mi), MI_HEIGHT(mi));
    return True;
  }

  return False;
}


/*
 *-----------------------------------------------------------------------------
 *-----------------------------------------------------------------------------
 *    Xlock hooks.
 *-----------------------------------------------------------------------------
 *-----------------------------------------------------------------------------
 */

/*
 *-----------------------------------------------------------------------------
 *    Initialize hypertorus.  Called each time the window changes.
 *-----------------------------------------------------------------------------
 */

ENTRYPOINT void init_hypertorus(ModeInfo *mi)
{
  hypertorusstruct *hp;

  MI_INIT(mi, hyper);
  hp = &hyper[MI_SCREEN(mi)];

  hp->trackballs[0] = gltrackball_init(True);
  hp->trackballs[1] = gltrackball_init(True);
  hp->current_trackball = 0;
  hp->button_pressed = False;

  /* Set the display mode. */
  if (!strcasecmp(mode,"wireframe") || !strcasecmp(mode,"0"))
  {
    display_mode = DISP_WIREFRAME;
  }
  else if (!strcasecmp(mode,"surface") || !strcasecmp(mode,"1"))
  {
    display_mode = DISP_SURFACE;
  }
  else if (!strcasecmp(mode,"transparent") || !strcasecmp(mode,"2"))
  {
    display_mode = DISP_TRANSPARENT;
  }
  else
  {
    display_mode = DISP_SURFACE;
  }

  /* Set the appearance. */
  if (!strcasecmp(appear,"solid") || !strcasecmp(appear,"0"))
  {
    appearance = APPEARANCE_SOLID;
  }
  else if (!strcasecmp(appear,"bands") || !strcasecmp(appear,"1"))
  {
    appearance = APPEARANCE_BANDS;
    num_spirals = 0;
  }
  else if (!strcasecmp(appear,"spirals-1") || !strcasecmp(appear,"3"))
  {
    appearance = APPEARANCE_SPIRALS;
    num_spirals = 1;
  }
  else if (!strcasecmp(appear,"spirals-2") || !strcasecmp(appear,"4"))
  {
    appearance = APPEARANCE_SPIRALS;
    num_spirals = 2;
  }
  else if (!strcasecmp(appear,"spirals-4") || !strcasecmp(appear,"5"))
  {
    appearance = APPEARANCE_SPIRALS;
    num_spirals = 4;
  }
  else if (!strcasecmp(appear,"spirals-8") || !strcasecmp(appear,"6"))
  {
    appearance = APPEARANCE_SPIRALS;
    num_spirals = 8;
  }
  else if (!strcasecmp(appear,"spirals-16") || !strcasecmp(appear,"7"))
  {
    appearance = APPEARANCE_SPIRALS;
    num_spirals = 16;
  }
  else
  {
    appearance = APPEARANCE_BANDS;
    num_spirals = 0;
  }

  /* Set the color mode. */
  if (!strcasecmp(color_mode,"onesided"))
  {
    colors = COLORS_ONESIDED;
  }
  else if (!strcasecmp(color_mode,"twosided"))
  {
    colors = COLORS_TWOSIDED;
  }
  else if (!strcasecmp(color_mode,"colorwheel"))
  {
    colors = COLORS_COLORWHEEL;
  }
  else
  {
    colors = COLORS_COLORWHEEL;
  }

  /* Set the 3d projection mode. */
  if (!strcasecmp(proj_3d,"perspective") || !strcasecmp(proj_3d,"0"))
  {
    projection_3d = DISP_3D_PERSPECTIVE;
  }
  else if (!strcasecmp(proj_3d,"orthographic") || !strcasecmp(proj_3d,"1"))
  {
    projection_3d = DISP_3D_ORTHOGRAPHIC;
  }
  else
  {
    projection_3d = DISP_3D_PERSPECTIVE;
  }

  /* Set the 4d projection mode. */
  if (!strcasecmp(proj_4d,"perspective") || !strcasecmp(proj_4d,"0"))
  {
    projection_4d = DISP_4D_PERSPECTIVE;
  }
  else if (!strcasecmp(proj_4d,"orthographic") || !strcasecmp(proj_4d,"1"))
  {
    projection_4d = DISP_4D_ORTHOGRAPHIC;
  }
  else
  {
    projection_4d = DISP_4D_PERSPECTIVE;
  }

  /* make multiple screens rotate at slightly different rates. */
  hp->speed_scale = 0.9 + frand(0.3);

  if ((hp->glx_context = init_GL(mi)) != NULL)
  {
    reshape_hypertorus(mi,MI_WIDTH(mi),MI_HEIGHT(mi));
    init(mi);
  }
  else
  {
    MI_CLEARWINDOW(mi);
  }
}

/*
 *-----------------------------------------------------------------------------
 *    Called by the mainline code periodically to update the display.
 *-----------------------------------------------------------------------------
 */
ENTRYPOINT void draw_hypertorus(ModeInfo *mi)
{
  Display          *display = MI_DISPLAY(mi);
  Window           window = MI_WINDOW(mi);
  hypertorusstruct *hp;

  if (hyper == NULL)
    return;
  hp = &hyper[MI_SCREEN(mi)];

  MI_IS_DRAWN(mi) = True;
  if (!hp->glx_context)
    return;

  glXMakeCurrent(display, window, *hp->glx_context);

  glClearColor(0.0f,0.0f,0.0f,1.0f);
  glClearDepth(1.0f);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

  display_hypertorus(mi);

  if (MI_IS_FPS(mi))
    do_fps (mi);

  glFlush();

  glXSwapBuffers(display,window);
}


#ifndef STANDALONE
ENTRYPOINT void change_hypertorus(ModeInfo *mi)
{
  hypertorusstruct *hp = &hyper[MI_SCREEN(mi)];

  if (!hp->glx_context)
    return;

  glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *hp->glx_context);
  init(mi);
}
#endif /* !STANDALONE */


ENTRYPOINT void free_hypertorus(ModeInfo *mi)
{
  hypertorusstruct *hp = &hyper[MI_SCREEN(mi)];
  if (!hp->glx_context) return;
  glXMakeCurrent (MI_DISPLAY(mi), MI_WINDOW(mi), *hp->glx_context);
  gltrackball_free (hp->trackballs[0]);
  gltrackball_free (hp->trackballs[1]);
#ifdef HAVE_GLSL
  if (hp->use_shaders)
  {
    glDeleteBuffers(1,&hp->vertex_uv_buffer);
    glDeleteBuffers(1,&hp->color_buffer);
    glDeleteBuffers(1,&hp->indices_buffer);
    if (hp->shader_program != 0)
    {
      glUseProgram(0);
      glDeleteProgram(hp->shader_program);
    }
  }
#endif /* HAVE_GLSL */
}


XSCREENSAVER_MODULE ("Hypertorus", hypertorus)

#endif /* USE_GL */