This is PolyhedraView.m in view mode; [Download] [Up]
// // RegularPolyhedraView - a flexible, bouncing polyhedron. // // Module for BackSpace.app // 22 Nov 91 - 8 Dec 91. // Simon Marchant, and Paul Brown (simon@math.berkeley.edu, // pbrown@math.berkeley.edu). // #import "PolyhedraView.h" #import "PolyhedraViewWraps.h" #import <appkit/graphics.h> #import <appkit/Matrix.h> #import <libc.h> #import <dpsclient/wraps.h> #import <math.h> #import <appkit/publicWraps.h> // Number of vertices of the polyhedron static int theNumVertices[NUM_POLYHEDRA] = {4, 8, 6, 20, 12}; // Number of vertices adjacent to a vertex. static int theNumAdjacents[NUM_POLYHEDRA] = {3, 3, 4, 3, 5}; // Number of faces of the polyhedron. static int theNumFaces[NUM_POLYHEDRA] = {4, 6, 8, 12, 20}; // Number of vertices on each face. static int theVerticesPerFace[NUM_POLYHEDRA] = {3, 4, 3, 5, 3}; // Number of non NO_DRAW faces - i.e. faces that we actually bother drawing. static int realFaces[NUM_POLYHEDRA] = {3, 4, 4, 9, 14}; // Numbers describing the 3D co-ordinates of the polyhedra - the initial positions. static D3_PT offsets[NUM_POLYHEDRA][MAX_NUM_VERTICES] = { {{ 0, 0, 1.73205}, // Tetrahedron { 0, 1.63299, -0.57735}, {-1.41421, -0.816497, -0.57735}, { 1.41421, -0.816497, -0.57735}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}}, {{ 0.707107, 0.707107, 0.707107}, // Cube. {-0.707107, 0.707107, 0.707107}, {-0.707107, -0.707107, 0.707107}, { 0.707107, -0.707107, 0.707107}, {-0.707107, -0.707107, -0.707107}, { 0.707107, -0.707107, -0.707107}, { 0.707107, 0.707107, -0.707107}, {-0.707107, 0.707107, -0.707107}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}}, {{ 0, 0, 1.41421}, // Octahedron { 1.41421, 0, 0}, { 0, 1.41421, 0}, { 0, 0, -1.41421}, {-1.41421, 0, 0}, { 0, -1.41421, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}}, {{ 0.525731, 0.381966, 0.850651}, //Dodecahedron. {-0.200811, 0.618034, 0.850651}, {-0.649839, 0, 0.850651}, {-0.200811, -0.618034, 0.850651}, { 0.525731, -0.381966, 0.850651}, { 0.850651, 0.618034, 0.200811}, {-0.32492, 1.0, 0.200811}, {-1.05146, 0, 0.200811}, {-0.32492, -1.0, 0.200811}, { 0.850651, -0.618034, 0.200811}, { 0.32492, 1.0, -0.200811}, {-0.850651, 0.618034, -0.200811}, {-0.850651, -0.618034, -0.200811}, { 0.32492, -1.0, -0.200811}, { 1.05146, 0, -0.200811}, { 0.200811, 0.618034, -0.850651}, {-0.525731, 0.381966, -0.850651}, {-0.525731, -0.381966, -0.850651}, { 0.200811, -0.618034, -0.850651}, { 0.649839, 0, -0.850651}}, {{ 0, 0, 1.0}, // Icosahedron. { 0.894427, 0, 0.447214}, { 0.276393, 0.850651, 0.447214}, {-0.723607, 0.525731, 0.447214}, {-0.723607, -0.525731, 0.447214}, { 0.276393, -0.850651, 0.447214}, { 0.723607, 0.525731, -0.447214}, {-0.276393, 0.850651, -0.447214}, {-0.894427, 0, -0.447214}, {-0.276393, -0.850651, -0.447214}, { 0.723607, -0.525731, -0.447214}, { 0, 0, -1.0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}}}; // List of faces in the polyhedron. static int faces[NUM_POLYHEDRA][MAX_NUM_FACES][MAX_VERTICES_PER_FACE] = { {{0, 1, 2, -1, -1}, // Tetrahedron. {0, 2, 3, -1, -1}, {0, 3, 1, -1, -1}, {1, 3, 2, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}}, {{0, 1, 2, 3, -1}, // Cube. {0, 3, 5, 6, -1}, {0, 6, 7, 1, -1}, {1, 7, 4, 2, -1}, {4, 7, 6, 5, -1}, {2, 4, 5, 3, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}}, {{0, 1, 2, -1, -1}, // Octahedron. {0, 2, 4, -1, -1}, {0, 4, 5, -1, -1}, {0, 5, 1, -1, -1}, {1, 5, 3, -1, -1}, {1, 3, 2, -1, -1}, {3, 5, 4, -1, -1}, {2, 3, 4, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}}, {{0, 1, 2, 3, 4}, // Dodecahedron. {0, 4, 9, 14, 5}, {0, 5, 10, 6, 1}, {1, 6, 11, 7, 2}, {2, 7, 12, 8, 3}, {3, 4, 9, 13, 8}, {5, 14, 19, 15, 10}, {6, 10, 15, 16, 11}, {7, 11, 16, 17, 12}, {8, 12, 17, 18, 13}, {9, 13, 18, 19, 14}, {15, 19, 18, 17, 16}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}, {-1, -1, -1, -1, -1}}, {{0, 2, 1, -1, -1}, // Icosahedron. {0, 3, 2, -1, -1}, {0, 4, 3, -1, -1}, {0, 5, 4, -1, -1}, {0, 1, 5, -1, -1}, {1, 2, 6, -1, -1}, {2, 3, 7, -1, -1}, {3, 4, 8, -1, -1}, {4, 5, 9, -1, -1}, {5, 1, 10, -1, -1}, {6, 2, 7, -1, -1}, {7, 3, 8, -1, -1}, {8, 4, 9, -1, -1}, {9, 5, 10, -1, -1}, {10, 1, 6, -1, -1}, {6, 7, 11, -1, -1}, {7, 8, 11, -1, -1}, {8, 9, 11, -1, -1}, {9, 10, 11, -1, -1}, {10, 6, 11, -1, -1}}}; // Array following contains the vertex adjacency information, so we can // determine which n vertices are adjacent to any given one. typedef struct { float r,g,b; } rgbcolor; static rgbcolor clut[5] = { {1,0,0}, {0,1,.2}, {0,0,1}, {1,.8,0}, {1,.3,0} }; // Two "pseudo-colours". #define TRANSPARENT -1 #define NO_DRAW -2 // Colour that you draw the face with. // TRANSPARENT means just draw the edges. // NO_DRAW means that the face is transparent, and other faces // draw all the edges of the face - so no need to draw it. static int faceColour[NUM_POLYHEDRA][MAX_NUM_FACES] = {{0, TRANSPARENT, TRANSPARENT, NO_DRAW, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT}, {1, NO_DRAW, TRANSPARENT, NO_DRAW, 2, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT}, {3, NO_DRAW, 4, NO_DRAW, 0, NO_DRAW, NO_DRAW, 1, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT}, {2, NO_DRAW, TRANSPARENT, NO_DRAW, TRANSPARENT, TRANSPARENT, TRANSPARENT, 3, TRANSPARENT, NO_DRAW, 4, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT}, {0, TRANSPARENT, 1, NO_DRAW, TRANSPARENT, NO_DRAW, NO_DRAW, NO_DRAW, 2, 3, 4, 0, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, NO_DRAW, 1, NO_DRAW, 2}}; @implementation PolyhedraView // Distance between two points. Inlined for efficiency. inline float distance(NXCoord xcrd, NXCoord ycrd, NXCoord zcrd); inline float distance(NXCoord xcrd, NXCoord ycrd, NXCoord zcrd) { return sqrt(xcrd * xcrd + ycrd * ycrd + zcrd * zcrd); } // Draw a line in the proper perspectice projection from pt1 to pt2 - perspectiveLineFrom:(D3_PT)pt1 to:(D3_PT)pt2 { if (perspectivePt.z == 0) return self; PSmoveto(pt1.x - pt1.z * (pt1.x - perspectivePt.x) / perspectivePt.z, pt1.y - pt1.z * (pt1.y - perspectivePt.y) / perspectivePt.z); PSlineto(pt2.x - pt2.z * (pt2.x - perspectivePt.x) / perspectivePt.z, pt2.y - pt2.z * (pt2.y - perspectivePt.y) / perspectivePt.z); return self; } // Draw the background room - in the appropriate gray. - drawBoxInColour:(float)theGray { return self; //by sam, I don't like the room! #ifdef VANNA D3_PT pt1, pt2; PSsetgray (theGray); pt1.x = 0; pt1.y = 0; pt1.z = 0; pt2.x = 0; pt2.y = 0; pt2.z = backTopRight.z; [self perspectiveLineFrom:pt1 to:pt2]; pt1.z = backTopRight.z; pt1.y = backTopRight.y; [self perspectiveLineFrom:pt2 to:pt1]; pt2.z = 0; pt2.y = backTopRight.y; [self perspectiveLineFrom:pt1 to:pt2]; pt2 = backTopRight; [self perspectiveLineFrom:pt1 to:pt2]; pt1.x = backTopRight.x; pt1.z = 0; [self perspectiveLineFrom:pt2 to:pt1]; pt1.y = 0; pt1.z = backTopRight.z; [self perspectiveLineFrom:pt2 to:pt1]; pt2.y = 0; pt2.z = 0; [self perspectiveLineFrom:pt1 to:pt2]; pt2.z = backTopRight.z; pt2.x = 0; [self perspectiveLineFrom:pt1 to:pt2]; PSstroke(); return self; #endif } // Draw the polyhedron at the current position. - drawPolyhedron { int i, j, k, m, n=0; float faceVerticesZ[MAX_NUM_FACES][MAX_VERTICES_PER_FACE]; float sortedVerticesZ[MAX_NUM_FACES][MAX_VERTICES_PER_FACE]; NXPoint faceVerticesScreen[MAX_NUM_FACES][MAX_VERTICES_PER_FACE]; BOOL drawn[MAX_NUM_FACES]; BOOL intersect; NXPoint *thisFace, *tempFace, *firstVertex, *secondVertex, *thirdVertex, *fourthVertex; int colours[MAX_NUM_FACES]; float r[MAX_NUM_FACES]; float g[MAX_NUM_FACES]; float b[MAX_NUM_FACES]; VERTEX *thisVertex; float firstVertexZ, secondVertexZ, thirdVertexZ, fourthVertexZ; float det, s=0, t=0; // Pre-compute the positions of each of the vertices as they're drawn on the screen. We'll need // them a little later, and we'll keep them around until we erase this polyhedron from the screen. for (i = 0; i < numVertices; i++) { thisVertex = vertices + i; thisVertex->screenPos.x = perspectivePt.x + (thisVertex->pos.x - perspectivePt.x) * perspectivePt.z / (perspectivePt.z + thisVertex->pos.z); thisVertex->screenPos.y = perspectivePt.y + (thisVertex->pos.y - perspectivePt.y) * perspectivePt.z / (perspectivePt.z + thisVertex->pos.z); } // Pick out the faces we have to draw, and grab an ordered list of the z-coordinates of each // vertex in each face, for later on. k = 0; for (i = 0; i < numFaces; i++) if (faceColour[polyhedron][i] != NO_DRAW) { for (j = 0; j < verticesPerFace; j++) { thisVertex = &( vertices[faces[polyhedron][i][j]]); faceVerticesScreen[k][j] = thisVertex -> screenPos; faceVerticesZ[k][j] = thisVertex->pos.z; for (m = 0; (m < j) && (sortedVerticesZ[k][m] > thisVertex->pos.z); m++) ; for (n = j; n > m; n--) sortedVerticesZ[k][n] = sortedVerticesZ[k][n - 1]; sortedVerticesZ[k][m] = thisVertex->pos.z; } colours[k] = faceColour[polyhedron][i]; if (colours[k] != TRANSPARENT) { r[k] = clut[colours[k]].r; g[k] = clut[colours[k]].g; b[k] = clut[colours[k]].b; } drawn[k] = NO; k++; } // Now, run through the list of faces we have to draw, and select the next one to // draw - by making sure that it's not in front of any faces we haven't drawn // yet. for (i = 0; i < numDrawFaces; i++) { for (k = 0; drawn[k]; k++) ; thisFace = (NXPoint *)&(faceVerticesScreen[k]); for (j = k + 1; j < numDrawFaces; j++) if (!drawn[j]) { tempFace = (NXPoint *)&(faceVerticesScreen[j]); intersect = NO; // check for edges intersecting. for (m = 0; (!intersect) && (m < verticesPerFace); m++) for (n = 0; (!intersect) && (n < verticesPerFace); n++) { firstVertex = thisFace + m; secondVertex = (m + 1 == verticesPerFace) ? thisFace : thisFace + m + 1; thirdVertex = tempFace + n; fourthVertex = (n + 1 == verticesPerFace) ? tempFace : tempFace + n + 1; if (((firstVertex->x != thirdVertex->x) || (firstVertex->y != thirdVertex->y)) && ((firstVertex->x != fourthVertex->x) || (firstVertex->y != fourthVertex->y)) && ((secondVertex->x != thirdVertex->x) || (secondVertex->y != thirdVertex->y)) && ((secondVertex->x != fourthVertex->x) || (secondVertex->y != fourthVertex->y))) if ((det = ((firstVertex->x - secondVertex->x) * (fourthVertex->y - thirdVertex->y) - (firstVertex->y - secondVertex->y) * (fourthVertex->x - thirdVertex->x))) != 0) { t = ((fourthVertex->y - thirdVertex->y) * (fourthVertex->x - secondVertex->x) + (thirdVertex->x - fourthVertex->x) * (fourthVertex->y - secondVertex->y)) / det; s = ((secondVertex->y - firstVertex->y) * (fourthVertex->x - secondVertex->x) + (firstVertex->x - secondVertex->x) * (fourthVertex->y - secondVertex->y)) / det; if ((t > 0.0) && (t < 1.0) && (s > 0.0) & (s < 1.0)) intersect = YES; } } m --; n --; // if no edges intersect, order by z-coordinates. if (!intersect) { for (m = 0; (m < verticesPerFace) && (sortedVerticesZ[k][m] == sortedVerticesZ[j][m]); m++) ; if ((m != verticesPerFace) && (sortedVerticesZ[j][m] > sortedVerticesZ[k][m])) { k = j; thisFace = tempFace; } // else // ; } else // if there's a pair of edges intersecting, look at the z-coord at the // intersection pt - the largest z-coord is the one we drawn. { firstVertexZ = faceVerticesZ[k][m]; secondVertexZ = (m + 1 == verticesPerFace) ? faceVerticesZ[k][0] : faceVerticesZ[k][m + 1]; thirdVertexZ = faceVerticesZ[j][n]; fourthVertexZ = (n + 1 == verticesPerFace) ? faceVerticesZ[j][0] : faceVerticesZ[j][n + 1]; if (firstVertexZ * t + secondVertexZ * (1 - t) < thirdVertexZ * s + fourthVertexZ * (1 - s)) { k = j; thisFace = tempFace; } } } // Let the wraps do the drawing, depending on the number of vertices in // the face, and whether or not we should fill the face. if (verticesPerFace == 3) if (colours[k] != TRANSPARENT) colourTriangle(thisFace[0].x, thisFace[0].y, thisFace[1].x, thisFace[1].y, thisFace[2].x, thisFace[2].y, r[k],g[k],b[k]); else outlineTriangle(thisFace[0].x, thisFace[0].y, thisFace[1].x, thisFace[1].y, thisFace[2].x, thisFace[2].y); else if (verticesPerFace == 4) if (colours[k] != TRANSPARENT) colourSquare(thisFace[0].x, thisFace[0].y, thisFace[1].x, thisFace[1].y, thisFace[2].x, thisFace[2].y, thisFace[3].x, thisFace[3].y, r[k],g[k],b[k]); else outlineSquare(thisFace[0].x, thisFace[0].y, thisFace[1].x, thisFace[1].y, thisFace[2].x, thisFace[2].y, thisFace[3].x, thisFace[3].y); else if (verticesPerFace == 5) if (colours[k] != TRANSPARENT) colourPentagon(thisFace[0].x, thisFace[0].y, thisFace[1].x, thisFace[1].y, thisFace[2].x, thisFace[2].y, thisFace[3].x, thisFace[3].y, thisFace[4].x, thisFace[4].y, r[k],g[k],b[k]); else outlinePentagon(thisFace[0].x, thisFace[0].y, thisFace[1].x, thisFace[1].y, thisFace[2].x, thisFace[2].y, thisFace[3].x, thisFace[3].y, thisFace[4].x, thisFace[4].y); drawn[k] = YES; } return self; } // Erase the polyhedron. Quick, and dirty - just erase a large rectangle that // covers the entire polyhedron on the screen - if it erase some of the background, so // what? We'll redraw that in a little while, anyway. This method has the prime virtue of // being fast, fast, fast. - erasePolyhedron { int i; NXRect eraseRect; float maxX, maxY, minX, minY; NXPoint thisPt; maxY = maxX = 0; minX = frame.size.width; minY = frame.size.height; for (i = 0; i < numVertices; i++) { thisPt = vertices[i].screenPos; if (thisPt.x > maxX) maxX = thisPt.x; if (thisPt.y > maxY) maxY = thisPt.y; if (thisPt.x < minX) minX = thisPt.x; if (thisPt.y < minY) minY = thisPt.y; } eraseRect.origin.x = minX; eraseRect.origin.y = minY; eraseRect.size.width = maxX - minX + 1; eraseRect.size.height = maxY - minY + 1; PSsetgray(NX_BLACK); NXRectFill(&eraseRect); return self; } // Do one animation step. - oneStep { int i, j; float length; float dotProduct; D3_PT velForce[MAX_NUM_VERTICES], force[MAX_NUM_VERTICES]; float theForce; D3_PT sumVel; // Cycle the background box colours. if (((backStep ++) % 20) == 0) { switch (backStep / 20) { #ifdef VANNA case 0: [self drawBoxInColour:NX_WHITE]; break; case 1: [self drawBoxInColour:NX_LTGRAY]; break; case 2: [self drawBoxInColour:NX_DKGRAY]; break; case 3: [self drawBoxInColour:NX_BLACK]; break; #endif default: // Compute average velocity of icosahedron // If it's too small, give it a random kick sumVel.x = sumVel.y = sumVel.z = 0; for (i = 0; i < numVertices; i++) { sumVel.x += vertices[i].vel.x; sumVel.y += vertices[i].vel.y; sumVel.z += vertices[i].vel.z; } if (distance(sumVel.x, sumVel.y, sumVel.z) / numVertices < 0.5) //.33 { sumVel.x = randBetween(-INIT_VELOCITY, INIT_VELOCITY); sumVel.y = randBetween(-INIT_VELOCITY, INIT_VELOCITY); sumVel.z = randBetween(-INIT_VELOCITY, INIT_VELOCITY); for (i = 0; i < numVertices; i++) { vertices[i].vel.x += sumVel.x; vertices[i].vel.y += sumVel.y; vertices[i].vel.z += sumVel.z; } } backStep = 0; break; } } // If we're not doing anything about the polyhedron, leave now. if (noAnimation) return self; // Erase it. [self erasePolyhedron]; // Move it, bouncing off walls as necessary for (i = 0; i < numVertices; i++) { vertices[i].pos.x += vertices[i].vel.x; if ((vertices[i].pos.x < 0) || (vertices[i].pos.x > backTopRight.x)) vertices[i].vel.x = -vertices[i].vel.x; vertices[i].pos.y += vertices[i].vel.y; if ((vertices[i].pos.y < 0) || (vertices[i].pos.y > backTopRight.y)) vertices[i].vel.y = -vertices[i].vel.y; vertices[i].pos.z += vertices[i].vel.z; if ((vertices[i].pos.z < 0) || (vertices[i].pos.z > backTopRight.z)) vertices[i].vel.z = -vertices[i].vel.z; } // draw it [self drawPolyhedron]; for (i = 0; i < numVertices; i++) { velForce[i].x = force[i].x = 0; velForce[i].y = force[i].y = 0; velForce[i].z = force[i].z = 0; } // calculate the force on each vertex. // Notice the use of symmetry here to cut down the amount of computation // i.e. the force on vertex j exerted by the spring from vertex i is minus // that on vertex i exerted by vertex j ..... for (i = 0; i < numVertices; i++) { for (j = i + 1; j < numVertices; j++) { // spring forces (Hookes' Law - remember that?) length = distance(vertices[i].pos.x - vertices[j].pos.x, vertices[i].pos.y - vertices[j].pos.y, vertices[i].pos.z - vertices[j].pos.z); theForce = (vertices[i].pos.x - vertices[j].pos.x) / length * (restLengths[i][j] - length) * SPRING_K; force[i].x += theForce; force[j].x -= theForce; theForce = (vertices[i].pos.y - vertices[j].pos.y) / length * (restLengths[i][j] - length) * SPRING_K; force[i].y += theForce; force[j].y -= theForce; theForce = (vertices[i].pos.z - vertices[j].pos.z) / length * (restLengths[i][j] - length) * SPRING_K; force[i].z += theForce; force[j].z -= theForce; // Velocity damping - only for adjacent vertices if (isAdjacent[i][j]) { dotProduct = ((vertices[i].pos.x - vertices[j].pos.x) * (vertices[i].vel.x - vertices[j].vel.x) + (vertices[i].pos.y - vertices[j].pos.y) * (vertices[i].vel.y - vertices[j].vel.y) + (vertices[i].pos.z - vertices[j].pos.z) * (vertices[i].vel.z - vertices[j].vel.z)) / length/length * damping; theForce = dotProduct * (vertices[i].pos.x - vertices[j].pos.x); velForce[i].x -= theForce; velForce[j].x += theForce; theForce = dotProduct * (vertices[i].pos.y - vertices[j].pos.y); velForce[i].y -= theForce; velForce[j].y += theForce; theForce = dotProduct * (vertices[i].pos.z - vertices[j].pos.z); //xxx velForce[i].z -= theForce; velForce[j].z += theForce; } } } // Change the velocities (F = ma !!). Make sure the velocities don't get too big. // (Stability check). for (i = 0; i < numVertices; i++) { vertices[i].vel.x += (velForce[i].x + force[i].x) / vertices[i].mass; if (fabs(vertices[i].vel.x) > MAX_VEL) vertices[i].vel.x = vertices[i].vel.x / fabs(vertices[i].vel.x) * MAX_VEL; vertices[i].vel.y += (velForce[i].y + force[i].y) / vertices[i].mass; if (fabs(vertices[i].vel.y) > MAX_VEL) vertices[i].vel.y = vertices[i].vel.y / fabs(vertices[i].vel.y) * MAX_VEL; vertices[i].vel.z += (velForce[i].z + force[i].z) / vertices[i].mass; if (fabs(vertices[i].vel.z) > MAX_VEL) vertices[i].vel.z = vertices[i].vel.z / fabs(vertices[i].vel.z) * MAX_VEL; } return self; } // Just erase ourself. - drawSelf:(const NXRect *)rects :(int)rectCount { if (!rects || !rectCount) return self; PSsetlinewidth(0); PSsetgray(0); NXRectFill(rects); return self; } // Somebody just changed the size of the box we're sitting in - recompute // stuff, and start the animation again. - frameChanged:(const NXRect *)frameRect { D3_PT initPos, initVel; int i, j; float length; [self useNewFrame:frameRect]; // Compute the room size. backTopRight.x = frameRect->size.width; backTopRight.y = frameRect->size.height; backTopRight.z = frameRect->size.height; // Where the perspective's coming from. perspectivePt.x = backTopRight.x / 2; perspectivePt.y = backTopRight.y / 2; perspectivePt.z = backTopRight.y * DEPTH; // Compute initial velocity. initVel.x = randBetween(-INIT_VELOCITY, INIT_VELOCITY); initVel.y = randBetween(-INIT_VELOCITY, INIT_VELOCITY); initVel.z = randBetween(-INIT_VELOCITY, INIT_VELOCITY); // If the room's too small, we're going to have problems, so don't // stick the polyhedron in. if ((frameRect->size.width < 240) || (frameRect->size.height < 240)) noAnimation = YES; else { noAnimation = NO; // Compute initial position. initPos.x = randBetween(120, frameRect->size.width - 120); initPos.y = randBetween(120, frameRect->size.height - 120); initPos.z = randBetween(120, frameRect->size.height - 120); // Compute the rest lengths of the springs. length = distance(offsets[polyhedron][0].x - offsets[polyhedron][1].x, offsets[polyhedron][0].y - offsets[polyhedron][1].y, offsets[polyhedron][0].z - offsets[polyhedron][1].z); for (i = 0; i < numVertices; i++) { vertices[i].pos.x = initPos.x + offsets[polyhedron][i].x * SPRING_REST_LEN / length; vertices[i].pos.y = initPos.y + offsets[polyhedron][i].y * SPRING_REST_LEN / length; vertices[i].pos.z = initPos.z + offsets[polyhedron][i].z * SPRING_REST_LEN / length; vertices[i].vel = initVel; } for (i = 0; i < numVertices; i++) for (j = 0; j < numVertices; j++) { length = distance(vertices[i].pos.x - vertices[j].pos.x, vertices[i].pos.y - vertices[j].pos.y, vertices[i].pos.z - vertices[j].pos.z); restLengths[i][j] = length; } } // Compute the damping factor damping = DAMPING * realAdjacents / numVertices; // sanity check: if this number is too big, then velocity // damping contributes to instability, rather than curing it... if (damping > 0.3) damping = 0.3; return self; } // If we get either setFrame, or sizeTo messages, we'd better recompute the // box and stuff. - setFrame:(const NXRect *)frameRect { [super setFrame:frameRect]; [self frameChanged: frameRect]; return self; } - sizeTo:(NXCoord)width :(NXCoord)height { NXRect frameRect; [super sizeTo:width:height]; frameRect.origin.x = 0; frameRect.origin.y = 0; frameRect.size.width = width; frameRect.size.height = height; [self frameChanged: &frameRect]; return self; } - initFrame:(const NXRect *)frameRect { [super initFrame: frameRect]; [self useNewFrame:frameRect]; if (frameRect != NULL) [self setFrame:frameRect]; return self; } - useNewFrame:(const NXRect *)frameRect { int i, j, k; D3_PT initVel; BOOL foundVertex; // Decide which Polyhedron. if (selectedIndex == 0) polyhedron = random() % 5; else polyhedron = selectedIndex-1; numVertices = theNumVertices[polyhedron]; numAdjacents = theNumAdjacents[polyhedron]; numFaces = theNumFaces[polyhedron]; numDrawFaces = realFaces[polyhedron]; verticesPerFace = theVerticesPerFace[polyhedron]; // Compute adjacency info. // Notice that for the purposes of velocity damping, adjacent // is the same as "are vertices on the same face." Not "are // vertices on the same edge." for (i = 0; i < numVertices; i++) { for (j = 0; j < numVertices; j++) isAdjacent[i][j] = NO; for (j = 0; j < numFaces; j++) { foundVertex = NO; for (k = 0; k < verticesPerFace; k++) if (faces[polyhedron][j][k] == i) foundVertex = YES; if (foundVertex) for (k = 0; k < verticesPerFace; k++) if (faces[polyhedron][j][k] != i) isAdjacent[i][faces[polyhedron][j][k]] = YES; } } realAdjacents = 0; for (i = 0; i < numVertices; i++) if (isAdjacent[0][i]) realAdjacents ++; backTopRight.x = 0; backTopRight.y = 0; backTopRight.z = 0; perspectivePt.x = 0; perspectivePt.y = 0; perspectivePt.z = 0; noAnimation = YES; initVel.x = randBetween(-INIT_VELOCITY, INIT_VELOCITY); initVel.y = randBetween(-INIT_VELOCITY, INIT_VELOCITY); initVel.z = randBetween(-INIT_VELOCITY, INIT_VELOCITY); // set up the initial position of the icosahedron. for (i = 0; i < numVertices; i++) { vertices[i].mass = MASS; vertices[i].vel = initVel; } backStep = 0; damping = DAMPING; return self; } - setSelectedIndex:sender { int val; val = [sender selectedRow]; if (selectedIndex == val) return self; selectedIndex = val; [self frameChanged: &bounds]; [self display]; return self; } - kickIt:sender { int i; float x,y,z; x = randBetween(-INIT_VELOCITY, INIT_VELOCITY); y = randBetween(-INIT_VELOCITY, INIT_VELOCITY); z = randBetween(-INIT_VELOCITY, INIT_VELOCITY); for (i = 0; i < numVertices; i++) { vertices[i].vel.x += x; vertices[i].vel.y += y; vertices[i].vel.z += z; } return self; } - inspector:sender { char buf[MAXPATHLEN]; if (!inspectorPanel) { sprintf(buf,"%s/Polyhedra.nib",[sender moduleDirectory:"Polyhedra"]); [NXApp loadNibFile:buf owner:self withNames:NO]; } return inspectorPanel; } - (BOOL) useBufferedWindow { return YES; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.