Draw the Utah Teapot

20

4

The Utah teapot, originally created by Martin Newell, is a convenient object for testing 3D graphics programs.

The task is to create a wireframe image of the teapot in perspective projection. To encourage the idea of a source-code application, the viewing and camera control may be isolated and excluded from the count. This is so the parameters and input file may be modified and the code re-used to generate diverse images, but it is not necessary to create a full-fledged utility that accepts complicated command-line arguments or such. A "hackerly" balance is sought.

wireframe teapot

ref. StackOverflow: How do Bezier Patches work in the Utah Teapot?

So there are three subtasks here:

  • read-in the teapot data in its original format.
  • subdivide the patch data using deCasteljau splitting or other method. Other methods are using Bezier basis matrices and evaluating the polynomials (standard refs, like Foley and van Dam, Newmann and Sproull), or Bernstein basis methods (which are still beyond me).
  • project the points to 2D (if the language does not support 3D natively) and draw the outline of each small patch as seen from an Eye point whose view is centered on a LookAt point and whose vertical axis is aligned with the vertical axis of the teapot (ie. draw it "upright" from a nice vantagepoint).

Assuming that reading line-oriented text data from a file is little trouble, this challenge is really about getting hands-on with Bi-Cubic Bezier patch data.

Since the simple normal test for backface culling is not sufficient (the patches are not all oriented outward), no hidden-line or -surface removal is necessary. As a wireframe, it should look just fine with the back visible. The appearance may be improved by adjusting the line-width depending upon the distance from the eye, but this is not strictly necessary (my own programs do not do this).

This is both and . Answers competing in the golf should include a count as normal. But submissions in unusual languages are very much encouraged, even if they're not particularly short.

For Kolmogorov-complexity enthusiasts, there is a more concise dataset where the full set may be reconstructed by adding rotations and mirroring of patches. And in Jim Blinn's A Trip Down the Graphics Pipeline, there is an even more concise method of generation by using the fact that the individual patches have rotational or other symmetries. The entire body (or lid) can be described by a single Bezier curve which is rotated around the y-axis. The spout and handles can be described by the two curves of their profile, and then selecting the intermediate control points to approximate a circular extrusion.

luser droog

Posted 2014-03-02T06:26:09.463

Reputation: 4 535

I’d suggest disallowing builtins like glutSolidTeapot and glutWireTeapot!

– Anders Kaseorg – 2016-07-07T08:48:43.313

@AndersKaseorg I think I covered that by requiring to read-in the original data. ...That said, I have been lax in enforcing this rule. A more closely valid answer would easily take the checkmark, even if it's longer. – luser droog – 2016-09-12T23:00:18.630

@luserdroog Imagine a solution that reads the original data, ignores it, and calls glutWireTeapot. – Anders Kaseorg – 2016-09-13T08:20:13.987

This question does not appear to fit the [tag:rosetta-stone] tag description. A [tag:rosetta-stone] challenge involves the answers providing solutions in a number of different languages not the answers providing solutions in "unusual" languages. – 0 ' – 2017-04-04T20:23:54.243

Should i include the count of the array of point in my count? – TheDoctor – 2014-03-02T15:48:10.137

I would rather see it coming from a file, ... but no, no need to count the patch data however it comes. – luser droog – 2014-03-03T03:02:20.613

Answers

9

Processing (java), 314 (237 without camera control)

Not including the array definitions:

void setup(){size(640,480,P3D);}void draw(){background(0);noFill();stroke(255);translate(width/2,height/2,70);scale(30);rotateX(map(mouseX,0,width,0,TWO_PI));rotateY(map(mouseY,0,height,0,TWO_PI));for(int[] p:patches){beginShape();for(int pt:p){vertex(data[pt-1][0],data[pt-1][1],data[pt-1][2]);}endShape(CLOSE);}}

Data array definitions:

