14#define BOOST_TEST_MODULE Test O2FlatCSG class
15#define BOOST_TEST_MAIN
16#define BOOST_TEST_DYN_LINK
17#include <boost/test/unit_test.hpp>
43 explicit Rng(
unsigned long long seed) : mState(seed) {}
44 double uniform(
double low,
double high)
46 mState = mState * 6364136223846793005ULL + 1442695040888963407ULL;
47 const double unit =
static_cast<double>((mState >> 11) & ((1ULL << 53) - 1)) /
static_cast<double>(1ULL << 53);
48 return low + unit * (high - low);
52 unsigned long long mState;
56void planeQuadric(
const double n[3],
const double p[3],
double coeff[10])
61 coeff[6] = 0.5 *
n[0];
62 coeff[7] = 0.5 *
n[1];
63 coeff[8] = 0.5 *
n[2];
64 coeff[9] = -(
n[0] * p[0] +
n[1] * p[1] +
n[2] * p[2]);
68void zCylinderQuadric(
double r,
double coeff[10])
70 const double values[10] = {1., 0., 0., 1., 0., 0., 0., 0., 0., -
r *
r};
81void tiltedCylinderQuadric(
double r,
double coeff[10])
83 const double s = 1. / std::sqrt(3.);
84 const double d[3] = {s, s, s};
87 for (
int column = 0; column < 3; ++column) {
88 a[
row][column] = (
row == column ? 1. : 0.) - d[
row] * d[column];
104void addBoxCell(O2FlatCSG& solid,
double dx,
double dy,
double dz)
106 const double half[3] = {dx, dy, dz};
107 const int first = solid.GetNhalfspaces();
108 for (
int axis = 0; axis < 3; ++axis) {
109 for (
int sense = -1; sense <= 1; sense += 2) {
110 double normal[3] = {0., 0., 0.};
111 double through[3] = {0., 0., 0.};
112 normal[axis] =
static_cast<double>(sense);
113 through[axis] = sense *
half[axis];
115 planeQuadric(normal, through, coeff);
116 solid.AddQuadric(1., coeff);
119 solid.AddCell(
first, 6, 8. * dx * dy * dz);
125 O2FlatCSG solid(
"box");
126 addBoxCell(solid, 3., 4., 5.);
131 Rng rng(20260824ULL);
133 for (
int trial = 0; trial < 20000; ++trial) {
134 const double point[3] = {rng.uniform(-6., 6.), rng.uniform(-7., 7.), rng.uniform(-8., 8.)};
136 if (std::abs(std::abs(point[0]) - 3.) < 1.e-9 || std::abs(std::abs(point[1]) - 4.) < 1.e-9 ||
137 std::abs(std::abs(point[2]) - 5.) < 1.e-9) {
140 BOOST_REQUIRE_EQUAL(solid.Contains_Loop(point),
reference.Contains(point));
141 BOOST_REQUIRE_EQUAL(solid.Contains(point), solid.Contains_Loop(point));
144 BOOST_CHECK_GT(scored, 19000);
151 O2FlatCSG solid(
"tube");
153 zCylinderQuadric(5., coeff);
154 solid.AddQuadric(1., coeff);
155 zCylinderQuadric(2., coeff);
156 solid.AddQuadric(-1., coeff);
157 const double up[3] = {0., 0., 1.};
158 const double down[3] = {0., 0., -1.};
159 const double top[3] = {0., 0., 7.};
160 const double bottom[3] = {0., 0., -7.};
161 planeQuadric(up,
top, coeff);
162 solid.AddQuadric(1., coeff);
163 planeQuadric(down,
bottom, coeff);
164 solid.AddQuadric(1., coeff);
165 solid.AddCell(0, 4, TMath::Pi() * (25. - 4.) * 14.);
169 for (
int trial = 0; trial < 20000; ++trial) {
170 const double point[3] = {rng.uniform(-6., 6.), rng.uniform(-6., 6.), rng.uniform(-8., 8.)};
171 const double radius = std::hypot(point[0], point[1]);
172 if (std::abs(radius - 2.) < 1.e-9 || std::abs(radius - 5.) < 1.e-9 ||
173 std::abs(std::abs(point[2]) - 7.) < 1.e-9) {
176 BOOST_TEST_CONTEXT(
"point = (" << point[0] <<
", " << point[1] <<
", " << point[2] <<
")")
178 BOOST_REQUIRE_EQUAL(solid.Contains_Loop(point),
reference.Contains(point));
185 O2FlatCSG solid(
"two_boxes");
186 addBoxCell(solid, 1., 1., 1.);
188 const int first = solid.GetNhalfspaces();
189 const double centre = 10.;
190 for (
int axis = 0; axis < 3; ++axis) {
191 for (
int sense = -1; sense <= 1; sense += 2) {
192 double normal[3] = {0., 0., 0.};
193 double through[3] = {centre, 0., 0.};
194 normal[axis] =
static_cast<double>(sense);
195 through[axis] += (axis == 0 ? sense * 1. : 0.);
197 through[axis] = sense * 1.;
200 planeQuadric(normal, through, coeff);
201 solid.AddQuadric(1., coeff);
204 solid.AddCell(
first, 6, 8.);
206 const double inFirst[3] = {0., 0., 0.};
207 const double inSecond[3] = {10., 0., 0.};
208 const double between[3] = {5., 0., 0.};
216 O2FlatCSG solid(
"box_dist");
217 addBoxCell(solid, 3., 4., 5.);
221 for (
int trial = 0; trial < 20000; ++trial) {
222 double point[3] = {rng.uniform(-12., 12.), rng.uniform(-12., 12.), rng.uniform(-12., 12.)};
227 dir[
index] = rng.uniform(-1., 1.);
229 norm = std::sqrt(dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2]);
230 }
while (norm < 1.e-3);
234 const bool inside =
reference.Contains(point);
235 if (inside !=
static_cast<bool>(solid.Contains_Loop(point))) {
238 const double mine = inside ? solid.DistFromInside_Loop(point, dir, TGeoShape::Big())
239 : solid.DistFromOutside_Loop(point, dir, TGeoShape::Big());
240 const double theirs = inside ?
reference.DistFromInside(point, dir, 3, TGeoShape::Big(),
nullptr)
241 :
reference.DistFromOutside(point, dir, 3, TGeoShape::Big(),
nullptr);
242 if (theirs >= TGeoShape::Big()) {
243 BOOST_REQUIRE_GE(mine, TGeoShape::Big());
245 BOOST_REQUIRE_SMALL(mine - theirs, 1.e-9);
254 O2FlatCSG solid(
"tube_dist");
256 zCylinderQuadric(5., coeff);
257 solid.AddQuadric(1., coeff);
258 zCylinderQuadric(2., coeff);
259 solid.AddQuadric(-1., coeff);
260 const double up[3] = {0., 0., 1.};
261 const double down[3] = {0., 0., -1.};
262 const double top[3] = {0., 0., 7.};
263 const double bottom[3] = {0., 0., -7.};
264 planeQuadric(up,
top, coeff);
265 solid.AddQuadric(1., coeff);
266 planeQuadric(down,
bottom, coeff);
267 solid.AddQuadric(1., coeff);
268 solid.AddCell(0, 4, 0.);
273 const double origin[3] = {-9., 0., 0.};
274 const double dir[3] = {1., 0., 0.};
275 BOOST_CHECK_SMALL(solid.DistFromOutside_Loop(
origin, dir, TGeoShape::Big()) - 4., 1.e-12);
277 const double inWall[3] = {-4., 0., 0.};
278 BOOST_CHECK_SMALL(solid.DistFromInside_Loop(inWall, dir, TGeoShape::Big()) - 2., 1.e-12);
280 const double inBore[3] = {0., 0., 0.};
282 BOOST_CHECK_SMALL(solid.DistFromOutside_Loop(inBore, dir, TGeoShape::Big()) - 2., 1.e-12);
285 for (
int trial = 0; trial < 20000; ++trial) {
286 double point[3] = {rng.uniform(-9., 9.), rng.uniform(-9., 9.), rng.uniform(-10., 10.)};
291 direction[
index] = rng.uniform(-1., 1.);
293 norm = std::sqrt(direction[0] * direction[0] + direction[1] * direction[1] + direction[2] * direction[2]);
294 }
while (norm < 1.e-3);
296 direction[
index] /= norm;
298 const bool inside =
reference.Contains(point);
299 if (inside !=
static_cast<bool>(solid.Contains_Loop(point))) {
302 const double mine = inside ? solid.DistFromInside_Loop(point, direction, TGeoShape::Big())
303 : solid.DistFromOutside_Loop(point, direction, TGeoShape::Big());
304 const double theirs = inside ?
reference.DistFromInside(point, direction, 3, TGeoShape::Big(),
nullptr)
305 :
reference.DistFromOutside(point, direction, 3, TGeoShape::Big(),
nullptr);
306 if (theirs >= TGeoShape::Big()) {
307 BOOST_REQUIRE_GE(mine, TGeoShape::Big());
309 BOOST_REQUIRE_SMALL(mine - theirs, 1.e-8);
318 O2FlatCSG solid(
"touching");
319 addBoxCell(solid, 1., 1., 1.);
320 const int first = solid.GetNhalfspaces();
321 const double planes[6][2][3] = {{{1., 0., 0.}, {3., 0., 0.}},
322 {{-1., 0., 0.}, {1., 0., 0.}},
323 {{0., 1., 0.}, {0., 1., 0.}},
324 {{0., -1., 0.}, {0., -1., 0.}},
325 {{0., 0., 1.}, {0., 0., 1.}},
326 {{0., 0., -1.}, {0., 0., -1.}}};
327 for (
const auto& plane : planes) {
329 planeQuadric(plane[0], plane[1], coeff);
330 solid.AddQuadric(1., coeff);
332 solid.AddCell(
first, 6, 8.);
334 const double origin[3] = {0., 0., 0.};
335 const double dir[3] = {1., 0., 0.};
336 BOOST_CHECK_SMALL(solid.DistFromInside_Loop(
origin, dir, TGeoShape::Big()) - 3., 1.e-12);
344 O2FlatCSG solid(
"tangent_ray");
346 zCylinderQuadric(5., coeff);
347 solid.AddQuadric(1., coeff);
348 const auto& cylinder = solid.GetHalfspace(0);
350 const double origin[3] = {5., 0., 0.};
351 const double dir[3] = {0., 1., 0.};
353 const int found = O2FlatCSG::HalfspaceRoots(cylinder,
origin, dir, roots);
355 BOOST_REQUIRE_EQUAL(found, 1);
357 BOOST_CHECK_SMALL(roots[0], 1.e-12);
361 const double hit[3] = {
origin[0] + roots[0] * dir[0],
origin[1] + roots[0] * dir[1],
362 origin[2] + roots[0] * dir[2]};
363 BOOST_CHECK_SMALL(O2FlatCSG::EvalHalfspace(cylinder, hit), 1.e-9);
369 O2FlatCSG solid(
"torus");
370 const double centre[3] = {0., 0., 0.};
371 const double axis[3] = {0., 0., 1.};
372 solid.AddTorus(1., centre, axis, 10., 3.);
373 solid.AddCell(0, 1, 2. * TMath::Pi() * TMath::Pi() * 10. * 9.);
377 int scoredPoints = 0;
378 for (
int trial = 0; trial < 20000; ++trial) {
379 const double point[3] = {rng.uniform(-15., 15.), rng.uniform(-15., 15.), rng.uniform(-5., 5.)};
380 const double radial = std::hypot(point[0], point[1]);
381 const double distance = std::hypot(radial - 10., point[2]) - 3.;
385 BOOST_REQUIRE_EQUAL(solid.Contains_Loop(point),
reference.Contains(point));
388 BOOST_CHECK_GT(scoredPoints, 19000);
390 for (
int trial = 0; trial < 20000; ++trial) {
391 double point[3] = {rng.uniform(-20., 20.), rng.uniform(-20., 20.), rng.uniform(-8., 8.)};
396 dir[
index] = rng.uniform(-1., 1.);
398 norm = std::sqrt(dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2]);
399 }
while (norm < 1.e-3);
403 const bool inside =
reference.Contains(point);
404 if (inside !=
static_cast<bool>(solid.Contains_Loop(point))) {
407 const double mine = inside ? solid.DistFromInside_Loop(point, dir, TGeoShape::Big())
408 : solid.DistFromOutside_Loop(point, dir, TGeoShape::Big());
409 const double theirs = inside ?
reference.DistFromInside(point, dir, 3, TGeoShape::Big(),
nullptr)
410 :
reference.DistFromOutside(point, dir, 3, TGeoShape::Big(),
nullptr);
411 if (theirs >= TGeoShape::Big()) {
412 BOOST_REQUIRE_GE(mine, TGeoShape::Big());
415 BOOST_REQUIRE_SMALL(mine - theirs, 1.e-6);
423 const double axis[3] = {0., 1. / std::sqrt(2.), 1. / std::sqrt(2.)};
424 const double centre[3] = {1., 2., 3.};
425 O2FlatCSG solid(
"tilted_torus");
426 solid.AddTorus(1., centre, axis, 8., 2.);
427 solid.AddCell(0, 1, 0.);
430 for (
int trial = 0; trial < 20000; ++trial) {
431 const double point[3] = {rng.uniform(-14., 16.), rng.uniform(-13., 17.), rng.uniform(-12., 18.)};
433 const double offset[3] = {point[0] - centre[0], point[1] - centre[1], point[2] - centre[2]};
439 const double rho = std::sqrt(radialVec[0] * radialVec[0] + radialVec[1] * radialVec[1] +
440 radialVec[2] * radialVec[2]);
441 const double signedDistance = std::hypot(rho - 8., along) - 2.;
442 if (std::abs(signedDistance) < 1.e-9) {
445 BOOST_REQUIRE_EQUAL(
static_cast<bool>(solid.Contains_Loop(point)), signedDistance < 0.);
453 const double s = 1. / std::sqrt(2.);
455 auto rotateToWorld = [s](
const double local[3],
double world[3]) {
457 world[1] = s * local[1] + s * local[2];
458 world[2] = -s * local[1] + s * local[2];
462 for (
int trial = 0; trial < 20000; ++trial) {
463 double localPoint[3] = {rng.uniform(-20., 20.), rng.uniform(-20., 20.), rng.uniform(-8., 8.)};
468 localDir[
index] = rng.uniform(-1., 1.);
470 norm = std::sqrt(localDir[0] * localDir[0] + localDir[1] * localDir[1] + localDir[2] * localDir[2]);
471 }
while (norm < 1.e-3);
473 localDir[
index] /= norm;
475 double worldPoint[3];
477 rotateToWorld(localPoint, worldPoint);
478 rotateToWorld(localDir, worldDir);
483 const bool inside =
reference.Contains(localPoint);
484 if (inside !=
static_cast<bool>(solid.Contains_Loop(worldPoint))) {
487 const double mine = inside ? solid.DistFromInside_Loop(worldPoint, worldDir, TGeoShape::Big())
488 : solid.DistFromOutside_Loop(worldPoint, worldDir, TGeoShape::Big());
489 const double theirs = inside ?
reference.DistFromInside(localPoint, localDir, 3, TGeoShape::Big(),
nullptr)
490 :
reference.DistFromOutside(localPoint, localDir, 3, TGeoShape::Big(),
nullptr);
491 if (theirs >= TGeoShape::Big()) {
492 BOOST_REQUIRE_GE(mine, TGeoShape::Big());
494 BOOST_REQUIRE_SMALL(mine - theirs, 1.e-6);
502 O2FlatCSG solid(
"range");
504 zCylinderQuadric(5., coeff);
505 const int cylinder = solid.AddQuadric(1., coeff);
506 const double normal[3] = {0., 0., 1.};
507 const double through[3] = {0., 0., 2.};
508 planeQuadric(normal, through, coeff);
509 const int plane = solid.AddQuadric(-1., coeff);
510 const double centre[3] = {1., 0., 0.};
511 const double axis[3] = {0., 0., 1.};
512 const int torus = solid.AddTorus(1., centre, axis, 7., 2.);
513 tiltedCylinderQuadric(5., coeff);
514 const int tilted = solid.AddQuadric(1., coeff);
517 const std::vector<int> halfspaces = {cylinder, plane, torus, tilted};
519 auto checkBox = [&](
const double* lo,
const double* hi) {
520 for (
int which : halfspaces) {
523 O2FlatCSG::HalfspaceRange(solid.GetHalfspace(which), lo, hi, rangeLo, rangeHi);
524 BOOST_REQUIRE_LE(rangeLo, rangeHi);
526 auto checkPoint = [&](
const double point[3]) {
527 const double value = O2FlatCSG::EvalHalfspace(solid.GetHalfspace(which), point);
528 BOOST_REQUIRE_GE(
value, rangeLo - 1.e-9);
529 BOOST_REQUIRE_LE(
value, rangeHi + 1.e-9);
535 for (
int cx : {0, 1}) {
536 for (
int cy : {0, 1}) {
537 for (
int cz : {0, 1}) {
538 const double corner[3] = {cx ? hi[0] : lo[0], cy ? hi[1] : lo[1], cz ? hi[2] : lo[2]};
543 const double mid[3] = {0.5 * (lo[0] + hi[0]), 0.5 * (lo[1] + hi[1]), 0.5 * (lo[2] + hi[2])};
544 for (
int faceAxis = 0; faceAxis < 3; ++faceAxis) {
545 for (
int side : {0, 1}) {
546 double face[3] = {mid[0], mid[1], mid[2]};
547 face[faceAxis] =
side ? hi[faceAxis] : lo[faceAxis];
551 for (
int edgeAxis = 0; edgeAxis < 3; ++edgeAxis) {
552 const int other1 = (edgeAxis + 1) % 3;
553 const int other2 = (edgeAxis + 2) % 3;
554 for (
int s1 : {0, 1}) {
555 for (
int s2 : {0, 1}) {
557 edge[edgeAxis] = mid[edgeAxis];
558 edge[other1] =
s1 ? hi[other1] : lo[other1];
559 edge[other2] = s2 ? hi[other2] : lo[other2];
566 for (
int sample = 0; sample < 200; ++sample) {
567 const double point[3] = {rng.uniform(lo[0], hi[0]), rng.uniform(lo[1], hi[1]),
568 rng.uniform(lo[2], hi[2])};
574 for (
int trial = 0; trial < 3000; ++trial) {
578 const double a = rng.uniform(-12., 12.);
579 const double b =
a + rng.uniform(0.01, 6.);
588 const double extreme[4][3][2] = {
589 {{-0.005, 0.005}, {-12., 12.}, {-0.5, 0.5}},
590 {{-12., 12.}, {-0.005, 0.005}, {3., 27.}},
591 {{2., 2.01}, {-1., 1.}, {-12., 12.}},
592 {{-24., 0.}, {5., 5.01}, {-3., 3.}},
594 for (
const auto&
box : extreme) {
595 const double lo[3] = {
box[0][0],
box[1][0],
box[2][0]};
596 const double hi[3] = {
box[0][1],
box[1][1],
box[2][1]};
604 O2FlatCSG solid(
"boxes");
606 zCylinderQuadric(5., coeff);
607 solid.AddQuadric(1., coeff);
608 zCylinderQuadric(2., coeff);
609 solid.AddQuadric(-1., coeff);
610 const double up[3] = {0., 0., 1.};
611 const double down[3] = {0., 0., -1.};
612 const double top[3] = {0., 0., 7.};
613 const double bottom[3] = {0., 0., -7.};
614 planeQuadric(up,
top, coeff);
615 solid.AddQuadric(1., coeff);
616 planeQuadric(down,
bottom, coeff);
617 solid.AddQuadric(1., coeff);
618 solid.AddCell(0, 4, 0.);
619 const double lo[3] = {-5., -5., -7.};
620 const double hi[3] = {5., 5., 7.};
621 solid.SetCellBBox(0, lo, hi);
624 BOOST_CHECK_GT(solid.GetNboxes(), 1);
627 int insideSamples = 0;
628 for (
int trial = 0; trial < 50000; ++trial) {
629 const double point[3] = {rng.uniform(-6., 6.), rng.uniform(-6., 6.), rng.uniform(-8., 8.)};
630 if (!solid.Contains_Loop(point)) {
635 bool covered =
false;
637 const auto&
box = solid.GetBox(
index);
638 covered = point[0] >=
box.min[0] && point[0] <=
box.max[0] && point[1] >=
box.min[1] &&
639 point[1] <=
box.max[1] && point[2] >=
box.min[2] && point[2] <=
box.max[2];
641 BOOST_REQUIRE(covered);
643 BOOST_CHECK_GT(insideSamples, 5000);
647 const auto&
box = solid.GetBox(
index);
648 for (
int sample = 0; sample < 200; ++sample) {
649 const double point[3] = {rng.uniform(
box.min[0],
box.max[0]),
650 rng.uniform(
box.min[1],
box.max[1]),
651 rng.uniform(
box.min[2],
box.max[2])};
652 bool byActive =
true;
653 for (
int slot = 0; slot <
box.nActive && byActive; ++slot) {
654 byActive = O2FlatCSG::EvalHalfspace(
655 solid.GetHalfspace(solid.GetActive(
box.firstActive + slot)), point) <= 0.;
657 BOOST_REQUIRE_EQUAL(byActive, solid.CellContains(
box.cell, point));
664 O2FlatCSG solid(
"solid_boxes");
665 addBoxCell(solid, 4., 4., 4.);
666 const double lo[3] = {-4., -4., -4.};
667 const double hi[3] = {4., 4., 4.};
668 solid.SetCellBBox(0, lo, hi);
673 solid.SetSplitDepth(6);
674 solid.SetMinBoxFraction(0.01);
679 if (solid.GetBox(
index).nActive == 0) {
683 BOOST_CHECK_GT(solidBoxes, 0);
690 O2FlatCSG solid(
"missing_bbox");
691 addBoxCell(solid, 1., 1., 1.);
692 const int first = solid.GetNhalfspaces();
693 const double centre[3] = {10., 0., 0.};
694 for (
int axis = 0; axis < 3; ++axis) {
695 for (
int sense = -1; sense <= 1; sense += 2) {
696 double normal[3] = {0., 0., 0.};
697 double through[3] = {centre[0], centre[1], centre[2]};
698 normal[axis] =
static_cast<double>(sense);
699 through[axis] += sense * 1.;
701 planeQuadric(normal, through, coeff);
702 solid.AddQuadric(1., coeff);
705 solid.AddCell(
first, 6, 8.);
707 const double lo[3] = {-1., -1., -1.};
708 const double hi[3] = {1., 1., 1.};
709 solid.SetCellBBox(0, lo, hi);
722 O2FlatCSG solid(
"inverted_bbox");
723 addBoxCell(solid, 1., 1., 1.);
724 const double lo[3] = {-1., -1., -1.};
725 const double hi[3] = {1., 1., 1.};
726 solid.SetCellBBox(0, hi, lo);
739 O2FlatCSG solid(
"nan_bbox");
740 addBoxCell(solid, 1., 1., 1.);
741 const double nan = std::numeric_limits<double>::quiet_NaN();
742 const double lo[3] = {-1., -1., -1.};
743 const double hi[3] = {1., nan, 1.};
744 solid.SetCellBBox(0, lo, hi);
764void buildBracket(O2FlatCSG& solid,
double planeScale = 1.)
767 const auto scaledPlane = [&](
const double* normal,
const double* through) {
768 planeQuadric(normal, through, coeff);
770 coeff[
index] *= planeScale;
774 const double arm[6][2][3] = {{{1., 0., 0.}, {10., 0., 0.}},
775 {{-1., 0., 0.}, {-10., 0., 0.}},
776 {{0., 1., 0.}, {0., 1., 0.}},
777 {{0., -1., 0.}, {0., -1., 0.}},
778 {{0., 0., 1.}, {0., 0., 1.}},
779 {{0., 0., -1.}, {0., 0., -1.}}};
780 int first = solid.GetNhalfspaces();
781 for (
const auto& plane : arm) {
782 scaledPlane(plane[0], plane[1]);
783 solid.AddQuadric(1., coeff);
785 solid.AddCell(
first, 6, 8. * 10. * 1. * 1.);
786 const double armLo[3] = {-10., -1., -1.};
787 const double armHi[3] = {10., 1., 1.};
788 solid.SetCellBBox(0, armLo, armHi);
791 const double upright[6][2][3] = {{{1., 0., 0.}, {10., 0., 0.}},
792 {{-1., 0., 0.}, {8., 0., 0.}},
793 {{0., 1., 0.}, {0., 1., 0.}},
794 {{0., -1., 0.}, {0., -1., 0.}},
795 {{0., 0., 1.}, {0., 0., 12.}},
796 {{0., 0., -1.}, {0., 0., 1.}}};
797 first = solid.GetNhalfspaces();
798 for (
const auto& plane : upright) {
799 scaledPlane(plane[0], plane[1]);
800 solid.AddQuadric(1., coeff);
802 solid.AddCell(
first, 6, 2. * 2. * 11.);
803 const double uprightLo[3] = {8., -1., 1.};
804 const double uprightHi[3] = {10., 1., 12.};
805 solid.SetCellBBox(1, uprightLo, uprightHi);
809 first = solid.GetNhalfspaces();
810 const double centreShift = -8.;
812 const double outer[10] = {1., 0., 0., 1., 0., 0., -centreShift, 0., 0.,
813 centreShift * centreShift - 9.};
814 solid.AddQuadric(1., outer);
815 const double inner[10] = {1., 0., 0., 1., 0., 0., -centreShift, 0., 0.,
816 centreShift * centreShift - 1.};
817 solid.AddQuadric(-1., inner);
818 const double washer[2][2][3] = {{{0., 0., 1.}, {0., 0., 3.}}, {{0., 0., -1.}, {0., 0., 1.}}};
819 for (
const auto& plane : washer) {
820 scaledPlane(plane[0], plane[1]);
821 solid.AddQuadric(1., coeff);
823 solid.AddCell(
first, 4, TMath::Pi() * (9. - 1.) * 2.);
824 const double washerLo[3] = {-11., -3., 1.};
825 const double washerHi[3] = {-5., 3., 3.};
826 solid.SetCellBBox(2, washerLo, washerHi);
832 O2FlatCSG solid(
"bracket");
835 BOOST_CHECK_GT(solid.GetNboxes(), 3);
836 BOOST_CHECK_GT(solid.GetBVHMemory(), 0u);
839 for (
int trial = 0; trial < 200000; ++trial) {
840 const double point[3] = {rng.uniform(-13., 13.), rng.uniform(-5., 5.), rng.uniform(-3., 14.)};
841 BOOST_REQUIRE_EQUAL(solid.Contains(point), solid.Contains_Loop(point));
847 O2FlatCSG solid(
"bracket_points");
850 constexpr int kPoints = 4000;
851 std::vector<double> points(3 * kPoints, 0.);
852 BOOST_REQUIRE(solid.GetPointsOnSegments(kPoints, points.data()));
853 const double zAxis[3] = {0., 0., 1.};
855 const double* point = &points[3 *
index];
856 double normal[3] = {0., 0., 0.};
857 solid.ComputeNormal(point, zAxis, normal);
860 for (
int axis = 0; axis < 3; ++axis) {
861 below[axis] = point[axis] - 1.e-6 * normal[axis];
862 above[axis] = point[axis] + 1.e-6 * normal[axis];
864 BOOST_TEST_CONTEXT(
"point = (" << point[0] <<
", " << point[1] <<
", " << point[2] <<
")")
866 BOOST_CHECK_NE(solid.Contains(below), solid.Contains(above));
873 O2FlatCSG solid(
"bracket_dist");
878 for (
int trial = 0; trial < 200000; ++trial) {
879 double point[3] = {rng.uniform(-16., 16.), rng.uniform(-8., 8.), rng.uniform(-6., 17.)};
884 dir[
index] = rng.uniform(-1., 1.);
886 norm = std::sqrt(dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2]);
887 }
while (norm < 1.e-3);
891 if (solid.Contains_Loop(point)) {
892 BOOST_REQUIRE_EQUAL(solid.DistFromInside(point, dir, 3, TGeoShape::Big(),
nullptr),
893 solid.DistFromInside_Loop(point, dir, TGeoShape::Big()));
895 BOOST_REQUIRE_EQUAL(solid.DistFromOutside(point, dir, 3, TGeoShape::Big(),
nullptr),
896 solid.DistFromOutside_Loop(point, dir, TGeoShape::Big()));
905 O2FlatCSG solid(
"bracket_long");
908 const double dir[3] = {1., 0., 0.};
911 const double origin[3] = {-20., 0., 0.};
912 BOOST_CHECK_SMALL(solid.DistFromOutside(
origin, dir, 3, TGeoShape::Big(),
nullptr) - 10., 1.e-12);
917 const double inArm[3] = {0., 0., 0.};
920 BOOST_CHECK_SMALL(solid.DistFromInside(inArm, dir, 3, TGeoShape::Big(),
nullptr) - 10., 1.e-12);
928 const double grazing[3] = {-8.75 - 2.e-11, 1. + 1.e-11, 0.5};
929 const double slant = 1. / std::sqrt(2.);
930 const double slantDir[3] = {slant, -slant, 0.};
931 const double entered = solid.DistFromOutside(grazing, slantDir, 3, TGeoShape::Big(),
nullptr);
932 BOOST_CHECK_EQUAL(entered, solid.DistFromOutside_Loop(grazing, slantDir, TGeoShape::Big()));
935 BOOST_CHECK_GT(entered, 0.);
936 BOOST_CHECK_LT(entered, TGeoShape::Tolerance());
937 BOOST_CHECK_LT(entered, 2.e-11 * std::sqrt(2.));
953 O2FlatCSG solid(
"bracket_scaled");
954 buildBracket(solid, 3.);
956 BOOST_REQUIRE(solid.IsClosed());
960 for (
int trial = 0; trial < 200000; ++trial) {
961 double point[3] = {rng.uniform(-16., 16.), rng.uniform(-8., 8.), rng.uniform(-6., 17.)};
966 dir[
index] = rng.uniform(-1., 1.);
968 norm = std::sqrt(dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2]);
969 }
while (norm < 1.e-3);
973 const bool inside = solid.Contains_Loop(point);
975 BOOST_REQUIRE_EQUAL(solid.Contains(point), inside);
976 const double accelerated =
977 inside ? solid.DistFromInside(point, dir, 3, TGeoShape::Big(),
nullptr)
978 : solid.DistFromOutside(point, dir, 3, TGeoShape::Big(),
nullptr);
979 const double twin = inside ? solid.DistFromInside_Loop(point, dir, TGeoShape::Big())
980 : solid.DistFromOutside_Loop(point, dir, TGeoShape::Big());
981 const double slack = std::abs(accelerated - twin);
982 worst = std::max(worst, slack);
986 BOOST_REQUIRE_LE(slack, 1.e-13 * std::max(1., std::abs(twin)));
988 BOOST_TEST_MESSAGE(
"largest accelerated-vs-twin gap under a x3 plane rescale: " << worst);
993 BOOST_CHECK_LE(worst, 1.e-12);
1001 O2FlatCSG solid(
"bracket_unclosed");
1002 buildBracket(solid);
1004 const double extra[6][2][3] = {{{1., 0., 0.}, {14., 0., 0.}},
1005 {{-1., 0., 0.}, {12., 0., 0.}},
1006 {{0., 1., 0.}, {0., 1., 0.}},
1007 {{0., -1., 0.}, {0., -1., 0.}},
1008 {{0., 0., 1.}, {0., 0., 1.}},
1009 {{0., 0., -1.}, {0., 0., -1.}}};
1011 const int first = solid.GetNhalfspaces();
1012 for (
const auto& plane : extra) {
1013 planeQuadric(plane[0], plane[1], coeff);
1014 solid.AddQuadric(1., coeff);
1016 solid.AddCell(
first, 6, 2. * 2. * 2.);
1019 BOOST_REQUIRE(!solid.IsClosed());
1024 const double inArm[3] = {0., 0., 0.};
1025 const double inUpright[3] = {9., 0., 6.};
1026 const double inWasher[3] = {-10.5, 0., 2.};
1027 const double inExtra[3] = {13., 0., 0.};
1029 const double inBore[3] = {-8., 0., 2.};
1030 const double outside[3] = {0., 0., 20.};
1039 for (
int trial = 0; trial < 20000; ++trial) {
1040 const double point[3] = {rng.uniform(-16., 16.), rng.uniform(-5., 5.), rng.uniform(-3., 14.)};
1041 BOOST_REQUIRE_EQUAL(solid.Contains(point), solid.Contains_Loop(point));
1046 dir[
index] = rng.uniform(-1., 1.);
1048 norm = std::sqrt(dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2]);
1049 }
while (norm < 1.e-3);
1053 const bool inside = solid.Contains_Loop(point);
1055 BOOST_REQUIRE_EQUAL(solid.DistFromInside(point, dir, 3, TGeoShape::Big(),
nullptr),
1056 solid.DistFromInside_Loop(point, dir, TGeoShape::Big()));
1058 BOOST_REQUIRE_EQUAL(solid.DistFromOutside(point, dir, 3, TGeoShape::Big(),
nullptr),
1059 solid.DistFromOutside_Loop(point, dir, TGeoShape::Big()));
1063 BOOST_REQUIRE_EQUAL(solid.Safety(point, inside), solid.Safety_Loop(point, inside));
1069 O2FlatCSG solid(
"bracket_safety");
1070 buildBracket(solid);
1074 for (
int trial = 0; trial < 50000; ++trial) {
1075 double point[3] = {rng.uniform(-16., 16.), rng.uniform(-8., 8.), rng.uniform(-6., 17.)};
1076 const bool inside = solid.Contains_Loop(point);
1077 const double safety = solid.Safety(point, inside);
1078 BOOST_REQUIRE_GE(safety, 0.);
1079 BOOST_REQUIRE_EQUAL(safety, solid.Safety_Loop(point, inside));
1082 for (
int probe = 0; probe < 40; ++probe) {
1087 dir[
index] = rng.uniform(-1., 1.);
1089 norm = std::sqrt(dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2]);
1090 }
while (norm < 1.e-3);
1091 const double reach = safety * rng.uniform(0., 0.999) / norm;
1092 const double near[3] = {point[0] + reach * dir[0], point[1] + reach * dir[1],
1093 point[2] + reach * dir[2]};
1094 BOOST_REQUIRE_EQUAL(
static_cast<bool>(solid.Contains_Loop(near)), inside);
1110 O2FlatCSG solid(
"bracket_safety_deep");
1111 buildBracket(solid);
1112 solid.SetSplitDepth(14);
1115 solid.SetMinBoxFraction(0.002);
1118 bool sawPositiveInsideSafety =
false;
1119 Rng rng(11235813ULL);
1120 for (
int trial = 0; trial < 50000; ++trial) {
1121 double point[3] = {rng.uniform(-16., 16.), rng.uniform(-8., 8.), rng.uniform(-6., 17.)};
1122 const bool inside = solid.Contains_Loop(point);
1123 const double safety = solid.Safety(point, inside);
1124 BOOST_REQUIRE_GE(safety, 0.);
1125 BOOST_REQUIRE_EQUAL(safety, solid.Safety_Loop(point, inside));
1126 if (inside && safety > 0.) {
1127 sawPositiveInsideSafety =
true;
1131 for (
int probe = 0; probe < 40; ++probe) {
1136 dir[
index] = rng.uniform(-1., 1.);
1138 norm = std::sqrt(dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2]);
1139 }
while (norm < 1.e-3);
1140 const double reach = safety * rng.uniform(0., 0.999) / norm;
1141 const double near[3] = {point[0] + reach * dir[0], point[1] + reach * dir[1],
1142 point[2] + reach * dir[2]};
1143 BOOST_REQUIRE_EQUAL(
static_cast<bool>(solid.Contains_Loop(near)), inside);
1146 BOOST_REQUIRE(sawPositiveInsideSafety);
1151 O2FlatCSG solid(
"bracket_capacity");
1152 buildBracket(solid);
1154 const double expected = 8. * 10. * 1. * 1. + 2. * 2. * 11. + TMath::Pi() * (9. - 1.) * 2.;
1155 BOOST_CHECK_SMALL(solid.Capacity() -
expected, 1.e-12);
1164 O2FlatCSG solid(
"bracket_bbox");
1165 buildBracket(solid);
1170 for (
int trial = 0; trial < 50000; ++trial) {
1171 const double point[3] = {rng.uniform(-13., 13.), rng.uniform(-5., 5.), rng.uniform(-3., 14.)};
1172 if (solid.Contains_Loop(point)) {
1173 BOOST_REQUIRE(solid.TGeoBBox::Contains(point));
1180 const double cellLo[3][3] = {{-10., -1., -1.}, {8., -1., 1.}, {-11., -3., 1.}};
1181 const double cellHi[3][3] = {{10., 1., 1.}, {10., 1., 12.}, {-5., 3., 3.}};
1182 double unionLo[3] = {cellLo[0][0], cellLo[0][1], cellLo[0][2]};
1183 double unionHi[3] = {cellHi[0][0], cellHi[0][1], cellHi[0][2]};
1184 for (
int cell = 1; cell < 3; ++cell) {
1190 const double*
origin = solid.GetOrigin();
1192 const double dHalf =
index == 0 ? solid.GetDX() : (
index == 1 ? solid.GetDY() : solid.GetDZ());
1200 O2FlatCSG solid(
"box_normal");
1201 addBoxCell(solid, 3., 4., 5.);
1202 const double lo[3] = {-3., -4., -5.};
1203 const double hi[3] = {3., 4., 5.};
1204 solid.SetCellBBox(0, lo, hi);
1207 const double onFace[3] = {3., 1., 1.};
1208 const double dir[3] = {1., 0., 0.};
1209 double normal[3] = {0., 0., 0.};
1210 solid.ComputeNormal(onFace, dir, normal);
1211 BOOST_CHECK_SMALL(normal[0] - 1., 1.e-12);
1212 BOOST_CHECK_SMALL(normal[1], 1.e-12);
1213 BOOST_CHECK_SMALL(normal[2], 1.e-12);
1229 O2FlatCSG solid(
"scale_invariance");
1231 zCylinderQuadric(100., coeff);
1232 solid.AddQuadric(1., coeff);
1233 solid.AddCell(0, 1, 0.);
1235 const double planeNormal[3] = {0., 0., 1.};
1236 const double planeThrough[3] = {0., 0., 0.05};
1237 planeQuadric(planeNormal, planeThrough, coeff);
1238 const int planeFirst = solid.GetNhalfspaces();
1239 solid.AddQuadric(1., coeff);
1240 solid.AddCell(planeFirst, 1, 0.);
1242 BOOST_REQUIRE(!solid.IsClosed());
1244 const double point[3] = {99.9995, 0., 0.};
1245 const double dir[3] = {1., 0., 0.};
1246 double normal[3] = {0., 0., 0.};
1247 solid.ComputeNormal(point, dir, normal);
1248 BOOST_CHECK_SMALL(normal[0] - 1., 1.e-9);
1249 BOOST_CHECK_SMALL(normal[1], 1.e-9);
1250 BOOST_CHECK_SMALL(normal[2], 1.e-9);
1264 O2FlatCSG solid(
"flat_cell");
1266 const double planes[6][2][3] = {
1267 {{1., 0., 0.}, {50., 0., 0.}}, {{-1., 0., 0.}, {-50., 0., 0.}}, {{0., 1., 0.}, {0., 50., 0.}}, {{0., -1., 0.}, {0., -50., 0.}}, {{0., 0., 1.}, {0., 0., 0.}}, {{0., 0., -1.}, {0., 0., 0.}}};
1268 for (
const auto& plane : planes) {
1269 planeQuadric(plane[0], plane[1], coeff);
1270 solid.AddQuadric(1., coeff);
1272 solid.AddCell(0, 6, 0.);
1273 const double lo[3] = {-50., -50., 0.};
1274 const double hi[3] = {50., 50., 0.};
1275 solid.SetCellBBox(0, lo, hi);
1278 BOOST_REQUIRE(solid.IsClosed());
1281 BOOST_CHECK_LT(solid.GetNboxes(), 600);
1286 O2FlatCSG original(
"bracket_io");
1287 buildBracket(original);
1288 original.CloseShape();
1290 const std::string
path =
"testFlatCSG_roundtrip.bin";
1293 O2FlatCSG loaded(
"bracket_io_loaded");
1295 loaded.CloseShape();
1303 for (
int trial = 0; trial < 100000; ++trial) {
1304 const double point[3] = {rng.uniform(-13., 13.), rng.uniform(-5., 5.), rng.uniform(-3., 14.)};
1305 BOOST_REQUIRE_EQUAL(loaded.Contains(point), original.Contains(point));
1307 std::filesystem::remove(
path);
1316 const std::string
path =
"testFlatCSG_unclosed.bin";
1318 O2FlatCSG neverClosed(
"bracket_never_closed");
1319 buildBracket(neverClosed);
1320 BOOST_REQUIRE(!neverClosed.IsClosed());
1324 O2FlatCSG refused(
"bracket_refused_close");
1326 const double plane[2][3] = {{1., 0., 0.}, {0., 0., 0.}};
1327 planeQuadric(plane[0], plane[1], coeff);
1328 refused.AddQuadric(1., coeff);
1329 refused.AddCell(0, 1, 0.);
1330 refused.CloseShape();
1331 BOOST_REQUIRE(!refused.IsClosed());
1338 O2FlatCSG original(
"bracket_trunc");
1339 buildBracket(original);
1340 original.CloseShape();
1341 const std::string
path =
"testFlatCSG_truncated.bin";
1343 std::filesystem::resize_file(
path, std::filesystem::file_size(
path) - 17);
1345 O2FlatCSG loaded(
"bracket_trunc_loaded");
1347 std::filesystem::remove(
path);
1352 O2FlatCSG original(
"bracket_root");
1353 buildBracket(original);
1354 original.CloseShape();
1356 const std::string
path =
"testFlatCSG_shape.root";
1358 TFile file(
path.c_str(),
"RECREATE");
1359 file.WriteObject(&original,
"shape");
1361 O2FlatCSG* restored =
nullptr;
1363 TFile file(
path.c_str(),
"READ");
1364 file.GetObject(
"shape", restored);
1366 BOOST_REQUIRE(restored !=
nullptr);
1367 restored->CloseShape();
1370 for (
int trial = 0; trial < 100000; ++trial) {
1371 const double point[3] = {rng.uniform(-13., 13.), rng.uniform(-5., 5.), rng.uniform(-3., 14.)};
1372 BOOST_REQUIRE_EQUAL(restored->Contains(point), original.Contains(point));
1374 std::filesystem::remove(
path);
1380void addBoxAsCell(O2FlatCSG& solid,
const double* lo,
const double* hi)
1382 const int first = solid.GetNhalfspaces();
1384 for (
int axis = 0; axis < 3; ++axis) {
1385 for (
int sense = -1; sense <= 1; sense += 2) {
1386 double normal[3] = {0., 0., 0.};
1387 double through[3] = {0., 0., 0.};
1388 normal[axis] =
static_cast<double>(sense);
1389 through[axis] = sense > 0 ? hi[axis] : lo[axis];
1390 planeQuadric(normal, through, coeff);
1391 solid.AddQuadric(1., coeff);
1394 const int cell = solid.AddCell(
first, 6, (hi[0] - lo[0]) * (hi[1] - lo[1]) * (hi[2] - lo[2]));
1395 solid.SetCellBBox(cell, lo, hi);
1401void buildStaggeredChain(O2FlatCSG& solid)
1403 const double nearLo[3] = {0., -1., -1.};
1404 const double nearHi[3] = {1., 20., 1.};
1405 const double farLo[3] = {5., -1., -1.};
1406 const double farHi[3] = {6., 20., 1.};
1407 const double middleLo[3] = {1., -1., -1.};
1408 const double middleHi[3] = {5., 1., 1.};
1409 addBoxAsCell(solid, nearLo, nearHi);
1410 addBoxAsCell(solid, farLo, farHi);
1411 addBoxAsCell(solid, middleLo, middleHi);
1418 O2FlatCSG solid(
"staggered");
1419 buildStaggeredChain(solid);
1423 const double point[3] = {0.5, 0., 0.};
1424 const double dir[3] = {1., 0., 0.};
1425 O2FlatCSG::ResetUnprunedRetryCounter();
1426 const double distance = solid.DistFromInside(point, dir, 3, TGeoShape::Big(),
nullptr);
1428 BOOST_CHECK_CLOSE(
distance, 5.5, 1.e-9);
1429 BOOST_CHECK_GT(O2FlatCSG::GetUnprunedRetryCount(), 0);
1434 O2FlatCSG solid(
"staggered_random");
1435 buildStaggeredChain(solid);
1439 for (
int trial = 0; trial < 100000; ++trial) {
1440 double point[3] = {rng.uniform(-1., 7.), rng.uniform(-2., 21.), rng.uniform(-2., 2.)};
1441 if (!solid.Contains_Loop(point)) {
1448 dir[
index] = rng.uniform(-1., 1.);
1450 norm = std::sqrt(dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2]);
1451 }
while (norm < 1.e-3);
1456 BOOST_REQUIRE_EQUAL(solid.DistFromInside(point, dir, 3, TGeoShape::Big(),
nullptr),
1457 solid.DistFromInside_Loop(point, dir, TGeoShape::Big()));
1459 BOOST_REQUIRE_EQUAL(solid.DistFromInside(point, dir, 3, 2.,
nullptr),
1460 solid.DistFromInside_Loop(point, dir, 2.));
1462 BOOST_CHECK_GT(inside, 1000);
header::DataOrigin origin
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat s1
GLdouble GLdouble GLdouble GLdouble top
GLenum GLuint GLint GLenum face
GLboolean GLboolean GLboolean b
GLsizei GLsizei GLfloat distance
GLsizei const GLfloat * value
GLenum GLsizei GLsizei GLint * values
GLsizei const GLchar *const * path
GLboolean GLboolean GLboolean GLboolean a
GLsizei const GLint * box
bool LoadFlatCSG(const std::string &file, O2FlatCSG &solid)
Load a flat-CSG sidecar (flatcsg_*.bin, version 1) into solid; call CloseShape() after....
bool WriteFlatCSG(const std::string &file, const O2FlatCSG &solid)
std::map< std::string, ID > expected
BOOST_AUTO_TEST_CASE(box_from_six_planes_contains_like_TGeoBBox)
BOOST_CHECK_EQUAL(triggersD.size(), triggers.size())