mirror of
https://github.com/Relintai/pandemonium_engine.git
synced 2024-12-26 21:57:16 +01:00
557 lines
17 KiB
C++
557 lines
17 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 <math.h>
|
|
#include <stdio.h>
|
|
#include "Recast.h"
|
|
#include "RecastAlloc.h"
|
|
#include "RecastAssert.h"
|
|
|
|
/// Check whether two bounding boxes overlap
|
|
///
|
|
/// @param[in] aMin Min axis extents of bounding box A
|
|
/// @param[in] aMax Max axis extents of bounding box A
|
|
/// @param[in] bMin Min axis extents of bounding box B
|
|
/// @param[in] bMax Max axis extents of bounding box B
|
|
/// @returns true if the two bounding boxes overlap. False otherwise.
|
|
static bool overlapBounds(const float* aMin, const float* aMax, const float* bMin, const float* bMax)
|
|
{
|
|
return
|
|
aMin[0] <= bMax[0] && aMax[0] >= bMin[0] &&
|
|
aMin[1] <= bMax[1] && aMax[1] >= bMin[1] &&
|
|
aMin[2] <= bMax[2] && aMax[2] >= bMin[2];
|
|
}
|
|
|
|
/// Allocates a new span in the heightfield.
|
|
/// Use a memory pool and free list to minimize actual allocations.
|
|
///
|
|
/// @param[in] hf The heightfield
|
|
/// @returns A pointer to the allocated or re-used span memory.
|
|
static rcSpan* allocSpan(rcHeightfield& hf)
|
|
{
|
|
// If necessary, allocate new page and update the freelist.
|
|
if (hf.freelist == NULL || hf.freelist->next == NULL)
|
|
{
|
|
// Create new page.
|
|
// Allocate memory for the new pool.
|
|
rcSpanPool* spanPool = (rcSpanPool*)rcAlloc(sizeof(rcSpanPool), RC_ALLOC_PERM);
|
|
if (spanPool == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
// Add the pool into the list of pools.
|
|
spanPool->next = hf.pools;
|
|
hf.pools = spanPool;
|
|
|
|
// Add new spans to the free list.
|
|
rcSpan* freeList = hf.freelist;
|
|
rcSpan* head = &spanPool->items[0];
|
|
rcSpan* it = &spanPool->items[RC_SPANS_PER_POOL];
|
|
do
|
|
{
|
|
--it;
|
|
it->next = freeList;
|
|
freeList = it;
|
|
}
|
|
while (it != head);
|
|
hf.freelist = it;
|
|
}
|
|
|
|
// Pop item from the front of the free list.
|
|
rcSpan* newSpan = hf.freelist;
|
|
hf.freelist = hf.freelist->next;
|
|
return newSpan;
|
|
}
|
|
|
|
/// Releases the memory used by the span back to the heightfield, so it can be re-used for new spans.
|
|
/// @param[in] hf The heightfield.
|
|
/// @param[in] span A pointer to the span to free
|
|
static void freeSpan(rcHeightfield& hf, rcSpan* span)
|
|
{
|
|
if (span == NULL)
|
|
{
|
|
return;
|
|
}
|
|
// Add the span to the front of the free list.
|
|
span->next = hf.freelist;
|
|
hf.freelist = span;
|
|
}
|
|
|
|
/// Adds a span to the heightfield. If the new span overlaps existing spans,
|
|
/// it will merge the new span with the existing ones.
|
|
///
|
|
/// @param[in] hf Heightfield to add spans to
|
|
/// @param[in] x The new span's column cell x index
|
|
/// @param[in] z The new span's column cell z index
|
|
/// @param[in] min The new span's minimum cell index
|
|
/// @param[in] max The new span's maximum cell index
|
|
/// @param[in] areaID The new span's area type ID
|
|
/// @param[in] flagMergeThreshold How close two spans maximum extents need to be to merge area type IDs
|
|
static bool addSpan(rcHeightfield& hf,
|
|
const int x, const int z,
|
|
const unsigned short min, const unsigned short max,
|
|
const unsigned char areaID, const int flagMergeThreshold)
|
|
{
|
|
// Create the new span.
|
|
rcSpan* newSpan = allocSpan(hf);
|
|
if (newSpan == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
newSpan->smin = min;
|
|
newSpan->smax = max;
|
|
newSpan->area = areaID;
|
|
newSpan->next = NULL;
|
|
|
|
const int columnIndex = x + z * hf.width;
|
|
rcSpan* previousSpan = NULL;
|
|
rcSpan* currentSpan = hf.spans[columnIndex];
|
|
|
|
// Insert the new span, possibly merging it with existing spans.
|
|
while (currentSpan != NULL)
|
|
{
|
|
if (currentSpan->smin > newSpan->smax)
|
|
{
|
|
// Current span is completely after the new span, break.
|
|
break;
|
|
}
|
|
|
|
if (currentSpan->smax < newSpan->smin)
|
|
{
|
|
// Current span is completely before the new span. Keep going.
|
|
previousSpan = currentSpan;
|
|
currentSpan = currentSpan->next;
|
|
}
|
|
else
|
|
{
|
|
// The new span overlaps with an existing span. Merge them.
|
|
if (currentSpan->smin < newSpan->smin)
|
|
{
|
|
newSpan->smin = currentSpan->smin;
|
|
}
|
|
if (currentSpan->smax > newSpan->smax)
|
|
{
|
|
newSpan->smax = currentSpan->smax;
|
|
}
|
|
|
|
// Merge flags.
|
|
if (rcAbs((int)newSpan->smax - (int)currentSpan->smax) <= flagMergeThreshold)
|
|
{
|
|
// Higher area ID numbers indicate higher resolution priority.
|
|
newSpan->area = rcMax(newSpan->area, currentSpan->area);
|
|
}
|
|
|
|
// Remove the current span since it's now merged with newSpan.
|
|
// Keep going because there might be other overlapping spans that also need to be merged.
|
|
rcSpan* next = currentSpan->next;
|
|
freeSpan(hf, currentSpan);
|
|
if (previousSpan)
|
|
{
|
|
previousSpan->next = next;
|
|
}
|
|
else
|
|
{
|
|
hf.spans[columnIndex] = next;
|
|
}
|
|
currentSpan = next;
|
|
}
|
|
}
|
|
|
|
// Insert new span after prev
|
|
if (previousSpan != NULL)
|
|
{
|
|
newSpan->next = previousSpan->next;
|
|
previousSpan->next = newSpan;
|
|
}
|
|
else
|
|
{
|
|
// This span should go before the others in the list
|
|
newSpan->next = hf.spans[columnIndex];
|
|
hf.spans[columnIndex] = newSpan;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool rcAddSpan(rcContext* context, rcHeightfield& heightfield,
|
|
const int x, const int z,
|
|
const unsigned short spanMin, const unsigned short spanMax,
|
|
const unsigned char areaID, const int flagMergeThreshold)
|
|
{
|
|
rcAssert(context);
|
|
|
|
if (!addSpan(heightfield, x, z, spanMin, spanMax, areaID, flagMergeThreshold))
|
|
{
|
|
context->log(RC_LOG_ERROR, "rcAddSpan: Out of memory.");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
enum rcAxis
|
|
{
|
|
RC_AXIS_X = 0,
|
|
RC_AXIS_Y = 1,
|
|
RC_AXIS_Z = 2
|
|
};
|
|
|
|
/// Divides a convex polygon of max 12 vertices into two convex polygons
|
|
/// across a separating axis.
|
|
///
|
|
/// @param[in] inVerts The input polygon vertices
|
|
/// @param[in] inVertsCount The number of input polygon vertices
|
|
/// @param[out] outVerts1 Resulting polygon 1's vertices
|
|
/// @param[out] outVerts1Count The number of resulting polygon 1 vertices
|
|
/// @param[out] outVerts2 Resulting polygon 2's vertices
|
|
/// @param[out] outVerts2Count The number of resulting polygon 2 vertices
|
|
/// @param[in] axisOffset THe offset along the specified axis
|
|
/// @param[in] axis The separating axis
|
|
static void dividePoly(const float* inVerts, int inVertsCount,
|
|
float* outVerts1, int* outVerts1Count,
|
|
float* outVerts2, int* outVerts2Count,
|
|
float axisOffset, rcAxis axis)
|
|
{
|
|
rcAssert(inVertsCount <= 12);
|
|
|
|
// How far positive or negative away from the separating axis is each vertex.
|
|
float inVertAxisDelta[12];
|
|
for (int inVert = 0; inVert < inVertsCount; ++inVert)
|
|
{
|
|
inVertAxisDelta[inVert] = axisOffset - inVerts[inVert * 3 + axis];
|
|
}
|
|
|
|
int poly1Vert = 0;
|
|
int poly2Vert = 0;
|
|
for (int inVertA = 0, inVertB = inVertsCount - 1; inVertA < inVertsCount; inVertB = inVertA, ++inVertA)
|
|
{
|
|
// If the two vertices are on the same side of the separating axis
|
|
bool sameSide = (inVertAxisDelta[inVertA] >= 0) == (inVertAxisDelta[inVertB] >= 0);
|
|
|
|
if (!sameSide)
|
|
{
|
|
float s = inVertAxisDelta[inVertB] / (inVertAxisDelta[inVertB] - inVertAxisDelta[inVertA]);
|
|
outVerts1[poly1Vert * 3 + 0] = inVerts[inVertB * 3 + 0] + (inVerts[inVertA * 3 + 0] - inVerts[inVertB * 3 + 0]) * s;
|
|
outVerts1[poly1Vert * 3 + 1] = inVerts[inVertB * 3 + 1] + (inVerts[inVertA * 3 + 1] - inVerts[inVertB * 3 + 1]) * s;
|
|
outVerts1[poly1Vert * 3 + 2] = inVerts[inVertB * 3 + 2] + (inVerts[inVertA * 3 + 2] - inVerts[inVertB * 3 + 2]) * s;
|
|
rcVcopy(&outVerts2[poly2Vert * 3], &outVerts1[poly1Vert * 3]);
|
|
poly1Vert++;
|
|
poly2Vert++;
|
|
|
|
// add the inVertA point to the right polygon. Do NOT add points that are on the dividing line
|
|
// since these were already added above
|
|
if (inVertAxisDelta[inVertA] > 0)
|
|
{
|
|
rcVcopy(&outVerts1[poly1Vert * 3], &inVerts[inVertA * 3]);
|
|
poly1Vert++;
|
|
}
|
|
else if (inVertAxisDelta[inVertA] < 0)
|
|
{
|
|
rcVcopy(&outVerts2[poly2Vert * 3], &inVerts[inVertA * 3]);
|
|
poly2Vert++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// add the inVertA point to the right polygon. Addition is done even for points on the dividing line
|
|
if (inVertAxisDelta[inVertA] >= 0)
|
|
{
|
|
rcVcopy(&outVerts1[poly1Vert * 3], &inVerts[inVertA * 3]);
|
|
poly1Vert++;
|
|
if (inVertAxisDelta[inVertA] != 0)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
rcVcopy(&outVerts2[poly2Vert * 3], &inVerts[inVertA * 3]);
|
|
poly2Vert++;
|
|
}
|
|
}
|
|
|
|
*outVerts1Count = poly1Vert;
|
|
*outVerts2Count = poly2Vert;
|
|
}
|
|
|
|
/// Rasterize a single triangle to the heightfield.
|
|
///
|
|
/// This code is extremely hot, so much care should be given to maintaining maximum perf here.
|
|
///
|
|
/// @param[in] v0 Triangle vertex 0
|
|
/// @param[in] v1 Triangle vertex 1
|
|
/// @param[in] v2 Triangle vertex 2
|
|
/// @param[in] areaID The area ID to assign to the rasterized spans
|
|
/// @param[in] hf Heightfield to rasterize into
|
|
/// @param[in] hfBBMin The min extents of the heightfield bounding box
|
|
/// @param[in] hfBBMax The max extents of the heightfield bounding box
|
|
/// @param[in] cellSize The x and z axis size of a voxel in the heightfield
|
|
/// @param[in] inverseCellSize 1 / cellSize
|
|
/// @param[in] inverseCellHeight 1 / cellHeight
|
|
/// @param[in] flagMergeThreshold The threshold in which area flags will be merged
|
|
/// @returns true if the operation completes successfully. false if there was an error adding spans to the heightfield.
|
|
static bool rasterizeTri(const float* v0, const float* v1, const float* v2,
|
|
const unsigned char areaID, rcHeightfield& hf,
|
|
const float* hfBBMin, const float* hfBBMax,
|
|
const float cellSize, const float inverseCellSize, const float inverseCellHeight,
|
|
const int flagMergeThreshold)
|
|
{
|
|
// Calculate the bounding box of the triangle.
|
|
float triBBMin[3];
|
|
rcVcopy(triBBMin, v0);
|
|
rcVmin(triBBMin, v1);
|
|
rcVmin(triBBMin, v2);
|
|
|
|
float triBBMax[3];
|
|
rcVcopy(triBBMax, v0);
|
|
rcVmax(triBBMax, v1);
|
|
rcVmax(triBBMax, v2);
|
|
|
|
// If the triangle does not touch the bounding box of the heightfield, skip the triangle.
|
|
if (!overlapBounds(triBBMin, triBBMax, hfBBMin, hfBBMax))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
const int w = hf.width;
|
|
const int h = hf.height;
|
|
const float by = hfBBMax[1] - hfBBMin[1];
|
|
|
|
// Calculate the footprint of the triangle on the grid's z-axis
|
|
int z0 = (int)((triBBMin[2] - hfBBMin[2]) * inverseCellSize);
|
|
int z1 = (int)((triBBMax[2] - hfBBMin[2]) * inverseCellSize);
|
|
|
|
// use -1 rather than 0 to cut the polygon properly at the start of the tile
|
|
z0 = rcClamp(z0, -1, h - 1);
|
|
z1 = rcClamp(z1, 0, h - 1);
|
|
|
|
// Clip the triangle into all grid cells it touches.
|
|
float buf[7 * 3 * 4];
|
|
float* in = buf;
|
|
float* inRow = buf + 7 * 3;
|
|
float* p1 = inRow + 7 * 3;
|
|
float* p2 = p1 + 7 * 3;
|
|
|
|
rcVcopy(&in[0], v0);
|
|
rcVcopy(&in[1 * 3], v1);
|
|
rcVcopy(&in[2 * 3], v2);
|
|
int nvRow;
|
|
int nvIn = 3;
|
|
|
|
for (int z = z0; z <= z1; ++z)
|
|
{
|
|
// Clip polygon to row. Store the remaining polygon as well
|
|
const float cellZ = hfBBMin[2] + (float)z * cellSize;
|
|
dividePoly(in, nvIn, inRow, &nvRow, p1, &nvIn, cellZ + cellSize, RC_AXIS_Z);
|
|
rcSwap(in, p1);
|
|
|
|
if (nvRow < 3)
|
|
{
|
|
continue;
|
|
}
|
|
if (z < 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// find X-axis bounds of the row
|
|
float minX = inRow[0];
|
|
float maxX = inRow[0];
|
|
for (int vert = 1; vert < nvRow; ++vert)
|
|
{
|
|
if (minX > inRow[vert * 3])
|
|
{
|
|
minX = inRow[vert * 3];
|
|
}
|
|
if (maxX < inRow[vert * 3])
|
|
{
|
|
maxX = inRow[vert * 3];
|
|
}
|
|
}
|
|
int x0 = (int)((minX - hfBBMin[0]) * inverseCellSize);
|
|
int x1 = (int)((maxX - hfBBMin[0]) * inverseCellSize);
|
|
if (x1 < 0 || x0 >= w)
|
|
{
|
|
continue;
|
|
}
|
|
x0 = rcClamp(x0, -1, w - 1);
|
|
x1 = rcClamp(x1, 0, w - 1);
|
|
|
|
int nv;
|
|
int nv2 = nvRow;
|
|
|
|
for (int x = x0; x <= x1; ++x)
|
|
{
|
|
// Clip polygon to column. store the remaining polygon as well
|
|
const float cx = hfBBMin[0] + (float)x * cellSize;
|
|
dividePoly(inRow, nv2, p1, &nv, p2, &nv2, cx + cellSize, RC_AXIS_X);
|
|
rcSwap(inRow, p2);
|
|
|
|
if (nv < 3)
|
|
{
|
|
continue;
|
|
}
|
|
if (x < 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Calculate min and max of the span.
|
|
float spanMin = p1[1];
|
|
float spanMax = p1[1];
|
|
for (int vert = 1; vert < nv; ++vert)
|
|
{
|
|
spanMin = rcMin(spanMin, p1[vert * 3 + 1]);
|
|
spanMax = rcMax(spanMax, p1[vert * 3 + 1]);
|
|
}
|
|
spanMin -= hfBBMin[1];
|
|
spanMax -= hfBBMin[1];
|
|
|
|
// Skip the span if it's completely outside the heightfield bounding box
|
|
if (spanMax < 0.0f)
|
|
{
|
|
continue;
|
|
}
|
|
if (spanMin > by)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Clamp the span to the heightfield bounding box.
|
|
if (spanMin < 0.0f)
|
|
{
|
|
spanMin = 0;
|
|
}
|
|
if (spanMax > by)
|
|
{
|
|
spanMax = by;
|
|
}
|
|
|
|
// Snap the span to the heightfield height grid.
|
|
unsigned short spanMinCellIndex = (unsigned short)rcClamp((int)floorf(spanMin * inverseCellHeight), 0, RC_SPAN_MAX_HEIGHT);
|
|
unsigned short spanMaxCellIndex = (unsigned short)rcClamp((int)ceilf(spanMax * inverseCellHeight), (int)spanMinCellIndex + 1, RC_SPAN_MAX_HEIGHT);
|
|
|
|
if (!addSpan(hf, x, z, spanMinCellIndex, spanMaxCellIndex, areaID, flagMergeThreshold))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool rcRasterizeTriangle(rcContext* context,
|
|
const float* v0, const float* v1, const float* v2,
|
|
const unsigned char areaID, rcHeightfield& heightfield, const int flagMergeThreshold)
|
|
{
|
|
rcAssert(context != NULL);
|
|
|
|
rcScopedTimer timer(context, RC_TIMER_RASTERIZE_TRIANGLES);
|
|
|
|
// Rasterize the single triangle.
|
|
const float inverseCellSize = 1.0f / heightfield.cs;
|
|
const float inverseCellHeight = 1.0f / heightfield.ch;
|
|
if (!rasterizeTri(v0, v1, v2, areaID, heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs, inverseCellSize, inverseCellHeight, flagMergeThreshold))
|
|
{
|
|
context->log(RC_LOG_ERROR, "rcRasterizeTriangle: Out of memory.");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool rcRasterizeTriangles(rcContext* context,
|
|
const float* verts, const int /*nv*/,
|
|
const int* tris, const unsigned char* triAreaIDs, const int numTris,
|
|
rcHeightfield& heightfield, const int flagMergeThreshold)
|
|
{
|
|
rcAssert(context != NULL);
|
|
|
|
rcScopedTimer timer(context, RC_TIMER_RASTERIZE_TRIANGLES);
|
|
|
|
// Rasterize the triangles.
|
|
const float inverseCellSize = 1.0f / heightfield.cs;
|
|
const float inverseCellHeight = 1.0f / heightfield.ch;
|
|
for (int triIndex = 0; triIndex < numTris; ++triIndex)
|
|
{
|
|
const float* v0 = &verts[tris[triIndex * 3 + 0] * 3];
|
|
const float* v1 = &verts[tris[triIndex * 3 + 1] * 3];
|
|
const float* v2 = &verts[tris[triIndex * 3 + 2] * 3];
|
|
if (!rasterizeTri(v0, v1, v2, triAreaIDs[triIndex], heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs, inverseCellSize, inverseCellHeight, flagMergeThreshold))
|
|
{
|
|
context->log(RC_LOG_ERROR, "rcRasterizeTriangles: Out of memory.");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool rcRasterizeTriangles(rcContext* context,
|
|
const float* verts, const int /*nv*/,
|
|
const unsigned short* tris, const unsigned char* triAreaIDs, const int numTris,
|
|
rcHeightfield& heightfield, const int flagMergeThreshold)
|
|
{
|
|
rcAssert(context != NULL);
|
|
|
|
rcScopedTimer timer(context, RC_TIMER_RASTERIZE_TRIANGLES);
|
|
|
|
// Rasterize the triangles.
|
|
const float inverseCellSize = 1.0f / heightfield.cs;
|
|
const float inverseCellHeight = 1.0f / heightfield.ch;
|
|
for (int triIndex = 0; triIndex < numTris; ++triIndex)
|
|
{
|
|
const float* v0 = &verts[tris[triIndex * 3 + 0] * 3];
|
|
const float* v1 = &verts[tris[triIndex * 3 + 1] * 3];
|
|
const float* v2 = &verts[tris[triIndex * 3 + 2] * 3];
|
|
if (!rasterizeTri(v0, v1, v2, triAreaIDs[triIndex], heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs, inverseCellSize, inverseCellHeight, flagMergeThreshold))
|
|
{
|
|
context->log(RC_LOG_ERROR, "rcRasterizeTriangles: Out of memory.");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool rcRasterizeTriangles(rcContext* context,
|
|
const float* verts, const unsigned char* triAreaIDs, const int numTris,
|
|
rcHeightfield& heightfield, const int flagMergeThreshold)
|
|
{
|
|
rcAssert(context != NULL);
|
|
|
|
rcScopedTimer timer(context, RC_TIMER_RASTERIZE_TRIANGLES);
|
|
|
|
// Rasterize the triangles.
|
|
const float inverseCellSize = 1.0f / heightfield.cs;
|
|
const float inverseCellHeight = 1.0f / heightfield.ch;
|
|
for (int triIndex = 0; triIndex < numTris; ++triIndex)
|
|
{
|
|
const float* v0 = &verts[(triIndex * 3 + 0) * 3];
|
|
const float* v1 = &verts[(triIndex * 3 + 1) * 3];
|
|
const float* v2 = &verts[(triIndex * 3 + 2) * 3];
|
|
if (!rasterizeTri(v0, v1, v2, triAreaIDs[triIndex], heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs, inverseCellSize, inverseCellHeight, flagMergeThreshold))
|
|
{
|
|
context->log(RC_LOG_ERROR, "rcRasterizeTriangles: Out of memory.");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|