Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
364da47
quest: Add bump-backed MarchingCubes implementation and labels the ol…
kennyweiss Jul 10, 2026
6058d66
quest: Add bump-backend Marching Cubes test for structured/unstructur…
kennyweiss Jul 10, 2026
b34a798
quest: Adds bump-based support to MC example
kennyweiss Jul 10, 2026
57a2057
quest: Reuse vertices when triangulating MC polygons
kennyweiss Jul 11, 2026
ac1a631
quest: MarchingCubes example now has a hard dependency on bump
kennyweiss Jul 13, 2026
8edbcea
quest: Fixes MC dependencies on conduit and bump
kennyweiss Jul 14, 2026
3c835a5
quest: Cleans up dynamic->static dispatch in MC example
kennyweiss Jul 14, 2026
cabeb77
quest: In MarchingCubes example, make Inout params a local variable
kennyweiss Jul 14, 2026
def815e
quest: Bugfix when checking MC values for bump-based contours
kennyweiss Jul 14, 2026
6a45fd9
quest: Adds support to MarchingCubes query for single domain mesh blu…
kennyweiss Jul 14, 2026
6f39126
Refactors python script that generates multidomain blueprint meshes f…
kennyweiss Jul 14, 2026
4895862
quest: Fixes indexing striding for MC example
kennyweiss Jul 15, 2026
2d5d67f
quest: Optimization for bump-based MC -- only extract from non-empty …
kennyweiss Jul 15, 2026
4fa3e05
quest: Optimization for bump-backed MC
kennyweiss Jul 15, 2026
efdfa4f
quest: More speedups for bump-backed MC on structured meshes w/ SEQ p…
kennyweiss Jul 15, 2026
0097268
Adds check to MC example that mesh and function dimensionality agree
kennyweiss Jul 15, 2026
5074a34
quest: Fixes cuda build of MC code/examples
kennyweiss Jul 15, 2026
b9093dd
quest: More bugfix for MC on cuda
kennyweiss Jul 16, 2026
bfd8287
Bump: Fix strided structured topology dispatch predicate
kennyweiss Aug 28, 2026
dd84b5a
Quest: Reconcile and optimize bump-based Marching Cubes corner classi…
kennyweiss Aug 28, 2026
5c018d6
Quest: Validate Marching Cubes bump input and route to the appropriat…
kennyweiss Aug 28, 2026
071c9b3
Quest: Fix bump-based MC output and parent zone naming
kennyweiss Aug 28, 2026
fa8bb6b
Quest: Documents that MC computeIsocontour does not clear existing co…
kennyweiss Aug 28, 2026
95d3076
Quest: Removes unnecessary parent cell numbering from native to bump
kennyweiss Aug 28, 2026
137dda0
Quest: Adds `if constexpr` in some bump MC functions
kennyweiss Aug 31, 2026
3250704
Quest: Fixes verification checks that prevented the MC example to run…
kennyweiss Aug 31, 2026
8d7bcde
Consolidates unstructured MC Python generator script into getn-multid…
kennyweiss Aug 31, 2026
f7f3fad
Quest: Increases test coverage for unstructured bump-based MC
kennyweiss Aug 31, 2026
f435f45
Quest: Moves unstructured hex example to axom_data
kennyweiss Aug 31, 2026
84a558b
Quest: Adds a test to compare native and bump-based MC on structured …
kennyweiss Aug 31, 2026
ad6757d
make style after rebasing
Aug 31, 2026
5b728ac
Quest: Updates Marching Cubes user docs
kennyweiss Aug 31, 2026
a1932be
Quest: Bugfix for cuda build -- lambda capture within `if constexpr`
kennyweiss Sep 1, 2026
6cb5b52
Quest: Fix hip memory space issue when copying to/from device
kennyweiss Sep 1, 2026
7e930e9
Updates RELEASE-NOTES
kennyweiss Jul 10, 2026
5feb576
Updates data submodule for new test data
kennyweiss Sep 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,20 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/

## [Unreleased] - Release date yyyy-mm-dd

### Added
- Quest: `MarchingCubes` gained an optional `bump` backend, selected with `setUseBumpBackend(true)`.
It extends isocontour extraction to `unstructured` meshes (quads in 2D, hexs in 3D)
and to `uniform` and `rectilinear` topologies. The legacy backend requires a `structured` topology
with an `explicit` coordset. It runs on all runtime policies and preserves the existing output API.

### Changed
- Quest: `MarchingCubes::setMesh()` now accepts either a single-domain or a multi-domain Blueprint mesh.

