// Define variables for width/height/center of our window int winWidth=300, winHeight=300; int winCenX=winWidth/2, winCenY=winHeight/2; double pi = 3.141593; // You'd think the system would have this. class shape3d // This class, the base class for the shape system, just defines // a few polymorphic functions that all types of shapes will // implement. { morph to draw(win win){} // Draw shape morph to move(double dx,dy,dz){} // Relative move morph to moveTo(double dx,dy,dz){} // Move center to morph to zoom(double factor){} // Shrink or expand morph to rotate(double x,y,z){} // Rotate on each axis // Some shortcut functions to rotate about a // particular axis. to rotateX(double angle) {rotate(angle,0,0);} to rotateY(double angle) {rotate(0,angle,0);} to rotateZ(double angle) {rotate(0,0,angle);} } flow rotate2d(double x,y,angle) into (double rx, ry) // Rotate x and y by angle in two dimensions. // No need to embed this in the class heirarchy, it's // a pure flowing function taught in linear algebra // courses worldwide. { double s = sin(angle); double c = cos(angle); rx = x*c + y*s; ry = -x*s + y*c; } flow rotateAroundCenter(point3d point, point3d center, double ax,ay,az) into point3d dest // Rotate self around a center point. The numbers ax,ay,az // specify the amount to rotate around the x,y,and z axis. { // Convert our our coordinate system to center's. double x = point.x - center.x; double y = point.y - center.y; double z = point.z - center.z; // Apply rotations in x,y,z order. A more sophisticated // program could do all three of these with a single // matrix multiply. (y,z) = rotate2d(y,z,ax); (x,z) = rotate2d(x,z,ay); (x,y) = rotate2d(x,y,az); // Convert back from center coordinate space to ours. dest = (x + center.x, y+center.y, z+center.z); } flow zToScale(double z) into double scale // A function to convert a z-coordinate // into a factor for scaling x and y // coordinates to simulate perspective. { // Get a local copy of Z coordinate and turn // it into a scaling factor. We act as if we // are viewing from 500 units back from center // of 3-D world. double eyeDistance=500; z += eyeDistance; if (z < 1) z = 1; // Stay away from zero, it's nasty scale = winWidth/z; // Far away things are smaller... } class int2d // A class that stores a point as transformed to pixel // coordinates. { int x,y; } class point3d extends shape3d // Our first actual shape. It's just a point. We'll draw // it as a single pixel dot. We'll also compose some // of our other objects out of these point3d's. { double x=0,y=0,z=0; // Our data, it's not much. flow toTwoD() into (int2d flat) // Convert 3-D point into a position in window pixel // coordinates using a quick and dirty approximate // perspective transformation. { double scale = zToScale(z); flat = (self.x * scale + winCenX, self.y * scale + winCenY); } morph to draw(win win) { int2d flat = toTwoD(); win.plotDot(flat.x,flat.y); } morph to move(double dx,dy,dz) // Relative move. { x += dx; y += dy; z += dz; } morph to moveTo(double x,y,z) // Absolute move. { self.x = x; self.y = y; self.z = z; } morph to zoom(double scaleFactor) // Control size of object { // Nothing for us to do here, since we are // always just a single pixel. But we // still have to have a body for this // function or the compiler will complain. } morph to rotate(double dx,dy,dz) { // Also nothing for us to do here. A point // is a point from any angle! } } class sphere extends point3d // A sphere - the second-easiest thing in the 3D world. // Just a point with a radius. { double radius = 10; // Avoid spheres that look like points. morph to draw(win win) { // We'll just draw ourselves as a circle. // Get radius. int pixelRadius = zToScale(radius); // Call toTwoD in parent to get our center point. int2d flat = toTwoD(); win.circle(flat.x,flat.y,pixelRadius); } morph to zoom(double factor) { radius *= factor; } // For all other methods we depend on the parent class } class polyhedron extends shape3d // This is a polyhedron, which is the base class // for most of our other shapes. A polyhedron is composed of // points and facets. A facet is a planar polygon // whose edges connect some of the polyhedron's // vertexes. { point3d center; // Our center point array of point3d vertices; // Points relative to center array of facet facets; // We'll define a facet later morph to draw(win win) // Draw polyhedron { // We project our vertices into 2D space, and // then call facet drawer. We can do the // projection part in parallel. ugly(vertices); ugly(facets); array of int2d vertices2d = para(v in vertices) get v.toTwoD(); for (facet in facets) facet.draw(win, vertices2d); } morph to move(double dx,dy,dz) // To move self, just need to move center point { center.move(dx,dy,dz); } morph to moveTo(double x,y,z) { center.move(x,y,z); } morph to zoom(double factor) // Ah, this one we can do in parallel! { para (v in vertices) do { v.x *= factor; v.y *= factor; v.z *= factor; } } morph to rotate(double x,y,z) // We can rotate in parallel too. { para (v in vertices) do v = rotateAroundCenter(v, center,x,y,z); } to init(array of point3d vertices, array of facet facets) // This class needs real data to initialize it. // The children classes such as slab, pyramid // and, cylinder will fill these in. { if (vertices == nil || facets == nil) punt("Polyhedron missing required init data."); self.center = (); self.vertices = vertices; self.facets = facets; } } class facet // This defines a facet (face) of a polyhedron. It amounts // to a little array of indexes that point back to // the polyhedron's vertex array. All vertexes in a // facet need to be in the same plane. { array of int points; // Indexes into vertex array to draw(win win, array of int2d vertices) // Draw a line around a facet of a polyhedron. { int2d a,b; // Draw lines between vertex and previous // vertex. for (i in 1 til points.size) { a = vertices[points[i-1]]; b = vertices[points[i]]; win.line(a.x, a.y, b.x, b.y); } // Draw a final line between the first and // last vertex. a = vertices[points[0]]; b = vertices[points[points.size-1]]; win.line(a.x, a.y, b.x, b.y); } } class pyramid extends polyhedron // This is an Egyptian style pyramid, with a // square base and triangular sides. { to init(double height, double width) { double w = width/2; array of point3d corners = ( (-w, -w, 0), (w, -w, 0), (w, w, 0), (-w, w, 0), (0, 0, height), ); array of facet faces = ( ((0,1,2,3),), ((0,1,4),), ((1,2,4),), ((2,3,4),), ((3,0,4),), ); parent.init(corners, faces); } } class slab extends polyhedron // A slab is the three-D equivalent of a rectangle. // All of the angles are 90 degrees. The sides may // be different sizes though. { to init(double dx,dy,dz) // Initialize a slab of the given dimensions. { // We'll be centered at zero, so it'll be // a bit easier if we divide dimensions by // two. dx /= 2; dy /= 2; dz /= 2; array of point3d corners= ( (-dx, -dy, -dz), (dx, -dy, -dz), (dx, dy, -dz), (-dx, dy, -dz), (-dx, -dy, dz), (dx, -dy, dz), (dx, dy, dz), (-dx, dy, dz), ); array of facet faces = ( ((0,1,2,3),), ((4,5,6,7),), ((0,1,5,4),), ((2,3,7,6),), ((1,2,5,6),), ((0,3,4,7),), ); parent.init(corners, faces); } } class cube extends slab // This is a special case of the slab { to init(double size) { parent.init(size, size, size); } } class cylinder extends polyhedron // This is a vertical cylinder. The cylinder is // not entirely smooth. Basically the top and the // bottom are regular polygons with n vertices in // them. The sides are rectangles. As n grows larger // the top and bottom start to look more like a circle // and the rectangles in the sides start blending into // each other. { to init (double height, double radius, int n) { array[2*n] of point3d vertices; array[2+n] of facet facets; double da = 2*pi/n; // Angle between points. double h = height/2; // Fill in vertices array in parallel by just // going around circle twice effectively. There's // a little bit of math here so we might as well // do it in parallel. para (key@v in vertices) do { v = (); (v.x,v.y) = rotate2d(0, radius, key*da); if (key < n) v.z = -h; else v.z = h; } // Fill in facets except for the top and bottom // and one lonely little strip on the side. for (i in 1 til n) facets[i] = ((i-1, i, n+i, n+i-1),); // Fill in the last strip facets[0] = ((n-1, 0, n+0, n+n-1),); // Fill in the top and bottom. array[n] of int iTop, iBottom; for (i in 0 til n) { iTop[i] = i; iBottom[i] = i+n; } facets[n] = (iTop); facets[n+1] = (iBottom); parent.init(vertices, facets); } } // We're done with all of the shape defining code // at last! Now we'll write a little application with // a real minimal keyboard-driven user interface. // It opens a window, and then sits in a loop waiting // for the next key. It responds to these key // commands: // + - make shape bigger // - - make shape smaller // x - rotate in x axis // y - rotate in y axis // z - rotate in z axis // p - make shape a pyramid // c - make shape a cube // s - make shape a slab // o - make shape a sphere // 9 - make shape a "cylinder" with 9 sides // 8 - make shape a "cylinder" with 8 sides // ... // 3 - make shape a "cylinder" with 3 sides // q - quits program // The shape initially will be a cube. // Make an instance of each shape, but don't display // them. pyramid pyramid = (height@100, width@150); cube cube = (100); slab slab = (dx@25, dy@50, dz@100); sphere sphere = (radius@50); cylinder c3 = (height@100, radius@20, n@3); cylinder c4 = (height@100, radius@20, n@4); cylinder c5 = (height@100, radius@20, n@5); cylinder c6 = (height@100, radius@20, n@6); cylinder c7 = (height@100, radius@20, n@7); cylinder c8 = (height@100, radius@20, n@8); cylinder c9 = (height@100, radius@20, n@9); // Make a dir to associate keys and shapes dir of shape3d shapeDir = ( "p"@pyramid, "c"@cube, "s"@slab, "o"@sphere, "3"@c3, "4"@c4, "5"@c5, "6"@c6, "7"@c7, "8"@c8, "9"@c9, ); // Initialize active shape to cube and open up our // window shape3d shape = cube; win win = winOpen(width@300, height@300); // Do main program loop for (;;) { win.clear(); shape.draw(win); string key = keyIn(); shape3d newShape = shapeDir[key]; if (newShape) // we found shape in dictionary shape = newShape; else if (key == 'q') break; // Exit main loop else if (key == '+') shape.zoom(1.1); else if (key == '-') shape.zoom(0.9); else if (key == 'x') shape.rotateX(pi/10); else if (key == 'y') shape.rotateY(pi/10); else if (key == 'z') shape.rotateZ(pi/10); } /*-soon cylinder c3 = (height@100, radius@20, n@3); win win = winOpen(width@300, height@300); win.clear(); c3.draw(win); soon-*/