PageRenderTime 41ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/indra/llmath/llsphere.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 371 lines | 235 code | 39 blank | 97 comment | 50 complexity | 9b10fb4d5f8ff8ad553e69c8dbfcd611 MD5 | raw file
Possible License(s): LGPL-2.1
  1. /**
  2. * @file llsphere.cpp
  3. * @author Andrew Meadows
  4. * @brief Simple line class that can compute nearest approach between two lines
  5. *
  6. * $LicenseInfo:firstyear=2007&license=viewerlgpl$
  7. * Second Life Viewer Source Code
  8. * Copyright (C) 2010, Linden Research, Inc.
  9. *
  10. * This library is free software; you can redistribute it and/or
  11. * modify it under the terms of the GNU Lesser General Public
  12. * License as published by the Free Software Foundation;
  13. * version 2.1 of the License only.
  14. *
  15. * This library is distributed in the hope that it will be useful,
  16. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  18. * Lesser General Public License for more details.
  19. *
  20. * You should have received a copy of the GNU Lesser General Public
  21. * License along with this library; if not, write to the Free Software
  22. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  23. *
  24. * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
  25. * $/LicenseInfo$
  26. */
  27. #include "linden_common.h"
  28. #include "llsphere.h"
  29. LLSphere::LLSphere()
  30. : mCenter(0.f, 0.f, 0.f),
  31. mRadius(0.f)
  32. { }
  33. LLSphere::LLSphere( const LLVector3& center, F32 radius)
  34. {
  35. set(center, radius);
  36. }
  37. void LLSphere::set( const LLVector3& center, F32 radius )
  38. {
  39. mCenter = center;
  40. setRadius(radius);
  41. }
  42. void LLSphere::setCenter( const LLVector3& center)
  43. {
  44. mCenter = center;
  45. }
  46. void LLSphere::setRadius( F32 radius)
  47. {
  48. if (radius < 0.f)
  49. {
  50. radius = -radius;
  51. }
  52. mRadius = radius;
  53. }
  54. const LLVector3& LLSphere::getCenter() const
  55. {
  56. return mCenter;
  57. }
  58. F32 LLSphere::getRadius() const
  59. {
  60. return mRadius;
  61. }
  62. // returns 'TRUE' if this sphere completely contains other_sphere
  63. BOOL LLSphere::contains(const LLSphere& other_sphere) const
  64. {
  65. F32 separation = (mCenter - other_sphere.mCenter).length();
  66. return (mRadius >= separation + other_sphere.mRadius) ? TRUE : FALSE;
  67. }
  68. // returns 'TRUE' if this sphere completely contains other_sphere
  69. BOOL LLSphere::overlaps(const LLSphere& other_sphere) const
  70. {
  71. F32 separation = (mCenter - other_sphere.mCenter).length();
  72. return (separation <= mRadius + other_sphere.mRadius) ? TRUE : FALSE;
  73. }
  74. // returns overlap
  75. // negative overlap is closest approach
  76. F32 LLSphere::getOverlap(const LLSphere& other_sphere) const
  77. {
  78. // separation is distance from other_sphere's edge and this center
  79. return (mCenter - other_sphere.mCenter).length() - mRadius - other_sphere.mRadius;
  80. }
  81. bool LLSphere::operator==(const LLSphere& rhs) const
  82. {
  83. // TODO? -- use approximate equality for centers?
  84. return (mRadius == rhs.mRadius
  85. && mCenter == rhs.mCenter);
  86. }
  87. std::ostream& operator<<( std::ostream& output_stream, const LLSphere& sphere)
  88. {
  89. output_stream << "{center=" << sphere.mCenter << "," << "radius=" << sphere.mRadius << "}";
  90. return output_stream;
  91. }
  92. // static
  93. // removes any spheres that are contained in others
  94. void LLSphere::collapse(std::vector<LLSphere>& sphere_list)
  95. {
  96. std::vector<LLSphere>::iterator first_itr = sphere_list.begin();
  97. while (first_itr != sphere_list.end())
  98. {
  99. bool delete_from_front = false;
  100. std::vector<LLSphere>::iterator second_itr = first_itr;
  101. ++second_itr;
  102. while (second_itr != sphere_list.end())
  103. {
  104. if (second_itr->contains(*first_itr))
  105. {
  106. delete_from_front = true;
  107. break;
  108. }
  109. else if (first_itr->contains(*second_itr))
  110. {
  111. sphere_list.erase(second_itr++);
  112. }
  113. else
  114. {
  115. ++second_itr;
  116. }
  117. }
  118. if (delete_from_front)
  119. {
  120. sphere_list.erase(first_itr++);
  121. }
  122. else
  123. {
  124. ++first_itr;
  125. }
  126. }
  127. }
  128. // static
  129. // returns the bounding sphere that contains both spheres
  130. LLSphere LLSphere::getBoundingSphere(const LLSphere& first_sphere, const LLSphere& second_sphere)
  131. {
  132. LLVector3 direction = second_sphere.mCenter - first_sphere.mCenter;
  133. // HACK -- it is possible to get enough floating point error in the
  134. // other getBoundingSphere() method that we have to add some slop
  135. // at the end. Unfortunately, this breaks the link-order invarience
  136. // for the linkability tests... unless we also apply the same slop
  137. // here.
  138. F32 half_milimeter = 0.0005f;
  139. F32 distance = direction.length();
  140. if (0.f == distance)
  141. {
  142. direction.setVec(1.f, 0.f, 0.f);
  143. }
  144. else
  145. {
  146. direction.normVec();
  147. }
  148. // the 'edge' is measured from the first_sphere's center
  149. F32 max_edge = 0.f;
  150. F32 min_edge = 0.f;
  151. max_edge = llmax(max_edge + first_sphere.getRadius(), max_edge + distance + second_sphere.getRadius() + half_milimeter);
  152. min_edge = llmin(min_edge - first_sphere.getRadius(), min_edge + distance - second_sphere.getRadius() - half_milimeter);
  153. F32 radius = 0.5f * (max_edge - min_edge);
  154. LLVector3 center = first_sphere.mCenter + (0.5f * (max_edge + min_edge)) * direction;
  155. return LLSphere(center, radius);
  156. }
  157. // static
  158. // returns the bounding sphere that contains an arbitrary set of spheres
  159. LLSphere LLSphere::getBoundingSphere(const std::vector<LLSphere>& sphere_list)
  160. {
  161. // this algorithm can get relatively inaccurate when the sphere
  162. // collection is 'small' (contained within a bounding sphere of about
  163. // 2 meters or less)
  164. // TODO -- improve the accuracy for small collections of spheres
  165. LLSphere bounding_sphere( LLVector3(0.f, 0.f, 0.f), 0.f );
  166. S32 sphere_count = sphere_list.size();
  167. if (1 == sphere_count)
  168. {
  169. // trivial case -- single sphere
  170. std::vector<LLSphere>::const_iterator sphere_itr = sphere_list.begin();
  171. bounding_sphere = *sphere_itr;
  172. }
  173. else if (2 == sphere_count)
  174. {
  175. // trivial case -- two spheres
  176. std::vector<LLSphere>::const_iterator first_sphere = sphere_list.begin();
  177. std::vector<LLSphere>::const_iterator second_sphere = first_sphere;
  178. ++second_sphere;
  179. bounding_sphere = LLSphere::getBoundingSphere(*first_sphere, *second_sphere);
  180. }
  181. else if (sphere_count > 0)
  182. {
  183. // non-trivial case -- we will approximate the solution
  184. //
  185. // NOTE -- there is a fancy/fast way to do this for large
  186. // numbers of arbirary N-dimensional spheres -- you can look it
  187. // up on the net. We're dealing with 3D spheres at collection
  188. // sizes of 256 spheres or smaller, so we just use this
  189. // brute force method.
  190. // TODO -- perhaps would be worthwile to test for the solution where
  191. // the largest spanning radius just happens to work. That is, where
  192. // there are really two spheres that determine the bounding sphere,
  193. // and all others are contained therein.
  194. // compute the AABB
  195. std::vector<LLSphere>::const_iterator first_itr = sphere_list.begin();
  196. LLVector3 max_corner = first_itr->getCenter() + first_itr->getRadius() * LLVector3(1.f, 1.f, 1.f);
  197. LLVector3 min_corner = first_itr->getCenter() - first_itr->getRadius() * LLVector3(1.f, 1.f, 1.f);
  198. {
  199. std::vector<LLSphere>::const_iterator sphere_itr = sphere_list.begin();
  200. for (++sphere_itr; sphere_itr != sphere_list.end(); ++sphere_itr)
  201. {
  202. LLVector3 center = sphere_itr->getCenter();
  203. F32 radius = sphere_itr->getRadius();
  204. for (S32 i=0; i<3; ++i)
  205. {
  206. if (center.mV[i] + radius > max_corner.mV[i])
  207. {
  208. max_corner.mV[i] = center.mV[i] + radius;
  209. }
  210. if (center.mV[i] - radius < min_corner.mV[i])
  211. {
  212. min_corner.mV[i] = center.mV[i] - radius;
  213. }
  214. }
  215. }
  216. }
  217. // get the starting center and radius from the AABB
  218. LLVector3 diagonal = max_corner - min_corner;
  219. F32 bounding_radius = 0.5f * diagonal.length();
  220. LLVector3 bounding_center = 0.5f * (max_corner + min_corner);
  221. // compute the starting step-size
  222. F32 minimum_radius = 0.5f * llmin(diagonal.mV[VX], llmin(diagonal.mV[VY], diagonal.mV[VZ]));
  223. F32 step_length = bounding_radius - minimum_radius;
  224. S32 step_count = 0;
  225. S32 max_step_count = 12;
  226. F32 half_milimeter = 0.0005f;
  227. // wander the center around in search of tighter solutions
  228. S32 last_dx = 2; // 2 is out of bounds --> no match
  229. S32 last_dy = 2;
  230. S32 last_dz = 2;
  231. while (step_length > half_milimeter
  232. && step_count < max_step_count)
  233. {
  234. // the algorithm for testing the maximum radius could be expensive enough
  235. // that it makes sense to NOT duplicate testing when possible, so we keep
  236. // track of where we last tested, and only test the new points
  237. S32 best_dx = 0;
  238. S32 best_dy = 0;
  239. S32 best_dz = 0;
  240. // sample near the center of the box
  241. bool found_better_center = false;
  242. for (S32 dx = -1; dx < 2; ++dx)
  243. {
  244. for (S32 dy = -1; dy < 2; ++dy)
  245. {
  246. for (S32 dz = -1; dz < 2; ++dz)
  247. {
  248. if (dx == 0 && dy == 0 && dz == 0)
  249. {
  250. continue;
  251. }
  252. // count the number of indecies that match the last_*'s
  253. S32 match_count = 0;
  254. if (last_dx == dx) ++match_count;
  255. if (last_dy == dy) ++match_count;
  256. if (last_dz == dz) ++match_count;
  257. if (match_count == 2)
  258. {
  259. // we've already tested this point
  260. continue;
  261. }
  262. LLVector3 center = bounding_center;
  263. center.mV[VX] += (F32) dx * step_length;
  264. center.mV[VY] += (F32) dy * step_length;
  265. center.mV[VZ] += (F32) dz * step_length;
  266. // compute the radius of the bounding sphere
  267. F32 max_radius = 0.f;
  268. std::vector<LLSphere>::const_iterator sphere_itr;
  269. for (sphere_itr = sphere_list.begin(); sphere_itr != sphere_list.end(); ++sphere_itr)
  270. {
  271. F32 radius = (sphere_itr->getCenter() - center).length() + sphere_itr->getRadius();
  272. if (radius > max_radius)
  273. {
  274. max_radius = radius;
  275. }
  276. }
  277. if (max_radius < bounding_radius)
  278. {
  279. best_dx = dx;
  280. best_dy = dy;
  281. best_dz = dz;
  282. bounding_center = center;
  283. bounding_radius = max_radius;
  284. found_better_center = true;
  285. }
  286. }
  287. }
  288. }
  289. if (found_better_center)
  290. {
  291. // remember where we came from so we can avoid retesting
  292. last_dx = -best_dx;
  293. last_dy = -best_dy;
  294. last_dz = -best_dz;
  295. }
  296. else
  297. {
  298. // reduce the step size
  299. step_length *= 0.5f;
  300. //++step_count;
  301. // reset the last_*'s
  302. last_dx = 2; // 2 is out of bounds --> no match
  303. last_dy = 2;
  304. last_dz = 2;
  305. }
  306. }
  307. // HACK -- it is possible to get enough floating point error for the
  308. // bounding sphere to too small on the order of 10e-6, but we only need
  309. // it to be accurate to within about half a millimeter
  310. bounding_radius += half_milimeter;
  311. // this algorithm can get relatively inaccurate when the sphere
  312. // collection is 'small' (contained within a bounding sphere of about
  313. // 2 meters or less)
  314. // TODO -- fix this
  315. /* debug code
  316. {
  317. std::vector<LLSphere>::const_iterator sphere_itr;
  318. for (sphere_itr = sphere_list.begin(); sphere_itr != sphere_list.end(); ++sphere_itr)
  319. {
  320. F32 radius = (sphere_itr->getCenter() - bounding_center).length() + sphere_itr->getRadius();
  321. if (radius + 0.1f > bounding_radius)
  322. {
  323. std::cout << " rad = " << radius << " bounding - rad = " << (bounding_radius - radius) << std::endl;
  324. }
  325. }
  326. std::cout << "\n" << std::endl;
  327. }
  328. */
  329. bounding_sphere.set(bounding_center, bounding_radius);
  330. }
  331. return bounding_sphere;
  332. }