### Fixed
- Bump: `dispatch_any_structured_topology` now detects strided-structured topologies. It previously
probed `offsets` and `strides` at the topology root. Blueprint stores them under `elements/dims`.


## [Version 0.15.0] - Release date 2026-08-28

### Added
Expand Down
1 change: 1 addition & 0 deletions src/axom/bump/Unique.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ struct Unique<axom::SEQ_EXEC, KeyType>
// Make unique values and store the indices.
std::unordered_map<KeyType, axom::IndexType> unique_map;
const axom::IndexType n = keys_orig_view.size();
unique_map.reserve(static_cast<std::size_t>(n));
for(axom::IndexType index = 0; index < n; ++index)
{
const auto k = keys_orig_view[index];
Expand Down
26 changes: 26 additions & 0 deletions src/axom/bump/tests/bump_views.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,32 @@ struct test_strided_structured

TEST(bump_views, strided_structured_seq) { test_strided_structured::test(); }

template <int NDIMS>
void test_strided_structured_any_dispatch()
{
conduit::Node hostMesh;
axom::blueprint::testing::data::strided_structured<NDIMS>(hostMesh);

bool callback_invoked = false;
bool supports_strided_structured = false;
views::dispatch_structured_topologies<views::select_dimensions(NDIMS)>(
hostMesh["topologies/mesh"],
[&](const std::string&, auto topoView) {
callback_invoked = true;
supports_strided_structured =
views::view_traits<decltype(topoView)>::supports_strided_structured();
});

EXPECT_TRUE(callback_invoked);
EXPECT_TRUE(supports_strided_structured);
}

TEST(bump_views, strided_structured_any_dispatch)
{
test_strided_structured_any_dispatch<2>();
test_strided_structured_any_dispatch<3>();
}

//------------------------------------------------------------------------------
template <typename ExecSpace>
struct test_braid2d_mat
Expand Down
4 changes: 2 additions & 2 deletions src/axom/bump/views/dispatch_structured_topology.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ struct dispatch_any_structured_topology<true, 3, FuncType>
*/
static void execute(const conduit::Node& topo, FuncType&& func)
{
const std::string offsetsKey("offsets"), stridesKey("strides");
const std::string offsetsKey("elements/dims/offsets"), stridesKey("elements/dims/strides");
const std::string type = topo.fetch_existing("type").as_string();
const std::string shape("hex");

Expand Down Expand Up @@ -470,7 +470,7 @@ struct dispatch_any_structured_topology<true, 2, FuncType>
*/
static void execute(const conduit::Node& topo, FuncType&& func)
{
const std::string offsetsKey("offsets"), stridesKey("strides");
const std::string offsetsKey("elements/dims/offsets"), stridesKey("elements/dims/strides");
const std::string type = topo.fetch_existing("type").as_string();
const std::string shape("quad");
if(type == "structured" && topo.has_path(offsetsKey) && topo.has_path(stridesKey))
Expand Down
35 changes: 22 additions & 13 deletions src/axom/quest/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -136,19 +136,28 @@ if(CONDUIT_FOUND AND AXOM_ENABLE_MPI)
conduit::conduit_mpi)
endif()

blt_list_append(
TO quest_headers
ELEMENTS MarchingCubes.hpp detail/MarchingCubesSingleDomain.hpp detail/MarchingCubesImpl.hpp
IF CONDUIT_FOUND
)

blt_list_append(
TO quest_sources
ELEMENTS MarchingCubes.cpp detail/MarchingCubesSingleDomain.cpp
IF CONDUIT_FOUND
)

blt_list_append( TO quest_depends_on ELEMENTS conduit::conduit IF CONDUIT_FOUND )
if(CONDUIT_FOUND)
blt_list_append(
TO quest_headers
ELEMENTS MarchingCubes.hpp
detail/MarchingCubesSingleDomain.hpp
detail/MarchingCubesImpl.hpp)

blt_list_append(
TO quest_sources
ELEMENTS MarchingCubes.cpp
detail/MarchingCubesSingleDomain.cpp)

if(AXOM_ENABLE_BUMP)
blt_list_append(
TO quest_headers
ELEMENTS detail/MarchingCubesBumpAdaptor.hpp
detail/MarchingCubesBumpImpl.hpp)
endif()

blt_list_append( TO quest_depends_on ELEMENTS conduit::conduit)
endif()


if(AXOM_ENABLE_KLEE AND AXOM_ENABLE_SIDRE)
if(MFEM_FOUND OR CONDUIT_FOUND)
Expand Down
128 changes: 110 additions & 18 deletions src/axom/quest/MarchingCubes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@
#include "axom/quest/detail/MarchingCubesImpl.hpp"
#include "axom/fmt.hpp"

namespace axom
{
namespace quest
namespace axom::quest
{
const axom::StackArray<axom::IndexType, 2> twoZeros {0, 0};

Expand All @@ -42,6 +40,7 @@ MarchingCubes::MarchingCubes(RuntimePolicy runtimePolicy,
, m_crossingFlags(0, 0, m_allocatorID)
, m_scannedFlags(0, 0, m_allocatorID)
, m_facetIncrs(0, 0, m_allocatorID)
, m_nodeCount(0)
, m_facetNodeIds(twoZeros, m_allocatorID)
, m_facetNodeCoords(twoZeros, m_allocatorID)
, m_facetParentIds(0, 0, m_allocatorID)
Expand All @@ -53,8 +52,27 @@ void MarchingCubes::setMesh(const conduit::Node& bpMesh,
const std::string& topologyName,
const std::string& maskField)
{
SLIC_ASSERT_MSG(conduit::blueprint::mesh::is_multi_domain(bpMesh),
"MarchingCubes class input mesh must be in multidomain format.");
const conduit::Node* mdMesh = &bpMesh;
if(bpMesh.has_path("topologies/" + topologyName))
{
m_singleDomainMesh.reset();
m_singleDomainMesh.append().set_external(bpMesh);
mdMesh = &m_singleDomainMesh;
}
else if(conduit::blueprint::mesh::is_multi_domain(bpMesh))
{
m_singleDomainMesh.reset();
}
else
{
// Neither a single domain carrying the requested topology nor a valid multi-domain mesh.
// Error out here since wrapping it would defers the failure into an opaque fetch_existing() below.
SLIC_ERROR(
axom::fmt::format("MarchingCubes::setMesh: the input mesh is neither a multi-domain "
"Blueprint mesh nor a single domain containing topology '{}'.",
topologyName));
return;
}

m_topologyName = topologyName;
m_maskFieldName = maskField;
Expand All @@ -66,7 +84,7 @@ void MarchingCubes::setMesh(const conduit::Node& bpMesh,
domains is m_domainCount, not m_singles.size(). To *really*
deallocate memory, deallocate the MarchingCubes object.
*/
auto newDomainCount = conduit::blueprint::mesh::number_of_domains(bpMesh);
auto newDomainCount = conduit::blueprint::mesh::number_of_domains(*mdMesh);

if(m_singles.size() < newDomainCount)
{
Expand All @@ -80,7 +98,7 @@ void MarchingCubes::setMesh(const conduit::Node& bpMesh,

for(int d = 0; d < newDomainCount; ++d)
{
const auto& dom = bpMesh.child(d);
const auto& dom = mdMesh->child(d);
m_singles[d]->setDomain(dom, m_topologyName, maskField);
}
for(int d = newDomainCount; d < m_singles.size(); ++d)
Expand All @@ -101,22 +119,43 @@ void MarchingCubes::setFunctionField(const std::string& fcnField)
}
}

void MarchingCubes::setUseBumpBackend(bool useBump)
{
#if !defined(AXOM_USE_BUMP)
SLIC_ERROR_IF(useBump,
"MarchingCubes bump backend requires Axom to be configured "
"with the bump component.");
#endif
m_useBumpBackend = useBump;
}

void MarchingCubes::computeIsocontour(double contourVal)
{
AXOM_ANNOTATE_SCOPE("MarchingCubes::computeIsoContour");

/*
NOTE: the accumulators are deliberately not reset here.
Successive computeIsocontour() calls accumulate into one facet buffer.
It calls clearOutput() once, then loops over function fields and mask values
calling computeIsocontour() for each, recording a running prefix sum of facet counts per strategy.
*/

// Mark and scan domains while adding up their
// facet counts to get the total facet counts.
m_facetIndexOffsets.resize(m_singles.size());
m_nodeIndexOffsets.resize(m_singles.size());
for(axom::IndexType d = 0; d < m_domainCount; ++d)
{
auto& single = *m_singles[d];
single.setContourValue(contourVal);
single.setMaskValue(m_maskVal);
single.setRobustnessPolicy(m_robustnessPolicy);
single.markCrossings();
single.scanCrossings();
m_facetIndexOffsets[d] = m_facetCount;
m_nodeIndexOffsets[d] = m_nodeCount;
m_facetCount += single.getContourCellCount();
m_nodeCount += single.getContourNodeCount();
}

allocateOutputBuffers();
Expand All @@ -130,7 +169,8 @@ void MarchingCubes::computeIsocontour(double contourVal)
m_singles[d]->getImpl().setOutputBuffers(facetNodeIdsView,
facetNodeCoordsView,
facetParentIdsView,
m_facetIndexOffsets[d]);
m_facetIndexOffsets[d],
m_nodeIndexOffsets[d]);
}

for(axom::IndexType d = 0; d < m_domainCount; ++d)
Expand All @@ -147,16 +187,16 @@ void MarchingCubes::computeIsocontour(double contourVal)
}
}

axom::IndexType MarchingCubes::getContourNodeCount() const
{
axom::IndexType contourNodeCount =
(m_domainCount > 0) ? m_facetCount * m_singles[0]->spatialDimension() : 0;
return contourNodeCount;
}
axom::IndexType MarchingCubes::getContourNodeCount() const { return m_nodeCount; }

void MarchingCubes::clearOutput()
{
for(axom::IndexType d = 0; d < m_domainCount; ++d)
{
m_singles[d]->getImpl().clearDomain();
}
m_facetCount = 0;
m_nodeCount = 0;
m_facetNodeIds.clear();
m_facetNodeCoords.clear();
m_facetParentIds.clear();
Expand Down Expand Up @@ -234,19 +274,71 @@ void MarchingCubes::populateContourMesh(axom::mint::UnstructuredMesh<axom::mint:
}
}

void MarchingCubes::populateContourMeshBlueprint(conduit::Node& bpMesh, bool triangulate) const
{
AXOM_ANNOTATE_SCOPE("MarchingCubes::populateContourMeshBlueprint");
bpMesh.reset();

SLIC_ERROR_IF(!m_useBumpBackend,
"MarchingCubes Blueprint contour output is available only when "
"setUseBumpBackend(true) was used.");

for(axom::IndexType d = 0; d < m_domainCount; ++d)
{
const auto& single = *m_singles[d];
const auto& impl = single.getImpl();
SLIC_ERROR_IF(!impl.hasContourMeshBlueprint(),
"MarchingCubes has no Blueprint contour output. "
"Call computeIsocontour() before requesting it.");

conduit::Node& outDom = bpMesh.append();
impl.copyContourMeshBlueprint(outDom, triangulate);
if(!outDom.has_path("state/domain_id"))
{
outDom["state/domain_id"] = single.getDomainId(static_cast<int32_t>(d));
}
}
}

void MarchingCubes::relinquishContourDataBlueprint(conduit::Node& bpMesh)
{
AXOM_ANNOTATE_SCOPE("MarchingCubes::relinquishContourDataBlueprint");
bpMesh.reset();

SLIC_ERROR_IF(!m_useBumpBackend,
"MarchingCubes Blueprint contour output is available only when "
"setUseBumpBackend(true) was used.");

for(axom::IndexType d = 0; d < m_domainCount; ++d)
{
auto& single = *m_singles[d];
auto& impl = single.getImpl();
SLIC_ERROR_IF(!impl.hasContourMeshBlueprint(),
"MarchingCubes has no Blueprint contour output. "
"Call computeIsocontour() before requesting it.");

conduit::Node& outDom = bpMesh.append();
impl.relinquishContourMeshBlueprint(outDom);
if(!outDom.has_path("state/domain_id"))
{
outDom["state/domain_id"] = single.getDomainId(static_cast<int32_t>(d));
}
}

clearOutput();
}

void MarchingCubes::allocateOutputBuffers()
{
AXOM_ANNOTATE_SCOPE("MarchingCubes::allocateOutputBuffers");
if(!m_singles.empty())
{
int ndim = m_singles[0]->spatialDimension();
const auto nodeCount = m_facetCount * ndim;
m_facetNodeIds.resize(axom::StackArray<axom::IndexType, 2> {m_facetCount, ndim}, 0);
m_facetNodeCoords.resize(axom::StackArray<axom::IndexType, 2> {nodeCount, ndim}, 0.0);
m_facetNodeCoords.resize(axom::StackArray<axom::IndexType, 2> {m_nodeCount, ndim}, 0.0);
m_facetParentIds.resize(axom::StackArray<axom::IndexType, 1> {m_facetCount}, 0);
m_facetDomainIds.resize(axom::StackArray<axom::IndexType, 1> {m_facetCount}, 0);
}
}

} // end namespace quest
} // end namespace axom
} // end namespace axom::quest
Loading