mirror of
https://github.com/Relintai/pandemonium_engine.git
synced 2024-12-27 06:07:14 +01:00
576 lines
14 KiB
C++
576 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);
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
*/
|