Skip to content

Commit

Permalink
Add CSG solids (#1137)
Browse files Browse the repository at this point in the history
  • Loading branch information
sethrj committed Mar 8, 2024
1 parent 1209964 commit 7eae521
Show file tree
Hide file tree
Showing 13 changed files with 753 additions and 3 deletions.
7 changes: 5 additions & 2 deletions doc/main/api/orange.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ Shape
boxes. Shapes should be as simple as possible, aligned along and centered on
the Z axis.
Solid
NOT YET IMPLEMENTED: a shape that's hollowed out and/or has a slice removed.
A shape that's hollowed out and/or has a slice removed. It is equivalent to
a CSG operation on two shapes of the same type and an azimuthal wedge.
ExtrudedSolid
NOT YET IMPLEMENTED: a union of transformed solids along the Z axis, which
can also be hollowed and sliced.
Expand All @@ -57,6 +58,8 @@ be reused in multiple locations.

.. doxygenclass:: celeritas::orangeinp::Shape

.. doxygenclass:: celeritas::orangeinp::Solid

.. doxygenclass:: celeritas::orangeinp::Transformed

.. doxygenclass:: celeritas::orangeinp::NegatedObject
Expand Down Expand Up @@ -130,7 +133,7 @@ this construction process are:
- Simplifying and normalizing surfaces (e.g., ensuring planes are pointing in a
"positive" direction and converting arbitrary planes to axis-aligned planes)
- Deduplicating "close" surfaces to eliminate boundary crossing errors
- Naming constructed surfaces based on the constructing surface and a FaceNamer
- Naming constructed surfaces based on the constructing surface type
- Constructing bounding boxes using the original and simplified surfaces, as
well as additional specifications from the convex regions
- Adding surfaces as leaf nodes to the CSG tree, and defining additional nodes
Expand Down
1 change: 1 addition & 0 deletions src/orange/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ list(APPEND SOURCES
orangeinp/CsgTypes.cc
orangeinp/CsgTreeUtils.cc
orangeinp/Shape.cc
orangeinp/Solid.cc
orangeinp/Transformed.cc
orangeinp/detail/BoundingZone.cc
orangeinp/detail/BuildConvexRegion.cc
Expand Down
19 changes: 19 additions & 0 deletions src/orange/orangeinp/ConvexRegion.cc
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,16 @@ Cone::Cone(Real2 const& radii, real_type halfheight)
CELER_VALIDATE(hh_ > 0, << "nonpositive halfheight: " << hh_);
}

//---------------------------------------------------------------------------//
/*!
* Whether this encloses another cone.
*/
bool Cone::encloses(Cone const& other) const
{
return radii_[0] >= other.radii_[0] && radii_[1] >= other.radii_[1]
&& hh_ >= other.hh_;
}

//---------------------------------------------------------------------------//
/*!
* Build surfaces.
Expand Down Expand Up @@ -203,6 +213,15 @@ Cylinder::Cylinder(real_type radius, real_type halfheight)
CELER_VALIDATE(hh_ > 0, << "nonpositive half-height: " << hh_);
}

//---------------------------------------------------------------------------//
/*!
* Whether this encloses another cylinder.
*/
bool Cylinder::encloses(Cylinder const& other) const
{
return radius_ >= other.radius_ && hh_ >= other.hh_;
}

//---------------------------------------------------------------------------//
/*!
* Build surfaces.
Expand Down
12 changes: 12 additions & 0 deletions src/orange/orangeinp/ConvexRegion.hh
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,19 @@ class Cone final : public ConvexRegionInterface
// Construct with Z half-height and lo, hi radii
Cone(Real2 const& radii, real_type halfheight);

//// INTERFACE ////

// Build surfaces
void build(ConvexSurfaceBuilder&) const final;

// Output to JSON
void output(JsonPimpl*) const final;

//// TEMPLATE INTERFACE ////

// Whether this encloses another cone
bool encloses(Cone const& other) const;

//// ACCESSORS ////

//! Lower and upper radii
Expand Down Expand Up @@ -146,6 +153,11 @@ class Cylinder final : public ConvexRegionInterface
// Output to JSON
void output(JsonPimpl*) const final;

//// TEMPLATE INTERFACE ////

// Whether this encloses another cylinder
bool encloses(Cylinder const& other) const;

//// ACCESSORS ////

//! Radius
Expand Down
24 changes: 24 additions & 0 deletions src/orange/orangeinp/ObjectIO.json.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "CsgObject.hh"
#include "ObjectInterface.hh"
#include "Shape.hh"
#include "Solid.hh"
#include "Transformed.hh"

#define SIO_ATTR_PAIR(OBJ, ATTR) \
Expand Down Expand Up @@ -122,6 +123,21 @@ void to_json(nlohmann::json& j, ShapeBase const& obj)
SIO_ATTR_PAIR(obj, interior)};
}

void to_json(nlohmann::json& j, SolidBase const& obj)
{
j = {{"_type", "solid"},
SIO_ATTR_PAIR(obj, label),
SIO_ATTR_PAIR(obj, interior)};
if (auto* cr = obj.excluded())
{
j["excluded"] = *cr;
}
if (auto sea = obj.enclosed_angle())
{
j["enclosed_angle"] = sea;
}
}

void to_json(nlohmann::json& j, Transformed const& obj)
{
j = {{"_type", "transformed"},
Expand All @@ -131,6 +147,14 @@ void to_json(nlohmann::json& j, Transformed const& obj)
}
//!@}

//---------------------------------------------------------------------------//
//!@{
//! Write helper classes to JSON
void to_json(nlohmann::json& j, SolidEnclosedAngle const& sea)
{
j = {{"start", sea.start().value()}, {"interior", sea.interior().value()}};
}

//---------------------------------------------------------------------------//
//!@{
//! Write convex regions to JSON
Expand Down
7 changes: 7 additions & 0 deletions src/orange/orangeinp/ObjectIO.json.hh
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@ template<OperatorToken Op>
class JoinObjects;
class NegatedObject;
class ShapeBase;
class SolidBase;
class Transformed;

class SolidEnclosedAngle;

class ConvexRegionInterface;
class Box;
class Cone;
Expand All @@ -43,8 +46,12 @@ template<OperatorToken Op>
void to_json(nlohmann::json& j, JoinObjects<Op> const& sb);
void to_json(nlohmann::json& j, NegatedObject const& sb);
void to_json(nlohmann::json& j, ShapeBase const& sb);
void to_json(nlohmann::json& j, SolidBase const& sb);
void to_json(nlohmann::json& j, Transformed const& sb);

// Write helper classes to JSON
void to_json(nlohmann::json& j, SolidEnclosedAngle const& sea);

// Write convex regions to JSON
void to_json(nlohmann::json& j, ConvexRegionInterface const& cr);
void to_json(nlohmann::json& j, Box const& cr);
Expand Down
170 changes: 170 additions & 0 deletions src/orange/orangeinp/Solid.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
//----------------------------------*-C++-*----------------------------------//
// Copyright 2024 UT-Battelle, LLC, and other Celeritas developers.
// See the top-level COPYRIGHT file for details.
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
//---------------------------------------------------------------------------//
//! \file orange/orangeinp/Solid.cc
//---------------------------------------------------------------------------//
#include "Solid.hh"

#include "corecel/io/JsonPimpl.hh"
#include "corecel/math/Algorithms.hh"

#include "ConvexSurfaceBuilder.hh"
#include "CsgTreeUtils.hh"

#include "detail/BoundingZone.hh"
#include "detail/BuildConvexRegion.hh"
#include "detail/CsgUnitBuilder.hh"
#include "detail/VolumeBuilder.hh"

#if CELERITAS_USE_JSON
# include "ObjectIO.json.hh"
#endif

namespace celeritas
{
namespace orangeinp
{
//---------------------------------------------------------------------------//
/*!
* Construct from a starting angle and interior angle.
*/
SolidEnclosedAngle::SolidEnclosedAngle(Turn start, Turn interior)
: start_{start}, interior_{interior}
{
CELER_VALIDATE(interior_ > zero_quantity() && interior_ <= Turn{1},
<< "invalid interior angle " << interior.value()
<< " [turns]: must be in (0, 1]");
}

//---------------------------------------------------------------------------//
/*!
* Construct a wedge shape to intersect (inside) or subtract (outside).
*/
auto SolidEnclosedAngle::make_wedge() const -> SenseWedge
{
CELER_EXPECT(*this);
// Get the start value between [0, 1)
real_type start = eumod(start_.value(), real_type{1});
real_type interior = interior_.value();
Sense sense = Sense::inside;
if (interior > real_type{0.5})
{
// Subtract the complement of the wedge
sense = Sense::outside;
start = eumod(start + interior, real_type{1});
interior = 1 - interior;
}

return {sense, InfWedge{Turn{start}, Turn{interior}}};
}

//---------------------------------------------------------------------------//
/*!
* Construct a volume from this shape.
*/
NodeId SolidBase::build(VolumeBuilder& vb) const
{
std::vector<NodeId> nodes;

// Build the outside-of-the-shell node
nodes.push_back(build_convex_region(
vb, std::string{this->label()}, "interior", this->interior()));

if (auto* exclu = this->excluded())
{
// Construct the excluded region by building a convex solid, then
// negating it
NodeId smaller = build_convex_region(
vb, std::string{this->label()}, "excluded", *exclu);
nodes.push_back(vb.insert_region({}, Negated{smaller}));
}

if (auto const& sea = this->enclosed_angle())
{
// The enclosed angle is "true" (specified by the user to truncate the
// shape azimuthally): construct a wedge to be added or deleted
auto&& [sense, wedge] = sea.make_wedge();
NodeId wedge_id = build_convex_region(
vb, std::string{this->label()}, "angle", wedge);
if (sense == Sense::outside)
{
wedge_id = vb.insert_region({}, Negated{wedge_id});
}
nodes.push_back(wedge_id);
}

// Intersect the given surfaces to create a new CSG node
return vb.insert_region(Label{std::string{this->label()}},
Joined{op_and, std::move(nodes)});
}

//---------------------------------------------------------------------------//
/*!
* Output to JSON.
*/
void SolidBase::output(JsonPimpl* j) const
{
to_json_pimpl(j, *this);
}

//---------------------------------------------------------------------------//
/*!
* Construct with an excluded interior and enclosed angle.
*/
template<class T>
Solid<T>::Solid(std::string&& label,
T&& interior,
T&& excluded,
SolidEnclosedAngle enclosed)
: label_{std::move(label)}
, interior_{std::move(interior)}
, exclusion_{std::move(excluded)}
, enclosed_(enclosed)
{
CELER_VALIDATE(interior_.encloses(*exclusion_),
<< "solid '" << this->label()
<< "' was given an interior region that is not enclosed by "
"its exterior");
}

//---------------------------------------------------------------------------//
/*!
* Construct with an enclosed angle.
*/
template<class T>
Solid<T>::Solid(std::string&& label, T&& interior, SolidEnclosedAngle enclosed)
: label_{std::move(label)}
, interior_{std::move(interior)}
, enclosed_(enclosed)
{
CELER_VALIDATE(enclosed_,
<< "solid '" << this->label()
<< "' did not exclude an interior or a wedge (use a Shape "
"instead)");
}

//---------------------------------------------------------------------------//
/*!
* Construct with only an excluded interior.
*/
template<class T>
Solid<T>::Solid(std::string&& label, T&& interior, T&& excluded)
: Solid{std::move(label),
std::move(interior),
std::move(excluded),
SolidEnclosedAngle{}}
{
}

//---------------------------------------------------------------------------//
// EXPLICIT INSTANTIATION
//---------------------------------------------------------------------------//

template class Solid<Cone>;
template class Solid<Cylinder>;

//---------------------------------------------------------------------------//
} // namespace orangeinp
} // namespace celeritas

0 comments on commit 7eae521

Please sign in to comment.