MinimumVolumeBox3.h 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195
  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. #include <Mathematics/ConvexHull3.h>
  9. #include <Mathematics/MinimumAreaBox2.h>
  10. #include <thread>
  11. // Compute a minimum-volume oriented box containing the specified points. The
  12. // algorithm is really about computing the minimum-volume box containing the
  13. // convex hull of the points, so we must compute the convex hull or you must
  14. // pass an already built hull to the code.
  15. //
  16. // The minimum-volume oriented box has a face coincident with a hull face
  17. // or has three mutually orthogonal edges coincident with three hull edges
  18. // that (of course) are mutually orthogonal.
  19. // J.O'Rourke, "Finding minimal enclosing boxes",
  20. // Internat. J. Comput. Inform. Sci., 14:183-199, 1985.
  21. //
  22. // A detailed description of the algorithm and implementation is found in
  23. // the documents
  24. // https://www.geometrictools.com/Documentation/MinimumVolumeBox.pdf
  25. // https://www.geometrictools.com/Documentation/MinimumAreaRectangle.pdf
  26. //
  27. // NOTE: This algorithm guarantees a correct output only when ComputeType is
  28. // an exact arithmetic type that supports division. In GTEngine, one such
  29. // type is BSRational<UIntegerAP32> (arbitrary precision). Another such type
  30. // is BSRational<UIntegerFP32<N>> (fixed precision), where N is chosen large
  31. // enough for your input data sets. If you choose ComputeType to be 'float'
  32. // or 'double', the output is not guaranteed to be correct.
  33. //
  34. // See GeometricTools/GTEngine/Samples/Geometrics/MinimumVolumeBox3 for an
  35. // example of how to use the code.
  36. namespace WwiseGTE
  37. {
  38. template <typename InputType, typename ComputeType>
  39. class MinimumVolumeBox3
  40. {
  41. public:
  42. // The class is a functor to support computing the minimum-volume box
  43. // of multiple data sets using the same class object. For
  44. // multithreading in ProcessFaces, choose 'numThreads' subject to the
  45. // constraints
  46. // 1 <= numThreads <= std::thread::hardware_concurrency()
  47. // To execute ProcessEdges in a thread separate from the main thread,
  48. // choose 'threadProcessEdges' to 'true'.
  49. MinimumVolumeBox3(unsigned int numThreads = 1, bool threadProcessEdges = false)
  50. :
  51. mNumThreads(numThreads),
  52. mThreadProcessEdges(threadProcessEdges),
  53. mNumPoints(0),
  54. mPoints(nullptr),
  55. mComputePoints(nullptr),
  56. mUseRotatingCalipers(true),
  57. mVolume((InputType)0),
  58. mZero(0),
  59. mOne(1),
  60. mNegOne(-1),
  61. mHalf((InputType)0.5)
  62. {
  63. }
  64. // The points are arbitrary, so we must compute the convex hull from
  65. // them in order to compute the minimum-area box. The input
  66. // parameters are necessary for using ConvexHull3.
  67. OrientedBox3<InputType> operator()(int numPoints, Vector3<InputType> const* points,
  68. InputType epsilon = (InputType)0,
  69. bool useRotatingCalipers = !std::is_floating_point<ComputeType>::value)
  70. {
  71. mNumPoints = numPoints;
  72. mPoints = points;
  73. mUseRotatingCalipers = useRotatingCalipers;
  74. mHull.clear();
  75. mUniqueIndices.clear();
  76. // Get the convex hull of the points.
  77. ConvexHull3<InputType, ComputeType> ch3;
  78. ch3(mNumPoints, mPoints, epsilon);
  79. int dimension = ch3.GetDimension();
  80. OrientedBox3<InputType> itMinBox;
  81. if (dimension == 0)
  82. {
  83. // The points are all effectively the same (using fuzzy
  84. // epsilon).
  85. itMinBox.center = mPoints[0];
  86. itMinBox.axis[0] = Vector3<InputType>::Unit(0);
  87. itMinBox.axis[1] = Vector3<InputType>::Unit(1);
  88. itMinBox.axis[2] = Vector3<InputType>::Unit(2);
  89. itMinBox.extent[0] = (InputType)0;
  90. itMinBox.extent[1] = (InputType)0;
  91. itMinBox.extent[2] = (InputType)0;
  92. mHull.resize(1);
  93. mHull[0] = 0;
  94. return itMinBox;
  95. }
  96. if (dimension == 1)
  97. {
  98. // The points effectively lie on a line (using fuzzy epsilon).
  99. // Determine the extreme t-values for the points represented
  100. // as P = origin + t*direction. We know that 'origin' is an
  101. // input vertex, so we can start both t-extremes at zero.
  102. Line3<InputType> const& line = ch3.GetLine();
  103. InputType tmin = (InputType)0, tmax = (InputType)0;
  104. int imin = 0, imax = 0;
  105. for (int i = 0; i < mNumPoints; ++i)
  106. {
  107. Vector3<InputType> diff = mPoints[i] - line.origin;
  108. InputType t = Dot(diff, line.direction);
  109. if (t > tmax)
  110. {
  111. tmax = t;
  112. imax = i;
  113. }
  114. else if (t < tmin)
  115. {
  116. tmin = t;
  117. imin = i;
  118. }
  119. }
  120. itMinBox.center = line.origin + ((InputType)0.5) * (tmin + tmax) * line.direction;
  121. itMinBox.extent[0] = ((InputType)0.5) * (tmax - tmin);
  122. itMinBox.extent[1] = (InputType)0;
  123. itMinBox.extent[2] = (InputType)0;
  124. itMinBox.axis[0] = line.direction;
  125. ComputeOrthogonalComplement(1, &itMinBox.axis[0]);
  126. mHull.resize(2);
  127. mHull[0] = imin;
  128. mHull[1] = imax;
  129. return itMinBox;
  130. }
  131. if (dimension == 2)
  132. {
  133. // The points effectively line on a plane (using fuzzy
  134. // epsilon). Project the points onto the plane and compute
  135. // the minimum-area bounding box of them.
  136. Plane3<InputType> const& plane = ch3.GetPlane();
  137. // Get a coordinate system relative to the plane of the
  138. // points. Choose the origin to be any of the input points.
  139. Vector3<InputType> origin = mPoints[0];
  140. Vector3<InputType> basis[3];
  141. basis[0] = plane.normal;
  142. ComputeOrthogonalComplement(1, basis);
  143. // Project the input points onto the plane.
  144. std::vector<Vector2<InputType>> projection(mNumPoints);
  145. for (int i = 0; i < mNumPoints; ++i)
  146. {
  147. Vector3<InputType> diff = mPoints[i] - origin;
  148. projection[i][0] = Dot(basis[1], diff);
  149. projection[i][1] = Dot(basis[2], diff);
  150. }
  151. // Compute the minimum area box in 2D.
  152. MinimumAreaBox2<InputType, ComputeType> mab2;
  153. OrientedBox2<InputType> rectangle = mab2(mNumPoints, &projection[0]);
  154. // Lift the values into 3D.
  155. itMinBox.center = origin + rectangle.center[0] * basis[1] + rectangle.center[1] * basis[2];
  156. itMinBox.axis[0] = rectangle.axis[0][0] * basis[1] + rectangle.axis[0][1] * basis[2];
  157. itMinBox.axis[1] = rectangle.axis[1][0] * basis[1] + rectangle.axis[1][1] * basis[2];
  158. itMinBox.axis[2] = basis[0];
  159. itMinBox.extent[0] = rectangle.extent[0];
  160. itMinBox.extent[1] = rectangle.extent[1];
  161. itMinBox.extent[2] = (InputType)0;
  162. mHull = mab2.GetHull();
  163. return itMinBox;
  164. }
  165. // Get the set of unique indices of the hull. This is used to
  166. // project hull vertices onto lines.
  167. ETManifoldMesh const& mesh = ch3.GetHullMesh();
  168. mHull.resize(3 * mesh.GetTriangles().size());
  169. int h = 0;
  170. for (auto const& element : mesh.GetTriangles())
  171. {
  172. for (int i = 0; i < 3; ++i, ++h)
  173. {
  174. int index = element.first.V[i];
  175. mHull[h] = index;
  176. mUniqueIndices.insert(index);
  177. }
  178. }
  179. mComputePoints = ch3.GetQuery().GetVertices();
  180. Box minBox, minBoxEdges;
  181. minBox.volume = mNegOne;
  182. minBoxEdges.volume = mNegOne;
  183. if (mThreadProcessEdges)
  184. {
  185. std::thread doEdges(
  186. [this, &mesh, &minBoxEdges]()
  187. {
  188. ProcessEdges(mesh, minBoxEdges);
  189. });
  190. ProcessFaces(mesh, minBox);
  191. doEdges.join();
  192. }
  193. else
  194. {
  195. ProcessEdges(mesh, minBoxEdges);
  196. ProcessFaces(mesh, minBox);
  197. }
  198. if (minBoxEdges.volume != mNegOne
  199. && minBoxEdges.volume < minBox.volume)
  200. {
  201. minBox = minBoxEdges;
  202. }
  203. ConvertTo(minBox, itMinBox);
  204. mComputePoints = nullptr;
  205. return itMinBox;
  206. }
  207. // The points form a nondegenerate convex polyhedron. The indices
  208. // input must be nonnull and specify the triangle faces.
  209. OrientedBox3<InputType> operator()(int numPoints, Vector3<InputType> const* points,
  210. int numIndices, int const* indices,
  211. bool useRotatingCalipers = !std::is_floating_point<ComputeType>::value)
  212. {
  213. mNumPoints = numPoints;
  214. mPoints = points;
  215. mUseRotatingCalipers = useRotatingCalipers;
  216. mUniqueIndices.clear();
  217. // Build the mesh from the indices. The box construction uses the
  218. // edge map of the mesh.
  219. ETManifoldMesh mesh;
  220. int numTriangles = numIndices / 3;
  221. for (int t = 0; t < numTriangles; ++t)
  222. {
  223. int v0 = *indices++;
  224. int v1 = *indices++;
  225. int v2 = *indices++;
  226. mesh.Insert(v0, v1, v2);
  227. }
  228. // Get the set of unique indices of the hull. This is used to
  229. // project hull vertices onto lines.
  230. mHull.resize(3 * mesh.GetTriangles().size());
  231. int h = 0;
  232. for (auto const& element : mesh.GetTriangles())
  233. {
  234. for (int i = 0; i < 3; ++i, ++h)
  235. {
  236. int index = element.first.V[i];
  237. mHull[h] = index;
  238. mUniqueIndices.insert(index);
  239. }
  240. }
  241. // Create the ComputeType points to be used downstream.
  242. std::vector<Vector3<ComputeType>> computePoints(mNumPoints);
  243. for (auto i : mUniqueIndices)
  244. {
  245. for (int j = 0; j < 3; ++j)
  246. {
  247. computePoints[i][j] = (ComputeType)mPoints[i][j];
  248. }
  249. }
  250. OrientedBox3<InputType> itMinBox;
  251. mComputePoints = &computePoints[0];
  252. Box minBox, minBoxEdges;
  253. minBox.volume = mNegOne;
  254. minBoxEdges.volume = mNegOne;
  255. if (mThreadProcessEdges)
  256. {
  257. std::thread doEdges(
  258. [this, &mesh, &minBoxEdges]()
  259. {
  260. ProcessEdges(mesh, minBoxEdges);
  261. });
  262. ProcessFaces(mesh, minBox);
  263. doEdges.join();
  264. }
  265. else
  266. {
  267. ProcessEdges(mesh, minBoxEdges);
  268. ProcessFaces(mesh, minBox);
  269. }
  270. if (minBoxEdges.volume != mNegOne && minBoxEdges.volume < minBox.volume)
  271. {
  272. minBox = minBoxEdges;
  273. }
  274. ConvertTo(minBox, itMinBox);
  275. mComputePoints = nullptr;
  276. return itMinBox;
  277. }
  278. // Member access.
  279. inline int GetNumPoints() const
  280. {
  281. return mNumPoints;
  282. }
  283. inline Vector3<InputType> const* GetPoints() const
  284. {
  285. return mPoints;
  286. }
  287. inline std::vector<int> const& GetHull() const
  288. {
  289. return mHull;
  290. }
  291. inline InputType GetVolume() const
  292. {
  293. return mVolume;
  294. }
  295. private:
  296. struct Box
  297. {
  298. Vector3<ComputeType> P, U[3];
  299. ComputeType sqrLenU[3], range[3][2], volume;
  300. };
  301. struct ExtrudeRectangle
  302. {
  303. Vector3<ComputeType> U[2];
  304. std::array<int, 4> index;
  305. ComputeType sqrLenU[2], area;
  306. };
  307. // Compute the minimum-volume box relative to each hull face.
  308. void ProcessFaces(ETManifoldMesh const& mesh, Box& minBox)
  309. {
  310. // Get the mesh data structures.
  311. auto const& tmap = mesh.GetTriangles();
  312. auto const& emap = mesh.GetEdges();
  313. // Compute inner-pointing face normals for searching boxes
  314. // supported by a face and an extreme vertex. The indirection in
  315. // triNormalMap, using an integer index instead of the
  316. // normal/sqrlength pair itself, avoids expensive copies when
  317. // using exact arithmetic.
  318. std::vector<Vector3<ComputeType>> normal(tmap.size());
  319. std::map<std::shared_ptr<Triangle>, int> triNormalMap;
  320. int index = 0;
  321. for (auto const& element : tmap)
  322. {
  323. auto tri = element.second;
  324. Vector3<ComputeType> const& v0 = mComputePoints[tri->V[0]];
  325. Vector3<ComputeType> const& v1 = mComputePoints[tri->V[1]];
  326. Vector3<ComputeType> const& v2 = mComputePoints[tri->V[2]];
  327. Vector3<ComputeType> edge1 = v1 - v0;
  328. Vector3<ComputeType> edge2 = v2 - v0;
  329. normal[index] = Cross(edge2, edge1); // inner-pointing normal
  330. if (normal[index] == Vector3<ComputeType>(0))
  331. continue;
  332. triNormalMap[tri] = index++;
  333. }
  334. // Process the triangle faces. For each face, compute the
  335. // polyline of edges that supports the bounding box with a face
  336. // coincident to the triangle face. The projection of the
  337. // polyline onto the plane of the triangle face is a convex
  338. // polygon, so we can use the method of rotating calipers to
  339. // compute its minimum-area box efficiently.
  340. unsigned int numFaces = static_cast<unsigned int>(tmap.size());
  341. if (mNumThreads > 1 && numFaces >= mNumThreads)
  342. {
  343. // Repackage the triangle pointers to support the partitioning
  344. // of faces for multithreaded face processing.
  345. std::vector<std::shared_ptr<Triangle>> triangles;
  346. triangles.reserve(numFaces);
  347. for (auto const& element : tmap)
  348. {
  349. triangles.push_back(element.second);
  350. }
  351. // Partition the data for multiple threads.
  352. unsigned int numFacesPerThread = numFaces / mNumThreads;
  353. std::vector<unsigned int> imin(mNumThreads), imax(mNumThreads);
  354. std::vector<Box> localMinBox(mNumThreads);
  355. for (unsigned int t = 0; t < mNumThreads; ++t)
  356. {
  357. imin[t] = t * numFacesPerThread;
  358. imax[t] = imin[t] + numFacesPerThread - 1;
  359. localMinBox[t].volume = mNegOne;
  360. }
  361. imax[mNumThreads - 1] = numFaces - 1;
  362. // Execute the face processing in multiple threads.
  363. std::vector<std::thread> process(mNumThreads);
  364. for (unsigned int t = 0; t < mNumThreads; ++t)
  365. {
  366. process[t] = std::thread([this, t, &imin, &imax, &triangles,
  367. &normal, &triNormalMap, &emap, &localMinBox]()
  368. {
  369. for (unsigned int i = imin[t]; i <= imax[t]; ++i)
  370. {
  371. auto const& supportTri = triangles[i];
  372. ProcessFace(supportTri, normal, triNormalMap, emap, localMinBox[t]);
  373. }
  374. });
  375. }
  376. // Wait for all threads to finish.
  377. for (unsigned int t = 0; t < mNumThreads; ++t)
  378. {
  379. process[t].join();
  380. // Update the minimum-volume box candidate.
  381. if (minBox.volume == mNegOne || localMinBox[t].volume < minBox.volume)
  382. {
  383. minBox = localMinBox[t];
  384. }
  385. }
  386. }
  387. else
  388. {
  389. for (auto const& element : tmap)
  390. {
  391. auto const& supportTri = element.second;
  392. ProcessFace(supportTri, normal, triNormalMap, emap, minBox);
  393. }
  394. }
  395. }
  396. // Compute the minimum-volume box for each triple of orthgonal hull
  397. // edges.
  398. void ProcessEdges(ETManifoldMesh const& mesh, Box& minBox)
  399. {
  400. // The minimum-volume box can also be supported by three mutually
  401. // orthogonal edges of the convex hull. For each triple of
  402. // orthogonal edges, compute the minimum-volume box for that
  403. // coordinate frame by projecting the points onto the axes of the
  404. // frame. Use a hull vertex as the origin.
  405. int index = mesh.GetTriangles().begin()->first.V[0];
  406. Vector3<ComputeType> const& origin = mComputePoints[index];
  407. Vector3<ComputeType> U[3];
  408. std::array<ComputeType, 3> sqrLenU;
  409. auto const& emap = mesh.GetEdges();
  410. auto e2 = emap.begin(), end = emap.end();
  411. for (/**/; e2 != end; ++e2)
  412. {
  413. U[2] = mComputePoints[e2->first.V[1]] - mComputePoints[e2->first.V[0]];
  414. auto e1 = e2;
  415. for (++e1; e1 != end; ++e1)
  416. {
  417. U[1] = mComputePoints[e1->first.V[1]] - mComputePoints[e1->first.V[0]];
  418. if (Dot(U[1], U[2]) != mZero)
  419. {
  420. continue;
  421. }
  422. sqrLenU[1] = Dot(U[1], U[1]);
  423. auto e0 = e1;
  424. for (++e0; e0 != end; ++e0)
  425. {
  426. U[0] = mComputePoints[e0->first.V[1]] - mComputePoints[e0->first.V[0]];
  427. sqrLenU[0] = Dot(U[0], U[0]);
  428. if (Dot(U[0], U[1]) != mZero || Dot(U[0], U[2]) != mZero)
  429. {
  430. continue;
  431. }
  432. // The three edges are mutually orthogonal. To
  433. // support exact rational arithmetic for volume
  434. // computation, we replace U[2] by a parallel vector.
  435. U[2] = Cross(U[0], U[1]);
  436. sqrLenU[2] = sqrLenU[0] * sqrLenU[1];
  437. // Project the vertices onto the lines containing the
  438. // edges. Use vertex 0 as the origin.
  439. std::array<ComputeType, 3> umin, umax;
  440. for (int j = 0; j < 3; ++j)
  441. {
  442. umin[j] = mZero;
  443. umax[j] = mZero;
  444. }
  445. for (auto i : mUniqueIndices)
  446. {
  447. Vector3<ComputeType> diff = mComputePoints[i] - origin;
  448. for (int j = 0; j < 3; ++j)
  449. {
  450. ComputeType dot = Dot(diff, U[j]);
  451. if (dot < umin[j])
  452. {
  453. umin[j] = dot;
  454. }
  455. else if (dot > umax[j])
  456. {
  457. umax[j] = dot;
  458. }
  459. }
  460. }
  461. ComputeType volume = (umax[0] - umin[0]) / sqrLenU[0];
  462. volume *= (umax[1] - umin[1]) / sqrLenU[1];
  463. volume *= (umax[2] - umin[2]);
  464. // Update current minimum-volume box (if necessary).
  465. if (minBox.volume == mOne || volume < minBox.volume)
  466. {
  467. // The edge keys have unordered vertices, so it is
  468. // possible that {U[0],U[1],U[2]} is a left-handed
  469. // set. We need a right-handed set.
  470. if (DotCross(U[0], U[1], U[2]) < mZero)
  471. {
  472. U[2] = -U[2];
  473. }
  474. minBox.P = origin;
  475. for (int j = 0; j < 3; ++j)
  476. {
  477. minBox.U[j] = U[j];
  478. minBox.sqrLenU[j] = sqrLenU[j];
  479. for (int k = 0; k < 3; ++k)
  480. {
  481. minBox.range[k][0] = umin[k];
  482. minBox.range[k][1] = umax[k];
  483. }
  484. }
  485. minBox.volume = volume;
  486. }
  487. }
  488. }
  489. }
  490. }
  491. // Compute the minimum-volume box relative to a single hull face.
  492. typedef ETManifoldMesh::Triangle Triangle;
  493. void ProcessFace(std::shared_ptr<Triangle> const& supportTri,
  494. std::vector<Vector3<ComputeType>> const& normal,
  495. std::map<std::shared_ptr<Triangle>, int> const& triNormalMap,
  496. ETManifoldMesh::EMap const& emap, Box& localMinBox)
  497. {
  498. // Get the supporting triangle information.
  499. Vector3<ComputeType> const& supportNormal = normal[triNormalMap.find(supportTri)->second];
  500. static const Vector3<ComputeType> sZero = { 0.0, 0.0, 0.0 };
  501. if (supportNormal == sZero)
  502. return;
  503. // Build the polyline of supporting edges. The pair
  504. // (v,polyline[v]) represents an edge directed appropriately
  505. // (see next set of comments).
  506. std::vector<int> polyline(mNumPoints);
  507. int polylineStart = -1;
  508. for (auto const& edgeElement : emap)
  509. {
  510. auto const& edge = *edgeElement.second;
  511. auto const& tri0 = edge.T[0].lock();
  512. auto const& tri1 = edge.T[1].lock();
  513. auto const& normal0 = normal[triNormalMap.find(tri0)->second];
  514. auto const& normal1 = normal[triNormalMap.find(tri1)->second];
  515. ComputeType dot0 = Dot(supportNormal, normal0);
  516. ComputeType dot1 = Dot(supportNormal, normal1);
  517. std::shared_ptr<Triangle> tri;
  518. if (dot0 < mZero && dot1 >= mZero)
  519. {
  520. tri = tri0;
  521. }
  522. else if (dot1 < mZero && dot0 >= mZero)
  523. {
  524. tri = tri1;
  525. }
  526. if (tri)
  527. {
  528. // The edge supports the bounding box. Insert the edge
  529. // in the list using clockwise order relative to tri.
  530. // This will lead to a polyline whose projection onto the
  531. // plane of the hull face is a convex polygon that is
  532. // counterclockwise oriented.
  533. for (int j0 = 2, j1 = 0; j1 < 3; j0 = j1++)
  534. {
  535. if (tri->V[j1] == edge.V[0])
  536. {
  537. if (tri->V[j0] == edge.V[1])
  538. {
  539. polyline[edge.V[1]] = edge.V[0];
  540. }
  541. else
  542. {
  543. polyline[edge.V[0]] = edge.V[1];
  544. }
  545. polylineStart = edge.V[0];
  546. break;
  547. }
  548. }
  549. }
  550. }
  551. if (polylineStart == -1)
  552. return;
  553. // Rearrange the edges to form a closed polyline. For M vertices,
  554. // each ComputeBoxFor*() function starts with the edge from
  555. // closedPolyline[M-1] to closedPolyline[0].
  556. std::vector<int> closedPolyline(mNumPoints);
  557. int numClosedPolyline = 0;
  558. int v = polylineStart;
  559. for (auto& cp : closedPolyline)
  560. {
  561. cp = v;
  562. ++numClosedPolyline;
  563. v = polyline[v];
  564. if (v == polylineStart)
  565. {
  566. break;
  567. }
  568. }
  569. closedPolyline.resize(numClosedPolyline);
  570. // This avoids redundant face testing in the O(n^2) or O(n)
  571. // algorithms, and it simplifies the O(n) implementation.
  572. RemoveCollinearPoints(supportNormal, closedPolyline);
  573. // Compute the box coincident to the hull triangle that has
  574. // minimum area on the face coincident with the triangle.
  575. Box faceBox;
  576. if (mUseRotatingCalipers)
  577. {
  578. ComputeBoxForFaceOrderN(supportNormal, closedPolyline, faceBox);
  579. }
  580. else
  581. {
  582. ComputeBoxForFaceOrderNSqr(supportNormal, closedPolyline, faceBox);
  583. }
  584. // Update the minimum-volume box candidate.
  585. if (localMinBox.volume == mNegOne || faceBox.volume < localMinBox.volume)
  586. {
  587. localMinBox = faceBox;
  588. }
  589. }
  590. // The rotating calipers algorithm has a loop invariant that requires
  591. // the convex polygon not to have collinear points. Any such points
  592. // must be removed first. The code is also executed for the O(n^2)
  593. // algorithm to reduce the number of process edges.
  594. void RemoveCollinearPoints(Vector3<ComputeType> const& N, std::vector<int>& polyline)
  595. {
  596. std::vector<int> tmpPolyline = polyline;
  597. int const numPolyline = static_cast<int>(polyline.size());
  598. int numNoncollinear = 0;
  599. Vector3<ComputeType> ePrev =
  600. mComputePoints[tmpPolyline[0]] - mComputePoints[tmpPolyline.back()];
  601. for (int i0 = 0, i1 = 1; i0 < numPolyline; ++i0)
  602. {
  603. Vector3<ComputeType> eNext =
  604. mComputePoints[tmpPolyline[i1]] - mComputePoints[tmpPolyline[i0]];
  605. ComputeType tsp = DotCross(ePrev, eNext, N);
  606. if (tsp != mZero)
  607. {
  608. polyline[numNoncollinear++] = tmpPolyline[i0];
  609. }
  610. ePrev = eNext;
  611. if (++i1 == numPolyline)
  612. {
  613. i1 = 0;
  614. }
  615. }
  616. polyline.resize(numNoncollinear);
  617. }
  618. // This is the slow order O(n^2) search.
  619. void ComputeBoxForFaceOrderNSqr(Vector3<ComputeType> const& N, std::vector<int> const& polyline, Box& box)
  620. {
  621. // This code processes the polyline terminator associated with a
  622. // convex hull face of inner-pointing normal N. The polyline is
  623. // usually not contained by a plane, and projecting the polyline
  624. // to a convex polygon in a plane perpendicular to N introduces
  625. // floating-point rounding errors. The minimum-area box for the
  626. // projected polyline is computed indirectly to support exact
  627. // rational arithmetic.
  628. box.P = mComputePoints[polyline[0]];
  629. box.U[2] = N;
  630. box.sqrLenU[2] = Dot(N, N);
  631. box.range[1][0] = mZero;
  632. box.volume = mNegOne;
  633. int const numPolyline = static_cast<int>(polyline.size());
  634. for (int i0 = numPolyline - 1, i1 = 0; i1 < numPolyline; i0 = i1++)
  635. {
  636. // Create a coordinate system for the plane perpendicular to
  637. // the face normal and containing a polyline vertex.
  638. Vector3<ComputeType> const& P = mComputePoints[polyline[i0]];
  639. Vector3<ComputeType> E = mComputePoints[polyline[i1]] - mComputePoints[polyline[i0]];
  640. Vector3<ComputeType> U1 = Cross(N, E);
  641. Vector3<ComputeType> U0 = Cross(U1, N);
  642. // Compute the smallest rectangle containing the projected
  643. // polyline.
  644. ComputeType min0 = mZero, max0 = mZero, max1 = mZero;
  645. for (int j = 0; j < numPolyline; ++j)
  646. {
  647. Vector3<ComputeType> diff = mComputePoints[polyline[j]] - P;
  648. ComputeType dot = Dot(U0, diff);
  649. if (dot < min0)
  650. {
  651. min0 = dot;
  652. }
  653. else if (dot > max0)
  654. {
  655. max0 = dot;
  656. }
  657. dot = Dot(U1, diff);
  658. if (dot > max1)
  659. {
  660. max1 = dot;
  661. }
  662. }
  663. // The true area is Area(rectangle)*Length(N). After the
  664. // smallest scaled-area rectangle is computed and returned,
  665. // the box.volume is updated to be the actual squared volume
  666. // of the box.
  667. ComputeType sqrLenU1 = Dot(U1, U1);
  668. ComputeType volume = (max0 - min0) * max1 / sqrLenU1;
  669. if (box.volume == mNegOne || volume < box.volume)
  670. {
  671. box.P = P;
  672. box.U[0] = U0;
  673. box.U[1] = U1;
  674. box.sqrLenU[0] = sqrLenU1 * box.sqrLenU[2];
  675. box.sqrLenU[1] = sqrLenU1;
  676. box.range[0][0] = min0;
  677. box.range[0][1] = max0;
  678. box.range[1][1] = max1;
  679. box.volume = volume;
  680. }
  681. }
  682. // Compute the range of points in the support-normal direction.
  683. box.range[2][0] = mZero;
  684. box.range[2][1] = mZero;
  685. for (auto i : mUniqueIndices)
  686. {
  687. Vector3<ComputeType> diff = mComputePoints[i] - box.P;
  688. ComputeType height = Dot(box.U[2], diff);
  689. if (height < box.range[2][0])
  690. {
  691. box.range[2][0] = height;
  692. }
  693. else if (height > box.range[2][1])
  694. {
  695. box.range[2][1] = height;
  696. }
  697. }
  698. // Compute the actual volume.
  699. box.volume *= (box.range[2][1] - box.range[2][0]) / box.sqrLenU[2];
  700. }
  701. // This is the rotating calipers version, which is O(n).
  702. void ComputeBoxForFaceOrderN(Vector3<ComputeType> const& N, std::vector<int> const& polyline, Box& box)
  703. {
  704. // This code processes the polyline terminator associated with a
  705. // convex hull face of inner-pointing normal N. The polyline is
  706. // usually not contained by a plane, and projecting the polyline
  707. // to a convex polygon in a plane perpendicular to N introduces
  708. // floating-point rounding errors. The minimum-area box for the
  709. // projected polyline is computed indirectly to support exact
  710. // rational arithmetic.
  711. // When the bounding box corresponding to a polyline edge is
  712. // computed, we mark the edge as visited. If the edge is
  713. // encountered later, the algorithm terminates.
  714. std::vector<bool> visited(polyline.size());
  715. std::fill(visited.begin(), visited.end(), false);
  716. // Start the minimum-area rectangle search with the edge from the
  717. // last polyline vertex to the first. When updating the extremes,
  718. // we want the bottom-most point on the left edge, the top-most
  719. // point on the right edge, the left-most point on the top edge,
  720. // and the right-most point on the bottom edge. The polygon edges
  721. // starting at these points are then guaranteed not to coincide
  722. // with a box edge except when an extreme point is shared by two
  723. // box edges (at a corner).
  724. ExtrudeRectangle minRct =
  725. SmallestRectangle((int)polyline.size() - 1, 0, N, polyline);
  726. visited[minRct.index[0]] = true;
  727. // Execute the rotating calipers algorithm.
  728. ExtrudeRectangle rct = minRct;
  729. for (size_t i = 0; i < polyline.size(); ++i)
  730. {
  731. std::array<std::pair<ComputeType, int>, 4> A;
  732. int numA;
  733. if (!ComputeAngles(N, polyline, rct, A, numA))
  734. {
  735. // The polyline projects to a rectangle, so the search is
  736. // over.
  737. break;
  738. }
  739. // Indirectly sort the A-array.
  740. std::array<int, 4> sort = SortAngles(A, numA);
  741. // Update the supporting indices (rct.index[]) and the
  742. // rectangle axis directions (rct.U[]).
  743. if (!UpdateSupport(A, numA, sort, N, polyline, visited, rct))
  744. {
  745. // We have already processed the rectangle polygon edge,
  746. // so the search is over.
  747. break;
  748. }
  749. if (rct.area < minRct.area)
  750. {
  751. minRct = rct;
  752. }
  753. }
  754. // Store relevant box information for computing volume and
  755. // converting to an InputType bounding box.
  756. box.P = mComputePoints[polyline[minRct.index[0]]];
  757. box.U[0] = minRct.U[0];
  758. box.U[1] = minRct.U[1];
  759. box.U[2] = N;
  760. box.sqrLenU[0] = minRct.sqrLenU[0];
  761. box.sqrLenU[1] = minRct.sqrLenU[1];
  762. box.sqrLenU[2] = Dot(box.U[2], box.U[2]);
  763. // Compute the range of points in the plane perpendicular to the
  764. // support normal.
  765. box.range[0][0] = Dot(box.U[0], mComputePoints[polyline[minRct.index[3]]] - box.P);
  766. box.range[0][1] = Dot(box.U[0], mComputePoints[polyline[minRct.index[1]]] - box.P);
  767. box.range[1][0] = mZero;
  768. box.range[1][1] = Dot(box.U[1], mComputePoints[polyline[minRct.index[2]]] - box.P);
  769. // Compute the range of points in the support-normal direction.
  770. box.range[2][0] = mZero;
  771. box.range[2][1] = mZero;
  772. for (auto i : mUniqueIndices)
  773. {
  774. Vector3<ComputeType> diff = mComputePoints[i] - box.P;
  775. ComputeType height = Dot(box.U[2], diff);
  776. if (height < box.range[2][0])
  777. {
  778. box.range[2][0] = height;
  779. }
  780. else if (height > box.range[2][1])
  781. {
  782. box.range[2][1] = height;
  783. }
  784. }
  785. // Compute the actual volume.
  786. box.volume =
  787. (box.range[0][1] - box.range[0][0]) *
  788. ((box.range[1][1] - box.range[1][0]) / box.sqrLenU[1]) *
  789. ((box.range[2][1] - box.range[2][0]) / box.sqrLenU[2]);
  790. }
  791. // Compute the smallest rectangle for the polyline edge <V[i0],V[i1]>.
  792. ExtrudeRectangle SmallestRectangle(int i0, int i1, Vector3<ComputeType> const& N, std::vector<int> const& polyline)
  793. {
  794. ExtrudeRectangle rct;
  795. Vector3<ComputeType> E = mComputePoints[polyline[i1]] - mComputePoints[polyline[i0]];
  796. rct.U[1] = Cross(N, E);
  797. rct.U[0] = Cross(rct.U[1], N);
  798. rct.index = { i1, i1, i1, i1 };
  799. rct.sqrLenU[0] = Dot(rct.U[0], rct.U[0]);
  800. rct.sqrLenU[1] = Dot(rct.U[1], rct.U[1]);
  801. Vector3<ComputeType> const& origin = mComputePoints[polyline[i1]];
  802. Vector2<ComputeType> support[4];
  803. for (int j = 0; j < 4; ++j)
  804. {
  805. support[j] = { mZero, mZero };
  806. }
  807. int i = 0;
  808. for (auto p : polyline)
  809. {
  810. Vector3<ComputeType> diff = mComputePoints[p] - origin;
  811. Vector2<ComputeType> v = { Dot(rct.U[0], diff), Dot(rct.U[1], diff) };
  812. // The right-most vertex of the bottom edge is vertices[i1].
  813. // The assumption of no triple of collinear vertices
  814. // guarantees that box.index[0] is i1, which is the initial
  815. // value assigned at the beginning of this function.
  816. // Therefore, there is no need to test for other vertices
  817. // farther to the right than vertices[i1].
  818. if (v[0] > support[1][0] ||
  819. (v[0] == support[1][0] && v[1] > support[1][1]))
  820. {
  821. // New right maximum OR same right maximum but closer
  822. // to top.
  823. rct.index[1] = i;
  824. support[1] = v;
  825. }
  826. if (v[1] > support[2][1] ||
  827. (v[1] == support[2][1] && v[0] < support[2][0]))
  828. {
  829. // New top maximum OR same top maximum but closer
  830. // to left.
  831. rct.index[2] = i;
  832. support[2] = v;
  833. }
  834. if (v[0] < support[3][0] ||
  835. (v[0] == support[3][0] && v[1] < support[3][1]))
  836. {
  837. // New left minimum OR same left minimum but closer
  838. // to bottom.
  839. rct.index[3] = i;
  840. support[3] = v;
  841. }
  842. ++i;
  843. }
  844. // The comment in the loop has the implication that
  845. // support[0] = { 0, 0 }, so the scaled height
  846. // (support[2][1] - support[0][1]) is simply support[2][1].
  847. ComputeType scaledWidth = support[1][0] - support[3][0];
  848. ComputeType scaledHeight = support[2][1];
  849. rct.area = scaledWidth * scaledHeight / rct.sqrLenU[1];
  850. return rct;
  851. }
  852. // Compute (sin(angle))^2 for the polyline edges emanating from the
  853. // support vertices of the rectangle. The return value is 'true' if
  854. // at least one angle is in [0,pi/2); otherwise, the return value is
  855. // 'false' and the original polyline must project to a rectangle.
  856. bool ComputeAngles(Vector3<ComputeType> const& N,
  857. std::vector<int> const& polyline, ExtrudeRectangle const& rct,
  858. std::array<std::pair<ComputeType, int>, 4>& A, int& numA) const
  859. {
  860. int const numPolyline = static_cast<int>(polyline.size());
  861. numA = 0;
  862. for (int k0 = 3, k1 = 0; k1 < 4; k0 = k1++)
  863. {
  864. if (rct.index[k0] != rct.index[k1])
  865. {
  866. // The rct edges are ordered in k1 as U[0], U[1],
  867. // -U[0], -U[1].
  868. int lookup = (k0 & 1);
  869. Vector3<ComputeType> D = ((k0 & 2) ? -rct.U[lookup] : rct.U[lookup]);
  870. int j0 = rct.index[k0], j1 = j0 + 1;
  871. if (j1 == numPolyline)
  872. {
  873. j1 = 0;
  874. }
  875. Vector3<ComputeType> E = mComputePoints[polyline[j1]] - mComputePoints[polyline[j0]];
  876. Vector3<ComputeType> Eperp = Cross(N, E);
  877. E = Cross(Eperp, N);
  878. Vector3<ComputeType> DxE = Cross(D, E);
  879. ComputeType csqrlen = Dot(DxE, DxE);
  880. ComputeType dsqrlen = rct.sqrLenU[lookup];
  881. ComputeType esqrlen = Dot(E, E);
  882. ComputeType sinThetaSqr = csqrlen / (dsqrlen * esqrlen);
  883. A[numA++] = std::make_pair(sinThetaSqr, k0);
  884. }
  885. }
  886. return numA > 0;
  887. }
  888. // Sort the angles indirectly. The sorted indices are returned. This
  889. // avoids swapping elements of A[], which can be expensive when
  890. // ComputeType is an exact rational type.
  891. std::array<int, 4> SortAngles(std::array<std::pair<ComputeType, int>, 4> const& A, int numA) const
  892. {
  893. std::array<int, 4> sort = { 0, 1, 2, 3 };
  894. if (numA > 1)
  895. {
  896. if (numA == 2)
  897. {
  898. if (A[sort[0]].first > A[sort[1]].first)
  899. {
  900. std::swap(sort[0], sort[1]);
  901. }
  902. }
  903. else if (numA == 3)
  904. {
  905. if (A[sort[0]].first > A[sort[1]].first)
  906. {
  907. std::swap(sort[0], sort[1]);
  908. }
  909. if (A[sort[0]].first > A[sort[2]].first)
  910. {
  911. std::swap(sort[0], sort[2]);
  912. }
  913. if (A[sort[1]].first > A[sort[2]].first)
  914. {
  915. std::swap(sort[1], sort[2]);
  916. }
  917. }
  918. else // numA == 4
  919. {
  920. if (A[sort[0]].first > A[sort[1]].first)
  921. {
  922. std::swap(sort[0], sort[1]);
  923. }
  924. if (A[sort[2]].first > A[sort[3]].first)
  925. {
  926. std::swap(sort[2], sort[3]);
  927. }
  928. if (A[sort[0]].first > A[sort[2]].first)
  929. {
  930. std::swap(sort[0], sort[2]);
  931. }
  932. if (A[sort[1]].first > A[sort[3]].first)
  933. {
  934. std::swap(sort[1], sort[3]);
  935. }
  936. if (A[sort[1]].first > A[sort[2]].first)
  937. {
  938. std::swap(sort[1], sort[2]);
  939. }
  940. }
  941. }
  942. return sort;
  943. }
  944. bool UpdateSupport(std::array<std::pair<ComputeType, int>, 4> const& A, int numA,
  945. std::array<int, 4> const& sort, Vector3<ComputeType> const& N, std::vector<int> const& polyline,
  946. std::vector<bool>& visited, ExtrudeRectangle& rct)
  947. {
  948. // Replace the support vertices of those edges attaining minimum
  949. // angle with the other endpoints of the edges.
  950. int const numPolyline = static_cast<int>(polyline.size());
  951. auto const& amin = A[sort[0]];
  952. for (int k = 0; k < numA; ++k)
  953. {
  954. auto const& a = A[sort[k]];
  955. if (a.first == amin.first)
  956. {
  957. if (++rct.index[a.second] == numPolyline)
  958. {
  959. rct.index[a.second] = 0;
  960. }
  961. }
  962. else
  963. {
  964. break;
  965. }
  966. }
  967. int bottom = rct.index[amin.second];
  968. if (visited[bottom])
  969. {
  970. // We have already processed this polyline edge.
  971. return false;
  972. }
  973. visited[bottom] = true;
  974. // Cycle the vertices so that the bottom support occurs first.
  975. std::array<int, 4> nextIndex;
  976. for (int k = 0; k < 4; ++k)
  977. {
  978. nextIndex[k] = rct.index[(amin.second + k) % 4];
  979. }
  980. rct.index = nextIndex;
  981. // Compute the rectangle axis directions.
  982. int j1 = rct.index[0], j0 = j1 - 1;
  983. if (j1 < 0)
  984. {
  985. j1 = numPolyline - 1;
  986. }
  987. Vector3<ComputeType> E =
  988. mComputePoints[polyline[j1]] - mComputePoints[polyline[j0]];
  989. rct.U[1] = Cross(N, E);
  990. rct.U[0] = Cross(rct.U[1], N);
  991. rct.sqrLenU[0] = Dot(rct.U[0], rct.U[0]);
  992. rct.sqrLenU[1] = Dot(rct.U[1], rct.U[1]);
  993. // Compute the rectangle area.
  994. Vector3<ComputeType> diff[2] =
  995. {
  996. mComputePoints[polyline[rct.index[1]]]
  997. - mComputePoints[polyline[rct.index[3]]],
  998. mComputePoints[polyline[rct.index[2]]]
  999. - mComputePoints[polyline[rct.index[0]]]
  1000. };
  1001. ComputeType scaledWidth = Dot(rct.U[0], diff[0]);
  1002. ComputeType scaledHeight = Dot(rct.U[1], diff[1]);
  1003. rct.area = scaledWidth * scaledHeight / rct.sqrLenU[1];
  1004. return true;
  1005. }
  1006. // Convert the extruded box to the minimum-volume box of InputType.
  1007. // When the ComputeType is an exact rational type, the conversions are
  1008. // performed to avoid precision loss until necessary at the last step.
  1009. void ConvertTo(Box const& minBox, OrientedBox3<InputType>& itMinBox)
  1010. {
  1011. Vector3<ComputeType> center = minBox.P;
  1012. for (int i = 0; i < 3; ++i)
  1013. {
  1014. ComputeType average = mHalf * (minBox.range[i][0] + minBox.range[i][1]);
  1015. center += (average / minBox.sqrLenU[i]) * minBox.U[i];
  1016. }
  1017. // Calculate the squared extent using ComputeType to avoid loss of
  1018. // precision before computing a squared root.
  1019. Vector3<ComputeType> sqrExtent;
  1020. for (int i = 0; i < 3; ++i)
  1021. {
  1022. sqrExtent[i] = mHalf * (minBox.range[i][1] - minBox.range[i][0]);
  1023. sqrExtent[i] *= sqrExtent[i];
  1024. sqrExtent[i] /= minBox.sqrLenU[i];
  1025. }
  1026. for (int i = 0; i < 3; ++i)
  1027. {
  1028. itMinBox.center[i] = (InputType)center[i];
  1029. itMinBox.extent[i] = std::sqrt((InputType)sqrExtent[i]);
  1030. // Before converting to floating-point, factor out the maximum
  1031. // component using ComputeType to generate rational numbers in
  1032. // a range that avoids loss of precision during the conversion
  1033. // and normalization.
  1034. Vector3<ComputeType> const& axis = minBox.U[i];
  1035. ComputeType cmax = std::max<ComputeType>(std::abs((double)axis[0]), std::abs((double)axis[1]));
  1036. cmax = std::max<ComputeType>(cmax, std::abs((double)axis[2]));
  1037. ComputeType invCMax = mOne / cmax;
  1038. for (int j = 0; j < 3; ++j)
  1039. {
  1040. itMinBox.axis[i][j] = (InputType)(axis[j] * invCMax);
  1041. }
  1042. Normalize(itMinBox.axis[i]);
  1043. }
  1044. mVolume = (InputType)minBox.volume;
  1045. }
  1046. // The code is multithreaded, both for convex hull computation and
  1047. // computing minimum-volume extruded boxes for the hull faces. The
  1048. // default value is 1, which implies a single-threaded computation (on
  1049. // the main thread).
  1050. unsigned int mNumThreads;
  1051. bool mThreadProcessEdges;
  1052. // The input points to be bound.
  1053. int mNumPoints;
  1054. Vector3<InputType> const* mPoints;
  1055. // The ComputeType conversions of the input points. Only points of
  1056. // the convex hull (vertices of a convex polyhedron) are converted
  1057. // for performance when ComputeType is rational.
  1058. Vector3<ComputeType> const* mComputePoints;
  1059. // The indices into mPoints/mComputePoints for the convex hull
  1060. // vertices.
  1061. std::vector<int> mHull;
  1062. // The unique indices in mHull. This set allows us to compute only
  1063. // for the hull vertices and avoids redundant computations if the
  1064. // indices were to have repeated indices into mPoints/mComputePoints.
  1065. // This is a performance improvement for rational ComputeType.
  1066. std::set<int> mUniqueIndices;
  1067. // The caller can specify whether to use rotating calipers or the
  1068. // slower all-edge processing for computing an extruded bounding box.
  1069. bool mUseRotatingCalipers;
  1070. // The volume of the minimum-volume box. The ComputeType value is
  1071. // exact, so the only rounding errors occur in the conversion from
  1072. // ComputeType to InputType (default rounding mode is
  1073. // round-to-nearest-ties-to-even).
  1074. InputType mVolume;
  1075. // Convenient values that occur regularly in the code. When using
  1076. // rational ComputeType, we construct these numbers only once.
  1077. ComputeType mZero, mOne, mNegOne, mHalf;
  1078. };
  1079. }