#ifdef WIN32 #include "Base.h" #include "Platform.h" #include "FileSystem.h" #include "Game.h" #include "Form.h" #include "Vector2.h" #include "ScriptController.h" #include #include #ifdef USE_XINPUT #include #endif using gameplay::print; // Window defaults #define DEFAULT_RESOLUTION_X 1024 #define DEFAULT_RESOLUTION_Y 768 #define DEFAULT_COLOR_BUFFER_SIZE 32 #define DEFAULT_DEPTH_BUFFER_SIZE 24 #define DEFAULT_STENCIL_BUFFER_SIZE 8 static double __timeTicksPerMillis; static double __timeStart; static double __timeAbsolute; static bool __vsync = WINDOW_VSYNC; static float __roll; static float __pitch; static HINSTANCE __hinstance = 0; static HWND __attachToWindow = 0; static HWND __hwnd = 0; static HDC __hdc = 0; static HGLRC __hrc = 0; static bool __mouseCaptured = false; static POINT __mouseCapturePoint = { 0, 0 }; static bool __cursorVisible = true; static unsigned int __gamepadsConnected = 0; #ifdef USE_XINPUT struct XInputGamepad { static const unsigned int BUTTON_COUNT = 14; static const unsigned int JOYSTICK_COUNT = 2; static const unsigned int TRIGGER_COUNT = 2; std::string id; int handle; bool connected; XINPUT_STATE state; }; static XInputGamepad* __xinputGamepads = NULL; static DWORD getXInputGamepadButtonMask(unsigned int buttonHandle) { switch(buttonHandle) { case 0: return XINPUT_GAMEPAD_DPAD_UP; case 1: return XINPUT_GAMEPAD_DPAD_DOWN; case 2: return XINPUT_GAMEPAD_DPAD_LEFT; case 3: return XINPUT_GAMEPAD_DPAD_RIGHT; case 4: return XINPUT_GAMEPAD_START; case 5: return XINPUT_GAMEPAD_BACK; case 6: return XINPUT_GAMEPAD_LEFT_THUMB; case 7: return XINPUT_GAMEPAD_RIGHT_THUMB; case 8: return XINPUT_GAMEPAD_LEFT_SHOULDER; case 9: return XINPUT_GAMEPAD_RIGHT_SHOULDER; case 10: return XINPUT_GAMEPAD_A; case 11: return XINPUT_GAMEPAD_B; case 12: return XINPUT_GAMEPAD_X; case 13: return XINPUT_GAMEPAD_Y; default: return 0; } } static bool getXInputState(unsigned long gamepadHandle) { GP_ASSERT(0 <= gamepadHandle && gamepadHandle < XUSER_MAX_COUNT); if (XInputGetState((DWORD)gamepadHandle, &__xinputGamepads[gamepadHandle].state) == ERROR_SUCCESS) return (__xinputGamepads[gamepadHandle].connected = true); else return (__xinputGamepads[gamepadHandle].connected = false); } static bool getXInputButtonState(unsigned long gamepadHandle, unsigned long buttonHandle) { GP_ASSERT(0 <= gamepadHandle && gamepadHandle < XUSER_MAX_COUNT); GP_ASSERT(0 <= buttonHandle && buttonHandle < 14); WORD buttonMask = getXInputGamepadButtonMask(buttonHandle); // Conversion to button mask. if ((__xinputGamepads[gamepadHandle].state.Gamepad.wButtons & buttonMask) == buttonMask) return true; else return false; } static float normalizeXInputJoystickAxis(int axisValue, int deadZone) { int absAxisValue = abs(axisValue); if (absAxisValue < deadZone) { return 0.0f; } else { int value = axisValue; if (value < 0) value = -1; else if (value > 0) value = 1; else value = 0; return value * (absAxisValue - deadZone) / (float)(32768 - deadZone); } } static float getXInputJoystickAxisX(unsigned long gamepadHandle, unsigned long joystickHandle) { GP_ASSERT(0 <= gamepadHandle && gamepadHandle < XUSER_MAX_COUNT); GP_ASSERT(0 <= joystickHandle && joystickHandle < 2); switch(joystickHandle) { case 0: return normalizeXInputJoystickAxis(__xinputGamepads[gamepadHandle].state.Gamepad.sThumbLX, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE); case 1: return normalizeXInputJoystickAxis(__xinputGamepads[gamepadHandle].state.Gamepad.sThumbRX, XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE); default: return 0.0f; } } static float getXInputJoystickAxisY(unsigned long gamepadHandle, unsigned long joystickHandle) { GP_ASSERT(0 <= gamepadHandle && gamepadHandle < XUSER_MAX_COUNT); GP_ASSERT(0 <= joystickHandle && joystickHandle < 2); switch(joystickHandle) { case 0: return normalizeXInputJoystickAxis(__xinputGamepads[gamepadHandle].state.Gamepad.sThumbLY, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE); case 1: return normalizeXInputJoystickAxis(__xinputGamepads[gamepadHandle].state.Gamepad.sThumbRY, XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE); default: return 0.0f; } } static void getXInputJoystickAxisValues(unsigned long gamepadHandle, unsigned long joystickHandle, gameplay::Vector2* outValue) { GP_ASSERT(0 <= gamepadHandle && gamepadHandle < XUSER_MAX_COUNT); GP_ASSERT(0 <= joystickHandle && joystickHandle < 2); switch(joystickHandle) { case 0: outValue->x = normalizeXInputJoystickAxis(__xinputGamepads[gamepadHandle].state.Gamepad.sThumbLX, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE); outValue->y = normalizeXInputJoystickAxis(__xinputGamepads[gamepadHandle].state.Gamepad.sThumbLY, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE); break; case 1: outValue->x = normalizeXInputJoystickAxis(__xinputGamepads[gamepadHandle].state.Gamepad.sThumbRX, XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE); outValue->y = normalizeXInputJoystickAxis(__xinputGamepads[gamepadHandle].state.Gamepad.sThumbRY, XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE); break; } } #endif static gameplay::Keyboard::Key getKey(WPARAM win32KeyCode, bool shiftDown) { switch (win32KeyCode) { case VK_PAUSE: return gameplay::Keyboard::KEY_PAUSE; case VK_SCROLL: return gameplay::Keyboard::KEY_SCROLL_LOCK; case VK_PRINT: return gameplay::Keyboard::KEY_PRINT; case VK_ESCAPE: return gameplay::Keyboard::KEY_ESCAPE; case VK_BACK: return gameplay::Keyboard::KEY_BACKSPACE; case VK_TAB: return shiftDown ? gameplay::Keyboard::KEY_BACK_TAB : gameplay::Keyboard::KEY_TAB; case VK_RETURN: return gameplay::Keyboard::KEY_RETURN; case VK_CAPITAL: return gameplay::Keyboard::KEY_CAPS_LOCK; case VK_SHIFT: return gameplay::Keyboard::KEY_SHIFT; case VK_CONTROL: return gameplay::Keyboard::KEY_CTRL; case VK_MENU: return gameplay::Keyboard::KEY_ALT; case VK_APPS: return gameplay::Keyboard::KEY_MENU; case VK_LSHIFT: return gameplay::Keyboard::KEY_SHIFT; case VK_RSHIFT: return gameplay::Keyboard::KEY_SHIFT; case VK_LCONTROL: return gameplay::Keyboard::KEY_CTRL; case VK_RCONTROL: return gameplay::Keyboard::KEY_CTRL; case VK_LMENU: return gameplay::Keyboard::KEY_ALT; case VK_RMENU: return gameplay::Keyboard::KEY_ALT; case VK_LWIN: case VK_RWIN: return gameplay::Keyboard::KEY_HYPER; case VK_BROWSER_SEARCH: return gameplay::Keyboard::KEY_SEARCH; case VK_INSERT: return gameplay::Keyboard::KEY_INSERT; case VK_HOME: return gameplay::Keyboard::KEY_HOME; case VK_PRIOR: return gameplay::Keyboard::KEY_PG_UP; case VK_DELETE: return gameplay::Keyboard::KEY_DELETE; case VK_END: return gameplay::Keyboard::KEY_END; case VK_NEXT: return gameplay::Keyboard::KEY_PG_DOWN; case VK_LEFT: return gameplay::Keyboard::KEY_LEFT_ARROW; case VK_RIGHT: return gameplay::Keyboard::KEY_RIGHT_ARROW; case VK_UP: return gameplay::Keyboard::KEY_UP_ARROW; case VK_DOWN: return gameplay::Keyboard::KEY_DOWN_ARROW; case VK_NUMLOCK: return gameplay::Keyboard::KEY_NUM_LOCK; case VK_ADD: return gameplay::Keyboard::KEY_KP_PLUS; case VK_SUBTRACT: return gameplay::Keyboard::KEY_KP_MINUS; case VK_MULTIPLY: return gameplay::Keyboard::KEY_KP_MULTIPLY; case VK_DIVIDE: return gameplay::Keyboard::KEY_KP_DIVIDE; case VK_NUMPAD7: return gameplay::Keyboard::KEY_KP_HOME; case VK_NUMPAD8: return gameplay::Keyboard::KEY_KP_UP; case VK_NUMPAD9: return gameplay::Keyboard::KEY_KP_PG_UP; case VK_NUMPAD4: return gameplay::Keyboard::KEY_KP_LEFT; case VK_NUMPAD5: return gameplay::Keyboard::KEY_KP_FIVE; case VK_NUMPAD6: return gameplay::Keyboard::KEY_KP_RIGHT; case VK_NUMPAD1: return gameplay::Keyboard::KEY_KP_END; case VK_NUMPAD2: return gameplay::Keyboard::KEY_KP_DOWN; case VK_NUMPAD3: return gameplay::Keyboard::KEY_KP_PG_DOWN; case VK_NUMPAD0: return gameplay::Keyboard::KEY_KP_INSERT; case VK_DECIMAL: return gameplay::Keyboard::KEY_KP_DELETE; case VK_F1: return gameplay::Keyboard::KEY_F1; case VK_F2: return gameplay::Keyboard::KEY_F2; case VK_F3: return gameplay::Keyboard::KEY_F3; case VK_F4: return gameplay::Keyboard::KEY_F4; case VK_F5: return gameplay::Keyboard::KEY_F5; case VK_F6: return gameplay::Keyboard::KEY_F6; case VK_F7: return gameplay::Keyboard::KEY_F7; case VK_F8: return gameplay::Keyboard::KEY_F8; case VK_F9: return gameplay::Keyboard::KEY_F9; case VK_F10: return gameplay::Keyboard::KEY_F10; case VK_F11: return gameplay::Keyboard::KEY_F11; case VK_F12: return gameplay::Keyboard::KEY_F12; case VK_SPACE: return gameplay::Keyboard::KEY_SPACE; case 0x30: return shiftDown ? gameplay::Keyboard::KEY_RIGHT_PARENTHESIS : gameplay::Keyboard::KEY_ZERO; case 0x31: return shiftDown ? gameplay::Keyboard::KEY_EXCLAM : gameplay::Keyboard::KEY_ONE; case 0x32: return shiftDown ? gameplay::Keyboard::KEY_AT : gameplay::Keyboard::KEY_TWO; case 0x33: return shiftDown ? gameplay::Keyboard::KEY_NUMBER : gameplay::Keyboard::KEY_THREE; case 0x34: return shiftDown ? gameplay::Keyboard::KEY_DOLLAR : gameplay::Keyboard::KEY_FOUR; case 0x35: return shiftDown ? gameplay::Keyboard::KEY_PERCENT : gameplay::Keyboard::KEY_FIVE; case 0x36: return shiftDown ? gameplay::Keyboard::KEY_CIRCUMFLEX : gameplay::Keyboard::KEY_SIX; case 0x37: return shiftDown ? gameplay::Keyboard::KEY_AMPERSAND : gameplay::Keyboard::KEY_SEVEN; case 0x38: return shiftDown ? gameplay::Keyboard::KEY_ASTERISK : gameplay::Keyboard::KEY_EIGHT; case 0x39: return shiftDown ? gameplay::Keyboard::KEY_LEFT_PARENTHESIS : gameplay::Keyboard::KEY_NINE; case VK_OEM_PLUS: return shiftDown ? gameplay::Keyboard::KEY_EQUAL : gameplay::Keyboard::KEY_PLUS; case VK_OEM_COMMA: return shiftDown ? gameplay::Keyboard::KEY_LESS_THAN : gameplay::Keyboard::KEY_COMMA; case VK_OEM_MINUS: return shiftDown ? gameplay::Keyboard::KEY_UNDERSCORE : gameplay::Keyboard::KEY_MINUS; case VK_OEM_PERIOD: return shiftDown ? gameplay::Keyboard::KEY_GREATER_THAN : gameplay::Keyboard::KEY_PERIOD; case VK_OEM_1: return shiftDown ? gameplay::Keyboard::KEY_COLON : gameplay::Keyboard::KEY_SEMICOLON; case VK_OEM_2: return shiftDown ? gameplay::Keyboard::KEY_QUESTION : gameplay::Keyboard::KEY_SLASH; case VK_OEM_3: return shiftDown ? gameplay::Keyboard::KEY_TILDE : gameplay::Keyboard::KEY_GRAVE; case VK_OEM_4: return shiftDown ? gameplay::Keyboard::KEY_LEFT_BRACE : gameplay::Keyboard::KEY_LEFT_BRACKET; case VK_OEM_5: return shiftDown ? gameplay::Keyboard::KEY_BAR : gameplay::Keyboard::KEY_BACK_SLASH; case VK_OEM_6: return shiftDown ? gameplay::Keyboard::KEY_RIGHT_BRACE : gameplay::Keyboard::KEY_RIGHT_BRACKET; case VK_OEM_7: return shiftDown ? gameplay::Keyboard::KEY_QUOTE : gameplay::Keyboard::KEY_APOSTROPHE; case 0x41: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_A : gameplay::Keyboard::KEY_A; case 0x42: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_B : gameplay::Keyboard::KEY_B; case 0x43: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_C : gameplay::Keyboard::KEY_C; case 0x44: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_D : gameplay::Keyboard::KEY_D; case 0x45: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_E : gameplay::Keyboard::KEY_E; case 0x46: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_F : gameplay::Keyboard::KEY_F; case 0x47: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_G : gameplay::Keyboard::KEY_G; case 0x48: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_H : gameplay::Keyboard::KEY_H; case 0x49: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_I : gameplay::Keyboard::KEY_I; case 0x4A: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_J : gameplay::Keyboard::KEY_J; case 0x4B: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_K : gameplay::Keyboard::KEY_K; case 0x4C: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_L : gameplay::Keyboard::KEY_L; case 0x4D: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_M : gameplay::Keyboard::KEY_M; case 0x4E: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_N : gameplay::Keyboard::KEY_N; case 0x4F: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_O : gameplay::Keyboard::KEY_O; case 0x50: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_P : gameplay::Keyboard::KEY_P; case 0x51: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_Q : gameplay::Keyboard::KEY_Q; case 0x52: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_R : gameplay::Keyboard::KEY_R; case 0x53: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_S : gameplay::Keyboard::KEY_S; case 0x54: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_T : gameplay::Keyboard::KEY_T; case 0x55: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_U : gameplay::Keyboard::KEY_U; case 0x56: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_V : gameplay::Keyboard::KEY_V; case 0x57: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_W : gameplay::Keyboard::KEY_W; case 0x58: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_X : gameplay::Keyboard::KEY_X; case 0x59: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_Y : gameplay::Keyboard::KEY_Y; case 0x5A: return shiftDown ? gameplay::Keyboard::KEY_CAPITAL_Z : gameplay::Keyboard::KEY_Z; default: return gameplay::Keyboard::KEY_NONE; } } void UpdateCapture(LPARAM lParam) { if ((lParam & MK_LBUTTON) || (lParam & MK_MBUTTON) || (lParam & MK_RBUTTON)) SetCapture(__hwnd); else ReleaseCapture(); } void WarpMouse(int clientX, int clientY) { POINT p = { clientX, clientY }; ClientToScreen(__hwnd, &p); SetCursorPos(p.x, p.y); } LRESULT CALLBACK __WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { static gameplay::Game* game = gameplay::Game::getInstance(); if (!game->isInitialized() || hwnd != __hwnd) { // Ignore messages that are not for our game window. return DefWindowProc(hwnd, msg, wParam, lParam); } static int lx, ly; static bool shiftDown = false; static bool capsOn = false; switch (msg) { case WM_CLOSE: DestroyWindow(__hwnd); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; case WM_LBUTTONDOWN: { int x = GET_X_LPARAM(lParam); int y = GET_Y_LPARAM(lParam); UpdateCapture(wParam); if (!gameplay::Platform::mouseEventInternal(gameplay::Mouse::MOUSE_PRESS_LEFT_BUTTON, x, y, 0)) { gameplay::Platform::touchEventInternal(gameplay::Touch::TOUCH_PRESS, x, y, 0); } return 0; } case WM_LBUTTONUP: { int x = GET_X_LPARAM(lParam); int y = GET_Y_LPARAM(lParam); if (!gameplay::Platform::mouseEventInternal(gameplay::Mouse::MOUSE_RELEASE_LEFT_BUTTON, x, y, 0)) { gameplay::Platform::touchEventInternal(gameplay::Touch::TOUCH_RELEASE, x, y, 0); } UpdateCapture(wParam); return 0; } case WM_RBUTTONDOWN: UpdateCapture(wParam); lx = GET_X_LPARAM(lParam); ly = GET_Y_LPARAM(lParam); gameplay::Platform::mouseEventInternal(gameplay::Mouse::MOUSE_PRESS_RIGHT_BUTTON, lx, ly, 0); break; case WM_RBUTTONUP: gameplay::Platform::mouseEventInternal(gameplay::Mouse::MOUSE_RELEASE_RIGHT_BUTTON, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), 0); UpdateCapture(wParam); break; case WM_MBUTTONDOWN: UpdateCapture(wParam); gameplay::Platform::mouseEventInternal(gameplay::Mouse::MOUSE_PRESS_MIDDLE_BUTTON, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), 0); break; case WM_MBUTTONUP: gameplay::Platform::mouseEventInternal(gameplay::Mouse::MOUSE_RELEASE_MIDDLE_BUTTON, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), 0); UpdateCapture(wParam); break; case WM_MOUSEMOVE: { int x = GET_X_LPARAM(lParam); int y = GET_Y_LPARAM(lParam); if (__mouseCaptured) { // If the incoming position is the mouse capture point, ignore this event // since this is the event that warped the cursor back. if (x == __mouseCapturePoint.x && y == __mouseCapturePoint.y) break; // Convert to deltas x -= __mouseCapturePoint.x; y -= __mouseCapturePoint.y; // Warp mouse back to center of screen. WarpMouse(__mouseCapturePoint.x, __mouseCapturePoint.y); } // Allow Game::mouseEvent a chance to handle (and possibly consume) the event. if (!gameplay::Platform::mouseEventInternal(gameplay::Mouse::MOUSE_MOVE, x, y, 0)) { if ((wParam & MK_LBUTTON) == MK_LBUTTON) { // Mouse move events should be interpreted as touch move only if left mouse is held and the game did not consume the mouse event. gameplay::Platform::touchEventInternal(gameplay::Touch::TOUCH_MOVE, x, y, 0); return 0; } else if ((wParam & MK_RBUTTON) == MK_RBUTTON) { // Scale factors for the mouse movement used to simulate the accelerometer. RECT clientRect; GetClientRect(__hwnd, &clientRect); const float xFactor = 90.0f / clientRect.right; const float yFactor = 90.0f / clientRect.bottom; // Update the pitch and roll by adding the scaled deltas. __roll += (float)(x - lx) * xFactor; __pitch += -(float)(y - ly) * yFactor; // Clamp the values to the valid range. __roll = max(min(__roll, 90.0f), -90.0f); __pitch = max(min(__pitch, 90.0f), -90.0f); // Update the last X/Y values. lx = x; ly = y; } } break; } case WM_MOUSEWHEEL: tagPOINT point; point.x = GET_X_LPARAM(lParam); point.y = GET_Y_LPARAM(lParam); ScreenToClient(__hwnd, &point); gameplay::Platform::mouseEventInternal(gameplay::Mouse::MOUSE_WHEEL, point.x, point.y, GET_WHEEL_DELTA_WPARAM(wParam) / 120); break; case WM_KEYDOWN: if (wParam == VK_SHIFT || wParam == VK_LSHIFT || wParam == VK_RSHIFT) shiftDown = true; if (wParam == VK_CAPITAL) capsOn = !capsOn; // Suppress key repeats. if ((lParam & 0x40000000) == 0) gameplay::Platform::keyEventInternal(gameplay::Keyboard::KEY_PRESS, getKey(wParam, shiftDown ^ capsOn)); break; case WM_KEYUP: if (wParam == VK_SHIFT || wParam == VK_LSHIFT || wParam == VK_RSHIFT) shiftDown = false; gameplay::Platform::keyEventInternal(gameplay::Keyboard::KEY_RELEASE, getKey(wParam, shiftDown ^ capsOn)); break; case WM_CHAR: // Suppress key repeats. if ((lParam & 0x40000000) == 0) gameplay::Platform::keyEventInternal(gameplay::Keyboard::KEY_CHAR, wParam); break; case WM_UNICHAR: // Suppress key repeats. if ((lParam & 0x40000000) == 0) gameplay::Platform::keyEventInternal(gameplay::Keyboard::KEY_CHAR, wParam); break; case WM_SETFOCUS: break; case WM_KILLFOCUS: break; } return DefWindowProc(hwnd, msg, wParam, lParam); } namespace gameplay { struct WindowCreationParams { RECT rect; std::wstring windowName; bool fullscreen; int samples; }; extern void print(const char* format, ...) { va_list argptr; va_start(argptr, format); int sz = vfprintf(stderr, format, argptr); if (sz > 0) { char* buf = new char[sz + 1]; vsprintf(buf, format, argptr); buf[sz] = 0; OutputDebugStringA(buf); SAFE_DELETE_ARRAY(buf); } va_end(argptr); } Platform::Platform(Game* game) : _game(game) { } Platform::~Platform() { if (__hwnd) { DestroyWindow(__hwnd); __hwnd = 0; } #ifdef USE_XINPUT SAFE_DELETE_ARRAY(__xinputGamepads); #endif } bool createWindow(WindowCreationParams* params, HWND* hwnd, HDC* hdc) { bool fullscreen = false; RECT rect = { CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT }; std::wstring windowName; if (params) { windowName = params->windowName; memcpy(&rect, ¶ms->rect, sizeof(RECT)); fullscreen = params->fullscreen; } // Set the window style. DWORD style = (fullscreen ? WS_POPUP : WS_POPUP | WS_BORDER | WS_CAPTION | WS_SYSMENU) | WS_CLIPCHILDREN | WS_CLIPSIBLINGS; DWORD styleEx = (fullscreen ? WS_EX_APPWINDOW : WS_EX_APPWINDOW | WS_EX_WINDOWEDGE); // Adjust the window rectangle so the client size is the requested size. AdjustWindowRectEx(&rect, style, FALSE, styleEx); // Create the native Windows window. *hwnd = CreateWindowEx(styleEx, L"gameplay", windowName.c_str(), style, 0, 0, rect.right - rect.left, rect.bottom - rect.top, NULL, NULL, __hinstance, NULL); if (*hwnd == NULL) { GP_ERROR("Failed to create window."); return false; } // Get the drawing context. *hdc = GetDC(*hwnd); if (*hdc == NULL) { GP_ERROR("Failed to get device context."); return false; } // Center the window GetWindowRect(*hwnd, &rect); const int screenX = (GetSystemMetrics(SM_CXSCREEN) - rect.right) / 2; const int screenY = (GetSystemMetrics(SM_CYSCREEN) - rect.bottom) / 2; SetWindowPos(*hwnd, *hwnd, screenX, screenY, -1, -1, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); return true; } bool initializeGL(WindowCreationParams* params) { // Create a temporary window and context to we can initialize GLEW and get access // to additional OpenGL extension functions. This is a neccessary evil since the // function for querying GL extensions is a GL extension itself. HWND hwnd = NULL; HDC hdc = NULL; if (!createWindow(NULL, &hwnd, &hdc)) return false; PIXELFORMATDESCRIPTOR pfd; memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR)); pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); pfd.nVersion = 1; pfd.dwFlags = PFD_DOUBLEBUFFER | PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW; pfd.iPixelType = PFD_TYPE_RGBA; pfd.cColorBits = DEFAULT_COLOR_BUFFER_SIZE; pfd.cDepthBits = DEFAULT_DEPTH_BUFFER_SIZE; pfd.cStencilBits = DEFAULT_STENCIL_BUFFER_SIZE; pfd.iLayerType = PFD_MAIN_PLANE; int pixelFormat = ChoosePixelFormat(hdc, &pfd); if (pixelFormat == 0) { DestroyWindow(hwnd); GP_ERROR("Failed to choose a pixel format."); return false; } if (!SetPixelFormat(hdc, pixelFormat, &pfd)) { DestroyWindow(hwnd); GP_ERROR("Failed to set the pixel format."); return false; } HGLRC tempContext = wglCreateContext(hdc); if (!tempContext) { DestroyWindow(hwnd); GP_ERROR("Failed to create temporary context for initialization."); return false; } wglMakeCurrent(hdc, tempContext); // Initialize GLEW if (GLEW_OK != glewInit()) { wglDeleteContext(tempContext); DestroyWindow(hwnd); GP_ERROR("Failed to initialize GLEW."); return false; } // Choose pixel format using wglChoosePixelFormatARB, which allows us to specify // additional attributes such as multisampling. // // Note: Keep multisampling attributes at the start of the attribute lists since code below // assumes they are array elements 0 through 3. int attribList[] = { WGL_SAMPLES_ARB, params->samples, WGL_SAMPLE_BUFFERS_ARB, params->samples > 0 ? 1 : 0, WGL_DRAW_TO_WINDOW_ARB, GL_TRUE, WGL_SUPPORT_OPENGL_ARB, GL_TRUE, WGL_DOUBLE_BUFFER_ARB, GL_TRUE, WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, WGL_COLOR_BITS_ARB, DEFAULT_COLOR_BUFFER_SIZE, WGL_DEPTH_BITS_ARB, DEFAULT_DEPTH_BUFFER_SIZE, WGL_STENCIL_BITS_ARB, DEFAULT_STENCIL_BUFFER_SIZE, 0 }; UINT numFormats; if (!wglChoosePixelFormatARB(hdc, attribList, NULL, 1, &pixelFormat, &numFormats) || numFormats == 0) { bool valid = false; if (params->samples > 0) { GP_WARN("Failed to choose pixel format with WGL_SAMPLES_ARB == %d. Attempting to fallback to lower samples setting.", params->samples); while (params->samples > 0) { params->samples /= 2; attribList[1] = params->samples; attribList[3] = params->samples > 0 ? 1 : 0; if (wglChoosePixelFormatARB(hdc, attribList, NULL, 1, &pixelFormat, &numFormats) && numFormats > 0) { valid = true; GP_WARN("Found pixel format with WGL_SAMPLES_ARB == %d.", params->samples); break; } } } if (!valid) { wglDeleteContext(tempContext); DestroyWindow(hwnd); GP_ERROR("Failed to choose a pixel format."); return false; } } // Destroy old window DestroyWindow(hwnd); hwnd = NULL; hdc = NULL; // Create new/final window if needed if (params) { if (!createWindow(params, &__hwnd, &__hdc)) { wglDeleteContext(tempContext); return false; } } // Set final pixel format for window if (!SetPixelFormat(__hdc, pixelFormat, &pfd)) { GP_ERROR("Failed to set the pixel format: %d.", (int)GetLastError()); return false; } // Create our new GL context int attribs[] = { WGL_CONTEXT_MAJOR_VERSION_ARB, 3, WGL_CONTEXT_MINOR_VERSION_ARB, 1, 0 }; if (!(__hrc = wglCreateContextAttribsARB(__hdc, 0, attribs) ) ) { wglDeleteContext(tempContext); GP_ERROR("Failed to create OpenGL context."); return false; } // Delete the old/temporary context and window wglDeleteContext(tempContext); // Make the new context current if (!wglMakeCurrent(__hdc, __hrc) || !__hrc) { GP_ERROR("Failed to make the window current."); return false; } // Vertical sync. wglSwapIntervalEXT(__vsync ? 1 : 0); return true; } Platform* Platform::create(Game* game, void* attachToWindow) { GP_ASSERT(game); FileSystem::setResourcePath("./"); Platform* platform = new Platform(game); // Get the application module handle. __hinstance = ::GetModuleHandle(NULL); __attachToWindow = (HWND)attachToWindow; // Read window settings from config. WindowCreationParams params; params.fullscreen = false; params.rect.left = 0; params.rect.top = 0; params.rect.right = 0; params.rect.bottom = 0; params.samples = 0; if (game->getConfig()) { Properties* config = game->getConfig()->getNamespace("window", true); if (config) { // Read window title. const char* title = config->getString("title"); if (title) { int len = MultiByteToWideChar(CP_ACP, 0, title, -1, NULL, 0); wchar_t* wtitle = new wchar_t[len]; MultiByteToWideChar(CP_ACP, 0, title, -1, wtitle, len); params.windowName = wtitle; SAFE_DELETE_ARRAY(wtitle); } // Read window rect. int x = config->getInt("x"); if (x != 0) params.rect.left = x; int y = config->getInt("y"); if (y != 0) params.rect.top = y; int width = config->getInt("width"); if (width != 0) params.rect.right = params.rect.left + width; int height = config->getInt("height"); if (height != 0) params.rect.bottom = params.rect.top + height; // Read fullscreen state. params.fullscreen = config->getBool("fullscreen"); // Read multisampling state. params.samples = config->getInt("samples"); } } // If window size was not specified, set it to a default value if (params.rect.right == 0) params.rect.right = params.rect.left + DEFAULT_RESOLUTION_X; if (params.rect.bottom == 0) params.rect.bottom = params.rect.top + DEFAULT_RESOLUTION_Y; int width = params.rect.right - params.rect.left; int height = params.rect.bottom - params.rect.top; if (params.fullscreen) { // Enumerate all supposed display settings bool modeSupported = false; DWORD modeNum = 0; DEVMODE devMode; memset(&devMode, 0, sizeof(DEVMODE)); devMode.dmSize = sizeof(DEVMODE); devMode.dmDriverExtra = 0; while (EnumDisplaySettings(NULL, modeNum++, &devMode) != 0) { // Is mode supported? if (devMode.dmPelsWidth == width && devMode.dmPelsHeight == height && devMode.dmBitsPerPel == DEFAULT_COLOR_BUFFER_SIZE) { modeSupported = true; break; } } // If the requested mode is not supported, fall back to a safe default if (!modeSupported) { width = DEFAULT_RESOLUTION_X; height = DEFAULT_RESOLUTION_Y; params.rect.right = params.rect.left + width; params.rect.bottom = params.rect.top + height; } } if (!__attachToWindow) { // Register our window class. WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; wc.lpfnWndProc = (WNDPROC)__WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = __hinstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hIconSm = NULL; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = NULL; // No brush - we are going to paint our own background wc.lpszMenuName = NULL; // No default menu wc.lpszClassName = L"gameplay"; if (!::RegisterClassEx(&wc)) { GP_ERROR("Failed to register window class."); goto error; } if (params.fullscreen) { DEVMODE dm; memset(&dm, 0, sizeof(dm)); dm.dmSize= sizeof(dm); dm.dmPelsWidth = width; dm.dmPelsHeight = height; dm.dmBitsPerPel = DEFAULT_COLOR_BUFFER_SIZE; dm.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT; // Try to set selected mode and get results. NOTE: CDS_FULLSCREEN gets rid of start bar. if (ChangeDisplaySettings(&dm, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL) { params.fullscreen = false; GP_ERROR("Failed to start game in full-screen mode with resolution %dx%d.", width, height); goto error; } } if (!initializeGL(¶ms)) goto error; // Show the window. ShowWindow(__hwnd, SW_SHOW); } else { // Attach to a previous windows __hwnd = (HWND)__attachToWindow; __hdc = GetDC(__hwnd); SetWindowLongPtr(__hwnd, GWL_WNDPROC, (LONG)(WNDPROC)__WndProc); if (!initializeGL(NULL)) goto error; } #ifdef USE_XINPUT // Initialize XInputGamepads. __xinputGamepads = new XInputGamepad[XUSER_MAX_COUNT]; for (unsigned int i = 0; i < XUSER_MAX_COUNT; i++) { switch(i) { case 0: __xinputGamepads[i].id = "XINPUT 1"; break; case 1: __xinputGamepads[i].id = "XINPUT 2"; break; case 2: __xinputGamepads[i].id = "XINPUT 3"; break; case 3: __xinputGamepads[i].id = "XINPUT 4"; break; } __xinputGamepads[i].connected = false; __xinputGamepads[i].handle = -1; } #endif return platform; error: exit(0); return NULL; } int Platform::enterMessagePump() { GP_ASSERT(_game); // Get the initial time. LARGE_INTEGER tps; QueryPerformanceFrequency(&tps); __timeTicksPerMillis = (double)(tps.QuadPart / 1000L); LARGE_INTEGER queryTime; QueryPerformanceCounter(&queryTime); GP_ASSERT(__timeTicksPerMillis); __timeStart = queryTime.QuadPart / __timeTicksPerMillis; // Set the initial pitch and roll values. __pitch = 0.0; __roll = 0.0; SwapBuffers(__hdc); if (_game->getState() != Game::RUNNING) _game->run(); if (__attachToWindow) return 0; // Enter event dispatch loop. MSG msg; while (true) { if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); if (msg.message == WM_QUIT) { _game->exit(); break; } } else { _game->frame(); SwapBuffers(__hdc); } // If we are done, then exit. if (_game->getState() == Game::UNINITIALIZED) break; } return msg.wParam; } void Platform::signalShutdown() { // nothing to do } unsigned int Platform::getDisplayWidth() { static RECT rect; GetClientRect(__hwnd, &rect); return rect.right; } unsigned int Platform::getDisplayHeight() { static RECT rect; GetClientRect(__hwnd, &rect); return rect.bottom; } double Platform::getAbsoluteTime() { LARGE_INTEGER queryTime; QueryPerformanceCounter(&queryTime); GP_ASSERT(__timeTicksPerMillis); __timeAbsolute = queryTime.QuadPart / __timeTicksPerMillis; return __timeAbsolute; } void Platform::setAbsoluteTime(double time) { __timeAbsolute = time; } bool Platform::isVsync() { return __vsync; } void Platform::setVsync(bool enable) { wglSwapIntervalEXT(enable ? 1 : 0); __vsync = enable; } void Platform::swapBuffers() { if (__hdc) SwapBuffers(__hdc); } void Platform::sleep(long ms) { Sleep(ms); } void Platform::setMultiTouch(bool enabled) { // not supported } bool Platform::isMultiTouch() { return false; } void Platform::getAccelerometerValues(float* pitch, float* roll) { GP_ASSERT(pitch); GP_ASSERT(roll); *pitch = __pitch; *roll = __roll; } bool Platform::hasMouse() { return true; } void Platform::setMouseCaptured(bool captured) { if (captured != __mouseCaptured) { if (captured) { // Hide the cursor and warp it to the center of the screen __mouseCapturePoint.x = getDisplayWidth() / 2; __mouseCapturePoint.y = getDisplayHeight() / 2; ShowCursor(FALSE); WarpMouse(__mouseCapturePoint.x, __mouseCapturePoint.y); } else { // Restore cursor WarpMouse(__mouseCapturePoint.x, __mouseCapturePoint.y); ShowCursor(TRUE); } __mouseCaptured = captured; } } bool Platform::isMouseCaptured() { return __mouseCaptured; } void Platform::setCursorVisible(bool visible) { if (visible != __cursorVisible) { ShowCursor(visible ? TRUE : FALSE); __cursorVisible = visible; } } bool Platform::isCursorVisible() { return __cursorVisible; } void Platform::displayKeyboard(bool display) { // Do nothing. } bool Platform::isGestureSupported(Gesture::GestureEvent evt) { return false; } void Platform::registerGesture(Gesture::GestureEvent evt) { } void Platform::unregisterGesture(Gesture::GestureEvent evt) { } bool Platform::isGestureRegistered(Gesture::GestureEvent evt) { return false; } unsigned int Platform::getGamepadsConnected() { // Check to see what xbox360 gamepads are connected. __gamepadsConnected = 0; #ifdef USE_XINPUT for (unsigned int i = 0; i < XUSER_MAX_COUNT; i++) { if (isGamepadConnected(i)) __gamepadsConnected++; } #endif return __gamepadsConnected; } bool Platform::isGamepadConnected(unsigned int gamepadHandle) { #ifdef USE_XINPUT GP_ASSERT(0 <= gamepadHandle); GP_ASSERT(gamepadHandle < XUSER_MAX_COUNT); Game* game = Game::getInstance(); GP_ASSERT(game); if (__xinputGamepads[gamepadHandle].handle == -1) __xinputGamepads[gamepadHandle].handle = game->createGamepad(__xinputGamepads[gamepadHandle].id.c_str(), gamepadHandle, __xinputGamepads[gamepadHandle].BUTTON_COUNT, __xinputGamepads[gamepadHandle].JOYSTICK_COUNT, __xinputGamepads[gamepadHandle].TRIGGER_COUNT); bool isConnected = __xinputGamepads[gamepadHandle].connected; if (getXInputState(gamepadHandle)) { if (!isConnected) { __xinputGamepads[gamepadHandle].connected = true; Gamepad* gamepad = game->getGamepad(__xinputGamepads[gamepadHandle].handle); GP_ASSERT(gamepad); if (game->isInitialized()) game->gamepadEvent(Gamepad::CONNECTED_EVENT, game->getGamepad(__xinputGamepads[gamepadHandle].handle)); } return true; } else { if (isConnected) { // if it was connected, but now isn't pass the detached event to gamepadEvent() __xinputGamepads[gamepadHandle].connected = false; Gamepad* gamepad = game->getGamepad(__xinputGamepads[gamepadHandle].handle); GP_ASSERT(gamepad); if (game->isInitialized()) game->gamepadEvent(Gamepad::DISCONNECTED_EVENT, game->getGamepad(__xinputGamepads[gamepadHandle].handle)); } return false; } #endif return false; } const char* Platform::getGamepadId(unsigned int gamepadHandle) { #ifdef USE_XINPUT GP_ASSERT(0 <= gamepadHandle); GP_ASSERT(gamepadHandle < XUSER_MAX_COUNT); return __xinputGamepads[gamepadHandle].id.c_str(); #endif return NULL; } unsigned int Platform::getGamepadButtonCount(unsigned int gamepadHandle) { #ifdef USE_XINPUT GP_ASSERT(0 <= gamepadHandle); GP_ASSERT(gamepadHandle < XUSER_MAX_COUNT); if (!__xinputGamepads[gamepadHandle].connected) return 0; return XInputGamepad::BUTTON_COUNT; #endif return 0; } bool Platform::getGamepadButtonState(unsigned int gamepadHandle, unsigned int buttonIndex) { #ifdef USE_XINPUT GP_ASSERT(0 <= gamepadHandle); GP_ASSERT(gamepadHandle < XUSER_MAX_COUNT); return getXInputButtonState(gamepadHandle, buttonIndex); #endif return false; } unsigned int Platform::getGamepadJoystickCount(unsigned int gamepadHandle) { #ifdef USE_XINPUT GP_ASSERT(0 <= gamepadHandle); GP_ASSERT(gamepadHandle < XUSER_MAX_COUNT); if (!__xinputGamepads[gamepadHandle].connected) return 0; return XInputGamepad::JOYSTICK_COUNT; #endif return 0; } float Platform::getGamepadJoystickAxisX(unsigned int gamepadHandle, unsigned int joystickIndex) { #ifdef USE_XINPUT GP_ASSERT(0 <= gamepadHandle); GP_ASSERT(gamepadHandle < XUSER_MAX_COUNT); return getXInputJoystickAxisX(gamepadHandle, joystickIndex); #endif return 0.0f; } float Platform::getGamepadJoystickAxisY(unsigned int gamepadHandle, unsigned int joystickIndex) { #ifdef USE_XINPUT GP_ASSERT(0 <= gamepadHandle); GP_ASSERT(gamepadHandle < XUSER_MAX_COUNT); return getXInputJoystickAxisY(gamepadHandle, joystickIndex); #endif return 0.0f; } void Platform::getGamepadJoystickAxisValues(unsigned int gamepadHandle, unsigned int joystickIndex, Vector2* outValue) { #ifdef USE_XINPUT GP_ASSERT(0 <= gamepadHandle); GP_ASSERT(gamepadHandle < XUSER_MAX_COUNT); getXInputJoystickAxisValues(gamepadHandle, joystickIndex, outValue); #endif } bool Platform::isGamepadJoystickActive(unsigned int gamepadHandle, unsigned int joystickIndex) { #ifdef USE_XINPUT GP_ASSERT(0 <= gamepadHandle); GP_ASSERT(gamepadHandle < XUSER_MAX_COUNT); return (getXInputJoystickAxisX(gamepadHandle, joystickIndex) != 0.0f || getXInputJoystickAxisY(gamepadHandle, joystickIndex) != 0.0f); #endif return false; } unsigned int Platform::getGamepadTriggerCount(unsigned int gamepadHandle) { #ifdef USE_XINPUT GP_ASSERT(0 <= gamepadHandle); GP_ASSERT(gamepadHandle < XUSER_MAX_COUNT); return XInputGamepad::TRIGGER_COUNT; #endif return 0; } float Platform::getGamepadTriggerValue(unsigned int gamepadHandle, unsigned int triggerIndex) { #ifdef USE_XINPUT GP_ASSERT(0 <= gamepadHandle); GP_ASSERT(gamepadHandle < XUSER_MAX_COUNT); //TODO: #endif return 0.0f; } void Platform::touchEventInternal(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex) { if (!Form::touchEventInternal(evt, x, y, contactIndex)) { Game::getInstance()->touchEvent(evt, x, y, contactIndex); Game::getInstance()->getScriptController()->touchEvent(evt, x, y, contactIndex); } } void Platform::keyEventInternal(Keyboard::KeyEvent evt, int key) { if (!Form::keyEventInternal(evt, key)) { Game::getInstance()->keyEvent(evt, key); Game::getInstance()->getScriptController()->keyEvent(evt, key); } } bool Platform::mouseEventInternal(Mouse::MouseEvent evt, int x, int y, int wheelDelta) { if (Form::mouseEventInternal(evt, x, y, wheelDelta)) { return true; } else if (Game::getInstance()->mouseEvent(evt, x, y, wheelDelta)) { return true; } else { return Game::getInstance()->getScriptController()->mouseEvent(evt, x, y, wheelDelta); } } } #endif