/* Copyright (c) 2015 Alex Diener This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. Alex Diener alex@ludobloom.com */ #include "testharness/BouncingBallScreen.h" #include "testharness/SingleFrameScreen2D.h" #include "testharness/SingleFrameScreen3D.h" #include "testharness/SharedEvents.h" #include "testharness/TestHarness_globals.h" #include "testharness/TrimeshViewerScreen.h" #include "3dmodelio/TextureAtlasData.h" #include "gamemath/Vector2i.h" #include "jsonserialization/JSONDeserializationContext.h" #include "pngimageio/PNGImageIO.h" #include "screenmanager/ScreenManager.h" #include "shell/Shell.h" #include "shell/ShellCallbacks.h" #include "shell/ShellKeyCodes.h" #include "utilities/IOUtilities.h" #if defined(STEM_PLATFORM_macosx) #include "nsopenglshell/NSOpenGLShell.h" #include "nsopenglshell/NSOpenGLTarget.h" #elif defined(STEM_PLATFORM_windows) #include "wglshell/WGLShell.h" #include "wglshell/WGLTarget.h" #elif defined(STEM_PLATFORM_linux) #include "glxshell/GLXShell.h" #include "glxshell/GLXTarget.h" #endif #include #include #include #include // TODO: // - Show contact area (and vectors?) in scene 1 // - Animate mode (interpolate all shapes to first collision, resolve, continue until all resolved) // - Hotkey to resolve detected collision in scene 1 (move lastPosition to point of intersection) // - Display numbers for all relevant properties of selected object // Feature wishlist: // - Demo scenes: Prebuilt setups where collidable objects can be controlled // - Platformer controls with gravity // - Overhead controls // - Bouncing balls // - Single-frame and sub-frame simulation (set up lastPosition and position for each object, and step through intersection tests and resolution in detail) // - Place arbitrary shapes into a simulation and allow them to intercollide // - Stress test (control number/speed of collisiding objects with performance metrics) static ScreenManager * screenManager; static SingleFrameScreen2D * singleFrameScreen2D; static SingleFrameScreen3D * singleFrameScreen3D; static BouncingBallScreen * bouncingBallScreen; static TrimeshViewerScreen * trimeshViewerScreen; static Renderer * renderer; static bool Target_draw(double referenceTime, double activeDrawDelta) { return EventDispatcher_dispatchEvent(screenManager->eventDispatcher, ATOM(EVENT_DRAW), &activeDrawDelta); } static void Target_keyDown(unsigned int charCode, unsigned int keyCode, unsigned int modifiers, bool isRepeat, double referenceTime) { if (keyCode == KEY_CODE_ENTER && (modifiers & MODIFIER_ALT_BIT)) { if (Shell_isFullScreen()) { Shell_exitFullScreen(); } else { Shell_enterFullScreen(Shell_getDisplayIndexFromWindow(), false); } EventDispatcher_dispatchEvent(screenManager->eventDispatcher, ATOM(Shell_isFullScreen() ? EVENT_FULLSCREENED : EVENT_WINDOWED), NULL); } else if (keyCode == KEY_CODE_1 && !(modifiers & MODIFIER_SHIFT_BIT)) { ScreenManager_setScreen(screenManager, singleFrameScreen2D); } else if (keyCode == KEY_CODE_2 && !(modifiers & MODIFIER_SHIFT_BIT)) { ScreenManager_setScreen(screenManager, bouncingBallScreen); } else if (keyCode == KEY_CODE_3 && !(modifiers & MODIFIER_SHIFT_BIT)) { ScreenManager_setScreen(screenManager, singleFrameScreen3D); } else if (keyCode == KEY_CODE_4 && !(modifiers & MODIFIER_SHIFT_BIT)) { ScreenManager_setScreen(screenManager, trimeshViewerScreen); } else { struct keyEvent event; if (keyCode == KEY_CODE_SPACE) { g_spacebarDown = true; } event.keyCode = keyCode; event.charCode = charCode; event.modifiers = modifiers; event.isRepeat = isRepeat; EventDispatcher_dispatchEvent(screenManager->eventDispatcher, ATOM(EVENT_KEY_DOWN), &event); } } static void Target_keyUp(unsigned int keyCode, unsigned int modifiers, double referenceTime) { struct keyEvent event; if (keyCode == KEY_CODE_SPACE) { g_spacebarDown = false; } event.keyCode = keyCode; event.modifiers = modifiers; EventDispatcher_dispatchEvent(screenManager->eventDispatcher, ATOM(EVENT_KEY_UP), &event); } static void Target_keyModifiersChanged(unsigned int modifiers, unsigned int lastModifiers, double referenceTime) { struct keyEvent event; event.modifiers = modifiers; EventDispatcher_dispatchEvent(screenManager->eventDispatcher, ATOM(EVENT_KEY_MODIFIERS_CHANGED), &event); } static void Target_mouseDown(unsigned int buttonNumber, unsigned int buttonMask, float x, float y, unsigned int modifierFlags, double referenceTime) { struct mouseEvent event; event.button = buttonNumber; event.position = VECTOR2f(x, y); event.modifiers = modifierFlags; event.timestamp = referenceTime; EventDispatcher_dispatchEvent(screenManager->eventDispatcher, ATOM(EVENT_MOUSE_DOWN), &event); } static void Target_mouseUp(unsigned int buttonNumber, unsigned int buttonMask, float x, float y, unsigned int modifierFlags, double referenceTime) { struct mouseEvent event; event.button = buttonNumber; event.position = VECTOR2f(x, y); event.modifiers = modifierFlags; event.timestamp = referenceTime; EventDispatcher_dispatchEvent(screenManager->eventDispatcher, ATOM(EVENT_MOUSE_UP), &event); } static void Target_mouseMoved(float x, float y, float deltaX, float deltaY, unsigned int modifierFlags, double referenceTime) { struct mouseEvent event; event.position = VECTOR2f(x, y); event.delta = VECTOR2f(deltaX, deltaY); event.modifiers = modifierFlags; event.timestamp = referenceTime; EventDispatcher_dispatchEvent(screenManager->eventDispatcher, ATOM(EVENT_MOUSE_MOVED), &event); } static void Target_mouseDragged(unsigned int buttonMask, float x, float y, float deltaX, float deltaY, unsigned int modifierFlags, double referenceTime) { struct mouseEvent event; event.button = buttonMask; event.position = VECTOR2f(x, y); event.delta = VECTOR2f(deltaX / g_scaleFactor, deltaY / g_scaleFactor); event.modifiers = modifierFlags; event.timestamp = referenceTime; EventDispatcher_dispatchEvent(screenManager->eventDispatcher, ATOM(EVENT_MOUSE_DRAGGED), &event); } static void Target_scrollWheel(float x, float y, int deltaX, int deltaY, unsigned int modifierFlags, double referenceTime) { struct mouseWheelEvent event; event.x = x; event.y = y; event.deltaX = deltaX; event.deltaY = deltaY; event.modifiers = modifierFlags; event.timestamp = referenceTime; EventDispatcher_dispatchEvent(screenManager->eventDispatcher, ATOM(EVENT_MOUSE_SCROLL_WHEEL), &event); } static void Target_resized(unsigned int newWidth, unsigned int newHeight, double referenceTime) { g_viewWidth = newWidth; g_viewHeight = newHeight; g_viewRatio = (float) newWidth / newHeight; g_scaleFactor = Shell_getDisplayScaleFactor(Shell_getDisplayIndexFromWindow()); if (renderer != NULL) { Renderer_setViewport(renderer, 0, 0, newWidth, newHeight, NULL); } if (screenManager != NULL) { EventDispatcher_dispatchEvent(screenManager->eventDispatcher, ATOM(EVENT_RESIZED), NULL); } } static void Target_backgrounded(double referenceTime) { if (screenManager != NULL) { EventDispatcher_dispatchEvent(screenManager->eventDispatcher, ATOM(EVENT_BACKGROUNDED), NULL); } } static void Target_foregrounded(double referenceTime) { if (screenManager != NULL) { EventDispatcher_dispatchEvent(screenManager->eventDispatcher, ATOM(EVENT_FOREGROUNDED), NULL); } } static void registerShellCallbacks() { Shell_drawFunc(Target_draw); Shell_resizeFunc(Target_resized); Shell_keyDownFunc(Target_keyDown); Shell_keyUpFunc(Target_keyUp); Shell_keyModifiersChangedFunc(Target_keyModifiersChanged); Shell_mouseDownFunc(Target_mouseDown); Shell_mouseUpFunc(Target_mouseUp); Shell_mouseMovedFunc(Target_mouseMoved); Shell_mouseDraggedFunc(Target_mouseDragged); Shell_scrollWheelFunc(Target_scrollWheel); Shell_backgroundedFunc(Target_backgrounded); Shell_foregroundedFunc(Target_foregrounded); } #if defined(STEM_PLATFORM_macosx) void NSOpenGLTarget_configure(int argc, const char ** argv, struct NSOpenGLShellConfiguration * configuration) { configuration->fullScreenMenuItem = true; configuration->useGLCoreProfile = true; #elif defined(STEM_PLATFORM_windows) void WGLTarget_configure(void * instance, void * prevInstance, char * commandLine, int command, int argc, const char ** argv, struct WGLShellConfiguration * configuration) { configuration->useGLCoreProfile = true; #elif defined(STEM_PLATFORM_linux) void GLXTarget_configure(int argc, const char ** argv, struct GLXShellConfiguration * configuration) { #else #error Unsupported platform #endif int displayX, displayY; unsigned int displayWidth, displayHeight, windowWidth = 1280, windowHeight = 720; Vector2i preferredSizes[] = {{2560, 1440}, {1920, 1080}, {1280, 720}, {853, 480}}; unsigned int preferredSizeIndex; if (argc > 1 && sscanf(argv[1], "%d", &g_randomSeed)) { g_fixedRandomSeed = true; printf("Using fixed random seed: %d\n", g_randomSeed); } Shell_getSafeWindowRect(0, &displayX, &displayY, &displayWidth, &displayHeight); for (preferredSizeIndex = 0; preferredSizeIndex < sizeof(preferredSizes) / sizeof(Vector2i); preferredSizeIndex++) { if ((int) displayWidth >= preferredSizes[preferredSizeIndex].x && (int) displayHeight >= preferredSizes[preferredSizeIndex].y) { windowWidth = preferredSizes[preferredSizeIndex].x; windowHeight = preferredSizes[preferredSizeIndex].y; break; } } if (preferredSizeIndex >= sizeof(preferredSizes) / sizeof(Vector2i)) { windowWidth = displayWidth; windowHeight = displayHeight; } configuration->windowX = displayX + (displayWidth - windowWidth) / 2; configuration->windowY = displayY + (displayHeight - windowHeight) / 2; configuration->windowWidth = g_viewWidth = windowWidth; configuration->windowHeight = g_viewHeight = windowHeight; configuration->windowTitle = "Collision Test Harness"; configuration->displayMode.depthBuffer = true; g_scaleFactor = Shell_getDisplayScaleFactor(Shell_getDisplayIndexFromWindow()); registerShellCallbacks(); } void Target_init() { renderer = Renderer_create(); uint8_t whitePixel = 0xFF; g_blankTexture = Texture_createWithTexels2D(&whitePixel, 1, 1, 1, TEXEL_COMPONENT_UINT8_NORM, TEXEL_SWIZZLE_GRAY, 0); const char * resourcePath = Shell_getResourcePath(); char path[PATH_MAX]; snprintf_safe(path, sizeof(path), "%s/verdana_atlas.json", resourcePath); JSONDeserializationContext * context = JSONDeserializationContext_createWithResourceFile(path); if (context->status != SERIALIZATION_ERROR_OK) { #ifdef DEBUG fprintf(stderr, "Fatal error: Couldn't load verdana_atlas.json (status %d)\n", context->status); #endif exit(EXIT_FAILURE); } TextureAtlasData * atlasData = TextureAtlasData_deserialize(context); call_virtual(dispose, context); if (atlasData == NULL) { #ifdef DEBUG fprintf(stderr, "Fatal error: Couldn't deserialize verdana_atlas.json (status %d)\n", context->status); #endif exit(EXIT_FAILURE); } snprintf_safe(path, sizeof(path), "%s/verdana.png", resourcePath); BitmapImage * image = PNGImageIO_loadPNGResourceFile(path, BITMAP_PIXEL_FORMAT_RGBA_8888, true); if (image == NULL) { #ifdef DEBUG fprintf(stderr, "Fatal error: Couldn't load verdana.png\n"); #endif exit(EXIT_FAILURE); } g_fontTexture = Texture_createWithTexels2D(image->pixels, image->width, image->height, 4, TEXEL_COMPONENT_UINT8_NORM, TEXEL_SWIZZLE_DEFAULT, TEXTURE_OPTION_MAGNIFY_NEAREST | TEXTURE_OPTION_MINIFY_NEAREST); TextureAtlas * atlas = TextureAtlasData_createTextureAtlas(atlasData, image->width, image->height); BitmapImage_dispose(image); TextureAtlasData_dispose(atlasData); snprintf_safe(path, sizeof(path), "%s/verdana_font.json", resourcePath); context = JSONDeserializationContext_createWithResourceFile(path); if (context->status != SERIALIZATION_ERROR_OK) { #ifdef DEBUG fprintf(stderr, "Fatal error: Couldn't load verdana_font.json (status %d)\n", context->status); #endif exit(EXIT_FAILURE); } g_font = BitmapFont_deserialize(context); call_virtual(dispose, context); if (g_font == NULL) { #ifdef DEBUG fprintf(stderr, "Fatal error: Couldn't deserialize verdana_font_json (status %d)\n", context->status); #endif exit(EXIT_FAILURE); } BitmapFont_readAtlasEntries(g_font, atlas); TextureAtlas_dispose(atlas); screenManager = ScreenManager_create(); singleFrameScreen2D = SingleFrameScreen2D_create(renderer); singleFrameScreen3D = SingleFrameScreen3D_create(renderer); bouncingBallScreen = BouncingBallScreen_create(renderer); trimeshViewerScreen = TrimeshViewerScreen_create(renderer); ScreenManager_addScreen(screenManager, singleFrameScreen2D); ScreenManager_addScreen(screenManager, singleFrameScreen3D); ScreenManager_addScreen(screenManager, bouncingBallScreen); ScreenManager_addScreen(screenManager, trimeshViewerScreen); ScreenManager_setScreen(screenManager, singleFrameScreen2D); Shell_setVSync(false, false); Shell_setVSync(false, true); Shell_mainLoop(); }