JurassicParkTrespasser/jp2_pc/Source/Lib/Renderer/LineSide2D.cpp
2020-04-01 21:44:07 +02:00

772 lines
18 KiB
C++

/***********************************************************************************************
*
* Copyright © DreamWorks Interactive, 1997.
*
* Contents:
* Implementation of LineSide2D.hpp.
*
* Bugs:
*
* To do:
*
***********************************************************************************************
*
* $Log:: /JP2_PC/Source/Lib/Renderer/LineSide2D.cpp $
*
* 28 9/24/98 6:01p Asouth
* moved _alloca out of parameter list
*
* 27 8/27/98 1:50p Asouth
* Loop variable moved into block scope
*
* 26 5/21/98 10:40p Pkeet
* Fixed crash bugs.
*
* 25 3/10/98 1:20p Pkeet
* Added include to "LocalArray.hpp."
*
* 24 2/23/98 2:28p Agrant
* Made the 'GrahamSubScan' function safer.
*
* 23 97/10/31 5:39p Pkeet
* Replace floating point comparisons with integer comparisons. Changed threshold values
* slightly.
*
* 22 97/10/31 5:05p Pkeet
* Added a test to remove points that are close together and almost colinear.
*
* 21 97/10/31 4:08p Pkeet
* Made the dot product test work.
*
* 20 97/10/30 7:07p Pkeet
* Fixed compile bug.
*
* 19 97/10/30 7:03p Pkeet
* Added the 'RemoveSameAnglePoints' function.
*
* 18 97/10/30 11:14a Pkeet
* Changed the code that removes close point to scale according to the minimum dimensions of the
* point set.
*
* 17 97/10/29 4:00p Pkeet
* Implemented the 'RemoveClosePoints' function.
*
* 16 8/19/97 7:02p Bbell
* Uncommented simplification algorithm.
*
* 15 8/19/97 11:28a Bbell
* Disabled simplification algorithm.
*
* 14 8/18/97 11:55p Pkeet
* Fixed the simplification routine.
*
* 13 8/18/97 11:44a Pkeet
* Removed profile stats.
*
* 12 8/16/97 2:33p Pkeet
* Commented out the simplify call temporarily to prevent a crash bug.
*
* 11 97/08/13 10:56p Pkeet
* Fixed a bug with the initial sort.
*
* 10 97/07/28 2:14p Pkeet
* Still more bugs.
*
* 9 97/07/25 17:00 Speter
* Moved all definitions of cache profile stats to RenderCache.cpp, to solve initialisation
* dependency problems.
*
* 8 97/07/23 11:20a Pkeet
* Fixed bug in Graham's scan.
*
* 7 97/07/18 11:56a Pkeet
* Limited the size of the border.
*
* 6 97/07/17 4:48p Pkeet
* Cleaned up and commented code. Replaced the map type used for sorting in RadialSort with a
* sort array. Interleaved slope and segment information for the radial sort.
*
**********************************************************************************************/
//
// Required includes.
//
#include <math.h>
#include <algorithm>
#include <set>
#include "Common.hpp"
#include "Lib/Std/CircularList.hpp"
#include "LineSide2D.hpp"
#include "Lib/Sys/textout.hpp"
#include "Lib/Sys/Profile.hpp"
#include "Lib/Renderer/RenderCacheInterface.hpp"
#include "LineSide2DTest.hpp"
#include "Lib/Transform/VectorRange.hpp"
#include "Lib/Std/LocalArray.hpp"
//
// Type definitions.
//
// An STL-style container that wraps.
typedef CCircularList< CVector2<> > TCList;
//
// Constants.
//
// Angle threshold for colinear point elimination.
const float fColinearThresholdSqr = Sqr(0.99975f);
// Distance threshold for colinear point elimination using proximity.
const float fNearThresholdSqr = Sqr(0.05f);
// Angle threshold for colinear point elimination.
const float fNearThresholdAngleSqr = Sqr(0.995f);
//
// Function prototypes.
//
//**********************************************************************************************
//
void GrahamSubScan
(
TCList& clist // List to remove points from.
);
//
// Removes points from the list that do not contribute to a convex polygon.
//
//**********************************
//**********************************************************************************************
//
void Simplify
(
TCList& clist, // List of convex points to simplify.
TReal r_scale, // Point to screen point conversion scale.
CVector2<> v2_min, // Minimum bounding rectangle.
CVector2<> v2_max // Maximum bounding rectangle.
);
//
// Simplifies a convex polygon by sides that do not reduce the overall area by much.
//
//**********************************
//**********************************************************************************************
//
void RadialOrder
(
CPArray< CVector2<> >& rpav2, // Input array of points to sort.
TCList& clist // Output list of points.
);
//
// Orders the points in counter clockwise direction from the topmost point.
//
//**********************************
//
// Class definitions.
//
//**********************************************************************************************
//
class CRadial
//
// Object for sorting points radially around a point.
//
// Prefix: rad
//
// Notes:
// Instead of doing a conversion to polar coordinates (an angle), the angle is represented
// by a segment value and a slope (dx/dy or dy/dx).
//
// Points are first place into a segment:
//
// ------------------
// |\ | / |
// | \ 7 | 0 / |
// | \ | / |
// | \ | / |
// | 6 \ | / 1 |
// | \ | / |
// | \ |/ |
// |-----------------
// | /|\ |
// | / | \ |
// | 5 / | \ 2 |
// | / | \ |
// | / | \ |
// | / | \ |
// | / 4 | 3 \ |
// |/ | \|
// -------------------
//
//**********************************
{
public:
uint32 u4BinAngle; // Combined segment and slope data.
CVector2<>* pv2Point; // Pointer to the original vector.
public:
//******************************************************************************************
//
// Constructors.
//
// Default constructor.
CRadial()
{
}
// Constructor from a point and a centre point.
CRadial(const CVector2<>& v2_centre, CVector2<>* pv2_pt)
{
uint32 u4_slope; // Gradient information.
pv2Point = pv2_pt;
float f_x = pv2_pt->tX - v2_centre.tX;
float f_y = pv2_pt->tY - v2_centre.tY;
if (f_x == 0.0f && f_y == 0.0f)
{
u4BinAngle = 0;
return;
}
//
// Select the segment the point lies within.
//
// Half the number of possible segements.
if (f_x >= TReal(0))
{
// Choose from segments 0 to 3.
if (f_y >= TReal(0))
{
// Choose from segments 0 and 1.
if (f_y > f_x)
u4BinAngle = 0;
else
u4BinAngle = 1;
}
else
{
// Choose from segments 2 and 3.
if (f_y > -f_x)
u4BinAngle = 2;
else
u4BinAngle = 3;
}
}
else
{
// Choose from segments 4 to 7.
if (f_y >= TReal(0))
{
// Choose from segments 6 and 7.
if (f_y > -f_x)
u4BinAngle = 7;
else
u4BinAngle = 6;
}
else
{
// Choose from segments 4 and 5.
if (-f_y > -f_x)
u4BinAngle = 4;
else
u4BinAngle = 5;
}
}
//
// Calculate the slope appropriate to the segment.
//
float f_slope;
switch (u4BinAngle)
{
case 0:
case 3:
case 4:
case 7:
f_slope = f_x / f_y;
break;
case 1:
case 2:
case 5:
case 6:
f_slope = f_y / f_x;
break;
default:
Assert(0);
}
f_slope = Abs(f_slope);
u4_slope = u4FromFloat(f_slope);
// Combine the segement with the slope for a single value that can be compared.
if (u4BinAngle & 1)
u4_slope = ~u4_slope;
u4_slope &= 0x7FFFFFFF;
u4_slope >>= 14;
u4BinAngle = (u4BinAngle << 28) | u4_slope;
}
//******************************************************************************************
//
// Member functions.
//
//**************************************************************************************
//
bool operator()(const CRadial& rad_0, const CRadial& rad_1) const
//
// Returns 'true' angle '0' is clockwise from angle '1.'
//
//**************************************
{
return rad_0.u4BinAngle > rad_1.u4BinAngle;
}
};
//
// Function implementations.
//
//**********************************************************************************************
float fGetScale(const TCList& clist)
{
Assert(clist.size() > 2);
// Initialize with the first point in the list.
TCList::iterator it = clist.begin();
CVector2<> v2_min = *it;
CVector2<> v2_max = *it;
// Increment to the next point.
++it;
// Compare all of the points in the circular buffer.
for (; it != clist.end(); ++it)
{
v2_min.tX = Min(v2_min.tX, (*it).tX);
v2_min.tY = Min(v2_min.tY, (*it).tY);
v2_max.tX = Max(v2_max.tX, (*it).tX);
v2_max.tY = Max(v2_max.tY, (*it).tY);
}
// Get the smallest delta.
return Min(v2_max.tX - v2_min.tX, v2_max.tY - v2_min.tY);
}
//**********************************************************************************************
//
inline bool bColinear
(
const CVector2<>& v2_a, // End point.
const CVector2<>& v2_b, // Middle point.
const CVector2<>& v2_c, // End point.
float f_threshold
)
//
// Returns 'true' if the middle point is on the line formed by the two end points.
//
// Notes:
// Uses the dot product. To avoid normalizing the two vectors, the threshold value
// is multiplied by the squares of the two lines.
//
//**************************************
{
bool b_retval = false;
CVector2<> v2_line_a = v2_a - v2_b;
CVector2<> v2_line_b = v2_c - v2_b;
float f_dot = v2_line_a * v2_line_b;
if (u4FromFloat(f_dot) & 0x80000000)
{
float f_dot_sqr = f_dot * f_dot;
float f_a_sqr = v2_line_a.tLenSqr();
float f_b_sqr = v2_line_b.tLenSqr();
float f_ab_sqr = f_a_sqr * f_b_sqr;
float f_abthresh_sqr = fColinearThresholdSqr * f_ab_sqr;
b_retval = u4FromFloat(f_dot_sqr) > u4FromFloat(f_abthresh_sqr);
if (!b_retval)
{
if ((u4FromFloat(f_a_sqr) < u4FromFloat(f_threshold)) ||
(u4FromFloat(f_b_sqr) < u4FromFloat(f_threshold)))
{
f_abthresh_sqr = fNearThresholdAngleSqr * f_ab_sqr;
b_retval = u4FromFloat(f_dot_sqr) > u4FromFloat(f_abthresh_sqr);
}
}
}
return b_retval;
}
//**********************************************************************************************
//
void RemoveColinearPoints
(
TCList& clist, // Circular buffer to remove points from.
float f_scale // Scale.
)
//
// Returns 'true' if the mid point is colinear with the two other points, or forms a concave
// line.
//
//**************************************
{
Assert(clist.size() > 2);
float f_threshold = fNearThresholdSqr * f_scale * f_scale;
int i_count_since_last_removal = 0;
TCList::iterator it = clist.begin();
//
// Iterate through points until the loop goes once through all the points without
// eliminating any.
//
for (;i_count_since_last_removal <= clist.size();)
{
// Get three points.
TCList::iterator it_next = it;
++it_next;
TCList::iterator it_next_next = it_next;
++it_next_next;
// If the middle point is colinear, eliminated it.
if (bColinear(*it, *it_next, *it_next_next, f_threshold))
{
// Reset the counter.
i_count_since_last_removal = 0;
// Remove the point.
clist.erase(it_next);
}
else
{
// Get the next point.
++i_count_since_last_removal;
++it;
}
}
}
//**********************************************************************************************
void GrahamScan(CPArray< CVector2<> >& rpav2, TReal r_scale, const CVector2<>& v2_min,
const CVector2<>& v2_max)
{
Assert(rpav2.atArray);
Assert(rpav2.uLen > 2);
// If there are not points to sort, do nothing.
if (rpav2.uLen < 3)
return;
CCycleTimer ctmr; // Timer object.
// Create the circular buffer.
void* clistBuffer = _alloca(rpav2.uLen * 2 * CCircularList<CVector2<>*>::uSizeOfElement());
TCList clist
(
clistBuffer,
rpav2.uLen * 2
);
//
// Sort points in radial order counter-clockwise from the topmost point.
//
RadialOrder(rpav2, clist);
Assert(clist.size() > 2);
// Debug and statistics information.
CVector2<> v2_centre = *clist.begin();
VerifyRadialOrder(v2_centre, rpav2, clist, (const char*)"Scan1.txt");
//
// Remove points that do not contribute to a convex polygon.
//
GrahamSubScan(clist);
Assert(clist.size() > 2);
VerifyRadialOrder(v2_centre, rpav2, clist, (const char*)"Scan2.txt");
// Find a range scale.
float f_scale = fGetScale(clist);
//
// Remove points that fall approximately on line.
//
RemoveColinearPoints(clist, f_scale);
Assert(clist.size() > 2);
VerifyRadialOrder(v2_centre, rpav2, clist, (const char*)"Scan3.txt");
//
// Remove points that are not significant in the convex polygon.
//
Simplify(clist, r_scale, v2_min, v2_max);
Assert(clist.size() > 2);
VerifyRadialOrder(v2_centre, rpav2, clist, (const char*)"Scan4.txt");
// Rebuild the array.
uint u = 0;
TCList::iterator itc = clist.begin();
do
{
rpav2[u++] = *itc;
++itc;
}
while (itc != clist.begin());
rpav2.uLen = u;
Assert(rpav2.uLen > 2);
// Debug and statistics information.
VerifyRadialOrder(v2_centre, rpav2, clist, (const char*)"ScanFinish.txt");
}
//**********************************************************************************************
void RemovePoint(CPArray<CRadial>& arad, uint u)
{
Assert(arad.uLen > 1);
for (; u < arad.uLen - 1; ++u)
arad[u] = arad[u + 1];
--arad.uLen;
}
//**********************************************************************************************
void RemoveSameAnglePoints(CPArray<CRadial>& arad, const CVector2<>& v2)
{
for (;;)
{
bool b_removed = false;
for (uint u = 0; u < arad.uLen - 1;)
{
if (arad[u].u4BinAngle == arad[u + 1].u4BinAngle)
{
if ((*arad[u].pv2Point - v2).tLenSqr() < (*arad[u + 1].pv2Point - v2).tLenSqr())
RemovePoint(arad, u);
else
RemovePoint(arad, u + 1);
b_removed = true;
}
else
{
++u;
}
}
if (!b_removed)
return;
}
}
//**********************************************************************************************
void RadialOrder(CPArray< CVector2<> >& rpav2, TCList& clist)
{
// If there are not points to sort, do nothing.
if (rpav2.uLen < 2)
return;
#if bOUTPUT_LINESIDE2D_DATA
// Open a file to write to.
CConsoleBuffer con(125, 80);
con.OpenFileSession("SortPointsArray.txt");
#endif // bOUTPUT_LINESIDE2D_DATA
CLArray(CRadial, parad, rpav2.uLen); // Storage for the radial sort array.
// Find the highest point.
uint u_highest = uGetLargestYIndex(rpav2);
CVector2<> v2 = rpav2[u_highest];
// Add points to the array.
uint u;
for (u = 0; u < rpav2.uLen; ++u)
parad[u] = CRadial(v2, &rpav2[u]);
#if bOUTPUT_LINESIDE2D_DATA
con.Print("\nBefore:\n\n");
for (int i = 0; i < parad.uLen; ++i)
PrintPos(v2, *(parad[i].pv2Point), con);
#endif // bOUTPUT_LINESIDE2D_DATA
// Sort the radial array using STL's QSort routine.
std::sort(parad.atArray, parad.atArray + parad.uLen, CRadial());
RemoveSameAnglePoints(parad, v2);
#if bOUTPUT_LINESIDE2D_DATA
con.Print("\n\n\nAfter:\n\n");
for (i = 0; i < parad.uLen; ++i)
PrintPos(v2, *(parad[i].pv2Point), con);
con.CloseFileSession();
#endif // bOUTPUT_LINESIDE2D_DATA
for (u = 0; u < parad.uLen; ++u)
clist.push_back(*parad[u].pv2Point);
// Find the highest point.
TCList::iterator it = clist.begin();
TCList::iterator it_highest = it;
++it;
for (; it != clist.end(); ++it)
{
if ((*it).tY > (*it_highest).tY)
it_highest = it;
}
// Set the beginning of the list to the highest point.
clist.SetBegin(it_highest);
}
//**********************************************************************************************
void GrahamSubScan(TCList& clist)
{
TCList::iterator it_a = clist.begin();
for (;;)
{
// If the list has three points or less, there is no point to a scan.
if (clist.size() <= 4)
return;
// Get the middle point.
TCList::iterator it_b = it_a.itNext();
// Get the end point.
TCList::iterator it_c = it_b.itNext();
// Try to remove point b.
if (iLineSide(*it_a, *it_c, *it_b) != iSIDE_POS)
{
// Remove point b.
clist.erase(it_b);
// Try and move point a back.
if (it_a != clist.begin())
--it_a;
}
else
{
++it_a;
// Loop is done if it wraps.
if (it_a == clist.begin())
return;
}
}
}
//**********************************************************************************************
//
template<class T> bool bSimplify
(
const T& it_a,
const T& it_b,
const T& it_c,
const T& it_d,
CVector2<>& rv3,
TReal r_pix_sqr,
TReal r_min_len,
CVector2<>& v2_min,
CVector2<>& v2_max
)
//
// Returns 'true' if the point set should be simplified.
//
//**************************************
{
// Don't apply simplification to longer line segments.
if (rLineLengthApprox2D((*it_b), (*it_c)) > r_min_len)
return false;
// Find the intersection point.
if (!bIntersection2D(rv3, *it_a, *it_b, *it_c, *it_d))
return false;
if (rv3.tX < v2_min.tX || rv3.tX > v2_max.tX)
return false;
if (rv3.tY < v2_min.tY || rv3.tY > v2_max.tY)
return false;
// If the area is larger than the threshold, preserve the point.
TReal r_area = rTriangleArea2D2(rv3, *it_b, *it_c);
if (r_area > r_pix_sqr)
return true;
return false;
}
//**********************************************************************************************
void Simplify(TCList& clist, TReal r_scale, CVector2<> v2_min, CVector2<> v2_max)
{
// If the list has three points or less, there is no point to a scan.
if (clist.size() <= 3)
return;
// Get the threshold pixel to line area.
TReal r_pix_sqr = rcsRenderCacheSettings.rPixelsPerArea * (r_scale * r_scale);
TReal r_min_len = rcsRenderCacheSettings.rPixelsPerLine * r_scale;
// Create a small border area for the cache.
{
CVector2<> v2_d = (v2_max - v2_min) * TReal(0.05);
// Add a minimum of three pixels to the border.
SetMinMax(v2_d.tX, 3.0f, 8.0f);
SetMinMax(v2_d.tY, 3.0f, 8.0f);
v2_min -= v2_d;
v2_max += v2_d;
}
// Get the beginning of the list.
TCList::iterator it_a = clist.begin();
for (;;)
{
CVector2<> v2_new; // Temporary storage for the newly generated point.
// Get the next three points.
TCList::iterator it_b = it_a.itNext();
TCList::iterator it_c = it_b.itNext();
TCList::iterator it_d = it_c.itNext();
// Try to replace b and c with the intersection of lines ab and cd.
if (bSimplify(it_a, it_b, it_c, it_d, v2_new, r_pix_sqr, r_min_len, v2_min, v2_max))
{
// Insert the new point.
clist.insert(it_d, v2_new);
// Remove points b and c.
clist.erase(it_b);
clist.erase(it_c);
// Try and move point a back.
if (it_a != clist.begin())
--it_a;
}
else
{
++it_a;
// Loop is done if it wraps.
if (it_a == clist.begin())
return;
}
}
}