1 //
2 // Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
3 //
4 // This software is provided 'as-is', without any express or implied
5 // warranty.  In no event will the authors be held liable for any damages
6 // arising from the use of this software.
7 // Permission is granted to anyone to use this software for any purpose,
8 // including commercial applications, and to alter it and redistribute it
9 // freely, subject to the following restrictions:
10 // 1. The origin of this software must not be misrepresented; you must not
11 //    claim that you wrote the original software. If you use this software
12 //    in a product, an acknowledgment in the product documentation would be
13 //    appreciated but is not required.
14 // 2. Altered source versions must be plainly marked as such, and must not be
15 //    misrepresented as being the original software.
16 // 3. This notice may not be removed or altered from any source distribution.
17 //
18 
19 #include <cstdio>
20 #define _USE_MATH_DEFINES
21 #include <cmath>
22 
23 #include "SDL.h"
24 #include "SDL_opengl.h"
25 #ifdef __APPLE__
26 #	include <OpenGL/glu.h>
27 #else
28 #	include <GL/glu.h>
29 #endif
30 
31 #include <vector>
32 #include <string>
33 
34 #include "imgui.h"
35 #include "imguiRenderGL.h"
36 
37 #include "Recast.h"
38 #include "RecastDebugDraw.h"
39 #include "InputGeom.h"
40 #include "TestCase.h"
41 #include "Filelist.h"
42 #include "Sample_SoloMesh.h"
43 #include "Sample_TileMesh.h"
44 #include "Sample_TempObstacles.h"
45 #include "Sample_Debug.h"
46 
47 #ifdef WIN32
48 #	define snprintf _snprintf
49 #	define putenv _putenv
50 #endif
51 
52 using std::string;
53 using std::vector;
54 
55 struct SampleItem
56 {
57 	Sample* (*create)();
58 	const string name;
59 };
createSolo()60 Sample* createSolo() { return new Sample_SoloMesh(); }
createTile()61 Sample* createTile() { return new Sample_TileMesh(); }
createTempObstacle()62 Sample* createTempObstacle() { return new Sample_TempObstacles(); }
createDebug()63 Sample* createDebug() { return new Sample_Debug(); }
64 static SampleItem g_samples[] =
65 {
66 	{ createSolo, "Solo Mesh" },
67 	{ createTile, "Tile Mesh" },
68 	{ createTempObstacle, "Temp Obstacles" },
69 };
70 static const int g_nsamples = sizeof(g_samples) / sizeof(SampleItem);
71 
main(int,char **)72 int main(int /*argc*/, char** /*argv*/)
73 {
74 	// Init SDL
75 	if (SDL_Init(SDL_INIT_EVERYTHING) != 0)
76 	{
77 		printf("Could not initialise SDL.\nError: %s\n", SDL_GetError());
78 		return -1;
79 	}
80 
81 	// Enable depth buffer.
82 	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
83 	SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
84 
85 	// Set color channel depth.
86 	SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
87 	SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
88 	SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
89 	SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
90 
91 	// 4x MSAA.
92 	SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
93 	SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4);
94 
95 	SDL_DisplayMode displayMode;
96 	SDL_GetCurrentDisplayMode(0, &displayMode);
97 
98 	bool presentationMode = false;
99 	Uint32 flags = SDL_WINDOW_OPENGL;
100 	int width;
101 	int height;
102 	if (presentationMode)
103 	{
104 		// Create a fullscreen window at the native resolution.
105 		width = displayMode.w;
106 		height = displayMode.h;
107 		flags |= SDL_WINDOW_FULLSCREEN;
108 	}
109 	else
110 	{
111 		float aspect = 16.0f / 9.0f;
112 		width = rcMin(displayMode.w, (int)(displayMode.h * aspect)) - 80;
113 		height = displayMode.h - 80;
114 	}
115 
116 	SDL_Window* window;
117 	SDL_Renderer* renderer;
118 	int errorCode = SDL_CreateWindowAndRenderer(width, height, flags, &window, &renderer);
119 
120 	if (errorCode != 0 || !window || !renderer)
121 	{
122 		printf("Could not initialise SDL opengl\nError: %s\n", SDL_GetError());
123 		return -1;
124 	}
125 
126 	SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
127 	SDL_GL_CreateContext(window);
128 
129 	if (!imguiRenderGLInit("DroidSans.ttf"))
130 	{
131 		printf("Could not init GUI renderer.\n");
132 		SDL_Quit();
133 		return -1;
134 	}
135 
136 	float t = 0.0f;
137 	float timeAcc = 0.0f;
138 	Uint32 prevFrameTime = SDL_GetTicks();
139 	int mousePos[2] = {0, 0};
140 	int origMousePos[2] = {0, 0}; // Used to compute mouse movement totals across frames.
141 
142 	float cameraEulers[] = {45, -45};
143 	float cameraPos[] = {0, 0, 0};
144 	float camr = 1000;
145 	float origCameraEulers[] = {0, 0}; // Used to compute rotational changes across frames.
146 
147 	float moveFront = 0.0f, moveBack = 0.0f, moveLeft = 0.0f, moveRight = 0.0f, moveUp = 0.0f, moveDown = 0.0f;
148 
149 	float scrollZoom = 0;
150 	bool rotate = false;
151 	bool movedDuringRotate = false;
152 	float rayStart[3];
153 	float rayEnd[3];
154 	bool mouseOverMenu = false;
155 
156 	bool showMenu = !presentationMode;
157 	bool showLog = false;
158 	bool showTools = true;
159 	bool showLevels = false;
160 	bool showSample = false;
161 	bool showTestCases = false;
162 
163 	// Window scroll positions.
164 	int propScroll = 0;
165 	int logScroll = 0;
166 	int toolsScroll = 0;
167 
168 	string sampleName = "Choose Sample...";
169 
170 	vector<string> files;
171 	const string meshesFolder = "Meshes";
172 	string meshName = "Choose Mesh...";
173 
174 	float markerPosition[3] = {0, 0, 0};
175 	bool markerPositionSet = false;
176 
177 	InputGeom* geom = 0;
178 	Sample* sample = 0;
179 
180 	const string testCasesFolder = "TestCases";
181 	TestCase* test = 0;
182 
183 	BuildContext ctx;
184 
185 	// Fog.
186 	float fogColor[4] = { 0.32f, 0.31f, 0.30f, 1.0f };
187 	glEnable(GL_FOG);
188 	glFogi(GL_FOG_MODE, GL_LINEAR);
189 	glFogf(GL_FOG_START, camr * 0.1f);
190 	glFogf(GL_FOG_END, camr * 1.25f);
191 	glFogfv(GL_FOG_COLOR, fogColor);
192 
193 	glEnable(GL_CULL_FACE);
194 	glDepthFunc(GL_LEQUAL);
195 
196 	bool done = false;
197 	while(!done)
198 	{
199 		// Handle input events.
200 		int mouseScroll = 0;
201 		bool processHitTest = false;
202 		bool processHitTestShift = false;
203 		SDL_Event event;
204 
205 		while (SDL_PollEvent(&event))
206 		{
207 			switch (event.type)
208 			{
209 				case SDL_KEYDOWN:
210 					// Handle any key presses here.
211 					if (event.key.keysym.sym == SDLK_ESCAPE)
212 					{
213 						done = true;
214 					}
215 					else if (event.key.keysym.sym == SDLK_t)
216 					{
217 						showLevels = false;
218 						showSample = false;
219 						showTestCases = true;
220 						scanDirectory(testCasesFolder, ".txt", files);
221 					}
222 					else if (event.key.keysym.sym == SDLK_TAB)
223 					{
224 						showMenu = !showMenu;
225 					}
226 					else if (event.key.keysym.sym == SDLK_SPACE)
227 					{
228 						if (sample)
229 							sample->handleToggle();
230 					}
231 					else if (event.key.keysym.sym == SDLK_1)
232 					{
233 						if (sample)
234 							sample->handleStep();
235 					}
236 					else if (event.key.keysym.sym == SDLK_9)
237 					{
238 						if (sample && geom)
239 						{
240 							string savePath = meshesFolder + "/";
241 							BuildSettings settings;
242 							memset(&settings, 0, sizeof(settings));
243 
244 							rcVcopy(settings.navMeshBMin, geom->getNavMeshBoundsMin());
245 							rcVcopy(settings.navMeshBMax, geom->getNavMeshBoundsMax());
246 
247 							sample->collectSettings(settings);
248 
249 							geom->saveGeomSet(&settings);
250 						}
251 					}
252 					break;
253 
254 				case SDL_MOUSEWHEEL:
255 					if (event.wheel.y < 0)
256 					{
257 						// wheel down
258 						if (mouseOverMenu)
259 						{
260 							mouseScroll++;
261 						}
262 						else
263 						{
264 							scrollZoom += 1.0f;
265 						}
266 					}
267 					else
268 					{
269 						if (mouseOverMenu)
270 						{
271 							mouseScroll--;
272 						}
273 						else
274 						{
275 							scrollZoom -= 1.0f;
276 						}
277 					}
278 					break;
279 				case SDL_MOUSEBUTTONDOWN:
280 					if (event.button.button == SDL_BUTTON_RIGHT)
281 					{
282 						if (!mouseOverMenu)
283 						{
284 							// Rotate view
285 							rotate = true;
286 							movedDuringRotate = false;
287 							origMousePos[0] = mousePos[0];
288 							origMousePos[1] = mousePos[1];
289 							origCameraEulers[0] = cameraEulers[0];
290 							origCameraEulers[1] = cameraEulers[1];
291 						}
292 					}
293 					break;
294 
295 				case SDL_MOUSEBUTTONUP:
296 					// Handle mouse clicks here.
297 					if (event.button.button == SDL_BUTTON_RIGHT)
298 					{
299 						rotate = false;
300 						if (!mouseOverMenu)
301 						{
302 							if (!movedDuringRotate)
303 							{
304 								processHitTest = true;
305 								processHitTestShift = true;
306 							}
307 						}
308 					}
309 					else if (event.button.button == SDL_BUTTON_LEFT)
310 					{
311 						if (!mouseOverMenu)
312 						{
313 							processHitTest = true;
314 							processHitTestShift = (SDL_GetModState() & KMOD_SHIFT) ? true : false;
315 						}
316 					}
317 
318 					break;
319 
320 				case SDL_MOUSEMOTION:
321 					mousePos[0] = event.motion.x;
322 					mousePos[1] = height-1 - event.motion.y;
323 
324 					if (rotate)
325 					{
326 						int dx = mousePos[0] - origMousePos[0];
327 						int dy = mousePos[1] - origMousePos[1];
328 						cameraEulers[0] = origCameraEulers[0] - dy * 0.25f;
329 						cameraEulers[1] = origCameraEulers[1] + dx * 0.25f;
330 						if (dx * dx + dy * dy > 3 * 3)
331 						{
332 							movedDuringRotate = true;
333 						}
334 					}
335 					break;
336 
337 				case SDL_QUIT:
338 					done = true;
339 					break;
340 
341 				default:
342 					break;
343 			}
344 		}
345 
346 		unsigned char mouseButtonMask = 0;
347 		if (SDL_GetMouseState(0, 0) & SDL_BUTTON_LMASK)
348 			mouseButtonMask |= IMGUI_MBUT_LEFT;
349 		if (SDL_GetMouseState(0, 0) & SDL_BUTTON_RMASK)
350 			mouseButtonMask |= IMGUI_MBUT_RIGHT;
351 
352 		Uint32 time = SDL_GetTicks();
353 		float dt = (time - prevFrameTime) / 1000.0f;
354 		prevFrameTime = time;
355 
356 		t += dt;
357 
358 		// Hit test mesh.
359 		if (processHitTest && geom && sample)
360 		{
361 			float hitTime;
362 			bool hit = geom->raycastMesh(rayStart, rayEnd, hitTime);
363 
364 			if (hit)
365 			{
366 				if (SDL_GetModState() & KMOD_CTRL)
367 				{
368 					// Marker
369 					markerPositionSet = true;
370 					markerPosition[0] = rayStart[0] + (rayEnd[0] - rayStart[0]) * hitTime;
371 					markerPosition[1] = rayStart[1] + (rayEnd[1] - rayStart[1]) * hitTime;
372 					markerPosition[2] = rayStart[2] + (rayEnd[2] - rayStart[2]) * hitTime;
373 				}
374 				else
375 				{
376 					float pos[3];
377 					pos[0] = rayStart[0] + (rayEnd[0] - rayStart[0]) * hitTime;
378 					pos[1] = rayStart[1] + (rayEnd[1] - rayStart[1]) * hitTime;
379 					pos[2] = rayStart[2] + (rayEnd[2] - rayStart[2]) * hitTime;
380 					sample->handleClick(rayStart, pos, processHitTestShift);
381 				}
382 			}
383 			else
384 			{
385 				if (SDL_GetModState() & KMOD_CTRL)
386 				{
387 					// Marker
388 					markerPositionSet = false;
389 				}
390 			}
391 		}
392 
393 		// Update sample simulation.
394 		const float SIM_RATE = 20;
395 		const float DELTA_TIME = 1.0f / SIM_RATE;
396 		timeAcc = rcClamp(timeAcc + dt, -1.0f, 1.0f);
397 		int simIter = 0;
398 		while (timeAcc > DELTA_TIME)
399 		{
400 			timeAcc -= DELTA_TIME;
401 			if (simIter < 5 && sample)
402 			{
403 				sample->handleUpdate(DELTA_TIME);
404 			}
405 			simIter++;
406 		}
407 
408 		// Clamp the framerate so that we do not hog all the CPU.
409 		const float MIN_FRAME_TIME = 1.0f / 40.0f;
410 		if (dt < MIN_FRAME_TIME)
411 		{
412 			int ms = (int)((MIN_FRAME_TIME - dt) * 1000.0f);
413 			if (ms > 10) ms = 10;
414 			if (ms >= 0) SDL_Delay(ms);
415 		}
416 
417 		// Set the viewport.
418 		glViewport(0, 0, width, height);
419 		GLint viewport[4];
420 		glGetIntegerv(GL_VIEWPORT, viewport);
421 
422 		// Clear the screen
423 		glClearColor(0.3f, 0.3f, 0.32f, 1.0f);
424 		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
425 		glEnable(GL_BLEND);
426 		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
427 		glDisable(GL_TEXTURE_2D);
428 		glEnable(GL_DEPTH_TEST);
429 
430 		// Compute the projection matrix.
431 		glMatrixMode(GL_PROJECTION);
432 		glLoadIdentity();
433 		gluPerspective(50.0f, (float)width/(float)height, 1.0f, camr);
434 		GLdouble projectionMatrix[16];
435 		glGetDoublev(GL_PROJECTION_MATRIX, projectionMatrix);
436 
437 		// Compute the modelview matrix.
438 		glMatrixMode(GL_MODELVIEW);
439 		glLoadIdentity();
440 		glRotatef(cameraEulers[0], 1, 0, 0);
441 		glRotatef(cameraEulers[1], 0, 1, 0);
442 		glTranslatef(-cameraPos[0], -cameraPos[1], -cameraPos[2]);
443 		GLdouble modelviewMatrix[16];
444 		glGetDoublev(GL_MODELVIEW_MATRIX, modelviewMatrix);
445 
446 		// Get hit ray position and direction.
447 		GLdouble x, y, z;
448 		gluUnProject(mousePos[0], mousePos[1], 0.0f, modelviewMatrix, projectionMatrix, viewport, &x, &y, &z);
449 		rayStart[0] = (float)x;
450 		rayStart[1] = (float)y;
451 		rayStart[2] = (float)z;
452 		gluUnProject(mousePos[0], mousePos[1], 1.0f, modelviewMatrix, projectionMatrix, viewport, &x, &y, &z);
453 		rayEnd[0] = (float)x;
454 		rayEnd[1] = (float)y;
455 		rayEnd[2] = (float)z;
456 
457 		// Handle keyboard movement.
458 		const Uint8* keystate = SDL_GetKeyboardState(NULL);
459 		moveFront	= rcClamp(moveFront	+ dt * 4 * ((keystate[SDL_SCANCODE_W] || keystate[SDL_SCANCODE_UP		]) ? 1 : -1), 0.0f, 1.0f);
460 		moveLeft	= rcClamp(moveLeft	+ dt * 4 * ((keystate[SDL_SCANCODE_A] || keystate[SDL_SCANCODE_LEFT		]) ? 1 : -1), 0.0f, 1.0f);
461 		moveBack	= rcClamp(moveBack	+ dt * 4 * ((keystate[SDL_SCANCODE_S] || keystate[SDL_SCANCODE_DOWN		]) ? 1 : -1), 0.0f, 1.0f);
462 		moveRight	= rcClamp(moveRight	+ dt * 4 * ((keystate[SDL_SCANCODE_D] || keystate[SDL_SCANCODE_RIGHT	]) ? 1 : -1), 0.0f, 1.0f);
463 		moveUp		= rcClamp(moveUp	+ dt * 4 * ((keystate[SDL_SCANCODE_Q] || keystate[SDL_SCANCODE_PAGEUP	]) ? 1 : -1), 0.0f, 1.0f);
464 		moveDown	= rcClamp(moveDown	+ dt * 4 * ((keystate[SDL_SCANCODE_E] || keystate[SDL_SCANCODE_PAGEDOWN	]) ? 1 : -1), 0.0f, 1.0f);
465 
466 		float keybSpeed = 22.0f;
467 		if (SDL_GetModState() & KMOD_SHIFT)
468 		{
469 			keybSpeed *= 4.0f;
470 		}
471 
472 		float movex = (moveRight - moveLeft) * keybSpeed * dt;
473 		float movey = (moveBack - moveFront) * keybSpeed * dt + scrollZoom * 2.0f;
474 		scrollZoom = 0;
475 
476 		cameraPos[0] += movex * (float)modelviewMatrix[0];
477 		cameraPos[1] += movex * (float)modelviewMatrix[4];
478 		cameraPos[2] += movex * (float)modelviewMatrix[8];
479 
480 		cameraPos[0] += movey * (float)modelviewMatrix[2];
481 		cameraPos[1] += movey * (float)modelviewMatrix[6];
482 		cameraPos[2] += movey * (float)modelviewMatrix[10];
483 
484 		cameraPos[1] += (moveUp - moveDown) * keybSpeed * dt;
485 
486 		glEnable(GL_FOG);
487 
488 		if (sample)
489 			sample->handleRender();
490 		if (test)
491 			test->handleRender();
492 
493 		glDisable(GL_FOG);
494 
495 		// Render GUI
496 		glDisable(GL_DEPTH_TEST);
497 		glMatrixMode(GL_PROJECTION);
498 		glLoadIdentity();
499 		gluOrtho2D(0, width, 0, height);
500 		glMatrixMode(GL_MODELVIEW);
501 		glLoadIdentity();
502 
503 		mouseOverMenu = false;
504 
505 		imguiBeginFrame(mousePos[0], mousePos[1], mouseButtonMask, mouseScroll);
506 
507 		if (sample)
508 		{
509 			sample->handleRenderOverlay((double*)projectionMatrix, (double*)modelviewMatrix, (int*)viewport);
510 		}
511 		if (test)
512 		{
513 			if (test->handleRenderOverlay((double*)projectionMatrix, (double*)modelviewMatrix, (int*)viewport))
514 				mouseOverMenu = true;
515 		}
516 
517 		// Help text.
518 		if (showMenu)
519 		{
520 			const char msg[] = "W/S/A/D: Move  RMB: Rotate";
521 			imguiDrawText(280, height-20, IMGUI_ALIGN_LEFT, msg, imguiRGBA(255,255,255,128));
522 		}
523 
524 		if (showMenu)
525 		{
526 			if (imguiBeginScrollArea("Properties", width-250-10, 10, 250, height-20, &propScroll))
527 				mouseOverMenu = true;
528 
529 			if (imguiCheck("Show Log", showLog))
530 				showLog = !showLog;
531 			if (imguiCheck("Show Tools", showTools))
532 				showTools = !showTools;
533 
534 			imguiSeparator();
535 			imguiLabel("Sample");
536 			if (imguiButton(sampleName.c_str()))
537 			{
538 				if (showSample)
539 				{
540 					showSample = false;
541 				}
542 				else
543 				{
544 					showSample = true;
545 					showLevels = false;
546 					showTestCases = false;
547 				}
548 			}
549 
550 			imguiSeparator();
551 			imguiLabel("Input Mesh");
552 			if (imguiButton(meshName.c_str()))
553 			{
554 				if (showLevels)
555 				{
556 					showLevels = false;
557 				}
558 				else
559 				{
560 					showSample = false;
561 					showTestCases = false;
562 					showLevels = true;
563 					scanDirectory(meshesFolder, ".obj", files);
564 					scanDirectoryAppend(meshesFolder, ".gset", files);
565 				}
566 			}
567 			if (geom)
568 			{
569 				char text[64];
570 				snprintf(text, 64, "Verts: %.1fk  Tris: %.1fk",
571 						 geom->getMesh()->getVertCount()/1000.0f,
572 						 geom->getMesh()->getTriCount()/1000.0f);
573 				imguiValue(text);
574 			}
575 			imguiSeparator();
576 
577 			if (geom && sample)
578 			{
579 				imguiSeparatorLine();
580 
581 				sample->handleSettings();
582 
583 				if (imguiButton("Build"))
584 				{
585 					ctx.resetLog();
586 					if (!sample->handleBuild())
587 					{
588 						showLog = true;
589 						logScroll = 0;
590 					}
591 					ctx.dumpLog("Build log %s:", meshName.c_str());
592 
593 					// Clear test.
594 					delete test;
595 					test = 0;
596 				}
597 
598 				imguiSeparator();
599 			}
600 
601 			if (sample)
602 			{
603 				imguiSeparatorLine();
604 				sample->handleDebugMode();
605 			}
606 
607 			imguiEndScrollArea();
608 		}
609 
610 		// Sample selection dialog.
611 		if (showSample)
612 		{
613 			static int levelScroll = 0;
614 			if (imguiBeginScrollArea("Choose Sample", width-10-250-10-200, height-10-250, 200, 250, &levelScroll))
615 				mouseOverMenu = true;
616 
617 			Sample* newSample = 0;
618 			for (int i = 0; i < g_nsamples; ++i)
619 			{
620 				if (imguiItem(g_samples[i].name.c_str()))
621 				{
622 					newSample = g_samples[i].create();
623 					if (newSample)
624 						sampleName = g_samples[i].name;
625 				}
626 			}
627 			if (newSample)
628 			{
629 				delete sample;
630 				sample = newSample;
631 				sample->setContext(&ctx);
632 				if (geom)
633 				{
634 					sample->handleMeshChanged(geom);
635 				}
636 				showSample = false;
637 			}
638 
639 			if (geom || sample)
640 			{
641 				const float* bmin = 0;
642 				const float* bmax = 0;
643 				if (geom)
644 				{
645 					bmin = geom->getNavMeshBoundsMin();
646 					bmax = geom->getNavMeshBoundsMax();
647 				}
648 				// Reset camera and fog to match the mesh bounds.
649 				if (bmin && bmax)
650 				{
651 					camr = sqrtf(rcSqr(bmax[0]-bmin[0]) +
652 								 rcSqr(bmax[1]-bmin[1]) +
653 								 rcSqr(bmax[2]-bmin[2])) / 2;
654 					cameraPos[0] = (bmax[0] + bmin[0]) / 2 + camr;
655 					cameraPos[1] = (bmax[1] + bmin[1]) / 2 + camr;
656 					cameraPos[2] = (bmax[2] + bmin[2]) / 2 + camr;
657 					camr *= 3;
658 				}
659 				cameraEulers[0] = 45;
660 				cameraEulers[1] = -45;
661 				glFogf(GL_FOG_START, camr*0.1f);
662 				glFogf(GL_FOG_END, camr*1.25f);
663 			}
664 
665 			imguiEndScrollArea();
666 		}
667 
668 		// Level selection dialog.
669 		if (showLevels)
670 		{
671 			static int levelScroll = 0;
672 			if (imguiBeginScrollArea("Choose Level", width - 10 - 250 - 10 - 200, height - 10 - 450, 200, 450, &levelScroll))
673 				mouseOverMenu = true;
674 
675 			vector<string>::const_iterator fileIter = files.begin();
676 			vector<string>::const_iterator filesEnd = files.end();
677 			vector<string>::const_iterator levelToLoad = filesEnd;
678 			for (; fileIter != filesEnd; ++fileIter)
679 			{
680 				if (imguiItem(fileIter->c_str()))
681 				{
682 					levelToLoad = fileIter;
683 				}
684 			}
685 
686 			if (levelToLoad != filesEnd)
687 			{
688 				meshName = *levelToLoad;
689 				showLevels = false;
690 
691 				delete geom;
692 				geom = 0;
693 
694 				string path = meshesFolder + "/" + meshName;
695 
696 				geom = new InputGeom;
697 				if (!geom->load(&ctx, path))
698 				{
699 					delete geom;
700 					geom = 0;
701 
702 					// Destroy the sample if it already had geometry loaded, as we've just deleted it!
703 					if (sample && sample->getInputGeom())
704 					{
705 						delete sample;
706 						sample = 0;
707 					}
708 
709 					showLog = true;
710 					logScroll = 0;
711 					ctx.dumpLog("Geom load log %s:", meshName.c_str());
712 				}
713 				if (sample && geom)
714 				{
715 					sample->handleMeshChanged(geom);
716 				}
717 
718 				if (geom || sample)
719 				{
720 					const float* bmin = 0;
721 					const float* bmax = 0;
722 					if (geom)
723 					{
724 						bmin = geom->getNavMeshBoundsMin();
725 						bmax = geom->getNavMeshBoundsMax();
726 					}
727 					// Reset camera and fog to match the mesh bounds.
728 					if (bmin && bmax)
729 					{
730 						camr = sqrtf(rcSqr(bmax[0]-bmin[0]) +
731 									 rcSqr(bmax[1]-bmin[1]) +
732 									 rcSqr(bmax[2]-bmin[2])) / 2;
733 						cameraPos[0] = (bmax[0] + bmin[0]) / 2 + camr;
734 						cameraPos[1] = (bmax[1] + bmin[1]) / 2 + camr;
735 						cameraPos[2] = (bmax[2] + bmin[2]) / 2 + camr;
736 						camr *= 3;
737 					}
738 					cameraEulers[0] = 45;
739 					cameraEulers[1] = -45;
740 					glFogf(GL_FOG_START, camr * 0.1f);
741 					glFogf(GL_FOG_END, camr * 1.25f);
742 				}
743 			}
744 
745 			imguiEndScrollArea();
746 
747 		}
748 
749 		// Test cases
750 		if (showTestCases)
751 		{
752 			static int testScroll = 0;
753 			if (imguiBeginScrollArea("Choose Test To Run", width-10-250-10-200, height-10-450, 200, 450, &testScroll))
754 				mouseOverMenu = true;
755 
756 			vector<string>::const_iterator fileIter = files.begin();
757 			vector<string>::const_iterator filesEnd = files.end();
758 			vector<string>::const_iterator testToLoad = filesEnd;
759 			for (; fileIter != filesEnd; ++fileIter)
760 			{
761 				if (imguiItem(fileIter->c_str()))
762 				{
763 					testToLoad = fileIter;
764 				}
765 			}
766 
767 			if (testToLoad != filesEnd)
768 			{
769 				string path = testCasesFolder + "/" + *testToLoad;
770 				test = new TestCase;
771 				if (test)
772 				{
773 					// Load the test.
774 					if (!test->load(path))
775 					{
776 						delete test;
777 						test = 0;
778 					}
779 
780 					// Create sample
781 					Sample* newSample = 0;
782 					for (int i = 0; i < g_nsamples; ++i)
783 					{
784 						if (g_samples[i].name == test->getSampleName())
785 						{
786 							newSample = g_samples[i].create();
787 							if (newSample)
788 								sampleName = g_samples[i].name;
789 						}
790 					}
791 
792 					delete sample;
793 					sample = newSample;
794 
795 					if (sample)
796 					{
797 						sample->setContext(&ctx);
798 						showSample = false;
799 					}
800 
801 					// Load geom.
802 					meshName = test->getGeomFileName();
803 
804 
805 					path = meshesFolder + "/" + meshName;
806 
807 					delete geom;
808 					geom = new InputGeom;
809 					if (!geom || !geom->load(&ctx, path))
810 					{
811 						delete geom;
812 						geom = 0;
813 						delete sample;
814 						sample = 0;
815 						showLog = true;
816 						logScroll = 0;
817 						ctx.dumpLog("Geom load log %s:", meshName.c_str());
818 					}
819 					if (sample && geom)
820 					{
821 						sample->handleMeshChanged(geom);
822 					}
823 
824 					// This will ensure that tile & poly bits are updated in tiled sample.
825 					if (sample)
826 						sample->handleSettings();
827 
828 					ctx.resetLog();
829 					if (sample && !sample->handleBuild())
830 					{
831 						ctx.dumpLog("Build log %s:", meshName.c_str());
832 					}
833 
834 					if (geom || sample)
835 					{
836 						const float* bmin = 0;
837 						const float* bmax = 0;
838 						if (geom)
839 						{
840 							bmin = geom->getNavMeshBoundsMin();
841 							bmax = geom->getNavMeshBoundsMax();
842 						}
843 						// Reset camera and fog to match the mesh bounds.
844 						if (bmin && bmax)
845 						{
846 							camr = sqrtf(rcSqr(bmax[0] - bmin[0]) +
847 										 rcSqr(bmax[1] - bmin[1]) +
848 										 rcSqr(bmax[2] - bmin[2])) / 2;
849 							cameraPos[0] = (bmax[0] + bmin[0]) / 2 + camr;
850 							cameraPos[1] = (bmax[1] + bmin[1]) / 2 + camr;
851 							cameraPos[2] = (bmax[2] + bmin[2]) / 2 + camr;
852 							camr *= 3;
853 						}
854 						cameraEulers[0] = 45;
855 						cameraEulers[1] = -45;
856 						glFogf(GL_FOG_START, camr * 0.2f);
857 						glFogf(GL_FOG_END, camr * 1.25f);
858 					}
859 
860 					// Do the tests.
861 					if (sample)
862 						test->doTests(sample->getNavMesh(), sample->getNavMeshQuery());
863 				}
864 			}
865 
866 			imguiEndScrollArea();
867 		}
868 
869 
870 		// Log
871 		if (showLog && showMenu)
872 		{
873 			if (imguiBeginScrollArea("Log", 250 + 20, 10, width - 300 - 250, 200, &logScroll))
874 				mouseOverMenu = true;
875 			for (int i = 0; i < ctx.getLogCount(); ++i)
876 				imguiLabel(ctx.getLogText(i));
877 			imguiEndScrollArea();
878 		}
879 
880 		// Left column tools menu
881 		if (!showTestCases && showTools && showMenu) // && geom && sample)
882 		{
883 			if (imguiBeginScrollArea("Tools", 10, 10, 250, height - 20, &toolsScroll))
884 				mouseOverMenu = true;
885 
886 			if (sample)
887 				sample->handleTools();
888 
889 			imguiEndScrollArea();
890 		}
891 
892 		// Marker
893 		if (markerPositionSet && gluProject((GLdouble)markerPosition[0], (GLdouble)markerPosition[1], (GLdouble)markerPosition[2],
894 								  modelviewMatrix, projectionMatrix, viewport, &x, &y, &z))
895 		{
896 			// Draw marker circle
897 			glLineWidth(5.0f);
898 			glColor4ub(240,220,0,196);
899 			glBegin(GL_LINE_LOOP);
900 			const float r = 25.0f;
901 			for (int i = 0; i < 20; ++i)
902 			{
903 				const float a = (float)i / 20.0f * RC_PI*2;
904 				const float fx = (float)x + cosf(a)*r;
905 				const float fy = (float)y + sinf(a)*r;
906 				glVertex2f(fx,fy);
907 			}
908 			glEnd();
909 			glLineWidth(1.0f);
910 		}
911 
912 		imguiEndFrame();
913 		imguiRenderGLDraw();
914 
915 		glEnable(GL_DEPTH_TEST);
916 		SDL_GL_SwapWindow(window);
917 	}
918 
919 	imguiRenderGLDestroy();
920 
921 	SDL_Quit();
922 
923 	delete sample;
924 	delete geom;
925 
926 	return 0;
927 }
928