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