+/* Set up the squeezed torus Klein bottle coordinates, colors, and texture. */
+static void setup_squeezed_torus(ModeInfo *mi, double umin, double umax,
+ double vmin, double vmax)
+{
+ int i, j, k, l;
+ double u, v, ur, vr;
+ double cu, su, cv, sv, cv2, sv2;
+ kleinstruct *kb = &klein[MI_SCREEN(mi)];
+
+ ur = umax-umin;
+ vr = vmax-vmin;
+ for (i=0; i<=NUMU; i++)
+ {
+ for (j=0; j<=NUMV; j++)
+ {
+ k = i*(NUMV+1)+j;
+ u = -ur*j/NUMU+umin;
+ v = vr*i/NUMV+vmin;
+ if (kb->colors == COLORS_DEPTH)
+ color(kb,(sin(u)*sin(0.5*v)+1.0)*M_PI*2.0/3.0,kb->col[k]);
+ else
+ color(kb,v,kb->col[k]);
+ kb->tex[k][0] = -32*u/(2.0*M_PI);
+ kb->tex[k][1] = 32*v/(2.0*M_PI);
+ cu = cos(u);
+ su = sin(u);
+ cv = cos(v);
+ sv = sin(v);
+ cv2 = cos(0.5*v);
+ sv2 = sin(0.5*v);
+ kb->x[k][0] = (SQUEEZED_TORUS_RADIUS+cu)*cv;
+ kb->x[k][1] = (SQUEEZED_TORUS_RADIUS+cu)*sv;
+ kb->x[k][2] = su*cv2;
+ kb->x[k][3] = su*sv2;
+ kb->xu[k][0] = -su*cv;
+ kb->xu[k][1] = -su*sv;
+ kb->xu[k][2] = cu*cv2;
+ kb->xu[k][3] = cu*sv2;
+ kb->xv[k][0] = -(SQUEEZED_TORUS_RADIUS+cu)*sv;
+ kb->xv[k][1] = (SQUEEZED_TORUS_RADIUS+cu)*cv;
+ kb->xv[k][2] = -0.5*su*sv2;
+ kb->xv[k][3] = 0.5*su*cv2;
+ for (l=0; l<4; l++)
+ {
+ kb->x[k][l] /= SQUEEZED_TORUS_RADIUS+1.25;
+ kb->xu[k][l] /= SQUEEZED_TORUS_RADIUS+1.25;
+ kb->xv[k][l] /= SQUEEZED_TORUS_RADIUS+1.25;
+ }
+ }
+ }
+}
+
+
+/* Set up the Lawson Klein bottle coordinates, colors, and texture. */
+static void setup_lawson(ModeInfo *mi, double umin, double umax, double vmin,
+ double vmax)
+{
+ int i, j, k;
+ double u, v, ur, vr;
+ double cu, su, cv, sv, cv2, sv2;
+ kleinstruct *kb = &klein[MI_SCREEN(mi)];
+
+ ur = umax-umin;
+ vr = vmax-vmin;
+ for (i=0; i<=NUMV; i++)
+ {
+ for (j=0; j<=NUMU; j++)
+ {
+ k = i*(NUMU+1)+j;
+ u = -ur*j/NUMU+umin;
+ v = vr*i/NUMV+vmin;
+ if (kb->colors == COLORS_DEPTH)
+ color(kb,(sin(u)*cos(0.5*v)+1.0)*M_PI*2.0/3.0,kb->col[k]);
+ else
+ color(kb,v,kb->col[k]);
+ kb->tex[k][0] = -32*u/(2.0*M_PI);
+ kb->tex[k][1] = 32*v/(2.0*M_PI);
+ cu = cos(u);
+ su = sin(u);
+ cv = cos(v);
+ sv = sin(v);
+ cv2 = cos(0.5*v);
+ sv2 = sin(0.5*v);
+ kb->x[k][0] = cu*cv;
+ kb->x[k][1] = cu*sv;
+ kb->x[k][2] = su*sv2;
+ kb->x[k][3] = su*cv2;
+ kb->xu[k][0] = -su*cv;
+ kb->xu[k][1] = -su*sv;
+ kb->xu[k][2] = cu*sv2;
+ kb->xu[k][3] = cu*cv2;
+ kb->xv[k][0] = -cu*sv;
+ kb->xv[k][1] = cu*cv;
+ kb->xv[k][2] = su*cv2*0.5;
+ kb->xv[k][3] = -su*sv2*0.5;
+ }
+ }
+}
+
+
+/* Draw a figure-8 Klein bottle projected into 3D. */
+static int figure8(ModeInfo *mi, double umin, double umax, double vmin,
+ double vmax)
+{
+ int polys = 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_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 };
+ float p[3], pu[3], pv[3], pm[3], n[3], b[3], mat[4][4];
+ int i, j, k, l, m, o;
+ double u, v;
+ double xx[4], xxu[4], xxv[4], y[4], yu[4], yv[4];
+ double q, r, s, t;
+ double cu, su, cv, sv, cv2, sv2, c2u, s2u;
+ float q1[4], q2[4], r1[4][4], r2[4][4];
+ kleinstruct *kb = &klein[MI_SCREEN(mi)];
+
+ if (kb->view == VIEW_WALK || kb->view == VIEW_WALKTURN)
+ {
+ /* Compute the rotation that rotates the Klein bottle in 4D without the
+ trackball rotations. */
+ rotateall4d(kb->zeta,kb->eta,kb->theta,mat);
+
+ u = kb->umove;
+ v = kb->vmove;
+ cu = cos(u);
+ su = sin(u);
+ cv = cos(v);
+ sv = sin(v);
+ cv2 = cos(0.5*v);
+ sv2 = sin(0.5*v);
+ c2u = cos(2.0*u);
+ s2u = sin(2.0*u);
+ xx[0] = (su*cv2-s2u*sv2+FIGURE_8_RADIUS)*cv;
+ xx[1] = (su*cv2-s2u*sv2+FIGURE_8_RADIUS)*sv;
+ xx[2] = su*sv2+s2u*cv2;
+ xx[3] = cu;
+ xxu[0] = (cu*cv2-2.0*c2u*sv2)*cv;
+ xxu[1] = (cu*cv2-2.0*c2u*sv2)*sv;
+ xxu[2] = cu*sv2+2.0*c2u*cv2;
+ xxu[3] = -su;
+ xxv[0] = ((-0.5*su*sv2-0.5*s2u*cv2)*cv-
+ (su*cv2-s2u*sv2+FIGURE_8_RADIUS)*sv);
+ xxv[1] = ((-0.5*su*sv2-0.5*s2u*cv2)*sv+
+ (su*cv2-s2u*sv2+FIGURE_8_RADIUS)*cv);
+ xxv[2] = 0.5*su*cv2-0.5*s2u*sv2;
+ xxv[3] = 0.0;
+ for (l=0; l<4; l++)
+ {
+ xx[l] /= FIGURE_8_RADIUS+1.25;
+ xxu[l] /= FIGURE_8_RADIUS+1.25;
+ xxv[l] /= FIGURE_8_RADIUS+1.25;
+ }
+ for (l=0; l<4; l++)
+ {
+ y[l] = (mat[l][0]*xx[0]+mat[l][1]*xx[1]+
+ mat[l][2]*xx[2]+mat[l][3]*xx[3]);
+ yu[l] = (mat[l][0]*xxu[0]+mat[l][1]*xxu[1]+
+ mat[l][2]*xxu[2]+mat[l][3]*xxu[3]);
+ yv[l] = (mat[l][0]*xxv[0]+mat[l][1]*xxv[1]+
+ mat[l][2]*xxv[2]+mat[l][3]*xxv[3]);
+ }
+ if (kb->projection_4d == DISP_4D_ORTHOGRAPHIC)
+ {
+ for (l=0; l<3; l++)
+ {
+ p[l] = y[l]+kb->offset4d[l];
+ pu[l] = yu[l];
+ pv[l] = yv[l];
+ }
+ }
+ else
+ {
+ s = y[3]+kb->offset4d[3];
+ q = 1.0/s;
+ t = q*q;
+ for (l=0; l<3; l++)
+ {
+ r = y[l]+kb->offset4d[l];
+ p[l] = r*q;
+ pu[l] = (yu[l]*s-r*yu[3])*t;
+ pv[l] = (yv[l]*s-r*yv[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 = 1.0/(kb->side*4.0*sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]));
+ n[0] *= t;
+ n[1] *= t;
+ n[2] *= t;
+ pm[0] = pu[0]*kb->dumove+pv[0]*kb->dvmove;
+ pm[1] = pu[1]*kb->dumove+pv[1]*kb->dvmove;
+ pm[2] = pu[2]*kb->dumove+pv[2]*kb->dvmove;
+ t = 1.0/(4.0*sqrt(pm[0]*pm[0]+pm[1]*pm[1]+pm[2]*pm[2]));
+ pm[0] *= t;
+ pm[1] *= t;
+ pm[2] *= t;
+ b[0] = n[1]*pm[2]-n[2]*pm[1];
+ b[1] = n[2]*pm[0]-n[0]*pm[2];
+ b[2] = n[0]*pm[1]-n[1]*pm[0];
+ t = 1.0/(4.0*sqrt(b[0]*b[0]+b[1]*b[1]+b[2]*b[2]));
+ b[0] *= t;
+ b[1] *= t;
+ b[2] *= t;
+
+ /* Compute alpha, beta, delta from the three basis vectors.
+ | -b[0] -b[1] -b[2] |
+ m = | n[0] n[1] n[2] |
+ | -pm[0] -pm[1] -pm[2] |
+ */
+ kb->alpha = atan2(-n[2],-pm[2])*180/M_PI;
+ kb->beta = atan2(-b[2],sqrt(b[0]*b[0]+b[1]*b[1]))*180/M_PI;
+ kb->delta = atan2(b[1],-b[0])*180/M_PI;
+
+ /* Compute the rotation that rotates the Klein bottle in 4D. */
+ rotateall(kb->alpha,kb->beta,kb->delta,kb->zeta,kb->eta,kb->theta,mat);
+
+ u = kb->umove;
+ v = kb->vmove;
+ cu = cos(u);
+ su = sin(u);
+ cv = cos(v);
+ sv = sin(v);
+ cv2 = cos(0.5*v);
+ sv2 = sin(0.5*v);
+ /*c2u = cos(2.0*u);*/
+ s2u = sin(2.0*u);
+ xx[0] = (su*cv2-s2u*sv2+FIGURE_8_RADIUS)*cv;
+ xx[1] = (su*cv2-s2u*sv2+FIGURE_8_RADIUS)*sv;
+ xx[2] = su*sv2+s2u*cv2;
+ xx[3] = cu;
+ for (l=0; l<4; l++)
+ xx[l] /= FIGURE_8_RADIUS+1.25;
+ for (l=0; l<4; l++)
+ {
+ r = 0.0;
+ for (m=0; m<4; m++)
+ r += mat[l][m]*xx[m];
+ y[l] = r;
+ }
+ if (kb->projection_4d == DISP_4D_ORTHOGRAPHIC)
+ {
+ for (l=0; l<3; l++)
+ p[l] = y[l]+kb->offset4d[l];
+ }
+ else
+ {
+ s = y[3]+kb->offset4d[3];
+ for (l=0; l<3; l++)
+ p[l] = (y[l]+kb->offset4d[l])/s;
+ }
+
+ kb->offset3d[0] = -p[0];
+ kb->offset3d[1] = -p[1]-DELTAY;
+ kb->offset3d[2] = -p[2];
+ }
+ else
+ {
+ /* Compute the rotation that rotates the Klein bottle in 4D, including
+ the trackball rotations. */
+ rotateall(kb->alpha,kb->beta,kb->delta,kb->zeta,kb->eta,kb->theta,r1);
+
+ gltrackball_get_quaternion(kb->trackballs[0],q1);
+ gltrackball_get_quaternion(kb->trackballs[1],q2);
+ quats_to_rotmat(q1,q2,r2);
+
+ mult_rotmat(r2,r1,mat);
+ }
+
+ /* Project the points from 4D to 3D. */
+ for (i=0; i<=NUMU; i++)
+ {
+ for (j=0; j<=NUMV; j++)
+ {
+ o = i*(NUMV+1)+j;
+ for (l=0; l<4; l++)
+ {
+ y[l] = (mat[l][0]*kb->x[o][0]+mat[l][1]*kb->x[o][1]+
+ mat[l][2]*kb->x[o][2]+mat[l][3]*kb->x[o][3]);
+ yu[l] = (mat[l][0]*kb->xu[o][0]+mat[l][1]*kb->xu[o][1]+
+ mat[l][2]*kb->xu[o][2]+mat[l][3]*kb->xu[o][3]);
+ yv[l] = (mat[l][0]*kb->xv[o][0]+mat[l][1]*kb->xv[o][1]+
+ mat[l][2]*kb->xv[o][2]+mat[l][3]*kb->xv[o][3]);
+ }
+ if (kb->projection_4d == DISP_4D_ORTHOGRAPHIC)
+ {
+ for (l=0; l<3; l++)
+ {
+ kb->pp[o][l] = (y[l]+kb->offset4d[l])+kb->offset3d[l];
+ pu[l] = yu[l];
+ pv[l] = yv[l];
+ }
+ }
+ else
+ {
+ s = y[3]+kb->offset4d[3];
+ q = 1.0/s;
+ t = q*q;
+ for (l=0; l<3; l++)
+ {
+ r = y[l]+kb->offset4d[l];
+ kb->pp[o][l] = r*q+kb->offset3d[l];
+ pu[l] = (yu[l]*s-r*yu[3])*t;
+ pv[l] = (yv[l]*s-r*yv[3])*t;
+ }
+ }
+ kb->pn[o][0] = pu[1]*pv[2]-pu[2]*pv[1];
+ kb->pn[o][1] = pu[2]*pv[0]-pu[0]*pv[2];
+ kb->pn[o][2] = pu[0]*pv[1]-pu[1]*pv[0];
+ t = 1.0/sqrt(kb->pn[o][0]*kb->pn[o][0]+kb->pn[o][1]*kb->pn[o][1]+
+ kb->pn[o][2]*kb->pn[o][2]);
+ kb->pn[o][0] *= t;
+ kb->pn[o][1] *= t;
+ kb->pn[o][2] *= t;
+ }
+ }
+
+ if (kb->colors == COLORS_TWOSIDED)
+ {
+ glColor3fv(mat_diff_red);
+ if (kb->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);
+ }
+ }
+ glBindTexture(GL_TEXTURE_2D,kb->tex_name);
+
+ for (i=0; i<NUMU; i++)
+ {
+ if (kb->appearance == APPEARANCE_BANDS && ((i & (NUMB-1)) >= NUMB/2))
+ continue;
+ if (kb->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;
+ o = l*(NUMV+1)+m;
+ glNormal3fv(kb->pn[o]);
+ glTexCoord2fv(kb->tex[o]);
+ if (kb->colors != COLORS_TWOSIDED)
+ {
+ glColor3fv(kb->col[o]);
+ glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE,kb->col[o]);
+ }
+ glVertex3fv(kb->pp[o]);
+ polys++;
+ }
+ }
+ glEnd();
+ }
+ polys /= 2;
+ return polys;
+}
+
+
+/* Draw a squeezed torus Klein bottle projected into 3D. */
+static int squeezed_torus(ModeInfo *mi, double umin, double umax, double vmin,
+ double vmax)
+{
+ int polys = 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_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 };
+ float p[3], pu[3], pv[3], pm[3], n[3], b[3], mat[4][4];
+ int i, j, k, l, m, o;
+ double u, v;
+ double xx[4], xxu[4], xxv[4], y[4], yu[4], yv[4];
+ double q, r, s, t;
+ double cu, su, cv, sv, cv2, sv2;
+ float q1[4], q2[4], r1[4][4], r2[4][4];
+ kleinstruct *kb = &klein[MI_SCREEN(mi)];
+
+ if (kb->view == VIEW_WALK || kb->view == VIEW_WALKTURN)
+ {
+ /* Compute the rotation that rotates the Klein bottle in 4D without the
+ trackball rotations. */
+ rotateall4d(kb->zeta,kb->eta,kb->theta,mat);
+
+ u = kb->umove;
+ v = kb->vmove;
+ cu = cos(u);
+ su = sin(u);
+ cv = cos(v);
+ sv = sin(v);
+ cv2 = cos(0.5*v);
+ sv2 = sin(0.5*v);
+ xx[0] = (SQUEEZED_TORUS_RADIUS+cu)*cv;
+ xx[1] = (SQUEEZED_TORUS_RADIUS+cu)*sv;
+ xx[2] = su*cv2;
+ xx[3] = su*sv2;
+ xxu[0] = -su*cv;
+ xxu[1] = -su*sv;
+ xxu[2] = cu*cv2;
+ xxu[3] = cu*sv2;
+ xxv[0] = -(SQUEEZED_TORUS_RADIUS+cu)*sv;
+ xxv[1] = (SQUEEZED_TORUS_RADIUS+cu)*cv;
+ xxv[2] = -0.5*su*sv2;
+ xxv[3] = 0.5*su*cv2;
+ for (l=0; l<4; l++)
+ {
+ xx[l] /= SQUEEZED_TORUS_RADIUS+1.25;
+ xxu[l] /= SQUEEZED_TORUS_RADIUS+1.25;
+ xxv[l] /= SQUEEZED_TORUS_RADIUS+1.25;
+ }
+ for (l=0; l<4; l++)
+ {
+ y[l] = (mat[l][0]*xx[0]+mat[l][1]*xx[1]+
+ mat[l][2]*xx[2]+mat[l][3]*xx[3]);
+ yu[l] = (mat[l][0]*xxu[0]+mat[l][1]*xxu[1]+
+ mat[l][2]*xxu[2]+mat[l][3]*xxu[3]);
+ yv[l] = (mat[l][0]*xxv[0]+mat[l][1]*xxv[1]+
+ mat[l][2]*xxv[2]+mat[l][3]*xxv[3]);
+ }
+ if (kb->projection_4d == DISP_4D_ORTHOGRAPHIC)
+ {
+ for (l=0; l<3; l++)
+ {
+ p[l] = y[l]+kb->offset4d[l];
+ pu[l] = yu[l];
+ pv[l] = yv[l];
+ }
+ }
+ else
+ {
+ s = y[3]+kb->offset4d[3];
+ q = 1.0/s;
+ t = q*q;
+ for (l=0; l<3; l++)
+ {
+ r = y[l]+kb->offset4d[l];
+ p[l] = r*q;
+ pu[l] = (yu[l]*s-r*yu[3])*t;
+ pv[l] = (yv[l]*s-r*yv[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 = 1.0/(kb->side*4.0*sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]));
+ n[0] *= t;
+ n[1] *= t;
+ n[2] *= t;
+ pm[0] = pu[0]*kb->dumove+pv[0]*kb->dvmove;
+ pm[1] = pu[1]*kb->dumove+pv[1]*kb->dvmove;
+ pm[2] = pu[2]*kb->dumove+pv[2]*kb->dvmove;
+ t = 1.0/(4.0*sqrt(pm[0]*pm[0]+pm[1]*pm[1]+pm[2]*pm[2]));
+ pm[0] *= t;
+ pm[1] *= t;
+ pm[2] *= t;
+ b[0] = n[1]*pm[2]-n[2]*pm[1];
+ b[1] = n[2]*pm[0]-n[0]*pm[2];
+ b[2] = n[0]*pm[1]-n[1]*pm[0];
+ t = 1.0/(4.0*sqrt(b[0]*b[0]+b[1]*b[1]+b[2]*b[2]));
+ b[0] *= t;
+ b[1] *= t;
+ b[2] *= t;
+
+ /* Compute alpha, beta, delta from the three basis vectors.
+ | -b[0] -b[1] -b[2] |
+ m = | n[0] n[1] n[2] |
+ | -pm[0] -pm[1] -pm[2] |
+ */
+ kb->alpha = atan2(-n[2],-pm[2])*180/M_PI;
+ kb->beta = atan2(-b[2],sqrt(b[0]*b[0]+b[1]*b[1]))*180/M_PI;
+ kb->delta = atan2(b[1],-b[0])*180/M_PI;
+
+ /* Compute the rotation that rotates the Klein bottle in 4D. */
+ rotateall(kb->alpha,kb->beta,kb->delta,kb->zeta,kb->eta,kb->theta,mat);
+
+ u = kb->umove;
+ v = kb->vmove;
+ cu = cos(u);
+ su = sin(u);
+ cv = cos(v);
+ sv = sin(v);
+ cv2 = cos(0.5*v);
+ sv2 = sin(0.5*v);
+ xx[0] = (SQUEEZED_TORUS_RADIUS+cu)*cv;
+ xx[1] = (SQUEEZED_TORUS_RADIUS+cu)*sv;
+ xx[2] = su*cv2;
+ xx[3] = su*sv2;
+ for (l=0; l<4; l++)
+ xx[l] /= SQUEEZED_TORUS_RADIUS+1.25;
+ for (l=0; l<4; l++)
+ {
+ r = 0.0;
+ for (m=0; m<4; m++)
+ r += mat[l][m]*xx[m];
+ y[l] = r;
+ }
+ if (kb->projection_4d == DISP_4D_ORTHOGRAPHIC)
+ {
+ for (l=0; l<3; l++)
+ p[l] = y[l]+kb->offset4d[l];
+ }
+ else
+ {
+ s = y[3]+kb->offset4d[3];
+ for (l=0; l<3; l++)
+ p[l] = (y[l]+kb->offset4d[l])/s;
+ }
+
+ kb->offset3d[0] = -p[0];
+ kb->offset3d[1] = -p[1]-DELTAY;
+ kb->offset3d[2] = -p[2];
+ }
+ else
+ {
+ /* Compute the rotation that rotates the Klein bottle in 4D, including
+ the trackball rotations. */
+ rotateall(kb->alpha,kb->beta,kb->delta,kb->zeta,kb->eta,kb->theta,r1);
+
+ gltrackball_get_quaternion(kb->trackballs[0],q1);
+ gltrackball_get_quaternion(kb->trackballs[1],q2);
+ quats_to_rotmat(q1,q2,r2);
+
+ mult_rotmat(r2,r1,mat);
+ }
+
+ /* Project the points from 4D to 3D. */
+ for (i=0; i<=NUMU; i++)
+ {
+ for (j=0; j<=NUMV; j++)
+ {
+ o = i*(NUMV+1)+j;
+ for (l=0; l<4; l++)
+ {
+ y[l] = (mat[l][0]*kb->x[o][0]+mat[l][1]*kb->x[o][1]+
+ mat[l][2]*kb->x[o][2]+mat[l][3]*kb->x[o][3]);
+ yu[l] = (mat[l][0]*kb->xu[o][0]+mat[l][1]*kb->xu[o][1]+
+ mat[l][2]*kb->xu[o][2]+mat[l][3]*kb->xu[o][3]);
+ yv[l] = (mat[l][0]*kb->xv[o][0]+mat[l][1]*kb->xv[o][1]+
+ mat[l][2]*kb->xv[o][2]+mat[l][3]*kb->xv[o][3]);
+ }
+ if (kb->projection_4d == DISP_4D_ORTHOGRAPHIC)
+ {
+ for (l=0; l<3; l++)
+ {
+ kb->pp[o][l] = (y[l]+kb->offset4d[l])+kb->offset3d[l];
+ pu[l] = yu[l];
+ pv[l] = yv[l];
+ }
+ }
+ else
+ {
+ s = y[3]+kb->offset4d[3];
+ q = 1.0/s;
+ t = q*q;
+ for (l=0; l<3; l++)
+ {
+ r = y[l]+kb->offset4d[l];
+ kb->pp[o][l] = r*q+kb->offset3d[l];
+ pu[l] = (yu[l]*s-r*yu[3])*t;
+ pv[l] = (yv[l]*s-r*yv[3])*t;
+ }
+ }
+ kb->pn[o][0] = pu[1]*pv[2]-pu[2]*pv[1];
+ kb->pn[o][1] = pu[2]*pv[0]-pu[0]*pv[2];
+ kb->pn[o][2] = pu[0]*pv[1]-pu[1]*pv[0];
+ t = 1.0/sqrt(kb->pn[o][0]*kb->pn[o][0]+kb->pn[o][1]*kb->pn[o][1]+
+ kb->pn[o][2]*kb->pn[o][2]);
+ kb->pn[o][0] *= t;
+ kb->pn[o][1] *= t;
+ kb->pn[o][2] *= t;
+ }
+ }
+
+ if (kb->colors == COLORS_TWOSIDED)
+ {
+ glColor3fv(mat_diff_red);
+ if (kb->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);
+ }
+ }
+ glBindTexture(GL_TEXTURE_2D,kb->tex_name);
+
+ for (i=0; i<NUMU; i++)
+ {
+ if (kb->appearance == APPEARANCE_BANDS && ((i & (NUMB-1)) >= NUMB/2))
+ continue;
+ if (kb->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;
+ o = l*(NUMV+1)+m;
+ glNormal3fv(kb->pn[o]);
+ glTexCoord2fv(kb->tex[o]);
+ if (kb->colors != COLORS_TWOSIDED)
+ {
+ glColor3fv(kb->col[o]);
+ glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE,kb->col[o]);
+ }
+ glVertex3fv(kb->pp[o]);
+ polys++;
+ }
+ }
+ glEnd();
+ }
+ polys /= 2;
+ return polys;
+}
+
+
+/* Draw a Lawson Klein bottle projected into 3D. */
+static int lawson(ModeInfo *mi, double umin, double umax, double vmin,
+ double vmax)
+{
+ int polys = 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_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 };
+ float p[3], pu[3], pv[3], pm[3], n[3], b[3], mat[4][4];
+ int i, j, k, l, m, o;
+ double u, v;
+ double cu, su, cv, sv, cv2, sv2;
+ double xx[4], xxu[4], xxv[4], y[4], yu[4], yv[4];
+ double q, r, s, t;
+ float q1[4], q2[4], r1[4][4], r2[4][4];
+ kleinstruct *kb = &klein[MI_SCREEN(mi)];
+
+ if (kb->view == VIEW_WALK || kb->view == VIEW_WALKTURN)
+ {
+ /* Compute the rotation that rotates the Klein bottle in 4D without the
+ trackball rotations. */
+ rotateall4d(kb->zeta,kb->eta,kb->theta,mat);
+
+ u = kb->umove;
+ v = kb->vmove;
+ cu = cos(u);
+ su = sin(u);
+ cv = cos(v);
+ sv = sin(v);
+ cv2 = cos(0.5*v);
+ sv2 = sin(0.5*v);
+ xx[0] = cu*cv;
+ xx[1] = cu*sv;
+ xx[2] = su*sv2;
+ xx[3] = su*cv2;
+ xxu[0] = -su*cv;
+ xxu[1] = -su*sv;
+ xxu[2] = cu*sv2;
+ xxu[3] = cu*cv2;
+ xxv[0] = -cu*sv;
+ xxv[1] = cu*cv;
+ xxv[2] = su*cv2*0.5;
+ xxv[3] = -su*sv2*0.5;
+ for (l=0; l<4; l++)
+ {
+ y[l] = (mat[l][0]*xx[0]+mat[l][1]*xx[1]+
+ mat[l][2]*xx[2]+mat[l][3]*xx[3]);
+ yu[l] = (mat[l][0]*xxu[0]+mat[l][1]*xxu[1]+
+ mat[l][2]*xxu[2]+mat[l][3]*xxu[3]);
+ yv[l] = (mat[l][0]*xxv[0]+mat[l][1]*xxv[1]+
+ mat[l][2]*xxv[2]+mat[l][3]*xxv[3]);
+ }
+ if (kb->projection_4d == DISP_4D_ORTHOGRAPHIC)
+ {
+ for (l=0; l<3; l++)
+ {
+ p[l] = y[l]+kb->offset4d[l];
+ pu[l] = yu[l];
+ pv[l] = yv[l];
+ }
+ }
+ else
+ {
+ s = y[3]+kb->offset4d[3];
+ q = 1.0/s;
+ t = q*q;
+ for (l=0; l<3; l++)
+ {
+ r = y[l]+kb->offset4d[l];
+ p[l] = r*q;
+ pu[l] = (yu[l]*s-r*yu[3])*t;
+ pv[l] = (yv[l]*s-r*yv[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 = 1.0/(kb->side*4.0*sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]));
+ n[0] *= t;
+ n[1] *= t;
+ n[2] *= t;
+ pm[0] = pu[0]*kb->dumove+pv[0]*kb->dvmove;
+ pm[1] = pu[1]*kb->dumove+pv[1]*kb->dvmove;
+ pm[2] = pu[2]*kb->dumove+pv[2]*kb->dvmove;
+ t = 1.0/(4.0*sqrt(pm[0]*pm[0]+pm[1]*pm[1]+pm[2]*pm[2]));
+ pm[0] *= t;
+ pm[1] *= t;
+ pm[2] *= t;
+ b[0] = n[1]*pm[2]-n[2]*pm[1];
+ b[1] = n[2]*pm[0]-n[0]*pm[2];
+ b[2] = n[0]*pm[1]-n[1]*pm[0];
+ t = 1.0/(4.0*sqrt(b[0]*b[0]+b[1]*b[1]+b[2]*b[2]));
+ b[0] *= t;
+ b[1] *= t;
+ b[2] *= t;
+
+ /* Compute alpha, beta, delta from the three basis vectors.
+ | -b[0] -b[1] -b[2] |
+ m = | n[0] n[1] n[2] |
+ | -pm[0] -pm[1] -pm[2] |
+ */
+ kb->alpha = atan2(-n[2],-pm[2])*180/M_PI;
+ kb->beta = atan2(-b[2],sqrt(b[0]*b[0]+b[1]*b[1]))*180/M_PI;
+ kb->delta = atan2(b[1],-b[0])*180/M_PI;
+
+ /* Compute the rotation that rotates the Klein bottle in 4D. */
+ rotateall(kb->alpha,kb->beta,kb->delta,kb->zeta,kb->eta,kb->theta,mat);
+
+ u = kb->umove;
+ v = kb->vmove;
+ cu = cos(u);
+ su = sin(u);
+ cv = cos(v);
+ sv = sin(v);
+ cv2 = cos(0.5*v);
+ sv2 = sin(0.5*v);
+ xx[0] = cu*cv;
+ xx[1] = cu*sv;
+ xx[2] = su*sv2;
+ xx[3] = su*cv2;
+ for (l=0; l<4; l++)
+ {
+ r = 0.0;
+ for (m=0; m<4; m++)
+ r += mat[l][m]*xx[m];
+ y[l] = r;
+ }
+ if (kb->projection_4d == DISP_4D_ORTHOGRAPHIC)
+ {
+ for (l=0; l<3; l++)
+ p[l] = y[l]+kb->offset4d[l];
+ }
+ else
+ {
+ s = y[3]+kb->offset4d[3];
+ for (l=0; l<3; l++)
+ p[l] = (y[l]+kb->offset4d[l])/s;
+ }
+
+ kb->offset3d[0] = -p[0];
+ kb->offset3d[1] = -p[1]-DELTAY;
+ kb->offset3d[2] = -p[2];
+ }
+ else
+ {
+ /* Compute the rotation that rotates the Klein bottle in 4D, including
+ the trackball rotations. */
+ rotateall(kb->alpha,kb->beta,kb->delta,kb->zeta,kb->eta,kb->theta,r1);
+
+ gltrackball_get_quaternion(kb->trackballs[0],q1);
+ gltrackball_get_quaternion(kb->trackballs[1],q2);
+ quats_to_rotmat(q1,q2,r2);
+
+ mult_rotmat(r2,r1,mat);
+ }
+
+ /* Project the points from 4D to 3D. */
+ for (i=0; i<=NUMV; i++)
+ {
+ for (j=0; j<=NUMU; j++)
+ {
+ o = i*(NUMU+1)+j;
+ for (l=0; l<4; l++)
+ {
+ y[l] = (mat[l][0]*kb->x[o][0]+mat[l][1]*kb->x[o][1]+
+ mat[l][2]*kb->x[o][2]+mat[l][3]*kb->x[o][3]);
+ yu[l] = (mat[l][0]*kb->xu[o][0]+mat[l][1]*kb->xu[o][1]+
+ mat[l][2]*kb->xu[o][2]+mat[l][3]*kb->xu[o][3]);
+ yv[l] = (mat[l][0]*kb->xv[o][0]+mat[l][1]*kb->xv[o][1]+
+ mat[l][2]*kb->xv[o][2]+mat[l][3]*kb->xv[o][3]);
+ }
+ if (kb->projection_4d == DISP_4D_ORTHOGRAPHIC)
+ {
+ for (l=0; l<3; l++)
+ {
+ kb->pp[o][l] = (y[l]+kb->offset4d[l])+kb->offset3d[l];
+ pu[l] = yu[l];
+ pv[l] = yv[l];
+ }
+ }
+ else
+ {
+ s = y[3]+kb->offset4d[3];
+ q = 1.0/s;
+ t = q*q;
+ for (l=0; l<3; l++)
+ {
+ r = y[l]+kb->offset4d[l];
+ kb->pp[o][l] = r*q+kb->offset3d[l];
+ pu[l] = (yu[l]*s-r*yu[3])*t;
+ pv[l] = (yv[l]*s-r*yv[3])*t;
+ }
+ }
+ kb->pn[o][0] = pu[1]*pv[2]-pu[2]*pv[1];
+ kb->pn[o][1] = pu[2]*pv[0]-pu[0]*pv[2];
+ kb->pn[o][2] = pu[0]*pv[1]-pu[1]*pv[0];
+ t = 1.0/sqrt(kb->pn[o][0]*kb->pn[o][0]+kb->pn[o][1]*kb->pn[o][1]+
+ kb->pn[o][2]*kb->pn[o][2]);
+ kb->pn[o][0] *= t;
+ kb->pn[o][1] *= t;
+ kb->pn[o][2] *= t;
+ }
+ }
+
+ if (kb->colors == COLORS_TWOSIDED)
+ {
+ glColor3fv(mat_diff_red);
+ if (kb->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);
+ }
+ }
+ glBindTexture(GL_TEXTURE_2D,kb->tex_name);
+
+ for (i=0; i<NUMV; i++)
+ {
+ if (kb->appearance == APPEARANCE_BANDS && ((i & (NUMB-1)) >= NUMB/2))
+ continue;
+ if (kb->display_mode == DISP_WIREFRAME)
+ glBegin(GL_QUAD_STRIP);
+ else
+ glBegin(GL_TRIANGLE_STRIP);
+ for (j=0; j<=NUMU; j++)
+ {
+ for (k=0; k<=1; k++)
+ {
+ l = (i+k);
+ m = j;
+ o = l*(NUMU+1)+m;
+ glNormal3fv(kb->pn[o]);
+ glTexCoord2fv(kb->tex[o]);
+ if (kb->colors != COLORS_TWOSIDED)
+ {
+ glColor3fv(kb->col[o]);
+ glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE,kb->col[o]);
+ }
+ glVertex3fv(kb->pp[o]);
+ polys++;
+ }
+ }
+ glEnd();
+ }
+ polys /= 2;
+ return polys;
+}
+
+
+/* Generate a texture image that shows the orientation reversal. */
+static void gen_texture(ModeInfo *mi)
+{
+ kleinstruct *kb = &klein[MI_SCREEN(mi)];
+
+ glGenTextures(1,&kb->tex_name);
+ glBindTexture(GL_TEXTURE_2D,kb->tex_name);
+ glPixelStorei(GL_UNPACK_ALIGNMENT,1);
+ glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
+ glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
+ glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
+ glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
+ glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
+ glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,TEX_DIMENSION,TEX_DIMENSION,0,
+ GL_LUMINANCE,GL_UNSIGNED_BYTE,texture);
+}
+
+
+static void init(ModeInfo *mi)
+{
+ 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 };
+ kleinstruct *kb = &klein[MI_SCREEN(mi)];
+
+ if (walk_speed == 0.0)
+ walk_speed = 20.0;
+
+ if (kb->view == VIEW_TURN)
+ {
+ kb->alpha = frand(360.0);
+ kb->beta = frand(360.0);
+ kb->delta = frand(360.0);
+ }
+ else
+ {
+ kb->alpha = 0.0;
+ kb->beta = 0.0;
+ kb->delta = 0.0;
+ }
+ kb->zeta = 0.0;
+ if (kb->bottle_type == KLEIN_BOTTLE_FIGURE_8 ||
+ kb->bottle_type == KLEIN_BOTTLE_SQUEEZED_TORUS)
+ kb->eta = 0.0;
+ else
+ kb->eta = 45.0;
+ kb->theta = 0.0;
+ kb->umove = frand(2.0*M_PI);
+ kb->vmove = frand(2.0*M_PI);
+ kb->dumove = 0.0;
+ kb->dvmove = 0.0;
+ kb->side = 1;
+
+ if (kb->bottle_type == KLEIN_BOTTLE_FIGURE_8)
+ {
+ kb->offset4d[0] = 0.0;
+ kb->offset4d[1] = 0.0;
+ kb->offset4d[2] = 0.0;
+ kb->offset4d[3] = 1.5;
+ kb->offset3d[0] = 0.0;
+ kb->offset3d[1] = 0.0;
+ if (kb->projection_4d == DISP_4D_ORTHOGRAPHIC)
+ kb->offset3d[2] = -2.1;
+ else
+ kb->offset3d[2] = -1.9;
+ kb->offset3d[3] = 0.0;
+ }
+ else if (kb->bottle_type == KLEIN_BOTTLE_SQUEEZED_TORUS)
+ {
+ kb->offset4d[0] = 0.0;
+ kb->offset4d[1] = 0.0;
+ kb->offset4d[2] = 0.0;
+ kb->offset4d[3] = 1.4;
+ kb->offset3d[0] = 0.0;
+ kb->offset3d[1] = 0.0;
+ kb->offset3d[2] = -2.0;
+ kb->offset3d[3] = 0.0;
+ }
+ else /* kb->bottle_type == KLEIN_BOTTLE_LAWSON */
+ {
+ kb->offset4d[0] = 0.0;
+ kb->offset4d[1] = 0.0;
+ kb->offset4d[2] = 0.0;
+ if (kb->projection_4d == DISP_4D_PERSPECTIVE &&
+ kb->projection_3d == DISP_3D_ORTHOGRAPHIC)
+ kb->offset4d[3] = 1.5;
+ else
+ kb->offset4d[3] = 1.1;
+ kb->offset3d[0] = 0.0;
+ kb->offset3d[1] = 0.0;
+ if (kb->projection_4d == DISP_4D_ORTHOGRAPHIC)
+ kb->offset3d[2] = -2.0;
+ else
+ kb->offset3d[2] = -5.0;
+ kb->offset3d[3] = 0.0;
+ }
+
+ gen_texture(mi);
+ if (kb->bottle_type == KLEIN_BOTTLE_FIGURE_8)
+ setup_figure8(mi,0.0,2.0*M_PI,0.0,2.0*M_PI);
+ else if (kb->bottle_type == KLEIN_BOTTLE_SQUEEZED_TORUS)
+ setup_squeezed_torus(mi,0.0,2.0*M_PI,0.0,2.0*M_PI);
+ else /* kb->bottle_type == KLEIN_BOTTLE_LAWSON */
+ setup_lawson(mi,0.0,2.0*M_PI,0.0,2.0*M_PI);
+
+ if (marks)
+ glEnable(GL_TEXTURE_2D);
+ else
+ glDisable(GL_TEXTURE_2D);
+
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ if (kb->projection_3d == DISP_3D_PERSPECTIVE ||
+ kb->view == VIEW_WALK || kb->view == VIEW_WALKTURN)
+ {
+ if (kb->view == VIEW_WALK || kb->view == VIEW_WALKTURN)
+ gluPerspective(60.0,1.0,0.01,10.0);
+ else
+ gluPerspective(60.0,1.0,0.1,10.0);
+ }
+ else
+ {
+ glOrtho(-1.0,1.0,-1.0,1.0,0.1,10.0);
+ }
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+
+# ifdef HAVE_JWZGLES /* #### glPolygonMode other than GL_FILL unimplemented */
+ if (kb->display_mode == DISP_WIREFRAME)
+ kb->display_mode = DISP_SURFACE;
+# endif
+
+ if (kb->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);
+ 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 (kb->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);
+ 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 /* kb->display_mode == DISP_WIREFRAME */
+ {
+ glDisable(GL_DEPTH_TEST);
+ glShadeModel(GL_FLAT);
+ glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
+ glDisable(GL_LIGHTING);
+ glDisable(GL_LIGHT0);
+ glDisable(GL_BLEND);
+ }
+}
+
+
+/* Redisplay the Klein bottle. */
+static void display_klein(ModeInfo *mi)
+{
+ kleinstruct *kb = &klein[MI_SCREEN(mi)];
+
+ if (!kb->button_pressed)
+ {
+ if (kb->view == VIEW_TURN)
+ {
+ kb->alpha += speed_wx * kb->speed_scale;
+ if (kb->alpha >= 360.0)
+ kb->alpha -= 360.0;
+ kb->beta += speed_wy * kb->speed_scale;
+ if (kb->beta >= 360.0)
+ kb->beta -= 360.0;
+ kb->delta += speed_wz * kb->speed_scale;
+ if (kb->delta >= 360.0)
+ kb->delta -= 360.0;
+ kb->zeta += speed_xy * kb->speed_scale;
+ if (kb->zeta >= 360.0)
+ kb->zeta -= 360.0;
+ kb->eta += speed_xz * kb->speed_scale;
+ if (kb->eta >= 360.0)
+ kb->eta -= 360.0;
+ kb->theta += speed_yz * kb->speed_scale;
+ if (kb->theta >= 360.0)
+ kb->theta -= 360.0;
+ }
+ if (kb->view == VIEW_WALKTURN)
+ {
+ kb->zeta += speed_xy * kb->speed_scale;
+ if (kb->zeta >= 360.0)
+ kb->zeta -= 360.0;
+ kb->eta += speed_xz * kb->speed_scale;
+ if (kb->eta >= 360.0)
+ kb->eta -= 360.0;
+ kb->theta += speed_yz * kb->speed_scale;
+ if (kb->theta >= 360.0)
+ kb->theta -= 360.0;
+ }
+ if (kb->view == VIEW_WALK || kb->view == VIEW_WALKTURN)
+ {
+ kb->dvmove = cos(walk_direction*M_PI/180.0)*walk_speed*M_PI/4096.0;
+ kb->vmove += kb->dvmove;
+ if (kb->vmove >= 2.0*M_PI)
+ {
+ kb->vmove -= 2.0*M_PI;
+ kb->umove = 2.0*M_PI-kb->umove;
+ kb->side = -kb->side;
+ }
+ kb->dumove = (kb->side*sin(walk_direction*M_PI/180.0)*
+ walk_speed*M_PI/4096.0);
+ kb->umove += kb->dumove;
+ if (kb->umove >= 2.0*M_PI)
+ kb->umove -= 2.0*M_PI;
+ if (kb->umove < 0.0)
+ kb->umove += 2.0*M_PI;
+ }
+ }
+
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ if (kb->projection_3d == DISP_3D_PERSPECTIVE ||
+ kb->view == VIEW_WALK || kb->view == VIEW_WALKTURN)
+ {
+ if (kb->view == VIEW_WALK || kb->view == VIEW_WALKTURN)
+ gluPerspective(60.0,kb->aspect,0.01,10.0);
+ else
+ gluPerspective(60.0,kb->aspect,0.1,10.0);
+ }
+ else
+ {
+ if (kb->aspect >= 1.0)
+ glOrtho(-kb->aspect,kb->aspect,-1.0,1.0,0.1,10.0);
+ else
+ glOrtho(-1.0,1.0,-1.0/kb->aspect,1.0/kb->aspect,0.1,10.0);
+ }
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+
+ if (kb->bottle_type == KLEIN_BOTTLE_FIGURE_8)
+ mi->polygon_count = figure8(mi,0.0,2.0*M_PI,0.0,2.0*M_PI);
+ else if (kb->bottle_type == KLEIN_BOTTLE_SQUEEZED_TORUS)
+ mi->polygon_count = squeezed_torus(mi,0.0,2.0*M_PI,0.0,2.0*M_PI);
+ else /* kb->bottle_type == KLEIN_BOTTLE_LAWSON */
+ mi->polygon_count = lawson(mi,0.0,2.0*M_PI,0.0,2.0*M_PI);
+}
+
+
+ENTRYPOINT void reshape_klein(ModeInfo *mi, int width, int height)
+{
+ kleinstruct *kb = &klein[MI_SCREEN(mi)];
+
+ kb->WindW = (GLint)width;
+ kb->WindH = (GLint)height;
+ glViewport(0,0,width,height);
+ kb->aspect = (GLfloat)width/(GLfloat)height;
+}
+
+
+ENTRYPOINT Bool klein_handle_event(ModeInfo *mi, XEvent *event)
+{
+ kleinstruct *kb = &klein[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)
+ {
+ kb->button_pressed = True;
+ gltrackball_start(kb->trackballs[kb->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)
+ {
+ kb->button_pressed = False;
+ return True;
+ }
+ else if (event->xany.type == KeyPress)
+ {
+ if (sym == XK_Shift_L || sym == XK_Shift_R)
+ {
+ kb->current_trackball = 1;
+ if (kb->button_pressed)
+ gltrackball_start(kb->trackballs[kb->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)
+ {
+ kb->current_trackball = 0;
+ if (kb->button_pressed)
+ gltrackball_start(kb->trackballs[kb->current_trackball],
+ event->xbutton.x, event->xbutton.y,
+ MI_WIDTH(mi), MI_HEIGHT(mi));
+ return True;
+ }
+ }
+ else if (event->xany.type == MotionNotify && kb->button_pressed)
+ {
+ gltrackball_track(kb->trackballs[kb->current_trackball],
+ event->xmotion.x, event->xmotion.y,
+ MI_WIDTH(mi), MI_HEIGHT(mi));
+ return True;
+ }
+
+ return False;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *-----------------------------------------------------------------------------
+ * Xlock hooks.
+ *-----------------------------------------------------------------------------
+ *-----------------------------------------------------------------------------
+ */
+
+/*
+ *-----------------------------------------------------------------------------
+ * Initialize klein. Called each time the window changes.
+ *-----------------------------------------------------------------------------
+ */
+
+ENTRYPOINT void init_klein(ModeInfo *mi)
+{
+ kleinstruct *kb;
+
+ MI_INIT(mi, klein, NULL);
+ kb = &klein[MI_SCREEN(mi)];
+
+
+ kb->trackballs[0] = gltrackball_init(True);
+ kb->trackballs[1] = gltrackball_init(True);
+ kb->current_trackball = 0;
+ kb->button_pressed = False;
+
+ /* Set the Klein bottle. */
+ if (!strcasecmp(klein_bottle,"random"))
+ {
+ kb->bottle_type = random() % NUM_KLEIN_BOTTLES;
+ }
+ else if (!strcasecmp(klein_bottle,"figure-8"))
+ {
+ kb->bottle_type = KLEIN_BOTTLE_FIGURE_8;
+ }
+ else if (!strcasecmp(klein_bottle,"squeezed-torus"))
+ {
+ kb->bottle_type = KLEIN_BOTTLE_SQUEEZED_TORUS;
+ }
+ else if (!strcasecmp(klein_bottle,"lawson"))
+ {
+ kb->bottle_type = KLEIN_BOTTLE_LAWSON;
+ }
+ else
+ {
+ kb->bottle_type = random() % NUM_KLEIN_BOTTLES;
+ }
+
+ /* Set the display mode. */
+ if (!strcasecmp(mode,"random"))
+ {
+ kb->display_mode = random() % NUM_DISPLAY_MODES;
+ }
+ else if (!strcasecmp(mode,"wireframe"))
+ {
+ kb->display_mode = DISP_WIREFRAME;
+ }
+ else if (!strcasecmp(mode,"surface"))
+ {
+ kb->display_mode = DISP_SURFACE;
+ }
+ else if (!strcasecmp(mode,"transparent"))
+ {
+ kb->display_mode = DISP_TRANSPARENT;
+ }
+ else
+ {
+ kb->display_mode = random() % NUM_DISPLAY_MODES;
+ }
+
+ /* Orientation marks don't make sense in wireframe mode. */
+ if (kb->display_mode == DISP_WIREFRAME)
+ marks = False;
+
+ /* Set the appearance. */
+ if (!strcasecmp(appear,"random"))
+ {
+ kb->appearance = random() % NUM_APPEARANCES;
+ }
+ else if (!strcasecmp(appear,"solid"))
+ {
+ kb->appearance = APPEARANCE_SOLID;
+ }
+ else if (!strcasecmp(appear,"bands"))
+ {
+ kb->appearance = APPEARANCE_BANDS;
+ }
+ else
+ {
+ kb->appearance = random() % NUM_APPEARANCES;
+ }
+
+ /* Set the color mode. */
+ if (!strcasecmp(color_mode,"random"))
+ {
+ kb->colors = random() % NUM_COLORS;
+ }
+ else if (!strcasecmp(color_mode,"two-sided"))
+ {
+ kb->colors = COLORS_TWOSIDED;
+ }
+ else if (!strcasecmp(color_mode,"rainbow"))
+ {
+ kb->colors = COLORS_RAINBOW;
+ }
+ else if (!strcasecmp(color_mode,"depth"))
+ {
+ kb->colors = COLORS_DEPTH;
+ }
+ else
+ {
+ kb->colors = random() % NUM_COLORS;
+ }
+
+ /* Set the view mode. */
+ if (!strcasecmp(view_mode,"random"))
+ {
+ kb->view = random() % NUM_VIEW_MODES;
+ }
+ else if (!strcasecmp(view_mode,"walk"))
+ {
+ kb->view = VIEW_WALK;
+ }
+ else if (!strcasecmp(view_mode,"turn"))
+ {
+ kb->view = VIEW_TURN;
+ }
+ else if (!strcasecmp(view_mode,"walk-turn"))
+ {
+ kb->view = VIEW_WALKTURN;
+ }
+ else
+ {
+ kb->view = random() % NUM_VIEW_MODES;
+ }
+
+ /* Set the 3d projection mode. */
+ if (!strcasecmp(proj_3d,"random"))
+ {
+ /* Orthographic projection only makes sense in turn mode. */
+ if (kb->view == VIEW_TURN)
+ kb->projection_3d = random() % NUM_DISP_3D_MODES;
+ else
+ kb->projection_3d = DISP_3D_PERSPECTIVE;
+ }
+ else if (!strcasecmp(proj_3d,"perspective"))
+ {
+ kb->projection_3d = DISP_3D_PERSPECTIVE;
+ }
+ else if (!strcasecmp(proj_3d,"orthographic"))
+ {
+ kb->projection_3d = DISP_3D_ORTHOGRAPHIC;
+ }
+ else
+ {
+ /* Orthographic projection only makes sense in turn mode. */
+ if (kb->view == VIEW_TURN)
+ kb->projection_3d = random() % NUM_DISP_3D_MODES;
+ else
+ kb->projection_3d = DISP_3D_PERSPECTIVE;
+ }
+
+ /* Set the 4d projection mode. */
+ if (!strcasecmp(proj_4d,"random"))
+ {
+ kb->projection_4d = random() % NUM_DISP_4D_MODES;
+ }
+ else if (!strcasecmp(proj_4d,"perspective"))
+ {
+ kb->projection_4d = DISP_4D_PERSPECTIVE;
+ }
+ else if (!strcasecmp(proj_4d,"orthographic"))
+ {
+ kb->projection_4d = DISP_4D_ORTHOGRAPHIC;
+ }
+ else
+ {
+ kb->projection_4d = random() % NUM_DISP_4D_MODES;
+ }
+
+ /* Modify the speeds to a useful range in walk-and-turn mode. */
+ if (kb->view == VIEW_WALKTURN)
+ {
+ speed_wx *= 0.2;
+ speed_wy *= 0.2;
+ speed_wz *= 0.2;
+ speed_xy *= 0.2;
+ speed_xz *= 0.2;
+ speed_yz *= 0.2;
+ }
+
+ /* make multiple screens rotate at slightly different rates. */
+ kb->speed_scale = 0.9 + frand(0.3);
+
+ if ((kb->glx_context = init_GL(mi)) != NULL)
+ {
+ reshape_klein(mi,MI_WIDTH(mi),MI_HEIGHT(mi));
+ glDrawBuffer(GL_BACK);
+ init(mi);
+ }
+ else
+ {
+ MI_CLEARWINDOW(mi);
+ }
+}
+
+/*
+ *-----------------------------------------------------------------------------
+ * Called by the mainline code periodically to update the display.
+ *-----------------------------------------------------------------------------
+ */
+ENTRYPOINT void draw_klein(ModeInfo *mi)
+{
+ Display *display = MI_DISPLAY(mi);
+ Window window = MI_WINDOW(mi);
+ kleinstruct *kb;
+
+ if (klein == NULL)
+ return;
+ kb = &klein[MI_SCREEN(mi)];
+
+ MI_IS_DRAWN(mi) = True;
+ if (!kb->glx_context)
+ return;
+
+ glXMakeCurrent(display,window,*(kb->glx_context));
+
+ glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
+ glLoadIdentity();
+
+ display_klein(mi);
+
+ if (MI_IS_FPS(mi))
+ do_fps (mi);
+
+ glFlush();
+
+ glXSwapBuffers(display,window);
+}
+
+
+#ifndef STANDALONE
+ENTRYPOINT void change_klein(ModeInfo *mi)
+{
+ kleinstruct *kb = &klein[MI_SCREEN(mi)];
+
+ if (!kb->glx_context)
+ return;
+
+ glXMakeCurrent(MI_DISPLAY(mi),MI_WINDOW(mi),*(kb->glx_context));
+ init(mi);
+}
+#endif /* !STANDALONE */
+
+XSCREENSAVER_MODULE ("Klein", klein)
+
+#endif /* USE_GL */