From ab023b1700877e09d7ed03b327fc64838cf58a69 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Fri, 20 Nov 2009 13:26:13 +0000 Subject: [PATCH] Added dtNavMesh, which is combination of dtStatNavMesh and dtTiledNavMesh. Added Sample_DynMesh which is used for dtNavMesh testing for now. --- Detour/Include/DetourCommon.h | 14 +- Detour/Include/DetourDebugDraw.h | 4 + Detour/Include/DetourNavMesh.h | 343 ++ Detour/Include/DetourNavMeshBuilder.h | 29 + Detour/Source/DetourDebugDraw.cpp | 269 ++ Detour/Source/DetourNavMesh.cpp | 1530 +++++++ Detour/Source/DetourNavMeshBuilder.cpp | 408 ++ .../Bin/Recast.app/Contents/MacOS/Recast | Bin 387408 -> 447812 bytes .../Xcode/Recast.xcodeproj/memon.pbxuser | 4052 ++++++++++++++++- .../Recast.xcodeproj/memon.perspectivev3 | 317 +- .../Xcode/Recast.xcodeproj/project.pbxproj | 18 + RecastDemo/Include/Sample_DynMesh.h | 114 + RecastDemo/Source/Sample_DynMesh.cpp | 931 ++++ RecastDemo/Source/main.cpp | 4 +- 14 files changed, 7902 insertions(+), 131 deletions(-) create mode 100644 Detour/Include/DetourNavMesh.h create mode 100644 Detour/Include/DetourNavMeshBuilder.h create mode 100644 Detour/Source/DetourNavMesh.cpp create mode 100644 Detour/Source/DetourNavMeshBuilder.cpp create mode 100644 RecastDemo/Include/Sample_DynMesh.h create mode 100644 RecastDemo/Source/Sample_DynMesh.cpp diff --git a/Detour/Include/DetourCommon.h b/Detour/Include/DetourCommon.h index 1881d80..5486098 100644 --- a/Detour/Include/DetourCommon.h +++ b/Detour/Include/DetourCommon.h @@ -113,7 +113,7 @@ inline bool vequal(const float* p0, const float* p1) return d < thr; } -inline int nextPow2(int v) +inline unsigned int nextPow2(unsigned int v) { v--; v |= v >> 1; @@ -125,6 +125,18 @@ inline int nextPow2(int v) return v; } +inline unsigned int ilog2(unsigned int v) +{ + unsigned int r; + unsigned int shift; + r = (v > 0xffff) << 4; v >>= r; + shift = (v > 0xff) << 3; v >>= shift; r |= shift; + shift = (v > 0xf) << 2; v >>= shift; r |= shift; + shift = (v > 0x3) << 1; v >>= shift; r |= shift; + r |= (v >> 1); + return r; +} + inline int align4(int x) { return (x+3) & ~3; } inline float vdot2D(const float* u, const float* v) diff --git a/Detour/Include/DetourDebugDraw.h b/Detour/Include/DetourDebugDraw.h index 8e1cfe4..6c46208 100755 --- a/Detour/Include/DetourDebugDraw.h +++ b/Detour/Include/DetourDebugDraw.h @@ -21,6 +21,7 @@ #include "DetourStatNavMesh.h" #include "DetourTileNavMesh.h" +#include "DetourNavMesh.h" void dtDebugDrawStatNavMeshPoly(const dtStatNavMesh* mesh, dtStatPolyRef ref, const float* col); void dtDebugDrawStatNavMeshBVTree(const dtStatNavMesh* mesh); @@ -29,4 +30,7 @@ void dtDebugDrawStatNavMesh(const dtStatNavMesh* mesh, bool drawClosedList = fal void dtDebugDrawTiledNavMesh(const dtTiledNavMesh* mesh); void dtDebugDrawTiledNavMeshPoly(const dtTiledNavMesh* mesh, dtTilePolyRef ref, const float* col); +void dtDebugDrawNavMesh(const dtNavMesh* mesh); +void dtDebugDrawNavMeshPoly(const dtNavMesh* mesh, dtPolyRef ref, const float* col); + #endif // DETOURDEBUGDRAW_H \ No newline at end of file diff --git a/Detour/Include/DetourNavMesh.h b/Detour/Include/DetourNavMesh.h new file mode 100644 index 0000000..e275709 --- /dev/null +++ b/Detour/Include/DetourNavMesh.h @@ -0,0 +1,343 @@ +// +// 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. +// + +#ifndef DETOURNAVMESH_H +#define DETOURNAVMESH_H + +// Reference to navigation polygon. +typedef unsigned int dtPolyRef; + +// The bits used in the poly ref. +/*static const int DT_REF_SALT_BITS = 12; +static const int DT_REF_TILE_BITS = 12; +static const int DT_REF_POLY_BITS = 8; +static const int DT_REF_SALT_MASK = (1<> (m_polyBits+m_tileBits)) & ((1<> m_polyBits) - 1) & ((1<npolys; ++i) + { + const dtPoly* p = &header->polys[i]; + const dtPolyDetail* pd = &header->dmeshes[i]; + + for (int j = 0, nj = (int)p->nv; j < nj; ++j) + { + if (inner) + { + if (p->n[j] == 0) continue; + if (p->n[j] & 0x8000) + { + bool con = false; + for (int k = 0; k < p->nlinks; ++k) + { + if (header->links[p->links+k].e == j) + { + con = true; + break; + } + } + if (con) + glColor4ub(255,255,255,128); + else + glColor4ub(0,0,0,128); + } + else + glColor4ub(0,48,64,32); + } + else + { + if (p->n[j] != 0) continue; + } + + const float* v0 = &header->verts[p->v[j]*3]; + const float* v1 = &header->verts[p->v[(j+1)%nj]*3]; + + // Draw detail mesh edges which align with the actual poly edge. + // This is really slow. + for (int k = 0; k < pd->ntris; ++k) + { + const unsigned char* t = &header->dtris[(pd->tbase+k)*4]; + const float* tv[3]; + for (int m = 0; m < 3; ++m) + { + if (t[m] < p->nv) + tv[m] = &header->verts[p->v[t[m]]*3]; + else + tv[m] = &header->dverts[(pd->vbase+(t[m]-p->nv))*3]; + } + for (int m = 0, n = 2; m < 3; n=m++) + { + if (((t[3] >> (n*2)) & 0x3) == 0) continue; // Skip inner detail edges. + if (distancePtLine2d(tv[n],v0,v1) < thr && + distancePtLine2d(tv[m],v0,v1) < thr) + { + glVertex3fv(tv[n]); + glVertex3fv(tv[m]); + } + } + } + } + } + glEnd(); +} + +static void drawMeshTile(const dtMeshHeader* header) +{ + glBegin(GL_TRIANGLES); + for (int i = 0; i < header->npolys; ++i) + { + const dtPoly* p = &header->polys[i]; + const dtPolyDetail* pd = &header->dmeshes[i]; + + glColor4ub(0,196,255,64); + + for (int j = 0; j < pd->ntris; ++j) + { + const unsigned char* t = &header->dtris[(pd->tbase+j)*4]; + for (int k = 0; k < 3; ++k) + { + if (t[k] < p->nv) + glVertex3fv(&header->verts[p->v[t[k]]*3]); + else + glVertex3fv(&header->dverts[(pd->vbase+t[k]-p->nv)*3]); + } + } + } + glEnd(); + + // Draw inter poly boundaries + glColor4ub(0,48,64,32); + glLineWidth(1.5f); + + drawPolyBoundaries(header, true); + + // Draw outer poly boundaries + glLineWidth(2.5f); + glColor4ub(0,48,64,220); + + drawPolyBoundaries(header, false); + + glLineWidth(1.0f); + + glPointSize(3.0f); + glColor4ub(0,0,0,196); + glBegin(GL_POINTS); + for (int i = 0; i < header->nverts; ++i) + { + const float* v = &header->verts[i*3]; + glVertex3f(v[0], v[1], v[2]); + } + glEnd(); + glPointSize(1.0f); + +/* + // Draw BV nodes. + const float col[] = { 1,1,1,0.5f }; + const float cs = 1.0f / header->bvquant; + glBegin(GL_LINES); + for (int i = 0; i < header->nbvtree; ++i) + { + const dtBVNode* n = &header->bvtree[i]; + if (n->i < 0) // Leaf indices are positive. + continue; + drawBoxWire(header->bmin[0] + n->bmin[0]*cs, + header->bmin[1] + n->bmin[1]*cs, + header->bmin[2] + n->bmin[2]*cs, + header->bmin[0] + n->bmax[0]*cs, + header->bmin[1] + n->bmax[1]*cs, + header->bmin[2] + n->bmax[2]*cs, col); + } + glEnd(); +*/ + + // Draw portals + /* glBegin(GL_LINES); + + for (int i = 0; i < header->nportals[0]; ++i) + { + const dtTilePortal* p = &header->portals[0][i]; + if (p->ncon) + glColor4ub(255,255,255,192); + else + glColor4ub(255,0,0,64); + glVertex3f(header->bmax[0]-0.1f, p->bmin[1], p->bmin[0]); + glVertex3f(header->bmax[0]-0.1f, p->bmax[1], p->bmin[0]); + + glVertex3f(header->bmax[0]-0.1f, p->bmax[1], p->bmin[0]); + glVertex3f(header->bmax[0]-0.1f, p->bmax[1], p->bmax[0]); + + glVertex3f(header->bmax[0]-0.1f, p->bmax[1], p->bmax[0]); + glVertex3f(header->bmax[0]-0.1f, p->bmin[1], p->bmax[0]); + + glVertex3f(header->bmax[0]-0.1f, p->bmin[1], p->bmax[0]); + glVertex3f(header->bmax[0]-0.1f, p->bmin[1], p->bmin[0]); + } + for (int i = 0; i < header->nportals[1]; ++i) + { + const dtTilePortal* p = &header->portals[1][i]; + if (p->ncon) + glColor4ub(255,255,255,192); + else + glColor4ub(255,0,0,64); + glVertex3f(p->bmin[0], p->bmin[1], header->bmax[2]-0.1f); + glVertex3f(p->bmin[0], p->bmax[1], header->bmax[2]-0.1f); + + glVertex3f(p->bmin[0], p->bmax[1], header->bmax[2]-0.1f); + glVertex3f(p->bmax[0], p->bmax[1], header->bmax[2]-0.1f); + + glVertex3f(p->bmax[0], p->bmax[1], header->bmax[2]-0.1f); + glVertex3f(p->bmax[0], p->bmin[1], header->bmax[2]-0.1f); + + glVertex3f(p->bmax[0], p->bmin[1], header->bmax[2]-0.1f); + glVertex3f(p->bmin[0], p->bmin[1], header->bmax[2]-0.1f); + } + for (int i = 0; i < header->nportals[2]; ++i) + { + const dtTilePortal* p = &header->portals[2][i]; + if (p->ncon) + glColor4ub(255,255,255,192); + else + glColor4ub(255,0,0,64); + glVertex3f(header->bmin[0]+0.1f, p->bmin[1], p->bmin[0]); + glVertex3f(header->bmin[0]+0.1f, p->bmax[1], p->bmin[0]); + + glVertex3f(header->bmin[0]+0.1f, p->bmax[1], p->bmin[0]); + glVertex3f(header->bmin[0]+0.1f, p->bmax[1], p->bmax[0]); + + glVertex3f(header->bmin[0]+0.1f, p->bmax[1], p->bmax[0]); + glVertex3f(header->bmin[0]+0.1f, p->bmin[1], p->bmax[0]); + + glVertex3f(header->bmin[0]+0.1f, p->bmin[1], p->bmax[0]); + glVertex3f(header->bmin[0]+0.1f, p->bmin[1], p->bmin[0]); + } + for (int i = 0; i < header->nportals[3]; ++i) + { + const dtTilePortal* p = &header->portals[3][i]; + if (p->ncon) + glColor4ub(255,255,255,192); + else + glColor4ub(255,0,0,64); + glVertex3f(p->bmin[0], p->bmin[1], header->bmin[2]+0.1f); + glVertex3f(p->bmin[0], p->bmax[1], header->bmin[2]+0.1f); + + glVertex3f(p->bmin[0], p->bmax[1], header->bmin[2]+0.1f); + glVertex3f(p->bmax[0], p->bmax[1], header->bmin[2]+0.1f); + + glVertex3f(p->bmax[0], p->bmax[1], header->bmin[2]+0.1f); + glVertex3f(p->bmax[0], p->bmin[1], header->bmin[2]+0.1f); + + glVertex3f(p->bmax[0], p->bmin[1], header->bmin[2]+0.1f); + glVertex3f(p->bmin[0], p->bmin[1], header->bmin[2]+0.1f); + } + glEnd();*/ +} + +void dtDebugDrawNavMesh(const dtNavMesh* mesh) +{ + if (!mesh) return; + + for (int i = 0; i < mesh->getMaxTiles(); ++i) + { + const dtMeshTile* tile = mesh->getTile(i); + if (!tile->header) continue; + drawMeshTile(tile->header); + } +} + + + +void dtDebugDrawNavMeshPoly(const dtNavMesh* mesh, dtPolyRef ref, const float* col) +{ + int ip = 0; + const dtMeshTile* tile = mesh->getTileByRef(ref, &ip); + if (!tile) + return; + const dtMeshHeader* header = tile->header; + const dtPoly* p = &header->polys[ip]; + const dtPolyDetail* pd = &header->dmeshes[ip]; + + glColor4f(col[0],col[1],col[2],0.25f); + + glBegin(GL_TRIANGLES); + for (int i = 0; i < pd->ntris; ++i) + { + const unsigned char* t = &header->dtris[(pd->tbase+i)*4]; + for (int j = 0; j < 3; ++j) + { + if (t[j] < p->nv) + glVertex3fv(&header->verts[p->v[t[j]]*3]); + else + glVertex3fv(&header->dverts[(pd->vbase+t[j]-p->nv)*3]); + } + } + glEnd(); +} + diff --git a/Detour/Source/DetourNavMesh.cpp b/Detour/Source/DetourNavMesh.cpp new file mode 100644 index 0000000..29a0e9d --- /dev/null +++ b/Detour/Source/DetourNavMesh.cpp @@ -0,0 +1,1530 @@ +// +// 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 +#include +#include +#include +#include "DetourNavMesh.h" +#include "DetourNode.h" +#include "DetourCommon.h" + + +static const unsigned short EXT_LINK = 0x8000; + +inline int opposite(int side) { return (side+2) & 0x3; } + +inline bool overlapBoxes(const float* amin, const float* amax, + const float* bmin, const float* bmax) +{ + bool overlap = true; + overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap; + overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap; + overlap = (amin[2] > bmax[2] || amax[2] < bmin[2]) ? false : overlap; + return overlap; +} + +inline bool overlapRects(const float* amin, const float* amax, + const float* bmin, const float* bmax) +{ + bool overlap = true; + overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap; + overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap; + return overlap; +} + +static void calcRect(const float* va, const float* vb, + float* bmin, float* bmax, + int side, float padx, float pady) +{ + if ((side&1) == 0) + { + bmin[0] = min(va[2],vb[2]) + padx; + bmin[1] = min(va[1],vb[1]) - pady; + bmax[0] = max(va[2],vb[2]) - padx; + bmax[1] = max(va[1],vb[1]) + pady; + } + else + { + bmin[0] = min(va[0],vb[0]) + padx; + bmin[1] = min(va[1],vb[1]) - pady; + bmax[0] = max(va[0],vb[0]) - padx; + bmax[1] = max(va[1],vb[1]) + pady; + } +} + +inline int computeTileHash(int x, int y, const int mask) +{ + const unsigned int h1 = 0x8da6b343; // Large multiplicative constants; + const unsigned int h2 = 0xd8163841; // here arbitrarily chosen primes + unsigned int n = h1 * x + h2 * y; + return (int)(n & mask); +} + +////////////////////////////////////////////////////////////////////////////////////////// +dtNavMesh::dtNavMesh() : + m_tileSize(0), + m_portalHeight(0), + m_nextFree(0), + m_tmpLinks(0), + m_ntmpLinks(0), + m_nodePool(0), + m_openList(0), + m_posLookup(0), + m_tiles(0) +{ +} + +dtNavMesh::~dtNavMesh() +{ + for (int i = 0; i < m_maxTiles; ++i) + { + if (m_tiles[i].data && m_tiles[i].ownsData) + { + delete [] m_tiles[i].data; + m_tiles[i].data = 0; + m_tiles[i].dataSize = 0; + } + } + delete [] m_tmpLinks; + delete m_nodePool; + delete m_openList; + delete [] m_posLookup; + delete [] m_tiles; +} + +bool dtNavMesh::init(const float* orig, float tileSize, float portalHeight, + int maxTiles, int maxPolys, int maxNodes) +{ + vcopy(m_orig, orig); + m_tileSize = tileSize; + m_portalHeight = portalHeight; + + // Init tiles + m_maxTiles = maxTiles; + m_tileLutSize = nextPow2(maxTiles/4); + if (!m_tileLutSize) m_tileLutSize = 1; + m_tileLutMask = m_tileLutSize-1; + + m_tiles = new dtMeshTile[m_maxTiles]; + if (!m_tiles) + return false; + m_posLookup = new dtMeshTile*[m_tileLutSize]; + if (!m_posLookup) + return false; + memset(m_tiles, 0, sizeof(dtMeshTile)*m_maxTiles); + memset(m_posLookup, 0, sizeof(dtMeshTile*)*m_tileLutSize); + m_nextFree = 0; + for (int i = m_maxTiles-1; i >= 0; --i) + { + m_tiles[i].next = m_nextFree; + m_nextFree = &m_tiles[i]; + } + + if (!m_nodePool) + { + m_nodePool = new dtNodePool(maxNodes, nextPow2(maxNodes/4)); + if (!m_nodePool) + return false; + } + + if (!m_openList) + { + m_openList = new dtNodeQueue(maxNodes); + if (!m_openList) + return false; + } + + // Init ID generator values. + m_tileBits = max((unsigned int)1,ilog2(nextPow2((unsigned int)maxTiles))); + m_polyBits = max((unsigned int)1,ilog2(nextPow2((unsigned int)maxPolys))); + m_saltBits = 32 - m_tileBits - m_polyBits; + if (m_saltBits < 10) + return false; + + return true; +} + +////////////////////////////////////////////////////////////////////////////////////////// +int dtNavMesh::findConnectingPolys(const float* va, const float* vb, + dtMeshTile* tile, int side, + dtPolyRef* con, float* conarea, int maxcon) +{ + if (!tile) return 0; + dtMeshHeader* h = tile->header; + + float amin[2], amax[2]; + calcRect(va,vb, amin,amax, side, 0.01f, m_portalHeight); + + // Remove links pointing to 'side' and compact the links array. + float bmin[2], bmax[2]; + unsigned short m = EXT_LINK | (unsigned short)side; + int n = 0; + + dtPolyRef base = getTileId(tile); + + for (int i = 0; i < h->npolys; ++i) + { + dtPoly* poly = &h->polys[i]; + for (int j = 0; j < poly->nv; ++j) + { + // Skip edges which do not point to the right side. + if (poly->n[j] != m) continue; + // Check if the segments touch. + const float* vc = &h->verts[poly->v[j]*3]; + const float* vd = &h->verts[poly->v[(j+1) % (int)poly->nv]*3]; + calcRect(vc,vd, bmin,bmax, side, 0.01f, m_portalHeight); + if (!overlapRects(amin,amax, bmin,bmax)) continue; + // Add return value. + if (n < maxcon) + { + conarea[n*2+0] = max(amin[0], bmin[0]); + conarea[n*2+1] = min(amax[0], bmax[0]); + con[n] = base | (unsigned int)i; + n++; + } + break; + } + } + return n; +} + +void dtNavMesh::removeExtLinks(dtMeshTile* tile, int side) +{ + if (!tile) return; + dtMeshHeader* h = tile->header; + + // Remove links pointing to 'side' and compact the links array. + dtLink* pool = m_tmpLinks; + int nlinks = 0; + for (int i = 0; i < h->npolys; ++i) + { + dtPoly* poly = &h->polys[i]; + int plinks = nlinks; + int nplinks = 0; + for (int j = 0; j < poly->nlinks; ++j) + { + dtLink* link = &h->links[poly->links+j]; + if ((int)link->side != side) + { + if (nlinks < h->maxlinks) + { + dtLink* dst = &pool[nlinks++]; + memcpy(dst, link, sizeof(dtLink)); + nplinks++; + } + } + } + poly->links = plinks; + poly->nlinks = nplinks; + } + h->nlinks = nlinks; + if (h->nlinks) + memcpy(h->links, m_tmpLinks, sizeof(dtLink)*nlinks); +} + +void dtNavMesh::buildExtLinks(dtMeshTile* tile, dtMeshTile* target, int side) +{ + if (!tile) return; + dtMeshHeader* h = tile->header; + + // Remove links pointing to 'side' and compact the links array. + dtLink* pool = m_tmpLinks; + int nlinks = 0; + for (int i = 0; i < h->npolys; ++i) + { + dtPoly* poly = &h->polys[i]; + int plinks = nlinks; + int nplinks = 0; + // Copy internal and other external links. + for (int j = 0; j < poly->nlinks; ++j) + { + dtLink* link = &h->links[poly->links+j]; + if ((int)link->side != side) + { + if (nlinks < h->maxlinks) + { + dtLink* dst = &pool[nlinks++]; + memcpy(dst, link, sizeof(dtLink)); + nplinks++; + } + } + } + // Create new links. + unsigned short m = EXT_LINK | (unsigned short)side; + for (int j = 0; j < poly->nv; ++j) + { + // Skip edges which do not point to the right side. + if (poly->n[j] != m) continue; + + // Create new links + const float* va = &h->verts[poly->v[j]*3]; + const float* vb = &h->verts[poly->v[(j+1)%(int)poly->nv]*3]; + dtPolyRef nei[4]; + float neia[4*2]; + int nnei = findConnectingPolys(va,vb, target, opposite(side), nei,neia,4); + for (int k = 0; k < nnei; ++k) + { + if (nlinks < h->maxlinks) + { + dtLink* link = &pool[nlinks++]; + link->ref = nei[k]; + link->p = (unsigned short)i; + link->e = (unsigned char)j; + link->side = (unsigned char)side; + + // Compress portal limits to a byte value. + if (side == 0 || side == 2) + { + const float lmin = min(va[2], vb[2]); + const float lmax = max(va[2], vb[2]); + link->bmin = (unsigned char)(clamp((neia[k*2+0]-lmin)/(lmax-lmin), 0.0f, 1.0f)*255.0f); + link->bmax = (unsigned char)(clamp((neia[k*2+1]-lmin)/(lmax-lmin), 0.0f, 1.0f)*255.0f); + } + else + { + const float lmin = min(va[0], vb[0]); + const float lmax = max(va[0], vb[0]); + link->bmin = (unsigned char)(clamp((neia[k*2+0]-lmin)/(lmax-lmin), 0.0f, 1.0f)*255.0f); + link->bmax = (unsigned char)(clamp((neia[k*2+1]-lmin)/(lmax-lmin), 0.0f, 1.0f)*255.0f); + } + nplinks++; + } + } + } + + poly->links = plinks; + poly->nlinks = nplinks; + } + h->nlinks = nlinks; + if (h->nlinks) + memcpy(h->links, m_tmpLinks, sizeof(dtLink)*nlinks); +} + +void dtNavMesh::buildIntLinks(dtMeshTile* tile) +{ + if (!tile) return; + dtMeshHeader* h = tile->header; + + dtPolyRef base = getTileId(tile); + dtLink* pool = h->links; + int nlinks = 0; + for (int i = 0; i < h->npolys; ++i) + { + dtPoly* poly = &h->polys[i]; + poly->links = nlinks; + poly->nlinks = 0; + for (int j = 0; j < poly->nv; ++j) + { + // Skip hard and non-internal edges. + if (poly->n[j] == 0 || (poly->n[j] & EXT_LINK)) continue; + + if (nlinks < h->maxlinks) + { + dtLink* link = &pool[nlinks++]; + link->ref = base | (unsigned int)(poly->n[j]-1); + link->p = (unsigned short)i; + link->e = (unsigned char)j; + link->side = 0xff; + link->bmin = link->bmax = 0; + poly->nlinks++; + } + } + } + h->nlinks = nlinks; +} + +bool dtNavMesh::addTileAt(int x, int y, unsigned char* data, int dataSize, bool ownsData) +{ + if (getTileAt(x,y)) + return false; + // Make sure there is enough space for new tile. + if (!m_nextFree) + return false; + // Make sure the data is in right format. + dtMeshHeader* header = (dtMeshHeader*)data; + if (header->magic != DT_NAVMESH_MAGIC) + return false; + if (header->version != DT_NAVMESH_VERSION) + return false; + + // Make sure the tmp link array is large enough. + if (header->maxlinks > m_ntmpLinks) + { + m_ntmpLinks = header->maxlinks; + delete [] m_tmpLinks; + m_tmpLinks = 0; + m_tmpLinks = new dtLink[m_ntmpLinks]; + } + if (!m_tmpLinks) + return false; + + // Allocate a tile. + dtMeshTile* tile = m_nextFree; + m_nextFree = tile->next; + tile->next = 0; + + // Insert tile into the position lut. + int h = computeTileHash(x,y,m_tileLutMask); + tile->next = m_posLookup[h]; + m_posLookup[h] = tile; + + // Patch header pointers. + const int headerSize = align4(sizeof(dtMeshHeader)); + const int vertsSize = align4(sizeof(float)*3*header->nverts); + const int polysSize = align4(sizeof(dtPoly)*header->npolys); + const int linksSize = align4(sizeof(dtLink)*(header->maxlinks)); + const int detailMeshesSize = align4(sizeof(dtPolyDetail)*header->ndmeshes); + const int detailVertsSize = align4(sizeof(float)*3*header->ndverts); + const int detailTrisSize = align4(sizeof(unsigned char)*4*header->ndtris); + const int bvtreeSize = header->nbvtree ? align4(sizeof(dtBVNode)*header->npolys*2) : 0; + + unsigned char* d = data + headerSize; + header->verts = (float*)d; d += vertsSize; + header->polys = (dtPoly*)d; d += polysSize; + header->links = (dtLink*)d; d += linksSize; + header->dmeshes = (dtPolyDetail*)d; d += detailMeshesSize; + header->dverts = (float*)d; d += detailVertsSize; + header->dtris = (unsigned char*)d; d += detailTrisSize; + header->bvtree = header->nbvtree ? (dtBVNode*)d : 0; d += bvtreeSize; + + // Init tile. + tile->header = header; + tile->x = x; + tile->y = y; + tile->data = data; + tile->dataSize = dataSize; + tile->ownsData = ownsData; + + buildIntLinks(tile); + + // Create connections connections. + for (int i = 0; i < 4; ++i) + { + dtMeshTile* nei = getNeighbourTileAt(x,y,i); + if (nei) + { + buildExtLinks(tile, nei, i); + buildExtLinks(nei, tile, opposite(i)); + } + } + + return true; +} + +dtMeshTile* dtNavMesh::getTileAt(int x, int y) +{ + // Find tile based on hash. + int h = computeTileHash(x,y,m_tileLutMask); + dtMeshTile* tile = m_posLookup[h]; + while (tile) + { + if (tile->x == x && tile->y == y) + return tile; + tile = tile->next; + } + return 0; +} + +int dtNavMesh::getMaxTiles() const +{ + return m_maxTiles; +} + +dtMeshTile* dtNavMesh::getTile(int i) +{ + return &m_tiles[i]; +} + +const dtMeshTile* dtNavMesh::getTile(int i) const +{ + return &m_tiles[i]; +} + +const dtMeshTile* dtNavMesh::getTileByRef(dtPolyRef ref, int* polyIndex) const +{ + unsigned int salt, it, ip; + dtDecodePolyId(ref, salt, it, ip); + if (it >= (unsigned int)m_maxTiles) return 0; + if (m_tiles[it].salt != salt || m_tiles[it].header == 0) return 0; + if (ip >= (unsigned int)m_tiles[it].header->npolys) return 0; + if (polyIndex) *polyIndex = (int)ip; + return &m_tiles[it]; +} + +dtMeshTile* dtNavMesh::getNeighbourTileAt(int x, int y, int side) +{ + switch (side) + { + case 0: x++; break; + case 1: y++; break; + case 2: x--; break; + case 3: y--; break; + }; + return getTileAt(x,y); +} + +bool dtNavMesh::removeTileAt(int x, int y, unsigned char** data, int* dataSize) +{ + // Remove tile from hash lookup. + int h = computeTileHash(x,y,m_tileLutMask); + dtMeshTile* prev = 0; + dtMeshTile* tile = m_posLookup[h]; + while (tile) + { + if (tile->x == x && tile->y == y) + { + if (prev) + prev->next = tile->next; + else + m_posLookup[h] = tile->next; + break; + } + prev = tile; + tile = tile->next; + } + if (!tile) + return false; + + // Remove connections to neighbour tiles. + for (int i = 0; i < 4; ++i) + { + dtMeshTile* nei = getNeighbourTileAt(x,y,i); + if (!nei) continue; + removeExtLinks(nei, opposite(i)); + } + + + // Reset tile. + if (tile->ownsData) + { + // Owns data + delete [] tile->data; + tile->data = 0; + tile->dataSize = 0; + if (data) *data = 0; + if (dataSize) *dataSize = 0; + } + else + { + if (data) *data = tile->data; + if (dataSize) *dataSize = tile->dataSize; + } + tile->header = 0; + tile->x = tile->y = 0; + tile->salt++; + + // Add to free list. + tile->next = m_nextFree; + m_nextFree = tile; + + return true; +} + + + +bool dtNavMesh::closestPointToPoly(dtPolyRef ref, const float* pos, float* closest) const +{ + unsigned int salt, it, ip; + dtDecodePolyId(ref, salt, it, ip); + if (it >= m_maxTiles) return false; + if (m_tiles[it].salt != salt || m_tiles[it].header == 0) return false; + const dtMeshHeader* header = m_tiles[it].header; + + if (ip >= (unsigned int)header->npolys) return false; + const dtPoly* poly = &header->polys[ip]; + + float closestDistSqr = FLT_MAX; + const dtPolyDetail* pd = &header->dmeshes[ip]; + + for (int j = 0; j < pd->ntris; ++j) + { + const unsigned char* t = &header->dtris[(pd->tbase+j)*4]; + const float* v[3]; + for (int k = 0; k < 3; ++k) + { + if (t[k] < poly->nv) + v[k] = &header->verts[poly->v[t[k]]*3]; + else + v[k] = &header->dverts[(pd->vbase+(t[k]-poly->nv))*3]; + } + float pt[3]; + closestPtPointTriangle(pt, pos, v[0], v[1], v[2]); + float d = vdistSqr(pos, pt); + if (d < closestDistSqr) + { + vcopy(closest, pt); + closestDistSqr = d; + } + } + + return true; +} + +bool dtNavMesh::getPolyHeight(dtPolyRef ref, const float* pos, float* height) const +{ + unsigned int salt, it, ip; + dtDecodePolyId(ref, salt, it, ip); + if (it >= (unsigned int)m_maxTiles) return false; + if (m_tiles[it].salt != salt || m_tiles[it].header == 0) return false; + const dtMeshHeader* header = m_tiles[it].header; + + if (ip >= (unsigned int)header->npolys) return false; + const dtPoly* poly = &header->polys[ip]; + + const dtPolyDetail* pd = &header->dmeshes[ip]; + for (int j = 0; j < pd->ntris; ++j) + { + const unsigned char* t = &header->dtris[(pd->tbase+j)*4]; + const float* v[3]; + for (int k = 0; k < 3; ++k) + { + if (t[k] < poly->nv) + v[k] = &header->verts[poly->v[t[k]]*3]; + else + v[k] = &header->dverts[(pd->vbase+(t[k]-poly->nv))*3]; + } + float h; + if (closestHeightPointTriangle(pos, v[0], v[1], v[2], h)) + { + if (height) + *height = h; + return true; + } + } + + return false; +} + + +dtPolyRef dtNavMesh::findNearestPoly(const float* center, const float* extents) +{ + // Get nearby polygons from proximity grid. + dtPolyRef polys[128]; + int npolys = queryPolygons(center, extents, polys, 128); + + // Find nearest polygon amongst the nearby polygons. + dtPolyRef nearest = 0; + float nearestDistanceSqr = FLT_MAX; + for (int i = 0; i < npolys; ++i) + { + dtPolyRef ref = polys[i]; + float closest[3]; + if (!closestPointToPoly(ref, center, closest)) + continue; + float d = vdistSqr(center, closest); + if (d < nearestDistanceSqr) + { + nearestDistanceSqr = d; + nearest = ref; + } + } + + return nearest; +} + +dtPolyRef dtNavMesh::getTileId(dtMeshTile* tile) +{ + if (!tile) return 0; + const unsigned int it = tile - m_tiles; + return dtEncodePolyId(tile->salt, it, 0); +} + +int dtNavMesh::queryTilePolygons(dtMeshTile* tile, + const float* qmin, const float* qmax, + dtPolyRef* polys, const int maxPolys) +{ + const dtMeshHeader* header = tile->header; + if (header->bvtree) + { + const dtBVNode* node = &header->bvtree[0]; + const dtBVNode* end = &header->bvtree[header->nbvtree]; + + // Calculate quantized box + unsigned short bmin[3], bmax[3]; + // Clamp query box to world box. + float minx = clamp(qmin[0], header->bmin[0], header->bmax[0]) - header->bmin[0]; + float miny = clamp(qmin[1], header->bmin[1], header->bmax[1]) - header->bmin[1]; + float minz = clamp(qmin[2], header->bmin[2], header->bmax[2]) - header->bmin[2]; + float maxx = clamp(qmax[0], header->bmin[0], header->bmax[0]) - header->bmin[0]; + float maxy = clamp(qmax[1], header->bmin[1], header->bmax[1]) - header->bmin[1]; + float maxz = clamp(qmax[2], header->bmin[2], header->bmax[2]) - header->bmin[2]; + // Quantize + bmin[0] = (unsigned short)(header->bvquant * minx) & 0xfffe; + bmin[1] = (unsigned short)(header->bvquant * miny) & 0xfffe; + bmin[2] = (unsigned short)(header->bvquant * minz) & 0xfffe; + bmax[0] = (unsigned short)(header->bvquant * maxx + 1) | 1; + bmax[1] = (unsigned short)(header->bvquant * maxy + 1) | 1; + bmax[2] = (unsigned short)(header->bvquant * maxz + 1) | 1; + + // Traverse tree + dtPolyRef base = getTileId(tile); + int n = 0; + while (node < end) + { + bool overlap = checkOverlapBox(bmin, bmax, node->bmin, node->bmax); + bool isLeafNode = node->i >= 0; + + if (isLeafNode && overlap) + { + if (n < maxPolys) + polys[n++] = base | (dtPolyRef)node->i; + } + + if (overlap || isLeafNode) + node++; + else + { + const int escapeIndex = -node->i; + node += escapeIndex; + } + } + + return n; + } + else + { + float bmin[3], bmax[3]; + const dtMeshHeader* header = tile->header; + int n = 0; + dtPolyRef base = getTileId(tile); + for (int i = 0; i < header->npolys; ++i) + { + // Calc polygon bounds. + dtPoly* p = &header->polys[i]; + const float* v = &header->verts[p->v[0]*3]; + vcopy(bmin, v); + vcopy(bmax, v); + for (int j = 1; j < p->nv; ++j) + { + v = &header->verts[p->v[j]*3]; + vmin(bmin, v); + vmax(bmax, v); + } + if (overlapBoxes(qmin,qmax, bmin,bmax)) + { + if (n < maxPolys) + polys[n++] = base | (dtPolyRef)i; + } + } + return n; + } +} + +int dtNavMesh::queryPolygons(const float* center, const float* extents, + dtPolyRef* polys, const int maxPolys) +{ + float bmin[3], bmax[3]; + bmin[0] = center[0] - extents[0]; + bmin[1] = center[1] - extents[1]; + bmin[2] = center[2] - extents[2]; + + bmax[0] = center[0] + extents[0]; + bmax[1] = center[1] + extents[1]; + bmax[2] = center[2] + extents[2]; + + // Find tiles the query touches. + const int minx = (int)floorf((bmin[0]-m_orig[0]) / m_tileSize); + const int maxx = (int)ceilf((bmax[0]-m_orig[0]) / m_tileSize); + + const int miny = (int)floorf((bmin[2]-m_orig[2]) / m_tileSize); + const int maxy = (int)ceilf((bmax[2]-m_orig[2]) / m_tileSize); + + int n = 0; + for (int y = miny; y < maxy; ++y) + { + for (int x = minx; x < maxx; ++x) + { + dtMeshTile* tile = getTileAt(x,y); + if (!tile) continue; + n += queryTilePolygons(tile, bmin, bmax, polys+n, maxPolys-n); + if (n >= maxPolys) return n; + } + } + + printf("---\n"); + for (int i = 0; i < n; ++i) + { + unsigned int salt, it, ip; + dtDecodePolyId(polys[i], salt, it, ip); + printf("0x%08x s:%d t:%d p:%d\n", (unsigned int)polys[i], salt, it, ip); + } + + return n; +} + +int dtNavMesh::findPath(dtPolyRef startRef, dtPolyRef endRef, + const float* startPos, const float* endPos, + dtPolyRef* path, const int maxPathSize) +{ + if (!startRef || !endRef) + return 0; + + if (!maxPathSize) + return 0; + + if (!getPolyByRef(startRef) || !getPolyByRef(endRef)) + return 0; + + if (startRef == endRef) + { + path[0] = startRef; + return 1; + } + + if (!m_nodePool || !m_openList) + return 0; + + m_nodePool->clear(); + m_openList->clear(); + + static const float H_SCALE = 1.1f; // Heuristic scale. + + dtNode* startNode = m_nodePool->getNode(startRef); + startNode->pidx = 0; + startNode->cost = 0; + startNode->total = vdist(startPos, endPos) * H_SCALE; + startNode->id = startRef; + startNode->flags = DT_NODE_OPEN; + m_openList->push(startNode); + + dtNode* lastBestNode = startNode; + float lastBestNodeCost = startNode->total; + while (!m_openList->empty()) + { + dtNode* bestNode = m_openList->pop(); + + if (bestNode->id == endRef) + { + lastBestNode = bestNode; + break; + } + + // Get poly and tile. + unsigned int salt, it, ip; + dtDecodePolyId(bestNode->id, salt, it, ip); + // The API input has been cheked already, skip checking internal data. + const dtMeshHeader* header = m_tiles[it].header; + const dtPoly* poly = &header->polys[ip]; + + for (int i = 0; i < poly->nlinks; ++i) + { + dtPolyRef neighbour = header->links[poly->links+i].ref; + if (neighbour) + { + // Skip parent node. + if (bestNode->pidx && m_nodePool->getNodeAtIdx(bestNode->pidx)->id == neighbour) + continue; + + dtNode* parent = bestNode; + dtNode newNode; + newNode.pidx = m_nodePool->getNodeIdx(parent); + newNode.id = neighbour; + + // Calculate cost. + float p0[3], p1[3]; + if (!parent->pidx) + vcopy(p0, startPos); + else + getEdgeMidPoint(m_nodePool->getNodeAtIdx(parent->pidx)->id, parent->id, p0); + getEdgeMidPoint(parent->id, newNode.id, p1); + newNode.cost = parent->cost + vdist(p0,p1); + // Special case for last node. + if (newNode.id == endRef) + newNode.cost += vdist(p1, endPos); + + // Heuristic + const float h = vdist(p1,endPos)*H_SCALE; + newNode.total = newNode.cost + h; + + dtNode* actualNode = m_nodePool->getNode(newNode.id); + if (!actualNode) + continue; + + if (!((actualNode->flags & DT_NODE_OPEN) && newNode.total > actualNode->total) && + !((actualNode->flags & DT_NODE_CLOSED) && newNode.total > actualNode->total)) + { + actualNode->flags &= ~DT_NODE_CLOSED; + actualNode->pidx = newNode.pidx; + actualNode->cost = newNode.cost; + actualNode->total = newNode.total; + + if (h < lastBestNodeCost) + { + lastBestNodeCost = h; + lastBestNode = actualNode; + } + + if (actualNode->flags & DT_NODE_OPEN) + { + m_openList->modify(actualNode); + } + else + { + actualNode->flags |= DT_NODE_OPEN; + m_openList->push(actualNode); + } + } + } + } + bestNode->flags |= DT_NODE_CLOSED; + } + + // Reverse the path. + dtNode* prev = 0; + dtNode* node = lastBestNode; + do + { + dtNode* next = m_nodePool->getNodeAtIdx(node->pidx); + node->pidx = m_nodePool->getNodeIdx(prev); + prev = node; + node = next; + } + while (node); + + // Store path + node = prev; + int n = 0; + do + { + path[n++] = node->id; + node = m_nodePool->getNodeAtIdx(node->pidx); + } + while (node && n < maxPathSize); + + return n; +} + +int dtNavMesh::findStraightPath(const float* startPos, const float* endPos, + const dtPolyRef* path, const int pathSize, + float* straightPath, const int maxStraightPathSize) +{ + if (!maxStraightPathSize) + return 0; + + if (!path[0]) + return 0; + + int straightPathSize = 0; + + float closestStartPos[3]; + if (!closestPointToPoly(path[0], startPos, closestStartPos)) + return 0; + + // Add start point. + vcopy(&straightPath[straightPathSize*3], closestStartPos); + straightPathSize++; + if (straightPathSize >= maxStraightPathSize) + return straightPathSize; + + float closestEndPos[3]; + if (!closestPointToPoly(path[pathSize-1], endPos, closestEndPos)) + return 0; + + float portalApex[3], portalLeft[3], portalRight[3]; + + if (pathSize > 1) + { + vcopy(portalApex, closestStartPos); + vcopy(portalLeft, portalApex); + vcopy(portalRight, portalApex); + int apexIndex = 0; + int leftIndex = 0; + int rightIndex = 0; + + for (int i = 0; i < pathSize; ++i) + { + float left[3], right[3]; + if (i < pathSize-1) + { + // Next portal. + if (!getPortalPoints(path[i], path[i+1], left, right)) + { + if (!closestPointToPoly(path[i], endPos, closestEndPos)) + return 0; + vcopy(&straightPath[straightPathSize*3], closestEndPos); + straightPathSize++; + return straightPathSize; + } + } + else + { + // End of the path. + vcopy(left, closestEndPos); + vcopy(right, closestEndPos); + } + + // Right vertex. + if (vequal(portalApex, portalRight)) + { + vcopy(portalRight, right); + rightIndex = i; + } + else + { + if (triArea2D(portalApex, portalRight, right) <= 0.0f) + { + if (triArea2D(portalApex, portalLeft, right) > 0.0f) + { + vcopy(portalRight, right); + rightIndex = i; + } + else + { + vcopy(portalApex, portalLeft); + apexIndex = leftIndex; + + if (!vequal(&straightPath[(straightPathSize-1)*3], portalApex)) + { + vcopy(&straightPath[straightPathSize*3], portalApex); + straightPathSize++; + if (straightPathSize >= maxStraightPathSize) + return straightPathSize; + } + + vcopy(portalLeft, portalApex); + vcopy(portalRight, portalApex); + leftIndex = apexIndex; + rightIndex = apexIndex; + + // Restart + i = apexIndex; + + continue; + } + } + } + + // Left vertex. + if (vequal(portalApex, portalLeft)) + { + vcopy(portalLeft, left); + leftIndex = i; + } + else + { + if (triArea2D(portalApex, portalLeft, left) >= 0.0f) + { + if (triArea2D(portalApex, portalRight, left) < 0.0f) + { + vcopy(portalLeft, left); + leftIndex = i; + } + else + { + vcopy(portalApex, portalRight); + apexIndex = rightIndex; + + if (!vequal(&straightPath[(straightPathSize-1)*3], portalApex)) + { + vcopy(&straightPath[straightPathSize*3], portalApex); + straightPathSize++; + if (straightPathSize >= maxStraightPathSize) + return straightPathSize; + } + + vcopy(portalLeft, portalApex); + vcopy(portalRight, portalApex); + leftIndex = apexIndex; + rightIndex = apexIndex; + + // Restart + i = apexIndex; + + continue; + } + } + } + } + } + + // Add end point. + vcopy(&straightPath[straightPathSize*3], closestEndPos); + straightPathSize++; + + return straightPathSize; +} + +// Returns portal points between two polygons. +bool dtNavMesh::getPortalPoints(dtPolyRef from, dtPolyRef to, float* left, float* right) const +{ + unsigned int salt, it, ip; + dtDecodePolyId(from, salt, it, ip); + if (it >= (unsigned int)m_maxTiles) return false; + if (m_tiles[it].salt != salt || m_tiles[it].header == 0) return false; + if (ip >= (unsigned int)m_tiles[it].header->npolys) return false; + const dtMeshHeader* fromHeader = m_tiles[it].header; + const dtPoly* fromPoly = &fromHeader->polys[ip]; + + for (int i = 0; i < fromPoly->nlinks; ++i) + { + const dtLink* link = &fromHeader->links[fromPoly->links+i]; + if (link->ref == to) + { + // Find portal vertices. + const int v0 = fromPoly->v[link->e]; + const int v1 = fromPoly->v[(link->e+1) % fromPoly->nv]; + vcopy(left, &fromHeader->verts[v0*3]); + vcopy(right, &fromHeader->verts[v1*3]); + // If the link is at tile boundary, clamp the vertices to + // the link width. + if (link->side == 0 || link->side == 2) + { + // Unpack portal limits. + const float smin = min(left[2],right[2]); + const float smax = max(left[2],right[2]); + const float s = (smax-smin) / 255.0f; + const float lmin = smin + link->bmin*s; + const float lmax = smin + link->bmax*s; + left[2] = max(left[2],lmin); + left[2] = min(left[2],lmax); + right[2] = max(right[2],lmin); + right[2] = min(right[2],lmax); + } + else if (link->side == 1 || link->side == 3) + { + // Unpack portal limits. + const float smin = min(left[0],right[0]); + const float smax = max(left[0],right[0]); + const float s = (smax-smin) / 255.0f; + const float lmin = smin + link->bmin*s; + const float lmax = smin + link->bmax*s; + left[0] = max(left[0],lmin); + left[0] = min(left[0],lmax); + right[0] = max(right[0],lmin); + right[0] = min(right[0],lmax); + } + return true; + } + } + return false; +} + +// Returns edge mid point between two polygons. +bool dtNavMesh::getEdgeMidPoint(dtPolyRef from, dtPolyRef to, float* mid) const +{ + float left[3], right[3]; + if (!getPortalPoints(from, to, left,right)) return false; + mid[0] = (left[0]+right[0])*0.5f; + mid[1] = (left[1]+right[1])*0.5f; + mid[2] = (left[2]+right[2])*0.5f; + return true; +} + +int dtNavMesh::raycast(dtPolyRef centerRef, const float* startPos, const float* endPos, + float& t, dtPolyRef* path, const int pathSize) +{ + t = 0; + + if (!centerRef || !getPolyByRef(centerRef)) + return 0; + + dtPolyRef curRef = centerRef; + float verts[DT_VERTS_PER_POLYGON*3]; + int n = 0; + + while (curRef) + { + // Cast ray against current polygon. + + // The API input has been cheked already, skip checking internal data. + unsigned int salt, it, ip; + dtDecodePolyId(curRef, salt, it, ip); + const dtMeshHeader* header = m_tiles[it].header; + const dtPoly* poly = &header->polys[ip]; + + // Collect vertices. + int nv = 0; + for (int i = 0; i < (int)poly->nv; ++i) + { + vcopy(&verts[nv*3], &header->verts[poly->v[i]*3]); + nv++; + } + if (nv < 3) + { + // Hit bad polygon, report hit. + return n; + } + + float tmin, tmax; + int segMin, segMax; + if (!intersectSegmentPoly2D(startPos, endPos, verts, nv, tmin, tmax, segMin, segMax)) + { + // Could not hit the polygon, keep the old t and report hit. + return n; + } + // Keep track of furthest t so far. + if (tmax > t) + t = tmax; + + if (n < pathSize) + path[n++] = curRef; + + // Follow neighbours. + dtPolyRef nextRef = 0; + for (int i = 0; i < poly->nlinks; ++i) + { + const dtLink* link = &header->links[poly->links+i]; + if ((int)link->e == segMax) + { + // If the link is internal, just return the ref. + if (link->side == 0xff) + { + nextRef = link->ref; + break; + } + + // If the link is at tile boundary, + const int v0 = poly->v[link->e]; + const int v1 = poly->v[(link->e+1) % poly->nv]; + const float* left = &header->verts[v0*3]; + const float* right = &header->verts[v1*3]; + + // Check that the intersection lies inside the link portal. + if (link->side == 0 || link->side == 2) + { + // Calculate link size. + const float smin = min(left[2],right[2]); + const float smax = max(left[2],right[2]); + const float s = (smax-smin) / 255.0f; + const float lmin = smin + link->bmin*s; + const float lmax = smin + link->bmax*s; + // Find Z intersection. + float z = startPos[2] + (endPos[2]-startPos[2])*tmax; + if (z >= lmin && z <= lmax) + { + nextRef = link->ref; + break; + } + } + else if (link->side == 1 || link->side == 3) + { + // Calculate link size. + const float smin = min(left[0],right[0]); + const float smax = max(left[0],right[0]); + const float s = (smax-smin) / 255.0f; + const float lmin = smin + link->bmin*s; + const float lmax = smin + link->bmax*s; + // Find X intersection. + float x = startPos[0] + (endPos[0]-startPos[0])*tmax; + if (x >= lmin && x <= lmax) + { + nextRef = link->ref; + break; + } + } + } + } + + if (!nextRef) + { + // No neighbour, we hit a wall. + return n; + } + + // No hit, advance to neighbour polygon. + curRef = nextRef; + } + + return n; +} + +int dtNavMesh::findPolysAround(dtPolyRef centerRef, const float* centerPos, float radius, + dtPolyRef* resultRef, dtPolyRef* resultParent, float* resultCost, + const int maxResult) +{ + if (!centerRef) return 0; + if (!getPolyByRef(centerRef)) return 0; + if (!m_nodePool || !m_openList) return 0; + + m_nodePool->clear(); + m_openList->clear(); + + dtNode* startNode = m_nodePool->getNode(centerRef); + startNode->pidx = 0; + startNode->cost = 0; + startNode->total = 0; + startNode->id = centerRef; + startNode->flags = DT_NODE_OPEN; + m_openList->push(startNode); + + int n = 0; + if (n < maxResult) + { + if (resultRef) + resultRef[n] = startNode->id; + if (resultParent) + resultParent[n] = 0; + if (resultCost) + resultCost[n] = 0; + ++n; + } + + const float radiusSqr = sqr(radius); + + while (!m_openList->empty()) + { + dtNode* bestNode = m_openList->pop(); + + // Get poly and tile. + unsigned int salt, it, ip; + dtDecodePolyId(bestNode->id, salt, it, ip); + // The API input has been cheked already, skip checking internal data. + const dtMeshHeader* header = m_tiles[it].header; + const dtPoly* poly = &header->polys[ip]; + + for (int i = 0; i < poly->nlinks; ++i) + { + const dtLink* link = &header->links[poly->links+i]; + dtPolyRef neighbour = link->ref; + if (neighbour) + { + // Skip parent node. + if (bestNode->pidx && m_nodePool->getNodeAtIdx(bestNode->pidx)->id == neighbour) + continue; + + // Calc distance to the edge. + const float* va = &header->verts[poly->v[link->e]*3]; + const float* vb = &header->verts[poly->v[(link->e+1)%poly->nv]*3]; + float tseg; + float distSqr = distancePtSegSqr2D(centerPos, va, vb, tseg); + + // If the circle is not touching the next polygon, skip it. + if (distSqr > radiusSqr) + continue; + + dtNode* parent = bestNode; + dtNode newNode; + newNode.pidx = m_nodePool->getNodeIdx(parent); + newNode.id = neighbour; + + // Cost + float p0[3], p1[3]; + if (!parent->pidx) + vcopy(p0, centerPos); + else + getEdgeMidPoint(m_nodePool->getNodeAtIdx(parent->pidx)->id, parent->id, p0); + getEdgeMidPoint(parent->id, newNode.id, p1); + newNode.total = parent->total + vdist(p0,p1); + + dtNode* actualNode = m_nodePool->getNode(newNode.id); + if (!actualNode) + continue; + + if (!((actualNode->flags & DT_NODE_OPEN) && newNode.total > actualNode->total) && + !((actualNode->flags & DT_NODE_CLOSED) && newNode.total > actualNode->total)) + { + actualNode->flags &= ~DT_NODE_CLOSED; + actualNode->pidx = newNode.pidx; + actualNode->total = newNode.total; + + if (actualNode->flags & DT_NODE_OPEN) + { + m_openList->modify(actualNode); + } + else + { + if (n < maxResult) + { + if (resultRef) + resultRef[n] = actualNode->id; + if (resultParent) + resultParent[n] = m_nodePool->getNodeAtIdx(actualNode->pidx)->id; + if (resultCost) + resultCost[n] = actualNode->total; + ++n; + } + actualNode->flags = DT_NODE_OPEN; + m_openList->push(actualNode); + } + } + } + } + } + + return n; +} + +float dtNavMesh::findDistanceToWall(dtPolyRef centerRef, const float* centerPos, float maxRadius, + float* hitPos, float* hitNormal) +{ + if (!centerRef) return 0; + if (!getPolyByRef(centerRef)) return 0; + if (!m_nodePool || !m_openList) return 0; + + m_nodePool->clear(); + m_openList->clear(); + + dtNode* startNode = m_nodePool->getNode(centerRef); + startNode->pidx = 0; + startNode->cost = 0; + startNode->total = 0; + startNode->id = centerRef; + startNode->flags = DT_NODE_OPEN; + m_openList->push(startNode); + + float radiusSqr = sqr(maxRadius); + + while (!m_openList->empty()) + { + dtNode* bestNode = m_openList->pop(); + + // Get poly and tile. + unsigned int salt, it, ip; + dtDecodePolyId(bestNode->id, salt, it, ip); + // The API input has been cheked already, skip checking internal data. + const dtMeshHeader* header = m_tiles[it].header; + const dtPoly* poly = &header->polys[ip]; + + // Hit test walls. + for (int i = 0, j = (int)poly->nv-1; i < (int)poly->nv; j = i++) + { + // Skip non-solid edges. + if (poly->n[j] & EXT_LINK) + { + // Tile border. + bool solid = true; + for (int i = 0; i < poly->nlinks; ++i) + { + const dtLink* link = &header->links[poly->links+i]; + if (link->e == j && link->ref != 0) + { + solid = false; + break; + } + } + if (!solid) continue; + } + else if (poly->n[j]) + { + // Internal edge + continue; + } + + // Calc distance to the edge. + const float* vj = &header->verts[poly->v[j]*3]; + const float* vi = &header->verts[poly->v[i]*3]; + float tseg; + float distSqr = distancePtSegSqr2D(centerPos, vj, vi, tseg); + + // Edge is too far, skip. + if (distSqr > radiusSqr) + continue; + + // Hit wall, update radius. + radiusSqr = distSqr; + // Calculate hit pos. + hitPos[0] = vj[0] + (vi[0] - vj[0])*tseg; + hitPos[1] = vj[1] + (vi[1] - vj[1])*tseg; + hitPos[2] = vj[2] + (vi[2] - vj[2])*tseg; + } + + for (int i = 0; i < poly->nlinks; ++i) + { + const dtLink* link = &header->links[poly->links+i]; + dtPolyRef neighbour = link->ref; + if (neighbour) + { + // Skip parent node. + if (bestNode->pidx && m_nodePool->getNodeAtIdx(bestNode->pidx)->id == neighbour) + continue; + + // Calc distance to the edge. + const float* va = &header->verts[poly->v[link->e]*3]; + const float* vb = &header->verts[poly->v[(link->e+1)%poly->nv]*3]; + float tseg; + float distSqr = distancePtSegSqr2D(centerPos, va, vb, tseg); + + // If the circle is not touching the next polygon, skip it. + if (distSqr > radiusSqr) + continue; + + dtNode* parent = bestNode; + dtNode newNode; + newNode.pidx = m_nodePool->getNodeIdx(parent); + newNode.id = neighbour; + + float p0[3], p1[3]; + if (!parent->pidx) + vcopy(p0, centerPos); + else + getEdgeMidPoint(m_nodePool->getNodeAtIdx(parent->pidx)->id, parent->id, p0); + getEdgeMidPoint(parent->id, newNode.id, p1); + newNode.total = parent->total + vdist(p0,p1); + + dtNode* actualNode = m_nodePool->getNode(newNode.id); + if (!actualNode) + continue; + + if (!((actualNode->flags & DT_NODE_OPEN) && newNode.total > actualNode->total) && + !((actualNode->flags & DT_NODE_CLOSED) && newNode.total > actualNode->total)) + { + actualNode->flags &= ~DT_NODE_CLOSED; + actualNode->pidx = newNode.pidx; + actualNode->total = newNode.total; + + if (actualNode->flags & DT_NODE_OPEN) + { + m_openList->modify(actualNode); + } + else + { + actualNode->flags = DT_NODE_OPEN; + m_openList->push(actualNode); + } + } + } + } + } + + // Calc hit normal. + vsub(hitNormal, centerPos, hitPos); + vnormalize(hitNormal); + + return sqrtf(radiusSqr); +} + +const dtPoly* dtNavMesh::getPolyByRef(dtPolyRef ref) const +{ + unsigned int salt, it, ip; + dtDecodePolyId(ref, salt, it, ip); + if (it >= (unsigned int)m_maxTiles) return 0; + if (m_tiles[it].salt != salt || m_tiles[it].header == 0) return 0; + if (ip >= (unsigned int)m_tiles[it].header->npolys) return 0; + return &m_tiles[it].header->polys[ip]; +} + +const float* dtNavMesh::getPolyVertsByRef(dtPolyRef ref) const +{ + unsigned int salt, it, ip; + dtDecodePolyId(ref, salt, it, ip); + if (it >= (unsigned int)m_maxTiles) return 0; + if (m_tiles[it].salt != salt || m_tiles[it].header == 0) return 0; + if (ip >= (unsigned int)m_tiles[it].header->npolys) return 0; + return m_tiles[it].header->verts; +} + +const dtLink* dtNavMesh::getPolyLinksByRef(dtPolyRef ref) const +{ + unsigned int salt, it, ip; + dtDecodePolyId(ref, salt, it, ip); + if (it >= (unsigned int)m_maxTiles) return 0; + if (m_tiles[it].salt != salt || m_tiles[it].header == 0) return 0; + if (ip >= (unsigned int)m_tiles[it].header->npolys) return 0; + return m_tiles[it].header->links; +} diff --git a/Detour/Source/DetourNavMeshBuilder.cpp b/Detour/Source/DetourNavMeshBuilder.cpp new file mode 100644 index 0000000..c5d92e8 --- /dev/null +++ b/Detour/Source/DetourNavMeshBuilder.cpp @@ -0,0 +1,408 @@ +// +// 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 +#include +#include +#include +#include "DetourNavMesh.h" +#include "DetourCommon.h" + + + +struct BVItem +{ + unsigned short bmin[3]; + unsigned short bmax[3]; + int i; +}; + +static int compareItemX(const void* va, const void* vb) +{ + const BVItem* a = (const BVItem*)va; + const BVItem* b = (const BVItem*)vb; + if (a->bmin[0] < b->bmin[0]) + return -1; + if (a->bmin[0] > b->bmin[0]) + return 1; + return 0; +} + +static int compareItemY(const void* va, const void* vb) +{ + const BVItem* a = (const BVItem*)va; + const BVItem* b = (const BVItem*)vb; + if (a->bmin[1] < b->bmin[1]) + return -1; + if (a->bmin[1] > b->bmin[1]) + return 1; + return 0; +} + +static int compareItemZ(const void* va, const void* vb) +{ + const BVItem* a = (const BVItem*)va; + const BVItem* b = (const BVItem*)vb; + if (a->bmin[2] < b->bmin[2]) + return -1; + if (a->bmin[2] > b->bmin[2]) + return 1; + return 0; +} + +static void calcExtends(BVItem* items, int nitems, int imin, int imax, + unsigned short* bmin, unsigned short* bmax) +{ + bmin[0] = items[imin].bmin[0]; + bmin[1] = items[imin].bmin[1]; + bmin[2] = items[imin].bmin[2]; + + bmax[0] = items[imin].bmax[0]; + bmax[1] = items[imin].bmax[1]; + bmax[2] = items[imin].bmax[2]; + + for (int i = imin+1; i < imax; ++i) + { + const BVItem& it = items[i]; + if (it.bmin[0] < bmin[0]) bmin[0] = it.bmin[0]; + if (it.bmin[1] < bmin[1]) bmin[1] = it.bmin[1]; + if (it.bmin[2] < bmin[2]) bmin[2] = it.bmin[2]; + + if (it.bmax[0] > bmax[0]) bmax[0] = it.bmax[0]; + if (it.bmax[1] > bmax[1]) bmax[1] = it.bmax[1]; + if (it.bmax[2] > bmax[2]) bmax[2] = it.bmax[2]; + } +} + +inline int longestAxis(unsigned short x, unsigned short y, unsigned short z) +{ + int axis = 0; + unsigned short maxVal = x; + if (y > maxVal) + { + axis = 1; + maxVal = y; + } + if (z > maxVal) + { + axis = 2; + maxVal = z; + } + return axis; +} + +static void subdivide(BVItem* items, int nitems, int imin, int imax, int& curNode, dtBVNode* nodes) +{ + int inum = imax - imin; + int icur = curNode; + + dtBVNode& node = nodes[curNode++]; + + if (inum == 1) + { + // Leaf + node.bmin[0] = items[imin].bmin[0]; + node.bmin[1] = items[imin].bmin[1]; + node.bmin[2] = items[imin].bmin[2]; + + node.bmax[0] = items[imin].bmax[0]; + node.bmax[1] = items[imin].bmax[1]; + node.bmax[2] = items[imin].bmax[2]; + + node.i = items[imin].i; + } + else + { + // Split + calcExtends(items, nitems, imin, imax, node.bmin, node.bmax); + + int axis = longestAxis(node.bmax[0] - node.bmin[0], + node.bmax[1] - node.bmin[1], + node.bmax[2] - node.bmin[2]); + + if (axis == 0) + { + // Sort along x-axis + qsort(items+imin, inum, sizeof(BVItem), compareItemX); + } + else if (axis == 1) + { + // Sort along y-axis + qsort(items+imin, inum, sizeof(BVItem), compareItemY); + } + else + { + // Sort along z-axis + qsort(items+imin, inum, sizeof(BVItem), compareItemZ); + } + + int isplit = imin+inum/2; + + // Left + subdivide(items, nitems, imin, isplit, curNode, nodes); + // Right + subdivide(items, nitems, isplit, imax, curNode, nodes); + + int iescape = curNode - icur; + // Negative index means escape. + node.i = -iescape; + } +} + +static int createBVTree(const unsigned short* verts, const int nverts, + const unsigned short* polys, const int npolys, const int nvp, + float cs, float ch, + int nnodes, dtBVNode* nodes) +{ + // Build tree + BVItem* items = new BVItem[npolys]; + for (int i = 0; i < npolys; i++) + { + BVItem& it = items[i]; + it.i = i; + // Calc polygon bounds. + const unsigned short* p = &polys[i*nvp*2]; + it.bmin[0] = it.bmax[0] = verts[p[0]*3+0]; + it.bmin[1] = it.bmax[1] = verts[p[0]*3+1]; + it.bmin[2] = it.bmax[2] = verts[p[0]*3+2]; + + for (int j = 1; j < nvp; ++j) + { + if (p[j] == 0xffff) break; + unsigned short x = verts[p[j]*3+0]; + unsigned short y = verts[p[j]*3+1]; + unsigned short z = verts[p[j]*3+2]; + + if (x < it.bmin[0]) it.bmin[0] = x; + if (y < it.bmin[1]) it.bmin[1] = y; + if (z < it.bmin[2]) it.bmin[2] = z; + + if (x > it.bmax[0]) it.bmax[0] = x; + if (y > it.bmax[1]) it.bmax[1] = y; + if (z > it.bmax[2]) it.bmax[2] = z; + } + // Remap y + it.bmin[1] = (unsigned short)floorf((float)it.bmin[1]*ch/cs); + it.bmax[1] = (unsigned short)ceilf((float)it.bmax[1]*ch/cs); + } + + int curNode = 0; + subdivide(items, npolys, 0, npolys, curNode, nodes); + + delete [] items; + + return curNode; +} + + +bool dtCreateNavMeshData(const unsigned short* verts, const int nverts, + const unsigned short* polys, const int npolys, const int nvp, + const unsigned short* dmeshes, const float* dverts, const int ndverts, + const unsigned char* dtris, const int ndtris, + const float* bmin, const float* bmax, float cs, float ch, int tileSize, int walkableClimb, + unsigned char** outData, int* outDataSize) +{ + if (nvp != DT_VERTS_PER_POLYGON) + return false; + if (nverts >= 0xffff) + return false; + + if (!nverts) + return false; + if (!npolys) + return false; + if (!dmeshes || !dverts || ! dtris) + return false; + + // Find portal edges which are at tile borders. + int nedges = 0; + int nportals = 0; + for (int i = 0; i < npolys; ++i) + { + const unsigned short* p = &polys[i*2*nvp]; + for (int j = 0; j < nvp; ++j) + { + if (p[j] == 0xffff) break; + int nj = j+1; + if (nj >= nvp || p[nj] == 0xffff) nj = 0; + const unsigned short* va = &verts[p[j]*3]; + const unsigned short* vb = &verts[p[nj]*3]; + + nedges++; + + if (va[0] == tileSize && vb[0] == tileSize) + nportals++; // x+ + else if (va[2] == tileSize && vb[2] == tileSize) + nportals++; // z+ + else if (va[0] == 0 && vb[0] == 0) + nportals++; // x- + else if (va[2] == 0 && vb[2] == 0) + nportals++; // z- + } + } + + const int maxLinks = nedges + nportals*2; + + + // Find unique detail vertices. + int uniqueDetailVerts = 0; + if (dmeshes) + { + for (int i = 0; i < npolys; ++i) + { + const unsigned short* p = &polys[i*nvp*2]; + int ndv = dmeshes[i*4+1]; + int nv = 0; + for (int j = 0; j < nvp; ++j) + { + if (p[j] == 0xffff) break; + nv++; + } + ndv -= nv; + uniqueDetailVerts += ndv; + } + } + + // Calculate data size + const int headerSize = align4(sizeof(dtMeshHeader)); + const int vertsSize = align4(sizeof(float)*3*nverts); + const int polysSize = align4(sizeof(dtPoly)*npolys); + const int linksSize = align4(sizeof(dtLink)*maxLinks); + const int detailMeshesSize = align4(sizeof(dtPolyDetail)*npolys); + const int detailVertsSize = align4(sizeof(float)*3*uniqueDetailVerts); + const int detailTrisSize = align4(sizeof(unsigned char)*4*ndtris); + const int bvtreeSize = align4(sizeof(dtBVNode)*npolys*2); + + const int dataSize = headerSize + vertsSize + polysSize + linksSize + + detailMeshesSize + detailVertsSize + detailTrisSize + bvtreeSize; + unsigned char* data = new unsigned char[dataSize]; + if (!data) + return false; + memset(data, 0, dataSize); + + unsigned char* d = data; + dtMeshHeader* header = (dtMeshHeader*)d; d += headerSize; + float* navVerts = (float*)d; d += vertsSize; + dtPoly* navPolys = (dtPoly*)d; d += polysSize; + d += linksSize; + dtPolyDetail* navDMeshes = (dtPolyDetail*)d; d += detailMeshesSize; + float* navDVerts = (float*)d; d += detailVertsSize; + unsigned char* navDTris = (unsigned char*)d; d += detailTrisSize; + dtBVNode* navBvtree = (dtBVNode*)d; d += bvtreeSize; + + + // Store header + header->magic = DT_NAVMESH_MAGIC; + header->version = DT_NAVMESH_VERSION; + header->npolys = npolys; + header->nverts = nverts; + header->maxlinks = maxLinks; + header->bmin[0] = bmin[0]; + header->bmin[1] = bmin[1]; + header->bmin[2] = bmin[2]; + header->bmax[0] = bmax[0]; + header->bmax[1] = bmax[1]; + header->bmax[2] = bmax[2]; + header->ndmeshes = npolys; + header->ndverts = uniqueDetailVerts; + header->ndtris = ndtris; + header->bvquant = 1.0f/cs; + + // Store vertices + for (int i = 0; i < nverts; ++i) + { + const unsigned short* iv = &verts[i*3]; + float* v = &navVerts[i*3]; + v[0] = bmin[0] + iv[0] * cs; + v[1] = bmin[1] + iv[1] * ch; + v[2] = bmin[2] + iv[2] * cs; + } + + // Store polygons + const unsigned short* src = polys; + for (int i = 0; i < npolys; ++i) + { + dtPoly* p = &navPolys[i]; + p->nv = 0; + for (int j = 0; j < nvp; ++j) + { + if (src[j] == 0xffff) break; + p->v[j] = src[j]; + p->n[j] = (src[nvp+j]+1) & 0xffff; + p->nv++; + } + src += nvp*2; + } + + // Store portal edges. + for (int i = 0; i < npolys; ++i) + { + dtPoly* poly = &navPolys[i]; + for (int j = 0; j < poly->nv; ++j) + { + int nj = j+1; + if (nj >= poly->nv) nj = 0; + + const unsigned short* va = &verts[poly->v[j]*3]; + const unsigned short* vb = &verts[poly->v[nj]*3]; + + if (va[0] == tileSize && vb[0] == tileSize) // x+ + poly->n[j] = 0x8000 | 0; + else if (va[2] == tileSize && vb[2] == tileSize) // z+ + poly->n[j] = 0x8000 | 1; + else if (va[0] == 0 && vb[0] == 0) // x- + poly->n[j] = 0x8000 | 2; + else if (va[2] == 0 && vb[2] == 0) // z- + poly->n[j] = 0x8000 | 3; + } + } + + // Store detail meshes and vertices. + // The nav polygon vertices are stored as the first vertices on each mesh. + // We compress the mesh data by skipping them and using the navmesh coordinates. + unsigned short vbase = 0; + for (int i = 0; i < npolys; ++i) + { + dtPolyDetail& dtl = navDMeshes[i]; + const int vb = dmeshes[i*4+0]; + const int ndv = dmeshes[i*4+1]; + const int nv = navPolys[i].nv; + dtl.vbase = vbase; + dtl.nverts = ndv-nv; + dtl.tbase = dmeshes[i*4+2]; + dtl.ntris = dmeshes[i*4+3]; + // Copy vertices except the first 'nv' verts which are equal to nav poly verts. + if (ndv-nv) + { + memcpy(&navDVerts[vbase*3], &dverts[(vb+nv)*3], sizeof(float)*3*(ndv-nv)); + vbase += ndv-nv; + } + } + // Store triangles. + memcpy(navDTris, dtris, sizeof(unsigned char)*4*ndtris); + + // Store and create BVtree. + // TODO: take detail mesh into account! use byte per bbox extent? + header->nbvtree = createBVTree(verts, nverts, polys, npolys, nvp, + cs, ch, npolys*2, navBvtree); + + *outData = data; + *outDataSize = dataSize; + + return true; +} diff --git a/RecastDemo/Bin/Recast.app/Contents/MacOS/Recast b/RecastDemo/Bin/Recast.app/Contents/MacOS/Recast index 79c9ca9c433329516e29940402ac1df3b858c6f4..17cc4e7a441993f7c480161c284b05cf0869fd93 100755 GIT binary patch delta 113589 zcma%k34Baf`~RGCW=JGVLN<~}L_|VJ5D^jt8Dgs-A$E$^zNmtMHOwe zmLOVNXe@n8OG`_uMQM$FZE2PKzvtY0GZXT@zyJGrn|q%1JkN9XbDuj-eivB$aqyCf zm3&MF`9B}i(pilvtqe67wk$UqR#slRtA$sW0;9o?7z|xLFfM)4+-vX-qhVGpgJJM` zqtco*uYCTD(a;aV=MNhVMCneeE2~M(N=ZK$4Ni-}kaR>jLRxr*hnNg&n86V4ZBj}q zgeY}ss8>;#$xsD?MP`$7%CovMf`)sY1TG%9lfWsoaiwtBwFIz_N%@v0dIL-a1khko zd|9)~slbE)kqQizm5*aJz4`-d1hzlKID^T6ivSM>Y-Sz139zi1V&$MsEyaL7LnHYqoZEoiJs zscULcX)6r&5o%4!Xj7s$KtDhLHS>;`!URt)aDL#qqlyR@mPs&F$*2%2#3le$1TmBJ z>a7Z4-rH1UDhySTKU4^-1Z1kv7h z@(8bd65?whkZLldEmx{|2FIe5geFhB3(D^XgBN5nG&xT+hWKv>1ol-n7-pdrf-6tI zVKOuUX3cezAy~=rjPps%?w{H6-8SozE+j`b3;u1Ivez?-t_($#0t&n-ltEN*Ku1AZ|nLt#m{;jP$ zR!aRDK@OW*%+K)Ci$_z%ID}g&X^$VU^j@Mi2r5f|K7V-PutB{IcEAfY%5fT)D(yC zUxo5BQuN}{)Ksh(}(^1Q2vHm!0?Nl7Jl~PZ|9L&f3u+?Ep zE*03}unl_0qztPb$7U@yDl4itZZmDM(eS|RV=y#^GhA3Ph@y3Z-Hnjy(fp8k{!Zz$DhAWsSxeQ~QzP?b2p*X09eZF_ zng<7kyZL&htglz3ugb*WYE^XaG}K$cwOMa?xjVQnGren4ehY5SIN+Kgb=X=w+lRDb ztKK##3jrqq-VzeU%y^y+NoX3U`HV@$JYR7$+Mep22UM;^LA!{A!LHh_v1DeiuV{uFtSumduH=Q*GV6RoMzr$I&^YUiWm0!)svK`Bl%J96u<3*pZGh=;s^`gk z%S9JGHs69k=+tt(g%SA-Q%n(!tPPg9o3BFBe4U`1ufx*(vksdM!~F9uNHrgAq?2L(m7^A? z+6X#QHD5r=&WHpy=Pi?RGolU4!!y2i0?Wno)!OZt2cB!A5*WiXE;2%?5E;dGFBRox zY|j$eK&%(wrNB49^NYyN>;mu}buz484tKv)NcyD{biZ_1_e+OWzjUb7FC9_FFC7WL zZaQoNQhuzH%(C7zDgII6ECxs=@Z+=#Qf)wQ(#f#l{87tA zwY%s@wYvzY5~lWy_@il&-nmNmHNmXYVw18erdRm9*`*7GQ=L2oMyvxE>jc-4>k|2O>A`c2qpFnm@0W*)yH5*X&a)mC=q<7iYrnO$9EQd-8= zVe2zz#it1iNo#jUD~sp<^zff_`yk z!sbpjDr4ib*z&n1<#2o*HWtrYcv|rEZrW1r`m z3R*TjMVN;bHx11uRyB{8-2%>gi?-i8$D}N47T@{pIk3i3EKqNum~T0TeuE>h!65pN zcxu$~l2LA}Op>w4mQ!SyT0{Pl6z9*;FDmy_392Tg;eVDBNG zmbJ1qh= zL8Z9`>MFG6TQFmf5hNdneF_-GQ53cypfZ?WN}h}MoU^)~5rWynVH+l%@!(T-weqtr z78Xv0t|e+UFlMk?(u^4`6am{`#p-4xL^`{J;<91>i#Ei&*|1Fdk}Z@xZ$tRs=pw?< z0+lIKFu|+!QUvBIIjsZR9+Lg&ygducXNI&z9RxvQB9)E3MpvW8t&65+hr1f!s@oc z)&;WPp0{^J5K^UocFS4!W*;t9r%Zv9B8IjU87VzN9fbHntu1j5J?}1B&s`L4l=Pzh zlt`xuT=p#VmxMB!Z;Re7nx-uJN1$!RDg6cx`sz#NZWn*6!&ZHWuuVjms3ooSR)SS@ z`iZARr++$Fa_PMdv%_pb7u(#>|DvFuU^wb1m@5_R6AI9YmP+W9s&0xhuXR;x9jWPx z4B|D(_T1Ppg2A4Jeb_-&OtbJ7qWa&f{}xoyZ1+yWe!J?0R&8da5BjP&h#T^|L@Ha` zdRkWrZgiEd7ag_}nhj-IW45g9s|g|uq9MO3W6D4wj)v6Dzv$kOZQMoCke|P(ze%Js z1YEMk$~tM1XjoMieS0$Qn%jj9Xg)|7BwDY!TGC>(V=@c^_QXI**RZPvR_vKumWq8< zh9L(G4vSMviop|1%GGuatbdF*8D7C;-%!I})Zm)`U5i<0v0WM`V-@ePH7c#GtdUnI zm1pEB4SQHxW-VQjFvAssvbWtXMifLABV))XtbCb>^8eD|cJ-GKdpt;j?4 zf?FC!E=FkEVN&v;E$5+Os;{;l#-MA9>Tk@F{7TFAp&dGa&tcQ_dE_4xI}SH>2W%ob_f03Varpi#T;;} zFI>Z9ej0Otx43n1-vxjdCrXuwZKb@DcxALn`6Rt5`*0w3YU$0a4FnUyymX|D5mb4L zD5xL?yIa{-WYlk!mP_uV2KCza9SI?hH^w?_DoJdvq02*d3mM1av1KbdYPo_CDDQL( z3ezid7-_zT!*ZoW>}PN$m~+ZugKp(y$9e1%*qzm>E$ffxyPXpJb4S5mi`+iiEqDcE$;zF0ZrRBXDikC7^Dr5s17RV29yAcFo35wQNagWj^y!MoA^)&pz zJn)#qHoX+;bMS%L7knNgO$BjXpOD~4v17IP*^2zoX>5lg^~$|pF)5aAJ=iOFzT2%O zWBrZFPu&Kx!^2HVazu)=s{SIp(dqUj|3Ks=Yk%$!+N|d zDv+m#dRRJ{Wh)L48lvV9oKSieusefIO2^D(wg=CJnGM(^JPR|^*w=$i1=$ zMz9-~;Pm9L4D98>g8Q2aM)o>Ln9l$lTC;1iJN-?{OWC!|Ukg@?2d08^L70TNPdw|U zoXD=mI)gzl)>9c-1w5ns|1cT%UD> z)xn^6zh8jg!))Qn{CvwAE$+^2lk!!+M0Ti;Num84vws0M>tB}*1w5pGfL~)Num<C!bsMY&qwTR7dwp>)F$~>X6PVCjtFlN#4C`%Dx(-OJ z+zJv7V+9#iZ$3AfR8u|};LoOmyk|h&1{WabQn*eIdf8Pz>m?KxiSe)Yoprr%J{y?G zWZN3r^3BAu+|x-F%*Cx2zW~^CaQW z;(W_y4Pj3*DN(bVv0=?k1%qc_rOdanQR!smEF{sST(M4JA2!9!MD9m~wE>~xyw6x( zV`IVYc_#=v7iUry&3}cN8p-p#@m4(M^^@aC4Su^N^H5^*suQyh_xqSCih1&4O{21EL38#H&vOgHd>_};M}8lS{C|P5_tu zhro8ue~GoMZ&Fs~Kk)2OPt5&yubC8|75<8Kk&m+P_0APd68li;zBs+0x4os2?W>FY zg*O%ESsSBsNhk}&v+I(V*}zDXvVBQr#fp(a%|peybQ1pC-gQbfcf zDn6MYH|e9|0! zNL?f!c%$#E8UYOsThC_&gErM6EZ+-D)JGq(k1OFN#K-&SvL^+%KWC>Tf&|_uGr@h#wilzdO&weEW zc5dn8HO8cJbE8S=vNe)+c&vEsY|ZKb?7H)SGI`rXdf)*Rv{E9s*P$04KuM%BetTo) z%}fRBx1T5U@&hQUt9-Phai#AJsNXbLX(!Dd@dd<=DCJ2<>=B z$=RPmL;q6t?4JX%g5(2JDYgBsY(IFICO#;baVUTYaOszADZ5v2=F1Hd%sHG&tKTU& zaCkAHg}*CZkG7>9ZWSy&nm|InJpX)vA7WH?Mbg8U=aoyxg34j|OE6%#g2Df*Ms&#y zCH<=owBZe9=~vZ;Rlk8;t|Ba)sp1_Wwp?N-sjY-hufxA10x7OHHKY?rUEr>uQ5Wra zsQ|Kmbb-R_ipPJ_y%&IHD-IWT5KH*&9pv2){`wE8R^T*ZDLF28$8uY|(W!VIZ{}SB zJZ6(?-C>S$>3Fd6>hYw2H3E4=oIX&k*iEAsLZ@0ABJF@$wyCmNZ_F6ngv*<&Ts7y~bXWa$-9z z?yfyLtFX+esO*>S~@KEE9X^W3(*{sD0mPJAEEAm zN(@y+An8tA_p8$S$L1Bs{$j+z^R9C02mk)vWw|JSih9Hv4p1icOzmE-h6q)(+^Q>- zRaN5`ic*^$bf@a?P2umdvr5nNji~divfzA8dhD#S;rxFBXZ|Sq&R*3@L}stx z9R8!S@RuC=#}7({Ut1eL#O1eA>sRcdf3?$7-zYbJZ60_8*^SyRPzF@K_}O+wNxrZ> zkV@L|QkBPr%3G%uP?a1w*ey z5PJMn!NT9ilC-bB7Tria_&3Lek_3gel5dRa}yi%=hvd?=ZBTc*E)$h;;skdQPAc3O5!GY$YCV;dHv;b zapLRkH^&pfJMos}edE@vM(RIsd$ovKQ;Dn>QkeMs)1E6{Ele(y&ApgmH!nzk46`!XA*d;tCJoRT^;koGPUUqXA zc2m!~upwKSvf^1o)*Sew&pNVvz|~56Rd*Jm-!0D;%H?Y$HJWoEHLoOIa_uZhWK{v* zE@{v9ZDq@wk$ARZr*Zix(wN-?{62Ywb=hU)9)#3mVJI{uB;4=cozR;n^lJTz?=bR_ zgmh$ewlThwkOAh_U=c5OQt@)F*1OC z4$SY2v}0$s8F_spFu!kOg?1z9Oxdih#=>(INCPAD-_E$F7dgT@ZfAwxd68L!`EE4w zjLKw5=(vrrPHf(7MUi%7fVmF#nP7QZnY3bGZZPszRY)jXjl#exqz2oK!mKJJlQjg- zcU4FtOJ2vgmzkup?I68mCdq6(FpJHko6l>qhD~ZX{XxEACUNWokVAb)s_(-M!hrQ! zY~K3N$j1TAegu7+56Lul0bP&H&Gkm^=L>heK484BFB#2}fH~$1{?@>FRfS3SfqA(q zY3cuj>-&Ig?Jk3VVB|ZhlBVU5x_)3Rd{&kCQZ^ons``@~Y{+Wkt^hKUu#Z+5`Itab zmswF*5l9-aZYUfHB!97HDBz&gnO$2c3lTi7I%&w(tTYx5u1?;ktnNF;LK;FQkgyet zw7p})V`c@rzWbP`vF+%Rw-vd4i&){(P*O;(6_$(6GXI&nD|O%qiH?46nLy^2BHtFs z#S%HYEOOd&oKk$ibDR>H4x~_zyEupCZ87M?QdrrwQ0`QW{B$_k*Saz|<#oiSbbV}M zXrj&J@_a`TRBY(iKS$jyj64M;Cu@;lR)E6oTBLT(mf&~uB`*o%!dB!hm?OXw7eRtp zMHD(kka~2;9sXJbsa1Jxo{SxK^Q-b0Umrp0M_f`-28Uw_LSWA-cI>=@f;svWPG2hD zy$I4#s7R>|6%dpefDRj!@lmx&beN_h$l(x1*t1URDo(2M+iH`ZEODukSBxa#%!oq0 zNc25n!USOaHI*Xc@T{6hB1j)bl34S!rA8bGhQT0g@$-i9T6IWZ&5cWphF37%i8JDP zZ2$AdNV>RXs|Mx%bx3XY$`T__u0uA~{Mf4Z-hra`;;PmX1i|!3hYh3^QRFn+K8NuE z(TEoch4-V#2*P4>G!zPj)1%2G!dA{={8kLOP$=}MN18D@r(3ygeiL*maK=Nb9u4M``qAD9Ypk#shC*&U2@I+#MdYk&S)}t*@NbDHZP+Ok9>kN}xR=1@ zGIYCIr`T6JLu_JyyeQxDHl!uSY%}t8jW7jWu^9_5HX=zR?xT6`;)?30WS%xxh;PDf zNEaV9*T_3GA<30nLrC7272ES7dG5C?fPdJ8^o=_N5tl8SRa>Ca;9Qa`l!HFDqJ+)w(b~so$Td3JBUInP9 zg(b7G{3MYCHYUf&vy({2m~c1QDdV&|kq5K1>Tha@tFw&!KSJ)y*~UWBoLndDhnX1j zEl7ahhcg5-u5*jDt>!y3j65hA%u~VAEtw2tekg2B2JhwRg15+J(2vuN{3pR%03qKL z@>+uyQ0EHDX*jRh;3;KMk8ae{X_)X+AbW1QvG7I;IZYeT*HqzUMYuN26Yn-O;ZsvY z#iw1uho>0%&DIcJJJncNJC&G-|4ZO;s3!)8n);G5#aP&~4JoR?hKw<;NGAtl49GUo zt(!7RJ9T_NS}4aM4wlJd$kEY8emxxv=h`twUb`cCBgSV;Svh%>xjah9t=IPIm?(Z6 zg=lny+%_nv)`={RiGXVN`lQ^S$nlVJ`jzdqkys}?LGDW^Ae~8eY=+KqQS8~FI!z+h z$b+UhIN@AyOyFBXBCj)+*ndYF3y*aspAh!?2qS;3E1A!34rPT;yAt1u?DxTFoh;Ia z<)bh*iv+S6C@jn(^_j6hwmDf?HiLorU0{-csn&}`u>FIKJgFDy%dQMG^5wm-d^=FM z*$XovFojinlgc#obbql!@Hm>9Ci=P9tfYFVyNTldMxN4#^k&zx8DG(d^sSQG&xrSX zOxR69eAb8bVxfIlVeh`g#Mtm`BOf#XW3NeX5g+)vTOU19y^Y*HfF!YJC>$9;zKx3o zj~-a_{KFVC;FZ(dKUtb|v5vWwW#q*JA$_=)v9M$ysZoJhx*2)MaPk@(odMT|qaPja z!uavwqzQWl5`6_;|Jj8V4tj+wX2JH(=vjFwm@`G>9eHV@1SckUHWpSGMS^K?R>$%= zT6Q!Rri>v!P}ap_Ec72w1`%ep$lXYNzHS1k!`=t^u732%nWhcqk-9EzhfxgxMg->W8A%HB(W2Clu}pI~r+)SA*Pz zwecnGC~4!S)t&H1IRshViT_5}(MGM__#QZh=J(e~Xv5jTqHV8eQ_VaHIW=g#gl?ik$tCl2Yh&TNuaPLi z`llHAm(xh?ieIsT7!>Vq_ zg68V4ae<<0Tz0r4ivoG^4Dt%4fjo95X-K~d;QeQk7}_q7FPw>OWB&mD-AvMoz7@c$ z&LaI;T5}_xI1AH~DDYV%rs}X{;qyg%THa>SH?oqktj{B%{LU=WF!W6%B_~^Uo8OnK z%!H@N%$VCk_%ItOMm%K-4KjN#vYgh(P~{dzK4dlt4jc?QT;r-m=m0X(YMHtx6Q%mTVD+tV9zoTlpKt{a~h&25T|oU zLO>@VlJPE1wJj5SQ0IyyBd(2@uExhu~KNHuiWx|g!N4B3C@ zlUmIlH&b~a$AWFu=-z;BfO=A*=0UCN+b?IV6Y%RpkLz64m%J zhz`qL@v2?MW2>)l;I4FF>SkvzL3XPo2lC^rIE8(iXyor($y0W}DGIryc7@|j5xEvT zYz`u~p((Zmxg?jcR@<_YrK7v~f-f4TPH{xb3aQ+Z4EOM!Q z3hvu^q!G=Z%WEwl{g}C-ky{s#Q0hIGuUf_X+R>B15lXl?4#z}2SLV{yBXOG1q4_N=`;HHKBM zAQh+cdUHuDlFdhoM>^j$m&B7yer_(w_x|RlI5pzPXsai?K@IOs?ikr0KB-@(Kdh{; z^@r~uBKkwzpnAfcv(lZ$>h4;)bM`rRFtSezcdRxN8&FpYU6DcuN?^|Us=czK01eD8hnTgZ**DYG2`=DQF1;K*==q!fiL(cmZ!J#R8g^|_d(-x9YGWVnYpRRIxWs%kF?O?-kuSEB%znmN;=V_m7Dbp2mC3kw zh6|ePo334WwlnFOv#;t)vS{IuQr=5SnmEGE?hmq;ScNg#%3w}P`l z9KDz<41Y@!_o91yh=eZD&BaIh+Og7a8?WjhkuB2%*VG;MtQ{gdYGq9kb#-^J0I^3O z_Qt~*Ep-*SbQB5OHuK32QpfkVFn1L#Y8neaa*!CpKC8kC&%8mZ6aUBpe9nPGt=wqH zGhL5RBWFvnj(i+qER0z~<`SB|mv3H5uCUY^SdHHz(aax({cn-g?7k=CgA}Yoq7Z9l zwPj(GLJrcVpYYMkNCdge-&jVf*YWn0{fcS{U*eh$b_NBB8>T5ZCSYU*7z>XtBNGWz z0*pL$1zEyOD15(yG-qG=V~XaamiLGL5E!sna`P09$)0^wIG*D+mvyL$#(kGeuY25A zjNT&QlS94U`@$WwNmRVa7dxRqblHyYkpV61N&0bBVu>_NVm7H58PX2G;FV1?hh>S1 zm3r>_;DRX#pQTiX!YA*M+U6eMJGW}Obg439yunHmZ4Q=5ahc?>3FNSq*!OpU?BbO$ zy&ei5tt62w4TY1UaNaC^3F78eq^kcGvztS*+Et(@t|C!v3JQZ(AuxBrb8bMcs($e* z(x6kJ#LSl%^?iRs%X~A(UT#XL16f(F@Z72*7KVp7*I)Y#eaeB0BuSKhpR8i!2WzlM zb{&O!t6|(c6uPe_b^Ws;R~l$(S9_2gt8vO^Rg8rvSCf8(dOqb1){yBmc@y8bhMcGK zp76Z2BrJU16DqC3wV-^pek}|?IU&|L3`jK4&%AgocFzm=jkTn@Sum^V5j&6$^@LYj zN7^-f>M2~_jc>t`6IIF(ViWljE&;%}afQtMbz~peR@n9fa)glU-19?R;0>&ZqsGT1 ziof?EuHnC}VC0?~NCVp9-NK{|WICyG#wa2aA`FT%qR(%{TZiGi#U@glR(qQd+C+lP zcOUAe6+3KqAMyp8$ihG!>98CV8$Q|B8#7~J z@bbdmpOAD)SKr}lKP4ULwrBkMr(_V#c*Z+_hUcB9eDh~GOuqY+lP#nVZT*z@7tbsI z^1>}-mXOQcO8j{GR^n3%jB^30wD%K!Xe+rv@7>^AwvjCM(i6sWJ((X5-cH7n9)|z z_}y+SurRBTSWI-#9lmleZWyZG;dl0upqh2=GsBHxSP{uoWDes3X)m^8`3G57v{EIN zC+N;S@HBj$T1BPO1NV6IedIIx_MO6O`^bG{gIwUh96;|Vy2ZVVu&dPJZnt>fBGR88 zxLLTnh%_ejpX>bQK@v3P;&q0+5s{LEE}*R>$0bh8c))2Z$!>`gQ-;LJd!O|ZCuR)$ za*>i#W{FxTh?ZQtB}H3WUc1gG93oBV`Rj!r9wHk(=%$POrLS??T62+a`5LE?F_-z` zuSqSsQiJ0z7k>3M@gVfGi~RfJI6BU`$k_?fg0{U_*!l#yN@%xVdGxpB5{*4y=yj63 zMy%J6k)rRM2BUnViPo|AsswrL?H~v>z$Ui}%?0EQsG?Y^>I2euH`v_RZU#BjYs3~H zh`xXhQ&bL5kRSw*^PGG~0*(3L;CH_#{xMF1qQiFRKYDNTdkzi)Y%BKbhtCZCt;imq zdrf6+D|5-`p3tBKky$?X9Sus7&@By0k}qDOVCh(8Y*1^54&pp@EBjK#d}+ zJJN0`O{|{$10VDQ3AQTVV>673Ur&FAmsDU9-!%J)KI5>lqq3&7NaR1vI--@4=~Sqf z<#%>?=cVab+)>k!@c*r&8unV^0|STc6HQ5aNACS2NwJO?4Q0qA%zB7L&j&oBV!@-m z!B3Sd;#Ofh{}KHSn(aR+V}H&noI(mhnt{0RyW}Xi9SQ z@TC0TN~S#*p3|C+=DLp5|650)^*JR!YfApqy-9N^DQz2gDUGx&MGg7 zAl=bW+3mF;rE6-fXn%LTif#|0^t|4`G$nm?+q#r*+yBy0;k4Qh1B7jpyFRnRxat~i zpG7k+qkO%0{)cVRnvMgygPATJ?!91(n+}1u$}4QGZzgF_Z^Z9V5#5=gx@@e%ulcpYBl z0`ZK@-T$19MH-@1NACVY=$J_98~gZYm&ktl*1p1VzY*k!07P7+PccK)do8wsyGe$+{t zq|`nnmKQNe;v-A>{myQy*uQv~3sc9L3y+DNe8w<`E$#)rC03qzjWqPD{{rPDE1!Oi zbO`HSEY|g-JEWzUY!?E?CPKcos#?srbqo#RMK?)>71xmwP=6=od&i)yJKrQ#+xHW) zmecZ@)t);At(I&#ZM8ekd?|D)r8=Dw4&15NaTBn1gEpHROM^Upu_g2TD@hjt61bJ7CUTw2Y=}fi7BTYl4o%H9nynt+`%v0!BOn+R&KgW zZqWAN7!S4OgeI#o;@mY2d<1HSb!p)!a!4F6Posht%+$Xc?fA;YE z_raa=MS0xYHSRG@`1A)PzUpjn;YZLu(!2BOFZdS^kofb@N0j?KBpv)3Ef?)Gu$1A> z=S;nC&qF~sy4UB7zx$BXr{_QCM<0?{_8Ab}|3vTFI%=3q$xX|B}Q?cQtrg2oHS<_?ZSb3*iF|92M9P(m#@KB33$3LUxWW5;6)ldtp*P*0o+NKufYchc%?>v zRGqI9@W;A*b^eQh`|9;qUmi*mD-F}dt1llwgM;)iMTCidds>tA3tPT~_BE${>go** zKV{4RroHISIlMceJ=nGpSXFn)>DIb*Zr!w%ly9D%aK)_$cNA>vS@7jdZ3i#XJD zS}6`SnH5ipmY-nfsgy2e*`F}}Ev51F@)AxMZA5P`7tb2}B}N13)3^AmjMic6K4$z) zM*You?QSLjS-@x{>#b82hl|<}$dmYSmsVa=Q(&|-lzyzujzM@sSZbSoeo=p?D1D7< zrupt8#{E7-uu`BimCU1C?C1rrMx$k!@$TU@nw~35i6^_s+*tz zf^|)_W$S0iY}8%bv8jxOoKKT!MdR^(~~v}IIvt@XYkRdd4VsM5LB|9@}r)#7b`#(ad;&f#NI|B zxe{$ln58GtS#02?53sR{z{_2(`-YB~1-5-xNor}izod7PPcO`7c% zfc9milQik^n~Lvh00maMFSlq%>=BT)o@fh$Vo85$~6yW?muh??Zc7 zyzvF9DjnzaDBqZd1foR*maSy`fe#|^Il`j1L@nnhfJEgb<%qA^#oPE& zOH%3}R~JYrBi&GwuDjvI)(hXo_xsZ7?9=zK7w|>J-@zJJ#l?U`#XZXr-L;cPSEWgr zFa74K_~kOvnVR&ha-`eugx7fq7$5N4aP3F@oHi!FAQ6P`-(|@4hb@o6gba{Aq{YnH<~No$+wmzFhnjlhB6c zVm3RjzA8U1hC~dwoHrjr^A6v`V zJFOB7t=U~nqG$87&O1Nf#sdRs{mL0YsZ-olOmW=;F`<{vZkxpHR%aXkB#=e~+CFu~ zM4u&}Y z;KFhwZhZEP5BUW(nX9|kbXLeYw(xeiMniJw1x z#!p>AO<^_MYkDl?az5idYoMk(<;eB?l-p~#*W`g09L_&K~5cqvFzX5^3ZsiE<`Q#a2dJz@14sow2 zNyrVrTlElBG`<|UCZF(6LR=LER!kQXXFlOiAQAXRITD|I{EV-?gqpgAy4RE~^W2U}6(mOKW z^o$?8Ol$aAmWbsv_qaSoyKNW(OqREutv}*R!)QogVmTUup<&{$*meIJh8>zVgdnxa z`Q;{FIUG{G%aMA2<1@bPH%RpjFC(?v*{~JzgR-WELpC5GFVl73^H>3uH$5JFCg}uZ3{3(2-1w!APc6l#3j8NJKCKj=Ch&@mPcOx%3;e9t zu{)`adz`S^v|Z@UY_+rC+U()ftD-i8%_+Ahf9FfS(aes5{3*`K`Aq&cXFrwFp69h@ zRn?#EytPQF0QXy>k6B_PE(mw103NO{60r3x` z#6I+{Aij|=-ge%!<=iyL--($zS0mJiPVuvP;K||3>d>Z5_b(6_AJO-;L=)Tv3i^kV zAil2#L1qKv)x01qUcmS>R5<)kaA=t@8mR{`@`ZFuq~nS^An`hSa(bzLO^y8QxT(9e z#Q4b?L(T;ue*nU`T7?=uBZ{WiOqa~-VGI&fRP*p%*SP^VRh|n`v~k3%60^)<+jfZ> z%+aF$uFK55_B z1SH#Q-aKK_LtAlV>Cd9cWH#H)NNfX+#bb44Wi%E{L;_w}jc08xjiyLv{5*Vo5ktcp zZxeW#QztzBw$2NJ3)}=Foxjf25-c7^Y7A@7i5_OdyVeVy=UW;ztpj=W@XUoY=61(cr`3yW;~#9zZynA|`!c>o-A zGg*AjAk2dn95uUYOrg+#%xMi8Jxbp~MmTJ*YDjpQrZDuon$_2GgJyerQ!Y2ps5sSIh1=3g=PM)=7=&3Cbvb3w-?G()9X z^G*Up+slirdm`SVgJD(#Zn#h^aHT$i2K-Qg|5oDRpZb1A;D-r(p}^+}1H{Aw>?;Dh zL1;Ec`iQb!Er*M;)e7K<Tr`In{Ltscf1d*@o>Pz#ath@>co zK6q48B7Pd|LktY(9(?@yejLprZ0JnJAH~y7EC7YHMijs1g9iP(5e;VuE)Qu;W1{QN zC>^_aLGW)$$q@@L?ep+ic;vi49hb?CX)X2*3QHSP|A0Ny#T4_leYxnncFWty;kK`x zBvOAuJ!Fb?C~CqlDD|>N%n~i2HJJDW*`_r!RvCxw7quY~>(Vm1P1;NuZ$lf@-9#<_ zCgR;kl)=joMA%00a8^<2m+)^VITO_KM8VrYEmtP^Cd8Qy!1)rQ@MRMkXC)V?0ecPn zhfSFe{&o?jc)GAKp{pux5YqP$Bj;88aqCI&LmPn7k5d1P_B=6IMdvSmK|2@B=yT@YxR?F=JU^H~Bii>AvU$7l0gBdj zH;OP26^n+|up0#S2Aa>2hx<_-yG~$#0+yd@N?)SB7kI5i+@+mIdoOEB?UZPU1Rzop zsm1ry6wv|Jh!uAweqIsX>8(UsJ5YEC9KK)nU6p}OexoXW>=nf0iL`EfX^_fzul}ym z&Z$^gSEa)*H{!N(Ek@TP0>yHvs8Hj#H^pOwIU}Sam+Ktan-`;ibq$rl07=_uyUE%QzR_PoimrZJ3O^ z*%q{xHD|IME3(f|RLioESM6MK= z+#GpHV#vVRML+U!EosBb`=A3EbNK5#_y_-}CB-k$VK$T3wHI4be+fNqNn_%^618D4 z3tg#F*KY0C*>fhy!KgtMQJ>S#$unBv-}bw#nKb*b?WlpYk2bUkZFh!0XoD;Nbz?Afw59cZ zD>|Q+;D;|TNQBCQQH)P-OXDJf$HZ@{dh#J*?|SnTqzV61kPf7O=8L?jB@&Q#E@ zm0-h&DNjThX4);ef^YT>?$?f{`qn`7?0HMHYL1L#e0V#W#+p4tdT={h%#0%#pPWYP zvY%dMd{r84&E7`gLK;>_2f8P<;8I!?8e3>nSs0}k)>&xG@K4p54zr#7eMFg_jbFc3 z*E6{Wl-;ml1UrVwQGT$jBrL~TCU+%A@qLtwuPHiRe0v&L@qM73`@iBZwWr-G`Do4D z=qTUVp2pEDKk~EfX*jL=75}F_?ODlBqt89Udv^d{ImTynpf)z<6~;?C&?u`(on#QH zGsD$ET8yrXK7tL0d>YS-r!n#_9?sE%N84PdpzN?U*Cwgc`d3imgC<=0b;VkAuhcd1 z6OwtQkn5Ki&-3-^c!lucFvbt1(-gLIIOE7|itFbkl#BckTh7^P<~N!lRL)hyM1|Vy z34^x+cE%NBR9wLaJTObndXoh9*CAqJvb-tp)6IF#D-gGIBiOqP!ImWzS4bN=(i*;> zT@;<7SnCvhhvM~jN9s=}<#1<58cox4c-2lcfu#>&g&jN5IruOY;{WMP?d+k|6l3L=wIo~qeHU6Ybin|OH!&T^e}DnIHuaqaekcAN=%~__Iy{@^iCO*( zK8at@jau2MZj^gu&jc>`It?Sg%SeNLm zF&bi-jsOR5)QtxD_MDG#F;#q8C5A;a)QWfTAwJ@054fjI*z%ySX3L$fnk{v^K4%LM zsx6`*Y$1L4XBjlLPHT;`)RYbyqSO>{sHRMu=Wa?CDoyE58?w*)VPDyu)lkGl(`^O{020gewl))&c5L@zz%u4Ot#@4<(@U zUtyiCHv-ZoA31WYH0kyM=o9Wl{a5 z+}B~YLoEh=RhZn1&Z8`~J>^B&^m~@po))g?gWpe*sGWW3D-6?GVa!(QHY0N}wI|L#5fbpemi;0b&9cLQkqrvDCB`Qs0Y zJ;T45Dik51+F2^(C!rP^RN>%Le(4{4N;h~Q?N~dpl?)2<1CPu6z%np4w}AQafpiON zfx?_Y^aJt%j~Gn5(`KQ3>R{SA<$g_h_C>S}&I(<{B)2U^sWt`K-ppf-49cy%S#vBzpl-3Lxjgt5w zC$tN`y;axC&0-++=*(6OrC}@{G7&>*G&7;lbttXRe(Z!_VGx+jC`im46iyFCqQTKsn% zumTLipVEk-f=Pf?i^T6{mbK{9S76bN_GK-q1p%xn(xT1n8Bcr_7KOhGi$Xd`s13R# z-yz&f@N}q=xL!V z{rImVXn^0u7buhbxW`Bu?>F}aN<%;1WhCtoRzIz*8$$rY4V;Y)&YSHR-#!v^N5__w z-x*2kcW5fO#SRXkvFEPw7pb4cNYmWXuB@?#Bz&T@xsCvaRwy+%TSDuQQJU7(qiFs5 z3k0|N8CR*+W6)}NPHSCAJ{(13%LzgIsytydO(@6FXTE&)XxcQaa@(?wwgap?`b`_8 z;EdKxsyqgc1`2M=$uf?fgh@-=ly!8ygk6qq0<1e42Cc}WRl|q$+4}WI2=3C%Sv7nj zbN08?vRXYMS#@l=jz;_N9%E@P>xw2hGZw304hEIX5se;4eL6m?A#%A~<797Zp&2}) z%b;5i0SxE)M#x>O4vi@TO^^`gCZ0T=HYl(0D)ZUn=?u@W zS_(G~gYnsT8cVyx^VkXWfd}jqJ6;1P82J@0;#NcrMq_hvpqwJ5pP4BPA6_F=PwG*= zT@>EulwY2R9l9t~o1_)mO`=`d*<{9*Ni?&@7i}<;Ys*kL`mOj16Fz?dI+=_~@_uv1 zaRbjkTdV6uTnij0 zrqD*_`Oty}iuS?zaUO&VrqE_#t$@s*dIkT4k9dc_B2fe*L6dw3sywIC(O%s&L~S6f zQ=w{lGpR}*iC$~QIDRiVBKEx=?r-`XfW#~NSB0$RtQh*D8f$l6`8C=q=|+~GXOiTs zQ${*alYXxpY0vK5_8OhbrX=E;e;RGVTA_efDxW8Q5hd5Ed6{S&xm~TNA+mMEb-X)3 zcb(T?N8GMMdF*tgX{}9QJa0N}#Evy#{PXGb<=7$kKuEvmsO9VlNch7Wzvz$UCEj5M z5(-L$ju}WEvqHx?foX$6;7k-43RyFeJa)7Z<6CFq72!G*&dj9i0xIGSf#_;7d29m= zLnXB-cg&(agICAPmY;j*UxZG5cO}#H7sRu|C$lIe;qx2Gp=K)@A^Omp_$Qu$%R$Nw!9CCq6NU`ZxQlli+csi+zb9ptqdPdr^G6C0=4` z>0+%Twfl$DIY<}Np^66lmWXpkCvKWUBdkA{Q)f5mL0^>VVmBJ13my?a|8!%WT&wf9 zii4kA2Sfq?z)>rw4~%m)29>5FTg^?i-nky8Lv`MH{7XcTHC*e?H_V~qlD_-$X=y@K ze^l<$^%5Wvny@A=cXeH<>AZ^-BeDoT^E1IpYtZ}Ye6f`VMVv(?GFI4q_AW>8NC$qz zO2=V8)HIjgp^p#osJXNiowSm_G8esJUVTh{b7@mLEtUT~mxctr*-pkoW{<_y*W>Z+ z``kQ_R*%iZ*FY|V+XE5?zgbSbNfw?t4{2m3H+R3q_c|`=gMfr|VL8$TY5d$g+Q#?g z)o6$1-e^QIS`GCWuWv(AS=Fyx2~zdSC=Jn+UM)vyl{7xbMtk&s8#yd`O4^z;A^HNoL0}zdtb^6BWakBE*`LsrCVi~Q`n$`~GXsywXPnwThvwb&Q zz2bA!J%Vh)R*N*f7zx?phN;F2AIr4%c!f{`RgkD=m|R1)G}&c z)zk(jzZm~MZFrLfbYScasCPx@7eK<0cI8NXo67esz(st-d!iHhXq^cECOfaTkjDGw zOSGSc{;>|@nG0z_+SF8+{^q3-v`_GM_5&sV5DtMpVnnNn9Fxnbw@qu_c@eFX=KWtZ z`8e;l)-@S?bWQJp`!X1mKjdZlMav4?2*$j|yx?9s1!xYo9O!ODv*FEs^NzFGK zia9d(bsAhb`4=d#6y;|XJAY`&H@%Lmw6Q>`TyfyK_&RR%^huP**{O-9x8#X-r1pI6 zW~V+G*G%E#?6~37B|Nglw0k7w87G&iA3tHIl>@f88LP{!Oy+mpJa+JK)g8#D^xlg)W#YI9c zA(?k_pxSDQuBhsUR%)SDyI8GuwZpyIw}trT7SDLC-%;&V2dZsWj*@xJd8If0A93#< zpL6-gkMDh5i(zcoi47aWFl-ES9(J>Gniz_779CIwmDzM#ncG4RFXdd4N}13ohhdqd zBFYpR4fkv`5z1!2=j(dE-}ilQ?mpk&@Ar889>4vu`+C2w*XcUHuGe*P6}yS)!n@I< zZ$s=@Hx&QEtE{TvQeEulZ@Y@!$>giL-lOPC5PSP=h<(islUHJ?;X+rjhndc=D?Psu zV#y7~l`e+ZRl3-{3thxk&u8)lvGf*UpB( z4Z6S|7P$(%!{oENkXQ_Xx80CG+?l#9b`|Iaf4BW+XPVCx;a%=8&m*1az+#sQTRn&= zUg=EU?|`D88;XQZ6#tHsB1DvSqPH3OX*bA+V(5?ya$xmXW`m)*4M z%^j$q62(lW7}AlVmVu%cW&MN`%51{Xi&&1Q;(>+hbpAB=$z67n7xpNf)9gx|&^ZRs#pUp2 zdj(*wLNMun@|W~6iSIjKdZ3C)Kct!taG3P74Gf1#|HcD?Hm?wS^=q6a!a-;wW8T4T?{Cxl)wYc2J}u8H!?ux1{fx;vtEKA55EzS^^?SZ}%VCcU;+G^)A5TW7QWRa(DRbgk7aQ2!qHYB}Zniv#D6 zznT5OIx#}je8yjod1Mwn{k~{k^HNRyJ8L?<^S_?%eH?9@W_(9ar@9~D z`wip!dNK{;??uM<)?`}D-!~0@FDKJ+{(j%2Po}yX@co5JKaGa*_g5zUG+NBxWhVVJ zI?mtTwRHWWr%~Mx@%@8IKb3~@cc>A+bt*09?{>!b%Twt%f1fkuPo=sW@ohKhU!h?e zaaJ$Zz&|sSea@qeuODX8+>K&Ha7jn!KBha>oF1MF)o{RLqj*G&&!-_DiIEA_uzm}J zQ*C%^sUvcIwg(`LH;VsOHGsXyW)NOvGYBuTc1uSvyvPBYL>m!&k@{_d7kU4?w00A` z$gg=CUgTF1Ncb3Dzvtr|nhBW1i*4lp3Eaf>5QzH(j^jd! z^I{Cyg@A(C5Xk!kj^kSb4wO$ss?S?JREpUKu|ERftWQO9^|e^D&9v+!ElV_}fX!mO zc({aSZx$KVYw!ZTB}b2m{0wXIOFSeE`b>&-vKm1seLfXeS=6pw!Pri0D#5t3MZmn;5x= zHll7JQ5z1pC3X9o#U9ngw%j6o#HxD~y9Jc9@6pIDBC(;7+4oV-zDYS-MAP8^;(YE- zIhAk03Ca!S6qN;rZ6ZI1`ECQp zALKp7u~6rjybUGw^fnPy{bfYpK~}}KcA=B4&%gA|HgxA_QQ7Wo6Jc6y3f(Tc$e;D^ z1~qy8Jp&7}TYFK;cF{ik&_BZCbO?58-NEl;xQI|aw}kaEyJ{sUjH}?M*r)nlUZ&SWg#I3R+I}?6UGX1V9f`XLl_?GOTjz8W@Pac zouBTpO0|?uHN`T$p~}?e{PLwkY;jLRCUY@SLKwv%v$&zFK*Tr&qD1aX{wFG9&fK6I zJ9xG3HqH1_gp03k(dsWnu-{j=_3Ybui?eT0rgh#htb2VaS~c+UKxb-h8TThx80Xb^T8vG#ci1%-kO2rLD_Yv__o%=>!VIV4HQW_q0c0{sgX7o+^bC(DX>#i`or6bbVancNM`&T!F$C62r zp>?JrVCo)!gC6-x#E6|YXyI4FzutPpsi%akDTSW1x7yp@;0y?$wN}l$&ZWN;p<1}f z{);YsCC2vt2QWPB%c~eA-7UvvueN6bf*UiDyBOtXl;aubN~8Az&gVE*0SFaKhOG-0 zGq2N{uSJaK-sj6JXk9k;gfzZRcfLmd^4#;(W;c$lJw}^%i_RkJ9~!tvv<#?MpsJ6@ zE}Zf5_=^8!TG#zUVSBLT_{-l_aArDjw*5o%_K2Ac4*XM9t1Q-PeW_mBr>{}Zy`s5j zH-VnnD+UGK3DCVdKLD0|FZK|uEpf^*U5SIesP{g& z06qVt)O~O%p1&sOwUt=^WDS1*&A~n**bzLBy8?hJtk;(3h=+x|N+WWxn*B`)y~Dq6 zmC&&qjKBknsm*@TOtdJb#Qow~f1h)DE-8Ov7 zDk>V+rM_3F*8$Pbm#r$)wg+<9MRH(g;*vQF>$g8!tmt$g^u6=|+{}$WL~7F$x$9cS z+XPX!B9Z6zC$G?n1LDERCxGO189j_I&YQdI6$7u_4MMURm=wD5*>q0kto)YYZDZ691ewQ?bHt)Spy}rRtz;l;r4@HlX{ zoWA=`d?q%`px2IySP?#qwjUMIqh5fO2lpy8v+3`~8tpg(CKpnpRuWnjn5p>|4K!oN zG?g0ug*VPs^!&)l0h_G0e`VM>#IZZv86Z#j+sUUUwtiHU;V9{Sr)!jN+Cn94j3 z0F8Mh+;GEV<3HsUMZJM$OJ^;(>w9vx>Fw@sf7O*6o(lG`KWA_YZ12)_n_cR_f6A%v z4TYMs^iF2{h!HT2*-boSA3e59d7N1D4i3 z{Gap8=E@xxqvMtmgL~jdZn!@NgE{I)(Jm}ty))11x$?kuGg$pLdHm*vN8#;q`uIoD z!T0*ZT-Nbim630~&r7Q1gDN0Wet997E?!_Sx+ zp63*>hS=)|4Skh{vt8LYV)ic@>;r6W*eBki9>;~n|1QKh680WCdO&ML*J+o8|8E2n-6C1K}F@eiEDe5=1+$?u=V-;@H>IggRr07uh z{d{n;q*+taJd(%R|G44GW7iEQkB)cmFSC#SqKhX*-mo`MnfW}O)56P9e#SVF#sRe) z_32-ZY{QKxtMSFd-v`(3XS?t^mH#Z-H+-rc1~Q{4|NX@jF%Kf>p05ZDY*LA1U4vsY zI_`^`4CHmn_(k-qKMI7LIKIL(jjM0}U+DK=M2hc}flxIwkuw;*_THcA#Z#h<@9zvv zA7Ny>^=JBsp?z^=!j?JOz)ko$`@$)4OnYwkC9|&fvhTg-n!GH0aq>p}d4KZWzlwg+ zX)Bz-F&(5*?PzF!LKQ3I*mlffX))pU8(XNsuOjBL-+n^ejyo_hZ0bM9wLK2&NF9z- zXLI!@l#s?xfNQ@8IYvE2hL_@tHJaf@qYEYU$*-czs7G)>%d|HZR-&F`P?H<#sU?-t zvjHnm_D>gJ5XHN6Nek@#uTt!9BHH^DK>C7HmvfZ-o9H5zE}+kU6TQ8MJ7UZsejd_p zA74U2XE4N1bg6l!QPBey=NWAE2Em*&*b98FnAS1w33z)<>)*uPeueT4Ts9`EZ(`;n ziz(tP2(n!gWD-PPp(oDj%om>p^B=BI7H|Vkxxw8~R8F%RU@x`jIaE4R|HCYz{}qZo z2NAd3kcAY{lyka>_2(dBZxQ7HH!!&Le*Kr)%V|MFi17Q}MTDJ2bS|QV-yx#68!~S@ zrTwmp*!nv}d~T;>zzuxH4es2_<+KXVY*BcDiwG}XhiPG_(FG9kh8wc7OSGs!7lD4M zkyvw?3V<89)(!5oOE_c@0ueFiT|`8%h=9v9;XFj_b3;~gk=C8pMI1j55pQ3jQs4%j zbA$WL#d6x+2qNMyI7R4-nbj`Q%nJ}v;fCx?A!S|AMV!9?5wkBY{{s>tuisx3BMQsu zXk%=49FAp9M`=&sMBFN*c_0fI=!Wd*AC&WlF5&tgkT9u`d<^qkrsN?Dlg7yF90&QY~ zJU0ZJ&r^XxQ1=p;54k{5mw@}Ho7NV7o+ey!VSbpIA39GvnV>#=tyP;Rt`;x7BqCby z2_)C`Al&tLAMTI>>UCL!d!I`)`x2wYbw9>puHz_!x?LijKh5Q!Ki#D0=ik*_lgES3 zwEwc0-{EvubgcS*`=4^voD)xW;6XNPirr&cfU(hiY&S+y8}L8Nr4+je>UcSpL2Cc~ zJd+MDsFYe$N)?@2h1sp>RPgCsq24E{-KjehD$h-?T$mojG>n;rQeC<^7nLro8gu#Aap%@l6^%O+ z@K`pL-4ZR^SU0Ksgy#t-+Rd=1evQjn3W@{r@L{mm=VEnmWOd+agNN z-i{5dw?%*)gTQ-${7)87ZfBY7Bhgv>ef$3)vveC1V78_cWRJgt$|SpQ71{K!n4`&B zTLlG|VUKr7mZ11DY$)uwMbO$ZtdSo<;JY%>AocStC{w&$W64G1alZb<)n|!{LWUyB z5A9sNGHzJ2GgKVkKw*muGM>bFTEPkwETjtl02Rl#QrO}`4IWU&Bm3!c5vs|Fp9^~P zo>(MzZN{ab72-?T=rch-RbcL!uhTgr*T{}j&7iQ$s!_Pp`+3 z&>Hfj6zfIy?wWFlCa0|vRNGsI$wUOAy=BMhE7o#FJw}7PWl+#$^>LDY-Y0S99JkF5 zK9*_qdxr`-U|z7rTMm(buMx{@%Me+xMo<$DWFydv1Meg7SZ&!Y`q?$i64@KX72nTU z>-(w*JF~YN>*N~?fA;ex7!(^*s9#*mjAAb2x1Bq;h>nm zT2OKwxmaGx)b!VaIG|n&LLmELAK6k9MeozgE%7$-Vqe*?xo@UwgH;|2I>ini{BNryq;{K^`|}c zbCvs#XNYnZW5DERh<-&tJwx;g z0_qu}1kgD$Tj|vqQFw+ZlwJsyq4LHeBnR&m(T-qQPyUvs(UD;Js+_q{Q2!A5n#3-t z?1LeA{iMr_nXLU`^hd^EkyB}~=%=EOaU=FIcL!-%Y zHo1l>qL66|4O*<>{F$a@UvDCD&-?Agdi}SC$%ei=UpMN1Qck87AM{z6>@12G(?xU` z?aWx?tcxF4l%4RZK=UFziHJOe7c?Au^f@Ako^C3e%Z?d>Y)$2A@#{ir-AtYp-@HvV z!ezH=-EpS2IGzgHpbagjm%`Cs?tDwo58?8z)`yNXmk)_+3n-w49FB*No^K%|#Ia0T z+Cp07(PWLzw2*YR|Wd?3f4~|K@hvTAtIQX;mB9OKwOLbheFbAu8Ua2O{N* zvR<0Vo)#%RHSHaGwXKZQTG7U~GD2HQKem<4>hzh-RZA_DtV|Kh+sTLJ#1uhE?c~?; z(^)Ezk-e(DjC(+SiJO&vkCB0?t7aev)%6unw(=M_M@1!|9od$_Y^?k^&oe5L$9&Kk zgJ7b9@gQznhRp$$2|B7s9uMDad zzmuguAD!a>C*t^1M?~}!n-W|i_W0Qos0n7pnbtPC^063zC5pn$`BjPjygL|WrRGTca zXZM!xYQ7V&-%76?H#4pHpq72)O8LSIVo9I$hV*+M*-AE7AFbq{&+BZzdtT6(zH(`5 zJkDo2F5(K(``Ha#quRJecIHDdRGA%1~h+4F}K7lLWZU<1&pHVT|`d6qNS# zlfiQ11lSV&kVU{F?|2y{`vTHC9`CfCevT!jxk%ax2+RcsP)Yhg0dcoAbI6RB4I8+M z+|43I@q9fxCtmJUl3usSNO@|!pieBaT?d1Jd+9Si)>ODsiZ-cLQddqnI;T%{PBr_> z^)e3VbYlK6QkrJmasVG7AlhqNxk;$fy zUclK`M@N2?3oZF+PGyPy>LqM81$;sQv+w}XKFz!uEz5Z|T8I-`>N5h_ZhunHrw__T z{;Y>F0+r5qus9ZD{ctV&?1OT(CNB@uC}p5*+Y12$Y5xp1en5a}SFra7$M_j!CQ18Sau;fV~qGDIWW5IMfiPh+7R=Z;sVwc%G(F+_$|g|!_joB6on zfjIl|p)yQ__L+#{`Z7-V?9J(VAh{KWdTWlvYjQqc9?P7A$n15);cG@L;fPVr2tPAo1xNI9Mg*A=t2v@M z766juNcsM=TK2z580}iEdRnAgzw|EqdqZ0p`SjutF|Lic4{n!|}Mamb3ifo_9WwmOp7Nh?}AuG)eD9R-| z1e#d~nVOZsM(*{j%og$;i=aEt$yu@@UeJsQvbEfTz=jFvab_d%Gef2!ASOziyn?`@ ziSqF{uKA`Eyfq5_dgu^H@y0PkJ^fAlv3I`IBN^WL(hSSdB7vR*n{zs9)c$!H(4erR z-m3H~MIOz8PY1ac)KAd3=Vhn=h(6L6+i;(kLmQO2rG5(0elN(Gt&-5(Oci@76=x1r z5UBVBj{oT@ruPxqJ6@2pG}*t8pqQ6r54oqkMpHSkAAv7kk~4apAL3*+Q?WWb*krY< zr^(9R3n9zVyg2M|V8cZUmZ`~M97S9u*f@cgS->=Hkbs{=4^d`d^brkf{YqFuI2VhII_X24BYqD{4S(w>n zTD)Hq(?l`etM<*XU=xo^jIWBp1)`z_C*IlhhK*!S=dw~-&wMO92bqFl(Xci z@Xp`}HB=9l&ywFMW9vvc>?TB^s^yLtgG&pr`>9)syr;D)>FF%CldvT-tw(y8#dauC zWi*(aZY$`?R2k6fmJ9mN|B3!yTampkRa!N_H-gRDTi0^3*eBCEDMX_#b7Y)-=Div^QkKTIg6? z4~8jZ0OBME;@`mRl>@j{6ZS9yXWx)b=-W3?@Wxk^3iw)KFfasxCfQF}UR*GX=$Lesi~tnm6#6ozF#bx3^|1q0^`+`& zBjF2@b|9|Ta@i!YdLR_FuJ+^FbUGwU&yXpa(WAr$Rf<*5qGKFs;`8PUm=9%IbHJ3l z0k(yp)`z&k5GydJl&fb;ILAf7GcJGC>|g;PDmc4tF8&Is(mCgGjAk zV02ZoFMu!Khs=xNC9KSGh~BkAMzq4q1>Cr>R6ngnrd9uPm46hJ3QjfOl}XYMbWgpD zN&JQSB75n(a+Fr*D^2yimcmRcg7nXOa-{$GNmaYKdi4Z7vl3}N032E=Uyfd%|~U1m#QkOeFXJfBO5pDGr0_AvfZAfeoK&Bb$QkPS;Qx=kq?Uj_vqU-GMIle z*2>_v1MjKL+n6;KVL!1QjS$cEc^Rin%}B(cV+TpQwXz43z6jF)mn@F2g^zULJ&MCz zhkqY8e%HS*L%?$R`><%T)=3|Z+rJKC#@wTG>tq9btGL?h^|;3Cb^4y`6&;OPFGE?S zl=Vm@XpgU#ZFPRl3|94PdK>Ag%rYuRPjyWiJ_;D7R{G$(C}-mUg$4_$*j}wN10cPS=2m zxIsoYTJOb^Iy=1a&p1X}yPC%CtcFJ?Hpn(2?GDY_AVZ_CS67o`!zaN%=m^dzV5xD@ z1OEV+r+_zU_UBsT?BWelYVs(!UH?$_3DNj8oH^n2<#vJU$3*rA1P$3J11A3X04si2 zk2=gzfuK44*hxlI_zEZ#5Yy2t7vNRUo_-9|Q3F$P&R4(!2c`lZgt+fFVxBnn7L{(4 z&sjEmayOz+Hp8D%nc*|)Fc1AD?0)dev=-dbYcm(yhN8{rUNfy#sA$zkvS}SxAlM)M zNRE;*PifgLHpxdd@z705`&2gT^3YAAap0Qw83CErA^Nv6kZ!VpRF}^iFc$hLoN1_C z4D(^g&FnKD%l9>L<+{%0%Js@zuIb-ZxGf0#4!K;<}mEDW&xAXYd85{h2-gGa0Ih9DWgAhRec7H9GOR9Pd}}nwcr1`LcMF zR@-|tc#G`hCI-78mT!^mM9p$Kv<2SO3uSa|3;eg2u2N_g8sh;RfXR}9{5vvBP8M%p zrMxWJ9>3Xlvap!aV_bl9YZ7+f7TBWf0|Da6IQN6bqX{=^s6o8aYgrxqvmgCJ25K=+ zVZ$OSyiNe0M*-I!V*$UviYwtP(ATLR8C-%AD54Ly$yVY49AMdoDf&Z2+5c{nuV~F| zm-S4=QkfZq^=JlRDE@ev7VeM}YMsHBgwv1ZV$=5DgjL0+!giTjd?}j;PgW5y$`{&= z@_}r&$roOx@n6b0V&UcNqA%r#LM(lY7VVNa6!x|3Bn+>mZAIDS_Gjq2mq>Gf}9r(XTD z%Xo9%(j2~pZ9U@gOrx_+gxNRai?{1Fz{rbCsZ>sDG`jYUY}jq-mnag;b|cdi`#CyD z&qf8A3`i9n#(qhC525xV4dV~V$c7~sVY#Ij-hwv_%4VjA^GXABJ$aEfAL2T7kxnyy zyNgtQ2n}cWC2E<+4e1g+%)jLqDTRNxT%=8Tz#IK49nF*D#RI=n%wgciZKrXEWk0{W z+gRUYx^kf`p=cZJI1C8^+v&n#T+s`S!oQU*#lmeg_*>aiByOVx-^%Wy{xvN_{NACVnJ!wWS22n@s@&Lbu4iNK^2zZt&Hl&eLYho|3{8NH`- zfwU%{?_`F5(FNS0>L@TZ2F^&%{_H#XlqLrJPIbSR-C7LW!l{_2r$V9hR*`lv1M6S^Rr8SOLoGPDr!4-9ztY`f@=z}vBlpPktfp@B zRA!{X;NT~$P^pXftqO9}1+w-NsyHqiinE_k@K3VckmdPo=_@NUxnHJL|K#HQ71GTe zr>!%skA9~*Lqz;@8q%%v72Rq0a-1ZaeFg;q-6=RRn~&d~C#iWphAGdJG%gpXYGrwv7q3Hsn>KF`3@QNi|#M4F;^-2R~aCVccKS> zg>CftPc-LOS>Iy6s=DT7=$fbDQt%+GnDXjs`WaULJ4A9-;14K<>>ucc^|lf^{i__< zpvF&7A4W}3Mj{43aPm|c(&s9zDL{r4FvoD6VDM;JZe z=mm}=>^UX0_cs|7v9Ux~uh601K{w3aF3!I9o7|^~=nk~+EQ-??_kNs}O+!nfbt7!& z{sK?9n=p*qqB0!~@cGpDf)%3&fGRJrA zQ7CCSX-zrlf#NT?%&85IICxnvm79))-gTqfrZhwj(MH2rkwFW zI{RI6mQ5F%Wq~S~JDiQJ5W@w-A9}&GzjA+>*S$!uUyy^u*dz4q1q?iw-p#&s0sDx8 zcUifUU6hfu)V{RPDPySp%|ePTlwHM-R+?IfS~9r}{aGl3=wP92EzY*)58R(jWmH-y z18bQ*QCkC0(`?+eh$df@p#eq9q2`VpC}~UQ|J>Azzo8E=V*K=agN|L4i-#UPq?fTT zn?1hllSzKiUhRTYtM-`WXT~V!p#9o;PPcwJRZjl>DLzv`S(jwfVPhOj)e5~{aUQfU z0lC>_gY8a}?dvXVL*1~QSU}A#%ZOnYzA<&>8t$vt@CWS;E3r-5&fEPmV|?wxg>B)f z`?W3oJ$uzZ;;%PFd8*GSTz<1MudS zY{$iSU3X{^wD}4~lllj;uUx_2E2-_*XkU=llx#(!r<}P@qwk7vLuLX3cZ;yMvK9jE zt^o4eUX8|G!G$*45LmD7y&=Q{(=Y47S*ApX;XOzhYJ+z zqCx1U##RCA$PMp@a&`M@4;x99*f>_CbZDv}GcOK<7ltp3a_I8ii+J75XW`S_z4K0z zNXLtCk<9otgK=D3hNZxoQlR!fD_?7;p~~&XNROIG@o>K4%(p^Pe%2umGZF`5?VU`l zP=^R*Fb0|M2nRfZxq1Np?ZHJ9cUAcPf17*rcK?5rdr=1C`*W{Y9ja0p>1!h0mwTRg zWmM0-RD1E)x{h&Ga_2x2TWMD$#jyaOj&{64;9q#du~*#PtZQWq3wkqyzFR z!7~!WxdnMyQ*L^^dPE_{LA?~4CDf5;dY1jY3t4suzCCq%u>-Ok4GCR4GC7nBg1sEb zom~&;A4nL+u*{Cgm@*3X&kZ)hs$h3l-kRmYX2qMDQl6ErKX}m zj0IJap$aDlg4NoFFMsPbCDzz&ca=dveW(iRMQ}Dydnj#UGY;JTRfT$F9B;Kh<8WqT zBwhGbzH`c;Ix>mJmUGhLiNC%snm^eXlW~2AjkoRt_2P}^ zYzXU{24?Iqp4j`Z;|>Ib|2*z6cZWuO{t~a&@2-^bhJ}lV-&F=g(y_lpuoh1xe~EV5 zAPT-A_66ewoP6ho{r!xX7>Sv@Ep8CqxgjF!pNESBorU7F)Zo+Yrs6a5rs9)+Q*5iA zU`xueSvF9gIhud3Gl^t!Uy6#V3^xaBx zZi(pDLpMOInuoy*H^au6iv(t2oVj>>1D&}g2K-O_Q#cQEdbREu0{-CJIC3*Mi_&h3 zK+!sj-o^L-q>obci70H`G_iqh-u^$x+qZ?~>BlXA|Lh%+&iTB`O+GK>eD?ZB^lnwe zJ{o?iGan#lrOLBl3Wd4_zcz{?A9X8x%0D7VYlj*g!gZFrZPkA$7n#>*Xhe92lZ!DF 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ξ%BCS836%p)+9-45xC}NSHb&G$pxS_o* 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