float [][] data = {{1.4,0.0,2.4},
{1.4,-0.784,2.4},
{0.784,-1.4,2.4},
{0.0,-1.4,2.4},
{1.3375,0.0,2.53125},
{1.3375,-0.749,2.53125},
{0.749,-1.3375,2.53125},
{0.0,-1.3375,2.53125},
{1.4375,0.0,2.53125},
{1.4375,-0.805,2.53125},
{0.805,-1.4375,2.53125},
{0.0,-1.4375,2.53125},
{1.5,0.0,2.4},
{1.5,-0.84,2.4},
{0.84,-1.5,2.4},
{0.0,-1.5,2.4},
{-0.784,-1.4,2.4},
{-1.4,-0.784,2.4},
{-1.4,0.0,2.4},
{-0.749,-1.3375,2.53125},
{-1.3375,-0.749,2.53125},
{-1.3375,0.0,2.53125},
{-0.805,-1.4375,2.53125},
{-1.4375,-0.805,2.53125},
{-1.4375,0.0,2.53125},
{-0.84,-1.5,2.4},
{-1.5,-0.84,2.4},
{-1.5,0.0,2.4},
{-1.4,0.784,2.4},
{-0.784,1.4,2.4},
{0.0,1.4,2.4},
{-1.3375,0.749,2.53125},
{-0.749,1.3375,2.53125},
{0.0,1.3375,2.53125},
{-1.4375,0.805,2.53125},
{-0.805,1.4375,2.53125},
{0.0,1.4375,2.53125},
{-1.5,0.84,2.4},
{-0.84,1.5,2.4},
{0.0,1.5,2.4},
{0.784,1.4,2.4},
{1.4,0.784,2.4},
{0.749,1.3375,2.53125},
{1.3375,0.749,2.53125},
{0.805,1.4375,2.53125},
{1.4375,0.805,2.53125},
{0.84,1.5,2.4},
{1.5,0.84,2.4},
{1.75,0.0,1.875},
{1.75,-0.98,1.875},
{0.98,-1.75,1.875},
{0.0,-1.75,1.875},
{2.0,0.0,1.35},
{2.0,-1.12,1.35},
{1.12,-2.0,1.35},
{0.0,-2.0,1.35},
{2.0,0.0,0.9},
{2.0,-1.12,0.9},
{1.12,-2.0,0.9},
{0.0,-2.0,0.9},
{-0.98,-1.75,1.875},
{-1.75,-0.98,1.875},
{-1.75,0.0,1.875},
{-1.12,-2.0,1.35},
{-2.0,-1.12,1.35},
{-2.0,0.0,1.35},
{-1.12,-2.0,0.9},
{-2.0,-1.12,0.9},
{-2.0,0.0,0.9},
{-1.75,0.98,1.875},
{-0.98,1.75,1.875},
{0.0,1.75,1.875},
{-2.0,1.12,1.35},
{-1.12,2.0,1.35},
{0.0,2.0,1.35},
{-2.0,1.12,0.9},
{-1.12,2.0,0.9},
{0.0,2.0,0.9},
{0.98,1.75,1.875},
{1.75,0.98,1.875},
{1.12,2.0,1.35},
{2.0,1.12,1.35},
{1.12,2.0,0.9},
{2.0,1.12,0.9},
{2.0,0.0,0.45},
{2.0,-1.12,0.45},
{1.12,-2.0,0.45},
{0.0,-2.0,0.45},
{1.5,0.0,0.225},
{1.5,-0.84,0.225},
{0.84,-1.5,0.225},
{0.0,-1.5,0.225},
{1.5,0.0,0.15},
{1.5,-0.84,0.15},
{0.84,-1.5,0.15},
{0.0,-1.5,0.15},
{-1.12,-2.0,0.45},
{-2.0,-1.12,0.45},
{-2.0,0.0,0.45},
{-0.84,-1.5,0.225},
{-1.5,-0.84,0.225},
{-1.5,0.0,0.225},
{-0.84,-1.5,0.15},
{-1.5,-0.84,0.15},
{-1.5,0.0,0.15},
{-2.0,1.12,0.45},
{-1.12,2.0,0.45},
{0.0,2.0,0.45},
{-1.5,0.84,0.225},
{-0.84,1.5,0.225},
{0.0,1.5,0.225},
{-1.5,0.84,0.15},
{-0.84,1.5,0.15},
{0.0,1.5,0.15},
{1.12,2.0,0.45},
{2.0,1.12,0.45},
{0.84,1.5,0.225},
{1.5,0.84,0.225},
{0.84,1.5,0.15},
{1.5,0.84,0.15},
{-1.6,0.0,2.025},
{-1.6,-0.3,2.025},
{-1.5,-0.3,2.25},
{-1.5,0.0,2.25},
{-2.3,0.0,2.025},
{-2.3,-0.3,2.025},
{-2.5,-0.3,2.25},
{-2.5,0.0,2.25},
{-2.7,0.0,2.025},
{-2.7,-0.3,2.025},
{-3.0,-0.3,2.25},
{-3.0,0.0,2.25},
{-2.7,0.0,1.8},
{-2.7,-0.3,1.8},
{-3.0,-0.3,1.8},
{-3.0,0.0,1.8},
{-1.5,0.3,2.25},
{-1.6,0.3,2.025},
{-2.5,0.3,2.25},
{-2.3,0.3,2.025},
{-3.0,0.3,2.25},
{-2.7,0.3,2.025},
{-3.0,0.3,1.8},
{-2.7,0.3,1.8},
{-2.7,0.0,1.575},
{-2.7,-0.3,1.575},
{-3.0,-0.3,1.35},
{-3.0,0.0,1.35},
{-2.5,0.0,1.125},
{-2.5,-0.3,1.125},
{-2.65,-0.3,0.9375},
{-2.65,0.0,0.9375},
{-2.0,-0.3,0.9},
{-1.9,-0.3,0.6},
{-1.9,0.0,0.6},
{-3.0,0.3,1.35},
{-2.7,0.3,1.575},
{-2.65,0.3,0.9375},
{-2.5,0.3,1.125},
{-1.9,0.3,0.6},
{-2.0,0.3,0.9},
{1.7,0.0,1.425},
{1.7,-0.66,1.425},
{1.7,-0.66,0.6},
{1.7,0.0,0.6},
{2.6,0.0,1.425},
{2.6,-0.66,1.425},
{3.1,-0.66,0.825},
{3.1,0.0,0.825},
{2.3,0.0,2.1},
{2.3,-0.25,2.1},
{2.4,-0.25,2.025},
{2.4,0.0,2.025},
{2.7,0.0,2.4},
{2.7,-0.25,2.4},
{3.3,-0.25,2.4},
{3.3,0.0,2.4},
{1.7,0.66,0.6},
{1.7,0.66,1.425},
{3.1,0.66,0.825},
{2.6,0.66,1.425},
{2.4,0.25,2.025},
{2.3,0.25,2.1},
{3.3,0.25,2.4},
{2.7,0.25,2.4},
{2.8,0.0,2.475},
{2.8,-0.25,2.475},
{3.525,-0.25,2.49375},
{3.525,0.0,2.49375},
{2.9,0.0,2.475},
{2.9,-0.15,2.475},
{3.45,-0.15,2.5125},
{3.45,0.0,2.5125},
{2.8,0.0,2.4},
{2.8,-0.15,2.4},
{3.2,-0.15,2.4},
{3.2,0.0,2.4},
{3.525,0.25,2.49375},
{2.8,0.25,2.475},
{3.45,0.15,2.5125},
{2.9,0.15,2.475},
{3.2,0.15,2.4},
{2.8,0.15,2.4},
{0.0,0.0,3.15},
{0.0,-0.002,3.15},
{0.002,0.0,3.15},
{0.8,0.0,3.15},
{0.8,-0.45,3.15},
{0.45,-0.8,3.15},
{0.0,-0.8,3.15},
{0.0,0.0,2.85},
{0.2,0.0,2.7},
{0.2,-0.112,2.7},
{0.112,-0.2,2.7},
{0.0,-0.2,2.7},
{-0.002,0.0,3.15},
{-0.45,-0.8,3.15},
{-0.8,-0.45,3.15},
{-0.8,0.0,3.15},
{-0.112,-0.2,2.7},
{-0.2,-0.112,2.7},
{-0.2,0.0,2.7},
{0.0,0.002,3.15},
{-0.8,0.45,3.15},
{-0.45,0.8,3.15},
{0.0,0.8,3.15},
{-0.2,0.112,2.7},
{-0.112,0.2,2.7},
{0.0,0.2,2.7},
{0.45,0.8,3.15},
{0.8,0.45,3.15},
{0.112,0.2,2.7},
{0.2,0.112,2.7},
{0.4,0.0,2.55},
{0.4,-0.224,2.55},
{0.224,-0.4,2.55},
{0.0,-0.4,2.55},
{1.3,0.0,2.55},
{1.3,-0.728,2.55},
{0.728,-1.3,2.55},
{0.0,-1.3,2.55},
{1.3,0.0,2.4},
{1.3,-0.728,2.4},
{0.728,-1.3,2.4},
{0.0,-1.3,2.4},
{-0.224,-0.4,2.55},
{-0.4,-0.224,2.55},
{-0.4,0.0,2.55},
{-0.728,-1.3,2.55},
{-1.3,-0.728,2.55},
{-1.3,0.0,2.55},
{-0.728,-1.3,2.4},
{-1.3,-0.728,2.4},
{-1.3,0.0,2.4},
{-0.4,0.224,2.55},
{-0.224,0.4,2.55},
{0.0,0.4,2.55},
{-1.3,0.728,2.55},
{-0.728,1.3,2.55},
{0.0,1.3,2.55},
{-1.3,0.728,2.4},
{-0.728,1.3,2.4},
{0.0,1.3,2.4},
{0.224,0.4,2.55},
{0.4,0.224,2.55},
{0.728,1.3,2.55},
{1.3,0.728,2.55},
{0.728,1.3,2.4},
{1.3,0.728,2.4},
{0.0,0.0,0.0},
{1.5,0.0,0.15},
{1.5,0.84,0.15},
{0.84,1.5,0.15},
{0.0,1.5,0.15},
{1.5,0.0,0.075},
{1.5,0.84,0.075},
{0.84,1.5,0.075},
{0.0,1.5,0.075},
{1.425,0.0,0.0},
{1.425,0.798,0.0},
{0.798,1.425,0.0},
{0.0,1.425,0.0},
{-0.84,1.5,0.15},
{-1.5,0.84,0.15},
{-1.5,0.0,0.15},
{-0.84,1.5,0.075},
{-1.5,0.84,0.075},
{-1.5,0.0,0.075},
{-0.798,1.425,0.0},
{-1.425,0.798,0.0},
{-1.425,0.0,0.0},
{-1.5,-0.84,0.15},
{-0.84,-1.5,0.15},
{0.0,-1.5,0.15},
{-1.5,-0.84,0.075},
{-0.84,-1.5,0.075},
{0.0,-1.5,0.075},
{-1.425,-0.798,0.0},
{-0.798,-1.425,0.0},
{0.0,-1.425,0.0},
{0.84,-1.5,0.15},
{1.5,-0.84,0.15},
{0.84,-1.5,0.075},
{1.5,-0.84,0.075},
{0.798,-1.425,0.0},
{1.425,-0.798,0.0}
};

