I prematurely posted a code golf challenge to draw the Utah Teapot using this dataset (just the teapot). (Revised and Posted teapot challenge) But when I looked deeper at the data in order to whip up a little example, I realized I have no idea what’s going on with that data. I have a good understanding of Bezier curves in 2D, implemented deCasteljau. But for 3D does it work the same?
Yes! It does!
The data contains patches containing 16 vertices each. Is there a standard ordering for how these are laid out? And if they correspond to the 2D curves, then the four corner points actually touch the surface and the remaining 12 are controls, right?
Yes!
My “original plan” was to simplify the shape to rectangles, project them to the canvas, and draw filled shapes in a grey computed by the magnitude of the dot product of the patch normal to a light vector. If I simplify it that far, will it even look like a teapot? Does one have to use raytracing to achieve a recognizable image?
That’s subjective. 🙁
While this may look like several questions, but they are all aspects of this one: “Please, kindly Guru, school me on some Bezier Patches? What do I need to know to draw the teapot?”
Here’s the code I’ve written so far. (uses this matrix library: mat.ps)
%!
%%BoundingBox: 115 243 493 487
%-115 -243 translate
(mat.ps)run %include matrix library
/tok{ token pop exch pop }def
/s{(,){search{ tok 3 1 roll }{ tok exit }ifelse }loop }def
/f(teapot)(r)file def
/patch[ f token pop { [ f 100 string readline pop s ] } repeat ]def
/vert[ f token pop { [ f 100 string readline pop s ] } repeat ]def
%vert == patch == %test data input
/I3 3 ident def % 3D identity matrix
/Cam [ 0 0 10 ] def % world coords of camera center viewpoint
/Theta [ 0 0 0 ] def % y-rotation x-rotation z-rotation
/Eye [ 0 0 15 ] def % eye relative to camera vp
/Rot I3 def % initial rotation seq
/makerot {
Theta 0 get roty % pan
Theta 1 get rotx matmul % tilt
Theta 2 get rotz matmul % twist
} def
/proj {
Cam {sub} vop % translate to camera coords
Rot matmul % perform camera rotation
0 get aload pop Eye aload pop % extract dot x,y,z and eye xyz
4 3 roll div exch neg % perform perspective projection
4 3 roll add 1 index mul
4 1 roll 3 1 roll sub mul exch % (ez/dz)(dx-ex) (ez/dz)(dy-ey)
} def
/R 20 def
/H -3 def
/ang 0 def
{
300 700 translate
1 70 dup dup scale div setlinewidth
/Cam [ ang sin R mul H ang cos R mul ] def % camera revolves around Y axis at height H, dist R
/Theta [ ang H R atan 0 ] def % rotate camera back to origin
/Rot makerot def % squash rotation sequence into a matrix
patch {
% Four corners
%[ exch dup 0 get exch dup 3 get exch dup 12 get exch 15 get ]
% Boundary curves
[ exch
dup 8 get exch dup 4 get exch dup 0 get exch %curveto4
dup 14 get exch dup 13 get exch dup 12 get exch %curveto3
dup 7 get exch dup 11 get exch dup 15 get exch %curveto2
dup 1 get exch dup 2 get exch dup 3 get exch %curveto1
dup 0 get exch %moveto
pop ]
{ 1 sub vert exch get proj } forall
moveto
curveto curveto curveto curveto
stroke
%flushpage flush (%lineedit)(r)file pop
} forall
pstack
showpage
%exit
/ang ang 10 add def
} loop
Here’s the original Newell Teapot dataset.
And here’s my spectacularly bad image:

Update: bugfix. Maybe they are laid out ‘normally’ after all. Selecting the correct corners at least gives a symmetrical shape:

Update: boundary curves looks better.

A Bi-Cubic Bezier surface patch is a 4×4 array of 3D points. Yes, the four corners touch the surface; and the rows are Bezier curves, and columns are also Bezier curves. But the deCasteljau algorithm is based on calculating the median between two points, and is equally meaningful in 3D as in 2D.
The next step in completing the above code is subdividing the patches to cover smaller portions. Then the simple boundary curve extraction above becomes a suitable polygon mesh.
Start by flattening the patches, inserting the vertex data directly instead of using a separate cache. This code iterates through the patches, looking up points in the vertex array and constructs a new array of patches which is then redefined with the same name.
Then we need the deCasteljau algorithm to split Bezier curves.
vopcomes from the matrix library and applies a binary operation upon corresponding elements of a vector and produces a new vector as the result.Then some stack manipulation to apply to each row of a patch and assemble the results into 2 new patches.
An ugly utility lets us reuse the row code for columns. The stack comments were necessary to write this procedure, so they’re probably necessary to read it.
n j rollrolls n elements (to the left), j times; == the top j elements above n-th element (counting from 1). So n steady decreases, selecting where to put the element, and j selects which element to put there (dragging everything else with it). Ifbindwere applied, this procedure would be substantially faster than a dictionary-based procedure.Then apply these functions to the patch data. Again, redefining patch each time.
This gives the ability to deal with smaller fragments.

Add a visibility test.
Producing

Update: test was backwards.

Update: Test is Useless! You can see from the image that the bottom piece is not oriented outward, and of course, backface-culling doesn’t prevent the handle from showing through the pot. This calls for hidden surface removal. And since Postscript doesn’t have support for a Z-buffer, I guess it’ll have to be a Binary Space Partition. So it’s back to the books for me.
Update: Add a Model->World transform to turn the thing upright.
Producing this.

Complete program so far. (uses matrix library:mat.ps.) In ghostscript, you can view an animated rotation by holding
[enter].