866 lines
20 KiB
C++

//
// Copyright (c) 2009-2010 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 <stdio.h>
#define _USE_MATH_DEFINES
#include <math.h>
#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 "Filelist.h"
#include "SlideShow.h"
#include "Sample_SoloMeshSimple.h"
#include "Sample_SoloMeshTiled.h"
#include "Sample_TileMesh.h"
#include "Sample_Debug.h"
#ifdef WIN32
# define snprintf _snprintf
#endif
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;
SlideShow slideShow;
slideShow.init("slides/");
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_t)
{
showLevels = false;
showSample = false;
showTestCases = true;
scanDirectory("Tests", ".txt", files);
}
else if (event.key.keysym.sym == SDLK_TAB)
{
showMenu = !showMenu;
}
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);
}
}
else if (event.key.keysym.sym == SDLK_RIGHT)
{
slideShow.nextSlide();
}
else if (event.key.keysym.sym == SDLK_LEFT)
{
slideShow.prevSlide();
}
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();
}
slideShow.updateAndDraw(dt, (float)width, (float)height);
// 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;
}