Skip to content

Commit

Permalink
Add CSG operations on objects (celeritas-project#1130)
Browse files Browse the repository at this point in the history
  • Loading branch information
sethrj committed Mar 2, 2024
1 parent 507f55c commit 25c67f9
Show file tree
Hide file tree
Showing 12 changed files with 772 additions and 15 deletions.
1 change: 1 addition & 0 deletions src/orange/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ list(APPEND SOURCES
detail/UniverseInserter.cc
orangeinp/ConvexRegion.cc
orangeinp/ConvexSurfaceBuilder.cc
orangeinp/CsgObject.cc
orangeinp/CsgTree.cc
orangeinp/CsgTypes.cc
orangeinp/CsgTreeUtils.cc
Expand Down
155 changes: 155 additions & 0 deletions src/orange/orangeinp/CsgObject.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
//----------------------------------*-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/CsgObject.cc
//---------------------------------------------------------------------------//
#include "CsgObject.hh"

#include <utility>

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

namespace celeritas
{
namespace orangeinp
{
//---------------------------------------------------------------------------//
// NEGATED
//---------------------------------------------------------------------------//
/*!
* Construct with the object to negate and an empty name.
*/
NegatedObject::NegatedObject(SPConstObject obj)
: NegatedObject{{}, std::move(obj)}
{
}

//---------------------------------------------------------------------------//
/*!
* Construct with a name and object.
*/
NegatedObject::NegatedObject(std::string&& label, SPConstObject obj)
: label_{std::move(label)}, obj_{std::move(obj)}
{
CELER_EXPECT(obj_);
}

//---------------------------------------------------------------------------//
/*!
* Construct a volume from this object.
*/
NodeId NegatedObject::build(VolumeBuilder& vb) const
{
// Build object to be negated
auto daughter_id = obj_->build(vb);
// Add the new region (or anti-region)
return vb.insert_region(Label{label_}, Negated{daughter_id});
}

//---------------------------------------------------------------------------//
// JOIN_OBJECTS
//---------------------------------------------------------------------------//
template<OperatorToken Op>
constexpr OperatorToken JoinObjects<Op>::op_token;

//---------------------------------------------------------------------------//
/*!
* Construct with a name and a vector of objects.
*/
template<OperatorToken Op>
JoinObjects<Op>::JoinObjects(std::string&& label, VecObject&& objects)
: label_{std::move(label)}, objects_{std::move(objects)}
{
CELER_EXPECT(!label_.empty());
CELER_EXPECT(std::all_of(
objects_.begin(), objects_.end(), [](SPConstObject const& obj) {
return static_cast<bool>(obj);
}));
CELER_EXPECT(!objects_.empty());
}

//---------------------------------------------------------------------------//
/*!
* Construct a volume from the joined objets.
*/
template<OperatorToken Op>
NodeId JoinObjects<Op>::build(VolumeBuilder& vb) const
{
// Vector of nodes and cumulative bounding zone being built
std::vector<NodeId> nodes;
for (auto const& obj : objects_)
{
// Construct the daughter CSG node
auto daughter_id = obj->build(vb);
nodes.push_back(daughter_id);
}

// Add the combined region
return vb.insert_region(Label{label_}, Joined{op_token, std::move(nodes)});
}

//---------------------------------------------------------------------------//
// FREE FUNCTIONS
//---------------------------------------------------------------------------//
/*!
* Make a new object that is the second object subtracted from the first.
*
* This just takes the intersection the first object and the negated second:
* \verbatim A - B <=> A & ~B \endverbatim
*/
std::shared_ptr<AllObjects const>
make_subtraction(std::string&& label,
std::shared_ptr<ObjectInterface const> const& minuend,
std::shared_ptr<ObjectInterface const> const& subtrahend)
{
CELER_EXPECT(!label.empty());
CELER_EXPECT(minuend && subtrahend);

return std::make_shared<AllObjects>(
std::move(label),
AllObjects::VecObject{
{minuend, std::make_shared<NegatedObject>(subtrahend)}});
}

//---------------------------------------------------------------------------//
/*!
* Make a combination of possibly negated objects.
*
* The Region Definition Vector is the SCALE way for defining media,
* boundaries, etc. It must not be empty.
*/
std::shared_ptr<AllObjects const> make_rdv(
std::string&& label,
std::vector<std::pair<Sense, std::shared_ptr<ObjectInterface const>>>&& inp)
{
CELER_EXPECT(!label.empty());
CELER_EXPECT(!inp.empty());

AllObjects::VecObject objects;
for (auto&& [sense, obj] : std::move(inp))
{
CELER_EXPECT(obj);
// Negate 'outside' objects
if (sense == Sense::outside)
{
obj = std::make_shared<NegatedObject>(obj);
}
objects.push_back(std::move(obj));
}

return std::make_shared<AllObjects>(std::move(label), std::move(objects));
}

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

template class JoinObjects<op_and>;
template class JoinObjects<op_or>;

//---------------------------------------------------------------------------//
} // namespace orangeinp
} // namespace celeritas
106 changes: 106 additions & 0 deletions src/orange/orangeinp/CsgObject.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
//----------------------------------*-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/CsgObject.hh
//! \brief CSG operations on Object instances: negation, union, intersection
//---------------------------------------------------------------------------//
#pragma once

#include <string>
#include <vector>

#include "ObjectInterface.hh"

namespace celeritas
{
namespace orangeinp
{
//---------------------------------------------------------------------------//
/*!
* Everywhere *but* the embedded object.
*/
class NegatedObject : public ObjectInterface
{
public:
// Construct with the object to negate and an empty name
explicit NegatedObject(SPConstObject obj);

// Construct with a name and object
NegatedObject(std::string&& label, SPConstObject obj);

//! Get the user-provided label
std::string_view label() const final { return label_; }

// Construct a volume from this object
NodeId build(VolumeBuilder&) const final;

private:
std::string label_;
SPConstObject obj_;
};

//---------------------------------------------------------------------------//
/*!
* Join all of the given objects with an intersection or union.
*/
template<OperatorToken Op>
class JoinObjects : public ObjectInterface
{
static_assert(Op == op_and || Op == op_or);

public:
//!@{
//! \name Type aliases
using VecObject = std::vector<SPConstObject>;
//!@}

//! Operation joining the daughters ("and" or "or")
static constexpr OperatorToken op_token = Op;

public:
// Construct with a label and vector of objects
JoinObjects(std::string&& label, VecObject&& objects);

//! Access the vector of daughter objects
VecObject const& daughters() const { return objects_; }

//! Get the user-provided label
std::string_view label() const final { return label_; }

// Construct a volume from this object
NodeId build(VolumeBuilder&) const final;

private:
std::string label_;
VecObject objects_;
};

//---------------------------------------------------------------------------//
// TYPE ALIASES
//---------------------------------------------------------------------------//

//! Union of the given objects
using AnyObjects = JoinObjects<op_or>;
//! Intersection of the given objects
using AllObjects = JoinObjects<op_and>;

//---------------------------------------------------------------------------//
// FREE FUNCTIONS
//---------------------------------------------------------------------------//

// Make a new object that is the second object subtracted from the first
std::shared_ptr<AllObjects const>
make_subtraction(std::string&& label,
std::shared_ptr<ObjectInterface const> const& minuend,
std::shared_ptr<ObjectInterface const> const& subtrahend);

// Make a combination of possibly negated objects
std::shared_ptr<AllObjects const> make_rdv(
std::string&& label,
std::vector<std::pair<Sense, std::shared_ptr<ObjectInterface const>>>&&);

//---------------------------------------------------------------------------//
} // namespace orangeinp
} // namespace celeritas
8 changes: 3 additions & 5 deletions src/orange/orangeinp/Shape.cc
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,9 @@ NodeId ShapeBase::build(VolumeBuilder& vb) const
this->build_interior(sb);

// Intersect the given surfaces to create a new CSG node
auto node_id = vb.insert_region(Label{std::move(css.object_name)},
Joined{op_and, std::move(css.nodes)},
calc_merged_bzone(css));

return node_id;
return vb.insert_region(Label{std::move(css.object_name)},
Joined{op_and, std::move(css.nodes)},
calc_merged_bzone(css));
}

//---------------------------------------------------------------------------//
Expand Down
9 changes: 4 additions & 5 deletions src/orange/orangeinp/detail/BoundingZone.hh
Original file line number Diff line number Diff line change
Expand Up @@ -84,20 +84,19 @@ namespace detail
* The following set algebra can be used:
* - Involution: \verbatim ~~A <=> A \endverbatim
* - De Morgan's law 1: \verbatim ~A | ~B <=> ~(A & B) \endverbatim
* - De Morgan's law 1:\verbatim ~A & ~B <=> ~(A | B) \endverbatim
* - De Morgan's law 2:\verbatim ~A & ~B <=> ~(A | B) \endverbatim
* - Set difference: \verbatim A & ~B <=> A - B \endverbatim
* - Negated set difference: \verbatim A | ~B <=> ~(B - A) \endverbatim
*
* The default bounding zone places all points in the \em indeterminate zone:
* the exterior is the "universe set" (infinite) and the interior is the "empty
* set" (null).
* The default bounding zone is the empty set: nothing is inside, everything is
* known outside.
*/
struct BoundingZone
{
using BBox = ::celeritas::BoundingBox<>;

BBox interior;
BBox exterior = BBox::from_infinite();
BBox exterior;
bool negated{false}; //!< "exterior" means "known inside"

// Flip inside and outside
Expand Down
18 changes: 18 additions & 0 deletions src/orange/orangeinp/detail/CsgUnitBuilder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,21 @@ CsgUnitBuilder::CsgUnitBuilder(CsgUnit* u, Tolerance<> const& tol)
unit_->metadata.resize(unit_->tree.size());
}

//---------------------------------------------------------------------------//
/*!
* Access a bounding zone by ID.
*/
BoundingZone const& CsgUnitBuilder::bounds(NodeId nid) const
{
CELER_EXPECT(nid < unit_->tree.size());

auto iter = unit_->regions.find(nid);
CELER_VALIDATE(iter != unit_->regions.end(),
<< "cannot access bounds for node " << nid.unchecked_get()
<< ", which is not a region");
return iter->second.bounds;
}

//---------------------------------------------------------------------------//
/*!
* Set a bounding zone and transform for a node.
Expand Down Expand Up @@ -61,6 +76,9 @@ void CsgUnitBuilder::insert_region(NodeId n,
{
// TODO: we need to implement transform soft equivalence
// and simplification
// TODO: transformed shapes that are later defined as volumes (in
// an RDV or single-item Join function) result in the same node
// with two different transforms.
CELER_LOG(warning)
<< "While re-inserting region for node " << n.get()
<< ": existing transform "
Expand Down
19 changes: 19 additions & 0 deletions src/orange/orangeinp/detail/CsgUnitBuilder.hh
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ class CsgUnitBuilder
template<class S>
inline S const& surface(NodeId) const;

// Access a typed CSG node after insertion
template<class T>
inline T const& node(NodeId) const;

// Access a bounding zone by ID
BoundingZone const& bounds(NodeId) const;

// Access a transform by ID
inline VariantTransform const& transform(TransformId) const;

Expand Down Expand Up @@ -118,6 +125,18 @@ S const& CsgUnitBuilder::surface(NodeId nid) const
return std::get<S>(vs);
}

//---------------------------------------------------------------------------//
/*!
* Access a CSG node after insertion.
*/
template<class T>
T const& CsgUnitBuilder::node(NodeId nid) const
{
auto const& node = unit_->tree[nid];
CELER_ASSUME(std::holds_alternative<T>(node));
return std::get<T>(node);
}

//---------------------------------------------------------------------------//
/*!
* Access a transform by ID.
Expand Down

0 comments on commit 25c67f9

Please sign in to comment.