// shapes3d - a ParaFlow program for creating and manipulating // three dimensional shapes of various forms. This just // renders the shapes in black and white wire-frame. It // assumes that we have a simple windows library capable // of drawing lines and circles. include win // Get window library from somewhere // Define variables for width/height/center of our window int winWidth=300, winHeight=300; int winCenX=winWidth/2, winCenY=winHeight/2; 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(window win); // Draw shape morph flow move(double dx,dy,dz); // Relative move morph flow moveTo(double dx,dy,dz); // Move center to morph flow zoom(double factor); // Shrink or expand morph flow rotate(double x,y,z);// Rotate on each axis // Some shortcut functions to rotate about a // particular axis. flow rotateX(double angle) {rotate(angle,0,0);} flow rotateY(double angle) {rotate(0,angle,0);} flow rotateZ(double angle) {rotate(0,0,angle);} } flow rotate(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 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 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,y,z; // Our data, it's not much. flow toTwoD() into (int x,y) // Convert 3-D point into a position in window pixel // coordinates using a quick and dirty approximate // perspective transformation. { double scale = zToScale(z); x = self.x * scale + winCenX; y = self.y * scale + winCenY; } morph to draw(window win) { (int x,y) = toTwoD(); win.plotDot(x,y); } morph flow move(double dx,dy,dz) // Relative move. { x += dx; y += dy; z += dz; } morph flow moveTo(double x,y,z) // Absolute move. { self.x = x; self.y = y; self.z = z; } morph flow 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 flow rotate(double dx,dy,dz) { // Also nothing for us to do here. A point // is a point from any angle! } flow rotateAroundCenter(point3d center, double ax,ay,az) // 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 = self.x - center.x; double y = self.y - center.y; double z = self.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) = rotate(y,z,ax); (x,z) = rotate(x,z,ay); (x,y) = rotate(x,y,az); // Convert back from center coordinate space to ours. self.x = x + center.x; self.y = y + center.y; self.z = z + center.z; } } 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(window win) { // We'll just draw ourselves as a circle. // Get radius. int pixelRadius = zToScale(radius); // Call toTwoD in parent to get our center point. (int x,y) = toTwoD(); win.circle(x,y,pixelRadius); } // 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(window win) // Draw polyhedron { // We project our vertices into 2D space, and // then call facet drawer. We can do the // projection part in parallel. array of int2d vertices2d = para(v in vertices) get v.toTwoD(); for (facet in facets) facet.draw(win, vertices2d); } morph para move(double dx,dy,dz) // To move self, just need to move center point { center.move(dx,dy,dz); } morph para moveTo(double x,y,z) { center.move(x,y,z); } 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; } } to rotate(double x,y,z) // We can rotate in parallel too. { para (v in vertices) do v.rotateAroundCenter(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 (center == nil || vertices == nil || facets == nil) punt("Polyhedron missing required init data."); self.vertices = vertices; self.facets = facets; } } class int2d // A class that stores a point as transformed to pixel // coordinates. { int x,y; } 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 facet.draw(window 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 vertices.size) { a = vertices[i-1]; b = vertices[i]; win.line(a.x, a.y, b.x, b.y); } // Draw a final line between the first and // last vertex. a = vertices[0]; b = vertices[vertices.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 vertex 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 facets 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.x,v.y) = rotate(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; window 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); }