static game_state *execute_move(const game_state *from, const char *move) { game_state *ret; float angle; struct solid *poly; int pkey[2]; int skey[2], dkey[2]; int i, j, dest; int direction; switch (*move) { case 'L': direction = LEFT; break; case 'R': direction = RIGHT; break; case 'U': direction = UP; break; case 'D': direction = DOWN; break; default: return NULL; } dest = find_move_dest(from, direction, skey, dkey); if (dest < 0) return NULL; ret = dup_game(from); ret->current = dest; /* * So we know what grid square we're aiming for, and we also * know the two key points (as indices in both the source and * destination grid squares) which are invariant between source * and destination. * * Next we must roll the polyhedron on to that square. So we * find the indices of the key points within the polyhedron's * vertex array, then use those in a call to transform_poly, * and align the result on the new grid square. */ { int all_pkey[4]; align_poly(from->solid, &from->grid->squares[from->current], all_pkey); pkey[0] = all_pkey[skey[0]]; pkey[1] = all_pkey[skey[1]]; /* * Now pkey[0] corresponds to skey[0] and dkey[0], and * likewise [1]. */ } /* * Now find the angle through which to rotate the polyhedron. * Do this by finding the two faces that share the two vertices * we've found, and taking the dot product of their normals. */ { int f[2], nf = 0; float dp; for (i = 0; i < from->solid->nfaces; i++) { int match = 0; for (j = 0; j < from->solid->order; j++) if (from->solid->faces[i*from->solid->order + j] == pkey[0] || from->solid->faces[i*from->solid->order + j] == pkey[1]) match++; if (match == 2) { assert(nf < 2); f[nf++] = i; } } assert(nf == 2); dp = 0; for (i = 0; i < 3; i++) dp += (from->solid->normals[f[0]*3+i] * from->solid->normals[f[1]*3+i]); angle = (float)acos(dp); } /* * Now transform the polyhedron. We aren't entirely sure * whether we need to rotate through angle or -angle, and the * simplest way round this is to try both and see which one * aligns successfully! * * Unfortunately, _both_ will align successfully if this is a * cube, which won't tell us anything much. So for that * particular case, I resort to gross hackery: I simply negate * the angle before trying the alignment, depending on the * direction. Which directions work which way is determined by * pure trial and error. I said it was gross :-/ */ { int all_pkey[4]; int success; if (from->solid->order == 4 && direction == UP) angle = -angle; /* HACK */ poly = transform_poly(from->solid, from->grid->squares[from->current].flip, pkey[0], pkey[1], angle); flip_poly(poly, from->grid->squares[ret->current].flip); success = align_poly(poly, &from->grid->squares[ret->current], all_pkey); if (!success) { sfree(poly); angle = -angle; poly = transform_poly(from->solid, from->grid->squares[from->current].flip, pkey[0], pkey[1], angle); flip_poly(poly, from->grid->squares[ret->current].flip); success = align_poly(poly, &from->grid->squares[ret->current], all_pkey); } assert(success); } /* * Now we have our rotated polyhedron, which we expect to be * exactly congruent to the one we started with - but with the * faces permuted. So we map that congruence and thereby figure * out how to permute the faces as a result of the polyhedron * having rolled. */ { int *newcolours = snewn(from->solid->nfaces, int); for (i = 0; i < from->solid->nfaces; i++) newcolours[i] = -1; for (i = 0; i < from->solid->nfaces; i++) { int nmatch = 0; /* * Now go through the transformed polyhedron's faces * and figure out which one's normal is approximately * equal to this one. */ for (j = 0; j < poly->nfaces; j++) { float dist; int k; dist = 0; for (k = 0; k < 3; k++) dist += SQ(poly->normals[j*3+k] - from->solid->normals[i*3+k]); if (APPROXEQ(dist, 0)) { nmatch++; newcolours[i] = ret->facecolours[j]; } } assert(nmatch == 1); } for (i = 0; i < from->solid->nfaces; i++) assert(newcolours[i] != -1); sfree(ret->facecolours); ret->facecolours = newcolours; } ret->movecount++; /* * And finally, swap the colour between the bottom face of the * polyhedron and the face we've just landed on. * * We don't do this if the game is already complete, since we * allow the user to roll the fully blue polyhedron around the * grid as a feeble reward. */ if (!ret->completed) { i = lowest_face(from->solid); j = ret->facecolours[i]; ret->facecolours[i] = GET_SQUARE(ret, ret->current); SET_SQUARE(ret, ret->current, j); /* * Detect game completion. */ j = 0; for (i = 0; i < ret->solid->nfaces; i++) if (ret->facecolours[i]) j++; if (j == ret->solid->nfaces) { ret->completed = ret->movecount; } } sfree(poly); /* * Align the normal polyhedron with its grid square, to get key * points for non-animated display. */ { int pkey[4]; int success; success = align_poly(ret->solid, &ret->grid->squares[ret->current], pkey); assert(success); ret->dpkey[0] = pkey[0]; ret->dpkey[1] = pkey[1]; ret->dgkey[0] = 0; ret->dgkey[1] = 1; } ret->spkey[0] = pkey[0]; ret->spkey[1] = pkey[1]; ret->sgkey[0] = skey[0]; ret->sgkey[1] = skey[1]; ret->previous = from->current; ret->angle = angle; return ret; }
static void game_redraw(drawing *dr, game_drawstate *ds, const game_state *oldstate, const game_state *state, int dir, const game_ui *ui, float animtime, float flashtime) { int i, j; struct bbox bb = find_bbox(&state->params); struct solid *poly; const int *pkey, *gkey; float t[3]; float angle; int square; draw_rect(dr, 0, 0, XSIZE(GRID_SCALE, bb, state->solid), YSIZE(GRID_SCALE, bb, state->solid), COL_BACKGROUND); if (dir < 0) { const game_state *t; /* * This is an Undo. So reverse the order of the states, and * run the roll timer backwards. */ assert(oldstate); t = oldstate; oldstate = state; state = t; animtime = ROLLTIME - animtime; } if (!oldstate) { oldstate = state; angle = 0.0; square = state->current; pkey = state->dpkey; gkey = state->dgkey; } else { angle = state->angle * animtime / ROLLTIME; square = state->previous; pkey = state->spkey; gkey = state->sgkey; } state = oldstate; for (i = 0; i < state->grid->nsquares; i++) { int coords[8]; for (j = 0; j < state->grid->squares[i].npoints; j++) { coords[2*j] = ((int)(state->grid->squares[i].points[2*j] * GRID_SCALE) + ds->ox); coords[2*j+1] = ((int)(state->grid->squares[i].points[2*j+1]*GRID_SCALE) + ds->oy); } draw_polygon(dr, coords, state->grid->squares[i].npoints, GET_SQUARE(state, i) ? COL_BLUE : COL_BACKGROUND, COL_BORDER); } /* * Now compute and draw the polyhedron. */ poly = transform_poly(state->solid, state->grid->squares[square].flip, pkey[0], pkey[1], angle); /* * Compute the translation required to align the two key points * on the polyhedron with the same key points on the current * face. */ for (i = 0; i < 3; i++) { float tc = 0.0; for (j = 0; j < 2; j++) { float grid_coord; if (i < 2) { grid_coord = state->grid->squares[square].points[gkey[j]*2+i]; } else { grid_coord = 0.0; } tc += (grid_coord - poly->vertices[pkey[j]*3+i]); } t[i] = tc / 2; } for (i = 0; i < poly->nvertices; i++) for (j = 0; j < 3; j++) poly->vertices[i*3+j] += t[j]; /* * Now actually draw each face. */ for (i = 0; i < poly->nfaces; i++) { float points[8]; int coords[8]; for (j = 0; j < poly->order; j++) { int f = poly->faces[i*poly->order + j]; points[j*2] = (poly->vertices[f*3+0] - poly->vertices[f*3+2] * poly->shear); points[j*2+1] = (poly->vertices[f*3+1] - poly->vertices[f*3+2] * poly->shear); } for (j = 0; j < poly->order; j++) { coords[j*2] = (int)floor(points[j*2] * GRID_SCALE) + ds->ox; coords[j*2+1] = (int)floor(points[j*2+1] * GRID_SCALE) + ds->oy; } /* * Find out whether these points are in a clockwise or * anticlockwise arrangement. If the latter, discard the * face because it's facing away from the viewer. * * This would involve fiddly winding-number stuff for a * general polygon, but for the simple parallelograms we'll * be seeing here, all we have to do is check whether the * corners turn right or left. So we'll take the vector * from point 0 to point 1, turn it right 90 degrees, * and check the sign of the dot product with that and the * next vector (point 1 to point 2). */ { float v1x = points[2]-points[0]; float v1y = points[3]-points[1]; float v2x = points[4]-points[2]; float v2y = points[5]-points[3]; float dp = v1x * v2y - v1y * v2x; if (dp <= 0) continue; } draw_polygon(dr, coords, poly->order, state->facecolours[i] ? COL_BLUE : COL_BACKGROUND, COL_BORDER); } sfree(poly); draw_update(dr, 0, 0, XSIZE(GRID_SCALE, bb, state->solid), YSIZE(GRID_SCALE, bb, state->solid)); /* * Update the status bar. */ { char statusbuf[256]; if (state->completed) { strcpy(statusbuf, _("COMPLETED!")); strcpy(statusbuf+strlen(statusbuf), " "); } else statusbuf[0] = '\0'; sprintf(statusbuf+strlen(statusbuf), _("Moves: %d"), (state->completed ? state->completed : state->movecount)); status_bar(dr, statusbuf); } }
/* convert a PolyList into a linked list of PolyListNodes, subdivide * non-flat or concave polgons */ static PolyListNode * PolyListToLinkedPoyList(Transform T, Transform Tdual, Transform TxT, const void **tagged_app, PolyListNode **plistp, PolyList *pl, struct obstack *scratch) { PolyListNode *plist = NULL; int pnr; if (!plistp) { plistp = &plist; } PolyListComputeNormals(pl, PL_HASVN|PL_HASPN|PL_HASPFL); for (pnr = 0; pnr < pl->n_polys; pnr++) { PolyListNode *new_pn; Poly *poly; if (pl->p[pnr].flags & POLY_NOPOLY) { /* degenerated, just skip it */ continue; } poly = &pl->p[pnr]; poly->flags |= pl->geomflags; if (T && T != TM_IDENTITY) { poly = transform_poly(T, Tdual, TxT, poly, scratch); } switch (pl->p[pnr].n_vertices) { case 3: /* ok */ new_pn = new_poly_list_node(tagged_app, scratch); new_pn->poly = poly; ListPush(*plistp, new_pn); break; #if !HAVE_LIBGLU case 4: /* supported */ if (pl->p[pnr].flags & (POLY_NONFLAT|POLY_CONCAVE)) { /* split this polygon along a diagonal, if the polygon is * concave: split across the unique concave vertex. */ int concave; if (pl->p[pnr].flags & POLY_CONCAVE) { Point3 nu; /* We need to determine the concave vertex */ PolyNormal(poly, &nu, pl->geomflags & VERT_4D, false, NULL, &concave); } else { concave= 0; } split_quad_poly(concave, poly, plistp, tagged_app, scratch); } else { new_pn = new_poly_list_node(tagged_app, scratch); new_pn->poly = poly; ListPush(*plistp, new_pn); } break; default: if (pl->p[pnr].flags & (POLY_NONFLAT|POLY_CONCAVE)) { static int was_here; if (!was_here ) { GeomError(1, "Non-flat or concave polygons not supported yet.\n"); was_here = 1; } } new_pn = new_poly_list_node(tagged_app, scratch); new_pn->poly = poly; ListPush(*plistp, new_pn); break; #else case 4: /* if we want to be able to render polygons with * self-intersections "correctly", then we always have to use * the GLU tesselater for polygons with more than 4 vertices and * for non-convex quadrilaterals. We can handle non-flat * quadrilaterals ourselves. */ if ((pl->p[pnr].flags & (POLY_NONFLAT|POLY_CONCAVE)) == POLY_NONFLAT) { /* Split this polygon along a diagonal. Leave concave * quadrilaterals to the GLU tesselator; they could have * self-intersections. */ split_quad_poly(0, poly, plistp, tagged_app, scratch); } else if ((pl->p[pnr].flags & POLY_CONCAVE) == 0) { new_pn = new_poly_list_node(tagged_app, scratch); new_pn->poly = poly; ListPush(*plistp, new_pn); } break; /* otherwise fall into the > 4 vertices case and leave * everything to the GLU tesselator. */ default: { /* We use the GLU tesselator here, if available. It is not * necessary to reinvent the wheel; also, the OpenGL MG backend * also uses the tesselator (so we will get comparable shapes * w/o translucency). */ static GLUtesselator *glutess; struct tess_data tessdata[1]; VARARRAY2(dv, GLdouble, poly->n_vertices, 3); Vertex **vp; int i; if (glutess == NULL) { glutess = gluNewTess(); gluTessProperty(glutess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NONZERO); gluTessCallback(glutess, GLU_TESS_BEGIN_DATA, (GLvoid (*)())tess_begin_data); gluTessCallback(glutess, GLU_TESS_VERTEX_DATA, (GLvoid (*)())tess_vertex_data); gluTessCallback(glutess, GLU_TESS_COMBINE_DATA, (GLvoid (*)())tess_combine_data); } tessdata->trickyp = poly; tessdata->polyflags = poly->flags; tessdata->pn = &poly->pn; tessdata->scratch = scratch; tessdata->plistp = plistp; tessdata->tagged_app = tagged_app; /* tell GLU what we think is a good approximation for the normal */ gluTessNormal(glutess, poly->pn.x, poly->pn.y, poly->pn.z); /* rest is done in the callback functions */ gluTessBeginPolygon(glutess, tessdata); gluTessBeginContour(glutess); for (i = 0, vp = poly->v; i < poly->n_vertices; i++, vp++) { HPt3Coord w = (*vp)->pt.w ? (*vp)->pt.w : 1e20; if (w == 1.0) { dv[i][0] = (*vp)->pt.x; dv[i][1] = (*vp)->pt.y; dv[i][2] = (*vp)->pt.z; } else { dv[i][0] = (*vp)->pt.x / w; dv[i][1] = (*vp)->pt.y / w; dv[i][2] = (*vp)->pt.z / w; } gluTessVertex(glutess, dv[i], *vp); } gluTessEndContour(glutess); gluTessEndPolygon(glutess); break; /* out of switch */ } /* default */ #endif } /* switch */ } /* for */ return *plistp; }