/* -*- c++ -*- FILE: MagicCubeObj.cpp RCS REVISION: $Revision: 1.17 $ COPYRIGHT: (c) 1999 -- 2003 Melinda Green, Don Hatch, and Jay Berkenbilt - Superliminal Software LICENSE: Free to use and modify for non-commercial purposes as long as the following conditions are adhered to: 1) Obvious credit for the source of this code and the designs it embodies are clearly made, and 2) Ports and derived versions of 4D Magic Cube programs are not distributed without the express written permission of the authors. DESCRIPTION: Implementation of MagicCubeObject class */ #include "stdafx.h" #include "MagicCubeObj.h" // note that including these files only in this source file // effectivly hides those modules from the rest of the app. // #include "Polymgr.h" #include "Puzzlest.h" #include "History.h" /* * copied from polymgr.cpp * NOTE: Don't change the order here. * the miserable little hack in polymgr_pick_grip * depends on the assumption that cubequads[i] is on axis i%3 * in direction (i<3 ? -1 : 1). * static const int cubequads[6][4] = { {0,4,6,2}, {0,1,5,4}, {0,2,3,1}, {7,5,1,3}, {7,3,2,6}, {7,6,4,5}, }; */ /* * tri_block contains the relative indices of the 12 triangles * in each block which describes each sticker's geometry. * The indices were carefully chosen to meet two important * constraints. First, the triangles needed to be arranged * in the same order as the quads in cubequads above since * that is the ordering used by the polymgr module. In other * words, the first two triangles make up the first quad, the * second pair, the second quad, etc. * The second constraint is due to the "flexible" object * requirement that the first index of each triangle must * be unique to all triangles that have the same normal, * and that all such parallel triangles should be grouped * together for effeciency. */ static const int tri_block[12][3] = { {0, 4, 6}, {0, 6, 2}, {1, 5, 4}, {1, 4, 0}, {2, 3, 1}, {2, 1, 0}, {5, 1, 3}, {5, 3, 7}, {6, 7, 3}, {6, 3, 2}, {7, 6, 4}, {7, 4, 5}, }; extern const double face_colors[][3] = { {0, 0, 1}, // center {.5, 0, 0}, // front {1, .5, 0}, // bottom {1, 0, .5}, // left {.9, .5, 1}, // right {.4, 1, 1}, // top {1, 1, .5}, // back {0, 1, .5}, // invis }; static struct stickerspec null_grip = { 0, 0, 0, 0, 0 }; static real linear_interp(real val) { return val; } static int color_comp(const void *a, const void *b) { int color_a = ((const MagicCubeObject::IdColor *)a)->color; int color_b = ((const MagicCubeObject::IdColor *)b)->color; return color_a - color_b; } MagicCubeObject::MagicCubeObject (int ns, FILE *fp, const double colors[][3], LPDIRECT3DRMDEVICE d, LPDIRECT3DRM lpd3drm): Flexible (d, lpd3drm, 8 * ns * ns * ns * 8, // number of verts 8, // verts per block ns * ns * ns * 8, // number of blocks 12, // triangles per block tri_block, // vertex indices for each block 8, // number of colors colors ? colors : face_colors, // the colors ns * ns * ns, // vertex blocks per color // 1.,1.,1., // light color TRUE) // flat shading { m_preferences.setLength(ns); m_nslices = ns; m_faceshrink = FACESHRINK; // default m_stickershrink = STICKERSHRINK; // default m_nstickers = ns * ns * ns * 8; m_nverts = 8 * m_nstickers; m_nquads = 6 * m_nstickers; m_verts3 = new Vert3[m_nverts]; m_quads = new Quad[m_nquads]; m_stickerids = new int[m_nquads]; m_culled = new int[m_nquads]; m_geom2id = new IdColor[m_nstickers]; m_is_valid = m_verts3 && m_quads && m_stickerids && m_culled && m_geom2id; m_polymgr = new PolygonManager4D(m_preferences); m_puzzle_state = new PuzzleState(m_preferences, m_polymgr); m_history = new History(m_preferences, m_polymgr); // polymgr_set_shrinkers(ns/(ns-1+.3), .3); if (m_is_valid) Reset(); if (fp) { ReadFrom(fp); update_sticker_map(); } } MagicCubeObject::~MagicCubeObject () { delete m_history; delete m_polymgr; delete m_puzzle_state; if (m_verts3) delete[]m_verts3; if (m_quads) delete[]m_quads; if (m_stickerids) delete[]m_stickerids; if (m_culled) delete[]m_culled; if (m_geom2id) delete[]m_geom2id; } void MagicCubeObject::Reset() { m_twist_grip = null_grip; m_twist_dir = 0; m_twist_slicemask = 0; m_twist_inc = 0.0; m_twist_nframes = 0; m_twist_frame_number = 0; m_undoing = 0; m_cheating = 0; m_polymgr->reset(); m_puzzle_state->reset(); m_history->reset(); update_sticker_map(); } int MagicCubeObject::IsSolved() const { return m_puzzle_state->isSolved(); } // Starts a rotate-to-center animation // int MagicCubeObject::RotateFaceToCenter(int sticker_geom_id) { if (m_twist_dir != 0) return 0; // puzzle is currently being twisted // look up the puzzlestate id for the sticker at the given // position within the display list. // int stickerid = m_geom2id[sticker_geom_id].id; int faceid = stickerid / (m_nstickers / 8); if (!m_polymgr->facetocenterToGrip(faceid, &m_twist_grip)) return 0; // can't rotate that to center m_twist_slicemask = ~0; m_twist_dir = 1; m_twist_nframes = m_polymgr->getTwistNFrames(&m_twist_grip); m_twist_inc = 1.0 / m_twist_nframes; m_twist_frame_number = 0; return 1; } // Starts a twist animation // int MagicCubeObject::TwistAbout(int sticker_geom_id, int dir, int mask) { if (m_twist_dir != 0) return 0; // puzzle is currently being twisted // look up the puzzlestate id for the sticker at the given // position within the display list. // int stickerid = m_geom2id[sticker_geom_id].id; m_twist_grip.id_within_cube = stickerid; m_polymgr->fillStickerspecFromIdAndLength(&m_twist_grip, 3); if (3 == m_twist_grip.dim) { // The sticker specified is at the center of a face and therefore // can't be twisted about. // Reset the current twist grip to the null grip. // m_twist_grip = null_grip; return 0; } // It's a legal twist, so set up the animation parameters that the // UpdateVertexData method uses // m_twist_dir = dir; m_twist_slicemask = mask; // (1<getTwistNFrames(&m_twist_grip); m_twist_inc = 1.0 / m_twist_nframes; m_twist_frame_number = 0; return 1; } bool MagicCubeObject::Undo(bool across_scramble_boundary_ok) { if(m_history->atScrambleBoundary() && !across_scramble_boundary_ok) return false; // not allowed // try the undo if(m_history->undo(&m_twist_grip, &m_twist_dir, &m_twist_slicemask)) { m_twist_nframes = m_polymgr->getTwistNFrames(&m_twist_grip); m_twist_inc = 1.0 / m_twist_nframes; m_twist_frame_number = 0; m_undoing = 1; return true; } return false; // nothing to undo } void MagicCubeObject::Scramble(int n_scramblechens) { struct stickerspec grip; int i, previous_face = -1; // NOTE: The UNIX code for this in EventHandler.cpp // selects slicesmask at random for length > 3. // Without that, scrambling is incomplete. if(n_scramblechens == NSCRAMBLECHEN) Reset(); for (i = 0; i < n_scramblechens; ++i) { do { grip.id_within_cube = rand() % m_nstickers; m_polymgr->fillStickerspecFromIdAndLength(&grip, 3); } while (grip.dim != 2 || i > 0 && grip.face == previous_face || i > 0 && grip.face == m_polymgr->oppositeFace(previous_face)); previous_face = grip.face; m_puzzle_state->applyMove(&grip, CCW, 1); m_history->apply(&grip, CCW, 1); } m_history->mark(MARK_SCRAMBLE_BOUNDARY); update_sticker_map(); } void MagicCubeObject::set_vert_colors(D3DVERTEX v[]) { for (int i = 0; i < m_nverts; i++) { // static const Vert3 null_vert = {0,0,0}; int sticker = i / 8; int vert_in_sticker = i % 8; int which = m_geom2id[sticker].id; // const real *pos = m_culled[which] ? null_vert : // m_verts3[8*which+vert_in_sticker]; const real *pos = m_verts3[8 * which + vert_in_sticker]; v[i].x = pos[0]; v[i].y = pos[1]; v[i].z = pos[2]; if (m_culled[which]) v[i].x += 1000000; // send vert into never-never land } } void MagicCubeObject::update_sticker_map() { int s; const int stickers_per_color = m_nslices * m_nslices * m_nslices; // Get the pusslestate's idea of the sticker colors // for (s = 0; s < m_nstickers; s++) { m_geom2id[s].color = m_puzzle_state->idToColor(s); m_geom2id[s].id = s; } // Sort that list by color because that's the // way the stickers are ordered in the display list. // The result is a map from a position in the // display list to the puzzlestate's idea of the id for // for each sticker. // qsort(m_geom2id, m_nstickers, sizeof(IdColor), color_comp); // Below is a bubble sort version which can be used when qsort // is not available. Note that it terminates when the first // seven faces have been sorted because the eighth face must // also be correct by then. #if 0 for(s=0; s<7*stickers_per_color; s++) { int region = s / stickers_per_color; int this_color = m_geom2id[s].color; if(this_color != region) { // swap this sticker with a further one of the right color int i; for(i=m_nstickers-1; i>s; --i) { // find one of right color int further_color = m_geom2id[i].color; if(further_color == region) break; } assert(i > s); IdColor tmp; SWAP(m_geom2id[s], m_geom2id[i], tmp); } } #endif } void MagicCubeObject::Cheat() { m_history->compress(); // remove the rotates and redundancies if (!m_puzzle_state->isSolved()) { Undo(); // to get it started m_cheating = 1; // to keep it going till done } } int MagicCubeObject::UpdateVertexData(D3DVERTEX v[]) { if (m_twist_dir != 0) { // an animation is in progress m_twist_frame_number++; // incriment the frame number if (m_twist_frame_number == m_twist_nframes) { // The last one was the final frame of animation, so // the animation needs to be turned off, // the move needs to be recorded in the history, // the geometry needs to be "squared up", // the sticker colors moved to their new positions, and // the puzzle state updated. if (m_undoing) m_undoing = 0; else if (!m_cheating) m_history->apply(&m_twist_grip, m_twist_dir, m_twist_slicemask); m_puzzle_state->applyMove(&m_twist_grip, m_twist_dir, m_twist_slicemask); update_sticker_map(); m_twist_dir = 0; // this turns animation off m_twist_grip = null_grip; m_twist_slicemask = 0; m_twist_frame_number = 0; if (m_cheating) { if (m_puzzle_state->isSolved()) { m_cheating = 0; m_history->clear(); } else Undo(); // sets dir, sticker, slicemask and start // anim } } } m_polymgr->calc3DFrameInfo( /* INPUTS */ &m_twist_grip, // grip to twist about m_twist_dir, // dir m_twist_slicemask, // slicemask m_twist_frame_number * m_twist_inc, // frac to interp linear_interp, // interp func m_nverts, // number of 4D verts NULL, // 4D verts (NULL means assume untwisted) m_nslices * 4./3., // 4D eye distance from origin 0, // whether to cull inverted cells /* OUTPUTS */ &m_nverts, m_verts3, &m_nquads, m_quads, m_stickerids, m_culled); set_vert_colors(v); ComputeFlatNormals(v, 8, m_nquads / 6, 12, tri_block); return 2 * m_nquads; } int MagicCubeObject::ReadFrom(FILE *fp) { int puz_ok = m_puzzle_state->read(fp); int his_ok = m_history->read(fp); return puz_ok && his_ok; } void MagicCubeObject::SaveTo(FILE *fp) const { m_puzzle_state->dump(fp); m_history->dump(fp); } #if 0 static void handle_button_up(struct event *event, void *arg) { struct stickerspec grip; int x, y, dir, slicesmask; #if 0 if (number_of_reference_stickers_needed) { _get_a_reference_sticker(event, arg); return; } #endif if (event->button == MIDDLEBUTTON) if (event->shift_is_down || event->control_is_down) { dragging = 1; return; } else dragging = 0; x = event->x; y = event->y; if (polymgr_pick_grip(x, y, &untwisted_frame, &grip)) { switch (event->button) { case LEFTBUTTON: if (grip.dim == 3) { fprintf(stderr, "Can't twist that.\n"); return; } /* FIX THIS-- following code is duplicated below */ if (event->shift_is_down) if (event->control_is_down) slicesmask = ~(1 << (length - 1)); /* all but last */ else slicesmask = ~0; /* everything */ else if (event->control_is_down) slicesmask = (1 << 0) | (1 << 1); /* two layers */ else slicesmask = 1; /* one layer */ dir = CCW; break; case RIGHTBUTTON: if (grip.dim == 3) { fprintf(stderr, "Can't twist that.\n"); return; } /* FIX THIS-- following code is duplicated above */ if (event->shift_is_down) if (event->control_is_down) slicesmask = ~(1 << (length - 1)); /* all but last */ else slicesmask = ~0; /* everything */ else if (event->control_is_down) slicesmask = (1 << 0) | (1 << 1); /* two layers */ else slicesmask = 1; /* one layer */ dir = CW; break; case MIDDLEBUTTON: if (!polymgr_facetocenter_to_grip(grip.face, &grip)) { fprintf(stderr, "Can't rotate that to center.\n"); return; } slicesmask = ~0; dir = CCW; break; } show_animation(&grip, dir, slicesmask); puzzlestate_apply_move(&grip, dir, slicesmask); if (preferences.getBoolProperty(M4D_DRAW_NEW_STATE)) machine_draw_frame(&untwisted_frame); history_do(&grip, dir, slicesmask); macro_add_move(&grip, dir, slicesmask); /* doesn't hurt */ } else { #if 0 printf("%d,%d:", x, y); printf("missed!\n"); #endif machine_bell(); } } #endif // Local Variables: // c-basic-offset: 4 // c-comment-only-line-offset: 0 // c-file-offsets: ((defun-block-intro . +) (block-open . 0) (substatement-open . 0) (statement-cont . +) (statement-case-open . +4) (arglist-intro . +) (arglist-close . +) (inline-open . 0)) // indent-tabs-mode: nil // End: