Ellipse.h 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. // SPDX-FileCopyrightText: 2021 Jorrit Rouwe
  2. // SPDX-License-Identifier: MIT
  3. #pragma once
  4. #include <Math/Float2.h>
  5. namespace JPH {
  6. /// Ellipse centered around the origin
  7. /// @see https://en.wikipedia.org/wiki/Ellipse
  8. class Ellipse
  9. {
  10. public:
  11. /// Construct ellipse with radius A along the X-axis and B along the Y-axis
  12. Ellipse(float inA, float inB) : mA(inA), mB(inB) { JPH_ASSERT(inA > 0.0f); JPH_ASSERT(inB > 0.0f); }
  13. /// Check if inPoint is inside the ellipsse
  14. bool IsInside(const Float2 &inPoint) const
  15. {
  16. return Square(inPoint.x / mA) + Square(inPoint.y / mB) <= 1.0f;
  17. }
  18. /// Get the closest point on the ellipse to inPoint
  19. /// Assumes inPoint is outside the ellipse
  20. /// @see Rotation Joint Limits in Quaterion Space by Gino van den Bergen, section 10.1 in Game Engine Gems 3.
  21. Float2 GetClosestPoint(const Float2 &inPoint) const
  22. {
  23. float a_sq = Square(mA);
  24. float b_sq = Square(mB);
  25. // Equation of ellipse: f(x, y) = (x/a)^2 + (y/b)^2 - 1 = 0 [1]
  26. // Normal on surface: (df/dx, df/dy) = (2 x / a^2, 2 y / b^2)
  27. // Closest point (x', y') on ellipse to point (x, y): (x', y') + t (x / a^2, y / b^2) = (x, y)
  28. // <=> (x', y') = (a^2 x / (t + a^2), b^2 y / (t + b^2))
  29. // Requiring point to be on ellipse (substituting into [1]): g(t) = (a x / (t + a^2))^2 + (b y / (t + b^2))^2 - 1 = 0
  30. // Newton raphson iteration, starting at t = 0
  31. float t = 0.0f;
  32. for (;;)
  33. {
  34. // Calculate g(t)
  35. float t_plus_a_sq = t + a_sq;
  36. float t_plus_b_sq = t + b_sq;
  37. float gt = Square(mA * inPoint.x / t_plus_a_sq) + Square(mB * inPoint.y / t_plus_b_sq) - 1.0f;
  38. // Check if g(t) it is close enough to zero
  39. if (abs(gt) < 1.0e-6f)
  40. return Float2(a_sq * inPoint.x / t_plus_a_sq, b_sq * inPoint.y / t_plus_b_sq);
  41. // Get derivative dg/dt = g'(t) = -2 (b^2 y^2 / (t + b^2)^3 + a^2 x^2 / (t + a^2)^3)
  42. float gt_accent = -2.0f *
  43. (a_sq * Square(inPoint.x) / Cubed(t_plus_a_sq)
  44. + b_sq * Square(inPoint.y) / Cubed(t_plus_b_sq));
  45. // Calculate t for next iteration: tn+1 = tn - g(t) / g'(t)
  46. float tn = t - gt / gt_accent;
  47. t = tn;
  48. }
  49. }
  50. /// Get normal at point inPoint (non-normalized vector)
  51. Float2 GetNormal(const Float2 &inPoint) const
  52. {
  53. // Calculated by [d/dx f(x, y), d/dy f(x, y)], where f(x, y) is the ellipse equation from above
  54. return Float2(inPoint.x / Square(mA), inPoint.y / Square(mB));
  55. }
  56. private:
  57. float mA; ///< Radius along X-axis
  58. float mB; ///< Radius along Y-axis
  59. };
  60. } // JPH