Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

327 lines
11 KiB

  1. //============ Copyright (c) Valve Corporation, All rights reserved. ============
  2. //
  3. // Code to compute the equation of a plane with a least-squares residual fit.
  4. //
  5. //===============================================================================
  6. #include "vplane.h"
  7. #include "mathlib.h"
  8. #include <algorithm>
  9. using namespace std;
  10. //////////////////////////////////////////////////////////////////////////
  11. // Forward Declarations
  12. //////////////////////////////////////////////////////////////////////////
  13. static const float DETERMINANT_EPSILON = 1e-6f;
  14. template< int PRIMARY_AXIS >
  15. bool ComputeLeastSquaresPlaneFit( const Vector *pPoints, int nNumPoints, VPlane *pFitPlane );
  16. //////////////////////////////////////////////////////////////////////////
  17. // Public Implementation
  18. //////////////////////////////////////////////////////////////////////////
  19. bool ComputeLeastSquaresPlaneFitX( const Vector *pPoints, int nNumPoints, VPlane *pFitPlane )
  20. {
  21. return ComputeLeastSquaresPlaneFit<0>( pPoints, nNumPoints, pFitPlane );
  22. }
  23. bool ComputeLeastSquaresPlaneFitY( const Vector *pPoints, int nNumPoints, VPlane *pFitPlane )
  24. {
  25. return ComputeLeastSquaresPlaneFit<1>( pPoints, nNumPoints, pFitPlane );
  26. }
  27. bool ComputeLeastSquaresPlaneFitZ( const Vector *pPoints, int nNumPoints, VPlane *pFitPlane )
  28. {
  29. return ComputeLeastSquaresPlaneFit<2>( pPoints, nNumPoints, pFitPlane );
  30. }
  31. float ComputeSquaredError( const Vector *pPoints, int nNumPoints, const VPlane *pFitPlane )
  32. {
  33. float flSqrError = 0.0f;
  34. float flError = 0.0f;
  35. for ( int i = 0; i < nNumPoints; ++ i )
  36. {
  37. float flDist = pFitPlane->DistTo( pPoints[i] );
  38. flError += flDist;
  39. flSqrError += flDist * flDist;
  40. }
  41. return flSqrError;
  42. }
  43. //////////////////////////////////////////////////////////////////////////
  44. // Private Implementation
  45. //////////////////////////////////////////////////////////////////////////
  46. // Because this is not a least-squares orthogonal distance fit, an axis must be specified along which residuals are computed.
  47. // A traditional least-squares linear regression computes residuals along the y-axis and fits to a function of x, meaning that vertical lines cannot be properly fit.
  48. // Similarly, this algorithm cannot properly fit planes which lie along a plane parallel to the primary axis
  49. //
  50. // PRIMARY_AXIS
  51. // X = 0
  52. // Y = 1
  53. // Z = 2
  54. template< int PRIMARY_AXIS >
  55. bool ComputeLeastSquaresPlaneFit( const Vector *pPoints, int nNumPoints, VPlane *pFitPlane )
  56. {
  57. memset( pFitPlane, 0, sizeof( VPlane ) );
  58. if ( nNumPoints < 3 )
  59. {
  60. // We must have at least 3 points to fit a plane
  61. return false;
  62. }
  63. Vector vCentroid( 0, 0, 0 ); // averages: x-bar, y-bar, z-bar
  64. Vector vSquaredSums( 0, 0, 0 ); // x => x*x, y => y*y, z => z*z
  65. Vector vCrossSums( 0, 0, 0 ); // x => y*z, y => x*z, z >= x*y
  66. float flNumPoints = ( float )nNumPoints;
  67. for ( int i = 0; i < nNumPoints; ++ i )
  68. {
  69. vCentroid += pPoints[i];
  70. vSquaredSums += pPoints[i] * pPoints[i];
  71. vCrossSums.x += pPoints[i].y * pPoints[i].z;
  72. vCrossSums.y += pPoints[i].x * pPoints[i].z;
  73. vCrossSums.z += pPoints[i].x * pPoints[i].y;
  74. }
  75. vCentroid /= ( float ) nNumPoints;
  76. if ( PRIMARY_AXIS == 0 )
  77. {
  78. // swap X and Z
  79. swap( vCentroid.x, vCentroid.z );
  80. swap( vSquaredSums.x, vSquaredSums.z );
  81. swap( vCrossSums.x, vCrossSums.z );
  82. }
  83. else if ( PRIMARY_AXIS == 1 )
  84. {
  85. // Swap Y and Z
  86. swap( vCentroid.y, vCentroid.z );
  87. swap( vSquaredSums.y, vSquaredSums.z );
  88. swap( vCrossSums.y, vCrossSums.z );
  89. }
  90. // Solve system of equations:
  91. // (example assumes primary axis is Z)
  92. //
  93. // A * ( sum( xi * xi ) - n * vCentroid.x^2 ) + B * ( sum( xi * yi ) - n * vCentroid.x * vCentroid.y ) - sum( xi * zi ) + n * vCentroid.x * vCentroid.z = 0
  94. // A * ( sum( xi * yi ) - n * vCentroid.x * vCentroid.y ) + B * ( sum( yi * yi ) - n * vCentroid.y ^ 2 ) - sum( yi * zi ) + n * vCentroid.y * vCentroid.z = 0
  95. // C = vCentroid.z - A * vCentroid.x - B * vCentroid.y
  96. //
  97. // where z = Ax + By + C
  98. //
  99. // Transform to:
  100. // [ m11 m12 ] [ A ] = [ c1 ]
  101. // [ m21 m22 ] [ B ] = [ c2 ]
  102. //
  103. // M * x = C
  104. // Take the inverse of M, post-multiply by C:
  105. // x = M_inverse * C
  106. float flM11 = vSquaredSums.x - flNumPoints * vCentroid.x * vCentroid.x;
  107. float flM12 = vCrossSums.z - flNumPoints * vCentroid.x * vCentroid.y;
  108. float flC1 = vCrossSums.y - flNumPoints * vCentroid.x * vCentroid.z;
  109. float flM21 = vCrossSums.z - flNumPoints * vCentroid.x * vCentroid.y;
  110. float flM22 = vSquaredSums.y - flNumPoints * vCentroid.y * vCentroid.y;
  111. float flC2 = vCrossSums.x - flNumPoints * vCentroid.y * vCentroid.z;
  112. float flDeterminant = flM11 * flM22 - flM12 * flM21;
  113. if ( fabsf( flDeterminant ) > DETERMINANT_EPSILON )
  114. {
  115. float flInvDeterminant = 1.0f / flDeterminant;
  116. float flA = flInvDeterminant * ( flM22 * flC1 - flM12 * flC2 );
  117. float flB = flInvDeterminant * ( -flM21 * flC1 + flM11 * flC2 );
  118. float flC = vCentroid.z - flA * vCentroid.x - flB * vCentroid.y;
  119. pFitPlane->m_Normal = Vector( -flA, -flB, 1.0f );
  120. float flScale = pFitPlane->m_Normal.NormalizeInPlace();
  121. pFitPlane->m_Dist = flC * 1.0f / flScale;
  122. if ( PRIMARY_AXIS == 0 )
  123. {
  124. // swap X and Z
  125. swap( pFitPlane->m_Normal.x, pFitPlane->m_Normal.z );
  126. }
  127. else if ( PRIMARY_AXIS == 1 )
  128. {
  129. // Swap Y and Z
  130. swap( pFitPlane->m_Normal.y, pFitPlane->m_Normal.z );
  131. }
  132. return true;
  133. }
  134. // Bad determinant
  135. return false;
  136. }
  137. struct Complex_t
  138. {
  139. float r;
  140. float i;
  141. Complex_t() { }
  142. Complex_t( float flR, float flI ) : r( flR ), i( flI ) { }
  143. static Complex_t FromPolar( float flRadius, float flTheta )
  144. {
  145. return Complex_t( flRadius * cosf( flTheta ), flRadius * sinf( flTheta ) );
  146. }
  147. static Complex_t SquareRoot( float flValue )
  148. {
  149. if ( flValue < 0.0f )
  150. {
  151. return Complex_t( 0.0f, sqrtf( -flValue ) );
  152. }
  153. else
  154. {
  155. return Complex_t( sqrtf( flValue ), 0.0f );
  156. }
  157. }
  158. Complex_t operator+( const Complex_t &rhs ) const
  159. {
  160. return Complex_t( r + rhs.r, i + rhs.i );
  161. }
  162. Complex_t operator-( const Complex_t &rhs ) const
  163. {
  164. return Complex_t( r - rhs.r, i - rhs.i );
  165. }
  166. Complex_t operator*( const Complex_t &rhs ) const
  167. {
  168. return Complex_t( r * rhs.r - i * rhs.i, r * rhs.i + i * rhs.r );
  169. }
  170. Complex_t operator*( float rhs ) const
  171. {
  172. return Complex_t( r * rhs, i * rhs );
  173. }
  174. Complex_t operator/( float rhs ) const
  175. {
  176. return Complex_t( r / rhs, i / rhs );
  177. }
  178. Complex_t CubeRoot() const
  179. {
  180. float flRadius = sqrtf( r * r + i * i );
  181. float flTheta = atan2f( i, r );
  182. //if ( flTheta < 0.0f ) flTheta += 2.0f * 3.14159f;
  183. // Demoivre's theorem for principal root
  184. return FromPolar( powf( flRadius, 1.0f / 3.0f ), flTheta / 3.0f );
  185. }
  186. };
  187. // [kutta]
  188. // This code is a work-in-progress; need to write code to robustly find an eigenvector given its eigenvalue.
  189. #if USE_ORTHOGONAL_LEAST_SQUARES
  190. template< int PRIMARY_AXIS = 0 >
  191. bool TryFindEigenvector( float flEigenvalue, const Vector *pMatrix, Vector *pEigenvector )
  192. {
  193. const float flCoefficientEpsilon = 1e-3;
  194. const int nOtherRow1 = ( PRIMARY_AXIS + 1 ) % 3;
  195. const int nOtherRow2 = ( PRIMARY_AXIS + 2 ) % 3;
  196. bool bUseRow1 = fabsf( pMatrix[0][nOtherRow1] / flEigenvalue ) > flCoefficientEpsilon && fabsf( pMatrix[0][nOtherRow2] / flEigenvalue ) > flCoefficientEpsilon );
  197. bool bUseRow2 = fabsf( pMatrix[1][nOtherRow1] / flEigenvalue ) > flCoefficientEpsilon && fabsf( pMatrix[1][nOtherRow2] / flEigenvalue ) > flCoefficientEpsilon );
  198. bool bUseRow3 = fabsf( pMatrix[2][nOtherRow1] / flEigenvalue ) > flCoefficientEpsilon && fabsf( pMatrix[2][nOtherRow2] / flEigenvalue ) > flCoefficientEpsilon );
  199. // ...
  200. }
  201. bool ComputeLeastSquaresOrthogonalPlaneFit( const Vector *pPoints, int nNumPoints, VPlane *pFitPlane )
  202. {
  203. memset( pFitPlane, 0, sizeof( VPlane ) );
  204. if ( nNumPoints < 3 )
  205. {
  206. // We must have at least 3 points to fit a plane
  207. return false;
  208. }
  209. Vector vCentroid( 0, 0, 0 ); // averages: x-bar, y-bar, z-bar
  210. Vector vSquaredSums( 0, 0, 0 ); // x => x*x, y => y*y, z => z*z
  211. Vector vCrossSums( 0, 0, 0 ); // x => y*z, y => x*z, z >= x*y
  212. float flNumPoints = ( float )nNumPoints;
  213. for ( int i = 0; i < nNumPoints; ++ i )
  214. {
  215. vCentroid += pPoints[i];
  216. vSquaredSums += pPoints[i] * pPoints[i];
  217. vCrossSums.x += pPoints[i].y * pPoints[i].z;
  218. vCrossSums.y += pPoints[i].x * pPoints[i].z;
  219. vCrossSums.z += pPoints[i].x * pPoints[i].y;
  220. }
  221. vCentroid /= ( float ) nNumPoints;
  222. // Re-center the squared and cross sums
  223. vSquaredSums.x -= flNumPoints * vCentroid.x * vCentroid.x;
  224. vSquaredSums.y -= flNumPoints * vCentroid.y * vCentroid.y;
  225. vSquaredSums.z -= flNumPoints * vCentroid.z * vCentroid.z;
  226. vCrossSums.x -= flNumPoints * vCentroid.y * vCentroid.z;
  227. vCrossSums.y -= flNumPoints * vCentroid.x * vCentroid.z;
  228. vCrossSums.z -= flNumPoints * vCentroid.x * vCentroid.y;
  229. // Best fit normal occurs at the minimum of the Rayleigh quotient:
  230. //
  231. // n' * M * n
  232. // ----------
  233. // n' * n
  234. //
  235. // Where M is the covariance matrix.
  236. // M is computed from ( A' * A ) where A is a 3xN matrix of x/y/z residuals for each point in the data set.
  237. // Solve for eigenvalues & eigenvectors of 3x3 real symmetric covariance matrix.
  238. // The resulting characteristic polynormial equation is a cubic of the form:
  239. // x^3 + Ax^2 + Bx + C = 0
  240. //
  241. // All roots of the equation and eigenvalues are positive real values; the lowest one corresponds to the eigenvalue which is the normal to the best fit plane.
  242. float flA = -( vSquaredSums.x + vSquaredSums.y + vSquaredSums.z );
  243. float flB = vSquaredSums.x * vSquaredSums.z + vSquaredSums.x * vSquaredSums.y + vSquaredSums.y * vSquaredSums.z - vCrossSums.x * vCrossSums.x - vCrossSums.y * vCrossSums.y - vCrossSums.z * vCrossSums.z;
  244. float flC = -( vSquaredSums.x * vSquaredSums.y * vSquaredSums.z + 2.0f * vCrossSums.x * vCrossSums.y * vCrossSums.z ) + ( vSquaredSums.x * vCrossSums.x * vCrossSums.x + vSquaredSums.y * vCrossSums.y * vCrossSums.y + vSquaredSums.z * vCrossSums.z * vCrossSums.z );
  245. // Using formula for roots of cubic polynomial, see http://en.wikipedia.org/wiki/Cubic_function
  246. float flM = 2.0f * flA * flA * flA - 9.0f * flA * flB + 27.0f * flC;
  247. float flK = flA * flA - 3.0f * flB;
  248. float flN = flM * flM - 4.0f * flK * flK * flK;
  249. Complex_t flSolutions[3];
  250. const Complex_t omega1( -0.5f, 0.5f * sqrtf( 3.0f ) );
  251. const Complex_t omega2( -0.5f, -0.5f * sqrtf( 3.0f ) );
  252. Complex_t complexA = Complex_t( flA, 0.0f );
  253. Complex_t complexM = Complex_t( flM, 0.0f );
  254. Complex_t intermediateA = ( ( complexM + Complex_t::SquareRoot( flN ) ) / 2.0f );
  255. Complex_t intermediateB = ( ( complexM - Complex_t::SquareRoot( flN ) ) / 2.0f );
  256. Complex_t cubeA = intermediateA.CubeRoot();
  257. Complex_t cubeB = intermediateB.CubeRoot();
  258. Complex_t tempA = cubeA * cubeA * cubeA;
  259. Complex_t tempB = cubeB * cubeB * cubeB;
  260. flSolutions[0] = ( complexA + cubeA + cubeB ) * -1.0f / 3.0f;
  261. flSolutions[1] = ( complexA + ( omega2 * cubeA ) + ( omega1 * cubeB ) ) * -1.0f / 3.0f;
  262. flSolutions[2] = ( complexA + ( omega1 * cubeA ) + ( omega2 * cubeB ) ) * -1.0f / 3.0f;
  263. float flMinEigenvalue = MIN( flSolutions[0].r, flSolutions[1].r );
  264. flMinEigenvalue = MIN( flMinEigenvalue, flSolutions[2].r );
  265. // Subtract eigenvalue from the diagonal of the matrix to get a 3x3, real-symmetric, non-invertible matrix.
  266. // Pick 2 non-zero rows from this matrix and construct a system of 2 equations.
  267. return true;
  268. }
  269. #endif // USE_ORTHOGONAL_LEAST_SQUARES