ApprEllipse2.h 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. // David Eberly, Geometric Tools, Redmond WA 98052
  2. // Copyright (c) 1998-2020
  3. // Distributed under the Boost Software License, Version 1.0.
  4. // https://www.boost.org/LICENSE_1_0.txt
  5. // https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt
  6. // Version: 4.0.2019.08.13
  7. #pragma once
  8. // The ellipse in general form is X^t A X + B^t X + C = 0 where A is a
  9. // positive definite 2x2 matrix, B is a 2x1 vector, C is a scalar, and X is
  10. // a 2x1 vector X. Completing the square, (X-U)^t A (X-U) = U^t A U - C
  11. // where U = -0.5 A^{-1} B. Define M = A/(U^t A U - C). The ellipse is
  12. // (X-U)^t M (X-U) = 1. Factor M = R^t D R where R is orthonormal and D is
  13. // diagonal with positive diagonal terms. The ellipse in factored form is
  14. // (X-U)^t R^t D^t R (X-U) = 1. Find the least squares fit of a set of N
  15. // points P[0] through P[N-1]. The return value is the least-squares energy
  16. // function at (U,R,D).
  17. #include <Mathematics/ContOrientedBox2.h>
  18. #include <Mathematics/DistPointHyperellipsoid.h>
  19. #include <Mathematics/Matrix2x2.h>
  20. #include <Mathematics/MinimizeN.h>
  21. namespace WwiseGTE
  22. {
  23. template <typename Real>
  24. class ApprEllipse2
  25. {
  26. public:
  27. Real operator()(int numPoints, Vector2<Real> const* points,
  28. Vector2<Real>& center, Matrix2x2<Real>& rotate, Real diagonal[2]) const
  29. {
  30. // Energy function is E : R^5 -> R where
  31. // V = (V0, V1, V2, V3, V4)
  32. // = (D[0], D[1], U.x, U.y, atan2(R(0,1),R(0,0))).
  33. std::function<Real(Real const*)> energy =
  34. [numPoints, points](Real const* input)
  35. {
  36. return Energy(numPoints, points, input);
  37. };
  38. MinimizeN<Real> minimizer(5, energy, 8, 8, 32);
  39. // The initial guess for the minimizer is based on an oriented box
  40. // that contains the points.
  41. OrientedBox2<Real> box;
  42. GetContainer(numPoints, points, box);
  43. center = box.center;
  44. for (int i = 0; i < 2; ++i)
  45. {
  46. rotate.SetRow(i, box.axis[i]);
  47. diagonal[i] = box.extent[i];
  48. }
  49. Real angle = std::atan2(rotate(0, 1), rotate(0, 0));
  50. Real e0 =
  51. diagonal[0] * std::fabs(rotate(0, 0)) +
  52. diagonal[1] * std::fabs(rotate(1, 0));
  53. Real e1 =
  54. diagonal[0] * std::fabs(rotate(0, 1)) +
  55. diagonal[1] * std::fabs(rotate(1, 1));
  56. Real v0[5] =
  57. {
  58. (Real)0.5 * diagonal[0],
  59. (Real)0.5 * diagonal[1],
  60. center[0] - e0,
  61. center[1] - e1,
  62. -(Real)GTE_C_PI
  63. };
  64. Real v1[5] =
  65. {
  66. (Real)2 * diagonal[0],
  67. (Real)2 * diagonal[1],
  68. center[0] + e0,
  69. center[1] + e1,
  70. (Real)GTE_C_PI
  71. };
  72. Real vInitial[5] =
  73. {
  74. diagonal[0],
  75. diagonal[1],
  76. center[0],
  77. center[1],
  78. angle
  79. };
  80. Real vMin[5], error;
  81. minimizer.GetMinimum(v0, v1, vInitial, vMin, error);
  82. diagonal[0] = vMin[0];
  83. diagonal[1] = vMin[1];
  84. center[0] = vMin[2];
  85. center[1] = vMin[3];
  86. MakeRotation(-vMin[4], rotate);
  87. return error;
  88. }
  89. private:
  90. static Real Energy(int numPoints, Vector2<Real> const* points, Real const* input)
  91. {
  92. // Build rotation matrix.
  93. Matrix2x2<Real> rotate;
  94. MakeRotation(-input[4], rotate);
  95. Ellipse2<Real> ellipse(Vector2<Real>::Zero(), { Vector2<Real>::Unit(0),
  96. Vector2<Real>::Unit(1) }, { input[0], input[1] });
  97. // Transform the points to the coordinate system of center C and
  98. // columns of rotation R.
  99. DCPQuery<Real, Vector2<Real>, Ellipse2<Real>> peQuery;
  100. Real energy = (Real)0;
  101. for (int i = 0; i < numPoints; ++i)
  102. {
  103. Vector2<Real> diff = points[i] - Vector2<Real>{ input[2], input[3] };
  104. Vector2<Real> prod = rotate * diff;
  105. Real dist = peQuery(prod, ellipse).distance;
  106. energy += dist;
  107. }
  108. return energy;
  109. }
  110. };
  111. }