Path: blob/master/thirdparty/recastnavigation/Recast/Source/RecastContour.cpp
9913 views
//1// Copyright (c) 2009-2010 Mikko Mononen [email protected]2//3// This software is provided 'as-is', without any express or implied4// warranty. In no event will the authors be held liable for any damages5// arising from the use of this software.6// Permission is granted to anyone to use this software for any purpose,7// including commercial applications, and to alter it and redistribute it8// freely, subject to the following restrictions:9// 1. The origin of this software must not be misrepresented; you must not10// claim that you wrote the original software. If you use this software11// in a product, an acknowledgment in the product documentation would be12// appreciated but is not required.13// 2. Altered source versions must be plainly marked as such, and must not be14// misrepresented as being the original software.15// 3. This notice may not be removed or altered from any source distribution.16//1718#include <math.h>19#include <string.h>20#include <stdio.h>21#include <stdlib.h>22#include "Recast.h"23#include "RecastAlloc.h"24#include "RecastAssert.h"252627static int getCornerHeight(int x, int y, int i, int dir,28const rcCompactHeightfield& chf,29bool& isBorderVertex)30{31const rcCompactSpan& s = chf.spans[i];32int ch = (int)s.y;33int dirp = (dir+1) & 0x3;3435unsigned int regs[4] = {0,0,0,0};3637// Combine region and area codes in order to prevent38// border vertices which are in between two areas to be removed.39regs[0] = chf.spans[i].reg | (chf.areas[i] << 16);4041if (rcGetCon(s, dir) != RC_NOT_CONNECTED)42{43const int ax = x + rcGetDirOffsetX(dir);44const int ay = y + rcGetDirOffsetY(dir);45const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dir);46const rcCompactSpan& as = chf.spans[ai];47ch = rcMax(ch, (int)as.y);48regs[1] = chf.spans[ai].reg | (chf.areas[ai] << 16);49if (rcGetCon(as, dirp) != RC_NOT_CONNECTED)50{51const int ax2 = ax + rcGetDirOffsetX(dirp);52const int ay2 = ay + rcGetDirOffsetY(dirp);53const int ai2 = (int)chf.cells[ax2+ay2*chf.width].index + rcGetCon(as, dirp);54const rcCompactSpan& as2 = chf.spans[ai2];55ch = rcMax(ch, (int)as2.y);56regs[2] = chf.spans[ai2].reg | (chf.areas[ai2] << 16);57}58}59if (rcGetCon(s, dirp) != RC_NOT_CONNECTED)60{61const int ax = x + rcGetDirOffsetX(dirp);62const int ay = y + rcGetDirOffsetY(dirp);63const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dirp);64const rcCompactSpan& as = chf.spans[ai];65ch = rcMax(ch, (int)as.y);66regs[3] = chf.spans[ai].reg | (chf.areas[ai] << 16);67if (rcGetCon(as, dir) != RC_NOT_CONNECTED)68{69const int ax2 = ax + rcGetDirOffsetX(dir);70const int ay2 = ay + rcGetDirOffsetY(dir);71const int ai2 = (int)chf.cells[ax2+ay2*chf.width].index + rcGetCon(as, dir);72const rcCompactSpan& as2 = chf.spans[ai2];73ch = rcMax(ch, (int)as2.y);74regs[2] = chf.spans[ai2].reg | (chf.areas[ai2] << 16);75}76}7778// Check if the vertex is special edge vertex, these vertices will be removed later.79for (int j = 0; j < 4; ++j)80{81const int a = j;82const int b = (j+1) & 0x3;83const int c = (j+2) & 0x3;84const int d = (j+3) & 0x3;8586// The vertex is a border vertex there are two same exterior cells in a row,87// followed by two interior cells and none of the regions are out of bounds.88const bool twoSameExts = (regs[a] & regs[b] & RC_BORDER_REG) != 0 && regs[a] == regs[b];89const bool twoInts = ((regs[c] | regs[d]) & RC_BORDER_REG) == 0;90const bool intsSameArea = (regs[c]>>16) == (regs[d]>>16);91const bool noZeros = regs[a] != 0 && regs[b] != 0 && regs[c] != 0 && regs[d] != 0;92if (twoSameExts && twoInts && intsSameArea && noZeros)93{94isBorderVertex = true;95break;96}97}9899return ch;100}101102static void walkContour(int x, int y, int i,103const rcCompactHeightfield& chf,104unsigned char* flags, rcIntArray& points)105{106// Choose the first non-connected edge107unsigned char dir = 0;108while ((flags[i] & (1 << dir)) == 0)109dir++;110111unsigned char startDir = dir;112int starti = i;113114const unsigned char area = chf.areas[i];115116int iter = 0;117while (++iter < 40000)118{119if (flags[i] & (1 << dir))120{121// Choose the edge corner122bool isBorderVertex = false;123bool isAreaBorder = false;124int px = x;125int py = getCornerHeight(x, y, i, dir, chf, isBorderVertex);126int pz = y;127switch(dir)128{129case 0: pz++; break;130case 1: px++; pz++; break;131case 2: px++; break;132}133int r = 0;134const rcCompactSpan& s = chf.spans[i];135if (rcGetCon(s, dir) != RC_NOT_CONNECTED)136{137const int ax = x + rcGetDirOffsetX(dir);138const int ay = y + rcGetDirOffsetY(dir);139const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dir);140r = (int)chf.spans[ai].reg;141if (area != chf.areas[ai])142isAreaBorder = true;143}144if (isBorderVertex)145r |= RC_BORDER_VERTEX;146if (isAreaBorder)147r |= RC_AREA_BORDER;148points.push(px);149points.push(py);150points.push(pz);151points.push(r);152153flags[i] &= ~(1 << dir); // Remove visited edges154dir = (dir+1) & 0x3; // Rotate CW155}156else157{158int ni = -1;159const int nx = x + rcGetDirOffsetX(dir);160const int ny = y + rcGetDirOffsetY(dir);161const rcCompactSpan& s = chf.spans[i];162if (rcGetCon(s, dir) != RC_NOT_CONNECTED)163{164const rcCompactCell& nc = chf.cells[nx+ny*chf.width];165ni = (int)nc.index + rcGetCon(s, dir);166}167if (ni == -1)168{169// Should not happen.170return;171}172x = nx;173y = ny;174i = ni;175dir = (dir+3) & 0x3; // Rotate CCW176}177178if (starti == i && startDir == dir)179{180break;181}182}183}184185static float distancePtSeg(const int x, const int z,186const int px, const int pz,187const int qx, const int qz)188{189float pqx = (float)(qx - px);190float pqz = (float)(qz - pz);191float dx = (float)(x - px);192float dz = (float)(z - pz);193float d = pqx*pqx + pqz*pqz;194float t = pqx*dx + pqz*dz;195if (d > 0)196t /= d;197if (t < 0)198t = 0;199else if (t > 1)200t = 1;201202dx = px + t*pqx - x;203dz = pz + t*pqz - z;204205return dx*dx + dz*dz;206}207208static void simplifyContour(rcIntArray& points, rcIntArray& simplified,209const float maxError, const int maxEdgeLen, const int buildFlags)210{211// Add initial points.212bool hasConnections = false;213for (int i = 0; i < points.size(); i += 4)214{215if ((points[i+3] & RC_CONTOUR_REG_MASK) != 0)216{217hasConnections = true;218break;219}220}221222if (hasConnections)223{224// The contour has some portals to other regions.225// Add a new point to every location where the region changes.226for (int i = 0, ni = points.size()/4; i < ni; ++i)227{228int ii = (i+1) % ni;229const bool differentRegs = (points[i*4+3] & RC_CONTOUR_REG_MASK) != (points[ii*4+3] & RC_CONTOUR_REG_MASK);230const bool areaBorders = (points[i*4+3] & RC_AREA_BORDER) != (points[ii*4+3] & RC_AREA_BORDER);231if (differentRegs || areaBorders)232{233simplified.push(points[i*4+0]);234simplified.push(points[i*4+1]);235simplified.push(points[i*4+2]);236simplified.push(i);237}238}239}240241if (simplified.size() == 0)242{243// If there is no connections at all,244// create some initial points for the simplification process.245// Find lower-left and upper-right vertices of the contour.246int llx = points[0];247int lly = points[1];248int llz = points[2];249int lli = 0;250int urx = points[0];251int ury = points[1];252int urz = points[2];253int uri = 0;254for (int i = 0; i < points.size(); i += 4)255{256int x = points[i+0];257int y = points[i+1];258int z = points[i+2];259if (x < llx || (x == llx && z < llz))260{261llx = x;262lly = y;263llz = z;264lli = i/4;265}266if (x > urx || (x == urx && z > urz))267{268urx = x;269ury = y;270urz = z;271uri = i/4;272}273}274simplified.push(llx);275simplified.push(lly);276simplified.push(llz);277simplified.push(lli);278279simplified.push(urx);280simplified.push(ury);281simplified.push(urz);282simplified.push(uri);283}284285// Add points until all raw points are within286// error tolerance to the simplified shape.287const int pn = points.size()/4;288for (int i = 0; i < simplified.size()/4; )289{290int ii = (i+1) % (simplified.size()/4);291292int ax = simplified[i*4+0];293int az = simplified[i*4+2];294int ai = simplified[i*4+3];295296int bx = simplified[ii*4+0];297int bz = simplified[ii*4+2];298int bi = simplified[ii*4+3];299300// Find maximum deviation from the segment.301float maxd = 0;302int maxi = -1;303int ci, cinc, endi;304305// Traverse the segment in lexilogical order so that the306// max deviation is calculated similarly when traversing307// opposite segments.308if (bx > ax || (bx == ax && bz > az))309{310cinc = 1;311ci = (ai+cinc) % pn;312endi = bi;313}314else315{316cinc = pn-1;317ci = (bi+cinc) % pn;318endi = ai;319rcSwap(ax, bx);320rcSwap(az, bz);321}322323// Tessellate only outer edges or edges between areas.324if ((points[ci*4+3] & RC_CONTOUR_REG_MASK) == 0 ||325(points[ci*4+3] & RC_AREA_BORDER))326{327while (ci != endi)328{329float d = distancePtSeg(points[ci*4+0], points[ci*4+2], ax, az, bx, bz);330if (d > maxd)331{332maxd = d;333maxi = ci;334}335ci = (ci+cinc) % pn;336}337}338339340// If the max deviation is larger than accepted error,341// add new point, else continue to next segment.342if (maxi != -1 && maxd > (maxError*maxError))343{344// Add space for the new point.345simplified.resize(simplified.size()+4);346const int n = simplified.size()/4;347for (int j = n-1; j > i; --j)348{349simplified[j*4+0] = simplified[(j-1)*4+0];350simplified[j*4+1] = simplified[(j-1)*4+1];351simplified[j*4+2] = simplified[(j-1)*4+2];352simplified[j*4+3] = simplified[(j-1)*4+3];353}354// Add the point.355simplified[(i+1)*4+0] = points[maxi*4+0];356simplified[(i+1)*4+1] = points[maxi*4+1];357simplified[(i+1)*4+2] = points[maxi*4+2];358simplified[(i+1)*4+3] = maxi;359}360else361{362++i;363}364}365366// Split too long edges.367if (maxEdgeLen > 0 && (buildFlags & (RC_CONTOUR_TESS_WALL_EDGES|RC_CONTOUR_TESS_AREA_EDGES)) != 0)368{369for (int i = 0; i < simplified.size()/4; )370{371const int ii = (i+1) % (simplified.size()/4);372373const int ax = simplified[i*4+0];374const int az = simplified[i*4+2];375const int ai = simplified[i*4+3];376377const int bx = simplified[ii*4+0];378const int bz = simplified[ii*4+2];379const int bi = simplified[ii*4+3];380381// Find maximum deviation from the segment.382int maxi = -1;383int ci = (ai+1) % pn;384385// Tessellate only outer edges or edges between areas.386bool tess = false;387// Wall edges.388if ((buildFlags & RC_CONTOUR_TESS_WALL_EDGES) && (points[ci*4+3] & RC_CONTOUR_REG_MASK) == 0)389tess = true;390// Edges between areas.391if ((buildFlags & RC_CONTOUR_TESS_AREA_EDGES) && (points[ci*4+3] & RC_AREA_BORDER))392tess = true;393394if (tess)395{396int dx = bx - ax;397int dz = bz - az;398if (dx*dx + dz*dz > maxEdgeLen*maxEdgeLen)399{400// Round based on the segments in lexilogical order so that the401// max tesselation is consistent regardles in which direction402// segments are traversed.403const int n = bi < ai ? (bi+pn - ai) : (bi - ai);404if (n > 1)405{406if (bx > ax || (bx == ax && bz > az))407maxi = (ai + n/2) % pn;408else409maxi = (ai + (n+1)/2) % pn;410}411}412}413414// If the max deviation is larger than accepted error,415// add new point, else continue to next segment.416if (maxi != -1)417{418// Add space for the new point.419simplified.resize(simplified.size()+4);420const int n = simplified.size()/4;421for (int j = n-1; j > i; --j)422{423simplified[j*4+0] = simplified[(j-1)*4+0];424simplified[j*4+1] = simplified[(j-1)*4+1];425simplified[j*4+2] = simplified[(j-1)*4+2];426simplified[j*4+3] = simplified[(j-1)*4+3];427}428// Add the point.429simplified[(i+1)*4+0] = points[maxi*4+0];430simplified[(i+1)*4+1] = points[maxi*4+1];431simplified[(i+1)*4+2] = points[maxi*4+2];432simplified[(i+1)*4+3] = maxi;433}434else435{436++i;437}438}439}440441for (int i = 0; i < simplified.size()/4; ++i)442{443// The edge vertex flag is take from the current raw point,444// and the neighbour region is take from the next raw point.445const int ai = (simplified[i*4+3]+1) % pn;446const int bi = simplified[i*4+3];447simplified[i*4+3] = (points[ai*4+3] & (RC_CONTOUR_REG_MASK|RC_AREA_BORDER)) | (points[bi*4+3] & RC_BORDER_VERTEX);448}449450}451452static int calcAreaOfPolygon2D(const int* verts, const int nverts)453{454int area = 0;455for (int i = 0, j = nverts-1; i < nverts; j=i++)456{457const int* vi = &verts[i*4];458const int* vj = &verts[j*4];459area += vi[0] * vj[2] - vj[0] * vi[2];460}461return (area+1) / 2;462}463464// TODO: these are the same as in RecastMesh.cpp, consider using the same.465// Last time I checked the if version got compiled using cmov, which was a lot faster than module (with idiv).466inline int prev(int i, int n) { return i-1 >= 0 ? i-1 : n-1; }467inline int next(int i, int n) { return i+1 < n ? i+1 : 0; }468469inline int area2(const int* a, const int* b, const int* c)470{471return (b[0] - a[0]) * (c[2] - a[2]) - (c[0] - a[0]) * (b[2] - a[2]);472}473474// Exclusive or: true iff exactly one argument is true.475// The arguments are negated to ensure that they are 0/1476// values. Then the bitwise Xor operator may apply.477// (This idea is due to Michael Baldwin.)478inline bool xorb(bool x, bool y)479{480return !x ^ !y;481}482483// Returns true iff c is strictly to the left of the directed484// line through a to b.485inline bool left(const int* a, const int* b, const int* c)486{487return area2(a, b, c) < 0;488}489490inline bool leftOn(const int* a, const int* b, const int* c)491{492return area2(a, b, c) <= 0;493}494495inline bool collinear(const int* a, const int* b, const int* c)496{497return area2(a, b, c) == 0;498}499500// Returns true iff ab properly intersects cd: they share501// a point interior to both segments. The properness of the502// intersection is ensured by using strict leftness.503static bool intersectProp(const int* a, const int* b, const int* c, const int* d)504{505// Eliminate improper cases.506if (collinear(a,b,c) || collinear(a,b,d) ||507collinear(c,d,a) || collinear(c,d,b))508return false;509510return xorb(left(a,b,c), left(a,b,d)) && xorb(left(c,d,a), left(c,d,b));511}512513// Returns T iff (a,b,c) are collinear and point c lies514// on the closed segement ab.515static bool between(const int* a, const int* b, const int* c)516{517if (!collinear(a, b, c))518return false;519// If ab not vertical, check betweenness on x; else on y.520if (a[0] != b[0])521return ((a[0] <= c[0]) && (c[0] <= b[0])) || ((a[0] >= c[0]) && (c[0] >= b[0]));522else523return ((a[2] <= c[2]) && (c[2] <= b[2])) || ((a[2] >= c[2]) && (c[2] >= b[2]));524}525526// Returns true iff segments ab and cd intersect, properly or improperly.527static bool intersect(const int* a, const int* b, const int* c, const int* d)528{529if (intersectProp(a, b, c, d))530return true;531else if (between(a, b, c) || between(a, b, d) ||532between(c, d, a) || between(c, d, b))533return true;534else535return false;536}537538static bool vequal(const int* a, const int* b)539{540return a[0] == b[0] && a[2] == b[2];541}542543static bool intersectSegContour(const int* d0, const int* d1, int i, int n, const int* verts)544{545// For each edge (k,k+1) of P546for (int k = 0; k < n; k++)547{548int k1 = next(k, n);549// Skip edges incident to i.550if (i == k || i == k1)551continue;552const int* p0 = &verts[k * 4];553const int* p1 = &verts[k1 * 4];554if (vequal(d0, p0) || vequal(d1, p0) || vequal(d0, p1) || vequal(d1, p1))555continue;556557if (intersect(d0, d1, p0, p1))558return true;559}560return false;561}562563static bool inCone(int i, int n, const int* verts, const int* pj)564{565const int* pi = &verts[i * 4];566const int* pi1 = &verts[next(i, n) * 4];567const int* pin1 = &verts[prev(i, n) * 4];568569// If P[i] is a convex vertex [ i+1 left or on (i-1,i) ].570if (leftOn(pin1, pi, pi1))571return left(pi, pj, pin1) && left(pj, pi, pi1);572// Assume (i-1,i,i+1) not collinear.573// else P[i] is reflex.574return !(leftOn(pi, pj, pi1) && leftOn(pj, pi, pin1));575}576577578static void removeDegenerateSegments(rcIntArray& simplified)579{580// Remove adjacent vertices which are equal on xz-plane,581// or else the triangulator will get confused.582int npts = simplified.size()/4;583for (int i = 0; i < npts; ++i)584{585int ni = next(i, npts);586587if (vequal(&simplified[i*4], &simplified[ni*4]))588{589// Degenerate segment, remove.590for (int j = i; j < simplified.size()/4-1; ++j)591{592simplified[j*4+0] = simplified[(j+1)*4+0];593simplified[j*4+1] = simplified[(j+1)*4+1];594simplified[j*4+2] = simplified[(j+1)*4+2];595simplified[j*4+3] = simplified[(j+1)*4+3];596}597simplified.resize(simplified.size()-4);598npts--;599}600}601}602603604static bool mergeContours(rcContour& ca, rcContour& cb, int ia, int ib)605{606const int maxVerts = ca.nverts + cb.nverts + 2;607int* verts = (int*)rcAlloc(sizeof(int)*maxVerts*4, RC_ALLOC_PERM);608if (!verts)609return false;610611int nv = 0;612613// Copy contour A.614for (int i = 0; i <= ca.nverts; ++i)615{616int* dst = &verts[nv*4];617const int* src = &ca.verts[((ia+i)%ca.nverts)*4];618dst[0] = src[0];619dst[1] = src[1];620dst[2] = src[2];621dst[3] = src[3];622nv++;623}624625// Copy contour B626for (int i = 0; i <= cb.nverts; ++i)627{628int* dst = &verts[nv*4];629const int* src = &cb.verts[((ib+i)%cb.nverts)*4];630dst[0] = src[0];631dst[1] = src[1];632dst[2] = src[2];633dst[3] = src[3];634nv++;635}636637rcFree(ca.verts);638ca.verts = verts;639ca.nverts = nv;640641rcFree(cb.verts);642cb.verts = 0;643cb.nverts = 0;644645return true;646}647648struct rcContourHole649{650rcContour* contour;651int minx, minz, leftmost;652};653654struct rcContourRegion655{656rcContour* outline;657rcContourHole* holes;658int nholes;659};660661struct rcPotentialDiagonal662{663int vert;664int dist;665};666667// Finds the lowest leftmost vertex of a contour.668static void findLeftMostVertex(rcContour* contour, int* minx, int* minz, int* leftmost)669{670*minx = contour->verts[0];671*minz = contour->verts[2];672*leftmost = 0;673for (int i = 1; i < contour->nverts; i++)674{675const int x = contour->verts[i*4+0];676const int z = contour->verts[i*4+2];677if (x < *minx || (x == *minx && z < *minz))678{679*minx = x;680*minz = z;681*leftmost = i;682}683}684}685686static int compareHoles(const void* va, const void* vb)687{688const rcContourHole* a = (const rcContourHole*)va;689const rcContourHole* b = (const rcContourHole*)vb;690if (a->minx == b->minx)691{692if (a->minz < b->minz)693return -1;694if (a->minz > b->minz)695return 1;696}697else698{699if (a->minx < b->minx)700return -1;701if (a->minx > b->minx)702return 1;703}704return 0;705}706707708static int compareDiagDist(const void* va, const void* vb)709{710const rcPotentialDiagonal* a = (const rcPotentialDiagonal*)va;711const rcPotentialDiagonal* b = (const rcPotentialDiagonal*)vb;712if (a->dist < b->dist)713return -1;714if (a->dist > b->dist)715return 1;716return 0;717}718719720static void mergeRegionHoles(rcContext* ctx, rcContourRegion& region)721{722// Sort holes from left to right.723for (int i = 0; i < region.nholes; i++)724findLeftMostVertex(region.holes[i].contour, ®ion.holes[i].minx, ®ion.holes[i].minz, ®ion.holes[i].leftmost);725726qsort(region.holes, region.nholes, sizeof(rcContourHole), compareHoles);727728int maxVerts = region.outline->nverts;729for (int i = 0; i < region.nholes; i++)730maxVerts += region.holes[i].contour->nverts;731732rcScopedDelete<rcPotentialDiagonal> diags((rcPotentialDiagonal*)rcAlloc(sizeof(rcPotentialDiagonal)*maxVerts, RC_ALLOC_TEMP));733if (!diags)734{735ctx->log(RC_LOG_WARNING, "mergeRegionHoles: Failed to allocated diags %d.", maxVerts);736return;737}738739rcContour* outline = region.outline;740741// Merge holes into the outline one by one.742for (int i = 0; i < region.nholes; i++)743{744rcContour* hole = region.holes[i].contour;745746int index = -1;747int bestVertex = region.holes[i].leftmost;748for (int iter = 0; iter < hole->nverts; iter++)749{750// Find potential diagonals.751// The 'best' vertex must be in the cone described by 3 cosequtive vertices of the outline.752// ..o j-1753// |754// | * best755// |756// j o-----o j+1757// :758int ndiags = 0;759const int* corner = &hole->verts[bestVertex*4];760for (int j = 0; j < outline->nverts; j++)761{762if (inCone(j, outline->nverts, outline->verts, corner))763{764int dx = outline->verts[j*4+0] - corner[0];765int dz = outline->verts[j*4+2] - corner[2];766diags[ndiags].vert = j;767diags[ndiags].dist = dx*dx + dz*dz;768ndiags++;769}770}771// Sort potential diagonals by distance, we want to make the connection as short as possible.772qsort(diags, ndiags, sizeof(rcPotentialDiagonal), compareDiagDist);773774// Find a diagonal that is not intersecting the outline not the remaining holes.775index = -1;776for (int j = 0; j < ndiags; j++)777{778const int* pt = &outline->verts[diags[j].vert*4];779bool intersect = intersectSegContour(pt, corner, diags[i].vert, outline->nverts, outline->verts);780for (int k = i; k < region.nholes && !intersect; k++)781intersect |= intersectSegContour(pt, corner, -1, region.holes[k].contour->nverts, region.holes[k].contour->verts);782if (!intersect)783{784index = diags[j].vert;785break;786}787}788// If found non-intersecting diagonal, stop looking.789if (index != -1)790break;791// All the potential diagonals for the current vertex were intersecting, try next vertex.792bestVertex = (bestVertex + 1) % hole->nverts;793}794795if (index == -1)796{797ctx->log(RC_LOG_WARNING, "mergeHoles: Failed to find merge points for %p and %p.", region.outline, hole);798continue;799}800if (!mergeContours(*region.outline, *hole, index, bestVertex))801{802ctx->log(RC_LOG_WARNING, "mergeHoles: Failed to merge contours %p and %p.", region.outline, hole);803continue;804}805}806}807808809/// @par810///811/// The raw contours will match the region outlines exactly. The @p maxError and @p maxEdgeLen812/// parameters control how closely the simplified contours will match the raw contours.813///814/// Simplified contours are generated such that the vertices for portals between areas match up.815/// (They are considered mandatory vertices.)816///817/// Setting @p maxEdgeLength to zero will disabled the edge length feature.818///819/// See the #rcConfig documentation for more information on the configuration parameters.820///821/// @see rcAllocContourSet, rcCompactHeightfield, rcContourSet, rcConfig822bool rcBuildContours(rcContext* ctx, const rcCompactHeightfield& chf,823const float maxError, const int maxEdgeLen,824rcContourSet& cset, const int buildFlags)825{826rcAssert(ctx);827828const int w = chf.width;829const int h = chf.height;830const int borderSize = chf.borderSize;831832rcScopedTimer timer(ctx, RC_TIMER_BUILD_CONTOURS);833834rcVcopy(cset.bmin, chf.bmin);835rcVcopy(cset.bmax, chf.bmax);836if (borderSize > 0)837{838// If the heightfield was build with bordersize, remove the offset.839const float pad = borderSize*chf.cs;840cset.bmin[0] += pad;841cset.bmin[2] += pad;842cset.bmax[0] -= pad;843cset.bmax[2] -= pad;844}845cset.cs = chf.cs;846cset.ch = chf.ch;847cset.width = chf.width - chf.borderSize*2;848cset.height = chf.height - chf.borderSize*2;849cset.borderSize = chf.borderSize;850cset.maxError = maxError;851852int maxContours = rcMax((int)chf.maxRegions, 8);853cset.conts = (rcContour*)rcAlloc(sizeof(rcContour)*maxContours, RC_ALLOC_PERM);854if (!cset.conts)855return false;856cset.nconts = 0;857858rcScopedDelete<unsigned char> flags((unsigned char*)rcAlloc(sizeof(unsigned char)*chf.spanCount, RC_ALLOC_TEMP));859if (!flags)860{861ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'flags' (%d).", chf.spanCount);862return false;863}864865ctx->startTimer(RC_TIMER_BUILD_CONTOURS_TRACE);866867// Mark boundaries.868for (int y = 0; y < h; ++y)869{870for (int x = 0; x < w; ++x)871{872const rcCompactCell& c = chf.cells[x+y*w];873for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)874{875unsigned char res = 0;876const rcCompactSpan& s = chf.spans[i];877if (!chf.spans[i].reg || (chf.spans[i].reg & RC_BORDER_REG))878{879flags[i] = 0;880continue;881}882for (int dir = 0; dir < 4; ++dir)883{884unsigned short r = 0;885if (rcGetCon(s, dir) != RC_NOT_CONNECTED)886{887const int ax = x + rcGetDirOffsetX(dir);888const int ay = y + rcGetDirOffsetY(dir);889const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir);890r = chf.spans[ai].reg;891}892if (r == chf.spans[i].reg)893res |= (1 << dir);894}895flags[i] = res ^ 0xf; // Inverse, mark non connected edges.896}897}898}899900ctx->stopTimer(RC_TIMER_BUILD_CONTOURS_TRACE);901902rcIntArray verts(256);903rcIntArray simplified(64);904905for (int y = 0; y < h; ++y)906{907for (int x = 0; x < w; ++x)908{909const rcCompactCell& c = chf.cells[x+y*w];910for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)911{912if (flags[i] == 0 || flags[i] == 0xf)913{914flags[i] = 0;915continue;916}917const unsigned short reg = chf.spans[i].reg;918if (!reg || (reg & RC_BORDER_REG))919continue;920const unsigned char area = chf.areas[i];921922verts.clear();923simplified.clear();924925ctx->startTimer(RC_TIMER_BUILD_CONTOURS_TRACE);926walkContour(x, y, i, chf, flags, verts);927ctx->stopTimer(RC_TIMER_BUILD_CONTOURS_TRACE);928929ctx->startTimer(RC_TIMER_BUILD_CONTOURS_SIMPLIFY);930simplifyContour(verts, simplified, maxError, maxEdgeLen, buildFlags);931removeDegenerateSegments(simplified);932ctx->stopTimer(RC_TIMER_BUILD_CONTOURS_SIMPLIFY);933934935// Store region->contour remap info.936// Create contour.937if (simplified.size()/4 >= 3)938{939if (cset.nconts >= maxContours)940{941// Allocate more contours.942// This happens when a region has holes.943const int oldMax = maxContours;944maxContours *= 2;945rcContour* newConts = (rcContour*)rcAlloc(sizeof(rcContour)*maxContours, RC_ALLOC_PERM);946for (int j = 0; j < cset.nconts; ++j)947{948newConts[j] = cset.conts[j];949// Reset source pointers to prevent data deletion.950cset.conts[j].verts = 0;951cset.conts[j].rverts = 0;952}953rcFree(cset.conts);954cset.conts = newConts;955956ctx->log(RC_LOG_WARNING, "rcBuildContours: Expanding max contours from %d to %d.", oldMax, maxContours);957}958959rcContour* cont = &cset.conts[cset.nconts++];960961cont->nverts = simplified.size()/4;962cont->verts = (int*)rcAlloc(sizeof(int)*cont->nverts*4, RC_ALLOC_PERM);963if (!cont->verts)964{965ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'verts' (%d).", cont->nverts);966return false;967}968memcpy(cont->verts, &simplified[0], sizeof(int)*cont->nverts*4);969if (borderSize > 0)970{971// If the heightfield was build with bordersize, remove the offset.972for (int j = 0; j < cont->nverts; ++j)973{974int* v = &cont->verts[j*4];975v[0] -= borderSize;976v[2] -= borderSize;977}978}979980cont->nrverts = verts.size()/4;981cont->rverts = (int*)rcAlloc(sizeof(int)*cont->nrverts*4, RC_ALLOC_PERM);982if (!cont->rverts)983{984ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'rverts' (%d).", cont->nrverts);985return false;986}987memcpy(cont->rverts, &verts[0], sizeof(int)*cont->nrverts*4);988if (borderSize > 0)989{990// If the heightfield was build with bordersize, remove the offset.991for (int j = 0; j < cont->nrverts; ++j)992{993int* v = &cont->rverts[j*4];994v[0] -= borderSize;995v[2] -= borderSize;996}997}998999cont->reg = reg;1000cont->area = area;1001}1002}1003}1004}10051006// Merge holes if needed.1007if (cset.nconts > 0)1008{1009// Calculate winding of all polygons.1010rcScopedDelete<signed char> winding((signed char*)rcAlloc(sizeof(signed char)*cset.nconts, RC_ALLOC_TEMP));1011if (!winding)1012{1013ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'hole' (%d).", cset.nconts);1014return false;1015}1016int nholes = 0;1017for (int i = 0; i < cset.nconts; ++i)1018{1019rcContour& cont = cset.conts[i];1020// If the contour is wound backwards, it is a hole.1021winding[i] = calcAreaOfPolygon2D(cont.verts, cont.nverts) < 0 ? -1 : 1;1022if (winding[i] < 0)1023nholes++;1024}10251026if (nholes > 0)1027{1028// Collect outline contour and holes contours per region.1029// We assume that there is one outline and multiple holes.1030const int nregions = chf.maxRegions+1;1031rcScopedDelete<rcContourRegion> regions((rcContourRegion*)rcAlloc(sizeof(rcContourRegion)*nregions, RC_ALLOC_TEMP));1032if (!regions)1033{1034ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'regions' (%d).", nregions);1035return false;1036}1037memset(regions, 0, sizeof(rcContourRegion)*nregions);10381039rcScopedDelete<rcContourHole> holes((rcContourHole*)rcAlloc(sizeof(rcContourHole)*cset.nconts, RC_ALLOC_TEMP));1040if (!holes)1041{1042ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'holes' (%d).", cset.nconts);1043return false;1044}1045memset(holes, 0, sizeof(rcContourHole)*cset.nconts);10461047for (int i = 0; i < cset.nconts; ++i)1048{1049rcContour& cont = cset.conts[i];1050// Positively would contours are outlines, negative holes.1051if (winding[i] > 0)1052{1053if (regions[cont.reg].outline)1054ctx->log(RC_LOG_ERROR, "rcBuildContours: Multiple outlines for region %d.", cont.reg);1055regions[cont.reg].outline = &cont;1056}1057else1058{1059regions[cont.reg].nholes++;1060}1061}1062int index = 0;1063for (int i = 0; i < nregions; i++)1064{1065if (regions[i].nholes > 0)1066{1067regions[i].holes = &holes[index];1068index += regions[i].nholes;1069regions[i].nholes = 0;1070}1071}1072for (int i = 0; i < cset.nconts; ++i)1073{1074rcContour& cont = cset.conts[i];1075rcContourRegion& reg = regions[cont.reg];1076if (winding[i] < 0)1077reg.holes[reg.nholes++].contour = &cont;1078}10791080// Finally merge each regions holes into the outline.1081for (int i = 0; i < nregions; i++)1082{1083rcContourRegion& reg = regions[i];1084if (!reg.nholes) continue;10851086if (reg.outline)1087{1088mergeRegionHoles(ctx, reg);1089}1090else1091{1092// The region does not have an outline.1093// This can happen if the contour becaomes selfoverlapping because of1094// too aggressive simplification settings.1095ctx->log(RC_LOG_ERROR, "rcBuildContours: Bad outline for region %d, contour simplification is likely too aggressive.", i);1096}1097}1098}10991100}11011102return true;1103}110411051106