/* -*- c++ -*- FILE: MagicCube4dView.cpp RCS REVISION: $Revision: 1.10 $ 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: MagicCube4dView.cpp : implementation of the CMagicCube4dView class */ // COM requires that this is defined in one and only one place // before d3d.h so here it is: // #define INITGUID #include "Stdafx.h" #include "MagicCube4d.h" #include "MagicCube4dDoc.h" #include "MagicCube4dView.h" //#include "fire.h" #include "MagicCubeObj.h" #include "SQuat.h" static double background_color[3] = { .3, .3, .3 }; #define LOAD_COLOR(into, from, frac, white) { \ into.peRed = BYTE(white * frac * from[0]); \ into.peGreen = BYTE(white * frac * from[1]); \ into.peBlue = BYTE(white * frac * from[2]); \ into.peFlags = PC_NOCOLLAPSE; \ } #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // CMagicCube4dView IMPLEMENT_DYNCREATE(CMagicCube4dView, CView) BEGIN_MESSAGE_MAP(CMagicCube4dView, CView) //{{AFX_MSG_MAP(CMagicCube4dView) ON_WM_LBUTTONDOWN() ON_WM_LBUTTONUP() ON_WM_MOUSEMOVE() ON_WM_RBUTTONUP() ON_COMMAND(ID_EDIT_UNDO, OnEditUndo) ON_COMMAND(ID_SCRAMBLE_1, OnScramble1) ON_COMMAND(ID_SCRAMBLE_2, OnScramble2) ON_COMMAND(ID_SCRAMBLE_3, OnScramble3) ON_COMMAND(ID_SCRAMBLE_4, OnScramble4) ON_COMMAND(ID_SCRAMBLE_5, OnScramble5) ON_COMMAND(ID_SCRAMBLE_6, OnScramble6) ON_COMMAND(ID_SCRAMBLE_7, OnScramble7) ON_COMMAND(ID_SCRAMBLE_8, OnScramble8) ON_COMMAND(ID_SCRAMBLE_FULL, OnScrambleFull) ON_COMMAND(ID_SOLVE, OnSolve) ON_COMMAND(ID_RESET, OnReset) ON_COMMAND(ID_NEW_PUZZLE2, OnNewPuzzle2) ON_COMMAND(ID_NEW_PUZZLE3, OnNewPuzzle3) ON_COMMAND(ID_NEW_PUZZLE4, OnNewPuzzle4) ON_COMMAND(ID_NEW_PUZZLE5, OnNewPuzzle5) ON_COMMAND(ID_FILE_SAVE, OnFileSave) ON_WM_KEYDOWN() ON_WM_KEYUP() ON_WM_KILLFOCUS() ON_COMMAND(ID_RENDERING_SOFTWAREONLY, OnRenderingSoftwareonly) ON_COMMAND(ID_RENDERING_DEFAULT, OnRenderingDefault) //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CMagicCube4dView construction/destruction static void magiccube_error_handler(const char *str, HRESULT, void *win) { HWND hwnd = (HWND) win; MessageBox(hwnd, str, NULL, MB_OK); exit(1); } CMagicCube4dView::CMagicCube4dView () { // TODO: add construction code here m_width = m_height = 0; m_d3d = NULL; m_d3ddev = NULL; m_d3dview = NULL; m_d3drm = NULL; m_scene = NULL; m_objs = NULL; m_camera = NULL; m_lights = NULL; m_dir_light = NULL; m_amb_light = NULL; m_clipper = NULL; m_d3drmdev = NULL; m_d3drmwindev = NULL; m_view = NULL; m_wrapd3d.SetErrorHandler(magiccube_error_handler, m_hWnd); m_wrapd3d.SetFatalErrorHandler(magiccube_error_handler, m_hWnd); m_nslices = 3; // default puzzle is 3x3x3x3 m_puzzle = NULL; m_tmp_logfile = NULL; m_left_mouse_down = FALSE; m_scramble_state = SCRAMBLE_NONE; m_user_twists = 0; m_user_twisting = 0; m_user_twist_mask = 0; // no num key down ZEROVEC2(m_last_point); m_software_render_only = true; } CMagicCube4dView::~CMagicCube4dView () { if (m_puzzle) delete m_puzzle; m_wrapd3d.Destroy(); RELEASE(m_d3drm); } void CMagicCube4dView::Init3D(int w, int h) { m_update_needed = true; if (m_width > 0) { // not the first time m_wrapd3d.DestroyButNotDirectDraw(); RELEASE(m_d3drm); } CDC *cdc = GetDC(); int bpp = cdc->GetDeviceCaps(BITSPIXEL); PALETTEENTRY colors[1000]; int ncolors = 0; // HERE'S HOW TO USE THE WINDOW'S EXISTING PALETTE: // CPalette *tmppal = cdc->GetCurrentPalette(); // int ncolors = tmppal->GetPaletteEntries(0, 1000, colors); // HERE'S A CUSTOM PALETTE: extern const double face_colors[][3]; const int nshades = 12; // shades per color BYTE white = bpp <= 8 ? ((1 << bpp) - 1) : 255; LOAD_COLOR(colors[0], face_colors[0], 0, white); LOAD_COLOR(colors[1], background_color, 1, white); ncolors = 2; for (int c = 0; c < 8; c++) { for (int i = nshades; i > 0; i--) { double frac = (double)i / nshades; LOAD_COLOR(colors[ncolors], face_colors[c], frac, white); ncolors++; } } m_width = w; m_height = h; m_wrapd3d.Create(m_hWnd, FALSE, w, h, bpp, colors, ncolors, m_software_render_only); m_d3d = m_wrapd3d.Direct3D(); m_d3ddev = m_wrapd3d.Direct3DDevice(); HRESULT rval; /* * Create the D3DRM object */ rval = Direct3DRMCreate(&m_d3drm); if (rval != D3DRM_OK) m_wrapd3d.FatalError("Failed to create Direct3DRM.\n%s", rval); /* * Create the master scene frame plus a frame for visible objects. * Note that objects put in the root frame cannot be transformed * due to a D3D bug, so the sub-frame is important. */ rval = m_d3drm->CreateFrame(NULL, &m_scene); if (rval != D3DRM_OK) m_wrapd3d.FatalError("Failed to create the master scene frame.\n%s", rval); // if(m_wrapd3d.CurrentMode().bitsPerPixel > 8) { rval = m_scene->SetSceneBackgroundRGB(D3DVAL (background_color[0]), D3DVAL (background_color[1]), D3DVAL (background_color[2])); if (rval != D3DRM_OK) m_wrapd3d.FatalError("Failed to set background color.\n%s", rval); rval = m_d3drm->CreateFrame(m_scene, &m_objs); if (rval != D3DRM_OK) m_wrapd3d.FatalError("Failed to create the objs frame.\n%s", rval); /* * Setup Don's favorite initial orientation */ rval = m_objs->AddRotation(D3DRMCOMBINE_AFTER, D3DVAL (1), D3DVAL (0), D3DVAL (0), D3DVAL (DTOR(-30))); rval = m_objs->AddRotation(D3DRMCOMBINE_AFTER, D3DVAL (0), D3DVAL (1), D3DVAL (0), D3DVAL (DTOR(42))); /* * Create the camera frame */ rval = m_d3drm->CreateFrame(m_scene, &m_camera); if (rval != D3DRM_OK) m_wrapd3d.FatalError("Failed to create the camera's frame.\n%s", rval); rval = m_camera->SetPosition(m_scene, D3DVAL (0), D3DVAL (0), D3DVAL (-4)); if (rval != D3DRM_OK) m_wrapd3d. FatalError("Failed to position the camera in the frame.\n%s", rval); /* * Create the lights frame, and lights */ rval = m_d3drm->CreateFrame(m_scene, &m_lights); if (rval != D3DRM_OK) m_wrapd3d.FatalError("Failed to create the light's frame.\n%s", rval); rval = m_lights->SetOrientation( m_scene, D3DVAL(-1), D3DVAL(-1), D3DVAL(2), // direction D3DVAL(0), D3DVAL(1), D3DVAL(0)); // up if (rval != D3DRM_OK) m_wrapd3d.FatalError("Failed to orient the lights in the frame.\n%s", rval); rval = m_d3drm->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL, D3DVAL(1), D3DVAL(1), D3DVAL(1), &m_dir_light); rval = m_lights->AddLight(m_dir_light); rval = m_d3drm->CreateLightRGB(D3DRMLIGHT_AMBIENT, D3DVAL (.2), D3DVAL (.2), D3DVAL (.2), &m_amb_light); rval = m_scene->AddLight(m_amb_light); rval = m_d3drm->CreateDeviceFromD3D(m_d3d, m_d3ddev, &m_d3drmdev); // create retained mode device and viewport // if (rval != DD_OK) m_wrapd3d.FatalError("Failed to create device", rval); if (FAILED (m_d3drmdev-> QueryInterface(IID_IDirect3DRMWinDevice, (void **)&m_d3drmwindev))) MessageBox("Failed to get win rmdevice from rmdevice"); rval = m_d3drm->CreateViewport(m_d3drmdev, m_camera, 0, 0, m_d3drmdev->GetWidth(), m_d3drmdev->GetHeight(), &m_view); if (rval != D3DRM_OK) m_wrapd3d.FatalError("Failed to create the viewport.\n%s", rval); // create puzzle object and add to scene // if (m_puzzle) { // this tmp logfile buisness is a dirty hack to // allow recreation of the puzzle's state when // we're simply resizing the window, because we // don't really want to distroy the puzzle anyway m_tmp_logfile = fopen("tmp.log", "w+"); if (m_tmp_logfile) { fprintf(m_tmp_logfile, "%s %d %ld %d\n", MAGIC_NUMBER, MAGICCUBE_FILE_VERSION, m_scramble_state, m_user_twists); m_puzzle->SaveTo(m_tmp_logfile); fseek(m_tmp_logfile, 0, SEEK_SET); fflush(m_tmp_logfile); } } NewPuzzle(m_nslices); } void CMagicCube4dView::NewPuzzle(int ns) { if (ns > MAXLENGTH) { MessageBox("Tried to create too large a puzzle"); return; } if (m_puzzle) { // m_objs->DeleteVisual(m_puzzle->GetVisual()); delete m_puzzle; } FILE *logfp; if (m_tmp_logfile) logfp = m_tmp_logfile; else logfp = fopen("MagicCube4D.log", "r"); if (logfp) { int file_version; char magic_number[20]; // length must be consistent with // scanf format magic_number[19] = '\0'; fscanf(logfp, "%19s %d%ld%d", magic_number, &file_version, &m_scramble_state, &m_user_twists); if (!((file_version == MAGICCUBE_FILE_VERSION) && (strcmp(magic_number, MAGIC_NUMBER) == 0))) { MessageBox("Bad MagicCube4D.log file or old version."); fclose(logfp); logfp = NULL; } } FILE *colorsfp = fopen("MagicCube4D.rgb", "r"); double colors[9][3]; if (colorsfp) { for (int i = 0; i < 9; i++) if (3 != fscanf(colorsfp, "%lf%lf%lf", &colors[i][0], &colors[i][1], &colors[i][2])) { colorsfp = NULL; break; } } m_puzzle = new MagicCubeObject (m_nslices, logfp, colorsfp ? colors : NULL, m_d3drmdev, m_d3drm); if (colorsfp) { SET3(background_color, colors[8]); fclose(colorsfp); } if (logfp) fclose(logfp); if (m_tmp_logfile) unlink("tmp.log"); if (!m_puzzle) m_wrapd3d.FatalError("Failed to create the MagicCubeObject.\n", 0); else if (!m_puzzle->IsValid()) m_wrapd3d. FatalError("Couldn't init MagicCubeObject. Not enough memory?\n", 0); /* This mesh casts shadows just fine */ #if 0 HRESULT res; LPDIRECT3DRMMESHBUILDER teapot_builder = NULL; LPDIRECT3DRMMESH teapot_mesh = NULL; res = m_d3drm->CreateMeshBuilder(&teapot_builder); res = teapot_builder->Load("tpot0.x", NULL, D3DRMLOAD_FROMFILE, NULL, NULL); res = teapot_builder->CreateMesh(&teapot_mesh); m_objs->AddVisual(teapot_mesh); #endif m_objs->AddVisual(m_puzzle->GetVisual()); /* User Visuals, disappear when casting shadows */ #if 0 IDirect3DRMVisual *shadow; res = m_d3drm->CreateShadow(m_puzzle->GetVisual(), m_dir_light, D3DVAL (0), D3DVAL (-1), D3DVAL (0), D3DVAL (0), D3DVAL (1), D3DVAL (0), &shadow); // res = m_shadow_frame->AddVisual(shadow); res = m_objs->AddVisual(shadow); #endif } ///////////////////////////////////////////////////////////////////////////// // CMagicCube4dView drawing void CMagicCube4dView::InitDrawing() { CRect r; GetClientRect(r); if (m_width != r.Width() || m_height != r.Height()) Init3D(r.Width(), r.Height()); m_update_needed = true; Update3d(); } void CMagicCube4dView::OnDraw(CDC *pDC) { CMagicCube4dDoc *pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here /* CPaintDC dc(this); // device context for painting if(m_d3drmwindev) m_d3drmwindev->HandlePaint(dc); */ InitDrawing(); } BOOL CMagicCube4dView::Update3d() { HRESULT rval; if (!(m_update_needed || m_user_twisting || m_left_mouse_down || (m_puzzle && m_puzzle->IsAnimating()))) { return m_puzzle && m_puzzle->IsAnimating(); } m_update_needed = false; // Useful debugging statement toggles color each frame #if 0 static int cnt = 0; rval = m_scene->SetSceneBackgroundRGB(cnt % 2 == 0 ? 0 : D3DVAL (background_color[0]), cnt % 2 == 0 ? 0 : D3DVAL (background_color[1]), cnt % 2 == 0 ? 0 : D3DVAL (background_color[2])); if (rval != D3DRM_OK) m_wrapd3d.FatalError("Failed to set background color.\n%s", rval); cnt++; #endif // Force a D3D redraw and get the resulting image into the // front buffer. // rval = m_view->Clear(); rval = m_view->Render(m_scene); rval = m_d3drmdev->Update(); // Flip or blt if (m_wrapd3d.IsFullScreen()) { m_wrapd3d.Flip(); } else { CRect clientRect; CRect screenRect; // destination rectangle in front buffer // ??? be smarter about what to update // ??? make sure back buffer is updated correctly GetClientRect(&clientRect); m_view->ForceUpdate(0, 0, clientRect.Width(), clientRect.Height()); screenRect = clientRect; ClientToScreen(screenRect); // ??? be smarter about what to update m_wrapd3d.BltBackBuffer(screenRect, clientRect); } // Check to see if a user initiated twist solved a scramble. // If so, trigger a reward and reset m_scramble_state. // Also turns off m_user_twisting when an animation finishes. // // if (m_user_twisting) { if (m_puzzle) { if (!m_puzzle->IsAnimating()) { // finished twisting m_user_twisting = 0; if (m_puzzle->IsSolved()) { switch (m_scramble_state) { case SCRAMBLE_PARTIAL: MessageBeep(MB_ICONASTERISK); break; case SCRAMBLE_FULL: // this should really be a BIG reward MessageBeep(MB_ICONASTERISK); break; } m_scramble_state = SCRAMBLE_NONE; // Note: history should still contain moves } } } else // should never happen m_user_twisting = 0; } return m_puzzle && m_puzzle->IsAnimating(); } ///////////////////////////////////////////////////////////////////////////// // CMagicCube4dView diagnostics #ifdef _DEBUG void CMagicCube4dView::AssertValid() const { CView::AssertValid(); } void CMagicCube4dView::Dump(CDumpContext & dc) const { CView::Dump(dc); } CMagicCube4dDoc *CMagicCube4dView::GetDocument() // non-debug version is inline { ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMagicCube4dDoc))); return (CMagicCube4dDoc *)m_pDocument; } #endif // _DEBUG ///////////////////////////////////////////////////////////////////////////// // CMagicCube4dView message handlers void CMagicCube4dView::OnActivateView(BOOL bActivate, CView * pActivateView, CView * pDeactiveView) { if (m_d3drmwindev) m_d3drmwindev->HandleActivate(bActivate); CView::OnActivateView(bActivate, pActivateView, pDeactiveView); } void CMagicCube4dView::OnLButtonDown(UINT nFlags, CPoint point) { m_left_mouse_down = TRUE; m_left_dragged = FALSE; m_last_point[0] = point.x; m_last_point[1] = point.y; SetCapture(); CView::OnLButtonDown(nFlags, point); } void CMagicCube4dView::DoPick(int x, int y, int dir, int rotate) { int block, tri; int hit = m_puzzle->Pick(m_view, x, y, &block, &tri); if (hit) { int success; if (rotate) success = m_puzzle->RotateFaceToCenter(block); else // twist top slice by default unless num keys // pressed success = m_puzzle->TwistAbout(block, dir, m_user_twist_mask ? m_user_twist_mask : 1); if (success) { m_user_twists++; m_user_twisting = 1; // GetDocument()->SetModifiedFlag(TRUE); } else // can't twist or rotate that MessageBeep(MB_ICONEXCLAMATION); } else // missed MessageBeep(MB_ICONEXCLAMATION); } void CMagicCube4dView::OnLButtonUp(UINT nFlags, CPoint point) { if (!m_left_dragged) // no motion means do a pick DoPick(point.x, point.y, -1, nFlags & MK_CONTROL); ReleaseCapture(); m_left_mouse_down = FALSE; m_left_dragged = FALSE; CView::OnLButtonUp(nFlags, point); } void CMagicCube4dView::OnRButtonUp(UINT nFlags, CPoint point) { DoPick(point.x, point.y, 1, nFlags & MK_CONTROL); CView::OnRButtonUp(nFlags, point); } #define NORMALIZE_VEC2(dst, src) { \ double len = sqrt(NORMSQRD2(src)); \ VDS2(dst, src, len); } #define NORMALIZE_VEC3(dst, src) { \ double len = sqrt(NORMSQRD3(src)); \ VDS3(dst, src, len); } void CMagicCube4dView::OnMouseMove(UINT nFlags, CPoint point) { if (TRUE == m_left_mouse_down) { m_left_dragged = TRUE; double end[2], drag_dir[2]; Vector3 axis; end[0] = point.x; end[1] = point.y; VMV2(drag_dir, m_last_point, end); drag_dir[1] *= -1; // in Windows, Y is down, so invert it XV2(axis, drag_dir); double len = sqrt(NORMSQRD2(axis)); if (len > .0001) { // do nothing if ended where we started VDS2(axis, axis, len); axis[2] = 0; double rads = len / 200.0; Matrix3 mat(SQuat (axis, rads)); D3DRMMATRIX4D d3dmat; SETMAT4from3(d3dmat, mat, 0, 1); m_objs->AddTransform(D3DRMCOMBINE_AFTER, d3dmat); } m_last_point[0] = point.x; m_last_point[1] = point.y; } CView::OnMouseMove(nFlags, point); } void CMagicCube4dView::OnEditUndo() { if( ! m_puzzle->Undo(m_shift_down)) { // FIX: shiftmask not working MessageBeep(MB_ICONASTERISK); return; } if (0 == m_user_twists) m_scramble_state = SCRAMBLE_NONE; // scramble now doesn't count else m_user_twists--; } void CMagicCube4dView::OnScramble1() { Scramble(1); } void CMagicCube4dView::OnScramble2() { Scramble(2); } void CMagicCube4dView::OnScramble3() { Scramble(3); } void CMagicCube4dView::OnScramble4() { Scramble(4); } void CMagicCube4dView::OnScramble5() { Scramble(5); } void CMagicCube4dView::OnScramble6() { Scramble(6); } void CMagicCube4dView::OnScramble7() { Scramble(7); } void CMagicCube4dView::OnScramble8() { Scramble(8); } void CMagicCube4dView::OnScrambleFull() { Scramble(NSCRAMBLECHEN); } void CMagicCube4dView::Scramble(int scrambulations) { if (!m_puzzle) return; m_puzzle->Scramble(scrambulations); if (scrambulations < NSCRAMBLECHEN) m_scramble_state = SCRAMBLE_PARTIAL; // he's practicing else m_scramble_state = SCRAMBLE_FULL; // he's going for it! m_update_needed = true; m_user_twists = 0; // there are no user twists after the last // scramble (i.e. this one) } void CMagicCube4dView::OnSolve() { m_puzzle->Cheat(); m_scramble_state = SCRAMBLE_NONE; m_user_twists = 0; } void CMagicCube4dView::OnReset() { m_puzzle->Reset(); m_scramble_state = SCRAMBLE_NONE; m_user_twists = 0; m_user_twisting = 0; // in case user resets during a twist m_update_needed = true; } void CMagicCube4dView::OnNewPuzzle2() { NewPuzzle(2); } void CMagicCube4dView::OnNewPuzzle3() { NewPuzzle(3); } void CMagicCube4dView::OnNewPuzzle4() { NewPuzzle(4); } void CMagicCube4dView::OnNewPuzzle5() { NewPuzzle(5); } void CMagicCube4dView::OnFileSave() { if (m_puzzle) { FILE *fp; if ((fp = fopen("MagicCube4D.log", "w")) == NULL) MessageBox("Can't save to MagicCube4D.log, sorry."); else { fprintf(fp, "%s %d %ld %d\n", MAGIC_NUMBER, MAGICCUBE_FILE_VERSION, m_scramble_state, m_user_twists); m_puzzle->SaveTo(fp); fclose(fp); } } else MessageBox("No puzzle to save."); } // message handlers for key presses. // currently, all they look for are number keys that // desnigate slice planes to twist. void CMagicCube4dView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { if ('0' < nChar && nChar <= '9') { m_user_twist_mask |= 1 << (nChar - '0' - 1); SetFocus(); } m_shift_down = nChar == 16; CView::OnKeyDown(nChar, nRepCnt, nFlags); } void CMagicCube4dView::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags) { if ('0' < nChar && nChar <= '9') { m_user_twist_mask &= ~(1 << (nChar - '0' - 1)); } m_shift_down = FALSE; CView::OnKeyUp(nChar, nRepCnt, nFlags); } void CMagicCube4dView::OnKillFocus(CWnd * pNewWnd) { CView::OnKillFocus(pNewWnd); m_user_twist_mask = 0; // in case we miss a keyup event } void CMagicCube4dView::OnRenderingSoftwareonly() { m_software_render_only = true; InitDrawing(); } void CMagicCube4dView::OnRenderingDefault() { m_software_render_only = false; InitDrawing(); } // 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: