Files
2022-07-31 17:34:54 +10:00

652 lines
16 KiB
C++

// MIT License
// Copyright (c) 2019 Erin Catto
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#define _CRT_SECURE_NO_WARNINGS
#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS 1
#include "imgui/imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"
#include "draw.h"
#include "settings.h"
#include "test.h"
#include <algorithm>
#include <stdio.h>
#include <thread>
#include <chrono>
#if defined(_WIN32)
#include <crtdbg.h>
#endif
GLFWwindow* g_mainWindow = nullptr;
static int32 s_testSelection = 0;
static Test* s_test = nullptr;
static Settings s_settings;
static bool s_rightMouseDown = false;
static b2Vec2 s_clickPointWS = b2Vec2_zero;
void glfwErrorCallback(int error, const char* description)
{
fprintf(stderr, "GLFW error occured. Code: %d. Description: %s\n", error, description);
}
static inline bool CompareTests(const TestEntry& a, const TestEntry& b)
{
int result = strcmp(a.category, b.category);
if (result == 0)
{
result = strcmp(a.name, b.name);
}
return result < 0;
}
static void SortTests()
{
std::sort(g_testEntries, g_testEntries + g_testCount, CompareTests);
}
static void CreateUI(GLFWwindow* window, const char* glslVersion = NULL)
{
IMGUI_CHECKVERSION();
ImGui::CreateContext();
bool success;
success = ImGui_ImplGlfw_InitForOpenGL(window, false);
if (success == false)
{
printf("ImGui_ImplGlfw_InitForOpenGL failed\n");
assert(false);
}
success = ImGui_ImplOpenGL3_Init(glslVersion);
if (success == false)
{
printf("ImGui_ImplOpenGL3_Init failed\n");
assert(false);
}
// Search for font file
const char* fontPath1 = "data/droid_sans.ttf";
const char* fontPath2 = "../data/droid_sans.ttf";
const char* fontPath = nullptr;
FILE* file1 = fopen(fontPath1, "rb");
FILE* file2 = fopen(fontPath2, "rb");
if (file1)
{
fontPath = fontPath1;
fclose(file1);
}
if (file2)
{
fontPath = fontPath2;
fclose(file2);
}
if (fontPath)
{
ImGui::GetIO().Fonts->AddFontFromFileTTF(fontPath, 13.0f);
}
}
static void ResizeWindowCallback(GLFWwindow*, int width, int height)
{
g_camera.m_width = width;
g_camera.m_height = height;
s_settings.m_windowWidth = width;
s_settings.m_windowHeight = height;
}
static void KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
ImGui_ImplGlfw_KeyCallback(window, key, scancode, action, mods);
if (ImGui::GetIO().WantCaptureKeyboard)
{
return;
}
if (action == GLFW_PRESS)
{
switch (key)
{
case GLFW_KEY_ESCAPE:
// Quit
glfwSetWindowShouldClose(g_mainWindow, GL_TRUE);
break;
case GLFW_KEY_LEFT:
// Pan left
if (mods == GLFW_MOD_CONTROL)
{
b2Vec2 newOrigin(2.0f, 0.0f);
s_test->ShiftOrigin(newOrigin);
}
else
{
g_camera.m_center.x -= 0.5f;
}
break;
case GLFW_KEY_RIGHT:
// Pan right
if (mods == GLFW_MOD_CONTROL)
{
b2Vec2 newOrigin(-2.0f, 0.0f);
s_test->ShiftOrigin(newOrigin);
}
else
{
g_camera.m_center.x += 0.5f;
}
break;
case GLFW_KEY_DOWN:
// Pan down
if (mods == GLFW_MOD_CONTROL)
{
b2Vec2 newOrigin(0.0f, 2.0f);
s_test->ShiftOrigin(newOrigin);
}
else
{
g_camera.m_center.y -= 0.5f;
}
break;
case GLFW_KEY_UP:
// Pan up
if (mods == GLFW_MOD_CONTROL)
{
b2Vec2 newOrigin(0.0f, -2.0f);
s_test->ShiftOrigin(newOrigin);
}
else
{
g_camera.m_center.y += 0.5f;
}
break;
case GLFW_KEY_HOME:
// Reset view
g_camera.m_zoom = 1.0f;
g_camera.m_center.Set(0.0f, 20.0f);
break;
case GLFW_KEY_Z:
// Zoom out
g_camera.m_zoom = b2Min(1.1f * g_camera.m_zoom, 20.0f);
break;
case GLFW_KEY_X:
// Zoom in
g_camera.m_zoom = b2Max(0.9f * g_camera.m_zoom, 0.02f);
break;
case GLFW_KEY_R:
// Reset test
delete s_test;
s_test = g_testEntries[s_settings.m_testIndex].createFcn();
break;
case GLFW_KEY_SPACE:
// Launch a bomb.
if (s_test)
{
s_test->LaunchBomb();
}
break;
case GLFW_KEY_O:
s_settings.m_singleStep = true;
break;
case GLFW_KEY_P:
s_settings.m_pause = !s_settings.m_pause;
break;
case GLFW_KEY_LEFT_BRACKET:
// Switch to previous test
--s_testSelection;
if (s_testSelection < 0)
{
s_testSelection = g_testCount - 1;
}
break;
case GLFW_KEY_RIGHT_BRACKET:
// Switch to next test
++s_testSelection;
if (s_testSelection == g_testCount)
{
s_testSelection = 0;
}
break;
case GLFW_KEY_TAB:
g_debugDraw.m_showUI = !g_debugDraw.m_showUI;
default:
if (s_test)
{
s_test->Keyboard(key);
}
}
}
else if (action == GLFW_RELEASE)
{
s_test->KeyboardUp(key);
}
// else GLFW_REPEAT
}
static void CharCallback(GLFWwindow* window, unsigned int c)
{
ImGui_ImplGlfw_CharCallback(window, c);
}
static void MouseButtonCallback(GLFWwindow* window, int32 button, int32 action, int32 mods)
{
ImGui_ImplGlfw_MouseButtonCallback(window, button, action, mods);
double xd, yd;
glfwGetCursorPos(g_mainWindow, &xd, &yd);
b2Vec2 ps((float)xd, (float)yd);
// Use the mouse to move things around.
if (button == GLFW_MOUSE_BUTTON_1)
{
//<##>
//ps.Set(0, 0);
b2Vec2 pw = g_camera.ConvertScreenToWorld(ps);
if (action == GLFW_PRESS)
{
if (mods == GLFW_MOD_SHIFT)
{
s_test->ShiftMouseDown(pw);
}
else
{
s_test->MouseDown(pw);
}
}
if (action == GLFW_RELEASE)
{
s_test->MouseUp(pw);
}
}
else if (button == GLFW_MOUSE_BUTTON_2)
{
if (action == GLFW_PRESS)
{
s_clickPointWS = g_camera.ConvertScreenToWorld(ps);
s_rightMouseDown = true;
}
if (action == GLFW_RELEASE)
{
s_rightMouseDown = false;
}
}
}
static void MouseMotionCallback(GLFWwindow*, double xd, double yd)
{
b2Vec2 ps((float)xd, (float)yd);
b2Vec2 pw = g_camera.ConvertScreenToWorld(ps);
s_test->MouseMove(pw);
if (s_rightMouseDown)
{
b2Vec2 diff = pw - s_clickPointWS;
g_camera.m_center.x -= diff.x;
g_camera.m_center.y -= diff.y;
s_clickPointWS = g_camera.ConvertScreenToWorld(ps);
}
}
static void ScrollCallback(GLFWwindow* window, double dx, double dy)
{
ImGui_ImplGlfw_ScrollCallback(window, dx, dy);
if (ImGui::GetIO().WantCaptureMouse)
{
return;
}
if (dy > 0)
{
g_camera.m_zoom /= 1.1f;
}
else
{
g_camera.m_zoom *= 1.1f;
}
}
static void RestartTest()
{
delete s_test;
s_test = g_testEntries[s_settings.m_testIndex].createFcn();
}
static void UpdateUI()
{
int menuWidth = 180;
if (g_debugDraw.m_showUI)
{
ImGui::SetNextWindowPos(ImVec2((float)g_camera.m_width - menuWidth - 10, 10));
ImGui::SetNextWindowSize(ImVec2((float)menuWidth, (float)g_camera.m_height - 20));
ImGui::Begin("Tools", &g_debugDraw.m_showUI, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
if (ImGui::BeginTabBar("ControlTabs", ImGuiTabBarFlags_None))
{
if (ImGui::BeginTabItem("Controls"))
{
ImGui::SliderInt("Vel Iters", &s_settings.m_velocityIterations, 0, 50);
ImGui::SliderInt("Pos Iters", &s_settings.m_positionIterations, 0, 50);
ImGui::SliderFloat("Hertz", &s_settings.m_hertz, 5.0f, 120.0f, "%.0f hz");
ImGui::Separator();
ImGui::Checkbox("Sleep", &s_settings.m_enableSleep);
ImGui::Checkbox("Warm Starting", &s_settings.m_enableWarmStarting);
ImGui::Checkbox("Time of Impact", &s_settings.m_enableContinuous);
ImGui::Checkbox("Sub-Stepping", &s_settings.m_enableSubStepping);
ImGui::Separator();
ImGui::Checkbox("Shapes", &s_settings.m_drawShapes);
ImGui::Checkbox("Joints", &s_settings.m_drawJoints);
ImGui::Checkbox("AABBs", &s_settings.m_drawAABBs);
ImGui::Checkbox("Contact Points", &s_settings.m_drawContactPoints);
ImGui::Checkbox("Contact Normals", &s_settings.m_drawContactNormals);
ImGui::Checkbox("Contact Impulses", &s_settings.m_drawContactImpulse);
ImGui::Checkbox("Friction Impulses", &s_settings.m_drawFrictionImpulse);
ImGui::Checkbox("Center of Masses", &s_settings.m_drawCOMs);
ImGui::Checkbox("Statistics", &s_settings.m_drawStats);
ImGui::Checkbox("Profile", &s_settings.m_drawProfile);
ImVec2 button_sz = ImVec2(-1, 0);
if (ImGui::Button("Pause (P)", button_sz))
{
s_settings.m_pause = !s_settings.m_pause;
}
if (ImGui::Button("Single Step (O)", button_sz))
{
s_settings.m_singleStep = !s_settings.m_singleStep;
}
if (ImGui::Button("Restart (R)", button_sz))
{
RestartTest();
}
if (ImGui::Button("Quit", button_sz))
{
glfwSetWindowShouldClose(g_mainWindow, GL_TRUE);
}
ImGui::EndTabItem();
}
ImGuiTreeNodeFlags leafNodeFlags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick;
leafNodeFlags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen;
ImGuiTreeNodeFlags nodeFlags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick;
if (ImGui::BeginTabItem("Tests"))
{
int categoryIndex = 0;
const char* category = g_testEntries[categoryIndex].category;
int i = 0;
while (i < g_testCount)
{
bool categorySelected = strcmp(category, g_testEntries[s_settings.m_testIndex].category) == 0;
ImGuiTreeNodeFlags nodeSelectionFlags = categorySelected ? ImGuiTreeNodeFlags_Selected : 0;
bool nodeOpen = ImGui::TreeNodeEx(category, nodeFlags | nodeSelectionFlags);
if (nodeOpen)
{
while (i < g_testCount && strcmp(category, g_testEntries[i].category) == 0)
{
ImGuiTreeNodeFlags selectionFlags = 0;
if (s_settings.m_testIndex == i)
{
selectionFlags = ImGuiTreeNodeFlags_Selected;
}
ImGui::TreeNodeEx((void*)(intptr_t)i, leafNodeFlags | selectionFlags, "%s", g_testEntries[i].name);
if (ImGui::IsItemClicked())
{
delete s_test;
s_settings.m_testIndex = i;
s_test = g_testEntries[i].createFcn();
s_testSelection = i;
}
++i;
}
ImGui::TreePop();
}
else
{
while (i < g_testCount && strcmp(category, g_testEntries[i].category) == 0)
{
++i;
}
}
if (i < g_testCount)
{
category = g_testEntries[i].category;
categoryIndex = i;
}
}
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
ImGui::End();
s_test->UpdateUI();
}
}
//
int main(int, char**)
{
#if defined(_WIN32)
// Enable memory-leak reports
_CrtSetDbgFlag(_CRTDBG_LEAK_CHECK_DF | _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG));
#endif
char buffer[128];
s_settings.Load();
SortTests();
glfwSetErrorCallback(glfwErrorCallback);
g_camera.m_width = s_settings.m_windowWidth;
g_camera.m_height = s_settings.m_windowHeight;
if (glfwInit() == 0)
{
fprintf(stderr, "Failed to initialize GLFW\n");
return -1;
}
#if __APPLE__
const char* glslVersion = "#version 150";
#else
const char* glslVersion = NULL;
#endif
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
sprintf(buffer, "Box2D Testbed Version %d.%d.%d", b2_version.major, b2_version.minor, b2_version.revision);
bool fullscreen = false;
if (fullscreen)
{
g_mainWindow = glfwCreateWindow(1920, 1080, buffer, glfwGetPrimaryMonitor(), NULL);
}
else
{
g_mainWindow = glfwCreateWindow(g_camera.m_width, g_camera.m_height, buffer, NULL, NULL);
}
if (g_mainWindow == NULL)
{
fprintf(stderr, "Failed to open GLFW g_mainWindow.\n");
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(g_mainWindow);
// Load OpenGL functions using glad
int version = gladLoadGL(glfwGetProcAddress);
printf("GL %d.%d\n", GLAD_VERSION_MAJOR(version), GLAD_VERSION_MINOR(version));
printf("OpenGL %s, GLSL %s\n", glGetString(GL_VERSION), glGetString(GL_SHADING_LANGUAGE_VERSION));
glfwSetScrollCallback(g_mainWindow, ScrollCallback);
glfwSetWindowSizeCallback(g_mainWindow, ResizeWindowCallback);
glfwSetKeyCallback(g_mainWindow, KeyCallback);
glfwSetCharCallback(g_mainWindow, CharCallback);
glfwSetMouseButtonCallback(g_mainWindow, MouseButtonCallback);
glfwSetCursorPosCallback(g_mainWindow, MouseMotionCallback);
glfwSetScrollCallback(g_mainWindow, ScrollCallback);
g_debugDraw.Create();
CreateUI(g_mainWindow, glslVersion);
s_settings.m_testIndex = b2Clamp(s_settings.m_testIndex, 0, g_testCount - 1);
s_testSelection = s_settings.m_testIndex;
s_test = g_testEntries[s_settings.m_testIndex].createFcn();
// Control the frame rate. One draw per monitor refresh.
//glfwSwapInterval(1);
glClearColor(0.2f, 0.2f, 0.2f, 1.0f);
std::chrono::duration<double> frameTime(0.0);
std::chrono::duration<double> sleepAdjust(0.0);
while (!glfwWindowShouldClose(g_mainWindow))
{
std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
glfwGetWindowSize(g_mainWindow, &g_camera.m_width, &g_camera.m_height);
int bufferWidth, bufferHeight;
glfwGetFramebufferSize(g_mainWindow, &bufferWidth, &bufferHeight);
glViewport(0, 0, bufferWidth, bufferHeight);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
if (g_debugDraw.m_showUI)
{
ImGui::SetNextWindowPos(ImVec2(0.0f, 0.0f));
ImGui::SetNextWindowSize(ImVec2(float(g_camera.m_width), float(g_camera.m_height)));
ImGui::SetNextWindowBgAlpha(0.0f);
ImGui::Begin("Overlay", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoScrollbar);
ImGui::End();
const TestEntry& entry = g_testEntries[s_settings.m_testIndex];
sprintf(buffer, "%s : %s", entry.category, entry.name);
s_test->DrawTitle(buffer);
}
s_test->Step(s_settings);
UpdateUI();
// ImGui::ShowDemoWindow();
if (g_debugDraw.m_showUI)
{
sprintf(buffer, "%.1f ms", 1000.0 * frameTime.count());
g_debugDraw.DrawString(5, g_camera.m_height - 20, buffer);
}
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
glfwSwapBuffers(g_mainWindow);
if (s_testSelection != s_settings.m_testIndex)
{
s_settings.m_testIndex = s_testSelection;
delete s_test;
s_test = g_testEntries[s_settings.m_testIndex].createFcn();
g_camera.m_zoom = 1.0f;
g_camera.m_center.Set(0.0f, 20.0f);
}
glfwPollEvents();
// Throttle to cap at 60Hz. This adaptive using a sleep adjustment. This could be improved by
// using mm_pause or equivalent for the last millisecond.
std::chrono::steady_clock::time_point t2 = std::chrono::steady_clock::now();
std::chrono::duration<double> target(1.0 / 60.0);
std::chrono::duration<double> timeUsed = t2 - t1;
std::chrono::duration<double> sleepTime = target - timeUsed + sleepAdjust;
if (sleepTime > std::chrono::duration<double>(0))
{
std::this_thread::sleep_for(sleepTime);
}
std::chrono::steady_clock::time_point t3 = std::chrono::steady_clock::now();
frameTime = t3 - t1;
// Compute the sleep adjustment using a low pass filter
sleepAdjust = 0.9 * sleepAdjust + 0.1 * (target - frameTime);
}
delete s_test;
s_test = nullptr;
g_debugDraw.Destroy();
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
glfwTerminate();
s_settings.Save();
return 0;
}