static unsigned short getHeight(const float fx, const float fy, const float fz, const float /*cs*/, const float ics, const float ch, const rcHeightPatch& hp) { int ix = (int)floorf(fx*ics + 0.01f); int iz = (int)floorf(fz*ics + 0.01f); ix = rcClamp(ix-hp.xmin, 0, hp.width - 1); iz = rcClamp(iz-hp.ymin, 0, hp.height - 1); unsigned short h = hp.data[ix+iz*hp.width]; if (h == RC_UNSET_HEIGHT) { // Special case when data might be bad. // Find nearest neighbour pixel which has valid height. const int off[8*2] = { -1,0, -1,-1, 0,-1, 1,-1, 1,0, 1,1, 0,1, -1,1}; float dmin = FLT_MAX; for (int i = 0; i < 8; ++i) { const int nx = ix+off[i*2+0]; const int nz = iz+off[i*2+1]; if (nx < 0 || nz < 0 || nx >= hp.width || nz >= hp.height) continue; const unsigned short nh = hp.data[nx+nz*hp.width]; if (nh == RC_UNSET_HEIGHT) continue; const float d = fabsf(nh*ch - fy); if (d < dmin) { h = nh; dmin = d; } } } return h; }
static unsigned short getHeight(const float* pos, const float* bmin, const float ics, const rcHeightPatch& hp) { int ix = (int)floorf((pos[0]-bmin[0])*ics + 0.01f); int iz = (int)floorf((pos[2]-bmin[2])*ics + 0.01f); ix = rcClamp(ix-hp.xmin, 0, hp.width); iz = rcClamp(iz-hp.ymin, 0, hp.height); unsigned short h = hp.data[ix+iz*hp.width]; return h; }
void RecastToolKit::Update(float dt) { // Update and render glViewport(0, 0, m_Width, m_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)m_Width / (float)m_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. glGetDoublev(GL_PROJECTION_MATRIX, projectionMatrix); glGetDoublev(GL_MODELVIEW_MATRIX, modelviewMatrix); glGetIntegerv(GL_VIEWPORT, viewport); gluUnProject(mx, my, 0.0f, modelviewMatrix, projectionMatrix, viewport, &x, &y, &z); rays[0] = (float)x; rays[1] = (float)y; rays[2] = (float)z; gluUnProject(mx, my, 1.0f, modelviewMatrix, projectionMatrix, viewport, &x, &y, &z); raye[0] = (float)x; raye[1] = (float)y; raye[2] = (float)z; float keybSpeed = 22.0f; if (SDL_GetModState() & KMOD_SHIFT) keybSpeed *= 4.0f; moveW = rcClamp(moveW + dt * 4 * forward, 0.0f, 1.0f); moveS = rcClamp(moveS + dt * 4 * reverse, 0.0f, 1.0f); moveA = rcClamp(moveA + dt * 4 * left, 0.0f, 1.0f); moveD = rcClamp(moveD + dt * 4 * right, 0.0f, 1.0f); float movex = (moveD - moveA) * keybSpeed * dt; float movey = (moveS - moveW) * keybSpeed * dt + scrollZoom * 2.0f; scrollZoom = 0; camx += movex * (float)modelviewMatrix[0]; camy += movex * (float)modelviewMatrix[4]; camz += movex * (float)modelviewMatrix[8]; camx += movey * (float)modelviewMatrix[2]; camy += movey * (float)modelviewMatrix[6]; camz += movey * (float)modelviewMatrix[10]; }
void rcCalcBounds( const dtCoordinates* verts, const int nv, const dtCoordinates& square_min, const dtCoordinates& square_max, dtCoordinates& bmin, dtCoordinates& bmax ) { // Calculate bounding box. rcVcopy(bmin, verts[0]); rcVcopy(bmax, verts[0]); for (int i = 1; i < nv; ++i) { const dtCoordinates v( verts[i] ); rcVmin(bmin, v); rcVmax(bmax, v); bmin.SetX( rcClamp( bmin.X(), square_min.X(), square_max.X() ) ); bmin.SetZ( rcClamp( bmin.Z(), square_min.Z(), square_max.Z() ) ); bmax.SetX( rcClamp( bmax.X(), square_min.X(), square_max.X() ) ); bmax.SetZ( rcClamp( bmax.Z(), square_min.Z(), square_max.Z() ) ); } }
void RecastToolKit::UpdateSimulation(float dt) { // Update sample simulation. const float SIM_RATE = 20; const float DELTA_TIME = 1.0f / SIM_RATE; m_TimeAcc = rcClamp(m_TimeAcc + dt, -1.0f, 1.0f); int simIter = 0; while (m_TimeAcc > DELTA_TIME) { m_TimeAcc -= DELTA_TIME; if (simIter < 5 && sample) sample->handleUpdate(DELTA_TIME); simIter++; } }
bool rcBuildCompactHeightfield(rcContext* ctx, const int walkableHeight, const int walkableClimb, rcHeightfield& hf, rcCompactHeightfield& chf) { rcAssert(ctx); ctx->startTimer(RC_TIMER_BUILD_COMPACTHEIGHTFIELD); const int w = hf.width; const int h = hf.height; const int spanCount = rcGetHeightFieldSpanCount(ctx, hf); // Fill in header. chf.width = w; chf.height = h; chf.spanCount = spanCount; chf.walkableHeight = walkableHeight; chf.walkableClimb = walkableClimb; chf.maxRegions = 0; rcVcopy(chf.bmin, hf.bmin); rcVcopy(chf.bmax, hf.bmax); chf.bmax[1] += walkableHeight*hf.ch; chf.cs = hf.cs; chf.ch = hf.ch; chf.cells = (rcCompactCell*)rcAlloc(sizeof(rcCompactCell)*w*h, RC_ALLOC_PERM); if (!chf.cells) { ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.cells' (%d)", w*h); return false; } memset(chf.cells, 0, sizeof(rcCompactCell)*w*h); chf.spans = (rcCompactSpan*)rcAlloc(sizeof(rcCompactSpan)*spanCount, RC_ALLOC_PERM); if (!chf.spans) { ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.spans' (%d)", spanCount); return false; } memset(chf.spans, 0, sizeof(rcCompactSpan)*spanCount); chf.areas = (unsigned char*)rcAlloc(sizeof(unsigned char)*spanCount, RC_ALLOC_PERM); if (!chf.areas) { ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.areas' (%d)", spanCount); return false; } memset(chf.areas, RC_NULL_AREA, sizeof(unsigned char)*spanCount); const int MAX_HEIGHT = 0xffff; // Fill in cells and spans. int idx = 0; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { const rcSpan* s = hf.spans[x + y*w]; // If there are no spans at this cell, just leave the data to index=0, count=0. if (!s) continue; rcCompactCell& c = chf.cells[x + y*w]; c.index = idx; c.count = 0; while (s) { if (s->area != RC_NULL_AREA) { const int bot = (int)s->smax; const int top = s->next ? (int)s->next->smin : MAX_HEIGHT; chf.spans[idx].y = (unsigned short)rcClamp(bot, 0, 0xffff); chf.spans[idx].h = (unsigned char)rcClamp(top - bot, 0, 0xff); chf.areas[idx] = s->area; idx++; c.count++; } s = s->next; } } } // Find neighbour connections. const int MAX_LAYERS = RC_NOT_CONNECTED - 1; int tooHighNeighbour = 0; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { const rcCompactCell& c = chf.cells[x + y*w]; for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i) { rcCompactSpan& s = chf.spans[i]; for (int dir = 0; dir < 4; ++dir) { rcSetCon(s, dir, RC_NOT_CONNECTED); const int nx = x + rcGetDirOffsetX(dir); const int ny = y + rcGetDirOffsetY(dir); // First check that the neighbour cell is in bounds. if (nx < 0 || ny < 0 || nx >= w || ny >= h) continue; // Iterate over all neighbour spans and check if any of the is // accessible from current cell. const rcCompactCell& nc = chf.cells[nx + ny*w]; for (int k = (int)nc.index, nk = (int)(nc.index + nc.count); k < nk; ++k) { const rcCompactSpan& ns = chf.spans[k]; const int bot = rcMax(s.y, ns.y); const int top = rcMin(s.y + s.h, ns.y + ns.h); // Check that the gap between the spans is walkable, // and that the climb height between the gaps is not too high. if ((top - bot) >= walkableHeight && rcAbs((int)ns.y - (int)s.y) <= walkableClimb) { // Mark direction as walkable. const int b = k - (int)nc.index; if (b < 0 || b > MAX_LAYERS) { tooHighNeighbour = rcMax(tooHighNeighbour, b); continue; } rcSetCon(s, dir, b); break; } } } } } } if (tooHighNeighbour > MAX_LAYERS) { ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Heightfield has too many layers %d (max: %d)", tooHighNeighbour, MAX_LAYERS); } ctx->stopTimer(RC_TIMER_BUILD_COMPACTHEIGHTFIELD); return true; }
static bool rasterizeTri(const float* v0, const float* v1, const float* v2, const unsigned char area, rcHeightfield& hf, const float* bmin, const float* bmax, const float cs, const float ics, const float ich, const int flagMergeThr) { const int w = hf.width; const int h = hf.height; float tmin[3], tmax[3]; const float by = bmax[1] - bmin[1]; // Calculate the bounding box of the triangle. rcVcopy(tmin, v0); rcVcopy(tmax, v0); rcVmin(tmin, v1); rcVmin(tmin, v2); rcVmax(tmax, v1); rcVmax(tmax, v2); // If the triangle does not touch the bbox of the heightfield, skip the triagle. if (!overlapBounds(bmin, bmax, tmin, tmax)) return true; // Calculate the footprint of the triangle on the grid's y-axis int y0 = (int)((tmin[2] - bmin[2])*ics); int y1 = (int)((tmax[2] - bmin[2])*ics); y0 = rcClamp(y0, 0, h-1); y1 = rcClamp(y1, 0, h-1); // Clip the triangle into all grid cells it touches. float buf[7*3*4]; float *in = buf, *inrow = buf+7*3, *p1 = inrow+7*3, *p2 = p1+7*3; rcVcopy(&in[0], v0); rcVcopy(&in[1*3], v1); rcVcopy(&in[2*3], v2); int nvrow, nvIn = 3; for (int y = y0; y <= y1; ++y) { // Clip polygon to row. Store the remaining polygon as well const float cz = bmin[2] + y*cs; dividePoly(in, nvIn, inrow, &nvrow, p1, &nvIn, cz+cs, 2); rcSwap(in, p1); if (nvrow < 3) continue; // find the horizontal bounds in the row float minX = inrow[0], maxX = inrow[0]; for (int i=1; i<nvrow; ++i) { if (minX > inrow[i*3]) minX = inrow[i*3]; if (maxX < inrow[i*3]) maxX = inrow[i*3]; } int x0 = (int)((minX - bmin[0])*ics); int x1 = (int)((maxX - bmin[0])*ics); x0 = rcClamp(x0, 0, w-1); x1 = rcClamp(x1, 0, w-1); int nv, nv2 = nvrow; for (int x = x0; x <= x1; ++x) { // Clip polygon to column. store the remaining polygon as well const float cx = bmin[0] + x*cs; dividePoly(inrow, nv2, p1, &nv, p2, &nv2, cx+cs, 0); rcSwap(inrow, p2); if (nv < 3) continue; // Calculate min and max of the span. float smin = p1[1], smax = p1[1]; for (int i = 1; i < nv; ++i) { smin = rcMin(smin, p1[i*3+1]); smax = rcMax(smax, p1[i*3+1]); } smin -= bmin[1]; smax -= bmin[1]; // Skip the span if it is outside the heightfield bbox if (smax < 0.0f) continue; if (smin > by) continue; // Clamp the span to the heightfield bbox. if (smin < 0.0f) smin = 0; if (smax > by) smax = by; // Snap the span to the heightfield height grid. unsigned short ismin = (unsigned short)rcClamp((int)floorf(smin * ich), 0, RC_SPAN_MAX_HEIGHT); unsigned short ismax = (unsigned short)rcClamp((int)ceilf(smax * ich), (int)ismin+1, RC_SPAN_MAX_HEIGHT); if (!addSpan(hf, x, y, ismin, ismax, area, flagMergeThr)) return false; } } return true; }
int main(int /*argc*/, char** /*argv*/) { // Init SDL if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { printf("Could not initialise SDL\n"); return -1; } // Center window char env[] = "SDL_VIDEO_CENTERED=1"; putenv(env); // 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); //#ifndef WIN32 SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4); //#endif const SDL_VideoInfo* vi = SDL_GetVideoInfo(); bool presentationMode = false; int width, height; SDL_Surface* screen = 0; if (presentationMode) { width = 1700; height = 1000; screen = SDL_SetVideoMode(width, height, 0, SDL_OPENGL|SDL_FULLSCREEN); } else { width = 1700; height = 1000; screen = SDL_SetVideoMode(width, height, 0, SDL_OPENGL); } if (!screen) { printf("Could not initialise SDL opengl\n"); return -1; } glEnable(GL_MULTISAMPLE); 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; float timeAcc = 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; float scrollZoom = 0; bool rotate = false; bool movedDuringRotate = false; float rays[3], raye[3]; bool mouseOverMenu = false; bool showMenu = !presentationMode; bool showLog = false; bool showTools = true; bool showLevels = false; bool showSample = false; bool showTestCases = false; int propScroll = 0; int logScroll = 0; int toolsScroll = 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; BuildContext ctx; glEnable(GL_CULL_FACE); float fogCol[4] = { 0.32f, 0.31f, 0.30f, 1.0f }; glEnable(GL_FOG); glFogi(GL_FOG_MODE, GL_LINEAR); glFogf(GL_FOG_START, camr*0.1f); glFogf(GL_FOG_END, camr*1.25f); glFogfv(GL_FOG_COLOR, fogCol); glDepthFunc(GL_LEQUAL); bool done = false; while(!done) { // Handle input events. int mscroll = 0; bool processHitTest = false; bool processHitTestShift = false; 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->handleToggle(); } else if (event.key.keysym.sym == SDLK_1) { if (sample) sample->handleStep(); } else if (event.key.keysym.sym == SDLK_9) { if (geom) geom->save("geomset.txt"); } else if (event.key.keysym.sym == SDLK_0) { delete geom; geom = new InputGeom; if (!geom || !geom->load(&ctx, "geomset.txt")) { delete geom; geom = 0; showLog = true; logScroll = 0; ctx.dumpLog("Geom load log %s:", meshName); } 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: if (event.button.button == SDL_BUTTON_RIGHT) { if (!mouseOverMenu) { // Rotate view rotate = true; movedDuringRotate = false; origx = mx; origy = my; origrx = rx; origry = ry; } } else if (event.button.button == SDL_BUTTON_WHEELUP) { if (mouseOverMenu) mscroll--; else scrollZoom -= 1.0f; } else if (event.button.button == SDL_BUTTON_WHEELDOWN) { if (mouseOverMenu) mscroll++; else scrollZoom += 1.0f; } break; case SDL_MOUSEBUTTONUP: // Handle mouse clicks here. if (event.button.button == SDL_BUTTON_RIGHT) { rotate = false; if (!mouseOverMenu) { if (!movedDuringRotate) { processHitTest = true; processHitTestShift = true; } } } else if (event.button.button == SDL_BUTTON_LEFT) { if (!mouseOverMenu) { processHitTest = true; processHitTestShift = (SDL_GetModState() & KMOD_SHIFT) ? true : 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; if (dx*dx+dy*dy > 3*3) movedDuringRotate = true; } 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; // Hit test mesh. if (processHitTest && geom && sample) { float hitt; bool hit = geom->raycastMesh(rays, raye, hitt); if (hit) { if (SDL_GetModState() & KMOD_CTRL) { // Marker mposSet = true; mpos[0] = rays[0] + (raye[0] - rays[0])*hitt; mpos[1] = rays[1] + (raye[1] - rays[1])*hitt; mpos[2] = rays[2] + (raye[2] - rays[2])*hitt; } else { float pos[3]; pos[0] = rays[0] + (raye[0] - rays[0])*hitt; pos[1] = rays[1] + (raye[1] - rays[1])*hitt; pos[2] = rays[2] + (raye[2] - rays[2])*hitt; sample->handleClick(rays, pos, processHitTestShift); } } else { if (SDL_GetModState() & KMOD_CTRL) { // Marker mposSet = false; } } } // Update sample simulation. const float SIM_RATE = 20; const float DELTA_TIME = 1.0f/SIM_RATE; timeAcc = rcClamp(timeAcc+dt, -1.0f, 1.0f); int simIter = 0; while (timeAcc > DELTA_TIME) { timeAcc -= DELTA_TIME; if (simIter < 5) { if (sample) sample->handleUpdate(DELTA_TIME); } simIter++; } // Clamp the framerate so that we do not hog all the CPU. const float MIN_FRAME_TIME = 1.0f/40.0f; if (dt < MIN_FRAME_TIME) { int ms = (int)((MIN_FRAME_TIME - dt)*1000.0f); if (ms > 10) ms = 10; if (ms >= 0) SDL_Delay(ms); } // 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; movey += scrollZoom * 2.0f; scrollZoom = 0; 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"; imguiDrawText(280, height-20, IMGUI_ALIGN_LEFT, msg, imguiRGBA(255,255,255,128)); } if (showMenu) { if (imguiBeginScrollArea("Properties", width-250-10, 10, 250, height-20, &propScroll)) mouseOverMenu = true; if (imguiCheck("Show Log", showLog)) showLog = !showLog; if (imguiCheck("Show Tools", showTools)) showTools = !showTools; 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) { imguiSeparatorLine(); sample->handleSettings(); if (imguiButton("Build")) { ctx.resetLog(); if (!sample->handleBuild()) { showLog = true; logScroll = 0; } ctx.dumpLog("Build log %s:", meshName); // Clear test. delete test; test = 0; } imguiSeparator(); } if (sample) { imguiSeparatorLine(); sample->handleDebugMode(); } imguiEndScrollArea(); } // Sample selection dialog. if (showSample) { static int levelScroll = 0; if (imguiBeginScrollArea("Choose Sample", 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; sample->setContext(&ctx); 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.1f); 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-450, 200, 450, &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(&ctx, path)) { delete geom; geom = 0; showLog = true; logScroll = 0; ctx.dumpLog("Geom load log %s:", meshName); } 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.1f); 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; sample->setContext(&ctx); 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(&ctx, path)) { delete geom; geom = 0; showLog = true; logScroll = 0; ctx.dumpLog("Geom load log %s:", meshName); } if (sample && geom) { sample->handleMeshChanged(geom); } // This will ensure that tile & poly bits are updated in tiled sample. if (sample) sample->handleSettings(); ctx.resetLog(); if (sample && !sample->handleBuild()) { ctx.dumpLog("Build log %s:", meshName); } 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(), sample->getNavMeshQuery()); } } imguiEndScrollArea(); } // Log if (showLog && showMenu) { if (imguiBeginScrollArea("Log", 250+20, 10, width - 300 - 250, 200, &logScroll)) mouseOverMenu = true; for (int i = 0; i < ctx.getLogCount(); ++i) imguiLabel(ctx.getLogText(i)); imguiEndScrollArea(); } // Tools if (!showTestCases && showTools && showMenu) // && geom && sample) { if (imguiBeginScrollArea("Tools", 10, 10, 250, height-20, &toolsScroll)) mouseOverMenu = true; if (sample) 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 * RC_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; }
static void rasterizeTri(const float* v0, const float* v1, const float* v2, const unsigned char area, rcHeightfield& hf, const float* bmin, const float* bmax, const float cs, const float ics, const float ich, const int flagMergeThr) { const int w = hf.width; const int h = hf.height; float tmin[3], tmax[3]; const float by = bmax[1] - bmin[1]; // Calculate the bounding box of the triangle. rcVcopy(tmin, v0); rcVcopy(tmax, v0); rcVmin(tmin, v1); rcVmin(tmin, v2); rcVmax(tmax, v1); rcVmax(tmax, v2); // If the triangle does not touch the bbox of the heightfield, skip the triagle. if (!overlapBounds(bmin, bmax, tmin, tmax)) return; // Calculate the footpring of the triangle on the grid. int x0 = (int)((tmin[0] - bmin[0])*ics); int y0 = (int)((tmin[2] - bmin[2])*ics); int x1 = (int)((tmax[0] - bmin[0])*ics); int y1 = (int)((tmax[2] - bmin[2])*ics); x0 = rcClamp(x0, 0, w-1); y0 = rcClamp(y0, 0, h-1); x1 = rcClamp(x1, 0, w-1); y1 = rcClamp(y1, 0, h-1); // Clip the triangle into all grid cells it touches. float in[7*3], out[7*3], inrow[7*3]; for (int y = y0; y <= y1; ++y) { // Clip polygon to row. rcVcopy(&in[0], v0); rcVcopy(&in[1*3], v1); rcVcopy(&in[2*3], v2); int nvrow = 3; const float cz = bmin[2] + y*cs; nvrow = clipPoly(in, nvrow, out, 0, 1, -cz); if (nvrow < 3) continue; nvrow = clipPoly(out, nvrow, inrow, 0, -1, cz+cs); if (nvrow < 3) continue; for (int x = x0; x <= x1; ++x) { // Clip polygon to column. int nv = nvrow; const float cx = bmin[0] + x*cs; nv = clipPoly(inrow, nv, out, 1, 0, -cx); if (nv < 3) continue; nv = clipPoly(out, nv, in, -1, 0, cx+cs); if (nv < 3) continue; // Calculate min and max of the span. float smin = in[1], smax = in[1]; for (int i = 1; i < nv; ++i) { smin = rcMin(smin, in[i*3+1]); smax = rcMax(smax, in[i*3+1]); } smin -= bmin[1]; smax -= bmin[1]; // Skip the span if it is outside the heightfield bbox if (smax < 0.0f) continue; if (smin > by) continue; // Clamp the span to the heightfield bbox. if (smin < 0.0f) smin = 0; if (smax > by) smax = by; // Snap the span to the heightfield height grid. unsigned short ismin = (unsigned short)rcClamp((int)floorf(smin * ich), 0, RC_SPAN_MAX_HEIGHT); unsigned short ismax = (unsigned short)rcClamp((int)ceilf(smax * ich), (int)ismin+1, RC_SPAN_MAX_HEIGHT); addSpan(hf, x, y, ismin, ismax, area, flagMergeThr); } } }
bool rcBuildCompactHeightfield(const int walkableHeight, const int walkableClimb, unsigned char flags, rcHeightfield& hf, rcCompactHeightfield& chf) { rcTimeVal startTime = rcGetPerformanceTimer(); const int w = hf.width; const int h = hf.height; const int spanCount = getSpanCount(flags, hf); // Fill in header. chf.width = w; chf.height = h; chf.spanCount = spanCount; chf.walkableHeight = walkableHeight; chf.walkableClimb = walkableClimb; chf.maxRegions = 0; vcopy(chf.bmin, hf.bmin); vcopy(chf.bmax, hf.bmax); chf.bmax[1] += walkableHeight*hf.ch; chf.cs = hf.cs; chf.ch = hf.ch; chf.cells = new rcCompactCell[w*h]; if (!chf.cells) { if (rcGetLog()) rcGetLog()->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.cells' (%d)", w*h); return false; } memset(chf.cells, 0, sizeof(rcCompactCell)*w*h); chf.spans = new rcCompactSpan[spanCount]; if (!chf.spans) { if (rcGetLog()) rcGetLog()->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.spans' (%d)", spanCount); return false; } memset(chf.spans, 0, sizeof(rcCompactSpan)*spanCount); const int MAX_HEIGHT = 0xffff; // Fill in cells and spans. int idx = 0; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { const rcSpan* s = hf.spans[x + y*w]; // If there are no spans at this cell, just leave the data to index=0, count=0. if (!s) continue; rcCompactCell& c = chf.cells[x+y*w]; c.index = idx; c.count = 0; while (s) { if (s->flags == flags) { const int bot = (int)s->smax; const int top = s->next ? (int)s->next->smin : MAX_HEIGHT; chf.spans[idx].y = (unsigned short)rcClamp(bot, 0, 0xffff); chf.spans[idx].h = (unsigned char)rcClamp(top - bot, 0, 0xff); idx++; c.count++; } s = s->next; } } } // Find neighbour connections. for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { const rcCompactCell& c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { rcCompactSpan& s = chf.spans[i]; for (int dir = 0; dir < 4; ++dir) { setCon(s, dir, 0xf); const int nx = x + rcGetDirOffsetX(dir); const int ny = y + rcGetDirOffsetY(dir); // First check that the neighbour cell is in bounds. if (nx < 0 || ny < 0 || nx >= w || ny >= h) continue; // Iterate over all neighbour spans and check if any of the is // accessible from current cell. const rcCompactCell& nc = chf.cells[nx+ny*w]; for (int k = (int)nc.index, nk = (int)(nc.index+nc.count); k < nk; ++k) { const rcCompactSpan& ns = chf.spans[k]; const int bot = rcMax(s.y, ns.y); const int top = rcMin(s.y+s.h, ns.y+ns.h); // Check that the gap between the spans is walkable, // and that the climb height between the gaps is not too high. if ((top - bot) >= walkableHeight && rcAbs((int)ns.y - (int)s.y) <= walkableClimb) { // Mark direction as walkable. setCon(s, dir, k - (int)nc.index); break; } } } } } } rcTimeVal endTime = rcGetPerformanceTimer(); if (rcGetBuildTimes()) rcGetBuildTimes()->buildCompact += rcGetDeltaTimeUsec(startTime, endTime); return true; }
static unsigned short getHeight(const float fx, const float fy, const float fz, const float /*cs*/, const float ics, const float ch, const int radius, const rcHeightPatch& hp) { int ix = (int)floorf(fx*ics + 0.01f); int iz = (int)floorf(fz*ics + 0.01f); ix = rcClamp(ix-hp.xmin, 0, hp.width - 1); iz = rcClamp(iz-hp.ymin, 0, hp.height - 1); unsigned short h = hp.data[ix+iz*hp.width]; if (h == RC_UNSET_HEIGHT) { // Special case when data might be bad. // Walk adjacent cells in a spiral up to 'radius', and look // for a pixel which has a valid height. int x = 1, z = 0, dx = 1, dz = 0; int maxSize = radius * 2 + 1; int maxIter = maxSize * maxSize - 1; int nextRingIterStart = 8; int nextRingIters = 16; float dmin = FLT_MAX; for (int i = 0; i < maxIter; i++) { const int nx = ix + x; const int nz = iz + z; if (nx >= 0 && nz >= 0 && nx < hp.width && nz < hp.height) { const unsigned short nh = hp.data[nx + nz*hp.width]; if (nh != RC_UNSET_HEIGHT) { const float d = fabsf(nh*ch - fy); if (d < dmin) { h = nh; dmin = d; } } } // We are searching in a grid which looks approximately like this: // __________ // |2 ______ 2| // | |1 __ 1| | // | | |__| | | // | |______| | // |__________| // We want to find the best height as close to the center cell as possible. This means that // if we find a height in one of the neighbor cells to the center, we don't want to // expand further out than the 8 neighbors - we want to limit our search to the closest // of these "rings", but the best height in the ring. // For example, the center is just 1 cell. We checked that at the entrance to the function. // The next "ring" contains 8 cells (marked 1 above). Those are all the neighbors to the center cell. // The next one again contains 16 cells (marked 2). In general each ring has 8 additional cells, which // can be thought of as adding 2 cells around the "center" of each side when we expand the ring. // Here we detect if we are about to enter the next ring, and if we are and we have found // a height, we abort the search. if (i + 1 == nextRingIterStart) { if (h != RC_UNSET_HEIGHT) break; nextRingIterStart += nextRingIters; nextRingIters += 8; } if ((x == z) || ((x < 0) && (x == -z)) || ((x > 0) && (x == 1 - z))) { int tmp = dx; dx = -dz; dz = tmp; } x += dx; z += dz; } } return h; }