1 // MIT License
2 
3 // Copyright (c) 2019 Erin Catto
4 
5 // Permission is hereby granted, free of charge, to any person obtaining a copy
6 // of this software and associated documentation files (the "Software"), to deal
7 // in the Software without restriction, including without limitation the rights
8 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 // copies of the Software, and to permit persons to whom the Software is
10 // furnished to do so, subject to the following conditions:
11 
12 // The above copyright notice and this permission notice shall be included in all
13 // copies or substantial portions of the Software.
14 
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 // SOFTWARE.
22 
23 #define _CRT_SECURE_NO_WARNINGS
24 #define IMGUI_DISABLE_OBSOLETE_FUNCTIONS 1
25 
26 #include "imgui/imgui.h"
27 #include "imgui_impl_glfw.h"
28 #include "imgui_impl_opengl3.h"
29 #include "draw.h"
30 #include "settings.h"
31 #include "test.h"
32 
33 #include <algorithm>
34 #include <stdio.h>
35 #include <thread>
36 #include <chrono>
37 
38 #if defined(_WIN32)
39 #include <crtdbg.h>
40 #endif
41 
42 GLFWwindow* g_mainWindow = nullptr;
43 static int32 s_testSelection = 0;
44 static Test* s_test = nullptr;
45 static Settings s_settings;
46 static bool s_rightMouseDown = false;
47 static b2Vec2 s_clickPointWS = b2Vec2_zero;
48 
glfwErrorCallback(int error,const char * description)49 void glfwErrorCallback(int error, const char* description)
50 {
51 	fprintf(stderr, "GLFW error occured. Code: %d. Description: %s\n", error, description);
52 }
53 
CompareTests(const TestEntry & a,const TestEntry & b)54 static inline bool CompareTests(const TestEntry& a, const TestEntry& b)
55 {
56 	int result = strcmp(a.category, b.category);
57 	if (result == 0)
58 	{
59 		result = strcmp(a.name, b.name);
60 	}
61 
62 	return result < 0;
63 }
64 
SortTests()65 static void SortTests()
66 {
67 	std::sort(g_testEntries, g_testEntries + g_testCount, CompareTests);
68 }
69 
CreateUI(GLFWwindow * window,const char * glslVersion=NULL)70 static void CreateUI(GLFWwindow* window, const char* glslVersion = NULL)
71 {
72 	IMGUI_CHECKVERSION();
73 	ImGui::CreateContext();
74 
75 	bool success;
76 	success = ImGui_ImplGlfw_InitForOpenGL(window, false);
77 	if (success == false)
78 	{
79 		printf("ImGui_ImplGlfw_InitForOpenGL failed\n");
80 		assert(false);
81 	}
82 
83 	success = ImGui_ImplOpenGL3_Init(glslVersion);
84 	if (success == false)
85 	{
86 		printf("ImGui_ImplOpenGL3_Init failed\n");
87 		assert(false);
88 	}
89 
90 	// Search for font file
91 	const char* fontPath1 = "data/droid_sans.ttf";
92 	const char* fontPath2 = "../data/droid_sans.ttf";
93 	const char* fontPath = nullptr;
94 	FILE* file1 = fopen(fontPath1, "rb");
95 	FILE* file2 = fopen(fontPath2, "rb");
96 	if (file1)
97 	{
98 		fontPath = fontPath1;
99 		fclose(file1);
100 	}
101 
102 	if (file2)
103 	{
104 		fontPath = fontPath2;
105 		fclose(file2);
106 	}
107 
108 	if (fontPath)
109 	{
110 		ImGui::GetIO().Fonts->AddFontFromFileTTF(fontPath, 13.0f);
111 	}
112 }
113 
ResizeWindowCallback(GLFWwindow *,int width,int height)114 static void ResizeWindowCallback(GLFWwindow*, int width, int height)
115 {
116 	g_camera.m_width = width;
117 	g_camera.m_height = height;
118 	s_settings.m_windowWidth = width;
119 	s_settings.m_windowHeight = height;
120 }
121 
KeyCallback(GLFWwindow * window,int key,int scancode,int action,int mods)122 static void KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods)
123 {
124 	ImGui_ImplGlfw_KeyCallback(window, key, scancode, action, mods);
125 	if (ImGui::GetIO().WantCaptureKeyboard)
126 	{
127 		return;
128 	}
129 
130 	if (action == GLFW_PRESS)
131 	{
132 		switch (key)
133 		{
134 		case GLFW_KEY_ESCAPE:
135 			// Quit
136 			glfwSetWindowShouldClose(g_mainWindow, GL_TRUE);
137 			break;
138 
139 		case GLFW_KEY_LEFT:
140 			// Pan left
141 			if (mods == GLFW_MOD_CONTROL)
142 			{
143 				b2Vec2 newOrigin(2.0f, 0.0f);
144 				s_test->ShiftOrigin(newOrigin);
145 			}
146 			else
147 			{
148 				g_camera.m_center.x -= 0.5f;
149 			}
150 			break;
151 
152 		case GLFW_KEY_RIGHT:
153 			// Pan right
154 			if (mods == GLFW_MOD_CONTROL)
155 			{
156 				b2Vec2 newOrigin(-2.0f, 0.0f);
157 				s_test->ShiftOrigin(newOrigin);
158 			}
159 			else
160 			{
161 				g_camera.m_center.x += 0.5f;
162 			}
163 			break;
164 
165 		case GLFW_KEY_DOWN:
166 			// Pan down
167 			if (mods == GLFW_MOD_CONTROL)
168 			{
169 				b2Vec2 newOrigin(0.0f, 2.0f);
170 				s_test->ShiftOrigin(newOrigin);
171 			}
172 			else
173 			{
174 				g_camera.m_center.y -= 0.5f;
175 			}
176 			break;
177 
178 		case GLFW_KEY_UP:
179 			// Pan up
180 			if (mods == GLFW_MOD_CONTROL)
181 			{
182 				b2Vec2 newOrigin(0.0f, -2.0f);
183 				s_test->ShiftOrigin(newOrigin);
184 			}
185 			else
186 			{
187 				g_camera.m_center.y += 0.5f;
188 			}
189 			break;
190 
191 		case GLFW_KEY_HOME:
192 			// Reset view
193 			g_camera.m_zoom = 1.0f;
194 			g_camera.m_center.Set(0.0f, 20.0f);
195 			break;
196 
197 		case GLFW_KEY_Z:
198 			// Zoom out
199 			g_camera.m_zoom = b2Min(1.1f * g_camera.m_zoom, 20.0f);
200 			break;
201 
202 		case GLFW_KEY_X:
203 			// Zoom in
204 			g_camera.m_zoom = b2Max(0.9f * g_camera.m_zoom, 0.02f);
205 			break;
206 
207 		case GLFW_KEY_R:
208 			// Reset test
209 			delete s_test;
210 			s_test = g_testEntries[s_settings.m_testIndex].createFcn();
211 			break;
212 
213 		case GLFW_KEY_SPACE:
214 			// Launch a bomb.
215 			if (s_test)
216 			{
217 				s_test->LaunchBomb();
218 			}
219 			break;
220 
221 		case GLFW_KEY_O:
222 			s_settings.m_singleStep = true;
223 			break;
224 
225 		case GLFW_KEY_P:
226 			s_settings.m_pause = !s_settings.m_pause;
227 			break;
228 
229 		case GLFW_KEY_LEFT_BRACKET:
230 			// Switch to previous test
231 			--s_testSelection;
232 			if (s_testSelection < 0)
233 			{
234 				s_testSelection = g_testCount - 1;
235 			}
236 			break;
237 
238 		case GLFW_KEY_RIGHT_BRACKET:
239 			// Switch to next test
240 			++s_testSelection;
241 			if (s_testSelection == g_testCount)
242 			{
243 				s_testSelection = 0;
244 			}
245 			break;
246 
247 		case GLFW_KEY_TAB:
248 			g_debugDraw.m_showUI = !g_debugDraw.m_showUI;
249 
250 		default:
251 			if (s_test)
252 			{
253 				s_test->Keyboard(key);
254 			}
255 		}
256 	}
257 	else if (action == GLFW_RELEASE)
258 	{
259 		s_test->KeyboardUp(key);
260 	}
261 	// else GLFW_REPEAT
262 }
263 
CharCallback(GLFWwindow * window,unsigned int c)264 static void CharCallback(GLFWwindow* window, unsigned int c)
265 {
266 	ImGui_ImplGlfw_CharCallback(window, c);
267 }
268 
MouseButtonCallback(GLFWwindow * window,int32 button,int32 action,int32 mods)269 static void MouseButtonCallback(GLFWwindow* window, int32 button, int32 action, int32 mods)
270 {
271 	ImGui_ImplGlfw_MouseButtonCallback(window, button, action, mods);
272 
273 	double xd, yd;
274 	glfwGetCursorPos(g_mainWindow, &xd, &yd);
275 	b2Vec2 ps((float)xd, (float)yd);
276 
277 	// Use the mouse to move things around.
278 	if (button == GLFW_MOUSE_BUTTON_1)
279 	{
280         //<##>
281         //ps.Set(0, 0);
282 		b2Vec2 pw = g_camera.ConvertScreenToWorld(ps);
283 		if (action == GLFW_PRESS)
284 		{
285 			if (mods == GLFW_MOD_SHIFT)
286 			{
287 				s_test->ShiftMouseDown(pw);
288 			}
289 			else
290 			{
291 				s_test->MouseDown(pw);
292 			}
293 		}
294 
295 		if (action == GLFW_RELEASE)
296 		{
297 			s_test->MouseUp(pw);
298 		}
299 	}
300 	else if (button == GLFW_MOUSE_BUTTON_2)
301 	{
302 		if (action == GLFW_PRESS)
303 		{
304 			s_clickPointWS = g_camera.ConvertScreenToWorld(ps);
305 			s_rightMouseDown = true;
306 		}
307 
308 		if (action == GLFW_RELEASE)
309 		{
310 			s_rightMouseDown = false;
311 		}
312 	}
313 }
314 
MouseMotionCallback(GLFWwindow *,double xd,double yd)315 static void MouseMotionCallback(GLFWwindow*, double xd, double yd)
316 {
317 	b2Vec2 ps((float)xd, (float)yd);
318 
319 	b2Vec2 pw = g_camera.ConvertScreenToWorld(ps);
320 	s_test->MouseMove(pw);
321 
322 	if (s_rightMouseDown)
323 	{
324 		b2Vec2 diff = pw - s_clickPointWS;
325 		g_camera.m_center.x -= diff.x;
326 		g_camera.m_center.y -= diff.y;
327 		s_clickPointWS = g_camera.ConvertScreenToWorld(ps);
328 	}
329 }
330 
ScrollCallback(GLFWwindow * window,double dx,double dy)331 static void ScrollCallback(GLFWwindow* window, double dx, double dy)
332 {
333 	ImGui_ImplGlfw_ScrollCallback(window, dx, dy);
334 	if (ImGui::GetIO().WantCaptureMouse)
335 	{
336 		return;
337 	}
338 
339 	if (dy > 0)
340 	{
341 		g_camera.m_zoom /= 1.1f;
342 	}
343 	else
344 	{
345 		g_camera.m_zoom *= 1.1f;
346 	}
347 }
348 
RestartTest()349 static void RestartTest()
350 {
351 	delete s_test;
352 	s_test = g_testEntries[s_settings.m_testIndex].createFcn();
353 }
354 
UpdateUI()355 static void UpdateUI()
356 {
357 	int menuWidth = 180;
358 	if (g_debugDraw.m_showUI)
359 	{
360 		ImGui::SetNextWindowPos(ImVec2((float)g_camera.m_width - menuWidth - 10, 10));
361 		ImGui::SetNextWindowSize(ImVec2((float)menuWidth, (float)g_camera.m_height - 20));
362 
363 		ImGui::Begin("Tools", &g_debugDraw.m_showUI, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
364 
365 		if (ImGui::BeginTabBar("ControlTabs", ImGuiTabBarFlags_None))
366 		{
367 			if (ImGui::BeginTabItem("Controls"))
368 			{
369 				ImGui::SliderInt("Vel Iters", &s_settings.m_velocityIterations, 0, 50);
370 				ImGui::SliderInt("Pos Iters", &s_settings.m_positionIterations, 0, 50);
371 				ImGui::SliderFloat("Hertz", &s_settings.m_hertz, 5.0f, 120.0f, "%.0f hz");
372 
373 				ImGui::Separator();
374 
375 				ImGui::Checkbox("Sleep", &s_settings.m_enableSleep);
376 				ImGui::Checkbox("Warm Starting", &s_settings.m_enableWarmStarting);
377 				ImGui::Checkbox("Time of Impact", &s_settings.m_enableContinuous);
378 				ImGui::Checkbox("Sub-Stepping", &s_settings.m_enableSubStepping);
379 
380 				ImGui::Separator();
381 
382 				ImGui::Checkbox("Shapes", &s_settings.m_drawShapes);
383 				ImGui::Checkbox("Joints", &s_settings.m_drawJoints);
384 				ImGui::Checkbox("AABBs", &s_settings.m_drawAABBs);
385 				ImGui::Checkbox("Contact Points", &s_settings.m_drawContactPoints);
386 				ImGui::Checkbox("Contact Normals", &s_settings.m_drawContactNormals);
387 				ImGui::Checkbox("Contact Impulses", &s_settings.m_drawContactImpulse);
388 				ImGui::Checkbox("Friction Impulses", &s_settings.m_drawFrictionImpulse);
389 				ImGui::Checkbox("Center of Masses", &s_settings.m_drawCOMs);
390 				ImGui::Checkbox("Statistics", &s_settings.m_drawStats);
391 				ImGui::Checkbox("Profile", &s_settings.m_drawProfile);
392 
393 				ImVec2 button_sz = ImVec2(-1, 0);
394 				if (ImGui::Button("Pause (P)", button_sz))
395 				{
396 					s_settings.m_pause = !s_settings.m_pause;
397 				}
398 
399 				if (ImGui::Button("Single Step (O)", button_sz))
400 				{
401 					s_settings.m_singleStep = !s_settings.m_singleStep;
402 				}
403 
404 				if (ImGui::Button("Restart (R)", button_sz))
405 				{
406 					RestartTest();
407 				}
408 
409 				if (ImGui::Button("Quit", button_sz))
410 				{
411 					glfwSetWindowShouldClose(g_mainWindow, GL_TRUE);
412 				}
413 
414 				ImGui::EndTabItem();
415 			}
416 
417 			ImGuiTreeNodeFlags leafNodeFlags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick;
418 			leafNodeFlags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen;
419 
420 			ImGuiTreeNodeFlags nodeFlags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick;
421 
422 			if (ImGui::BeginTabItem("Tests"))
423 			{
424 				int categoryIndex = 0;
425 				const char* category = g_testEntries[categoryIndex].category;
426 				int i = 0;
427 				while (i < g_testCount)
428 				{
429 					bool categorySelected = strcmp(category, g_testEntries[s_settings.m_testIndex].category) == 0;
430 					ImGuiTreeNodeFlags nodeSelectionFlags = categorySelected ? ImGuiTreeNodeFlags_Selected : 0;
431 					bool nodeOpen = ImGui::TreeNodeEx(category, nodeFlags | nodeSelectionFlags);
432 
433 					if (nodeOpen)
434 					{
435 						while (i < g_testCount && strcmp(category, g_testEntries[i].category) == 0)
436 						{
437 							ImGuiTreeNodeFlags selectionFlags = 0;
438 							if (s_settings.m_testIndex == i)
439 							{
440 								selectionFlags = ImGuiTreeNodeFlags_Selected;
441 							}
442 							ImGui::TreeNodeEx((void*)(intptr_t)i, leafNodeFlags | selectionFlags, "%s", g_testEntries[i].name);
443 							if (ImGui::IsItemClicked())
444 							{
445 								delete s_test;
446 								s_settings.m_testIndex = i;
447 								s_test = g_testEntries[i].createFcn();
448 								s_testSelection = i;
449 							}
450 							++i;
451 						}
452 						ImGui::TreePop();
453 					}
454 					else
455 					{
456 						while (i < g_testCount && strcmp(category, g_testEntries[i].category) == 0)
457 						{
458 							++i;
459 						}
460 					}
461 
462 					if (i < g_testCount)
463 					{
464 						category = g_testEntries[i].category;
465 						categoryIndex = i;
466 					}
467 				}
468 				ImGui::EndTabItem();
469 			}
470 			ImGui::EndTabBar();
471 		}
472 
473 		ImGui::End();
474 
475 		s_test->UpdateUI();
476 	}
477 }
478 
479 //
main(int,char **)480 int main(int, char**)
481 {
482 #if defined(_WIN32)
483 	// Enable memory-leak reports
484 	_CrtSetDbgFlag(_CRTDBG_LEAK_CHECK_DF | _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG));
485 #endif
486 
487 	char buffer[128];
488 
489 	s_settings.Load();
490 	SortTests();
491 
492 	glfwSetErrorCallback(glfwErrorCallback);
493 
494 	g_camera.m_width = s_settings.m_windowWidth;
495 	g_camera.m_height = s_settings.m_windowHeight;
496 
497 	if (glfwInit() == 0)
498 	{
499 		fprintf(stderr, "Failed to initialize GLFW\n");
500 		return -1;
501 	}
502 
503 #if __APPLE__
504     const char* glslVersion = "#version 150";
505 #else
506     const char* glslVersion = NULL;
507 #endif
508 
509 	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
510 	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
511 	glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
512 	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
513 
514 	sprintf(buffer, "Box2D Testbed Version %d.%d.%d", b2_version.major, b2_version.minor, b2_version.revision);
515 
516 	bool fullscreen = false;
517 	if (fullscreen)
518 	{
519 		g_mainWindow = glfwCreateWindow(1920, 1080, buffer, glfwGetPrimaryMonitor(), NULL);
520 	}
521 	else
522 	{
523 		g_mainWindow = glfwCreateWindow(g_camera.m_width, g_camera.m_height, buffer, NULL, NULL);
524 	}
525 
526 	if (g_mainWindow == NULL)
527 	{
528 		fprintf(stderr, "Failed to open GLFW g_mainWindow.\n");
529 		glfwTerminate();
530 		return -1;
531 	}
532 
533 	glfwMakeContextCurrent(g_mainWindow);
534 
535 	// Load OpenGL functions using glad
536 	int version = gladLoadGL(glfwGetProcAddress);
537 	printf("GL %d.%d\n", GLAD_VERSION_MAJOR(version), GLAD_VERSION_MINOR(version));
538 	printf("OpenGL %s, GLSL %s\n", glGetString(GL_VERSION), glGetString(GL_SHADING_LANGUAGE_VERSION));
539 
540 	glfwSetScrollCallback(g_mainWindow, ScrollCallback);
541 	glfwSetWindowSizeCallback(g_mainWindow, ResizeWindowCallback);
542 	glfwSetKeyCallback(g_mainWindow, KeyCallback);
543 	glfwSetCharCallback(g_mainWindow, CharCallback);
544 	glfwSetMouseButtonCallback(g_mainWindow, MouseButtonCallback);
545 	glfwSetCursorPosCallback(g_mainWindow, MouseMotionCallback);
546 	glfwSetScrollCallback(g_mainWindow, ScrollCallback);
547 
548 	g_debugDraw.Create();
549 
550 	CreateUI(g_mainWindow, glslVersion);
551 
552 	s_settings.m_testIndex = b2Clamp(s_settings.m_testIndex, 0, g_testCount - 1);
553 	s_testSelection = s_settings.m_testIndex;
554 	s_test = g_testEntries[s_settings.m_testIndex].createFcn();
555 
556 	// Control the frame rate. One draw per monitor refresh.
557 	//glfwSwapInterval(1);
558 
559 	glClearColor(0.2f, 0.2f, 0.2f, 1.0f);
560 
561 	std::chrono::duration<double> frameTime(0.0);
562 	std::chrono::duration<double> sleepAdjust(0.0);
563 
564 	while (!glfwWindowShouldClose(g_mainWindow))
565 	{
566 		std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
567 
568 		glfwGetWindowSize(g_mainWindow, &g_camera.m_width, &g_camera.m_height);
569 
570         int bufferWidth, bufferHeight;
571         glfwGetFramebufferSize(g_mainWindow, &bufferWidth, &bufferHeight);
572         glViewport(0, 0, bufferWidth, bufferHeight);
573 
574 		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
575 
576 		ImGui_ImplOpenGL3_NewFrame();
577 		ImGui_ImplGlfw_NewFrame();
578 
579 		ImGui::NewFrame();
580 
581 		if (g_debugDraw.m_showUI)
582 		{
583 			ImGui::SetNextWindowPos(ImVec2(0.0f, 0.0f));
584 			ImGui::SetNextWindowSize(ImVec2(float(g_camera.m_width), float(g_camera.m_height)));
585 			ImGui::SetNextWindowBgAlpha(0.0f);
586 			ImGui::Begin("Overlay", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoScrollbar);
587 			ImGui::End();
588 
589 			const TestEntry& entry = g_testEntries[s_settings.m_testIndex];
590 			sprintf(buffer, "%s : %s", entry.category, entry.name);
591 			s_test->DrawTitle(buffer);
592 		}
593 
594 		s_test->Step(s_settings);
595 
596 		UpdateUI();
597 
598 		// ImGui::ShowDemoWindow();
599 
600 		if (g_debugDraw.m_showUI)
601 		{
602 			sprintf(buffer, "%.1f ms", 1000.0 * frameTime.count());
603 			g_debugDraw.DrawString(5, g_camera.m_height - 20, buffer);
604 		}
605 
606 		ImGui::Render();
607 		ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
608 
609 		glfwSwapBuffers(g_mainWindow);
610 
611 		if (s_testSelection != s_settings.m_testIndex)
612 		{
613 			s_settings.m_testIndex = s_testSelection;
614 			delete s_test;
615 			s_test = g_testEntries[s_settings.m_testIndex].createFcn();
616 			g_camera.m_zoom = 1.0f;
617 			g_camera.m_center.Set(0.0f, 20.0f);
618 		}
619 
620 		glfwPollEvents();
621 
622 		// Throttle to cap at 60Hz. This adaptive using a sleep adjustment. This could be improved by
623 		// using mm_pause or equivalent for the last millisecond.
624 		std::chrono::steady_clock::time_point t2 = std::chrono::steady_clock::now();
625 		std::chrono::duration<double> target(1.0 / 60.0);
626 		std::chrono::duration<double> timeUsed = t2 - t1;
627 		std::chrono::duration<double> sleepTime = target - timeUsed + sleepAdjust;
628 		if (sleepTime > std::chrono::duration<double>(0))
629 		{
630 			std::this_thread::sleep_for(sleepTime);
631 		}
632 
633 		std::chrono::steady_clock::time_point t3 = std::chrono::steady_clock::now();
634 		frameTime = t3 - t1;
635 
636 		// Compute the sleep adjustment using a low pass filter
637 		sleepAdjust = 0.9 * sleepAdjust + 0.1 * (target - frameTime);
638 	}
639 
640 	delete s_test;
641 	s_test = nullptr;
642 
643 	g_debugDraw.Destroy();
644 	ImGui_ImplOpenGL3_Shutdown();
645 	ImGui_ImplGlfw_Shutdown();
646 	glfwTerminate();
647 
648 	s_settings.Save();
649 
650 	return 0;
651 }
652