polys(alloc, maxVertsPerCont*MAX_VERTS_PER_POLY);
if (!polys)
return DT_FAILURE | DT_OUT_OF_MEMORY;
for (int i = 0; i < lcset.nconts; ++i)
{
dtTileCacheContour& cont = lcset.conts[i];
// Skip null contours.
if (cont.nverts < 3)
continue;
// Triangulate contour
for (int j = 0; j < cont.nverts; ++j)
indices[j] = (unsigned short)j;
int ntris = triangulate(cont.nverts, cont.verts, &indices[0], &tris[0]);
if (ntris <= 0)
{
// TODO: issue warning!
ntris = -ntris;
}
// Add and merge vertices.
for (int j = 0; j < cont.nverts; ++j)
{
const unsigned char* v = &cont.verts[j*4];
indices[j] = addVertex((unsigned short)v[0], (unsigned short)v[1], (unsigned short)v[2],
mesh.verts, firstVert, nextVert, mesh.nverts);
if (v[3] & 0x80)
{
// This vertex should be removed.
vflags[indices[j]] = 1;
}
}
// Build initial polygons.
int npolys = 0;
memset(polys, 0xff, sizeof(unsigned short) * maxVertsPerCont * MAX_VERTS_PER_POLY);
for (int j = 0; j < ntris; ++j)
{
const unsigned short* t = &tris[j*3];
if (t[0] != t[1] && t[0] != t[2] && t[1] != t[2])
{
polys[npolys*MAX_VERTS_PER_POLY+0] = indices[t[0]];
polys[npolys*MAX_VERTS_PER_POLY+1] = indices[t[1]];
polys[npolys*MAX_VERTS_PER_POLY+2] = indices[t[2]];
npolys++;
}
}
if (!npolys)
continue;
// Merge polygons.
int maxVertsPerPoly =MAX_VERTS_PER_POLY ;
if (maxVertsPerPoly > 3)
{
for(;;)
{
// Find best polygons to merge.
int bestMergeVal = 0;
int bestPa = 0, bestPb = 0, bestEa = 0, bestEb = 0;
for (int j = 0; j < npolys-1; ++j)
{
unsigned short* pj = &polys[j*MAX_VERTS_PER_POLY];
for (int k = j+1; k < npolys; ++k)
{
unsigned short* pk = &polys[k*MAX_VERTS_PER_POLY];
int ea, eb;
int v = getPolyMergeValue(pj, pk, mesh.verts, ea, eb);
if (v > bestMergeVal)
{
bestMergeVal = v;
bestPa = j;
bestPb = k;
bestEa = ea;
bestEb = eb;
}
}
}
if (bestMergeVal > 0)
{
// Found best, merge.
unsigned short* pa = &polys[bestPa*MAX_VERTS_PER_POLY];
unsigned short* pb = &polys[bestPb*MAX_VERTS_PER_POLY];
mergePolys(pa, pb, bestEa, bestEb);
memcpy(pb, &polys[(npolys-1)*MAX_VERTS_PER_POLY], sizeof(unsigned short)*MAX_VERTS_PER_POLY);
npolys--;
}
else
{
// Could not merge any polygons, stop.
break;
}
}
}
// Store polygons.
for (int j = 0; j < npolys; ++j)
{
unsigned short* p = &mesh.polys[mesh.npolys*MAX_VERTS_PER_POLY*2];
unsigned short* q = &polys[j*MAX_VERTS_PER_POLY];
for (int k = 0; k < MAX_VERTS_PER_POLY; ++k)
p[k] = q[k];
mesh.areas[mesh.npolys] = cont.area;
mesh.npolys++;
if (mesh.npolys > maxTris)
return DT_FAILURE | DT_BUFFER_TOO_SMALL;
}
}
// Remove edge vertices.
for (int i = 0; i < mesh.nverts; ++i)
{
if (vflags[i])
{
if (!canRemoveVertex(mesh, (unsigned short)i))
continue;
dtStatus status = removeVertex(mesh, (unsigned short)i, maxTris);
if (dtStatusFailed(status))
return status;
// Remove vertex
// Note: mesh.nverts is already decremented inside removeVertex()!
for (int j = i; j < mesh.nverts; ++j)
vflags[j] = vflags[j+1];
--i;
}
}
// Calculate adjacency.
if (!buildMeshAdjacency(alloc, mesh.polys, mesh.npolys, mesh.verts, mesh.nverts, lcset))
return DT_FAILURE | DT_OUT_OF_MEMORY;
return DT_SUCCESS;
}
dtStatus dtMarkCylinderArea(dtTileCacheLayer& layer, const float* orig, const float cs, const float ch,
const float* pos, const float radius, const float height, const unsigned char areaId)
{
float bmin[3], bmax[3];
bmin[0] = pos[0] - radius;
bmin[1] = pos[1];
bmin[2] = pos[2] - radius;
bmax[0] = pos[0] + radius;
bmax[1] = pos[1] + height;
bmax[2] = pos[2] + radius;
const float r2 = dtSqr(radius/cs + 0.5f);
const int w = (int)layer.header->width;
const int h = (int)layer.header->height;
const float ics = 1.0f/cs;
const float ich = 1.0f/ch;
const float px = (pos[0]-orig[0])*ics;
const float pz = (pos[2]-orig[2])*ics;
int minx = (int)dtMathFloorf((bmin[0]-orig[0])*ics);
int miny = (int)dtMathFloorf((bmin[1]-orig[1])*ich);
int minz = (int)dtMathFloorf((bmin[2]-orig[2])*ics);
int maxx = (int)dtMathFloorf((bmax[0]-orig[0])*ics);
int maxy = (int)dtMathFloorf((bmax[1]-orig[1])*ich);
int maxz = (int)dtMathFloorf((bmax[2]-orig[2])*ics);
if (maxx < 0) return DT_SUCCESS;
if (minx >= w) return DT_SUCCESS;
if (maxz < 0) return DT_SUCCESS;
if (minz >= h) return DT_SUCCESS;
if (minx < 0) minx = 0;
if (maxx >= w) maxx = w-1;
if (minz < 0) minz = 0;
if (maxz >= h) maxz = h-1;
for (int z = minz; z <= maxz; ++z)
{
for (int x = minx; x <= maxx; ++x)
{
const float dx = (float)(x+0.5f) - px;
const float dz = (float)(z+0.5f) - pz;
if (dx*dx + dz*dz > r2)
continue;
const int y = layer.heights[x+z*w];
if (y < miny || y > maxy)
continue;
layer.areas[x+z*w] = areaId;
}
}
return DT_SUCCESS;
}
dtStatus dtMarkBoxArea(dtTileCacheLayer& layer, const float* orig, const float cs, const float ch,
const float* bmin, const float* bmax, const unsigned char areaId)
{
const int w = (int)layer.header->width;
const int h = (int)layer.header->height;
const float ics = 1.0f/cs;
const float ich = 1.0f/ch;
int minx = (int)floorf((bmin[0]-orig[0])*ics);
int miny = (int)floorf((bmin[1]-orig[1])*ich);
int minz = (int)floorf((bmin[2]-orig[2])*ics);
int maxx = (int)floorf((bmax[0]-orig[0])*ics);
int maxy = (int)floorf((bmax[1]-orig[1])*ich);
int maxz = (int)floorf((bmax[2]-orig[2])*ics);
if (maxx < 0) return DT_SUCCESS;
if (minx >= w) return DT_SUCCESS;
if (maxz < 0) return DT_SUCCESS;
if (minz >= h) return DT_SUCCESS;
if (minx < 0) minx = 0;
if (maxx >= w) maxx = w-1;
if (minz < 0) minz = 0;
if (maxz >= h) maxz = h-1;
for (int z = minz; z <= maxz; ++z)
{
for (int x = minx; x <= maxx; ++x)
{
const int y = layer.heights[x+z*w];
if (y < miny || y > maxy)
continue;
layer.areas[x+z*w] = areaId;
}
}
return DT_SUCCESS;
}
dtStatus dtMarkBoxArea(dtTileCacheLayer& layer, const float* orig, const float cs, const float ch,
const float* center, const float* halfExtents, const float* rotAux, const unsigned char areaId)
{
const int w = (int)layer.header->width;
const int h = (int)layer.header->height;
const float ics = 1.0f/cs;
const float ich = 1.0f/ch;
float cx = (center[0] - orig[0])*ics;
float cz = (center[2] - orig[2])*ics;
float maxr = 1.41f*dtMax(halfExtents[0], halfExtents[2]);
int minx = (int)floorf(cx - maxr*ics);
int maxx = (int)floorf(cx + maxr*ics);
int minz = (int)floorf(cz - maxr*ics);
int maxz = (int)floorf(cz + maxr*ics);
int miny = (int)floorf((center[1]-halfExtents[1]-orig[1])*ich);
int maxy = (int)floorf((center[1]+halfExtents[1]-orig[1])*ich);
if (maxx < 0) return DT_SUCCESS;
if (minx >= w) return DT_SUCCESS;
if (maxz < 0) return DT_SUCCESS;
if (minz >= h) return DT_SUCCESS;
if (minx < 0) minx = 0;
if (maxx >= w) maxx = w-1;
if (minz < 0) minz = 0;
if (maxz >= h) maxz = h-1;
float xhalf = halfExtents[0]*ics + 0.5f;
float zhalf = halfExtents[2]*ics + 0.5f;
for (int z = minz; z <= maxz; ++z)
{
for (int x = minx; x <= maxx; ++x)
{
float x2 = 2.0f*(float(x) - cx);
float z2 = 2.0f*(float(z) - cz);
float xrot = rotAux[1]*x2 + rotAux[0]*z2;
if (xrot > xhalf || xrot < -xhalf)
continue;
float zrot = rotAux[1]*z2 - rotAux[0]*x2;
if (zrot > zhalf || zrot < -zhalf)
continue;
const int y = layer.heights[x+z*w];
if (y < miny || y > maxy)
continue;
layer.areas[x+z*w] = areaId;
}
}
return DT_SUCCESS;
}
dtStatus dtBuildTileCacheLayer(dtTileCacheCompressor* comp,
dtTileCacheLayerHeader* header,
const unsigned char* heights,
const unsigned char* areas,
const unsigned char* cons,
unsigned char** outData, int* outDataSize)
{
const int headerSize = dtAlign4(sizeof(dtTileCacheLayerHeader));
const int gridSize = (int)header->width * (int)header->height;
const int maxDataSize = headerSize + comp->maxCompressedSize(gridSize*3);
unsigned char* data = (unsigned char*)dtAlloc(maxDataSize, DT_ALLOC_PERM);
if (!data)
return DT_FAILURE | DT_OUT_OF_MEMORY;
memset(data, 0, maxDataSize);
// Store header
memcpy(data, header, sizeof(dtTileCacheLayerHeader));
// Concatenate grid data for compression.
const int bufferSize = gridSize*3;
unsigned char* buffer = (unsigned char*)dtAlloc(bufferSize, DT_ALLOC_TEMP);
if (!buffer)
{
dtFree(data);
return DT_FAILURE | DT_OUT_OF_MEMORY;
}
memcpy(buffer, heights, gridSize);
memcpy(buffer+gridSize, areas, gridSize);
memcpy(buffer+gridSize*2, cons, gridSize);
// Compress
unsigned char* compressed = data + headerSize;
const int maxCompressedSize = maxDataSize - headerSize;
int compressedSize = 0;
dtStatus status = comp->compress(buffer, bufferSize, compressed, maxCompressedSize, &compressedSize);
if (dtStatusFailed(status))
{
dtFree(buffer);
dtFree(data);
return status;
}
*outData = data;
*outDataSize = headerSize + compressedSize;
dtFree(buffer);
return DT_SUCCESS;
}
void dtFreeTileCacheLayer(dtTileCacheAlloc* alloc, dtTileCacheLayer* layer)
{
dtAssert(alloc);
// The layer is allocated as one conitguous blob of data.
alloc->free(layer);
}
dtStatus dtDecompressTileCacheLayer(dtTileCacheAlloc* alloc, dtTileCacheCompressor* comp,
unsigned char* compressed, const int compressedSize,
dtTileCacheLayer** layerOut)
{
dtAssert(alloc);
dtAssert(comp);
if (!layerOut)
return DT_FAILURE | DT_INVALID_PARAM;
if (!compressed)
return DT_FAILURE | DT_INVALID_PARAM;
*layerOut = 0;
dtTileCacheLayerHeader* compressedHeader = (dtTileCacheLayerHeader*)compressed;
if (compressedHeader->magic != DT_TILECACHE_MAGIC)
return DT_FAILURE | DT_WRONG_MAGIC;
if (compressedHeader->version != DT_TILECACHE_VERSION)
return DT_FAILURE | DT_WRONG_VERSION;
const int layerSize = dtAlign4(sizeof(dtTileCacheLayer));
const int headerSize = dtAlign4(sizeof(dtTileCacheLayerHeader));
const int gridSize = (int)compressedHeader->width * (int)compressedHeader->height;
const int bufferSize = layerSize + headerSize + gridSize*4;
unsigned char* buffer = (unsigned char*)alloc->alloc(bufferSize);
if (!buffer)
return DT_FAILURE | DT_OUT_OF_MEMORY;
memset(buffer, 0, bufferSize);
dtTileCacheLayer* layer = (dtTileCacheLayer*)buffer;
dtTileCacheLayerHeader* header = (dtTileCacheLayerHeader*)(buffer + layerSize);
unsigned char* grids = buffer + layerSize + headerSize;
const int gridsSize = bufferSize - (layerSize + headerSize);
// Copy header
memcpy(header, compressedHeader, headerSize);
// Decompress grid.
int size = 0;
dtStatus status = comp->decompress(compressed+headerSize, compressedSize-headerSize,
grids, gridsSize, &size);
if (dtStatusFailed(status))
{
alloc->free(buffer);
return status;
}
layer->header = header;
layer->heights = grids;
layer->areas = grids + gridSize;
layer->cons = grids + gridSize*2;
layer->regs = grids + gridSize*3;
*layerOut = layer;
return DT_SUCCESS;
}
bool dtTileCacheHeaderSwapEndian(unsigned char* data, const int dataSize)
{
dtIgnoreUnused(dataSize);
dtTileCacheLayerHeader* header = (dtTileCacheLayerHeader*)data;
int swappedMagic = DT_TILECACHE_MAGIC;
int swappedVersion = DT_TILECACHE_VERSION;
dtSwapEndian(&swappedMagic);
dtSwapEndian(&swappedVersion);
if ((header->magic != DT_TILECACHE_MAGIC || header->version != DT_TILECACHE_VERSION) &&
(header->magic != swappedMagic || header->version != swappedVersion))
{
return false;
}
dtSwapEndian(&header->magic);
dtSwapEndian(&header->version);
dtSwapEndian(&header->tx);
dtSwapEndian(&header->ty);
dtSwapEndian(&header->tlayer);
dtSwapEndian(&header->bmin[0]);
dtSwapEndian(&header->bmin[1]);
dtSwapEndian(&header->bmin[2]);
dtSwapEndian(&header->bmax[0]);
dtSwapEndian(&header->bmax[1]);
dtSwapEndian(&header->bmax[2]);
dtSwapEndian(&header->hmin);
dtSwapEndian(&header->hmax);
// width, height, minx, maxx, miny, maxy are unsigned char, no need to swap.
return true;
}
recastnavigation-e75adf86f91eb3082220085e42dda62679f9a3ea/Docs/ 0000775 0000000 0000000 00000000000 14012455231 0022753 5 ustar 00root root 0000000 0000000 recastnavigation-e75adf86f91eb3082220085e42dda62679f9a3ea/Docs/Conceptual/ 0000775 0000000 0000000 00000000000 14012455231 0025050 5 ustar 00root root 0000000 0000000 recastnavigation-e75adf86f91eb3082220085e42dda62679f9a3ea/Docs/Conceptual/license_c.txt 0000664 0000000 0000000 00000001632 14012455231 0027537 0 ustar 00root root 0000000 0000000
/**
@page License License
Copyright (c) 2009-2011 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.
*/ recastnavigation-e75adf86f91eb3082220085e42dda62679f9a3ea/Docs/Conceptual/mainpage_c.txt 0000664 0000000 0000000 00000012564 14012455231 0027704 0 ustar 00root root 0000000 0000000 /// @mainpage Recast Navigation
///
/// @image html recast_intro.png
///
/// Recast
///
/// _Recast_ is a state of the art navigation mesh construction toolset for
/// games.
///
/// - It is automatic, which means that you can throw any level
/// geometry at it and you will get a robust mesh out.
/// - It is fast, which means swift turnaround times for level designers.
/// - It is open source, so it comes with full source and you can
/// customize it to your hearts content.
///
/// The latest version can be found on
/// GitHub.
///
/// The _Recast_ process starts with constructing a voxel mold from level
/// geometry and then casting a navigation mesh over it. The process
/// consists of three steps: building the voxel mold, partitioning the
/// mold into simple regions, and triangulating the regions as simple polygons.
///
/// -# The voxel mold is built from the input triangle mesh by
/// rasterizing the triangles into a multi-layer heightfield. Some
/// simple filters are then applied to the mold to prune out locations
/// where the character would not be able to move.
/// -# The walkable areas described by the mold are divided into simple
/// overlayed 2D regions. The resulting regions have only one
/// non-overlapping contour, which simplifies the final step of the
/// process tremendously.
/// -# The navigation polygons are generated from the regions by first
/// tracing the boundaries and then simplifying them. The resulting
/// polygons are finally converted to convex polygons which makes them
/// perfect for pathfinding and spatial reasoning about the level.
///
/// Detour
///
/// _Recast_ is accompanied by _Detour_, a path-finding and spatial reasoning
/// toolkit. You can use any navigation mesh with _Detour_, but of course
/// the data generated by _Recast_ fits perfectly.
///
/// _Detour_ offers a simple static navigation mesh that is suitable for
/// many simple cases, as well as a tiled navigation mesh that allows you
/// to add and remove pieces of the mesh. The tiled mesh allows you to
/// create systems where you stream new navigation data in and out as
/// the player progresses the level, or regenerate tiles as the
/// world changes.
///
/// Recast Demo
///
/// You can find a comprehensive demo project in the `RecastDemo` folder. It
/// is a kitchen sink demo containing all the major functionality of the library.
/// If you are new to _Recast_ & _Detour_, check out
///
/// Sample_SoloMesh.cpp to get started with building navmeshes and
///
/// NavMeshTesterTool.cpp to see how _Detour_ can be used to find paths.
///
/// Building RecastDemo
///
/// RecastDemo uses [premake5](http://premake.github.io/) to build platform specific projects.
/// Download it and make sure it's available on your path, or specify the path to it.
///
/// Linux
///
/// - Install SDl2 and its dependencies according to your distro's guidelines.
/// - run `premake5 gmake` from the `RecastDemo` folder.
/// - `cd Build/gmake` then `make`
/// - Run `RecastDemo\Bin\RecastDemo`
///
/// OSX
///
/// - Grab the latest SDL2 development library dmg from [here](https://www.libsdl.org/download-2.0.php) and place `SDL2.framework` in `/Library/Frameworks/`
/// - Navigate to the `RecastDemo` folder and run `premake5 xcode4`
/// - Open `Build/xcode4/recastnavigation.xcworkspace`
/// - Select the "RecastDemo" project in the left pane, go to the "BuildPhases" tab and expand "Link Binary With Libraries"
/// - Remove the existing entry for SDL2 (it should have a white box icon) and re-add it by hitting the plus, selecting "Add Other", and selecting `/Library/Frameworks/SDL2.framework`. It should now have a suitcase icon.
/// - Set the RecastDemo project as the target and build.
///
/// Windows
///
/// - Grab the latest SDL2 development library release from [here](https://www.libsdl.org/download-2.0.php) and unzip it `RecastDemo\Contrib`. Rename the SDL folder such that the path `RecastDemo\Contrib\SDL\lib\x86` is valid.
/// - Run `"premake5" vs2015` from the `RecastDemo` folder
/// - Open the solution, build, and run.
///
/// Integrating With Your Own Project
///
/// It is recommended to add the source directories `DebugUtils`, `Detour`,
/// `DetourCrowd`, `DetourTileCache`, and `Recast` into your own project
/// depending on which parts of the project you need. For example your
/// level building tool could include `DebugUtils`, `Recast`, and `Detour`,
/// and your game runtime could just include `Detour`.
///
/// Contributing
/// All development is centralized in github. Check out the Contributing Guidelines for more information.
///
/// Discuss
///
/// - Discuss _Recast_ and _Detour_:
///
/// Recast Navigation Group
/// - Development Blog:
/// Digesting Duck
///
/// License
///
/// _Recast Navigation_ is licensed under the ZLib license.
///
recastnavigation-e75adf86f91eb3082220085e42dda62679f9a3ea/Docs/DoxygenLayout.xml 0000664 0000000 0000000 00000013752 14012455231 0026320 0 ustar 00root root 0000000 0000000
recastnavigation-e75adf86f91eb3082220085e42dda62679f9a3ea/Docs/Extern/ 0000775 0000000 0000000 00000000000 14012455231 0024220 5 ustar 00root root 0000000 0000000 recastnavigation-e75adf86f91eb3082220085e42dda62679f9a3ea/Docs/Extern/Recast_api.txt 0000664 0000000 0000000 00000042755 14012455231 0027050 0 ustar 00root root 0000000 0000000 // This file contains the detail API documentation for
// elements defined in the Recast.h.
/**
@defgroup recast Recast
Members in this module are used to create mesh data that is then
used to create Detour navigation meshes.
The are a large number of possible ways to building navigation mesh data.
One of the simple piplines is as follows:
-# Prepare the input triangle mesh.
-# Build a #rcHeightfield.
-# Build a #rcCompactHeightfield.
-# Build a #rcContourSet.
-# Build a #rcPolyMesh.
-# Build a #rcPolyMeshDetail.
-# Use the rcPolyMesh and rcPolyMeshDetail to build a Detour navigation mesh
tile.
The general life-cycle of the main classes is as follows:
-# Allocate the object using the Recast allocator. (E.g. #rcAllocHeightfield)
-# Initialize or build the object. (E.g. #rcCreateHeightfield)
-# Update the object as needed. (E.g. #rcRasterizeTriangles)
-# Use the object as part of the pipeline.
-# Free the object using the Recast allocator. (E.g. #rcFreeHeightField)
@note This is a summary list of members. Use the index or search
feature to find minor members.
@struct rcConfig
@par
The is a convenience structure that represents an aggregation of parameters
used at different stages in the Recast build process. Some
values are derived during the build process. Not all parameters
are used for all build processes.
Units are usually in voxels (vx) or world units (wu). The units for voxels,
grid size, and cell size are all based on the values of #cs and #ch.
In this documentation, the term 'field' refers to heightfield and
contour data structures that define spacial information using an integer
grid.
The upper and lower limits for the various parameters often depend on
the platform's floating point accuraccy as well as interdependencies between
the values of multiple parameters. See the individual parameter
documentation for details.
@var rcConfig::borderSize
@par
This value represents the the closest the walkable area of the heightfield
should come to the xz-plane AABB of the field. It does not have any
impact on the borders around internal obstructions.
@var rcConfig::tileSize
@par
This field is only used when building multi-tile meshes.
@var rcConfig::cs
@par
@p cs and #ch define voxel/grid/cell size. So their values have significant
side effects on all parameters defined in voxel units.
The minimum value for this parameter depends on the platform's floating point
accuracy, with the practical minimum usually around 0.05.
@var rcConfig::ch
@par
#cs and @p ch define voxel/grid/cell size. So their values have significant
side effects on all parameters defined in voxel units.
The minimum value for this parameter depends on the platform's floating point
accuracy, with the practical minimum usually around 0.05.
@var rcConfig::walkableSlopeAngle
@par
The practical upper limit for this parameter is usually around 85 degrees.
@var rcConfig::walkableHeight
@par
Permits detection of overhangs in the source geometry that make the geometry
below un-walkable. The value is usually set to the maximum agent height.
@var rcConfig::walkableClimb
@par
Allows the mesh to flow over low lying obstructions such as curbs and
up/down stairways. The value is usually set to how far up/down an agent can step.
@var rcConfig::walkableRadius
@par
In general, this is the closest any part of the final mesh should get to an
obstruction in the source geometry. It is usually set to the maximum
agent radius.
While a value of zero is legal, it is not recommended and can result in
odd edge case issues.
@var rcConfig::maxEdgeLen
@par
Extra vertices will be inserted as needed to keep contour edges below this
length. A value of zero effectively disables this feature.
@var rcConfig::maxSimplificationError
@par
The effect of this parameter only applies to the xz-plane.
@var rcConfig::minRegionArea
@par
Any regions that are smaller than this area will be marked as unwalkable.
This is useful in removing useless regions that can sometimes form on
geometry such as table tops, box tops, etc.
@var rcConfig::maxVertsPerPoly
@par
If the mesh data is to be used to construct a Detour navigation mesh, then the upper limit
is limited to <= #DT_VERTS_PER_POLYGON.
@struct rcHeightfield
@par
The grid of a heightfield is layed out on the xz-plane based on the
value of #cs. Spans exist within the grid columns with the span
min/max values at increments of #ch from the base of the grid. The smallest
possible span size is (#cs width) * (#cs depth) * (#ch height). (Which is a single voxel.)
The standard process for buidling a heightfield is to allocate it using
#rcAllocHeightfield, initialize it using #rcCreateHeightfield, then
add spans using the various helper functions such as #rcRasterizeTriangle.
Building a heightfield is one of the first steps in creating a polygon mesh
from source geometry. After it is populated, it is used to build a
rcCompactHeightfield.
Example of iterating the spans in a heightfield:
@code
// Where hf is a reference to an heightfield object.
const float* orig = hf.bmin;
const float cs = hf.cs;
const float ch = hf.ch;
const int w = hf.width;
const int h = hf.height;
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
// Deriving the minimum corner of the grid location.
float fx = orig[0] + x*cs;
float fz = orig[2] + y*cs;
// The base span in the column. (May be null.)
const rcSpan* s = hf.spans[x + y*w];
while (s)
{
// Detriving the minium and maximum world position of the span.
float fymin = orig[1]+s->smin*ch;
float fymax = orig[1] + s->smax*ch;
// Do other things with the span before moving up the column.
s = s->next;
}
}
}
@endcode
@see rcAllocHeightfield, rcFreeHeightField, rcCreateHeightfield
@struct rcCompactCell
@par
See the rcCompactHeightfield documentation for an example of how compact cells
are used to iterate the heightfield.
Useful instances of this type can only by obtained from a #rcCompactHeightfield object.
@see rcCompactHeightfield
@struct rcCompactSpan
@par
The span represents open, unobstructed space within a compact heightfield column.
See the rcCompactHeightfield documentation for an example of iterating spans and searching
span connections.
Useful instances of this type can only by obtained from a #rcCompactHeightfield object.
@see rcCompactHeightfield
@struct rcCompactHeightfield
@par
For this type of heightfield, the spans represent the open (unobstructed)
space above the solid surfaces of a voxel field. It is usually created from
a #rcHeightfield object. Data is stored in a compact, efficient manner,
but the structure is not condusive to adding and removing spans.
The standard process for buidling a compact heightfield is to allocate it
using #rcAllocCompactHeightfield, build it using #rcBuildCompactHeightfield,
then run it through the various helper functions to generate neighbor
and region data.
Connected neighbor spans form non-overlapping surfaces. When neighbor
information is generated, spans will include data that can be used to
locate axis-neighbors. Axis-neighbors are connected
spans that are offset from the current cell column as follows:
Direction 0 = (-1, 0)
Direction 1 = (0, 1)
Direction 2 = (1, 0)
Direction 3 = (0, -1)
Example of iterating and inspecting spans, including connected neighbors:
@code
// Where chf is an instance of a rcCompactHeightfield.
const float cs = chf.cs;
const float ch = chf.ch;
for (int y = 0; y < chf.height; ++y)
{
for (int x = 0; x < chf.width; ++x)
{
// Deriving the minimum corner of the grid location.
const float fx = chf.bmin[0] + x*cs;
const float fz = chf.bmin[2] + y*cs;
// Get the cell for the grid location then iterate
// up the column.
const rcCompactCell& c = chf.cells[x+y*chf.width];
for (unsigned i = c.index, ni = c.index+c.count; i < ni; ++i)
{
const rcCompactSpan& s = chf.spans[i];
Deriving the minimum (floor) of the span.
const float fy = chf.bmin[1] + (s.y+1)*ch;
// Testing the area assignment of the span.
if (chf.areas[i] == RC_WALKABLE_AREA)
{
// The span is in the default 'walkable area'.
}
else if (chf.areas[i] == RC_NULL_AREA)
{
// The surface is not considered walkable.
// E.g. It was filtered out during the build processes.
}
else
{
// Do something. (Only applicable for custom build
// build processes.)
}
// Iterating the connected axis-neighbor spans.
for (int dir = 0; dir < 4; ++dir)
{
if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
{
// There is a neighbor in this direction.
const int nx = x + rcGetDirOffsetX(dir);
const int ny = y + rcGetDirOffsetY(dir);
const int ni = (int)chf.cells[nx+ny*w].index + rcGetCon(s, 0);
const rcCompactSpan& ns = chf.spans[ni];
// Do something with the neighbor span.
}
}
}
}
}
@endcode
@see rcAllocCompactHeightfield, rcFreeCompactHeightfield, rcBuildCompactHeightfield
@struct rcContour
@par
A contour only exists within the context of a #rcContourSet object.
While the height of the contour's border may vary, the contour will always
form a simple polygon when projected onto the xz-plane.
Example of converting vertices into world space:
@code
// Where cset is the rcContourSet object to which the contour belongs.
float worldX = cset.bmin[0] + vertX * cset.cs;
float worldY = cset.bmin[1] + vertY * cset.ch;
float worldZ = cset.bmin[2] + vertZ * cset.cs;
@endcode
@see rcContourSet
@var rcContour::verts
@par
The simplified contour is a version of the raw contour with all
'unnecessary' vertices removed. Whether a vertex is
considered unnecessary depends on the contour build process.
The data format is as follows: (x, y, z, r) * #nverts
A contour edge is formed by the current and next vertex. The r-value
represents region and connection information for the edge. For example:
@code
int r = verts[i*4+3];
int regionId = r & RC_CONTOUR_REG_MASK;
if (r & RC_BORDER_VERTEX)
{
// The edge represents a solid border.
}
if (r & RC_AREA_BORDER)
{
// The edge represents a transition between different areas.
}
@endcode
@var rcContour::rverts
@par
See #verts for information on element layout.
@struct rcContourSet
@par
All contours within the set share the minimum bounds and cell sizes of the set.
The standard process for building a contour set is to allocate it
using #rcAllocContourSet, then initialize it using #rcBuildContours.
@see rcAllocContourSet, rcFreeContourSet, rcBuildContours
@struct rcPolyMesh
@par
A mesh of potentially overlapping convex polygons of between three
and #nvp vertices. The mesh exists within the context of an axis-aligned
bounding box (AABB) with vertices laid out in an evenly spaced grid, based
on the values of #cs and #ch.
The standard process for building a contour set is to allocate it using
#rcAllocPolyMesh, the initialize it using #rcBuildPolyMesh
Example of iterating the polygons:
@code
// Where mesh is a reference to a rcPolyMesh object.
const int nvp = mesh.nvp;
const float cs = mesh.cs;
const float ch = mesh.ch;
const float* orig = mesh.bmin;
for (int i = 0; i < mesh.npolys; ++i)
{
const unsigned short* p = &mesh.polys[i*nvp*2];
// Iterate the vertices.
unsigned short vi[3]; // The vertex indices.
for (int j = 0; j < nvp; ++j)
{
if (p[j] == RC_MESH_NULL_IDX)
break; // End of vertices.
if (p[j + nvp] == RC_MESH_NULL_IDX)
{
// The edge beginning with this vertex is a solid border.
}
else
{
// The edge beginning with this vertex connects to
// polygon p[j + nvp].
}
// Convert to world space.
const unsigned short* v = &mesh.verts[p[j]*3];
const float x = orig[0] + v[0]*cs;
const float y = orig[1] + v[1]*ch;
const float z = orig[2] + v[2]*cs;
// Do something with the vertices.
}
}
@endcode
@see rcAllocPolyMesh, rcFreePolyMesh, rcBuildPolyMesh
@var rcPolyMesh::verts
@par
The values of #bmin ,#cs, and #ch are used to convert vertex coordinates
to world space as follows:
@code
float worldX = bmin[0] + verts[i*3+0] * cs
float worldY = bmin[1] + verts[i*3+1] * ch
float worldZ = bmin[2] + verts[i*3+2] * cs
@endcode
@var rcPolyMesh::polys
@par
Each entry is 2 * #nvp in length. The first half of the entry
contains the indices of the polygon. The first instance of #RC_MESH_NULL_IDX
indicates the end of the indices for the entry. The second half contains
indices to neighbor polygons. A value of #RC_MESH_NULL_IDX indicates no
connection for the associated edge. (I.e. The edge is a solid border.)
For example:
nvp = 6
For the entry: (1, 3, 4, 8, RC_MESH_NULL_IDX, RC_MESH_NULL_IDX,
18, RC_MESH_NULL_IDX , 21, RC_MESH_NULL_IDX, RC_MESH_NULL_IDX, RC_MESH_NULL_IDX)
(1, 3, 4, 8) defines a polygon with 4 vertices.
Edge 1->3 is shared with polygon 18.
Edge 4->8 is shared with polygon 21.
Edges 3->4 and 4->8 are border edges not shared with any other polygon.
@var rcPolyMesh::areas
@par
The standard build process assigns the value of #RC_WALKABLE_AREA to all walkable polygons.
This value can then be changed to meet user requirements.
@struct rcPolyMeshDetail
@par
The detail mesh is made up of triangle sub-meshes that provide extra
height detail for each polygon in its assoicated polygon mesh.
The standard process for building a detail mesh is to allocate it
using #rcAllocPolyMeshDetail, then build it using #rcBuildPolyMeshDetail.
See the individual field definitions for details realted to the structure
the mesh.
@see rcAllocPolyMeshDetail, rcFreePolyMeshDetail, rcBuildPolyMeshDetail, rcPolyMesh
@var rcPolyMeshDetail::meshes
@par
[(baseVertIndex, vertCount, baseTriIndex, triCount) * #nmeshes]
Maximum number of vertices per sub-mesh: 127
Maximum number of triangles per sub-mesh: 255
The sub-meshes are stored in the same order as the polygons from the
rcPolyMesh they represent. E.g. rcPolyMeshDetail sub-mesh 5 is associated
with #rcPolyMesh polygon 5.
Example of iterating the triangles in a sub-mesh.
@code
// Where dmesh is a reference to a rcPolyMeshDetail object.
// Iterate the sub-meshes. (One for each source polygon.)
for (int i = 0; i < dmesh.nmeshes; ++i)
{
const unsigned int* meshDef = &dmesh.meshes[i*4];
const unsigned int baseVerts = meshDef[0];
const unsigned int baseTri = meshDef[2];
const int ntris = (int)meshDef[3];
const float* verts = &dmesh.verts[baseVerts*3];
const unsigned char* tris = &dmesh.tris[baseTri*4];
// Iterate the sub-mesh's triangles.
for (int j = 0; j < ntris; ++j)
{
const float x = verts[tris[j*4+0]*3];
const float y = verts[tris[j*4+1]*3];
const float z = verts[tris[j*4+2]*3];
// Do something with the vertex.
}
}
@endcode
@var rcPolyMeshDetail::verts
@par
[(x, y, z) * #nverts]
The vertices are grouped by sub-mesh and will contain duplicates since
each sub-mesh is independently defined.
The first group of vertices for each sub-mesh are in the same order as
the vertices for the sub-mesh's associated PolyMesh polygon. These
vertices are followed by any additional detail vertices. So it the
associated polygon has 5 vertices, the sub-mesh will have a minimum
of 5 vertices and the first 5 vertices will be equivalent to the 5
polygon vertices.
@var rcPolyMeshDetail::tris
@par
[(vertIndexA, vertIndexB, vertIndexC, flags) * #ntris]
The triangles are grouped by sub-mesh.
Vertex Indices
The vertex indices in the triangle array are local to the sub-mesh, not global.
To translate into an global index in the vertices array, the values must be
offset by the sub-mesh's base vertex index.
Example: If the baseVertexIndex for the sub-mesh is 5 and the triangle entry
is (4, 8, 7, 0), then the actual indices for the vertices are (4 + 5, 8 + 5, 7 + 5).
@b Flags
The flags entry indicates which edges are internal and which are external to
the sub-mesh. Internal edges connect to other triangles within the same sub-mesh.
External edges represent portals to other sub-meshes or the null region.
Each flag is stored in a 2-bit position. Where position 0 is the lowest 2-bits
and position 4 is the highest 2-bits:
Position 0: Edge AB (>> 0)
Position 1: Edge BC (>> 2)
Position 2: Edge CA (>> 4)
Position 4: Unused
Testing can be performed as follows:
@code
if (((flags >> 2) & 0x3) != 0)
{
// Edge BC is an external edge.
}
@endcode
@fn void rcSetCon(rcCompactSpan &s, int dir, int i)
@par
This function is used by the build process. It is rarely of use to end users.
@see #rcCompactHeightfield, #rcCompactSpan
@fn int rcGetCon(const rcCompactSpan &s, int dir)
@par
Can be used to locate neighbor spans in a compact heightfield. See the
#rcCompactHeightfield documentation for details on its use.
@see #rcCompactHeightfield, #rcCompactSpan
@fn int rcGetDirOffsetX(int dir)
@par
The value of @p dir will be automatically wrapped. So a value of 6 will be interpreted as 2.
See the #rcCompactHeightfield documentation for usage details.
@fn int rcGetDirOffsetY(int dir)
@par
The value of @p dir will be automatically wrapped. So a value of 6 will be interpreted as 2.
See the #rcCompactHeightfield documentation for usage details.
*/
recastnavigation-e75adf86f91eb3082220085e42dda62679f9a3ea/Docs/Images/ 0000775 0000000 0000000 00000000000 14012455231 0024160 5 ustar 00root root 0000000 0000000 recastnavigation-e75adf86f91eb3082220085e42dda62679f9a3ea/Docs/Images/recast_intro.png 0000664 0000000 0000000 00001343160 14012455231 0027372 0 ustar 00root root 0000000 0000000 ‰PNG
IHDR £…ü pHYs šœ
iCCPPhotoshop ICC profile xÚYgPTݲí&0Ãs$£ä,9GÉIaÈ99+** *Q"$U1b@QDÅHPTDy?ß½ï¾÷ãU½þqªkíîÕ«{שSûl ¡F\\Ê“Èt¶2¥{zyÓIÀ¬ÀB ÇLˆ3qr²‡ÿÕÇ ¸§Èˆ‹‹‚ÿ›q' N ”
€´ `íqÌD B H¥$Æ%J €‡ééå
@¨ žÐ_~; ðüò‡€‡éêl@˜ ³2ÌP Ú; '†&°±¹b‚Âc xè DÃÀ0F€? ¬‰ŽŽ
* ¹€á ý7΀¿œFè_ÿW/ @6Oˆ‹b¤Áÿ·EG%ý©! ¬ ‘.v À€¤2,\ @ )
¶±ÿK4uþw…'Ú¸ 2–díöÛéf " Èrd¬3 ° 1Ž À€J&˜yÿâDµÒÃ\=~ÇØ›[ êÉŒuþ–ìòOO3søÁ°u 6 4ŸÁø¥Ž²r€U hc\¢“ëïZ7c¢~÷‚¾aZ:ÿö—‚,\þÔJsµþÅq$2]qb"!á–6¿4`*aLë?¸q\”“ý¯\Ì•™äì R XHpŒÛoN,?ˆan÷k&Ø~°0! ~ ìÁÌ?é1@‡@ˆ…(ˆ&ýÏ
á%áá9a”0Ixð7ÚìO„CÄþÅÿwtx1 ªáB¸!®‡Ûㆸ1nˆ«á:¸îŸµ›ïÚÞýUõKk(ƒâoÄô·úäUïžËüo93þS“%¼ &„þ‰P9©2£²ü'ÿŸŽ‰Ds¢5Ñ’(íÀÎaƒXv
ëÂÚ€Žõ`íØ0vkûù1~O… Á vÁL†˜ÿQQÒ߈ß(››&8C0Ä@$DAøß
îð˜þ,I@‡ ˆ…»¿=þ™´®†k⦸nˆëçÃ…@×ÀupÜ×Ã5qÝÙÅïFB€LH†`H€Hx LˆNNM 0‹Kc†‡†%ÒMâ⢂×Ðmb•ÖÐÕTTÕÁÓË›þëÕ^p ¾[ÿ`g ^ ÊÒ?Xl@ó }Ë?˜ôC þ9€c§“˜É¿0 € `19P5Ð=0°Gp/ØÑÀ„È„ÍPû
jà0‡SpÚ úà
\‡Û0
`¦á-ÌÂ"|G„„ÐnDG¤‘Õˆ¢ƒ"ˆ=âŒx!þH(ƒ$!™È¤ )EªCÈ äÒô!×;Èä2ƒÌ#K(†²¢<¨(*ƒ*£:¨ j‡º¢ÐP4MG·¢EhZ‡6¢hzE'Ñ·èg0*ƇI`Š˜f†9bÞXÆÄ²±|¬«Ãš°Nl»‡Mbï°o8çÆé¸"®‡[ãnx gã…x~oÅ/á÷ðgø,þ“@#ˆVÖlž„PB
!PF8J8O¸L%L‰D"Q–¨M´&z#ˆÄBâb3±—x‡8EüL"‘I«I$Gƒ”HÊ#U’I=¤»¤iÒW2•,NV#[’½É1ä\r¹ÜM¾K~EþÎÂÁ"Ͳ–Å‘%ˆ%¥˜¥ž¥“åË4Ëw
'E–b@q¥DP6S*(M”˔ǔ*•ºŠªK]O
§n¢VPOS¯RŸQ¿±r±*°š±ú²&±±cíe}Àº@£ÑdhÆ4oZ"ˆv‚6@{BûÊÆÍ¦ÄfÃÄ–ÃVÍÖÊv—í;»4» ûFötö2ösì·Øßq°pÈp˜q08²9ª9:8îs|æäæTåtäŒæ,älà¼Æùš‹Ä%ÃeÁĵ•ë0× ×7Æ-ÉmÆÈ½…»žû2÷4‘G–dž'‚§€çÏMžY^.^
^wÞTÞjÞ‹¼“|ŸŸ
__1ßY¾1¾%~Q~þ`þüMüwù¿ä4Œ
, Ò-#w¶ NáB
Bë…R„
]z'Ì#¬'(œ/|Vø¡*¢ â,’!rXdX䳨˜¨•hœh¥è€è;1>1c±±½bÝb3âÜâ†âáâ{Å{ÄßÐyé&ô(zý}VBDÂZ"IâÄM‰ï«dW¹Ê]Õ¼jB’"©#"¹W²_rVJ\jT¦ÔI©‡Ò,Ò:ÒaÒåÒƒÒ_ddeŸi_Mß<ß±
²R7\Û(´1jãE?v?†ß9‚¿‡ƒÿ2ÑQÇø`°?`6Ð,°<ðmqÐÞ ™`ƒàÒàW!!¥!¯C
B÷„΄…•…½7¯
Ÿ‹°Ž¨‰øéy,r%Ê#ª9šíÝÃs)V,65öNÜ긼¸Éøµñûâg™vÌ£ H†„öDžÄ¸Äá$¹¤mIÏ’
“«“¿¦¸§œKåLINSHÛ™ö*Ý2ýHž˜ÑŸ)‘¹9óY–IÖ¡l$; »?G2gkÎô&«MÇ7S6Gn¾‘«’[šûi‹Ç–΢[7mÚfµíd[3ïþv½í5;ðá;nîTßY¹óg~PþPJAYÁra`áÐ.Õ]»VŠBŠnk,!–Ä”Œí6Ú}¼”³4½tjϺ={é{ó÷~Úç·ïZ™FYM9¥<©|²Â¾¢½Rª²¤r¹*¬j´Ú´ºy¿Èþû¿:p÷ ñÁ¦Ñš‚š¥ÚðÚñCV‡ZëdêÊ'~Yï^?xDçȉ£BGŽþ8slò¸óñK'´Oœhi(>‰žL:9ÓèÛxû”ù©ö&ŦCÍ|ͧátÒé7güÏŒµ;ÛNç\S‹tËþóÜçó[‘Ö´ÖÙ¶°¶Év¯ö;¶ýzç/(]8Ö%ÑU}‘÷bq7¥{k÷JOzÏçÞ¸Þw}¡}Sý~ý<F.¿tó²Ýå«W,¯šö\5¸ÚumíµŽ!¡¶ëZ×[‡5‡Ïßмqþ¦ÖÍÖ[Ú·ÚoëÞî¼£§û®Ñݾ{æ÷®ŒØŒ\u½3æ66~ß÷þäxÐøëQæ&?üþhÓcÂãü މ²'"OêžÊ?mžÔš¼øÌüÙðs—禧޾Hx±<½õ%íeÙ+ñW'^«½îš±œ¹ýÆçÍôÛ¸·ßßå½ç|¿ÿƒÜ‡–Ƈg=g§ç˜s+ó…‚Ç>i|êÿìôùÉbôâ÷/ù_¿ÿ¦ómpÉcéÕ÷”eÒrÅù?í~>^‰^Y‰c0 € 0€æÀ}€ÂöëlôÛ0 wd'Yàfe¢‰,Ì"H¡S
Y]iÉl•ìÏ95¸²¹ÇxUùòø_ Ú-z’ļd”Ô'™9ùêÕ
kÚ”L•惘¨-hlÕÒnÐÕ];¬ïa0ibüÖ4ÚlÖ"Úò¥µŸÍ=;+û Ç"§yg}—,×.·¯êžQ^uÞc¾ä
:CüJý/2f¹‚TƒC"C·„U„ŸŒèŠŠ~3»Obr'ˆ$
'ñ$S’—SÞ¤Ž¤µ§Wf$g:fÉd}Ͼ•S¿)u³O®á–U[I[?lËëÝÞ¸£jg~~v³0bW@‘GñÚÁ’o»•^ÜS³w˾ð2Çrí
z%¥òSÕÓêáý:žª©©-<”\ç{ظ~ÕôÈó£½Çjçœjp=iÓh|J§IµYþ´Ä³¬gž{×rÿ|Ok}[^{d‡S§özK×§‹Ïºo÷ôõ¶ôï?8°ïҮ˛®0õ¯
\]¼62tþzùpÆ
ÿ›Ö·Toß!ÞY¸ûô^ÏHÍhæ˜Ç}åqlü‡^x]œ:!91ô$é©ÜÓ“µÏ6<~>>UöÂešcúÆË¯Ì_¼î˜a¼AßT¿Õ{;õ®ìýº¤=fé³}sNscó¶óíkŽ~’øTóYâsã¢Éâ³/Õ_#¿ù.e~¿ÿ£~e ÜTñ¼°˜Hò'{²¸Pl©¶¬ëil…ì}_¹T¸Ãxó¾âWHæ‰í¥'ILHZH—‘”-–[Rˆ\ýXÑ^©CEQµJM#GsVÛOgp²^¥þOà £Ë&’¦ÙfãÊ–yVã6
¶©vMöTG§€õ;›\n¹~t§y¬ö´ôò÷Nó)ñßкñŠß¨ÿãcÀ· &†PCÙÂ8¹"x"y¢¸¢Ùc(±hì׸·ñ˜ƒ gË“2’}RtSùRçÓn§ŸÎ(ÉŒÍrÌVÍáÎYÜôhs_î±-Å[S·äÙn—ßïx¶³'AF¡×.Ý"Á¢ïÅ%=»ëJ·í‰ÜëµÏºL³\¢‚R1_9^ÕU]³?÷@ÀA³©ZBíÌ¡±º«‡;ë›Ô:¶ûøö
±'ýN4É7s5/Ÿ~yæÎÙîsM-‡Î—µ·´tìê,½PÙUw±¡»¼gKol߆þuF—4/+^‘”¿ªxMcÈðºí°Ë
Ç›–·ôo«Þ‘¹+|mY}16zÿòøùGî~”ôØmBó ï“ÏOG&Ï=+}?åôByš}úÃË[¯š_ÍD¿±}+óöç»{ïë?0?Ìâ³WçvÎ[/z>mø4û9ùóÏŽ_Ô¾L|=ø¹äõÝc9ìGáÏžßû/‰œF½0Nì,îC :ˆL’&'_e)¡Q-YUi’l"ì\lœ\âÜê<ëx™|ùï
"BÂ!"¥¢}bïéB¦«"%K¥Ú¥ŸÊ‚œ„¼‘‚Ïjæš-Šû”Ž+w¨©>R{¯þC“ª%¬-§£k±ÖQo½¾“Á:Ck##c5 SVÓOfÌ;-*-SܬUl(6¶Ív¹öîëˆÏ{œ®Ïvöw1u•tÃÝ^¸xÔxfzyz«ûÐ|^ùön¨Øëgé/â?˸P¤L
©M3矊h‰Ì‹òŒ–^޹[gÇ÷23´æ’“…“ï¦ä§§.¦5¦32ø3neîÈ2Íú‘}!'m“Í]¹Ù[L¶b[¶mÊÓÍ[Ø~jGØNéSù‡‚
ågwum+v.¡—¼ßÝYºmã^þ½Oö.-—/SÑP^%WõªúÄþ‚±k4jykÝ®k8¼¹ÞõˆÌ‘Å£—Ž•9áÞ°þ¤}£å)£&f…Ó"g(gÏ>97Ðräü¶ÖضÔö¢Žúή#]ºÉ=’½†}Þý){.¾<|åíUÁk¶C9×[‡çoªßJ½=p—÷^ôȵ1…ûE㋃½›(|ª=ùáyÛ‹Š—{_·¼ùñ>oÖa!fñÓwÁ•€_ÿÈ ˆZ ¥ n‹ .›
® È.ðS œh ®º€>³to: ‡¬þ~?@à ~ ƒ