Project
Loading...
Searching...
No Matches
O2SurfaceSolidIO.cxx
Go to the documentation of this file.
1// Copyright 2019-2026 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
13
16
21
22#include "BoundedSurface.h"
23
24#include <TError.h>
25
26#include <cmath>
27#include <cstdint>
28#include <cstring>
29#include <fstream>
30#include <vector>
31
32namespace o2
33{
34namespace cad
35{
36
38
39namespace
40{
41
44constexpr uint32_t kSidecarVersionMin = 1;
45constexpr uint32_t kSidecarVersionMax = 3;
46
48constexpr double kSidecarV1FallbackTolerance = 1.e-6;
49
50constexpr uint32_t kFlagInnerWall = 1u << 0;
51
53constexpr uint32_t kFlatCSGVersion = 1;
54constexpr uint64_t kFlatCSGHalfspaceBytes = 100;
55constexpr uint64_t kFlatCSGCellBytes = 64;
56
57enum SurfaceType : uint32_t {
58 kPlane = 1,
59 kCylinder = 2,
60 kCone = 3,
61 kSphere = 4,
62 kTorus = 5,
63};
64
65enum CurveType : uint32_t {
66 kLineSegment = 0,
67 kCircularArc = 1,
68 kBSpline2D = 2,
69};
70
72bool parseBSplineEdge(const std::vector<double>& params, O2BVHSurfaceSolid::PlanarBoundaryCurve& curve)
73{
74 if (params.size() < 2) {
75 return false;
76 }
77 const int degree = static_cast<int>(std::lround(params[0]));
78 const int nPoles = static_cast<int>(std::lround(params[1]));
79 if (degree < 1 || nPoles < degree + 1) {
80 return false;
81 }
82 const size_t nKnots = static_cast<size_t>(nPoles) + degree + 1;
83 const size_t expected = 2 + 2 * static_cast<size_t>(nPoles) + static_cast<size_t>(nPoles) + nKnots;
84 if (params.size() < expected) {
85 return false;
86 }
87 std::vector<O2BVHSurfaceSolid::Point2D> poles(nPoles);
88 size_t offset = 2;
89 for (int i = 0; i < nPoles; ++i) {
90 poles[i] = {params[offset], params[offset + 1]};
91 offset += 2;
92 }
93 std::vector<double> weights(nPoles);
94 for (int i = 0; i < nPoles; ++i) {
95 weights[i] = params[offset++];
96 }
97 std::vector<double> knots(nKnots);
98 for (size_t i = 0; i < nKnots; ++i) {
99 knots[i] = params[offset++];
100 }
101 curve = O2BVHSurfaceSolid::PlanarBoundaryCurve::makeBSpline(degree, std::move(poles), std::move(weights),
102 std::move(knots));
103 return true;
104}
105
106struct SidecarEdge {
107 uint32_t curveType = 0;
108 std::vector<double> params;
109};
110
111struct SidecarWire {
112 uint32_t role = 0; // 0 = outer, 1 = inner
113 std::vector<SidecarEdge> edges;
114};
115
117template <typename T>
118bool readValue(std::ifstream& in, T& value)
119{
120 in.read(reinterpret_cast<char*>(&value), sizeof(value));
121 return static_cast<bool>(in);
122}
123
125template <typename T>
126void writeValue(std::ofstream& out, const T& value)
127{
128 out.write(reinterpret_cast<const char*>(&value), sizeof(value));
129}
130
132uint64_t bytesRemaining(std::ifstream& in, std::streamoff fileSize)
133{
134 if (!in) {
135 return 0;
136 }
137 const std::streamoff here = in.tellg();
138 return here < 0 || here > fileSize ? 0 : static_cast<uint64_t>(fileSize - here);
139}
140
141bool readDoubles(std::ifstream& in, std::vector<double>& values, uint32_t n, std::streamoff fileSize)
142{
143 if (static_cast<uint64_t>(n) * sizeof(double) > bytesRemaining(in, fileSize)) {
144 return false;
145 }
146 values.resize(n);
147 in.read(reinterpret_cast<char*>(values.data()), static_cast<std::streamsize>(n) * sizeof(double));
148 return static_cast<bool>(in);
149}
150
151O2BVHSurfaceSolid::Point3D point3(const std::vector<double>& p, size_t offset)
152{
153 return {p[offset], p[offset + 1], p[offset + 2]};
154}
155
157bool edgeEndpoints(const SidecarEdge& edge, O2BVHSurfaceSolid::Point2D& start, O2BVHSurfaceSolid::Point2D& end,
158 O2BVHSurfaceSolid::PlanarBoundaryCurve& bspline)
159{
160 if (edge.curveType == kLineSegment && edge.params.size() >= 4) {
161 start = {edge.params[0], edge.params[1]};
162 end = {edge.params[2], edge.params[3]};
163 return true;
164 }
165 if (edge.curveType == kCircularArc && edge.params.size() >= 5) {
166 const double cu = edge.params[0], cv = edge.params[1], r = edge.params[2];
167 const double a0 = edge.params[3], a1 = edge.params[3] + edge.params[4];
168 start = {cu + r * std::cos(a0), cv + r * std::sin(a0)};
169 end = {cu + r * std::cos(a1), cv + r * std::sin(a1)};
170 return true;
171 }
172 if (edge.curveType == kBSpline2D) {
173 if (!parseBSplineEdge(edge.params, bspline)) {
174 return false;
175 }
176 // Evaluate the curve rather than read its first and last poles, which lie off the curve for an
177 // unclamped or periodic knot vector.
178 std::vector<surface::Vec2> poles;
179 poles.reserve(bspline.poles.size());
180 for (const auto& pole : bspline.poles) {
181 poles.push_back({pole[0], pole[1]});
182 }
183 const surface::Curve2D evaluated =
184 surface::Curve2D::makeBSpline(bspline.degree, std::move(poles), bspline.weights, bspline.knots);
185 const surface::Vec2 first = evaluated.startPoint();
186 const surface::Vec2 last = evaluated.endPoint();
187 start = {first.uCoord, first.vCoord};
188 end = {last.uCoord, last.vCoord};
189 return true;
190 }
191 return false;
192}
193
195struct RecordMetric {
196 uint32_t surfaceType = 0;
197 const double* params = nullptr;
198
199 static void evaluate(const void* context, const surface::Vec2& uv, double& gUU, double& gUV, double& gVV)
200 {
201 const auto& record = *static_cast<const RecordMetric*>(context);
202 const double* p = record.params;
203 switch (record.surfaceType) {
204 case kPlane:
205 surface::planeParametricMetric({p[3], p[4], p[5]}, {p[6], p[7], p[8]}, gUU, gUV, gVV);
206 return;
207 case kCylinder:
208 surface::cylinderParametricMetric(p[9], gUU, gUV, gVV);
209 return;
210 case kCone: {
211 // r(h) = radiusAtMin + slope * (h - heightMin), with slope from the two radii/heights
212 const double slope = (p[10] - p[9]) / (p[12] - p[11]);
213 surface::coneParametricMetric(p[9] + slope * (uv.vCoord - p[11]), slope, gUU, gUV, gVV);
214 return;
215 }
216 case kSphere:
217 surface::sphereParametricMetric(p[9], uv.vCoord, gUU, gUV, gVV);
218 return;
219 case kTorus:
220 surface::torusParametricMetric(p[9], p[10], uv.vCoord, gUU, gUV, gVV);
221 return;
222 default:
223 // an unknown type is rejected further down; the identity keeps this total meanwhile
224 gUU = 1.;
225 gUV = 0.;
226 gVV = 1.;
227 return;
228 }
229 }
230
231 surface::ParametricMetric metric() const { return {&evaluate, this}; }
232};
233
236bool wireToCurves(const std::string& file, size_t surfaceIndex, const SidecarWire& wire,
237 std::vector<O2BVHSurfaceSolid::PlanarBoundaryCurve>& curves, bool& anyArc,
238 const surface::ParametricMetric& metric, double joinTolerance, const char* toleranceOrigin)
239{
240 using Curve = O2BVHSurfaceSolid::PlanarBoundaryCurve;
241 curves.clear();
242 curves.reserve(wire.edges.size());
243 // every edge's endpoints, and a B-spline edge's parsed curve, computed once
244 const size_t nEdges = wire.edges.size();
245 std::vector<O2BVHSurfaceSolid::Point2D> starts(nEdges);
246 std::vector<O2BVHSurfaceSolid::Point2D> ends(nEdges);
247 std::vector<Curve> bsplines(nEdges);
248 for (size_t e = 0; e < nEdges; ++e) {
249 if (!edgeEndpoints(wire.edges[e], starts[e], ends[e], bsplines[e])) {
250 ::Error("LoadSurfaceSolid", "%s: surface %zu: unsupported or malformed wire edge %zu", file.c_str(),
251 surfaceIndex, e);
252 return false;
253 }
254 }
255 for (size_t e = 0; e < nEdges; ++e) {
256 const auto& edge = wire.edges[e];
257 const auto& end = ends[e];
258 const auto& nextStart = starts[(e + 1) % nEdges];
259 const double joinGapSq = metric.distanceSq({end[0], end[1]}, {nextStart[0], nextStart[1]});
260 if (joinGapSq > joinTolerance * joinTolerance) {
261 ::Error("LoadSurfaceSolid",
262 "%s: surface %zu: wire edge %zu end does not join the next edge start (gap %.3g cm, tolerance %.3g cm, "
263 "%s)",
264 file.c_str(), surfaceIndex, e, std::sqrt(joinGapSq), joinTolerance, toleranceOrigin);
265 return false;
266 }
267 if (edge.curveType == kCircularArc) {
268 anyArc = true;
269 curves.push_back(Curve::makeArc({edge.params[0], edge.params[1]}, edge.params[2], edge.params[3],
270 edge.params[3] + edge.params[4]));
271 } else if (edge.curveType == kBSpline2D) {
272 anyArc = true; // a bspline is a curved edge, so route the plane through AddCurvedPlanarSurface
273 curves.push_back(std::move(bsplines[e]));
274 } else {
275 curves.push_back(Curve::makeLine(starts[e], end));
276 }
277 }
278 return true;
279}
280
282struct TrimWording {
283 const char* moreThanOneOuter;
284 const char* noOuter;
285};
286constexpr TrimWording kPlaneWording{"%s: plane surface %zu has more than one outer wire",
287 "%s: plane surface %zu has no outer wire"};
288constexpr TrimWording kQuadricWording{"%s: quadric surface %zu has more than one outer trim wire",
289 "%s: quadric surface %zu trim block has no outer wire"};
290
292bool collectTrim(const std::string& file, size_t surfaceIndex, const TrimWording& wording,
293 const std::vector<SidecarWire>& wires, std::vector<O2BVHSurfaceSolid::PlanarBoundaryCurve>& outer,
294 std::vector<std::vector<O2BVHSurfaceSolid::PlanarBoundaryCurve>>& inners, bool& anyArc,
295 const surface::ParametricMetric& metric, double joinTolerance, const char* toleranceOrigin)
296{
297 bool haveOuter = false;
298 for (const auto& wire : wires) {
299 std::vector<O2BVHSurfaceSolid::PlanarBoundaryCurve> curves;
300 if (!wireToCurves(file, surfaceIndex, wire, curves, anyArc, metric, joinTolerance, toleranceOrigin)) {
301 return false;
302 }
303 if (wire.role == 0) {
304 if (haveOuter) {
305 ::Error("LoadSurfaceSolid", wording.moreThanOneOuter, file.c_str(), surfaceIndex);
306 return false;
307 }
308 outer = std::move(curves);
309 haveOuter = true;
310 } else {
311 inners.push_back(std::move(curves));
312 }
313 }
314 if (!haveOuter) {
315 ::Error("LoadSurfaceSolid", wording.noOuter, file.c_str(), surfaceIndex);
316 return false;
317 }
318 return true;
319}
320
322void reorderEdgeRefsToKernelOrder(const std::vector<SidecarWire>& wires, std::vector<unsigned int>& edgeIds,
323 std::vector<unsigned char>& edgeFlags)
324{
325 size_t totalEdges = 0;
326 for (const auto& wire : wires) {
327 totalEdges += wire.edges.size();
328 }
329 if (wires.empty() || totalEdges != edgeIds.size()) {
330 return;
331 }
332 // kernel offset of each sidecar wire: the outer wire first, then the inner wires in file order
333 std::vector<size_t> kernelOffset(wires.size(), 0);
334 size_t running = 0;
335 for (size_t w = 0; w < wires.size(); ++w) {
336 if (wires[w].role == 0) {
337 kernelOffset[w] = 0;
338 running = wires[w].edges.size();
339 break;
340 }
341 }
342 for (size_t w = 0; w < wires.size(); ++w) {
343 if (wires[w].role != 0) {
344 kernelOffset[w] = running;
345 running += wires[w].edges.size();
346 }
347 }
348
349 std::vector<unsigned int> permutedIds(edgeIds.size());
350 std::vector<unsigned char> permutedFlags(edgeFlags.size());
351 size_t sidecarOffset = 0;
352 for (size_t w = 0; w < wires.size(); ++w) {
353 for (size_t e = 0; e < wires[w].edges.size(); ++e) {
354 permutedIds[kernelOffset[w] + e] = edgeIds[sidecarOffset + e];
355 permutedFlags[kernelOffset[w] + e] = edgeFlags[sidecarOffset + e];
356 }
357 sidecarOffset += wires[w].edges.size();
358 }
359 edgeIds.swap(permutedIds);
360 edgeFlags.swap(permutedFlags);
361}
362
363} // namespace
364
365bool LoadSurfaceSolid(const std::string& file, O2BVHSurfaceSolid& solid)
366{
367 std::ifstream in(file, std::ios::binary);
368 if (!in) {
369 ::Error("LoadSurfaceSolid", "Cannot open surface sidecar file %s", file.c_str());
370 return false;
371 }
372
373 in.seekg(0, std::ios::end);
374 const std::streamoff fileSize = in.tellg();
375 in.seekg(0, std::ios::beg);
376
377 char magic[4];
378 in.read(magic, sizeof(magic));
379 if (!in || std::memcmp(magic, "O2SS", 4) != 0) {
380 ::Error("LoadSurfaceSolid", "%s is not a surface sidecar file (bad magic)", file.c_str());
381 return false;
382 }
383
384 uint32_t version = 0, nSurfaces = 0, reserved = 0;
385 if (!readValue(in, version) || !readValue(in, nSurfaces) || !readValue(in, reserved)) {
386 ::Error("LoadSurfaceSolid", "%s: truncated header", file.c_str());
387 return false;
388 }
389 if (version < kSidecarVersionMin || version > kSidecarVersionMax) {
390 ::Error("LoadSurfaceSolid", "%s: unsupported sidecar version %u (reader supports %u..%u)", file.c_str(), version,
391 kSidecarVersionMin, kSidecarVersionMax);
392 return false;
393 }
394
395 uint32_t nModelEdges = 0;
396 if (version >= 2) {
397 double modelTolerance = 0.;
398 if (!readValue(in, modelTolerance)) {
399 ::Error("LoadSurfaceSolid", "%s: truncated version-2 header (no model tolerance)", file.c_str());
400 return false;
401 }
402 solid.SetModelTolerance(modelTolerance);
403 if (version >= 3 && !readValue(in, nModelEdges)) {
404 ::Error("LoadSurfaceSolid", "%s: truncated version-3 header (no edge table size)", file.c_str());
405 return false;
406 }
407 } else {
408 ::Warning("LoadSurfaceSolid",
409 "%s is a version-1 sidecar and states no model tolerance; assuming %g cm (the extractor's precision). "
410 "Re-run the converter to record the model's own value.",
411 file.c_str(), kSidecarV1FallbackTolerance);
412 solid.SetModelTolerance(kSidecarV1FallbackTolerance);
413 }
414
415 // the wire-join band, from the header: the band the kernel's Add*Surface applies to the same wires
416 const double joinTolerance = surface::wireJoinToleranceFor(solid.GetModelTolerance());
417 const char* toleranceOrigin = joinTolerance > surface::kWireJoinTolerance
418 ? "declared by the model"
419 : "the extractor-precision fallback";
420
421 for (size_t s = 0; s < nSurfaces; ++s) {
422 uint32_t surfaceType = 0, flags = 0, nParams = 0;
423 if (!readValue(in, surfaceType) || !readValue(in, flags) || !readValue(in, nParams)) {
424 ::Error("LoadSurfaceSolid", "%s: truncated surface record %zu", file.c_str(), s);
425 return false;
426 }
427 std::vector<double> p;
428 if (!readDoubles(in, p, nParams, fileSize)) {
429 ::Error("LoadSurfaceSolid", "%s: truncated parameters of surface %zu", file.c_str(), s);
430 return false;
431 }
432
433 // The wire block is self-describing; read it unconditionally.
434 uint32_t nWires = 0;
435 if (!readValue(in, nWires)) {
436 ::Error("LoadSurfaceSolid", "%s: truncated wire count of surface %zu", file.c_str(), s);
437 return false;
438 }
439 // 8 bytes of header per wire is the floor, so a count beyond that cannot be honest
440 if (static_cast<uint64_t>(nWires) * 8u > bytesRemaining(in, fileSize)) {
441 ::Error("LoadSurfaceSolid", "%s: surface %zu claims %u wires, more than the file holds", file.c_str(), s, nWires);
442 return false;
443 }
444 std::vector<SidecarWire> wires(nWires);
445 for (auto& wire : wires) {
446 uint32_t nEdges = 0;
447 if (!readValue(in, wire.role) || !readValue(in, nEdges)) {
448 ::Error("LoadSurfaceSolid", "%s: truncated wire header in surface %zu", file.c_str(), s);
449 return false;
450 }
451 if (static_cast<uint64_t>(nEdges) * 8u > bytesRemaining(in, fileSize)) {
452 ::Error("LoadSurfaceSolid", "%s: surface %zu claims %u wire edges, more than the file holds", file.c_str(), s,
453 nEdges);
454 return false;
455 }
456 wire.edges.resize(nEdges);
457 for (auto& edge : wire.edges) {
458 uint32_t nCurveParams = 0;
459 if (!readValue(in, edge.curveType) || !readValue(in, nCurveParams) ||
460 !readDoubles(in, edge.params, nCurveParams, fileSize)) {
461 ::Error("LoadSurfaceSolid", "%s: truncated edge record in surface %zu", file.c_str(), s);
462 return false;
463 }
464 }
465 }
466
467 // Version 3: the face's boundary edge identities, in the sidecar's own wire order.
468 std::vector<unsigned int> edgeIds;
469 std::vector<unsigned char> edgeFlags;
470 if (version >= 3) {
471 uint32_t nEdgeRefs = 0;
472 if (!readValue(in, nEdgeRefs)) {
473 ::Error("LoadSurfaceSolid", "%s: truncated edge identity count of surface %zu", file.c_str(), s);
474 return false;
475 }
476 if (static_cast<uint64_t>(nEdgeRefs) * 5u > bytesRemaining(in, fileSize)) {
477 ::Error("LoadSurfaceSolid", "%s: surface %zu claims %u edge identities, more than the file holds",
478 file.c_str(), s, nEdgeRefs);
479 return false;
480 }
481 edgeIds.resize(nEdgeRefs);
482 edgeFlags.resize(nEdgeRefs);
483 for (uint32_t e = 0; e < nEdgeRefs; ++e) {
484 uint32_t edgeId = 0;
485 uint8_t edgeFlag = 0;
486 if (!readValue(in, edgeId) || !readValue(in, edgeFlag)) {
487 ::Error("LoadSurfaceSolid", "%s: truncated edge identity %u of surface %zu", file.c_str(), e, s);
488 return false;
489 }
490 if (nModelEdges > 0 && edgeId >= nModelEdges) {
491 ::Error("LoadSurfaceSolid", "%s: surface %zu edge identity %u is %u, outside the model's %u edge(s)",
492 file.c_str(), s, e, edgeId, nModelEdges);
493 return false;
494 }
495 edgeIds[e] = edgeId;
496 edgeFlags[e] = edgeFlag;
497 }
498 }
499
500 const bool innerWall = (flags & kFlagInnerWall) != 0;
501 const RecordMetric recordMetric{surfaceType, p.data()};
502 bool added = false;
503
504 // one quadric: check the parameter count, then add the surface untrimmed or with its trim block
505 const auto addQuadric = [&](const char* name, uint32_t expectedParams, const auto& addUntrimmed,
506 const auto& addTrimmed) {
507 if (nParams != expectedParams) {
508 ::Error("LoadSurfaceSolid", "%s: %s surface %zu has %u parameters, expected %u", file.c_str(), name, s,
509 nParams, expectedParams);
510 return false;
511 }
512 if (wires.empty()) {
513 added = addUntrimmed();
514 return true;
515 }
516 std::vector<O2BVHSurfaceSolid::PlanarBoundaryCurve> outer;
517 std::vector<std::vector<O2BVHSurfaceSolid::PlanarBoundaryCurve>> inners;
518 bool anyArc = false; // quadric domains accept both line and arc trim edges
519 if (!collectTrim(file, s, kQuadricWording, wires, outer, inners, anyArc, recordMetric.metric(), joinTolerance,
520 toleranceOrigin)) {
521 return false;
522 }
523 added = addTrimmed(outer, inners);
524 return true;
525 };
526
527 switch (surfaceType) {
528 case kPlane: {
529 if (nParams != 9) {
530 ::Error("LoadSurfaceSolid", "%s: plane surface %zu has %u parameters, expected 9", file.c_str(), s, nParams);
531 return false;
532 }
533 // Read every wire as a general line/arc loop. A pure line-segment loop keeps the
534 // polygon path (AddPlanarSurface, general-metric); any arc routes to the curved path.
535 std::vector<O2BVHSurfaceSolid::PlanarBoundaryCurve> outer;
536 std::vector<std::vector<O2BVHSurfaceSolid::PlanarBoundaryCurve>> inners;
537 bool anyArc = false;
538 if (!collectTrim(file, s, kPlaneWording, wires, outer, inners, anyArc, recordMetric.metric(), joinTolerance,
539 toleranceOrigin)) {
540 return false;
541 }
542 if (anyArc) {
543 added = solid.AddCurvedPlanarSurface(point3(p, 0), point3(p, 3), point3(p, 6), outer, inners);
544 } else {
545 const auto toPolygon = [](const std::vector<O2BVHSurfaceSolid::PlanarBoundaryCurve>& curves) {
546 std::vector<O2BVHSurfaceSolid::Point2D> polygon;
547 polygon.reserve(curves.size());
548 for (const auto& c : curves) {
549 polygon.push_back(c.lineStart);
550 }
551 return polygon;
552 };
553 std::vector<std::vector<O2BVHSurfaceSolid::Point2D>> innerPolys;
554 innerPolys.reserve(inners.size());
555 for (const auto& inner : inners) {
556 innerPolys.push_back(toPolygon(inner));
557 }
558 added = solid.AddPlanarSurface(point3(p, 0), point3(p, 3), point3(p, 6), toPolygon(outer), innerPolys);
559 }
560 break;
561 }
562 case kCylinder:
563 if (!addQuadric(
564 "cylinder", 14,
565 [&] {
566 return solid.AddCylindricalSurface(point3(p, 0), point3(p, 3), point3(p, 6), p[9], p[10], p[11], p[12],
567 p[13], innerWall);
568 },
569 [&](const auto& outer, const auto& inners) {
570 return solid.AddCylindricalSurface(point3(p, 0), point3(p, 3), point3(p, 6), p[9], p[10], p[11], p[12],
571 p[13], innerWall, outer, inners);
572 })) {
573 return false;
574 }
575 break;
576 case kCone:
577 if (!addQuadric(
578 "cone", 15,
579 [&] {
580 return solid.AddConicalSurface(point3(p, 0), point3(p, 3), point3(p, 6), p[9], p[10], p[11], p[12],
581 p[13], p[14], innerWall);
582 },
583 [&](const auto& outer, const auto& inners) {
584 return solid.AddConicalSurface(point3(p, 0), point3(p, 3), point3(p, 6), p[9], p[10], p[11], p[12],
585 p[13], p[14], innerWall, outer, inners);
586 })) {
587 return false;
588 }
589 break;
590 case kSphere:
591 if (!addQuadric(
592 "sphere", 14,
593 [&] {
594 return solid.AddSphericalSurface(point3(p, 0), point3(p, 3), point3(p, 6), p[9], p[10], p[11], p[12],
595 p[13], innerWall);
596 },
597 [&](const auto& outer, const auto& inners) {
598 return solid.AddSphericalSurface(point3(p, 0), point3(p, 3), point3(p, 6), p[9], p[10], p[11], p[12],
599 p[13], innerWall, outer, inners);
600 })) {
601 return false;
602 }
603 break;
604 case kTorus:
605 if (!addQuadric(
606 "torus", 15,
607 [&] {
608 return solid.AddToroidalSurface(point3(p, 0), point3(p, 3), point3(p, 6), p[9], p[10], p[11], p[12],
609 p[13], p[14], innerWall);
610 },
611 [&](const auto& outer, const auto& inners) {
612 return solid.AddToroidalSurface(point3(p, 0), point3(p, 3), point3(p, 6), p[9], p[10], p[11], p[12],
613 p[13], p[14], innerWall, outer, inners);
614 })) {
615 return false;
616 }
617 break;
618 default:
619 ::Error("LoadSurfaceSolid", "%s: surface %zu has unknown surface type %u", file.c_str(), s, surfaceType);
620 return false;
621 }
622
623 if (!added) {
624 ::Error("LoadSurfaceSolid", "%s: surface %zu was rejected by O2BVHSurfaceSolid", file.c_str(), s);
625 return false;
626 }
627 if (!edgeIds.empty()) {
628 reorderEdgeRefsToKernelOrder(wires, edgeIds, edgeFlags);
629 solid.SetSurfaceBoundaryEdges(static_cast<int>(s), edgeIds, edgeFlags);
630 }
631 }
632
633 return true;
634}
635
636bool LoadFacetSolid(const std::string& file, O2Tessellated& solid)
637{
638 std::ifstream in(file, std::ios::binary);
639 if (!in) {
640 ::Error("LoadFacetSolid", "Cannot open facet sidecar file %s", file.c_str());
641 return false;
642 }
643
644 in.seekg(0, std::ios::end);
645 const std::streamoff fileSize = in.tellg();
646 in.seekg(0, std::ios::beg);
647
648 uint32_t nTriangles = 0;
649 if (!readValue(in, nTriangles)) {
650 ::Error("LoadFacetSolid", "%s: truncated header", file.c_str());
651 return false;
652 }
653
654 // one record is nine float32; the count is checked against the file before one block read
655 const uint64_t recordsBytes = static_cast<uint64_t>(nTriangles) * 9u * sizeof(float);
656 const uint64_t remaining = bytesRemaining(in, fileSize);
657 if (recordsBytes > remaining) {
658 ::Error("LoadFacetSolid", "%s: truncated: %u facet record(s) need %llu byte(s), found %llu", file.c_str(),
659 nTriangles, static_cast<unsigned long long>(recordsBytes), static_cast<unsigned long long>(remaining));
660 return false;
661 }
662 std::vector<float> records(9 * static_cast<size_t>(nTriangles));
663 in.read(reinterpret_cast<char*>(records.data()), static_cast<std::streamsize>(recordsBytes));
664 if (!in) {
665 ::Error("LoadFacetSolid", "%s: truncated facet records", file.c_str());
666 return false;
667 }
668
669 uint32_t nDegenerate = 0;
670 for (uint32_t i = 0; i < nTriangles; ++i) {
671 const float* v = &records[9 * static_cast<size_t>(i)];
672 const O2Tessellated::Vertex_t p0(v[0], v[1], v[2]);
673 const O2Tessellated::Vertex_t p1(v[3], v[4], v[5]);
674 const O2Tessellated::Vertex_t p2(v[6], v[7], v[8]);
675 if (!solid.AddFacet(p0, p1, p2)) {
676 // a degenerate facet is a mesh property, not a format error: count it and carry on
677 ++nDegenerate;
678 continue;
679 }
680 }
681 if (nDegenerate > 0) {
682 ::Warning("LoadFacetSolid", "%s: skipped %u degenerate facet(s) of %u", file.c_str(), nDegenerate, nTriangles);
683 }
684
685 return true;
686}
687
688bool LoadFlatCSG(const std::string& file, O2FlatCSG& solid)
689{
690 std::ifstream in(file, std::ios::binary);
691 if (!in) {
692 ::Error("LoadFlatCSG", "Cannot open flat-CSG sidecar file %s", file.c_str());
693 return false;
694 }
695
696 in.seekg(0, std::ios::end);
697 const std::streamoff fileSize = in.tellg();
698 in.seekg(0, std::ios::beg);
699
700 char magic[8];
701 in.read(magic, sizeof(magic));
702 if (!in || std::memcmp(magic, "O2FLTCSG", sizeof(magic)) != 0) {
703 ::Error("LoadFlatCSG", "%s is not a flat-CSG sidecar file (bad magic)", file.c_str());
704 return false;
705 }
706
707 uint32_t version = 0, nHalfspaces = 0, nCells = 0;
708 if (!readValue(in, version) || !readValue(in, nHalfspaces) || !readValue(in, nCells)) {
709 ::Error("LoadFlatCSG", "%s: truncated header", file.c_str());
710 return false;
711 }
712 if (version != kFlatCSGVersion) {
713 ::Error("LoadFlatCSG", "%s: unsupported sidecar version %u (reader supports %u)", file.c_str(), version,
714 kFlatCSGVersion);
715 return false;
716 }
717
718 // refuse a file whose length does not match its header before reading a record
719 const uint64_t expected =
720 static_cast<uint64_t>(nHalfspaces) * kFlatCSGHalfspaceBytes + static_cast<uint64_t>(nCells) * kFlatCSGCellBytes;
721 const uint64_t remaining = bytesRemaining(in, fileSize);
722 if (remaining != expected) {
723 ::Error("LoadFlatCSG",
724 "%s: file length does not match its header (%u halfspace(s) + %u cell(s) implies %llu more byte(s), "
725 "found %llu)",
726 file.c_str(), nHalfspaces, nCells, static_cast<unsigned long long>(expected),
727 static_cast<unsigned long long>(remaining));
728 return false;
729 }
730
731 // Every field below is read on its own -- see readValue's comment on why a struct-based read of
732 // the 100-byte halfspace record would be wrong for every record after the first.
733 for (uint32_t h = 0; h < nHalfspaces; ++h) {
734 int32_t kind = 0;
735 double sign = 0.;
736 if (!readValue(in, kind) || !readValue(in, sign)) {
737 ::Error("LoadFlatCSG", "%s: truncated halfspace record %u", file.c_str(), h);
738 return false;
739 }
740 double c[11];
741 bool ok = true;
742 for (int i = 0; i < 11 && ok; ++i) {
743 ok = readValue(in, c[i]);
744 }
745 if (!ok) {
746 ::Error("LoadFlatCSG", "%s: truncated halfspace record %u", file.c_str(), h);
747 return false;
748 }
749 bool finite = std::isfinite(sign);
750 for (double value : c) {
751 finite = finite && std::isfinite(value);
752 }
753 if (!finite) {
754 ::Error("LoadFlatCSG", "%s: halfspace %u has a non-finite coefficient", file.c_str(), h);
755 return false;
756 }
758 solid.AddQuadric(sign, c);
759 } else if (kind == FlatCSGHalfspace::kTorus) {
760 const double centre[3] = {c[0], c[1], c[2]};
761 const double axis[3] = {c[3], c[4], c[5]};
762 if (!(axis[0] * axis[0] + axis[1] * axis[1] + axis[2] * axis[2] > 0.)) {
763 ::Error("LoadFlatCSG", "%s: torus halfspace %u has a zero axis", file.c_str(), h);
764 return false;
765 }
766 solid.AddTorus(sign, centre, axis, c[6], c[7]);
767 } else {
768 ::Error("LoadFlatCSG", "%s: halfspace %u has unknown kind %d", file.c_str(), h, kind);
769 return false;
770 }
771 }
772
773 for (uint32_t cellIdx = 0; cellIdx < nCells; ++cellIdx) {
774 int32_t first = 0, count = 0;
775 double volume = 0.;
776 if (!readValue(in, first) || !readValue(in, count) || !readValue(in, volume)) {
777 ::Error("LoadFlatCSG", "%s: truncated cell record %u", file.c_str(), cellIdx);
778 return false;
779 }
780 double lo[3], hi[3];
781 bool ok = true;
782 for (int i = 0; i < 3 && ok; ++i) {
783 ok = readValue(in, lo[i]);
784 }
785 for (int i = 0; i < 3 && ok; ++i) {
786 ok = readValue(in, hi[i]);
787 }
788 if (!ok) {
789 ::Error("LoadFlatCSG", "%s: truncated cell record %u", file.c_str(), cellIdx);
790 return false;
791 }
792 if (first < 0 || count <= 0 || static_cast<int64_t>(first) + count > static_cast<int64_t>(nHalfspaces)) {
793 ::Error("LoadFlatCSG", "%s: cell %u has an invalid range (first=%d, count=%d) into %u halfspace(s)",
794 file.c_str(), cellIdx, first, count, nHalfspaces);
795 return false;
796 }
797 solid.AddCell(first, count, volume);
798 solid.SetCellBBox(static_cast<int>(cellIdx), lo, hi);
799 }
800
801 return true;
802}
803
804bool WriteFlatCSG(const std::string& file, const O2FlatCSG& solid)
805{
806 // refuse an unclosed shape: its unset cell boxes would read back as zeros and pass validation on reload
807 if (!solid.IsClosed()) {
808 ::Error("WriteFlatCSG",
809 "%s: shape %s is not closed (CloseShape() was never called, or refused); refusing to "
810 "write a sidecar that may encode a degenerate cell box",
811 file.c_str(), solid.GetName());
812 return false;
813 }
814
815 std::ofstream out(file, std::ios::binary);
816 if (!out) {
817 ::Error("WriteFlatCSG", "Cannot open %s for writing", file.c_str());
818 return false;
819 }
820
821 out.write("O2FLTCSG", 8);
822 const uint32_t version = kFlatCSGVersion;
823 const uint32_t nHalfspaces = static_cast<uint32_t>(solid.GetNhalfspaces());
824 const uint32_t nCells = static_cast<uint32_t>(solid.GetNcells());
825 writeValue(out, version);
826 writeValue(out, nHalfspaces);
827 writeValue(out, nCells);
828
829 // field by field, byte-identical to the loader and to cadsupport/flat.py's writer
830 for (uint32_t h = 0; h < nHalfspaces; ++h) {
831 const FlatCSGHalfspace& halfspace = solid.GetHalfspace(static_cast<int>(h));
832 const int32_t kind = halfspace.kind;
833 writeValue(out, kind);
834 writeValue(out, halfspace.sign);
835 for (double value : halfspace.c) {
836 writeValue(out, value);
837 }
838 }
839 for (uint32_t cellIdx = 0; cellIdx < nCells; ++cellIdx) {
840 const FlatCSGCell& cell = solid.GetCell(static_cast<int>(cellIdx));
841 const int32_t first = cell.first;
842 const int32_t count = cell.count;
843 writeValue(out, first);
844 writeValue(out, count);
845 writeValue(out, cell.volume);
846 double lo[3], hi[3];
847 solid.GetCellBBox(static_cast<int>(cellIdx), lo, hi);
848 for (double value : lo) {
849 writeValue(out, value);
850 }
851 for (double value : hi) {
852 writeValue(out, value);
853 }
854 }
855
856 if (!out) {
857 ::Error("WriteFlatCSG", "%s: write failed", file.c_str());
858 return false;
859 }
860 return true;
861}
862
863} // namespace cad
864} // namespace o2
std::vector< o2::soa::IndexRecord > records
Private analytic bounded surfaces, trim wires and closure checks behind O2BVHSurfaceSolid.
int32_t i
constexpr int p2()
constexpr int p1()
constexpr to accelerate the coordinates changing
uint32_t role
std::vector< SidecarEdge > edges
const char * moreThanOneOuter
uint32_t curveType
const char * noOuter
uint32_t surfaceType
uint16_t slope
Definition RawData.h:1
uint32_t c
Definition RawData.h:2
uint32_t version
Definition RawData.h:8
Class for time synchronization of RawReader instances.
bool AddFacet(const Vertex_t &pt0, const Vertex_t &pt1, const Vertex_t &pt2)
Adding a triangular facet from vertex positions in absolute coordinates.
Tessellated::Vertex_t Vertex_t
bool AddCylindricalSurface(const Point3D &centerPoint, const Point3D &axis, const Point3D &referenceAxisU, double radius, double heightMin, double heightMax, double phiStart=0., double phiSweep=6.283185307179586, bool innerWall=false)
Add a cylindrical wall of radius around axis over a height range and a phi sweep; innerWall points th...
std::array< double, 3 > Point3D
std::array< double, 2 > Point2D
bool AddCurvedPlanarSurface(const Point3D &origin, const Point3D &axisU, const Point3D &axisV, const std::vector< PlanarBoundaryCurve > &outerWire, const std::vector< std::vector< PlanarBoundaryCurve > > &innerWires={})
Add an exact planar surface bounded by line/arc wires; axisU and axisV are orthonormal and axisU x ax...
bool AddConicalSurface(const Point3D &centerPoint, const Point3D &axis, const Point3D &referenceAxisU, double radiusAtMin, double radiusAtMax, double heightMin, double heightMax, double phiStart=0., double phiSweep=6.283185307179586, bool innerWall=false)
Add a conical wall whose radius runs linearly from radiusAtMin to radiusAtMax; one radius may be zero...
void SetModelTolerance(double toleranceCm)
bool SetSurfaceBoundaryEdges(int surfaceIndex, const std::vector< unsigned int > &edgeIds, const std::vector< unsigned char > &edgeFlags)
Attach surface surfaceIndex's edge identities in trim-curve order; false on a bad index or mismatched...
bool AddSphericalSurface(const Point3D &center, const Point3D &polarAxis, const Point3D &referenceAxisU, double radius, double thetaMin=0., double thetaMax=3.141592653589793, double phiStart=0., double phiSweep=6.283185307179586, bool innerWall=false)
Add a spherical surface of radius trimmed to a theta range and a phi sweep; the defaults give a full ...
bool AddToroidalSurface(const Point3D &centerPoint, const Point3D &axis, const Point3D &referenceAxisU, double majorRadius, double minorRadius, double phiStart=0., double phiSweep=6.283185307179586, double tubeStart=0., double tubeSweep=6.283185307179586, bool innerWall=false)
Add a toroidal surface trimmed to a phiRing x phiTube rectangle; the defaults give a full torus,...
bool AddPlanarSurface(const Point3D &origin, const Point3D &axisU, const Point3D &axisV, const std::vector< Point2D > &outerWire, const std::vector< std::vector< Point2D > > &innerWires={})
int AddCell(int first, int count, double volume)
Append a cell over [first, first + count) of the halfspace array; returns its index.
int AddQuadric(double sign, const double coeff[10])
Append a quadric halfspace; returns its index. sign is +1 or -1, inside is sign*Q <= 0.
int GetNhalfspaces() const
Definition O2FlatCSG.h:75
const FlatCSGCell & GetCell(int index) const
Definition O2FlatCSG.h:78
int GetNcells() const
Definition O2FlatCSG.h:76
int AddTorus(double sign, const double *centre, const double *axis, double major, double minor)
Append a torus halfspace, inside sign * (sqrt((rho - major)^2 + z^2) - minor) <= 0 about unit axis; r...
bool IsClosed() const
Definition O2FlatCSG.h:89
void GetCellBBox(int cell, double *lo, double *hi) const
const FlatCSGHalfspace & GetHalfspace(int index) const
Definition O2FlatCSG.h:77
void SetCellBBox(int cell, const double *lo, const double *hi)
GLdouble n
Definition glcorearb.h:1982
GLsizei const GLuint const GLfloat * weights
Definition glcorearb.h:5475
GLint GLsizei count
Definition glcorearb.h:399
GLuint GLuint end
Definition glcorearb.h:469
const GLdouble * v
Definition glcorearb.h:832
GLuint const GLchar * name
Definition glcorearb.h:781
GLsizei const GLfloat * value
Definition glcorearb.h:819
GLenum const GLfloat * params
Definition glcorearb.h:272
GLenum GLsizei GLsizei GLint * values
Definition glcorearb.h:1576
GLintptr offset
Definition glcorearb.h:660
GLbitfield flags
Definition glcorearb.h:1570
GLboolean r
Definition glcorearb.h:1233
GLuint start
Definition glcorearb.h:469
GLubyte GLubyte GLubyte GLubyte w
Definition glcorearb.h:852
constexpr double wireJoinToleranceFor(double modelTolerance)
The wire-join band for a model with a declared tolerance: that tolerance when looser than kWireJoinTo...
void coneParametricMetric(double radiusAtHeight, double slope, double &gUU, double &gUV, double &gVV)
void planeParametricMetric(const Vec3 &axisU, const Vec3 &axisV, double &gUU, double &gUV, double &gVV)
constexpr double kWireJoinTolerance
Wire-closure tolerance, a 3D length in cm through the surface metric: the CAD extractor's endpoint pr...
void torusParametricMetric(double majorRadius, double minorRadius, double phiTube, double &gUU, double &gUV, double &gVV)
Torus, (u, v) = (phiRing[rad], phiTube[rad]). The ring scale runs from R - r to R + r.
void cylinderParametricMetric(double radius, double &gUU, double &gUV, double &gVV)
Cylinder, (u, v) = (phi[rad], h[cm]).
void sphereParametricMetric(double radius, double theta, double &gUU, double &gUV, double &gVV)
bool LoadFlatCSG(const std::string &file, O2FlatCSG &solid)
Load a flat-CSG sidecar (flatcsg_*.bin, version 1) into solid; call CloseShape() after....
bool LoadFacetSolid(const std::string &file, o2::base::O2Tessellated &solid)
bool WriteFlatCSG(const std::string &file, const O2FlatCSG &solid)
bool LoadSurfaceSolid(const std::string &file, O2BVHSurfaceSolid &solid)
int32_t const char * file
a couple of static helper functions to create timestamp values for CCDB queries or override obsolete ...
One DNF cell: [first, first + count) of the halfspace array, intersected; volume is its own volume.
Definition O2FlatCSG.h:37
static PlanarBoundaryCurve makeBSpline(int splineDegree, std::vector< Point2D > splinePoles, std::vector< double > splineWeights, std::vector< double > splineKnots)
static Curve2D makeBSpline(int splineDegree, std::vector< Vec2 > splinePoles, std::vector< double > splineWeights, std::vector< double > splineKnots)
std::map< std::string, ID > expected