Sphere.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. // This code is in the public domain -- Ignacio Castaño <castano@gmail.com>
  2. #include "Sphere.h"
  3. #include "Vector.inl"
  4. #include "Box.inl"
  5. #include <float.h> // FLT_MAX
  6. using namespace nv;
  7. const float radiusEpsilon = 1e-4f;
  8. Sphere::Sphere(Vector3::Arg p0, Vector3::Arg p1)
  9. {
  10. if (p0 == p1) *this = Sphere(p0);
  11. else {
  12. center = (p0 + p1) * 0.5f;
  13. radius = length(p0 - center) + radiusEpsilon;
  14. float d0 = length(p0 - center);
  15. float d1 = length(p1 - center);
  16. nvDebugCheck(equal(d0, radius - radiusEpsilon));
  17. nvDebugCheck(equal(d1, radius - radiusEpsilon));
  18. }
  19. }
  20. Sphere::Sphere(Vector3::Arg p0, Vector3::Arg p1, Vector3::Arg p2)
  21. {
  22. if (p0 == p1 || p0 == p2) *this = Sphere(p1, p2);
  23. else if (p1 == p2) *this = Sphere(p0, p2);
  24. else {
  25. Vector3 a = p1 - p0;
  26. Vector3 b = p2 - p0;
  27. Vector3 c = cross(a, b);
  28. float denominator = 2.0f * lengthSquared(c);
  29. if (!isZero(denominator)) {
  30. Vector3 d = (lengthSquared(b) * cross(c, a) + lengthSquared(a) * cross(b, c)) / denominator;
  31. center = p0 + d;
  32. radius = length(d) + radiusEpsilon;
  33. float d0 = length(p0 - center);
  34. float d1 = length(p1 - center);
  35. float d2 = length(p2 - center);
  36. nvDebugCheck(equal(d0, radius - radiusEpsilon));
  37. nvDebugCheck(equal(d1, radius - radiusEpsilon));
  38. nvDebugCheck(equal(d2, radius - radiusEpsilon));
  39. }
  40. else {
  41. // @@ This is a specialization of the code below, but really, the only thing we need to do here is to find the two most distant points.
  42. // Compute all possible spheres, invalidate those that do not contain the four points, keep the smallest.
  43. Sphere s0(p1, p2);
  44. float d0 = distanceSquared(s0, p0);
  45. if (d0 > 0) s0.radius = NV_FLOAT_MAX;
  46. Sphere s1(p0, p2);
  47. float d1 = distanceSquared(s1, p1);
  48. if (d1 > 0) s1.radius = NV_FLOAT_MAX;
  49. Sphere s2(p0, p1);
  50. float d2 = distanceSquared(s2, p2);
  51. if (d2 > 0) s1.radius = NV_FLOAT_MAX;
  52. if (s0.radius < s1.radius && s0.radius < s2.radius) {
  53. center = s0.center;
  54. radius = s0.radius;
  55. }
  56. else if (s1.radius < s2.radius) {
  57. center = s1.center;
  58. radius = s1.radius;
  59. }
  60. else {
  61. center = s2.center;
  62. radius = s2.radius;
  63. }
  64. }
  65. }
  66. }
  67. Sphere::Sphere(Vector3::Arg p0, Vector3::Arg p1, Vector3::Arg p2, Vector3::Arg p3)
  68. {
  69. if (p0 == p1 || p0 == p2 || p0 == p3) *this = Sphere(p1, p2, p3);
  70. else if (p1 == p2 || p1 == p3) *this = Sphere(p0, p2, p3);
  71. else if (p2 == p3) *this = Sphere(p0, p1, p2);
  72. else {
  73. // @@ This only works if the points are not coplanar!
  74. Vector3 a = p1 - p0;
  75. Vector3 b = p2 - p0;
  76. Vector3 c = p3 - p0;
  77. float denominator = 2.0f * dot(c, cross(a, b)); // triple product.
  78. if (!isZero(denominator)) {
  79. Vector3 d = (lengthSquared(c) * cross(a, b) + lengthSquared(b) * cross(c, a) + lengthSquared(a) * cross(b, c)) / denominator;
  80. center = p0 + d;
  81. radius = length(d) + radiusEpsilon;
  82. float d0 = length(p0 - center);
  83. float d1 = length(p1 - center);
  84. float d2 = length(p2 - center);
  85. float d3 = length(p3 - center);
  86. nvDebugCheck(equal(d0, radius - radiusEpsilon));
  87. nvDebugCheck(equal(d1, radius - radiusEpsilon));
  88. nvDebugCheck(equal(d2, radius - radiusEpsilon));
  89. nvDebugCheck(equal(d3, radius - radiusEpsilon));
  90. }
  91. else {
  92. // Compute all possible spheres, invalidate those that do not contain the four points, keep the smallest.
  93. Sphere s0(p1, p2, p3);
  94. float d0 = distanceSquared(s0, p0);
  95. if (d0 > 0) s0.radius = NV_FLOAT_MAX;
  96. Sphere s1(p0, p2, p3);
  97. float d1 = distanceSquared(s1, p1);
  98. if (d1 > 0) s1.radius = NV_FLOAT_MAX;
  99. Sphere s2(p0, p1, p3);
  100. float d2 = distanceSquared(s2, p2);
  101. if (d2 > 0) s2.radius = NV_FLOAT_MAX;
  102. Sphere s3(p0, p1, p2);
  103. float d3 = distanceSquared(s3, p3);
  104. if (d3 > 0) s2.radius = NV_FLOAT_MAX;
  105. if (s0.radius < s1.radius && s0.radius < s2.radius && s0.radius < s3.radius) {
  106. center = s0.center;
  107. radius = s0.radius;
  108. }
  109. else if (s1.radius < s2.radius && s1.radius < s3.radius) {
  110. center = s1.center;
  111. radius = s1.radius;
  112. }
  113. else if (s1.radius < s3.radius) {
  114. center = s2.center;
  115. radius = s2.radius;
  116. }
  117. else {
  118. center = s3.center;
  119. radius = s3.radius;
  120. }
  121. }
  122. }
  123. }
  124. float nv::distanceSquared(const Sphere & sphere, const Vector3 & point)
  125. {
  126. return lengthSquared(sphere.center - point) - square(sphere.radius);
  127. }
  128. // Implementation of "MiniBall" based on:
  129. // http://www.flipcode.com/archives/Smallest_Enclosing_Spheres.shtml
  130. static Sphere recurseMini(const Vector3 *P[], uint p, uint b = 0)
  131. {
  132. Sphere MB;
  133. switch(b)
  134. {
  135. case 0:
  136. MB = Sphere(*P[0]);
  137. break;
  138. case 1:
  139. MB = Sphere(*P[-1]);
  140. break;
  141. case 2:
  142. MB = Sphere(*P[-1], *P[-2]);
  143. break;
  144. case 3:
  145. MB = Sphere(*P[-1], *P[-2], *P[-3]);
  146. break;
  147. case 4:
  148. MB = Sphere(*P[-1], *P[-2], *P[-3], *P[-4]);
  149. return MB;
  150. }
  151. for (uint i = 0; i < p; i++)
  152. {
  153. if (distanceSquared(MB, *P[i]) > 0) // Signed square distance to sphere
  154. {
  155. for (uint j = i; j > 0; j--)
  156. {
  157. swap(P[j], P[j-1]);
  158. }
  159. MB = recurseMini(P + 1, i, b + 1);
  160. }
  161. }
  162. return MB;
  163. }
  164. static bool allInside(const Sphere & sphere, const Vector3 * pointArray, const uint pointCount) {
  165. for (uint i = 0; i < pointCount; i++) {
  166. if (distanceSquared(sphere, pointArray[i]) >= NV_EPSILON) {
  167. return false;
  168. }
  169. }
  170. return true;
  171. }
  172. Sphere nv::miniBall(const Vector3 * pointArray, const uint pointCount)
  173. {
  174. nvDebugCheck(pointArray != NULL);
  175. nvDebugCheck(pointCount > 0);
  176. const Vector3 **L = new const Vector3*[pointCount];
  177. for (uint i = 0; i < pointCount; i++) {
  178. L[i] = &pointArray[i];
  179. }
  180. Sphere sphere = recurseMini(L, pointCount);
  181. delete [] L;
  182. nvDebugCheck(allInside(sphere, pointArray, pointCount));
  183. return sphere;
  184. }
  185. // Approximate bounding sphere, based on "An Efficient Bounding Sphere" by Jack Ritter, from "Graphics Gems"
  186. Sphere nv::approximateSphere_Ritter(const Vector3 * pointArray, const uint pointCount)
  187. {
  188. nvDebugCheck(pointArray != NULL);
  189. nvDebugCheck(pointCount > 0);
  190. Vector3 xmin, xmax, ymin, ymax, zmin, zmax;
  191. xmin = xmax = ymin = ymax = zmin = zmax = pointArray[0];
  192. // FIRST PASS: find 6 minima/maxima points
  193. xmin.x = ymin.y = zmin.z = FLT_MAX;
  194. xmax.x = ymax.y = zmax.z = -FLT_MAX;
  195. for (uint i = 0; i < pointCount; i++)
  196. {
  197. const Vector3 & p = pointArray[i];
  198. if (p.x < xmin.x) xmin = p;
  199. if (p.x > xmax.x) xmax = p;
  200. if (p.y < ymin.y) ymin = p;
  201. if (p.y > ymax.y) ymax = p;
  202. if (p.z < zmin.z) zmin = p;
  203. if (p.z > zmax.z) zmax = p;
  204. }
  205. float xspan = lengthSquared(xmax - xmin);
  206. float yspan = lengthSquared(ymax - ymin);
  207. float zspan = lengthSquared(zmax - zmin);
  208. // Set points dia1 & dia2 to the maximally separated pair.
  209. Vector3 dia1 = xmin;
  210. Vector3 dia2 = xmax;
  211. float maxspan = xspan;
  212. if (yspan > maxspan) {
  213. maxspan = yspan;
  214. dia1 = ymin;
  215. dia2 = ymax;
  216. }
  217. if (zspan > maxspan) {
  218. dia1 = zmin;
  219. dia2 = zmax;
  220. }
  221. // |dia1-dia2| is a diameter of initial sphere
  222. // calc initial center
  223. Sphere sphere;
  224. sphere.center = (dia1 + dia2) / 2.0f;
  225. // calculate initial radius**2 and radius
  226. float rad_sq = lengthSquared(dia2 - sphere.center);
  227. sphere.radius = sqrtf(rad_sq);
  228. // SECOND PASS: increment current sphere
  229. for (uint i = 0; i < pointCount; i++)
  230. {
  231. const Vector3 & p = pointArray[i];
  232. float old_to_p_sq = lengthSquared(p - sphere.center);
  233. if (old_to_p_sq > rad_sq) // do r**2 test first
  234. {
  235. // this point is outside of current sphere
  236. float old_to_p = sqrtf(old_to_p_sq);
  237. // calc radius of new sphere
  238. sphere.radius = (sphere.radius + old_to_p) / 2.0f;
  239. rad_sq = sphere.radius * sphere.radius; // for next r**2 compare
  240. float old_to_new = old_to_p - sphere.radius;
  241. // calc center of new sphere
  242. sphere.center = (sphere.radius * sphere.center + old_to_new * p) / old_to_p;
  243. }
  244. }
  245. nvDebugCheck(allInside(sphere, pointArray, pointCount));
  246. return sphere;
  247. }
  248. static float computeSphereRadius(const Vector3 & center, const Vector3 * pointArray, const uint pointCount) {
  249. float maxRadius2 = 0;
  250. for (uint i = 0; i < pointCount; i++)
  251. {
  252. const Vector3 & p = pointArray[i];
  253. float r2 = lengthSquared(center - p);
  254. if (r2 > maxRadius2) {
  255. maxRadius2 = r2;
  256. }
  257. }
  258. return sqrtf(maxRadius2) + radiusEpsilon;
  259. }
  260. Sphere nv::approximateSphere_AABB(const Vector3 * pointArray, const uint pointCount)
  261. {
  262. nvDebugCheck(pointArray != NULL);
  263. nvDebugCheck(pointCount > 0);
  264. Box box;
  265. box.clearBounds();
  266. for (uint i = 0; i < pointCount; i++) {
  267. box.addPointToBounds(pointArray[i]);
  268. }
  269. Sphere sphere;
  270. sphere.center = box.center();
  271. sphere.radius = computeSphereRadius(sphere.center, pointArray, pointCount);
  272. nvDebugCheck(allInside(sphere, pointArray, pointCount));
  273. return sphere;
  274. }
  275. static void computeExtremalPoints(const Vector3 & dir, const Vector3 * pointArray, uint pointCount, Vector3 * minPoint, Vector3 * maxPoint) {
  276. nvDebugCheck(pointCount > 0);
  277. uint mini = 0;
  278. uint maxi = 0;
  279. float minDist = FLT_MAX;
  280. float maxDist = -FLT_MAX;
  281. for (uint i = 0; i < pointCount; i++) {
  282. float d = dot(dir, pointArray[i]);
  283. if (d < minDist) {
  284. minDist = d;
  285. mini = i;
  286. }
  287. if (d > maxDist) {
  288. maxDist = d;
  289. maxi = i;
  290. }
  291. }
  292. nvDebugCheck(minDist != FLT_MAX);
  293. nvDebugCheck(maxDist != -FLT_MAX);
  294. *minPoint = pointArray[mini];
  295. *maxPoint = pointArray[maxi];
  296. }
  297. // EPOS algorithm based on:
  298. // http://www.ep.liu.se/ecp/034/009/ecp083409.pdf
  299. Sphere nv::approximateSphere_EPOS6(const Vector3 * pointArray, uint pointCount)
  300. {
  301. nvDebugCheck(pointArray != NULL);
  302. nvDebugCheck(pointCount > 0);
  303. Vector3 extremalPoints[6];
  304. // Compute 6 extremal points.
  305. computeExtremalPoints(Vector3(1, 0, 0), pointArray, pointCount, extremalPoints+0, extremalPoints+1);
  306. computeExtremalPoints(Vector3(0, 1, 0), pointArray, pointCount, extremalPoints+2, extremalPoints+3);
  307. computeExtremalPoints(Vector3(0, 0, 1), pointArray, pointCount, extremalPoints+4, extremalPoints+5);
  308. Sphere sphere = miniBall(extremalPoints, 6);
  309. sphere.radius = computeSphereRadius(sphere.center, pointArray, pointCount);
  310. nvDebugCheck(allInside(sphere, pointArray, pointCount));
  311. return sphere;
  312. }
  313. Sphere nv::approximateSphere_EPOS14(const Vector3 * pointArray, uint pointCount)
  314. {
  315. nvDebugCheck(pointArray != NULL);
  316. nvDebugCheck(pointCount > 0);
  317. Vector3 extremalPoints[14];
  318. // Compute 14 extremal points.
  319. computeExtremalPoints(Vector3(1, 0, 0), pointArray, pointCount, extremalPoints+0, extremalPoints+1);
  320. computeExtremalPoints(Vector3(0, 1, 0), pointArray, pointCount, extremalPoints+2, extremalPoints+3);
  321. computeExtremalPoints(Vector3(0, 0, 1), pointArray, pointCount, extremalPoints+4, extremalPoints+5);
  322. float d = sqrtf(1.0f/3.0f);
  323. computeExtremalPoints(Vector3(d, d, d), pointArray, pointCount, extremalPoints+6, extremalPoints+7);
  324. computeExtremalPoints(Vector3(-d, d, d), pointArray, pointCount, extremalPoints+8, extremalPoints+9);
  325. computeExtremalPoints(Vector3(-d, -d, d), pointArray, pointCount, extremalPoints+10, extremalPoints+11);
  326. computeExtremalPoints(Vector3(d, -d, d), pointArray, pointCount, extremalPoints+12, extremalPoints+13);
  327. Sphere sphere = miniBall(extremalPoints, 14);
  328. sphere.radius = computeSphereRadius(sphere.center, pointArray, pointCount);
  329. nvDebugCheck(allInside(sphere, pointArray, pointCount));
  330. return sphere;
  331. }