mirror of
https://github.com/Relintai/pandemonium_engine.git
synced 2024-12-28 22:57:15 +01:00
708d7d8700
In some cases Godot can generate input parameters to Recast that cause it to crash. Specifically when baking NavigationMeshes for input meshes that have axis extents less than half the NavigationMesh CellSize.
This has been fixed upstream in Recast (in 3901c5854c
). Updating Godot's Recast integration fixes this crash issue in Godot as well.
(cherry picked from commit 36de150c74e8c86e204ce64dc5a2865b6aee79a7)
581 lines
14 KiB
C++
581 lines
14 KiB
C++
//
|
|
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
|
|
//
|
|
// This software is provided 'as-is', without any express or implied
|
|
// warranty. In no event will the authors be held liable for any damages
|
|
// arising from the use of this software.
|
|
// Permission is granted to anyone to use this software for any purpose,
|
|
// including commercial applications, and to alter it and redistribute it
|
|
// freely, subject to the following restrictions:
|
|
// 1. The origin of this software must not be misrepresented; you must not
|
|
// claim that you wrote the original software. If you use this software
|
|
// in a product, an acknowledgment in the product documentation would be
|
|
// appreciated but is not required.
|
|
// 2. Altered source versions must be plainly marked as such, and must not be
|
|
// misrepresented as being the original software.
|
|
// 3. This notice may not be removed or altered from any source distribution.
|
|
//
|
|
|
|
#include <float.h>
|
|
#define _USE_MATH_DEFINES
|
|
#include <math.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include "Recast.h"
|
|
#include "RecastAlloc.h"
|
|
#include "RecastAssert.h"
|
|
|
|
namespace
|
|
{
|
|
/// Allocates and constructs an object of the given type, returning a pointer.
|
|
/// TODO: Support constructor args.
|
|
/// @param[in] hint Hint to the allocator.
|
|
template <typename T>
|
|
T* rcNew(rcAllocHint hint) {
|
|
T* ptr = (T*)rcAlloc(sizeof(T), hint);
|
|
::new(rcNewTag(), (void*)ptr) T();
|
|
return ptr;
|
|
}
|
|
|
|
/// Destroys and frees an object allocated with rcNew.
|
|
/// @param[in] ptr The object pointer to delete.
|
|
template <typename T>
|
|
void rcDelete(T* ptr) {
|
|
if (ptr) {
|
|
ptr->~T();
|
|
rcFree((void*)ptr);
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
|
|
float rcSqrt(float x)
|
|
{
|
|
return sqrtf(x);
|
|
}
|
|
|
|
/// @class rcContext
|
|
/// @par
|
|
///
|
|
/// This class does not provide logging or timer functionality on its
|
|
/// own. Both must be provided by a concrete implementation
|
|
/// by overriding the protected member functions. Also, this class does not
|
|
/// provide an interface for extracting log messages. (Only adding them.)
|
|
/// So concrete implementations must provide one.
|
|
///
|
|
/// If no logging or timers are required, just pass an instance of this
|
|
/// class through the Recast build process.
|
|
///
|
|
|
|
/// @par
|
|
///
|
|
/// Example:
|
|
/// @code
|
|
/// // Where ctx is an instance of rcContext and filepath is a char array.
|
|
/// ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Could not load '%s'", filepath);
|
|
/// @endcode
|
|
void rcContext::log(const rcLogCategory category, const char* format, ...)
|
|
{
|
|
if (!m_logEnabled)
|
|
return;
|
|
static const int MSG_SIZE = 512;
|
|
char msg[MSG_SIZE];
|
|
va_list ap;
|
|
va_start(ap, format);
|
|
int len = vsnprintf(msg, MSG_SIZE, format, ap);
|
|
if (len >= MSG_SIZE)
|
|
{
|
|
len = MSG_SIZE-1;
|
|
msg[MSG_SIZE-1] = '\0';
|
|
}
|
|
va_end(ap);
|
|
doLog(category, msg, len);
|
|
}
|
|
|
|
void rcContext::doResetLog()
|
|
{
|
|
// Defined out of line to fix the weak v-tables warning
|
|
}
|
|
|
|
rcHeightfield* rcAllocHeightfield()
|
|
{
|
|
return rcNew<rcHeightfield>(RC_ALLOC_PERM);
|
|
}
|
|
rcHeightfield::rcHeightfield()
|
|
: width()
|
|
, height()
|
|
, bmin()
|
|
, bmax()
|
|
, cs()
|
|
, ch()
|
|
, spans()
|
|
, pools()
|
|
, freelist()
|
|
{
|
|
}
|
|
|
|
rcHeightfield::~rcHeightfield()
|
|
{
|
|
// Delete span array.
|
|
rcFree(spans);
|
|
// Delete span pools.
|
|
while (pools)
|
|
{
|
|
rcSpanPool* next = pools->next;
|
|
rcFree(pools);
|
|
pools = next;
|
|
}
|
|
}
|
|
|
|
void rcFreeHeightField(rcHeightfield* hf)
|
|
{
|
|
rcDelete(hf);
|
|
}
|
|
|
|
rcCompactHeightfield* rcAllocCompactHeightfield()
|
|
{
|
|
return rcNew<rcCompactHeightfield>(RC_ALLOC_PERM);
|
|
}
|
|
|
|
void rcFreeCompactHeightfield(rcCompactHeightfield* chf)
|
|
{
|
|
rcDelete(chf);
|
|
}
|
|
|
|
rcCompactHeightfield::rcCompactHeightfield()
|
|
: width(),
|
|
height(),
|
|
spanCount(),
|
|
walkableHeight(),
|
|
walkableClimb(),
|
|
borderSize(),
|
|
maxDistance(),
|
|
maxRegions(),
|
|
bmin(),
|
|
bmax(),
|
|
cs(),
|
|
ch(),
|
|
cells(),
|
|
spans(),
|
|
dist(),
|
|
areas()
|
|
{
|
|
}
|
|
rcCompactHeightfield::~rcCompactHeightfield()
|
|
{
|
|
rcFree(cells);
|
|
rcFree(spans);
|
|
rcFree(dist);
|
|
rcFree(areas);
|
|
}
|
|
|
|
rcHeightfieldLayerSet* rcAllocHeightfieldLayerSet()
|
|
{
|
|
return rcNew<rcHeightfieldLayerSet>(RC_ALLOC_PERM);
|
|
}
|
|
void rcFreeHeightfieldLayerSet(rcHeightfieldLayerSet* lset)
|
|
{
|
|
rcDelete(lset);
|
|
}
|
|
|
|
rcHeightfieldLayerSet::rcHeightfieldLayerSet()
|
|
: layers(), nlayers() {}
|
|
rcHeightfieldLayerSet::~rcHeightfieldLayerSet()
|
|
{
|
|
for (int i = 0; i < nlayers; ++i)
|
|
{
|
|
rcFree(layers[i].heights);
|
|
rcFree(layers[i].areas);
|
|
rcFree(layers[i].cons);
|
|
}
|
|
rcFree(layers);
|
|
}
|
|
|
|
|
|
rcContourSet* rcAllocContourSet()
|
|
{
|
|
return rcNew<rcContourSet>(RC_ALLOC_PERM);
|
|
}
|
|
void rcFreeContourSet(rcContourSet* cset)
|
|
{
|
|
rcDelete(cset);
|
|
}
|
|
|
|
rcContourSet::rcContourSet()
|
|
: conts(),
|
|
nconts(),
|
|
bmin(),
|
|
bmax(),
|
|
cs(),
|
|
ch(),
|
|
width(),
|
|
height(),
|
|
borderSize(),
|
|
maxError() {}
|
|
rcContourSet::~rcContourSet()
|
|
{
|
|
for (int i = 0; i < nconts; ++i)
|
|
{
|
|
rcFree(conts[i].verts);
|
|
rcFree(conts[i].rverts);
|
|
}
|
|
rcFree(conts);
|
|
}
|
|
|
|
|
|
rcPolyMesh* rcAllocPolyMesh()
|
|
{
|
|
return rcNew<rcPolyMesh>(RC_ALLOC_PERM);
|
|
}
|
|
void rcFreePolyMesh(rcPolyMesh* pmesh)
|
|
{
|
|
rcDelete(pmesh);
|
|
}
|
|
|
|
rcPolyMesh::rcPolyMesh()
|
|
: verts(),
|
|
polys(),
|
|
regs(),
|
|
flags(),
|
|
areas(),
|
|
nverts(),
|
|
npolys(),
|
|
maxpolys(),
|
|
nvp(),
|
|
bmin(),
|
|
bmax(),
|
|
cs(),
|
|
ch(),
|
|
borderSize(),
|
|
maxEdgeError() {}
|
|
|
|
rcPolyMesh::~rcPolyMesh()
|
|
{
|
|
rcFree(verts);
|
|
rcFree(polys);
|
|
rcFree(regs);
|
|
rcFree(flags);
|
|
rcFree(areas);
|
|
}
|
|
|
|
rcPolyMeshDetail* rcAllocPolyMeshDetail()
|
|
{
|
|
rcPolyMeshDetail* dmesh = (rcPolyMeshDetail*)rcAlloc(sizeof(rcPolyMeshDetail), RC_ALLOC_PERM);
|
|
memset(dmesh, 0, sizeof(rcPolyMeshDetail));
|
|
return dmesh;
|
|
}
|
|
|
|
void rcFreePolyMeshDetail(rcPolyMeshDetail* dmesh)
|
|
{
|
|
if (!dmesh) return;
|
|
rcFree(dmesh->meshes);
|
|
rcFree(dmesh->verts);
|
|
rcFree(dmesh->tris);
|
|
rcFree(dmesh);
|
|
}
|
|
|
|
void rcCalcBounds(const float* verts, int nv, float* bmin, float* bmax)
|
|
{
|
|
// Calculate bounding box.
|
|
rcVcopy(bmin, verts);
|
|
rcVcopy(bmax, verts);
|
|
for (int i = 1; i < nv; ++i)
|
|
{
|
|
const float* v = &verts[i*3];
|
|
rcVmin(bmin, v);
|
|
rcVmax(bmax, v);
|
|
}
|
|
}
|
|
|
|
void rcCalcGridSize(const float* bmin, const float* bmax, float cs, int* w, int* h)
|
|
{
|
|
*w = (int)((bmax[0] - bmin[0])/cs+0.5f);
|
|
*h = (int)((bmax[2] - bmin[2])/cs+0.5f);
|
|
}
|
|
|
|
/// @par
|
|
///
|
|
/// See the #rcConfig documentation for more information on the configuration parameters.
|
|
///
|
|
/// @see rcAllocHeightfield, rcHeightfield
|
|
bool rcCreateHeightfield(rcContext* ctx, rcHeightfield& hf, int width, int height,
|
|
const float* bmin, const float* bmax,
|
|
float cs, float ch)
|
|
{
|
|
rcIgnoreUnused(ctx);
|
|
|
|
hf.width = width;
|
|
hf.height = height;
|
|
rcVcopy(hf.bmin, bmin);
|
|
rcVcopy(hf.bmax, bmax);
|
|
hf.cs = cs;
|
|
hf.ch = ch;
|
|
hf.spans = (rcSpan**)rcAlloc(sizeof(rcSpan*)*hf.width*hf.height, RC_ALLOC_PERM);
|
|
if (!hf.spans)
|
|
return false;
|
|
memset(hf.spans, 0, sizeof(rcSpan*)*hf.width*hf.height);
|
|
return true;
|
|
}
|
|
|
|
static void calcTriNormal(const float* v0, const float* v1, const float* v2, float* norm)
|
|
{
|
|
float e0[3], e1[3];
|
|
rcVsub(e0, v1, v0);
|
|
rcVsub(e1, v2, v0);
|
|
rcVcross(norm, e0, e1);
|
|
rcVnormalize(norm);
|
|
}
|
|
|
|
/// @par
|
|
///
|
|
/// Only sets the area id's for the walkable triangles. Does not alter the
|
|
/// area id's for unwalkable triangles.
|
|
///
|
|
/// See the #rcConfig documentation for more information on the configuration parameters.
|
|
///
|
|
/// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles
|
|
void rcMarkWalkableTriangles(rcContext* ctx, const float walkableSlopeAngle,
|
|
const float* verts, int nv,
|
|
const int* tris, int nt,
|
|
unsigned char* areas)
|
|
{
|
|
rcIgnoreUnused(ctx);
|
|
rcIgnoreUnused(nv);
|
|
|
|
const float walkableThr = cosf(walkableSlopeAngle/180.0f*RC_PI);
|
|
|
|
float norm[3];
|
|
|
|
for (int i = 0; i < nt; ++i)
|
|
{
|
|
const int* tri = &tris[i*3];
|
|
calcTriNormal(&verts[tri[0]*3], &verts[tri[1]*3], &verts[tri[2]*3], norm);
|
|
// Check if the face is walkable.
|
|
if (norm[1] > walkableThr)
|
|
areas[i] = RC_WALKABLE_AREA;
|
|
}
|
|
}
|
|
|
|
/// @par
|
|
///
|
|
/// Only sets the area id's for the unwalkable triangles. Does not alter the
|
|
/// area id's for walkable triangles.
|
|
///
|
|
/// See the #rcConfig documentation for more information on the configuration parameters.
|
|
///
|
|
/// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles
|
|
void rcClearUnwalkableTriangles(rcContext* ctx, const float walkableSlopeAngle,
|
|
const float* verts, int /*nv*/,
|
|
const int* tris, int nt,
|
|
unsigned char* areas)
|
|
{
|
|
rcIgnoreUnused(ctx);
|
|
|
|
const float walkableThr = cosf(walkableSlopeAngle/180.0f*RC_PI);
|
|
|
|
float norm[3];
|
|
|
|
for (int i = 0; i < nt; ++i)
|
|
{
|
|
const int* tri = &tris[i*3];
|
|
calcTriNormal(&verts[tri[0]*3], &verts[tri[1]*3], &verts[tri[2]*3], norm);
|
|
// Check if the face is walkable.
|
|
if (norm[1] <= walkableThr)
|
|
areas[i] = RC_NULL_AREA;
|
|
}
|
|
}
|
|
|
|
int rcGetHeightFieldSpanCount(rcContext* ctx, rcHeightfield& hf)
|
|
{
|
|
rcIgnoreUnused(ctx);
|
|
|
|
const int w = hf.width;
|
|
const int h = hf.height;
|
|
int spanCount = 0;
|
|
for (int y = 0; y < h; ++y)
|
|
{
|
|
for (int x = 0; x < w; ++x)
|
|
{
|
|
for (rcSpan* s = hf.spans[x + y*w]; s; s = s->next)
|
|
{
|
|
if (s->area != RC_NULL_AREA)
|
|
spanCount++;
|
|
}
|
|
}
|
|
}
|
|
return spanCount;
|
|
}
|
|
|
|
/// @par
|
|
///
|
|
/// This is just the beginning of the process of fully building a compact heightfield.
|
|
/// Various filters may be applied, then the distance field and regions built.
|
|
/// E.g: #rcBuildDistanceField and #rcBuildRegions
|
|
///
|
|
/// See the #rcConfig documentation for more information on the configuration parameters.
|
|
///
|
|
/// @see rcAllocCompactHeightfield, rcHeightfield, rcCompactHeightfield, rcConfig
|
|
bool rcBuildCompactHeightfield(rcContext* ctx, const int walkableHeight, const int walkableClimb,
|
|
rcHeightfield& hf, rcCompactHeightfield& chf)
|
|
{
|
|
rcAssert(ctx);
|
|
|
|
rcScopedTimer timer(ctx, RC_TIMER_BUILD_COMPACTHEIGHTFIELD);
|
|
|
|
const int w = hf.width;
|
|
const int h = hf.height;
|
|
const int spanCount = rcGetHeightFieldSpanCount(ctx, hf);
|
|
|
|
// Fill in header.
|
|
chf.width = w;
|
|
chf.height = h;
|
|
chf.spanCount = spanCount;
|
|
chf.walkableHeight = walkableHeight;
|
|
chf.walkableClimb = walkableClimb;
|
|
chf.maxRegions = 0;
|
|
rcVcopy(chf.bmin, hf.bmin);
|
|
rcVcopy(chf.bmax, hf.bmax);
|
|
chf.bmax[1] += walkableHeight*hf.ch;
|
|
chf.cs = hf.cs;
|
|
chf.ch = hf.ch;
|
|
chf.cells = (rcCompactCell*)rcAlloc(sizeof(rcCompactCell)*w*h, RC_ALLOC_PERM);
|
|
if (!chf.cells)
|
|
{
|
|
ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.cells' (%d)", w*h);
|
|
return false;
|
|
}
|
|
memset(chf.cells, 0, sizeof(rcCompactCell)*w*h);
|
|
chf.spans = (rcCompactSpan*)rcAlloc(sizeof(rcCompactSpan)*spanCount, RC_ALLOC_PERM);
|
|
if (!chf.spans)
|
|
{
|
|
ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.spans' (%d)", spanCount);
|
|
return false;
|
|
}
|
|
memset(chf.spans, 0, sizeof(rcCompactSpan)*spanCount);
|
|
chf.areas = (unsigned char*)rcAlloc(sizeof(unsigned char)*spanCount, RC_ALLOC_PERM);
|
|
if (!chf.areas)
|
|
{
|
|
ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.areas' (%d)", spanCount);
|
|
return false;
|
|
}
|
|
memset(chf.areas, RC_NULL_AREA, sizeof(unsigned char)*spanCount);
|
|
|
|
const int MAX_HEIGHT = 0xffff;
|
|
|
|
// Fill in cells and spans.
|
|
int idx = 0;
|
|
for (int y = 0; y < h; ++y)
|
|
{
|
|
for (int x = 0; x < w; ++x)
|
|
{
|
|
const rcSpan* s = hf.spans[x + y*w];
|
|
// If there are no spans at this cell, just leave the data to index=0, count=0.
|
|
if (!s) continue;
|
|
rcCompactCell& c = chf.cells[x+y*w];
|
|
c.index = idx;
|
|
c.count = 0;
|
|
while (s)
|
|
{
|
|
if (s->area != RC_NULL_AREA)
|
|
{
|
|
const int bot = (int)s->smax;
|
|
const int top = s->next ? (int)s->next->smin : MAX_HEIGHT;
|
|
chf.spans[idx].y = (unsigned short)rcClamp(bot, 0, 0xffff);
|
|
chf.spans[idx].h = (unsigned char)rcClamp(top - bot, 0, 0xff);
|
|
chf.areas[idx] = s->area;
|
|
idx++;
|
|
c.count++;
|
|
}
|
|
s = s->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find neighbour connections.
|
|
const int MAX_LAYERS = RC_NOT_CONNECTED-1;
|
|
int tooHighNeighbour = 0;
|
|
for (int y = 0; y < h; ++y)
|
|
{
|
|
for (int x = 0; x < w; ++x)
|
|
{
|
|
const rcCompactCell& c = chf.cells[x+y*w];
|
|
for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
|
|
{
|
|
rcCompactSpan& s = chf.spans[i];
|
|
|
|
for (int dir = 0; dir < 4; ++dir)
|
|
{
|
|
rcSetCon(s, dir, RC_NOT_CONNECTED);
|
|
const int nx = x + rcGetDirOffsetX(dir);
|
|
const int ny = y + rcGetDirOffsetY(dir);
|
|
// First check that the neighbour cell is in bounds.
|
|
if (nx < 0 || ny < 0 || nx >= w || ny >= h)
|
|
continue;
|
|
|
|
// Iterate over all neighbour spans and check if any of the is
|
|
// accessible from current cell.
|
|
const rcCompactCell& nc = chf.cells[nx+ny*w];
|
|
for (int k = (int)nc.index, nk = (int)(nc.index+nc.count); k < nk; ++k)
|
|
{
|
|
const rcCompactSpan& ns = chf.spans[k];
|
|
const int bot = rcMax(s.y, ns.y);
|
|
const int top = rcMin(s.y+s.h, ns.y+ns.h);
|
|
|
|
// Check that the gap between the spans is walkable,
|
|
// and that the climb height between the gaps is not too high.
|
|
if ((top - bot) >= walkableHeight && rcAbs((int)ns.y - (int)s.y) <= walkableClimb)
|
|
{
|
|
// Mark direction as walkable.
|
|
const int lidx = k - (int)nc.index;
|
|
if (lidx < 0 || lidx > MAX_LAYERS)
|
|
{
|
|
tooHighNeighbour = rcMax(tooHighNeighbour, lidx);
|
|
continue;
|
|
}
|
|
rcSetCon(s, dir, lidx);
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (tooHighNeighbour > MAX_LAYERS)
|
|
{
|
|
ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Heightfield has too many layers %d (max: %d)",
|
|
tooHighNeighbour, MAX_LAYERS);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
static int getHeightfieldMemoryUsage(const rcHeightfield& hf)
|
|
{
|
|
int size = 0;
|
|
size += sizeof(hf);
|
|
size += hf.width * hf.height * sizeof(rcSpan*);
|
|
|
|
rcSpanPool* pool = hf.pools;
|
|
while (pool)
|
|
{
|
|
size += (sizeof(rcSpanPool) - sizeof(rcSpan)) + sizeof(rcSpan)*RC_SPANS_PER_POOL;
|
|
pool = pool->next;
|
|
}
|
|
return size;
|
|
}
|
|
|
|
static int getCompactHeightFieldMemoryusage(const rcCompactHeightfield& chf)
|
|
{
|
|
int size = 0;
|
|
size += sizeof(rcCompactHeightfield);
|
|
size += sizeof(rcCompactSpan) * chf.spanCount;
|
|
size += sizeof(rcCompactCell) * chf.width * chf.height;
|
|
return size;
|
|
}
|
|
*/
|