`zJ1Im5zb=PPna{6Jtxc=%BDM< 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

+PxbCUGBc%4y*l`O7{Jst zz4Gt&EPb(=J+frIw1y7_)}%Ff*x7N3U3RIw>~uRkB*|=D_n|v!>eh48^ypNl-V=R? zXJroT-RgxAI=(N?b#}TPS@Z#QYr5@yK%MusT`X^UbNipRcjWM_=f)1?)o9mu%#fjZ zjoaGku1W*Owi?*)xxuY)vE))@1QoenGQqB-=RRt$(ot*d7(J(xT`4cUz1`4VA&?pc z8@$D^j1GhJdw;X*7Zf{$C$3Hquf78YO)y^C_IBi(ojwkH;uJ929$K#SbpQf)p zX&)%kfAqk@sUMjt!+o>R-%XqUT3h}&hCzel**>plS` zcW)kT``Va9`|e&?4Ryw+cDZ8rl=z~)cFa9FH~!qNQ2gGja;2{w`#@Idd+ZvK!C5&w zSi9a%@ZGOI+&^<*-x1GcwI1ix`TJKl?DyQz%mKV|tx>vvnxti&|FUh%v!C40K)19A|Br6ZuHC+wyTTV~g_zn1;b`fVo z&p|t;V(^A2yC>@Q&KMTRV66Tg8#-wZ_OufnCY2;t$vreRoTuAsQwYQuDE`@8%-A2&_X> zcu#=_3P_UOx$bv!y>rFyO05D{6jGRMnHMq4{>7DePXk*A2lQCq@jRw6QaRWd=#|gf z6|3CMu1=a9c#9Xh3>?&YScWMAC+Xp5?aI!%`T@Ks?|x)QM-D?FqN#Qc-bvQ{upMVR z`_^R++X>DVYclO>4xk*HK5#pg8*ab%fSCPvyS&~r+@5~V9ol{q3O#kWZRHIaVOOZ{ z`=49&|HDJeB;nB`tN*dVWSTTQ4TBria=hF40D7RE9QA=+X5LcUn)ea>kkf0};dvkA z+Iw6LdiUUt{`UuM>$+oyjFyK=9Xy2eo1fW_=(s6%gkEzLkCuZU+FzIO-uFz(!!zkb zXtCZs8s2Gl^QLUGtAzdcN7R8Y>`C$Ztyk@q%(t{&{I&h6uC~vv9#onCq}u&ShNv-^7|titnEFflXx>|6iK4dk^1p?_IC&v_Nbl z5`rGVb^he0s)8Au-NCx$p3#h3KQ ztL!&)^4|N}eo3c(Vu$+J>0|SeGSWHx z`+Ej=Zn_6n1X}$2SNtzsjXU?J*{*@R)9%%I0g?g}K#qQRAh&$-zEh+I@Y(|3mNVPr8lhf0k|3^@;n7 aKkx?d|D&THS>wUI_3qKN+XE_I3I87n#h94@ delta 84546 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 (F##{yqCeU!U+ah|2I5l>zVABr7X!;{ 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ξvYw0WR{(dx zF*Yv?Y`a${*Ju$g#-`{rsI>?*JEiCv?RXE@_$Rj(jcb0s=as@16u?DuZw=gm>#=5Aa~5-rMz9|5q%7s?{hX)zH*UNj2Mx;=VIL>uv`?p17y zv&1|}jE8|PwH2QU*IoK2LGiw{xE)3zhj+4}idcwW7W>Xlor<>?OJnbC=N$E7#X-U4 zSR1%<{9M$cgw+bmr{(v-!g>@QUA;XzYA-=t1kDnfy&-(d7xnT0 zdue1Xu&l?vfyRRQs&y3A#S|Pz?bA_2)K(5S+SfeeW1&;IyGgFA7L}B0N&4E7w7;YH zT)YEG13HOQmdF@%$8eC+F-CakUMto+Gr9uKlCIb2-Oljx&#!d)w6ka>wt~3PSp@f) z_f?S&;0)(x$;}+gZ0rcJqAtN)uwCxI1J69#bmv85I|?rvE8$Xr=6H5TvM=4FQ{OJ4 zVi^tx(w^g%Gr4_MLtKr$UQ0>rB3A3-qt7*}(M{B74ni>KaF#S-J)(P{S( zf&avQ>mfosi}Jy7if2y|WCY&d$G0~{8J>qy8y%Tg1v?|+Q-$YVU}=f7V42dTx0o$r zBlkcTxti|}!EB%hTDpwHh4mQ2_CZYZIZ?V`R%=-_W{a_f7G6$^7HrY0(8Akk(V8uq z7h05aTC``2N_@Io^b|3Bw3N;L&;eFy_l;ZkYOt`pIX@=$# zc`QSAM7nHYAqZK`Pn;oJu}DvO@E?(4h=>H8HjNh3#b*n3sys$i6>UItAA<&`42U_5 zc!Ah8Ml2L_K}3xe1EUCq-04doMk0yv_O%j$FZ(`n`f_MK?rMYTV+%N&k!ri=>*VpisP{jthr_zx`=WFC6|Z!iKM!}My)ULz z(X9ZfEQi+_BHxy~R4&C^qcjT6&#aqA?LuhPF-?bHvB-dP==1;wL?Dc$}1`@{a}aZasdJ90~LDES-XUvzof{toLE7JkK4cQ`32(Qni0ubsVQ_zxB^}>{%T;wdf~UROytH7+>{2I2=4r z_!dW_TD22KK?;ZxOgx!~xh@m6=VN%xMB;oAKvU<7l1@{kBvR*Ntibjl0#ZsX5Z?>& z*@s%ng+wvP6dowt4VECSC&Xzfu}j1cM)|%2WTfOkg!}TzMpkV+EjB=-{>wzYs1Pa zf)}A7DxGCiWGDWo=YVKT*h)rQx(*yEF5oII4Rm9zvCOHuEuTUfm_ssR zo@g4(IqLtm?j)_V|Cj3sSSLo8m!a7>gaeUX7>H5`-i#F@AZ9#lPDDRJ7o=s?TWE#w zn&f_BamU+>aac|k#TZBdkQn`hck(@H5}h>esJ+b)C<<1HzeI}xn4*0uel^7VJvDhL zVM(`M&icXk74-o8V3i2yvc5lr#JBIuk-l7E2hs_#8LO+ttxwW#q%45TWhbUTn3AzbqI8-c8xQ&IolhD#@Y zf(GU3Wj^U!w*=i-gJAFIqf@E1nA_vHLrUwlqL=Q`hn4@Fym&aXt43daE_#-6!T89! z;nCULZR@R*_c}-z0-m<(#5nPO53~sDM7h$#dT@B`pXc+PiYOO%yP(lrhmi&>sN8zd zq28umNNzJ!rokT2iSckO<4`nphFV;*4!+q-Ct5GM#46$GFS%dHzcg?L<0D1Q!WsI+3mtft&)KYEC%?JT;s=@GI65?doJI@XSpj@T?#} zw|jDc;8_9B?-D5*pzobL1)d!Sk{Eq}E8qc)k8w%MoKz&Fy)$qmSuvSrb$jY`e}nM% zEjc9@LuI?YlKjmEL&*{-ak)t7N?(X>#vh$%>KDR~e;aKSel@Z>sUAE0F7_YWQ6urp zT`_gm)M*JDH1Ke8{R`2QomPO;|DPgeZG@^cs}tpH6czc`^GoZu>sQcFL-dYK7}gB> zQh2iE`(MJJmz`+Qm!dqIrR70oc~-f+|E1-!_$Cp^5^8KhJi$C`lc=F2hA3gB-Akyb z{X=MEsugqhSHzqy7;=Vp%vVB6cZPz4=z)Ik$-;JK;>3M2*)Dt~CWFK;?LYFxVVN`NOp(1goV__sqO&~{60mCN%(k&`vvaekRk2xTo}JkfR*e%T8S~tit_+GYebt_f}4gZ63nr5Bmlh_d|QG~mX|fXDQCCt zp7Pn(Lg?Z>Nc?<@Xj$RQj$B^V?DI>|D>0dl$j z1!k1|5=@pLrlvcJfb%7Ysj1?Wk|#(oqkvO_1L56_t(Y{1wxP9K#XHfxv7f{_^Yn&d z2ICOVUMG1hkj<(H@5K1&xJ3+Rd?lkd+{Hu)Ey_P!QO9i}*rO;2@zb}7ULy1>E#=}i z(MLD#wW7#fBD8)hRY@YEpG@{ijDKL6<$&o4512~wIfZ*bxtV;D^;#B&6AxOYBwo z#I>e2dqsq?u@z0)E5eMKttfdf)Vs5BTy-z%^Ys=dr~2cbd|!10vX{)_^V^5S9GAqy+}))f%e&f!{nv zui1cn4vJYu%?2r-9u!{~#?T~+N)-)^+ox%2s_11bI8DtCqmBE|X?m0@Iv4@UtVwfi zT69>1ReZ20*Ks2H43a?0s>oBw!m7LQ6#aZy)HAxDqGCr-&nuszdPl?(IPCL387JGPkcKi=R4hit_+pyCPh-PACuKQ%e*u z-39pfaEXLm2hL$}3n@c&;sotHCbk)4>(byKM6C+82xqw-Fo%`!DBE+ag>^lGw*4R$ zdJOy6Y9md=Yf;+hduC*_M1V!Lik#oFbM;(Y^-fBaF5 zGZxgOW~ansV{HW0IxQv`RpoCrI)7TUE|c&f@(uCkjAP9+D%I2vsoEJ)uencZE^ppj zn#$LzeGYs3g5rgBBFH`+Cf?5X1A1U2imU_9d>ZXe$!CObM3dsXT9B2D z5@`YCA7QBeJv}~y(u+9w{3NRTCRRs-IVbZORAvZl;+#-kbU;`qR;PYHp-kmfqxnC9 z|3Wp|`V%V7!|HVDCsd?W)yX)E-%qPk4gPIYoqC@IU-)dAepU=OK98Wxv*7nSNFL`z zEAP_>IJ{|A-~!o)Zx7J0bMWEL0a|iSL~)2NoD-Fefd?q>oM>hQ9-yXaqKR?)JDQp% z0*ve5(aJR8Z>;-{_NR$T%zraY)G@vbC!e1&P#hD^eG)Vi=r{2@->Wd@azQ6J_FswN z8Os?`mA?O3%r7&i3Pw!@878~Iwlh=upBF=P!(NFF{~{WP1?}Tl%(G%4Sv)1OvSo`UNNN$s1A*Ml)w#$>@paaqDDV_m znkq>X_EAVv*)^1PQ7knM1=FlcqHO6^LAoZ;hDM`@m(zfxAo}`}INtmWED}u@^glcu zkabZC_~aW7P)-qKmPDQuLB@PT+taa6X6rX}I$cD>ydJ7lY?T;q896&A!4v0> z)T)W`&wOQL2#b}0@IAi1baxrLokAz=f~f6fthd(>pv21YDIr_vCe!T0!{{ zhmF69@|_6|_uTDhtqQjBPLU>VmDuiGY2u|lHuc+1$=8tRhbz+lYXUF0yd?WI;UE6& zg$zq#8i%DLAD4Uc6=AvlUyAq*3!jf$(#+o=A#_y=;=qJ zuA^dw`BCC^RNaz(w3C0Y`_ix1(M^5fOCC2+;g0-3t)ODI7U{ZNDVVHf8^vEx~mGUFKz>n`=;YYP+bQ8fcK5tI{ z&A`~GLwTxoQyTU`YPIULiXEDb2qr z-YEY?IRqbrnsW2IU~B?Orc98M&nV^(BuEBJeDc&v5H?r|Qm4_s3)i(L6nRTjDj!-X z5rZB`ECz|)?MY8*^es^?e3B(>PG&*y#=Jt42#aT3l1s9z zYw={N6FwY#FD@!X51-bKf=cU{WH zyP~r0H$QH5TCRtlU@EP(Zc_^!auTg1ILZfm}F;|K}q8SqbWqjs7!z0mWsD zk2{Ze%_=osJnZpeMa{(a_KWulLsb)#{lvObS!h25+Q%vsK!1mj>Mz!Cn6< z;S?$t>#c&*-v0LA#qM*8eWQrjGnM}>w$WYs<-Q2-IL z?adKg%#m)`vvA;%sQSrAC&Q`VBXlO)i>0i5gq>JowJWY}_0h{x$Mt4!+O*!>oib>H z8C$wst1%Nt4GkPUWw(giB_L(K2efrj%S*Oe32_uG&96eOi zx(%NC_Tcdov^FpWb{R8j>iEExTW!9%A;Xem3tHAfr8(T8*& zIcnIOBc}~Z@jqbpcB5-Q;2y3WH=X@rWKb@ZK5kZ`ahaxfna04qO4!VKI#h~UAikA> zxik>=wnKrrjtg}2q#0lv9hB>+Og|qt{V4E9+@~}SGR_^xHo8d`QIWh(n5JzS*b?de zAK@YsWE1-NwCU%Dept(eu?Bsd1_5b#rmYwt1Fz0tD0R}Tz@`!~1=Hbl)6X>t#Tyn6Z-GBpS%23oPINAa8394l` z1KiTV_qcwp;~+h{Wd=~vDYKYuK|{7iSj$sPj_Kl79b=LejdC4XbQnCv0})eik&xEm}mCkk;hd0ip*wbcRxw`1^oER4-8V>XO)VXR;o`TCq`FnUX)OH&w! zkEKJun89vCL63k{WvX=%auQ(7Z=dTZNss0U!_2+yC(}a&4$h_Te=@7sc8p<*kAFo#lR;Vmbyo%)Atw6nq}^#|Fekr?#!j_XHg59@62_4NuRsh0$$^^o`7#Zpg@_1 z6nG9vycg!XR+gpxju3LXOxTs{SZ8I!Smt>QGm0122ee*V%;;Q)E9Mc1eO(wMkQg;A zBXsIo-Ep~&ofLE)+q@6`Y`VK)VOKjeDc7-{X8nvB6q$x{(ReBglspe^n;tYM4ZhT! zk?UB*)hxhO<2cXArNwE8#PXTBjvy)DwF~g1I23xuPtMMD#926Q%+~_vl)8_A*0sO)SRGjl>&9Zl}K4G;w&RTtKJD0nizoSSyYxSL72<3US0pkj| zofUZ9?p((?t`vUM`WLgbQR9Lvy<+noc<>7Xbmzahju=^XjS?@o(1qDNFo>-x3!DpQ zkk_7WcuZV@ENdj@q*HmR+9aOwB+FNWLCA!w&gkgk}V2lWSSL43ijYxBl73ky3DD$h}b=`7# zj+a)A&re4kcEK$^o|ln#*ZlGv_lh(HXa-FhbQuA<*eTD^N+zdkJNP>~IFA;?Mezf{ zd5+gmzHq5LeSaB^V0KuZW3!Td8M4Q9g6u0Okgr$GbNCk{|J!CTWxQm$S0FdCcAjIS z)tn^TQJ0b!^oD0X^vJXYx*(4XNcbKSZ22AYkfkkc7JRQG1YboEGV10zl1d;5w<0+2 zf^eJ`SryeX&ynQ@^sfjjtL~h3W|gzx4}rXQW#h5@7qe8^Ce>02Qj&#m*B-7!16o7U}(%s$i95_^#X@KO zjsRJmi%*Dc6v%S2B+1sAnYvp>?#1T8*wiv|FSZKCUY3!I`xzKjAIAxm3Zt)O%N^M+ z824Jn-7rRBj2~zf`fD&YgmF0?&N4$N^(JDs4(76w+clCSmXJpeZ=#gFo+#4>?TKp~ z@UesPsKqVdse|$ylchM%G~ix?^BiyD*W4FQ56>YLcHBa}&OqLrrNckLjN&d zftur<3Em?U^BhZD3agWwx2b8N)AJl}Swhh>m9m8)jw7uk7zOL?y;N>ew2`hYOwC8vz`PZKlnSjGSBf24Mqnck`|#ZW}>&hl9cE0psv@EnqKI5 zT@HML#>s`6r{M{4eK?*XKEATMg?9kfBz?%4BUS`OvD48)0f*FxE9)cO&#Yx4?@#@D>D$qa5vN zC=d6($a9o0Y)&cg4IWAGxKaql6?C`q!Ha4St22f5m zntUFdqyu|ss;VdVGN{Ncg+x&3>bIsp@_R?RhRs| zXkFYWBGdGj#fReV!n-@)=Q%=X=L@qUR6dC54hF9r&T~ArTH^tyP}vn*nEKhhK7Ji)0W+OGXoJI+8p+mROjRoFE z?Y{nV9wt*5s|OZCwakQRkX1%j!C2vZp2H1#QL}Ua_e~XP?P;^LK9QoHn%=gx-uaHV ztpvRWvb2?;Cqc@Adx&kfle7l>%x?|p-8exL3XPngQ(z1)Oi(sj&6_pmhf%eiC0$wn9NPO0uTbNLfktiXjESFEUVpVR?Q2M)%s`JS&V$F3UEZ8~oC z9ry%IY}2N&mCWwlflq?5qJNRu-G=~&_Q^+6FK2i8PcV90+YiqbJvDKt_!}#okKtM> zR9mF8xU(7eVLwIB_~F<2Jmy3bAm)9I+ps0pFW<3S)*iPUBx;HNY~;?@wE`l(v_B&L z3}d$m1M;mlv`aZo^h(8#JApUKac)&AUT&b`XoKr?0odLJaMel-?l>v(FC>}I;C#n4 z7n=Dernuaj4}B-!kx%alTZlDt9R#fQ#C*pTYUyqZaZP}6<|Nz{{+C%p+?#^?t(jOc zJ)Z)eBc=MygfuHzd9Jk$#{Xi_X#VO`q~v|I-AWPvhxx2N;gVMlWIL-CHw4*QriraP zNL3TLS!@mCHLI?1Eq)Foh9%sjb1fd8!-ZEGxmlc!gVUzraC1 zuj7cki>;D7w}Ug!(oZ4_xCxb|ATzTI=8~%f%m z9sbLtbF6(OH{TI$4U2iKo$EBJ5rgl)B(8LKpg)jPv11_H78zdb-R$%}RC&Lmqyr;O z+4CQ|j){Le;Ne&+>SSqbM#0#voP!*BC?A6Y9FB%EB(4X5ufshJAho=cwMQ@-)Z+q24teXcnO$C{NdaGF@DA;ksvS2Q79$YNkLiGxZ$HI@_w)5~3Z*epW5Gc_DYNwRb=r z!|JeQhivY)ejOZ+=LK33(~aBc>bUCXkAH+(j%@9n9TcG3D%mb{b2x@s9E-9|54Q}s z^1y{-M_FMgXE18ewRNMcUSic+BpAC)SnEL9mpZX|Am6tt*Ljdpvc9--<+=y%fd;tu z;m*9tMMg4mNQK1|ZRR@;OmSsrcGj?!vh{q|fr@(bf?3&Z3asB9?Qq;;BlqC$q zE}H^Zo@_lC=b--xTMdzoR{}o+VH*Gj%vEn<{L>MnrCX3Qhjs`{P0U9rNzrt@S zeh=bT^U7FU44(#`5`#>w7(Nr9hHr;Ie(`q_7smC=6IuyrvdkGJEjy?E3)u6urcSe- zFEmz8@Lwzoa0P&P%Re@|!CbtExeUxa5#~R(_b(!V&7uEr9|m(#4{Q9xz9!5{-p6fU zOQ~Mc=7-B4%Gi9GxCg?E1t>0lWzeYyz_7cVCIfp4znTO8eFMqT<`-CVG^Cq4fwZQa ztubvYXFHX0!pAmVkCa|CcC*rgy=$jvkkF5sTAQ1?b`oDUKL1<#CtZy)ZA$=;%+$5f z__pBNkMAVDz1b3@IxEvZlBh+OuMYHXZkduq-=%ow8PV8QKdULkq|4QuFW?{o`;o zEL9k;sF9}D`)xz3im@`DY}6)=f?PcJl7KTk2cRcc3JxDxgvyk`t;)LBEjgOC4;rF0Fp6?a)4Wy71Ux~a9q zR|h-36EstsjBhu-QTRsS>x(Z2UsHSy@KwfF8eegIc@CjH!Dq*J4BuD4w&Hg&zA5;| z;TwssFTT3?O5=NhqL+p5cYK%gMQ`mV5XbTD!#5aTAAFthHN!U(-*SA(`2P79^~lr` zl|9>WQE*nf)JL3YK>=~#84Dhk`8K46}H^g=#r*uvN=Jf;iB_zv`=a^JH*j7G(+lA_noq>eT4S`P0 zff>Qn9-Evi+h0SYV$4~c;<311w(wfM^QN2`X<4*EI1RD9|E%vh}BH?M50SExTL9mR%ZzR+B#^Or_7KdLi#IPJb zZ+u>U;DfsnaYF0H^i@;q;R|=5<8C6gKo7bO&+eMqRAAabxW`u)ZX2?D4_HKk7zD7` zU6b?*wt6XtYuSDfDWBG}z2lBN@<#0`5%E_ajDl6%+x5l35;=Of_O%XN7$fkXe<1Xe zz(XZXjiYSAp2vXwsxZ(raHuWF^9jIg1%jsS$+jSmEkRH`_Oo1X38GVQ?J+!9)4~)s zJeXcE<{hGGtrX@RVqr&s^;g)D5bDzseEUM7X;av~P+Gv)qDr_WP+^NI(SBf_{lcI* zP#9=>_%1>?7T_)gf~IpL5yAw3XB3zaM%(&9;$~p?6t+2xro9gAdbp;&*58WB^>EtE z*vT4*iNa3Speu|Oi^RL13M&>#?yZ1%*VeR73iGZ_jTx(74~q>7t6z`a1%?;6F{f4- zXgaqB0Ss-3ok9u>ZAhos@daRO6?UN^y0eD7%pk>HC z{a?pwqXI!&(dh{Az5;P=|IB_i-`M}c9EQ3w&L1t{K#S%_9~j2w<8uECELJXlm)kg8H(n1Fh|kc4muRgZK1~? zD{yE>DYmVxd`({f&I&so^gk=?XvF+4;9OxrQ|$XlxNQJS4z@th7We>QH3fpE7M#Sd zx6-s|1%j5K$8vxJ6}Y?=&25JaTG<-aPGO+w#srk2bg^g=N zKJ9@GX{%`$6*iBW+|k+)1S?%&!y0bivNE zoc6Y29^9inRTd39kxK_Kmy6M~vTs`g%EeH9#uh@IR83(EyV7vRCcLR>O%*obOMH3z{oj#Nnc z26(~*VuyzNA=4<7-1iA23)9>ux+|Ld$dZaiKPk&bL?8hClYi4eaM>tpcN8?{&)(9s z{{rVEeD)SSegm=CjSisnFbmt=hnk}@d)7uf9-%PM^v)3E?S6nU3f$k97Ig;WY_yWI z6b70GehkJOfNK<((~oX-gfG|7w46}bwfHhkM|TdZ2-Lvtkf}>>kPL-!*sXd zG^rDsoG@Tp6&^O6wz2z;FdtP|M}>`p`Le>s4Y!)6Ht(QkQdpaJWDGry0n1a^v3F=R zH(@14Xj&=YoH`{&P%>jPfdwjT<_J2=*b!j$6m~@M)g6frOks6LQUnsmV-&D%3L7<& z1~8TYY^cH#M#@C==>9IWYzpiCE*)aNCBTvtw&Y!Uz}OjJUnuO%yH*qX1lWFsJ$YAF z0N*n(hq+*mhoR7AMf0%f&O0=7iY++80nAxSh9EBgEG4D>qiJ`5b2^mz51r?x{P-A6 zE03@?gq#0=4)Cdg|(Yc-56WB6#c2fRxYJ!6Hz+T zfK5~wXsZt?u?(w+3IuI6g&hH|Rv>8l9^(a%u>f}|aO^UfFP__gol+QRIfn2&1@MLf zL0hAZ>j0lC5Ht;Bk;Rsy2}1I6Y>F+HS?C!Fth&NL(_$X0v;o*&fuLo8Jl_R4Oo5>3 z`&e7~vhe^ow|PLEq-YF~tp;H=0B2SkqpYmH+1Uk0p{&u}Oh}vuoWnD7Ik}_f^_aUt z)4o;M+!fTAvE3`tohoeiN_v;E;cGOl%zKvG;cI9mWA5uP`catsIy%Kz=v+x3cK|sEnv*ttZ6kzTmG7xX+L8_x1d{B z*w8I>kFm^cST0gn<~H)}1#I0;s6OxJqf8k%hey-eYo(`ooy zaNq5+rma_4x64*LG!EESg^jyR$C0=9S(dW9RJy00a&KTfuWI+s=4w-0$ zg6E8pipCt$^0*lQr^l;6v&Tn@%#t?WfDUewf|*AZJVCyxXiSu?2-^WTBiwGX72$G4MiSc~@JHaB(%Wv*-2Mp6*x%7_ zC~WNSw3o4CcThN{SbWFs&>di&-gZq3P#9>cIUbeE`!4nyIl;TsW&j)?2Nt8S<9Dsj z*Zm%bq6%}rM;n=MM3$yaR@jIv3p<{T8tinNZFS}i?_*%1u!i@k{y+pK>JeHyg@L9u z;}DnxfIllR;SntdqetVX7*Q&$@l$II-254a)l;q5fu=F+cqPD41+IK1dm@iCVD%N2 z_KbQCg1|`Z=kBGj$k%8tV?!!pJGR1xRHVJYJWGY?+Af8GrWWhq$smAd6gVi12D}T$ z3xHix*aC&^26jtfyTizT2Kam{>)KO=`BtXn4}i4;rcJYA(yp?U_u4pMr4%-f`VX~* zytWQdn1a?x$m2YqW(qoAnT8L6v_9dw)0UM^UdEvB(L!1n3w!)Ib=@4T_ zfF&#JNH{%U>>{uO3cIM>W&^vdux#bls|sSKFs~}?*0*fw53FL&`k$tXh8~D+{Kw{7 z17>F_KLeVpbHl&%1 zr8d&FbqY&uMB5k(Y@%zwC@ioET_1+2)nk~Q@$5eH-|@_an@hmCHs>~>=EEV!Xsc@_ zV=c^ROQRXv(q7jZD{M=9N@i?RCtaJSuuYxlEMrT$>e?ZNE$K@6jP2^CYj+g3s~bhU z18m2ex>hXC@?*!FG=Q-s-E}QOVN1HxBF2jK(6u)eR;&jdV$7$vu8mWePj7m_SRAlb z3XAJa{v&`5eM{F)DQxIl6vLQDf28>=%U_TFG?TIF!w@%xRUby%7(4%tuKh=0=ij00 zjBOdAYiks?WdwPQ1lDvSRPhRHI+2<)79I;fW?O!Q$I@tEp51}@DhxC|?2M|s2w*J* zE{dhI%(xj?TZMt9fG!vlo(I@pfuQMJ3|#xf>Dm|t`ovicK@_lA3X6)fs#;HATNKta z&Z=tDfL&17v^cA(Edr*+TftrwNB*cKWh=rgClu%!t15b|vukVsz*)9dfc{t42!XyI zS^_u26XcVp_bgqjuQ1T`u&=EmwOeBIj_3l^DQypEmgb&W*u%Gh%O-OyX>Ddv*eIm* zsd!x*tFTk?)RVDpb9HTj!n)0+xs2s}q-zHimV@IVjE$PFYu6MuYChdztSNTBc+9o@ zXqrGi?;*sK7GuFhVW25?FVem?u1FiHz}lbCBxZcJOxNZr3^eskum!!|ak;MTQ6SSu zVmXS!3Xr6E6y>B@=~{aQmXw@=L6`i9PK~xz^&A3vGA2Y^PeIc_EI4O5FnXP+?5FBl*YLf|WR?u%!G%M+^GVOwi zj-`FxQ!8*K&3qquI|$f)g$+{JT41(?7T?;Hbe;Li zeWq&_6;|#u3rhl4Phm-)QS))&%Uq3?Nnx3*X*6R|$+|X0VNuDH%vb`jbqY&Jrn8Lo zS*vU371n1hRJF}h^G2fZ5bS>72 zeMN^Dd$3K{zERkNZB`4Lwq4h*C@gKe)w}fEi4($!mLENLQp^OnwWpw#D$JfjGZ}mR zAd0-gUO!0N7;ASJ1yEt_4%2nUx*Wj(Sz%p{kjF$+jZ|QF6$YAOQ7B6VAJw%Ii!6_s zuDgiFO@zXyH|S3k-uozRh5(NQV7nETa8x#_9{Ye@P}shs;u+LVf%n_V}1eH+X}mIj9$Q(QV)QAs4%9NE~bdd zsH7Wyz#v57psgx(44^H^@*T9L+V68**L)P%=eP{J$2efY3LAHvo=rx4I-Jn8MhXK> zo%^9T@I9$(eG~|q-bl2S_ZSC&>kcnyeUW6Tdkz3|R+Fk9Th-*_BGrT!y1oS-ifZ!V zNh=L_5$tD$T{~$NYhDz)tFVSYTG&!x`V!0S(jRHSRHRkZX~aNbQKxAUV|#znwT=qg z`;*myAHzn8sR}!mMh}>;I~Lo%QCRm&5$EP z3N9(^z<`x_Q15VYkrZ+J*n;Fe7CmPY{<&6@h$nrVocS{~`oM@u3{NJsp485akqZBsu4n@Sm zEpNC;QrPi(mRsI{u~T6e6vmq{(iC><9vx!0&wynq>=`i3Li~KO2}56Dg@x&|pg%-U zjz9W(qOJV!5dZ>(g?vzip2xJSg@4Ahr$uioLVs6;K3jyo%Cwio|3K2ze2LA!rv7PR zspHFYu=BK&J8hs>T4D6|B-U{HW=pCTjDVnzg zOjL9l=nbHqdH&rh+=RTu7F6>*Kxea1XZ61`a01RWeGi;75HwXodV6}f8rls7f|j7i zCV&nFZgQo)A0lyilTW2&%Qa}K30=A;@BWEaAZQDG0dSZCK}*ns_Xo{U;BvMHwiao5Q&C$5f~MGMC_TK(Xr2P+m8Icx z;d)6e_@Xe-mU{j?z|#r@EnR!ou5DN4HXF53eaqTofSB{4((;vXMhb=7-(xjG!O>|rYbP7uEqE+u%!wE&5Y%1ZwKHk zLT5np0G4-&=$~734(NuUoyDWa=hS$KEx6`5V9wHU1TGC(RR?etob%O!0%J?a20$sQ`2GMQ73R~QIwabBLk7Sc=BCM=MkARn ziIpU%xjs@fb{<*U-1Pv4$|&O)H|T$M7I6^%;YKTHasBCRA_~cnfrgf>upt92tTj#z z>{D3l!4$Cwah*E^@mCmVTtEuPZzCTRhHZjWlQ#%e9cpMLzO+06EkTc5fME*E9ZC-% z(WBjPLyJ*ZyW!-&7#MFb9H+2JqbP$hr-gw z&~?UmbK?Kk*15pxRPAqk&AVq_xADG{m@>H(l3Q{w5i@SN<$j5fgpk{rq7#lirRJ1E z2}_7cGE68Fk|am}`WMB7(~WdJ4mIijJbOLwX@Ab=@ALV2zx&y1U*G-iz1LdL zZ%xH@ZfE8{WMlZL#A#N{lm@mZ#^E>B?vaR@DY4xnb~1lcdASjjCoL~G_?^Y+eB37u z98_*%=OF$l@!Vtf5*JQg8!wcIT!qhyKLF? zA5YGYnB$rc=99AOpbHQZx0`sryT#CVmLaC^>{>B>XamK4z>kROOM4xhxFsFT4{k|~ zPeDsuL>)J{i|AD7jUy8~bO<^hSef~DE^cG7U!Z*_?H8xT*KNRcu+fzR2gZ>&&7Vyr zg59HuuFyUb!A{%)af(E+dtT8uI$vU|0-OH~woMP|8`3ImwnwSaOUjWQxsOk7wdEH< zo4Y4sD!=TsxqIx*)FS&Lrk1qGK0ATh%KZ`3R@%z_b{RGLQin>Lb9fJX*GNRocMa5+2o!$o1i7W-t^7x>n#aY z6LYY!mC$+Wz&5@GdvMcl5wl$)*sj7eIGzvjh(xdxtCdE~8Hr$9+oEJ9{=7l*D{h@& zCyu@pG1Vo4?HrbDY~ZJr2(};m6=E*LF%rS{&etKXiFoEkiD3IZ#8@=hGapKfCfg26 zQ)*SrDCe2vSKY=l>}5++YR6~b&nGmM1h(JbgB6~J*k2+z5M#Y6cqT_;uL?HuEWV>1 zG0&`#26h$y&miuV2zJe8r4auqu{35EV(C~hawpPWbGuWVW{as+y4*8a(kfkUf1*~m zif1NCt6Rm^S_W-)x@Xo(o1Jd^L5qKOrDr~p1`gg3;zd-^&!P3Cai!r^XXXZ$pUek71j)E;?GM;J;wM6^A`xs)j6uuv)aO!?V_ma6 z(_C}9X4yl0%pHf;SK9F`yK+@ZMul;hu8Suw4LCJi1b7-K3RD>(kpaBc%1|%_GK5 z71{&Rp6u<;OszhiStPAiAA5=A=Jxf>CTVl~+Qxa%4nx~3?Xc70>HW~yD-GN{ zQ6kvAD2;$vrpO)Mh<^559!_0tOeap=qu~F3bKZkx%D&;2+0)PVdLDbR@OIBMleX}7 zI~!X3#i5=VB@JvpVBc=`FwZ0S_*mX2T#Pj^uSPkEBAVFQn5*&$Qm@j)dH8C;k>6 z8X%{F^Q40V=TUqa#7z>xK|go=ONhH9f?el#v%5TVP-3&Y?5i(e-}jHgzDwIb%ATZF zbhl?NNh`YBp5Kg<_QPn;T(!&X5;(Xc$I|ZcOhbuj_t@+gQE=S7xU)$EyPN6}h!Z3p zf$@w5%_PrQX<)m64P5&n=1bf^$(`Qx$)4FQEq$^b^%55DGtDy}O9R^*xbR~TznA#f zG+WGq^QYrNdeiMU*wrRhL#!nc?8L1Qvm}Cp`f2Pu#NiUpPqz=R!ReXt0B&Q_W;|eD zg%&>vZGkkfs~40)%##SVt7hX4n*N|?wo3#%XF)c^k0oY5XtUR1Gp%NN=Df64Gu^j! zaF%DTf6E=s!CCeQ%Eg&UkSz@y)Wl*3AwHcbm}8r*!wO%W%RX&8}vZHS9tm*BpzySzYSpCA;VRY@>_6U-{;O+ z`7QSR%Q*7#Tk*_JXj^UF0(=db+dOlxG_ZXGZEdlKwDHZp5BPU$;tXTU+dAbuWVWr<+>4laOrNwH_@ zO9Z>-uFSVR(?KFQsGY_$_IT!QiC|lkRn|)oXG^@a$F|~)xx>52&&VIL-J&)Oa)LNYM%rt2&PP#k&gj2Y6NCUeox6)}mWF%HPZ3n%A zg}40+siNfuX3R?W?U-9IU26nGrH$m(x5$qoO!yt~A2zGUeLl9?41iO21>^Ee= zNsRsGUVrvMTP+RjY!v4pZkGsle%oYpn0+h}>^5Ex;!hI6d;nDS(+;@DG_$K(ikWsb zP|WnJnc__FNpUA|_xG@D6c;P9vIdKpwl!MJY^}*;{3?*OWfqIm!2AV{##om3>`tlZ zm)1mnD})+Q)2_xlAvgxE0d6ne10E#)1w2)}@fujm#bay11}d&x8y0TOD|C z!8MX{3^RX{(90ZbjL9BVuu*Q9NgnmZOw_nV%mj_0V&-Jb5;GU$DKT>}o)uZgOTHu{>SmZNxN> z4-nG|{ugl<@H{b%-y6W(Xds#_C+!M)74U3A$poSi|J*aET)BdL88nT7@9~- z>*hW&?U~0C{0&TxV!AP-xOlM>zM&1XhMO?7Up5ufcG*QtYvl+r9hDD@>7-mKri1bo zF^!XNi)olV4d&URb1$+_m%;bX8MF$v(F|GyZx_=Vc)ysIz=dF*I@$X!&kK1QrdfTGq-FoP0Yat#z1l`bJpGQLz+l!h(quM86o)sF;z7x}MmR!N<^qJw$ z#06z&C2J(6gRGsH&av@gI>w$B(F>OlQ#dIef6+aA4 zin$fhjnqg?&&dEWJtxz|^qMRd(_^wpOmE3Mz9#4?`ASSHNx8V&04*eS#I%ld2J>vu zYB5|)i^ViCtrhdcv{V#`X{Go?ObbOynp=-g&qH-h(Cg4rOpn7IVtN~vgL$gio&AcK zUDxlZ40}<(lg^&El*?RscDPj+v!AW4nB8l4T!!C2SIU01Y|UVI+T&vOqOBCO_v|$> zJIxM>*;)1tm}iB3S*evAv%9J(nCoXhQGYQygUnyWUk}Ai{#i}1ALuPHyMMkEv!~~x zm_0mKUhY=Pj-6Iw_UCjJvm0lGnB6y1#q71o6|=Ku6`A*ccG7IY1do8d4#!l8ZT0^W zvyDDpSx1Iu=IUZLFW)F;v+_VNkC?5%4~p3e{G6Drzk9@N^!3kRf*WR|Y_y77n2jN| zz|>iXze&ux`v5Vk-G39aww*6#&GLYlHOi!Pw;t9cyNFqX9OKwGtU0dM4AvMAh*?wo zL(H01%PZW9SaIqvW`${zn3bYO#HU?W$0gmB~hv_)kV)`lb#q?8tE~Zy3t(q&(F$g+|*(NnZ%y9_{#CY-b&4-%c zoA1wJzWG+I?pDM%-yAXDe7`2+t%#|u7sRYTRW`AHxZMPow2euHiKX8ng9ZApD z=KDWRFME^G#x5@Xutt*>FINOJv;lnQLFBj$`K9rzf~5+0+*^*S@8g zx%QpKr$6bYgppxIjOs7h@)*Ttg9Z3~4|D>gu=9nL_ zo@)ZexKP(4NlMJoEV_z0+Qlf9r|ELqb-r7eZqdh8h`C)as1Q4Gc8l3i(;k z<8M@|=Z=hHUo;YPgAR?MWfx5VsKIVI+hDy3p32{&xyHpVQ@fnsKgOcB#Cygp!j|5NLtvZxsxnc%#5 z<8HKu;6maVVlwUBVy0Ow5wq!fpO|C4d?RLqYI0+@EYpEnikT5TUd)uMWxghur}crD z`B}*~xP{pcHdIVI@d9yv4s4KO_CtLnX2yxv#4XFdsODnk8ch&0gZL$}&qlRRHNpPF zs!iR(%s%NV=Eyc<#mviICg$)A#bP?feiU;!hMV#8<(Xs3@k}xMx7Lf9esjDT?*A;r zOqMFm-2zP19w=t+&0}KbLcc3!;&y5aSDu-;Sz;z{FAy_n`-FJ)D{vd*XUh9TzfZAq z;zlU=ILc7LKSh{ETnXcnF88c z%*NC{j(x)fwXvGPY|okEo*i(i14b_JNY5;mo;?*;&xihF)=O$ z%%RmiQxVSqUUTfmtag*`|7`MY8q7eG8jhx;I2#vBKk>&n)g#61@t-W_@C@?;#vdE` z0&RTKIp|1{^v2K+iP^z=TxEv&ID#J&6S%mNTf5U(fQ?-)W~yw1fYIQHpK4p_97w5` zbdDZyzw{b7b92N@0$E0`n1cc2@$3(D>?nHL4}ydHKfBn!(hT(Do8QIk^S}INce>bx zUr)>~{#(T7hZr+h%oKttV&)<|FJ=zHn_}i3d@hF1z&Fu0ZUZzTG!(NhKTFKM{=s5) z-A@v;Z-0)MxkQ`A%pv+r%)Fy`Tep7ZG<6g+M`?P%`2LxUwMa9V(6lqLzzaw-6*JlE zlK5(H`YmpyOzvtdX7X1TF>N`25z{fWT1=+-chGJBgWDFjmZjf*dgu36_bOxv)hXJBypoAx$t<;cGE{nX!&;12mB~ z6Vt;vSWF}7EHRCttHrcx9v0J%c~M+C8s_m%Zk;rob|+)|!BCgeG=t93&0;!BPl@T5 z^*XzS>HEA+OmA(LnBLnv#IVJhX<`nqxj;;h?RqiC-SUrUV$`j8jTdu}t}D8@jTPXG zG!;(<_YikLnK9yX(0wuOiR;DmNdAwQzRDzQgI^i_ls7o`gTXkbXa-G?YsEBo9u(93 zc}7fgX!)*gMf7^s7t`X|RZM5+Jz}N?EEF@zV3(MQ31_?dZbi&UxT>3DCKwD8GXr6< zn8^`uh?zZcOw9C(-^5IoxT?Eb4|62ih?yF3hj>H@M#vE#@lW94p$QKCu|nJkg*J#e z)JKt+8Acz8nGN-Wm>Ey0J=}(wnbc6sEUIo|W=f3{Go$HoaB%-;8dQ;HFiq-|nAu;^ zo^GW~i)tWdw%5&KW`lJXGmmSi7|C5`f|!|LGsLubSus*KeN&_f=D(g0GuJeFo7*sR zTbqiR3wpblxvX;%{5%?^#mt#KCT6Z|gmwVVXP1aTAQXT==$ z_afQDyIncd`}=^)c?@X^C!jxYL^nB$6ieci$ae6=_e<*z4K%rRHu<)Rb09L@yl`hCD?4Mgh( zI-8|)yy!_}+ygjz-F&;=QcpU zPa`osP~F6Ie2o-uo$jNVRTDI6Ef>@7wM9(#&ii6I)xH(es+H8=ZHy+QE5vkRH4)Qu z)=^Bi*d2-TSav#@_ka4$p3n^1-j<7LPTDG_qv?Gyy-~--^oCs&@2H7Esln);;+J4* z3<$UaIm>aN4+*h9%}tbLp#`A@3PQX;ROUp8e+}_v1A~nP>!}vvE+HQ0xIDL&>(~z_ zR)l73b{y>WI{}+uuMdUvuN+6XqS6qj4GMN5(60$`vk-Rp0jzPKXz~@`1iS#BaJX!Lob7xc=OV-V4n*65>xod^W^Ch4?~<4Sv^&b(RTnJjCgL zvTv&YIbmvtxIu`UhPb_B+yl|B7vjMo9v$LIA)XoH+<^V0U_}cVh4r7UG5>zA40ALp&(NcZYa#h#w5`-^INC%;M0D zH6h*-;DsYuK&al*A2~R;W)TT+lIJPh3fa5Ye zm{B49{t(Xy@x%XN9RFkgnZO+|#7jf`e2CYE_>~ZE5Ap60zZ>Fzg!m{K#~&Q!snCqG zAufwY>>m|OYBC1JlFw z=}#2Q(&v-TF6)yZKB?YEoy%C1+B)mZb+|6rKe5+t(@8k}A46Q_rR>_1j=hTf{Ahc8K^Rs{ZKcEx8mM0Z3FL{G$Rh+c@^h(3tEh<=Fv2re@KpL_9)I)Hc&@d4s+ zd0e(f03RZbB0fWWjyQ?nLZ|TgE#eG<{+aI)wQ(4A5Z5B=BCbQ!L)1rbnFjc5h@h9| zdPHNy4TvU)rif;U=7<)EmKAW>wgTLUxCzl3aWkR~qAlVU1dTZD5t)cA1UJ$FpB)jM z5S==rYJ9+D+G^)gnxG-YWX{FBClF5}<{{=IXcFRI>wFrq5b+FR5n?fd%PhgCwGW+3 zxxCj3;B$zTh*gL@#PbLiTaC{b5HBKLLaafoMXW=tN8}?mAT}aiMiitLcK9J>QK}vD zdrBqicZrrQZ1a1{6EWL-QnbAt`)Ksu!V?vv+8GGO=;f<~O!# z{UrS+L246<7{E&Xal>mN;GDRDo1aOH*ejtMWSHq7Ol2ciC&#l_;z}9XG-DH zYS9}b_U9VWeTDz38NJc7jp{~I3rE+E-j!lMsTVz2xVL_^NyK`Mq6h4f+oP8ie%B~k zG1+F^h;`TB6urupDTq!kY~LcfJXYAaOZ0TaKF~c{-)`+5U1rDjh%PHk?-^~<%C@>S zszdwj;pm5va>4#L#i7`17Duye&5hA33PPBXSmartGroupTreeModuleOutlineStateSelectionKey - 11 + 9 3 1 0 PBXSmartGroupTreeModuleOutlineStateVisibleRectKey - {{0, 35}, {282, 660}} + {{0, 39}, {282, 660}} PBXTopSmartGroupGIDs @@ -321,7 +321,7 @@ PBXProjectModuleGUID 6B8632A30F78115100E2684A PBXProjectModuleLabel - DetourTileNavMesh.cpp + DetourDebugDraw.cpp PBXSplitModuleInNavigatorKey Split0 @@ -329,11 +329,11 @@ PBXProjectModuleGUID 6B8632A40F78115100E2684A PBXProjectModuleLabel - DetourTileNavMesh.cpp + DetourDebugDraw.cpp _historyCapacity 0 bookmark - 6B8DE88110B68A7500DF20FB + 6B8DEA7C10B6CFC900DF20FB history 6B3BFADD107A80E1006284CD @@ -341,37 +341,45 @@ 6B57D358108C66B200DDD053 6B57D38F108C69E400DDD053 6B7FB74D1091EBDE001BA51A - 6B8DE6FE10B01BBF00DF20FB 6B8DE70110B01BBF00DF20FB - 6B8DE70310B01BBF00DF20FB - 6B8DE70410B01BBF00DF20FB - 6B8DE70710B01BBF00DF20FB 6B8DE70A10B01BBF00DF20FB 6B8DE70B10B01BBF00DF20FB 6B8DE70D10B01BBF00DF20FB 6B8DE76D10B0243500DF20FB 6B8DE7F110B0517A00DF20FB 6B8DE7F310B0517A00DF20FB - 6B8DE7F410B0517A00DF20FB - 6B8DE7F510B0517A00DF20FB - 6B8DE7F610B0517A00DF20FB 6B8DE7F710B0517A00DF20FB 6B8DE7F810B0517A00DF20FB - 6B8DE81F10B0528100DF20FB - 6B8DE82010B0528100DF20FB 6B8DE84910B0584400DF20FB 6B8DE84A10B0584400DF20FB - 6B8DE85110B6873400DF20FB 6B8DE85210B6873400DF20FB 6B8DE85C10B6899100DF20FB - 6B8DE86B10B68A1300DF20FB - 6B8DE86C10B68A1300DF20FB - 6B8DE86D10B68A1300DF20FB - 6B8DE86E10B68A1300DF20FB - 6B8DE87A10B68A7500DF20FB - 6B8DE87B10B68A7500DF20FB - 6B8DE87C10B68A7500DF20FB - 6B8DE87D10B68A7500DF20FB + 6B8DE89910B6B3F800DF20FB + 6B8DE89A10B6B3F800DF20FB + 6B8DE89B10B6B3F800DF20FB + 6B8DE8A110B6B3F800DF20FB + 6B8DE8E710B6B59A00DF20FB + 6B8DE8E910B6B59A00DF20FB + 6B8DE8EA10B6B59A00DF20FB + 6B8DE8FD10B6B76800DF20FB + 6B8DE92D10B6BCDA00DF20FB + 6B8DE98B10B6C53B00DF20FB + 6B8DE98C10B6C53B00DF20FB + 6B8DE98D10B6C53B00DF20FB + 6B8DE98E10B6C53B00DF20FB + 6B8DE98F10B6C53B00DF20FB + 6B8DEA0110B6CA1500DF20FB + 6B8DEA0310B6CA1500DF20FB + 6B8DEA3610B6CBC200DF20FB + 6B8DEA3810B6CBC200DF20FB + 6B8DEA4010B6CC0400DF20FB + 6B8DEA6410B6CF6400DF20FB + 6B8DEA6510B6CF6400DF20FB + 6B8DEA6610B6CF6400DF20FB + 6B8DEA6710B6CF6400DF20FB + 6B8DEA7710B6CFC900DF20FB + 6B8DEA7810B6CFC900DF20FB + 6B8DEA7910B6CFC900DF20FB prevStack @@ -506,6 +514,196 @@ 6B8DE87E10B68A7500DF20FB 6B8DE87F10B68A7500DF20FB 6B8DE88010B68A7500DF20FB + 6B8DE8A310B6B3F800DF20FB + 6B8DE8A410B6B3F800DF20FB + 6B8DE8A510B6B3F800DF20FB + 6B8DE8A610B6B3F800DF20FB + 6B8DE8A710B6B3F800DF20FB + 6B8DE8A810B6B3F800DF20FB + 6B8DE8A910B6B3F800DF20FB + 6B8DE8AA10B6B3F800DF20FB + 6B8DE8AB10B6B3F800DF20FB + 6B8DE8AC10B6B3F800DF20FB + 6B8DE8AD10B6B3F800DF20FB + 6B8DE8AE10B6B3F800DF20FB + 6B8DE8AF10B6B3F800DF20FB + 6B8DE8B010B6B3F800DF20FB + 6B8DE8B110B6B3F800DF20FB + 6B8DE8B210B6B3F800DF20FB + 6B8DE8B310B6B3F800DF20FB + 6B8DE8B410B6B3F800DF20FB + 6B8DE8B510B6B3F800DF20FB + 6B8DE8B610B6B3F800DF20FB + 6B8DE8B710B6B3F800DF20FB + 6B8DE8B810B6B3F800DF20FB + 6B8DE8B910B6B3F800DF20FB + 6B8DE8BA10B6B3F800DF20FB + 6B8DE8BB10B6B3F800DF20FB + 6B8DE8BC10B6B3F800DF20FB + 6B8DE8BD10B6B3F800DF20FB + 6B8DE8BE10B6B3F800DF20FB + 6B8DE8BF10B6B3F800DF20FB + 6B8DE8C010B6B3F800DF20FB + 6B8DE8C110B6B3F800DF20FB + 6B8DE8C210B6B3F800DF20FB + 6B8DE8C310B6B3F800DF20FB + 6B8DE8C410B6B3F800DF20FB + 6B8DE8C510B6B3F800DF20FB + 6B8DE8C610B6B3F800DF20FB + 6B8DE8C710B6B3F800DF20FB + 6B8DE8C810B6B3F800DF20FB + 6B8DE8C910B6B3F800DF20FB + 6B8DE8CA10B6B3F800DF20FB + 6B8DE8CB10B6B3F800DF20FB + 6B8DE8CC10B6B3F800DF20FB + 6B8DE8CD10B6B3F800DF20FB + 6B8DE8CE10B6B3F800DF20FB + 6B8DE8CF10B6B3F800DF20FB + 6B8DE8D010B6B3F800DF20FB + 6B8DE8D110B6B3F800DF20FB + 6B8DE8D210B6B3F800DF20FB + 6B8DE8D310B6B3F800DF20FB + 6B8DE8D410B6B3F800DF20FB + 6B8DE8D510B6B3F800DF20FB + 6B8DE8D610B6B3F800DF20FB + 6B8DE8D710B6B3F800DF20FB + 6B8DE8D810B6B3F800DF20FB + 6B8DE8EC10B6B59A00DF20FB + 6B8DE8ED10B6B59A00DF20FB + 6B8DE8EE10B6B59A00DF20FB + 6B8DE8EF10B6B59A00DF20FB + 6B8DE8F010B6B59A00DF20FB + 6B8DE8F110B6B59A00DF20FB + 6B8DE90010B6B76800DF20FB + 6B8DE90110B6B76800DF20FB + 6B8DE90210B6B76800DF20FB + 6B8DE90310B6B76800DF20FB + 6B8DE90410B6B76800DF20FB + 6B8DE90510B6B76800DF20FB + 6B8DE90610B6B76800DF20FB + 6B8DE90710B6B76800DF20FB + 6B8DE90D10B6BAD500DF20FB + 6B8DE90E10B6BAD500DF20FB + 6B8DE90F10B6BAD500DF20FB + 6B8DE91010B6BAD500DF20FB + 6B8DE91110B6BAD500DF20FB + 6B8DE91210B6BAD500DF20FB + 6B8DE91310B6BAD500DF20FB + 6B8DE91410B6BAD500DF20FB + 6B8DE91510B6BAD500DF20FB + 6B8DE91610B6BAD500DF20FB + 6B8DE91710B6BAD500DF20FB + 6B8DE91810B6BAD500DF20FB + 6B8DE91910B6BAD500DF20FB + 6B8DE91A10B6BAD500DF20FB + 6B8DE91B10B6BAD500DF20FB + 6B8DE91C10B6BAD500DF20FB + 6B8DE91D10B6BAD500DF20FB + 6B8DE91E10B6BAD500DF20FB + 6B8DE91F10B6BAD500DF20FB + 6B8DE92010B6BAD500DF20FB + 6B8DE92110B6BAD500DF20FB + 6B8DE93110B6BCDA00DF20FB + 6B8DE93210B6BCDA00DF20FB + 6B8DE93310B6BCDA00DF20FB + 6B8DE93410B6BCDA00DF20FB + 6B8DE93510B6BCDA00DF20FB + 6B8DE93610B6BCDA00DF20FB + 6B8DE93D10B6BD1E00DF20FB + 6B8DE94A10B6BEDB00DF20FB + 6B8DE94B10B6BEDB00DF20FB + 6B8DE94C10B6BEDB00DF20FB + 6B8DE94D10B6BEDB00DF20FB + 6B8DE94E10B6BEDB00DF20FB + 6B8DE94F10B6BEDB00DF20FB + 6B8DE95010B6BEDB00DF20FB + 6B8DE95110B6BEDB00DF20FB + 6B8DE95210B6BEDB00DF20FB + 6B8DE95310B6BEDB00DF20FB + 6B8DE95410B6BEDB00DF20FB + 6B8DE95F10B6BF0100DF20FB + 6B8DE96010B6BF0100DF20FB + 6B8DE96B10B6BF6100DF20FB + 6B8DE97610B6C04900DF20FB + 6B8DE97710B6C04900DF20FB + 6B8DE97810B6C04900DF20FB + 6B8DE99410B6C53B00DF20FB + 6B8DE99510B6C53B00DF20FB + 6B8DE99610B6C53B00DF20FB + 6B8DE99710B6C53B00DF20FB + 6B8DE99810B6C53B00DF20FB + 6B8DE99910B6C53B00DF20FB + 6B8DE99A10B6C53B00DF20FB + 6B8DE99B10B6C53B00DF20FB + 6B8DE99C10B6C53B00DF20FB + 6B8DE99D10B6C53B00DF20FB + 6B8DE99E10B6C53B00DF20FB + 6B8DE99F10B6C53B00DF20FB + 6B8DE9A010B6C53B00DF20FB + 6B8DE9A110B6C53B00DF20FB + 6B8DE9A210B6C53B00DF20FB + 6B8DE9A310B6C53B00DF20FB + 6B8DE9A410B6C53B00DF20FB + 6B8DE9A510B6C53B00DF20FB + 6B8DE9A610B6C53B00DF20FB + 6B8DE9A710B6C53B00DF20FB + 6B8DE9A810B6C53B00DF20FB + 6B8DE9A910B6C53B00DF20FB + 6B8DE9AA10B6C53B00DF20FB + 6B8DE9AB10B6C53B00DF20FB + 6B8DE9AC10B6C53B00DF20FB + 6B8DE9AD10B6C53B00DF20FB + 6B8DE9AE10B6C53B00DF20FB + 6B8DE9AF10B6C53B00DF20FB + 6B8DE9B010B6C53B00DF20FB + 6B8DE9B110B6C53B00DF20FB + 6B8DE9B210B6C53B00DF20FB + 6B8DE9B310B6C53B00DF20FB + 6B8DE9B410B6C53B00DF20FB + 6B8DE9B510B6C53B00DF20FB + 6B8DE9B610B6C53B00DF20FB + 6B8DE9BD10B6C61D00DF20FB + 6B8DE9C510B6C64100DF20FB + 6B8DE9D010B6C75600DF20FB + 6B8DE9D610B6C7DD00DF20FB + 6B8DE9DB10B6C83E00DF20FB + 6B8DE9E410B6C8BE00DF20FB + 6B8DE9E510B6C8BE00DF20FB + 6B8DE9EC10B6C97B00DF20FB + 6B8DE9ED10B6C97B00DF20FB + 6B8DE9EE10B6C97B00DF20FB + 6B8DE9F710B6C9B700DF20FB + 6B8DE9F810B6C9B700DF20FB + 6B8DEA0510B6CA1500DF20FB + 6B8DEA0610B6CA1500DF20FB + 6B8DEA0710B6CA1500DF20FB + 6B8DEA0810B6CA1500DF20FB + 6B8DEA1010B6CAAD00DF20FB + 6B8DEA1710B6CAF600DF20FB + 6B8DEA1810B6CAF600DF20FB + 6B8DEA2310B6CB3000DF20FB + 6B8DEA2D10B6CB8A00DF20FB + 6B8DEA2E10B6CB8A00DF20FB + 6B8DEA2F10B6CB8A00DF20FB + 6B8DEA3A10B6CBC200DF20FB + 6B8DEA3B10B6CBC200DF20FB + 6B8DEA3C10B6CBC200DF20FB + 6B8DEA3D10B6CBC200DF20FB + 6B8DEA4310B6CC0400DF20FB + 6B8DEA4410B6CC0400DF20FB + 6B8DEA4510B6CC0400DF20FB + 6B8DEA4E10B6CC2600DF20FB + 6B8DEA5210B6CC5500DF20FB + 6B8DEA6910B6CF6400DF20FB + 6B8DEA6A10B6CF6400DF20FB + 6B8DEA6B10B6CF6400DF20FB + 6B8DEA6C10B6CF6400DF20FB + 6B8DEA6D10B6CF6400DF20FB + 6B8DEA6E10B6CF6400DF20FB + 6B8DEA6F10B6CF6400DF20FB + 6B8DEA7010B6CF6400DF20FB + 6B8DEA7A10B6CFC900DF20FB + 6B8DEA7B10B6CFC900DF20FB SplitCount @@ -519,18 +717,18 @@ GeometryConfiguration Frame - {{0, 0}, {976, 521}} + {{0, 0}, {976, 548}} RubberWindowFrame 0 59 1280 719 0 0 1280 778 Module PBXNavigatorGroup Proportion - 521pt + 548pt Proportion - 152pt + 125pt Tabs @@ -544,7 +742,7 @@ GeometryConfiguration Frame - {{10, 27}, {976, -27}} + {{10, 27}, {976, 262}} Module XCDetailModule @@ -560,7 +758,9 @@ GeometryConfiguration Frame - {{10, 27}, {976, 175}} + {{10, 27}, {976, 98}} + RubberWindowFrame + 0 59 1280 719 0 0 1280 778 Module PBXProjectFindModule @@ -598,9 +798,7 @@ GeometryConfiguration Frame - {{10, 27}, {976, 125}} - RubberWindowFrame - 0 59 1280 719 0 0 1280 778 + {{10, 27}, {976, 90}} Module PBXBuildResultsModule @@ -681,12 +879,12 @@ GeometryConfiguration Frame - {{0, 0}, {1280, 305}} + {{0, 0}, {1280, 359}} Module PBXDebugCLIModule Proportion - 305pt + 359pt ContentConfiguration @@ -705,8 +903,8 @@ yes sizes - {{0, 0}, {620, 135}} - {{620, 0}, {660, 135}} + {{0, 0}, {620, 115}} + {{620, 0}, {660, 115}} VerticalSplitView @@ -721,8 +919,8 @@ yes sizes - {{0, 0}, {1280, 135}} - {{0, 135}, {1280, 233}} + {{0, 0}, {1280, 115}} + {{0, 115}, {1280, 199}} @@ -742,7 +940,7 @@ DebugSTDIOWindowFrame {{200, 200}, {500, 300}} Frame - {{0, 310}, {1280, 368}} + {{0, 364}, {1280, 314}} PBXDebugSessionStackFrameViewKey DebugVariablesTableConfiguration @@ -755,13 +953,13 @@ 430 Frame - {{620, 0}, {660, 135}} + {{620, 0}, {660, 115}} Module PBXDebugSessionModule Proportion - 368pt + 314pt Name @@ -816,6 +1014,9 @@ 5 WindowOrderList + 6B8DE95710B6BEDB00DF20FB + 6B8DE95810B6BEDB00DF20FB + 6B8DE8DB10B6B3F800DF20FB /Users/memon/Code/recastnavigation/RecastDemo/Build/Xcode/Recast.xcodeproj WindowString @@ -1465,22 +1666,40 @@ 100 100 700 500 0 0 1280 1002 + FirstTimeWindowDisplayed + Identifier windowTool.bookmarks + IsVertical + Layout Dock + ContentConfiguration + + PBXProjectModuleGUID + 6B8DE8DA10B6B3F800DF20FB + PBXProjectModuleLabel + Bookmarks + + GeometryConfiguration + + Frame + {{0, 0}, {401, 202}} + RubberWindowFrame + 21 533 401 222 0 0 1280 778 + Module PBXBookmarksModule Proportion - 166pt + 202pt Proportion - 166pt + 202pt Name @@ -1490,9 +1709,19 @@ PBXBookmarksModule StatusbarIsVisible - 0 + + TableOfContents + + 6B8DE8DB10B6B3F800DF20FB + 6B8DE8DC10B6B3F800DF20FB + 6B8DE8DA10B6B3F800DF20FB + WindowString - 538 42 401 187 0 0 1280 1002 + 21 533 401 222 0 0 1280 778 + WindowToolGUID + 6B8DE8DB10B6B3F800DF20FB + WindowToolIsVisible + Identifier diff --git a/RecastDemo/Build/Xcode/Recast.xcodeproj/project.pbxproj b/RecastDemo/Build/Xcode/Recast.xcodeproj/project.pbxproj index 9af2ebb..4a279ea 100644 --- a/RecastDemo/Build/Xcode/Recast.xcodeproj/project.pbxproj +++ b/RecastDemo/Build/Xcode/Recast.xcodeproj/project.pbxproj @@ -35,6 +35,9 @@ 6B62416A103434880002E346 /* RecastMeshDetail.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6B624169103434880002E346 /* RecastMeshDetail.cpp */; }; 6B8632DA0F78122C00E2684A /* SDL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6B8632D90F78122C00E2684A /* SDL.framework */; }; 6B8632DC0F78123E00E2684A /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6B8632DB0F78123E00E2684A /* OpenGL.framework */; }; + 6B8DE88910B69E3E00DF20FB /* DetourNavMesh.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6B8DE88710B69E3E00DF20FB /* DetourNavMesh.cpp */; }; + 6B8DE88A10B69E3E00DF20FB /* DetourNavMeshBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6B8DE88810B69E3E00DF20FB /* DetourNavMeshBuilder.cpp */; }; + 6B8DE8F810B6B70E00DF20FB /* Sample_DynMesh.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6B8DE8F710B6B70E00DF20FB /* Sample_DynMesh.cpp */; }; 6BB788170FC0472B003C24DB /* ChunkyTriMesh.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6BB788160FC0472B003C24DB /* ChunkyTriMesh.cpp */; }; 6BDD9E0A0F91113800904EEF /* DetourDebugDraw.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6BDD9E070F91113800904EEF /* DetourDebugDraw.cpp */; }; 6BDD9E0B0F91113800904EEF /* DetourStatNavMesh.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6BDD9E080F91113800904EEF /* DetourStatNavMesh.cpp */; }; @@ -96,6 +99,12 @@ 6B624169103434880002E346 /* RecastMeshDetail.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = RecastMeshDetail.cpp; path = ../../../Recast/Source/RecastMeshDetail.cpp; sourceTree = SOURCE_ROOT; }; 6B8632D90F78122C00E2684A /* SDL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SDL.framework; path = Library/Frameworks/SDL.framework; sourceTree = SDKROOT; }; 6B8632DB0F78123E00E2684A /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; }; + 6B8DE88710B69E3E00DF20FB /* DetourNavMesh.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = DetourNavMesh.cpp; path = ../../../Detour/Source/DetourNavMesh.cpp; sourceTree = SOURCE_ROOT; }; + 6B8DE88810B69E3E00DF20FB /* DetourNavMeshBuilder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = DetourNavMeshBuilder.cpp; path = ../../../Detour/Source/DetourNavMeshBuilder.cpp; sourceTree = SOURCE_ROOT; }; + 6B8DE88B10B69E4C00DF20FB /* DetourNavMesh.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DetourNavMesh.h; path = ../../../Detour/Include/DetourNavMesh.h; sourceTree = SOURCE_ROOT; }; + 6B8DE88C10B69E4C00DF20FB /* DetourNavMeshBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DetourNavMeshBuilder.h; path = ../../../Detour/Include/DetourNavMeshBuilder.h; sourceTree = SOURCE_ROOT; }; + 6B8DE8F610B6B70100DF20FB /* Sample_DynMesh.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Sample_DynMesh.h; path = ../../Include/Sample_DynMesh.h; sourceTree = SOURCE_ROOT; }; + 6B8DE8F710B6B70E00DF20FB /* Sample_DynMesh.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Sample_DynMesh.cpp; path = ../../Source/Sample_DynMesh.cpp; sourceTree = SOURCE_ROOT; }; 6BB788160FC0472B003C24DB /* ChunkyTriMesh.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ChunkyTriMesh.cpp; path = ../../Source/ChunkyTriMesh.cpp; sourceTree = SOURCE_ROOT; }; 6BB788180FC04753003C24DB /* ChunkyTriMesh.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ChunkyTriMesh.h; path = ../../Include/ChunkyTriMesh.h; sourceTree = SOURCE_ROOT; }; 6BDD9E040F91112200904EEF /* DetourDebugDraw.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DetourDebugDraw.h; path = ../../../Detour/Include/DetourDebugDraw.h; sourceTree = SOURCE_ROOT; }; @@ -238,6 +247,8 @@ children = ( 6B25B6100FFA62AD004F1BC4 /* Sample.h */, 6B25B6140FFA62BE004F1BC4 /* Sample.cpp */, + 6B8DE8F610B6B70100DF20FB /* Sample_DynMesh.h */, + 6B8DE8F710B6B70E00DF20FB /* Sample_DynMesh.cpp */, 6B2AEC510FFB8946005BE9CC /* Sample_TileMesh.h */, 6B2AEC520FFB8958005BE9CC /* Sample_TileMesh.cpp */, 6B2AEC570FFB89F4005BE9CC /* Sample_StatMeshTiled.h */, @@ -253,6 +264,10 @@ 6BDD9E030F91110C00904EEF /* Detour */ = { isa = PBXGroup; children = ( + 6B8DE88B10B69E4C00DF20FB /* DetourNavMesh.h */, + 6B8DE88710B69E3E00DF20FB /* DetourNavMesh.cpp */, + 6B8DE88C10B69E4C00DF20FB /* DetourNavMeshBuilder.h */, + 6B8DE88810B69E3E00DF20FB /* DetourNavMeshBuilder.cpp */, 6BDD9E040F91112200904EEF /* DetourDebugDraw.h */, 6BDD9E070F91113800904EEF /* DetourDebugDraw.cpp */, 6BDD9E050F91112200904EEF /* DetourStatNavMesh.h */, @@ -355,6 +370,9 @@ 6B1185FE10068B150018F96F /* DetourCommon.cpp in Sources */, 6B555DB1100B212E00247EA3 /* imguiRenderGL.cpp in Sources */, 6B62416A103434880002E346 /* RecastMeshDetail.cpp in Sources */, + 6B8DE88910B69E3E00DF20FB /* DetourNavMesh.cpp in Sources */, + 6B8DE88A10B69E3E00DF20FB /* DetourNavMeshBuilder.cpp in Sources */, + 6B8DE8F810B6B70E00DF20FB /* Sample_DynMesh.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/RecastDemo/Include/Sample_DynMesh.h b/RecastDemo/Include/Sample_DynMesh.h new file mode 100644 index 0000000..b49f619 --- /dev/null +++ b/RecastDemo/Include/Sample_DynMesh.h @@ -0,0 +1,114 @@ +// +// 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. +// + +#ifndef RECASTSAMPLEDYNMESH_H +#define RECASTSAMPLEDYNMESH_H + +#include "Sample.h" +#include "DetourNavMesh.h" +#include "Recast.h" +#include "RecastLog.h" +#include "ChunkyTriMesh.h" + +class Sample_DynMesh : public Sample +{ +protected: + + bool m_keepInterResults; + rcBuildTimes m_buildTimes; + + dtNavMesh* m_navMesh; + rcChunkyTriMesh* m_chunkyMesh; + unsigned char* m_triflags; + rcHeightfield* m_solid; + rcCompactHeightfield* m_chf; + rcContourSet* m_cset; + rcPolyMesh* m_pmesh; + rcPolyMeshDetail* m_dmesh; + rcConfig m_cfg; + + float m_tileSize; + + float m_spos[3]; + float m_epos[3]; + bool m_sposSet; + bool m_eposSet; + + float m_tileCol[4]; + float m_tileBmin[3]; + float m_tileBmax[3]; + float m_tileBuildTime; + float m_tileMemUsage; + int m_tileTriCount; + + enum ToolMode + { + TOOLMODE_CREATE_TILES, + TOOLMODE_PATHFIND, + TOOLMODE_RAYCAST, + TOOLMODE_DISTANCE_TO_WALL, + TOOLMODE_FIND_POLYS_AROUND, + }; + + dtPolyRef m_startRef; + dtPolyRef m_endRef; + float m_polyPickExt[3]; + + static const int MAX_POLYS = 256; + + dtPolyRef m_polys[MAX_POLYS]; + dtPolyRef m_parent[MAX_POLYS]; + int m_npolys; + float m_straightPath[MAX_POLYS*3]; + int m_nstraightPath; + float m_hitPos[3]; + float m_hitNormal[3]; + float m_distanceToWall; + + ToolMode m_toolMode; + + void toolRecalc(); + + void buildTile(const float* pos); + void removeTile(const float* pos); + + unsigned char* buildTileMesh(const float* bmin, const float* bmax, int& dataSize); + + void cleanup(); + +public: + Sample_DynMesh(); + virtual ~Sample_DynMesh(); + + virtual void handleSettings(); + virtual void handleTools(); + virtual void handleDebugMode(); + + virtual void setToolStartPos(const float* p); + virtual void setToolEndPos(const float* p); + + virtual void handleRender(); + virtual void handleRenderOverlay(double* proj, double* model, int* view); + virtual void handleMeshChanged(const float* verts, int nverts, + const int* tris, const float* trinorms, int ntris, + const float* bmin, const float* bmax); + virtual bool handleBuild(); +}; + + +#endif // RECASTSAMPLEDYNMESH_H diff --git a/RecastDemo/Source/Sample_DynMesh.cpp b/RecastDemo/Source/Sample_DynMesh.cpp new file mode 100644 index 0000000..67fdd79 --- /dev/null +++ b/RecastDemo/Source/Sample_DynMesh.cpp @@ -0,0 +1,931 @@ +// +// 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. +// + +#define _USE_MATH_DEFINES +#include +#include +#include +#include "SDL.h" +#include "SDL_opengl.h" +#include "imgui.h" +#include "Sample.h" +#include "Sample_DynMesh.h" +#include "Recast.h" +#include "RecastTimer.h" +#include "RecastDebugDraw.h" +#include "DetourNavMesh.h" +#include "DetourNavMeshBuilder.h" +#include "DetourDebugDraw.h" + +#ifdef WIN32 +# define snprintf _snprintf +#endif + + +Sample_DynMesh::Sample_DynMesh() : + m_keepInterResults(false), + m_navMesh(0), + m_chunkyMesh(0), + m_triflags(0), + m_solid(0), + m_chf(0), + m_cset(0), + m_pmesh(0), + m_dmesh(0), + m_tileSize(32), + m_sposSet(false), + m_eposSet(false), + m_tileBuildTime(0), + m_tileMemUsage(0), + m_tileTriCount(0), + m_startRef(0), + m_endRef(0), + m_npolys(0), + m_nstraightPath(0), + m_distanceToWall(0), + m_toolMode(TOOLMODE_CREATE_TILES) +{ + resetCommonSettings(); + memset(m_tileBmin, 0, sizeof(m_tileBmin)); + memset(m_tileBmax, 0, sizeof(m_tileBmax)); + m_polyPickExt[0] = 2; + m_polyPickExt[1] = 4; + m_polyPickExt[2] = 2; +} + +Sample_DynMesh::~Sample_DynMesh() +{ + cleanup(); + delete m_navMesh; + delete m_chunkyMesh; +} + +void Sample_DynMesh::cleanup() +{ + delete [] m_triflags; + m_triflags = 0; + delete m_solid; + m_solid = 0; + delete m_chf; + m_chf = 0; + delete m_cset; + m_cset = 0; + delete m_pmesh; + m_pmesh = 0; + delete m_dmesh; + m_dmesh = 0; +} + +void Sample_DynMesh::handleSettings() +{ + Sample::handleCommonSettings(); + + imguiLabel("Tiling"); + imguiSlider("TileSize", &m_tileSize, 16.0f, 1024.0f, 16.0f); + + char text[64]; + int gw = 0, gh = 0; + rcCalcGridSize(m_bmin, m_bmax, m_cellSize, &gw, &gh); + const int ts = (int)m_tileSize; + const int tw = (gw + ts-1) / ts; + const int th = (gh + ts-1) / ts; + snprintf(text, 64, "Tiles %d x %d", tw, th); + imguiValue(text); +} + +void Sample_DynMesh::toolRecalc() +{ + m_startRef = 0; + if (m_sposSet) + m_startRef = m_navMesh->findNearestPoly(m_spos, m_polyPickExt); + + m_endRef = 0; + if (m_eposSet) + m_endRef = m_navMesh->findNearestPoly(m_epos, m_polyPickExt); + + if (m_toolMode == TOOLMODE_PATHFIND) + { + if (m_sposSet && m_eposSet && m_startRef && m_endRef) + { + m_npolys = m_navMesh->findPath(m_startRef, m_endRef, m_spos, m_epos, m_polys, MAX_POLYS); + if (m_npolys) + m_nstraightPath = m_navMesh->findStraightPath(m_spos, m_epos, m_polys, m_npolys, m_straightPath, MAX_POLYS); + } + else + { + m_npolys = 0; + m_nstraightPath = 0; + } + } + else if (m_toolMode == TOOLMODE_RAYCAST) + { + m_nstraightPath = 0; + if (m_sposSet && m_eposSet && m_startRef) + { + float t = 0; + m_npolys = 0; + m_nstraightPath = 2; + m_straightPath[0] = m_spos[0]; + m_straightPath[1] = m_spos[1]; + m_straightPath[2] = m_spos[2]; + m_npolys = m_navMesh->raycast(m_startRef, m_spos, m_epos, t, m_polys, MAX_POLYS); + if (m_npolys && t < 1) + { + m_straightPath[3] = m_spos[0] + (m_epos[0] - m_spos[0]) * t; + m_straightPath[4] = m_spos[1] + (m_epos[1] - m_spos[1]) * t; + m_straightPath[5] = m_spos[2] + (m_epos[2] - m_spos[2]) * t; + } + else + { + m_straightPath[3] = m_epos[0]; + m_straightPath[4] = m_epos[1]; + m_straightPath[5] = m_epos[2]; + } + } + } + else if (m_toolMode == TOOLMODE_DISTANCE_TO_WALL) + { + m_distanceToWall = 0; + if (m_sposSet && m_startRef) + m_distanceToWall = m_navMesh->findDistanceToWall(m_startRef, m_spos, 100.0f, m_hitPos, m_hitNormal); + } + else if (m_toolMode == TOOLMODE_FIND_POLYS_AROUND) + { + if (m_sposSet && m_startRef && m_eposSet) + { + const float dx = m_epos[0] - m_spos[0]; + const float dz = m_epos[2] - m_spos[2]; + float dist = sqrtf(dx*dx + dz*dz); + m_npolys = m_navMesh->findPolysAround(m_startRef, m_spos, dist, m_polys, m_parent, 0, MAX_POLYS); + } + } + +} + +void Sample_DynMesh::handleTools() +{ + if (imguiCheck("Create Tiles", m_toolMode == TOOLMODE_CREATE_TILES)) + { + m_toolMode = TOOLMODE_CREATE_TILES; + toolRecalc(); + } + if (imguiCheck("Pathfind", m_toolMode == TOOLMODE_PATHFIND)) + { + m_toolMode = TOOLMODE_PATHFIND; + toolRecalc(); + } + if (imguiCheck("Distance to Wall", m_toolMode == TOOLMODE_DISTANCE_TO_WALL)) + { + m_toolMode = TOOLMODE_DISTANCE_TO_WALL; + toolRecalc(); + } + if (imguiCheck("Raycast", m_toolMode == TOOLMODE_RAYCAST)) + { + m_toolMode = TOOLMODE_RAYCAST; + toolRecalc(); + } + if (imguiCheck("Find Polys Around", m_toolMode == TOOLMODE_FIND_POLYS_AROUND)) + { + m_toolMode = TOOLMODE_FIND_POLYS_AROUND; + toolRecalc(); + } + if (imguiButton("Create All")) + { + int gw = 0, gh = 0; + rcCalcGridSize(m_bmin, m_bmax, m_cellSize, &gw, &gh); + const int ts = (int)m_tileSize; + const int tw = (gw + ts-1) / ts; + const int th = (gh + ts-1) / ts; + const float tcs = m_tileSize*m_cellSize; + + for (int y = 0; y < th; ++y) + { + for (int x = 0; x < tw; ++x) + { + m_tileBmin[0] = m_bmin[0] + x*tcs; + m_tileBmin[1] = m_bmin[1]; + m_tileBmin[2] = m_bmin[2] + y*tcs; + + m_tileBmax[0] = m_bmin[0] + (x+1)*tcs; + m_tileBmax[1] = m_bmax[1]; + m_tileBmax[2] = m_bmin[2] + (y+1)*tcs; + + int dataSize = 0; + unsigned char* data = buildTileMesh(m_tileBmin, m_tileBmax, dataSize); + if (data) + { + // Remove any previous data (navmesh owns and deletes the data). + m_navMesh->removeTileAt(x,y,0,0); + // Let the navmesh own the data. + if (!m_navMesh->addTileAt(x,y,data,dataSize,true)) + delete [] data; + } + } + } + + toolRecalc(); + } +} + +void Sample_DynMesh::handleDebugMode() +{ + if (m_navMesh) + { + imguiValue("Navmesh ready."); + imguiValue("Use 'Create Tiles' tool to experiment."); + imguiValue("LMB: (Re)Create tiles."); + imguiValue("LMB+SHIFT: Remove tiles."); + } + +} + +static void getPolyCenter(dtNavMesh* navMesh, dtPolyRef ref, float* center) +{ + const dtPoly* p = navMesh->getPolyByRef(ref); + if (!p) return; + const float* verts = navMesh->getPolyVertsByRef(ref); + center[0] = 0; + center[1] = 0; + center[2] = 0; + for (int i = 0; i < (int)p->nv; ++i) + { + const float* v = &verts[p->v[i]*3]; + center[0] += v[0]; + center[1] += v[1]; + center[2] += v[2]; + } + const float s = 1.0f / p->nv; + center[0] *= s; + center[1] *= s; + center[2] *= s; +} + +void Sample_DynMesh::handleRender() +{ + if (!m_verts || !m_tris || !m_trinorms) + return; + + DebugDrawGL dd; + + // Draw mesh + if (m_navMesh) + rcDebugDrawMesh(&dd, m_verts, m_nverts, m_tris, m_trinorms, m_ntris, 0); + else + rcDebugDrawMeshSlope(&dd, m_verts, m_nverts, m_tris, m_trinorms, m_ntris, m_agentMaxSlope); + + glDepthMask(GL_FALSE); + + // Draw bounds + float col[4] = {1,1,1,0.5f}; + rcDebugDrawBoxWire(&dd, m_bmin[0],m_bmin[1],m_bmin[2], m_bmax[0],m_bmax[1],m_bmax[2], col); + + // Tiling grid. + const int ts = (int)m_tileSize; + int gw = 0, gh = 0; + rcCalcGridSize(m_bmin, m_bmax, m_cellSize, &gw, &gh); + int tw = (gw + ts-1) / ts; + int th = (gh + ts-1) / ts; + const float s = ts*m_cellSize; + glBegin(GL_LINES); + glColor4ub(0,0,0,64); + for (int y = 0; y < th; ++y) + { + for (int x = 0; x < tw; ++x) + { + float fx, fy, fz; + fx = m_bmin[0] + x*s; + fy = m_bmin[1]; + fz = m_bmin[2] + y*s; + + glVertex3f(fx,fy,fz); + glVertex3f(fx+s,fy,fz); + glVertex3f(fx,fy,fz); + glVertex3f(fx,fy,fz+s); + + if (x+1 >= tw) + { + glVertex3f(fx+s,fy,fz); + glVertex3f(fx+s,fy,fz+s); + } + if (y+1 >= th) + { + glVertex3f(fx,fy,fz+s); + glVertex3f(fx+s,fy,fz+s); + } + } + } + glEnd(); + + // Draw active tile + rcDebugDrawBoxWire(&dd, m_tileBmin[0],m_tileBmin[1],m_tileBmin[2], m_tileBmax[0],m_tileBmax[1],m_tileBmax[2], m_tileCol); + + if (m_navMesh) + dtDebugDrawNavMesh(m_navMesh); + + if (m_sposSet) + { + const float s = 0.5f; + glColor4ub(64,16,0,255); + glLineWidth(3.0f); + glBegin(GL_LINES); + glVertex3f(m_spos[0]-s,m_spos[1]+m_cellHeight,m_spos[2]); + glVertex3f(m_spos[0]+s,m_spos[1]+m_cellHeight,m_spos[2]); + glVertex3f(m_spos[0],m_spos[1]-s+m_cellHeight,m_spos[2]); + glVertex3f(m_spos[0],m_spos[1]+s+m_cellHeight,m_spos[2]); + glVertex3f(m_spos[0],m_spos[1]+m_cellHeight,m_spos[2]-s); + glVertex3f(m_spos[0],m_spos[1]+m_cellHeight,m_spos[2]+s); + glEnd(); + glLineWidth(1.0f); + } + if (m_eposSet) + { + const float s = 0.5f; + glColor4ub(16,64,0,255); + glLineWidth(3.0f); + glBegin(GL_LINES); + glVertex3f(m_epos[0]-s,m_epos[1]+m_cellHeight,m_epos[2]); + glVertex3f(m_epos[0]+s,m_epos[1]+m_cellHeight,m_epos[2]); + glVertex3f(m_epos[0],m_epos[1]-s+m_cellHeight,m_epos[2]); + glVertex3f(m_epos[0],m_epos[1]+s+m_cellHeight,m_epos[2]); + glVertex3f(m_epos[0],m_epos[1]+m_cellHeight,m_epos[2]-s); + glVertex3f(m_epos[0],m_epos[1]+m_cellHeight,m_epos[2]+s); + glEnd(); + glLineWidth(1.0f); + } + + static const float startCol[4] = { 0.5f, 0.1f, 0.0f, 0.75f }; + static const float endCol[4] = { 0.2f, 0.4f, 0.0f, 0.75f }; + static const float pathCol[4] = {0,0,0,0.25f}; + + if (m_toolMode == TOOLMODE_PATHFIND) + { + dtDebugDrawNavMeshPoly(m_navMesh, m_startRef, startCol); + dtDebugDrawNavMeshPoly(m_navMesh, m_endRef, endCol); + + if (m_npolys) + { + for (int i = 1; i < m_npolys-1; ++i) + dtDebugDrawNavMeshPoly(m_navMesh, m_polys[i], pathCol); + } + if (m_nstraightPath) + { + glColor4ub(64,16,0,220); + glLineWidth(3.0f); + glBegin(GL_LINE_STRIP); + for (int i = 0; i < m_nstraightPath; ++i) + glVertex3f(m_straightPath[i*3], m_straightPath[i*3+1]+0.4f, m_straightPath[i*3+2]); + glEnd(); + glLineWidth(1.0f); + glPointSize(4.0f); + glBegin(GL_POINTS); + for (int i = 0; i < m_nstraightPath; ++i) + glVertex3f(m_straightPath[i*3], m_straightPath[i*3+1]+0.4f, m_straightPath[i*3+2]); + glEnd(); + glPointSize(1.0f); + } + } + else if (m_toolMode == TOOLMODE_RAYCAST) + { + dtDebugDrawNavMeshPoly(m_navMesh, m_startRef, startCol); + + if (m_nstraightPath) + { + for (int i = 1; i < m_npolys; ++i) + dtDebugDrawNavMeshPoly(m_navMesh, m_polys[i], pathCol); + + glColor4ub(64,16,0,220); + glLineWidth(3.0f); + glBegin(GL_LINE_STRIP); + for (int i = 0; i < m_nstraightPath; ++i) + glVertex3f(m_straightPath[i*3], m_straightPath[i*3+1]+0.4f, m_straightPath[i*3+2]); + glEnd(); + glLineWidth(1.0f); + glPointSize(4.0f); + glBegin(GL_POINTS); + for (int i = 0; i < m_nstraightPath; ++i) + glVertex3f(m_straightPath[i*3], m_straightPath[i*3+1]+0.4f, m_straightPath[i*3+2]); + glEnd(); + glPointSize(1.0f); + } + } + else if (m_toolMode == TOOLMODE_DISTANCE_TO_WALL) + { + dtDebugDrawNavMeshPoly(m_navMesh, m_startRef, startCol); + const float col[4] = {1,1,1,0.5f}; + rcDebugDrawCylinderWire(&dd, m_spos[0]-m_distanceToWall, m_spos[1]+0.02f, m_spos[2]-m_distanceToWall, + m_spos[0]+m_distanceToWall, m_spos[1]+m_agentHeight, m_spos[2]+m_distanceToWall, col); + glLineWidth(3.0f); + glColor4fv(col); + glBegin(GL_LINES); + glVertex3f(m_hitPos[0], m_hitPos[1] + 0.02f, m_hitPos[2]); + glVertex3f(m_hitPos[0], m_hitPos[1] + m_agentHeight, m_hitPos[2]); + glEnd(); + glLineWidth(1.0f); + } + else if (m_toolMode == TOOLMODE_FIND_POLYS_AROUND) + { + const float cola[4] = {0,0,0,0.5f}; + for (int i = 0; i < m_npolys; ++i) + { + dtDebugDrawNavMeshPoly(m_navMesh, m_polys[i], pathCol); + if (m_parent[i]) + { + float p0[3], p1[3]; + getPolyCenter(m_navMesh, m_polys[i], p0); + getPolyCenter(m_navMesh, m_parent[i], p1); + glColor4ub(0,0,0,128); + rcDrawArc(&dd, p0, p1, cola, 2.0f); + } + } + + const float dx = m_epos[0] - m_spos[0]; + const float dz = m_epos[2] - m_spos[2]; + float dist = sqrtf(dx*dx + dz*dz); + const float col[4] = {1,1,1,0.5f}; + rcDebugDrawCylinderWire(&dd, m_spos[0]-dist, m_spos[1]+0.02f, m_spos[2]-dist, + m_spos[0]+dist, m_spos[1]+m_agentHeight, m_spos[2]+dist, col); + } + + glDepthMask(GL_TRUE); + +} + +void Sample_DynMesh::handleRenderOverlay(double* proj, double* model, int* view) +{ + GLdouble x, y, z; + + // Draw start and end point labels + if (m_tileBuildTime > 0.0f && gluProject((GLdouble)(m_tileBmin[0]+m_tileBmax[0])/2, (GLdouble)(m_tileBmin[1]+m_tileBmax[1])/2, (GLdouble)(m_tileBmin[2]+m_tileBmax[2])/2, + model, proj, view, &x, &y, &z)) + { + char text[32]; + snprintf(text,32,"%.3fms / %dTris / %.1fkB", m_tileBuildTime, m_tileTriCount, m_tileMemUsage); + imguiDrawText((int)x, (int)y-25, IMGUI_ALIGN_CENTER, text, imguiRGBA(0,0,0,220)); + } +} + +void Sample_DynMesh::handleMeshChanged(const float* verts, int nverts, + const int* tris, const float* trinorms, int ntris, + const float* bmin, const float* bmax) +{ + m_verts = verts; + m_nverts = nverts; + m_tris = tris; + m_trinorms = trinorms; + m_ntris = ntris; + vcopy(m_bmin, bmin); + vcopy(m_bmax, bmax); + + delete m_chunkyMesh; + m_chunkyMesh = 0; + delete m_navMesh; + m_navMesh = 0; + cleanup(); +} + +void Sample_DynMesh::setToolStartPos(const float* p) +{ + m_sposSet = true; + vcopy(m_spos, p); + + if (m_toolMode == TOOLMODE_CREATE_TILES) + removeTile(m_spos); + else + toolRecalc(); +} + +void Sample_DynMesh::setToolEndPos(const float* p) +{ + if (!m_navMesh) + return; + + m_eposSet = true; + vcopy(m_epos, p); + + if (m_toolMode == TOOLMODE_CREATE_TILES) + buildTile(m_epos); + else + toolRecalc(); +} + +bool Sample_DynMesh::handleBuild() +{ + if (!m_verts || !m_tris) + { + printf("No verts or tris\n"); + return false; + } + + delete m_navMesh; + m_navMesh = new dtNavMesh; + if (!m_navMesh) + { + printf("Could not allocate navmehs\n"); + return false; + } + if (!m_navMesh->init(m_bmin, m_tileSize*m_cellSize, m_agentMaxClimb*m_cellHeight, 512, 512, 2048)) + { + printf("Could not init navmesh\n"); + return false; + } + + // Build chunky mesh. + delete m_chunkyMesh; + m_chunkyMesh = new rcChunkyTriMesh; + if (!m_chunkyMesh) + { + if (rcGetLog()) + rcGetLog()->log(RC_LOG_ERROR, "buildTiledNavigation: Out of memory 'm_chunkyMesh'."); + return false; + } + if (!rcCreateChunkyTriMesh(m_verts, m_tris, m_ntris, 256, m_chunkyMesh)) + { + if (rcGetLog()) + rcGetLog()->log(RC_LOG_ERROR, "buildTiledNavigation: Could not build chunky mesh."); + return false; + } + + return true; +} + +void Sample_DynMesh::buildTile(const float* pos) +{ + if (!m_navMesh) + return; + + const float ts = m_tileSize*m_cellSize; + const int tx = (int)floorf((pos[0]-m_bmin[0]) / ts); + const int ty = (int)floorf((pos[2]-m_bmin[2]) / ts); + if (tx < 0 || ty < 0) + return; + + m_tileBmin[0] = m_bmin[0] + tx*ts; + m_tileBmin[1] = m_bmin[1]; + m_tileBmin[2] = m_bmin[2] + ty*ts; + + m_tileBmax[0] = m_bmin[0] + (tx+1)*ts; + m_tileBmax[1] = m_bmax[1]; + m_tileBmax[2] = m_bmin[2] + (ty+1)*ts; + + m_tileCol[0] = 0.3f; m_tileCol[1] = 0.8f; m_tileCol[2] = 0; m_tileCol[3] = 1; + + int dataSize = 0; + unsigned char* data = buildTileMesh(m_tileBmin, m_tileBmax, dataSize); + + if (data) + { + // Remove any previous data (navmesh owns and deletes the data). + m_navMesh->removeTileAt(tx,ty,0,0); + + // Let the navmesh own the data. + if (!m_navMesh->addTileAt(tx,ty,data,dataSize,true)) + delete [] data; + } +} + +void Sample_DynMesh::removeTile(const float* pos) +{ + if (!m_navMesh) + return; + + const float ts = m_tileSize*m_cellSize; + const int tx = (int)floorf((pos[0]-m_bmin[0]) / ts); + const int ty = (int)floorf((pos[2]-m_bmin[2]) / ts); + + m_tileBmin[0] = m_bmin[0] + tx*ts; + m_tileBmin[1] = m_bmin[1]; + m_tileBmin[2] = m_bmin[2] + ty*ts; + + m_tileBmax[0] = m_bmin[0] + (tx+1)*ts; + m_tileBmax[1] = m_bmax[1]; + m_tileBmax[2] = m_bmin[2] + (ty+1)*ts; + + m_tileCol[0] = 0.8f; m_tileCol[1] = 0.1f; m_tileCol[2] = 0; m_tileCol[3] = 1; + + unsigned char* rdata = 0; + int rdataSize = 0; + if (m_navMesh->removeTileAt(tx,ty,&rdata,&rdataSize)) + delete [] rdata; +} + +unsigned char* Sample_DynMesh::buildTileMesh(const float* bmin, const float* bmax, int& dataSize) +{ + if (!m_verts || ! m_tris) + { + if (rcGetLog()) + rcGetLog()->log(RC_LOG_ERROR, "buildNavigation: Input mesh is not specified."); + return 0; + } + + cleanup(); + + // Init build configuration from GUI + memset(&m_cfg, 0, sizeof(m_cfg)); + m_cfg.cs = m_cellSize; + m_cfg.ch = m_cellHeight; + m_cfg.walkableSlopeAngle = m_agentMaxSlope; + m_cfg.walkableHeight = (int)ceilf(m_agentHeight / m_cfg.ch); + m_cfg.walkableClimb = (int)ceilf(m_agentMaxClimb / m_cfg.ch); + m_cfg.walkableRadius = (int)ceilf(m_agentRadius / m_cfg.cs); + m_cfg.maxEdgeLen = (int)(m_edgeMaxLen / m_cellSize); + m_cfg.maxSimplificationError = m_edgeMaxError; + m_cfg.minRegionSize = (int)rcSqr(m_regionMinSize); + m_cfg.mergeRegionSize = (int)rcSqr(m_regionMergeSize); + m_cfg.maxVertsPerPoly = (int)m_vertsPerPoly; + m_cfg.tileSize = (int)m_tileSize; + m_cfg.borderSize = m_cfg.walkableRadius + 3; // Reserve enough padding. + m_cfg.width = m_cfg.tileSize + m_cfg.borderSize*2; + m_cfg.height = m_cfg.tileSize + m_cfg.borderSize*2; + m_cfg.detailSampleDist = m_detailSampleDist < 0.9f ? 0 : m_cellSize * m_detailSampleDist; + m_cfg.detailSampleMaxError = m_cellHeight * m_detailSampleMaxError; + + vcopy(m_cfg.bmin, bmin); + vcopy(m_cfg.bmax, bmax); + m_cfg.bmin[0] -= m_cfg.borderSize*m_cfg.cs; + m_cfg.bmin[2] -= m_cfg.borderSize*m_cfg.cs; + m_cfg.bmax[0] += m_cfg.borderSize*m_cfg.cs; + m_cfg.bmax[2] += m_cfg.borderSize*m_cfg.cs; + + // Reset build times gathering. + memset(&m_buildTimes, 0, sizeof(m_buildTimes)); + rcSetBuildTimes(&m_buildTimes); + + // Start the build process. + rcTimeVal totStartTime = rcGetPerformanceTimer(); + + if (rcGetLog()) + { + rcGetLog()->log(RC_LOG_PROGRESS, "Building navigation:"); + rcGetLog()->log(RC_LOG_PROGRESS, " - %d x %d cells", m_cfg.width, m_cfg.height); + rcGetLog()->log(RC_LOG_PROGRESS, " - %.1fK verts, %.1fK tris", m_nverts/1000.0f, m_ntris/1000.0f); + } + + // Allocate voxel heighfield where we rasterize our input data to. + m_solid = new rcHeightfield; + if (!m_solid) + { + if (rcGetLog()) + rcGetLog()->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'solid'."); + return 0; + } + if (!rcCreateHeightfield(*m_solid, m_cfg.width, m_cfg.height, m_cfg.bmin, m_cfg.bmax, m_cfg.cs, m_cfg.ch)) + { + if (rcGetLog()) + rcGetLog()->log(RC_LOG_ERROR, "buildNavigation: Could not create solid heightfield."); + return 0; + } + + // Allocate array that can hold triangle flags. + // If you have multiple meshes you need to process, allocate + // and array which can hold the max number of triangles you need to process. + m_triflags = new unsigned char[m_chunkyMesh->maxTrisPerChunk]; + if (!m_triflags) + { + if (rcGetLog()) + rcGetLog()->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'triangleFlags' (%d).", m_chunkyMesh->maxTrisPerChunk); + return 0; + } + + + float tbmin[2], tbmax[2]; + tbmin[0] = m_cfg.bmin[0]; + tbmin[1] = m_cfg.bmin[2]; + tbmax[0] = m_cfg.bmax[0]; + tbmax[1] = m_cfg.bmax[2]; + int cid[256];// TODO: Make grow when returning too many items. + const int ncid = rcGetChunksInRect(m_chunkyMesh, tbmin, tbmax, cid, 256); + if (!ncid) + return 0; + + m_tileTriCount = 0; + + for (int i = 0; i < ncid; ++i) + { + const rcChunkyTriMeshNode& node = m_chunkyMesh->nodes[cid[i]]; + const int* tris = &m_chunkyMesh->tris[node.i*3]; + const int ntris = node.n; + + m_tileTriCount += ntris; + + memset(m_triflags, 0, ntris*sizeof(unsigned char)); + rcMarkWalkableTriangles(m_cfg.walkableSlopeAngle, + m_verts, m_nverts, tris, ntris, m_triflags); + + rcRasterizeTriangles(m_verts, m_nverts, tris, m_triflags, ntris, *m_solid); + } + + if (!m_keepInterResults) + { + delete [] m_triflags; + m_triflags = 0; + } + + // Once all geoemtry is rasterized, we do initial pass of filtering to + // remove unwanted overhangs caused by the conservative rasterization + // as well as filter spans where the character cannot possibly stand. + rcFilterLedgeSpans(m_cfg.walkableHeight, m_cfg.walkableClimb, *m_solid); + rcFilterWalkableLowHeightSpans(m_cfg.walkableHeight, *m_solid); + + // Compact the heightfield so that it is faster to handle from now on. + // This will result more cache coherent data as well as the neighbours + // between walkable cells will be calculated. + m_chf = new rcCompactHeightfield; + if (!m_chf) + { + if (rcGetLog()) + rcGetLog()->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'chf'."); + return 0; + } + if (!rcBuildCompactHeightfield(m_cfg.walkableHeight, m_cfg.walkableClimb, RC_WALKABLE, *m_solid, *m_chf)) + { + if (rcGetLog()) + rcGetLog()->log(RC_LOG_ERROR, "buildNavigation: Could not build compact data."); + return 0; + } + + if (!m_keepInterResults) + { + delete m_solid; + m_solid = 0; + } + + // Prepare for region partitioning, by calculating distance field along the walkable surface. + if (!rcBuildDistanceField(*m_chf)) + { + if (rcGetLog()) + rcGetLog()->log(RC_LOG_ERROR, "buildNavigation: Could not build distance field."); + return 0; + } + + // Partition the walkable surface into simple regions without holes. + if (!rcBuildRegions(*m_chf, m_cfg.walkableRadius, m_cfg.borderSize, m_cfg.minRegionSize, m_cfg.mergeRegionSize)) + { + if (rcGetLog()) + rcGetLog()->log(RC_LOG_ERROR, "buildNavigation: Could not build regions."); + return 0; + } + + // Create contours. + m_cset = new rcContourSet; + if (!m_cset) + { + if (rcGetLog()) + rcGetLog()->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'cset'."); + return 0; + } + if (!rcBuildContours(*m_chf, m_cfg.maxSimplificationError, m_cfg.maxEdgeLen, *m_cset)) + { + if (rcGetLog()) + rcGetLog()->log(RC_LOG_ERROR, "buildNavigation: Could not create contours."); + return 0; + } + + if (m_cset->nconts == 0) + { + return 0; + } + + // Build polygon navmesh from the contours. + m_pmesh = new rcPolyMesh; + if (!m_pmesh) + { + if (rcGetLog()) + rcGetLog()->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'pmesh'."); + return 0; + } + if (!rcBuildPolyMesh(*m_cset, m_cfg.maxVertsPerPoly, *m_pmesh)) + { + if (rcGetLog()) + rcGetLog()->log(RC_LOG_ERROR, "buildNavigation: Could not triangulate contours."); + return 0; + } + + // Build detail mesh. + m_dmesh = new rcPolyMeshDetail; + if (!m_dmesh) + { + if (rcGetLog()) + rcGetLog()->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'dmesh'."); + return 0; + } + + if (!rcBuildPolyMeshDetail(*m_pmesh, *m_chf, + m_cfg.detailSampleDist, m_cfg.detailSampleMaxError, + *m_dmesh)) + { + if (rcGetLog()) + rcGetLog()->log(RC_LOG_ERROR, "buildNavigation: Could build polymesh detail."); + return 0; + } + + if (!m_keepInterResults) + { + delete m_chf; + m_chf = 0; + delete m_cset; + m_cset = 0; + } + + unsigned char* navData = 0; + int navDataSize = 0; + if (m_cfg.maxVertsPerPoly <= DT_VERTS_PER_POLYGON) + { + // Remove padding from the polymesh data. TODO: Remove this odditity. + for (int i = 0; i < m_pmesh->nverts; ++i) + { + unsigned short* v = &m_pmesh->verts[i*3]; + v[0] -= (unsigned short)m_cfg.borderSize; + v[2] -= (unsigned short)m_cfg.borderSize; + } + + if (m_pmesh->nverts >= 0xffff) + { + // The vertex indices are ushorts, and cannot point to more than 0xffff vertices. + if (rcGetLog()) + rcGetLog()->log(RC_LOG_ERROR, "Too many vertices per tile %d (max: %d).", m_pmesh->nverts, 0xffff); + return false; + } +/* if (m_pmesh->npolys > DT_MAX_TILES) + { + // If you hit this error, you have too many polygons per tile. + // You can trade off tile count to poly count by adjusting DT_TILE_REF_TILE_BITS and DT_TILE_REF_POLY_BITS. + // The current setup is optimized for large number of tiles and small number of polys per tile. + if (rcGetLog()) + rcGetLog()->log(RC_LOG_ERROR, "Too many polygons per tile %d (max: %d).", m_pmesh->npolys, DT_MAX_TILES); + return false; + }*/ + + if (!dtCreateNavMeshData(m_pmesh->verts, m_pmesh->nverts, + m_pmesh->polys, m_pmesh->npolys, m_pmesh->nvp, + m_dmesh->meshes, m_dmesh->verts, m_dmesh->nverts, m_dmesh->tris, m_dmesh->ntris, + bmin, bmax, m_cfg.cs, m_cfg.ch, m_cfg.tileSize, m_cfg.walkableClimb, &navData, &navDataSize)) + { + if (rcGetLog()) + rcGetLog()->log(RC_LOG_ERROR, "Could not build Detour navmesh."); + return 0; + } + } + m_tileMemUsage = navDataSize/1024.0f; + + rcTimeVal totEndTime = rcGetPerformanceTimer(); + + // Show performance stats. + if (rcGetLog()) + { + const float pc = 100.0f / rcGetDeltaTimeUsec(totStartTime, totEndTime); + + rcGetLog()->log(RC_LOG_PROGRESS, "Rasterize: %.1fms (%.1f%%)", m_buildTimes.rasterizeTriangles/1000.0f, m_buildTimes.rasterizeTriangles*pc); + + rcGetLog()->log(RC_LOG_PROGRESS, "Build Compact: %.1fms (%.1f%%)", m_buildTimes.buildCompact/1000.0f, m_buildTimes.buildCompact*pc); + + rcGetLog()->log(RC_LOG_PROGRESS, "Filter Border: %.1fms (%.1f%%)", m_buildTimes.filterBorder/1000.0f, m_buildTimes.filterBorder*pc); + rcGetLog()->log(RC_LOG_PROGRESS, "Filter Walkable: %.1fms (%.1f%%)", m_buildTimes.filterWalkable/1000.0f, m_buildTimes.filterWalkable*pc); + rcGetLog()->log(RC_LOG_PROGRESS, "Filter Reachable: %.1fms (%.1f%%)", m_buildTimes.filterMarkReachable/1000.0f, m_buildTimes.filterMarkReachable*pc); + + rcGetLog()->log(RC_LOG_PROGRESS, "Build Distancefield: %.1fms (%.1f%%)", m_buildTimes.buildDistanceField/1000.0f, m_buildTimes.buildDistanceField*pc); + rcGetLog()->log(RC_LOG_PROGRESS, " - distance: %.1fms (%.1f%%)", m_buildTimes.buildDistanceFieldDist/1000.0f, m_buildTimes.buildDistanceFieldDist*pc); + rcGetLog()->log(RC_LOG_PROGRESS, " - blur: %.1fms (%.1f%%)", m_buildTimes.buildDistanceFieldBlur/1000.0f, m_buildTimes.buildDistanceFieldBlur*pc); + + rcGetLog()->log(RC_LOG_PROGRESS, "Build Regions: %.1fms (%.1f%%)", m_buildTimes.buildRegions/1000.0f, m_buildTimes.buildRegions*pc); + rcGetLog()->log(RC_LOG_PROGRESS, " - watershed: %.1fms (%.1f%%)", m_buildTimes.buildRegionsReg/1000.0f, m_buildTimes.buildRegionsReg*pc); + rcGetLog()->log(RC_LOG_PROGRESS, " - expand: %.1fms (%.1f%%)", m_buildTimes.buildRegionsExp/1000.0f, m_buildTimes.buildRegionsExp*pc); + rcGetLog()->log(RC_LOG_PROGRESS, " - find catchment basins: %.1fms (%.1f%%)", m_buildTimes.buildRegionsFlood/1000.0f, m_buildTimes.buildRegionsFlood*pc); + rcGetLog()->log(RC_LOG_PROGRESS, " - filter: %.1fms (%.1f%%)", m_buildTimes.buildRegionsFilter/1000.0f, m_buildTimes.buildRegionsFilter*pc); + + rcGetLog()->log(RC_LOG_PROGRESS, "Build Contours: %.1fms (%.1f%%)", m_buildTimes.buildContours/1000.0f, m_buildTimes.buildContours*pc); + rcGetLog()->log(RC_LOG_PROGRESS, " - trace: %.1fms (%.1f%%)", m_buildTimes.buildContoursTrace/1000.0f, m_buildTimes.buildContoursTrace*pc); + rcGetLog()->log(RC_LOG_PROGRESS, " - simplify: %.1fms (%.1f%%)", m_buildTimes.buildContoursSimplify/1000.0f, m_buildTimes.buildContoursSimplify*pc); + + rcGetLog()->log(RC_LOG_PROGRESS, "Build Polymesh: %.1fms (%.1f%%)", m_buildTimes.buildPolymesh/1000.0f, m_buildTimes.buildPolymesh*pc); + rcGetLog()->log(RC_LOG_PROGRESS, "Build Polymesh Detail: %.1fms (%.1f%%)", m_buildTimes.buildDetailMesh/1000.0f, m_buildTimes.buildDetailMesh*pc); + rcGetLog()->log(RC_LOG_PROGRESS, "Merge Polymeshes: %.1fms (%.1f%%)", m_buildTimes.mergePolyMesh/1000.0f, m_buildTimes.mergePolyMesh*pc); + rcGetLog()->log(RC_LOG_PROGRESS, "Merge Polymesh Details: %.1fms (%.1f%%)", m_buildTimes.mergePolyMeshDetail/1000.0f, m_buildTimes.mergePolyMeshDetail*pc); + + + rcGetLog()->log(RC_LOG_PROGRESS, "Build Polymesh: %.1fms (%.1f%%)", m_buildTimes.buildPolymesh/1000.0f, m_buildTimes.buildPolymesh*pc); + + rcGetLog()->log(RC_LOG_PROGRESS, "Polymesh: Verts:%d Polys:%d", m_pmesh->nverts, m_pmesh->npolys); + + rcGetLog()->log(RC_LOG_PROGRESS, "TOTAL: %.1fms", rcGetDeltaTimeUsec(totStartTime, totEndTime)/1000.0f); + } + + m_tileBuildTime = rcGetDeltaTimeUsec(totStartTime, totEndTime)/1000.0f; + + dataSize = navDataSize; + return navData; +} diff --git a/RecastDemo/Source/main.cpp b/RecastDemo/Source/main.cpp index 0641bdc..2635983 100644 --- a/RecastDemo/Source/main.cpp +++ b/RecastDemo/Source/main.cpp @@ -18,6 +18,7 @@ #include "Sample_StatMeshSimple.h" #include "Sample_StatMeshTiled.h" #include "Sample_TileMesh.h" +#include "Sample_DynMesh.h" #ifdef WIN32 # define snprintf _snprintf @@ -197,16 +198,17 @@ struct SampleItem Sample* createStatSimple() { return new Sample_StatMeshSimple(); } Sample* createStatTiled() { return new Sample_StatMeshTiled(); } Sample* createTile() { return new Sample_TileMesh(); } +Sample* createDyn() { return new Sample_DynMesh(); } static SampleItem g_samples[] = { { createStatSimple, "Static Mesh (Simple)" }, { createStatTiled, "Static Mesh (Tiled)" }, { createTile, "Tile Mesh" }, +{ createDyn, "Dyn Mesh" }, }; static const int g_nsamples = sizeof(g_samples)/sizeof(SampleItem); - int main(int argc, char *argv[]) { // Init SDL