int [][] patches = {
    {32},
{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16},
{4,17,18,19,8,20,21,22,12,23,24,25,16,26,27,28},
{19,29,30,31,22,32,33,34,25,35,36,37,28,38,39,40},
{31,41,42,1,34,43,44,5,37,45,46,9,40,47,48,13},
{13,14,15,16,49,50,51,52,53,54,55,56,57,58,59,60},
{16,26,27,28,52,61,62,63,56,64,65,66,60,67,68,69},
{28,38,39,40,63,70,71,72,66,73,74,75,69,76,77,78},
{40,47,48,13,72,79,80,49,75,81,82,53,78,83,84,57},
{57,58,59,60,85,86,87,88,89,90,91,92,93,94,95,96},
{60,67,68,69,88,97,98,99,92,100,101,102,96,103,104,105},
{69,76,77,78,99,106,107,108,102,109,110,111,105,112,113,114},
{78,83,84,57,108,115,116,85,111,117,118,89,114,119,120,93},
{121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136},
{124,137,138,121,128,139,140,125,132,141,142,129,136,143,144,133},
{133,134,135,136,145,146,147,148,149,150,151,152,69,153,154,155},
{136,143,144,133,148,156,157,145,152,158,159,149,155,160,161,69},
{162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177},
{165,178,179,162,169,180,181,166,173,182,183,170,177,184,185,174},
{174,175,176,177,186,187,188,189,190,191,192,193,194,195,196,197},
{177,184,185,174,189,198,199,186,193,200,201,190,197,202,203,194},
{204,204,204,204,207,208,209,210,211,211,211,211,212,213,214,215},
{204,204,204,204,210,217,218,219,211,211,211,211,215,220,221,222},
{204,204,204,204,219,224,225,226,211,211,211,211,222,227,228,229},
{204,204,204,204,226,230,231,207,211,211,211,211,229,232,233,212},
{212,213,214,215,234,235,236,237,238,239,240,241,242,243,244,245},
{215,220,221,222,237,246,247,248,241,249,250,251,245,252,253,254},
{222,227,228,229,248,255,256,257,251,258,259,260,254,261,262,263},
{229,232,233,212,257,264,265,234,260,266,267,238,263,268,269,242},
{270,270,270,270,279,280,281,282,275,276,277,278,271,272,273,274},
{270,270,270,270,282,289,290,291,278,286,287,288,274,283,284,285},
{270,270,270,270,291,298,299,300,288,295,296,297,285,292,293,294},
{270,270,270,270,300,305,306,279,297,303,304,275,294,301,302,271},
{306}
};

