aboutsummaryrefslogtreecommitdiff
path: root/examples/teapot.c
diff options
context:
space:
mode:
authorRadosław Kujawa <radoslaw.kujawa@c0ff33.net>2026-07-18 00:14:46 +0200
committerRadosław Kujawa <radoslaw.kujawa@c0ff33.net>2026-07-18 00:14:46 +0200
commitb3d6f168c17ebd60acc673ddfb2e71494054b599 (patch)
tree8b62eaa189ce6d738604ab0a4ad02b8f2be2415b /examples/teapot.c
parent14f2715c3d07953168cda37eab60db4d6d05e046 (diff)
TEAPOT TIME!
Diffstat (limited to 'examples/teapot.c')
-rw-r--r--examples/teapot.c288
1 files changed, 288 insertions, 0 deletions
diff --git a/examples/teapot.c b/examples/teapot.c
new file mode 100644
index 0000000..ff67f95
--- /dev/null
+++ b/examples/teapot.c
@@ -0,0 +1,288 @@
+/*
+ * 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 <sys/time.h>
+#include <math.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <GL/gl.h>
+#include <GL/vrmesa.h>
+
+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;
+}