// 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. // // The class hierarchy of the shapes is // shape3d // sphere // polyhedron // pyramid // slab // cube // cylinder import 'math' // For pi include 'plotter' // Plots lines and circles global class shape // 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(plotter plot){} // 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);} } global class sphere extends shape // A sphere - the second-easiest thing in the 3D world. // Just a center point and a radius. { point3d center = (); double radius = 10; // Avoid spheres that look like points. morph to draw(plotter plot) { // We'll just draw ourselves as a circle. // Get radius. int pixelRadius = zToScale(radius); // Call toTwoD in parent to get our center point. point2d flat = toTwoD(center); plot.circle(flat.x,flat.y,pixelRadius); } morph to move(double dx,dy,dz) // Relative move. { center.move(dx,dy,dz); } morph to moveTo(double x,y,z) // Absolute move. { center = (x,y,z); } morph to zoom(double factor) // Zooming is easy, just scale radius. { radius *= factor; } morph to rotate(double dx,dy,dz) // Rotation is no work at all for a sphere, // but we still need to define the method. { } } global class polyhedron extends shape // 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 point2d flattened; // Points in 2-D coordinates array of facet facets; // We'll define a facet later morph to draw(plotter plot) // Draw polyhedron { // We project our vertices into 2D space, and // then call facet drawer. We can do the // projection part in parallel. para (i@flat in flattened) do { point3d v = vertices[i]; point3d offset = (v.x+center.x, v.y+center.y, v.z+center.z); flat = toTwoD(offset); } for (facet in facets) facet.draw(plot, flattened); } 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 = (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; array[vertices.size] of point2d flattened; self.flattened = flattened; } } global 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(plotter plot, array of point2d vertices) // Draw a line around a facet of a polyhedron. { point2d 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]]; plot.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]]; plot.line(a.x, a.y, b.x, b.y); } } global 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); } } global 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,6,5),), ((0,3,7,4),), ); parent.init(corners, faces); } } global class cube extends slab // This is a special case of the slab { to init(double size) { parent.init(size, size, size); } } global 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*math.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); } } /* Now we've defined all the shapes. We still need to * define the basic three dimensional point, and some * mathematically oriented functions to rotate things * and do perspective transformations. */ global class point3d // A point in three dimensional space. { double x,y,z; // Our data, it's not much. to move(int dx,dy,dz) // Move point a given amound { x += dx; y += dy; z += dz; } } class point2d // A two dimensional point. This is what is plotted // after the perspective transform. { double x,y; } 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 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 = 250/z; // Far away things are smaller... } flow toTwoD(point3d pt) into (point2d flat) // Convert 3-D point into a position in window pixel // coordinates using a quick and dirty approximate // perspective transformation. { double scale = zToScale(pt.z); flat = (pt.x*scale + winCenX, pt.y*scale + winCenY); } // Define variables for width/height/center of our viewing window int winCenX = 100, winCenY = 100; global to setWindowSize(double width,height) // We need to know view window size so we can plot things // relative to its center. { winCenX = width/2; winCenY = height/2; } flow rotateAroundCenter(point3d point, point3d center, double ax,ay,az) into point3d dest // Rotate point 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); }