1 /* -*- c++ -*-
2 FILE: MagicCubeObj.cpp
3 RCS REVISION: $Revision: 1.17 $
4 
5 COPYRIGHT: (c) 1999 -- 2003 Melinda Green, Don Hatch, and Jay Berkenbilt - Superliminal Software
6 
7 LICENSE: Free to use and modify for non-commercial purposes as long as the
8     following conditions are adhered to:
9     1) Obvious credit for the source of this code and the designs it embodies
10        are clearly made, and
11     2) Ports and derived versions of 4D Magic Cube programs are not distributed
12        without the express written permission of the authors.
13 
14 DESCRIPTION:
15     Implementation of MagicCubeObject class
16 */
17 
18 #include "stdafx.h"
19 #include "MagicCubeObj.h"
20 
21 // note that including these files only in this source file
22 // effectivly hides those modules from the rest of the app.
23 //
24 #include "Polymgr.h"
25 #include "Puzzlest.h"
26 #include "History.h"
27 
28 /*
29  * copied from polymgr.cpp
30  * NOTE: Don't change the order here.
31  * the miserable little hack in polymgr_pick_grip
32  * depends on the assumption that cubequads[i] is on axis i%3
33  * in direction (i<3 ? -1 : 1).
34  *
35 static const int cubequads[6][4] = {
36     {0,4,6,2},
37     {0,1,5,4},
38     {0,2,3,1},
39     {7,5,1,3},
40     {7,3,2,6},
41     {7,6,4,5},
42 };
43 */
44 
45 /*
46  * tri_block contains the relative indices of the 12 triangles
47  * in each block which describes each sticker's geometry.
48  * The indices were carefully chosen to meet two important
49  * constraints. First, the triangles needed to be arranged
50  * in the same order as the quads in cubequads above since
51  * that is the ordering used by the polymgr module. In other
52  * words, the first two triangles make up the first quad, the
53  * second pair, the second quad, etc.
54  * The second constraint is due to the "flexible" object
55  * requirement that the first index of each triangle must
56  * be unique to all triangles that have the same normal,
57  * and that all such parallel triangles should be grouped
58  * together for effeciency.
59  */
60 static const int tri_block[12][3] = {
61     {0, 4, 6},
62     {0, 6, 2},
63     {1, 5, 4},
64     {1, 4, 0},
65     {2, 3, 1},
66     {2, 1, 0},
67     {5, 1, 3},
68     {5, 3, 7},
69     {6, 7, 3},
70     {6, 3, 2},
71     {7, 6, 4},
72     {7, 4, 5},
73 };
74 
75 extern const double face_colors[][3] = {
76     {0, 0, 1},                  // center
77     {.5, 0, 0},                 // front
78     {1, .5, 0},                 // bottom
79     {1, 0, .5},                 // left
80     {.9, .5, 1},                // right
81     {.4, 1, 1},                 // top
82     {1, 1, .5},                 // back
83     {0, 1, .5},                 // invis
84 };
85 
86 static struct stickerspec null_grip = { 0, 0, 0, 0, 0 };
87 
88 static real
linear_interp(real val)89 linear_interp(real val)
90 {
91     return val;
92 }
93 
94 static int
color_comp(const void * a,const void * b)95 color_comp(const void *a, const void *b)
96 {
97     int         color_a = ((const MagicCubeObject::IdColor *)a)->color;
98     int         color_b = ((const MagicCubeObject::IdColor *)b)->color;
99     return color_a - color_b;
100 }
101 
102 
103 
MagicCubeObject(int ns,FILE * fp,const double colors[][3],LPDIRECT3DRMDEVICE d,LPDIRECT3DRM lpd3drm)104 MagicCubeObject::MagicCubeObject (int ns, FILE *fp, const double colors[][3],
105                                   LPDIRECT3DRMDEVICE d,
106                                   LPDIRECT3DRM lpd3drm):
107     Flexible (d, lpd3drm,
108               8 * ns * ns * ns * 8, // number of verts
109               8,    // verts per block
110               ns * ns * ns * 8, // number of blocks
111               12,   // triangles per block
112               tri_block,    // vertex indices for each block
113               8,    // number of colors
114               colors ? colors : face_colors,    // the colors
115               ns * ns * ns, // vertex blocks per color
116               // 1.,1.,1., // light color
117               TRUE) // flat shading
118 {
119     m_preferences.setLength(ns);
120     m_nslices = ns;
121     m_faceshrink = FACESHRINK;  // default
122     m_stickershrink = STICKERSHRINK;    // default
123     m_nstickers = ns * ns * ns * 8;
124     m_nverts = 8 * m_nstickers;
125     m_nquads = 6 * m_nstickers;
126     m_verts3 = new Vert3[m_nverts];
127     m_quads = new Quad[m_nquads];
128     m_stickerids = new int[m_nquads];
129     m_culled = new int[m_nquads];
130     m_geom2id = new IdColor[m_nstickers];
131     m_is_valid = m_verts3 && m_quads && m_stickerids && m_culled && m_geom2id;
132     m_polymgr = new PolygonManager4D(m_preferences);
133     m_puzzle_state = new PuzzleState(m_preferences, m_polymgr);
134     m_history = new History(m_preferences, m_polymgr);
135 
136     // polymgr_set_shrinkers(ns/(ns-1+.3), .3);
137 
138     if (m_is_valid)
139         Reset();
140 
141     if (fp)
142     {
143         ReadFrom(fp);
144         update_sticker_map();
145     }
146 }
147 
148 
~MagicCubeObject()149 MagicCubeObject::~MagicCubeObject ()
150 {
151     delete m_history;
152     delete m_polymgr;
153     delete m_puzzle_state;
154     if (m_verts3)
155         delete[]m_verts3;
156     if (m_quads)
157         delete[]m_quads;
158     if (m_stickerids)
159         delete[]m_stickerids;
160     if (m_culled)
161         delete[]m_culled;
162     if (m_geom2id)
163         delete[]m_geom2id;
164 }
165 
166 
Reset()167 void MagicCubeObject::Reset()
168 {
169     m_twist_grip = null_grip;
170     m_twist_dir = 0;
171     m_twist_slicemask = 0;
172     m_twist_inc = 0.0;
173     m_twist_nframes = 0;
174     m_twist_frame_number = 0;
175     m_undoing = 0;
176     m_cheating = 0;
177     m_polymgr->reset();
178     m_puzzle_state->reset();
179     m_history->reset();
180     update_sticker_map();
181 }
182 
IsSolved() const183 int MagicCubeObject::IsSolved() const
184 {
185     return m_puzzle_state->isSolved();
186 }
187 
188 // Starts a rotate-to-center animation
189 //
RotateFaceToCenter(int sticker_geom_id)190 int MagicCubeObject::RotateFaceToCenter(int sticker_geom_id)
191 {
192     if (m_twist_dir != 0)
193         return 0;               // puzzle is currently being twisted
194 
195     // look up the puzzlestate id for the sticker at the given
196     // position within the display list.
197     //
198     int         stickerid = m_geom2id[sticker_geom_id].id;
199     int         faceid = stickerid / (m_nstickers / 8);
200 
201     if (!m_polymgr->facetocenterToGrip(faceid, &m_twist_grip))
202         return 0;               // can't rotate that to center
203     m_twist_slicemask = ~0;
204     m_twist_dir = 1;
205     m_twist_nframes = m_polymgr->getTwistNFrames(&m_twist_grip);
206     m_twist_inc = 1.0 / m_twist_nframes;
207     m_twist_frame_number = 0;
208 
209     return 1;
210 }
211 
212 // Starts a twist animation
213 //
TwistAbout(int sticker_geom_id,int dir,int mask)214 int MagicCubeObject::TwistAbout(int sticker_geom_id, int dir, int mask)
215 {
216     if (m_twist_dir != 0)
217         return 0;               // puzzle is currently being twisted
218 
219     // look up the puzzlestate id for the sticker at the given
220     // position within the display list.
221     //
222     int         stickerid = m_geom2id[sticker_geom_id].id;
223 
224     m_twist_grip.id_within_cube = stickerid;
225     m_polymgr->fillStickerspecFromIdAndLength(&m_twist_grip, 3);
226 
227     if (3 == m_twist_grip.dim)
228     {
229         // The sticker specified is at the center of a face and therefore
230         // can't be twisted about.
231         // Reset the current twist grip to the null grip.
232         //
233         m_twist_grip = null_grip;
234         return 0;
235     }
236 
237     // It's a legal twist, so set up the animation parameters that the
238     // UpdateVertexData method uses
239     //
240     m_twist_dir = dir;
241     m_twist_slicemask = mask;   // (1<<depth) - 1;
242     m_twist_nframes = m_polymgr->getTwistNFrames(&m_twist_grip);
243     m_twist_inc = 1.0 / m_twist_nframes;
244     m_twist_frame_number = 0;
245 
246     return 1;
247 }
248 
249 
Undo(bool across_scramble_boundary_ok)250 bool MagicCubeObject::Undo(bool across_scramble_boundary_ok)
251 {
252     if(m_history->atScrambleBoundary() && !across_scramble_boundary_ok)
253         return false; // not allowed
254     // try the undo
255     if(m_history->undo(&m_twist_grip, &m_twist_dir, &m_twist_slicemask))
256     {
257         m_twist_nframes = m_polymgr->getTwistNFrames(&m_twist_grip);
258         m_twist_inc = 1.0 / m_twist_nframes;
259         m_twist_frame_number = 0;
260         m_undoing = 1;
261         return true;
262     }
263     return false; // nothing to undo
264 }
265 
266 
Scramble(int n_scramblechens)267 void MagicCubeObject::Scramble(int n_scramblechens)
268 {
269     struct stickerspec grip;
270     int i, previous_face = -1;
271 
272     // NOTE: The UNIX code for this in EventHandler.cpp
273     // selects slicesmask at random for length > 3.
274     // Without that, scrambling is incomplete.
275 
276     if(n_scramblechens == NSCRAMBLECHEN)
277         Reset();
278 
279     for (i = 0; i < n_scramblechens; ++i)
280     {
281         do
282         {
283             grip.id_within_cube = rand() % m_nstickers;
284             m_polymgr->fillStickerspecFromIdAndLength(&grip, 3);
285         }
286         while (grip.dim != 2 ||
287                i > 0 && grip.face == previous_face ||
288                i > 0 && grip.face == m_polymgr->oppositeFace(previous_face));
289 
290         previous_face = grip.face;
291         m_puzzle_state->applyMove(&grip, CCW, 1);
292         m_history->apply(&grip, CCW, 1);
293     }
294     m_history->mark(MARK_SCRAMBLE_BOUNDARY);
295     update_sticker_map();
296 }
297 
298 
set_vert_colors(D3DVERTEX v[])299 void MagicCubeObject::set_vert_colors(D3DVERTEX v[])
300 {
301     for (int i = 0; i < m_nverts; i++)
302     {
303         // static const Vert3 null_vert = {0,0,0};
304         int         sticker = i / 8;
305         int         vert_in_sticker = i % 8;
306         int         which = m_geom2id[sticker].id;
307         // const real *pos = m_culled[which] ? null_vert :
308         // m_verts3[8*which+vert_in_sticker];
309         const real *pos = m_verts3[8 * which + vert_in_sticker];
310         v[i].x = pos[0];
311         v[i].y = pos[1];
312         v[i].z = pos[2];
313         if (m_culled[which])
314             v[i].x += 1000000;  // send vert into never-never land
315     }
316 }
317 
318 
update_sticker_map()319 void MagicCubeObject::update_sticker_map()
320 {
321     int         s;
322     const int   stickers_per_color = m_nslices * m_nslices * m_nslices;
323 
324     // Get the pusslestate's idea of the sticker colors
325     //
326     for (s = 0; s < m_nstickers; s++)
327     {
328         m_geom2id[s].color = m_puzzle_state->idToColor(s);
329         m_geom2id[s].id = s;
330     }
331 
332     // Sort that list by color because that's the
333     // way the stickers are ordered in the display list.
334     // The result is a map from a position in the
335     // display list to the puzzlestate's idea of the id for
336     // for each sticker.
337     //
338     qsort(m_geom2id, m_nstickers, sizeof(IdColor), color_comp);
339 
340     // Below is a bubble sort version which can be used when qsort
341     // is not available. Note that it terminates when the first
342     // seven faces have been sorted because the eighth face must
343     // also be correct by then.
344 #if 0
345     for(s=0; s<7*stickers_per_color; s++) {
346         int region = s / stickers_per_color;
347         int this_color = m_geom2id[s].color;
348         if(this_color != region) {
349             // swap this sticker with a further one of the right color
350             int i;
351             for(i=m_nstickers-1; i>s; --i) { // find one of right color
352                 int further_color = m_geom2id[i].color;
353                 if(further_color == region)
354                     break;
355             }
356             assert(i > s);
357             IdColor tmp;
358             SWAP(m_geom2id[s], m_geom2id[i], tmp);
359         }
360     }
361 #endif
362 }
363 
364 
Cheat()365 void MagicCubeObject::Cheat()
366 {
367     m_history->compress();         // remove the rotates and redundancies
368     if (!m_puzzle_state->isSolved())
369     {
370         Undo();                 // to get it started
371         m_cheating = 1;         // to keep it going till done
372     }
373 }
374 
375 
UpdateVertexData(D3DVERTEX v[])376 int MagicCubeObject::UpdateVertexData(D3DVERTEX v[])
377 {
378     if (m_twist_dir != 0)
379     {                           // an animation is in progress
380         m_twist_frame_number++; // incriment the frame number
381         if (m_twist_frame_number == m_twist_nframes)
382         {
383             // The last one was the final frame of animation, so
384             // the animation needs to be turned off,
385             // the move needs to be recorded in the history,
386             // the geometry needs to be "squared up",
387             // the sticker colors moved to their new positions, and
388             // the puzzle state updated.
389             if (m_undoing)
390                 m_undoing = 0;
391             else if (!m_cheating)
392                 m_history->apply(&m_twist_grip, m_twist_dir, m_twist_slicemask);
393 
394             m_puzzle_state->applyMove(&m_twist_grip, m_twist_dir, m_twist_slicemask);
395             update_sticker_map();
396 
397             m_twist_dir = 0;    // this turns animation off
398             m_twist_grip = null_grip;
399             m_twist_slicemask = 0;
400             m_twist_frame_number = 0;
401 
402             if (m_cheating)
403             {
404                 if (m_puzzle_state->isSolved())
405                 {
406                     m_cheating = 0;
407                     m_history->clear();
408                 }
409                 else
410                     Undo();     // sets dir, sticker, slicemask and start
411                                 // anim
412             }
413         }
414     }
415     m_polymgr->calc3DFrameInfo(
416         /* INPUTS */
417         &m_twist_grip, // grip to twist about
418         m_twist_dir, // dir
419         m_twist_slicemask, // slicemask
420         m_twist_frame_number * m_twist_inc, // frac to interp
421         linear_interp, // interp func
422         m_nverts, // number of 4D verts
423         NULL, // 4D verts (NULL means assume untwisted)
424         m_nslices * 4./3., // 4D eye distance from origin
425         0, // whether to cull inverted cells
426         /* OUTPUTS */
427         &m_nverts, m_verts3,
428         &m_nquads, m_quads,
429         m_stickerids, m_culled);
430 
431     set_vert_colors(v);
432     ComputeFlatNormals(v, 8, m_nquads / 6, 12, tri_block);
433     return 2 * m_nquads;
434 }
435 
436 
ReadFrom(FILE * fp)437 int MagicCubeObject::ReadFrom(FILE *fp)
438 {
439     int puz_ok = m_puzzle_state->read(fp);
440     int his_ok = m_history->read(fp);
441     return puz_ok && his_ok;
442 }
443 
444 
SaveTo(FILE * fp) const445 void MagicCubeObject::SaveTo(FILE *fp) const
446 {
447     m_puzzle_state->dump(fp);
448     m_history->dump(fp);
449 }
450 
451 #if 0
452 
453 static void
454 handle_button_up(struct event *event, void *arg)
455 {
456     struct stickerspec grip;
457     int         x, y, dir, slicesmask;
458 
459 #if 0
460     if (number_of_reference_stickers_needed) {
461         _get_a_reference_sticker(event, arg);
462         return;
463     }
464 #endif
465 
466     if (event->button == MIDDLEBUTTON)
467         if (event->shift_is_down || event->control_is_down)
468         {
469             dragging = 1;
470             return;
471         }
472         else
473             dragging = 0;
474 
475     x = event->x;
476     y = event->y;
477     if (polymgr_pick_grip(x, y, &untwisted_frame, &grip))
478     {
479         switch (event->button)
480         {
481         case LEFTBUTTON:
482             if (grip.dim == 3)
483             {
484                 fprintf(stderr, "Can't twist that.\n");
485                 return;
486             }
487 
488             /* FIX THIS-- following code is duplicated below */
489             if (event->shift_is_down)
490                 if (event->control_is_down)
491                     slicesmask = ~(1 << (length - 1));  /* all but last */
492                 else
493                     slicesmask = ~0;    /* everything */
494             else if (event->control_is_down)
495                 slicesmask = (1 << 0) | (1 << 1);   /* two layers */
496             else
497                 slicesmask = 1; /* one layer */
498 
499             dir = CCW;
500             break;
501         case RIGHTBUTTON:
502             if (grip.dim == 3)
503             {
504                 fprintf(stderr, "Can't twist that.\n");
505                 return;
506             }
507 
508             /* FIX THIS-- following code is duplicated above */
509             if (event->shift_is_down)
510                 if (event->control_is_down)
511                     slicesmask = ~(1 << (length - 1));  /* all but last */
512                 else
513                     slicesmask = ~0;    /* everything */
514             else if (event->control_is_down)
515                 slicesmask = (1 << 0) | (1 << 1);   /* two layers */
516             else
517                 slicesmask = 1; /* one layer */
518 
519             dir = CW;
520             break;
521         case MIDDLEBUTTON:
522             if (!polymgr_facetocenter_to_grip(grip.face, &grip))
523             {
524                 fprintf(stderr, "Can't rotate that to center.\n");
525                 return;
526             }
527             slicesmask = ~0;
528             dir = CCW;
529             break;
530         }
531         show_animation(&grip, dir, slicesmask);
532         puzzlestate_apply_move(&grip, dir, slicesmask);
533         if (preferences.getBoolProperty(M4D_DRAW_NEW_STATE))
534             machine_draw_frame(&untwisted_frame);
535         history_do(&grip, dir, slicesmask);
536         macro_add_move(&grip, dir, slicesmask); /* doesn't hurt */
537     }
538     else
539     {
540 #if 0
541         printf("%d,%d:", x, y);
542         printf("missed!\n");
543 #endif
544         machine_bell();
545     }
546 }
547 
548 #endif
549 
550 // Local Variables:
551 // c-basic-offset: 4
552 // c-comment-only-line-offset: 0
553 // 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))
554 // indent-tabs-mode: nil
555 // End:
556