More readable version:

void setup() {
  size(640,480,P3D);
}

void draw() {
  background(0);
  noFill();
  stroke(255);
  translate(width/2,height/2,70);
  scale(30);
  rotateX(map(mouseX,0,width,0,TWO_PI));
  rotateY(map(mouseY,0,height,0,TWO_PI));
  for (int[] p:patches) {
    beginShape();
    for (int pt:p) {
      vertex(data[pt-1][0],data[pt-1][2],data[pt-1][2]);
    }
    endShape(CLOSE); 
  }
}

And some pics:

finished product

Another version with some interesting effects:

void setup(){size(640,480,P3D);}
void draw(){
  background(0);noFill();stroke(255);
  translate(width/2,height/2,70);scale(30);
  rotateX(map(mouseX,0,width,0,TWO_PI));rotateY(map(mouseY,0,height,0,TWO_PI));
  for(int[] p:patches){
    //beginShape(QUADS);
    for(int pt:p){
      for(int pu:p){
        //vertex(data[pu-1][0],data[pu-1][4],data[pu-1][2]);
        line(data[pt-1][0],data[pt-1][5],data[pt-1][2],data[pu-1][0],data[pu-1][6],data[pu-1][2]);
    }}
    //endShape(CLOSE);
  }
}

version 2

TheDoctor

Posted 2014-03-02T06:26:09.463

Reputation: 7 793

stroke(-1) is one byte shorter than stroke(255) – user41805 – 2017-04-23T15:32:39.150

It should split the patches at least once, I think, for the spout to take shape. – luser droog – 2014-03-03T06:36:06.023

Yes the second picture is better. You're not really doing subdivision, though, it seems. The edges of each patch are Bezier curves... Even so, +1 It looks like a teapot! – luser droog – 2014-03-04T05:56:35.467

11

Postscript

Not fully golfed, but this illustrates a different approach than deCasteljau subdivision: evaluating the basis polynomial. Uses mat.ps.

(mat.ps)run[    % load matrix library, begin dictionary construction

/N 17
/C [ 0 7 4 ]   % Cam
/E [ 0 0 40 ] % Eye
/R 0 roty 120 rotx 90 rotz   % Rot: pan tilt twist
          matmul   matmul

/f(teapot)(r)file
/t{token pop exch pop}      % parse a number or other ps token
/s{(,){search not{t exit}if t 3 1 roll}loop}  % parse a comma-separated list
/r{token pop{[f 99 string readline pop s]}repeat}>>begin   % parse a count-prefixed paragraph of csv numbers
[/P[f r]/V[f r]/v{1 sub V exch get}        % Patches and Vertices and vert lookup shortcut
/B[[-1 3 -3 1][3 -6 3 0][-3 3 0 0][1 0 0 0]]              % Bezier basis matrix
/A{dup dup mul exch 2 copy mul 3 1 roll 1 4 array astore} % x->[x^3 x^2 x 1]
/M{[1 index 0 4 getinterval 2 index 4 4 getinterval       % flattened matrix->rowXcolumn matrix
3 index 8 4 getinterval 4 index 12 4 getinterval]exch pop}
/J{ C{sub}vop R matmul 0 get                              % perspective proJection  [x y z]->[X Y]
    aload pop E aload pop
    4 3 roll div exch neg
    4 3 roll add 1 index mul 4 1 roll
    3 1 roll sub mul}
>>begin

300 400 translate
1 14 dup dup scale div currentlinewidth mul setlinewidth  % global scale
/newline { /line {moveto /line {lineto} store} store } def
newline
P{
    8 dict begin
        [exch{v J 2 array astore}forall]/p exch def   % load patch vertices and project to 2D
        /X[p{0 get}forall] M B exch matmul B matmul def  % multiply control points by Bezier basis
        /Y[p{1 get}forall] M B exch matmul B matmul def

        0 1 N div 1 1 index .2 mul add{A/U exch def   % interpolate the polynomial over (u,v)/(N)
            /UX U X matmul def
            /UY U Y matmul def
            0 1 N div 1 1 index .2 mul add{A/V exch 1 array astore transpose def
                /UXV UX V matmul def
                /UYV UY V matmul def
                UXV 0 get 0 get
                UYV 0 get 0 get line
            }for
            newline
        }for

        0 1 N div 1 1 index .2 mul add{A/V exch def   % interpolate the polynomial over (u,v)/(N)
            /V [V] transpose def
            /XV X V matmul def
            /YV Y V matmul def
            0 1 N div 1 1 index .2 mul add{A/U exch 1 array astore transpose def
                /UXV U XV matmul def
                /UYV U YV matmul def
                UXV 0 get 0 get
                UYV 0 get 0 get line
            }for
            newline
        }for

    end

    %exit
}forall
stroke

Bezier-basis teapot

1112

Stripping out the vertical lines, and discounting the parameters, yields this 1112 char version. Uses mat.ps.

(mat.ps)run[    % 12

/N 17
/C [ 0 7 4 ]   % Cam 
/E [ 0 0 40 ] % Eye 
/R 0 roty 120 rotx 90 rotz   % Rot: pan tilt twist
          matmul   matmul

/f(teapot)(r)file/t{token pop exch pop}/s{(,){search not{t exit}if t   % 1100
3 1 roll}loop}/r{token pop{[f 99 string readline pop 
s]}repeat}>>begin[/P[f r]/V[f r]/v{1 sub 
V exch get}/B[[-1 3 -3 1][3 -6 3 0][-3 3 0 0][1 0 0 0]]/A{dup dup mul exch
2 copy mul 3 1 roll 1 4 array astore}/M{[1 index 0 4 getinterval 2 index 4 4 getinterval    
3 index 8 4 getinterval 4 index 12 4 getinterval]exch pop}/J{C{sub}vop R matmul 0 get    
aload pop E aload pop 4 3 roll div exch neg 4 3 roll add 1 index mul 4 1 roll
3 1 roll sub mul}>>begin 300 400 translate
1 14 dup dup scale div currentlinewidth mul setlinewidth  
/newline{/line{moveto/line{lineto}store}store}def newline
P{8 dict begin[exch{v J 2 array astore}forall]/p
exch def/X[p{0 get}forall] M B exch matmul B matmul
def/Y[p{1 get}forall] M B exch matmul B matmul def 
0 1 N div 1 1 index .2 mul add{A/U exch def/UX U X matmul def/UY U Y matmul def 
0 1 N div 1 1 index .2 mul add{A/V exch 1 array astore transpose
def/UXV UX V matmul def/UYV UY V matmul def UXV 0 get 0 get UYV 0 get 0 get line}for
newline}for end}forall stroke

Bezier-basis loops

luser droog

Posted 2014-03-02T06:26:09.463

Reputation: 4 535