// // Copyright (c) 2009 Mikko Mononen memon@inside.org // // 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. // #include #define _USE_MATH_DEFINES #include #ifdef WIN32 # include #else # include #endif #include "SDL.h" #include "SDL_opengl.h" #include "imgui.h" #include "imguiRenderGL.h" #include "Recast.h" #include "RecastDebugDraw.h" #include "InputGeom.h" #include "TestCase.h" #include "Sample_SoloMeshSimple.h" #include "Sample_SoloMeshTiled.h" #include "Sample_TileMesh.h" #include "Sample_Debug.h" #ifdef WIN32 # define snprintf _snprintf #endif struct FileList { static const int MAX_FILES = 256; inline FileList() : size(0) { memset(files, 0, sizeof(char*)*MAX_FILES); } inline ~FileList() { clear(); } void clear() { for (int i = 0; i < size; ++i) delete [] files[i]; size = 0; } void add(const char* path) { if (size >= MAX_FILES) return; int n = strlen(path); files[size] = new char[n+1]; strcpy(files[size], path); size++; } static int cmp(const void* a, const void* b) { return strcmp(*(const char**)a, *(const char**)b); } void sort() { if (size > 1) qsort(files, size, sizeof(char*), cmp); } char* files[MAX_FILES]; int size; }; static void scanDirectory(const char* path, const char* ext, FileList& list) { list.clear(); #ifdef WIN32 _finddata_t dir; char pathWithExt[MAX_PATH]; long fh; strcpy(pathWithExt, path); strcat(pathWithExt, "/*"); strcat(pathWithExt, ext); fh = _findfirst(pathWithExt, &dir); if (fh == -1L) return; do { list.add(dir.name); } while (_findnext(fh, &dir) == 0); _findclose(fh); #else dirent* current = 0; DIR* dp = opendir(path); if (!dp) return; while ((current = readdir(dp)) != 0) { int len = strlen(current->d_name); if (len > 4 && strncmp(current->d_name+len-4, ext, 4) == 0) { list.add(current->d_name); } } closedir(dp); #endif list.sort(); } struct SampleItem { Sample* (*create)(); const char* name; }; Sample* createSoloSimple() { return new Sample_SoloMeshSimple(); } Sample* createSoloTiled() { return new Sample_SoloMeshTiled(); } Sample* createTile() { return new Sample_TileMesh(); } Sample* createDebug() { return new Sample_Debug(); } static SampleItem g_samples[] = { { createSoloSimple, "Solo Mesh Simple" }, { createSoloTiled, "Solo Mesh Tiled" }, { createTile, "Tile Mesh" }, { createDebug, "Debug" }, }; static const int g_nsamples = sizeof(g_samples)/sizeof(SampleItem); int main(int argc, char *argv[]) { // Init SDL if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { printf("Could not initialise SDL\n"); return -1; } // Init OpenGL SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); const SDL_VideoInfo* vi = SDL_GetVideoInfo(); int width = vi->current_w - 20; int height = vi->current_h - 80; SDL_Surface* screen = SDL_SetVideoMode(width, height, 0, SDL_OPENGL); if (!screen) { printf("Could not initialise SDL opengl\n"); return -1; } SDL_WM_SetCaption("Recast Demo", 0); if (!imguiRenderGLInit("DroidSans.ttf")) { printf("Could not init GUI renderer.\n"); SDL_Quit(); return -1; } float t = 0.0f; Uint32 lastTime = SDL_GetTicks(); int mx = 0, my = 0; float rx = 45; float ry = -45; float moveW = 0, moveS = 0, moveA = 0, moveD = 0; float camx = 0, camy = 0, camz = 0, camr = 1000; float origrx = 0, origry = 0; int origx = 0, origy = 0; bool rotate = false; float rays[3], raye[3]; bool mouseOverMenu = false; bool showMenu = true; bool showLog = false; bool showDebugMode = true; bool showTools = true; bool showLevels = false; bool showSample = false; bool showTestCases = false; int propScroll = 0; int logScroll = 0; int toolsScroll = 0; int debugScroll = 0; char sampleName[64] = "Choose Sample..."; FileList files; char meshName[128] = "Choose Mesh..."; float mpos[3] = {0,0,0}; bool mposSet = false; InputGeom* geom = 0; Sample* sample = 0; TestCase* test = 0; rcLog log; log.clear(); rcSetLog(&log); glEnable(GL_CULL_FACE); float fogCol[4] = { 0.32f,0.25f,0.25f,1 }; glEnable(GL_FOG); glFogi(GL_FOG_MODE, GL_LINEAR); glFogf(GL_FOG_START, camr*0.2f); glFogf(GL_FOG_END, camr*1.25f); glFogfv(GL_FOG_COLOR, fogCol); glDepthFunc(GL_LEQUAL); glEnable(GL_POINT_SMOOTH); glEnable(GL_LINE_SMOOTH); bool done = false; while(!done) { // Handle input events. int mscroll = 0; SDL_Event event; while(SDL_PollEvent(&event)) { switch(event.type) { case SDL_KEYDOWN: // Handle any key presses here. if (event.key.keysym.sym == SDLK_ESCAPE) { done = true; } else if (event.key.keysym.sym == SDLK_TAB) { showLevels = false; showSample = false; showTestCases = true; scanDirectory("Tests", ".txt", files); } else if (event.key.keysym.sym == SDLK_SPACE) { if (sample) sample->handleStep(); } else if (event.key.keysym.sym == SDLK_1) { if (geom) geom->save("geomset.txt"); } else if (event.key.keysym.sym == SDLK_2) { delete geom; geom = new InputGeom; if (!geom || !geom->load("geomset.txt")) { delete geom; geom = 0; showLog = true; logScroll = 0; printf("Geom load log %s:\n", meshName); for (int i = 0; i < log.getMessageCount(); ++i) printf("%s\n", log.getMessageText(i)); } if (sample && geom) { sample->handleMeshChanged(geom); } if (geom || sample) { const float* bmin = 0; const float* bmax = 0; if (sample) { bmin = sample->getBoundsMin(); bmax = sample->getBoundsMax(); } else if (geom) { bmin = geom->getMeshBoundsMin(); bmax = geom->getMeshBoundsMax(); } // Reset camera and fog to match the mesh bounds. if (bmin && bmax) { camr = sqrtf(rcSqr(bmax[0]-bmin[0]) + rcSqr(bmax[1]-bmin[1]) + rcSqr(bmax[2]-bmin[2])) / 2; camx = (bmax[0] + bmin[0]) / 2 + camr; camy = (bmax[1] + bmin[1]) / 2 + camr; camz = (bmax[2] + bmin[2]) / 2 + camr; camr *= 3; } rx = 45; ry = -45; glFogf(GL_FOG_START, camr*0.2f); glFogf(GL_FOG_END, camr*1.25f); } } break; case SDL_MOUSEBUTTONDOWN: // Handle mouse clicks here. if (!mouseOverMenu) { if (event.button.button == SDL_BUTTON_RIGHT) { // Rotate view rotate = true; origx = mx; origy = my; origrx = rx; origry = ry; } else if (event.button.button == SDL_BUTTON_LEFT) { // Hit test mesh. if (geom && sample) { // Hit test mesh. float t; if (geom->raycastMesh(rays, raye, t)) { if (SDL_GetModState() & KMOD_CTRL) { mposSet = true; mpos[0] = rays[0] + (raye[0] - rays[0])*t; mpos[1] = rays[1] + (raye[1] - rays[1])*t; mpos[2] = rays[2] + (raye[2] - rays[2])*t; } else { float pos[3]; pos[0] = rays[0] + (raye[0] - rays[0])*t; pos[1] = rays[1] + (raye[1] - rays[1])*t; pos[2] = rays[2] + (raye[2] - rays[2])*t; bool shift = (SDL_GetModState() & KMOD_SHIFT) ? true : false; sample->handleClick(pos, shift); } } else { if (SDL_GetModState() & KMOD_CTRL) { mposSet = false; } } } } } if (event.button.button == SDL_BUTTON_WHEELUP) mscroll--; if (event.button.button == SDL_BUTTON_WHEELDOWN) mscroll++; break; case SDL_MOUSEBUTTONUP: // Handle mouse clicks here. if(event.button.button == SDL_BUTTON_RIGHT) { rotate = false; } break; case SDL_MOUSEMOTION: mx = event.motion.x; my = height-1 - event.motion.y; if (rotate) { int dx = mx - origx; int dy = my - origy; rx = origrx - dy*0.25f; ry = origry + dx*0.25f; } break; case SDL_QUIT: done = true; break; default: break; } } unsigned char mbut = 0; if (SDL_GetMouseState(0,0) & SDL_BUTTON_LMASK) mbut |= IMGUI_MBUT_LEFT; if (SDL_GetMouseState(0,0) & SDL_BUTTON_RMASK) mbut |= IMGUI_MBUT_RIGHT; Uint32 time = SDL_GetTicks(); float dt = (time - lastTime) / 1000.0f; lastTime = time; t += dt; // Update and render glViewport(0, 0, width, height); glClearColor(0.3f, 0.3f, 0.32f, 1.0f); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_TEXTURE_2D); // Render 3d glEnable(GL_DEPTH_TEST); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(50.0f, (float)width/(float)height, 1.0f, camr); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glRotatef(rx,1,0,0); glRotatef(ry,0,1,0); glTranslatef(-camx, -camy, -camz); // Get hit ray position and direction. GLdouble proj[16]; GLdouble model[16]; GLint view[4]; glGetDoublev(GL_PROJECTION_MATRIX, proj); glGetDoublev(GL_MODELVIEW_MATRIX, model); glGetIntegerv(GL_VIEWPORT, view); GLdouble x, y, z; gluUnProject(mx, my, 0.0f, model, proj, view, &x, &y, &z); rays[0] = (float)x; rays[1] = (float)y; rays[2] = (float)z; gluUnProject(mx, my, 1.0f, model, proj, view, &x, &y, &z); raye[0] = (float)x; raye[1] = (float)y; raye[2] = (float)z; // Handle keyboard movement. Uint8* keystate = SDL_GetKeyState(NULL); moveW = rcClamp(moveW + dt * 4 * (keystate[SDLK_w] ? 1 : -1), 0.0f, 1.0f); moveS = rcClamp(moveS + dt * 4 * (keystate[SDLK_s] ? 1 : -1), 0.0f, 1.0f); moveA = rcClamp(moveA + dt * 4 * (keystate[SDLK_a] ? 1 : -1), 0.0f, 1.0f); moveD = rcClamp(moveD + dt * 4 * (keystate[SDLK_d] ? 1 : -1), 0.0f, 1.0f); float keybSpeed = 22.0f; if (SDL_GetModState() & KMOD_SHIFT) keybSpeed *= 4.0f; float movex = (moveD - moveA) * keybSpeed * dt; float movey = (moveS - moveW) * keybSpeed * dt; camx += movex * (float)model[0]; camy += movex * (float)model[4]; camz += movex * (float)model[8]; camx += movey * (float)model[2]; camy += movey * (float)model[6]; camz += movey * (float)model[10]; glEnable(GL_FOG); if (sample) sample->handleRender(); if (test) test->handleRender(); glDisable(GL_FOG); // Render GUI glDisable(GL_DEPTH_TEST); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D(0, width, 0, height); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); mouseOverMenu = false; imguiBeginFrame(mx,my,mbut,mscroll); if (sample) { sample->handleRenderOverlay((double*)proj, (double*)model, (int*)view); } if (test) { if (test->handleRenderOverlay((double*)proj, (double*)model, (int*)view)) mouseOverMenu = true; } // Help text. if (showMenu) { const char msg[] = "W/S/A/D: Move RMB: Rotate LMB: Place Start LMB+SHIFT: Place End"; imguiDrawText(width/2, height-20, IMGUI_ALIGN_CENTER, msg, imguiRGBA(255,255,255,128)); } if (showMenu) { int propDiv = showDebugMode ? (int)(height*0.6f) : height; if (imguiBeginScrollArea("Properties", width-250-10, 10+height-propDiv, 250, propDiv-20, &propScroll)) mouseOverMenu = true; if (imguiCheck("Show Log", showLog)) showLog = !showLog; if (imguiCheck("Show Tools", showTools)) showTools = !showTools; if (imguiCheck("Show Debug Mode", showDebugMode)) showDebugMode = !showDebugMode; imguiSeparator(); imguiLabel("Sample"); if (imguiButton(sampleName)) { if (showSample) { showSample = false; } else { showSample = true; showLevels = false; showTestCases = false; } } imguiSeparator(); imguiLabel("Input Mesh"); if (imguiButton(meshName)) { if (showLevels) { showLevels = false; } else { showSample = false; showTestCases = false; showLevels = true; scanDirectory("Meshes", ".obj", files); } } if (geom) { char text[64]; snprintf(text, 64, "Verts: %.1fk Tris: %.1fk", geom->getMesh()->getVertCount()/1000.0f, geom->getMesh()->getTriCount()/1000.0f); imguiValue(text); } imguiSeparator(); if (geom && sample) { sample->handleSettings(); if (imguiButton("Build")) { log.clear(); if (!sample->handleBuild()) { showLog = true; logScroll = 0; } printf("Build log %s:\n", meshName); for (int i = 0; i < log.getMessageCount(); ++i) printf("%s\n", log.getMessageText(i)); // Clear test. delete test; test = 0; } imguiSeparator(); } imguiEndScrollArea(); if (showDebugMode) { if (imguiBeginScrollArea("Debug Mode", width-250-10, 10, 250, height-propDiv-10, &debugScroll)) mouseOverMenu = true; if (sample) sample->handleDebugMode(); imguiEndScrollArea(); } } // Sample selection dialog. if (showSample) { static int levelScroll = 0; if (imguiBeginScrollArea("Choose Level", width-10-250-10-200, height-10-250, 200, 250, &levelScroll)) mouseOverMenu = true; Sample* newSample = 0; for (int i = 0; i < g_nsamples; ++i) { if (imguiItem(g_samples[i].name)) { newSample = g_samples[i].create(); if (newSample) strcpy(sampleName, g_samples[i].name); } } if (newSample) { delete sample; sample = newSample; if (geom && sample) { sample->handleMeshChanged(geom); } showSample = false; } if (geom || sample) { const float* bmin = 0; const float* bmax = 0; if (sample) { bmin = sample->getBoundsMin(); bmax = sample->getBoundsMax(); } else if (geom) { bmin = geom->getMeshBoundsMin(); bmax = geom->getMeshBoundsMax(); } // Reset camera and fog to match the mesh bounds. if (bmin && bmax) { camr = sqrtf(rcSqr(bmax[0]-bmin[0]) + rcSqr(bmax[1]-bmin[1]) + rcSqr(bmax[2]-bmin[2])) / 2; camx = (bmax[0] + bmin[0]) / 2 + camr; camy = (bmax[1] + bmin[1]) / 2 + camr; camz = (bmax[2] + bmin[2]) / 2 + camr; camr *= 3; } rx = 45; ry = -45; glFogf(GL_FOG_START, camr*0.2f); glFogf(GL_FOG_END, camr*1.25f); } imguiEndScrollArea(); } // Level selection dialog. if (showLevels) { static int levelScroll = 0; if (imguiBeginScrollArea("Choose Level", width-10-250-10-200, height-10-250, 200, 250, &levelScroll)) mouseOverMenu = true; int levelToLoad = -1; for (int i = 0; i < files.size; ++i) { if (imguiItem(files.files[i])) levelToLoad = i; } if (levelToLoad != -1) { strncpy(meshName, files.files[levelToLoad], sizeof(meshName)); meshName[sizeof(meshName)-1] = '\0'; showLevels = false; delete geom; geom = 0; char path[256]; strcpy(path, "Meshes/"); strcat(path, meshName); geom = new InputGeom; if (!geom || !geom->loadMesh(path)) { delete geom; geom = 0; showLog = true; logScroll = 0; printf("Geom load log %s:\n", meshName); for (int i = 0; i < log.getMessageCount(); ++i) printf("%s\n", log.getMessageText(i)); } if (sample && geom) { sample->handleMeshChanged(geom); } if (geom || sample) { const float* bmin = 0; const float* bmax = 0; if (sample) { bmin = sample->getBoundsMin(); bmax = sample->getBoundsMax(); } else if (geom) { bmin = geom->getMeshBoundsMin(); bmax = geom->getMeshBoundsMax(); } // Reset camera and fog to match the mesh bounds. if (bmin && bmax) { camr = sqrtf(rcSqr(bmax[0]-bmin[0]) + rcSqr(bmax[1]-bmin[1]) + rcSqr(bmax[2]-bmin[2])) / 2; camx = (bmax[0] + bmin[0]) / 2 + camr; camy = (bmax[1] + bmin[1]) / 2 + camr; camz = (bmax[2] + bmin[2]) / 2 + camr; camr *= 3; } rx = 45; ry = -45; glFogf(GL_FOG_START, camr*0.2f); glFogf(GL_FOG_END, camr*1.25f); } } imguiEndScrollArea(); } // Test cases if (showTestCases) { static int testScroll = 0; if (imguiBeginScrollArea("Choose Test To Run", width-10-250-10-200, height-10-450, 200, 450, &testScroll)) mouseOverMenu = true; int testToLoad = -1; for (int i = 0; i < files.size; ++i) { if (imguiItem(files.files[i])) testToLoad = i; } if (testToLoad != -1) { char path[256]; strcpy(path, "Tests/"); strcat(path, files.files[testToLoad]); test = new TestCase; if (test) { // Load the test. if (!test->load(path)) { delete test; test = 0; } // Create sample Sample* newSample = 0; for (int i = 0; i < g_nsamples; ++i) { if (strcmp(g_samples[i].name, test->getSampleName()) == 0) { newSample = g_samples[i].create(); if (newSample) strcpy(sampleName, g_samples[i].name); } } if (newSample) { delete sample; sample = newSample; showSample = false; } // Load geom. strcpy(meshName, test->getGeomFileName()); meshName[sizeof(meshName)-1] = '\0'; delete geom; geom = 0; strcpy(path, "Meshes/"); strcat(path, meshName); geom = new InputGeom; if (!geom || !geom->loadMesh(path)) { delete geom; geom = 0; showLog = true; logScroll = 0; printf("Geom load log %s:\n", meshName); for (int i = 0; i < log.getMessageCount(); ++i) printf("%s\n", log.getMessageText(i)); } if (sample && geom) { sample->handleMeshChanged(geom); } log.clear(); if (sample && !sample->handleBuild()) { printf("Build log %s:\n", meshName); for (int i = 0; i < log.getMessageCount(); ++i) printf("%s\n", log.getMessageText(i)); } if (geom || sample) { const float* bmin = 0; const float* bmax = 0; if (sample) { bmin = sample->getBoundsMin(); bmax = sample->getBoundsMax(); } else if (geom) { bmin = geom->getMeshBoundsMin(); bmax = geom->getMeshBoundsMax(); } // Reset camera and fog to match the mesh bounds. if (bmin && bmax) { camr = sqrtf(rcSqr(bmax[0]-bmin[0]) + rcSqr(bmax[1]-bmin[1]) + rcSqr(bmax[2]-bmin[2])) / 2; camx = (bmax[0] + bmin[0]) / 2 + camr; camy = (bmax[1] + bmin[1]) / 2 + camr; camz = (bmax[2] + bmin[2]) / 2 + camr; camr *= 3; } rx = 45; ry = -45; glFogf(GL_FOG_START, camr*0.2f); glFogf(GL_FOG_END, camr*1.25f); } // Do the tests. if (sample) test->doTests(sample->getNavMesh()); } } imguiEndScrollArea(); } // Log if (showLog && showMenu) { if (imguiBeginScrollArea("Log", 10, 10, width - 300, 200, &logScroll)) mouseOverMenu = true; for (int i = 0; i < log.getMessageCount(); ++i) imguiLabel(log.getMessageText(i)); imguiEndScrollArea(); } // Tools if (!showTestCases && showTools && showMenu && geom && sample) { if (imguiBeginScrollArea("Tools", 10, height - 10 - 350, 200, 350, &toolsScroll)) mouseOverMenu = true; sample->handleTools(); imguiEndScrollArea(); } // Marker if (mposSet && gluProject((GLdouble)mpos[0], (GLdouble)mpos[1], (GLdouble)mpos[2], model, proj, view, &x, &y, &z)) { // Draw marker circle glLineWidth(5.0f); glColor4ub(240,220,0,196); glBegin(GL_LINE_LOOP); const float r = 25.0f; for (int i = 0; i < 20; ++i) { const float a = (float)i / 20.0f * (float)M_PI*2; const float fx = (float)x + cosf(a)*r; const float fy = (float)y + sinf(a)*r; glVertex2f(fx,fy); } glEnd(); glLineWidth(1.0f); } imguiEndFrame(); imguiRenderGLDraw(); glEnable(GL_DEPTH_TEST); SDL_GL_SwapBuffers(); } imguiRenderGLDestroy(); SDL_Quit(); delete sample; delete geom; return 0; }