/* * teapot - the Utah teapot, lit and spinning * * Build: * cc -O2 -I../include -o teapot teapot.c ../lib/libGL.a -lm * * Run: VERITE_UCODE=$HOME/v2000gl.uc \ * ./teapot [seconds] (0 = spin until ^C) * * Tessellation density is TEAPOT_GRID (default 8)... */ #include #include #include #include #include #include #include static const GLfloat teapot_patch[32][4][4][3] = { #include "teapot_data.inc" }; static volatile sig_atomic_t stop; static void onint(int sig) { (void)sig; stop = 1; } /* Cubic Bernstein basis and its derivative at t. */ static void bernstein(float t, float b[4], float db[4]) { float it = 1.0f - t; b[0] = it * it * it; b[1] = 3.0f * t * it * it; b[2] = 3.0f * t * t * it; b[3] = t * t * t; db[0] = -3.0f * it * it; db[1] = 3.0f * it * it - 6.0f * t * it; db[2] = 6.0f * t * it - 3.0f * t * t; db[3] = 3.0f * t * t; } /* * Evaluate patch p at (u,v) */ static void eval_patch(int p, float u, float v, float pos[3], float nrm[3]) { float bu[4], dbu[4], bv[4], dbv[4]; float du[3], dv[3], len; int i, j, k; if (u < 1e-4f) u = 1e-4f; else if (u > 1.0f - 1e-4f) u = 1.0f - 1e-4f; if (v < 1e-4f) v = 1e-4f; else if (v > 1.0f - 1e-4f) v = 1.0f - 1e-4f; bernstein(u, bu, dbu); bernstein(v, bv, dbv); for (k = 0; k < 3; k++) pos[k] = du[k] = dv[k] = 0.0f; for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) { const GLfloat *cp = teapot_patch[p][i][j]; float w = bu[i] * bv[j]; float wu = dbu[i] * bv[j]; float wv = bu[i] * dbv[j]; for (k = 0; k < 3; k++) { pos[k] += w * cp[k]; du[k] += wu * cp[k]; dv[k] += wv * cp[k]; } } /* normal = du x dv (points outward across the whole teaset) */ nrm[0] = du[1] * dv[2] - du[2] * dv[1]; nrm[1] = du[2] * dv[0] - du[0] * dv[2]; nrm[2] = du[0] * dv[1] - du[1] * dv[0]; len = sqrtf(nrm[0] * nrm[0] + nrm[1] * nrm[1] + nrm[2] * nrm[2]); if (len > 1e-8f) { nrm[0] /= len; nrm[1] /= len; nrm[2] /= len; } else { nrm[0] = nrm[1] = 0.0f; nrm[2] = 1.0f; } } struct vtx { GLfloat p[3]; GLfloat n[3]; }; static int grid; /* steps per patch edge */ static int row; /* vertices per patch edge = grid + 1 */ static struct vtx *mesh; /* [32][row*row], contiguous */ static void build_mesh(void) { GLfloat lo[3], hi[3]; GLfloat cx, cy, cz, dim, m, gscale; int p, a, b, k, n, nverts; row = grid + 1; nverts = 32 * row * row; mesh = malloc((size_t)nverts * sizeof(*mesh)); if (mesh == NULL) { perror("malloc"); exit(1); } for (k = 0; k < 3; k++) { lo[k] = 1e30f; hi[k] = -1e30f; } for (p = 0; p < 32; p++) for (a = 0; a < row; a++) for (b = 0; b < row; b++) { struct vtx *vp = &mesh[(p * row + a) * row + b]; eval_patch(p, (float)a / grid, (float)b / grid, vp->p, vp->n); for (k = 0; k < 3; k++) { if (vp->p[k] < lo[k]) lo[k] = vp->p[k]; if (vp->p[k] > hi[k]) hi[k] = vp->p[k]; } } /* Bake centre + uniform scale into the vertices so the modelview needs * no scale and the unit normals need no GL_NORMALIZE. */ cx = 0.5f * (lo[0] + hi[0]); cy = 0.5f * (lo[1] + hi[1]); cz = 0.5f * (lo[2] + hi[2]); dim = hi[0] - lo[0]; m = hi[1] - lo[1]; if (m > dim) dim = m; m = hi[2] - lo[2]; if (m > dim) dim = m; gscale = 3.0f / dim; for (n = 0; n < nverts; n++) { mesh[n].p[0] = (mesh[n].p[0] - cx) * gscale; mesh[n].p[1] = (mesh[n].p[1] - cy) * gscale; mesh[n].p[2] = (mesh[n].p[2] - cz) * gscale; } } static void draw_teapot(void) { int p, a, b; for (p = 0; p < 32; p++) for (a = 0; a < grid; a++) { const struct vtx *r0 = &mesh[(p * row + a) * row]; const struct vtx *r1 = &mesh[(p * row + a + 1) * row]; glBegin(GL_TRIANGLE_STRIP); for (b = 0; b < row; b++) { glNormal3fv(r0[b].n); glVertex3fv(r0[b].p); glNormal3fv(r1[b].n); glVertex3fv(r1[b].p); } glEnd(); } } int main(int argc, char **argv) { static const GLfloat lightpos[4] = { -0.4f, 0.7f, 1.0f, 0.0f }; static const GLfloat lightamb[4] = { 0.15f, 0.15f, 0.18f, 1.0f }; static const GLfloat lightdif[4] = { 0.9f, 0.9f, 0.85f, 1.0f }; static const GLfloat lightspc[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; static const GLfloat matdif[4] = { 0.75f, 0.42f, 0.16f, 1.0f }; static const GLfloat matspc[4] = { 0.85f, 0.85f, 0.75f, 1.0f }; static const GLfloat matamb[4] = { 0.25f, 0.14f, 0.06f, 1.0f }; vrMesaContext ctx; struct timeval t0, t1, tstart; const char *genv = getenv("TEAPOT_GRID"); double secs = 30.0; float spin = 0.0f; int frame = 0; if (argc > 1) secs = atof(argv[1]); grid = genv != NULL ? atoi(genv) : 8; if (grid < 2) grid = 2; else if (grid > 40) grid = 40; signal(SIGINT, onint); build_mesh(); ctx = vrMesaCreateContext(NULL); if (ctx == NULL) return 1; vrMesaMakeCurrent(ctx); printf("GL_RENDERER: %s (%s)\n", glGetString(GL_RENDERER), glGetString(GL_VERSION)); printf("teapot: 32 patches, grid %d -> %d triangles\n", grid, 32 * grid * grid * 2); glEnable(GL_DEPTH_TEST); glShadeModel(GL_SMOOTH); /* cross(du,dv) points outward across the whole teaset, so single-sided * lighting shades the exterior correctly and stays on the hardware * triangle path (two-sided lighting would force a swrast fallback). * Back faces fall behind the depth buffer, so culling is unnecessary. */ glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glLightfv(GL_LIGHT0, GL_AMBIENT, lightamb); glLightfv(GL_LIGHT0, GL_DIFFUSE, lightdif); glLightfv(GL_LIGHT0, GL_SPECULAR, lightspc); glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, matamb); glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, matdif); glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, matspc); glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 32.0f); glClearColor(0.07f, 0.08f, 0.11f, 1.0f); glViewport(0, 0, 640, 480); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glFrustum(-1.0, 1.0, -0.75, 0.75, 2.0, 80.0); gettimeofday(&tstart, NULL); t0 = tstart; while (!stop) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslatef(0.0f, -0.3f, -7.0f); /* Fixed light, pinned in eye space before any model rotation. */ glLightfv(GL_LIGHT0, GL_POSITION, lightpos); /* Stand the Z-up model upright (-90) and look slightly down (+15). */ glRotatef(-75.0f, 1.0f, 0.0f, 0.0f); glRotatef(spin, 0.0f, 0.0f, 1.0f); /* spin about the model axis */ draw_teapot(); vrMesaSwapBuffers(); spin += 1.3f; if (++frame % 100 == 0) { double dt; gettimeofday(&t1, NULL); dt = (t1.tv_sec - t0.tv_sec) + (t1.tv_usec - t0.tv_usec) / 1e6; printf("%d frames, %.1f fps\n", frame, 100.0 / dt); t0 = t1; } if (secs > 0) { gettimeofday(&t1, NULL); if ((t1.tv_sec - tstart.tv_sec) + (t1.tv_usec - tstart.tv_usec) / 1e6 >= secs) break; } } printf("teapot done after %d frames\n", frame); vrMesaDestroyContext(ctx); free(mesh); return 0; }