// Copyright Epic Games, Inc. All Rights Reserved. // Modified version of Recast/Detour's source file // // 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 "CoreMinimal.h" #define _USE_MATH_DEFINES #include "Recast/Recast.h" #include "Recast/RecastAlloc.h" #include "Recast/RecastAssert.h" #include "HAL/ConsoleManager.h" //@UE BEGIN namespace UE::Recast::Private { static bool bEnableSpanHeightRasterizationFix = true; static FAutoConsoleVariableRef CVarEnableSpanHeightRasterizationFix(TEXT("ai.nav.EnableSpanHeightRasterizationFix"), bEnableSpanHeightRasterizationFix, TEXT("Active by default. Enable rasterization fix for span height."), ECVF_Default); } //@UE END inline bool overlapBounds(const rcReal* amin, const rcReal* amax, const rcReal* bmin, const rcReal* 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 overlapInterval(unsigned short amin, unsigned short amax, unsigned short bmin, unsigned short bmax) { if (amax < bmin) return false; if (amin > bmax) return false; return true; } static rcSpan* allocSpan(rcHeightfield& hf) { // If running out of memory, allocate new page and update the freelist. if (!hf.freelist || !hf.freelist->next) { // Create new page. // Allocate memory for the new pool. rcSpanPool* pool = (rcSpanPool*)rcAlloc(sizeof(rcSpanPool), RC_ALLOC_PERM); if (!pool) return 0; pool->next = 0; // Add the pool into the list of pools. pool->next = hf.pools; hf.pools = pool; // Add new items to the free list. rcSpan* freelist = hf.freelist; rcSpan* head = &pool->items[0]; rcSpan* it = &pool->items[RC_SPANS_PER_POOL]; do { --it; it->next = freelist; freelist = it; } while (it != head); hf.freelist = it; } // Pop item from in front of the free list. rcSpan* it = hf.freelist; hf.freelist = hf.freelist->next; return it; } static void freeSpan(rcHeightfield& hf, rcSpan* ptr) { if (!ptr) return; // Add the node in front of the free list. ptr->next = hf.freelist; hf.freelist = ptr; } // @UE BEGIN // Merge overlapping spans static void mergeSpanData(rcSpanData& span, const rcSpanUInt smin, const rcSpanUInt smax, const unsigned char area, const int flagMergeThr) { // For spans whose tops are really close to each other, prefer walkable areas. // This is done in order to remove aliasing (similar to z-fighting) on surfaces close to each other. if (rcAbs((int)span.smax - (int)smax) <= flagMergeThr) { span.area = rcMax(span.area, area); } else { // Use the new spans area if it will become the top. if (smax > span.smax) span.area = area; } // Merge height intervals. if (smin < span.smin) span.smin = smin; if (smax > span.smax) span.smax = smax; } // @UE END static void addSpan(rcHeightfield& hf, const int x, const int y, const rcSpanUInt smin, const rcSpanUInt smax, const unsigned char area, const int flagMergeThr) { int idx = x + y*hf.width; rcSpan* s = allocSpan(hf); s->data.smin = smin; s->data.smax = smax; s->data.area = area; s->next = 0; // Empty cell, add the first span. if (!hf.spans[idx]) { hf.spans[idx] = s; return; } rcSpan* prev = 0; rcSpan* cur = hf.spans[idx]; // Insert and merge spans. while (cur) { if (cur->data.smin > s->data.smax) { // Current span is further than the new span, break. break; } else if (cur->data.smax < s->data.smin) { // Current span is before the new span advance. prev = cur; cur = cur->next; } else { // @UE BEGIN mergeSpanData(s->data, cur->data.smin, cur->data.smax, cur->data.area, flagMergeThr); // @UE END // Remove current span. rcSpan* next = cur->next; freeSpan(hf, cur); if (prev) prev->next = next; else hf.spans[idx] = next; cur = next; } } // Insert new span. if (prev) { s->next = prev->next; prev->next = s; } else { s->next = hf.spans[idx]; hf.spans[idx] = s; } } /// @par /// /// The span addition can be set to favor flags. If the span is merged to /// another span and the new @p smax is within @p flagMergeThr units /// from the existing span, the span flags are merged. /// /// @see rcHeightfield, rcSpan. void rcAddSpan(rcContext* /*ctx*/, rcHeightfield& hf, const int x, const int y, const rcSpanUInt smin, const rcSpanUInt smax, const unsigned char area, const int flagMergeThr) { // rcAssert(ctx); addSpan(hf, x,y, smin, smax, area, flagMergeThr); } void rcAddSpans(rcContext* /*ctx*/, rcHeightfield& hf, const int flagMergeThr, const rcSpanCache* cachedSpans, const int nspans) { const rcSpanCache* cachedInfo = cachedSpans; for (int i = 0; i < nspans; i++, cachedInfo++) { addSpan(hf, cachedInfo->x, cachedInfo->y, cachedInfo->data.smin, cachedInfo->data.smax, cachedInfo->data.area, flagMergeThr); } } int rcCountSpans(rcContext* /*ctx*/, rcHeightfield& hf) { if (hf.width > 0xffff || hf.height > 0xffff) { return 0; } int numSpans = 0; for (rcSpanPool* pool = hf.pools; pool; pool = pool->next) { numSpans += RC_SPANS_PER_POOL; } for (rcSpan* s = hf.freelist; s; s = s->next) { numSpans--; } return numSpans; } void rcCacheSpans(rcContext* /*ctx*/, rcHeightfield& hf, rcSpanCache* cachedSpans) { rcSpanCache* cachedInfo = cachedSpans; for (int iz = 0; iz < hf.height; iz++) { for (int ix = 0; ix < hf.width; ix++) { const int idx = ix + (iz * hf.width); for (rcSpan* s = hf.spans[idx]; s; s = s->next) { cachedInfo->x = (unsigned short)ix; cachedInfo->y = (unsigned short)iz; cachedInfo->data = s->data; cachedInfo++; } } } } static int clipPoly(const rcReal* in, int n, rcReal* out, rcReal pnx, rcReal pnz, rcReal pd) { rcReal d[12]; for (int i = 0; i < n; ++i) d[i] = pnx*in[i*3+0] + pnz*in[i*3+2] + pd; int m = 0; for (int i = 0, j = n-1; i < n; j=i, ++i) { bool ina = d[j] >= 0; bool inb = d[i] >= 0; if (ina != inb) { rcReal s = d[j] / (d[j] - d[i]); out[m*3+0] = in[j*3+0] + (in[i*3+0] - in[j*3+0])*s; out[m*3+1] = in[j*3+1] + (in[i*3+1] - in[j*3+1])*s; out[m*3+2] = in[j*3+2] + (in[i*3+2] - in[j*3+2])*s; m++; } if (inb) { out[m*3+0] = in[i*3+0]; out[m*3+1] = in[i*3+1]; out[m*3+2] = in[i*3+2]; m++; } } return m; } #if EPIC_ADDITION_USE_NEW_RECAST_RASTERIZER #define TEST_NEW_RASTERIZER (0) #define TEST_COVERAGE (0) static inline int intMax(int a, int b) { return a < b ? b : a; } static inline int intMin(int a, int b) { return a < b ? a : b; } #if TEST_COVERAGE static inline void markSpanSample(rcHeightfield& hf, const int x, const int y) { #if TEST_NEW_RASTERIZER rcAssert(x >= -1 && x < hf.width + 1 && y >= -1 && y < hf.height + 1); #endif hf.RowExt[y + 1].MinCol = intMin(hf.RowExt[y + 1].MinCol, x); hf.RowExt[y + 1].MaxCol = intMax(hf.RowExt[y + 1].MaxCol, x); } #endif static inline void addFlatSpanSample(rcHeightfield& hf, const int x, const int y) { #if TEST_NEW_RASTERIZER rcAssert(x >= -1 && x < hf.width + 1 && y >= -1 && y < hf.height + 1); #endif #if TEST_COVERAGE rcAssert(hf.RowExt[y + 1].MinCol == intMin(hf.RowExt[y + 1].MinCol, x)); rcAssert(hf.RowExt[y + 1].MaxCol == intMax(hf.RowExt[y + 1].MaxCol, x)); #endif hf.RowExt[y + 1].MinCol = intMin(hf.RowExt[y + 1].MinCol, x); hf.RowExt[y + 1].MaxCol = intMax(hf.RowExt[y + 1].MaxCol, x); } static inline int SampleIndex(rcHeightfield const& hf, const int x, const int y) { #if TEST_NEW_RASTERIZER rcAssert(x >= -1 && x < hf.width + 1 && y >= -1 && y < hf.height + 1); #endif return x + 1 + (y + 1)*(hf.width + 2); } static inline void addSpanSample(rcHeightfield& hf, const int x, const int y, int sint) { addFlatSpanSample(hf, x, y); int idx = SampleIndex(hf, x, y); rcTempSpan& Temp = hf.tempspans[idx]; Temp.sminmax[0] = Temp.sminmax[0] > sint ? sint : Temp.sminmax[0]; Temp.sminmax[1] = Temp.sminmax[1] < sint ? sint : Temp.sminmax[1]; } static inline void intersectX(const rcReal* v0, const rcReal* edge, rcReal cx, rcReal *pnt) { rcReal t = rcClamp((cx - v0[0]) * edge[9 + 0], 0.0f, 1.0f); // inverses pnt[0] = v0[0] + t * edge[0]; pnt[1] = v0[1] + t * edge[1]; pnt[2] = v0[2] + t * edge[2]; } static inline void intersectZ(const rcReal* v0, const rcReal* edge, rcReal cz, rcReal *pnt) { rcReal t = rcClamp((cz - v0[2]) * edge[9 + 2], 0.0f, 1.0f); //inverses pnt[0] = v0[0] + t * edge[0]; pnt[1] = v0[1] + t * edge[1]; pnt[2] = v0[2] + t * edge[2]; } #if TEST_NEW_RASTERIZER static void rasterizeTriTest(const rcReal* v0, const rcReal* v1, const rcReal* v2, int xtest, int ytest, int& outsmin, int& outsmax, const unsigned char area, rcHeightfield& hf, const rcReal* bmin, const rcReal* bmax, const rcReal cs, const rcReal ics, const rcReal ich, const int flagMergeThr) { outsmin = RC_SPAN_MAX_HEIGHT; outsmax = -1; const int w = hf.width; const int h = hf.height; rcReal tmin[3], tmax[3]; const rcReal by = bmax[1] - bmin[1]; // Calculate the bounding box of the triangle. rcVcopy(tmin, v0); rcVcopy(tmax, v0); rcVmin(tmin, v1); rcVmin(tmin, v2); rcVmax(tmax, v1); rcVmax(tmax, v2); // If the triangle does not touch the bbox of the heightfield, skip the triagle. if (!overlapBounds(bmin, bmax, tmin, tmax)) return; // Calculate the footpring of the triangle on the grid. int x0 = (int)((tmin[0] - bmin[0])*ics); int y0 = (int)((tmin[2] - bmin[2])*ics); int x1 = (int)((tmax[0] - bmin[0])*ics); int y1 = (int)((tmax[2] - bmin[2])*ics); x0 = rcClamp(x0, 0, w-1); y0 = rcClamp(y0, 0, h-1); x1 = rcClamp(x1, 0, w-1); y1 = rcClamp(y1, 0, h-1); // Clip the triangle into all grid cells it touches. rcReal in[7*3], out[7*3], inrow[7*3]; for (int y = y0; y <= y1; ++y) { if (y != ytest) continue; // Clip polygon to row. rcVcopy(&in[0], v0); rcVcopy(&in[1*3], v1); rcVcopy(&in[2*3], v2); int nvrow = 3; const rcReal cz = bmin[2] + y*cs; nvrow = clipPoly(in, nvrow, out, 0, 1, -cz); if (nvrow < 3) continue; nvrow = clipPoly(out, nvrow, inrow, 0, -1, cz+cs); if (nvrow < 3) continue; for (int x = x0; x <= x1; ++x) { if (x != xtest) continue; // Clip polygon to column. int nv = nvrow; const rcReal cx = bmin[0] + x*cs; nv = clipPoly(inrow, nv, out, 1, 0, -cx); if (nv < 3) continue; nv = clipPoly(out, nv, in, -1, 0, cx+cs); if (nv < 3) continue; // Calculate min and max of the span. rcReal smin = in[1], smax = in[1]; for (int i = 1; i < nv; ++i) { smin = rcMin(smin, in[i*3+1]); smax = rcMax(smax, in[i*3+1]); } smin -= bmin[1]; smax -= bmin[1]; // Skip the span if it is outside the heightfield bbox if (smax < 0.0f) continue; if (smin > by) continue; // Clamp the span to the heightfield bbox. if (smin < 0.0f) smin = 0; if (smax > by) smax = by; // Snap the span to the heightfield height grid. rcSpanUInt ismin = (rcSpanUInt)rcClamp((int)rcFloor(smin * ich), 0, RC_SPAN_MAX_HEIGHT); rcSpanUInt ismax = (rcSpanUInt)rcClamp((int)rcCeil(smax * ich), (int)ismin+1, RC_SPAN_MAX_HEIGHT); outsmin = ismin; outsmax = ismax; } } } #endif //TEST_NEW_RASTERIZER template static void rasterizeTri(const rcReal* v0, const rcReal* v1, const rcReal* v2, const unsigned char area, rcHeightfield& hf, const rcReal* bmin, const rcReal* bmax, const rcReal cs, const rcReal ics, const rcReal ich, const int flagMergeThr, const int rasterizationFlags, /*UE*/ const int* rasterizationMasks, /*UE*/ AddSpanFunc& Func /*UE*/) { rcEdgeHit* const hfEdgeHits = hf.EdgeHits; //this prevents a static analysis warning const int w = hf.width; const int h = hf.height; int intverts[3][2]; intverts[0][0] = (int)rcFloor((v0[0] - bmin[0])*ics); intverts[0][1] = (int)rcFloor((v0[2] - bmin[2])*ics); intverts[1][0] = (int)rcFloor((v1[0] - bmin[0])*ics); intverts[1][1] = (int)rcFloor((v1[2] - bmin[2])*ics); intverts[2][0] = (int)rcFloor((v2[0] - bmin[0])*ics); intverts[2][1] = (int)rcFloor((v2[2] - bmin[2])*ics); int x0 = intMin(intverts[0][0], intMin(intverts[1][0], intverts[2][0])); int x1 = intMax(intverts[0][0], intMax(intverts[1][0], intverts[2][0])); int y0 = intMin(intverts[0][1], intMin(intverts[1][1], intverts[2][1])); int y1 = intMax(intverts[0][1], intMax(intverts[1][1], intverts[2][1])); if (x1 < 0 || x0 >= w || y1 < 0 || y0 >= h) return; const rcReal by = bmax[1] - bmin[1]; const int projectTriToBottom = rasterizationFlags & RC_PROJECT_TO_BOTTOM; //UE // Calculate min and max of the triangle rcReal triangle_smin = rcMin(rcMin(v0[1], v1[1]), v2[1]); rcReal triangle_smax = rcMax(rcMax(v0[1], v1[1]), v2[1]); triangle_smin -= bmin[1]; triangle_smax -= bmin[1]; // Skip the span if it is outside the heightfield bbox if (triangle_smax < 0.0f) return; if (triangle_smin > by) return; if (x0 == x1 && y0 == y1) { // Clamp the span to the heightfield bbox. if (triangle_smin < 0.0f) triangle_smin = 0.0f; if (triangle_smax > by) triangle_smax = by; // Snap the span to the heightfield height grid. rcSpanUInt triangle_ismin = (rcSpanUInt)rcClamp((int)rcFloor(triangle_smin * ich), 0, RC_SPAN_MAX_HEIGHT); rcSpanUInt triangle_ismax = (rcSpanUInt)rcClamp((int)rcCeil(triangle_smax * ich), (int)triangle_ismin+1, RC_SPAN_MAX_HEIGHT); const int projectSpanToBottom = rasterizationMasks != nullptr ? (projectTriToBottom & rasterizationMasks[x0+y0*w]) : projectTriToBottom; //UE if (projectSpanToBottom) //UE { triangle_ismin = 0; //UE } Func.addSpan(hf, x0, y0, triangle_ismin, triangle_ismax, area, flagMergeThr); return; } constexpr int RANGE = INT_MAX; const int triangle_ismin = rcClamp((int)rcFloor(triangle_smin * ich), -RANGE, RANGE); const int triangle_ismax = rcClamp((int)rcFloor(triangle_smax * ich), -RANGE, RANGE); x0 = intMax(x0, 0); int x1_edge = intMin(x1, w); x1 = intMin(x1, w - 1); y0 = intMax(y0, 0); int y1_edge = intMin(y1, h); y1 = intMin(y1, h - 1); #if TEST_COVERAGE for (int y = y0; y <= y1; y++) { for (int x = x0; x <= x1; x++) { int outsmin, outsmax; rasterizeTriTest(v0, v1, v2, x, y, outsmin, outsmax, area, hf, bmin, bmax, cs, ics, ich, flagMergeThr); if (outsmin != RC_SPAN_MAX_HEIGHT) { markSpanSample(hf, x, y); } } } #endif rcReal edges[6][3]; rcReal vertarray[3][3]; rcVcopy(vertarray[0], v0); rcVcopy(vertarray[1], v1); rcVcopy(vertarray[2], v2); bool doFlat = true; if (doFlat && triangle_ismin == triangle_ismax) { // flat horizontal, much faster for (int basevert = 0; basevert < 3; basevert++) { int othervert = basevert == 2 ? 0 : basevert + 1; int edge = basevert == 0 ? 2 : basevert - 1; rcVsub(&edges[edge][0], vertarray[othervert], vertarray[basevert]); //rcVnormalize(&edges[edge][0]); edges[3 + edge][0] = 1.0f / edges[edge][0]; edges[3 + edge][1] = 1.0f / edges[edge][1]; edges[3 + edge][2] = 1.0f / edges[edge][2]; // drop the vert into the temp span area if (intverts[basevert][0] >= x0 && intverts[basevert][0] <= x1 && intverts[basevert][1] >= y0 && intverts[basevert][1] <= y1) { addFlatSpanSample(hf, intverts[basevert][0], intverts[basevert][1]); } // set up the edge intersections with horizontal planes if (intverts[basevert][1] != intverts[othervert][1]) { int edge0 = intMin(intverts[basevert][1], intverts[othervert][1]); int edge1 = intMax(intverts[basevert][1], intverts[othervert][1]); int loop0 = intMax(edge0 + 1, y0); int loop1 = intMin(edge1, y1_edge); unsigned char edgeBits = (unsigned char)((edge << 4) | (othervert << 2) | basevert); for (int y = loop0; y <= loop1; y++) { int HitIndex = !!hfEdgeHits[y].Hits[0]; hfEdgeHits[y].Hits[HitIndex] = edgeBits; } } // do the edge intersections with vertical planes if (intverts[basevert][0] != intverts[othervert][0]) { int edge0 = intMin(intverts[basevert][0], intverts[othervert][0]); int edge1 = intMax(intverts[basevert][0], intverts[othervert][0]); int loop0 = intMax(edge0 + 1, x0); int loop1 = intMin(edge1, x1_edge); rcReal temppnt[3]; rcReal cx = bmin[0] + cs * loop0; for (int x = loop0; x <= loop1; x++, cx += cs) { intersectX(vertarray[basevert], &edges[edge][0], cx, temppnt); int y = (int)rcFloor((temppnt[2] - bmin[2])*ics); if (y >= y0 && y <= y1) { addFlatSpanSample(hf, x, y); addFlatSpanSample(hf, x - 1, y); } } } } { // deal with the horizontal intersections int edge0 = intMin(intverts[0][1], intMin(intverts[1][1],intverts[2][1])); int edge1 = intMax(intverts[0][1], intMax(intverts[1][1],intverts[2][1])); int loop0 = intMax(edge0 + 1, y0); int loop1 = intMin(edge1, y1_edge); rcReal Inter[2][3]; int xInter[2]; rcReal cz = bmin[2] + cs * loop0; for (int y = loop0; y <= loop1; y++, cz += cs) { rcEdgeHit& Hits = hfEdgeHits[y]; if (Hits.Hits[0]) { rcAssert(Hits.Hits[1]); // must have two hits for (int i = 0; i < 2; i++) { int edge = Hits.Hits[i] >> 4; int othervert = (Hits.Hits[i] >> 2) & 3; int basevert = Hits.Hits[i] & 3; intersectZ(vertarray[basevert], &edges[edge][0], cz, Inter[i]); int x = (int)rcFloor((Inter[i][0] - bmin[0])*ics); xInter[i] = x; if (x >= x0 && x <= x1) { addFlatSpanSample(hf, x, y); addFlatSpanSample(hf, x, y - 1); } } if (xInter[0] != xInter[1]) { // now fill in the fully contained ones. int left = Inter[1][0] < Inter[0][0]; int xloop0 = intMax(xInter[left] + 1, x0); int xloop1 = intMin(xInter[1 - left], x1); if (xloop0 <= xloop1) { addFlatSpanSample(hf, xloop0, y); addFlatSpanSample(hf, xloop1, y); addFlatSpanSample(hf, xloop0 - 1, y); addFlatSpanSample(hf, xloop1 - 1, y); addFlatSpanSample(hf, xloop0, y - 1); addFlatSpanSample(hf, xloop1, y - 1); addFlatSpanSample(hf, xloop0 - 1, y - 1); addFlatSpanSample(hf, xloop1 - 1, y - 1); } } // reset for next triangle Hits.Hits[0] = 0; Hits.Hits[1] = 0; } } } if (rasterizationMasks == nullptr) //UE { // Snap the span to the heightfield height grid. rcSpanUInt triangle_ismin_clamp = (rcSpanUInt)rcClamp((int)triangle_ismin, 0, RC_SPAN_MAX_HEIGHT); const rcSpanUInt triangle_ismax_clamp = (rcSpanUInt)rcClamp((int)triangle_ismax, (int)triangle_ismin_clamp+1, RC_SPAN_MAX_HEIGHT); if (projectTriToBottom) //UE { triangle_ismin_clamp = 0; //UE } for (int y = y0; y <= y1; y++) { int xloop0 = intMax(hf.RowExt[y + 1].MinCol, x0); int xloop1 = intMin(hf.RowExt[y + 1].MaxCol, x1); for (int x = xloop0; x <= xloop1; x++) { Func.addSpan(hf, x, y, triangle_ismin_clamp, triangle_ismax_clamp, area, flagMergeThr); } // reset for next triangle hf.RowExt[y + 1].MinCol = hf.width + 2; hf.RowExt[y + 1].MaxCol = -2; } } else { // @UE BEGIN for (int y = y0; y <= y1; y++) { int xloop0 = intMax(hf.RowExt[y + 1].MinCol, x0); int xloop1 = intMin(hf.RowExt[y + 1].MaxCol, x1); for (int x = xloop0; x <= xloop1; x++) { // Snap the span to the heightfield height grid. rcSpanUInt triangle_ismin_clamp = (rcSpanUInt)rcClamp((int)triangle_ismin, 0, RC_SPAN_MAX_HEIGHT); const rcSpanUInt triangle_ismax_clamp = (rcSpanUInt)rcClamp((int)triangle_ismax, (int)triangle_ismin_clamp+1, RC_SPAN_MAX_HEIGHT); const int projectSpanToBottom = projectTriToBottom & rasterizationMasks[x+y*w]; //UE if (projectSpanToBottom) //UE { triangle_ismin_clamp = 0; //UE } Func.addSpan(hf, x, y, triangle_ismin_clamp, triangle_ismax_clamp, area, flagMergeThr); } // reset for next triangle hf.RowExt[y + 1].MinCol = hf.width + 2; hf.RowExt[y + 1].MaxCol = -2; } // @UE END } } else { //non-flat case for (int basevert = 0; basevert < 3; basevert++) { int othervert = basevert == 2 ? 0 : basevert + 1; int edge = basevert == 0 ? 2 : basevert - 1; rcVsub(&edges[edge][0], vertarray[othervert], vertarray[basevert]); //rcVnormalize(&edges[edge][0]); edges[3 + edge][0] = 1.0f / edges[edge][0]; edges[3 + edge][1] = 1.0f / edges[edge][1]; edges[3 + edge][2] = 1.0f / edges[edge][2]; // drop the vert into the temp span area if (intverts[basevert][0] >= x0 && intverts[basevert][0] <= x1 && intverts[basevert][1] >= y0 && intverts[basevert][1] <= y1) { rcReal sfloat = vertarray[basevert][1] - bmin[1]; const int sint = (int)rcClamp((int)rcFloor(sfloat * ich), -RANGE, RANGE); #if TEST_NEW_RASTERIZER rcAssert(sint >= triangle_ismin - 1 && sint <= triangle_ismax + 1); #endif addSpanSample(hf, intverts[basevert][0], intverts[basevert][1], sint); } // set up the edge intersections with horizontal planes if (intverts[basevert][1] != intverts[othervert][1]) { int edge0 = intMin(intverts[basevert][1], intverts[othervert][1]); int edge1 = intMax(intverts[basevert][1], intverts[othervert][1]); int loop0 = intMax(edge0 + 1, y0); int loop1 = intMin(edge1, y1_edge); unsigned char edgeBits = (unsigned char)((edge << 4) | (othervert << 2) | basevert); for (int y = loop0; y <= loop1; y++) { int HitIndex = !!hfEdgeHits[y].Hits[0]; hfEdgeHits[y].Hits[HitIndex] = edgeBits; } } // do the edge intersections with vertical planes if (intverts[basevert][0] != intverts[othervert][0]) { int edge0 = intMin(intverts[basevert][0], intverts[othervert][0]); int edge1 = intMax(intverts[basevert][0], intverts[othervert][0]); int loop0 = intMax(edge0 + 1, x0); int loop1 = intMin(edge1, x1_edge); rcReal temppnt[3]; rcReal cx = bmin[0] + cs * loop0; for (int x = loop0; x <= loop1; x++, cx += cs) { intersectX(vertarray[basevert], &edges[edge][0], cx, temppnt); int y = (int)rcFloor((temppnt[2] - bmin[2])*ics); if (y >= y0 && y <= y1) { rcReal sfloat = temppnt[1] - bmin[1]; const int sint = (int)rcClamp((int)rcFloor(sfloat * ich), -RANGE, RANGE); #if TEST_NEW_RASTERIZER rcAssert(sint >= triangle_ismin - 1 && sint <= triangle_ismax + 1); #endif addSpanSample(hf, x, y, sint); addSpanSample(hf, x - 1, y, sint); } } } } { // deal with the horizontal intersections int edge0 = intMin(intverts[0][1], intMin(intverts[1][1],intverts[2][1])); int edge1 = intMax(intverts[0][1], intMax(intverts[1][1],intverts[2][1])); int loop0 = intMax(edge0 + 1, y0); int loop1 = intMin(edge1, y1_edge); rcReal Inter[2][3]; int xInter[2]; rcReal cz = bmin[2] + cs * loop0; for (int y = loop0; y <= loop1; y++, cz += cs) { rcEdgeHit& Hits = hfEdgeHits[y]; if (Hits.Hits[0]) { rcAssert(Hits.Hits[1]); // must have two hits for (int i = 0; i < 2; i++) { int edge = Hits.Hits[i] >> 4; int othervert = (Hits.Hits[i] >> 2) & 3; int basevert = Hits.Hits[i] & 3; CA_SUPPRESS(6385); intersectZ(vertarray[basevert], &edges[edge][0], cz, Inter[i]); int x = (int)rcFloor((Inter[i][0] - bmin[0])*ics); xInter[i] = x; if (x >= x0 && x <= x1) { rcReal sfloat = Inter[i][1] - bmin[1]; const int sint = (int)rcClamp((int)rcFloor(sfloat * ich), -RANGE, RANGE); #if TEST_NEW_RASTERIZER rcAssert(sint >= triangle_ismin - 1 && sint <= triangle_ismax + 1); #endif addSpanSample(hf, x, y, sint); addSpanSample(hf, x, y - 1, sint); } } if (xInter[0] != xInter[1]) { // now fill in the fully contained ones. int left = Inter[1][0] < Inter[0][0]; int xloop0 = intMax(xInter[left] + 1, x0); int xloop1 = intMin(xInter[1 - left], x1_edge); rcReal d = 1.0f / (Inter[1-left][0] - Inter[left][0]); rcReal dy = Inter[1-left][1] - Inter[left][1]; //rcReal ds = dy * d; rcReal ds = 0.0f; rcReal t = rcClamp((rcReal(xloop0)*cs + bmin[0] - Inter[left][0]) * d, 0.0f, 1.0f); rcReal sfloat = (Inter[left][1] + t * dy) - bmin[1]; if (xloop1 - xloop0 > 0) { rcReal t2 = rcClamp((rcReal(xloop1)*cs + bmin[0] - Inter[left][0]) * d, 0.0f, 1.0f); rcReal sfloat2 = (Inter[left][1] + t2 * dy) - bmin[1]; ds = (sfloat2 - sfloat) / rcReal(xloop1 - xloop0); } for (int x = xloop0; x <= xloop1; x++, sfloat += ds) { const int sint = (int)rcClamp((int)rcFloor(sfloat * ich), -RANGE, RANGE); #if TEST_NEW_RASTERIZER rcAssert(sint >= triangle_ismin - 1 && sint <= triangle_ismax + 1); #endif addSpanSample(hf, x, y, sint); addSpanSample(hf, x - 1, y, sint); addSpanSample(hf, x, y - 1, sint); addSpanSample(hf, x - 1, y - 1, sint); } } // reset for next triangle Hits.Hits[0] = 0; Hits.Hits[1] = 0; } } } for (int y = y0; y <= y1; y++) { int xloop0 = intMax(hf.RowExt[y + 1].MinCol, x0); int xloop1 = intMin(hf.RowExt[y + 1].MaxCol, x1); for (int x = xloop0; x <= xloop1; x++) { int idx = SampleIndex(hf, x, y); rcTempSpan& Temp = hf.tempspans[idx]; int smin = Temp.sminmax[0]; // +1 because span sample heights are computed from rcFloor instead of rcCeil (and they need to have a height of at least 1) int smax = UE::Recast::Private::bEnableSpanHeightRasterizationFix ? Temp.sminmax[1] + 1 : Temp.sminmax[1]; //UE // reset for next triangle Temp.sminmax[0] = RANGE; Temp.sminmax[1] = -RANGE; // Skip the span if it is outside the heightfield bbox if (smin >= RC_SPAN_MAX_HEIGHT || smax < 0) continue; smin = intMax(smin, 0); smax = intMin(intMax(smax,smin+1), RC_SPAN_MAX_HEIGHT); const int projectSpanToBottom = rasterizationMasks != nullptr ? (projectTriToBottom & rasterizationMasks[x+y*w]) : projectTriToBottom; //UE if (projectSpanToBottom) //UE { smin = 0; //UE } #if TEST_NEW_RASTERIZER { int outsmin, outsmax; rasterizeTriTest(v0, v1, v2, x, y, outsmin, outsmax, area, hf, bmin, bmax, cs, ics, ich, flagMergeThr); const int tol = 1; if (outsmin > smin + tol || outsmin < smin - tol || outsmax > smax + tol || outsmax < smax - tol ) { Temp.sminmax[0] = RANGE; Temp.sminmax[1] = -RANGE; rasterizeTriTest(v0, v1, v2, x, y, outsmin, outsmax, area, hf, bmin, bmax, cs, ics, ich, flagMergeThr); if (outsmin != RC_SPAN_MAX_HEIGHT) { Temp.sminmax[0] = RANGE; Temp.sminmax[1] = -RANGE; } } } #endif Func.addSpan(hf, x, y, smin, smax, area, flagMergeThr); } // reset for next triangle hf.RowExt[y + 1].MinCol = hf.width + 2; hf.RowExt[y + 1].MaxCol = -2; } } #if TEST_NEW_RASTERIZER for (int y = 0; y < h; y++) { rcAssert(hf.RowExt[y + 1].MinCol == hf.width + 2 && hf.RowExt[y + 1].MaxCol == -2); rcEdgeHit& Hits = hfEdgeHits[y]; rcAssert(!Hits.Hits[0] && !Hits.Hits[1]); for (int x = 0; x < w; x++) { int idx = SampleIndex(hf, x, y); rcTempSpan& Temp = hf.tempspans[idx]; rcAssert(Temp.sminmax[0] == RANGE && Temp.sminmax[1] == -RANGE); } } rcEdgeHit& Hits = hfEdgeHits[h]; rcAssert(!Hits.Hits[0] && !Hits.Hits[1]); #endif } #else static void rasterizeTri(const rcReal* v0, const rcReal* v1, const rcReal* v2, const unsigned char area, rcHeightfield& hf, const rcReal* bmin, const rcReal* bmax, const rcReal cs, const rcReal ics, const rcReal ich, const int flagMergeThr, const int rasterizationFlags, //UE const int* rasterizationMasks) //UE { const int w = hf.width; const int h = hf.height; rcReal tmin[3], tmax[3]; const rcReal by = bmax[1] - bmin[1]; const int projectTriToBottom = rasterizationFlags & RC_PROJECT_TO_BOTTOM; //UE // Calculate the bounding box of the triangle. rcVcopy(tmin, v0); rcVcopy(tmax, v0); rcVmin(tmin, v1); rcVmin(tmin, v2); rcVmax(tmax, v1); rcVmax(tmax, v2); // If the triangle does not touch the bbox of the heightfield, skip the triangle. if (!overlapBounds(bmin, bmax, tmin, tmax)) return; // Calculate the footprint of the triangle on the grid. int x0 = (int)((tmin[0] - bmin[0])*ics); int y0 = (int)((tmin[2] - bmin[2])*ics); int x1 = (int)((tmax[0] - bmin[0])*ics); int y1 = (int)((tmax[2] - bmin[2])*ics); x0 = rcClamp(x0, 0, w-1); y0 = rcClamp(y0, 0, h-1); x1 = rcClamp(x1, 0, w-1); y1 = rcClamp(y1, 0, h-1); // Clip the triangle into all grid cells it touches. rcReal in[7*3], out[7*3], inrow[7*3]; for (int y = y0; y <= y1; ++y) { // Clip polygon to row. rcVcopy(&in[0], v0); rcVcopy(&in[1*3], v1); rcVcopy(&in[2*3], v2); int nvrow = 3; const rcReal cz = bmin[2] + y*cs; nvrow = clipPoly(in, nvrow, out, 0, 1, -cz); if (nvrow < 3) continue; nvrow = clipPoly(out, nvrow, inrow, 0, -1, cz+cs); if (nvrow < 3) continue; for (int x = x0; x <= x1; ++x) { // Clip polygon to column. int nv = nvrow; const rcReal cx = bmin[0] + x*cs; nv = clipPoly(inrow, nv, out, 1, 0, -cx); if (nv < 3) continue; nv = clipPoly(out, nv, in, -1, 0, cx+cs); if (nv < 3) continue; // Calculate min and max of the span. rcReal smin = in[1], smax = in[1]; for (int i = 1; i < nv; ++i) { smin = rcMin(smin, in[i*3+1]); smax = rcMax(smax, in[i*3+1]); } smin -= bmin[1]; smax -= bmin[1]; // Skip the span if it is outside the heightfield bbox if (smax < 0.0f) continue; if (smin > by) continue; // Clamp the span to the heightfield bbox. if (smin < 0.0f) smin = 0; if (smax > by) smax = by; // Snap the span to the heightfield height grid. unsigned short ismin = (unsigned short)rcClamp((int)rcFloor(smin * ich), 0, RC_SPAN_MAX_HEIGHT); unsigned short ismax = (unsigned short)rcClamp((int)rcCeil(smax * ich), (int)ismin+1, RC_SPAN_MAX_HEIGHT); const int projectSpanToBottom = rasterizationMasks != nullptr ? (projectTriToBottom & rasterizationMasks[x+y*w]) : projectTriToBottom; //UE if (projectSpanToBottom) //UE { ismin = 0; //UE } Func.addSpan(hf, x, y, ismin, ismax, area, flagMergeThr); } } } #endif //EPIC_ADDITION_USE_NEW_RECAST_RASTERIZER struct AddSpanInHeightfield { static void addSpan(rcHeightfield& hf, const int x, const int y, const rcSpanUInt smin, const rcSpanUInt smax, const unsigned char area, const int flagMergeThr) { ::addSpan(hf, x, y, smin, smax, area, flagMergeThr); } }; struct AddSpanInTempColumns { AddSpanInTempColumns(rcContext* inctx, rcHeightfield& hf, const float ics, const rcReal* vertsBMin, const rcReal* vertsBMax) { #if EPIC_ADDITION_USE_NEW_RECAST_RASTERIZER ctx = inctx; if (vertsBMin == nullptr || vertsBMax == nullptr) { if (ctx) { ctx->log(RC_LOG_WARNING, "rcRasterizeTriangles called for triangles to rasterize as filled convex but the verts bounds is not valid."); } return; } if (!hf.tempSpanColumns) { if (ctx) { ctx->log(RC_LOG_WARNING, "rcRasterizeTriangles called for triangles to rasterize as filled convex but tempSpanColumns was not allocated."); } return; } // clamp the verts bounding box to the heightfield const rcReal xMin = rcClamp(vertsBMin[0], hf.bmin[0], hf.bmax[0]); const rcReal xMax = rcClamp(vertsBMax[0], hf.bmin[0], hf.bmax[0]); const rcReal zMin = rcClamp(vertsBMin[2], hf.bmin[2], hf.bmax[2]); const rcReal zMax = rcClamp(vertsBMax[2], hf.bmin[2], hf.bmax[2]); if (xMax == xMin || zMax == zMin) { // this can be valid if we receive a geometry that is outside of the height field. No need to log return; } // convert it into heightfield span coords const int ixMin = ics * (xMin - hf.bmin[0]); const int izMin = ics * (zMin - hf.bmin[2]); const int ixMax = ics * (xMax - hf.bmin[0]); const int izMax = ics * (zMax - hf.bmin[2]); ibminx = ixMin; ibminz = izMin; // the max is inclusive so we need to add 1 to width and height ibwidth = rcClamp(ixMax - ixMin + 1, 0, hf.width); ibheight = rcClamp(izMax - izMin + 1, 0, hf.height); memset(hf.tempSpanColumns, 0, sizeof(rcSpanData)*ibwidth*ibheight); // we could keep an array of call idx to avoid doing that too often #endif // EPIC_ADDITION_USE_NEW_RECAST_RASTERIZER } void addSpan(rcHeightfield& hf, const int x, const int z, const rcSpanUInt smin, const rcSpanUInt smax, const unsigned char area, const int flagMergeThr) { #if EPIC_ADDITION_USE_NEW_RECAST_RASTERIZER // convert the heightfield coord to the tempSpanColumns coord const int tempColumnx = x - ibminx; const int tempColumnz = z - ibminz; if (tempColumnx < 0 || tempColumnz < 0 || tempColumnx >= ibwidth || tempColumnz >= ibheight) { // the span is outside of the verts bounding box, we have to skip it because it would mean we access invalid memory if (ctx) { ctx->log(RC_LOG_WARNING, "rcRasterizeTriangles trying to rasterize a triangle as filled convex but some spans are outside of the provided bounding box. Those spans will be skipped."); } return; } const int tempColumnIdx = tempColumnx + (tempColumnz) * ibwidth; rcSpanData& tempColumn = hf.tempSpanColumns[tempColumnIdx]; if (tempColumn.smin == 0 && tempColumn.smax == 0) { // first span added, store it as is tempColumn.smin = smin; tempColumn.smax = smax; tempColumn.area = area; } else { // need to merge with a previously added span mergeSpanData(tempColumn, smin, smax, area, flagMergeThr); } #endif // EPIC_ADDITION_USE_NEW_RECAST_RASTERIZER } void TransferColumnsToHeightfield(rcHeightfield& hf, const float flagMergeThr) { #if EPIC_ADDITION_USE_NEW_RECAST_RASTERIZER // push all the created spans into the heightfield for (int iz = 0; iz < ibheight; iz++) { for (int ix = 0; ix < ibwidth; ix++) { const int tempSpanColumnsIdx = ix + (iz * ibwidth); const rcSpanData& tempSpanColumn = hf.tempSpanColumns[tempSpanColumnsIdx]; if (tempSpanColumn.smin == 0 && tempSpanColumn.smax == 0) { continue; } ::addSpan(hf, ibminx + ix, ibminz + iz, tempSpanColumn.smin, tempSpanColumn.smax, tempSpanColumn.area, flagMergeThr); } } #endif // EPIC_ADDITION_USE_NEW_RECAST_RASTERIZER } bool IsValid() const { return ibwidth > 0 && ibheight > 0; } rcContext* ctx = nullptr; int ibminx = 0; int ibminz = 0; int ibwidth = 0; int ibheight = 0; }; /// @par /// /// No spans will be added if the triangle does not overlap the heightfield grid. /// /// @see rcHeightfield void rcRasterizeTriangle(rcContext* ctx, const rcReal* v0, const rcReal* v1, const rcReal* v2, const unsigned char area, rcHeightfield& solid, const int flagMergeThr, const int rasterizationFlags, const int* rasterizationMasks) //UE { rcAssert(ctx); ctx->startTimer(RC_TIMER_RASTERIZE_TRIANGLES); AddSpanInHeightfield Func; const rcReal ics = 1.0f/solid.cs; const rcReal ich = 1.0f/solid.ch; rasterizeTri(v0, v1, v2, area, solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr, rasterizationFlags, rasterizationMasks, Func); //UE ctx->stopTimer(RC_TIMER_RASTERIZE_TRIANGLES); } /// @par /// /// Spans will only be added for triangles that overlap the heightfield grid. /// /// @see rcHeightfield void rcRasterizeTriangles(rcContext* ctx, const rcReal* verts, const int /*nv*/, const int* tris, const unsigned char* areas, const int nt, rcHeightfield& solid, const int flagMergeThr, const int rasterizationFlags, const int* rasterizationMasks, //UE const rcReal* vertsbmin, const rcReal* vertsbmax) //UE { if (ctx) ctx->startTimer(RC_TIMER_RASTERIZE_TRIANGLES); const rcReal ics = 1.0f/solid.cs; const rcReal ich = 1.0f/solid.ch; const bool bAsFilledVerticalConvexVolume = rasterizationFlags & RC_RASTERIZE_AS_FILLED_CONVEX; if (!bAsFilledVerticalConvexVolume) { AddSpanInHeightfield Func; // Rasterize triangles. for (int i = 0; i < nt; ++i) { const rcReal* v0 = &verts[tris[i*3+0]*3]; const rcReal* v1 = &verts[tris[i*3+1]*3]; const rcReal* v2 = &verts[tris[i*3+2]*3]; // Rasterize. rasterizeTri(v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr, rasterizationFlags, rasterizationMasks, Func); //UE } } else { AddSpanInTempColumns Func(ctx, solid, ics, vertsbmin, vertsbmax); if (Func.IsValid()) { for (int i = 0; i < nt; ++i) { const rcReal* v0 = &verts[tris[i*3+0]*3]; const rcReal* v1 = &verts[tris[i*3+1]*3]; const rcReal* v2 = &verts[tris[i*3+2]*3]; rasterizeTri(v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr, rasterizationFlags, rasterizationMasks, Func); //UE } Func.TransferColumnsToHeightfield(solid, flagMergeThr); } } if (ctx) ctx->stopTimer(RC_TIMER_RASTERIZE_TRIANGLES); } /// @par /// /// Spans will only be added for triangles that overlap the heightfield grid. /// /// @see rcHeightfield void rcRasterizeTriangles(rcContext* ctx, const rcReal* verts, const int /*nv*/, const unsigned short* tris, const unsigned char* areas, const int nt, rcHeightfield& solid, const int flagMergeThr, const int rasterizationFlags, const int* rasterizationMasks, //UE const rcReal* vertsbmin, const rcReal* vertsbmax) //UE { if (ctx) ctx->startTimer(RC_TIMER_RASTERIZE_TRIANGLES); const rcReal ics = 1.0f/solid.cs; const rcReal ich = 1.0f/solid.ch; const bool bAsFilledVerticalConvexVolume = rasterizationFlags & RC_RASTERIZE_AS_FILLED_CONVEX; if (!bAsFilledVerticalConvexVolume) { AddSpanInHeightfield Func; // Rasterize triangles. for (int i = 0; i < nt; ++i) { const rcReal* v0 = &verts[tris[i*3+0]*3]; const rcReal* v1 = &verts[tris[i*3+1]*3]; const rcReal* v2 = &verts[tris[i*3+2]*3]; // Rasterize. rasterizeTri(v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr, rasterizationFlags, rasterizationMasks, Func); //UE } } else { AddSpanInTempColumns Func(ctx, solid, ics, vertsbmin, vertsbmax); if (Func.IsValid()) { for (int i = 0; i < nt; ++i) { const rcReal* v0 = &verts[tris[i*3+0]*3]; const rcReal* v1 = &verts[tris[i*3+1]*3]; const rcReal* v2 = &verts[tris[i*3+2]*3]; rasterizeTri(v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr, rasterizationFlags, rasterizationMasks, Func); //UE } Func.TransferColumnsToHeightfield(solid, flagMergeThr); } } if (ctx) ctx->stopTimer(RC_TIMER_RASTERIZE_TRIANGLES); } /// @par /// /// Spans will only be added for triangles that overlap the heightfield grid. /// /// @see rcHeightfield void rcRasterizeTriangles(rcContext* ctx, const rcReal* verts, const unsigned char* areas, const int nt, rcHeightfield& solid, const int flagMergeThr, const int rasterizationFlags, const int* rasterizationMasks, //UE const rcReal* vertsbmin, const rcReal* vertsbmax) //UE { if (ctx) ctx->startTimer(RC_TIMER_RASTERIZE_TRIANGLES); const rcReal ics = 1.0f/solid.cs; const rcReal ich = 1.0f/solid.ch; const bool bAsFilledVerticalConvexVolume = rasterizationFlags & RC_RASTERIZE_AS_FILLED_CONVEX; if (!bAsFilledVerticalConvexVolume) { AddSpanInHeightfield Func; // Rasterize triangles. for (int i = 0; i < nt; ++i) { const rcReal* v0 = &verts[(i*3+0)*3]; const rcReal* v1 = &verts[(i*3+1)*3]; const rcReal* v2 = &verts[(i*3+2)*3]; // Rasterize. rasterizeTri(v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr, rasterizationFlags, rasterizationMasks, Func); //UE } } else { AddSpanInTempColumns Func(ctx, solid, ics, vertsbmin, vertsbmax); if (Func.IsValid()) { for (int i = 0; i < nt; ++i) { const rcReal* v0 = &verts[(i*3+0)*3]; const rcReal* v1 = &verts[(i*3+1)*3]; const rcReal* v2 = &verts[(i*3+2)*3]; rasterizeTri(v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr, rasterizationFlags, rasterizationMasks, Func); //UE } Func.TransferColumnsToHeightfield(solid, flagMergeThr); } } if (ctx) ctx->stopTimer(RC_TIMER_RASTERIZE_TRIANGLES); }