File indexing completed on 2025-10-30 07:55:06
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include "Acts/Definitions/Algebra.hpp"
0010 #include "Acts/Definitions/Units.hpp"
0011 #include "Acts/Surfaces/BoundaryTolerance.hpp"
0012 #include "Acts/Utilities/BoundingBox.hpp"
0013 #include "Acts/Utilities/Frustum.hpp"
0014 #include "Acts/Utilities/Ray.hpp"
0015 #include "ActsTests/CommonHelpers/BenchmarkTools.hpp"
0016
0017 #include <algorithm>
0018 #include <chrono>
0019 #include <functional>
0020 #include <iostream>
0021 #include <map>
0022 #include <numbers>
0023 #include <random>
0024 #include <vector>
0025
0026 using namespace Acts;
0027 using namespace ActsTests;
0028
0029 struct O {};
0030 using Box = AxisAlignedBoundingBox<O, float, 3>;
0031 using VertexType = Box::VertexType;
0032 using vertex_array_type = Box::vertex_array_type;
0033 using value_type = Box::value_type;
0034 using Frustum3 = Frustum<float, 3, 4>;
0035 using Ray3 = Ray<float, 3>;
0036
0037 int main(int , char** ) {
0038 std::size_t n = 1000;
0039
0040 std::mt19937 rng(42);
0041 std::uniform_real_distribution<float> dir(0, 1);
0042 std::uniform_real_distribution<float> loc(-10, 10);
0043 std::uniform_real_distribution<float> ang(std::numbers::pi / 10.,
0044 std::numbers::pi / 4.);
0045
0046 Box testBox{nullptr, {0, 0, 0}, Box::Size{{1, 2, 3}}};
0047
0048 std::cout << "\n==== RAY ====\n" << std::endl;
0049
0050 std::map<std::string, bool (*)(const Box&, const Ray3&)> rayVariants;
0051
0052 rayVariants["Nominal"] = [](const auto& box, const auto& ray) -> bool {
0053 return box.intersect(ray);
0054 };
0055
0056 rayVariants["Incl. div., unroll"] = [](const Box& box,
0057 const Ray<float, 3>& ray) {
0058 const VertexType& origin = ray.origin();
0059
0060 const vertex_array_type& d = ray.dir();
0061
0062 double tmin = -INFINITY, tmax = INFINITY;
0063 if (d.x() != 0.0) {
0064 double tx1 = (box.min().x() - origin.x()) / d.x();
0065 double tx2 = (box.max().x() - origin.x()) / d.x();
0066
0067 tmin = std::max(tmin, std::min(tx1, tx2));
0068 tmax = std::min(tmax, std::max(tx1, tx2));
0069 }
0070
0071 if (d.y() != 0.0) {
0072 double ty1 = (box.min().y() - origin.y()) / d.y();
0073 double ty2 = (box.max().y() - origin.y()) / d.y();
0074
0075 tmin = std::max(tmin, std::min(ty1, ty2));
0076 tmax = std::min(tmax, std::max(ty1, ty2));
0077 }
0078
0079 if (d.z() != 0.0) {
0080 double tz1 = (box.min().z() - origin.z()) / d.z();
0081 double tz2 = (box.max().z() - origin.z()) / d.z();
0082
0083 tmin = std::max(tmin, std::min(tz1, tz2));
0084 tmax = std::min(tmax, std::max(tz1, tz2));
0085 }
0086
0087 return tmax > tmin && tmax > 0.0;
0088 };
0089
0090 rayVariants["Incl. div., loop"] = [](const Box& box,
0091 const Ray<float, 3>& ray) {
0092 const VertexType& origin = ray.origin();
0093
0094 const vertex_array_type& d = ray.dir();
0095
0096 double tmin = -INFINITY, tmax = INFINITY;
0097
0098 for (std::size_t i = 0; i < 3; i++) {
0099 if (d[i] == 0.0) {
0100 continue;
0101 }
0102 double t1 = (box.min()[i] - origin[i]) / d[i];
0103 double t2 = (box.max()[i] - origin[i]) / d[i];
0104 tmin = std::max(tmin, std::min(t1, t2));
0105 tmax = std::min(tmax, std::max(t1, t2));
0106 }
0107
0108 return tmax > tmin && tmax > 0.0;
0109 };
0110
0111 rayVariants["Incl. div., min/max alt., unroll"] =
0112 [](const Box& box, const Ray<float, 3>& ray) {
0113 const VertexType& origin = ray.origin();
0114 const vertex_array_type& d = ray.dir();
0115
0116 double tx1 = (box.min().x() - origin.x()) / d.x();
0117 double tx2 = (box.max().x() - origin.x()) / d.x();
0118 double tmin = std::min(tx1, tx2);
0119 double tmax = std::max(tx1, tx2);
0120
0121 double ty1 = (box.min().y() - origin.y()) / d.y();
0122 double ty2 = (box.max().y() - origin.y()) / d.y();
0123 tmin = std::max(tmin, std::min(ty1, ty2));
0124 tmax = std::min(tmax, std::max(ty1, ty2));
0125
0126 double tz1 = (box.min().z() - origin.z()) / d.z();
0127 double tz2 = (box.max().z() - origin.z()) / d.z();
0128 tmin = std::max(tmin, std::min(tz1, tz2));
0129 tmax = std::min(tmax, std::max(tz1, tz2));
0130
0131 return tmax > tmin && tmax > 0.0;
0132 };
0133
0134 rayVariants["No div., min/max alt, unroll"] = [](const Box& box,
0135 const Ray<float, 3>& ray) {
0136 const VertexType& origin = ray.origin();
0137 const vertex_array_type& id = ray.idir();
0138
0139 double tx1 = (box.min().x() - origin.x()) * id.x();
0140 double tx2 = (box.max().x() - origin.x()) * id.x();
0141 double tmin = std::min(tx1, tx2);
0142 double tmax = std::max(tx1, tx2);
0143
0144 double ty1 = (box.min().y() - origin.y()) * id.y();
0145 double ty2 = (box.max().y() - origin.y()) * id.y();
0146 tmin = std::max(tmin, std::min(ty1, ty2));
0147 tmax = std::min(tmax, std::max(ty1, ty2));
0148
0149 double tz1 = (box.min().z() - origin.z()) * id.z();
0150 double tz2 = (box.max().z() - origin.z()) * id.z();
0151 tmin = std::max(tmin, std::min(tz1, tz2));
0152 tmax = std::min(tmax, std::max(tz1, tz2));
0153
0154 return tmax > tmin && tmax > 0.0;
0155 };
0156
0157 rayVariants["No div., min/max orig, loop"] = [](const Box& box,
0158 const Ray<float, 3>& ray) {
0159 const VertexType& origin = ray.origin();
0160 const vertex_array_type& id = ray.idir();
0161 double tmin = -INFINITY, tmax = INFINITY;
0162
0163 for (std::size_t i = 0; i < 3; i++) {
0164 double t1 = (box.min()[i] - origin[i]) * id[i];
0165 double t2 = (box.max()[i] - origin[i]) * id[i];
0166 tmin = std::max(tmin, std::min(t1, t2));
0167 tmax = std::min(tmax, std::max(t1, t2));
0168 }
0169
0170 return tmax > tmin && tmax > 0.0;
0171 };
0172
0173 using Vector3F = Eigen::Matrix<float, 3, 1>;
0174
0175 std::vector<Ray3> rays{n, Ray3{Vector3F{0, 0, 0}, Vector3F{1, 0, 0}}};
0176 std::generate(rays.begin(), rays.end(), [&]() {
0177 const Vector3F d{dir(rng), dir(rng), dir(rng)};
0178 const Vector3F l{loc(rng), loc(rng), loc(rng)};
0179 return Ray3{l, d.normalized()};
0180 });
0181
0182 std::cout << "Make sure ray implementations are identical" << std::endl;
0183 for (const auto& ray : rays) {
0184 std::vector<std::pair<std::string, bool>> results;
0185
0186 std::transform(rayVariants.begin(), rayVariants.end(),
0187 std::back_inserter(results),
0188 [&](const auto& p) -> decltype(results)::value_type {
0189 const auto& [name, func] = p;
0190 return {name, func(testBox, ray)};
0191 });
0192
0193 bool all =
0194 std::ranges::all_of(results, [](const auto& r) { return r.second; });
0195 bool none =
0196 std::ranges::none_of(results, [](const auto& r) { return r.second; });
0197
0198 if (!all && !none) {
0199 std::cerr << "Discrepancy: " << std::endl;
0200 for (const auto& [name, result] : results) {
0201 std::cerr << " - " << name << ": " << result << std::endl;
0202 }
0203
0204 testBox.toStream(std::cerr);
0205 std::cerr << std::endl;
0206 std::cerr << "Ray: [" << ray.origin().transpose() << "], ["
0207 << ray.dir().transpose() << "]" << std::endl;
0208 return -1;
0209 }
0210 }
0211 std::cout << "Seems ok" << std::endl;
0212
0213 std::cout << "Run benchmarks: " << std::endl;
0214 for (const auto& p : rayVariants) {
0215
0216 std::cout << "- Benchmarking variant: '" << p.first << "'" << std::endl;
0217 auto bench_result = ActsTests::microBenchmark(
0218 [&](const auto& ray) { return p.second(testBox, ray); }, rays);
0219 std::cout << " " << bench_result << std::endl;
0220 }
0221
0222 std::cout << "\n==== FRUSTUM ====\n" << std::endl;
0223
0224 std::map<std::string, bool (*)(const Box&, const Frustum3&)> frustumVariants;
0225
0226 frustumVariants["Nominal"] = [](const auto& box,
0227 const auto& frustum) -> bool {
0228 return box.intersect(frustum);
0229 };
0230
0231 frustumVariants["Manual constexpr loop unroll, early ret."] =
0232 [](const Box& box, const Frustum3& fr) {
0233 constexpr std::size_t sides = 4;
0234
0235
0236 const auto& normals = fr.normals();
0237 const vertex_array_type fr_vmin = box.min() - fr.origin();
0238 const vertex_array_type fr_vmax = box.max() - fr.origin();
0239
0240 auto calc = [&](const auto& normal) {
0241 return (normal.array() < 0).template cast<value_type>() * fr_vmin +
0242 (normal.array() >= 0).template cast<value_type>() * fr_vmax;
0243 };
0244
0245 VertexType p_vtx;
0246
0247 p_vtx = calc(normals[0]);
0248 if (p_vtx.dot(normals[0]) < 0) {
0249 return false;
0250 }
0251
0252 p_vtx = calc(normals[1]);
0253 if (p_vtx.dot(normals[1]) < 0) {
0254 return false;
0255 }
0256
0257 p_vtx = calc(normals[2]);
0258 if (p_vtx.dot(normals[2]) < 0) {
0259 return false;
0260 }
0261
0262 if constexpr (sides > 2) {
0263 p_vtx = calc(normals[3]);
0264 if (p_vtx.dot(normals[3]) < 0) {
0265 return false;
0266 }
0267 }
0268
0269 if constexpr (sides > 3) {
0270 p_vtx = calc(normals[4]);
0271 if (p_vtx.dot(normals[4]) < 0) {
0272 return false;
0273 }
0274 }
0275
0276 if constexpr (sides > 4) {
0277 for (std::size_t i = 5; i <= fr.sides; i++) {
0278 const VertexType& normal = normals[i];
0279
0280 p_vtx = calc(normal);
0281 if (p_vtx.dot(normal) < 0) {
0282 return false;
0283 }
0284 }
0285 }
0286
0287 return true;
0288 };
0289
0290 frustumVariants["Nominal, no early ret."] = [](const Box& box,
0291 const Frustum3& fr) {
0292 const auto& normals = fr.normals();
0293 const vertex_array_type fr_vmin = box.min() - fr.origin();
0294 const vertex_array_type fr_vmax = box.max() - fr.origin();
0295
0296 VertexType p_vtx;
0297 bool result = true;
0298 for (std::size_t i = 0; i < fr.sides + 1; i++) {
0299 const VertexType& normal = normals[i];
0300
0301 p_vtx = (normal.array() < 0).template cast<value_type>() * fr_vmin +
0302 (normal.array() >= 0).template cast<value_type>() * fr_vmax;
0303
0304 result = result && (p_vtx.dot(normal) >= 0);
0305 }
0306 return result;
0307 };
0308
0309 frustumVariants["Manual constexpr unroll, early ret."] =
0310 [](const Box& box, const Frustum3& fr) {
0311 constexpr std::size_t sides = 4;
0312
0313
0314 const auto& normals = fr.normals();
0315 const vertex_array_type fr_vmin = box.min() - fr.origin();
0316 const vertex_array_type fr_vmax = box.max() - fr.origin();
0317
0318 auto calc = [&](const auto& normal) {
0319 return (normal.array() < 0).template cast<value_type>() * fr_vmin +
0320 (normal.array() >= 0).template cast<value_type>() * fr_vmax;
0321 };
0322
0323 VertexType p_vtx;
0324 bool result = true;
0325
0326 p_vtx = calc(normals[0]);
0327 result = result && (p_vtx.dot(normals[0]) >= 0);
0328
0329 p_vtx = calc(normals[1]);
0330 result = result && (p_vtx.dot(normals[1]) >= 0);
0331
0332 p_vtx = calc(normals[2]);
0333 result = result && (p_vtx.dot(normals[2]) >= 0);
0334
0335 if constexpr (sides > 2) {
0336 p_vtx = calc(normals[3]);
0337 result = result && (p_vtx.dot(normals[3]) >= 0);
0338 }
0339
0340 if constexpr (sides > 3) {
0341 p_vtx = calc(normals[4]);
0342 result = result && (p_vtx.dot(normals[4]) >= 0);
0343 }
0344
0345 if constexpr (sides > 4) {
0346 for (std::size_t i = 5; i <= fr.sides; i++) {
0347 const VertexType& normal = normals[i];
0348
0349 p_vtx = calc(normal);
0350 result = result && (p_vtx.dot(normal) >= 0);
0351 }
0352 }
0353
0354 return result;
0355 };
0356
0357 std::vector<Frustum3> frustums{
0358 n, Frustum3{{0, 0, 0}, {1, 0, 0}, std::numbers::pi / 2.}};
0359 std::generate(frustums.begin(), frustums.end(), [&]() {
0360 const Vector3F d{dir(rng), dir(rng), dir(rng)};
0361 const Vector3F l{loc(rng), loc(rng), loc(rng)};
0362 return Frustum3{l, d.normalized(), ang(rng)};
0363 });
0364
0365 std::cout << "Make sure frustum implementations are identical" << std::endl;
0366 for (const auto& fr : frustums) {
0367 std::vector<std::pair<std::string, bool>> results;
0368
0369 std::transform(frustumVariants.begin(), frustumVariants.end(),
0370 std::back_inserter(results),
0371 [&](const auto& p) -> decltype(results)::value_type {
0372 const auto& [name, func] = p;
0373 return {name, func(testBox, fr)};
0374 });
0375
0376 bool all =
0377 std::ranges::all_of(results, [](const auto& r) { return r.second; });
0378 bool none =
0379 std::ranges::none_of(results, [](const auto& r) { return r.second; });
0380
0381 if (!all && !none) {
0382 std::cerr << "Discrepancy: " << std::endl;
0383 for (const auto& [name, result] : results) {
0384 std::cerr << " - " << name << ": " << result << std::endl;
0385 }
0386
0387 testBox.toStream(std::cerr);
0388 std::cerr << std::endl;
0389 std::cerr << "Frustum: [" << fr.origin().transpose() << "], ["
0390 << fr.dir().transpose() << "]" << std::endl;
0391 return -1;
0392 }
0393 }
0394 std::cout << "Seems ok" << std::endl;
0395
0396 std::size_t iters_per_run = 1000;
0397
0398 std::vector<std::pair<std::string, Frustum3>> testFrusts = {
0399 {"away", Frustum3{{0, 0, -10}, {0, 0, -1}, std::numbers::pi / 4.}},
0400 {"towards", Frustum3{{0, 0, -10}, {0, 0, 1}, std::numbers::pi / 4.}},
0401 {"left", Frustum3{{0, 0, -10}, {0, 1, 0}, std::numbers::pi / 4.}},
0402 {"right", Frustum3{{0, 0, -10}, {0, -1, 0}, std::numbers::pi / 4.}},
0403 {"up", Frustum3{{0, 0, -10}, {1, 0, 0}, std::numbers::pi / 4.}},
0404 {"down", Frustum3{{0, 0, -10}, {-1, 0, 0}, std::numbers::pi / 4.}},
0405 };
0406
0407 std::cout << "Run benchmarks: " << std::endl;
0408
0409 for (const auto& fr_pair : testFrusts) {
0410 std::cout << "Frustum '" << fr_pair.first << "'" << std::endl;
0411
0412 for (const auto& p : frustumVariants) {
0413
0414 std::cout << "- Benchmarking variant: '" << p.first << "'" << std::endl;
0415 auto bench_result = ActsTests::microBenchmark(
0416 [&]() { return p.second(testBox, fr_pair.second); }, iters_per_run);
0417 std::cout << " " << bench_result << std::endl;
0418 }
0419
0420 std::cout << std::endl;
0421 }
0422 return 0;
0423 }