From ed51c0e3d6c83b2a9ff1e8a15e485a3824fd6ad2 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 22 May 2019 11:16:57 -0700 Subject: [PATCH 01/23] Initial stubs for new Material Interface Reconstruction component (mir) --- src/axom/CMakeLists.txt | 1 + src/axom/config.hpp.in | 1 + src/axom/mainpage.md | 2 + src/axom/mir/CMakeLists.txt | 65 ++++++++++++++++++++++++++++++ src/axom/mir/README.md | 8 ++++ src/axom/mir/docs/sphinx/index.rst | 12 ++++++ src/axom/mir/tests/CMakeLists.txt | 34 ++++++++++++++++ src/axom/mir/tests/mir_smoke.cpp | 38 +++++++++++++++++ src/docs/dependencies.dot | 1 + src/docs/doxygen/Doxyfile.in | 1 + src/index.rst | 3 ++ 11 files changed, 166 insertions(+) create mode 100644 src/axom/mir/CMakeLists.txt create mode 100644 src/axom/mir/README.md create mode 100644 src/axom/mir/docs/sphinx/index.rst create mode 100644 src/axom/mir/tests/CMakeLists.txt create mode 100644 src/axom/mir/tests/mir_smoke.cpp diff --git a/src/axom/CMakeLists.txt b/src/axom/CMakeLists.txt index 4dc4d676d4..467e1d0f7c 100644 --- a/src/axom/CMakeLists.txt +++ b/src/axom/CMakeLists.txt @@ -30,6 +30,7 @@ axom_add_component(COMPONENT_NAME primal DEFAULT_STATE ${AXOM_ENABLE_ALL_COMPONE axom_add_component(COMPONENT_NAME spin DEFAULT_STATE ${AXOM_ENABLE_ALL_COMPONENTS}) axom_add_component(COMPONENT_NAME sidre DEFAULT_STATE ${AXOM_ENABLE_ALL_COMPONENTS}) axom_add_component(COMPONENT_NAME quest DEFAULT_STATE ${AXOM_ENABLE_ALL_COMPONENTS}) +axom_add_component(COMPONENT_NAME mir DEFAULT_STATE ${AXOM_ENABLE_ALL_COMPONENTS}) # Combine all component object libraries into a unified library blt_add_library(NAME axom diff --git a/src/axom/config.hpp.in b/src/axom/config.hpp.in index 9992a767c1..399d296c6b 100644 --- a/src/axom/config.hpp.in +++ b/src/axom/config.hpp.in @@ -90,6 +90,7 @@ * Compiler defines for the toolkit components */ #cmakedefine AXOM_USE_MINT +#cmakedefine AXOM_USE_MIR #cmakedefine AXOM_USE_LUMBERJACK #cmakedefine AXOM_USE_PRIMAL #cmakedefine AXOM_USE_QUEST diff --git a/src/axom/mainpage.md b/src/axom/mainpage.md index 827988937a..60708dae52 100644 --- a/src/axom/mainpage.md +++ b/src/axom/mainpage.md @@ -8,6 +8,7 @@ Axom provides libraries that address common computer science needs. It grew fro * @subpage coretop provides shared utility functionality to all components. * @subpage lumberjacktop provides logging aggregation and filtering capability. * @subpage minttop provides a comprehensive mesh data model. +* @subpage mirtop provides algorithms for material interface reconstruction on multimaterial meshes. * @subpage primaltop provides an API for geometric primitives and computational geometry tests. * @subpage questtop provides an API to query point distance and position relative to meshes. * @subpage sidretop provides a data store with hierarchical structure. @@ -20,6 +21,7 @@ Dependencies between components are as follows: - Slic optionally depends on Lumberjack - Slam, Primal, Mint, Quest, Spin, and Sidre depend on Slic - Mint optionally depends on Sidre +- Mir depends on Slic - Spin depends on Primal and Slam - Quest depends on Slam, Primal, Spin, and Mint diff --git a/src/axom/mir/CMakeLists.txt b/src/axom/mir/CMakeLists.txt new file mode 100644 index 0000000000..6c7c67635e --- /dev/null +++ b/src/axom/mir/CMakeLists.txt @@ -0,0 +1,65 @@ +# Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +# other Axom Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (BSD-3-Clause) +#------------------------------------------------------------------------------ +# MIR -- Material Interface Reconstruction +#------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------ +# Check necessary dependencies +#------------------------------------------------------------------------------ +axom_component_requires(NAME MIR + COMPONENTS SLIC) + +#------------------------------------------------------------------------------ +# Specify all headers/sources +#------------------------------------------------------------------------------ +set(mir_headers + + ) + +set(mir_sources + ../Axom.cpp + ) + +#------------------------------------------------------------------------------ +# Build and install the library +#------------------------------------------------------------------------------ +set(mir_depends_on core slic) + +blt_add_library( + NAME mir + SOURCES ${mir_sources} + HEADERS ${mir_headers} + DEPENDS_ON ${mir_depends_on} + FOLDER axom/mir + OBJECT TRUE ) + +axom_write_unified_header(NAME mir + HEADERS ${mir_headers} ) + +axom_install_component(NAME mir + HEADERS ${mir_headers} ) + +#------------------------------------------------------------------------------ +# Add tests, benchmarks and examples +#------------------------------------------------------------------------------ +if (AXOM_ENABLE_TESTS) + add_subdirectory(tests) + if (ENABLE_BENCHMARKS) + # add_subdirectory(benchmarks) + endif() +endif() + +if (AXOM_ENABLE_EXAMPLES) + #add_subdirectory(examples) +endif() + + +#------------------------------------------------------------------------------ +# Add code checks +#------------------------------------------------------------------------------ +axom_add_code_checks( + PREFIX mir ) + diff --git a/src/axom/mir/README.md b/src/axom/mir/README.md new file mode 100644 index 0000000000..fa6b7fba6a --- /dev/null +++ b/src/axom/mir/README.md @@ -0,0 +1,8 @@ +MIR: Material Interface Reconstruction {#mirtop} +================================================ + +[MIR](@ref axom::mir) provides algorithms for reconstructing interfaces +between materials in a multimaterial mesh. + +The [Mir user documentation](../../../sphinx/axom_docs/html/axom/mir/docs/sphinx/index.html) +describes these algorithms. diff --git a/src/axom/mir/docs/sphinx/index.rst b/src/axom/mir/docs/sphinx/index.rst new file mode 100644 index 0000000000..22f5487fd6 --- /dev/null +++ b/src/axom/mir/docs/sphinx/index.rst @@ -0,0 +1,12 @@ +.. ## Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +.. ## other Axom Project Developers. See the top-level COPYRIGHT file for details. +.. ## +.. ## SPDX-License-Identifier: (BSD-3-Clause) + +======================= +Mir User Documentation +======================= + +Axom's Material Interface Reconstruction (MIR) component provides algorithms for +reconstructing the interface surfaces between different materials in multimaterial +meshes. diff --git a/src/axom/mir/tests/CMakeLists.txt b/src/axom/mir/tests/CMakeLists.txt new file mode 100644 index 0000000000..155f8ae486 --- /dev/null +++ b/src/axom/mir/tests/CMakeLists.txt @@ -0,0 +1,34 @@ +# Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +# other Axom Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (BSD-3-Clause) +#------------------------------------------------------------------------------ +# Mir unit tests +#------------------------------------------------------------------------------ + + +#------------------------------------------------------------------------------ +# Specify list of tests +#------------------------------------------------------------------------------ + +set(gtest_mir_tests + mir_smoke.cpp + ) + + +set(mir_tests_depends_on core slic mir gtest) + +#------------------------------------------------------------------------------ +# Add gtest based tests +#------------------------------------------------------------------------------ +foreach(test ${gtest_mir_tests}) + get_filename_component( test_name ${test} NAME_WE ) + blt_add_executable( NAME ${test_name}_test + SOURCES ${test} + OUTPUT_DIR ${TEST_OUTPUT_DIRECTORY} + DEPENDS_ON ${mir_tests_depends_on} + FOLDER axom/mir/tests ) + blt_add_test( NAME ${test_name} + COMMAND ${test_name}_test ) +endforeach() + diff --git a/src/axom/mir/tests/mir_smoke.cpp b/src/axom/mir/tests/mir_smoke.cpp new file mode 100644 index 0000000000..c71092b54d --- /dev/null +++ b/src/axom/mir/tests/mir_smoke.cpp @@ -0,0 +1,38 @@ +// Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#ifndef MIR_SMOKE_H_ +#define MIR_SMOKE_H_ + +#include "gtest/gtest.h" + +#include "axom/slic.hpp" +#include "axom/mir.hpp" + + +TEST(mir,smoke) +{ + SLIC_INFO("Running smoke test for MIR component"); + + EXPECT_TRUE( true ); + EXPECT_EQ( 0, 0 ); +} + + +//---------------------------------------------------------------------- + +int main(int argc, char* argv[]) +{ + int result = 0; + ::testing::InitGoogleTest(&argc, argv); + + axom::slic::UnitTestLogger logger; // create & initialize test logger, + + result = RUN_ALL_TESTS(); + return result; +} + + +#endif // MIR_SMOKE_H_ diff --git a/src/docs/dependencies.dot b/src/docs/dependencies.dot index 8e747c824c..75ab74554e 100644 --- a/src/docs/dependencies.dot +++ b/src/docs/dependencies.dot @@ -2,6 +2,7 @@ digraph dependencies { quest -> {slam primal mint spin}; {quest slam primal mint spin} -> {slic core}; mint -> sidre [style="dashed"]; + mir -> {slic core}; spin -> {slam primal}; sidre -> {slic core}; slic -> core; diff --git a/src/docs/doxygen/Doxyfile.in b/src/docs/doxygen/Doxyfile.in index d38a7d5aad..d76fccfee4 100644 --- a/src/docs/doxygen/Doxyfile.in +++ b/src/docs/doxygen/Doxyfile.in @@ -786,6 +786,7 @@ INPUT = @PROJECT_SOURCE_DIR@/axom/mainpage.md \ @PROJECT_SOURCE_DIR@/axom/mint/mesh \ @PROJECT_SOURCE_DIR@/axom/mint/utils \ @PROJECT_SOURCE_DIR@/axom/mint/execution \ + @PROJECT_SOURCE_DIR@/axom/mir/README.md \ @PROJECT_SOURCE_DIR@/axom/primal/README.md \ @PROJECT_SOURCE_DIR@/axom/primal/geometry \ @PROJECT_SOURCE_DIR@/axom/primal/operators \ diff --git a/src/index.rst b/src/index.rst index c4ac47919c..9ac474a4b6 100644 --- a/src/index.rst +++ b/src/index.rst @@ -61,6 +61,7 @@ for Axom software components: Spin (Spatial indexes) Quest (Querying on surface tool) Mint (Mesh data model) + Mir (Material interface reconstruction) Primal (Computational geometry primitives) -------------------------- @@ -71,6 +72,7 @@ Source Code Documentation * `Core <../../../doxygen/axom_doxygen/html/coretop.html>`_ * `Lumberjack <../../../doxygen/axom_doxygen/html/lumberjacktop.html>`_ * `Mint <../../../doxygen/axom_doxygen/html/minttop.html>`_ + * `Mir <../../../doxygen/axom_doxygen/html/mirtop.html>`_ * `Primal <../../../doxygen/axom_doxygen/html/primaltop.html>`_ * `Quest <../../../doxygen/axom_doxygen/html/questtop.html>`_ * `Sidre <../../../doxygen/axom_doxygen/html/sidretop.html>`_ @@ -86,6 +88,7 @@ Dependencies between modules are as follows: - Slic optionally depends on Lumberjack - Slam, Spin, Primal, Mint, Quest, and Sidre depend on Slic - Mint optionally depends on Sidre +- Mir depends on Slic - Quest depends on Slam, Spin, Primal, and Mint The figure below summarizes the dependencies between the modules. Solid links From c5a79c00237b9527f1577d29db0ad897b9ef6108 Mon Sep 17 00:00:00 2001 From: Sterbentz Date: Mon, 24 Jun 2019 17:15:07 -0700 Subject: [PATCH 02/23] Add uberenv_libs to gitignore. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 501c8041a6..5ab2039dfa 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ _axom_build_and_test_* tpl_dirs_summary.json *.swp *.vscode* +uberenv_libs From d13a5eb437a36c85c5717f476f10c7d3e9a05589 Mon Sep 17 00:00:00 2001 From: Sterbentz Date: Wed, 29 May 2019 08:14:31 -0700 Subject: [PATCH 03/23] Builds a simple mir program. --- src/axom/mir/CMakeLists.txt | 2 +- src/axom/mir/examples/CMakeLists.txt | 33 ++++++++++++++++++++ src/axom/mir/examples/mir_tutorial.cpp | 42 ++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 src/axom/mir/examples/CMakeLists.txt create mode 100644 src/axom/mir/examples/mir_tutorial.cpp diff --git a/src/axom/mir/CMakeLists.txt b/src/axom/mir/CMakeLists.txt index 6c7c67635e..8005392b9f 100644 --- a/src/axom/mir/CMakeLists.txt +++ b/src/axom/mir/CMakeLists.txt @@ -53,7 +53,7 @@ if (AXOM_ENABLE_TESTS) endif() if (AXOM_ENABLE_EXAMPLES) - #add_subdirectory(examples) + add_subdirectory(examples) endif() diff --git a/src/axom/mir/examples/CMakeLists.txt b/src/axom/mir/examples/CMakeLists.txt new file mode 100644 index 0000000000..b199beae56 --- /dev/null +++ b/src/axom/mir/examples/CMakeLists.txt @@ -0,0 +1,33 @@ +# Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +# other Axom Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (BSD-3-Clause) + +set( mir_examples + mir_tutorial.cpp + ) + +set( mir_example_dependencies + #core + mir + #slic + ) + +blt_list_append( TO mir_example_dependencies ELEMENTS sidre conduit IF AXOM_MIR_USE_SIDRE ) +blt_list_append( TO mir_example_dependencies ELEMENTS raja IF RAJA_FOUND ) +blt_list_append( TO mir_example_dependencies ELEMENTS openmp IF ENABLE_OPENMP ) +blt_list_append( TO mir_example_dependencies ELEMENTS cuda IF ENABLE_CUDA ) + +foreach( example ${mir_examples} ) + + get_filename_component( example_name ${example} NAME_WE ) + + blt_add_executable( + NAME ${example_name}_ex + SOURCES ${example} + OUTPUT_DIR ${EXAMPLE_OUTPUT_DIRECTORY} + DEPENDS_ON ${mir_example_dependencies} + FOLDER axom/mir/examples + ) + +endforeach() diff --git a/src/axom/mir/examples/mir_tutorial.cpp b/src/axom/mir/examples/mir_tutorial.cpp new file mode 100644 index 0000000000..7dbcb69a0d --- /dev/null +++ b/src/axom/mir/examples/mir_tutorial.cpp @@ -0,0 +1,42 @@ +// Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "axom/core.hpp" // for axom macros +#include "axom/mir.hpp" // for Mir classes & functions + + + +// C/C++ includes +#include // for definition of M_PI, exp() + +// namespace aliases +//namespace mir = axom::mir; +namespace numerics = axom::numerics; + +/*! + * \file + * + * \brief Various code snippets/examples used for the Mir tutorial section. + * + * \note These examples are designed to illustrate specific Mir + * concepts and capabilities. Consult the Tutorial section of Mir's + * User Guide for more details. + * + */ + + +/*! + * \brief Tutorial main + */ +int main( int AXOM_NOT_USED(argc), char** AXOM_NOT_USED(argv) ) +{ + + std::cout << "hello from the mir_tutorial!" << std::endl; + + return 0; +} + + + From 2dc5aafae0b9baf3532ee4683b95d7a4079178ae Mon Sep 17 00:00:00 2001 From: Sterbentz Date: Fri, 7 Jun 2019 08:20:14 -0700 Subject: [PATCH 04/23] Basic material interface reconstruction implemented for a simple two material quad test mesh. --- src/axom/mir/CMakeLists.txt | 10 +- src/axom/mir/CellData.cpp | 99 ++++ src/axom/mir/CellData.hpp | 51 ++ src/axom/mir/InterfaceReconstructor.cpp | 489 ++++++++++++++++++ src/axom/mir/InterfaceReconstructor.hpp | 64 +++ src/axom/mir/MIRMesh.cpp | 318 ++++++++++++ src/axom/mir/MIRMesh.hpp | 100 ++++ src/axom/mir/MIRMeshTypes.hpp | 74 +++ src/axom/mir/ZooBitMaps.cpp | 47 ++ src/axom/mir/ZooBitMaps.hpp | 17 + src/axom/mir/examples/CMakeLists.txt | 1 + src/axom/mir/examples/mir_tutorial.cpp | 446 +++++++++++++++- src/axom/mir/examples/mir_tutorial_simple.cpp | 122 +++++ 13 files changed, 1833 insertions(+), 5 deletions(-) create mode 100644 src/axom/mir/CellData.cpp create mode 100644 src/axom/mir/CellData.hpp create mode 100644 src/axom/mir/InterfaceReconstructor.cpp create mode 100644 src/axom/mir/InterfaceReconstructor.hpp create mode 100644 src/axom/mir/MIRMesh.cpp create mode 100644 src/axom/mir/MIRMesh.hpp create mode 100644 src/axom/mir/MIRMeshTypes.hpp create mode 100644 src/axom/mir/ZooBitMaps.cpp create mode 100644 src/axom/mir/ZooBitMaps.hpp create mode 100644 src/axom/mir/examples/mir_tutorial_simple.cpp diff --git a/src/axom/mir/CMakeLists.txt b/src/axom/mir/CMakeLists.txt index 8005392b9f..a31de3c473 100644 --- a/src/axom/mir/CMakeLists.txt +++ b/src/axom/mir/CMakeLists.txt @@ -16,11 +16,19 @@ axom_component_requires(NAME MIR # Specify all headers/sources #------------------------------------------------------------------------------ set(mir_headers - + MIRMesh.hpp + MIRMeshTypes.hpp + ZooBitMaps.hpp + InterfaceReconstructor.hpp + CellData.hpp ) set(mir_sources ../Axom.cpp + MIRMesh.cpp + InterfaceReconstructor.cpp + ZooBitMaps.cpp + CellData.cpp ) #------------------------------------------------------------------------------ diff --git a/src/axom/mir/CellData.cpp b/src/axom/mir/CellData.cpp new file mode 100644 index 0000000000..bfeee02462 --- /dev/null +++ b/src/axom/mir/CellData.cpp @@ -0,0 +1,99 @@ +// Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "CellData.hpp" + +namespace axom +{ +namespace mir +{ + + //-------------------------------------------------------------------------------- + + CellData::CellData() + { + + } + + //-------------------------------------------------------------------------------- + + CellData::CellData(int _numVerts, int _numElems, std::vector _evInds, std::vector _evBegins, std::vector _veInds, std::vector _veBegins, + std::vector _vertexPositions, std::vector > _vertexVolumeFractions) + { + numVerts = _numVerts; + numElems = _numElems; + evInds = _evInds; + evBegins = _evBegins; + veInds = _veInds; + veBegins = _veBegins; + vertexPositions = _vertexPositions; + vertexVolumeFractions = _vertexVolumeFractions; + } + + //-------------------------------------------------------------------------------- + + CellData::~CellData() + { + + } + + //-------------------------------------------------------------------------------- + + /// Merges the cell data from the given cell into this cell + void CellData::mergeCell(CellData cellToMerge) + { + // Initialize index offsets + int evBeginsOffset = evInds.size(); + int veBeginsOffset = veInds.size(); + + int vertexIndexOffset = numVerts; + int elementIndexOffset = numElems; + + // Merge the cell topology information + for (auto i = 0; i < cellToMerge.evInds.size(); ++i) + { + evInds.push_back(cellToMerge.evInds[i] + vertexIndexOffset); + } + + for (auto i = 1; i < cellToMerge.evBegins.size(); ++i) + { + evBegins.push_back(cellToMerge.evBegins[i] + evBeginsOffset); + } + + for (auto i = 0; i < cellToMerge.veInds.size(); ++i) + { + veInds.push_back(cellToMerge.veInds[i] + elementIndexOffset); + } + + for (auto i = 1; i < cellToMerge.veBegins.size(); ++i) + { + veBegins.push_back(cellToMerge.veBegins[i] + veBeginsOffset); + } + + // Merge the vertex positions + for (auto i = 0; i < cellToMerge.vertexPositions.size(); ++i) + { + vertexPositions.push_back(cellToMerge.vertexPositions[i]); + } + + // Merge the vertex volume fractions + for (auto matID = 0; matID < vertexVolumeFractions.size(); ++matID) + { + for (auto vID = 0; vID < cellToMerge.vertexVolumeFractions[matID].size(); ++vID) + { + vertexVolumeFractions[matID].push_back(cellToMerge.vertexVolumeFractions[matID][vID]); + } + } + + // Merge the total number of verts and elems in the resulting cell + numVerts += cellToMerge.numVerts; + numElems += cellToMerge.numElems; + + } + + //-------------------------------------------------------------------------------- + +} +} \ No newline at end of file diff --git a/src/axom/mir/CellData.hpp b/src/axom/mir/CellData.hpp new file mode 100644 index 0000000000..d8a66764f7 --- /dev/null +++ b/src/axom/mir/CellData.hpp @@ -0,0 +1,51 @@ +// Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#ifndef __CELL_DATA_H__ +#define __CELL_DATA_H__ + +#include "axom/core.hpp" // for axom macros +#include "axom/slam.hpp" + +#include "MIRMesh.hpp" + +namespace numerics = axom::numerics; +namespace slam = axom::slam; + +namespace axom +{ +namespace mir +{ + + class CellData + { + + public: + CellData(); + CellData(int _numVerts, int _numElems, std::vector _evInds, std::vector _evBegins, std::vector _veInds, std::vector _veBegins, + std::vector _vertexPositions, std::vector > _vertexVolumeFractions); + ~CellData(); + + void mergeCell(CellData cellToMerge); + + public: + int numVerts; + int numElems; + + // Cell connectivity/topology + std::vector evInds; + std::vector evBegins; + std::vector veInds; + std::vector veBegins; + + std::vector vertexPositions; + std::vector materialsInCell; // TODO: This is not currently being used. + std::vector > vertexVolumeFractions; + }; + +} +} + +#endif \ No newline at end of file diff --git a/src/axom/mir/InterfaceReconstructor.cpp b/src/axom/mir/InterfaceReconstructor.cpp new file mode 100644 index 0000000000..1f9f17a9fb --- /dev/null +++ b/src/axom/mir/InterfaceReconstructor.cpp @@ -0,0 +1,489 @@ +// Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "InterfaceReconstructor.hpp" + +namespace axom +{ +namespace mir +{ + +//-------------------------------------------------------------------------------- + + /// Default constructor + InterfaceReconstructor::InterfaceReconstructor() + { + + } + +//-------------------------------------------------------------------------------- + + /// Constructor + InterfaceReconstructor::InterfaceReconstructor(mir::MIRMesh* _mesh) + { + mesh = _mesh; + } + +//-------------------------------------------------------------------------------- + + /// Destructor + InterfaceReconstructor::~InterfaceReconstructor() + { + + } + +//-------------------------------------------------------------------------------- + + /// Computes the points where the element should be clipped based on the volume fractions of mat one and two. + /// TODO: This method needs to place the computed cell vertices into the intermediate mesh (pass this in as an argument) + void InterfaceReconstructor::computeClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh, + std::vector& _evInds, std::vector& _evBegins, std::vector& _veInds, std::vector& _veBegins, + std::vector& _vertexPositions, std::vector& _materialInCell, int* _numVerts, int* _numElements, std::vector >& _newVolumeFractionsAtVerts) + { + // Find the vertices associated with each element + auto elementVertices = mesh->bdry[eID]; + + // Check if one of the two currently considered materials is not present at the vertices of the current element + // if (mesh->materialVolumeFractionsElement[matOneID][eID] != 0.0 && mesh->materialVolumeFractionsElement[matTwoID][eID] != 0.0) // TODO: Figure out how to best handle this case + // { + // Triangle Case + if (elementVertices.size() == 3) + { + computeTriangleClippingPoints(eID, matOneID, matTwoID, tempMesh, _evInds, _evBegins, _veInds, _veBegins, _vertexPositions, _materialInCell, _numVerts, _numElements, _newVolumeFractionsAtVerts); + } + // Quad Case + if (elementVertices.size() == 4) + { + computeQuadClippingPoints(eID, matOneID, matTwoID, tempMesh, _evInds, _evBegins, _veInds, _veBegins, _vertexPositions, _materialInCell, _numVerts, _numElements, _newVolumeFractionsAtVerts); // Compute how the cell should be decomposed into new cells + } + // } + // else + // { + // printf("No cuts in element %d.\n", eID); + + // TODO: Add the current cells vertices and data back into the mesh + + // } + } + +//-------------------------------------------------------------------------------- + + /// Computes the points where the triangle element should be clipped based on the volume fractions of mat one and two. + void InterfaceReconstructor::computeTriangleClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh, + std::vector& _evInds, std::vector& _evBegins, std::vector& _veInds, std::vector& _veBegins, + std::vector& _vertexPositions, std::vector& _materialInCell, int* _numVerts, int* _numElements, std::vector >& _newVolumeFractionsAtVerts) + { + printf("Triangle clipping case not yet implemented.\n"); + } + +//-------------------------------------------------------------------------------- + + /// Computes how the given cell should be decomposed into new cells baed on teh vertex volume fractions of matOne and matTwo. + void InterfaceReconstructor::computeQuadClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh, + std::vector& out_evInds, std::vector& out_evBegins, std::vector& out_veInds, std::vector& out_veBegins, + std::vector& out_vertexPositions, std::vector& out_materialInCell, int* out_numVerts, int* out_numElements, std::vector >& out_newVolumeFractionsAtVerts) + { + printf("Processing element %d: ", eID); + + /**************************************************************** + * DETERMINE THE CLIPPING CASE + ****************************************************************/ + // Get the vertices of the current element to clip + auto elementVertices = tempMesh->bdry[eID]; + + // Determine which vertices correspond to upper left, lower left, lower right, and upper right vertices of the quad. + int upperLeftVertex = elementVertices[0]; + int lowerLeftVertex = elementVertices[1]; + int lowerRightVertex = elementVertices[2]; + int upperRightVertex = elementVertices[3]; + + // Determine the dominant color at each vertex + int upperLeftColor = tempMesh->materialVolumeFractionsVertex[matOneID][upperLeftVertex] > tempMesh->materialVolumeFractionsVertex[matTwoID][upperLeftVertex] ? matOneID : matTwoID; + int lowerLeftColor = tempMesh->materialVolumeFractionsVertex[matOneID][lowerLeftVertex] > tempMesh->materialVolumeFractionsVertex[matTwoID][lowerLeftVertex] ? matOneID : matTwoID; + int lowerRightColor = tempMesh->materialVolumeFractionsVertex[matOneID][lowerRightVertex] > tempMesh->materialVolumeFractionsVertex[matTwoID][lowerRightVertex] ? matOneID : matTwoID; + int upperRightColor = tempMesh->materialVolumeFractionsVertex[matOneID][upperRightVertex] > tempMesh->materialVolumeFractionsVertex[matTwoID][upperRightVertex] ? matOneID : matTwoID; + + // Create the index into the quad clipping lookup table using the dominant colors at each vertex + unsigned int caseIndex = 0; + if (upperLeftColor == matOneID) caseIndex |= 8; + if (lowerLeftColor == matOneID) caseIndex |= 4; + if (lowerRightColor == matOneID) caseIndex |= 2; + if (upperRightColor == matOneID) caseIndex |= 1; + + printf("caseIndex: %d\n", caseIndex); + + /**************************************************************** + * GENERATE NEW ELEMENTS + ****************************************************************/ + // Cell information indices + std::map > newElements; // hashmap of the new elements' vertices | Note: maps are ordered sets + std::map > newVertices; // hashmap of the new vertices' elements | Note: maps are ordered sets + std::map newPoints; // hashmap of the new vertices' positions | Note: maps are ordered sets + int verticesPresent[8]; // Array of flags denoting whether the vertex is present in the current case or not + axom::float64 verticesClippingTValue[8]; // Array of t values that denote the percent value of where the edge should be clipped + std::map > vertexVolumeFractionsMap; // index: map[vID][matID] = vertexVolumeFractionValues[numMaterials] | Note: maps are ordered sets + + int currentElementIndex = 0; // the next available element index + + // Create the new polygons based on the clipping case + int i = 0; + int numVertices = quadClipTable[caseIndex][i]; + while (numVertices != -1) // for each new element in the current clipping case + { + // for each vertex of the new element + for (int j = 0; j < numVertices; ++j) + { + // Find the id of the next vertex of the new element + int vID = quadClipTable[caseIndex][i + (j+1)]; + + // Associate the vertex and element together + newElements[currentElementIndex].push_back(vID); + newVertices[vID].push_back(currentElementIndex); + verticesPresent[vID] = 1; + + // Find t using "bilinear" interpolation method for any vertex that is not one of the original 4 vertices + if(vID == 4) + { + verticesClippingTValue[vID] = computeClippingPointOnEdge(upperLeftVertex, lowerLeftVertex, matOneID, matTwoID, tempMesh); // these might be the wrong vertex indices + } + else if(vID == 5) + { + verticesClippingTValue[vID] = computeClippingPointOnEdge(lowerLeftVertex, lowerRightVertex, matOneID, matTwoID, tempMesh); + } + else if(vID == 6) + { + verticesClippingTValue[vID] = computeClippingPointOnEdge(lowerRightVertex, upperRightVertex, matOneID, matTwoID, tempMesh); + } + else if(vID == 7) + { + verticesClippingTValue[vID] = computeClippingPointOnEdge(upperRightVertex, upperLeftVertex, matOneID, matTwoID, tempMesh); + } + } + + // Increment the element index counter, marking the current element as being finished processed + currentElementIndex++; + + // Increase index into lookup table to the next element + i += (numVertices + 1); + numVertices = quadClipTable[caseIndex][i]; + } + + /**************************************************************** + * CALCULATE THE NEW CELLS' DATA + ****************************************************************/ + // Calculate the total number of elements and vertices that were generated from splitting the current element + out_numElements[0] = (int) newElements.size(); + out_numVerts[0] = (int) newVertices.size(); + + // Generate the topology of the new elements (evInds, evBegins, etc) + // Store the evInds and evBegins data in the output vectors + int currentEVBeginIndex = 0; + for (auto itr = newElements.begin(); itr != newElements.end(); itr++) + { + // Push the start index of the next element + out_evBegins.push_back(currentEVBeginIndex); + + // Push the next element's vertices + for (unsigned int vIndex = 0; vIndex < itr->second.size(); ++vIndex) + { + out_evInds.push_back(itr->second[vIndex]); + ++currentEVBeginIndex; + } + } + + // Push the index that occurs after the last vertex + out_evBegins.push_back(currentEVBeginIndex); + + // Store the veInds and veBegins data in the output vectors + int currentVEBeginIndex = 0; + for (auto itr = newVertices.begin(); itr != newVertices.end(); itr++) + { + // Push the start index of the vertex's elements + out_veBegins.push_back(currentVEBeginIndex); + + // Push the next vertex's elements + for (unsigned int eIndex = 0; eIndex < itr->second.size(); eIndex++) + { + out_veInds.push_back(itr->second[eIndex]); + ++currentVEBeginIndex; + } + } + + // Push the index that occurs after the last element + out_veBegins.push_back(currentVEBeginIndex); + + // Find the position coordinates of the vertices and then store the positions of the vertices in the return vector + for (auto itr = newVertices.begin(); itr != newVertices.end(); itr++) + { + int vID = itr->first; + if (verticesPresent[vID] == 1) + { + if (vID == 0) + newPoints[vID] = tempMesh->vertexPositions[upperLeftVertex]; + if (vID == 1) + newPoints[vID] = tempMesh->vertexPositions[lowerLeftVertex]; + if (vID == 2) + newPoints[vID] = tempMesh->vertexPositions[lowerRightVertex]; + if (vID == 3) + newPoints[vID] = tempMesh->vertexPositions[upperRightVertex]; + if (vID == 4) + newPoints[vID] = interpolateVertexPosition(tempMesh->vertexPositions[upperLeftVertex], tempMesh->vertexPositions[lowerLeftVertex], verticesClippingTValue[vID]); + if (vID == 5) + newPoints[vID] = interpolateVertexPosition(tempMesh->vertexPositions[lowerLeftVertex], tempMesh->vertexPositions[lowerRightVertex], verticesClippingTValue[vID]); + if (vID == 6) + newPoints[vID] = interpolateVertexPosition(tempMesh->vertexPositions[lowerRightVertex], tempMesh->vertexPositions[upperRightVertex], verticesClippingTValue[vID]); + if (vID == 7) + newPoints[vID] = interpolateVertexPosition(tempMesh->vertexPositions[upperRightVertex], tempMesh->vertexPositions[upperLeftVertex], verticesClippingTValue[vID]); + } + } + + // Store the positions of the vertices in the return vector + for (auto itr = newPoints.begin(); itr != newPoints.end(); itr++) + { + out_vertexPositions.push_back(itr->second); + } + + // Calculate the vertex fractions at each vertex (use t value!) + // Make sure the output volume fractions containers are the proper size + out_newVolumeFractionsAtVerts.resize(tempMesh->numMaterials); + + for (auto itr = newVertices.begin(); itr != newVertices.end(); itr++) + { + int vID = itr->first; + // Ensure that the current vertex being considered is actually present in the current clipping case + if (verticesPresent[vID] == 1) + { + // vertexVolumeFractionsMap[vID].resize(tempMesh->numMaterials); + + for (int matID = 0; matID < tempMesh->numMaterials; ++matID) + { + if (vID == 0) + out_newVolumeFractionsAtVerts[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][upperLeftVertex]); + if (vID == 1) + out_newVolumeFractionsAtVerts[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][lowerLeftVertex]); + if (vID == 2) + out_newVolumeFractionsAtVerts[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][lowerRightVertex]); + if (vID == 3) + out_newVolumeFractionsAtVerts[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][upperRightVertex]); + if (vID == 4) + out_newVolumeFractionsAtVerts[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][upperLeftVertex], tempMesh->materialVolumeFractionsVertex[matID][lowerLeftVertex], verticesClippingTValue[vID])); + if (vID == 5) + out_newVolumeFractionsAtVerts[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][lowerLeftVertex], tempMesh->materialVolumeFractionsVertex[matID][lowerRightVertex], verticesClippingTValue[vID])); + if (vID == 6) + out_newVolumeFractionsAtVerts[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][lowerRightVertex], tempMesh->materialVolumeFractionsVertex[matID][upperRightVertex], verticesClippingTValue[vID])); + if (vID == 7) + out_newVolumeFractionsAtVerts[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][upperRightVertex], tempMesh->materialVolumeFractionsVertex[matID][upperLeftVertex], verticesClippingTValue[vID])); + } + } + } + + // Modify the evIndex values to account for the fact that perhaps not all 8 possible vertices are present + int evIndexSubtract[8]; + for (int i = 0; i < 8; ++i) + { + if (verticesPresent[i] == 1) + evIndexSubtract[i] = 0; + else + evIndexSubtract[i] = 1; + } + for (int i = 1; i < 8; ++i) + evIndexSubtract[i] += evIndexSubtract[i - 1]; + + for (unsigned int i = 0; i < out_evInds.size(); ++i) + { + out_evInds[i] -= evIndexSubtract[ out_evInds[i] ]; + } + + + + /**************************************************************** + * DEBUG PRINTING + ****************************************************************/ + { + // TESTING: Print out the values by which the evIndex values need to be substracted + // in order to ensure there are no gaps in the vertex ids in the resulting mesh. + // printf(" evIndexSubtract: { "); + // for (int i = 0; i < 8; i++) + // { + // printf("%d ", evIndexSubtract[i]); + // } + // printf("}\n"); + + // // TESTING: Print out the basic cell information + // printf(" out_numElements: %d\n", out_numElements[0]); + // printf(" out_numVerts: %d\n", out_numVerts[0]); + + // // TESTING: Print out the mesh topology information + // printf(" out_evInds: {"); + // for (int i = 0; i < out_evInds.size(); i++) + // { + // printf("%d ", out_evInds[i]); + // } + // printf("}\n"); + + // printf(" out_evBegins: {"); + // for (int i = 0; i < out_evBegins.size(); i++) + // { + // printf("%d ", out_evBegins[i]); + // } + // printf("}\n"); + + // printf(" out_veInds: {"); + // for (int i = 0; i < out_veInds.size(); i++) + // { + // printf("%d ", out_veInds[i]); + // } + // printf("}\n"); + + // printf(" out_veBegins: {"); + // for (int i = 0; i < out_veBegins.size(); i++) + // { + // printf("%d ", out_veBegins[i]); + // } + // printf("}\n"); + + // // TESTING: Print out the positions of the vertices + // for (auto itr = newPoints.begin(); itr != newPoints.end(); itr++) + // { + // printf(" Vertex %d: (%f, %f)\n", itr->first, itr->second.m_x, itr->second.m_y); + // } + + // TESTING: Print out the volume fractions at the vertices from the map + // for (auto itr = vertexVolumeFractionsMap.begin(); itr != vertexVolumeFractionsMap.end(); itr++) + // { + // int vID = itr->first; + // printf(" Vertex %d Volume Fractions:\n", vID); + // for (int matID = 0; matID < tempMesh->numMaterials; ++matID) + // { + // printf(" Mat %d: %f\n", matID, vertexVolumeFractionsMap[vID][matID]); + // } + // } + + // TESTING: Print out the volume fractions at the vertices from the vector of materials containing a vector of vertex volume fraction values + // for (int matID = 0; matID < out_newVolumeFractionsAtVerts.size(); ++matID) + // { + // printf(" Material %d:\n", matID); + // for (int vID = 0; vID < out_newVolumeFractionsAtVerts[matID].size(); ++vID) + // { + // printf(" Vertex %d: %f\n", vID, out_newVolumeFractionsAtVerts[matID][vID]); + // } + // } + } + } + +//-------------------------------------------------------------------------------- + +/// Performs linear interpolation between the two given vertex positions. +mir::Point2 InterfaceReconstructor::interpolateVertexPosition(mir::Point2 vertexOnePos, mir::Point2 vertexTwoPos, float t) +{ + mir::Point2 interpolatedPoint; + interpolatedPoint.m_x = (1 - t) * vertexOnePos.m_x + t * vertexTwoPos.m_x; + interpolatedPoint.m_y = (1 - t) * vertexOnePos.m_y + t * vertexTwoPos.m_y; + return interpolatedPoint; +} + +//-------------------------------------------------------------------------------- + +/// Performs linear interpolation between the two given float values +axom::float64 InterfaceReconstructor::lerpFloat(axom::float64 f0, axom::float64 f1, axom::float64 t) +{ + return (1 - t) * f0 + t * f1; +} + +//-------------------------------------------------------------------------------- + +/// Computes the t value as a percent from vertexOne to vertexTwo based on the two materials given. +/// The t value is the place where this edge should be clipped based on the two materials currently being considered. +axom::float64 InterfaceReconstructor::computeClippingPointOnEdge(const int vertexOneID, const int vertexTwoID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh) +{ + axom::float64 ret = 0.0; + + axom::float64 numerator = tempMesh->materialVolumeFractionsVertex[matTwoID][vertexOneID] - mesh->materialVolumeFractionsVertex[matOneID][vertexOneID]; + axom::float64 denominator = -mesh->materialVolumeFractionsVertex[matOneID][vertexOneID] + + mesh->materialVolumeFractionsVertex[matOneID][vertexTwoID] + + mesh->materialVolumeFractionsVertex[matTwoID][vertexOneID] + - mesh->materialVolumeFractionsVertex[matTwoID][vertexTwoID]; + + if (denominator != 0.0) + ret = numerator / denominator; + + return ret; +} + +//-------------------------------------------------------------------------------- + +mir::MIRMesh InterfaceReconstructor::computeReconstructedInterface() +{ + // Initialize the final mesh to be the same as the input mesh + mir::MIRMesh finalMesh(mesh); // TODO: This might just copy the reference to the original mesh, and not copy it. Could cause bugs when it gets overwritten. + + // For each material in the mesh, split the mesh on the current material and generate a new mesh (input into next iteration) + for (int matID = 0; matID < mesh->numMaterials - 1; ++matID) + { + // Copy the mesh to be split + mir::MIRMesh intermediateMesh(&finalMesh); + + // Update the materials upon which the split will occur + int matOne = matID; + int matTwo = matID + 1; + + // Process/split each element + std::vector temp_evInds[intermediateMesh.elems.size()]; // Store the vertex indices adjacent to each element for each cell + std::vector temp_evBegins[intermediateMesh.elems.size()]; // Store the starting indices into temp_evBewgins for each element-vertex for each cell + std::vector temp_veInds[intermediateMesh.elems.size()]; // Store the element indices adjacent to each vertex for each cell + std::vector temp_veBegins[intermediateMesh.elems.size()]; // Store the starting indices into temp_veInds for each vertex-element for each cell + + std::vector temp_vertexPositions[intermediateMesh.elems.size()]; // Store the positions of the vertices generated for each cell + std::vector materialInCell[intermediateMesh.elems.size()]; // Note: after splitting, the cells should all be clean and thus will have a vf of 1.0 or 0.0 for all materials + + int temp_numVerts[intermediateMesh.elems.size()]; // Store the number of vertices generated for each cell + int temp_numElements[intermediateMesh.elems.size()]; // Store the number of elements generated for each cell + + std::vector > temp_volumeFractionsVertex[intermediateMesh.elems.size()]; + + for (int eID = 0; eID < intermediateMesh.elems.size(); ++eID) + { + computeClippingPoints(eID, matOne, matTwo, &intermediateMesh, + temp_evInds[eID], temp_evBegins[eID], temp_veInds[eID], temp_veBegins[eID], temp_vertexPositions[eID], + materialInCell[eID], &(temp_numVerts[eID]), &(temp_numElements[eID]), temp_volumeFractionsVertex[eID]); + } + + // Copy the generated cell information into CellData structures + std::vector cellSplitInfo; + for (int eID = 0; eID < intermediateMesh.elems.size(); ++eID) + { + CellData nextCellSplitInfo(temp_numVerts[eID], temp_numElements[eID], temp_evInds[eID], temp_evBegins[eID], temp_veInds[eID], temp_veBegins[eID], temp_vertexPositions[eID], temp_volumeFractionsVertex[eID]); + cellSplitInfo.push_back(nextCellSplitInfo); + } + + // Merge each of the cells into the first CellData struct + for (unsigned int eID = 1; eID < cellSplitInfo.size(); ++eID) + cellSplitInfo[0].mergeCell(cellSplitInfo[eID]); + + mir::VertSet combined_verts(cellSplitInfo[0].numVerts); + mir::ElemSet combined_elems(cellSplitInfo[0].numElems); + + // TODO (later): Calculate the element volume fractions (note, they should all be either 0 or 1, since after the splits every cell should be clean) + + // Create the final, processed mesh + mir::MIRMesh processedMesh; + processedMesh.InitializeMesh(cellSplitInfo[0].evInds, cellSplitInfo[0].evBegins, cellSplitInfo[0].veInds, cellSplitInfo[0].veBegins, combined_verts, combined_elems, intermediateMesh.numMaterials); + processedMesh.constructMeshRelations(); + processedMesh.constructMeshVolumeFractionsVertex(cellSplitInfo[0].vertexVolumeFractions); + processedMesh.constructVertexPositionMap(cellSplitInfo[0].vertexPositions.data()); + + // Store the current mesh to be passed into the next iteration + finalMesh = processedMesh; + } + + // TODO: Run post-processing on mesh to meld the mesh vertices that are within a certain epsilon + + return finalMesh; +} + +//-------------------------------------------------------------------------------- + +} +} diff --git a/src/axom/mir/InterfaceReconstructor.hpp b/src/axom/mir/InterfaceReconstructor.hpp new file mode 100644 index 0000000000..ddb37b205a --- /dev/null +++ b/src/axom/mir/InterfaceReconstructor.hpp @@ -0,0 +1,64 @@ +// Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#ifndef __INTERFACE_RECONSTRUCTOR_H__ +#define __INTERFACE_RECONSTRUCTOR_H__ + +#include "axom/core.hpp" // for axom macros +#include "axom/slam.hpp" + +#include "MIRMesh.hpp" +#include "CellData.hpp" +#include "ZooBitMaps.hpp" +#include + +namespace numerics = axom::numerics; +namespace slam = axom::slam; + +namespace axom +{ +namespace mir +{ + class InterfaceReconstructor + { + public: + InterfaceReconstructor(); + InterfaceReconstructor(mir::MIRMesh* _mesh); + ~InterfaceReconstructor(); + + mir::MIRMesh computeReconstructedInterface(); + + + // std::vector computeVolumeFractionAverages(mir::MIRMesh* tempMesh); + + private: + mir::MIRMesh* mesh; + + public: + std::vector materialVolumeFractionsVertex; // volume fractions for each material for each vertex + + private: + void computeClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh, + std::vector& _evInds, std::vector& _evBegins, std::vector& _veInds, std::vector& _veBegins, + std::vector& _vertexPositions, std::vector& _materialInCell, int* _numVerts, int* _numElements, std::vector >& _newVolumeFractionsAtVerts); + + void computeTriangleClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh, + std::vector& _evInds, std::vector& _evBegins, std::vector& _veInds, std::vector& _veBegins, + std::vector& _vertexPositions, std::vector& _materialInCell, int* _numVerts, int* _numElements, std::vector >& _newVolumeFractionsAtVerts); + + void computeQuadClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh, + std::vector& out_evInds, std::vector& out_evBegins, std::vector& out_veInds, std::vector& out_veBegins, + std::vector& out_vertexPositions, std::vector& out_materialInCell, int* out_numVerts, int* out_numElements, std::vector >& out_newVolumeFractionsAtVerts); + + mir::Point2 interpolateVertexPosition(mir::Point2 vertexOnePos, mir::Point2 vertexTwoPos, float t); + axom::float64 lerpFloat(axom::float64 f0, axom::float64 f1, axom::float64 t); + axom::float64 computeClippingPointOnEdge(const int vertexOneID, const int vertexTwoID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh); + + void mergeCellSplitData(); + + }; +} +} +#endif \ No newline at end of file diff --git a/src/axom/mir/MIRMesh.cpp b/src/axom/mir/MIRMesh.cpp new file mode 100644 index 0000000000..89da4fc522 --- /dev/null +++ b/src/axom/mir/MIRMesh.cpp @@ -0,0 +1,318 @@ +// Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "MIRMesh.hpp" + +namespace axom +{ +namespace mir +{ + MIRMesh::MIRMesh() + { + + } + + MIRMesh::MIRMesh(MIRMesh* _mesh) // copy constructor + { + evInds = _mesh->evInds; + evBegins = _mesh->evBegins; + veInds = _mesh->veInds; + veBegins = _mesh->veBegins; + verts = _mesh->verts; + elems = _mesh->elems; + bdry = _mesh->bdry; + cobdry = _mesh->cobdry; + vertexPositions = _mesh->vertexPositions; + materialVolumeFractionsElement = _mesh->materialVolumeFractionsElement; + materialVolumeFractionsVertex = _mesh->materialVolumeFractionsVertex; + numMaterials = _mesh->numMaterials; + } + + MIRMesh::~MIRMesh() + { + + } + + /// Initializes a mesh with the given topology. + void MIRMesh::InitializeMesh(std::vector _evInds, std::vector _evBegins, std::vector _veInds, std::vector _veBegins, VertSet _verts, ElemSet _elems, int _numMaterials) + { + evInds = _evInds; + evBegins = _evBegins; + veInds = _veInds; + veBegins = _veBegins; + verts = _verts; + elems = _elems; + numMaterials = _numMaterials; + + // Check validity of the sets + SLIC_ASSERT_MSG( verts.isValid(), "Vertex set is not valid."); + SLIC_ASSERT_MSG( elems.isValid(), "Element set is not valid."); + } + +//-------------------------------------------------------------------------------- + + /// Constructs the mesh boundary and coboundary relations + void MIRMesh::constructMeshRelations() + { + // construct boundary relation from elements to vertices using variable cardinality + { + using RelationBuilder = ElemToVertRelation::RelationBuilder; + bdry = RelationBuilder() + .fromSet( &elems ) + .toSet( &verts ) + .begins( RelationBuilder::BeginsSetBuilder() + .size( elems.size() ) + .data( evBegins.data() ) ) + .indices ( RelationBuilder::IndicesSetBuilder() + .size( evInds.size() ) + .data( evInds.data() ) ); + } + + + { + // _quadmesh_example_construct_cobdry_relation_start + // construct coboundary relation from vertices to elements + using RelationBuilder = VertToElemRelation::RelationBuilder; + cobdry = RelationBuilder() + .fromSet( &verts ) + .toSet( &elems ) + .begins( RelationBuilder::BeginsSetBuilder() + .size( verts.size() ) + .data( veBegins.data() ) ) + .indices( RelationBuilder::IndicesSetBuilder() + .size( veInds.size() ) + .data( veInds.data() ) ); + // _quadmesh_example_construct_cobdry_relation_end + } + + SLIC_ASSERT_MSG( bdry.isValid(), "Boundary relation is not valid."); + SLIC_ASSERT_MSG( cobdry.isValid(), "Coboundary relation is not valid."); + + SLIC_INFO("Elem-Vert relation has size " << bdry.totalSize()); + SLIC_INFO("Vert-Elem relation has size " << cobdry.totalSize()); + } + +//-------------------------------------------------------------------------------- + + /// Constructs the volume fraction maps on the elements and vertices given element volume fraction data. + void MIRMesh::constructMeshVolumeFractionMaps(std::vector materialVolumeFractionsData) + { + // Initialize the maps for all of the materials with the input volume fraction data for each material + for (int matID = 0; matID < materialVolumeFractionsData.size(); ++matID) + { + // Initialize the map for the current material + materialVolumeFractionsElement.push_back(ScalarMap( &elems )); + + // Copy the data for the current material + for (int eID = 0; eID < elems.size(); ++eID) + { + materialVolumeFractionsElement[matID][eID] = materialVolumeFractionsData[matID][eID]; + } + + SLIC_ASSERT_MSG( materialVolumeFractionsElement[matID].isValid(), "Element volume fraction map is not valid."); + } + + // Initialize the maps for all of the vertex volume fractions + for (int matID = 0; matID < materialVolumeFractionsData.size(); ++matID) + { + // Initialize the new map for the volume fractions + materialVolumeFractionsVertex.push_back(ScalarMap( &verts ) ); + + // Calculate the average volume fraction value for the current vertex for the current material + for (int vID = 0; vID < verts.size(); ++vID) + { + // Compute the per vertex volume fractions for the green material + axom::float64 sum = 0; + auto vertexElements = cobdry[vID]; + + for (int i = 0; i < vertexElements.size(); ++i) + { + auto eID = vertexElements[i]; + sum += materialVolumeFractionsElement[matID][eID]; + } + + materialVolumeFractionsVertex[matID][vID] = sum / vertexElements.size(); + } + + SLIC_ASSERT_MSG( materialVolumeFractionsVertex[matID].isValid(), "Vertex volume fraction map is not valid."); + } + } + +//-------------------------------------------------------------------------------- + +/// Construct the vertex volume fraction data given vertex volume fraction data +void MIRMesh::constructMeshVolumeFractionsVertex(std::vector > vertexVF) +{ + // Initialize the maps for all of the materials with the input volume fraction data for each vertex + for (int matID = 0; matID < numMaterials; ++matID) + { + // Initialize the map for the current material + materialVolumeFractionsVertex.push_back(ScalarMap( &verts )); + + // Copy the data for the current material + for (int vID = 0; vID < verts.size(); ++vID) + { + materialVolumeFractionsVertex[matID][vID] = vertexVF[matID][vID]; + } + + SLIC_ASSERT_MSG( materialVolumeFractionsVertex[matID].isValid(), "Vertex volume fraction map is not valid."); + } +} + +//-------------------------------------------------------------------------------- + + /// Constucts the positions map on the vertices + void MIRMesh::constructVertexPositionMap(Point2* data) + { + // construct the position map on the vertices + vertexPositions = PointMap( &verts ); + + for (int vID = 0; vID < verts.size(); ++vID) + vertexPositions[vID] = data[vID]; + + SLIC_ASSERT_MSG( vertexPositions.isValid(), "Position map is not valid."); + } + +//-------------------------------------------------------------------------------- + + /// Prints out the map values for each element + void MIRMesh::printElementScalarMap(ScalarMap& elements, std::string prefix) + { + std::cout << prefix; + for (int eID = 0; eID < elems.size(); ++eID) + { + printf("Element %d: %f\n", eID, elements[eID]); + } + } + +//-------------------------------------------------------------------------------- + + /// Prints out the map values for each vertex + void MIRMesh::printVertexScalarMap(ScalarMap& vertices, std::string prefix) + { + std::cout << prefix; + for (int vID = 0; vID < verts.size(); ++vID) + { + printf("Vertex %d: %f\n", vID, vertices[vID]); + } + } + +//-------------------------------------------------------------------------------- + + void MIRMesh::print() + { + printf("------------------------Printing Mesh Information:------------------------\n"); + printf("number of vertices: %d\n", verts.size()); + printf("number of elements: %d\n", elems.size()); + printf("number of materials: %d\n", numMaterials); + + printf("evInds: { "); + for (int i = 0; i < evInds.size(); i++) + { + printf("%d ", evInds[i]); + } + printf("}\n"); + + printf("evBegins: { "); + for (int i = 0; i < evBegins.size(); i++) + { + printf("%d ", evBegins[i]); + } + printf("}\n"); + + printf("veInds: { "); + for (int i = 0; i < veInds.size(); i++) + { + printf("%d ", veInds[i]); + } + printf("}\n"); + + printf("veBegins: { "); + for (int i = 0; i < veBegins.size(); i++) + { + printf("%d ", veBegins[i]); + } + printf("}\n"); + + printf("vertexPositions: { "); + for (int i = 0; i < vertexPositions.size(); ++i) + { + printf("{%.2f, %.2f} ", vertexPositions[i].m_x, vertexPositions[i].m_y); + } + + printf("}\n"); + printf("--------------------------------------------------------------------------\n"); + } + +//-------------------------------------------------------------------------------- + + /// Reads in a constructs a mesh from the given file + void MIRMesh::readMeshFromFile() + { + printf("Mesh writing functionality not implemented yet."); + + } + +//-------------------------------------------------------------------------------- + + /// Writes out the mesh to a file + void MIRMesh::writeMeshToFile(std::string filename) + { + std::ofstream meshfile; + meshfile.open(filename); + std::ostream_iterator out_it(meshfile, " "); + + // write header + meshfile << "# vtk DataFile Version 3.0\n" + << "vtk output\n" + << "ASCII\n" + << "DATASET UNSTRUCTURED_GRID\n\n" + << "POINTS " << verts.size() << " double\n"; + + // write positions + for (int vID = 0; vID < verts.size(); ++vID) + { + meshfile << vertexPositions[vID].m_x << " " << vertexPositions[vID].m_y << " 0\n"; // must always set all 3 coords; set Z=0 for 2D + } + + // // write elem-to-vert boundary relation + // meshfile << "\nCELLS " << elems.size() << " " << 5 * elems.size(); // TODO: This will not always be 5 + // for(auto e: elems) + // { + // meshfile<<"\n4 "; // TODO: This will not always be 4 + // std::copy ( bdry.begin(e), bdry.end(e), out_it ); + // } + + // // write element types ( 9 == VTK_QUAD, 5 == VTK_TRIANGLE ) + // meshfile << "\n\nCELL_TYPES " << elems.size() << "\n"; + // for(int i=0 ; i< elems.size() ; ++i) + // { + // meshfile << "9 "; // TODO: This will not always be 9, but will be 5 for any triangle elements + // } + + // // write element ids + // meshfile << "\n\nCELL_DATA " << elems.size() + // << "\nSCALARS cellIds int 1" + // << "\nLOOKUP_TABLE default \n"; + // for(int i=0 ; i< elems.size() ; ++i) + // { + // meshfile << elems[i] <<" "; + // } + + // // write vertex ids + // meshfile << "\n\nPOINT_DATA " << verts.size() + // << "\nSCALARS vertIds int 1" + // << "\nLOOKUP_TABLE default \n"; + // for(int i=0 ; i< verts.size() ; ++i) + // { + // meshfile << verts[i] <<" "; + // } + meshfile <<"\n"; + } + +//-------------------------------------------------------------------------------- + +} +} \ No newline at end of file diff --git a/src/axom/mir/MIRMesh.hpp b/src/axom/mir/MIRMesh.hpp new file mode 100644 index 0000000000..696967840d --- /dev/null +++ b/src/axom/mir/MIRMesh.hpp @@ -0,0 +1,100 @@ +// Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#ifndef __MIR_MESH_H__ +#define __MIR_MESH_H__ + + +#include "axom/core.hpp" // for axom macros +#include "axom/slam.hpp" // unified header for slam classes and functions + +#include "MIRMeshTypes.hpp" + +// C/C++ includes +#include // for definition of M_PI, exp() +#include +#include +#include + +namespace numerics = axom::numerics; +namespace slam = axom::slam; + + +//-------------------------------------------------------------------------------- +namespace axom +{ +namespace mir +{ + struct EdgeClipInfo + { + int vertexOne; + int vertexTwo; + int colorOne; + int colorTwo; + float t; // The percent distance from vertexOne to VertexTwo where the clip occurs. + }; + + //-------------------------------------------------------------------------------- + + class MIRMesh + { + /**************************************************************** + * MESH FUNCTIONS + ****************************************************************/ + public: + MIRMesh(); + MIRMesh(MIRMesh* _mesh); // copy constructor + ~MIRMesh(); + + void InitializeMesh(std::vector _evInds, std::vector _evBegins, std::vector _veInds, std::vector _veBegins, VertSet _verts, ElemSet _elems, int _numMaterials); + + void constructMeshRelations(); /// Constructs the mesh boundary and coboundary relations + void constructMeshVolumeFractionMaps(std::vector materialVolumeFractionsData); /// Constructs the volume fraction maps on the vertices + void constructMeshVolumeFractionsVertex(std::vector > vertexVF); /// Constructs the vertex volume fraction map + void constructVertexPositionMap(Point2* data); /// Constucts the positions map on the vertices + + void printElementScalarMap(ScalarMap& elements, std::string prefix); /// Prints out the map values for each element + void printVertexScalarMap(ScalarMap& vertices, std::string prefix); /// Prints out the map values for each vertex + + void print(); // Print out a variety of useful information about the mesh + + void readMeshFromFile(); /// Reads in and constructs a mesh from a file + void writeMeshToFile(std::string filename); /// Writes out the current mesh to a file + void attachVertexMap(); /// Adds a map of data to the mesh + + /**************************************************************** + * VARIABLES + ****************************************************************/ + public: + // support data for mesh connectivity + std::vector evInds; + std::vector evBegins; + std::vector veInds; + std::vector veBegins; + + public: + // Mesh Set Definitions + VertSet verts; // the set of vertices in the mesh + ElemSet elems; // the set of elements in the mesh + + public: + // Mesh Relation Definitions + ElemToVertRelation bdry; // Boundary relation from elements to vertices + VertToElemRelation cobdry; // Coboundary relation from vertices to elements + + public: + // Mesh Map Definitions + PointMap vertexPositions; // vertex position + std::vector materialVolumeFractionsElement; // the volume fractions of each material for each element + std::vector materialVolumeFractionsVertex; // the volume fractions of each material for each vertex + + public: + int numMaterials; + }; + + //-------------------------------------------------------------------------------- +} +} +#endif \ No newline at end of file diff --git a/src/axom/mir/MIRMeshTypes.hpp b/src/axom/mir/MIRMeshTypes.hpp new file mode 100644 index 0000000000..d90ecbeb35 --- /dev/null +++ b/src/axom/mir/MIRMeshTypes.hpp @@ -0,0 +1,74 @@ +// Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#ifndef __MIR_MESH_TYPES_H__ +#define __MIR_MESH_TYPES_H__ + + +#include "axom/core.hpp" // for axom macros +// #include "axom/mir.hpp" // for Mir classes & functions +#include "axom/slam.hpp" + + +namespace numerics = axom::numerics; +namespace slam = axom::slam; + +namespace axom +{ +namespace mir +{ + /** + * \brief Simple 2D Point class for example + */ + struct Point2 + { + Point2(double x = 0., double y = 0.) : m_x(x), m_y(y) {} + + Point2(const Point2& other) : m_x(other.m_x), m_y(other.m_y) {} + + Point2& operator=(const Point2& other) + { m_x = other.m_x; m_y = other.m_y; return *this; } + + Point2& operator+=(const Point2& other) + { m_x += other.m_x; m_y += other.m_y; return *this; } + + Point2& operator/=(double val) + { m_x /= val; m_y += val; return *this; } + + double& operator[] (int i) { return (i==0) ? m_x : m_y; } + const double& operator[] (int i) const { return (i==0) ? m_x : m_y; } + + friend std::ostream& operator<<(std::ostream& os, const Point2& pt) + { return os << "{x:" << pt.m_x << ", y:" << pt.m_y <<"}"; } + + double m_x, m_y; + }; + + // SET TYPE ALIASES + using PosType = slam::DefaultPositionType; + using ElemType = slam::DefaultElementType; + + using ArrayIndir = slam::policies::ArrayIndirection< PosType, ElemType >; + + using VertSet = slam::PositionSet< PosType, ElemType >; + using ElemSet = slam::PositionSet< PosType, ElemType >; + + // RELATION TYPE ALIASES + using VarCard = slam::policies::VariableCardinality< PosType, ArrayIndir >; + + // Note: This is the actual relation type, which takes in a bunch of policies and data entries to relate to each other. + // Note: It is the relation of the elements to the vertices. + + using ElemToVertRelation = slam::StaticRelation< PosType, ElemType, VarCard, ArrayIndir, ElemSet, VertSet >; + using VertToElemRelation = slam::StaticRelation< PosType, ElemType, VarCard, ArrayIndir, VertSet, ElemSet >; + + // MAP TYPE ALIASES + using BaseSet = slam::Set< PosType, ElemType >; + using ScalarMap = slam::Map< BaseSet, axom::float64 >; // Note: Documentation has a typo in it. + using PointMap = slam::Map< BaseSet, Point2 >; + +} +} +#endif \ No newline at end of file diff --git a/src/axom/mir/ZooBitMaps.cpp b/src/axom/mir/ZooBitMaps.cpp new file mode 100644 index 0000000000..d2adf217a3 --- /dev/null +++ b/src/axom/mir/ZooBitMaps.cpp @@ -0,0 +1,47 @@ +// Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "ZooBitMaps.hpp" + +namespace axom +{ +namespace mir +{ + + // Quad Vertex Indices + // + // 0 7 3 + // @---------@---------@ + // | | + // | | + // | | + // 4 @ @ 6 + // | | + // | | + // | | + // @---------@---------@ + // 1 5 2 + // + const int quadClipTable[16][19] = + { + {4,0,1,2,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, + {3,3,7,6,4,7,0,2,6,3,0,1,2,-1,-1,-1,-1,-1,-1}, + {3,6,5,2,4,3,1,5,6,3,0,1,3,-1,-1,-1,-1,-1,-1}, + {4,0,1,5,7,4,7,5,2,3,-1,-1,-1,-1,-1,-1,-1,-1}, + {3,4,1,5,4,0,4,5,2,3,7,6,3,-1,-1,-1,-1,-1,-1}, + {3,4,1,5,4,0,4,5,2,4,0,2,6,7,3,7,6,3,-1}, + {4,0,4,6,3,4,4,1,2,6,-1,-1,-1,-1,-1,-1,-1,-1,-1}, + {3,0,4,7,4,4,1,3,7,3,1,2,3,-1,-1,-1,-1,-1,-1}, + {3,0,4,7,4,4,1,3,7,3,1,2,3,-1,-1,-1,-1,-1,-1}, + {4,0,4,6,3,4,4,1,2,6,-1,-1,-1,-1,-1,-1,-1,-1,-1}, + {3,4,1,5,4,0,4,5,2,4,0,2,6,7,3,7,6,3,-1}, + {3,4,1,5,4,0,4,5,2,3,7,6,3,-1,-1,-1,-1,-1,-1}, + {4,0,1,5,7,4,7,5,2,3,-1,-1,-1,-1,-1,-1,-1,-1}, + {3,6,5,2,4,3,1,5,6,3,0,1,3,-1,-1,-1,-1,-1,-1}, + {3,3,7,6,4,7,0,2,6,3,0,1,2,-1,-1,-1,-1,-1,-1}, + {4,0,1,2,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1} + }; +} +} \ No newline at end of file diff --git a/src/axom/mir/ZooBitMaps.hpp b/src/axom/mir/ZooBitMaps.hpp new file mode 100644 index 0000000000..09f76ede1e --- /dev/null +++ b/src/axom/mir/ZooBitMaps.hpp @@ -0,0 +1,17 @@ +// Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#ifndef __ZOO_BIT_MAPS_H__ +#define __ZOO_BIT_MAPS_H__ + + +namespace axom +{ +namespace mir +{ + extern const int quadClipTable[16][19]; +} +} +#endif \ No newline at end of file diff --git a/src/axom/mir/examples/CMakeLists.txt b/src/axom/mir/examples/CMakeLists.txt index b199beae56..605609bf02 100644 --- a/src/axom/mir/examples/CMakeLists.txt +++ b/src/axom/mir/examples/CMakeLists.txt @@ -5,6 +5,7 @@ set( mir_examples mir_tutorial.cpp + mir_tutorial_simple.cpp ) set( mir_example_dependencies diff --git a/src/axom/mir/examples/mir_tutorial.cpp b/src/axom/mir/examples/mir_tutorial.cpp index 7dbcb69a0d..576da4d49d 100644 --- a/src/axom/mir/examples/mir_tutorial.cpp +++ b/src/axom/mir/examples/mir_tutorial.cpp @@ -4,16 +4,437 @@ // SPDX-License-Identifier: (BSD-3-Clause) #include "axom/core.hpp" // for axom macros -#include "axom/mir.hpp" // for Mir classes & functions +// #include "axom/mir.hpp" // for Mir classes & functions +#include "axom/slam.hpp" // C/C++ includes #include // for definition of M_PI, exp() +#include // namespace aliases //namespace mir = axom::mir; namespace numerics = axom::numerics; +namespace slam = axom::slam; + +#define EPSILON_ZERO = 0.00000000001f; + +//-------------------------------------------------------------------------------- + +/** + * \brief Simple 2D Point class for example + */ +struct Point2 +{ + Point2(double x = 0., double y = 0.) : m_x(x), m_y(y) {} + + Point2(const Point2& other) : m_x(other.m_x), m_y(other.m_y) {} + + Point2& operator=(const Point2& other) + { m_x = other.m_x; m_y = other.m_y; return *this; } + + Point2& operator+=(const Point2& other) + { m_x += other.m_x; m_y += other.m_y; return *this; } + + Point2& operator/=(double val) + { m_x /= val; m_y += val; return *this; } + + double& operator[] (int i) { return (i==0) ? m_x : m_y; } + const double& operator[] (int i) const { return (i==0) ? m_x : m_y; } + + friend std::ostream& operator<<(std::ostream& os, const Point2& pt) + { return os << "{x:" << pt.m_x << ", y:" << pt.m_y <<"}"; } + + double m_x, m_y; +}; + +//-------------------------------------------------------------------------------- + +struct EdgeClipInfo +{ + int vertexOne; + int vertexTwo; + int colorOne; + int colorTwo; + float t; // The percent distance from vertexOne to VertexTwo where the clip occurs. +}; + +//-------------------------------------------------------------------------------- + +struct MIRMesh +{ + /**************************************************************** + * TYPE ALIASES + ****************************************************************/ + // SET TYPE ALIASES + using PosType = slam::DefaultPositionType; + using ElemType = slam::DefaultElementType; + + using ArrayIndir = slam::policies::ArrayIndirection< PosType, ElemType >; + + using VertSet = slam::PositionSet< PosType, ElemType >; + using ElemSet = slam::PositionSet< PosType, ElemType >; + + // RELATION TYPE ALIASES + using VarCard = slam::policies::VariableCardinality< PosType, ArrayIndir >; + + // Note: This is the actual relation type, which takes in a bunch of policies and data entries to relate to each other. + // Note: It is the relation of the elements to the vertices. + using ElemToVertRelation = slam::StaticRelation< PosType, ElemType, VarCard, ArrayIndir, ElemSet, VertSet >; + using VertToElemRelation = slam::StaticRelation< PosType, ElemType, VarCard, ArrayIndir, VertSet, ElemSet >; + + // MAP TYPE ALIASES + using BaseSet = slam::Set< PosType, ElemType >; + using ScalarMap = slam::Map< BaseSet, axom::float64 >; // Note: Documentation has a typo in it. + using PointMap = slam::Map< BaseSet, Point2 >; + + enum { GREEN = 0, BLUE = 1 }; + + /**************************************************************** + * MESH FUNCTIONS + ****************************************************************/ + void InitializeMesh() + { + // Create the mesh connectivity information + // data for element-vertex boundary relation + evInds = { + 0,4,5,1, // elem 0, card 4, start 0 + 1,5,6,2, // elem 1, card 4, start 4 + 2,6,7,3, // elem 2, card 4, start 8 + 4,8,9,5, // elem 3, card 4, start 12 + 5,9,10,6, // elem 4, card 4, start 16 + 6,10,11,7, // elem 5, card 4, start 20 + 8,12,13,9, // elem 6, card 4, start 24 + 9,13,14,10, // elem 7, card 4, start 28 + 10,14,15,11 // elem 8, card 4, start 32, end 36 + }; + + evBegins = { + 0,4,8,12,16,20,24,28,32,36 + }; + + // data for vertex-element coboundary relation + veInds = { + 0, // vert 0, card 1, start 0 + 0,1, // vert 1, card 2, start 1 + 1,2, // vert 2, card 2, start 3 + 2, // vert 3, card 1, start 5 + 0,3, // vert 4, card 2, start 6 + 0,1,3,4, // vert 5, card 4, start 8 + 1,2,4,5, // vert 6, card 4, start 12 + 2,5, // vert 7, card 2, start 16 + 3,6, // vert 8, card 2, start 18 + 3,4,6,7, // vert 9, card 4, start 20 + 4,5,7,8, // vert 10, card 4, start 24 + 5,8, // vert 11, card 2, start 28 + 6, // vert 12, card 1, start 30 + 6,7, // vert 13, card 2, start 31 + 7,8, // vert 14, card 2, start 33 + 8, // vert 15, card 1, start 35, end 36 + }; + veBegins = { + 0,1,3,5,6,8,12,16,18,20,24,28,30,31,33,35,36 + }; + + // Initialize the mesh sets + verts = VertSet(16); // Construct a vertex set with 11 vertices + elems = ElemSet(9); // Construct an element set with 5 elements + + // Check validity of the sets + SLIC_ASSERT_MSG( verts.isValid(), "Vertex set is not valid."); + SLIC_ASSERT_MSG( elems.isValid(), "Element set is not valid."); + + printf("Mesh has been initialized.\n"); + } + + /// Constructs the mesh boundary and coboundary relations + void constructMeshRelations() + { + + // construct boundary relation from elements to vertices using variable cardinality + { + using RelationBuilder = ElemToVertRelation::RelationBuilder; + bdry = RelationBuilder() + .fromSet( &elems ) + .toSet( &verts ) + .begins( RelationBuilder::BeginsSetBuilder() + .size( elems.size() ) + .data( evBegins.data() ) ) + .indices ( RelationBuilder::IndicesSetBuilder() + .size( evInds.size() ) + .data( evInds.data() ) ); + } + + + { + // _quadmesh_example_construct_cobdry_relation_start + // construct coboundary relation from vertices to elements + using RelationBuilder = VertToElemRelation::RelationBuilder; + cobdry = RelationBuilder() + .fromSet( &verts ) + .toSet( &elems ) + .begins( RelationBuilder::BeginsSetBuilder() + .size( verts.size() ) + .data( veBegins.data() ) ) + .indices( RelationBuilder::IndicesSetBuilder() + .size( veInds.size() ) + .data( veInds.data() ) ); + // _quadmesh_example_construct_cobdry_relation_end + } + + + SLIC_ASSERT_MSG( bdry.isValid(), "Boundary relation is not valid."); + SLIC_ASSERT_MSG( cobdry.isValid(), "Coboundary relation is not valid."); + + SLIC_INFO("Elem-Vert relation has size " << bdry.totalSize()); + SLIC_INFO("Vert-Elem relation has size " << cobdry.totalSize()); + + printf("Finished constructing mesh relations.\n"); + } + + /// Constructs the volume fraction maps on the vertices + void constructMeshVolumeFractionMaps() + { + // TODO: Determine better way to store volume fraction data than 1 map per material? + // Construct the scalar map on the elements for the green material + greenVolumeFractionsElements = ScalarMap( &elems ); + + // Set the green volume fraction for each element + greenVolumeFractionsElements[0] = 1.0; + greenVolumeFractionsElements[1] = 1.0; + greenVolumeFractionsElements[2] = 1.0; + greenVolumeFractionsElements[3] = 1.0; + greenVolumeFractionsElements[4] = 0.5; + greenVolumeFractionsElements[5] = 0.2; + greenVolumeFractionsElements[6] = 0.2; + greenVolumeFractionsElements[7] = 0.0; + greenVolumeFractionsElements[8] = 0.0; + + // Construct the scalar map on the elements for the blue material + blueVolumeFractionsElements = ScalarMap( &elems ); + + blueVolumeFractionsElements[0] = 0.0; + blueVolumeFractionsElements[1] = 0.0; + blueVolumeFractionsElements[2] = 0.0; + blueVolumeFractionsElements[3] = 0.0; + blueVolumeFractionsElements[4] = 0.5; + blueVolumeFractionsElements[5] = 0.8; + blueVolumeFractionsElements[6] = 0.8; + blueVolumeFractionsElements[7] = 1.0; + blueVolumeFractionsElements[8] = 1.0; + + printf("Finished constructing volume fractions for mesh elements.\n"); + } + + /// Constucts the positions map on the vertices + void constructVertexPositionMap() + { + // construct the position map on the vertices + vertexPositions = PointMap( &verts ); + + // first vertex is at origin + vertexPositions[0] = Point2( 0.0, 3.0 ); + vertexPositions[1] = Point2( 1.0, 3.0 ); + vertexPositions[2] = Point2( 2.0, 3.0 ); + vertexPositions[3] = Point2( 3.0, 3.0 ); + + vertexPositions[4] = Point2( 0.0, 2.0 ); + vertexPositions[5] = Point2( 1.0, 2.0 ); + vertexPositions[6] = Point2( 2.0, 2.0 ); + vertexPositions[7] = Point2( 3.0, 2.0 ); + + vertexPositions[8] = Point2( 0.0, 1.0 ); + vertexPositions[9] = Point2( 1.0, 1.0 ); + vertexPositions[10] = Point2( 2.0, 1.0 ); + vertexPositions[11] = Point2( 3.0, 1.0 ); + + vertexPositions[12] = Point2( 0.0, 0.0 ); + vertexPositions[13] = Point2( 1.0, 0.0 ); + vertexPositions[14] = Point2( 2.0, 0.0 ); + vertexPositions[15] = Point2( 3.0, 0.0 ); + + SLIC_ASSERT_MSG( vertexPositions.isValid(), "Position map is not valid."); + + SLIC_INFO("-- Vertex positions:"); + for(int vID=0 ; vID < verts.size() ; ++vID) + { + SLIC_INFO("Position of vert " << vID << " is " << vertexPositions[vID]); + } + } + + // Computes the average volume fraction at each vertex of the mesh + void computeVolumeFractionAverages() + { + // Initialize the vertex volume fraction maps + greenVolumeFractionsVertices = ScalarMap( &verts ); + blueVolumeFractionsVertices = ScalarMap( &verts ); + + for (int vID = 0; vID < verts.size(); ++vID) + { + // Compute the per vertex volume fractions for the green material + axom::float64 sum = 0; + auto vertexElements = cobdry[vID]; + for (int i = 0; i < vertexElements.size(); ++i) + { + auto eID = vertexElements[i]; + sum += greenVolumeFractionsElements[eID]; + } + greenVolumeFractionsVertices[vID] = sum / vertexElements.size(); + + // Compute the per vertex volume fractions for the blue material + sum = 0; + for (int i = 0; i < vertexElements.size(); ++i) + { + auto eID = vertexElements[i]; + sum += blueVolumeFractionsElements[eID]; + } + blueVolumeFractionsVertices[vID] = sum / vertexElements.size(); + } + + printf("Finished computing volume fraction averages.\n"); + } + + /// Prints out the map values for each element + void printElementScalarMap(ScalarMap& elements, std::string prefix) + { + std::cout << prefix; + for (int i = 0; i < elems.size(); ++i) + { + printf("Element %d: %f\n", i, elements[i]); + } + } + + /// Prints out the map values for each vertex + void printVertexScalarMap(ScalarMap& vertices, std::string prefix) + { + std::cout << prefix; + for (int i = 0; i < verts.size(); ++i) + { + printf("Vertex %d: %f\n", i, vertices[i]); + } + } + + /// Use bilinear interpolation to compute the points where the given element should be clipped. + /// Returns true if clip should occur, otherwise false. + /// If a clip should occur, the clipping points will be given in p1 and p2. + void computeClippingPoints(const int eID)//, Point2* p1, Point2* p2) + { + // Find the vertices associated with each element + auto elementVertices = bdry[eID]; + + // Check if one of the two currently considered materials is not present at the vertices of the current element + if (blueVolumeFractionsElements[eID] != 0.0 && greenVolumeFractionsElements[eID] != 0.0) + { + // Triangle Case + if (elementVertices.size() == 3) + { + computeTriangleClippingPoints(eID); + } + // Quad Case + if (elementVertices.size() == 4) + { + computeQuadClippingPoints(eID); + } + } + else + { + printf("No cuts in element %d.\n", eID); + } + } + + /// Computes the points where the given quad element should be clipped + void computeQuadClippingPoints(int eID) + { + auto elementVertices = bdry[eID]; + EdgeClipInfo eci[4]; + + // Initialize the edge clipping info + for (int i = 0; i < 4; ++i) + { + eci[i].vertexOne = elementVertices[ i ]; + eci[i].vertexTwo = elementVertices[ (i + 1) % 4 ]; + + if (blueVolumeFractionsVertices[eci[i].vertexOne] > greenVolumeFractionsVertices[eci[i].vertexOne]) + eci[i].colorOne = BLUE; + else + eci[i].colorOne = GREEN; + + if (blueVolumeFractionsVertices[eci[i].vertexTwo] > greenVolumeFractionsVertices[eci[i].vertexTwo]) + eci[i].colorTwo = BLUE; + else + eci[i].colorTwo = GREEN; + + eci[i].t = -1.0; // default t value means one material dominates the other and no clip should occur on this edge + } + + // Analyze the edge clipping info and determine where to clip the cell + for (int i = 0; i < 4; ++i) + { + if (eci[i].colorOne != eci[i].colorTwo) + { + axom::float64 numerator = blueVolumeFractionsVertices[eci[i].vertexOne] - greenVolumeFractionsVertices[eci[i].vertexOne]; + axom::float64 denominator = -greenVolumeFractionsVertices[eci[i].vertexOne] + + greenVolumeFractionsVertices[eci[i].vertexTwo] + + blueVolumeFractionsVertices[eci[i].vertexOne] + - blueVolumeFractionsVertices[eci[i].vertexTwo]; + + if (denominator != 0.0) + eci[i].t = numerator / denominator; + } + } + + // Print out the cutting information + bool cut = false; + for (int i = 0; i < 4; ++i) + { + if (eci[i].t > 0.0) + { + printf("Cutting element %d between vertices %d and %d. t = %f\n", eID, eci[i].vertexOne, eci[i].vertexTwo, eci[i].t); + cut = true; + } + } + if (!cut) + printf("No cuts in element %d.\n", eID); + + } + + /// Computes the points where the given triangle element should be clipped + void computeTriangleClippingPoints(int eID) + { + printf("Triangle clipping case not yet implemented.\n"); + } + + /**************************************************************** + * VARIABLES + ****************************************************************/ + public: + // Mesh Set Definitions + VertSet verts; // the set of vertices in the mesh + ElemSet elems; // the set of elements in the mesh + + // Mesh Relation Definitions + ElemToVertRelation bdry; // Boundary relation from elements to vertices + VertToElemRelation cobdry; // Coboundary relation from vertices to elements + + // Mesh Map Definitions + PointMap vertexPositions; // vertex position + ScalarMap greenVolumeFractionsElements; // the volume fractions of the green material for each element (NOT each vertex) + ScalarMap blueVolumeFractionsElements; // the volume fractions of the blue material for each element (NOT each vertex) + + ScalarMap greenVolumeFractionsVertices; + ScalarMap blueVolumeFractionsVertices; + + // support data for mesh connectivity + std::vector evInds; + std::vector evBegins; + std::vector veInds; + std::vector veBegins; +}; + + +//-------------------------------------------------------------------------------- + /*! * \file @@ -32,11 +453,28 @@ namespace numerics = axom::numerics; */ int main( int AXOM_NOT_USED(argc), char** AXOM_NOT_USED(argv) ) { + printf("Beginning the MIR Tutorial program.\n"); - std::cout << "hello from the mir_tutorial!" << std::endl; + MIRMesh testMesh; + testMesh.InitializeMesh(); + testMesh.constructMeshRelations(); + // testMesh.constructMeshPositionMap(); + testMesh.constructMeshVolumeFractionMaps(); + testMesh.computeVolumeFractionAverages(); - return 0; -} + // Print volume fraction debugging information + // testMesh.printElementScalarMap(testMesh.greenVolumeFractionsElements, "Green Volume Fractions per Element: \n"); + // testMesh.printElementScalarMap(testMesh.blueVolumeFractionsElements, "Blue Volume Fractions per Element: \n"); + // testMesh.printVertexScalarMap(testMesh.greenVolumeFractionsVertices, "Green Volume Fractions per Vertex: \n"); + // testMesh.printVertexScalarMap(testMesh.blueVolumeFractionsVertices, "Blue Volume Fractions per Vertex: \n"); + // Determine where to clip the cells + for (int i = 0; i < 9; ++i) + testMesh.computeClippingPoints(i); + + + return 0; +} +//-------------------------------------------------------------------------------- \ No newline at end of file diff --git a/src/axom/mir/examples/mir_tutorial_simple.cpp b/src/axom/mir/examples/mir_tutorial_simple.cpp new file mode 100644 index 0000000000..23bfc09769 --- /dev/null +++ b/src/axom/mir/examples/mir_tutorial_simple.cpp @@ -0,0 +1,122 @@ +// Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "axom/core.hpp" // for axom macros +// #include "axom/mir.hpp" // for Mir classes & functions +#include "axom/slam.hpp" + +#include "../MIRMesh.hpp" +#include "../InterfaceReconstructor.hpp" + +// namespace aliases +//namespace mir = axom::mir; +namespace numerics = axom::numerics; +namespace slam = axom::slam; +namespace mir = axom::mir; + +//-------------------------------------------------------------------------------- + +/*! + * \brief Tutorial main + */ +int main( int AXOM_NOT_USED(argc), char** AXOM_NOT_USED(argv) ) +{ + int numElements = 9; + int numVertices = 16; + + // Create the mesh connectivity information + std::vector evInds = { + 0,4,5,1, // elem 0, card 4, start 0 + 1,5,6,2, // elem 1, card 4, start 4 + 2,6,7,3, // elem 2, card 4, start 8 + 4,8,9,5, // elem 3, card 4, start 12 + 5,9,10,6, // elem 4, card 4, start 16 + 6,10,11,7, // elem 5, card 4, start 20 + 8,12,13,9, // elem 6, card 4, start 24 + 9,13,14,10, // elem 7, card 4, start 28 + 10,14,15,11 // elem 8, card 4, start 32, end 36 + }; + + std::vector evBegins = { + 0,4,8,12,16,20,24,28,32,36 + }; + std::vector veInds = { + 0, // vert 0, card 1, start 0 + 0,1, // vert 1, card 2, start 1 + 1,2, // vert 2, card 2, start 3 + 2, // vert 3, card 1, start 5 + 0,3, // vert 4, card 2, start 6 + 0,1,3,4, // vert 5, card 4, start 8 + 1,2,4,5, // vert 6, card 4, start 12 + 2,5, // vert 7, card 2, start 16 + 3,6, // vert 8, card 2, start 18 + 3,4,6,7, // vert 9, card 4, start 20 + 4,5,7,8, // vert 10, card 4, start 24 + 5,8, // vert 11, card 2, start 28 + 6, // vert 12, card 1, start 30 + 6,7, // vert 13, card 2, start 31 + 7,8, // vert 14, card 2, start 33 + 8, // vert 15, card 1, start 35, end 36 + }; + std::vector veBegins = { + 0,1,3,5,6,8,12,16,18,20,24,28,30,31,33,35,36 + }; + + mir::VertSet verts = mir::VertSet(numVertices); // Construct a vertex set with 16 vertices + mir::ElemSet elems = mir::ElemSet(numElements); // Construct an element set with 9 elements + + + int numMaterials = 2; + enum { GREEN = 0, BLUE = 1 }; + + std::vector materialVolumeFractionsData; + materialVolumeFractionsData.resize(numMaterials); + axom::float64 greenVolumeFractions[] = {1.0, 1.0, 1.0, 1.0, 0.5, 0.2, 0.2, 0.0, 0.0}; + materialVolumeFractionsData[GREEN] = greenVolumeFractions; + axom::float64 blueVolumeFractions[] = {0.0, 0.0, 0.0, 0.0, 0.5, 0.8, 0.8, 1.0, 1.0}; + materialVolumeFractionsData[BLUE] = blueVolumeFractions; + + + mir::Point2 points[numVertices]; + { + points[0] = mir::Point2( 0.0, 3.0 ); + points[1] = mir::Point2( 1.0, 3.0 ); + points[2] = mir::Point2( 2.0, 3.0 ); + points[3] = mir::Point2( 3.0, 3.0 ); + + points[4] = mir::Point2( 0.0, 2.0 ); + points[5] = mir::Point2( 1.0, 2.0 ); + points[6] = mir::Point2( 2.0, 2.0 ); + points[7] = mir::Point2( 3.0, 2.0 ); + + points[8] = mir::Point2( 0.0, 1.0 ); + points[9] = mir::Point2( 1.0, 1.0 ); + points[10] = mir::Point2( 2.0, 1.0 ); + points[11] = mir::Point2( 3.0, 1.0 ); + + points[12] = mir::Point2( 0.0, 0.0 ); + points[13] = mir::Point2( 1.0, 0.0 ); + points[14] = mir::Point2( 2.0, 0.0 ); + points[15] = mir::Point2( 3.0, 0.0 ); + } + + // Build the mesh + mir::MIRMesh testMesh; + testMesh.InitializeMesh(evInds, evBegins, veInds, veBegins, verts, elems, numMaterials); + testMesh.constructMeshRelations(); + testMesh.constructMeshVolumeFractionMaps(materialVolumeFractionsData); + testMesh.constructVertexPositionMap(points); + + // Begin material interface reconstruction + mir::InterfaceReconstructor reconstructor(&testMesh); + mir::MIRMesh processedMesh = reconstructor.computeReconstructedInterface(); + processedMesh.print(); + // processedMesh.writeMeshToFile("/Users/sterbentz3/Desktop/markoTestMesh.vtk"); + + return 0; +} + +//-------------------------------------------------------------------------------- + From 39cb6a83724142f65e8260ffc883e691b55dbbbc Mon Sep 17 00:00:00 2001 From: Sterbentz Date: Mon, 10 Jun 2019 15:24:26 -0700 Subject: [PATCH 05/23] Debugged element combination code. Implemented dominant color tracking. Added support for null material. Implemented parent element tracking. Added post-processing to ensure generated elements will always contain an acceptable dominant material. Implemented basic vtk file writing. --- src/axom/mir/CellData.cpp | 12 + src/axom/mir/CellData.hpp | 24 +- src/axom/mir/InterfaceReconstructor.cpp | 571 +++++++++--------- src/axom/mir/InterfaceReconstructor.hpp | 34 +- src/axom/mir/MIRMesh.cpp | 186 ++++-- src/axom/mir/MIRMesh.hpp | 27 +- src/axom/mir/MIRMeshTypes.hpp | 2 +- src/axom/mir/examples/mir_tutorial_simple.cpp | 8 +- 8 files changed, 478 insertions(+), 386 deletions(-) diff --git a/src/axom/mir/CellData.cpp b/src/axom/mir/CellData.cpp index bfeee02462..df4da4d785 100644 --- a/src/axom/mir/CellData.cpp +++ b/src/axom/mir/CellData.cpp @@ -86,6 +86,18 @@ namespace mir vertexVolumeFractions[matID].push_back(cellToMerge.vertexVolumeFractions[matID][vID]); } } + + // Merge the elements' dominant materials + for (auto i = 0; i < cellToMerge.elementDominantMaterials.size(); ++i) + { + elementDominantMaterials.push_back(cellToMerge.elementDominantMaterials[i]); + } + + // Merge the elements' parent ids + for (auto i = 0; i < cellToMerge.elementParents.size(); ++i) + { + elementParents.push_back(cellToMerge.elementParents[i]); + } // Merge the total number of verts and elems in the resulting cell numVerts += cellToMerge.numVerts; diff --git a/src/axom/mir/CellData.hpp b/src/axom/mir/CellData.hpp index d8a66764f7..d91c9761ec 100644 --- a/src/axom/mir/CellData.hpp +++ b/src/axom/mir/CellData.hpp @@ -9,7 +9,7 @@ #include "axom/core.hpp" // for axom macros #include "axom/slam.hpp" -#include "MIRMesh.hpp" +#include "MIRMeshTypes.hpp" namespace numerics = axom::numerics; namespace slam = axom::slam; @@ -19,6 +19,8 @@ namespace axom namespace mir { + /// Represents an arbitrary number of cells that are within the same local coordinate system (i.e. share a set of vertices and elements). + /// Intended to be used as a helper class to hold intermediate data while processing a mesh, and to be used as input to the MIRMesh class to fully initialize it. class CellData { @@ -40,9 +42,23 @@ namespace mir std::vector veInds; std::vector veBegins; - std::vector vertexPositions; - std::vector materialsInCell; // TODO: This is not currently being used. - std::vector > vertexVolumeFractions; + std::vector vertexPositions; // Data that goes into MIRMesh's vertexPositions PointMap + std::vector > vertexVolumeFractions; // Data that goes into MIRMesh's materialVolumeFractionsVertex ScalarMap + std::vector elementDominantMaterials; // Data that goes into MIRMesh's elementDominantColors IntMap + std::vector elementParents; // Data that goes into MIRMesh's elementParentIDs IntMap + }; + + class CellTopology + { + public: + CellTopology(); + ~CellTopology(); + + public: + std::vector evInds; + std::vector evBegins; + std::vector veInds; + std::vector veBegins; }; } diff --git a/src/axom/mir/InterfaceReconstructor.cpp b/src/axom/mir/InterfaceReconstructor.cpp index 1f9f17a9fb..0160709fde 100644 --- a/src/axom/mir/InterfaceReconstructor.cpp +++ b/src/axom/mir/InterfaceReconstructor.cpp @@ -23,7 +23,7 @@ namespace mir /// Constructor InterfaceReconstructor::InterfaceReconstructor(mir::MIRMesh* _mesh) { - mesh = _mesh; + originalMesh = _mesh; } //-------------------------------------------------------------------------------- @@ -36,101 +36,111 @@ namespace mir //-------------------------------------------------------------------------------- - /// Computes the points where the element should be clipped based on the volume fractions of mat one and two. - /// TODO: This method needs to place the computed cell vertices into the intermediate mesh (pass this in as an argument) - void InterfaceReconstructor::computeClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh, - std::vector& _evInds, std::vector& _evBegins, std::vector& _veInds, std::vector& _veBegins, - std::vector& _vertexPositions, std::vector& _materialInCell, int* _numVerts, int* _numElements, std::vector >& _newVolumeFractionsAtVerts) +/// Reconstructs the material interface and returns the resulting output mesh. +mir::MIRMesh InterfaceReconstructor::computeReconstructedInterface() +{ + // Initialize the final mesh to be the same as the input mesh + mir::MIRMesh finalMesh(originalMesh); + + // For each material in the mesh, split the mesh on the current material and generate a new mesh (input into next iteration) + for (int matID = 0; matID < originalMesh->numMaterials; ++matID) { - // Find the vertices associated with each element - auto elementVertices = mesh->bdry[eID]; + // Copy the mesh to be split + mir::MIRMesh intermediateMesh(&finalMesh); - // Check if one of the two currently considered materials is not present at the vertices of the current element - // if (mesh->materialVolumeFractionsElement[matOneID][eID] != 0.0 && mesh->materialVolumeFractionsElement[matTwoID][eID] != 0.0) // TODO: Figure out how to best handle this case - // { - // Triangle Case - if (elementVertices.size() == 3) - { - computeTriangleClippingPoints(eID, matOneID, matTwoID, tempMesh, _evInds, _evBegins, _veInds, _veBegins, _vertexPositions, _materialInCell, _numVerts, _numElements, _newVolumeFractionsAtVerts); - } - // Quad Case - if (elementVertices.size() == 4) - { - computeQuadClippingPoints(eID, matOneID, matTwoID, tempMesh, _evInds, _evBegins, _veInds, _veBegins, _vertexPositions, _materialInCell, _numVerts, _numElements, _newVolumeFractionsAtVerts); // Compute how the cell should be decomposed into new cells - } - // } - // else - // { - // printf("No cuts in element %d.\n", eID); + // Update the materials upon which the split will occur + int matOne = matID; + + // Create an array to store the output of each element being split. + CellData temp_cellData[intermediateMesh.elems.size()]; + + // Process/split each element + for (int eID = 0; eID < intermediateMesh.elems.size(); ++eID) + { + // Update the materials upon which the split will occur (should be the currently dominant material and the next material in the list that hasn't yet been split on) + int currentDominantMat = intermediateMesh.elementDominantMaterials[eID]; + printf("Processing materials: %d and %d for Element %d\n", currentDominantMat, matOne, eID); + computeClippingPoints(eID, currentDominantMat, matOne, &intermediateMesh, temp_cellData[eID]); + } - // TODO: Add the current cells vertices and data back into the mesh + // Merge each of the cells into the first CellData struct + for (unsigned int eID = 1; eID < intermediateMesh.elems.size(); ++eID) + temp_cellData[0].mergeCell(temp_cellData[eID]); + + mir::VertSet combined_verts(temp_cellData[0].numVerts); + mir::ElemSet combined_elems(temp_cellData[0].numElems); - // } + // Create the final, processed mesh + mir::MIRMesh processedMesh; + processedMesh.InitializeMesh(temp_cellData[0].evInds, temp_cellData[0].evBegins, temp_cellData[0].veInds, temp_cellData[0].veBegins, combined_verts, combined_elems, intermediateMesh.numMaterials); + processedMesh.constructMeshRelations(); + processedMesh.constructMeshVolumeFractionsVertex(temp_cellData[0].vertexVolumeFractions); + processedMesh.constructVertexPositionMap(temp_cellData[0].vertexPositions.data()); + processedMesh.constructElementParentMap(temp_cellData[0].elementParents.data()); + processedMesh.constructElementDominantMaterialMap(temp_cellData[0].elementDominantMaterials); + + // Store the current mesh to be passed into the next iteration + finalMesh = processedMesh; + finalMesh.constructMeshRelations(); // TODO: Figure out why this is necessary here... + + printf("-------------Finished processing material-------------\n"); } + // TODO: Run post-processing on mesh to meld the mesh vertices that are within a certain epsilon + + return finalMesh; +} + //-------------------------------------------------------------------------------- - /// Computes the points where the triangle element should be clipped based on the volume fractions of mat one and two. - void InterfaceReconstructor::computeTriangleClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh, - std::vector& _evInds, std::vector& _evBegins, std::vector& _veInds, std::vector& _veBegins, - std::vector& _vertexPositions, std::vector& _materialInCell, int* _numVerts, int* _numElements, std::vector >& _newVolumeFractionsAtVerts) + /// Computes the points where the element should be clipped based on the volume fractions of mat one and two. + void InterfaceReconstructor::computeClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh, CellData& out_cellData) { - printf("Triangle clipping case not yet implemented.\n"); + // Find the vertices associated with each element + auto elementVertices = tempMesh->bdry[eID]; + + // Triangle Case + if (elementVertices.size() == 3) + { + computeTriangleClippingPoints(eID, matOneID, matTwoID, tempMesh, out_cellData); + } + // Quad Case + if (elementVertices.size() == 4) + { + computeQuadClippingPoints(eID, matOneID, matTwoID, tempMesh, out_cellData); + } } //-------------------------------------------------------------------------------- - /// Computes how the given cell should be decomposed into new cells baed on teh vertex volume fractions of matOne and matTwo. - void InterfaceReconstructor::computeQuadClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh, - std::vector& out_evInds, std::vector& out_evBegins, std::vector& out_veInds, std::vector& out_veBegins, - std::vector& out_vertexPositions, std::vector& out_materialInCell, int* out_numVerts, int* out_numElements, std::vector >& out_newVolumeFractionsAtVerts) + void InterfaceReconstructor::computeQuadClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh, CellData& out_cellData) { - printf("Processing element %d: ", eID); + printf(" Processing element %d: ", eID); - /**************************************************************** - * DETERMINE THE CLIPPING CASE - ****************************************************************/ - // Get the vertices of the current element to clip + // Determine the clipping case auto elementVertices = tempMesh->bdry[eID]; - // Determine which vertices correspond to upper left, lower left, lower right, and upper right vertices of the quad. int upperLeftVertex = elementVertices[0]; int lowerLeftVertex = elementVertices[1]; int lowerRightVertex = elementVertices[2]; int upperRightVertex = elementVertices[3]; - // Determine the dominant color at each vertex - int upperLeftColor = tempMesh->materialVolumeFractionsVertex[matOneID][upperLeftVertex] > tempMesh->materialVolumeFractionsVertex[matTwoID][upperLeftVertex] ? matOneID : matTwoID; - int lowerLeftColor = tempMesh->materialVolumeFractionsVertex[matOneID][lowerLeftVertex] > tempMesh->materialVolumeFractionsVertex[matTwoID][lowerLeftVertex] ? matOneID : matTwoID; - int lowerRightColor = tempMesh->materialVolumeFractionsVertex[matOneID][lowerRightVertex] > tempMesh->materialVolumeFractionsVertex[matTwoID][lowerRightVertex] ? matOneID : matTwoID; - int upperRightColor = tempMesh->materialVolumeFractionsVertex[matOneID][upperRightVertex] > tempMesh->materialVolumeFractionsVertex[matTwoID][upperRightVertex] ? matOneID : matTwoID; - - // Create the index into the quad clipping lookup table using the dominant colors at each vertex - unsigned int caseIndex = 0; - if (upperLeftColor == matOneID) caseIndex |= 8; - if (lowerLeftColor == matOneID) caseIndex |= 4; - if (lowerRightColor == matOneID) caseIndex |= 2; - if (upperRightColor == matOneID) caseIndex |= 1; - + unsigned int caseIndex = determineQuadClippingCase(eID, tempMesh, matOneID, matTwoID, upperLeftVertex, lowerLeftVertex, lowerRightVertex, upperRightVertex); printf("caseIndex: %d\n", caseIndex); - /**************************************************************** - * GENERATE NEW ELEMENTS - ****************************************************************/ - // Cell information indices - std::map > newElements; // hashmap of the new elements' vertices | Note: maps are ordered sets - std::map > newVertices; // hashmap of the new vertices' elements | Note: maps are ordered sets - std::map newPoints; // hashmap of the new vertices' positions | Note: maps are ordered sets + // Generate new elements + std::map > newElements; // hashmap of the new elements' vertices | Note: maps are ordered sets + std::map > newVertices; // hashmap of the new vertices' elements | Note: maps are ordered sets int verticesPresent[8]; // Array of flags denoting whether the vertex is present in the current case or not axom::float64 verticesClippingTValue[8]; // Array of t values that denote the percent value of where the edge should be clipped - std::map > vertexVolumeFractionsMap; // index: map[vID][matID] = vertexVolumeFractionValues[numMaterials] | Note: maps are ordered sets - - int currentElementIndex = 0; // the next available element index // Create the new polygons based on the clipping case + int currentElementIndex = 0; // the next available element index int i = 0; int numVertices = quadClipTable[caseIndex][i]; - while (numVertices != -1) // for each new element in the current clipping case + + // for each new element in the current clipping case + while (numVertices != -1) { // for each vertex of the new element for (int j = 0; j < numVertices; ++j) @@ -143,10 +153,10 @@ namespace mir newVertices[vID].push_back(currentElementIndex); verticesPresent[vID] = 1; - // Find t using "bilinear" interpolation method for any vertex that is not one of the original 4 vertices + // Find t using linear interpolation for any vertex that is not one of the original 4 vertices if(vID == 4) { - verticesClippingTValue[vID] = computeClippingPointOnEdge(upperLeftVertex, lowerLeftVertex, matOneID, matTwoID, tempMesh); // these might be the wrong vertex indices + verticesClippingTValue[vID] = computeClippingPointOnEdge(upperLeftVertex, lowerLeftVertex, matOneID, matTwoID, tempMesh); } else if(vID == 5) { @@ -174,111 +184,60 @@ namespace mir * CALCULATE THE NEW CELLS' DATA ****************************************************************/ // Calculate the total number of elements and vertices that were generated from splitting the current element - out_numElements[0] = (int) newElements.size(); - out_numVerts[0] = (int) newVertices.size(); + out_cellData.numElems = (int) newElements.size(); + out_cellData.numVerts = (int) newVertices.size(); - // Generate the topology of the new elements (evInds, evBegins, etc) - // Store the evInds and evBegins data in the output vectors - int currentEVBeginIndex = 0; + generateTopologyDataFromQuad(newElements, newVertices, out_cellData); + generateVertexPositionsFromQuad(newVertices, tempMesh, verticesClippingTValue, upperLeftVertex, lowerLeftVertex, lowerRightVertex, upperRightVertex, out_cellData); + generateVertexVolumeFractionsFromQuad(newVertices, tempMesh, verticesClippingTValue, upperLeftVertex, lowerLeftVertex, lowerRightVertex, upperRightVertex, out_cellData); + + // Determine and store the dominant material of this element for (auto itr = newElements.begin(); itr != newElements.end(); itr++) { - // Push the start index of the next element - out_evBegins.push_back(currentEVBeginIndex); - - // Push the next element's vertices - for (unsigned int vIndex = 0; vIndex < itr->second.size(); ++vIndex) + int currentDominantMat = NULL_MAT; + for (int it = 0; it < itr->second.size(); ++it) { - out_evInds.push_back(itr->second[vIndex]); - ++currentEVBeginIndex; - } - } - - // Push the index that occurs after the last vertex - out_evBegins.push_back(currentEVBeginIndex); + // int temp_vID = newElements[currentElementIndex][it]; + int temp_vID = itr->second[it]; - // Store the veInds and veBegins data in the output vectors - int currentVEBeginIndex = 0; - for (auto itr = newVertices.begin(); itr != newVertices.end(); itr++) - { - // Push the start index of the vertex's elements - out_veBegins.push_back(currentVEBeginIndex); + // Find the vertex that is one of the four original vertices of the quad element + if (temp_vID == 0 || temp_vID == 1 || temp_vID == 2 || temp_vID == 3) + { + axom::float64 matOneVolumeFraction = -1.0, matTwoVolumeFraction = -1.0; + if (matOneID != NULL_MAT) + matOneVolumeFraction = out_cellData.vertexVolumeFractions[matOneID][temp_vID]; + if (matTwoID != NULL_MAT) + matTwoVolumeFraction = out_cellData.vertexVolumeFractions[matTwoID][temp_vID]; - // Push the next vertex's elements - for (unsigned int eIndex = 0; eIndex < itr->second.size(); eIndex++) - { - out_veInds.push_back(itr->second[eIndex]); - ++currentVEBeginIndex; + currentDominantMat = (matOneVolumeFraction > matTwoVolumeFraction) ? matOneID : matTwoID; + } } + out_cellData.elementDominantMaterials.push_back(currentDominantMat); } - - // Push the index that occurs after the last element - out_veBegins.push_back(currentVEBeginIndex); - // Find the position coordinates of the vertices and then store the positions of the vertices in the return vector - for (auto itr = newVertices.begin(); itr != newVertices.end(); itr++) - { - int vID = itr->first; - if (verticesPresent[vID] == 1) - { - if (vID == 0) - newPoints[vID] = tempMesh->vertexPositions[upperLeftVertex]; - if (vID == 1) - newPoints[vID] = tempMesh->vertexPositions[lowerLeftVertex]; - if (vID == 2) - newPoints[vID] = tempMesh->vertexPositions[lowerRightVertex]; - if (vID == 3) - newPoints[vID] = tempMesh->vertexPositions[upperRightVertex]; - if (vID == 4) - newPoints[vID] = interpolateVertexPosition(tempMesh->vertexPositions[upperLeftVertex], tempMesh->vertexPositions[lowerLeftVertex], verticesClippingTValue[vID]); - if (vID == 5) - newPoints[vID] = interpolateVertexPosition(tempMesh->vertexPositions[lowerLeftVertex], tempMesh->vertexPositions[lowerRightVertex], verticesClippingTValue[vID]); - if (vID == 6) - newPoints[vID] = interpolateVertexPosition(tempMesh->vertexPositions[lowerRightVertex], tempMesh->vertexPositions[upperRightVertex], verticesClippingTValue[vID]); - if (vID == 7) - newPoints[vID] = interpolateVertexPosition(tempMesh->vertexPositions[upperRightVertex], tempMesh->vertexPositions[upperLeftVertex], verticesClippingTValue[vID]); - } - } - - // Store the positions of the vertices in the return vector - for (auto itr = newPoints.begin(); itr != newPoints.end(); itr++) + // Determine and store the parent of this element + out_cellData.elementParents.resize(newElements.size()); + for (auto itr = newElements.begin(); itr != newElements.end(); itr++) { - out_vertexPositions.push_back(itr->second); + out_cellData.elementParents[itr->first] = tempMesh->elementParentIDs[eID]; } - // Calculate the vertex fractions at each vertex (use t value!) - // Make sure the output volume fractions containers are the proper size - out_newVolumeFractionsAtVerts.resize(tempMesh->numMaterials); - - for (auto itr = newVertices.begin(); itr != newVertices.end(); itr++) + // Check that each element is dominated by a material that is actually present in the original parent cell + for (auto itr = newElements.begin(); itr != newElements.end(); itr++) { - int vID = itr->first; - // Ensure that the current vertex being considered is actually present in the current clipping case - if (verticesPresent[vID] == 1) - { - // vertexVolumeFractionsMap[vID].resize(tempMesh->numMaterials); + int parentElementID = out_cellData.elementParents[itr->first]; + int currentDominantMaterial = out_cellData.elementDominantMaterials[itr->first]; - for (int matID = 0; matID < tempMesh->numMaterials; ++matID) - { - if (vID == 0) - out_newVolumeFractionsAtVerts[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][upperLeftVertex]); - if (vID == 1) - out_newVolumeFractionsAtVerts[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][lowerLeftVertex]); - if (vID == 2) - out_newVolumeFractionsAtVerts[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][lowerRightVertex]); - if (vID == 3) - out_newVolumeFractionsAtVerts[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][upperRightVertex]); - if (vID == 4) - out_newVolumeFractionsAtVerts[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][upperLeftVertex], tempMesh->materialVolumeFractionsVertex[matID][lowerLeftVertex], verticesClippingTValue[vID])); - if (vID == 5) - out_newVolumeFractionsAtVerts[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][lowerLeftVertex], tempMesh->materialVolumeFractionsVertex[matID][lowerRightVertex], verticesClippingTValue[vID])); - if (vID == 6) - out_newVolumeFractionsAtVerts[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][lowerRightVertex], tempMesh->materialVolumeFractionsVertex[matID][upperRightVertex], verticesClippingTValue[vID])); - if (vID == 7) - out_newVolumeFractionsAtVerts[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][upperRightVertex], tempMesh->materialVolumeFractionsVertex[matID][upperLeftVertex], verticesClippingTValue[vID])); - } + if (originalMesh->materialVolumeFractionsElement[currentDominantMaterial][parentElementID] == 0) + { + // This material is not present in the original element from which the current element comes from + if (currentDominantMaterial == matOneID) + out_cellData.elementDominantMaterials[itr->first] = matTwoID; + else + out_cellData.elementDominantMaterials[itr->first] = matOneID; } } - + // Modify the evIndex values to account for the fact that perhaps not all 8 possible vertices are present int evIndexSubtract[8]; for (int i = 0; i < 8; ++i) @@ -291,87 +250,41 @@ namespace mir for (int i = 1; i < 8; ++i) evIndexSubtract[i] += evIndexSubtract[i - 1]; - for (unsigned int i = 0; i < out_evInds.size(); ++i) + for (unsigned int i = 0; i < out_cellData.evInds.size(); ++i) { - out_evInds[i] -= evIndexSubtract[ out_evInds[i] ]; + out_cellData.evInds[i] -= evIndexSubtract[ out_cellData.evInds[i] ]; } + } +//-------------------------------------------------------------------------------- +/// Finds the bit map representing the clipping case for a quad. +unsigned int InterfaceReconstructor::determineQuadClippingCase(const int eID, mir::MIRMesh* tempMesh, const int matOneID, const int matTwoID, const int upperLeftVertex, const int lowerLeftVertex, const int lowerRightVertex, const int upperRightVertex) +{ + // Determine the dominant color at each vertex + int upperLeftColor = matOneID, lowerLeftColor = matOneID, lowerRightColor = matOneID, upperRightColor = matOneID; - /**************************************************************** - * DEBUG PRINTING - ****************************************************************/ + if (matOneID == NULL_MAT) + upperLeftColor = lowerLeftColor = lowerRightColor = upperRightColor = matTwoID; + if (matTwoID == NULL_MAT) + upperLeftColor = lowerLeftColor = lowerRightColor = upperRightColor = matOneID; + if (matOneID != NULL_MAT && matTwoID != NULL_MAT) { - // TESTING: Print out the values by which the evIndex values need to be substracted - // in order to ensure there are no gaps in the vertex ids in the resulting mesh. - // printf(" evIndexSubtract: { "); - // for (int i = 0; i < 8; i++) - // { - // printf("%d ", evIndexSubtract[i]); - // } - // printf("}\n"); - - // // TESTING: Print out the basic cell information - // printf(" out_numElements: %d\n", out_numElements[0]); - // printf(" out_numVerts: %d\n", out_numVerts[0]); - - // // TESTING: Print out the mesh topology information - // printf(" out_evInds: {"); - // for (int i = 0; i < out_evInds.size(); i++) - // { - // printf("%d ", out_evInds[i]); - // } - // printf("}\n"); - - // printf(" out_evBegins: {"); - // for (int i = 0; i < out_evBegins.size(); i++) - // { - // printf("%d ", out_evBegins[i]); - // } - // printf("}\n"); - - // printf(" out_veInds: {"); - // for (int i = 0; i < out_veInds.size(); i++) - // { - // printf("%d ", out_veInds[i]); - // } - // printf("}\n"); - - // printf(" out_veBegins: {"); - // for (int i = 0; i < out_veBegins.size(); i++) - // { - // printf("%d ", out_veBegins[i]); - // } - // printf("}\n"); - - // // TESTING: Print out the positions of the vertices - // for (auto itr = newPoints.begin(); itr != newPoints.end(); itr++) - // { - // printf(" Vertex %d: (%f, %f)\n", itr->first, itr->second.m_x, itr->second.m_y); - // } - - // TESTING: Print out the volume fractions at the vertices from the map - // for (auto itr = vertexVolumeFractionsMap.begin(); itr != vertexVolumeFractionsMap.end(); itr++) - // { - // int vID = itr->first; - // printf(" Vertex %d Volume Fractions:\n", vID); - // for (int matID = 0; matID < tempMesh->numMaterials; ++matID) - // { - // printf(" Mat %d: %f\n", matID, vertexVolumeFractionsMap[vID][matID]); - // } - // } - - // TESTING: Print out the volume fractions at the vertices from the vector of materials containing a vector of vertex volume fraction values - // for (int matID = 0; matID < out_newVolumeFractionsAtVerts.size(); ++matID) - // { - // printf(" Material %d:\n", matID); - // for (int vID = 0; vID < out_newVolumeFractionsAtVerts[matID].size(); ++vID) - // { - // printf(" Vertex %d: %f\n", vID, out_newVolumeFractionsAtVerts[matID][vID]); - // } - // } + upperLeftColor = tempMesh->materialVolumeFractionsVertex[matOneID][upperLeftVertex] > tempMesh->materialVolumeFractionsVertex[matTwoID][upperLeftVertex] ? matOneID : matTwoID; + lowerLeftColor = tempMesh->materialVolumeFractionsVertex[matOneID][lowerLeftVertex] > tempMesh->materialVolumeFractionsVertex[matTwoID][lowerLeftVertex] ? matOneID : matTwoID; + lowerRightColor = tempMesh->materialVolumeFractionsVertex[matOneID][lowerRightVertex] > tempMesh->materialVolumeFractionsVertex[matTwoID][lowerRightVertex] ? matOneID : matTwoID; + upperRightColor = tempMesh->materialVolumeFractionsVertex[matOneID][upperRightVertex] > tempMesh->materialVolumeFractionsVertex[matTwoID][upperRightVertex] ? matOneID : matTwoID; } - } + + // Create the index into the quad clipping lookup table using the dominant colors at each vertex + unsigned int caseIndex = 0; + if (upperLeftColor == matOneID) caseIndex |= 8; + if (lowerLeftColor == matOneID) caseIndex |= 4; + if (lowerRightColor == matOneID) caseIndex |= 2; + if (upperRightColor == matOneID) caseIndex |= 1; + + return caseIndex; +} //-------------------------------------------------------------------------------- @@ -400,87 +313,165 @@ axom::float64 InterfaceReconstructor::computeClippingPointOnEdge(const int verte { axom::float64 ret = 0.0; - axom::float64 numerator = tempMesh->materialVolumeFractionsVertex[matTwoID][vertexOneID] - mesh->materialVolumeFractionsVertex[matOneID][vertexOneID]; - axom::float64 denominator = -mesh->materialVolumeFractionsVertex[matOneID][vertexOneID] - + mesh->materialVolumeFractionsVertex[matOneID][vertexTwoID] - + mesh->materialVolumeFractionsVertex[matTwoID][vertexOneID] - - mesh->materialVolumeFractionsVertex[matTwoID][vertexTwoID]; + axom::float64 vfMatOneVertexOne = tempMesh->materialVolumeFractionsVertex[matOneID][vertexOneID]; // Changed these all from originalMesh->materialVolumeFractionsVertex[][] to tempMesh->... + axom::float64 vfMatTwoVertexOne = tempMesh->materialVolumeFractionsVertex[matTwoID][vertexOneID]; + axom::float64 vfMatOneVertexTwo = tempMesh->materialVolumeFractionsVertex[matOneID][vertexTwoID]; + axom::float64 vfMatTwoVertexTwo = tempMesh->materialVolumeFractionsVertex[matTwoID][vertexTwoID]; + + if (matOneID == NULL_MAT) + { + vfMatOneVertexOne = 0.0; + vfMatOneVertexTwo = 0.0; + } + + if (matTwoID == NULL_MAT) + { + vfMatTwoVertexOne = 0.0; + vfMatTwoVertexTwo = 0.0; + } + + axom::float64 numerator = vfMatTwoVertexOne - vfMatOneVertexOne; + axom::float64 denominator = -vfMatOneVertexOne + vfMatOneVertexTwo + vfMatTwoVertexOne - vfMatTwoVertexTwo; + + // axom::float64 numerator = originalMesh->materialVolumeFractionsVertex[matTwoID][vertexOneID] - originalMesh->materialVolumeFractionsVertex[matOneID][vertexOneID]; + // axom::float64 denominator = -originalMesh->materialVolumeFractionsVertex[matOneID][vertexOneID] + // + originalMesh->materialVolumeFractionsVertex[matOneID][vertexTwoID] + // + originalMesh->materialVolumeFractionsVertex[matTwoID][vertexOneID] + // - originalMesh->materialVolumeFractionsVertex[matTwoID][vertexTwoID]; if (denominator != 0.0) ret = numerator / denominator; + if (ret > 1.0 || ret < 0.0) + { + // This shouldn't happen... + printf("OUT OF BOUNDS T VALUE: %f\n", ret); + + // Clamp the t value + ret = fmin(1.0, ret); + ret = fmax(0.0, ret); + } + return ret; } //-------------------------------------------------------------------------------- -mir::MIRMesh InterfaceReconstructor::computeReconstructedInterface() +/// Generate the topology of the new elements (evInds, evBegins, veInds, veBegins) resulting from splitting a quad. +void InterfaceReconstructor::generateTopologyDataFromQuad(std::map > newElements, std::map > newVertices, CellData& out_cellData) { - // Initialize the final mesh to be the same as the input mesh - mir::MIRMesh finalMesh(mesh); // TODO: This might just copy the reference to the original mesh, and not copy it. Could cause bugs when it gets overwritten. + // Store the evInds and evBegins data in the output vectors + int currentEVBeginIndex = 0; + for (auto itr = newElements.begin(); itr != newElements.end(); itr++) + { + // Push the start index of the next element + out_cellData.evBegins.push_back(currentEVBeginIndex); - // For each material in the mesh, split the mesh on the current material and generate a new mesh (input into next iteration) - for (int matID = 0; matID < mesh->numMaterials - 1; ++matID) - { - // Copy the mesh to be split - mir::MIRMesh intermediateMesh(&finalMesh); + // Push the next element's vertices + for (unsigned int vIndex = 0; vIndex < itr->second.size(); ++vIndex) + { + out_cellData.evInds.push_back(itr->second[vIndex]); + ++currentEVBeginIndex; + } + } - // Update the materials upon which the split will occur - int matOne = matID; - int matTwo = matID + 1; + // Push the index that occurs after the last vertex + out_cellData.evBegins.push_back(currentEVBeginIndex); + + // Store the veInds and veBegins data in the output vectors + int currentVEBeginIndex = 0; + for (auto itr = newVertices.begin(); itr != newVertices.end(); itr++) + { + // Push the start index of the vertex's elements + out_cellData.veBegins.push_back(currentVEBeginIndex); - // Process/split each element - std::vector temp_evInds[intermediateMesh.elems.size()]; // Store the vertex indices adjacent to each element for each cell - std::vector temp_evBegins[intermediateMesh.elems.size()]; // Store the starting indices into temp_evBewgins for each element-vertex for each cell - std::vector temp_veInds[intermediateMesh.elems.size()]; // Store the element indices adjacent to each vertex for each cell - std::vector temp_veBegins[intermediateMesh.elems.size()]; // Store the starting indices into temp_veInds for each vertex-element for each cell + // Push the next vertex's elements + for (unsigned int eIndex = 0; eIndex < itr->second.size(); eIndex++) + { + out_cellData.veInds.push_back(itr->second[eIndex]); + ++currentVEBeginIndex; + } + } + + // Push the index that occurs after the last element + out_cellData.veBegins.push_back(currentVEBeginIndex); +} - std::vector temp_vertexPositions[intermediateMesh.elems.size()]; // Store the positions of the vertices generated for each cell - std::vector materialInCell[intermediateMesh.elems.size()]; // Note: after splitting, the cells should all be clean and thus will have a vf of 1.0 or 0.0 for all materials +//-------------------------------------------------------------------------------- - int temp_numVerts[intermediateMesh.elems.size()]; // Store the number of vertices generated for each cell - int temp_numElements[intermediateMesh.elems.size()]; // Store the number of elements generated for each cell +/// Generate the vertex position data for the new elements resulting from spltting a quad. +void InterfaceReconstructor::generateVertexPositionsFromQuad(std::map > newVertices, mir::MIRMesh* tempMesh, axom::float64* verticesClippingTValue, int upperLeftVertex, int lowerLeftVertex, int lowerRightVertex, int upperRightVertex, CellData& out_cellData) +{ + std::map newPoints; // hashmap of the new vertices' positions | Note: maps are ordered sets - std::vector > temp_volumeFractionsVertex[intermediateMesh.elems.size()]; + // Find the position coordinates of the vertices and then store the positions of the vertices in the return vector + for (auto itr = newVertices.begin(); itr != newVertices.end(); itr++) + { + int vID = itr->first; + if (vID == 0) + newPoints[vID] = tempMesh->vertexPositions[upperLeftVertex]; + if (vID == 1) + newPoints[vID] = tempMesh->vertexPositions[lowerLeftVertex]; + if (vID == 2) + newPoints[vID] = tempMesh->vertexPositions[lowerRightVertex]; + if (vID == 3) + newPoints[vID] = tempMesh->vertexPositions[upperRightVertex]; + if (vID == 4) + newPoints[vID] = interpolateVertexPosition(tempMesh->vertexPositions[upperLeftVertex], tempMesh->vertexPositions[lowerLeftVertex], verticesClippingTValue[vID]); + if (vID == 5) + newPoints[vID] = interpolateVertexPosition(tempMesh->vertexPositions[lowerLeftVertex], tempMesh->vertexPositions[lowerRightVertex], verticesClippingTValue[vID]); + if (vID == 6) + newPoints[vID] = interpolateVertexPosition(tempMesh->vertexPositions[lowerRightVertex], tempMesh->vertexPositions[upperRightVertex], verticesClippingTValue[vID]); + if (vID == 7) + newPoints[vID] = interpolateVertexPosition(tempMesh->vertexPositions[upperRightVertex], tempMesh->vertexPositions[upperLeftVertex], verticesClippingTValue[vID]); + } + + // Store the positions of the vertices in the return vector + for (auto itr = newPoints.begin(); itr != newPoints.end(); itr++) + { + out_cellData.vertexPositions.push_back(itr->second); + } +} - for (int eID = 0; eID < intermediateMesh.elems.size(); ++eID) - { - computeClippingPoints(eID, matOne, matTwo, &intermediateMesh, - temp_evInds[eID], temp_evBegins[eID], temp_veInds[eID], temp_veBegins[eID], temp_vertexPositions[eID], - materialInCell[eID], &(temp_numVerts[eID]), &(temp_numElements[eID]), temp_volumeFractionsVertex[eID]); - } +//-------------------------------------------------------------------------------- - // Copy the generated cell information into CellData structures - std::vector cellSplitInfo; - for (int eID = 0; eID < intermediateMesh.elems.size(); ++eID) +/// Generate the vertex volume fraction data for the new vertices resulting from splitting a quad. +void InterfaceReconstructor::generateVertexVolumeFractionsFromQuad(std::map > newVertices, mir::MIRMesh* tempMesh, axom::float64* verticesClippingTValue, int upperLeftVertex, int lowerLeftVertex, int lowerRightVertex, int upperRightVertex, CellData& out_cellData) +{ + // Calculate the vertex fractions at each vertex (use t value!) + // Make sure the output volume fractions containers are the proper size + out_cellData.vertexVolumeFractions.resize(tempMesh->numMaterials); + + for (auto itr = newVertices.begin(); itr != newVertices.end(); itr++) { - CellData nextCellSplitInfo(temp_numVerts[eID], temp_numElements[eID], temp_evInds[eID], temp_evBegins[eID], temp_veInds[eID], temp_veBegins[eID], temp_vertexPositions[eID], temp_volumeFractionsVertex[eID]); - cellSplitInfo.push_back(nextCellSplitInfo); + int vID = itr->first; + for (int matID = 0; matID < tempMesh->numMaterials; ++matID) + { + if (vID == 0) + out_cellData.vertexVolumeFractions[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][upperLeftVertex]); + if (vID == 1) + out_cellData.vertexVolumeFractions[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][lowerLeftVertex]); + if (vID == 2) + out_cellData.vertexVolumeFractions[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][lowerRightVertex]); + if (vID == 3) + out_cellData.vertexVolumeFractions[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][upperRightVertex]); + if (vID == 4) + out_cellData.vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][upperLeftVertex], tempMesh->materialVolumeFractionsVertex[matID][lowerLeftVertex], verticesClippingTValue[vID])); + if (vID == 5) + out_cellData.vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][lowerLeftVertex], tempMesh->materialVolumeFractionsVertex[matID][lowerRightVertex], verticesClippingTValue[vID])); + if (vID == 6) + out_cellData.vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][lowerRightVertex], tempMesh->materialVolumeFractionsVertex[matID][upperRightVertex], verticesClippingTValue[vID])); + if (vID == 7) + out_cellData.vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][upperRightVertex], tempMesh->materialVolumeFractionsVertex[matID][upperLeftVertex], verticesClippingTValue[vID])); + } } +} - // Merge each of the cells into the first CellData struct - for (unsigned int eID = 1; eID < cellSplitInfo.size(); ++eID) - cellSplitInfo[0].mergeCell(cellSplitInfo[eID]); - - mir::VertSet combined_verts(cellSplitInfo[0].numVerts); - mir::ElemSet combined_elems(cellSplitInfo[0].numElems); - - // TODO (later): Calculate the element volume fractions (note, they should all be either 0 or 1, since after the splits every cell should be clean) - - // Create the final, processed mesh - mir::MIRMesh processedMesh; - processedMesh.InitializeMesh(cellSplitInfo[0].evInds, cellSplitInfo[0].evBegins, cellSplitInfo[0].veInds, cellSplitInfo[0].veBegins, combined_verts, combined_elems, intermediateMesh.numMaterials); - processedMesh.constructMeshRelations(); - processedMesh.constructMeshVolumeFractionsVertex(cellSplitInfo[0].vertexVolumeFractions); - processedMesh.constructVertexPositionMap(cellSplitInfo[0].vertexPositions.data()); - - // Store the current mesh to be passed into the next iteration - finalMesh = processedMesh; - } - - // TODO: Run post-processing on mesh to meld the mesh vertices that are within a certain epsilon +//-------------------------------------------------------------------------------- - return finalMesh; +void InterfaceReconstructor::computeTriangleClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh, CellData& out_cellData) +{ + printf("Triangle clipping case not yet implemented.\n"); } //-------------------------------------------------------------------------------- diff --git a/src/axom/mir/InterfaceReconstructor.hpp b/src/axom/mir/InterfaceReconstructor.hpp index ddb37b205a..7f620f4405 100644 --- a/src/axom/mir/InterfaceReconstructor.hpp +++ b/src/axom/mir/InterfaceReconstructor.hpp @@ -30,34 +30,30 @@ namespace mir mir::MIRMesh computeReconstructedInterface(); - - // std::vector computeVolumeFractionAverages(mir::MIRMesh* tempMesh); - private: - mir::MIRMesh* mesh; - - public: - std::vector materialVolumeFractionsVertex; // volume fractions for each material for each vertex + mir::MIRMesh* originalMesh; private: - void computeClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh, - std::vector& _evInds, std::vector& _evBegins, std::vector& _veInds, std::vector& _veBegins, - std::vector& _vertexPositions, std::vector& _materialInCell, int* _numVerts, int* _numElements, std::vector >& _newVolumeFractionsAtVerts); - - void computeTriangleClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh, - std::vector& _evInds, std::vector& _evBegins, std::vector& _veInds, std::vector& _veBegins, - std::vector& _vertexPositions, std::vector& _materialInCell, int* _numVerts, int* _numElements, std::vector >& _newVolumeFractionsAtVerts); - void computeQuadClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh, - std::vector& out_evInds, std::vector& out_evBegins, std::vector& out_veInds, std::vector& out_veBegins, - std::vector& out_vertexPositions, std::vector& out_materialInCell, int* out_numVerts, int* out_numElements, std::vector >& out_newVolumeFractionsAtVerts); + // general clipping function + void computeQuadClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh, CellData& out_cellData); + + // triangle clipping function + void computeTriangleClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh, CellData& out_cellData); + // quad clipping functions + void computeClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh, CellData& out_cellData); + + // quad clipping interpolation functions mir::Point2 interpolateVertexPosition(mir::Point2 vertexOnePos, mir::Point2 vertexTwoPos, float t); axom::float64 lerpFloat(axom::float64 f0, axom::float64 f1, axom::float64 t); axom::float64 computeClippingPointOnEdge(const int vertexOneID, const int vertexTwoID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh); - void mergeCellSplitData(); - + // quad clipping points helper functions + unsigned int determineQuadClippingCase(const int eID, mir::MIRMesh* tempMesh, const int matOneID, const int matTwoID, const int upperLeftVertex, const int lowerLeftVertex, const int lowerRightVertex, const int upperRightVertex); + void generateTopologyDataFromQuad(std::map > newElements, std::map > newVertices, CellData& out_cellData); + void generateVertexPositionsFromQuad(std::map > newVertices, mir::MIRMesh* tempMesh, axom::float64* verticesClippingTValue, int upperLeftVertex, int lowerLeftVertex, int lowerRightVertex, int upperRightVertex, CellData& out_cellData); + void generateVertexVolumeFractionsFromQuad(std::map > newVertices, mir::MIRMesh* tempMesh, axom::float64* verticesClippingTValue, int upperLeftVertex, int lowerLeftVertex, int lowerRightVertex, int upperRightVertex, CellData& out_cellData); }; } } diff --git a/src/axom/mir/MIRMesh.cpp b/src/axom/mir/MIRMesh.cpp index 89da4fc522..11dcc49e84 100644 --- a/src/axom/mir/MIRMesh.cpp +++ b/src/axom/mir/MIRMesh.cpp @@ -16,10 +16,10 @@ namespace mir MIRMesh::MIRMesh(MIRMesh* _mesh) // copy constructor { - evInds = _mesh->evInds; - evBegins = _mesh->evBegins; - veInds = _mesh->veInds; - veBegins = _mesh->veBegins; + data.evInds = _mesh->data.evInds; + data.evBegins = _mesh->data.evBegins; + data.veInds = _mesh->data.veInds; + data.veBegins = _mesh->data.veBegins; verts = _mesh->verts; elems = _mesh->elems; bdry = _mesh->bdry; @@ -27,6 +27,8 @@ namespace mir vertexPositions = _mesh->vertexPositions; materialVolumeFractionsElement = _mesh->materialVolumeFractionsElement; materialVolumeFractionsVertex = _mesh->materialVolumeFractionsVertex; + elementParentIDs = _mesh->elementParentIDs; + elementDominantMaterials = _mesh->elementDominantMaterials; numMaterials = _mesh->numMaterials; } @@ -38,10 +40,10 @@ namespace mir /// Initializes a mesh with the given topology. void MIRMesh::InitializeMesh(std::vector _evInds, std::vector _evBegins, std::vector _veInds, std::vector _veBegins, VertSet _verts, ElemSet _elems, int _numMaterials) { - evInds = _evInds; - evBegins = _evBegins; - veInds = _veInds; - veBegins = _veBegins; + data.evInds = _evInds; + data.evBegins = _evBegins; + data.veInds = _veInds; + data.veBegins = _veBegins; verts = _verts; elems = _elems; numMaterials = _numMaterials; @@ -64,10 +66,10 @@ namespace mir .toSet( &verts ) .begins( RelationBuilder::BeginsSetBuilder() .size( elems.size() ) - .data( evBegins.data() ) ) + .data( data.evBegins.data() ) ) .indices ( RelationBuilder::IndicesSetBuilder() - .size( evInds.size() ) - .data( evInds.data() ) ); + .size( data.evInds.size() ) + .data( data.evInds.data() ) ); } @@ -80,10 +82,10 @@ namespace mir .toSet( &elems ) .begins( RelationBuilder::BeginsSetBuilder() .size( verts.size() ) - .data( veBegins.data() ) ) + .data( data.veBegins.data() ) ) .indices( RelationBuilder::IndicesSetBuilder() - .size( veInds.size() ) - .data( veInds.data() ) ); + .size( data.veInds.size() ) + .data( data.veInds.data() ) ); // _quadmesh_example_construct_cobdry_relation_end } @@ -175,6 +177,36 @@ void MIRMesh::constructMeshVolumeFractionsVertex(std::vector dominantMaterials) + { + // Initialize the map for the elements' dominant colors + elementDominantMaterials = IntMap( &elems ); + + // Copy the dat for the elements + for (int eID = 0; eID < elems.size(); ++eID) + elementDominantMaterials[eID] = dominantMaterials[eID]; + + SLIC_ASSERT_MSG( elementDominantMaterials.isValid(), "Element dominant materials map is not valid."); + } + //-------------------------------------------------------------------------------- /// Prints out the map values for each element @@ -203,56 +235,91 @@ void MIRMesh::constructMeshVolumeFractionsVertex(std::vector // for definition of M_PI, exp() @@ -21,12 +22,14 @@ namespace numerics = axom::numerics; namespace slam = axom::slam; - //-------------------------------------------------------------------------------- namespace axom { namespace mir { + + #define NULL_MAT -1 + struct EdgeClipInfo { int vertexOne; @@ -54,26 +57,21 @@ namespace mir void constructMeshVolumeFractionMaps(std::vector materialVolumeFractionsData); /// Constructs the volume fraction maps on the vertices void constructMeshVolumeFractionsVertex(std::vector > vertexVF); /// Constructs the vertex volume fraction map void constructVertexPositionMap(Point2* data); /// Constucts the positions map on the vertices + void constructElementParentMap(int* cellParents); /// Constructs the map of cells to their original cell parent + void constructElementDominantMaterialMap(std::vector dominantMaterials); /// Constructs the map of elements to their dominant materials void printElementScalarMap(ScalarMap& elements, std::string prefix); /// Prints out the map values for each element void printVertexScalarMap(ScalarMap& vertices, std::string prefix); /// Prints out the map values for each vertex void print(); // Print out a variety of useful information about the mesh - void readMeshFromFile(); /// Reads in and constructs a mesh from a file + void readMeshFromFile(std::string filename); /// Reads in and constructs a mesh from a file void writeMeshToFile(std::string filename); /// Writes out the current mesh to a file void attachVertexMap(); /// Adds a map of data to the mesh /**************************************************************** * VARIABLES ****************************************************************/ - public: - // support data for mesh connectivity - std::vector evInds; - std::vector evBegins; - std::vector veInds; - std::vector veBegins; - public: // Mesh Set Definitions VertSet verts; // the set of vertices in the mesh @@ -82,16 +80,23 @@ namespace mir public: // Mesh Relation Definitions ElemToVertRelation bdry; // Boundary relation from elements to vertices - VertToElemRelation cobdry; // Coboundary relation from vertices to elements + VertToElemRelation cobdry; // Coboundary relation from vertices to elements public: // Mesh Map Definitions - PointMap vertexPositions; // vertex position + PointMap vertexPositions; // vertex position for each vertex std::vector materialVolumeFractionsElement; // the volume fractions of each material for each element std::vector materialVolumeFractionsVertex; // the volume fractions of each material for each vertex public: int numMaterials; + + public: + IntMap elementParentIDs; // the ID of the parent element from the original mesh + IntMap elementDominantMaterials; // the dominant material of the cell (a processed mesh should have only one material per cell) + + public: + CellData data; // Contains mesh connectivity data, volume fraction data, vertex position data }; //-------------------------------------------------------------------------------- diff --git a/src/axom/mir/MIRMeshTypes.hpp b/src/axom/mir/MIRMeshTypes.hpp index d90ecbeb35..e861385932 100644 --- a/src/axom/mir/MIRMeshTypes.hpp +++ b/src/axom/mir/MIRMeshTypes.hpp @@ -68,7 +68,7 @@ namespace mir using BaseSet = slam::Set< PosType, ElemType >; using ScalarMap = slam::Map< BaseSet, axom::float64 >; // Note: Documentation has a typo in it. using PointMap = slam::Map< BaseSet, Point2 >; - + using IntMap = slam::Map< BaseSet, int >; } } #endif \ No newline at end of file diff --git a/src/axom/mir/examples/mir_tutorial_simple.cpp b/src/axom/mir/examples/mir_tutorial_simple.cpp index 23bfc09769..ae04cbf93f 100644 --- a/src/axom/mir/examples/mir_tutorial_simple.cpp +++ b/src/axom/mir/examples/mir_tutorial_simple.cpp @@ -102,18 +102,24 @@ int main( int AXOM_NOT_USED(argc), char** AXOM_NOT_USED(argv) ) points[15] = mir::Point2( 3.0, 0.0 ); } + int elementParents[9] = { 0,1,2,3,4,5,6,7,8 }; // For the base mesh, the parents are always themselves + + std::vector elementDominantMaterials = {NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT}; + // Build the mesh mir::MIRMesh testMesh; testMesh.InitializeMesh(evInds, evBegins, veInds, veBegins, verts, elems, numMaterials); testMesh.constructMeshRelations(); testMesh.constructMeshVolumeFractionMaps(materialVolumeFractionsData); testMesh.constructVertexPositionMap(points); + testMesh.constructElementParentMap(elementParents); + testMesh.constructElementDominantMaterialMap(elementDominantMaterials); // Begin material interface reconstruction mir::InterfaceReconstructor reconstructor(&testMesh); mir::MIRMesh processedMesh = reconstructor.computeReconstructedInterface(); processedMesh.print(); - // processedMesh.writeMeshToFile("/Users/sterbentz3/Desktop/markoTestMesh.vtk"); + processedMesh.writeMeshToFile("/Users/sterbentz3/Desktop/processedTestMesh.vtk"); return 0; } From 9314f5432c36044ef9171a966a9aa106657248bf Mon Sep 17 00:00:00 2001 From: Sterbentz Date: Fri, 14 Jun 2019 08:48:20 -0700 Subject: [PATCH 06/23] Implemented triangle clipping functionality. --- src/axom/mir/InterfaceReconstructor.cpp | 545 ++++++++++++------ src/axom/mir/InterfaceReconstructor.hpp | 7 +- src/axom/mir/MIRMesh.cpp | 2 +- src/axom/mir/MIRMeshTypes.hpp | 2 +- src/axom/mir/ZooBitMaps.cpp | 27 +- src/axom/mir/ZooBitMaps.hpp | 1 + src/axom/mir/examples/mir_tutorial_simple.cpp | 208 ++++++- 7 files changed, 619 insertions(+), 173 deletions(-) diff --git a/src/axom/mir/InterfaceReconstructor.cpp b/src/axom/mir/InterfaceReconstructor.cpp index 0160709fde..2984a870d2 100644 --- a/src/axom/mir/InterfaceReconstructor.cpp +++ b/src/axom/mir/InterfaceReconstructor.cpp @@ -12,27 +12,27 @@ namespace mir //-------------------------------------------------------------------------------- - /// Default constructor - InterfaceReconstructor::InterfaceReconstructor() - { - - } +/// Default constructor +InterfaceReconstructor::InterfaceReconstructor() +{ + +} //-------------------------------------------------------------------------------- - /// Constructor - InterfaceReconstructor::InterfaceReconstructor(mir::MIRMesh* _mesh) - { - originalMesh = _mesh; - } +/// Constructor +InterfaceReconstructor::InterfaceReconstructor(mir::MIRMesh* _mesh) +{ + originalMesh = _mesh; +} //-------------------------------------------------------------------------------- - /// Destructor - InterfaceReconstructor::~InterfaceReconstructor() - { +/// Destructor +InterfaceReconstructor::~InterfaceReconstructor() +{ - } +} //-------------------------------------------------------------------------------- @@ -81,9 +81,14 @@ mir::MIRMesh InterfaceReconstructor::computeReconstructedInterface() // Store the current mesh to be passed into the next iteration finalMesh = processedMesh; - finalMesh.constructMeshRelations(); // TODO: Figure out why this is necessary here... + finalMesh.constructMeshRelations(); printf("-------------Finished processing material-------------\n"); + + // Write out the intermediate meshes that are processed + std::string outFilename = "/Users/sterbentz3/Desktop/intermediateMesh" + std::to_string(matID) + ".vtk"; + finalMesh.writeMeshToFile(outFilename); + // finalMesh.print(); } // TODO: Run post-processing on mesh to meld the mesh vertices that are within a certain epsilon @@ -93,168 +98,168 @@ mir::MIRMesh InterfaceReconstructor::computeReconstructedInterface() //-------------------------------------------------------------------------------- - /// Computes the points where the element should be clipped based on the volume fractions of mat one and two. - void InterfaceReconstructor::computeClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh, CellData& out_cellData) - { - // Find the vertices associated with each element - auto elementVertices = tempMesh->bdry[eID]; +/// Computes the points where the element should be clipped based on the volume fractions of mat one and two. +void InterfaceReconstructor::computeClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh, CellData& out_cellData) +{ + // Find the vertices associated with each element + auto elementVertices = tempMesh->bdry[eID]; - // Triangle Case - if (elementVertices.size() == 3) - { - computeTriangleClippingPoints(eID, matOneID, matTwoID, tempMesh, out_cellData); - } - // Quad Case - if (elementVertices.size() == 4) - { - computeQuadClippingPoints(eID, matOneID, matTwoID, tempMesh, out_cellData); - } + // Triangle Case + if (elementVertices.size() == 3) + { + computeTriangleClippingPoints(eID, matOneID, matTwoID, tempMesh, out_cellData); + } + // Quad Case + if (elementVertices.size() == 4) + { + computeQuadClippingPoints(eID, matOneID, matTwoID, tempMesh, out_cellData); } +} //-------------------------------------------------------------------------------- - void InterfaceReconstructor::computeQuadClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh, CellData& out_cellData) +void InterfaceReconstructor::computeQuadClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh, CellData& out_cellData) +{ + printf(" Processing QUAD element %d: ", eID); + + // Determine the clipping case + auto elementVertices = tempMesh->bdry[eID]; + + int upperLeftVertex = elementVertices[0]; + int lowerLeftVertex = elementVertices[1]; + int lowerRightVertex = elementVertices[2]; + int upperRightVertex = elementVertices[3]; + + unsigned int caseIndex = determineQuadClippingCase(eID, tempMesh, matOneID, matTwoID, upperLeftVertex, lowerLeftVertex, lowerRightVertex, upperRightVertex); + printf("caseIndex: %d\n", caseIndex); + + // Generate new elements + std::map > newElements; // hashmap of the new elements' vertices | Note: maps are ordered sets + std::map > newVertices; // hashmap of the new vertices' elements | Note: maps are ordered sets + int verticesPresent[8] = {0,0,0,0,0,0,0,0}; // Array of flags denoting whether the vertex is present in the current case or not + axom::float64 verticesClippingTValue[8] = {0,0,0,0,0,0,0,0}; // Array of t values that denote the percent value of where the edge should be clipped + + // Create the new polygons based on the clipping case + int currentElementIndex = 0; // the next available element index + int i = 0; + int numVertices = quadClipTable[caseIndex][i]; + + // for each new element in the current clipping case + while (numVertices != -1) { - printf(" Processing element %d: ", eID); - - // Determine the clipping case - auto elementVertices = tempMesh->bdry[eID]; - - int upperLeftVertex = elementVertices[0]; - int lowerLeftVertex = elementVertices[1]; - int lowerRightVertex = elementVertices[2]; - int upperRightVertex = elementVertices[3]; - - unsigned int caseIndex = determineQuadClippingCase(eID, tempMesh, matOneID, matTwoID, upperLeftVertex, lowerLeftVertex, lowerRightVertex, upperRightVertex); - printf("caseIndex: %d\n", caseIndex); + // for each vertex of the new element + for (int j = 0; j < numVertices; ++j) + { + // Find the id of the next vertex of the new element + int vID = quadClipTable[caseIndex][i + (j+1)]; - // Generate new elements - std::map > newElements; // hashmap of the new elements' vertices | Note: maps are ordered sets - std::map > newVertices; // hashmap of the new vertices' elements | Note: maps are ordered sets - int verticesPresent[8]; // Array of flags denoting whether the vertex is present in the current case or not - axom::float64 verticesClippingTValue[8]; // Array of t values that denote the percent value of where the edge should be clipped + // Associate the vertex and element together + newElements[currentElementIndex].push_back(vID); + newVertices[vID].push_back(currentElementIndex); + verticesPresent[vID] = 1; - // Create the new polygons based on the clipping case - int currentElementIndex = 0; // the next available element index - int i = 0; - int numVertices = quadClipTable[caseIndex][i]; - - // for each new element in the current clipping case - while (numVertices != -1) - { - // for each vertex of the new element - for (int j = 0; j < numVertices; ++j) + // Find t using linear interpolation for any vertex that is not one of the original 4 vertices + if(vID == 4) + { + verticesClippingTValue[vID] = computeClippingPointOnEdge(upperLeftVertex, lowerLeftVertex, matOneID, matTwoID, tempMesh); + } + else if(vID == 5) + { + verticesClippingTValue[vID] = computeClippingPointOnEdge(lowerLeftVertex, lowerRightVertex, matOneID, matTwoID, tempMesh); + } + else if(vID == 6) { - // Find the id of the next vertex of the new element - int vID = quadClipTable[caseIndex][i + (j+1)]; - - // Associate the vertex and element together - newElements[currentElementIndex].push_back(vID); - newVertices[vID].push_back(currentElementIndex); - verticesPresent[vID] = 1; - - // Find t using linear interpolation for any vertex that is not one of the original 4 vertices - if(vID == 4) - { - verticesClippingTValue[vID] = computeClippingPointOnEdge(upperLeftVertex, lowerLeftVertex, matOneID, matTwoID, tempMesh); - } - else if(vID == 5) - { - verticesClippingTValue[vID] = computeClippingPointOnEdge(lowerLeftVertex, lowerRightVertex, matOneID, matTwoID, tempMesh); - } - else if(vID == 6) - { - verticesClippingTValue[vID] = computeClippingPointOnEdge(lowerRightVertex, upperRightVertex, matOneID, matTwoID, tempMesh); - } - else if(vID == 7) - { - verticesClippingTValue[vID] = computeClippingPointOnEdge(upperRightVertex, upperLeftVertex, matOneID, matTwoID, tempMesh); - } + verticesClippingTValue[vID] = computeClippingPointOnEdge(lowerRightVertex, upperRightVertex, matOneID, matTwoID, tempMesh); } + else if(vID == 7) + { + verticesClippingTValue[vID] = computeClippingPointOnEdge(upperRightVertex, upperLeftVertex, matOneID, matTwoID, tempMesh); + } + } - // Increment the element index counter, marking the current element as being finished processed - currentElementIndex++; + // Increment the element index counter, marking the current element as being finished processed + currentElementIndex++; - // Increase index into lookup table to the next element - i += (numVertices + 1); - numVertices = quadClipTable[caseIndex][i]; - } + // Increase index into lookup table to the next element + i += (numVertices + 1); + numVertices = quadClipTable[caseIndex][i]; + } - /**************************************************************** - * CALCULATE THE NEW CELLS' DATA - ****************************************************************/ - // Calculate the total number of elements and vertices that were generated from splitting the current element - out_cellData.numElems = (int) newElements.size(); - out_cellData.numVerts = (int) newVertices.size(); + /**************************************************************** + * CALCULATE THE NEW CELLS' DATA + ****************************************************************/ + // Calculate the total number of elements and vertices that were generated from splitting the current element + out_cellData.numElems = (int) newElements.size(); + out_cellData.numVerts = (int) newVertices.size(); - generateTopologyDataFromQuad(newElements, newVertices, out_cellData); - generateVertexPositionsFromQuad(newVertices, tempMesh, verticesClippingTValue, upperLeftVertex, lowerLeftVertex, lowerRightVertex, upperRightVertex, out_cellData); - generateVertexVolumeFractionsFromQuad(newVertices, tempMesh, verticesClippingTValue, upperLeftVertex, lowerLeftVertex, lowerRightVertex, upperRightVertex, out_cellData); + generateTopologyData(newElements, newVertices, out_cellData); + generateVertexPositionsFromQuad(newVertices, tempMesh, verticesClippingTValue, upperLeftVertex, lowerLeftVertex, lowerRightVertex, upperRightVertex, out_cellData); + generateVertexVolumeFractionsFromQuad(newVertices, tempMesh, verticesClippingTValue, upperLeftVertex, lowerLeftVertex, lowerRightVertex, upperRightVertex, out_cellData); - // Determine and store the dominant material of this element - for (auto itr = newElements.begin(); itr != newElements.end(); itr++) + // Determine and store the dominant material of this element + for (auto itr = newElements.begin(); itr != newElements.end(); itr++) + { + int currentDominantMat = NULL_MAT; + for (int it = 0; it < itr->second.size(); ++it) { - int currentDominantMat = NULL_MAT; - for (int it = 0; it < itr->second.size(); ++it) + // int temp_vID = newElements[currentElementIndex][it]; + int temp_vID = itr->second[it]; + + // Find the vertex that is one of the four original vertices of the quad element + if (temp_vID == 0 || temp_vID == 1 || temp_vID == 2 || temp_vID == 3) { - // int temp_vID = newElements[currentElementIndex][it]; - int temp_vID = itr->second[it]; - - // Find the vertex that is one of the four original vertices of the quad element - if (temp_vID == 0 || temp_vID == 1 || temp_vID == 2 || temp_vID == 3) - { - axom::float64 matOneVolumeFraction = -1.0, matTwoVolumeFraction = -1.0; - if (matOneID != NULL_MAT) - matOneVolumeFraction = out_cellData.vertexVolumeFractions[matOneID][temp_vID]; - if (matTwoID != NULL_MAT) - matTwoVolumeFraction = out_cellData.vertexVolumeFractions[matTwoID][temp_vID]; - - currentDominantMat = (matOneVolumeFraction > matTwoVolumeFraction) ? matOneID : matTwoID; - } + axom::float64 matOneVolumeFraction = -1.0, matTwoVolumeFraction = -1.0; + if (matOneID != NULL_MAT) + matOneVolumeFraction = out_cellData.vertexVolumeFractions[matOneID][temp_vID]; + if (matTwoID != NULL_MAT) + matTwoVolumeFraction = out_cellData.vertexVolumeFractions[matTwoID][temp_vID]; + + currentDominantMat = (matOneVolumeFraction > matTwoVolumeFraction) ? matOneID : matTwoID; } - out_cellData.elementDominantMaterials.push_back(currentDominantMat); } + out_cellData.elementDominantMaterials.push_back(currentDominantMat); + } - // Determine and store the parent of this element - out_cellData.elementParents.resize(newElements.size()); - for (auto itr = newElements.begin(); itr != newElements.end(); itr++) - { - out_cellData.elementParents[itr->first] = tempMesh->elementParentIDs[eID]; - } + // Determine and store the parent of this element + out_cellData.elementParents.resize(newElements.size()); + for (auto itr = newElements.begin(); itr != newElements.end(); itr++) + { + out_cellData.elementParents[itr->first] = tempMesh->elementParentIDs[eID]; + } - // Check that each element is dominated by a material that is actually present in the original parent cell - for (auto itr = newElements.begin(); itr != newElements.end(); itr++) - { - int parentElementID = out_cellData.elementParents[itr->first]; - int currentDominantMaterial = out_cellData.elementDominantMaterials[itr->first]; + // Check that each element is dominated by a material that is actually present in the original parent cell + for (auto itr = newElements.begin(); itr != newElements.end(); itr++) + { + int parentElementID = out_cellData.elementParents[itr->first]; + int currentDominantMaterial = out_cellData.elementDominantMaterials[itr->first]; - if (originalMesh->materialVolumeFractionsElement[currentDominantMaterial][parentElementID] == 0) - { - // This material is not present in the original element from which the current element comes from - if (currentDominantMaterial == matOneID) - out_cellData.elementDominantMaterials[itr->first] = matTwoID; - else - out_cellData.elementDominantMaterials[itr->first] = matOneID; - } - } - - // Modify the evIndex values to account for the fact that perhaps not all 8 possible vertices are present - int evIndexSubtract[8]; - for (int i = 0; i < 8; ++i) + if (originalMesh->materialVolumeFractionsElement[currentDominantMaterial][parentElementID] == 0) { - if (verticesPresent[i] == 1) - evIndexSubtract[i] = 0; + // This material is not present in the original element from which the current element comes from + if (currentDominantMaterial == matOneID) + out_cellData.elementDominantMaterials[itr->first] = matTwoID; else - evIndexSubtract[i] = 1; + out_cellData.elementDominantMaterials[itr->first] = matOneID; } - for (int i = 1; i < 8; ++i) - evIndexSubtract[i] += evIndexSubtract[i - 1]; + } + + // Modify the evIndex values to account for the fact that perhaps not all 8 possible vertices are present + int evIndexSubtract[8]; + for (int i = 0; i < 8; ++i) + { + if (verticesPresent[i] == 1) + evIndexSubtract[i] = 0; + else + evIndexSubtract[i] = 1; + } + for (int i = 1; i < 8; ++i) + evIndexSubtract[i] += evIndexSubtract[i - 1]; - for (unsigned int i = 0; i < out_cellData.evInds.size(); ++i) - { - out_cellData.evInds[i] -= evIndexSubtract[ out_cellData.evInds[i] ]; - } + for (unsigned int i = 0; i < out_cellData.evInds.size(); ++i) + { + out_cellData.evInds[i] -= evIndexSubtract[ out_cellData.evInds[i] ]; } +} //-------------------------------------------------------------------------------- @@ -319,16 +324,10 @@ axom::float64 InterfaceReconstructor::computeClippingPointOnEdge(const int verte axom::float64 vfMatTwoVertexTwo = tempMesh->materialVolumeFractionsVertex[matTwoID][vertexTwoID]; if (matOneID == NULL_MAT) - { - vfMatOneVertexOne = 0.0; - vfMatOneVertexTwo = 0.0; - } + vfMatOneVertexOne = vfMatOneVertexTwo = 0.0; if (matTwoID == NULL_MAT) - { - vfMatTwoVertexOne = 0.0; - vfMatTwoVertexTwo = 0.0; - } + vfMatTwoVertexOne = vfMatTwoVertexTwo = 0.0; axom::float64 numerator = vfMatTwoVertexOne - vfMatOneVertexOne; axom::float64 denominator = -vfMatOneVertexOne + vfMatOneVertexTwo + vfMatTwoVertexOne - vfMatTwoVertexTwo; @@ -345,7 +344,7 @@ axom::float64 InterfaceReconstructor::computeClippingPointOnEdge(const int verte if (ret > 1.0 || ret < 0.0) { // This shouldn't happen... - printf("OUT OF BOUNDS T VALUE: %f\n", ret); + printf(" OUT OF BOUNDS T VALUE: %f\n", ret); // Clamp the t value ret = fmin(1.0, ret); @@ -357,8 +356,8 @@ axom::float64 InterfaceReconstructor::computeClippingPointOnEdge(const int verte //-------------------------------------------------------------------------------- -/// Generate the topology of the new elements (evInds, evBegins, veInds, veBegins) resulting from splitting a quad. -void InterfaceReconstructor::generateTopologyDataFromQuad(std::map > newElements, std::map > newVertices, CellData& out_cellData) +/// Generate the topology of the new elements (evInds, evBegins, veInds, veBegins) resulting from a split. +void InterfaceReconstructor::generateTopologyData(std::map > newElements, std::map > newVertices, CellData& out_cellData) { // Store the evInds and evBegins data in the output vectors int currentEVBeginIndex = 0; @@ -471,10 +470,232 @@ void InterfaceReconstructor::generateVertexVolumeFractionsFromQuad(std::mapbdry[eID]; + + int upperVertex = elementVertices[0]; + int lowerLeftVertex = elementVertices[1]; + int lowerRightVertex = elementVertices[2]; + + unsigned int caseIndex = determineTriangleClippingCase(eID, tempMesh, matOneID, matTwoID, upperVertex, lowerLeftVertex, lowerRightVertex); + printf("caseIndex: %d\n", caseIndex); + + // Generate new elements + std::map > newElements; // hashmap of the new elements' vertices | Note: maps are ordered sets + std::map > newVertices; // hashmap of the new vertices' elements | Note: maps are ordered sets + int verticesPresent[6] = {0,0,0,0,0,0}; // Array of flags denoting whether the vertex is present in the current case or not + axom::float64 verticesClippingTValue[6] = {0,0,0,0,0,0}; // Array of t values that denote the percent value of where the edge should be clipped + + // Create the new polygons based on the clipping case + int currentElementIndex = 0; // the next available element index + int i = 0; + int numVertices = triangleClipTable[caseIndex][i]; + + // for each new element in the current clipping case + while (numVertices != -1) + { + // for each vertex of the new element + for (int j = 0; j < numVertices; ++j) + { + // Find the id of the next vertex of the new element + int vID = triangleClipTable[caseIndex][i + (j+1)]; + + // Associate the vertex and element together + newElements[currentElementIndex].push_back(vID); + newVertices[vID].push_back(currentElementIndex); + verticesPresent[vID] = 1; + + // Find t using linear interpolation for any vertex that is not one of the original 3 vertices + if(vID == 3) + { + verticesClippingTValue[vID] = computeClippingPointOnEdge(upperVertex, lowerLeftVertex, matOneID, matTwoID, tempMesh); + } + else if(vID == 4) + { + verticesClippingTValue[vID] = computeClippingPointOnEdge(lowerLeftVertex, lowerRightVertex, matOneID, matTwoID, tempMesh); + } + else if(vID == 5) + { + verticesClippingTValue[vID] = computeClippingPointOnEdge(lowerRightVertex, upperVertex, matOneID, matTwoID, tempMesh); + } + } + + // Increment the element index counter, marking the current element as being finished processed + currentElementIndex++; + + // Increase index into lookup table to the next element + i += (numVertices + 1); + numVertices = triangleClipTable[caseIndex][i]; + } + + /**************************************************************** + * CALCULATE THE NEW CELLS' DATA + ****************************************************************/ + // Calculate the total number of elements and vertices that were generated from splitting the current element + out_cellData.numElems = (int) newElements.size(); + out_cellData.numVerts = (int) newVertices.size(); + + generateTopologyData(newElements, newVertices, out_cellData); + generateVertexPositionsFromTriangle(newVertices, tempMesh, verticesClippingTValue, upperVertex, lowerLeftVertex, lowerRightVertex, out_cellData); + generateVertexVolumeFractionsFromTriangle(newVertices, tempMesh, verticesClippingTValue, upperVertex, lowerLeftVertex, lowerRightVertex, out_cellData); + + // Determine and store the dominant material of this element + for (auto itr = newElements.begin(); itr != newElements.end(); itr++) + { + int currentDominantMat = NULL_MAT; + for (int it = 0; it < itr->second.size(); ++it) + { + int temp_vID = itr->second[it]; + + // Find the vertex that is one of the three original vertices of the triangle element + if (temp_vID == 0 || temp_vID == 1 || temp_vID == 2) + { + axom::float64 matOneVolumeFraction = -1.0, matTwoVolumeFraction = -1.0; + if (matOneID != NULL_MAT) + matOneVolumeFraction = out_cellData.vertexVolumeFractions[matOneID][temp_vID]; + if (matTwoID != NULL_MAT) + matTwoVolumeFraction = out_cellData.vertexVolumeFractions[matTwoID][temp_vID]; + + currentDominantMat = (matOneVolumeFraction > matTwoVolumeFraction) ? matOneID : matTwoID; + } + } + out_cellData.elementDominantMaterials.push_back(currentDominantMat); + } + + // Determine and store the parent of this element + out_cellData.elementParents.resize(newElements.size()); + for (auto itr = newElements.begin(); itr != newElements.end(); itr++) + { + out_cellData.elementParents[itr->first] = tempMesh->elementParentIDs[eID]; + } + + // Check that each element is dominated by a material that is actually present in the original parent cell + for (auto itr = newElements.begin(); itr != newElements.end(); itr++) + { + int parentElementID = out_cellData.elementParents[itr->first]; + int currentDominantMaterial = out_cellData.elementDominantMaterials[itr->first]; + + if (originalMesh->materialVolumeFractionsElement[currentDominantMaterial][parentElementID] == 0) + { + // This material is not present in the original element from which the current element comes from + if (currentDominantMaterial == matOneID) + out_cellData.elementDominantMaterials[itr->first] = matTwoID; + else + out_cellData.elementDominantMaterials[itr->first] = matOneID; + } + } + + // Modify the evIndex values to account for the fact that perhaps not all 6 possible vertices are present + int evIndexSubtract[6]; + for (int i = 0; i < 6; ++i) + { + if (verticesPresent[i] == 1) + evIndexSubtract[i] = 0; + else + evIndexSubtract[i] = 1; + } + + for (int i = 1; i < 6; ++i) + evIndexSubtract[i] += evIndexSubtract[i - 1]; + + for (unsigned int i = 0; i < out_cellData.evInds.size(); ++i) + out_cellData.evInds[i] -= evIndexSubtract[ out_cellData.evInds[i] ]; + } //-------------------------------------------------------------------------------- +/// Finds the bit map representing the clipping case for a triangle. +unsigned int InterfaceReconstructor::determineTriangleClippingCase(const int eID, mir::MIRMesh* tempMesh, const int matOneID, const int matTwoID, const int upperVertex, const int lowerLeftVertex, const int lowerRightVertex) +{ + // Determine the dominant color at each vertex + int upperColor = matOneID, lowerLeftColor = matOneID, lowerRightColor = matOneID; + + if (matOneID == NULL_MAT) + upperColor = lowerLeftColor = lowerRightColor = matTwoID; + if (matTwoID == NULL_MAT) + upperColor = lowerLeftColor = lowerRightColor = matOneID; + if (matOneID != NULL_MAT && matTwoID != NULL_MAT) + { + upperColor = tempMesh->materialVolumeFractionsVertex[matOneID][upperVertex] > tempMesh->materialVolumeFractionsVertex[matTwoID][upperVertex] ? matOneID : matTwoID; + lowerLeftColor = tempMesh->materialVolumeFractionsVertex[matOneID][lowerLeftVertex] > tempMesh->materialVolumeFractionsVertex[matTwoID][lowerLeftVertex] ? matOneID : matTwoID; + lowerRightColor = tempMesh->materialVolumeFractionsVertex[matOneID][lowerRightVertex] > tempMesh->materialVolumeFractionsVertex[matTwoID][lowerRightVertex] ? matOneID : matTwoID; + } + + // Create the index into the quad clipping lookup table using the dominant colors at each vertex + unsigned int caseIndex = 0; + if (upperColor == matOneID) caseIndex |= 4; + if (lowerLeftColor == matOneID) caseIndex |= 2; + if (lowerRightColor == matOneID) caseIndex |= 1; + + return caseIndex; } + +//-------------------------------------------------------------------------------- + +/// Generate the vertex position data for the new elements resulting from spltting a quad. +void InterfaceReconstructor::generateVertexPositionsFromTriangle(std::map > newVertices, mir::MIRMesh* tempMesh, axom::float64* verticesClippingTValue, int upperVertex, int lowerLeftVertex, int lowerRightVertex, CellData& out_cellData) +{ + std::map newPoints; // hashmap of the new vertices' positions | Note: maps are ordered sets + + // Find the position coordinates of the vertices and then store the positions of the vertices in the return vector + for (auto itr = newVertices.begin(); itr != newVertices.end(); itr++) + { + int vID = itr->first; + if (vID == 0) + newPoints[vID] = tempMesh->vertexPositions[upperVertex]; + if (vID == 1) + newPoints[vID] = tempMesh->vertexPositions[lowerLeftVertex]; + if (vID == 2) + newPoints[vID] = tempMesh->vertexPositions[lowerRightVertex]; + if (vID == 3) + newPoints[vID] = interpolateVertexPosition(tempMesh->vertexPositions[upperVertex], tempMesh->vertexPositions[lowerLeftVertex], verticesClippingTValue[vID]); + if (vID == 4) + newPoints[vID] = interpolateVertexPosition(tempMesh->vertexPositions[lowerLeftVertex], tempMesh->vertexPositions[lowerRightVertex], verticesClippingTValue[vID]); + if (vID == 5) + newPoints[vID] = interpolateVertexPosition(tempMesh->vertexPositions[lowerRightVertex], tempMesh->vertexPositions[upperVertex], verticesClippingTValue[vID]); + } + + // Store the positions of the vertices in the return vector + for (auto itr = newPoints.begin(); itr != newPoints.end(); itr++) + { + out_cellData.vertexPositions.push_back(itr->second); + } +} + +//-------------------------------------------------------------------------------- + +/// Generate the vertex volume fraction data for the new vertices resulting from splitting a quad. +void InterfaceReconstructor::generateVertexVolumeFractionsFromTriangle(std::map > newVertices, mir::MIRMesh* tempMesh, axom::float64* verticesClippingTValue, int upperVertex, int lowerLeftVertex, int lowerRightVertex, CellData& out_cellData) +{ + // Calculate the vertex fractions at each vertex (use t value!) + // Make sure the output volume fractions containers are the proper size + out_cellData.vertexVolumeFractions.resize(tempMesh->numMaterials); + + for (auto itr = newVertices.begin(); itr != newVertices.end(); itr++) + { + int vID = itr->first; + for (int matID = 0; matID < tempMesh->numMaterials; ++matID) + { + if (vID == 0) + out_cellData.vertexVolumeFractions[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][upperVertex]); + if (vID == 1) + out_cellData.vertexVolumeFractions[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][lowerLeftVertex]); + if (vID == 2) + out_cellData.vertexVolumeFractions[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][lowerRightVertex]); + if (vID == 3) + out_cellData.vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][upperVertex], tempMesh->materialVolumeFractionsVertex[matID][lowerLeftVertex], verticesClippingTValue[vID])); + if (vID == 4) + out_cellData.vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][lowerLeftVertex], tempMesh->materialVolumeFractionsVertex[matID][lowerRightVertex], verticesClippingTValue[vID])); + if (vID == 5) + out_cellData.vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][lowerRightVertex], tempMesh->materialVolumeFractionsVertex[matID][upperVertex], verticesClippingTValue[vID])); + } + } +} + +//-------------------------------------------------------------------------------- + } +} \ No newline at end of file diff --git a/src/axom/mir/InterfaceReconstructor.hpp b/src/axom/mir/InterfaceReconstructor.hpp index 7f620f4405..b5e80817db 100644 --- a/src/axom/mir/InterfaceReconstructor.hpp +++ b/src/axom/mir/InterfaceReconstructor.hpp @@ -51,9 +51,14 @@ namespace mir // quad clipping points helper functions unsigned int determineQuadClippingCase(const int eID, mir::MIRMesh* tempMesh, const int matOneID, const int matTwoID, const int upperLeftVertex, const int lowerLeftVertex, const int lowerRightVertex, const int upperRightVertex); - void generateTopologyDataFromQuad(std::map > newElements, std::map > newVertices, CellData& out_cellData); + void generateTopologyData(std::map > newElements, std::map > newVertices, CellData& out_cellData); void generateVertexPositionsFromQuad(std::map > newVertices, mir::MIRMesh* tempMesh, axom::float64* verticesClippingTValue, int upperLeftVertex, int lowerLeftVertex, int lowerRightVertex, int upperRightVertex, CellData& out_cellData); void generateVertexVolumeFractionsFromQuad(std::map > newVertices, mir::MIRMesh* tempMesh, axom::float64* verticesClippingTValue, int upperLeftVertex, int lowerLeftVertex, int lowerRightVertex, int upperRightVertex, CellData& out_cellData); + + // triangle clipping points helper functions + unsigned int determineTriangleClippingCase(const int eID, mir::MIRMesh* tempMesh, const int matOneID, const int matTwoID, const int upperVertex, const int lowerLeftVertex, const int lowerRightVertex); + void generateVertexPositionsFromTriangle(std::map > newVertices, mir::MIRMesh* tempMesh, axom::float64* verticesClippingTValue, int upperVertex, int lowerLeftVertex, int lowerRightVertex, CellData& out_cellData); + void generateVertexVolumeFractionsFromTriangle(std::map > newVertices, mir::MIRMesh* tempMesh, axom::float64* verticesClippingTValue, int upperVertex, int lowerLeftVertex, int lowerRightVertex, CellData& out_cellData); }; } } diff --git a/src/axom/mir/MIRMesh.cpp b/src/axom/mir/MIRMesh.cpp index 11dcc49e84..a9036ba54e 100644 --- a/src/axom/mir/MIRMesh.cpp +++ b/src/axom/mir/MIRMesh.cpp @@ -276,7 +276,7 @@ void MIRMesh::constructMeshVolumeFractionsVertex(std::vector; - using ScalarMap = slam::Map< BaseSet, axom::float64 >; // Note: Documentation has a typo in it. + using ScalarMap = slam::Map< BaseSet, axom::float64 >; using PointMap = slam::Map< BaseSet, Point2 >; using IntMap = slam::Map< BaseSet, int >; } diff --git a/src/axom/mir/ZooBitMaps.cpp b/src/axom/mir/ZooBitMaps.cpp index d2adf217a3..3904fd525c 100644 --- a/src/axom/mir/ZooBitMaps.cpp +++ b/src/axom/mir/ZooBitMaps.cpp @@ -30,18 +30,41 @@ namespace mir {3,3,7,6,4,7,0,2,6,3,0,1,2,-1,-1,-1,-1,-1,-1}, {3,6,5,2,4,3,1,5,6,3,0,1,3,-1,-1,-1,-1,-1,-1}, {4,0,1,5,7,4,7,5,2,3,-1,-1,-1,-1,-1,-1,-1,-1}, - {3,4,1,5,4,0,4,5,2,3,7,6,3,-1,-1,-1,-1,-1,-1}, + {3,4,1,5,4,0,4,5,2,3,0,2,3,-1,-1,-1,-1,-1,-1}, {3,4,1,5,4,0,4,5,2,4,0,2,6,7,3,7,6,3,-1}, {4,0,4,6,3,4,4,1,2,6,-1,-1,-1,-1,-1,-1,-1,-1,-1}, {3,0,4,7,4,4,1,3,7,3,1,2,3,-1,-1,-1,-1,-1,-1}, {3,0,4,7,4,4,1,3,7,3,1,2,3,-1,-1,-1,-1,-1,-1}, {4,0,4,6,3,4,4,1,2,6,-1,-1,-1,-1,-1,-1,-1,-1,-1}, {3,4,1,5,4,0,4,5,2,4,0,2,6,7,3,7,6,3,-1}, - {3,4,1,5,4,0,4,5,2,3,7,6,3,-1,-1,-1,-1,-1,-1}, + {3,4,1,5,4,0,4,5,2,3,0,2,3,-1,-1,-1,-1,-1,-1}, {4,0,1,5,7,4,7,5,2,3,-1,-1,-1,-1,-1,-1,-1,-1}, {3,6,5,2,4,3,1,5,6,3,0,1,3,-1,-1,-1,-1,-1,-1}, {3,3,7,6,4,7,0,2,6,3,0,1,2,-1,-1,-1,-1,-1,-1}, {4,0,1,2,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1} }; + + + // Triangle Vertex Indices + // 0 + // @ + // / \ + // / \ + // 3 @ @ 5 + // / \ + // / \ + // 1 @-----@-----@ 2 + // 4 + const int triangleClipTable[8][10] = + { + {3,0,1,2,-1,-1,-1,-1,-1,-1}, + {4,0,1,4,5,3,5,4,2,-1}, + {4,0,3,4,2,3,3,1,4,-1}, + {4,3,1,2,5,3,0,3,5,-1}, + {4,3,1,2,5,3,0,3,5,-1}, + {4,0,3,4,2,3,3,1,4,-1}, + {4,0,1,4,5,3,5,4,2,-1}, + {3,0,1,2,-1,-1,-1,-1,-1,-1} + }; } } \ No newline at end of file diff --git a/src/axom/mir/ZooBitMaps.hpp b/src/axom/mir/ZooBitMaps.hpp index 09f76ede1e..e765ced325 100644 --- a/src/axom/mir/ZooBitMaps.hpp +++ b/src/axom/mir/ZooBitMaps.hpp @@ -12,6 +12,7 @@ namespace axom namespace mir { extern const int quadClipTable[16][19]; + extern const int triangleClipTable[8][10]; } } #endif \ No newline at end of file diff --git a/src/axom/mir/examples/mir_tutorial_simple.cpp b/src/axom/mir/examples/mir_tutorial_simple.cpp index ae04cbf93f..ab1b17b252 100644 --- a/src/axom/mir/examples/mir_tutorial_simple.cpp +++ b/src/axom/mir/examples/mir_tutorial_simple.cpp @@ -16,12 +16,38 @@ namespace numerics = axom::numerics; namespace slam = axom::slam; namespace mir = axom::mir; +// function templates +mir::MIRMesh initTestCaseOne(); // 3x3 grid, 2 materials +mir::MIRMesh initTestCaseTwo(); // 3x3 grid, 3 materials +mir::MIRMesh initTestCaseThree(); // triforce, 2 materials + //-------------------------------------------------------------------------------- /*! * \brief Tutorial main */ int main( int AXOM_NOT_USED(argc), char** AXOM_NOT_USED(argv) ) +{ + // Intialize a mesh for testing MIR + // mir::MIRMesh testMesh = initTestCaseOne(); + mir::MIRMesh testMesh = initTestCaseTwo(); + // mir::MIRMesh testMesh = initTestCaseThree(); + + // Begin material interface reconstruction + mir::InterfaceReconstructor reconstructor(&testMesh); + mir::MIRMesh processedMesh = reconstructor.computeReconstructedInterface(); + + // Output results + processedMesh.print(); + processedMesh.writeMeshToFile("/Users/sterbentz3/Desktop/processedTestMesh.vtk"); + + return 0; +} + +//-------------------------------------------------------------------------------- + +// Initialize mesh for Test Case 1 (from Meredith 2004) +mir::MIRMesh initTestCaseOne() { int numElements = 9; int numVertices = 16; @@ -115,13 +141,183 @@ int main( int AXOM_NOT_USED(argc), char** AXOM_NOT_USED(argv) ) testMesh.constructElementParentMap(elementParents); testMesh.constructElementDominantMaterialMap(elementDominantMaterials); - // Begin material interface reconstruction - mir::InterfaceReconstructor reconstructor(&testMesh); - mir::MIRMesh processedMesh = reconstructor.computeReconstructedInterface(); - processedMesh.print(); - processedMesh.writeMeshToFile("/Users/sterbentz3/Desktop/processedTestMesh.vtk"); + return testMesh; +} - return 0; +//-------------------------------------------------------------------------------- + +// Initialize mesh for Test Case 2 (from Meredith and Childs 2010) +mir::MIRMesh initTestCaseTwo() +{ + int numElements = 9; + int numVertices = 16; + + // Create the mesh connectivity information + std::vector evInds = { + 0,4,5,1, // elem 0, card 4, start 0 + 1,5,6,2, // elem 1, card 4, start 4 + 2,6,7,3, // elem 2, card 4, start 8 + 4,8,9,5, // elem 3, card 4, start 12 + 5,9,10,6, // elem 4, card 4, start 16 + 6,10,11,7, // elem 5, card 4, start 20 + 8,12,13,9, // elem 6, card 4, start 24 + 9,13,14,10, // elem 7, card 4, start 28 + 10,14,15,11 // elem 8, card 4, start 32, end 36 + }; + + std::vector evBegins = { + 0,4,8,12,16,20,24,28,32,36 + }; + std::vector veInds = { + 0, // vert 0, card 1, start 0 + 0,1, // vert 1, card 2, start 1 + 1,2, // vert 2, card 2, start 3 + 2, // vert 3, card 1, start 5 + 0,3, // vert 4, card 2, start 6 + 0,1,3,4, // vert 5, card 4, start 8 + 1,2,4,5, // vert 6, card 4, start 12 + 2,5, // vert 7, card 2, start 16 + 3,6, // vert 8, card 2, start 18 + 3,4,6,7, // vert 9, card 4, start 20 + 4,5,7,8, // vert 10, card 4, start 24 + 5,8, // vert 11, card 2, start 28 + 6, // vert 12, card 1, start 30 + 6,7, // vert 13, card 2, start 31 + 7,8, // vert 14, card 2, start 33 + 8, // vert 15, card 1, start 35, end 36 + }; + std::vector veBegins = { + 0,1,3,5,6,8,12,16,18,20,24,28,30,31,33,35,36 + }; + + mir::VertSet verts = mir::VertSet(numVertices); // Construct a vertex set with 16 vertices + mir::ElemSet elems = mir::ElemSet(numElements); // Construct an element set with 9 elements + + + int numMaterials = 3; + enum { BLUE = 2, RED = 0, ORANGE = 1 }; // TODO: the order orange, blue red causes bugs... might be fixed now... was due to incorrect clipping table entries + // TODO: orange, blue, red is fine + // TODO: blue, red, orange is fine + // TODO: red, orange, blue is fine + + std::vector materialVolumeFractionsData; + materialVolumeFractionsData.resize(numMaterials); + axom::float64 blueVolumeFractions[] = {1.0, 1.0, 1.0, 1.0, 0.5, 0.2, 0.2, 0.0, 0.0}; + materialVolumeFractionsData[BLUE] = blueVolumeFractions; + axom::float64 redVolumeFractions[] = {0.0, 0.0, 0.0, 0.0, 0.3, 0.8, 0.0, 0.3, 1.0}; + materialVolumeFractionsData[RED] = redVolumeFractions; + axom::float64 orangeVolumeFractions[] = {0.0, 0.0, 0.0, 0.0, 0.2, 0.0, 0.8, 0.7, 0.0}; + materialVolumeFractionsData[ORANGE] = orangeVolumeFractions; + + mir::Point2 points[numVertices]; + { + points[0] = mir::Point2( 0.0, 3.0 ); + points[1] = mir::Point2( 1.0, 3.0 ); + points[2] = mir::Point2( 2.0, 3.0 ); + points[3] = mir::Point2( 3.0, 3.0 ); + + points[4] = mir::Point2( 0.0, 2.0 ); + points[5] = mir::Point2( 1.0, 2.0 ); + points[6] = mir::Point2( 2.0, 2.0 ); + points[7] = mir::Point2( 3.0, 2.0 ); + + points[8] = mir::Point2( 0.0, 1.0 ); + points[9] = mir::Point2( 1.0, 1.0 ); + points[10] = mir::Point2( 2.0, 1.0 ); + points[11] = mir::Point2( 3.0, 1.0 ); + + points[12] = mir::Point2( 0.0, 0.0 ); + points[13] = mir::Point2( 1.0, 0.0 ); + points[14] = mir::Point2( 2.0, 0.0 ); + points[15] = mir::Point2( 3.0, 0.0 ); + } + + + int elementParents[9] = { 0,1,2,3,4,5,6,7,8 }; // For the base mesh, the parents are always themselves + + std::vector elementDominantMaterials = {NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT}; + + // Build the mesh + mir::MIRMesh testMesh; + testMesh.InitializeMesh(evInds, evBegins, veInds, veBegins, verts, elems, numMaterials); + testMesh.constructMeshRelations(); + testMesh.constructMeshVolumeFractionMaps(materialVolumeFractionsData); + testMesh.constructVertexPositionMap(points); + testMesh.constructElementParentMap(elementParents); + testMesh.constructElementDominantMaterialMap(elementDominantMaterials); + + return testMesh; +} + +//-------------------------------------------------------------------------------- + +// Initialize mesh for Test Case 3, which tests all the triangle clipping cases. +mir::MIRMesh initTestCaseThree() +{ + int numElements = 4; + int numVertices = 6; // OR create a middle triangle with all of one material, and then a ring of triangles around it that are full of the other material + + // Create the mesh connectivity information + std::vector evInds = { + 0,1,2, // elem 0, card 3, start 0 + 1,3,4, // elem 1, card 3, start 3 + 1,4,2, // elem 2, card 3, start 6 + 2,4,5 // elem 3, card 3, start 9, end 12 + }; + + std::vector evBegins = { + 0,3,6,9,12 + }; + std::vector veInds = { + 0, // vert 0, card 1, start 0 + 0,1,2, // vert 1, card 3, start 1 + 0,2,3, // vert 2, card 3, start 4 + 1, // vert 3, card 1, start 7 + 1,2,3, // vert 4, card 3, start 8 + 3 // vert 5, card 1, start 11, end 12 + }; + std::vector veBegins = { + 0,1,4,7,8,11,12 + }; + + mir::VertSet verts = mir::VertSet(numVertices); // Construct a vertex set with 24 vertices + mir::ElemSet elems = mir::ElemSet(numElements); // Construct an element set with 8 elements + + int numMaterials = 2; + enum { BLUE = 0, RED = 1, }; + + std::vector materialVolumeFractionsData; + materialVolumeFractionsData.resize(numMaterials); + axom::float64 blueVolumeFractions[] = {0.0, 0.5, 0.8, 0.5}; + materialVolumeFractionsData[BLUE] = blueVolumeFractions; + axom::float64 redVolumeFractions[] = {1.0, 0.5, 0.2, 0.5}; + materialVolumeFractionsData[RED] = redVolumeFractions; + + mir::Point2 points[numVertices]; + { + points[0] = mir::Point2( 1.0, 2.0 ); + points[1] = mir::Point2( 0.5, 1.0 ); + points[2] = mir::Point2( 1.5, 1.0 ); + points[3] = mir::Point2( 0.0, 0.0 ); + points[4] = mir::Point2( 1.0, 0.0 ); + points[5] = mir::Point2( 2.0, 0.0 ); + } + + + int elementParents[4] = { 0,1,2,3 }; // For the base mesh, the parents are always themselves + + std::vector elementDominantMaterials = {NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT}; + + // Build the mesh + mir::MIRMesh testMesh; + testMesh.InitializeMesh(evInds, evBegins, veInds, veBegins, verts, elems, numMaterials); + testMesh.constructMeshRelations(); + testMesh.constructMeshVolumeFractionMaps(materialVolumeFractionsData); + testMesh.constructVertexPositionMap(points); + testMesh.constructElementParentMap(elementParents); + testMesh.constructElementDominantMaterialMap(elementDominantMaterials); + + return testMesh; } //-------------------------------------------------------------------------------- From 91ee83bd8aeb47399d877a3903963d12508ff9eb Mon Sep 17 00:00:00 2001 From: Sterbentz Date: Fri, 14 Jun 2019 11:39:51 -0700 Subject: [PATCH 07/23] Implemented a function for calculating the element vertex fractions for the original mesh cells using the generated mesh. --- src/axom/mir/MIRMesh.cpp | 83 +++++++++++++++++++ src/axom/mir/MIRMesh.hpp | 7 ++ src/axom/mir/examples/mir_tutorial_simple.cpp | 7 +- 3 files changed, 92 insertions(+), 5 deletions(-) diff --git a/src/axom/mir/MIRMesh.cpp b/src/axom/mir/MIRMesh.cpp index a9036ba54e..b851348720 100644 --- a/src/axom/mir/MIRMesh.cpp +++ b/src/axom/mir/MIRMesh.cpp @@ -380,5 +380,88 @@ void MIRMesh::constructMeshVolumeFractionsVertex(std::vector totalAreaOriginalElements; // the total area of the original elements + std::map newElementAreas; // the area of each of the generated child elements + std::map numChildren; // the number of child elements generated from one of the original elements + + // Compute the total area of each element of the original mesh and also the area of each new element + for (int eID = 0; eID < elems.size(); ++eID) + { + int numVertices = data.evBegins[eID + 1] - data.evBegins[eID]; + if (numVertices == 3) + { + Point2 trianglePoints[3]; + for (int i = 0; i < 3; ++i) + trianglePoints[i] = vertexPositions[data.evInds[ data.evBegins[eID] + i] ]; + newElementAreas[eID] = computeTriangleArea(trianglePoints[0], trianglePoints[1], trianglePoints[2]); + } + if (numVertices == 4) + { + Point2 trianglePoints[4]; + for (int i = 0; i < 4; ++i) + trianglePoints[i] = vertexPositions[data.evInds[ data.evBegins[eID] + i] ]; + newElementAreas[eID] = computeQuadArea(trianglePoints[0], trianglePoints[1], trianglePoints[2], trianglePoints[3]); + } + + totalAreaOriginalElements[ elementParentIDs[eID] ] += newElementAreas[eID]; + numChildren[ elementParentIDs[eID] ] += .4; + } + + // Intialize the element volume fraction vectors + std::vector > elementVolumeFractions; // indexed as: elementVolumeFractions[material][originalElementID] = volumeFraction + elementVolumeFractions.resize( numMaterials ); + for (int i = 0; i < elementVolumeFractions.size(); ++i) + elementVolumeFractions[i].resize( totalAreaOriginalElements.size(), 0.0 ); + + // Compute the volume fractions for each of the original mesh elements + for (auto itr = newElementAreas.begin(); itr != newElementAreas.end(); itr++) + { + int parentElementID = elementParentIDs[itr->first]; + int materialID = elementDominantMaterials[itr->first]; + elementVolumeFractions[materialID][parentElementID] += (itr->second / totalAreaOriginalElements[parentElementID]); + } + + // Print out the results // TODO: Return the values and use them. + printf("elementVolumeFractions: {\n"); + for (int matID = 0; matID < elementVolumeFractions.size(); ++matID) + { + printf("Material %d: {", matID); + for (int eID = 0; eID < elementVolumeFractions[matID].size(); ++eID) + { + printf(" %f,", elementVolumeFractions[matID][eID]); + } + printf("}\n"); + } + printf("}\n"); +} + +//-------------------------------------------------------------------------------- + +/// Computes the area of the triangle defined by the given three vertex positions. Uses Heron's formula. +axom::float64 MIRMesh::computeTriangleArea(Point2 p0, Point2 p1, Point2 p2) +{ + axom::float64 a = sqrt( ((p1.m_x - p0.m_x) * (p1.m_x - p0.m_x)) + ((p1.m_y - p0.m_y) * (p1.m_y - p0.m_y)) );// the distance from p0 to p1 + axom::float64 b = sqrt( ((p2.m_x - p1.m_x) * (p2.m_x - p1.m_x)) + ((p2.m_y - p1.m_y) * (p2.m_y - p1.m_y)) );// the distance from p1 to p2 + axom::float64 c = sqrt( ((p0.m_x - p2.m_x) * (p0.m_x - p2.m_x)) + ((p0.m_y - p2.m_y) * (p0.m_y - p2.m_y)) );// the distance from p2 to p0 + + axom::float64 s = (a + b + c) / 2; // the semi-perimeter of the triangle + + return sqrt(s * (s - a) * (s - b) * (s - c)); +} + +//-------------------------------------------------------------------------------- + +/// Computes the area of the quad defined by the given four vertex positions. +/// Note: It is assumed the points are given in consecutive order. +axom::float64 MIRMesh::computeQuadArea(Point2 p0, Point2 p1, Point2 p2, Point2 p3) +{ + return computeTriangleArea(p0, p1, p2) + computeTriangleArea(p2, p3, p0); +} + +//-------------------------------------------------------------------------------- + } } \ No newline at end of file diff --git a/src/axom/mir/MIRMesh.hpp b/src/axom/mir/MIRMesh.hpp index 8a16272d9b..2145ce3172 100644 --- a/src/axom/mir/MIRMesh.hpp +++ b/src/axom/mir/MIRMesh.hpp @@ -69,6 +69,13 @@ namespace mir void writeMeshToFile(std::string filename); /// Writes out the current mesh to a file void attachVertexMap(); /// Adds a map of data to the mesh + + void computeOriginalElementVolumeFractions(); + + private: + axom::float64 computeTriangleArea(Point2 p0, Point2 p1, Point2 p2); + axom::float64 computeQuadArea(Point2 p0, Point2 p1, Point2 p2, Point2 p3); + /**************************************************************** * VARIABLES ****************************************************************/ diff --git a/src/axom/mir/examples/mir_tutorial_simple.cpp b/src/axom/mir/examples/mir_tutorial_simple.cpp index ab1b17b252..b9e0dc9b7b 100644 --- a/src/axom/mir/examples/mir_tutorial_simple.cpp +++ b/src/axom/mir/examples/mir_tutorial_simple.cpp @@ -40,6 +40,7 @@ int main( int AXOM_NOT_USED(argc), char** AXOM_NOT_USED(argv) ) // Output results processedMesh.print(); processedMesh.writeMeshToFile("/Users/sterbentz3/Desktop/processedTestMesh.vtk"); + processedMesh.computeOriginalElementVolumeFractions(); return 0; } @@ -193,12 +194,8 @@ mir::MIRMesh initTestCaseTwo() mir::VertSet verts = mir::VertSet(numVertices); // Construct a vertex set with 16 vertices mir::ElemSet elems = mir::ElemSet(numElements); // Construct an element set with 9 elements - int numMaterials = 3; - enum { BLUE = 2, RED = 0, ORANGE = 1 }; // TODO: the order orange, blue red causes bugs... might be fixed now... was due to incorrect clipping table entries - // TODO: orange, blue, red is fine - // TODO: blue, red, orange is fine - // TODO: red, orange, blue is fine + enum { BLUE = 2, RED = 0, ORANGE = 1 }; std::vector materialVolumeFractionsData; materialVolumeFractionsData.resize(numMaterials); From 5f8ee35271cfc6b908a1cf32b642637dc4ea70cd Mon Sep 17 00:00:00 2001 From: Sterbentz Date: Tue, 18 Jun 2019 15:00:04 -0700 Subject: [PATCH 08/23] Created a mesh tester class and generated addtional test cases. Implemented the iterative mesh interface reconstruction algorithm. --- src/axom/mir/CMakeLists.txt | 6 +- src/axom/mir/CellData.cpp | 18 +- src/axom/mir/CellData.hpp | 32 +- src/axom/mir/InterfaceReconstructor.cpp | 90 +- src/axom/mir/InterfaceReconstructor.hpp | 10 +- src/axom/mir/MIRMesh.cpp | 124 ++- src/axom/mir/MIRMesh.hpp | 17 +- src/axom/mir/MeshTester.cpp | 771 ++++++++++++++++++ src/axom/mir/MeshTester.hpp | 47 ++ .../{ZooBitMaps.cpp => ZooClippingTables.cpp} | 2 +- .../{ZooBitMaps.hpp => ZooClippingTables.hpp} | 4 +- src/axom/mir/examples/CMakeLists.txt | 2 +- .../mir/examples/mirConcentricCircles.cpp | 70 ++ src/axom/mir/examples/mir_tutorial.cpp | 480 ----------- src/axom/mir/examples/mir_tutorial_simple.cpp | 315 +------ 15 files changed, 1056 insertions(+), 932 deletions(-) create mode 100644 src/axom/mir/MeshTester.cpp create mode 100644 src/axom/mir/MeshTester.hpp rename src/axom/mir/{ZooBitMaps.cpp => ZooClippingTables.cpp} (98%) rename src/axom/mir/{ZooBitMaps.hpp => ZooClippingTables.hpp} (83%) create mode 100644 src/axom/mir/examples/mirConcentricCircles.cpp delete mode 100644 src/axom/mir/examples/mir_tutorial.cpp diff --git a/src/axom/mir/CMakeLists.txt b/src/axom/mir/CMakeLists.txt index a31de3c473..e1740e6ad4 100644 --- a/src/axom/mir/CMakeLists.txt +++ b/src/axom/mir/CMakeLists.txt @@ -18,17 +18,19 @@ axom_component_requires(NAME MIR set(mir_headers MIRMesh.hpp MIRMeshTypes.hpp - ZooBitMaps.hpp + ZooClippingTables.hpp InterfaceReconstructor.hpp CellData.hpp + MeshTester.hpp ) set(mir_sources ../Axom.cpp MIRMesh.cpp InterfaceReconstructor.cpp - ZooBitMaps.cpp + ZooClippingTables.cpp CellData.cpp + MeshTester.cpp ) #------------------------------------------------------------------------------ diff --git a/src/axom/mir/CellData.cpp b/src/axom/mir/CellData.cpp index df4da4d785..d1cad26aef 100644 --- a/src/axom/mir/CellData.cpp +++ b/src/axom/mir/CellData.cpp @@ -52,49 +52,49 @@ namespace mir int elementIndexOffset = numElems; // Merge the cell topology information - for (auto i = 0; i < cellToMerge.evInds.size(); ++i) + for (unsigned long i = 0; i < cellToMerge.evInds.size(); ++i) { evInds.push_back(cellToMerge.evInds[i] + vertexIndexOffset); } - for (auto i = 1; i < cellToMerge.evBegins.size(); ++i) + for (unsigned long i = 1; i < cellToMerge.evBegins.size(); ++i) { evBegins.push_back(cellToMerge.evBegins[i] + evBeginsOffset); } - for (auto i = 0; i < cellToMerge.veInds.size(); ++i) + for (unsigned long i = 0; i < cellToMerge.veInds.size(); ++i) { veInds.push_back(cellToMerge.veInds[i] + elementIndexOffset); } - for (auto i = 1; i < cellToMerge.veBegins.size(); ++i) + for (unsigned long i = 1; i < cellToMerge.veBegins.size(); ++i) { veBegins.push_back(cellToMerge.veBegins[i] + veBeginsOffset); } // Merge the vertex positions - for (auto i = 0; i < cellToMerge.vertexPositions.size(); ++i) + for (unsigned long i = 0; i < cellToMerge.vertexPositions.size(); ++i) { vertexPositions.push_back(cellToMerge.vertexPositions[i]); } // Merge the vertex volume fractions - for (auto matID = 0; matID < vertexVolumeFractions.size(); ++matID) + for (unsigned long matID = 0; matID < vertexVolumeFractions.size(); ++matID) { - for (auto vID = 0; vID < cellToMerge.vertexVolumeFractions[matID].size(); ++vID) + for (unsigned long vID = 0; vID < cellToMerge.vertexVolumeFractions[matID].size(); ++vID) { vertexVolumeFractions[matID].push_back(cellToMerge.vertexVolumeFractions[matID][vID]); } } // Merge the elements' dominant materials - for (auto i = 0; i < cellToMerge.elementDominantMaterials.size(); ++i) + for (unsigned long i = 0; i < cellToMerge.elementDominantMaterials.size(); ++i) { elementDominantMaterials.push_back(cellToMerge.elementDominantMaterials[i]); } // Merge the elements' parent ids - for (auto i = 0; i < cellToMerge.elementParents.size(); ++i) + for (unsigned long i = 0; i < cellToMerge.elementParents.size(); ++i) { elementParents.push_back(cellToMerge.elementParents[i]); } diff --git a/src/axom/mir/CellData.hpp b/src/axom/mir/CellData.hpp index d91c9761ec..88edd9f453 100644 --- a/src/axom/mir/CellData.hpp +++ b/src/axom/mir/CellData.hpp @@ -19,6 +19,24 @@ namespace axom namespace mir { + // Struct for collecting data that specifies a mesh or cell's connectivity/topology. + struct CellTopologyData + { + std::vector evInds; + std::vector evBegins; + std::vector veInds; + std::vector veBegins; + }; + + // Struct for collecting the data that will populate a mesh's map data structures. + struct CellMapData + { + std::vector vertexPositions; // Data that goes into MIRMesh's vertexPositions PointMap + std::vector > vertexVolumeFractions; // Data that goes into MIRMesh's materialVolumeFractionsVertex ScalarMap + std::vector elementDominantMaterials; // Data that goes into MIRMesh's elementDominantColors IntMap + std::vector elementParents; // Data that goes into MIRMesh's elementParentIDs IntMap + }; + /// Represents an arbitrary number of cells that are within the same local coordinate system (i.e. share a set of vertices and elements). /// Intended to be used as a helper class to hold intermediate data while processing a mesh, and to be used as input to the MIRMesh class to fully initialize it. class CellData @@ -47,20 +65,6 @@ namespace mir std::vector elementDominantMaterials; // Data that goes into MIRMesh's elementDominantColors IntMap std::vector elementParents; // Data that goes into MIRMesh's elementParentIDs IntMap }; - - class CellTopology - { - public: - CellTopology(); - ~CellTopology(); - - public: - std::vector evInds; - std::vector evBegins; - std::vector veInds; - std::vector veBegins; - }; - } } diff --git a/src/axom/mir/InterfaceReconstructor.cpp b/src/axom/mir/InterfaceReconstructor.cpp index 2984a870d2..529c26ead4 100644 --- a/src/axom/mir/InterfaceReconstructor.cpp +++ b/src/axom/mir/InterfaceReconstructor.cpp @@ -20,14 +20,6 @@ InterfaceReconstructor::InterfaceReconstructor() //-------------------------------------------------------------------------------- -/// Constructor -InterfaceReconstructor::InterfaceReconstructor(mir::MIRMesh* _mesh) -{ - originalMesh = _mesh; -} - -//-------------------------------------------------------------------------------- - /// Destructor InterfaceReconstructor::~InterfaceReconstructor() { @@ -37,8 +29,11 @@ InterfaceReconstructor::~InterfaceReconstructor() //-------------------------------------------------------------------------------- /// Reconstructs the material interface and returns the resulting output mesh. -mir::MIRMesh InterfaceReconstructor::computeReconstructedInterface() +mir::MIRMesh InterfaceReconstructor::computeReconstructedInterface(mir::MIRMesh* inputMesh) { + // Store a pointer to the original mesh + originalMesh = inputMesh; + // Initialize the final mesh to be the same as the input mesh mir::MIRMesh finalMesh(originalMesh); @@ -59,12 +54,11 @@ mir::MIRMesh InterfaceReconstructor::computeReconstructedInterface() { // Update the materials upon which the split will occur (should be the currently dominant material and the next material in the list that hasn't yet been split on) int currentDominantMat = intermediateMesh.elementDominantMaterials[eID]; - printf("Processing materials: %d and %d for Element %d\n", currentDominantMat, matOne, eID); computeClippingPoints(eID, currentDominantMat, matOne, &intermediateMesh, temp_cellData[eID]); } // Merge each of the cells into the first CellData struct - for (unsigned int eID = 1; eID < intermediateMesh.elems.size(); ++eID) + for (int eID = 1; eID < intermediateMesh.elems.size(); ++eID) temp_cellData[0].mergeCell(temp_cellData[eID]); mir::VertSet combined_verts(temp_cellData[0].numVerts); @@ -82,13 +76,6 @@ mir::MIRMesh InterfaceReconstructor::computeReconstructedInterface() // Store the current mesh to be passed into the next iteration finalMesh = processedMesh; finalMesh.constructMeshRelations(); - - printf("-------------Finished processing material-------------\n"); - - // Write out the intermediate meshes that are processed - std::string outFilename = "/Users/sterbentz3/Desktop/intermediateMesh" + std::to_string(matID) + ".vtk"; - finalMesh.writeMeshToFile(outFilename); - // finalMesh.print(); } // TODO: Run post-processing on mesh to meld the mesh vertices that are within a certain epsilon @@ -96,6 +83,48 @@ mir::MIRMesh InterfaceReconstructor::computeReconstructedInterface() return finalMesh; } +/// Reconstructs the material interface using an iterative optimization to improve the volume fractions of the resulting mesh. +/// Based on the Meredith and Childs 2010 paper. +mir::MIRMesh InterfaceReconstructor::computeReconstructedInterfaceIterative(mir::MIRMesh* inputMesh, int numIterations, axom::float64 percent) +{ + int numElems = inputMesh->elems.size(); + int numMaterials = inputMesh->numMaterials; + + // Make a copy of the original input mesh + mir::MIRMesh meshToImprove(inputMesh); + + // Calculate the reconstruction on the unmodified, input mesh + mir::MIRMesh resultingMesh = computeReconstructedInterface(&meshToImprove); + + for (int it = 0; it < numIterations; ++it) + { + // Calculate the output element volume fractions of the resulting output mesh + std::vector > resultingElementVF = resultingMesh.computeOriginalElementVolumeFractions(); + + // Initialize the vector to store the improved element volume fractions + std::vector > improvedElementVF; + improvedElementVF.resize(numMaterials); + + // Modify the copy of the original mesh's element volume fractions by percent difference (modify meshToImprove's elementVF) + for (int matID = 0; matID < numMaterials; ++matID) + { + for (int eID = 0; eID < numElems; ++eID) + { + axom::float64 difference = inputMesh->materialVolumeFractionsElement[matID][eID] - resultingElementVF[matID][eID]; + improvedElementVF[matID].push_back( inputMesh->materialVolumeFractionsElement[matID][eID] + ( difference * percent ) ); + } + } + + // Reconstruct the element AND vertex VFs + meshToImprove.constructMeshVolumeFractionsMaps(improvedElementVF); + + // Calculate the reconstruction on the modified, input mesh + resultingMesh = computeReconstructedInterface(&meshToImprove); + } + + return resultingMesh; +} + //-------------------------------------------------------------------------------- /// Computes the points where the element should be clipped based on the volume fractions of mat one and two. @@ -120,7 +149,7 @@ void InterfaceReconstructor::computeClippingPoints(const int eID, const int matO void InterfaceReconstructor::computeQuadClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh, CellData& out_cellData) { - printf(" Processing QUAD element %d: ", eID); + // printf(" Processing QUAD element %d: ", eID); // Determine the clipping case auto elementVertices = tempMesh->bdry[eID]; @@ -130,8 +159,8 @@ void InterfaceReconstructor::computeQuadClippingPoints(const int eID, const int int lowerRightVertex = elementVertices[2]; int upperRightVertex = elementVertices[3]; - unsigned int caseIndex = determineQuadClippingCase(eID, tempMesh, matOneID, matTwoID, upperLeftVertex, lowerLeftVertex, lowerRightVertex, upperRightVertex); - printf("caseIndex: %d\n", caseIndex); + unsigned int caseIndex = determineQuadClippingCase(tempMesh, matOneID, matTwoID, upperLeftVertex, lowerLeftVertex, lowerRightVertex, upperRightVertex); + // printf("caseIndex: %d\n", caseIndex); // Generate new elements std::map > newElements; // hashmap of the new elements' vertices | Note: maps are ordered sets @@ -200,7 +229,7 @@ void InterfaceReconstructor::computeQuadClippingPoints(const int eID, const int for (auto itr = newElements.begin(); itr != newElements.end(); itr++) { int currentDominantMat = NULL_MAT; - for (int it = 0; it < itr->second.size(); ++it) + for (unsigned long it = 0; it < itr->second.size(); ++it) { // int temp_vID = newElements[currentElementIndex][it]; int temp_vID = itr->second[it]; @@ -264,7 +293,7 @@ void InterfaceReconstructor::computeQuadClippingPoints(const int eID, const int //-------------------------------------------------------------------------------- /// Finds the bit map representing the clipping case for a quad. -unsigned int InterfaceReconstructor::determineQuadClippingCase(const int eID, mir::MIRMesh* tempMesh, const int matOneID, const int matTwoID, const int upperLeftVertex, const int lowerLeftVertex, const int lowerRightVertex, const int upperRightVertex) +unsigned int InterfaceReconstructor::determineQuadClippingCase(mir::MIRMesh* tempMesh, const int matOneID, const int matTwoID, const int upperLeftVertex, const int lowerLeftVertex, const int lowerRightVertex, const int upperRightVertex) { // Determine the dominant color at each vertex int upperLeftColor = matOneID, lowerLeftColor = matOneID, lowerRightColor = matOneID, upperRightColor = matOneID; @@ -331,12 +360,6 @@ axom::float64 InterfaceReconstructor::computeClippingPointOnEdge(const int verte axom::float64 numerator = vfMatTwoVertexOne - vfMatOneVertexOne; axom::float64 denominator = -vfMatOneVertexOne + vfMatOneVertexTwo + vfMatTwoVertexOne - vfMatTwoVertexTwo; - - // axom::float64 numerator = originalMesh->materialVolumeFractionsVertex[matTwoID][vertexOneID] - originalMesh->materialVolumeFractionsVertex[matOneID][vertexOneID]; - // axom::float64 denominator = -originalMesh->materialVolumeFractionsVertex[matOneID][vertexOneID] - // + originalMesh->materialVolumeFractionsVertex[matOneID][vertexTwoID] - // + originalMesh->materialVolumeFractionsVertex[matTwoID][vertexOneID] - // - originalMesh->materialVolumeFractionsVertex[matTwoID][vertexTwoID]; if (denominator != 0.0) ret = numerator / denominator; @@ -470,8 +493,6 @@ void InterfaceReconstructor::generateVertexVolumeFractionsFromQuad(std::mapbdry[eID]; @@ -479,8 +500,7 @@ void InterfaceReconstructor::computeTriangleClippingPoints(const int eID, const int lowerLeftVertex = elementVertices[1]; int lowerRightVertex = elementVertices[2]; - unsigned int caseIndex = determineTriangleClippingCase(eID, tempMesh, matOneID, matTwoID, upperVertex, lowerLeftVertex, lowerRightVertex); - printf("caseIndex: %d\n", caseIndex); + unsigned int caseIndex = determineTriangleClippingCase(tempMesh, matOneID, matTwoID, upperVertex, lowerLeftVertex, lowerRightVertex); // Generate new elements std::map > newElements; // hashmap of the new elements' vertices | Note: maps are ordered sets @@ -545,7 +565,7 @@ void InterfaceReconstructor::computeTriangleClippingPoints(const int eID, const for (auto itr = newElements.begin(); itr != newElements.end(); itr++) { int currentDominantMat = NULL_MAT; - for (int it = 0; it < itr->second.size(); ++it) + for (unsigned long it = 0; it < itr->second.size(); ++it) { int temp_vID = itr->second[it]; @@ -608,7 +628,7 @@ void InterfaceReconstructor::computeTriangleClippingPoints(const int eID, const //-------------------------------------------------------------------------------- /// Finds the bit map representing the clipping case for a triangle. -unsigned int InterfaceReconstructor::determineTriangleClippingCase(const int eID, mir::MIRMesh* tempMesh, const int matOneID, const int matTwoID, const int upperVertex, const int lowerLeftVertex, const int lowerRightVertex) +unsigned int InterfaceReconstructor::determineTriangleClippingCase(mir::MIRMesh* tempMesh, const int matOneID, const int matTwoID, const int upperVertex, const int lowerLeftVertex, const int lowerRightVertex) { // Determine the dominant color at each vertex int upperColor = matOneID, lowerLeftColor = matOneID, lowerRightColor = matOneID; diff --git a/src/axom/mir/InterfaceReconstructor.hpp b/src/axom/mir/InterfaceReconstructor.hpp index b5e80817db..8d0f5d3aa8 100644 --- a/src/axom/mir/InterfaceReconstructor.hpp +++ b/src/axom/mir/InterfaceReconstructor.hpp @@ -11,7 +11,7 @@ #include "MIRMesh.hpp" #include "CellData.hpp" -#include "ZooBitMaps.hpp" +#include "ZooClippingTables.hpp" #include namespace numerics = axom::numerics; @@ -25,10 +25,10 @@ namespace mir { public: InterfaceReconstructor(); - InterfaceReconstructor(mir::MIRMesh* _mesh); ~InterfaceReconstructor(); - mir::MIRMesh computeReconstructedInterface(); + mir::MIRMesh computeReconstructedInterface(mir::MIRMesh* inputMesh); + mir::MIRMesh computeReconstructedInterfaceIterative(mir::MIRMesh* inputMesh, int numIterations, axom::float64 percent); private: mir::MIRMesh* originalMesh; @@ -50,13 +50,13 @@ namespace mir axom::float64 computeClippingPointOnEdge(const int vertexOneID, const int vertexTwoID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh); // quad clipping points helper functions - unsigned int determineQuadClippingCase(const int eID, mir::MIRMesh* tempMesh, const int matOneID, const int matTwoID, const int upperLeftVertex, const int lowerLeftVertex, const int lowerRightVertex, const int upperRightVertex); + unsigned int determineQuadClippingCase(mir::MIRMesh* tempMesh, const int matOneID, const int matTwoID, const int upperLeftVertex, const int lowerLeftVertex, const int lowerRightVertex, const int upperRightVertex); void generateTopologyData(std::map > newElements, std::map > newVertices, CellData& out_cellData); void generateVertexPositionsFromQuad(std::map > newVertices, mir::MIRMesh* tempMesh, axom::float64* verticesClippingTValue, int upperLeftVertex, int lowerLeftVertex, int lowerRightVertex, int upperRightVertex, CellData& out_cellData); void generateVertexVolumeFractionsFromQuad(std::map > newVertices, mir::MIRMesh* tempMesh, axom::float64* verticesClippingTValue, int upperLeftVertex, int lowerLeftVertex, int lowerRightVertex, int upperRightVertex, CellData& out_cellData); // triangle clipping points helper functions - unsigned int determineTriangleClippingCase(const int eID, mir::MIRMesh* tempMesh, const int matOneID, const int matTwoID, const int upperVertex, const int lowerLeftVertex, const int lowerRightVertex); + unsigned int determineTriangleClippingCase(mir::MIRMesh* tempMesh, const int matOneID, const int matTwoID, const int upperVertex, const int lowerLeftVertex, const int lowerRightVertex); void generateVertexPositionsFromTriangle(std::map > newVertices, mir::MIRMesh* tempMesh, axom::float64* verticesClippingTValue, int upperVertex, int lowerLeftVertex, int lowerRightVertex, CellData& out_cellData); void generateVertexVolumeFractionsFromTriangle(std::map > newVertices, mir::MIRMesh* tempMesh, axom::float64* verticesClippingTValue, int upperVertex, int lowerLeftVertex, int lowerRightVertex, CellData& out_cellData); }; diff --git a/src/axom/mir/MIRMesh.cpp b/src/axom/mir/MIRMesh.cpp index b851348720..b116550666 100644 --- a/src/axom/mir/MIRMesh.cpp +++ b/src/axom/mir/MIRMesh.cpp @@ -96,51 +96,55 @@ namespace mir SLIC_INFO("Vert-Elem relation has size " << cobdry.totalSize()); } -//-------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------- + +/// Construct the and the element and vertex volume fraction data given the element volume fraction data +void MIRMesh::constructMeshVolumeFractionsMaps(std::vector > elementVF) +{ + // Clear the old maps + materialVolumeFractionsElement.clear(); + materialVolumeFractionsVertex.clear(); - /// Constructs the volume fraction maps on the elements and vertices given element volume fraction data. - void MIRMesh::constructMeshVolumeFractionMaps(std::vector materialVolumeFractionsData) + // Initialize the maps for all of the materials with the input volume fraction data for each material + for (int matID = 0; matID < numMaterials; ++matID) { - // Initialize the maps for all of the materials with the input volume fraction data for each material - for (int matID = 0; matID < materialVolumeFractionsData.size(); ++matID) + // Initialize the map for the current material + materialVolumeFractionsElement.push_back(ScalarMap( &elems )); + + // Copy the data for the current material + for (int eID = 0; eID < elems.size(); ++eID) { - // Initialize the map for the current material - materialVolumeFractionsElement.push_back(ScalarMap( &elems )); + materialVolumeFractionsElement[matID][eID] = elementVF[matID][eID]; + } - // Copy the data for the current material - for (int eID = 0; eID < elems.size(); ++eID) - { - materialVolumeFractionsElement[matID][eID] = materialVolumeFractionsData[matID][eID]; - } + SLIC_ASSERT_MSG( materialVolumeFractionsElement[matID].isValid(), "Element volume fraction map is not valid."); + } - SLIC_ASSERT_MSG( materialVolumeFractionsElement[matID].isValid(), "Element volume fraction map is not valid."); - } + // Initialize the maps for all of the vertex volume fractions + for (int matID = 0; matID < numMaterials; ++matID) + { + // Initialize the new map for the volume fractions + materialVolumeFractionsVertex.push_back(ScalarMap( &verts ) ); - // Initialize the maps for all of the vertex volume fractions - for (int matID = 0; matID < materialVolumeFractionsData.size(); ++matID) + // Calculate the average volume fraction value for the current vertex for the current material + for (int vID = 0; vID < verts.size(); ++vID) { - // Initialize the new map for the volume fractions - materialVolumeFractionsVertex.push_back(ScalarMap( &verts ) ); + // Compute the per vertex volume fractions for the green material + axom::float64 sum = 0; + auto vertexElements = cobdry[vID]; - // Calculate the average volume fraction value for the current vertex for the current material - for (int vID = 0; vID < verts.size(); ++vID) + for (int i = 0; i < vertexElements.size(); ++i) { - // Compute the per vertex volume fractions for the green material - axom::float64 sum = 0; - auto vertexElements = cobdry[vID]; - - for (int i = 0; i < vertexElements.size(); ++i) - { - auto eID = vertexElements[i]; - sum += materialVolumeFractionsElement[matID][eID]; - } - - materialVolumeFractionsVertex[matID][vID] = sum / vertexElements.size(); + auto eID = vertexElements[i]; + sum += materialVolumeFractionsElement[matID][eID]; } - SLIC_ASSERT_MSG( materialVolumeFractionsVertex[matID].isValid(), "Vertex volume fraction map is not valid."); + materialVolumeFractionsVertex[matID][vID] = sum / vertexElements.size(); } + + SLIC_ASSERT_MSG( materialVolumeFractionsVertex[matID].isValid(), "Vertex volume fraction map is not valid."); } +} //-------------------------------------------------------------------------------- @@ -209,30 +213,7 @@ void MIRMesh::constructMeshVolumeFractionsVertex(std::vector > MIRMesh::computeOriginalElementVolumeFractions() { std::map totalAreaOriginalElements; // the total area of the original elements std::map newElementAreas; // the area of each of the generated child elements @@ -413,7 +394,7 @@ void MIRMesh::computeOriginalElementVolumeFractions() // Intialize the element volume fraction vectors std::vector > elementVolumeFractions; // indexed as: elementVolumeFractions[material][originalElementID] = volumeFraction elementVolumeFractions.resize( numMaterials ); - for (int i = 0; i < elementVolumeFractions.size(); ++i) + for (unsigned long i = 0; i < elementVolumeFractions.size(); ++i) elementVolumeFractions[i].resize( totalAreaOriginalElements.size(), 0.0 ); // Compute the volume fractions for each of the original mesh elements @@ -424,18 +405,7 @@ void MIRMesh::computeOriginalElementVolumeFractions() elementVolumeFractions[materialID][parentElementID] += (itr->second / totalAreaOriginalElements[parentElementID]); } - // Print out the results // TODO: Return the values and use them. - printf("elementVolumeFractions: {\n"); - for (int matID = 0; matID < elementVolumeFractions.size(); ++matID) - { - printf("Material %d: {", matID); - for (int eID = 0; eID < elementVolumeFractions[matID].size(); ++eID) - { - printf(" %f,", elementVolumeFractions[matID][eID]); - } - printf("}\n"); - } - printf("}\n"); + return elementVolumeFractions; } //-------------------------------------------------------------------------------- @@ -455,7 +425,7 @@ axom::float64 MIRMesh::computeTriangleArea(Point2 p0, Point2 p1, Point2 p2) //-------------------------------------------------------------------------------- /// Computes the area of the quad defined by the given four vertex positions. -/// Note: It is assumed the points are given in consecutive order. +/// Note: It is assumed the points are given in consecutive, counter-clockwise order. axom::float64 MIRMesh::computeQuadArea(Point2 p0, Point2 p1, Point2 p2, Point2 p3) { return computeTriangleArea(p0, p1, p2) + computeTriangleArea(p2, p3, p0); diff --git a/src/axom/mir/MIRMesh.hpp b/src/axom/mir/MIRMesh.hpp index 2145ce3172..eb66116345 100644 --- a/src/axom/mir/MIRMesh.hpp +++ b/src/axom/mir/MIRMesh.hpp @@ -30,15 +30,6 @@ namespace mir #define NULL_MAT -1 - struct EdgeClipInfo - { - int vertexOne; - int vertexTwo; - int colorOne; - int colorTwo; - float t; // The percent distance from vertexOne to VertexTwo where the clip occurs. - }; - //-------------------------------------------------------------------------------- class MIRMesh @@ -54,23 +45,19 @@ namespace mir void InitializeMesh(std::vector _evInds, std::vector _evBegins, std::vector _veInds, std::vector _veBegins, VertSet _verts, ElemSet _elems, int _numMaterials); void constructMeshRelations(); /// Constructs the mesh boundary and coboundary relations - void constructMeshVolumeFractionMaps(std::vector materialVolumeFractionsData); /// Constructs the volume fraction maps on the vertices + void constructMeshVolumeFractionsMaps(std::vector > elementVF); /// Constructs the element and vertex volume fraction maps void constructMeshVolumeFractionsVertex(std::vector > vertexVF); /// Constructs the vertex volume fraction map void constructVertexPositionMap(Point2* data); /// Constucts the positions map on the vertices void constructElementParentMap(int* cellParents); /// Constructs the map of cells to their original cell parent void constructElementDominantMaterialMap(std::vector dominantMaterials); /// Constructs the map of elements to their dominant materials - void printElementScalarMap(ScalarMap& elements, std::string prefix); /// Prints out the map values for each element - void printVertexScalarMap(ScalarMap& vertices, std::string prefix); /// Prints out the map values for each vertex - void print(); // Print out a variety of useful information about the mesh void readMeshFromFile(std::string filename); /// Reads in and constructs a mesh from a file void writeMeshToFile(std::string filename); /// Writes out the current mesh to a file void attachVertexMap(); /// Adds a map of data to the mesh - - void computeOriginalElementVolumeFractions(); + std::vector > computeOriginalElementVolumeFractions(); private: axom::float64 computeTriangleArea(Point2 p0, Point2 p1, Point2 p2); diff --git a/src/axom/mir/MeshTester.cpp b/src/axom/mir/MeshTester.cpp new file mode 100644 index 0000000000..36ed346a7a --- /dev/null +++ b/src/axom/mir/MeshTester.cpp @@ -0,0 +1,771 @@ +// Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "MeshTester.hpp" + +namespace axom +{ +namespace mir +{ + +//-------------------------------------------------------------------------------- + +MeshTester::MeshTester() +{ + +} + +//-------------------------------------------------------------------------------- + +MeshTester::~MeshTester() +{ + +} + +//-------------------------------------------------------------------------------- + +/// Initialize mesh for Test Case 1 (from Meredith 2004) +MIRMesh MeshTester::initTestCaseOne() +{ + int numElements = 9; + int numVertices = 16; + + // Create the mesh connectivity information + std::vector evInds = { + 0,4,5,1, // elem 0, card 4, start 0 + 1,5,6,2, // elem 1, card 4, start 4 + 2,6,7,3, // elem 2, card 4, start 8 + 4,8,9,5, // elem 3, card 4, start 12 + 5,9,10,6, // elem 4, card 4, start 16 + 6,10,11,7, // elem 5, card 4, start 20 + 8,12,13,9, // elem 6, card 4, start 24 + 9,13,14,10, // elem 7, card 4, start 28 + 10,14,15,11 // elem 8, card 4, start 32, end 36 + }; + + std::vector evBegins = { + 0,4,8,12,16,20,24,28,32,36 + }; + std::vector veInds = { + 0, // vert 0, card 1, start 0 + 0,1, // vert 1, card 2, start 1 + 1,2, // vert 2, card 2, start 3 + 2, // vert 3, card 1, start 5 + 0,3, // vert 4, card 2, start 6 + 0,1,3,4, // vert 5, card 4, start 8 + 1,2,4,5, // vert 6, card 4, start 12 + 2,5, // vert 7, card 2, start 16 + 3,6, // vert 8, card 2, start 18 + 3,4,6,7, // vert 9, card 4, start 20 + 4,5,7,8, // vert 10, card 4, start 24 + 5,8, // vert 11, card 2, start 28 + 6, // vert 12, card 1, start 30 + 6,7, // vert 13, card 2, start 31 + 7,8, // vert 14, card 2, start 33 + 8, // vert 15, card 1, start 35, end 36 + }; + std::vector veBegins = { + 0,1,3,5,6,8,12,16,18,20,24,28,30,31,33,35,36 + }; + + mir::VertSet verts = mir::VertSet(numVertices); // Construct a vertex set with 16 vertices + mir::ElemSet elems = mir::ElemSet(numElements); // Construct an element set with 9 elements + + + int numMaterials = 2; + enum { GREEN = 0, BLUE = 1 }; + + std::vector > elementVF; elementVF.resize(numMaterials); + + std::vector greenVolumeFractions = {1.0, 1.0, 1.0, 1.0, 0.5, 0.2, 0.2, 0.0, 0.0}; + elementVF[GREEN] = greenVolumeFractions; + std::vector blueVolumeFractions = {0.0, 0.0, 0.0, 0.0, 0.5, 0.8, 0.8, 1.0, 1.0}; + elementVF[BLUE] = blueVolumeFractions; + + mir::Point2 points[numVertices]; + { + points[0] = mir::Point2( 0.0, 3.0 ); + points[1] = mir::Point2( 1.0, 3.0 ); + points[2] = mir::Point2( 2.0, 3.0 ); + points[3] = mir::Point2( 3.0, 3.0 ); + + points[4] = mir::Point2( 0.0, 2.0 ); + points[5] = mir::Point2( 1.0, 2.0 ); + points[6] = mir::Point2( 2.0, 2.0 ); + points[7] = mir::Point2( 3.0, 2.0 ); + + points[8] = mir::Point2( 0.0, 1.0 ); + points[9] = mir::Point2( 1.0, 1.0 ); + points[10] = mir::Point2( 2.0, 1.0 ); + points[11] = mir::Point2( 3.0, 1.0 ); + + points[12] = mir::Point2( 0.0, 0.0 ); + points[13] = mir::Point2( 1.0, 0.0 ); + points[14] = mir::Point2( 2.0, 0.0 ); + points[15] = mir::Point2( 3.0, 0.0 ); + } + + int elementParents[9] = { 0,1,2,3,4,5,6,7,8 }; // For the base mesh, the parents are always themselves + + std::vector elementDominantMaterials = {NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT}; + + // Build the mesh + mir::MIRMesh testMesh; + testMesh.InitializeMesh(evInds, evBegins, veInds, veBegins, verts, elems, numMaterials); + testMesh.constructMeshRelations(); + testMesh.constructMeshVolumeFractionsMaps(elementVF); + testMesh.constructVertexPositionMap(points); + testMesh.constructElementParentMap(elementParents); + testMesh.constructElementDominantMaterialMap(elementDominantMaterials); + + return testMesh; +} + +//-------------------------------------------------------------------------------- + +/// Initialize mesh for Test Case 2 (from Meredith and Childs 2010) +mir::MIRMesh MeshTester::initTestCaseTwo() +{ + int numElements = 9; + int numVertices = 16; + + // Create the mesh connectivity information + std::vector evInds = { + 0,4,5,1, // elem 0, card 4, start 0 + 1,5,6,2, // elem 1, card 4, start 4 + 2,6,7,3, // elem 2, card 4, start 8 + 4,8,9,5, // elem 3, card 4, start 12 + 5,9,10,6, // elem 4, card 4, start 16 + 6,10,11,7, // elem 5, card 4, start 20 + 8,12,13,9, // elem 6, card 4, start 24 + 9,13,14,10, // elem 7, card 4, start 28 + 10,14,15,11 // elem 8, card 4, start 32, end 36 + }; + + std::vector evBegins = { + 0,4,8,12,16,20,24,28,32,36 + }; + std::vector veInds = { + 0, // vert 0, card 1, start 0 + 0,1, // vert 1, card 2, start 1 + 1,2, // vert 2, card 2, start 3 + 2, // vert 3, card 1, start 5 + 0,3, // vert 4, card 2, start 6 + 0,1,3,4, // vert 5, card 4, start 8 + 1,2,4,5, // vert 6, card 4, start 12 + 2,5, // vert 7, card 2, start 16 + 3,6, // vert 8, card 2, start 18 + 3,4,6,7, // vert 9, card 4, start 20 + 4,5,7,8, // vert 10, card 4, start 24 + 5,8, // vert 11, card 2, start 28 + 6, // vert 12, card 1, start 30 + 6,7, // vert 13, card 2, start 31 + 7,8, // vert 14, card 2, start 33 + 8, // vert 15, card 1, start 35, end 36 + }; + std::vector veBegins = { + 0,1,3,5,6,8,12,16,18,20,24,28,30,31,33,35,36 + }; + + mir::VertSet verts = mir::VertSet(numVertices); // Construct a vertex set with 16 vertices + mir::ElemSet elems = mir::ElemSet(numElements); // Construct an element set with 9 elements + + int numMaterials = 3; + enum { BLUE = 0, RED = 1, ORANGE = 2 }; + + std::vector > elementVF; elementVF.resize(numMaterials); + + std::vector blueVolumeFractions = {1.0, 1.0, 1.0, 1.0, 0.5, 0.2, 0.2, 0.0, 0.0}; + elementVF[BLUE] = blueVolumeFractions; + std::vector redVolumeFractions = {0.0, 0.0, 0.0, 0.0, 0.3, 0.8, 0.0, 0.3, 1.0}; + elementVF[RED] = redVolumeFractions; + std::vector orangeVolumeFractions = {0.0, 0.0, 0.0, 0.0, 0.2, 0.0, 0.8, 0.7, 0.0}; + elementVF[ORANGE] = orangeVolumeFractions; + + mir::Point2 points[numVertices]; + { + points[0] = mir::Point2( 0.0, 3.0 ); + points[1] = mir::Point2( 1.0, 3.0 ); + points[2] = mir::Point2( 2.0, 3.0 ); + points[3] = mir::Point2( 3.0, 3.0 ); + + points[4] = mir::Point2( 0.0, 2.0 ); + points[5] = mir::Point2( 1.0, 2.0 ); + points[6] = mir::Point2( 2.0, 2.0 ); + points[7] = mir::Point2( 3.0, 2.0 ); + + points[8] = mir::Point2( 0.0, 1.0 ); + points[9] = mir::Point2( 1.0, 1.0 ); + points[10] = mir::Point2( 2.0, 1.0 ); + points[11] = mir::Point2( 3.0, 1.0 ); + + points[12] = mir::Point2( 0.0, 0.0 ); + points[13] = mir::Point2( 1.0, 0.0 ); + points[14] = mir::Point2( 2.0, 0.0 ); + points[15] = mir::Point2( 3.0, 0.0 ); + } + + + int elementParents[9] = { 0,1,2,3,4,5,6,7,8 }; // For the base mesh, the parents are always themselves + + std::vector elementDominantMaterials = {NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT}; + + // Build the mesh + mir::MIRMesh testMesh; + testMesh.InitializeMesh(evInds, evBegins, veInds, veBegins, verts, elems, numMaterials); + testMesh.constructMeshRelations(); + testMesh.constructMeshVolumeFractionsMaps(elementVF); + testMesh.constructVertexPositionMap(points); + testMesh.constructElementParentMap(elementParents); + testMesh.constructElementDominantMaterialMap(elementDominantMaterials); + + return testMesh; +} + +//-------------------------------------------------------------------------------- + +/// Initialize mesh for Test Case 3, which tests all the triangle clipping cases. +mir::MIRMesh MeshTester::initTestCaseThree() +{ + int numElements = 4; + int numVertices = 6; // OR create a middle triangle with all of one material, and then a ring of triangles around it that are full of the other material + + // Create the mesh connectivity information + std::vector evInds = { + 0,1,2, // elem 0, card 3, start 0 + 1,3,4, // elem 1, card 3, start 3 + 1,4,2, // elem 2, card 3, start 6 + 2,4,5 // elem 3, card 3, start 9, end 12 + }; + + std::vector evBegins = { + 0,3,6,9,12 + }; + std::vector veInds = { + 0, // vert 0, card 1, start 0 + 0,1,2, // vert 1, card 3, start 1 + 0,2,3, // vert 2, card 3, start 4 + 1, // vert 3, card 1, start 7 + 1,2,3, // vert 4, card 3, start 8 + 3 // vert 5, card 1, start 11, end 12 + }; + std::vector veBegins = { + 0,1,4,7,8,11,12 + }; + + mir::VertSet verts = mir::VertSet(numVertices); // Construct a vertex set with 24 vertices + mir::ElemSet elems = mir::ElemSet(numElements); // Construct an element set with 8 elements + + int numMaterials = 2; + enum { BLUE = 0, RED = 1, }; + + std::vector > elementVF; elementVF.resize(numMaterials); + + std::vector blueVolumeFractions = {0.0, 0.5, 0.8, 0.5}; + elementVF[BLUE] = blueVolumeFractions; + std::vector redVolumeFractions = {1.0, 0.5, 0.2, 0.5}; + elementVF[RED] = redVolumeFractions; + + mir::Point2 points[numVertices]; + { + points[0] = mir::Point2( 1.0, 2.0 ); + points[1] = mir::Point2( 0.5, 1.0 ); + points[2] = mir::Point2( 1.5, 1.0 ); + points[3] = mir::Point2( 0.0, 0.0 ); + points[4] = mir::Point2( 1.0, 0.0 ); + points[5] = mir::Point2( 2.0, 0.0 ); + } + + + int elementParents[4] = { 0,1,2,3 }; // For the base mesh, the parents are always themselves + + std::vector elementDominantMaterials = {NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT}; + + // Build the mesh + mir::MIRMesh testMesh; + testMesh.InitializeMesh(evInds, evBegins, veInds, veBegins, verts, elems, numMaterials); + testMesh.constructMeshRelations(); + testMesh.constructMeshVolumeFractionsMaps(elementVF); + testMesh.constructVertexPositionMap(points); + testMesh.constructElementParentMap(elementParents); + testMesh.constructElementDominantMaterialMap(elementDominantMaterials); + + return testMesh; +} + +//-------------------------------------------------------------------------------- + +/// Initialize mesh for Test Case 4, a 3x3 grid with a circle of one material in the middle +mir::MIRMesh MeshTester::initTestCaseFour() +{ + int numElements = 9; + int numVertices = 16; + + // Create the mesh connectivity information + std::vector evInds = { + 0,4,5,1, // elem 0, card 4, start 0 + 1,5,6,2, // elem 1, card 4, start 4 + 2,6,7,3, // elem 2, card 4, start 8 + 4,8,9,5, // elem 3, card 4, start 12 + 5,9,10,6, // elem 4, card 4, start 16 + 6,10,11,7, // elem 5, card 4, start 20 + 8,12,13,9, // elem 6, card 4, start 24 + 9,13,14,10, // elem 7, card 4, start 28 + 10,14,15,11 // elem 8, card 4, start 32, end 36 + }; + + std::vector evBegins = { + 0,4,8,12,16,20,24,28,32,36 + }; + std::vector veInds = { + 0, // vert 0, card 1, start 0 + 0,1, // vert 1, card 2, start 1 + 1,2, // vert 2, card 2, start 3 + 2, // vert 3, card 1, start 5 + 0,3, // vert 4, card 2, start 6 + 0,1,3,4, // vert 5, card 4, start 8 + 1,2,4,5, // vert 6, card 4, start 12 + 2,5, // vert 7, card 2, start 16 + 3,6, // vert 8, card 2, start 18 + 3,4,6,7, // vert 9, card 4, start 20 + 4,5,7,8, // vert 10, card 4, start 24 + 5,8, // vert 11, card 2, start 28 + 6, // vert 12, card 1, start 30 + 6,7, // vert 13, card 2, start 31 + 7,8, // vert 14, card 2, start 33 + 8, // vert 15, card 1, start 35, end 36 + }; + std::vector veBegins = { + 0,1,3,5,6,8,12,16,18,20,24,28,30,31,33,35,36 + }; + + mir::VertSet verts = mir::VertSet(numVertices); // Construct a vertex set with 16 vertices + mir::ElemSet elems = mir::ElemSet(numElements); // Construct an element set with 9 elements + + mir::Point2 points[numVertices]; + { + points[0] = mir::Point2( 0.0, 3.0 ); + points[1] = mir::Point2( 1.0, 3.0 ); + points[2] = mir::Point2( 2.0, 3.0 ); + points[3] = mir::Point2( 3.0, 3.0 ); + + points[4] = mir::Point2( 0.0, 2.0 ); + points[5] = mir::Point2( 1.0, 2.0 ); + points[6] = mir::Point2( 2.0, 2.0 ); + points[7] = mir::Point2( 3.0, 2.0 ); + + points[8] = mir::Point2( 0.0, 1.0 ); + points[9] = mir::Point2( 1.0, 1.0 ); + points[10] = mir::Point2( 2.0, 1.0 ); + points[11] = mir::Point2( 3.0, 1.0 ); + + points[12] = mir::Point2( 0.0, 0.0 ); + points[13] = mir::Point2( 1.0, 0.0 ); + points[14] = mir::Point2( 2.0, 0.0 ); + points[15] = mir::Point2( 3.0, 0.0 ); + } + + int numMaterials = 2; + enum { GREEN = 0, BLUE = 1 }; + + std::vector > elementVF; elementVF.resize(numMaterials); + + std::vector greenVolumeFractions; greenVolumeFractions.resize(numElements); + std::vector blueVolumeFractions; blueVolumeFractions.resize(numElements); + + // Generate the element volume fractions for the circle + mir::Point2 circleCenter(1.5, 1.5); + axom::float64 circleRadius = 1.25; + int gridSize = 1000; + for (int i = 0; i < numElements; ++i) + { + greenVolumeFractions[i] = calculatePercentOverlapMonteCarlo(gridSize, circleCenter, circleRadius, points[evInds[i * 4 + 0]], points[evInds[i * 4 + 1]], points[evInds[i * 4 + 2]], points[evInds[i * 4 + 3]]); + blueVolumeFractions[i] = 1.0 - greenVolumeFractions[i]; + } + + elementVF[GREEN] = greenVolumeFractions; + elementVF[BLUE] = blueVolumeFractions; + + int elementParents[9] = { 0,1,2,3,4,5,6,7,8 }; // For the base mesh, the parents are always themselves + + std::vector elementDominantMaterials = {NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT}; + + // Build the mesh + mir::MIRMesh testMesh; + testMesh.InitializeMesh(evInds, evBegins, veInds, veBegins, verts, elems, numMaterials); + testMesh.constructMeshRelations(); + testMesh.constructMeshVolumeFractionsMaps(elementVF); + testMesh.constructVertexPositionMap(points); + testMesh.constructElementParentMap(elementParents); + testMesh.constructElementDominantMaterialMap(elementDominantMaterials); + + return testMesh; +} + +//-------------------------------------------------------------------------------- + +/// Intializes a uniform grid with a circle of one material surrounded by another material. +mir::MIRMesh MeshTester::createUniformGridTestCaseMesh(int gridSize, mir::Point2 circleCenter, axom::float64 circleRadius) +{ + // Generate the mesh topology + mir::CellData cellData = generateGrid(gridSize); + + mir::VertSet verts = mir::VertSet(cellData.numVerts); // Construct the vertex set + mir::ElemSet elems = mir::ElemSet(cellData.numElems); // Construct the element set + + int numMaterials = 2; + enum { GREEN = 0, BLUE = 1 }; + + std::vector > elementVF; elementVF.resize(numMaterials); + + std::vector greenVolumeFractions; greenVolumeFractions.resize(cellData.numElems); + std::vector blueVolumeFractions; blueVolumeFractions.resize(cellData.numElems); + + // Generate the element volume fractions for the circle + int numMonteCarloSamples = 100; + for (int i = 0; i < cellData.numElems; ++i) + { + greenVolumeFractions[i] = calculatePercentOverlapMonteCarlo(numMonteCarloSamples, circleCenter, circleRadius, + cellData.vertexPositions[cellData.evInds[i * 4 + 0]], + cellData.vertexPositions[cellData.evInds[i * 4 + 1]], + cellData.vertexPositions[cellData.evInds[i * 4 + 2]], + cellData.vertexPositions[cellData.evInds[i * 4 + 3]]); + blueVolumeFractions[i] = 1.0 - greenVolumeFractions[i]; + } + + elementVF[GREEN] = greenVolumeFractions; + elementVF[BLUE] = blueVolumeFractions; + + int elementParents[cellData.numElems]; // For the base mesh, the parents are always themselves + std::vector elementDominantMaterials; + for (int i = 0; i < cellData.numElems; ++i) + { + elementParents[i] = i; + elementDominantMaterials.push_back(NULL_MAT); + } + + // Build the mesh + mir::MIRMesh testMesh; + testMesh.InitializeMesh(cellData.evInds, cellData.evBegins, cellData.veInds, cellData.veBegins, verts, elems, numMaterials); + testMesh.constructMeshRelations(); + testMesh.constructMeshVolumeFractionsMaps(elementVF); + testMesh.constructVertexPositionMap(cellData.vertexPositions.data()); + testMesh.constructElementParentMap(elementParents); + testMesh.constructElementDominantMaterialMap(elementDominantMaterials); + + return testMesh; + +} + +//-------------------------------------------------------------------------------- + +// Assumes the quad vertices are ordered the same as the clipping case. +axom::float64 MeshTester::calculatePercentOverlapMonteCarlo(int gridSize, mir::Point2 circle_center, axom::float64 circle_radius, mir::Point2 q_p0, mir::Point2 q_p1, mir::Point2 q_p2, mir::Point2 q_p3) +{ + // Check if any of the quad's corners are within the circle + axom::float64 distP0 = distance(q_p0, circle_center); + axom::float64 distP1 = distance(q_p1, circle_center); + axom::float64 distP2 = distance(q_p2, circle_center); + axom::float64 distP3 = distance(q_p3, circle_center); + + if (distP0 < circle_radius && distP1 < circle_radius && distP2 < circle_radius && distP3 < circle_radius) + { + // The entire quad overlaps the circle + return 1.0; + } + else if (distP0 < circle_radius || distP1 < circle_radius || distP2 < circle_radius || distP3 < circle_radius) + { + // Some of the quad overlaps the circle, so run the Monte Carlo sampling to determine how much + axom::float64 delta_x = abs(q_p2.m_x - q_p1.m_x) / (double) (gridSize - 1); + axom::float64 delta_y = abs(q_p0.m_y - q_p1.m_y) / (double) (gridSize - 1); + int countOverlap = 0; + for (int y = 0; y < gridSize; ++y) + { + for (int x = 0; x < gridSize; ++x) + { + mir::Point2 samplePoint(delta_x * x + q_p1.m_x, delta_y * y + q_p1.m_y); + if (distance(samplePoint, circle_center) < circle_radius) + ++countOverlap; + } + } + return countOverlap / (double) (gridSize * gridSize); + } + else + { + // None of the quad overlaps the circle + return 0; + } +} + +//-------------------------------------------------------------------------------- + +/// Generates a 2D uniform grid with n x n elements. +mir::CellData MeshTester::generateGrid(int n) +{ + // Generate the topology for a uniform quad mesh with n x n elements automatically + int numElements = n * n; + int numVertices = (n + 1) * (n + 1); + + // Generate the evInds + std::vector evInds; + for (int eID = 0; eID < numElements; ++eID) + { + int row = eID / n; // note the integer division + int vertsPerRow = n + 1; + int elemsPerRow = n; + + evInds.push_back( (eID % elemsPerRow) + row * vertsPerRow + 0); + evInds.push_back( (eID % elemsPerRow) + (row + 1) * vertsPerRow + 0); + evInds.push_back( (eID % elemsPerRow) + (row + 1) * vertsPerRow + 1); + evInds.push_back( (eID % elemsPerRow) + row * vertsPerRow + 1); + } + + // Generate the evBegins + std::vector evBegins; + evBegins.push_back(0); + for (int i = 0; i < n * n; ++i) + { + evBegins.push_back((i + 1) * 4); + } + + // Generate the veInds + std::map > veInds_data; + std::vector veInds; + for (int evInd_itr = 0; evInd_itr < numElements * 4; ++evInd_itr) + { + int currentElementID = evInd_itr / 4; // note the integer division + veInds_data[evInds[evInd_itr]].push_back(currentElementID); + } + + for (auto itr = veInds_data.begin(); itr != veInds_data.end(); itr++) + { + // Sort the vector + std::sort(itr->second.begin(), itr->second.end()); + + // Add the elements associated with the current vertex to veInds + for (unsigned long i = 0; i < itr->second.size(); ++i) + veInds.push_back(itr->second[i]); + } + + // Generate the veBegins + std::vector veBegins; + veBegins.push_back(0); + int currentIndexCount = 0; + for (auto itr = veInds_data.begin(); itr != veInds_data.end(); itr++) + { + currentIndexCount += itr->second.size(); + veBegins.push_back(currentIndexCount); + } + + // Generate the vertex positions + std::vector points; + for (int y = n; y > -1; --y) + { + for (int x = 0; x < n + 1; ++x) + { + points.push_back(mir::Point2(x, y)); + } + } + + mir::CellData data; + data.numVerts = numVertices; + data.numElems = numElements; + data.evInds = evInds; + data.evBegins = evBegins; + data.veInds = veInds; + data.veBegins = veBegins; + data.vertexPositions = points; + + // // Print out the results + // printf("evInds: { "); + // for (int i = 0; i < evInds.size(); i++) + // { + // printf("%d ", evInds[i]); + // if ((i+1) % 4 == 0 && i != 0) + // printf("\n"); + // } + // printf("}\n"); + + // printf("evBegins: { "); + // for (int i = 0; i < evBegins.size(); i++) + // { + // printf("%d ", evBegins[i]); + // } + // printf("}\n"); + + // printf("veInds: { "); + // for (int i = 0; i < veInds.size(); i++) + // { + // printf("%d ", veInds[i]); + // } + // printf("}\n"); + + // printf("veBegins: { "); + // for (int i = 0; i < veBegins.size(); i++) + // { + // printf("%d ", veBegins[i]); + // } + // printf("}\n"); + + // printf("points: { "); + // for (int i = 0; i < numVertices; ++i) + // { + // printf("{%.2f, %.2f} ", points[i].m_x, points[i].m_y); + // } + // printf("}\n"); + + return data; +} + +//-------------------------------------------------------------------------------- + +/// Calculate the distance between the two given points. +axom::float64 MeshTester::distance(mir::Point2 p0, mir::Point2 p1) +{ + return sqrt( ((p1.m_x - p0.m_x) * (p1.m_x - p0.m_x)) + ((p1.m_y - p0.m_y) * (p1.m_y - p0.m_y)) ); +} + +//-------------------------------------------------------------------------------- + +/// Multiple materials, multiple concentric circles. +/// Note: Assumes each circle has a unique material. +mir::MIRMesh MeshTester::initTestCaseFive(int gridSize, int numCircles) +{ + + // Generate the mesh topology + mir::CellData cellData = generateGrid(gridSize); + + mir::VertSet verts = mir::VertSet(cellData.numVerts); // Construct the vertex set + mir::ElemSet elems = mir::ElemSet(cellData.numElems); // Construct the element set + + // Generate the element volume fractions with concentric circles + int numMaterials = numCircles + 1; + int defaultMaterialID = numMaterials - 1; // default material is always the last index + + mir::Point2 circleCenter(gridSize / 2.0, gridSize / 2.0); // all circles are centered around the same point + + // Initialize the radii of the circles + std::vector circleRadii; + axom::float64 maxRadius = gridSize / 2.4; // Note: The choice of divisor is arbitrary + axom::float64 minRadius = gridSize / 8; // Note: The choice of divisor is arbitrary + + axom::float64 radiusDelta; + if (numCircles <= 1) + radiusDelta = (maxRadius - minRadius); + else + radiusDelta = (maxRadius - minRadius) / (double) (numCircles - 1); + + for (int i = 0; i < numCircles; ++i) + { + circleRadii.push_back( minRadius + (i * radiusDelta) ); + } + + // Initialize all material volume fractions to 0 + std::vector > materialVolumeFractionsData; + for (int i = 0; i < numMaterials; ++i) + { + std::vector tempVec; + tempVec.resize(cellData.numElems); + materialVolumeFractionsData.push_back(tempVec); + } + + // Use the uniform sampling method to generate volume fractions for each material + for (int eID = 0; eID < cellData.numElems; ++eID) + { + mir::Point2 v0 = cellData.vertexPositions[cellData.evInds[eID * 4 + 0]]; + mir::Point2 v1 = cellData.vertexPositions[cellData.evInds[eID * 4 + 1]]; + mir::Point2 v2 = cellData.vertexPositions[cellData.evInds[eID * 4 + 2]]; + mir::Point2 v3 = cellData.vertexPositions[cellData.evInds[eID * 4 + 3]]; + + // Run the uniform sampling to determine how much of the current cell is composed of each material + int materialCount[numMaterials]; for (int i = 0; i < numMaterials; ++i) materialCount[i] = 0; + + for (int matID = 0; matID < numMaterials; ++matID) + { + materialVolumeFractionsData[matID][eID] = materialCount[matID] / (double) (gridSize * gridSize); + } + + axom::float64 delta_x = abs(v2.m_x - v1.m_x) / (double) (gridSize - 1); + axom::float64 delta_y = abs(v0.m_y - v1.m_y) / (double) (gridSize - 1); + + for (int y = 0; y < gridSize; ++y) + { + for (int x = 0; x < gridSize; ++x) + { + mir::Point2 samplePoint(delta_x * x + v1.m_x, delta_y * y + v1.m_y); + bool isPointSampled = false; + for (int cID = 0; cID < numCircles && !isPointSampled; ++cID) + { + if (distance(samplePoint, circleCenter) < circleRadii[cID]) + { + materialCount[cID]++; + isPointSampled = true; + } + } + if (!isPointSampled) + { + // The point was not within any of the circles, so increment the count for the default material + materialCount[defaultMaterialID]++; + } + } + } + + // Assign the element volume fractions based on the count of the samples in each circle + for (int matID = 0; matID < numMaterials; ++matID) + { + materialVolumeFractionsData[matID][eID] = materialCount[matID] / (double) (gridSize * gridSize); + } + } + + int elementParents[cellData.numElems]; // For the base mesh, the parents are always themselves + std::vector elementDominantMaterials; + for (int i = 0; i < cellData.numElems; ++i) + { + elementParents[i] = i; + elementDominantMaterials.push_back(NULL_MAT); + } + + // Build the mesh + mir::MIRMesh testMesh; + testMesh.InitializeMesh(cellData.evInds, cellData.evBegins, cellData.veInds, cellData.veBegins, verts, elems, numMaterials); + testMesh.constructMeshRelations(); + testMesh.constructMeshVolumeFractionsMaps(materialVolumeFractionsData); + testMesh.constructVertexPositionMap(cellData.vertexPositions.data()); + testMesh.constructElementParentMap(elementParents); + testMesh.constructElementDominantMaterialMap(elementDominantMaterials); + + return testMesh; + +} + +//-------------------------------------------------------------------------------- + +/// Calculate the number of corners of the quad that are within the circle +int MeshTester::circleQuadCornersOverlaps(mir::Point2 circle_center, axom::float64 circle_radius, mir::Point2 q_p0, mir::Point2 q_p1, mir::Point2 q_p2, mir::Point2 q_p3) +{ + // Check if any of the quad's corners are within the circle + axom::float64 distP0 = distance(q_p0, circle_center); + axom::float64 distP1 = distance(q_p1, circle_center); + axom::float64 distP2 = distance(q_p2, circle_center); + axom::float64 distP3 = distance(q_p3, circle_center); + + int numCorners = 0; + + if (distP0 < circle_radius) + numCorners++; + if (distP1 < circle_radius) + numCorners++; + if (distP2 < circle_radius) + numCorners++; + if (distP3 < circle_radius) + numCorners++; + + return numCorners; +} + +//-------------------------------------------------------------------------------- + +} +} diff --git a/src/axom/mir/MeshTester.hpp b/src/axom/mir/MeshTester.hpp new file mode 100644 index 0000000000..b89cfa2b26 --- /dev/null +++ b/src/axom/mir/MeshTester.hpp @@ -0,0 +1,47 @@ +// Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#ifndef __MESH_TESTER_H__ +#define __MESH_TESTER_H__ + +#include "axom/core.hpp" // for axom macros +#include "axom/slam.hpp" // unified header for slam classes and functions + +#include "MIRMesh.hpp" + +#include + +namespace numerics = axom::numerics; +namespace slam = axom::slam; + +namespace axom +{ +namespace mir +{ + class MeshTester + { + public: + MeshTester(); + ~MeshTester(); + + public: + MIRMesh initTestCaseOne(); // 3x3 grid, 2 materials + mir::MIRMesh initTestCaseTwo(); // 3x3 grid, 3 materials + mir::MIRMesh initTestCaseThree(); // triforce, 2 materials + mir::MIRMesh initTestCaseFour(); // 3x3, 2 materials, circle of material + mir::MIRMesh createUniformGridTestCaseMesh(int gridSize, mir::Point2 circleCenter, axom::float64 circleRadius); + mir::MIRMesh initTestCaseFive(int gridSize, int numCircles); // multiple materials, multiple concentric circles + + private: + axom::float64 distance(mir::Point2 p0, mir::Point2 p1); + mir::CellData generateGrid(int n); + axom::float64 calculatePercentOverlapMonteCarlo(int n, mir::Point2 circle_center, axom::float64 circle_radius, mir::Point2 q_p0, mir::Point2 q_p1, mir::Point2 q_p2, mir::Point2 q_p3); + + int circleQuadCornersOverlaps(mir::Point2 circle_center, axom::float64 circle_radius, mir::Point2 q_p0, mir::Point2 q_p1, mir::Point2 q_p2, mir::Point2 q_p3); + }; +} +} + +#endif diff --git a/src/axom/mir/ZooBitMaps.cpp b/src/axom/mir/ZooClippingTables.cpp similarity index 98% rename from src/axom/mir/ZooBitMaps.cpp rename to src/axom/mir/ZooClippingTables.cpp index 3904fd525c..5cb584274c 100644 --- a/src/axom/mir/ZooBitMaps.cpp +++ b/src/axom/mir/ZooClippingTables.cpp @@ -3,7 +3,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#include "ZooBitMaps.hpp" +#include "ZooClippingTables.hpp" namespace axom { diff --git a/src/axom/mir/ZooBitMaps.hpp b/src/axom/mir/ZooClippingTables.hpp similarity index 83% rename from src/axom/mir/ZooBitMaps.hpp rename to src/axom/mir/ZooClippingTables.hpp index e765ced325..fcdfc20a9e 100644 --- a/src/axom/mir/ZooBitMaps.hpp +++ b/src/axom/mir/ZooClippingTables.hpp @@ -3,8 +3,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef __ZOO_BIT_MAPS_H__ -#define __ZOO_BIT_MAPS_H__ +#ifndef __ZOO_CLIPPING_TABLES_H__ +#define __ZOO_CLIPPING_TABLES_H__ namespace axom diff --git a/src/axom/mir/examples/CMakeLists.txt b/src/axom/mir/examples/CMakeLists.txt index 605609bf02..b1a647e764 100644 --- a/src/axom/mir/examples/CMakeLists.txt +++ b/src/axom/mir/examples/CMakeLists.txt @@ -4,8 +4,8 @@ # SPDX-License-Identifier: (BSD-3-Clause) set( mir_examples - mir_tutorial.cpp mir_tutorial_simple.cpp + mirConcentricCircles.cpp ) set( mir_example_dependencies diff --git a/src/axom/mir/examples/mirConcentricCircles.cpp b/src/axom/mir/examples/mirConcentricCircles.cpp new file mode 100644 index 0000000000..b445c859e7 --- /dev/null +++ b/src/axom/mir/examples/mirConcentricCircles.cpp @@ -0,0 +1,70 @@ +// Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "axom/core.hpp" // for axom macros +#include "axom/mir.hpp" // for Mir classes & functions +#include "axom/slam.hpp" + +#include +#include + +using Clock = std::chrono::high_resolution_clock; + +// namespace aliases +namespace numerics = axom::numerics; +namespace slam = axom::slam; +namespace mir = axom::mir; + +//-------------------------------------------------------------------------------- + +/*! + * \brief Tutorial main + */ +int main( int argc, char** argv ) +{ + + if (argc != 4) + { + printf("Incorrect number of args. Args are \n"); + return 0; + } + + try + { + // Parse the command line arguments + int gridSize = std::stoi(argv[1]); + int numCircles = std::stoi(argv[2]); + std::string outputFilePath = std::string(argv[3]); + + // Intialize a mesh for testing MIR + auto start_time = Clock::now(); + mir::MeshTester tester; + mir::MIRMesh testMesh = tester.initTestCaseFive(gridSize, numCircles); // grid size, numCircles + auto end_time = Clock::now(); + std::cout << "Mesh init time: " << std::chrono::duration_cast(end_time - start_time).count() << " ms" << std::endl; + + // Begin material interface reconstruction + start_time = Clock::now(); + mir::InterfaceReconstructor reconstructor; + mir::MIRMesh processedMesh = reconstructor.computeReconstructedInterface(&testMesh); + end_time = Clock::now(); + std::cout << "Material interface reconstruction time: " << std::chrono::duration_cast(end_time - start_time).count() << " ms" << std::endl; + + // Output results + processedMesh.writeMeshToFile(outputFilePath + "outputConcentricCircles.vtk"); + + return 0; + } + catch (std::invalid_argument const &e) + { + printf("Bad input. Arguments are \n"); + return 0; + } + catch (std::out_of_range const &e) + { + printf("Integer overflow. Arguments are \n"); + return 0; + } +} \ No newline at end of file diff --git a/src/axom/mir/examples/mir_tutorial.cpp b/src/axom/mir/examples/mir_tutorial.cpp deleted file mode 100644 index 576da4d49d..0000000000 --- a/src/axom/mir/examples/mir_tutorial.cpp +++ /dev/null @@ -1,480 +0,0 @@ -// Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and -// other Axom Project Developers. See the top-level COPYRIGHT file for details. -// -// SPDX-License-Identifier: (BSD-3-Clause) - -#include "axom/core.hpp" // for axom macros -// #include "axom/mir.hpp" // for Mir classes & functions -#include "axom/slam.hpp" - - - -// C/C++ includes -#include // for definition of M_PI, exp() -#include - -// namespace aliases -//namespace mir = axom::mir; -namespace numerics = axom::numerics; -namespace slam = axom::slam; - -#define EPSILON_ZERO = 0.00000000001f; - -//-------------------------------------------------------------------------------- - -/** - * \brief Simple 2D Point class for example - */ -struct Point2 -{ - Point2(double x = 0., double y = 0.) : m_x(x), m_y(y) {} - - Point2(const Point2& other) : m_x(other.m_x), m_y(other.m_y) {} - - Point2& operator=(const Point2& other) - { m_x = other.m_x; m_y = other.m_y; return *this; } - - Point2& operator+=(const Point2& other) - { m_x += other.m_x; m_y += other.m_y; return *this; } - - Point2& operator/=(double val) - { m_x /= val; m_y += val; return *this; } - - double& operator[] (int i) { return (i==0) ? m_x : m_y; } - const double& operator[] (int i) const { return (i==0) ? m_x : m_y; } - - friend std::ostream& operator<<(std::ostream& os, const Point2& pt) - { return os << "{x:" << pt.m_x << ", y:" << pt.m_y <<"}"; } - - double m_x, m_y; -}; - -//-------------------------------------------------------------------------------- - -struct EdgeClipInfo -{ - int vertexOne; - int vertexTwo; - int colorOne; - int colorTwo; - float t; // The percent distance from vertexOne to VertexTwo where the clip occurs. -}; - -//-------------------------------------------------------------------------------- - -struct MIRMesh -{ - /**************************************************************** - * TYPE ALIASES - ****************************************************************/ - // SET TYPE ALIASES - using PosType = slam::DefaultPositionType; - using ElemType = slam::DefaultElementType; - - using ArrayIndir = slam::policies::ArrayIndirection< PosType, ElemType >; - - using VertSet = slam::PositionSet< PosType, ElemType >; - using ElemSet = slam::PositionSet< PosType, ElemType >; - - // RELATION TYPE ALIASES - using VarCard = slam::policies::VariableCardinality< PosType, ArrayIndir >; - - // Note: This is the actual relation type, which takes in a bunch of policies and data entries to relate to each other. - // Note: It is the relation of the elements to the vertices. - using ElemToVertRelation = slam::StaticRelation< PosType, ElemType, VarCard, ArrayIndir, ElemSet, VertSet >; - using VertToElemRelation = slam::StaticRelation< PosType, ElemType, VarCard, ArrayIndir, VertSet, ElemSet >; - - // MAP TYPE ALIASES - using BaseSet = slam::Set< PosType, ElemType >; - using ScalarMap = slam::Map< BaseSet, axom::float64 >; // Note: Documentation has a typo in it. - using PointMap = slam::Map< BaseSet, Point2 >; - - enum { GREEN = 0, BLUE = 1 }; - - /**************************************************************** - * MESH FUNCTIONS - ****************************************************************/ - void InitializeMesh() - { - // Create the mesh connectivity information - // data for element-vertex boundary relation - evInds = { - 0,4,5,1, // elem 0, card 4, start 0 - 1,5,6,2, // elem 1, card 4, start 4 - 2,6,7,3, // elem 2, card 4, start 8 - 4,8,9,5, // elem 3, card 4, start 12 - 5,9,10,6, // elem 4, card 4, start 16 - 6,10,11,7, // elem 5, card 4, start 20 - 8,12,13,9, // elem 6, card 4, start 24 - 9,13,14,10, // elem 7, card 4, start 28 - 10,14,15,11 // elem 8, card 4, start 32, end 36 - }; - - evBegins = { - 0,4,8,12,16,20,24,28,32,36 - }; - - // data for vertex-element coboundary relation - veInds = { - 0, // vert 0, card 1, start 0 - 0,1, // vert 1, card 2, start 1 - 1,2, // vert 2, card 2, start 3 - 2, // vert 3, card 1, start 5 - 0,3, // vert 4, card 2, start 6 - 0,1,3,4, // vert 5, card 4, start 8 - 1,2,4,5, // vert 6, card 4, start 12 - 2,5, // vert 7, card 2, start 16 - 3,6, // vert 8, card 2, start 18 - 3,4,6,7, // vert 9, card 4, start 20 - 4,5,7,8, // vert 10, card 4, start 24 - 5,8, // vert 11, card 2, start 28 - 6, // vert 12, card 1, start 30 - 6,7, // vert 13, card 2, start 31 - 7,8, // vert 14, card 2, start 33 - 8, // vert 15, card 1, start 35, end 36 - }; - veBegins = { - 0,1,3,5,6,8,12,16,18,20,24,28,30,31,33,35,36 - }; - - // Initialize the mesh sets - verts = VertSet(16); // Construct a vertex set with 11 vertices - elems = ElemSet(9); // Construct an element set with 5 elements - - // Check validity of the sets - SLIC_ASSERT_MSG( verts.isValid(), "Vertex set is not valid."); - SLIC_ASSERT_MSG( elems.isValid(), "Element set is not valid."); - - printf("Mesh has been initialized.\n"); - } - - /// Constructs the mesh boundary and coboundary relations - void constructMeshRelations() - { - - // construct boundary relation from elements to vertices using variable cardinality - { - using RelationBuilder = ElemToVertRelation::RelationBuilder; - bdry = RelationBuilder() - .fromSet( &elems ) - .toSet( &verts ) - .begins( RelationBuilder::BeginsSetBuilder() - .size( elems.size() ) - .data( evBegins.data() ) ) - .indices ( RelationBuilder::IndicesSetBuilder() - .size( evInds.size() ) - .data( evInds.data() ) ); - } - - - { - // _quadmesh_example_construct_cobdry_relation_start - // construct coboundary relation from vertices to elements - using RelationBuilder = VertToElemRelation::RelationBuilder; - cobdry = RelationBuilder() - .fromSet( &verts ) - .toSet( &elems ) - .begins( RelationBuilder::BeginsSetBuilder() - .size( verts.size() ) - .data( veBegins.data() ) ) - .indices( RelationBuilder::IndicesSetBuilder() - .size( veInds.size() ) - .data( veInds.data() ) ); - // _quadmesh_example_construct_cobdry_relation_end - } - - - SLIC_ASSERT_MSG( bdry.isValid(), "Boundary relation is not valid."); - SLIC_ASSERT_MSG( cobdry.isValid(), "Coboundary relation is not valid."); - - SLIC_INFO("Elem-Vert relation has size " << bdry.totalSize()); - SLIC_INFO("Vert-Elem relation has size " << cobdry.totalSize()); - - printf("Finished constructing mesh relations.\n"); - } - - /// Constructs the volume fraction maps on the vertices - void constructMeshVolumeFractionMaps() - { - // TODO: Determine better way to store volume fraction data than 1 map per material? - // Construct the scalar map on the elements for the green material - greenVolumeFractionsElements = ScalarMap( &elems ); - - // Set the green volume fraction for each element - greenVolumeFractionsElements[0] = 1.0; - greenVolumeFractionsElements[1] = 1.0; - greenVolumeFractionsElements[2] = 1.0; - greenVolumeFractionsElements[3] = 1.0; - greenVolumeFractionsElements[4] = 0.5; - greenVolumeFractionsElements[5] = 0.2; - greenVolumeFractionsElements[6] = 0.2; - greenVolumeFractionsElements[7] = 0.0; - greenVolumeFractionsElements[8] = 0.0; - - // Construct the scalar map on the elements for the blue material - blueVolumeFractionsElements = ScalarMap( &elems ); - - blueVolumeFractionsElements[0] = 0.0; - blueVolumeFractionsElements[1] = 0.0; - blueVolumeFractionsElements[2] = 0.0; - blueVolumeFractionsElements[3] = 0.0; - blueVolumeFractionsElements[4] = 0.5; - blueVolumeFractionsElements[5] = 0.8; - blueVolumeFractionsElements[6] = 0.8; - blueVolumeFractionsElements[7] = 1.0; - blueVolumeFractionsElements[8] = 1.0; - - printf("Finished constructing volume fractions for mesh elements.\n"); - } - - /// Constucts the positions map on the vertices - void constructVertexPositionMap() - { - // construct the position map on the vertices - vertexPositions = PointMap( &verts ); - - // first vertex is at origin - vertexPositions[0] = Point2( 0.0, 3.0 ); - vertexPositions[1] = Point2( 1.0, 3.0 ); - vertexPositions[2] = Point2( 2.0, 3.0 ); - vertexPositions[3] = Point2( 3.0, 3.0 ); - - vertexPositions[4] = Point2( 0.0, 2.0 ); - vertexPositions[5] = Point2( 1.0, 2.0 ); - vertexPositions[6] = Point2( 2.0, 2.0 ); - vertexPositions[7] = Point2( 3.0, 2.0 ); - - vertexPositions[8] = Point2( 0.0, 1.0 ); - vertexPositions[9] = Point2( 1.0, 1.0 ); - vertexPositions[10] = Point2( 2.0, 1.0 ); - vertexPositions[11] = Point2( 3.0, 1.0 ); - - vertexPositions[12] = Point2( 0.0, 0.0 ); - vertexPositions[13] = Point2( 1.0, 0.0 ); - vertexPositions[14] = Point2( 2.0, 0.0 ); - vertexPositions[15] = Point2( 3.0, 0.0 ); - - SLIC_ASSERT_MSG( vertexPositions.isValid(), "Position map is not valid."); - - SLIC_INFO("-- Vertex positions:"); - for(int vID=0 ; vID < verts.size() ; ++vID) - { - SLIC_INFO("Position of vert " << vID << " is " << vertexPositions[vID]); - } - } - - // Computes the average volume fraction at each vertex of the mesh - void computeVolumeFractionAverages() - { - // Initialize the vertex volume fraction maps - greenVolumeFractionsVertices = ScalarMap( &verts ); - blueVolumeFractionsVertices = ScalarMap( &verts ); - - for (int vID = 0; vID < verts.size(); ++vID) - { - // Compute the per vertex volume fractions for the green material - axom::float64 sum = 0; - auto vertexElements = cobdry[vID]; - for (int i = 0; i < vertexElements.size(); ++i) - { - auto eID = vertexElements[i]; - sum += greenVolumeFractionsElements[eID]; - } - greenVolumeFractionsVertices[vID] = sum / vertexElements.size(); - - // Compute the per vertex volume fractions for the blue material - sum = 0; - for (int i = 0; i < vertexElements.size(); ++i) - { - auto eID = vertexElements[i]; - sum += blueVolumeFractionsElements[eID]; - } - blueVolumeFractionsVertices[vID] = sum / vertexElements.size(); - } - - printf("Finished computing volume fraction averages.\n"); - } - - /// Prints out the map values for each element - void printElementScalarMap(ScalarMap& elements, std::string prefix) - { - std::cout << prefix; - for (int i = 0; i < elems.size(); ++i) - { - printf("Element %d: %f\n", i, elements[i]); - } - } - - /// Prints out the map values for each vertex - void printVertexScalarMap(ScalarMap& vertices, std::string prefix) - { - std::cout << prefix; - for (int i = 0; i < verts.size(); ++i) - { - printf("Vertex %d: %f\n", i, vertices[i]); - } - } - - /// Use bilinear interpolation to compute the points where the given element should be clipped. - /// Returns true if clip should occur, otherwise false. - /// If a clip should occur, the clipping points will be given in p1 and p2. - void computeClippingPoints(const int eID)//, Point2* p1, Point2* p2) - { - // Find the vertices associated with each element - auto elementVertices = bdry[eID]; - - // Check if one of the two currently considered materials is not present at the vertices of the current element - if (blueVolumeFractionsElements[eID] != 0.0 && greenVolumeFractionsElements[eID] != 0.0) - { - // Triangle Case - if (elementVertices.size() == 3) - { - computeTriangleClippingPoints(eID); - } - // Quad Case - if (elementVertices.size() == 4) - { - computeQuadClippingPoints(eID); - } - } - else - { - printf("No cuts in element %d.\n", eID); - } - } - - /// Computes the points where the given quad element should be clipped - void computeQuadClippingPoints(int eID) - { - auto elementVertices = bdry[eID]; - EdgeClipInfo eci[4]; - - // Initialize the edge clipping info - for (int i = 0; i < 4; ++i) - { - eci[i].vertexOne = elementVertices[ i ]; - eci[i].vertexTwo = elementVertices[ (i + 1) % 4 ]; - - if (blueVolumeFractionsVertices[eci[i].vertexOne] > greenVolumeFractionsVertices[eci[i].vertexOne]) - eci[i].colorOne = BLUE; - else - eci[i].colorOne = GREEN; - - if (blueVolumeFractionsVertices[eci[i].vertexTwo] > greenVolumeFractionsVertices[eci[i].vertexTwo]) - eci[i].colorTwo = BLUE; - else - eci[i].colorTwo = GREEN; - - eci[i].t = -1.0; // default t value means one material dominates the other and no clip should occur on this edge - } - - // Analyze the edge clipping info and determine where to clip the cell - for (int i = 0; i < 4; ++i) - { - if (eci[i].colorOne != eci[i].colorTwo) - { - axom::float64 numerator = blueVolumeFractionsVertices[eci[i].vertexOne] - greenVolumeFractionsVertices[eci[i].vertexOne]; - axom::float64 denominator = -greenVolumeFractionsVertices[eci[i].vertexOne] - + greenVolumeFractionsVertices[eci[i].vertexTwo] - + blueVolumeFractionsVertices[eci[i].vertexOne] - - blueVolumeFractionsVertices[eci[i].vertexTwo]; - - if (denominator != 0.0) - eci[i].t = numerator / denominator; - } - } - - // Print out the cutting information - bool cut = false; - for (int i = 0; i < 4; ++i) - { - if (eci[i].t > 0.0) - { - printf("Cutting element %d between vertices %d and %d. t = %f\n", eID, eci[i].vertexOne, eci[i].vertexTwo, eci[i].t); - cut = true; - } - } - if (!cut) - printf("No cuts in element %d.\n", eID); - - } - - /// Computes the points where the given triangle element should be clipped - void computeTriangleClippingPoints(int eID) - { - printf("Triangle clipping case not yet implemented.\n"); - } - - /**************************************************************** - * VARIABLES - ****************************************************************/ - public: - // Mesh Set Definitions - VertSet verts; // the set of vertices in the mesh - ElemSet elems; // the set of elements in the mesh - - // Mesh Relation Definitions - ElemToVertRelation bdry; // Boundary relation from elements to vertices - VertToElemRelation cobdry; // Coboundary relation from vertices to elements - - // Mesh Map Definitions - PointMap vertexPositions; // vertex position - ScalarMap greenVolumeFractionsElements; // the volume fractions of the green material for each element (NOT each vertex) - ScalarMap blueVolumeFractionsElements; // the volume fractions of the blue material for each element (NOT each vertex) - - ScalarMap greenVolumeFractionsVertices; - ScalarMap blueVolumeFractionsVertices; - - // support data for mesh connectivity - std::vector evInds; - std::vector evBegins; - std::vector veInds; - std::vector veBegins; -}; - - -//-------------------------------------------------------------------------------- - - -/*! - * \file - * - * \brief Various code snippets/examples used for the Mir tutorial section. - * - * \note These examples are designed to illustrate specific Mir - * concepts and capabilities. Consult the Tutorial section of Mir's - * User Guide for more details. - * - */ - - -/*! - * \brief Tutorial main - */ -int main( int AXOM_NOT_USED(argc), char** AXOM_NOT_USED(argv) ) -{ - printf("Beginning the MIR Tutorial program.\n"); - - MIRMesh testMesh; - testMesh.InitializeMesh(); - testMesh.constructMeshRelations(); - // testMesh.constructMeshPositionMap(); - testMesh.constructMeshVolumeFractionMaps(); - testMesh.computeVolumeFractionAverages(); - - // Print volume fraction debugging information - // testMesh.printElementScalarMap(testMesh.greenVolumeFractionsElements, "Green Volume Fractions per Element: \n"); - // testMesh.printElementScalarMap(testMesh.blueVolumeFractionsElements, "Blue Volume Fractions per Element: \n"); - - // testMesh.printVertexScalarMap(testMesh.greenVolumeFractionsVertices, "Green Volume Fractions per Vertex: \n"); - // testMesh.printVertexScalarMap(testMesh.blueVolumeFractionsVertices, "Blue Volume Fractions per Vertex: \n"); - - // Determine where to clip the cells - for (int i = 0; i < 9; ++i) - testMesh.computeClippingPoints(i); - - - return 0; -} - -//-------------------------------------------------------------------------------- \ No newline at end of file diff --git a/src/axom/mir/examples/mir_tutorial_simple.cpp b/src/axom/mir/examples/mir_tutorial_simple.cpp index b9e0dc9b7b..9feba3ec87 100644 --- a/src/axom/mir/examples/mir_tutorial_simple.cpp +++ b/src/axom/mir/examples/mir_tutorial_simple.cpp @@ -4,23 +4,17 @@ // SPDX-License-Identifier: (BSD-3-Clause) #include "axom/core.hpp" // for axom macros -// #include "axom/mir.hpp" // for Mir classes & functions +#include "axom/mir.hpp" // for Mir classes & functions #include "axom/slam.hpp" -#include "../MIRMesh.hpp" -#include "../InterfaceReconstructor.hpp" +#include +using Clock = std::chrono::high_resolution_clock; // namespace aliases -//namespace mir = axom::mir; namespace numerics = axom::numerics; namespace slam = axom::slam; namespace mir = axom::mir; -// function templates -mir::MIRMesh initTestCaseOne(); // 3x3 grid, 2 materials -mir::MIRMesh initTestCaseTwo(); // 3x3 grid, 3 materials -mir::MIRMesh initTestCaseThree(); // triforce, 2 materials - //-------------------------------------------------------------------------------- /*! @@ -28,294 +22,33 @@ mir::MIRMesh initTestCaseThree(); // triforce, 2 materials */ int main( int AXOM_NOT_USED(argc), char** AXOM_NOT_USED(argv) ) { + // Intialize a mesh for testing MIR - // mir::MIRMesh testMesh = initTestCaseOne(); - mir::MIRMesh testMesh = initTestCaseTwo(); - // mir::MIRMesh testMesh = initTestCaseThree(); - - // Begin material interface reconstruction - mir::InterfaceReconstructor reconstructor(&testMesh); - mir::MIRMesh processedMesh = reconstructor.computeReconstructedInterface(); - - // Output results - processedMesh.print(); - processedMesh.writeMeshToFile("/Users/sterbentz3/Desktop/processedTestMesh.vtk"); - processedMesh.computeOriginalElementVolumeFractions(); - - return 0; -} - -//-------------------------------------------------------------------------------- - -// Initialize mesh for Test Case 1 (from Meredith 2004) -mir::MIRMesh initTestCaseOne() -{ - int numElements = 9; - int numVertices = 16; - - // Create the mesh connectivity information - std::vector evInds = { - 0,4,5,1, // elem 0, card 4, start 0 - 1,5,6,2, // elem 1, card 4, start 4 - 2,6,7,3, // elem 2, card 4, start 8 - 4,8,9,5, // elem 3, card 4, start 12 - 5,9,10,6, // elem 4, card 4, start 16 - 6,10,11,7, // elem 5, card 4, start 20 - 8,12,13,9, // elem 6, card 4, start 24 - 9,13,14,10, // elem 7, card 4, start 28 - 10,14,15,11 // elem 8, card 4, start 32, end 36 - }; - - std::vector evBegins = { - 0,4,8,12,16,20,24,28,32,36 - }; - std::vector veInds = { - 0, // vert 0, card 1, start 0 - 0,1, // vert 1, card 2, start 1 - 1,2, // vert 2, card 2, start 3 - 2, // vert 3, card 1, start 5 - 0,3, // vert 4, card 2, start 6 - 0,1,3,4, // vert 5, card 4, start 8 - 1,2,4,5, // vert 6, card 4, start 12 - 2,5, // vert 7, card 2, start 16 - 3,6, // vert 8, card 2, start 18 - 3,4,6,7, // vert 9, card 4, start 20 - 4,5,7,8, // vert 10, card 4, start 24 - 5,8, // vert 11, card 2, start 28 - 6, // vert 12, card 1, start 30 - 6,7, // vert 13, card 2, start 31 - 7,8, // vert 14, card 2, start 33 - 8, // vert 15, card 1, start 35, end 36 - }; - std::vector veBegins = { - 0,1,3,5,6,8,12,16,18,20,24,28,30,31,33,35,36 - }; - - mir::VertSet verts = mir::VertSet(numVertices); // Construct a vertex set with 16 vertices - mir::ElemSet elems = mir::ElemSet(numElements); // Construct an element set with 9 elements - + auto start_time = Clock::now(); + mir::MeshTester tester; + // mir::MIRMesh testMesh = tester.initTestCaseOne(); + mir::MIRMesh testMesh = tester.initTestCaseTwo(); + // mir::MIRMesh testMesh = tester.initTestCaseThree(); + // mir::MIRMesh testMesh = tester.initTestCaseFour(); + // mir::MIRMesh testMesh = tester.initTestCaseFive(20, 5); - int numMaterials = 2; - enum { GREEN = 0, BLUE = 1 }; - - std::vector materialVolumeFractionsData; - materialVolumeFractionsData.resize(numMaterials); - axom::float64 greenVolumeFractions[] = {1.0, 1.0, 1.0, 1.0, 0.5, 0.2, 0.2, 0.0, 0.0}; - materialVolumeFractionsData[GREEN] = greenVolumeFractions; - axom::float64 blueVolumeFractions[] = {0.0, 0.0, 0.0, 0.0, 0.5, 0.8, 0.8, 1.0, 1.0}; - materialVolumeFractionsData[BLUE] = blueVolumeFractions; - - - mir::Point2 points[numVertices]; - { - points[0] = mir::Point2( 0.0, 3.0 ); - points[1] = mir::Point2( 1.0, 3.0 ); - points[2] = mir::Point2( 2.0, 3.0 ); - points[3] = mir::Point2( 3.0, 3.0 ); - - points[4] = mir::Point2( 0.0, 2.0 ); - points[5] = mir::Point2( 1.0, 2.0 ); - points[6] = mir::Point2( 2.0, 2.0 ); - points[7] = mir::Point2( 3.0, 2.0 ); - - points[8] = mir::Point2( 0.0, 1.0 ); - points[9] = mir::Point2( 1.0, 1.0 ); - points[10] = mir::Point2( 2.0, 1.0 ); - points[11] = mir::Point2( 3.0, 1.0 ); - - points[12] = mir::Point2( 0.0, 0.0 ); - points[13] = mir::Point2( 1.0, 0.0 ); - points[14] = mir::Point2( 2.0, 0.0 ); - points[15] = mir::Point2( 3.0, 0.0 ); - } - - int elementParents[9] = { 0,1,2,3,4,5,6,7,8 }; // For the base mesh, the parents are always themselves - - std::vector elementDominantMaterials = {NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT}; - - // Build the mesh - mir::MIRMesh testMesh; - testMesh.InitializeMesh(evInds, evBegins, veInds, veBegins, verts, elems, numMaterials); - testMesh.constructMeshRelations(); - testMesh.constructMeshVolumeFractionMaps(materialVolumeFractionsData); - testMesh.constructVertexPositionMap(points); - testMesh.constructElementParentMap(elementParents); - testMesh.constructElementDominantMaterialMap(elementDominantMaterials); - - return testMesh; -} - -//-------------------------------------------------------------------------------- - -// Initialize mesh for Test Case 2 (from Meredith and Childs 2010) -mir::MIRMesh initTestCaseTwo() -{ - int numElements = 9; - int numVertices = 16; - - // Create the mesh connectivity information - std::vector evInds = { - 0,4,5,1, // elem 0, card 4, start 0 - 1,5,6,2, // elem 1, card 4, start 4 - 2,6,7,3, // elem 2, card 4, start 8 - 4,8,9,5, // elem 3, card 4, start 12 - 5,9,10,6, // elem 4, card 4, start 16 - 6,10,11,7, // elem 5, card 4, start 20 - 8,12,13,9, // elem 6, card 4, start 24 - 9,13,14,10, // elem 7, card 4, start 28 - 10,14,15,11 // elem 8, card 4, start 32, end 36 - }; - - std::vector evBegins = { - 0,4,8,12,16,20,24,28,32,36 - }; - std::vector veInds = { - 0, // vert 0, card 1, start 0 - 0,1, // vert 1, card 2, start 1 - 1,2, // vert 2, card 2, start 3 - 2, // vert 3, card 1, start 5 - 0,3, // vert 4, card 2, start 6 - 0,1,3,4, // vert 5, card 4, start 8 - 1,2,4,5, // vert 6, card 4, start 12 - 2,5, // vert 7, card 2, start 16 - 3,6, // vert 8, card 2, start 18 - 3,4,6,7, // vert 9, card 4, start 20 - 4,5,7,8, // vert 10, card 4, start 24 - 5,8, // vert 11, card 2, start 28 - 6, // vert 12, card 1, start 30 - 6,7, // vert 13, card 2, start 31 - 7,8, // vert 14, card 2, start 33 - 8, // vert 15, card 1, start 35, end 36 - }; - std::vector veBegins = { - 0,1,3,5,6,8,12,16,18,20,24,28,30,31,33,35,36 - }; - - mir::VertSet verts = mir::VertSet(numVertices); // Construct a vertex set with 16 vertices - mir::ElemSet elems = mir::ElemSet(numElements); // Construct an element set with 9 elements - - int numMaterials = 3; - enum { BLUE = 2, RED = 0, ORANGE = 1 }; - - std::vector materialVolumeFractionsData; - materialVolumeFractionsData.resize(numMaterials); - axom::float64 blueVolumeFractions[] = {1.0, 1.0, 1.0, 1.0, 0.5, 0.2, 0.2, 0.0, 0.0}; - materialVolumeFractionsData[BLUE] = blueVolumeFractions; - axom::float64 redVolumeFractions[] = {0.0, 0.0, 0.0, 0.0, 0.3, 0.8, 0.0, 0.3, 1.0}; - materialVolumeFractionsData[RED] = redVolumeFractions; - axom::float64 orangeVolumeFractions[] = {0.0, 0.0, 0.0, 0.0, 0.2, 0.0, 0.8, 0.7, 0.0}; - materialVolumeFractionsData[ORANGE] = orangeVolumeFractions; - - mir::Point2 points[numVertices]; - { - points[0] = mir::Point2( 0.0, 3.0 ); - points[1] = mir::Point2( 1.0, 3.0 ); - points[2] = mir::Point2( 2.0, 3.0 ); - points[3] = mir::Point2( 3.0, 3.0 ); - - points[4] = mir::Point2( 0.0, 2.0 ); - points[5] = mir::Point2( 1.0, 2.0 ); - points[6] = mir::Point2( 2.0, 2.0 ); - points[7] = mir::Point2( 3.0, 2.0 ); - - points[8] = mir::Point2( 0.0, 1.0 ); - points[9] = mir::Point2( 1.0, 1.0 ); - points[10] = mir::Point2( 2.0, 1.0 ); - points[11] = mir::Point2( 3.0, 1.0 ); - - points[12] = mir::Point2( 0.0, 0.0 ); - points[13] = mir::Point2( 1.0, 0.0 ); - points[14] = mir::Point2( 2.0, 0.0 ); - points[15] = mir::Point2( 3.0, 0.0 ); - } - - - int elementParents[9] = { 0,1,2,3,4,5,6,7,8 }; // For the base mesh, the parents are always themselves - - std::vector elementDominantMaterials = {NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT}; - - // Build the mesh - mir::MIRMesh testMesh; - testMesh.InitializeMesh(evInds, evBegins, veInds, veBegins, verts, elems, numMaterials); - testMesh.constructMeshRelations(); - testMesh.constructMeshVolumeFractionMaps(materialVolumeFractionsData); - testMesh.constructVertexPositionMap(points); - testMesh.constructElementParentMap(elementParents); - testMesh.constructElementDominantMaterialMap(elementDominantMaterials); - - return testMesh; -} - -//-------------------------------------------------------------------------------- - -// Initialize mesh for Test Case 3, which tests all the triangle clipping cases. -mir::MIRMesh initTestCaseThree() -{ - int numElements = 4; - int numVertices = 6; // OR create a middle triangle with all of one material, and then a ring of triangles around it that are full of the other material - - // Create the mesh connectivity information - std::vector evInds = { - 0,1,2, // elem 0, card 3, start 0 - 1,3,4, // elem 1, card 3, start 3 - 1,4,2, // elem 2, card 3, start 6 - 2,4,5 // elem 3, card 3, start 9, end 12 - }; - - std::vector evBegins = { - 0,3,6,9,12 - }; - std::vector veInds = { - 0, // vert 0, card 1, start 0 - 0,1,2, // vert 1, card 3, start 1 - 0,2,3, // vert 2, card 3, start 4 - 1, // vert 3, card 1, start 7 - 1,2,3, // vert 4, card 3, start 8 - 3 // vert 5, card 1, start 11, end 12 - }; - std::vector veBegins = { - 0,1,4,7,8,11,12 - }; - - mir::VertSet verts = mir::VertSet(numVertices); // Construct a vertex set with 24 vertices - mir::ElemSet elems = mir::ElemSet(numElements); // Construct an element set with 8 elements - - int numMaterials = 2; - enum { BLUE = 0, RED = 1, }; - - std::vector materialVolumeFractionsData; - materialVolumeFractionsData.resize(numMaterials); - axom::float64 blueVolumeFractions[] = {0.0, 0.5, 0.8, 0.5}; - materialVolumeFractionsData[BLUE] = blueVolumeFractions; - axom::float64 redVolumeFractions[] = {1.0, 0.5, 0.2, 0.5}; - materialVolumeFractionsData[RED] = redVolumeFractions; - - mir::Point2 points[numVertices]; - { - points[0] = mir::Point2( 1.0, 2.0 ); - points[1] = mir::Point2( 0.5, 1.0 ); - points[2] = mir::Point2( 1.5, 1.0 ); - points[3] = mir::Point2( 0.0, 0.0 ); - points[4] = mir::Point2( 1.0, 0.0 ); - points[5] = mir::Point2( 2.0, 0.0 ); - } + auto end_time = Clock::now(); + std::cout << "Mesh init time: " << std::chrono::duration_cast(end_time - start_time).count() << " ms" << std::endl; + // Begin material interface reconstruction + start_time = Clock::now(); - int elementParents[4] = { 0,1,2,3 }; // For the base mesh, the parents are always themselves + mir::InterfaceReconstructor reconstructor; + // mir::MIRMesh processedMesh = reconstructor.computeReconstructedInterface(&testMesh); + mir::MIRMesh processedMesh = reconstructor.computeReconstructedInterfaceIterative(&testMesh, 5, 0.9); // 5 iterations, 90 percent - std::vector elementDominantMaterials = {NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT}; + end_time = Clock::now(); + std::cout << "Material interface reconstruction time: " << std::chrono::duration_cast(end_time - start_time).count() << " ms" << std::endl; - // Build the mesh - mir::MIRMesh testMesh; - testMesh.InitializeMesh(evInds, evBegins, veInds, veBegins, verts, elems, numMaterials); - testMesh.constructMeshRelations(); - testMesh.constructMeshVolumeFractionMaps(materialVolumeFractionsData); - testMesh.constructVertexPositionMap(points); - testMesh.constructElementParentMap(elementParents); - testMesh.constructElementDominantMaterialMap(elementDominantMaterials); + // Output results + processedMesh.writeMeshToFile("/Users/sterbentz3/Desktop/iterativeOutputTestMesh2.vtk"); - return testMesh; + return 0; } -//-------------------------------------------------------------------------------- - +//-------------------------------------------------------------------------------- \ No newline at end of file From 1275d1b3b6a81529cee37776d1000ec02581c629 Mon Sep 17 00:00:00 2001 From: Sterbentz Date: Tue, 18 Jun 2019 17:02:23 -0700 Subject: [PATCH 09/23] Cleaned up the code. Changed CellData to be a wrapper class. --- src/axom/mir/CellData.cpp | 54 +- src/axom/mir/CellData.hpp | 15 +- src/axom/mir/InterfaceReconstructor.cpp | 100 ++-- src/axom/mir/InterfaceReconstructor.hpp | 6 +- src/axom/mir/MIRMesh.cpp | 469 +++++++++--------- src/axom/mir/MIRMesh.hpp | 30 +- src/axom/mir/MeshTester.cpp | 301 +++++------ src/axom/mir/examples/mir_tutorial_simple.cpp | 8 +- 8 files changed, 496 insertions(+), 487 deletions(-) diff --git a/src/axom/mir/CellData.cpp b/src/axom/mir/CellData.cpp index d1cad26aef..d8f35d30e4 100644 --- a/src/axom/mir/CellData.cpp +++ b/src/axom/mir/CellData.cpp @@ -19,21 +19,6 @@ namespace mir //-------------------------------------------------------------------------------- - CellData::CellData(int _numVerts, int _numElems, std::vector _evInds, std::vector _evBegins, std::vector _veInds, std::vector _veBegins, - std::vector _vertexPositions, std::vector > _vertexVolumeFractions) - { - numVerts = _numVerts; - numElems = _numElems; - evInds = _evInds; - evBegins = _evBegins; - veInds = _veInds; - veBegins = _veBegins; - vertexPositions = _vertexPositions; - vertexVolumeFractions = _vertexVolumeFractions; - } - - //-------------------------------------------------------------------------------- - CellData::~CellData() { @@ -45,64 +30,63 @@ namespace mir void CellData::mergeCell(CellData cellToMerge) { // Initialize index offsets - int evBeginsOffset = evInds.size(); - int veBeginsOffset = veInds.size(); + int evBeginsOffset = topology.evInds.size(); + int veBeginsOffset = topology.veInds.size(); int vertexIndexOffset = numVerts; int elementIndexOffset = numElems; // Merge the cell topology information - for (unsigned long i = 0; i < cellToMerge.evInds.size(); ++i) + for (unsigned long i = 0; i < cellToMerge.topology.evInds.size(); ++i) { - evInds.push_back(cellToMerge.evInds[i] + vertexIndexOffset); + topology.evInds.push_back(cellToMerge.topology.evInds[i] + vertexIndexOffset); } - for (unsigned long i = 1; i < cellToMerge.evBegins.size(); ++i) + for (unsigned long i = 1; i < cellToMerge.topology.evBegins.size(); ++i) { - evBegins.push_back(cellToMerge.evBegins[i] + evBeginsOffset); + topology.evBegins.push_back(cellToMerge.topology.evBegins[i] + evBeginsOffset); } - for (unsigned long i = 0; i < cellToMerge.veInds.size(); ++i) + for (unsigned long i = 0; i < cellToMerge.topology.veInds.size(); ++i) { - veInds.push_back(cellToMerge.veInds[i] + elementIndexOffset); + topology.veInds.push_back(cellToMerge.topology.veInds[i] + elementIndexOffset); } - for (unsigned long i = 1; i < cellToMerge.veBegins.size(); ++i) + for (unsigned long i = 1; i < cellToMerge.topology.veBegins.size(); ++i) { - veBegins.push_back(cellToMerge.veBegins[i] + veBeginsOffset); + topology.veBegins.push_back(cellToMerge.topology.veBegins[i] + veBeginsOffset); } // Merge the vertex positions - for (unsigned long i = 0; i < cellToMerge.vertexPositions.size(); ++i) + for (unsigned long i = 0; i < cellToMerge.mapData.vertexPositions.size(); ++i) { - vertexPositions.push_back(cellToMerge.vertexPositions[i]); + mapData.vertexPositions.push_back(cellToMerge.mapData.vertexPositions[i]); } // Merge the vertex volume fractions - for (unsigned long matID = 0; matID < vertexVolumeFractions.size(); ++matID) + for (unsigned long matID = 0; matID < mapData.vertexVolumeFractions.size(); ++matID) { - for (unsigned long vID = 0; vID < cellToMerge.vertexVolumeFractions[matID].size(); ++vID) + for (unsigned long vID = 0; vID < cellToMerge.mapData.vertexVolumeFractions[matID].size(); ++vID) { - vertexVolumeFractions[matID].push_back(cellToMerge.vertexVolumeFractions[matID][vID]); + mapData.vertexVolumeFractions[matID].push_back(cellToMerge.mapData.vertexVolumeFractions[matID][vID]); } } // Merge the elements' dominant materials - for (unsigned long i = 0; i < cellToMerge.elementDominantMaterials.size(); ++i) + for (unsigned long i = 0; i < cellToMerge.mapData.elementDominantMaterials.size(); ++i) { - elementDominantMaterials.push_back(cellToMerge.elementDominantMaterials[i]); + mapData.elementDominantMaterials.push_back(cellToMerge.mapData.elementDominantMaterials[i]); } // Merge the elements' parent ids - for (unsigned long i = 0; i < cellToMerge.elementParents.size(); ++i) + for (unsigned long i = 0; i < cellToMerge.mapData.elementParents.size(); ++i) { - elementParents.push_back(cellToMerge.elementParents[i]); + mapData.elementParents.push_back(cellToMerge.mapData.elementParents[i]); } // Merge the total number of verts and elems in the resulting cell numVerts += cellToMerge.numVerts; numElems += cellToMerge.numElems; - } //-------------------------------------------------------------------------------- diff --git a/src/axom/mir/CellData.hpp b/src/axom/mir/CellData.hpp index 88edd9f453..7603582a30 100644 --- a/src/axom/mir/CellData.hpp +++ b/src/axom/mir/CellData.hpp @@ -41,11 +41,8 @@ namespace mir /// Intended to be used as a helper class to hold intermediate data while processing a mesh, and to be used as input to the MIRMesh class to fully initialize it. class CellData { - public: CellData(); - CellData(int _numVerts, int _numElems, std::vector _evInds, std::vector _evBegins, std::vector _veInds, std::vector _veBegins, - std::vector _vertexPositions, std::vector > _vertexVolumeFractions); ~CellData(); void mergeCell(CellData cellToMerge); @@ -54,16 +51,8 @@ namespace mir int numVerts; int numElems; - // Cell connectivity/topology - std::vector evInds; - std::vector evBegins; - std::vector veInds; - std::vector veBegins; - - std::vector vertexPositions; // Data that goes into MIRMesh's vertexPositions PointMap - std::vector > vertexVolumeFractions; // Data that goes into MIRMesh's materialVolumeFractionsVertex ScalarMap - std::vector elementDominantMaterials; // Data that goes into MIRMesh's elementDominantColors IntMap - std::vector elementParents; // Data that goes into MIRMesh's elementParentIDs IntMap + CellTopologyData topology; + CellMapData mapData; }; } } diff --git a/src/axom/mir/InterfaceReconstructor.cpp b/src/axom/mir/InterfaceReconstructor.cpp index 529c26ead4..6408a1b653 100644 --- a/src/axom/mir/InterfaceReconstructor.cpp +++ b/src/axom/mir/InterfaceReconstructor.cpp @@ -66,12 +66,8 @@ mir::MIRMesh InterfaceReconstructor::computeReconstructedInterface(mir::MIRMesh* // Create the final, processed mesh mir::MIRMesh processedMesh; - processedMesh.InitializeMesh(temp_cellData[0].evInds, temp_cellData[0].evBegins, temp_cellData[0].veInds, temp_cellData[0].veBegins, combined_verts, combined_elems, intermediateMesh.numMaterials); - processedMesh.constructMeshRelations(); - processedMesh.constructMeshVolumeFractionsVertex(temp_cellData[0].vertexVolumeFractions); - processedMesh.constructVertexPositionMap(temp_cellData[0].vertexPositions.data()); - processedMesh.constructElementParentMap(temp_cellData[0].elementParents.data()); - processedMesh.constructElementDominantMaterialMap(temp_cellData[0].elementDominantMaterials); + processedMesh.initializeMesh(combined_verts, combined_elems, intermediateMesh.numMaterials, temp_cellData[0].topology, temp_cellData[0].mapData); + processedMesh.constructMeshVolumeFractionsVertex(temp_cellData[0].mapData.vertexVolumeFractions); // Store the current mesh to be passed into the next iteration finalMesh = processedMesh; @@ -239,36 +235,36 @@ void InterfaceReconstructor::computeQuadClippingPoints(const int eID, const int { axom::float64 matOneVolumeFraction = -1.0, matTwoVolumeFraction = -1.0; if (matOneID != NULL_MAT) - matOneVolumeFraction = out_cellData.vertexVolumeFractions[matOneID][temp_vID]; + matOneVolumeFraction = out_cellData.mapData.vertexVolumeFractions[matOneID][temp_vID]; if (matTwoID != NULL_MAT) - matTwoVolumeFraction = out_cellData.vertexVolumeFractions[matTwoID][temp_vID]; + matTwoVolumeFraction = out_cellData.mapData.vertexVolumeFractions[matTwoID][temp_vID]; currentDominantMat = (matOneVolumeFraction > matTwoVolumeFraction) ? matOneID : matTwoID; } } - out_cellData.elementDominantMaterials.push_back(currentDominantMat); + out_cellData.mapData.elementDominantMaterials.push_back(currentDominantMat); } // Determine and store the parent of this element - out_cellData.elementParents.resize(newElements.size()); + out_cellData.mapData.elementParents.resize(newElements.size()); for (auto itr = newElements.begin(); itr != newElements.end(); itr++) { - out_cellData.elementParents[itr->first] = tempMesh->elementParentIDs[eID]; + out_cellData.mapData.elementParents[itr->first] = tempMesh->elementParentIDs[eID]; } // Check that each element is dominated by a material that is actually present in the original parent cell for (auto itr = newElements.begin(); itr != newElements.end(); itr++) { - int parentElementID = out_cellData.elementParents[itr->first]; - int currentDominantMaterial = out_cellData.elementDominantMaterials[itr->first]; + int parentElementID = out_cellData.mapData.elementParents[itr->first]; + int currentDominantMaterial = out_cellData.mapData.elementDominantMaterials[itr->first]; if (originalMesh->materialVolumeFractionsElement[currentDominantMaterial][parentElementID] == 0) { // This material is not present in the original element from which the current element comes from if (currentDominantMaterial == matOneID) - out_cellData.elementDominantMaterials[itr->first] = matTwoID; + out_cellData.mapData.elementDominantMaterials[itr->first] = matTwoID; else - out_cellData.elementDominantMaterials[itr->first] = matOneID; + out_cellData.mapData.elementDominantMaterials[itr->first] = matOneID; } } @@ -284,9 +280,9 @@ void InterfaceReconstructor::computeQuadClippingPoints(const int eID, const int for (int i = 1; i < 8; ++i) evIndexSubtract[i] += evIndexSubtract[i - 1]; - for (unsigned int i = 0; i < out_cellData.evInds.size(); ++i) + for (unsigned int i = 0; i < out_cellData.topology.evInds.size(); ++i) { - out_cellData.evInds[i] -= evIndexSubtract[ out_cellData.evInds[i] ]; + out_cellData.topology.evInds[i] -= evIndexSubtract[ out_cellData.topology.evInds[i] ]; } } @@ -387,36 +383,36 @@ void InterfaceReconstructor::generateTopologyData(std::map for (auto itr = newElements.begin(); itr != newElements.end(); itr++) { // Push the start index of the next element - out_cellData.evBegins.push_back(currentEVBeginIndex); + out_cellData.topology.evBegins.push_back(currentEVBeginIndex); // Push the next element's vertices for (unsigned int vIndex = 0; vIndex < itr->second.size(); ++vIndex) { - out_cellData.evInds.push_back(itr->second[vIndex]); + out_cellData.topology.evInds.push_back(itr->second[vIndex]); ++currentEVBeginIndex; } } // Push the index that occurs after the last vertex - out_cellData.evBegins.push_back(currentEVBeginIndex); + out_cellData.topology.evBegins.push_back(currentEVBeginIndex); // Store the veInds and veBegins data in the output vectors int currentVEBeginIndex = 0; for (auto itr = newVertices.begin(); itr != newVertices.end(); itr++) { // Push the start index of the vertex's elements - out_cellData.veBegins.push_back(currentVEBeginIndex); + out_cellData.topology.veBegins.push_back(currentVEBeginIndex); // Push the next vertex's elements for (unsigned int eIndex = 0; eIndex < itr->second.size(); eIndex++) { - out_cellData.veInds.push_back(itr->second[eIndex]); + out_cellData.topology.veInds.push_back(itr->second[eIndex]); ++currentVEBeginIndex; } } // Push the index that occurs after the last element - out_cellData.veBegins.push_back(currentVEBeginIndex); + out_cellData.topology.veBegins.push_back(currentVEBeginIndex); } //-------------------------------------------------------------------------------- @@ -451,7 +447,7 @@ void InterfaceReconstructor::generateVertexPositionsFromQuad(std::mapsecond); + out_cellData.mapData.vertexPositions.push_back(itr->second); } } @@ -462,7 +458,7 @@ void InterfaceReconstructor::generateVertexVolumeFractionsFromQuad(std::mapnumMaterials); + out_cellData.mapData.vertexVolumeFractions.resize(tempMesh->numMaterials); for (auto itr = newVertices.begin(); itr != newVertices.end(); itr++) { @@ -470,21 +466,21 @@ void InterfaceReconstructor::generateVertexVolumeFractionsFromQuad(std::mapnumMaterials; ++matID) { if (vID == 0) - out_cellData.vertexVolumeFractions[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][upperLeftVertex]); + out_cellData.mapData.vertexVolumeFractions[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][upperLeftVertex]); if (vID == 1) - out_cellData.vertexVolumeFractions[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][lowerLeftVertex]); + out_cellData.mapData.vertexVolumeFractions[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][lowerLeftVertex]); if (vID == 2) - out_cellData.vertexVolumeFractions[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][lowerRightVertex]); + out_cellData.mapData.vertexVolumeFractions[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][lowerRightVertex]); if (vID == 3) - out_cellData.vertexVolumeFractions[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][upperRightVertex]); + out_cellData.mapData.vertexVolumeFractions[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][upperRightVertex]); if (vID == 4) - out_cellData.vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][upperLeftVertex], tempMesh->materialVolumeFractionsVertex[matID][lowerLeftVertex], verticesClippingTValue[vID])); + out_cellData.mapData.vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][upperLeftVertex], tempMesh->materialVolumeFractionsVertex[matID][lowerLeftVertex], verticesClippingTValue[vID])); if (vID == 5) - out_cellData.vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][lowerLeftVertex], tempMesh->materialVolumeFractionsVertex[matID][lowerRightVertex], verticesClippingTValue[vID])); + out_cellData.mapData.vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][lowerLeftVertex], tempMesh->materialVolumeFractionsVertex[matID][lowerRightVertex], verticesClippingTValue[vID])); if (vID == 6) - out_cellData.vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][lowerRightVertex], tempMesh->materialVolumeFractionsVertex[matID][upperRightVertex], verticesClippingTValue[vID])); + out_cellData.mapData.vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][lowerRightVertex], tempMesh->materialVolumeFractionsVertex[matID][upperRightVertex], verticesClippingTValue[vID])); if (vID == 7) - out_cellData.vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][upperRightVertex], tempMesh->materialVolumeFractionsVertex[matID][upperLeftVertex], verticesClippingTValue[vID])); + out_cellData.mapData.vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][upperRightVertex], tempMesh->materialVolumeFractionsVertex[matID][upperLeftVertex], verticesClippingTValue[vID])); } } } @@ -574,36 +570,36 @@ void InterfaceReconstructor::computeTriangleClippingPoints(const int eID, const { axom::float64 matOneVolumeFraction = -1.0, matTwoVolumeFraction = -1.0; if (matOneID != NULL_MAT) - matOneVolumeFraction = out_cellData.vertexVolumeFractions[matOneID][temp_vID]; + matOneVolumeFraction = out_cellData.mapData.vertexVolumeFractions[matOneID][temp_vID]; if (matTwoID != NULL_MAT) - matTwoVolumeFraction = out_cellData.vertexVolumeFractions[matTwoID][temp_vID]; + matTwoVolumeFraction = out_cellData.mapData.vertexVolumeFractions[matTwoID][temp_vID]; currentDominantMat = (matOneVolumeFraction > matTwoVolumeFraction) ? matOneID : matTwoID; } } - out_cellData.elementDominantMaterials.push_back(currentDominantMat); + out_cellData.mapData.elementDominantMaterials.push_back(currentDominantMat); } // Determine and store the parent of this element - out_cellData.elementParents.resize(newElements.size()); + out_cellData.mapData.elementParents.resize(newElements.size()); for (auto itr = newElements.begin(); itr != newElements.end(); itr++) { - out_cellData.elementParents[itr->first] = tempMesh->elementParentIDs[eID]; + out_cellData.mapData.elementParents[itr->first] = tempMesh->elementParentIDs[eID]; } // Check that each element is dominated by a material that is actually present in the original parent cell for (auto itr = newElements.begin(); itr != newElements.end(); itr++) { - int parentElementID = out_cellData.elementParents[itr->first]; - int currentDominantMaterial = out_cellData.elementDominantMaterials[itr->first]; + int parentElementID = out_cellData.mapData.elementParents[itr->first]; + int currentDominantMaterial = out_cellData.mapData.elementDominantMaterials[itr->first]; if (originalMesh->materialVolumeFractionsElement[currentDominantMaterial][parentElementID] == 0) { // This material is not present in the original element from which the current element comes from if (currentDominantMaterial == matOneID) - out_cellData.elementDominantMaterials[itr->first] = matTwoID; + out_cellData.mapData.elementDominantMaterials[itr->first] = matTwoID; else - out_cellData.elementDominantMaterials[itr->first] = matOneID; + out_cellData.mapData.elementDominantMaterials[itr->first] = matOneID; } } @@ -620,8 +616,8 @@ void InterfaceReconstructor::computeTriangleClippingPoints(const int eID, const for (int i = 1; i < 6; ++i) evIndexSubtract[i] += evIndexSubtract[i - 1]; - for (unsigned int i = 0; i < out_cellData.evInds.size(); ++i) - out_cellData.evInds[i] -= evIndexSubtract[ out_cellData.evInds[i] ]; + for (unsigned int i = 0; i < out_cellData.topology.evInds.size(); ++i) + out_cellData.topology.evInds[i] -= evIndexSubtract[ out_cellData.topology.evInds[i] ]; } @@ -681,7 +677,7 @@ void InterfaceReconstructor::generateVertexPositionsFromTriangle(std::mapsecond); + out_cellData.mapData.vertexPositions.push_back(itr->second); } } @@ -692,7 +688,7 @@ void InterfaceReconstructor::generateVertexVolumeFractionsFromTriangle(std::map< { // Calculate the vertex fractions at each vertex (use t value!) // Make sure the output volume fractions containers are the proper size - out_cellData.vertexVolumeFractions.resize(tempMesh->numMaterials); + out_cellData.mapData.vertexVolumeFractions.resize(tempMesh->numMaterials); for (auto itr = newVertices.begin(); itr != newVertices.end(); itr++) { @@ -700,17 +696,17 @@ void InterfaceReconstructor::generateVertexVolumeFractionsFromTriangle(std::map< for (int matID = 0; matID < tempMesh->numMaterials; ++matID) { if (vID == 0) - out_cellData.vertexVolumeFractions[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][upperVertex]); + out_cellData.mapData.vertexVolumeFractions[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][upperVertex]); if (vID == 1) - out_cellData.vertexVolumeFractions[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][lowerLeftVertex]); + out_cellData.mapData.vertexVolumeFractions[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][lowerLeftVertex]); if (vID == 2) - out_cellData.vertexVolumeFractions[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][lowerRightVertex]); + out_cellData.mapData.vertexVolumeFractions[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][lowerRightVertex]); if (vID == 3) - out_cellData.vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][upperVertex], tempMesh->materialVolumeFractionsVertex[matID][lowerLeftVertex], verticesClippingTValue[vID])); + out_cellData.mapData.vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][upperVertex], tempMesh->materialVolumeFractionsVertex[matID][lowerLeftVertex], verticesClippingTValue[vID])); if (vID == 4) - out_cellData.vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][lowerLeftVertex], tempMesh->materialVolumeFractionsVertex[matID][lowerRightVertex], verticesClippingTValue[vID])); + out_cellData.mapData.vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][lowerLeftVertex], tempMesh->materialVolumeFractionsVertex[matID][lowerRightVertex], verticesClippingTValue[vID])); if (vID == 5) - out_cellData.vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][lowerRightVertex], tempMesh->materialVolumeFractionsVertex[matID][upperVertex], verticesClippingTValue[vID])); + out_cellData.mapData.vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][lowerRightVertex], tempMesh->materialVolumeFractionsVertex[matID][upperVertex], verticesClippingTValue[vID])); } } } diff --git a/src/axom/mir/InterfaceReconstructor.hpp b/src/axom/mir/InterfaceReconstructor.hpp index 8d0f5d3aa8..cbda3dcb59 100644 --- a/src/axom/mir/InterfaceReconstructor.hpp +++ b/src/axom/mir/InterfaceReconstructor.hpp @@ -44,14 +44,16 @@ namespace mir // quad clipping functions void computeClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh, CellData& out_cellData); - // quad clipping interpolation functions + // clipping interpolation functions mir::Point2 interpolateVertexPosition(mir::Point2 vertexOnePos, mir::Point2 vertexTwoPos, float t); axom::float64 lerpFloat(axom::float64 f0, axom::float64 f1, axom::float64 t); axom::float64 computeClippingPointOnEdge(const int vertexOneID, const int vertexTwoID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh); + // general helper functions + void generateTopologyData(std::map > newElements, std::map > newVertices, CellData& out_cellData); + // quad clipping points helper functions unsigned int determineQuadClippingCase(mir::MIRMesh* tempMesh, const int matOneID, const int matTwoID, const int upperLeftVertex, const int lowerLeftVertex, const int lowerRightVertex, const int upperRightVertex); - void generateTopologyData(std::map > newElements, std::map > newVertices, CellData& out_cellData); void generateVertexPositionsFromQuad(std::map > newVertices, mir::MIRMesh* tempMesh, axom::float64* verticesClippingTValue, int upperLeftVertex, int lowerLeftVertex, int lowerRightVertex, int upperRightVertex, CellData& out_cellData); void generateVertexVolumeFractionsFromQuad(std::map > newVertices, mir::MIRMesh* tempMesh, axom::float64* verticesClippingTValue, int upperLeftVertex, int lowerLeftVertex, int lowerRightVertex, int upperRightVertex, CellData& out_cellData); diff --git a/src/axom/mir/MIRMesh.cpp b/src/axom/mir/MIRMesh.cpp index b116550666..9243bda9ad 100644 --- a/src/axom/mir/MIRMesh.cpp +++ b/src/axom/mir/MIRMesh.cpp @@ -9,93 +9,118 @@ namespace axom { namespace mir { - MIRMesh::MIRMesh() - { - } +//-------------------------------------------------------------------------------- - MIRMesh::MIRMesh(MIRMesh* _mesh) // copy constructor - { - data.evInds = _mesh->data.evInds; - data.evBegins = _mesh->data.evBegins; - data.veInds = _mesh->data.veInds; - data.veBegins = _mesh->data.veBegins; - verts = _mesh->verts; - elems = _mesh->elems; - bdry = _mesh->bdry; - cobdry = _mesh->cobdry; - vertexPositions = _mesh->vertexPositions; - materialVolumeFractionsElement = _mesh->materialVolumeFractionsElement; - materialVolumeFractionsVertex = _mesh->materialVolumeFractionsVertex; - elementParentIDs = _mesh->elementParentIDs; - elementDominantMaterials = _mesh->elementDominantMaterials; - numMaterials = _mesh->numMaterials; - } +MIRMesh::MIRMesh() +{ - MIRMesh::~MIRMesh() - { +} - } +//-------------------------------------------------------------------------------- - /// Initializes a mesh with the given topology. - void MIRMesh::InitializeMesh(std::vector _evInds, std::vector _evBegins, std::vector _veInds, std::vector _veBegins, VertSet _verts, ElemSet _elems, int _numMaterials) - { - data.evInds = _evInds; - data.evBegins = _evBegins; - data.veInds = _veInds; - data.veBegins = _veBegins; - verts = _verts; - elems = _elems; - numMaterials = _numMaterials; - - // Check validity of the sets - SLIC_ASSERT_MSG( verts.isValid(), "Vertex set is not valid."); - SLIC_ASSERT_MSG( elems.isValid(), "Element set is not valid."); - } +MIRMesh::MIRMesh(MIRMesh* _mesh) // copy constructor +{ + meshTopology.evInds = _mesh->meshTopology.evInds; + meshTopology.evBegins = _mesh->meshTopology.evBegins; + meshTopology.veInds = _mesh->meshTopology.veInds; + meshTopology.veBegins = _mesh->meshTopology.veBegins; + verts = _mesh->verts; + elems = _mesh->elems; + bdry = _mesh->bdry; + cobdry = _mesh->cobdry; + vertexPositions = _mesh->vertexPositions; + materialVolumeFractionsElement = _mesh->materialVolumeFractionsElement; + materialVolumeFractionsVertex = _mesh->materialVolumeFractionsVertex; + elementParentIDs = _mesh->elementParentIDs; + elementDominantMaterials = _mesh->elementDominantMaterials; + numMaterials = _mesh->numMaterials; +} //-------------------------------------------------------------------------------- - /// Constructs the mesh boundary and coboundary relations - void MIRMesh::constructMeshRelations() - { - // construct boundary relation from elements to vertices using variable cardinality - { - using RelationBuilder = ElemToVertRelation::RelationBuilder; - bdry = RelationBuilder() - .fromSet( &elems ) - .toSet( &verts ) - .begins( RelationBuilder::BeginsSetBuilder() - .size( elems.size() ) - .data( data.evBegins.data() ) ) - .indices ( RelationBuilder::IndicesSetBuilder() - .size( data.evInds.size() ) - .data( data.evInds.data() ) ); - } +MIRMesh::~MIRMesh() +{ + +} +//-------------------------------------------------------------------------------- - { - // _quadmesh_example_construct_cobdry_relation_start - // construct coboundary relation from vertices to elements - using RelationBuilder = VertToElemRelation::RelationBuilder; - cobdry = RelationBuilder() - .fromSet( &verts ) - .toSet( &elems ) - .begins( RelationBuilder::BeginsSetBuilder() - .size( verts.size() ) - .data( data.veBegins.data() ) ) - .indices( RelationBuilder::IndicesSetBuilder() - .size( data.veInds.size() ) - .data( data.veInds.data() ) ); - // _quadmesh_example_construct_cobdry_relation_end - } +/// Initialize a mesh with the given topology and data. +void MIRMesh::initializeMesh(VertSet _verts, ElemSet _elems, int _numMaterials, CellTopologyData _topology, CellMapData _mapData, std::vector > _elementVF) +{ + // Initialize the vertex and element sets + verts = _verts; + elems = _elems; + + numMaterials = _numMaterials; + + // Intialize the mesh topology + meshTopology.evInds = _topology.evInds; + meshTopology.evBegins = _topology.evBegins; + meshTopology.veInds = _topology.veInds; + meshTopology.veBegins = _topology.veBegins; + + // Initialize the mesh relations + constructMeshRelations(); + + // Initialize the mesh's data maps + constructVertexPositionMap(_mapData.vertexPositions.data()); + constructElementParentMap(_mapData.elementParents.data()); + constructElementDominantMaterialMap(_mapData.elementDominantMaterials); + + // Initialize the element and vertex volume fraction maps + if (_elementVF.size() > 0) + constructMeshVolumeFractionsMaps(_elementVF); + + // Check validity of the sets + SLIC_ASSERT_MSG( verts.isValid(), "Vertex set is not valid."); + SLIC_ASSERT_MSG( elems.isValid(), "Element set is not valid."); +} + +//-------------------------------------------------------------------------------- + +/// Constructs the mesh boundary and coboundary relations +void MIRMesh::constructMeshRelations() +{ + // construct boundary relation from elements to vertices using variable cardinality + { + using RelationBuilder = ElemToVertRelation::RelationBuilder; + bdry = RelationBuilder() + .fromSet( &elems ) + .toSet( &verts ) + .begins( RelationBuilder::BeginsSetBuilder() + .size( elems.size() ) + .data( meshTopology.evBegins.data() ) ) + .indices ( RelationBuilder::IndicesSetBuilder() + .size( meshTopology.evInds.size() ) + .data( meshTopology.evInds.data() ) ); + } - SLIC_ASSERT_MSG( bdry.isValid(), "Boundary relation is not valid."); - SLIC_ASSERT_MSG( cobdry.isValid(), "Coboundary relation is not valid."); - SLIC_INFO("Elem-Vert relation has size " << bdry.totalSize()); - SLIC_INFO("Vert-Elem relation has size " << cobdry.totalSize()); + { + // _quadmesh_example_construct_cobdry_relation_start + // construct coboundary relation from vertices to elements + using RelationBuilder = VertToElemRelation::RelationBuilder; + cobdry = RelationBuilder() + .fromSet( &verts ) + .toSet( &elems ) + .begins( RelationBuilder::BeginsSetBuilder() + .size( verts.size() ) + .data( meshTopology.veBegins.data() ) ) + .indices( RelationBuilder::IndicesSetBuilder() + .size( meshTopology.veInds.size() ) + .data( meshTopology.veInds.data() ) ); + // _quadmesh_example_construct_cobdry_relation_end } + SLIC_ASSERT_MSG( bdry.isValid(), "Boundary relation is not valid."); + SLIC_ASSERT_MSG( cobdry.isValid(), "Coboundary relation is not valid."); + + SLIC_INFO("Elem-Vert relation has size " << bdry.totalSize()); + SLIC_INFO("Vert-Elem relation has size " << cobdry.totalSize()); +} + //-------------------------------------------------------------------------------- /// Construct the and the element and vertex volume fraction data given the element volume fraction data @@ -169,196 +194,196 @@ void MIRMesh::constructMeshVolumeFractionsVertex(std::vector dominantMaterials) - { - // Initialize the map for the elements' dominant colors - elementDominantMaterials = IntMap( &elems ); +/// Constructs the elementDominantMaterials map of each element's single most dominant material +void MIRMesh::constructElementDominantMaterialMap(std::vector dominantMaterials) +{ + // Initialize the map for the elements' dominant colors + elementDominantMaterials = IntMap( &elems ); - // Copy the dat for the elements - for (int eID = 0; eID < elems.size(); ++eID) - elementDominantMaterials[eID] = dominantMaterials[eID]; + // Copy the dat for the elements + for (int eID = 0; eID < elems.size(); ++eID) + elementDominantMaterials[eID] = dominantMaterials[eID]; - SLIC_ASSERT_MSG( elementDominantMaterials.isValid(), "Element dominant materials map is not valid."); - } + SLIC_ASSERT_MSG( elementDominantMaterials.isValid(), "Element dominant materials map is not valid."); +} //-------------------------------------------------------------------------------- - /// Print out the properties of the mesh. - void MIRMesh::print() - { - printf("\n------------------------Printing Mesh Information:------------------------\n"); - printf("number of vertices: %d\n", verts.size()); - printf("number of elements: %d\n", elems.size()); - printf("number of materials: %d\n", numMaterials); +/// Print out the properties of the mesh. +void MIRMesh::print() +{ + printf("\n------------------------Printing Mesh Information:------------------------\n"); + printf("number of vertices: %d\n", verts.size()); + printf("number of elements: %d\n", elems.size()); + printf("number of materials: %d\n", numMaterials); - printf("evInds: { "); - for (unsigned long i = 0; i < data.evInds.size(); i++) - { - printf("%d ", data.evInds[i]); - } - printf("}\n"); + printf("evInds: { "); + for (unsigned long i = 0; i < meshTopology.evInds.size(); i++) + { + printf("%d ", meshTopology.evInds[i]); + } + printf("}\n"); - printf("evBegins: { "); - for (unsigned long i = 0; i < data.evBegins.size(); i++) - { - printf("%d ", data.evBegins[i]); - } - printf("}\n"); + printf("evBegins: { "); + for (unsigned long i = 0; i < meshTopology.evBegins.size(); i++) + { + printf("%d ", meshTopology.evBegins[i]); + } + printf("}\n"); - printf("veInds: { "); - for (unsigned long i = 0; i < data.veInds.size(); i++) - { - printf("%d ", data.veInds[i]); - } - printf("}\n"); + printf("veInds: { "); + for (unsigned long i = 0; i < meshTopology.veInds.size(); i++) + { + printf("%d ", meshTopology.veInds[i]); + } + printf("}\n"); - printf("veBegins: { "); - for (unsigned long i = 0; i < data.veBegins.size(); i++) - { - printf("%d ", data.veBegins[i]); - } - printf("}\n"); + printf("veBegins: { "); + for (unsigned long i = 0; i < meshTopology.veBegins.size(); i++) + { + printf("%d ", meshTopology.veBegins[i]); + } + printf("}\n"); - printf("vertexPositions: { "); - for (int i = 0; i < verts.size(); ++i) // TODO: This was previously vertexPositions.size() and was working... - { - printf("{%.2f, %.2f} ", vertexPositions[i].m_x, vertexPositions[i].m_y); - } - printf("}\n"); + printf("vertexPositions: { "); + for (int i = 0; i < verts.size(); ++i) + { + printf("{%.2f, %.2f} ", vertexPositions[i].m_x, vertexPositions[i].m_y); + } + printf("}\n"); - printf("elementParentIDs: { "); - for (int i = 0; i < elems.size(); ++i) - { - printf("%d ", elementParentIDs[i]); - } - printf("}\n"); + printf("elementParentIDs: { "); + for (int i = 0; i < elems.size(); ++i) + { + printf("%d ", elementParentIDs[i]); + } + printf("}\n"); - printf("elementDominantMaterials: { "); - for (int i = 0; i < elems.size(); ++i) - { - printf("%d ", elementDominantMaterials[i]); - } - printf("}\n"); + printf("elementDominantMaterials: { "); + for (int i = 0; i < elems.size(); ++i) + { + printf("%d ", elementDominantMaterials[i]); + } + printf("}\n"); - printf("vertexVolumeFractions: { \n"); - for (unsigned long i = 0; i < materialVolumeFractionsVertex.size(); ++i) + printf("vertexVolumeFractions: { \n"); + for (unsigned long i = 0; i < materialVolumeFractionsVertex.size(); ++i) + { + printf(" { "); + for (int j = 0; j < verts.size(); ++j) { - printf(" { "); - for (int j = 0; j < verts.size(); ++j) - { - printf("%.3f, ", materialVolumeFractionsVertex[i][j]); - } - printf("}\n"); + printf("%.3f, ", materialVolumeFractionsVertex[i][j]); } printf("}\n"); - printf("--------------------------------------------------------------------------\n"); } + printf("}\n"); + printf("--------------------------------------------------------------------------\n"); +} //-------------------------------------------------------------------------------- - /// Reads in and constructs a mesh from the given file - /// Note: Must currently be an ASCII, UNSTRUCTURED_GRID .vtk file - void MIRMesh::readMeshFromFile(std::string filename) - { - printf("Mesh reading functionality not implemented yet. Can't read file: %s", filename.c_str()); - - // Read in header +/// Reads in and constructs a mesh from the given file +/// Note: Must currently be an ASCII, UNSTRUCTURED_GRID .vtk file +void MIRMesh::readMeshFromFile(std::string filename) +{ + printf("Mesh reading functionality not implemented yet. Can't read file: %s", filename.c_str()); + + // Read in header - // Read in POINTS + // Read in POINTS - // Read in CELLS + // Read in CELLS - // Read in CELL_TYPES + // Read in CELL_TYPES - // Read in CELL_DATA (element volume fractions) - } + // Read in CELL_DATA (element volume fractions) +} //-------------------------------------------------------------------------------- - /// Writes out the mesh to a file - void MIRMesh::writeMeshToFile(std::string filename) - { - std::ofstream meshfile; - meshfile.open(filename); - std::ostream_iterator out_it(meshfile, " "); - - // write header - meshfile << "# vtk DataFile Version 3.0\n" - << "vtk output\n" - << "ASCII\n" - << "DATASET UNSTRUCTURED_GRID\n" - << "POINTS " << verts.size() << " double\n"; - - // write positions - for (int vID = 0; vID < verts.size(); ++vID) - { - meshfile << vertexPositions[vID].m_x << " " << vertexPositions[vID].m_y << " 0\n"; // must always set all 3 coords; set Z=0 for 2D - } - - meshfile << "\nCELLS " << elems.size() << " " << data.evInds.size() + elems.size(); - for (int i = 0; i < elems.size(); ++i) - { - int nVerts = data.evBegins[i+1] - data.evBegins[i]; - meshfile << "\n" << nVerts; - for (int j = 0; j < nVerts; ++j) - { - int startIndex = data.evBegins[i]; - meshfile << " " << data.evInds[startIndex + j]; - } - } +/// Writes out the mesh to a file +void MIRMesh::writeMeshToFile(std::string filename) +{ + std::ofstream meshfile; + meshfile.open(filename); + std::ostream_iterator out_it(meshfile, " "); + + // write header + meshfile << "# vtk DataFile Version 3.0\n" + << "vtk output\n" + << "ASCII\n" + << "DATASET UNSTRUCTURED_GRID\n" + << "POINTS " << verts.size() << " double\n"; + + // write positions + for (int vID = 0; vID < verts.size(); ++vID) + { + meshfile << vertexPositions[vID].m_x << " " << vertexPositions[vID].m_y << " 0\n"; // must always set all 3 coords; set Z=0 for 2D + } - meshfile << "\n\nCELL_TYPES " << elems.size() << "\n"; - for (int i = 0; i < elems.size(); ++i) + meshfile << "\nCELLS " << elems.size() << " " << meshTopology.evInds.size() + elems.size(); + for (int i = 0; i < elems.size(); ++i) + { + int nVerts = meshTopology.evBegins[i+1] - meshTopology.evBegins[i]; + meshfile << "\n" << nVerts; + for (int j = 0; j < nVerts; ++j) { - int nVerts = data.evBegins[i + 1] - data.evBegins[i]; - if (nVerts == 3) - meshfile << "5\n"; - else if (nVerts == 4) - meshfile << "9\n"; + int startIndex = meshTopology.evBegins[i]; + meshfile << " " << meshTopology.evInds[startIndex + j]; } + } - // write element materials - meshfile << "\n\nCELL_DATA " << elems.size() - << "\nSCALARS cellIds int 1" - << "\nLOOKUP_TABLE default \n"; - for(int i=0 ; i< elems.size() ; ++i) - { - meshfile << elementDominantMaterials[i] << " "; - } + meshfile << "\n\nCELL_TYPES " << elems.size() << "\n"; + for (int i = 0; i < elems.size(); ++i) + { + int nVerts = meshTopology.evBegins[i + 1] - meshTopology.evBegins[i]; + if (nVerts == 3) + meshfile << "5\n"; + else if (nVerts == 4) + meshfile << "9\n"; + } - meshfile <<"\n"; + // write element materials + meshfile << "\n\nCELL_DATA " << elems.size() + << "\nSCALARS cellIds int 1" + << "\nLOOKUP_TABLE default \n"; + for(int i=0 ; i< elems.size() ; ++i) + { + meshfile << elementDominantMaterials[i] << " "; } + meshfile <<"\n"; +} + //-------------------------------------------------------------------------------- /// Computes the volume fractions of the elements of the original mesh, @@ -371,19 +396,19 @@ std::vector > MIRMesh::computeOriginalElementVolumeFr // Compute the total area of each element of the original mesh and also the area of each new element for (int eID = 0; eID < elems.size(); ++eID) { - int numVertices = data.evBegins[eID + 1] - data.evBegins[eID]; + int numVertices = meshTopology.evBegins[eID + 1] - meshTopology.evBegins[eID]; if (numVertices == 3) { Point2 trianglePoints[3]; for (int i = 0; i < 3; ++i) - trianglePoints[i] = vertexPositions[data.evInds[ data.evBegins[eID] + i] ]; + trianglePoints[i] = vertexPositions[meshTopology.evInds[ meshTopology.evBegins[eID] + i] ]; newElementAreas[eID] = computeTriangleArea(trianglePoints[0], trianglePoints[1], trianglePoints[2]); } if (numVertices == 4) { Point2 trianglePoints[4]; for (int i = 0; i < 4; ++i) - trianglePoints[i] = vertexPositions[data.evInds[ data.evBegins[eID] + i] ]; + trianglePoints[i] = vertexPositions[meshTopology.evInds[ meshTopology.evBegins[eID] + i] ]; newElementAreas[eID] = computeQuadArea(trianglePoints[0], trianglePoints[1], trianglePoints[2], trianglePoints[3]); } diff --git a/src/axom/mir/MIRMesh.hpp b/src/axom/mir/MIRMesh.hpp index eb66116345..c85c18e936 100644 --- a/src/axom/mir/MIRMesh.hpp +++ b/src/axom/mir/MIRMesh.hpp @@ -42,24 +42,24 @@ namespace mir MIRMesh(MIRMesh* _mesh); // copy constructor ~MIRMesh(); - void InitializeMesh(std::vector _evInds, std::vector _evBegins, std::vector _veInds, std::vector _veBegins, VertSet _verts, ElemSet _elems, int _numMaterials); + void initializeMesh(VertSet _verts, ElemSet _elems, int _numMaterials, CellTopologyData _topology, CellMapData _mapData, std::vector > _elementVF = {}); - void constructMeshRelations(); /// Constructs the mesh boundary and coboundary relations - void constructMeshVolumeFractionsMaps(std::vector > elementVF); /// Constructs the element and vertex volume fraction maps + void constructMeshRelations(); /// Constructs the mesh boundary and coboundary relations + void constructMeshVolumeFractionsMaps(std::vector > elementVF); /// Constructs the element and vertex volume fraction maps void constructMeshVolumeFractionsVertex(std::vector > vertexVF); /// Constructs the vertex volume fraction map - void constructVertexPositionMap(Point2* data); /// Constucts the positions map on the vertices - void constructElementParentMap(int* cellParents); /// Constructs the map of cells to their original cell parent - void constructElementDominantMaterialMap(std::vector dominantMaterials); /// Constructs the map of elements to their dominant materials - void print(); // Print out a variety of useful information about the mesh + void print(); - void readMeshFromFile(std::string filename); /// Reads in and constructs a mesh from a file - void writeMeshToFile(std::string filename); /// Writes out the current mesh to a file - void attachVertexMap(); /// Adds a map of data to the mesh + void readMeshFromFile(std::string filename); + void writeMeshToFile(std::string filename); std::vector > computeOriginalElementVolumeFractions(); private: + void constructVertexPositionMap(Point2* data); /// Constucts the positions map on the vertices + void constructElementParentMap(int* cellParents); /// Constructs the map of cells to their original cell parent + void constructElementDominantMaterialMap(std::vector dominantMaterials); /// Constructs the map of elements to their dominant materials + axom::float64 computeTriangleArea(Point2 p0, Point2 p1, Point2 p2); axom::float64 computeQuadArea(Point2 p0, Point2 p1, Point2 p2, Point2 p3); @@ -81,16 +81,12 @@ namespace mir PointMap vertexPositions; // vertex position for each vertex std::vector materialVolumeFractionsElement; // the volume fractions of each material for each element std::vector materialVolumeFractionsVertex; // the volume fractions of each material for each vertex + IntMap elementParentIDs; // the ID of the parent element from the original mesh + IntMap elementDominantMaterials; // the dominant material of the cell (a processed mesh should have only one material per cell) public: int numMaterials; - - public: - IntMap elementParentIDs; // the ID of the parent element from the original mesh - IntMap elementDominantMaterials; // the dominant material of the cell (a processed mesh should have only one material per cell) - - public: - CellData data; // Contains mesh connectivity data, volume fraction data, vertex position data + CellTopologyData meshTopology; }; //-------------------------------------------------------------------------------- diff --git a/src/axom/mir/MeshTester.cpp b/src/axom/mir/MeshTester.cpp index 36ed346a7a..df9d76b7d8 100644 --- a/src/axom/mir/MeshTester.cpp +++ b/src/axom/mir/MeshTester.cpp @@ -84,41 +84,43 @@ MIRMesh MeshTester::initTestCaseOne() std::vector blueVolumeFractions = {0.0, 0.0, 0.0, 0.0, 0.5, 0.8, 0.8, 1.0, 1.0}; elementVF[BLUE] = blueVolumeFractions; - mir::Point2 points[numVertices]; + std::vector points = { - points[0] = mir::Point2( 0.0, 3.0 ); - points[1] = mir::Point2( 1.0, 3.0 ); - points[2] = mir::Point2( 2.0, 3.0 ); - points[3] = mir::Point2( 3.0, 3.0 ); - - points[4] = mir::Point2( 0.0, 2.0 ); - points[5] = mir::Point2( 1.0, 2.0 ); - points[6] = mir::Point2( 2.0, 2.0 ); - points[7] = mir::Point2( 3.0, 2.0 ); - - points[8] = mir::Point2( 0.0, 1.0 ); - points[9] = mir::Point2( 1.0, 1.0 ); - points[10] = mir::Point2( 2.0, 1.0 ); - points[11] = mir::Point2( 3.0, 1.0 ); - - points[12] = mir::Point2( 0.0, 0.0 ); - points[13] = mir::Point2( 1.0, 0.0 ); - points[14] = mir::Point2( 2.0, 0.0 ); - points[15] = mir::Point2( 3.0, 0.0 ); - } - - int elementParents[9] = { 0,1,2,3,4,5,6,7,8 }; // For the base mesh, the parents are always themselves - - std::vector elementDominantMaterials = {NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT}; + mir::Point2( 0.0, 3.0 ), + mir::Point2( 1.0, 3.0 ), + mir::Point2( 2.0, 3.0 ), + mir::Point2( 3.0, 3.0 ), + + mir::Point2( 0.0, 2.0 ), + mir::Point2( 1.0, 2.0 ), + mir::Point2( 2.0, 2.0 ), + mir::Point2( 3.0, 2.0 ), + + mir::Point2( 0.0, 1.0 ), + mir::Point2( 1.0, 1.0 ), + mir::Point2( 2.0, 1.0 ), + mir::Point2( 3.0, 1.0 ), + + mir::Point2( 0.0, 0.0 ), + mir::Point2( 1.0, 0.0 ), + mir::Point2( 2.0, 0.0 ), + mir::Point2( 3.0, 0.0 ) + }; + + CellTopologyData topology; + topology.evInds = evInds; + topology.evBegins = evBegins; + topology.veInds = veInds; + topology.veBegins = veBegins; + + CellMapData mapData; + mapData.elementDominantMaterials = {NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT}; + mapData.elementParents = { 0,1,2,3,4,5,6,7,8 }; // For the base mesh, the parents are always themselves + mapData.vertexPositions = points; // Build the mesh mir::MIRMesh testMesh; - testMesh.InitializeMesh(evInds, evBegins, veInds, veBegins, verts, elems, numMaterials); - testMesh.constructMeshRelations(); - testMesh.constructMeshVolumeFractionsMaps(elementVF); - testMesh.constructVertexPositionMap(points); - testMesh.constructElementParentMap(elementParents); - testMesh.constructElementDominantMaterialMap(elementDominantMaterials); + testMesh.initializeMesh(verts, elems, numMaterials, topology, mapData, elementVF); return testMesh; } @@ -184,42 +186,43 @@ mir::MIRMesh MeshTester::initTestCaseTwo() std::vector orangeVolumeFractions = {0.0, 0.0, 0.0, 0.0, 0.2, 0.0, 0.8, 0.7, 0.0}; elementVF[ORANGE] = orangeVolumeFractions; - mir::Point2 points[numVertices]; + std::vector points = { - points[0] = mir::Point2( 0.0, 3.0 ); - points[1] = mir::Point2( 1.0, 3.0 ); - points[2] = mir::Point2( 2.0, 3.0 ); - points[3] = mir::Point2( 3.0, 3.0 ); - - points[4] = mir::Point2( 0.0, 2.0 ); - points[5] = mir::Point2( 1.0, 2.0 ); - points[6] = mir::Point2( 2.0, 2.0 ); - points[7] = mir::Point2( 3.0, 2.0 ); - - points[8] = mir::Point2( 0.0, 1.0 ); - points[9] = mir::Point2( 1.0, 1.0 ); - points[10] = mir::Point2( 2.0, 1.0 ); - points[11] = mir::Point2( 3.0, 1.0 ); - - points[12] = mir::Point2( 0.0, 0.0 ); - points[13] = mir::Point2( 1.0, 0.0 ); - points[14] = mir::Point2( 2.0, 0.0 ); - points[15] = mir::Point2( 3.0, 0.0 ); - } - - - int elementParents[9] = { 0,1,2,3,4,5,6,7,8 }; // For the base mesh, the parents are always themselves - - std::vector elementDominantMaterials = {NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT}; + mir::Point2( 0.0, 3.0 ), + mir::Point2( 1.0, 3.0 ), + mir::Point2( 2.0, 3.0 ), + mir::Point2( 3.0, 3.0 ), + + mir::Point2( 0.0, 2.0 ), + mir::Point2( 1.0, 2.0 ), + mir::Point2( 2.0, 2.0 ), + mir::Point2( 3.0, 2.0 ), + + mir::Point2( 0.0, 1.0 ), + mir::Point2( 1.0, 1.0 ), + mir::Point2( 2.0, 1.0 ), + mir::Point2( 3.0, 1.0 ), + + mir::Point2( 0.0, 0.0 ), + mir::Point2( 1.0, 0.0 ), + mir::Point2( 2.0, 0.0 ), + mir::Point2( 3.0, 0.0 ) + }; + + CellTopologyData topology; + topology.evInds = evInds; + topology.evBegins = evBegins; + topology.veInds = veInds; + topology.veBegins = veBegins; + + CellMapData mapData; + mapData.elementDominantMaterials = {NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT}; + mapData.elementParents = { 0,1,2,3,4,5,6,7,8 }; // For the base mesh, the parents are always themselves + mapData.vertexPositions = points; // Build the mesh mir::MIRMesh testMesh; - testMesh.InitializeMesh(evInds, evBegins, veInds, veBegins, verts, elems, numMaterials); - testMesh.constructMeshRelations(); - testMesh.constructMeshVolumeFractionsMaps(elementVF); - testMesh.constructVertexPositionMap(points); - testMesh.constructElementParentMap(elementParents); - testMesh.constructElementDominantMaterialMap(elementDominantMaterials); + testMesh.initializeMesh(verts, elems, numMaterials, topology, mapData, elementVF); return testMesh; } @@ -268,29 +271,31 @@ mir::MIRMesh MeshTester::initTestCaseThree() std::vector redVolumeFractions = {1.0, 0.5, 0.2, 0.5}; elementVF[RED] = redVolumeFractions; - mir::Point2 points[numVertices]; + std::vector points = { - points[0] = mir::Point2( 1.0, 2.0 ); - points[1] = mir::Point2( 0.5, 1.0 ); - points[2] = mir::Point2( 1.5, 1.0 ); - points[3] = mir::Point2( 0.0, 0.0 ); - points[4] = mir::Point2( 1.0, 0.0 ); - points[5] = mir::Point2( 2.0, 0.0 ); - } - - - int elementParents[4] = { 0,1,2,3 }; // For the base mesh, the parents are always themselves - - std::vector elementDominantMaterials = {NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT}; + mir::Point2( 1.0, 2.0 ), + mir::Point2( 0.5, 1.0 ), + mir::Point2( 1.5, 1.0 ), + mir::Point2( 0.0, 0.0 ), + mir::Point2( 1.0, 0.0 ), + mir::Point2( 2.0, 0.0 ) + }; + + + CellTopologyData topology; + topology.evInds = evInds; + topology.evBegins = evBegins; + topology.veInds = veInds; + topology.veBegins = veBegins; + + CellMapData mapData; + mapData.elementDominantMaterials = {NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT}; + mapData.elementParents = { 0,1,2,3 }; // For the base mesh, the parents are always themselves + mapData.vertexPositions = points; // Build the mesh mir::MIRMesh testMesh; - testMesh.InitializeMesh(evInds, evBegins, veInds, veBegins, verts, elems, numMaterials); - testMesh.constructMeshRelations(); - testMesh.constructMeshVolumeFractionsMaps(elementVF); - testMesh.constructVertexPositionMap(points); - testMesh.constructElementParentMap(elementParents); - testMesh.constructElementDominantMaterialMap(elementDominantMaterials); + testMesh.initializeMesh(verts, elems, numMaterials, topology, mapData, elementVF); return testMesh; } @@ -344,28 +349,28 @@ mir::MIRMesh MeshTester::initTestCaseFour() mir::VertSet verts = mir::VertSet(numVertices); // Construct a vertex set with 16 vertices mir::ElemSet elems = mir::ElemSet(numElements); // Construct an element set with 9 elements - mir::Point2 points[numVertices]; + std::vector points = { - points[0] = mir::Point2( 0.0, 3.0 ); - points[1] = mir::Point2( 1.0, 3.0 ); - points[2] = mir::Point2( 2.0, 3.0 ); - points[3] = mir::Point2( 3.0, 3.0 ); - - points[4] = mir::Point2( 0.0, 2.0 ); - points[5] = mir::Point2( 1.0, 2.0 ); - points[6] = mir::Point2( 2.0, 2.0 ); - points[7] = mir::Point2( 3.0, 2.0 ); - - points[8] = mir::Point2( 0.0, 1.0 ); - points[9] = mir::Point2( 1.0, 1.0 ); - points[10] = mir::Point2( 2.0, 1.0 ); - points[11] = mir::Point2( 3.0, 1.0 ); - - points[12] = mir::Point2( 0.0, 0.0 ); - points[13] = mir::Point2( 1.0, 0.0 ); - points[14] = mir::Point2( 2.0, 0.0 ); - points[15] = mir::Point2( 3.0, 0.0 ); - } + mir::Point2( 0.0, 3.0 ), + mir::Point2( 1.0, 3.0 ), + mir::Point2( 2.0, 3.0 ), + mir::Point2( 3.0, 3.0 ), + + mir::Point2( 0.0, 2.0 ), + mir::Point2( 1.0, 2.0 ), + mir::Point2( 2.0, 2.0 ), + mir::Point2( 3.0, 2.0 ), + + mir::Point2( 0.0, 1.0 ), + mir::Point2( 1.0, 1.0 ), + mir::Point2( 2.0, 1.0 ), + mir::Point2( 3.0, 1.0 ), + + mir::Point2( 0.0, 0.0 ), + mir::Point2( 1.0, 0.0 ), + mir::Point2( 2.0, 0.0 ), + mir::Point2( 3.0, 0.0 ) + }; int numMaterials = 2; enum { GREEN = 0, BLUE = 1 }; @@ -388,18 +393,20 @@ mir::MIRMesh MeshTester::initTestCaseFour() elementVF[GREEN] = greenVolumeFractions; elementVF[BLUE] = blueVolumeFractions; - int elementParents[9] = { 0,1,2,3,4,5,6,7,8 }; // For the base mesh, the parents are always themselves + CellTopologyData topology; + topology.evInds = evInds; + topology.evBegins = evBegins; + topology.veInds = veInds; + topology.veBegins = veBegins; - std::vector elementDominantMaterials = {NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT}; + CellMapData mapData; + mapData.elementDominantMaterials = {NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT}; + mapData.elementParents = { 0,1,2,3,4,5,6,7,8 }; // For the base mesh, the parents are always themselves + mapData.vertexPositions = points; // Build the mesh mir::MIRMesh testMesh; - testMesh.InitializeMesh(evInds, evBegins, veInds, veBegins, verts, elems, numMaterials); - testMesh.constructMeshRelations(); - testMesh.constructMeshVolumeFractionsMaps(elementVF); - testMesh.constructVertexPositionMap(points); - testMesh.constructElementParentMap(elementParents); - testMesh.constructElementDominantMaterialMap(elementDominantMaterials); + testMesh.initializeMesh(verts, elems, numMaterials, topology, mapData, elementVF); return testMesh; } @@ -428,35 +435,40 @@ mir::MIRMesh MeshTester::createUniformGridTestCaseMesh(int gridSize, mir::Point2 for (int i = 0; i < cellData.numElems; ++i) { greenVolumeFractions[i] = calculatePercentOverlapMonteCarlo(numMonteCarloSamples, circleCenter, circleRadius, - cellData.vertexPositions[cellData.evInds[i * 4 + 0]], - cellData.vertexPositions[cellData.evInds[i * 4 + 1]], - cellData.vertexPositions[cellData.evInds[i * 4 + 2]], - cellData.vertexPositions[cellData.evInds[i * 4 + 3]]); + cellData.mapData.vertexPositions[cellData.topology.evInds[i * 4 + 0]], + cellData.mapData.vertexPositions[cellData.topology.evInds[i * 4 + 1]], + cellData.mapData.vertexPositions[cellData.topology.evInds[i * 4 + 2]], + cellData.mapData.vertexPositions[cellData.topology.evInds[i * 4 + 3]]); blueVolumeFractions[i] = 1.0 - greenVolumeFractions[i]; } elementVF[GREEN] = greenVolumeFractions; elementVF[BLUE] = blueVolumeFractions; - int elementParents[cellData.numElems]; // For the base mesh, the parents are always themselves + std::vector elementParents;// For the base mesh, the parents are always themselves std::vector elementDominantMaterials; for (int i = 0; i < cellData.numElems; ++i) { - elementParents[i] = i; + elementParents.push_back(i); elementDominantMaterials.push_back(NULL_MAT); } + CellTopologyData topology; + topology.evInds = cellData.topology.evInds; + topology.evBegins = cellData.topology.evBegins; + topology.veInds = cellData.topology.veInds; + topology.veBegins = cellData.topology.veBegins; + + CellMapData mapData; + mapData.elementDominantMaterials = elementDominantMaterials; + mapData.elementParents = elementParents; + mapData.vertexPositions = cellData.mapData.vertexPositions; + // Build the mesh mir::MIRMesh testMesh; - testMesh.InitializeMesh(cellData.evInds, cellData.evBegins, cellData.veInds, cellData.veBegins, verts, elems, numMaterials); - testMesh.constructMeshRelations(); - testMesh.constructMeshVolumeFractionsMaps(elementVF); - testMesh.constructVertexPositionMap(cellData.vertexPositions.data()); - testMesh.constructElementParentMap(elementParents); - testMesh.constructElementDominantMaterialMap(elementDominantMaterials); + testMesh.initializeMesh(verts, elems, numMaterials, topology, mapData, elementVF); return testMesh; - } //-------------------------------------------------------------------------------- @@ -572,11 +584,11 @@ mir::CellData MeshTester::generateGrid(int n) mir::CellData data; data.numVerts = numVertices; data.numElems = numElements; - data.evInds = evInds; - data.evBegins = evBegins; - data.veInds = veInds; - data.veBegins = veBegins; - data.vertexPositions = points; + data.topology.evInds = evInds; + data.topology.evBegins = evBegins; + data.topology.veInds = veInds; + data.topology.veBegins = veBegins; + data.mapData.vertexPositions = points; // // Print out the results // printf("evInds: { "); @@ -674,10 +686,10 @@ mir::MIRMesh MeshTester::initTestCaseFive(int gridSize, int numCircles) // Use the uniform sampling method to generate volume fractions for each material for (int eID = 0; eID < cellData.numElems; ++eID) { - mir::Point2 v0 = cellData.vertexPositions[cellData.evInds[eID * 4 + 0]]; - mir::Point2 v1 = cellData.vertexPositions[cellData.evInds[eID * 4 + 1]]; - mir::Point2 v2 = cellData.vertexPositions[cellData.evInds[eID * 4 + 2]]; - mir::Point2 v3 = cellData.vertexPositions[cellData.evInds[eID * 4 + 3]]; + mir::Point2 v0 = cellData.mapData.vertexPositions[cellData.topology.evInds[eID * 4 + 0]]; + mir::Point2 v1 = cellData.mapData.vertexPositions[cellData.topology.evInds[eID * 4 + 1]]; + mir::Point2 v2 = cellData.mapData.vertexPositions[cellData.topology.evInds[eID * 4 + 2]]; + mir::Point2 v3 = cellData.mapData.vertexPositions[cellData.topology.evInds[eID * 4 + 3]]; // Run the uniform sampling to determine how much of the current cell is composed of each material int materialCount[numMaterials]; for (int i = 0; i < numMaterials; ++i) materialCount[i] = 0; @@ -719,25 +731,30 @@ mir::MIRMesh MeshTester::initTestCaseFive(int gridSize, int numCircles) } } - int elementParents[cellData.numElems]; // For the base mesh, the parents are always themselves + std::vector elementParents; // For the base mesh, the parents are always themselves std::vector elementDominantMaterials; for (int i = 0; i < cellData.numElems; ++i) { - elementParents[i] = i; + elementParents.push_back(i); elementDominantMaterials.push_back(NULL_MAT); } + CellTopologyData topology; + topology.evInds = cellData.topology.evInds; + topology.evBegins = cellData.topology.evBegins; + topology.veInds = cellData.topology.veInds; + topology.veBegins = cellData.topology.veBegins; + + CellMapData mapData; + mapData.elementDominantMaterials = elementDominantMaterials; + mapData.elementParents = elementParents; + mapData.vertexPositions = cellData.mapData.vertexPositions; + // Build the mesh mir::MIRMesh testMesh; - testMesh.InitializeMesh(cellData.evInds, cellData.evBegins, cellData.veInds, cellData.veBegins, verts, elems, numMaterials); - testMesh.constructMeshRelations(); - testMesh.constructMeshVolumeFractionsMaps(materialVolumeFractionsData); - testMesh.constructVertexPositionMap(cellData.vertexPositions.data()); - testMesh.constructElementParentMap(elementParents); - testMesh.constructElementDominantMaterialMap(elementDominantMaterials); + testMesh.initializeMesh(verts, elems, numMaterials, topology, mapData, materialVolumeFractionsData); return testMesh; - } //-------------------------------------------------------------------------------- diff --git a/src/axom/mir/examples/mir_tutorial_simple.cpp b/src/axom/mir/examples/mir_tutorial_simple.cpp index 9feba3ec87..d35fe0bfbc 100644 --- a/src/axom/mir/examples/mir_tutorial_simple.cpp +++ b/src/axom/mir/examples/mir_tutorial_simple.cpp @@ -27,10 +27,10 @@ int main( int AXOM_NOT_USED(argc), char** AXOM_NOT_USED(argv) ) auto start_time = Clock::now(); mir::MeshTester tester; // mir::MIRMesh testMesh = tester.initTestCaseOne(); - mir::MIRMesh testMesh = tester.initTestCaseTwo(); + // mir::MIRMesh testMesh = tester.initTestCaseTwo(); // mir::MIRMesh testMesh = tester.initTestCaseThree(); // mir::MIRMesh testMesh = tester.initTestCaseFour(); - // mir::MIRMesh testMesh = tester.initTestCaseFive(20, 5); + mir::MIRMesh testMesh = tester.initTestCaseFive(25, 7); auto end_time = Clock::now(); std::cout << "Mesh init time: " << std::chrono::duration_cast(end_time - start_time).count() << " ms" << std::endl; @@ -40,13 +40,13 @@ int main( int AXOM_NOT_USED(argc), char** AXOM_NOT_USED(argv) ) mir::InterfaceReconstructor reconstructor; // mir::MIRMesh processedMesh = reconstructor.computeReconstructedInterface(&testMesh); - mir::MIRMesh processedMesh = reconstructor.computeReconstructedInterfaceIterative(&testMesh, 5, 0.9); // 5 iterations, 90 percent + mir::MIRMesh processedMesh = reconstructor.computeReconstructedInterfaceIterative(&testMesh, 5, 0.3); // 5 iterations, 90 percent end_time = Clock::now(); std::cout << "Material interface reconstruction time: " << std::chrono::duration_cast(end_time - start_time).count() << " ms" << std::endl; // Output results - processedMesh.writeMeshToFile("/Users/sterbentz3/Desktop/iterativeOutputTestMesh2.vtk"); + processedMesh.writeMeshToFile("/Users/sterbentz3/Desktop/iterativeOutputTestMesh5.vtk"); return 0; } From a6892f1d27eff425579d1f6f627d4e193f7ceaf0 Mon Sep 17 00:00:00 2001 From: Sterbentz Date: Fri, 21 Jun 2019 08:39:09 -0700 Subject: [PATCH 10/23] Cleaned up code. Inserted doxygen comments. --- src/axom/mir/CellData.cpp | 49 ++- src/axom/mir/CellData.hpp | 70 +++- src/axom/mir/InterfaceReconstructor.cpp | 352 +++++++++--------- src/axom/mir/InterfaceReconstructor.hpp | 258 +++++++++++-- src/axom/mir/MIRMesh.cpp | 253 ++++++------- src/axom/mir/MIRMesh.hpp | 177 +++++++-- src/axom/mir/MIRMeshTypes.hpp | 6 + src/axom/mir/MeshTester.cpp | 241 ++++++------ src/axom/mir/MeshTester.hpp | 147 +++++++- src/axom/mir/ZooClippingTables.cpp | 4 +- src/axom/mir/ZooClippingTables.hpp | 17 + src/axom/mir/examples/CMakeLists.txt | 2 +- ...Circles.cpp => mir_concentric_circles.cpp} | 16 +- src/axom/mir/examples/mir_tutorial_simple.cpp | 22 +- src/axom/mir/tests/CMakeLists.txt | 1 + .../mir/tests/mir_interface_reconstructor.cpp | 55 +++ 16 files changed, 1115 insertions(+), 555 deletions(-) rename src/axom/mir/examples/{mirConcentricCircles.cpp => mir_concentric_circles.cpp} (83%) create mode 100644 src/axom/mir/tests/mir_interface_reconstructor.cpp diff --git a/src/axom/mir/CellData.cpp b/src/axom/mir/CellData.cpp index d8f35d30e4..4e37813c86 100644 --- a/src/axom/mir/CellData.cpp +++ b/src/axom/mir/CellData.cpp @@ -26,67 +26,66 @@ namespace mir //-------------------------------------------------------------------------------- - /// Merges the cell data from the given cell into this cell - void CellData::mergeCell(CellData cellToMerge) + void CellData::mergeCell(const CellData& cellToMerge) { // Initialize index offsets - int evBeginsOffset = topology.evInds.size(); - int veBeginsOffset = topology.veInds.size(); + int evBeginsOffset = m_topology.m_evInds.size(); + int veBeginsOffset = m_topology.m_veInds.size(); - int vertexIndexOffset = numVerts; - int elementIndexOffset = numElems; + int vertexIndexOffset = m_numVerts; + int elementIndexOffset = m_numElems; // Merge the cell topology information - for (unsigned long i = 0; i < cellToMerge.topology.evInds.size(); ++i) + for (unsigned long i = 0; i < cellToMerge.m_topology.m_evInds.size(); ++i) { - topology.evInds.push_back(cellToMerge.topology.evInds[i] + vertexIndexOffset); + m_topology.m_evInds.push_back(cellToMerge.m_topology.m_evInds[i] + vertexIndexOffset); } - for (unsigned long i = 1; i < cellToMerge.topology.evBegins.size(); ++i) + for (unsigned long i = 1; i < cellToMerge.m_topology.m_evBegins.size(); ++i) { - topology.evBegins.push_back(cellToMerge.topology.evBegins[i] + evBeginsOffset); + m_topology.m_evBegins.push_back(cellToMerge.m_topology.m_evBegins[i] + evBeginsOffset); } - for (unsigned long i = 0; i < cellToMerge.topology.veInds.size(); ++i) + for (unsigned long i = 0; i < cellToMerge.m_topology.m_veInds.size(); ++i) { - topology.veInds.push_back(cellToMerge.topology.veInds[i] + elementIndexOffset); + m_topology.m_veInds.push_back(cellToMerge.m_topology.m_veInds[i] + elementIndexOffset); } - for (unsigned long i = 1; i < cellToMerge.topology.veBegins.size(); ++i) + for (unsigned long i = 1; i < cellToMerge.m_topology.m_veBegins.size(); ++i) { - topology.veBegins.push_back(cellToMerge.topology.veBegins[i] + veBeginsOffset); + m_topology.m_veBegins.push_back(cellToMerge.m_topology.m_veBegins[i] + veBeginsOffset); } // Merge the vertex positions - for (unsigned long i = 0; i < cellToMerge.mapData.vertexPositions.size(); ++i) + for (unsigned long i = 0; i < cellToMerge.m_mapData.m_vertexPositions.size(); ++i) { - mapData.vertexPositions.push_back(cellToMerge.mapData.vertexPositions[i]); + m_mapData.m_vertexPositions.push_back(cellToMerge.m_mapData.m_vertexPositions[i]); } // Merge the vertex volume fractions - for (unsigned long matID = 0; matID < mapData.vertexVolumeFractions.size(); ++matID) + for (unsigned long matID = 0; matID < m_mapData.m_vertexVolumeFractions.size(); ++matID) { - for (unsigned long vID = 0; vID < cellToMerge.mapData.vertexVolumeFractions[matID].size(); ++vID) + for (unsigned long vID = 0; vID < cellToMerge.m_mapData.m_vertexVolumeFractions[matID].size(); ++vID) { - mapData.vertexVolumeFractions[matID].push_back(cellToMerge.mapData.vertexVolumeFractions[matID][vID]); + m_mapData.m_vertexVolumeFractions[matID].push_back(cellToMerge.m_mapData.m_vertexVolumeFractions[matID][vID]); } } // Merge the elements' dominant materials - for (unsigned long i = 0; i < cellToMerge.mapData.elementDominantMaterials.size(); ++i) + for (unsigned long i = 0; i < cellToMerge.m_mapData.m_elementDominantMaterials.size(); ++i) { - mapData.elementDominantMaterials.push_back(cellToMerge.mapData.elementDominantMaterials[i]); + m_mapData.m_elementDominantMaterials.push_back(cellToMerge.m_mapData.m_elementDominantMaterials[i]); } // Merge the elements' parent ids - for (unsigned long i = 0; i < cellToMerge.mapData.elementParents.size(); ++i) + for (unsigned long i = 0; i < cellToMerge.m_mapData.m_elementParents.size(); ++i) { - mapData.elementParents.push_back(cellToMerge.mapData.elementParents[i]); + m_mapData.m_elementParents.push_back(cellToMerge.m_mapData.m_elementParents[i]); } // Merge the total number of verts and elems in the resulting cell - numVerts += cellToMerge.numVerts; - numElems += cellToMerge.numElems; + m_numVerts += cellToMerge.m_numVerts; + m_numElems += cellToMerge.m_numElems; } //-------------------------------------------------------------------------------- diff --git a/src/axom/mir/CellData.hpp b/src/axom/mir/CellData.hpp index 7603582a30..1a681e5d19 100644 --- a/src/axom/mir/CellData.hpp +++ b/src/axom/mir/CellData.hpp @@ -3,6 +3,13 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +/** + * \file CellData.hpp + * + * \brief Contains the specifications for the CellData class + * and CellTopologyData and CellMapData structs. + */ + #ifndef __CELL_DATA_H__ #define __CELL_DATA_H__ @@ -19,40 +26,69 @@ namespace axom namespace mir { - // Struct for collecting data that specifies a mesh or cell's connectivity/topology. + /** + * \struct CellTopologyData + * + * \brief Struct for collecting data that specifies a mesh or cell's connectivity/topology. + */ struct CellTopologyData { - std::vector evInds; - std::vector evBegins; - std::vector veInds; - std::vector veBegins; + std::vector m_evInds; + std::vector m_evBegins; + std::vector m_veInds; + std::vector m_veBegins; }; - // Struct for collecting the data that will populate a mesh's map data structures. + /** + * \struct CellMapData + * + * \brief Struct for collecting the data that will populate a mesh's map data structures. + */ struct CellMapData { - std::vector vertexPositions; // Data that goes into MIRMesh's vertexPositions PointMap - std::vector > vertexVolumeFractions; // Data that goes into MIRMesh's materialVolumeFractionsVertex ScalarMap - std::vector elementDominantMaterials; // Data that goes into MIRMesh's elementDominantColors IntMap - std::vector elementParents; // Data that goes into MIRMesh's elementParentIDs IntMap + std::vector m_vertexPositions; // Data that goes into MIRMesh's vertexPositions PointMap + std::vector > m_vertexVolumeFractions; // Data that goes into MIRMesh's materialVolumeFractionsVertex ScalarMap + std::vector m_elementDominantMaterials; // Data that goes into MIRMesh's elementDominantColors IntMap + std::vector m_elementParents; // Data that goes into MIRMesh's elementParentIDs IntMap }; - /// Represents an arbitrary number of cells that are within the same local coordinate system (i.e. share a set of vertices and elements). - /// Intended to be used as a helper class to hold intermediate data while processing a mesh, and to be used as input to the MIRMesh class to fully initialize it. + /** + * \class CellData + * + * \brief The CellData class represents an arbitrary number of cells that are + * within the same local coordinate system (i.e. share a set of vertices + * and elements). + * + * \detail This class is intended to be used as a helper class to hold intermediate + * data while processing a mesh, and to be used as input to the MIRMesh class + * to fully initialize it. + */ class CellData { public: + /** + * \brief Default constructor. + */ CellData(); + + /** + * \brief Default destructor. + */ ~CellData(); - void mergeCell(CellData cellToMerge); + /** + * \brief Merges the cell data from the given cell into this cell. + * + * \param cellToMerge The cell whose data will be taken and merged in. + */ + void mergeCell(const CellData& cellToMerge); public: - int numVerts; - int numElems; + int m_numVerts; + int m_numElems; - CellTopologyData topology; - CellMapData mapData; + CellTopologyData m_topology; + CellMapData m_mapData; }; } } diff --git a/src/axom/mir/InterfaceReconstructor.cpp b/src/axom/mir/InterfaceReconstructor.cpp index 6408a1b653..9152dbb30d 100644 --- a/src/axom/mir/InterfaceReconstructor.cpp +++ b/src/axom/mir/InterfaceReconstructor.cpp @@ -12,7 +12,6 @@ namespace mir //-------------------------------------------------------------------------------- -/// Default constructor InterfaceReconstructor::InterfaceReconstructor() { @@ -20,7 +19,6 @@ InterfaceReconstructor::InterfaceReconstructor() //-------------------------------------------------------------------------------- -/// Destructor InterfaceReconstructor::~InterfaceReconstructor() { @@ -28,46 +26,46 @@ InterfaceReconstructor::~InterfaceReconstructor() //-------------------------------------------------------------------------------- -/// Reconstructs the material interface and returns the resulting output mesh. -mir::MIRMesh InterfaceReconstructor::computeReconstructedInterface(mir::MIRMesh* inputMesh) +mir::MIRMesh InterfaceReconstructor::computeReconstructedInterface(mir::MIRMesh& inputMesh) { - // Store a pointer to the original mesh - originalMesh = inputMesh; + // Store a reference to the original mesh + m_originalMesh = inputMesh; // Initialize the final mesh to be the same as the input mesh - mir::MIRMesh finalMesh(originalMesh); + mir::MIRMesh finalMesh(m_originalMesh); // For each material in the mesh, split the mesh on the current material and generate a new mesh (input into next iteration) - for (int matID = 0; matID < originalMesh->numMaterials; ++matID) + for (int matID = 0; matID < m_originalMesh.m_numMaterials; ++matID) { // Copy the mesh to be split mir::MIRMesh intermediateMesh(&finalMesh); - // Update the materials upon which the split will occur - int matOne = matID; - // Create an array to store the output of each element being split. - CellData temp_cellData[intermediateMesh.elems.size()]; + CellData temp_cellData[intermediateMesh.m_elems.size()]; // Process/split each element - for (int eID = 0; eID < intermediateMesh.elems.size(); ++eID) + for (int eID = 0; eID < intermediateMesh.m_elems.size(); ++eID) { - // Update the materials upon which the split will occur (should be the currently dominant material and the next material in the list that hasn't yet been split on) - int currentDominantMat = intermediateMesh.elementDominantMaterials[eID]; - computeClippingPoints(eID, currentDominantMat, matOne, &intermediateMesh, temp_cellData[eID]); + // Update the materials upon which the split will occur + int currentDominantMat = intermediateMesh.m_elementDominantMaterials[eID]; + int matOne = matID; + + computeClippingPoints(eID, currentDominantMat, matOne, intermediateMesh, temp_cellData[eID]); } // Merge each of the cells into the first CellData struct - for (int eID = 1; eID < intermediateMesh.elems.size(); ++eID) + for (int eID = 1; eID < intermediateMesh.m_elems.size(); ++eID) + { temp_cellData[0].mergeCell(temp_cellData[eID]); + } - mir::VertSet combined_verts(temp_cellData[0].numVerts); - mir::ElemSet combined_elems(temp_cellData[0].numElems); + mir::VertSet combined_verts(temp_cellData[0].m_numVerts); + mir::ElemSet combined_elems(temp_cellData[0].m_numElems); // Create the final, processed mesh mir::MIRMesh processedMesh; - processedMesh.initializeMesh(combined_verts, combined_elems, intermediateMesh.numMaterials, temp_cellData[0].topology, temp_cellData[0].mapData); - processedMesh.constructMeshVolumeFractionsVertex(temp_cellData[0].mapData.vertexVolumeFractions); + processedMesh.initializeMesh(combined_verts, combined_elems, intermediateMesh.m_numMaterials, temp_cellData[0].m_topology, temp_cellData[0].m_mapData); + processedMesh.constructMeshVolumeFractionsVertex(temp_cellData[0].m_mapData.m_vertexVolumeFractions); // Store the current mesh to be passed into the next iteration finalMesh = processedMesh; @@ -79,18 +77,18 @@ mir::MIRMesh InterfaceReconstructor::computeReconstructedInterface(mir::MIRMesh* return finalMesh; } -/// Reconstructs the material interface using an iterative optimization to improve the volume fractions of the resulting mesh. -/// Based on the Meredith and Childs 2010 paper. -mir::MIRMesh InterfaceReconstructor::computeReconstructedInterfaceIterative(mir::MIRMesh* inputMesh, int numIterations, axom::float64 percent) +//-------------------------------------------------------------------------------- + +mir::MIRMesh InterfaceReconstructor::computeReconstructedInterfaceIterative(mir::MIRMesh& inputMesh, const int numIterations, const axom::float64 percent) { - int numElems = inputMesh->elems.size(); - int numMaterials = inputMesh->numMaterials; + int numElems = inputMesh.m_elems.size(); + int numMaterials = inputMesh.m_numMaterials; // Make a copy of the original input mesh mir::MIRMesh meshToImprove(inputMesh); // Calculate the reconstruction on the unmodified, input mesh - mir::MIRMesh resultingMesh = computeReconstructedInterface(&meshToImprove); + mir::MIRMesh resultingMesh = computeReconstructedInterface(meshToImprove); for (int it = 0; it < numIterations; ++it) { @@ -106,8 +104,8 @@ mir::MIRMesh InterfaceReconstructor::computeReconstructedInterfaceIterative(mir: { for (int eID = 0; eID < numElems; ++eID) { - axom::float64 difference = inputMesh->materialVolumeFractionsElement[matID][eID] - resultingElementVF[matID][eID]; - improvedElementVF[matID].push_back( inputMesh->materialVolumeFractionsElement[matID][eID] + ( difference * percent ) ); + axom::float64 difference = inputMesh.m_materialVolumeFractionsElement[matID][eID] - resultingElementVF[matID][eID]; + improvedElementVF[matID].push_back( inputMesh.m_materialVolumeFractionsElement[matID][eID] + ( difference * percent ) ); } } @@ -115,7 +113,7 @@ mir::MIRMesh InterfaceReconstructor::computeReconstructedInterfaceIterative(mir: meshToImprove.constructMeshVolumeFractionsMaps(improvedElementVF); // Calculate the reconstruction on the modified, input mesh - resultingMesh = computeReconstructedInterface(&meshToImprove); + resultingMesh = computeReconstructedInterface(meshToImprove); } return resultingMesh; @@ -123,11 +121,10 @@ mir::MIRMesh InterfaceReconstructor::computeReconstructedInterfaceIterative(mir: //-------------------------------------------------------------------------------- -/// Computes the points where the element should be clipped based on the volume fractions of mat one and two. -void InterfaceReconstructor::computeClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh, CellData& out_cellData) +void InterfaceReconstructor::computeClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh& tempMesh, CellData& out_cellData) { // Find the vertices associated with each element - auto elementVertices = tempMesh->bdry[eID]; + auto elementVertices = tempMesh.m_bdry[eID]; // Triangle Case if (elementVertices.size() == 3) @@ -143,12 +140,10 @@ void InterfaceReconstructor::computeClippingPoints(const int eID, const int matO //-------------------------------------------------------------------------------- -void InterfaceReconstructor::computeQuadClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh, CellData& out_cellData) +void InterfaceReconstructor::computeQuadClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh& tempMesh, CellData& out_cellData) { - // printf(" Processing QUAD element %d: ", eID); - // Determine the clipping case - auto elementVertices = tempMesh->bdry[eID]; + auto elementVertices = tempMesh.m_bdry[eID]; int upperLeftVertex = elementVertices[0]; int lowerLeftVertex = elementVertices[1]; @@ -156,7 +151,6 @@ void InterfaceReconstructor::computeQuadClippingPoints(const int eID, const int int upperRightVertex = elementVertices[3]; unsigned int caseIndex = determineQuadClippingCase(tempMesh, matOneID, matTwoID, upperLeftVertex, lowerLeftVertex, lowerRightVertex, upperRightVertex); - // printf("caseIndex: %d\n", caseIndex); // Generate new elements std::map > newElements; // hashmap of the new elements' vertices | Note: maps are ordered sets @@ -210,12 +204,9 @@ void InterfaceReconstructor::computeQuadClippingPoints(const int eID, const int numVertices = quadClipTable[caseIndex][i]; } - /**************************************************************** - * CALCULATE THE NEW CELLS' DATA - ****************************************************************/ // Calculate the total number of elements and vertices that were generated from splitting the current element - out_cellData.numElems = (int) newElements.size(); - out_cellData.numVerts = (int) newVertices.size(); + out_cellData.m_numElems = (int) newElements.size(); + out_cellData.m_numVerts = (int) newVertices.size(); generateTopologyData(newElements, newVertices, out_cellData); generateVertexPositionsFromQuad(newVertices, tempMesh, verticesClippingTValue, upperLeftVertex, lowerLeftVertex, lowerRightVertex, upperRightVertex, out_cellData); @@ -224,47 +215,30 @@ void InterfaceReconstructor::computeQuadClippingPoints(const int eID, const int // Determine and store the dominant material of this element for (auto itr = newElements.begin(); itr != newElements.end(); itr++) { - int currentDominantMat = NULL_MAT; - for (unsigned long it = 0; it < itr->second.size(); ++it) - { - // int temp_vID = newElements[currentElementIndex][it]; - int temp_vID = itr->second[it]; - - // Find the vertex that is one of the four original vertices of the quad element - if (temp_vID == 0 || temp_vID == 1 || temp_vID == 2 || temp_vID == 3) - { - axom::float64 matOneVolumeFraction = -1.0, matTwoVolumeFraction = -1.0; - if (matOneID != NULL_MAT) - matOneVolumeFraction = out_cellData.mapData.vertexVolumeFractions[matOneID][temp_vID]; - if (matTwoID != NULL_MAT) - matTwoVolumeFraction = out_cellData.mapData.vertexVolumeFractions[matTwoID][temp_vID]; - - currentDominantMat = (matOneVolumeFraction > matTwoVolumeFraction) ? matOneID : matTwoID; - } - } - out_cellData.mapData.elementDominantMaterials.push_back(currentDominantMat); + int dominantMaterial = determineDominantMaterial(Shape::Quad, itr->second, matOneID, matTwoID, out_cellData.m_mapData.m_vertexVolumeFractions); + out_cellData.m_mapData.m_elementDominantMaterials.push_back(dominantMaterial); } // Determine and store the parent of this element - out_cellData.mapData.elementParents.resize(newElements.size()); + out_cellData.m_mapData.m_elementParents.resize(newElements.size()); for (auto itr = newElements.begin(); itr != newElements.end(); itr++) { - out_cellData.mapData.elementParents[itr->first] = tempMesh->elementParentIDs[eID]; + out_cellData.m_mapData.m_elementParents[itr->first] = tempMesh.m_elementParentIDs[eID]; } // Check that each element is dominated by a material that is actually present in the original parent cell for (auto itr = newElements.begin(); itr != newElements.end(); itr++) { - int parentElementID = out_cellData.mapData.elementParents[itr->first]; - int currentDominantMaterial = out_cellData.mapData.elementDominantMaterials[itr->first]; + int parentElementID = out_cellData.m_mapData.m_elementParents[itr->first]; + int currentDominantMaterial = out_cellData.m_mapData.m_elementDominantMaterials[itr->first]; - if (originalMesh->materialVolumeFractionsElement[currentDominantMaterial][parentElementID] == 0) + if (m_originalMesh.m_materialVolumeFractionsElement[currentDominantMaterial][parentElementID] == 0) { // This material is not present in the original element from which the current element comes from if (currentDominantMaterial == matOneID) - out_cellData.mapData.elementDominantMaterials[itr->first] = matTwoID; + out_cellData.m_mapData.m_elementDominantMaterials[itr->first] = matTwoID; else - out_cellData.mapData.elementDominantMaterials[itr->first] = matOneID; + out_cellData.m_mapData.m_elementDominantMaterials[itr->first] = matOneID; } } @@ -280,16 +254,15 @@ void InterfaceReconstructor::computeQuadClippingPoints(const int eID, const int for (int i = 1; i < 8; ++i) evIndexSubtract[i] += evIndexSubtract[i - 1]; - for (unsigned int i = 0; i < out_cellData.topology.evInds.size(); ++i) + for (unsigned int i = 0; i < out_cellData.m_topology.m_evInds.size(); ++i) { - out_cellData.topology.evInds[i] -= evIndexSubtract[ out_cellData.topology.evInds[i] ]; + out_cellData.m_topology.m_evInds[i] -= evIndexSubtract[ out_cellData.m_topology.m_evInds[i] ]; } } //-------------------------------------------------------------------------------- -/// Finds the bit map representing the clipping case for a quad. -unsigned int InterfaceReconstructor::determineQuadClippingCase(mir::MIRMesh* tempMesh, const int matOneID, const int matTwoID, const int upperLeftVertex, const int lowerLeftVertex, const int lowerRightVertex, const int upperRightVertex) +unsigned int InterfaceReconstructor::determineQuadClippingCase(mir::MIRMesh& tempMesh, const int matOneID, const int matTwoID, const int upperLeftVertex, const int lowerLeftVertex, const int lowerRightVertex, const int upperRightVertex) { // Determine the dominant color at each vertex int upperLeftColor = matOneID, lowerLeftColor = matOneID, lowerRightColor = matOneID, upperRightColor = matOneID; @@ -300,10 +273,10 @@ unsigned int InterfaceReconstructor::determineQuadClippingCase(mir::MIRMesh* tem upperLeftColor = lowerLeftColor = lowerRightColor = upperRightColor = matOneID; if (matOneID != NULL_MAT && matTwoID != NULL_MAT) { - upperLeftColor = tempMesh->materialVolumeFractionsVertex[matOneID][upperLeftVertex] > tempMesh->materialVolumeFractionsVertex[matTwoID][upperLeftVertex] ? matOneID : matTwoID; - lowerLeftColor = tempMesh->materialVolumeFractionsVertex[matOneID][lowerLeftVertex] > tempMesh->materialVolumeFractionsVertex[matTwoID][lowerLeftVertex] ? matOneID : matTwoID; - lowerRightColor = tempMesh->materialVolumeFractionsVertex[matOneID][lowerRightVertex] > tempMesh->materialVolumeFractionsVertex[matTwoID][lowerRightVertex] ? matOneID : matTwoID; - upperRightColor = tempMesh->materialVolumeFractionsVertex[matOneID][upperRightVertex] > tempMesh->materialVolumeFractionsVertex[matTwoID][upperRightVertex] ? matOneID : matTwoID; + upperLeftColor = tempMesh.m_materialVolumeFractionsVertex[matOneID][upperLeftVertex] > tempMesh.m_materialVolumeFractionsVertex[matTwoID][upperLeftVertex] ? matOneID : matTwoID; + lowerLeftColor = tempMesh.m_materialVolumeFractionsVertex[matOneID][lowerLeftVertex] > tempMesh.m_materialVolumeFractionsVertex[matTwoID][lowerLeftVertex] ? matOneID : matTwoID; + lowerRightColor = tempMesh.m_materialVolumeFractionsVertex[matOneID][lowerRightVertex] > tempMesh.m_materialVolumeFractionsVertex[matTwoID][lowerRightVertex] ? matOneID : matTwoID; + upperRightColor = tempMesh.m_materialVolumeFractionsVertex[matOneID][upperRightVertex] > tempMesh.m_materialVolumeFractionsVertex[matTwoID][upperRightVertex] ? matOneID : matTwoID; } // Create the index into the quad clipping lookup table using the dominant colors at each vertex @@ -318,8 +291,7 @@ unsigned int InterfaceReconstructor::determineQuadClippingCase(mir::MIRMesh* tem //-------------------------------------------------------------------------------- -/// Performs linear interpolation between the two given vertex positions. -mir::Point2 InterfaceReconstructor::interpolateVertexPosition(mir::Point2 vertexOnePos, mir::Point2 vertexTwoPos, float t) +mir::Point2 InterfaceReconstructor::interpolateVertexPosition(const mir::Point2& vertexOnePos, const mir::Point2& vertexTwoPos, const float t) { mir::Point2 interpolatedPoint; interpolatedPoint.m_x = (1 - t) * vertexOnePos.m_x + t * vertexTwoPos.m_x; @@ -329,24 +301,21 @@ mir::Point2 InterfaceReconstructor::interpolateVertexPosition(mir::Point2 vertex //-------------------------------------------------------------------------------- -/// Performs linear interpolation between the two given float values -axom::float64 InterfaceReconstructor::lerpFloat(axom::float64 f0, axom::float64 f1, axom::float64 t) +axom::float64 InterfaceReconstructor::lerpFloat(const axom::float64 f0, const axom::float64 f1, const axom::float64 t) { return (1 - t) * f0 + t * f1; } //-------------------------------------------------------------------------------- -/// Computes the t value as a percent from vertexOne to vertexTwo based on the two materials given. -/// The t value is the place where this edge should be clipped based on the two materials currently being considered. -axom::float64 InterfaceReconstructor::computeClippingPointOnEdge(const int vertexOneID, const int vertexTwoID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh) +axom::float64 InterfaceReconstructor::computeClippingPointOnEdge(const int vertexOneID, const int vertexTwoID, const int matOneID, const int matTwoID, mir::MIRMesh& tempMesh) { axom::float64 ret = 0.0; - axom::float64 vfMatOneVertexOne = tempMesh->materialVolumeFractionsVertex[matOneID][vertexOneID]; // Changed these all from originalMesh->materialVolumeFractionsVertex[][] to tempMesh->... - axom::float64 vfMatTwoVertexOne = tempMesh->materialVolumeFractionsVertex[matTwoID][vertexOneID]; - axom::float64 vfMatOneVertexTwo = tempMesh->materialVolumeFractionsVertex[matOneID][vertexTwoID]; - axom::float64 vfMatTwoVertexTwo = tempMesh->materialVolumeFractionsVertex[matTwoID][vertexTwoID]; + axom::float64 vfMatOneVertexOne = tempMesh.m_materialVolumeFractionsVertex[matOneID][vertexOneID]; + axom::float64 vfMatTwoVertexOne = tempMesh.m_materialVolumeFractionsVertex[matTwoID][vertexOneID]; + axom::float64 vfMatOneVertexTwo = tempMesh.m_materialVolumeFractionsVertex[matOneID][vertexTwoID]; + axom::float64 vfMatTwoVertexTwo = tempMesh.m_materialVolumeFractionsVertex[matTwoID][vertexTwoID]; if (matOneID == NULL_MAT) vfMatOneVertexOne = vfMatOneVertexTwo = 0.0; @@ -375,50 +344,48 @@ axom::float64 InterfaceReconstructor::computeClippingPointOnEdge(const int verte //-------------------------------------------------------------------------------- -/// Generate the topology of the new elements (evInds, evBegins, veInds, veBegins) resulting from a split. -void InterfaceReconstructor::generateTopologyData(std::map > newElements, std::map > newVertices, CellData& out_cellData) +void InterfaceReconstructor::generateTopologyData(const std::map >& newElements, const std::map >& newVertices, CellData& out_cellData) { // Store the evInds and evBegins data in the output vectors int currentEVBeginIndex = 0; for (auto itr = newElements.begin(); itr != newElements.end(); itr++) { // Push the start index of the next element - out_cellData.topology.evBegins.push_back(currentEVBeginIndex); + out_cellData.m_topology.m_evBegins.push_back(currentEVBeginIndex); // Push the next element's vertices for (unsigned int vIndex = 0; vIndex < itr->second.size(); ++vIndex) { - out_cellData.topology.evInds.push_back(itr->second[vIndex]); + out_cellData.m_topology.m_evInds.push_back(itr->second[vIndex]); ++currentEVBeginIndex; } } // Push the index that occurs after the last vertex - out_cellData.topology.evBegins.push_back(currentEVBeginIndex); + out_cellData.m_topology.m_evBegins.push_back(currentEVBeginIndex); // Store the veInds and veBegins data in the output vectors int currentVEBeginIndex = 0; for (auto itr = newVertices.begin(); itr != newVertices.end(); itr++) { // Push the start index of the vertex's elements - out_cellData.topology.veBegins.push_back(currentVEBeginIndex); + out_cellData.m_topology.m_veBegins.push_back(currentVEBeginIndex); // Push the next vertex's elements for (unsigned int eIndex = 0; eIndex < itr->second.size(); eIndex++) { - out_cellData.topology.veInds.push_back(itr->second[eIndex]); + out_cellData.m_topology.m_veInds.push_back(itr->second[eIndex]); ++currentVEBeginIndex; } } // Push the index that occurs after the last element - out_cellData.topology.veBegins.push_back(currentVEBeginIndex); + out_cellData.m_topology.m_veBegins.push_back(currentVEBeginIndex); } //-------------------------------------------------------------------------------- -/// Generate the vertex position data for the new elements resulting from spltting a quad. -void InterfaceReconstructor::generateVertexPositionsFromQuad(std::map > newVertices, mir::MIRMesh* tempMesh, axom::float64* verticesClippingTValue, int upperLeftVertex, int lowerLeftVertex, int lowerRightVertex, int upperRightVertex, CellData& out_cellData) +void InterfaceReconstructor::generateVertexPositionsFromQuad(const std::map >& newVertices, mir::MIRMesh& tempMesh, axom::float64* verticesClippingTValue, const int upperLeftVertex, const int lowerLeftVertex, const int lowerRightVertex, const int upperRightVertex, CellData& out_cellData) { std::map newPoints; // hashmap of the new vertices' positions | Note: maps are ordered sets @@ -427,70 +394,69 @@ void InterfaceReconstructor::generateVertexPositionsFromQuad(std::mapfirst; if (vID == 0) - newPoints[vID] = tempMesh->vertexPositions[upperLeftVertex]; + newPoints[vID] = tempMesh.m_vertexPositions[upperLeftVertex]; if (vID == 1) - newPoints[vID] = tempMesh->vertexPositions[lowerLeftVertex]; + newPoints[vID] = tempMesh.m_vertexPositions[lowerLeftVertex]; if (vID == 2) - newPoints[vID] = tempMesh->vertexPositions[lowerRightVertex]; + newPoints[vID] = tempMesh.m_vertexPositions[lowerRightVertex]; if (vID == 3) - newPoints[vID] = tempMesh->vertexPositions[upperRightVertex]; + newPoints[vID] = tempMesh.m_vertexPositions[upperRightVertex]; if (vID == 4) - newPoints[vID] = interpolateVertexPosition(tempMesh->vertexPositions[upperLeftVertex], tempMesh->vertexPositions[lowerLeftVertex], verticesClippingTValue[vID]); + newPoints[vID] = interpolateVertexPosition(tempMesh.m_vertexPositions[upperLeftVertex], tempMesh.m_vertexPositions[lowerLeftVertex], verticesClippingTValue[vID]); if (vID == 5) - newPoints[vID] = interpolateVertexPosition(tempMesh->vertexPositions[lowerLeftVertex], tempMesh->vertexPositions[lowerRightVertex], verticesClippingTValue[vID]); + newPoints[vID] = interpolateVertexPosition(tempMesh.m_vertexPositions[lowerLeftVertex], tempMesh.m_vertexPositions[lowerRightVertex], verticesClippingTValue[vID]); if (vID == 6) - newPoints[vID] = interpolateVertexPosition(tempMesh->vertexPositions[lowerRightVertex], tempMesh->vertexPositions[upperRightVertex], verticesClippingTValue[vID]); + newPoints[vID] = interpolateVertexPosition(tempMesh.m_vertexPositions[lowerRightVertex], tempMesh.m_vertexPositions[upperRightVertex], verticesClippingTValue[vID]); if (vID == 7) - newPoints[vID] = interpolateVertexPosition(tempMesh->vertexPositions[upperRightVertex], tempMesh->vertexPositions[upperLeftVertex], verticesClippingTValue[vID]); + newPoints[vID] = interpolateVertexPosition(tempMesh.m_vertexPositions[upperRightVertex], tempMesh.m_vertexPositions[upperLeftVertex], verticesClippingTValue[vID]); } // Store the positions of the vertices in the return vector for (auto itr = newPoints.begin(); itr != newPoints.end(); itr++) { - out_cellData.mapData.vertexPositions.push_back(itr->second); + out_cellData.m_mapData.m_vertexPositions.push_back(itr->second); } } //-------------------------------------------------------------------------------- -/// Generate the vertex volume fraction data for the new vertices resulting from splitting a quad. -void InterfaceReconstructor::generateVertexVolumeFractionsFromQuad(std::map > newVertices, mir::MIRMesh* tempMesh, axom::float64* verticesClippingTValue, int upperLeftVertex, int lowerLeftVertex, int lowerRightVertex, int upperRightVertex, CellData& out_cellData) +void InterfaceReconstructor::generateVertexVolumeFractionsFromQuad(std::map >& newVertices, mir::MIRMesh& tempMesh, axom::float64* verticesClippingTValue, const int upperLeftVertex, const int lowerLeftVertex, const int lowerRightVertex, const int upperRightVertex, CellData& out_cellData) { // Calculate the vertex fractions at each vertex (use t value!) // Make sure the output volume fractions containers are the proper size - out_cellData.mapData.vertexVolumeFractions.resize(tempMesh->numMaterials); + out_cellData.m_mapData.m_vertexVolumeFractions.resize(tempMesh.m_numMaterials); for (auto itr = newVertices.begin(); itr != newVertices.end(); itr++) { int vID = itr->first; - for (int matID = 0; matID < tempMesh->numMaterials; ++matID) + for (int matID = 0; matID < tempMesh.m_numMaterials; ++matID) { if (vID == 0) - out_cellData.mapData.vertexVolumeFractions[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][upperLeftVertex]); + out_cellData.m_mapData.m_vertexVolumeFractions[matID].push_back(tempMesh.m_materialVolumeFractionsVertex[matID][upperLeftVertex]); if (vID == 1) - out_cellData.mapData.vertexVolumeFractions[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][lowerLeftVertex]); + out_cellData.m_mapData.m_vertexVolumeFractions[matID].push_back(tempMesh.m_materialVolumeFractionsVertex[matID][lowerLeftVertex]); if (vID == 2) - out_cellData.mapData.vertexVolumeFractions[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][lowerRightVertex]); + out_cellData.m_mapData.m_vertexVolumeFractions[matID].push_back(tempMesh.m_materialVolumeFractionsVertex[matID][lowerRightVertex]); if (vID == 3) - out_cellData.mapData.vertexVolumeFractions[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][upperRightVertex]); + out_cellData.m_mapData.m_vertexVolumeFractions[matID].push_back(tempMesh.m_materialVolumeFractionsVertex[matID][upperRightVertex]); if (vID == 4) - out_cellData.mapData.vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][upperLeftVertex], tempMesh->materialVolumeFractionsVertex[matID][lowerLeftVertex], verticesClippingTValue[vID])); + out_cellData.m_mapData.m_vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh.m_materialVolumeFractionsVertex[matID][upperLeftVertex], tempMesh.m_materialVolumeFractionsVertex[matID][lowerLeftVertex], verticesClippingTValue[vID])); if (vID == 5) - out_cellData.mapData.vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][lowerLeftVertex], tempMesh->materialVolumeFractionsVertex[matID][lowerRightVertex], verticesClippingTValue[vID])); + out_cellData.m_mapData.m_vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh.m_materialVolumeFractionsVertex[matID][lowerLeftVertex], tempMesh.m_materialVolumeFractionsVertex[matID][lowerRightVertex], verticesClippingTValue[vID])); if (vID == 6) - out_cellData.mapData.vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][lowerRightVertex], tempMesh->materialVolumeFractionsVertex[matID][upperRightVertex], verticesClippingTValue[vID])); + out_cellData.m_mapData.m_vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh.m_materialVolumeFractionsVertex[matID][lowerRightVertex], tempMesh.m_materialVolumeFractionsVertex[matID][upperRightVertex], verticesClippingTValue[vID])); if (vID == 7) - out_cellData.mapData.vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][upperRightVertex], tempMesh->materialVolumeFractionsVertex[matID][upperLeftVertex], verticesClippingTValue[vID])); + out_cellData.m_mapData.m_vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh.m_materialVolumeFractionsVertex[matID][upperRightVertex], tempMesh.m_materialVolumeFractionsVertex[matID][upperLeftVertex], verticesClippingTValue[vID])); } } } //-------------------------------------------------------------------------------- -void InterfaceReconstructor::computeTriangleClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh, CellData& out_cellData) +void InterfaceReconstructor::computeTriangleClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh& tempMesh, CellData& out_cellData) { // Determine the clipping case - auto elementVertices = tempMesh->bdry[eID]; + auto elementVertices = tempMesh.m_bdry[eID]; int upperVertex = elementVertices[0]; int lowerLeftVertex = elementVertices[1]; @@ -546,12 +512,9 @@ void InterfaceReconstructor::computeTriangleClippingPoints(const int eID, const numVertices = triangleClipTable[caseIndex][i]; } - /**************************************************************** - * CALCULATE THE NEW CELLS' DATA - ****************************************************************/ // Calculate the total number of elements and vertices that were generated from splitting the current element - out_cellData.numElems = (int) newElements.size(); - out_cellData.numVerts = (int) newVertices.size(); + out_cellData.m_numElems = (int) newElements.size(); + out_cellData.m_numVerts = (int) newVertices.size(); generateTopologyData(newElements, newVertices, out_cellData); generateVertexPositionsFromTriangle(newVertices, tempMesh, verticesClippingTValue, upperVertex, lowerLeftVertex, lowerRightVertex, out_cellData); @@ -560,46 +523,30 @@ void InterfaceReconstructor::computeTriangleClippingPoints(const int eID, const // Determine and store the dominant material of this element for (auto itr = newElements.begin(); itr != newElements.end(); itr++) { - int currentDominantMat = NULL_MAT; - for (unsigned long it = 0; it < itr->second.size(); ++it) - { - int temp_vID = itr->second[it]; - - // Find the vertex that is one of the three original vertices of the triangle element - if (temp_vID == 0 || temp_vID == 1 || temp_vID == 2) - { - axom::float64 matOneVolumeFraction = -1.0, matTwoVolumeFraction = -1.0; - if (matOneID != NULL_MAT) - matOneVolumeFraction = out_cellData.mapData.vertexVolumeFractions[matOneID][temp_vID]; - if (matTwoID != NULL_MAT) - matTwoVolumeFraction = out_cellData.mapData.vertexVolumeFractions[matTwoID][temp_vID]; - - currentDominantMat = (matOneVolumeFraction > matTwoVolumeFraction) ? matOneID : matTwoID; - } - } - out_cellData.mapData.elementDominantMaterials.push_back(currentDominantMat); + int dominantMaterial = determineDominantMaterial(Shape::Triangle, itr->second, matOneID, matTwoID, out_cellData.m_mapData.m_vertexVolumeFractions); + out_cellData.m_mapData.m_elementDominantMaterials.push_back(dominantMaterial); } // Determine and store the parent of this element - out_cellData.mapData.elementParents.resize(newElements.size()); + out_cellData.m_mapData.m_elementParents.resize(newElements.size()); for (auto itr = newElements.begin(); itr != newElements.end(); itr++) { - out_cellData.mapData.elementParents[itr->first] = tempMesh->elementParentIDs[eID]; + out_cellData.m_mapData.m_elementParents[itr->first] = tempMesh.m_elementParentIDs[eID]; } // Check that each element is dominated by a material that is actually present in the original parent cell for (auto itr = newElements.begin(); itr != newElements.end(); itr++) { - int parentElementID = out_cellData.mapData.elementParents[itr->first]; - int currentDominantMaterial = out_cellData.mapData.elementDominantMaterials[itr->first]; + int parentElementID = out_cellData.m_mapData.m_elementParents[itr->first]; + int currentDominantMaterial = out_cellData.m_mapData.m_elementDominantMaterials[itr->first]; - if (originalMesh->materialVolumeFractionsElement[currentDominantMaterial][parentElementID] == 0) + if (m_originalMesh.m_materialVolumeFractionsElement[currentDominantMaterial][parentElementID] == 0) { // This material is not present in the original element from which the current element comes from if (currentDominantMaterial == matOneID) - out_cellData.mapData.elementDominantMaterials[itr->first] = matTwoID; + out_cellData.m_mapData.m_elementDominantMaterials[itr->first] = matTwoID; else - out_cellData.mapData.elementDominantMaterials[itr->first] = matOneID; + out_cellData.m_mapData.m_elementDominantMaterials[itr->first] = matOneID; } } @@ -616,15 +563,14 @@ void InterfaceReconstructor::computeTriangleClippingPoints(const int eID, const for (int i = 1; i < 6; ++i) evIndexSubtract[i] += evIndexSubtract[i - 1]; - for (unsigned int i = 0; i < out_cellData.topology.evInds.size(); ++i) - out_cellData.topology.evInds[i] -= evIndexSubtract[ out_cellData.topology.evInds[i] ]; + for (unsigned int i = 0; i < out_cellData.m_topology.m_evInds.size(); ++i) + out_cellData.m_topology.m_evInds[i] -= evIndexSubtract[ out_cellData.m_topology.m_evInds[i] ]; } //-------------------------------------------------------------------------------- -/// Finds the bit map representing the clipping case for a triangle. -unsigned int InterfaceReconstructor::determineTriangleClippingCase(mir::MIRMesh* tempMesh, const int matOneID, const int matTwoID, const int upperVertex, const int lowerLeftVertex, const int lowerRightVertex) +unsigned int InterfaceReconstructor::determineTriangleClippingCase(mir::MIRMesh& tempMesh, const int matOneID, const int matTwoID, const int upperVertex, const int lowerLeftVertex, const int lowerRightVertex) { // Determine the dominant color at each vertex int upperColor = matOneID, lowerLeftColor = matOneID, lowerRightColor = matOneID; @@ -635,9 +581,9 @@ unsigned int InterfaceReconstructor::determineTriangleClippingCase(mir::MIRMesh* upperColor = lowerLeftColor = lowerRightColor = matOneID; if (matOneID != NULL_MAT && matTwoID != NULL_MAT) { - upperColor = tempMesh->materialVolumeFractionsVertex[matOneID][upperVertex] > tempMesh->materialVolumeFractionsVertex[matTwoID][upperVertex] ? matOneID : matTwoID; - lowerLeftColor = tempMesh->materialVolumeFractionsVertex[matOneID][lowerLeftVertex] > tempMesh->materialVolumeFractionsVertex[matTwoID][lowerLeftVertex] ? matOneID : matTwoID; - lowerRightColor = tempMesh->materialVolumeFractionsVertex[matOneID][lowerRightVertex] > tempMesh->materialVolumeFractionsVertex[matTwoID][lowerRightVertex] ? matOneID : matTwoID; + upperColor = tempMesh.m_materialVolumeFractionsVertex[matOneID][upperVertex] > tempMesh.m_materialVolumeFractionsVertex[matTwoID][upperVertex] ? matOneID : matTwoID; + lowerLeftColor = tempMesh.m_materialVolumeFractionsVertex[matOneID][lowerLeftVertex] > tempMesh.m_materialVolumeFractionsVertex[matTwoID][lowerLeftVertex] ? matOneID : matTwoID; + lowerRightColor = tempMesh.m_materialVolumeFractionsVertex[matOneID][lowerRightVertex] > tempMesh.m_materialVolumeFractionsVertex[matTwoID][lowerRightVertex] ? matOneID : matTwoID; } // Create the index into the quad clipping lookup table using the dominant colors at each vertex @@ -651,8 +597,7 @@ unsigned int InterfaceReconstructor::determineTriangleClippingCase(mir::MIRMesh* //-------------------------------------------------------------------------------- -/// Generate the vertex position data for the new elements resulting from spltting a quad. -void InterfaceReconstructor::generateVertexPositionsFromTriangle(std::map > newVertices, mir::MIRMesh* tempMesh, axom::float64* verticesClippingTValue, int upperVertex, int lowerLeftVertex, int lowerRightVertex, CellData& out_cellData) +void InterfaceReconstructor::generateVertexPositionsFromTriangle(const std::map >& newVertices, mir::MIRMesh& tempMesh, axom::float64* verticesClippingTValue, const int upperVertex, const int lowerLeftVertex, const int lowerRightVertex, CellData& out_cellData) { std::map newPoints; // hashmap of the new vertices' positions | Note: maps are ordered sets @@ -661,54 +606,107 @@ void InterfaceReconstructor::generateVertexPositionsFromTriangle(std::mapfirst; if (vID == 0) - newPoints[vID] = tempMesh->vertexPositions[upperVertex]; + newPoints[vID] = tempMesh.m_vertexPositions[upperVertex]; if (vID == 1) - newPoints[vID] = tempMesh->vertexPositions[lowerLeftVertex]; + newPoints[vID] = tempMesh.m_vertexPositions[lowerLeftVertex]; if (vID == 2) - newPoints[vID] = tempMesh->vertexPositions[lowerRightVertex]; + newPoints[vID] = tempMesh.m_vertexPositions[lowerRightVertex]; if (vID == 3) - newPoints[vID] = interpolateVertexPosition(tempMesh->vertexPositions[upperVertex], tempMesh->vertexPositions[lowerLeftVertex], verticesClippingTValue[vID]); + newPoints[vID] = interpolateVertexPosition(tempMesh.m_vertexPositions[upperVertex], tempMesh.m_vertexPositions[lowerLeftVertex], verticesClippingTValue[vID]); if (vID == 4) - newPoints[vID] = interpolateVertexPosition(tempMesh->vertexPositions[lowerLeftVertex], tempMesh->vertexPositions[lowerRightVertex], verticesClippingTValue[vID]); + newPoints[vID] = interpolateVertexPosition(tempMesh.m_vertexPositions[lowerLeftVertex], tempMesh.m_vertexPositions[lowerRightVertex], verticesClippingTValue[vID]); if (vID == 5) - newPoints[vID] = interpolateVertexPosition(tempMesh->vertexPositions[lowerRightVertex], tempMesh->vertexPositions[upperVertex], verticesClippingTValue[vID]); + newPoints[vID] = interpolateVertexPosition(tempMesh.m_vertexPositions[lowerRightVertex], tempMesh.m_vertexPositions[upperVertex], verticesClippingTValue[vID]); } // Store the positions of the vertices in the return vector for (auto itr = newPoints.begin(); itr != newPoints.end(); itr++) { - out_cellData.mapData.vertexPositions.push_back(itr->second); + out_cellData.m_mapData.m_vertexPositions.push_back(itr->second); } } //-------------------------------------------------------------------------------- -/// Generate the vertex volume fraction data for the new vertices resulting from splitting a quad. -void InterfaceReconstructor::generateVertexVolumeFractionsFromTriangle(std::map > newVertices, mir::MIRMesh* tempMesh, axom::float64* verticesClippingTValue, int upperVertex, int lowerLeftVertex, int lowerRightVertex, CellData& out_cellData) +void InterfaceReconstructor::generateVertexVolumeFractionsFromTriangle(const std::map >& newVertices, mir::MIRMesh& tempMesh, axom::float64* verticesClippingTValue, const int upperVertex, const int lowerLeftVertex, const int lowerRightVertex, CellData& out_cellData) { // Calculate the vertex fractions at each vertex (use t value!) // Make sure the output volume fractions containers are the proper size - out_cellData.mapData.vertexVolumeFractions.resize(tempMesh->numMaterials); + out_cellData.m_mapData.m_vertexVolumeFractions.resize(tempMesh.m_numMaterials); for (auto itr = newVertices.begin(); itr != newVertices.end(); itr++) { int vID = itr->first; - for (int matID = 0; matID < tempMesh->numMaterials; ++matID) + for (int matID = 0; matID < tempMesh.m_numMaterials; ++matID) { if (vID == 0) - out_cellData.mapData.vertexVolumeFractions[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][upperVertex]); + out_cellData.m_mapData.m_vertexVolumeFractions[matID].push_back(tempMesh.m_materialVolumeFractionsVertex[matID][upperVertex]); if (vID == 1) - out_cellData.mapData.vertexVolumeFractions[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][lowerLeftVertex]); + out_cellData.m_mapData.m_vertexVolumeFractions[matID].push_back(tempMesh.m_materialVolumeFractionsVertex[matID][lowerLeftVertex]); if (vID == 2) - out_cellData.mapData.vertexVolumeFractions[matID].push_back(tempMesh->materialVolumeFractionsVertex[matID][lowerRightVertex]); + out_cellData.m_mapData.m_vertexVolumeFractions[matID].push_back(tempMesh.m_materialVolumeFractionsVertex[matID][lowerRightVertex]); if (vID == 3) - out_cellData.mapData.vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][upperVertex], tempMesh->materialVolumeFractionsVertex[matID][lowerLeftVertex], verticesClippingTValue[vID])); + out_cellData.m_mapData.m_vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh.m_materialVolumeFractionsVertex[matID][upperVertex], tempMesh.m_materialVolumeFractionsVertex[matID][lowerLeftVertex], verticesClippingTValue[vID])); if (vID == 4) - out_cellData.mapData.vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][lowerLeftVertex], tempMesh->materialVolumeFractionsVertex[matID][lowerRightVertex], verticesClippingTValue[vID])); + out_cellData.m_mapData.m_vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh.m_materialVolumeFractionsVertex[matID][lowerLeftVertex], tempMesh.m_materialVolumeFractionsVertex[matID][lowerRightVertex], verticesClippingTValue[vID])); if (vID == 5) - out_cellData.mapData.vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh->materialVolumeFractionsVertex[matID][lowerRightVertex], tempMesh->materialVolumeFractionsVertex[matID][upperVertex], verticesClippingTValue[vID])); + out_cellData.m_mapData.m_vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh.m_materialVolumeFractionsVertex[matID][lowerRightVertex], tempMesh.m_materialVolumeFractionsVertex[matID][upperVertex], verticesClippingTValue[vID])); + } + } +} + +//-------------------------------------------------------------------------------- + +int InterfaceReconstructor::determineDominantMaterial(const Shape elementShape, const std::vector& vertexIDs, const int matOne, const int matTwo, const std::vector >& vertexVF) +{ + int dominantMaterial = matOne; + + axom::float64 matOneVF = -1.0; + axom::float64 matTwoVF = -1.0; + + if (elementShape == Shape::Triangle) + { + for (unsigned long it = 0; it < vertexIDs.size(); ++it) + { + int vID = vertexIDs[it]; + if (vID == 0 || vID == 1 || vID == 2) + { + if (matOne != NULL_MAT) + { + matOneVF = vertexVF[matOne][vID]; + } + if (matTwo != NULL_MAT) + { + matTwoVF = vertexVF[matTwo][vID]; + } + + dominantMaterial = (matOneVF > matTwoVF) ? matOne : matTwo; + } + } + } + + if (elementShape == Shape::Quad) + { + for (unsigned long it = 0; it < vertexIDs.size(); ++it) + { + int vID = vertexIDs[it]; + if (vID == 0 || vID == 1 || vID == 2 || vID == 3) + { + if (matOne != NULL_MAT) + { + matOneVF = vertexVF[matOne][vID]; + } + if (matTwo != NULL_MAT) + { + matTwoVF = vertexVF[matTwo][vID]; + } + + dominantMaterial = (matOneVF > matTwoVF) ? matOne : matTwo; } } + } + + return dominantMaterial; } //-------------------------------------------------------------------------------- diff --git a/src/axom/mir/InterfaceReconstructor.hpp b/src/axom/mir/InterfaceReconstructor.hpp index cbda3dcb59..646e1f284e 100644 --- a/src/axom/mir/InterfaceReconstructor.hpp +++ b/src/axom/mir/InterfaceReconstructor.hpp @@ -3,6 +3,13 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +/** + * \file InterfaceReconstructor.hpp + * + * \brief Contains the specification for the InterfaceReconstructor class. + * + */ + #ifndef __INTERFACE_RECONSTRUCTOR_H__ #define __INTERFACE_RECONSTRUCTOR_H__ @@ -12,6 +19,7 @@ #include "MIRMesh.hpp" #include "CellData.hpp" #include "ZooClippingTables.hpp" + #include namespace numerics = axom::numerics; @@ -21,46 +29,246 @@ namespace axom { namespace mir { + + /** + * \class InterfaceReconstructor + * + * \brief A class that contains the functionality for taking an input mesh + * with mixed cells and outputs a mesh with clean cells. + * + * \detail This class requires that the user create a mesh of type MIRMesh + * and pass that into one of the reconstruction methods. There are + * currently two reconstructions methods implemented: one is the + * a zoo-based algorithm described in Meredith 2004, and the other + * is the iterative version described in Meredith and Childs 2010. + */ class InterfaceReconstructor { public: + + /** + * \brief Default constructor. + */ InterfaceReconstructor(); + + + /** + * \brief Default destructor. + */ ~InterfaceReconstructor(); - mir::MIRMesh computeReconstructedInterface(mir::MIRMesh* inputMesh); - mir::MIRMesh computeReconstructedInterfaceIterative(mir::MIRMesh* inputMesh, int numIterations, axom::float64 percent); - private: - mir::MIRMesh* originalMesh; + /** + * \brief Performs material interface reconstruction using the zoo-based algorithm. + * + * \param inputMesh The mesh composed of mixed cells. + * + * \return The mesh composed of clean cells. + */ + mir::MIRMesh computeReconstructedInterface(mir::MIRMesh& inputMesh); + + + /** + * \brief Performs material interface reconstruction using an iterative version of the zoo-based algorithm. + * + * \param inputMesh The mesh made up of mixed cells. + * \param numIterations The number of iterations for which to run the algorithm. + * \param percent The percent of the difference to use when modifying the original element volume fractions. + * + * \return The mesh made up of clean cells. + */ + mir::MIRMesh computeReconstructedInterfaceIterative(mir::MIRMesh& inputMesh, const int numIterations, const axom::float64 percent); + + // private: + + /** + * \brief A wrapper function that calls the appropriate splitting method based on the shape of the given element. + * + * \param eID The ID of the element to be split. + * \param matOneID The ID of the first material to use for splitting the element, + * \param matTwoID The ID of the second material to use for splitting the element. + * \param tempMesh A pointer to the intermediate mesh that is currently being processed. + * \param out_cellData Container to store the output of splitting the element. + */ + void computeClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh& tempMesh, CellData& out_cellData); + + + /** + * \brief Splits the specified quad element into clean cells of the two given material types + * based on the volume fractions of the two materials. + * + * \param eID The ID of the quad element. + * \param matOneID The ID of the first material to use for splitting the quad, + * \param matTwoID The ID of the second material to use for splitting the quad. + * \param tempMesh A pointer to the intermediate mesh that is currently being processed. + * \param out_cellData Container to store the output of splitting the quad. + */ + void computeQuadClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh& tempMesh, CellData& out_cellData); + + + /** + * \brief Splits the specified triangle element into clean cells of the two given material types + * based on the volume fractions of the two materials. + * + * \param eID The ID of the triangle element. + * \param matOneID The ID of the first material to use for splitting the triangle, + * \param matTwoID The ID of the second material to use for splitting the triangle. + * \param tempMesh A pointer to the intermediate mesh that is currently being processed. + * \param out_cellData Container to store the output of splitting the triangle. + */ + void computeTriangleClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh& tempMesh, CellData& out_cellData); + + + /** + * \brief Performs linear interpolation between the two vertex positions. + * + * \param vertexOnePos The position of the first vertex. + * \param vertexTwoPos The position of the second vertex. + * \param t The percent of the distance from vertex one to vertex two to interpolate at. + * + * \return The interpolated position. + */ + mir::Point2 interpolateVertexPosition(const mir::Point2& vertexOnePos, const mir::Point2& vertexTwoPos, const float t); + + + /** + * \brief Performs linear interpolation between the two given float values. + * + * \param f0 The first float value. + * \param f1 The second float value. + * \param t The percent of the distance from the first float value to the second. + * + * \return The interpolated value. + */ + axom::float64 lerpFloat(const axom::float64 f0, const axom::float64 f1, const axom::float64 t); + + + /** + * \brief Computes the t value as a percent from vertex one to vertex two based on the materials given. + * + * \param vertexOneID The ID of the first vertex. + * \param vertexTwoID The ID of the second vertex. + * \param matOneID The ID of the first material to use for interpolating between. + * \param matTwoID The ID of the second material to use for interpolating between. + * \param tempMesh The intermediate mesh that is currently being processed. + * + * \return The t value, which is the percent distance from vertex one to vertex two where the two volume fractions are equal. + */ + axom::float64 computeClippingPointOnEdge(const int vertexOneID, const int vertexTwoID, const int matOneID, const int matTwoID, mir::MIRMesh& tempMesh); + + + /** + * \brief Generates the topology of the new elements resulting from a split. + * + * \param newElements An ordered map of the generated elements' IDs to a list of the vertex IDs in the local frame of the output element. + * \param newVertices An order map of the vertex IDs in the local frame of the output element to the generated elements' IDs it is associated with. + * \param out_cellData Container to store the topology data of the generated elements. + */ + void generateTopologyData(const std::map >& newElements, const std::map >& newVertices, CellData& out_cellData); + - private: + /** + * \brief Calculates the bit map representing the clipping case for a quad. + * + * \param tempMesh The intermediate mesh that is currently being processed. + * \param matOneID The ID of the first material being used for splitting. + * \param matTwoID The ID of the second material being used for splitting. + * \param upperLeftVertex The ID of the upper left vertex in the global frame of the original mesh. + * \param lowerLeftVertex The ID of the lower left vertex in the global frame of the original mesh. + * \param lowerRightVertex The ID of the lower right vertex in the global frame of the original mesh. + * \param upperRightVertex The ID of the upper right vertex in the global frame of the original mesh. + * + * \return The bitmap representing the clipping case. + */ + unsigned int determineQuadClippingCase(mir::MIRMesh& tempMesh, const int matOneID, const int matTwoID, const int upperLeftVertex, const int lowerLeftVertex, const int lowerRightVertex, const int upperRightVertex); - // general clipping function - void computeQuadClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh, CellData& out_cellData); + /** + * \brief Generate the vertex position data for the new elements resulting from splitting a quad. + * + * \param newVertices An order map of the vertex IDs in the local frame of the output element to the generated elements' IDs it is associated with. + * \param tempMesh The intermediate mesh that is currently being processed. + * \param verticesClippingTValue The set of t values where the quad should be split on each edge. + * \param upperLeftVertex The ID of the upper left vertex in the global frame of the original mesh. + * \param lowerLeftVertex The ID of the lower left vertex in the global frame of the original mesh. + * \param lowerRightVertex The ID of the lower right vertex in the global frame of the original mesh. + * \param upperRightVertex The ID of the upper right vertex in the global frame of the original mesh. + * \param out_cellData Container to store the vertex position data of the generated elements. + */ + void generateVertexPositionsFromQuad(const std::map >& newVertices, mir::MIRMesh& tempMesh, axom::float64* verticesClippingTValue, const int upperLeftVertex, const int lowerLeftVertex, const int lowerRightVertex, const int upperRightVertex, CellData& out_cellData); - // triangle clipping function - void computeTriangleClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh, CellData& out_cellData); + /** + * \brief Generate the vertex volume fraction data for the new vertices resulting from splitting a quad. + * + * \param newVertices An order map of the vertex IDs in the local frame of the output element to the generated elements' IDs it is associated with. + * \param tempMesh The intermediate mesh that is currently being processed. + * \param verticesClippingTValue The set of t values where the quad should be split on each edge. + * \param upperLeftVertex The ID of the upper left vertex in the global frame of the original mesh. + * \param lowerLeftVertex The ID of the lower left vertex in the global frame of the original mesh. + * \param lowerRightVertex The ID of the lower right vertex in the global frame of the original mesh. + * \param upperRightVertex The ID of the upper right vertex in the global frame of the original mesh. + * \param out_cellData Container to store the vertex volume fraction data of the generated elements. + */ + void generateVertexVolumeFractionsFromQuad(std::map >& newVertices, mir::MIRMesh&tempMesh, axom::float64* verticesClippingTValue, const int upperLeftVertex, const int lowerLeftVertex, const int lowerRightVertex, const int upperRightVertex, CellData& out_cellData); - // quad clipping functions - void computeClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh, CellData& out_cellData); + /** + * \brief Calculates the bit map representing the clipping case for a triangle. + * + * \param tempMesh The intermediate mesh that is currently being processed. + * \param matOneID The ID of the first material being used for splitting. + * \param matTwoID The ID of the second material being used for splitting. + * \param upperVertex The ID of the upper vertex in the global frame of the original mesh. + * \param lowerLeftVertex The ID of the lower left vertex in the global frame of the original mesh. + * \param lowerRightVertex The ID of the lower right vertex in the global frame of the original mesh. + * + * \return The bitmap representing the clipping case. + */ + unsigned int determineTriangleClippingCase(mir::MIRMesh& tempMesh, const int matOneID, const int matTwoID, const int upperVertex, const int lowerLeftVertex, const int lowerRightVertex); - // clipping interpolation functions - mir::Point2 interpolateVertexPosition(mir::Point2 vertexOnePos, mir::Point2 vertexTwoPos, float t); - axom::float64 lerpFloat(axom::float64 f0, axom::float64 f1, axom::float64 t); - axom::float64 computeClippingPointOnEdge(const int vertexOneID, const int vertexTwoID, const int matOneID, const int matTwoID, mir::MIRMesh* tempMesh); + /** + * \brief Generate the vertex position data for the new elements resulting from splitting a triangle. + * + * \param newVertices An order map of the vertex IDs in the local frame of the output element to the generated elements' IDs it is associated with. + * \param tempMesh The intermediate mesh that is currently being processed. + * \param verticesClippingTValue The set of t values where the quad should be split on each edge. + * \param upperVertex The ID of the upper vertex in the global frame of the original mesh. + * \param lowerLeftVertex The ID of the lower left vertex in the global frame of the original mesh. + * \param lowerRightVertex The ID of the lower right vertex in the global frame of the original mesh. + * \param out_cellData Container to store the vertex position data of the generated elements. + */ + void generateVertexPositionsFromTriangle(const std::map >& newVertices, mir::MIRMesh& tempMesh, axom::float64* verticesClippingTValue, const int upperVertex, const int lowerLeftVertex, const int lowerRightVertex, CellData& out_cellData); - // general helper functions - void generateTopologyData(std::map > newElements, std::map > newVertices, CellData& out_cellData); + /** + * \brief Generate the vertex volume fraction data for the new vertices resulting from splitting a triangle. + * + * \param newVertices An order map of the vertex IDs in the local frame of the output element to the generated elements' IDs it is associated with. + * \param tempMesh The intermediate mesh that is currently being processed. + * \param verticesClippingTValue The set of t values where the quad should be split on each edge. + * \param upperVertex The ID of the upper vertex in the global frame of the original mesh. + * \param lowerLeftVertex The ID of the lower left vertex in the global frame of the original mesh. + * \param lowerRightVertex The ID of the lower right vertex in the global frame of the original mesh. + * \param out_cellData Container to store the vertex volume fraction data of the generated elements. + */ + void generateVertexVolumeFractionsFromTriangle(const std::map >& newVertices, mir::MIRMesh& tempMesh, axom::float64* verticesClippingTValue, const int upperVertex, const int lowerLeftVertex, const int lowerRightVertex, CellData& out_cellData); - // quad clipping points helper functions - unsigned int determineQuadClippingCase(mir::MIRMesh* tempMesh, const int matOneID, const int matTwoID, const int upperLeftVertex, const int lowerLeftVertex, const int lowerRightVertex, const int upperRightVertex); - void generateVertexPositionsFromQuad(std::map > newVertices, mir::MIRMesh* tempMesh, axom::float64* verticesClippingTValue, int upperLeftVertex, int lowerLeftVertex, int lowerRightVertex, int upperRightVertex, CellData& out_cellData); - void generateVertexVolumeFractionsFromQuad(std::map > newVertices, mir::MIRMesh* tempMesh, axom::float64* verticesClippingTValue, int upperLeftVertex, int lowerLeftVertex, int lowerRightVertex, int upperRightVertex, CellData& out_cellData); + /** + * \brief Determines the mroe dominant material of the two given for the given element. + * + * \param elementShape An enumerator denoting the element's shape. + * \param vertexIDs A list of vertex IDs into the vertexVF param. + * \param matOne The ID of the first material. + * \param matTwo The ID of the second material. + * \param vertexVF The list of volume fractions associated with the given vertices in the vertexIDs param. + * + * \return The ID of the dominant material of the element. + * + * \note The dominant element for the 2D cases will be the same as the material present at one of the + * original vertices that existed prior to the split. So, if you can find this vertex and its dominant + * material, then you know the dominant material of this new element. + */ + int determineDominantMaterial(const Shape elementShape, const std::vector& vertexIDs, const int matOne, const int matTwo, const std::vector >& vertexVF); - // triangle clipping points helper functions - unsigned int determineTriangleClippingCase(mir::MIRMesh* tempMesh, const int matOneID, const int matTwoID, const int upperVertex, const int lowerLeftVertex, const int lowerRightVertex); - void generateVertexPositionsFromTriangle(std::map > newVertices, mir::MIRMesh* tempMesh, axom::float64* verticesClippingTValue, int upperVertex, int lowerLeftVertex, int lowerRightVertex, CellData& out_cellData); - void generateVertexVolumeFractionsFromTriangle(std::map > newVertices, mir::MIRMesh* tempMesh, axom::float64* verticesClippingTValue, int upperVertex, int lowerLeftVertex, int lowerRightVertex, CellData& out_cellData); + private: + mir::MIRMesh m_originalMesh; }; } } diff --git a/src/axom/mir/MIRMesh.cpp b/src/axom/mir/MIRMesh.cpp index 9243bda9ad..6e7c687dcf 100644 --- a/src/axom/mir/MIRMesh.cpp +++ b/src/axom/mir/MIRMesh.cpp @@ -19,22 +19,22 @@ MIRMesh::MIRMesh() //-------------------------------------------------------------------------------- -MIRMesh::MIRMesh(MIRMesh* _mesh) // copy constructor +MIRMesh::MIRMesh(MIRMesh* _mesh) { - meshTopology.evInds = _mesh->meshTopology.evInds; - meshTopology.evBegins = _mesh->meshTopology.evBegins; - meshTopology.veInds = _mesh->meshTopology.veInds; - meshTopology.veBegins = _mesh->meshTopology.veBegins; - verts = _mesh->verts; - elems = _mesh->elems; - bdry = _mesh->bdry; - cobdry = _mesh->cobdry; - vertexPositions = _mesh->vertexPositions; - materialVolumeFractionsElement = _mesh->materialVolumeFractionsElement; - materialVolumeFractionsVertex = _mesh->materialVolumeFractionsVertex; - elementParentIDs = _mesh->elementParentIDs; - elementDominantMaterials = _mesh->elementDominantMaterials; - numMaterials = _mesh->numMaterials; + m_meshTopology.m_evInds = _mesh->m_meshTopology.m_evInds; + m_meshTopology.m_evBegins = _mesh->m_meshTopology.m_evBegins; + m_meshTopology.m_veInds = _mesh->m_meshTopology.m_veInds; + m_meshTopology.m_veBegins = _mesh->m_meshTopology.m_veBegins; + m_verts = _mesh->m_verts; + m_elems = _mesh->m_elems; + m_bdry = _mesh->m_bdry; + m_cobdry = _mesh->m_cobdry; + m_vertexPositions = _mesh->m_vertexPositions; + m_materialVolumeFractionsElement = _mesh->m_materialVolumeFractionsElement; + m_materialVolumeFractionsVertex = _mesh->m_materialVolumeFractionsVertex; + m_elementParentIDs = _mesh->m_elementParentIDs; + m_elementDominantMaterials = _mesh->m_elementDominantMaterials; + m_numMaterials = _mesh->m_numMaterials; } //-------------------------------------------------------------------------------- @@ -46,55 +46,53 @@ MIRMesh::~MIRMesh() //-------------------------------------------------------------------------------- -/// Initialize a mesh with the given topology and data. void MIRMesh::initializeMesh(VertSet _verts, ElemSet _elems, int _numMaterials, CellTopologyData _topology, CellMapData _mapData, std::vector > _elementVF) { // Initialize the vertex and element sets - verts = _verts; - elems = _elems; + m_verts = _verts; + m_elems = _elems; - numMaterials = _numMaterials; + m_numMaterials = _numMaterials; // Intialize the mesh topology - meshTopology.evInds = _topology.evInds; - meshTopology.evBegins = _topology.evBegins; - meshTopology.veInds = _topology.veInds; - meshTopology.veBegins = _topology.veBegins; + m_meshTopology.m_evInds = _topology.m_evInds; + m_meshTopology.m_evBegins = _topology.m_evBegins; + m_meshTopology.m_veInds = _topology.m_veInds; + m_meshTopology.m_veBegins = _topology.m_veBegins; // Initialize the mesh relations constructMeshRelations(); // Initialize the mesh's data maps - constructVertexPositionMap(_mapData.vertexPositions.data()); - constructElementParentMap(_mapData.elementParents.data()); - constructElementDominantMaterialMap(_mapData.elementDominantMaterials); + constructVertexPositionMap(_mapData.m_vertexPositions.data()); + constructElementParentMap(_mapData.m_elementParents.data()); + constructElementDominantMaterialMap(_mapData.m_elementDominantMaterials); // Initialize the element and vertex volume fraction maps if (_elementVF.size() > 0) constructMeshVolumeFractionsMaps(_elementVF); // Check validity of the sets - SLIC_ASSERT_MSG( verts.isValid(), "Vertex set is not valid."); - SLIC_ASSERT_MSG( elems.isValid(), "Element set is not valid."); + SLIC_ASSERT_MSG( m_verts.isValid(), "Vertex set is not valid."); + SLIC_ASSERT_MSG( m_elems.isValid(), "Element set is not valid."); } //-------------------------------------------------------------------------------- -/// Constructs the mesh boundary and coboundary relations void MIRMesh::constructMeshRelations() { // construct boundary relation from elements to vertices using variable cardinality { using RelationBuilder = ElemToVertRelation::RelationBuilder; - bdry = RelationBuilder() - .fromSet( &elems ) - .toSet( &verts ) + m_bdry = RelationBuilder() + .fromSet( &m_elems ) + .toSet( &m_verts ) .begins( RelationBuilder::BeginsSetBuilder() - .size( elems.size() ) - .data( meshTopology.evBegins.data() ) ) + .size( m_elems.size() ) + .data( m_meshTopology.m_evBegins.data() ) ) .indices ( RelationBuilder::IndicesSetBuilder() - .size( meshTopology.evInds.size() ) - .data( meshTopology.evInds.data() ) ); + .size( m_meshTopology.m_evInds.size() ) + .data( m_meshTopology.m_evInds.data() ) ); } @@ -102,206 +100,200 @@ void MIRMesh::constructMeshRelations() // _quadmesh_example_construct_cobdry_relation_start // construct coboundary relation from vertices to elements using RelationBuilder = VertToElemRelation::RelationBuilder; - cobdry = RelationBuilder() - .fromSet( &verts ) - .toSet( &elems ) + m_cobdry = RelationBuilder() + .fromSet( &m_verts ) + .toSet( &m_elems ) .begins( RelationBuilder::BeginsSetBuilder() - .size( verts.size() ) - .data( meshTopology.veBegins.data() ) ) + .size( m_verts.size() ) + .data( m_meshTopology.m_veBegins.data() ) ) .indices( RelationBuilder::IndicesSetBuilder() - .size( meshTopology.veInds.size() ) - .data( meshTopology.veInds.data() ) ); + .size( m_meshTopology.m_veInds.size() ) + .data( m_meshTopology.m_veInds.data() ) ); // _quadmesh_example_construct_cobdry_relation_end } - SLIC_ASSERT_MSG( bdry.isValid(), "Boundary relation is not valid."); - SLIC_ASSERT_MSG( cobdry.isValid(), "Coboundary relation is not valid."); + SLIC_ASSERT_MSG( m_bdry.isValid(), "Boundary relation is not valid."); + SLIC_ASSERT_MSG( m_cobdry.isValid(), "Coboundary relation is not valid."); - SLIC_INFO("Elem-Vert relation has size " << bdry.totalSize()); - SLIC_INFO("Vert-Elem relation has size " << cobdry.totalSize()); + SLIC_INFO("Elem-Vert relation has size " << m_bdry.totalSize()); + SLIC_INFO("Vert-Elem relation has size " << m_cobdry.totalSize()); } //-------------------------------------------------------------------------------- -/// Construct the and the element and vertex volume fraction data given the element volume fraction data void MIRMesh::constructMeshVolumeFractionsMaps(std::vector > elementVF) { // Clear the old maps - materialVolumeFractionsElement.clear(); - materialVolumeFractionsVertex.clear(); + m_materialVolumeFractionsElement.clear(); + m_materialVolumeFractionsVertex.clear(); // Initialize the maps for all of the materials with the input volume fraction data for each material - for (int matID = 0; matID < numMaterials; ++matID) + for (int matID = 0; matID < m_numMaterials; ++matID) { // Initialize the map for the current material - materialVolumeFractionsElement.push_back(ScalarMap( &elems )); + m_materialVolumeFractionsElement.push_back(ScalarMap( &m_elems )); // Copy the data for the current material - for (int eID = 0; eID < elems.size(); ++eID) + for (int eID = 0; eID < m_elems.size(); ++eID) { - materialVolumeFractionsElement[matID][eID] = elementVF[matID][eID]; + m_materialVolumeFractionsElement[matID][eID] = elementVF[matID][eID]; } - SLIC_ASSERT_MSG( materialVolumeFractionsElement[matID].isValid(), "Element volume fraction map is not valid."); + SLIC_ASSERT_MSG( m_materialVolumeFractionsElement[matID].isValid(), "Element volume fraction map is not valid."); } // Initialize the maps for all of the vertex volume fractions - for (int matID = 0; matID < numMaterials; ++matID) + for (int matID = 0; matID < m_numMaterials; ++matID) { // Initialize the new map for the volume fractions - materialVolumeFractionsVertex.push_back(ScalarMap( &verts ) ); + m_materialVolumeFractionsVertex.push_back(ScalarMap( &m_verts ) ); // Calculate the average volume fraction value for the current vertex for the current material - for (int vID = 0; vID < verts.size(); ++vID) + for (int vID = 0; vID < m_verts.size(); ++vID) { // Compute the per vertex volume fractions for the green material axom::float64 sum = 0; - auto vertexElements = cobdry[vID]; + auto vertexElements = m_cobdry[vID]; for (int i = 0; i < vertexElements.size(); ++i) { auto eID = vertexElements[i]; - sum += materialVolumeFractionsElement[matID][eID]; + sum += m_materialVolumeFractionsElement[matID][eID]; } - materialVolumeFractionsVertex[matID][vID] = sum / vertexElements.size(); + m_materialVolumeFractionsVertex[matID][vID] = sum / vertexElements.size(); } - SLIC_ASSERT_MSG( materialVolumeFractionsVertex[matID].isValid(), "Vertex volume fraction map is not valid."); + SLIC_ASSERT_MSG( m_materialVolumeFractionsVertex[matID].isValid(), "Vertex volume fraction map is not valid."); } } //-------------------------------------------------------------------------------- -/// Construct the vertex volume fraction data given vertex volume fraction data void MIRMesh::constructMeshVolumeFractionsVertex(std::vector > vertexVF) { // Initialize the maps for all of the materials with the input volume fraction data for each vertex - for (int matID = 0; matID < numMaterials; ++matID) + for (int matID = 0; matID < m_numMaterials; ++matID) { // Initialize the map for the current material - materialVolumeFractionsVertex.push_back(ScalarMap( &verts )); + m_materialVolumeFractionsVertex.push_back(ScalarMap( &m_verts )); // Copy the data for the current material - for (int vID = 0; vID < verts.size(); ++vID) + for (int vID = 0; vID < m_verts.size(); ++vID) { - materialVolumeFractionsVertex[matID][vID] = vertexVF[matID][vID]; + m_materialVolumeFractionsVertex[matID][vID] = vertexVF[matID][vID]; } - SLIC_ASSERT_MSG( materialVolumeFractionsVertex[matID].isValid(), "Vertex volume fraction map is not valid."); + SLIC_ASSERT_MSG( m_materialVolumeFractionsVertex[matID].isValid(), "Vertex volume fraction map is not valid."); } } //-------------------------------------------------------------------------------- -/// Constucts the positions map on the vertices void MIRMesh::constructVertexPositionMap(Point2* data) { // construct the position map on the vertices - vertexPositions = PointMap( &verts ); + m_vertexPositions = PointMap( &m_verts ); - for (int vID = 0; vID < verts.size(); ++vID) - vertexPositions[vID] = data[vID]; + for (int vID = 0; vID < m_verts.size(); ++vID) + m_vertexPositions[vID] = data[vID]; - SLIC_ASSERT_MSG( vertexPositions.isValid(), "Position map is not valid."); + SLIC_ASSERT_MSG( m_vertexPositions.isValid(), "Position map is not valid."); } //-------------------------------------------------------------------------------- -/// Constructs the elementParentsID map of the ID of the parent element in the original mesh for each generated element void MIRMesh::constructElementParentMap(int* elementParents) { // Initialize the map for the elements' parent IDs - elementParentIDs = IntMap( &elems ); + m_elementParentIDs = IntMap( &m_elems ); // Copy the data for the elements - for (int eID = 0; eID < elems.size(); ++eID) - elementParentIDs[eID] = elementParents[eID]; + for (int eID = 0; eID < m_elems.size(); ++eID) + m_elementParentIDs[eID] = elementParents[eID]; - SLIC_ASSERT_MSG( elementParentIDs.isValid(), "Element parent map is not valid."); + SLIC_ASSERT_MSG( m_elementParentIDs.isValid(), "Element parent map is not valid."); } //-------------------------------------------------------------------------------- -/// Constructs the elementDominantMaterials map of each element's single most dominant material void MIRMesh::constructElementDominantMaterialMap(std::vector dominantMaterials) { // Initialize the map for the elements' dominant colors - elementDominantMaterials = IntMap( &elems ); + m_elementDominantMaterials = IntMap( &m_elems ); // Copy the dat for the elements - for (int eID = 0; eID < elems.size(); ++eID) - elementDominantMaterials[eID] = dominantMaterials[eID]; + for (int eID = 0; eID < m_elems.size(); ++eID) + m_elementDominantMaterials[eID] = dominantMaterials[eID]; - SLIC_ASSERT_MSG( elementDominantMaterials.isValid(), "Element dominant materials map is not valid."); + SLIC_ASSERT_MSG( m_elementDominantMaterials.isValid(), "Element dominant materials map is not valid."); } //-------------------------------------------------------------------------------- -/// Print out the properties of the mesh. void MIRMesh::print() { printf("\n------------------------Printing Mesh Information:------------------------\n"); - printf("number of vertices: %d\n", verts.size()); - printf("number of elements: %d\n", elems.size()); - printf("number of materials: %d\n", numMaterials); + printf("number of vertices: %d\n", m_verts.size()); + printf("number of elements: %d\n", m_elems.size()); + printf("number of materials: %d\n", m_numMaterials); printf("evInds: { "); - for (unsigned long i = 0; i < meshTopology.evInds.size(); i++) + for (unsigned long i = 0; i < m_meshTopology.m_evInds.size(); i++) { - printf("%d ", meshTopology.evInds[i]); + printf("%d ", m_meshTopology.m_evInds[i]); } printf("}\n"); printf("evBegins: { "); - for (unsigned long i = 0; i < meshTopology.evBegins.size(); i++) + for (unsigned long i = 0; i < m_meshTopology.m_evBegins.size(); i++) { - printf("%d ", meshTopology.evBegins[i]); + printf("%d ", m_meshTopology.m_evBegins[i]); } printf("}\n"); printf("veInds: { "); - for (unsigned long i = 0; i < meshTopology.veInds.size(); i++) + for (unsigned long i = 0; i < m_meshTopology.m_veInds.size(); i++) { - printf("%d ", meshTopology.veInds[i]); + printf("%d ", m_meshTopology.m_veInds[i]); } printf("}\n"); printf("veBegins: { "); - for (unsigned long i = 0; i < meshTopology.veBegins.size(); i++) + for (unsigned long i = 0; i < m_meshTopology.m_veBegins.size(); i++) { - printf("%d ", meshTopology.veBegins[i]); + printf("%d ", m_meshTopology.m_veBegins[i]); } printf("}\n"); printf("vertexPositions: { "); - for (int i = 0; i < verts.size(); ++i) + for (int i = 0; i < m_verts.size(); ++i) { - printf("{%.2f, %.2f} ", vertexPositions[i].m_x, vertexPositions[i].m_y); + printf("{%.2f, %.2f} ", m_vertexPositions[i].m_x, m_vertexPositions[i].m_y); } printf("}\n"); printf("elementParentIDs: { "); - for (int i = 0; i < elems.size(); ++i) + for (int i = 0; i < m_elems.size(); ++i) { - printf("%d ", elementParentIDs[i]); + printf("%d ", m_elementParentIDs[i]); } printf("}\n"); printf("elementDominantMaterials: { "); - for (int i = 0; i < elems.size(); ++i) + for (int i = 0; i < m_elems.size(); ++i) { - printf("%d ", elementDominantMaterials[i]); + printf("%d ", m_elementDominantMaterials[i]); } printf("}\n"); printf("vertexVolumeFractions: { \n"); - for (unsigned long i = 0; i < materialVolumeFractionsVertex.size(); ++i) + for (unsigned long i = 0; i < m_materialVolumeFractionsVertex.size(); ++i) { printf(" { "); - for (int j = 0; j < verts.size(); ++j) + for (int j = 0; j < m_verts.size(); ++j) { - printf("%.3f, ", materialVolumeFractionsVertex[i][j]); + printf("%.3f, ", m_materialVolumeFractionsVertex[i][j]); } printf("}\n"); } @@ -311,8 +303,6 @@ void MIRMesh::print() //-------------------------------------------------------------------------------- -/// Reads in and constructs a mesh from the given file -/// Note: Must currently be an ASCII, UNSTRUCTURED_GRID .vtk file void MIRMesh::readMeshFromFile(std::string filename) { printf("Mesh reading functionality not implemented yet. Can't read file: %s", filename.c_str()); @@ -330,7 +320,6 @@ void MIRMesh::readMeshFromFile(std::string filename) //-------------------------------------------------------------------------------- -/// Writes out the mesh to a file void MIRMesh::writeMeshToFile(std::string filename) { std::ofstream meshfile; @@ -342,30 +331,30 @@ void MIRMesh::writeMeshToFile(std::string filename) << "vtk output\n" << "ASCII\n" << "DATASET UNSTRUCTURED_GRID\n" - << "POINTS " << verts.size() << " double\n"; + << "POINTS " << m_verts.size() << " double\n"; // write positions - for (int vID = 0; vID < verts.size(); ++vID) + for (int vID = 0; vID < m_verts.size(); ++vID) { - meshfile << vertexPositions[vID].m_x << " " << vertexPositions[vID].m_y << " 0\n"; // must always set all 3 coords; set Z=0 for 2D + meshfile << m_vertexPositions[vID].m_x << " " << m_vertexPositions[vID].m_y << " 0\n"; // must always set all 3 coords; set Z=0 for 2D } - meshfile << "\nCELLS " << elems.size() << " " << meshTopology.evInds.size() + elems.size(); - for (int i = 0; i < elems.size(); ++i) + meshfile << "\nCELLS " << m_elems.size() << " " << m_meshTopology.m_evInds.size() + m_elems.size(); + for (int i = 0; i < m_elems.size(); ++i) { - int nVerts = meshTopology.evBegins[i+1] - meshTopology.evBegins[i]; + int nVerts = m_meshTopology.m_evBegins[i+1] - m_meshTopology.m_evBegins[i]; meshfile << "\n" << nVerts; for (int j = 0; j < nVerts; ++j) { - int startIndex = meshTopology.evBegins[i]; - meshfile << " " << meshTopology.evInds[startIndex + j]; + int startIndex = m_meshTopology.m_evBegins[i]; + meshfile << " " << m_meshTopology.m_evInds[startIndex + j]; } } - meshfile << "\n\nCELL_TYPES " << elems.size() << "\n"; - for (int i = 0; i < elems.size(); ++i) + meshfile << "\n\nCELL_TYPES " << m_elems.size() << "\n"; + for (int i = 0; i < m_elems.size(); ++i) { - int nVerts = meshTopology.evBegins[i + 1] - meshTopology.evBegins[i]; + int nVerts = m_meshTopology.m_evBegins[i + 1] - m_meshTopology.m_evBegins[i]; if (nVerts == 3) meshfile << "5\n"; else if (nVerts == 4) @@ -373,12 +362,12 @@ void MIRMesh::writeMeshToFile(std::string filename) } // write element materials - meshfile << "\n\nCELL_DATA " << elems.size() + meshfile << "\n\nCELL_DATA " << m_elems.size() << "\nSCALARS cellIds int 1" << "\nLOOKUP_TABLE default \n"; - for(int i=0 ; i< elems.size() ; ++i) + for(int i=0 ; i< m_elems.size() ; ++i) { - meshfile << elementDominantMaterials[i] << " "; + meshfile << m_elementDominantMaterials[i] << " "; } meshfile <<"\n"; @@ -386,7 +375,6 @@ void MIRMesh::writeMeshToFile(std::string filename) //-------------------------------------------------------------------------------- -/// Computes the volume fractions of the elements of the original mesh, std::vector > MIRMesh::computeOriginalElementVolumeFractions() { std::map totalAreaOriginalElements; // the total area of the original elements @@ -394,39 +382,39 @@ std::vector > MIRMesh::computeOriginalElementVolumeFr std::map numChildren; // the number of child elements generated from one of the original elements // Compute the total area of each element of the original mesh and also the area of each new element - for (int eID = 0; eID < elems.size(); ++eID) + for (int eID = 0; eID < m_elems.size(); ++eID) { - int numVertices = meshTopology.evBegins[eID + 1] - meshTopology.evBegins[eID]; + int numVertices = m_meshTopology.m_evBegins[eID + 1] - m_meshTopology.m_evBegins[eID]; if (numVertices == 3) { Point2 trianglePoints[3]; for (int i = 0; i < 3; ++i) - trianglePoints[i] = vertexPositions[meshTopology.evInds[ meshTopology.evBegins[eID] + i] ]; + trianglePoints[i] = m_vertexPositions[m_meshTopology.m_evInds[ m_meshTopology.m_evBegins[eID] + i] ]; newElementAreas[eID] = computeTriangleArea(trianglePoints[0], trianglePoints[1], trianglePoints[2]); } if (numVertices == 4) { Point2 trianglePoints[4]; for (int i = 0; i < 4; ++i) - trianglePoints[i] = vertexPositions[meshTopology.evInds[ meshTopology.evBegins[eID] + i] ]; + trianglePoints[i] = m_vertexPositions[m_meshTopology.m_evInds[ m_meshTopology.m_evBegins[eID] + i] ]; newElementAreas[eID] = computeQuadArea(trianglePoints[0], trianglePoints[1], trianglePoints[2], trianglePoints[3]); } - totalAreaOriginalElements[ elementParentIDs[eID] ] += newElementAreas[eID]; - numChildren[ elementParentIDs[eID] ] += .4; + totalAreaOriginalElements[ m_elementParentIDs[eID] ] += newElementAreas[eID]; + numChildren[ m_elementParentIDs[eID] ] += .4; } // Intialize the element volume fraction vectors std::vector > elementVolumeFractions; // indexed as: elementVolumeFractions[material][originalElementID] = volumeFraction - elementVolumeFractions.resize( numMaterials ); + elementVolumeFractions.resize( m_numMaterials ); for (unsigned long i = 0; i < elementVolumeFractions.size(); ++i) elementVolumeFractions[i].resize( totalAreaOriginalElements.size(), 0.0 ); // Compute the volume fractions for each of the original mesh elements for (auto itr = newElementAreas.begin(); itr != newElementAreas.end(); itr++) { - int parentElementID = elementParentIDs[itr->first]; - int materialID = elementDominantMaterials[itr->first]; + int parentElementID = m_elementParentIDs[itr->first]; + int materialID = m_elementDominantMaterials[itr->first]; elementVolumeFractions[materialID][parentElementID] += (itr->second / totalAreaOriginalElements[parentElementID]); } @@ -435,7 +423,6 @@ std::vector > MIRMesh::computeOriginalElementVolumeFr //-------------------------------------------------------------------------------- -/// Computes the area of the triangle defined by the given three vertex positions. Uses Heron's formula. axom::float64 MIRMesh::computeTriangleArea(Point2 p0, Point2 p1, Point2 p2) { axom::float64 a = sqrt( ((p1.m_x - p0.m_x) * (p1.m_x - p0.m_x)) + ((p1.m_y - p0.m_y) * (p1.m_y - p0.m_y)) );// the distance from p0 to p1 @@ -449,8 +436,6 @@ axom::float64 MIRMesh::computeTriangleArea(Point2 p0, Point2 p1, Point2 p2) //-------------------------------------------------------------------------------- -/// Computes the area of the quad defined by the given four vertex positions. -/// Note: It is assumed the points are given in consecutive, counter-clockwise order. axom::float64 MIRMesh::computeQuadArea(Point2 p0, Point2 p1, Point2 p2, Point2 p3) { return computeTriangleArea(p0, p1, p2) + computeTriangleArea(p2, p3, p0); diff --git a/src/axom/mir/MIRMesh.hpp b/src/axom/mir/MIRMesh.hpp index c85c18e936..63b23d2ccc 100644 --- a/src/axom/mir/MIRMesh.hpp +++ b/src/axom/mir/MIRMesh.hpp @@ -3,6 +3,13 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +/** + * \file MIRMesh.hpp + * + * \brief Contains the specification for the MIRMesh class. + * + */ + #ifndef __MIR_MESH_H__ #define __MIR_MESH_H__ @@ -28,65 +35,163 @@ namespace axom namespace mir { - #define NULL_MAT -1 + const int NULL_MAT = -1; //-------------------------------------------------------------------------------- + /** + * \class MIRMesh + * + * \brief The MIRMesh class represents a finite element mesh containing element volume fractions. + * + * \detail This class is meant to be used in conjunction with the InterfaceReconstructor class + * to process the mesh. + * + */ class MIRMesh { - /**************************************************************** - * MESH FUNCTIONS - ****************************************************************/ public: + /** + * \brief Default constructor. + */ MIRMesh(); - MIRMesh(MIRMesh* _mesh); // copy constructor - ~MIRMesh(); - - void initializeMesh(VertSet _verts, ElemSet _elems, int _numMaterials, CellTopologyData _topology, CellMapData _mapData, std::vector > _elementVF = {}); - - void constructMeshRelations(); /// Constructs the mesh boundary and coboundary relations - void constructMeshVolumeFractionsMaps(std::vector > elementVF); /// Constructs the element and vertex volume fraction maps - void constructMeshVolumeFractionsVertex(std::vector > vertexVF); /// Constructs the vertex volume fraction map - void print(); + /** + * \brief Copy constrcutor. + */ + MIRMesh(MIRMesh* _mesh); - void readMeshFromFile(std::string filename); - void writeMeshToFile(std::string filename); + /** + * \brief Default destructor. + */ + ~MIRMesh(); - std::vector > computeOriginalElementVolumeFractions(); + /** + * \brief Initializes a mesh with the provided data and topology. + * + * \param _verts The set of vertices of the mesh. + * \param _elems The set of elements of the mesh. + * \param _numMaterials The number of materials present in the mesh. + * \param _topology The topology/connectivity of the mesh. + * \param _mapData The data used to initialized the maps associated with the vertex and element sets. + * \param _elementVF The volume fractions of each element. Note that this is an optional parameter. + */ + void initializeMesh(VertSet _verts, ElemSet _elems, int _numMaterials, CellTopologyData _topology, CellMapData _mapData, std::vector > _elementVF = {}); + + /** + * \brief Constructs the mesh boundary and coboundary relations. + * + * \note The boundary relation is from each element to its vertices. + * \note The coboundary relation is from each vertex to its elements. + */ + void constructMeshRelations(); + + /** + * \brief Constructs the element and vertex volume fraction maps. + * + * \param elementVF The volume fractions of each element. + */ + void constructMeshVolumeFractionsMaps(std::vector > elementVF); + + /** + * \brief Constructs the vertex volume fraction map. + * + * \param vertexVF The volume fractions of each vertex. + */ + void constructMeshVolumeFractionsVertex(std::vector > vertexVF); + + /** + * \brief Prints out the data contained within this mesh in a nice format. + */ + void print(); + + /** + * \brief Reads in a mesh specified by the given file. + * + * \param filename The location where the mesh file will be read from. + */ + void readMeshFromFile(std::string filename); + + /** + * \brief Writes out the mesh to the given file. + * + * \param filename The location where the mesh file will be written. + * + * \note Currently reads in an ASCII, UNSTRUCTURED_GRID .vtk file. + */ + void writeMeshToFile(std::string filename); + + + /** + * \brief Computes the volume fractions of the elements of the original mesh. + */ + std::vector > computeOriginalElementVolumeFractions(); private: - void constructVertexPositionMap(Point2* data); /// Constucts the positions map on the vertices - void constructElementParentMap(int* cellParents); /// Constructs the map of cells to their original cell parent - void constructElementDominantMaterialMap(std::vector dominantMaterials); /// Constructs the map of elements to their dominant materials - axom::float64 computeTriangleArea(Point2 p0, Point2 p1, Point2 p2); - axom::float64 computeQuadArea(Point2 p0, Point2 p1, Point2 p2, Point2 p3); + /** + * \brief Constucts the positions map on the vertices. + * + * \param data The array of position data for each vertex. + */ + void constructVertexPositionMap(Point2* data); + + /** + * \brief Constructs the map of elements to their original element parent. + * + * \param cellParents The array of parent IDs for each element of the mesh. + */ + void constructElementParentMap(int* cellParents); + + /** + * \brief Constructs the map of elements to their dominant materials. + * + * \param dominantMaterials A vector of material ids that are the dominant material of each element. + */ + void constructElementDominantMaterialMap(std::vector dominantMaterials); + + /** + * \brief Computes the area of the triangle defined by the given three vertex positions using Heron's formula. + * + * \param p0 The position of the first vertex. + * \param p1 The position of the second vertex. + * \param p2 The position of the third vertex. + */ + axom::float64 computeTriangleArea(Point2 p0, Point2 p1, Point2 p2); + + /** + * \brief Computes the area of the quad defined by the given four vertex positions. + * + * \param p0 The position of the first vertex. + * \param p1 The position of the second vertex. + * \param p2 The position of the third vertex. + * \param p3 The position of the fourth vertex. + * + * \note It is assumed that the points are given in consecutive, counter-clockwise order. + */ + axom::float64 computeQuadArea(Point2 p0, Point2 p1, Point2 p2, Point2 p3); /**************************************************************** * VARIABLES ****************************************************************/ public: // Mesh Set Definitions - VertSet verts; // the set of vertices in the mesh - ElemSet elems; // the set of elements in the mesh + VertSet m_verts; // the set of vertices in the mesh + ElemSet m_elems; // the set of elements in the mesh - public: // Mesh Relation Definitions - ElemToVertRelation bdry; // Boundary relation from elements to vertices - VertToElemRelation cobdry; // Coboundary relation from vertices to elements + ElemToVertRelation m_bdry; // Boundary relation from elements to vertices + VertToElemRelation m_cobdry; // Coboundary relation from vertices to elements - public: // Mesh Map Definitions - PointMap vertexPositions; // vertex position for each vertex - std::vector materialVolumeFractionsElement; // the volume fractions of each material for each element - std::vector materialVolumeFractionsVertex; // the volume fractions of each material for each vertex - IntMap elementParentIDs; // the ID of the parent element from the original mesh - IntMap elementDominantMaterials; // the dominant material of the cell (a processed mesh should have only one material per cell) - - public: - int numMaterials; - CellTopologyData meshTopology; + PointMap m_vertexPositions; // vertex position for each vertex + std::vector m_materialVolumeFractionsElement; // the volume fractions of each material for each element + std::vector m_materialVolumeFractionsVertex; // the volume fractions of each material for each vertex + IntMap m_elementParentIDs; // the ID of the parent element from the original mesh + IntMap m_elementDominantMaterials; // the dominant material of the cell (a processed mesh should have only one material per cell) + + int m_numMaterials; // the number of materials present in the mesh + CellTopologyData m_meshTopology; // the topology/connectivity of the mesh }; //-------------------------------------------------------------------------------- diff --git a/src/axom/mir/MIRMeshTypes.hpp b/src/axom/mir/MIRMeshTypes.hpp index d51aecd177..b6fc18ec28 100644 --- a/src/axom/mir/MIRMeshTypes.hpp +++ b/src/axom/mir/MIRMeshTypes.hpp @@ -3,6 +3,12 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +/** + * \file MIRMeshTypes.hpp + * + * \brief Contains the specifications for types aliases used throughout the MIR component. + */ + #ifndef __MIR_MESH_TYPES_H__ #define __MIR_MESH_TYPES_H__ diff --git a/src/axom/mir/MeshTester.cpp b/src/axom/mir/MeshTester.cpp index df9d76b7d8..690c9ad35b 100644 --- a/src/axom/mir/MeshTester.cpp +++ b/src/axom/mir/MeshTester.cpp @@ -26,7 +26,6 @@ MeshTester::~MeshTester() //-------------------------------------------------------------------------------- -/// Initialize mesh for Test Case 1 (from Meredith 2004) MIRMesh MeshTester::initTestCaseOne() { int numElements = 9; @@ -108,15 +107,15 @@ MIRMesh MeshTester::initTestCaseOne() }; CellTopologyData topology; - topology.evInds = evInds; - topology.evBegins = evBegins; - topology.veInds = veInds; - topology.veBegins = veBegins; + topology.m_evInds = evInds; + topology.m_evBegins = evBegins; + topology.m_veInds = veInds; + topology.m_veBegins = veBegins; CellMapData mapData; - mapData.elementDominantMaterials = {NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT}; - mapData.elementParents = { 0,1,2,3,4,5,6,7,8 }; // For the base mesh, the parents are always themselves - mapData.vertexPositions = points; + mapData.m_elementDominantMaterials = {NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT}; + mapData.m_elementParents = { 0,1,2,3,4,5,6,7,8 }; // For the base mesh, the parents are always themselves + mapData.m_vertexPositions = points; // Build the mesh mir::MIRMesh testMesh; @@ -127,7 +126,6 @@ MIRMesh MeshTester::initTestCaseOne() //-------------------------------------------------------------------------------- -/// Initialize mesh for Test Case 2 (from Meredith and Childs 2010) mir::MIRMesh MeshTester::initTestCaseTwo() { int numElements = 9; @@ -210,15 +208,15 @@ mir::MIRMesh MeshTester::initTestCaseTwo() }; CellTopologyData topology; - topology.evInds = evInds; - topology.evBegins = evBegins; - topology.veInds = veInds; - topology.veBegins = veBegins; + topology.m_evInds = evInds; + topology.m_evBegins = evBegins; + topology.m_veInds = veInds; + topology.m_veBegins = veBegins; CellMapData mapData; - mapData.elementDominantMaterials = {NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT}; - mapData.elementParents = { 0,1,2,3,4,5,6,7,8 }; // For the base mesh, the parents are always themselves - mapData.vertexPositions = points; + mapData.m_elementDominantMaterials = {NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT}; + mapData.m_elementParents = { 0,1,2,3,4,5,6,7,8 }; // For the base mesh, the parents are always themselves + mapData.m_vertexPositions = points; // Build the mesh mir::MIRMesh testMesh; @@ -229,7 +227,6 @@ mir::MIRMesh MeshTester::initTestCaseTwo() //-------------------------------------------------------------------------------- -/// Initialize mesh for Test Case 3, which tests all the triangle clipping cases. mir::MIRMesh MeshTester::initTestCaseThree() { int numElements = 4; @@ -283,15 +280,15 @@ mir::MIRMesh MeshTester::initTestCaseThree() CellTopologyData topology; - topology.evInds = evInds; - topology.evBegins = evBegins; - topology.veInds = veInds; - topology.veBegins = veBegins; + topology.m_evInds = evInds; + topology.m_evBegins = evBegins; + topology.m_veInds = veInds; + topology.m_veBegins = veBegins; CellMapData mapData; - mapData.elementDominantMaterials = {NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT}; - mapData.elementParents = { 0,1,2,3 }; // For the base mesh, the parents are always themselves - mapData.vertexPositions = points; + mapData.m_elementDominantMaterials = {NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT}; + mapData.m_elementParents = { 0,1,2,3 }; // For the base mesh, the parents are always themselves + mapData.m_vertexPositions = points; // Build the mesh mir::MIRMesh testMesh; @@ -302,7 +299,6 @@ mir::MIRMesh MeshTester::initTestCaseThree() //-------------------------------------------------------------------------------- -/// Initialize mesh for Test Case 4, a 3x3 grid with a circle of one material in the middle mir::MIRMesh MeshTester::initTestCaseFour() { int numElements = 9; @@ -394,15 +390,15 @@ mir::MIRMesh MeshTester::initTestCaseFour() elementVF[BLUE] = blueVolumeFractions; CellTopologyData topology; - topology.evInds = evInds; - topology.evBegins = evBegins; - topology.veInds = veInds; - topology.veBegins = veBegins; + topology.m_evInds = evInds; + topology.m_evBegins = evBegins; + topology.m_veInds = veInds; + topology.m_veBegins = veBegins; CellMapData mapData; - mapData.elementDominantMaterials = {NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT}; - mapData.elementParents = { 0,1,2,3,4,5,6,7,8 }; // For the base mesh, the parents are always themselves - mapData.vertexPositions = points; + mapData.m_elementDominantMaterials = {NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT}; + mapData.m_elementParents = { 0,1,2,3,4,5,6,7,8 }; // For the base mesh, the parents are always themselves + mapData.m_vertexPositions = points; // Build the mesh mir::MIRMesh testMesh; @@ -413,32 +409,31 @@ mir::MIRMesh MeshTester::initTestCaseFour() //-------------------------------------------------------------------------------- -/// Intializes a uniform grid with a circle of one material surrounded by another material. mir::MIRMesh MeshTester::createUniformGridTestCaseMesh(int gridSize, mir::Point2 circleCenter, axom::float64 circleRadius) { // Generate the mesh topology mir::CellData cellData = generateGrid(gridSize); - mir::VertSet verts = mir::VertSet(cellData.numVerts); // Construct the vertex set - mir::ElemSet elems = mir::ElemSet(cellData.numElems); // Construct the element set + mir::VertSet verts = mir::VertSet(cellData.m_numVerts); // Construct the vertex set + mir::ElemSet elems = mir::ElemSet(cellData.m_numElems); // Construct the element set int numMaterials = 2; enum { GREEN = 0, BLUE = 1 }; std::vector > elementVF; elementVF.resize(numMaterials); - std::vector greenVolumeFractions; greenVolumeFractions.resize(cellData.numElems); - std::vector blueVolumeFractions; blueVolumeFractions.resize(cellData.numElems); + std::vector greenVolumeFractions; greenVolumeFractions.resize(cellData.m_numElems); + std::vector blueVolumeFractions; blueVolumeFractions.resize(cellData.m_numElems); // Generate the element volume fractions for the circle int numMonteCarloSamples = 100; - for (int i = 0; i < cellData.numElems; ++i) + for (int i = 0; i < cellData.m_numElems; ++i) { greenVolumeFractions[i] = calculatePercentOverlapMonteCarlo(numMonteCarloSamples, circleCenter, circleRadius, - cellData.mapData.vertexPositions[cellData.topology.evInds[i * 4 + 0]], - cellData.mapData.vertexPositions[cellData.topology.evInds[i * 4 + 1]], - cellData.mapData.vertexPositions[cellData.topology.evInds[i * 4 + 2]], - cellData.mapData.vertexPositions[cellData.topology.evInds[i * 4 + 3]]); + cellData.m_mapData.m_vertexPositions[cellData.m_topology.m_evInds[i * 4 + 0]], + cellData.m_mapData.m_vertexPositions[cellData.m_topology.m_evInds[i * 4 + 1]], + cellData.m_mapData.m_vertexPositions[cellData.m_topology.m_evInds[i * 4 + 2]], + cellData.m_mapData.m_vertexPositions[cellData.m_topology.m_evInds[i * 4 + 3]]); blueVolumeFractions[i] = 1.0 - greenVolumeFractions[i]; } @@ -447,22 +442,22 @@ mir::MIRMesh MeshTester::createUniformGridTestCaseMesh(int gridSize, mir::Point2 std::vector elementParents;// For the base mesh, the parents are always themselves std::vector elementDominantMaterials; - for (int i = 0; i < cellData.numElems; ++i) + for (int i = 0; i < cellData.m_numElems; ++i) { elementParents.push_back(i); elementDominantMaterials.push_back(NULL_MAT); } CellTopologyData topology; - topology.evInds = cellData.topology.evInds; - topology.evBegins = cellData.topology.evBegins; - topology.veInds = cellData.topology.veInds; - topology.veBegins = cellData.topology.veBegins; + topology.m_evInds = cellData.m_topology.m_evInds; + topology.m_evBegins = cellData.m_topology.m_evBegins; + topology.m_veInds = cellData.m_topology.m_veInds; + topology.m_veBegins = cellData.m_topology.m_veBegins; CellMapData mapData; - mapData.elementDominantMaterials = elementDominantMaterials; - mapData.elementParents = elementParents; - mapData.vertexPositions = cellData.mapData.vertexPositions; + mapData.m_elementDominantMaterials = elementDominantMaterials; + mapData.m_elementParents = elementParents; + mapData.m_vertexPositions = cellData.m_mapData.m_vertexPositions; // Build the mesh mir::MIRMesh testMesh; @@ -473,32 +468,31 @@ mir::MIRMesh MeshTester::createUniformGridTestCaseMesh(int gridSize, mir::Point2 //-------------------------------------------------------------------------------- -// Assumes the quad vertices are ordered the same as the clipping case. -axom::float64 MeshTester::calculatePercentOverlapMonteCarlo(int gridSize, mir::Point2 circle_center, axom::float64 circle_radius, mir::Point2 q_p0, mir::Point2 q_p1, mir::Point2 q_p2, mir::Point2 q_p3) +axom::float64 MeshTester::calculatePercentOverlapMonteCarlo(int gridSize, mir::Point2 circleCenter, axom::float64 circleRadius, mir::Point2 quadP0, mir::Point2 quadP1, mir::Point2 quadP2, mir::Point2 quadP3) { // Check if any of the quad's corners are within the circle - axom::float64 distP0 = distance(q_p0, circle_center); - axom::float64 distP1 = distance(q_p1, circle_center); - axom::float64 distP2 = distance(q_p2, circle_center); - axom::float64 distP3 = distance(q_p3, circle_center); + axom::float64 distP0 = distance(quadP0, circleCenter); + axom::float64 distP1 = distance(quadP1, circleCenter); + axom::float64 distP2 = distance(quadP2, circleCenter); + axom::float64 distP3 = distance(quadP3, circleCenter); - if (distP0 < circle_radius && distP1 < circle_radius && distP2 < circle_radius && distP3 < circle_radius) + if (distP0 < circleRadius && distP1 < circleRadius && distP2 < circleRadius && distP3 < circleRadius) { // The entire quad overlaps the circle return 1.0; } - else if (distP0 < circle_radius || distP1 < circle_radius || distP2 < circle_radius || distP3 < circle_radius) + else if (distP0 < circleRadius || distP1 < circleRadius || distP2 < circleRadius || distP3 < circleRadius) { // Some of the quad overlaps the circle, so run the Monte Carlo sampling to determine how much - axom::float64 delta_x = abs(q_p2.m_x - q_p1.m_x) / (double) (gridSize - 1); - axom::float64 delta_y = abs(q_p0.m_y - q_p1.m_y) / (double) (gridSize - 1); + axom::float64 delta_x = abs(quadP2.m_x - quadP1.m_x) / (double) (gridSize - 1); + axom::float64 delta_y = abs(quadP0.m_y - quadP1.m_y) / (double) (gridSize - 1); int countOverlap = 0; for (int y = 0; y < gridSize; ++y) { for (int x = 0; x < gridSize; ++x) { - mir::Point2 samplePoint(delta_x * x + q_p1.m_x, delta_y * y + q_p1.m_y); - if (distance(samplePoint, circle_center) < circle_radius) + mir::Point2 samplePoint(delta_x * x + quadP1.m_x, delta_y * y + quadP1.m_y); + if (distance(samplePoint, circleCenter) < circleRadius) ++countOverlap; } } @@ -513,20 +507,19 @@ axom::float64 MeshTester::calculatePercentOverlapMonteCarlo(int gridSize, mir::P //-------------------------------------------------------------------------------- -/// Generates a 2D uniform grid with n x n elements. -mir::CellData MeshTester::generateGrid(int n) +mir::CellData MeshTester::generateGrid(int gridSize) { // Generate the topology for a uniform quad mesh with n x n elements automatically - int numElements = n * n; - int numVertices = (n + 1) * (n + 1); + int numElements = gridSize * gridSize; + int numVertices = (gridSize + 1) * (gridSize + 1); // Generate the evInds std::vector evInds; for (int eID = 0; eID < numElements; ++eID) { - int row = eID / n; // note the integer division - int vertsPerRow = n + 1; - int elemsPerRow = n; + int row = eID / gridSize; // note the integer division + int vertsPerRow = gridSize + 1; + int elemsPerRow = gridSize; evInds.push_back( (eID % elemsPerRow) + row * vertsPerRow + 0); evInds.push_back( (eID % elemsPerRow) + (row + 1) * vertsPerRow + 0); @@ -537,7 +530,7 @@ mir::CellData MeshTester::generateGrid(int n) // Generate the evBegins std::vector evBegins; evBegins.push_back(0); - for (int i = 0; i < n * n; ++i) + for (int i = 0; i < numElements; ++i) { evBegins.push_back((i + 1) * 4); } @@ -573,22 +566,22 @@ mir::CellData MeshTester::generateGrid(int n) // Generate the vertex positions std::vector points; - for (int y = n; y > -1; --y) + for (int y = gridSize; y > -1; --y) { - for (int x = 0; x < n + 1; ++x) + for (int x = 0; x < gridSize + 1; ++x) { points.push_back(mir::Point2(x, y)); } } mir::CellData data; - data.numVerts = numVertices; - data.numElems = numElements; - data.topology.evInds = evInds; - data.topology.evBegins = evBegins; - data.topology.veInds = veInds; - data.topology.veBegins = veBegins; - data.mapData.vertexPositions = points; + data.m_numVerts = numVertices; + data.m_numElems = numElements; + data.m_topology.m_evInds = evInds; + data.m_topology.m_evBegins = evBegins; + data.m_topology.m_veInds = veInds; + data.m_topology.m_veBegins = veBegins; + data.m_mapData.m_vertexPositions = points; // // Print out the results // printf("evInds: { "); @@ -633,7 +626,6 @@ mir::CellData MeshTester::generateGrid(int n) //-------------------------------------------------------------------------------- -/// Calculate the distance between the two given points. axom::float64 MeshTester::distance(mir::Point2 p0, mir::Point2 p1) { return sqrt( ((p1.m_x - p0.m_x) * (p1.m_x - p0.m_x)) + ((p1.m_y - p0.m_y) * (p1.m_y - p0.m_y)) ); @@ -641,16 +633,14 @@ axom::float64 MeshTester::distance(mir::Point2 p0, mir::Point2 p1) //-------------------------------------------------------------------------------- -/// Multiple materials, multiple concentric circles. -/// Note: Assumes each circle has a unique material. mir::MIRMesh MeshTester::initTestCaseFive(int gridSize, int numCircles) { // Generate the mesh topology mir::CellData cellData = generateGrid(gridSize); - mir::VertSet verts = mir::VertSet(cellData.numVerts); // Construct the vertex set - mir::ElemSet elems = mir::ElemSet(cellData.numElems); // Construct the element set + mir::VertSet verts = mir::VertSet(cellData.m_numVerts); // Construct the vertex set + mir::ElemSet elems = mir::ElemSet(cellData.m_numElems); // Construct the element set // Generate the element volume fractions with concentric circles int numMaterials = numCircles + 1; @@ -679,17 +669,17 @@ mir::MIRMesh MeshTester::initTestCaseFive(int gridSize, int numCircles) for (int i = 0; i < numMaterials; ++i) { std::vector tempVec; - tempVec.resize(cellData.numElems); + tempVec.resize(cellData.m_numElems); materialVolumeFractionsData.push_back(tempVec); } // Use the uniform sampling method to generate volume fractions for each material - for (int eID = 0; eID < cellData.numElems; ++eID) + for (int eID = 0; eID < cellData.m_numElems; ++eID) { - mir::Point2 v0 = cellData.mapData.vertexPositions[cellData.topology.evInds[eID * 4 + 0]]; - mir::Point2 v1 = cellData.mapData.vertexPositions[cellData.topology.evInds[eID * 4 + 1]]; - mir::Point2 v2 = cellData.mapData.vertexPositions[cellData.topology.evInds[eID * 4 + 2]]; - mir::Point2 v3 = cellData.mapData.vertexPositions[cellData.topology.evInds[eID * 4 + 3]]; + mir::Point2 v0 = cellData.m_mapData.m_vertexPositions[cellData.m_topology.m_evInds[eID * 4 + 0]]; + mir::Point2 v1 = cellData.m_mapData.m_vertexPositions[cellData.m_topology.m_evInds[eID * 4 + 1]]; + mir::Point2 v2 = cellData.m_mapData.m_vertexPositions[cellData.m_topology.m_evInds[eID * 4 + 2]]; + mir::Point2 v3 = cellData.m_mapData.m_vertexPositions[cellData.m_topology.m_evInds[eID * 4 + 3]]; // Run the uniform sampling to determine how much of the current cell is composed of each material int materialCount[numMaterials]; for (int i = 0; i < numMaterials; ++i) materialCount[i] = 0; @@ -733,22 +723,22 @@ mir::MIRMesh MeshTester::initTestCaseFive(int gridSize, int numCircles) std::vector elementParents; // For the base mesh, the parents are always themselves std::vector elementDominantMaterials; - for (int i = 0; i < cellData.numElems; ++i) + for (int i = 0; i < cellData.m_numElems; ++i) { elementParents.push_back(i); elementDominantMaterials.push_back(NULL_MAT); } CellTopologyData topology; - topology.evInds = cellData.topology.evInds; - topology.evBegins = cellData.topology.evBegins; - topology.veInds = cellData.topology.veInds; - topology.veBegins = cellData.topology.veBegins; + topology.m_evInds = cellData.m_topology.m_evInds; + topology.m_evBegins = cellData.m_topology.m_evBegins; + topology.m_veInds = cellData.m_topology.m_veInds; + topology.m_veBegins = cellData.m_topology.m_veBegins; CellMapData mapData; - mapData.elementDominantMaterials = elementDominantMaterials; - mapData.elementParents = elementParents; - mapData.vertexPositions = cellData.mapData.vertexPositions; + mapData.m_elementDominantMaterials = elementDominantMaterials; + mapData.m_elementParents = elementParents; + mapData.m_vertexPositions = cellData.m_mapData.m_vertexPositions; // Build the mesh mir::MIRMesh testMesh; @@ -759,24 +749,23 @@ mir::MIRMesh MeshTester::initTestCaseFive(int gridSize, int numCircles) //-------------------------------------------------------------------------------- -/// Calculate the number of corners of the quad that are within the circle -int MeshTester::circleQuadCornersOverlaps(mir::Point2 circle_center, axom::float64 circle_radius, mir::Point2 q_p0, mir::Point2 q_p1, mir::Point2 q_p2, mir::Point2 q_p3) +int MeshTester::circleQuadCornersOverlaps(mir::Point2 circleCenter, axom::float64 circleRadius, mir::Point2 quadP0, mir::Point2 quadP1, mir::Point2 quadP2, mir::Point2 quadP3) { // Check if any of the quad's corners are within the circle - axom::float64 distP0 = distance(q_p0, circle_center); - axom::float64 distP1 = distance(q_p1, circle_center); - axom::float64 distP2 = distance(q_p2, circle_center); - axom::float64 distP3 = distance(q_p3, circle_center); + axom::float64 distP0 = distance(quadP0, circleCenter); + axom::float64 distP1 = distance(quadP1, circleCenter); + axom::float64 distP2 = distance(quadP2, circleCenter); + axom::float64 distP3 = distance(quadP3, circleCenter); int numCorners = 0; - if (distP0 < circle_radius) + if (distP0 < circleRadius) numCorners++; - if (distP1 < circle_radius) + if (distP1 < circleRadius) numCorners++; - if (distP2 < circle_radius) + if (distP2 < circleRadius) numCorners++; - if (distP3 < circle_radius) + if (distP3 < circleRadius) numCorners++; return numCorners; @@ -784,5 +773,41 @@ int MeshTester::circleQuadCornersOverlaps(mir::Point2 circle_center, axom::float //-------------------------------------------------------------------------------- +mir::MIRMesh MeshTester::initQuadClippingTestMesh() +{ + // Generate the mesh topology + int gridSize = 3; + mir::CellData cellData = generateGrid(gridSize); + + mir::VertSet verts = mir::VertSet(cellData.m_numVerts); // Construct the vertex set + mir::ElemSet elems = mir::ElemSet(cellData.m_numElems); // Construct the element set + + int numMaterials = 2; + + std::vector > elementVF; + elementVF.resize(numMaterials); + elementVF[0] = {1.0, 1.0, 1.0, 0.5, 0.5, 0.5, 0.0, 0.0, 0.0}; + elementVF[1] = {0.0, 0.0, 0.0, 0.5, 0.5, 0.5, 1.0, 1.0, 1.0}; + + std::vector elementParents; + std::vector elementDominantMaterials; + for (int i = 0; i < cellData.m_numElems; ++i) + { + elementParents.push_back(i); + elementDominantMaterials.push_back(NULL_MAT); + } + + cellData.m_mapData.m_elementDominantMaterials = elementDominantMaterials; + cellData.m_mapData.m_elementParents = elementParents; + + // Build the mesh + mir::MIRMesh testMesh; + testMesh.initializeMesh(verts, elems, numMaterials, cellData.m_topology, cellData.m_mapData, elementVF); + + return testMesh; +} + +//-------------------------------------------------------------------------------- + } } diff --git a/src/axom/mir/MeshTester.hpp b/src/axom/mir/MeshTester.hpp index b89cfa2b26..9641fec6a1 100644 --- a/src/axom/mir/MeshTester.hpp +++ b/src/axom/mir/MeshTester.hpp @@ -3,6 +3,13 @@ // // SPDX-License-Identifier: (BSD-3-Clause) +/** + * \file MeshTester.hpp + * + * \brief Contains the specification for the MeshTester class. + * + */ + #ifndef __MESH_TESTER_H__ #define __MESH_TESTER_H__ @@ -20,26 +27,144 @@ namespace axom { namespace mir { + /** + * \class MeshTester + * + * \brief A class used to generate MIRMeshs with specific properties so + * that the reconstruction output can be validated visually. + * + */ class MeshTester { public: + /** + * \brief Default constructor. + */ MeshTester(); + + /** + * \brief Default destructor. + */ ~MeshTester(); public: - MIRMesh initTestCaseOne(); // 3x3 grid, 2 materials - mir::MIRMesh initTestCaseTwo(); // 3x3 grid, 3 materials - mir::MIRMesh initTestCaseThree(); // triforce, 2 materials - mir::MIRMesh initTestCaseFour(); // 3x3, 2 materials, circle of material - mir::MIRMesh createUniformGridTestCaseMesh(int gridSize, mir::Point2 circleCenter, axom::float64 circleRadius); - mir::MIRMesh initTestCaseFive(int gridSize, int numCircles); // multiple materials, multiple concentric circles - + /** + * \brief Initializes an MIRMesh based on the example from Meredith 2004 paper. + * + * \note The mesh is a 3x3 uniform grid of quads with 2 materials. + * + * \return The generated mesh. + */ + MIRMesh initTestCaseOne(); + + /** + * \brief Initializes an MIRMesh based on the example from Meredith and Childs 2010 paper. + * + * \note The mesh is a 3x3 uniform grid of quads with 3 materials. + * + * \return The generated mesh. + */ + mir::MIRMesh initTestCaseTwo(); + + /** + * \brief Initializes an MIRMesh used for testing triangle clipping cases. + * + * \note The mesh is a set of four triangles with 2 materials + * + * \return The generated mesh. + */ + mir::MIRMesh initTestCaseThree(); + + /** + * \brief Intializes a mesh used for testing a single circle of one materials surrounded by another. + * + * \note The mesh is a 3x3 uniform grid with 2 materials and has a single circle in the center composed + * of one material and is surrounded by a second material. + * + * \return The generated mesh. + */ + mir::MIRMesh initTestCaseFour(); + + /** + * \brief Initializes a mesh to be used for testing a set of concentric circles centered in a uniform grid. + * + * \param gridSize The number of elements in the width and the height of the uniform grid. + * \param numCircles The number of concentric circles that are centered in the grid. + * + * \note Each circle is composed of a different material. + * + * \return The generated mesh. + */ + mir::MIRMesh initTestCaseFive(int gridSize, int numCircles); // multiple materials, multiple concentric circles + + /** + * \brief Initializes a mesh composed of a uniform grid with a circle of material in it. + * + * \param gridSize The number of elements in the width and height of the uniform grid. + * \param circleCenter The center point of the circle. + * \param circleRadius The radius of the circle. + * + * \return The generated mesh. + */ + mir::MIRMesh createUniformGridTestCaseMesh(int gridSize, mir::Point2 circleCenter, axom::float64 circleRadius); + + /** + * \brief Intializes a mesh to be used for validating the results of quad clipping. + * + * \note The mesh is a 3x3 uniform grid with 2 materials and element volume fraction such + * that the mesh would be split horizontally through the middle. + * + * \return The generated mesh. + */ + mir::MIRMesh initQuadClippingTestMesh(); + private: - axom::float64 distance(mir::Point2 p0, mir::Point2 p1); - mir::CellData generateGrid(int n); - axom::float64 calculatePercentOverlapMonteCarlo(int n, mir::Point2 circle_center, axom::float64 circle_radius, mir::Point2 q_p0, mir::Point2 q_p1, mir::Point2 q_p2, mir::Point2 q_p3); + + /** + * \brief Calculate the distance between the two given points. + * + * \param p0 The first point. + * \param p1 The second point. + * + * \return The distance between the two points. + */ + axom::float64 distance(mir::Point2 p0, mir::Point2 p1); + + /** + * \brief Generates a 2D uniform grid of n x n elements. + * + * \param gridSize The number of elements in the width and height of the uniform grid. + */ + mir::CellData generateGrid(int gridSize); + + /** + * \brief Calculates the percent overlap between the given circle and quad. + * + * \param gridSize The size of the uniform grid which will be sampled over to check for overlap. + * \param circleCenter The center point of the circle. + * \param circleRadius The radius of the circle. + * \param quadP0 The upper left vertex of the quad. + * \param quadP1 The lower left vertex of the quad. + * \param quadP2 The lower right vertex of the quad. + * \param quadP3 The upper right vertex of the quad. + * + * /return The percent value overlap of the circle and the quad between [0, 1]. + */ + axom::float64 calculatePercentOverlapMonteCarlo(int gridSize, mir::Point2 circleCenter, axom::float64 circleRadius, mir::Point2 quadP0, mir::Point2 quadP1, mir::Point2 quadP2, mir::Point2 quadP3); - int circleQuadCornersOverlaps(mir::Point2 circle_center, axom::float64 circle_radius, mir::Point2 q_p0, mir::Point2 q_p1, mir::Point2 q_p2, mir::Point2 q_p3); + /** + * \brief Calculates the number of corners of the quad that are within the circle. + * + * \param circleCenter The center point of the circle. + * \param circleRadius The radius of the circle. + * \param quadP0 The upper left vertex of the quad. + * \param quadP1 The lower left vertex of the quad. + * \param quadP2 The lower right vertex of the quad. + * \param quadP3 The upper right vertex of the quad. + * + * \return The number of corners of the quad that are within the circle. + */ + int circleQuadCornersOverlaps(mir::Point2 circleCenter, axom::float64 circleRadius, mir::Point2 quadP0, mir::Point2 quadP1, mir::Point2 quadP2, mir::Point2 quadP3); }; } } diff --git a/src/axom/mir/ZooClippingTables.cpp b/src/axom/mir/ZooClippingTables.cpp index 5cb584274c..05e96ea2d0 100644 --- a/src/axom/mir/ZooClippingTables.cpp +++ b/src/axom/mir/ZooClippingTables.cpp @@ -10,7 +10,7 @@ namespace axom namespace mir { - // Quad Vertex Indices + // Quad Vertex Local Indices // // 0 7 3 // @---------@---------@ @@ -45,7 +45,7 @@ namespace mir }; - // Triangle Vertex Indices + // Triangle Vertex Local Indices // 0 // @ // / \ diff --git a/src/axom/mir/ZooClippingTables.hpp b/src/axom/mir/ZooClippingTables.hpp index fcdfc20a9e..924b98792a 100644 --- a/src/axom/mir/ZooClippingTables.hpp +++ b/src/axom/mir/ZooClippingTables.hpp @@ -6,11 +6,28 @@ #ifndef __ZOO_CLIPPING_TABLES_H__ #define __ZOO_CLIPPING_TABLES_H__ +/** + * \file ZooClippingTables.hpp + * + * \brief Contains the defintions for the clipping cases and enumerator + * for the shape types in the zoo. + */ namespace axom { namespace mir { + + enum Shape + { + Triangle, + Quad, + Tetrahedron, + Triangular_Prism, + Pyramid, + Hexahedron + }; + extern const int quadClipTable[16][19]; extern const int triangleClipTable[8][10]; } diff --git a/src/axom/mir/examples/CMakeLists.txt b/src/axom/mir/examples/CMakeLists.txt index b1a647e764..16e15481a7 100644 --- a/src/axom/mir/examples/CMakeLists.txt +++ b/src/axom/mir/examples/CMakeLists.txt @@ -5,7 +5,7 @@ set( mir_examples mir_tutorial_simple.cpp - mirConcentricCircles.cpp + mir_concentric_circles.cpp ) set( mir_example_dependencies diff --git a/src/axom/mir/examples/mirConcentricCircles.cpp b/src/axom/mir/examples/mir_concentric_circles.cpp similarity index 83% rename from src/axom/mir/examples/mirConcentricCircles.cpp rename to src/axom/mir/examples/mir_concentric_circles.cpp index b445c859e7..48d361400f 100644 --- a/src/axom/mir/examples/mirConcentricCircles.cpp +++ b/src/axom/mir/examples/mir_concentric_circles.cpp @@ -39,18 +39,18 @@ int main( int argc, char** argv ) std::string outputFilePath = std::string(argv[3]); // Intialize a mesh for testing MIR - auto start_time = Clock::now(); + auto startTime = Clock::now(); mir::MeshTester tester; - mir::MIRMesh testMesh = tester.initTestCaseFive(gridSize, numCircles); // grid size, numCircles - auto end_time = Clock::now(); - std::cout << "Mesh init time: " << std::chrono::duration_cast(end_time - start_time).count() << " ms" << std::endl; + mir::MIRMesh testMesh = tester.initTestCaseFive(gridSize, numCircles); + auto endTime = Clock::now(); + std::cout << "Mesh init time: " << std::chrono::duration_cast(endTime - startTime).count() << " ms" << std::endl; // Begin material interface reconstruction - start_time = Clock::now(); + startTime = Clock::now(); mir::InterfaceReconstructor reconstructor; - mir::MIRMesh processedMesh = reconstructor.computeReconstructedInterface(&testMesh); - end_time = Clock::now(); - std::cout << "Material interface reconstruction time: " << std::chrono::duration_cast(end_time - start_time).count() << " ms" << std::endl; + mir::MIRMesh processedMesh = reconstructor.computeReconstructedInterface(testMesh); + endTime = Clock::now(); + std::cout << "Material interface reconstruction time: " << std::chrono::duration_cast(endTime - startTime).count() << " ms" << std::endl; // Output results processedMesh.writeMeshToFile(outputFilePath + "outputConcentricCircles.vtk"); diff --git a/src/axom/mir/examples/mir_tutorial_simple.cpp b/src/axom/mir/examples/mir_tutorial_simple.cpp index d35fe0bfbc..ad67317a6e 100644 --- a/src/axom/mir/examples/mir_tutorial_simple.cpp +++ b/src/axom/mir/examples/mir_tutorial_simple.cpp @@ -18,35 +18,35 @@ namespace mir = axom::mir; //-------------------------------------------------------------------------------- /*! - * \brief Tutorial main + * \brief Tutorial main showing how to initialize test cases and perform mir. */ int main( int AXOM_NOT_USED(argc), char** AXOM_NOT_USED(argv) ) { // Intialize a mesh for testing MIR - auto start_time = Clock::now(); + auto startTime = Clock::now(); mir::MeshTester tester; // mir::MIRMesh testMesh = tester.initTestCaseOne(); // mir::MIRMesh testMesh = tester.initTestCaseTwo(); // mir::MIRMesh testMesh = tester.initTestCaseThree(); // mir::MIRMesh testMesh = tester.initTestCaseFour(); - mir::MIRMesh testMesh = tester.initTestCaseFive(25, 7); + mir::MIRMesh testMesh = tester.initTestCaseFive(50, 25); - auto end_time = Clock::now(); - std::cout << "Mesh init time: " << std::chrono::duration_cast(end_time - start_time).count() << " ms" << std::endl; + auto endTime = Clock::now(); + std::cout << "Mesh init time: " << std::chrono::duration_cast(endTime - startTime).count() << " ms" << std::endl; // Begin material interface reconstruction - start_time = Clock::now(); + startTime = Clock::now(); mir::InterfaceReconstructor reconstructor; - // mir::MIRMesh processedMesh = reconstructor.computeReconstructedInterface(&testMesh); - mir::MIRMesh processedMesh = reconstructor.computeReconstructedInterfaceIterative(&testMesh, 5, 0.3); // 5 iterations, 90 percent + // mir::MIRMesh processedMesh = reconstructor.computeReconstructedInterface(testMesh); // Process once, with original Meredith algorithm + mir::MIRMesh processedMesh = reconstructor.computeReconstructedInterfaceIterative(testMesh, 5, 0.3); // 5 iterations, 30 percent with iterative Meredith algorithm - end_time = Clock::now(); - std::cout << "Material interface reconstruction time: " << std::chrono::duration_cast(end_time - start_time).count() << " ms" << std::endl; + endTime = Clock::now(); + std::cout << "Material interface reconstruction time: " << std::chrono::duration_cast(endTime - startTime).count() << " ms" << std::endl; // Output results - processedMesh.writeMeshToFile("/Users/sterbentz3/Desktop/iterativeOutputTestMesh5.vtk"); + processedMesh.writeMeshToFile("/Users/sterbentz3/Desktop/testOutputIterative6.vtk"); return 0; } diff --git a/src/axom/mir/tests/CMakeLists.txt b/src/axom/mir/tests/CMakeLists.txt index 155f8ae486..15a42d39ff 100644 --- a/src/axom/mir/tests/CMakeLists.txt +++ b/src/axom/mir/tests/CMakeLists.txt @@ -13,6 +13,7 @@ set(gtest_mir_tests mir_smoke.cpp + mir_interface_reconstructor.cpp ) diff --git a/src/axom/mir/tests/mir_interface_reconstructor.cpp b/src/axom/mir/tests/mir_interface_reconstructor.cpp new file mode 100644 index 0000000000..8a11c21e73 --- /dev/null +++ b/src/axom/mir/tests/mir_interface_reconstructor.cpp @@ -0,0 +1,55 @@ +// Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#ifndef MIR_SMOKE_H_ +#define MIR_SMOKE_H_ + +#include "gtest/gtest.h" + +#include "axom/slic.hpp" +#include "axom/mir.hpp" + +using namespace axom; + +TEST(mir_quad_clipping, quad_clipping_case_zero) +{ + // Initialize a 3x3 mesh with 2 materials + mir::MeshTester meshGenerator; + mir::MIRMesh testMesh = meshGenerator.initQuadClippingTestMesh(); + + // Initialize its volume fractions to custom values to guarantee this clipping case + std::vector > vertexVF; + vertexVF.resize(2); + vertexVF[0] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + vertexVF[1] = {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}; + testMesh.constructMeshVolumeFractionsVertex(vertexVF); + + int upperLeftVertexID = 5; + int lowerLeftVertexID = 9; + int lowerRightVertexID = 10; + int upperRightVertexID = 6; + + mir::InterfaceReconstructor reconstructor; + unsigned int clippingCase = reconstructor.determineQuadClippingCase( testMesh, 0, 1, upperLeftVertexID, lowerLeftVertexID, lowerRightVertexID, upperRightVertexID ); + + EXPECT_EQ( clippingCase, 0 ); +} + + +//---------------------------------------------------------------------- + +int main(int argc, char* argv[]) +{ + int result = 0; + ::testing::InitGoogleTest(&argc, argv); + + axom::slic::UnitTestLogger logger; // create & initialize test logger, + + result = RUN_ALL_TESTS(); + return result; +} + + +#endif // MIR_SMOKE_H_ From fcc143692b0929f9af94aadfbc61e15627a55fda Mon Sep 17 00:00:00 2001 From: Sterbentz Date: Wed, 26 Jun 2019 17:13:42 -0700 Subject: [PATCH 11/23] Refactored the interface reconstruction code. Ensured everything is properly commented. Added unit tests. Added a map to the mesh to store element shapes. --- src/axom/mir/CMakeLists.txt | 6 +- src/axom/mir/CellClipper.cpp | 157 +++++ src/axom/mir/CellClipper.hpp | 120 ++++ src/axom/mir/CellData.cpp | 6 + src/axom/mir/CellData.hpp | 1 + src/axom/mir/CellGenerator.cpp | 218 +++++++ src/axom/mir/CellGenerator.hpp | 150 +++++ src/axom/mir/InterfaceReconstructor.cpp | 611 +++--------------- src/axom/mir/InterfaceReconstructor.hpp | 212 +----- src/axom/mir/MIRMesh.cpp | 58 +- src/axom/mir/MIRMesh.hpp | 42 +- src/axom/mir/MIRMeshTypes.hpp | 11 + src/axom/mir/MIRUtilities.hpp | 209 ++++++ src/axom/mir/MeshTester.cpp | 39 +- src/axom/mir/MeshTester.hpp | 12 +- src/axom/mir/ZooClippingTables.cpp | 34 + src/axom/mir/ZooClippingTables.hpp | 16 +- .../mir/examples/mir_concentric_circles.cpp | 3 +- src/axom/mir/examples/mir_tutorial_simple.cpp | 28 +- src/axom/mir/tests/CMakeLists.txt | 3 + src/axom/mir/tests/mir_cell_clipper.cpp | 451 +++++++++++++ src/axom/mir/tests/mir_cell_generator.cpp | 231 +++++++ .../mir/tests/mir_interface_reconstructor.cpp | 31 +- src/axom/mir/tests/mir_utilities.cpp | 101 +++ src/docs/dependencies.dot | 2 +- src/index.rst | 2 +- 26 files changed, 1937 insertions(+), 817 deletions(-) create mode 100644 src/axom/mir/CellClipper.cpp create mode 100644 src/axom/mir/CellClipper.hpp create mode 100644 src/axom/mir/CellGenerator.cpp create mode 100644 src/axom/mir/CellGenerator.hpp create mode 100644 src/axom/mir/MIRUtilities.hpp create mode 100644 src/axom/mir/tests/mir_cell_clipper.cpp create mode 100644 src/axom/mir/tests/mir_cell_generator.cpp create mode 100644 src/axom/mir/tests/mir_utilities.cpp diff --git a/src/axom/mir/CMakeLists.txt b/src/axom/mir/CMakeLists.txt index e1740e6ad4..db7d2fda20 100644 --- a/src/axom/mir/CMakeLists.txt +++ b/src/axom/mir/CMakeLists.txt @@ -22,15 +22,19 @@ set(mir_headers InterfaceReconstructor.hpp CellData.hpp MeshTester.hpp + CellClipper.hpp + MIRUtilities.hpp + CellGenerator.hpp ) set(mir_sources - ../Axom.cpp MIRMesh.cpp InterfaceReconstructor.cpp ZooClippingTables.cpp CellData.cpp MeshTester.cpp + CellClipper.cpp + CellGenerator.cpp ) #------------------------------------------------------------------------------ diff --git a/src/axom/mir/CellClipper.cpp b/src/axom/mir/CellClipper.cpp new file mode 100644 index 0000000000..c5f25d4272 --- /dev/null +++ b/src/axom/mir/CellClipper.cpp @@ -0,0 +1,157 @@ +// Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "CellClipper.hpp" + +namespace axom +{ +namespace mir +{ + +//-------------------------------------------------------------------------------- + +CellClipper::CellClipper() +{ + +} + +//-------------------------------------------------------------------------------- + +CellClipper::~CellClipper() +{ + +} + +//-------------------------------------------------------------------------------- + +// Computes the t-values where each edge is clipped, as well as the topology of the new output cells after clipping the original cell +// Outputs the newElements, newVertices maps and the verticesClippingTValue array[] +void CellClipper::computeClippingPoints(const mir::Shape shapeType, + const std::vector >& vertexVF, + std::map >& newElements, + std::map >& newVertices, + axom::float64* tValues) +{ + // Determine the clipping case for the current element + unsigned int caseIndex = determineClippingCase( shapeType, vertexVF[0], vertexVF[1] ); + + std::vector > clipTable = getClipTable(shapeType); + + // Create the new polygons based on the clipping case + int currentElementIndex = 0; // the next available element index + int i = 0; + int numVertices = clipTable[caseIndex][i]; + + // for each new element in the current clipping case + while (numVertices != -1) + { + // for each vertex of the new element + for (int j = 0; j < numVertices; ++j) + { + // Find the id of the next vertex of the new element + int vID = clipTable[caseIndex][i + (j+1)]; + + // Associate the vertex and element together + newElements[currentElementIndex].push_back(vID); + newVertices[vID].push_back(currentElementIndex); + + if ( vID >= mir::utilities::numVerts(shapeType) ) + { + int vertexOneID = mir::utilities::getEdgeEndpoint(shapeType, vID, true); + int vertexTwoID = mir::utilities::getEdgeEndpoint(shapeType, vID, false); + + tValues[vID] = computeTValueOnEdge( vertexVF[0][vertexOneID], vertexVF[1][vertexOneID], vertexVF[0][vertexTwoID], vertexVF[1][vertexTwoID] ); + } + } + + // Increment the element index counter, marking the current element as being finished processed + currentElementIndex++; + + // Increase index into lookup table to the next element + i += (numVertices + 1); + numVertices = clipTable[caseIndex][i]; + } +} + +//-------------------------------------------------------------------------------- + +unsigned int CellClipper::determineClippingCase(const mir::Shape shapeType, + const std::vector& matOneVF, + const std::vector& matTwoVF) +{ + unsigned int caseIndex = 0; + + int numVertices = mir::utilities::numVerts(shapeType); + + for (int vID = 0; vID < numVertices; ++vID) + { + if (matOneVF[vID] > matTwoVF[vID]) + { + unsigned int bitIndex = (numVertices - 1) - vID; + + caseIndex |= (1 << bitIndex); + } + } + + return caseIndex; +} + +//-------------------------------------------------------------------------------- + +axom::float64 CellClipper::computeTValueOnEdge(axom::float64 vfMatOneVertexOne, + axom::float64 vfMatTwoVertexOne, + axom::float64 vfMatOneVertexTwo, + axom::float64 vfMatTwoVertexTwo) +{ + axom::float64 ret = 0.0; + + // TODO: Perhaps just handle NULL_MAT by return 0, since that is what will happen anyways? + // Handle NULL_MAT, which has a vf of -1.0, but which needs to be 0.0 for the purposes of computing the clipping point + if (vfMatOneVertexOne < 0.0) { vfMatOneVertexOne = 0.0; }; + if (vfMatTwoVertexOne < 0.0) { vfMatTwoVertexOne = 0.0; }; + if (vfMatOneVertexTwo < 0.0) { vfMatOneVertexTwo = 0.0; }; + if (vfMatTwoVertexTwo < 0.0) { vfMatTwoVertexTwo = 0.0; }; + + axom::float64 numerator = vfMatTwoVertexOne - vfMatOneVertexOne; + axom::float64 denominator = -vfMatOneVertexOne + vfMatOneVertexTwo + vfMatTwoVertexOne - vfMatTwoVertexTwo; + + if (denominator != 0.0) + { + ret = numerator / denominator; + } + + if (ret > 1.0 || ret < 0.0) + { + // This shouldn't happen... + printf(" OUT OF BOUNDS T VALUE: %f\n", ret); + + // Clamp the t value + ret = fmin(1.0, ret); + ret = fmax(0.0, ret); + } + + return ret; +} + +//-------------------------------------------------------------------------------- + +const std::vector >& CellClipper::getClipTable(const mir::Shape shapeType) +{ + switch ( shapeType ) + { + case mir::Shape::Triangle: + return triangleClipTableVec; + case mir::Shape::Quad: + return quadClipTableVec; + default: + printf("No clipping table for this shape type.\n"); + return triangleClipTableVec; + } +} + +//-------------------------------------------------------------------------------- + +} +} \ No newline at end of file diff --git a/src/axom/mir/CellClipper.hpp b/src/axom/mir/CellClipper.hpp new file mode 100644 index 0000000000..fcdd650db3 --- /dev/null +++ b/src/axom/mir/CellClipper.hpp @@ -0,0 +1,120 @@ +// Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +/** + * \file CellClipper.hpp + * + * \brief Contains the specification for the CellClipper class. + * + */ + +#ifndef __CELL_CLIPPER_H +#define __CELL_CLIPPER_H + +#include "axom/core.hpp" // for axom macros +#include "axom/slam.hpp" // unified header for slam classes and functions + +#include "MIRMesh.hpp" +#include "MIRUtilities.hpp" +#include "MIRMeshTypes.hpp" +#include "CellData.hpp" +#include "ZooClippingTables.hpp" +#include "MIRUtilities.hpp" + +//-------------------------------------------------------------------------------- + +namespace axom +{ +namespace mir +{ + +//-------------------------------------------------------------------------------- + + /** + * \class CellClipper + * + * \brief A class that contains the functionality for taking an input cell + * and determining how it should be clipped. + * + */ + class CellClipper + { + public: + /** + * \brief Default constructor. + */ + CellClipper(); + + /** + * \brief Default destructor. + */ + ~CellClipper(); + + /** + * \brief Computes the elements, vertices, and t values resulting from splitting the element with the given vertex volume fractions. + * + * \param shapeType The shape type of the element. + * \param vertexVF The vertex volume fractions of the two materials with which to clip the cell. + * \param newElements An ordered map of the generated elements' IDs to a list of the vertex IDs in the local frame of the output element. + * \param newVertices An order map of the vertex IDs in the local frame of the output element to the generated elements' IDs it is associated with. + * \param tValues An array of t values where each of the midpoint vertices are for the newly generated elements. + * + */ + void computeClippingPoints(const mir::Shape shapeType, + const std::vector >& vertexVF, + std::map >& newElements, + std::map >& newVertices, + axom::float64* tValues); + + /** + * \brief Determines the index into the clipping table of the given shape based on the volume fractions at its vertices. + * + * \param shapeType The shape type of the element. + * \param matOneVF The volume fractions at the vertices of the element for the first material. + * \param matTwoVF The volume fractions at the vertices of the element for the second material. + * + * \return The index into the clipping table. + */ + unsigned int determineClippingCase(const mir::Shape shapeType, + const std::vector& matOneVF, + const std::vector& matTwoVF); + + + /** + * \brief Computes the t value as a percent from vertex one to vertex two based on the materials given. + * + * \param vfMatOneVertexOne The volume fraction of material one present at vertex one. + * \param vfMatTwoVertexOne The volume fraction of material two present at vertex one. + * \param vfMatOneVertexTwo The volume fraction of material one present at vertex two. + * \param vfMatTwoVertexTwo The volume fraction of material two present at vertex two. + * + * \return The percent of the distance from vertex one to vertex two where the edge should be clipped. + * + * \note When one material's volume fractions dominates the other material's, then the edge should not be clipped and this function will return 0.0. + * \note When one of the materials is the NULL_MAT (and has vf = -1.0), these values are set to 0 in order to interpolate properly. + */ + axom::float64 computeTValueOnEdge(axom::float64 vfMatOneVertexOne, + axom::float64 vfMatTwoVertexOne, + axom::float64 vfMatOneVertexTwo, + axom::float64 vfMatTwoVertexTwo); + + private: + /** + * \brief Returns a reference to the appropriate clipping table to use for the shape type. + * + * \param The shape type of the element. + * + * \return A reference to the clipping table. + */ + const std::vector >& getClipTable(const mir::Shape shapeType); + + }; + +//-------------------------------------------------------------------------------- + +} +} + +#endif \ No newline at end of file diff --git a/src/axom/mir/CellData.cpp b/src/axom/mir/CellData.cpp index 4e37813c86..176d1d6fc7 100644 --- a/src/axom/mir/CellData.cpp +++ b/src/axom/mir/CellData.cpp @@ -83,6 +83,12 @@ namespace mir m_mapData.m_elementParents.push_back(cellToMerge.m_mapData.m_elementParents[i]); } + // Merge the elements' shape types + for (unsigned long i = 0; i < cellToMerge.m_mapData.m_shapeTypes.size(); ++i) + { + m_mapData.m_shapeTypes.push_back(cellToMerge.m_mapData.m_shapeTypes[i]); + } + // Merge the total number of verts and elems in the resulting cell m_numVerts += cellToMerge.m_numVerts; m_numElems += cellToMerge.m_numElems; diff --git a/src/axom/mir/CellData.hpp b/src/axom/mir/CellData.hpp index 1a681e5d19..a73134374c 100644 --- a/src/axom/mir/CellData.hpp +++ b/src/axom/mir/CellData.hpp @@ -50,6 +50,7 @@ namespace mir std::vector > m_vertexVolumeFractions; // Data that goes into MIRMesh's materialVolumeFractionsVertex ScalarMap std::vector m_elementDominantMaterials; // Data that goes into MIRMesh's elementDominantColors IntMap std::vector m_elementParents; // Data that goes into MIRMesh's elementParentIDs IntMap + std::vector m_shapeTypes; // Data that goes into MIRMesh's shapeType IntMap }; /** diff --git a/src/axom/mir/CellGenerator.cpp b/src/axom/mir/CellGenerator.cpp new file mode 100644 index 0000000000..05b61aa030 --- /dev/null +++ b/src/axom/mir/CellGenerator.cpp @@ -0,0 +1,218 @@ +// Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "CellGenerator.hpp" + + +namespace axom +{ +namespace mir +{ + +//-------------------------------------------------------------------------------- + +CellGenerator::CellGenerator() +{ + +} + +//-------------------------------------------------------------------------------- + +CellGenerator::~CellGenerator() +{ + +} + +//-------------------------------------------------------------------------------- + +void CellGenerator::generateTopologyData(const std::map >& newElements, + const std::map >& newVertices, + CellData& out_cellData) +{ + // Store the evInds and evBegins data in the output vectors + int currentEVBeginIndex = 0; + for (auto itr = newElements.begin(); itr != newElements.end(); itr++) + { + // Push the start index of the next element + out_cellData.m_topology.m_evBegins.push_back(currentEVBeginIndex); + + // Push the next element's vertices + for (unsigned int vIndex = 0; vIndex < itr->second.size(); ++vIndex) + { + out_cellData.m_topology.m_evInds.push_back(itr->second[vIndex]); + ++currentEVBeginIndex; + } + } + + // Push the index that occurs after the last vertex + out_cellData.m_topology.m_evBegins.push_back(currentEVBeginIndex); + + // Store the veInds and veBegins data in the output vectors + int currentVEBeginIndex = 0; + for (auto itr = newVertices.begin(); itr != newVertices.end(); itr++) + { + // Push the start index of the vertex's elements + out_cellData.m_topology.m_veBegins.push_back(currentVEBeginIndex); + + // Push the next vertex's elements + for (unsigned int eIndex = 0; eIndex < itr->second.size(); eIndex++) + { + out_cellData.m_topology.m_veInds.push_back(itr->second[eIndex]); + ++currentVEBeginIndex; + } + } + + // Push the index that occurs after the last element + out_cellData.m_topology.m_veBegins.push_back(currentVEBeginIndex); +} + +//-------------------------------------------------------------------------------- + +void CellGenerator::generateVertexPositions(const mir::Shape shapeType, + const std::map>& newVertices, + const std::vector& vertexPositions, + axom::float64* tValues, + CellData& out_cellData) +{ + for (auto itr = newVertices.begin(); itr != newVertices.end(); itr++) + { + int vID = itr->first; + + if ( vID < mir::utilities::numVerts(shapeType) ) + { + // This vertex is one of the shape's original vertices + out_cellData.m_mapData.m_vertexPositions.push_back( vertexPositions[vID] ); + } + else + { + // This vertex is between two of the shape's original vertices + int vIDFrom = mir::utilities::getEdgeEndpoint(shapeType, vID, true); + int vIDTo = mir::utilities::getEdgeEndpoint(shapeType, vID, false); + + out_cellData.m_mapData.m_vertexPositions.push_back( mir::utilities::interpolateVertexPosition( vertexPositions[vIDFrom], vertexPositions[vIDTo], tValues[vID] ) ); + } + } +} + +//-------------------------------------------------------------------------------- + +void CellGenerator::generateVertexVolumeFractions(const mir::Shape shapeType, + const std::map>& newVertices, + const std::vector >& vertexVF, + axom::float64* tValues, + CellData& out_cellData) +{ + out_cellData.m_mapData.m_vertexVolumeFractions.resize(vertexVF.size()); // vertexVF size is the number of materials + + for (auto itr = newVertices.begin(); itr != newVertices.end(); itr++) + { + int vID = itr->first; + + for (int matID = 0; matID < vertexVF.size(); ++matID) + { + if ( vID < mir::utilities::numVerts(shapeType) ) + { + // This vertex is one of the shape's original vertices + out_cellData.m_mapData.m_vertexVolumeFractions[matID].push_back( vertexVF[matID][vID] ); + } + else + { + // This vertex is between two of the shape's original vertices + int vIDFrom = mir::utilities::getEdgeEndpoint(shapeType, vID, true); + int vIDTo = mir::utilities::getEdgeEndpoint(shapeType, vID, false); + + out_cellData.m_mapData.m_vertexVolumeFractions[matID].push_back( mir::utilities::lerpFloat( vertexVF[matID][vIDFrom], vertexVF[matID][vIDTo], tValues[vID] ) ); + } + } + } +} + +//-------------------------------------------------------------------------------- + +int CellGenerator::determineCleanCellMaterial(const Shape elementShape, + const std::vector& vertexIDs, + const int matOne, + const int matTwo, + const std::vector >& vertexVF) +{ + int dominantMaterial = matOne; + + axom::float64 matOneVF = -1.0; + axom::float64 matTwoVF = -1.0; + + for (unsigned long it = 0; it < vertexIDs.size(); ++it) + { + int vID = vertexIDs[it]; + + if ( vID >= 0 && vID < mir::utilities::numVerts(elementShape) ) + { + if (matOne != NULL_MAT) + { + matOneVF = vertexVF[matOne][vID]; + } + if (matTwo != NULL_MAT) + { + matTwoVF = vertexVF[matTwo][vID]; + } + + dominantMaterial = (matOneVF > matTwoVF) ? matOne : matTwo; + } + } + + return dominantMaterial; +} + +//-------------------------------------------------------------------------------- + +mir::Shape CellGenerator::determineElementShapeType(const Shape parentShapeType, + const int numVerts) +{ + mir::Shape newShapeType; + if (parentShapeType == mir::Shape::Triangle || parentShapeType == mir::Shape::Quad) + { + // Handle the two-dimensional case + switch (numVerts) + { + case 3: + newShapeType = mir::Shape::Triangle; + break; + case 4: + newShapeType = mir::Shape::Quad; + break; + default: + newShapeType = mir::Shape::Triangle; + printf("Invalid number of vertices in determineElementShapeType().\n"); + break; + } + } + else + { + // Handle the three-dimensional case + switch (numVerts) + { + case 4: + newShapeType = mir::Shape::Tetrahedron; + break; + case 5: + newShapeType = mir::Shape::Pyramid; + break; + case 6: + newShapeType = mir::Shape::Triangular_Prism; + break; + case 8: + newShapeType = mir::Shape::Hexahedron; + break; + default: + newShapeType = mir::Shape::Tetrahedron; + printf("Invalid number of vertices in determineElementShapeType().\n"); + break; + } + } + return newShapeType; +} + + +} +} \ No newline at end of file diff --git a/src/axom/mir/CellGenerator.hpp b/src/axom/mir/CellGenerator.hpp new file mode 100644 index 0000000000..ccf189537b --- /dev/null +++ b/src/axom/mir/CellGenerator.hpp @@ -0,0 +1,150 @@ +// Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +/** + * \file CellGenerator.hpp + * + * \brief Contains the specification for the CellGenerator class. + * + */ + +#ifndef __CELL_GENERATOR_H__ +#define __CELL_GENERATOR_H__ + +#include "axom/core.hpp" // for axom macros +#include "axom/slam.hpp" // unified header for slam classes and functions + +#include "MIRMesh.hpp" +#include "MIRUtilities.hpp" +#include "MIRMeshTypes.hpp" +#include "CellData.hpp" +#include "ZooClippingTables.hpp" +#include "MIRUtilities.hpp" +#include "CellClipper.hpp" + +//-------------------------------------------------------------------------------- + +namespace axom +{ +namespace mir +{ + +//-------------------------------------------------------------------------------- + + /** + * \class CellGenerator + * + * \brief A class that generates that uses the clipping information to generate + * the data needed in order to produce a clean, reconstructed mesh. + */ + class CellGenerator + { + public: + + /** + * \brief Default constructor. + */ + CellGenerator(); + + /** + * \brief Default destructor. + */ + ~CellGenerator(); + + /** + * \brief Generates the topology of the new elements resulting from a split. + * + * \param newElements An ordered map of the generated elements' IDs to a list of the vertex IDs in the local frame of the output element. + * \param newVertices An order map of the vertex IDs in the local frame of the output element to the generated elements' IDs it is associated with. + * \param out_cellData Container to store the topology data of the generated elements. + */ + void generateTopologyData(const std::map >& newElements, + const std::map >& newVertices, + CellData& out_cellData); + + + /** + * \brief Generates the vertex positions values for each of the new vertices of the generated element. + * + * \param shapeType The shape type of the element. + * \param newVertices An ordered map of vertices that compose the newly generated elements. + * \param vertexPositions A vector of positions for each of the original element's vertices. + * \param tValues An array of t values where each of the midpoint vertices are for the newly generated elements. + * \param out_cellData Container to store the vertex position data of the generated elements. + * + * \note New vertex positions are interpolated from the original vertex positions. + */ + void generateVertexPositions(const mir::Shape shapeType, + const std::map>& newVertices, + const std::vector& vertexPositions, + axom::float64* tValues, + CellData& out_cellData); + + + /** + * \brief Generates the vertex volume fractions for each of the new vertices of the generated element. + * + * \param shapeType The shape type of the element. + * \param newVertices An ordered map of vertices that compose the newly generated elements. + * \param vertexVF A vector of positions for each of the original element's vertices. + * \param tValues An array of t values where each of the midpoint vertices are for the newly generated elements. + * \param out_cellData Container to store the vertex position data of the generated elements. + * + * \note New vertex positions are interpolated from the original vertex volume fractions. + */ + void generateVertexVolumeFractions(const mir::Shape shapeType, + const std::map>& newVertices, + const std::vector >& vertexVF, + axom::float64* tValues, + CellData& out_cellData); + + /** + * \brief Determines the more dominant material of the two given for the given element. + * + * \param shapeType An enumerator denoting the element's shape. + * \param vertexIDs A list of vertex IDs into the vertexVF param. + * \param matOne The ID of the first material. + * \param matTwo The ID of the second material. + * \param vertexVF The list of volume fractions associated with the given vertices in the vertexIDs param. + * + * \return The ID of the dominant material of the element. + * + * \note The dominant element for the 2D/3D cases will be the same as the material present at one of the + * original vertices that existed prior to the split. So, if you can find this vertex and its dominant + * material, then you know the dominant material of this new element. + * + * \note It is assumed that the given cell is one that results from splitting its parent cell. + */ + int determineCleanCellMaterial(const Shape shapeType, + const std::vector& vertexIDs, + const int matOne, + const int matTwo, + const std::vector >& vertexVF); + + /** + * \brief Determine the shape type of an element. + * + * \param parentShapeType The shape of the element from which the new element is generated. + * \param numVerts The number of vertices of the new element. + * + * \note It is assumed that the given cell is one that results from splitting its parent cell. + */ + mir::Shape determineElementShapeType(const Shape parentShapeType, + const int numVerts); + + /** + * \brief Ensures that the element is dominated by a material that is actually present in the original parent cell. + * + * \param + */ + // void fixDominantMaterial(mir::MIRMesh& originalMesh, const std::map >& newElements, const int matOne, const int matTwo, CellData& out_cellData); + }; + +//-------------------------------------------------------------------------------- + +} +} + +#endif \ No newline at end of file diff --git a/src/axom/mir/InterfaceReconstructor.cpp b/src/axom/mir/InterfaceReconstructor.cpp index 9152dbb30d..e4fdab75d8 100644 --- a/src/axom/mir/InterfaceReconstructor.cpp +++ b/src/axom/mir/InterfaceReconstructor.cpp @@ -26,7 +26,8 @@ InterfaceReconstructor::~InterfaceReconstructor() //-------------------------------------------------------------------------------- -mir::MIRMesh InterfaceReconstructor::computeReconstructedInterface(mir::MIRMesh& inputMesh) +void InterfaceReconstructor::computeReconstructedInterface(mir::MIRMesh& inputMesh, + mir::MIRMesh& outputMesh) { // Store a reference to the original mesh m_originalMesh = inputMesh; @@ -50,7 +51,7 @@ mir::MIRMesh InterfaceReconstructor::computeReconstructedInterface(mir::MIRMesh& int currentDominantMat = intermediateMesh.m_elementDominantMaterials[eID]; int matOne = matID; - computeClippingPoints(eID, currentDominantMat, matOne, intermediateMesh, temp_cellData[eID]); + generateCleanCells(eID, currentDominantMat, matOne, intermediateMesh, temp_cellData[eID]); } // Merge each of the cells into the first CellData struct @@ -73,13 +74,16 @@ mir::MIRMesh InterfaceReconstructor::computeReconstructedInterface(mir::MIRMesh& } // TODO: Run post-processing on mesh to meld the mesh vertices that are within a certain epsilon - - return finalMesh; + outputMesh = finalMesh; + // return finalMesh; } //-------------------------------------------------------------------------------- -mir::MIRMesh InterfaceReconstructor::computeReconstructedInterfaceIterative(mir::MIRMesh& inputMesh, const int numIterations, const axom::float64 percent) +void InterfaceReconstructor::computeReconstructedInterfaceIterative(mir::MIRMesh& inputMesh, + const int numIterations, + const axom::float64 percent, + mir::MIRMesh& outputMesh) { int numElems = inputMesh.m_elems.size(); int numMaterials = inputMesh.m_numMaterials; @@ -88,12 +92,13 @@ mir::MIRMesh InterfaceReconstructor::computeReconstructedInterfaceIterative(mir: mir::MIRMesh meshToImprove(inputMesh); // Calculate the reconstruction on the unmodified, input mesh - mir::MIRMesh resultingMesh = computeReconstructedInterface(meshToImprove); + computeReconstructedInterface(meshToImprove, outputMesh); for (int it = 0; it < numIterations; ++it) { + printf("iteration %d\n", it); // Calculate the output element volume fractions of the resulting output mesh - std::vector > resultingElementVF = resultingMesh.computeOriginalElementVolumeFractions(); + std::vector > resultingElementVF = outputMesh.computeOriginalElementVolumeFractions(); // Initialize the vector to store the improved element volume fractions std::vector > improvedElementVF; @@ -113,417 +118,100 @@ mir::MIRMesh InterfaceReconstructor::computeReconstructedInterfaceIterative(mir: meshToImprove.constructMeshVolumeFractionsMaps(improvedElementVF); // Calculate the reconstruction on the modified, input mesh - resultingMesh = computeReconstructedInterface(meshToImprove); - } - - return resultingMesh; -} - -//-------------------------------------------------------------------------------- - -void InterfaceReconstructor::computeClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh& tempMesh, CellData& out_cellData) -{ - // Find the vertices associated with each element - auto elementVertices = tempMesh.m_bdry[eID]; - - // Triangle Case - if (elementVertices.size() == 3) - { - computeTriangleClippingPoints(eID, matOneID, matTwoID, tempMesh, out_cellData); - } - // Quad Case - if (elementVertices.size() == 4) - { - computeQuadClippingPoints(eID, matOneID, matTwoID, tempMesh, out_cellData); + computeReconstructedInterface(meshToImprove, outputMesh); } } //-------------------------------------------------------------------------------- -void InterfaceReconstructor::computeQuadClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh& tempMesh, CellData& out_cellData) +void InterfaceReconstructor::generateCleanCells(const int eID, + const int matOne, + const int matTwo, + mir::MIRMesh& tempMesh, + CellData& out_cellData) { - // Determine the clipping case + mir::Shape shapeType = (mir::Shape) tempMesh.m_shapeTypes[eID]; auto elementVertices = tempMesh.m_bdry[eID]; - int upperLeftVertex = elementVertices[0]; - int lowerLeftVertex = elementVertices[1]; - int lowerRightVertex = elementVertices[2]; - int upperRightVertex = elementVertices[3]; - - unsigned int caseIndex = determineQuadClippingCase(tempMesh, matOneID, matTwoID, upperLeftVertex, lowerLeftVertex, lowerRightVertex, upperRightVertex); - - // Generate new elements + // Set up data structures needed to compute how element should be clipped std::map > newElements; // hashmap of the new elements' vertices | Note: maps are ordered sets std::map > newVertices; // hashmap of the new vertices' elements | Note: maps are ordered sets - int verticesPresent[8] = {0,0,0,0,0,0,0,0}; // Array of flags denoting whether the vertex is present in the current case or not - axom::float64 verticesClippingTValue[8] = {0,0,0,0,0,0,0,0}; // Array of t values that denote the percent value of where the edge should be clipped - - // Create the new polygons based on the clipping case - int currentElementIndex = 0; // the next available element index - int i = 0; - int numVertices = quadClipTable[caseIndex][i]; + std::vector verticesPresent(mir::utilities::maxPossibleNumVerts(shapeType), 0); // Vector of flags denoting whether the vertex is present in the current case or not + axom::float64* tValues = new axom::float64[ mir::utilities::maxPossibleNumVerts(shapeType) ]{0}; // Array of t values that denote the percent value of where the edge should be clipped - // for each new element in the current clipping case - while (numVertices != -1) - { - // for each vertex of the new element - for (int j = 0; j < numVertices; ++j) - { - // Find the id of the next vertex of the new element - int vID = quadClipTable[caseIndex][i + (j+1)]; - // Associate the vertex and element together - newElements[currentElementIndex].push_back(vID); - newVertices[vID].push_back(currentElementIndex); - verticesPresent[vID] = 1; - - // Find t using linear interpolation for any vertex that is not one of the original 4 vertices - if(vID == 4) - { - verticesClippingTValue[vID] = computeClippingPointOnEdge(upperLeftVertex, lowerLeftVertex, matOneID, matTwoID, tempMesh); - } - else if(vID == 5) - { - verticesClippingTValue[vID] = computeClippingPointOnEdge(lowerLeftVertex, lowerRightVertex, matOneID, matTwoID, tempMesh); - } - else if(vID == 6) - { - verticesClippingTValue[vID] = computeClippingPointOnEdge(lowerRightVertex, upperRightVertex, matOneID, matTwoID, tempMesh); - } - else if(vID == 7) - { - verticesClippingTValue[vID] = computeClippingPointOnEdge(upperRightVertex, upperLeftVertex, matOneID, matTwoID, tempMesh); - } - } - - // Increment the element index counter, marking the current element as being finished processed - currentElementIndex++; - - // Increase index into lookup table to the next element - i += (numVertices + 1); - numVertices = quadClipTable[caseIndex][i]; - } - - // Calculate the total number of elements and vertices that were generated from splitting the current element - out_cellData.m_numElems = (int) newElements.size(); - out_cellData.m_numVerts = (int) newVertices.size(); - - generateTopologyData(newElements, newVertices, out_cellData); - generateVertexPositionsFromQuad(newVertices, tempMesh, verticesClippingTValue, upperLeftVertex, lowerLeftVertex, lowerRightVertex, upperRightVertex, out_cellData); - generateVertexVolumeFractionsFromQuad(newVertices, tempMesh, verticesClippingTValue, upperLeftVertex, lowerLeftVertex, lowerRightVertex, upperRightVertex, out_cellData); - - // Determine and store the dominant material of this element - for (auto itr = newElements.begin(); itr != newElements.end(); itr++) + // Set up the volume fractions for the current element for the two materials currently being considered + std::vector > vertexVF(2); + for (int vID = 0; vID < elementVertices.size(); ++vID) { - int dominantMaterial = determineDominantMaterial(Shape::Quad, itr->second, matOneID, matTwoID, out_cellData.m_mapData.m_vertexVolumeFractions); - out_cellData.m_mapData.m_elementDominantMaterials.push_back(dominantMaterial); - } - - // Determine and store the parent of this element - out_cellData.m_mapData.m_elementParents.resize(newElements.size()); - for (auto itr = newElements.begin(); itr != newElements.end(); itr++) - { - out_cellData.m_mapData.m_elementParents[itr->first] = tempMesh.m_elementParentIDs[eID]; - } - - // Check that each element is dominated by a material that is actually present in the original parent cell - for (auto itr = newElements.begin(); itr != newElements.end(); itr++) - { - int parentElementID = out_cellData.m_mapData.m_elementParents[itr->first]; - int currentDominantMaterial = out_cellData.m_mapData.m_elementDominantMaterials[itr->first]; - - if (m_originalMesh.m_materialVolumeFractionsElement[currentDominantMaterial][parentElementID] == 0) + int originalVID = elementVertices[vID]; + if (matOne == NULL_MAT) { - // This material is not present in the original element from which the current element comes from - if (currentDominantMaterial == matOneID) - out_cellData.m_mapData.m_elementDominantMaterials[itr->first] = matTwoID; - else - out_cellData.m_mapData.m_elementDominantMaterials[itr->first] = matOneID; + vertexVF[0].push_back(-1.0); } - } - - // Modify the evIndex values to account for the fact that perhaps not all 8 possible vertices are present - int evIndexSubtract[8]; - for (int i = 0; i < 8; ++i) - { - if (verticesPresent[i] == 1) - evIndexSubtract[i] = 0; else - evIndexSubtract[i] = 1; - } - for (int i = 1; i < 8; ++i) - evIndexSubtract[i] += evIndexSubtract[i - 1]; - - for (unsigned int i = 0; i < out_cellData.m_topology.m_evInds.size(); ++i) - { - out_cellData.m_topology.m_evInds[i] -= evIndexSubtract[ out_cellData.m_topology.m_evInds[i] ]; - } -} - -//-------------------------------------------------------------------------------- - -unsigned int InterfaceReconstructor::determineQuadClippingCase(mir::MIRMesh& tempMesh, const int matOneID, const int matTwoID, const int upperLeftVertex, const int lowerLeftVertex, const int lowerRightVertex, const int upperRightVertex) -{ - // Determine the dominant color at each vertex - int upperLeftColor = matOneID, lowerLeftColor = matOneID, lowerRightColor = matOneID, upperRightColor = matOneID; - - if (matOneID == NULL_MAT) - upperLeftColor = lowerLeftColor = lowerRightColor = upperRightColor = matTwoID; - if (matTwoID == NULL_MAT) - upperLeftColor = lowerLeftColor = lowerRightColor = upperRightColor = matOneID; - if (matOneID != NULL_MAT && matTwoID != NULL_MAT) { - upperLeftColor = tempMesh.m_materialVolumeFractionsVertex[matOneID][upperLeftVertex] > tempMesh.m_materialVolumeFractionsVertex[matTwoID][upperLeftVertex] ? matOneID : matTwoID; - lowerLeftColor = tempMesh.m_materialVolumeFractionsVertex[matOneID][lowerLeftVertex] > tempMesh.m_materialVolumeFractionsVertex[matTwoID][lowerLeftVertex] ? matOneID : matTwoID; - lowerRightColor = tempMesh.m_materialVolumeFractionsVertex[matOneID][lowerRightVertex] > tempMesh.m_materialVolumeFractionsVertex[matTwoID][lowerRightVertex] ? matOneID : matTwoID; - upperRightColor = tempMesh.m_materialVolumeFractionsVertex[matOneID][upperRightVertex] > tempMesh.m_materialVolumeFractionsVertex[matTwoID][upperRightVertex] ? matOneID : matTwoID; + vertexVF[0].push_back( tempMesh.m_materialVolumeFractionsVertex[matOne][originalVID] ); } - // Create the index into the quad clipping lookup table using the dominant colors at each vertex - unsigned int caseIndex = 0; - if (upperLeftColor == matOneID) caseIndex |= 8; - if (lowerLeftColor == matOneID) caseIndex |= 4; - if (lowerRightColor == matOneID) caseIndex |= 2; - if (upperRightColor == matOneID) caseIndex |= 1; - - return caseIndex; -} - -//-------------------------------------------------------------------------------- - -mir::Point2 InterfaceReconstructor::interpolateVertexPosition(const mir::Point2& vertexOnePos, const mir::Point2& vertexTwoPos, const float t) -{ - mir::Point2 interpolatedPoint; - interpolatedPoint.m_x = (1 - t) * vertexOnePos.m_x + t * vertexTwoPos.m_x; - interpolatedPoint.m_y = (1 - t) * vertexOnePos.m_y + t * vertexTwoPos.m_y; - return interpolatedPoint; -} - -//-------------------------------------------------------------------------------- - -axom::float64 InterfaceReconstructor::lerpFloat(const axom::float64 f0, const axom::float64 f1, const axom::float64 t) -{ - return (1 - t) * f0 + t * f1; -} - -//-------------------------------------------------------------------------------- - -axom::float64 InterfaceReconstructor::computeClippingPointOnEdge(const int vertexOneID, const int vertexTwoID, const int matOneID, const int matTwoID, mir::MIRMesh& tempMesh) -{ - axom::float64 ret = 0.0; - - axom::float64 vfMatOneVertexOne = tempMesh.m_materialVolumeFractionsVertex[matOneID][vertexOneID]; - axom::float64 vfMatTwoVertexOne = tempMesh.m_materialVolumeFractionsVertex[matTwoID][vertexOneID]; - axom::float64 vfMatOneVertexTwo = tempMesh.m_materialVolumeFractionsVertex[matOneID][vertexTwoID]; - axom::float64 vfMatTwoVertexTwo = tempMesh.m_materialVolumeFractionsVertex[matTwoID][vertexTwoID]; - - if (matOneID == NULL_MAT) - vfMatOneVertexOne = vfMatOneVertexTwo = 0.0; - - if (matTwoID == NULL_MAT) - vfMatTwoVertexOne = vfMatTwoVertexTwo = 0.0; - - axom::float64 numerator = vfMatTwoVertexOne - vfMatOneVertexOne; - axom::float64 denominator = -vfMatOneVertexOne + vfMatOneVertexTwo + vfMatTwoVertexOne - vfMatTwoVertexTwo; - - if (denominator != 0.0) - ret = numerator / denominator; - - if (ret > 1.0 || ret < 0.0) - { - // This shouldn't happen... - printf(" OUT OF BOUNDS T VALUE: %f\n", ret); - - // Clamp the t value - ret = fmin(1.0, ret); - ret = fmax(0.0, ret); - } - - return ret; -} - -//-------------------------------------------------------------------------------- - -void InterfaceReconstructor::generateTopologyData(const std::map >& newElements, const std::map >& newVertices, CellData& out_cellData) -{ - // Store the evInds and evBegins data in the output vectors - int currentEVBeginIndex = 0; - for (auto itr = newElements.begin(); itr != newElements.end(); itr++) + if (matTwo == NULL_MAT) { - // Push the start index of the next element - out_cellData.m_topology.m_evBegins.push_back(currentEVBeginIndex); - - // Push the next element's vertices - for (unsigned int vIndex = 0; vIndex < itr->second.size(); ++vIndex) - { - out_cellData.m_topology.m_evInds.push_back(itr->second[vIndex]); - ++currentEVBeginIndex; - } + vertexVF[1].push_back(-1.0); } - - // Push the index that occurs after the last vertex - out_cellData.m_topology.m_evBegins.push_back(currentEVBeginIndex); - - // Store the veInds and veBegins data in the output vectors - int currentVEBeginIndex = 0; - for (auto itr = newVertices.begin(); itr != newVertices.end(); itr++) + else { - // Push the start index of the vertex's elements - out_cellData.m_topology.m_veBegins.push_back(currentVEBeginIndex); - - // Push the next vertex's elements - for (unsigned int eIndex = 0; eIndex < itr->second.size(); eIndex++) - { - out_cellData.m_topology.m_veInds.push_back(itr->second[eIndex]); - ++currentVEBeginIndex; - } + vertexVF[1].push_back( tempMesh.m_materialVolumeFractionsVertex[matTwo][originalVID] ); } - - // Push the index that occurs after the last element - out_cellData.m_topology.m_veBegins.push_back(currentVEBeginIndex); -} - -//-------------------------------------------------------------------------------- + } -void InterfaceReconstructor::generateVertexPositionsFromQuad(const std::map >& newVertices, mir::MIRMesh& tempMesh, axom::float64* verticesClippingTValue, const int upperLeftVertex, const int lowerLeftVertex, const int lowerRightVertex, const int upperRightVertex, CellData& out_cellData) -{ - std::map newPoints; // hashmap of the new vertices' positions | Note: maps are ordered sets + // Clip the element + CellClipper clipper; + clipper.computeClippingPoints(shapeType, vertexVF, newElements, newVertices, tValues); - // Find the position coordinates of the vertices and then store the positions of the vertices in the return vector + // Determine which vertices are present for (auto itr = newVertices.begin(); itr != newVertices.end(); itr++) { int vID = itr->first; - if (vID == 0) - newPoints[vID] = tempMesh.m_vertexPositions[upperLeftVertex]; - if (vID == 1) - newPoints[vID] = tempMesh.m_vertexPositions[lowerLeftVertex]; - if (vID == 2) - newPoints[vID] = tempMesh.m_vertexPositions[lowerRightVertex]; - if (vID == 3) - newPoints[vID] = tempMesh.m_vertexPositions[upperRightVertex]; - if (vID == 4) - newPoints[vID] = interpolateVertexPosition(tempMesh.m_vertexPositions[upperLeftVertex], tempMesh.m_vertexPositions[lowerLeftVertex], verticesClippingTValue[vID]); - if (vID == 5) - newPoints[vID] = interpolateVertexPosition(tempMesh.m_vertexPositions[lowerLeftVertex], tempMesh.m_vertexPositions[lowerRightVertex], verticesClippingTValue[vID]); - if (vID == 6) - newPoints[vID] = interpolateVertexPosition(tempMesh.m_vertexPositions[lowerRightVertex], tempMesh.m_vertexPositions[upperRightVertex], verticesClippingTValue[vID]); - if (vID == 7) - newPoints[vID] = interpolateVertexPosition(tempMesh.m_vertexPositions[upperRightVertex], tempMesh.m_vertexPositions[upperLeftVertex], verticesClippingTValue[vID]); - } - - // Store the positions of the vertices in the return vector - for (auto itr = newPoints.begin(); itr != newPoints.end(); itr++) - { - out_cellData.m_mapData.m_vertexPositions.push_back(itr->second); + verticesPresent[vID] = 1; } -} - -//-------------------------------------------------------------------------------- - -void InterfaceReconstructor::generateVertexVolumeFractionsFromQuad(std::map >& newVertices, mir::MIRMesh& tempMesh, axom::float64* verticesClippingTValue, const int upperLeftVertex, const int lowerLeftVertex, const int lowerRightVertex, const int upperRightVertex, CellData& out_cellData) -{ - // Calculate the vertex fractions at each vertex (use t value!) - // Make sure the output volume fractions containers are the proper size - out_cellData.m_mapData.m_vertexVolumeFractions.resize(tempMesh.m_numMaterials); - - for (auto itr = newVertices.begin(); itr != newVertices.end(); itr++) - { - int vID = itr->first; - for (int matID = 0; matID < tempMesh.m_numMaterials; ++matID) - { - if (vID == 0) - out_cellData.m_mapData.m_vertexVolumeFractions[matID].push_back(tempMesh.m_materialVolumeFractionsVertex[matID][upperLeftVertex]); - if (vID == 1) - out_cellData.m_mapData.m_vertexVolumeFractions[matID].push_back(tempMesh.m_materialVolumeFractionsVertex[matID][lowerLeftVertex]); - if (vID == 2) - out_cellData.m_mapData.m_vertexVolumeFractions[matID].push_back(tempMesh.m_materialVolumeFractionsVertex[matID][lowerRightVertex]); - if (vID == 3) - out_cellData.m_mapData.m_vertexVolumeFractions[matID].push_back(tempMesh.m_materialVolumeFractionsVertex[matID][upperRightVertex]); - if (vID == 4) - out_cellData.m_mapData.m_vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh.m_materialVolumeFractionsVertex[matID][upperLeftVertex], tempMesh.m_materialVolumeFractionsVertex[matID][lowerLeftVertex], verticesClippingTValue[vID])); - if (vID == 5) - out_cellData.m_mapData.m_vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh.m_materialVolumeFractionsVertex[matID][lowerLeftVertex], tempMesh.m_materialVolumeFractionsVertex[matID][lowerRightVertex], verticesClippingTValue[vID])); - if (vID == 6) - out_cellData.m_mapData.m_vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh.m_materialVolumeFractionsVertex[matID][lowerRightVertex], tempMesh.m_materialVolumeFractionsVertex[matID][upperRightVertex], verticesClippingTValue[vID])); - if (vID == 7) - out_cellData.m_mapData.m_vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh.m_materialVolumeFractionsVertex[matID][upperRightVertex], tempMesh.m_materialVolumeFractionsVertex[matID][upperLeftVertex], verticesClippingTValue[vID])); - } - } -} - -//-------------------------------------------------------------------------------- - -void InterfaceReconstructor::computeTriangleClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh& tempMesh, CellData& out_cellData) -{ - // Determine the clipping case - auto elementVertices = tempMesh.m_bdry[eID]; - int upperVertex = elementVertices[0]; - int lowerLeftVertex = elementVertices[1]; - int lowerRightVertex = elementVertices[2]; + // Calculate the total number of elements and vertices that were generated from splitting the current element + out_cellData.m_numElems = (int) newElements.size(); + out_cellData.m_numVerts = (int) newVertices.size(); - unsigned int caseIndex = determineTriangleClippingCase(tempMesh, matOneID, matTwoID, upperVertex, lowerLeftVertex, lowerRightVertex); + // Generate the topology and connectivity of the newly split element + CellGenerator cellGenerator; + cellGenerator.generateTopologyData(newElements, newVertices, out_cellData); - // Generate new elements - std::map > newElements; // hashmap of the new elements' vertices | Note: maps are ordered sets - std::map > newVertices; // hashmap of the new vertices' elements | Note: maps are ordered sets - int verticesPresent[6] = {0,0,0,0,0,0}; // Array of flags denoting whether the vertex is present in the current case or not - axom::float64 verticesClippingTValue[6] = {0,0,0,0,0,0}; // Array of t values that denote the percent value of where the edge should be clipped - - // Create the new polygons based on the clipping case - int currentElementIndex = 0; // the next available element index - int i = 0; - int numVertices = triangleClipTable[caseIndex][i]; + // Generate the vertex position values of the newly split element + std::vector originalElementVertexPositions; + for (int vID = 0; vID < elementVertices.size(); ++vID) + { + int originalVID = elementVertices[vID]; + originalElementVertexPositions.push_back( tempMesh.m_vertexPositions[originalVID] ); + } + cellGenerator.generateVertexPositions( shapeType, newVertices, originalElementVertexPositions, tValues, out_cellData); - // for each new element in the current clipping case - while (numVertices != -1) + // Generate the vertex volume fractions of the newly split elements + std::vector > originalElementVertexVF; + for (int matID = 0; matID < tempMesh.m_numMaterials; ++matID) { - // for each vertex of the new element - for (int j = 0; j < numVertices; ++j) + std::vector materialVertexVF; + for (int vID = 0; vID < elementVertices.size(); ++vID) { - // Find the id of the next vertex of the new element - int vID = triangleClipTable[caseIndex][i + (j+1)]; - - // Associate the vertex and element together - newElements[currentElementIndex].push_back(vID); - newVertices[vID].push_back(currentElementIndex); - verticesPresent[vID] = 1; + int originalVID = elementVertices[vID]; - // Find t using linear interpolation for any vertex that is not one of the original 3 vertices - if(vID == 3) - { - verticesClippingTValue[vID] = computeClippingPointOnEdge(upperVertex, lowerLeftVertex, matOneID, matTwoID, tempMesh); - } - else if(vID == 4) - { - verticesClippingTValue[vID] = computeClippingPointOnEdge(lowerLeftVertex, lowerRightVertex, matOneID, matTwoID, tempMesh); - } - else if(vID == 5) - { - verticesClippingTValue[vID] = computeClippingPointOnEdge(lowerRightVertex, upperVertex, matOneID, matTwoID, tempMesh); - } + materialVertexVF.push_back( tempMesh.m_materialVolumeFractionsVertex[matID][originalVID] ); } - - // Increment the element index counter, marking the current element as being finished processed - currentElementIndex++; - - // Increase index into lookup table to the next element - i += (numVertices + 1); - numVertices = triangleClipTable[caseIndex][i]; + originalElementVertexVF.push_back( materialVertexVF ); } - // Calculate the total number of elements and vertices that were generated from splitting the current element - out_cellData.m_numElems = (int) newElements.size(); - out_cellData.m_numVerts = (int) newVertices.size(); - - generateTopologyData(newElements, newVertices, out_cellData); - generateVertexPositionsFromTriangle(newVertices, tempMesh, verticesClippingTValue, upperVertex, lowerLeftVertex, lowerRightVertex, out_cellData); - generateVertexVolumeFractionsFromTriangle(newVertices, tempMesh, verticesClippingTValue, upperVertex, lowerLeftVertex, lowerRightVertex, out_cellData); + cellGenerator.generateVertexVolumeFractions( shapeType, newVertices, originalElementVertexVF, tValues, out_cellData); // Determine and store the dominant material of this element for (auto itr = newElements.begin(); itr != newElements.end(); itr++) { - int dominantMaterial = determineDominantMaterial(Shape::Triangle, itr->second, matOneID, matTwoID, out_cellData.m_mapData.m_vertexVolumeFractions); + int dominantMaterial = cellGenerator.determineCleanCellMaterial(shapeType, itr->second, matOne, matTwo, out_cellData.m_mapData.m_vertexVolumeFractions); out_cellData.m_mapData.m_elementDominantMaterials.push_back(dominantMaterial); } @@ -534,6 +222,14 @@ void InterfaceReconstructor::computeTriangleClippingPoints(const int eID, const out_cellData.m_mapData.m_elementParents[itr->first] = tempMesh.m_elementParentIDs[eID]; } + // Determine the generated elements' shape types + out_cellData.m_mapData.m_shapeTypes.resize(newElements.size()); + for (auto itr = newElements.begin(); itr != newElements.end(); itr++) + { + out_cellData.m_mapData.m_shapeTypes[itr->first] = cellGenerator.determineElementShapeType(shapeType, itr->second.size()); + } + + // TODO: Make a function that does this // Check that each element is dominated by a material that is actually present in the original parent cell for (auto itr = newElements.begin(); itr != newElements.end(); itr++) { @@ -543,170 +239,33 @@ void InterfaceReconstructor::computeTriangleClippingPoints(const int eID, const if (m_originalMesh.m_materialVolumeFractionsElement[currentDominantMaterial][parentElementID] == 0) { // This material is not present in the original element from which the current element comes from - if (currentDominantMaterial == matOneID) - out_cellData.m_mapData.m_elementDominantMaterials[itr->first] = matTwoID; + if (currentDominantMaterial == matOne) + out_cellData.m_mapData.m_elementDominantMaterials[itr->first] = matTwo; else - out_cellData.m_mapData.m_elementDominantMaterials[itr->first] = matOneID; + out_cellData.m_mapData.m_elementDominantMaterials[itr->first] = matOne; } - } - - // Modify the evIndex values to account for the fact that perhaps not all 6 possible vertices are present - int evIndexSubtract[6]; - for (int i = 0; i < 6; ++i) + } + + // Modify the evIndex values to account for the fact that perhaps not all 8 possible vertices are present + std::vector evIndexSubtract; + evIndexSubtract.resize(mir::utilities::maxPossibleNumVerts(shapeType), 0); + for (unsigned long i = 0; i < evIndexSubtract.size(); ++i) { if (verticesPresent[i] == 1) evIndexSubtract[i] = 0; else evIndexSubtract[i] = 1; } - - for (int i = 1; i < 6; ++i) + for (unsigned long i = 1; i < evIndexSubtract.size(); ++i) evIndexSubtract[i] += evIndexSubtract[i - 1]; - for (unsigned int i = 0; i < out_cellData.m_topology.m_evInds.size(); ++i) - out_cellData.m_topology.m_evInds[i] -= evIndexSubtract[ out_cellData.m_topology.m_evInds[i] ]; - -} - -//-------------------------------------------------------------------------------- - -unsigned int InterfaceReconstructor::determineTriangleClippingCase(mir::MIRMesh& tempMesh, const int matOneID, const int matTwoID, const int upperVertex, const int lowerLeftVertex, const int lowerRightVertex) -{ - // Determine the dominant color at each vertex - int upperColor = matOneID, lowerLeftColor = matOneID, lowerRightColor = matOneID; - - if (matOneID == NULL_MAT) - upperColor = lowerLeftColor = lowerRightColor = matTwoID; - if (matTwoID == NULL_MAT) - upperColor = lowerLeftColor = lowerRightColor = matOneID; - if (matOneID != NULL_MAT && matTwoID != NULL_MAT) - { - upperColor = tempMesh.m_materialVolumeFractionsVertex[matOneID][upperVertex] > tempMesh.m_materialVolumeFractionsVertex[matTwoID][upperVertex] ? matOneID : matTwoID; - lowerLeftColor = tempMesh.m_materialVolumeFractionsVertex[matOneID][lowerLeftVertex] > tempMesh.m_materialVolumeFractionsVertex[matTwoID][lowerLeftVertex] ? matOneID : matTwoID; - lowerRightColor = tempMesh.m_materialVolumeFractionsVertex[matOneID][lowerRightVertex] > tempMesh.m_materialVolumeFractionsVertex[matTwoID][lowerRightVertex] ? matOneID : matTwoID; - } - - // Create the index into the quad clipping lookup table using the dominant colors at each vertex - unsigned int caseIndex = 0; - if (upperColor == matOneID) caseIndex |= 4; - if (lowerLeftColor == matOneID) caseIndex |= 2; - if (lowerRightColor == matOneID) caseIndex |= 1; - - return caseIndex; -} - -//-------------------------------------------------------------------------------- - -void InterfaceReconstructor::generateVertexPositionsFromTriangle(const std::map >& newVertices, mir::MIRMesh& tempMesh, axom::float64* verticesClippingTValue, const int upperVertex, const int lowerLeftVertex, const int lowerRightVertex, CellData& out_cellData) -{ - std::map newPoints; // hashmap of the new vertices' positions | Note: maps are ordered sets - - // Find the position coordinates of the vertices and then store the positions of the vertices in the return vector - for (auto itr = newVertices.begin(); itr != newVertices.end(); itr++) - { - int vID = itr->first; - if (vID == 0) - newPoints[vID] = tempMesh.m_vertexPositions[upperVertex]; - if (vID == 1) - newPoints[vID] = tempMesh.m_vertexPositions[lowerLeftVertex]; - if (vID == 2) - newPoints[vID] = tempMesh.m_vertexPositions[lowerRightVertex]; - if (vID == 3) - newPoints[vID] = interpolateVertexPosition(tempMesh.m_vertexPositions[upperVertex], tempMesh.m_vertexPositions[lowerLeftVertex], verticesClippingTValue[vID]); - if (vID == 4) - newPoints[vID] = interpolateVertexPosition(tempMesh.m_vertexPositions[lowerLeftVertex], tempMesh.m_vertexPositions[lowerRightVertex], verticesClippingTValue[vID]); - if (vID == 5) - newPoints[vID] = interpolateVertexPosition(tempMesh.m_vertexPositions[lowerRightVertex], tempMesh.m_vertexPositions[upperVertex], verticesClippingTValue[vID]); - } - - // Store the positions of the vertices in the return vector - for (auto itr = newPoints.begin(); itr != newPoints.end(); itr++) - { - out_cellData.m_mapData.m_vertexPositions.push_back(itr->second); - } -} - -//-------------------------------------------------------------------------------- - -void InterfaceReconstructor::generateVertexVolumeFractionsFromTriangle(const std::map >& newVertices, mir::MIRMesh& tempMesh, axom::float64* verticesClippingTValue, const int upperVertex, const int lowerLeftVertex, const int lowerRightVertex, CellData& out_cellData) -{ - // Calculate the vertex fractions at each vertex (use t value!) - // Make sure the output volume fractions containers are the proper size - out_cellData.m_mapData.m_vertexVolumeFractions.resize(tempMesh.m_numMaterials); - - for (auto itr = newVertices.begin(); itr != newVertices.end(); itr++) - { - int vID = itr->first; - for (int matID = 0; matID < tempMesh.m_numMaterials; ++matID) - { - if (vID == 0) - out_cellData.m_mapData.m_vertexVolumeFractions[matID].push_back(tempMesh.m_materialVolumeFractionsVertex[matID][upperVertex]); - if (vID == 1) - out_cellData.m_mapData.m_vertexVolumeFractions[matID].push_back(tempMesh.m_materialVolumeFractionsVertex[matID][lowerLeftVertex]); - if (vID == 2) - out_cellData.m_mapData.m_vertexVolumeFractions[matID].push_back(tempMesh.m_materialVolumeFractionsVertex[matID][lowerRightVertex]); - if (vID == 3) - out_cellData.m_mapData.m_vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh.m_materialVolumeFractionsVertex[matID][upperVertex], tempMesh.m_materialVolumeFractionsVertex[matID][lowerLeftVertex], verticesClippingTValue[vID])); - if (vID == 4) - out_cellData.m_mapData.m_vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh.m_materialVolumeFractionsVertex[matID][lowerLeftVertex], tempMesh.m_materialVolumeFractionsVertex[matID][lowerRightVertex], verticesClippingTValue[vID])); - if (vID == 5) - out_cellData.m_mapData.m_vertexVolumeFractions[matID].push_back(lerpFloat(tempMesh.m_materialVolumeFractionsVertex[matID][lowerRightVertex], tempMesh.m_materialVolumeFractionsVertex[matID][upperVertex], verticesClippingTValue[vID])); - } - } -} - -//-------------------------------------------------------------------------------- - -int InterfaceReconstructor::determineDominantMaterial(const Shape elementShape, const std::vector& vertexIDs, const int matOne, const int matTwo, const std::vector >& vertexVF) -{ - int dominantMaterial = matOne; - - axom::float64 matOneVF = -1.0; - axom::float64 matTwoVF = -1.0; - - if (elementShape == Shape::Triangle) + for (unsigned long i = 0; i < out_cellData.m_topology.m_evInds.size(); ++i) { - for (unsigned long it = 0; it < vertexIDs.size(); ++it) - { - int vID = vertexIDs[it]; - if (vID == 0 || vID == 1 || vID == 2) - { - if (matOne != NULL_MAT) - { - matOneVF = vertexVF[matOne][vID]; - } - if (matTwo != NULL_MAT) - { - matTwoVF = vertexVF[matTwo][vID]; - } - - dominantMaterial = (matOneVF > matTwoVF) ? matOne : matTwo; - } - } - } - - if (elementShape == Shape::Quad) - { - for (unsigned long it = 0; it < vertexIDs.size(); ++it) - { - int vID = vertexIDs[it]; - if (vID == 0 || vID == 1 || vID == 2 || vID == 3) - { - if (matOne != NULL_MAT) - { - matOneVF = vertexVF[matOne][vID]; - } - if (matTwo != NULL_MAT) - { - matTwoVF = vertexVF[matTwo][vID]; - } - - dominantMaterial = (matOneVF > matTwoVF) ? matOne : matTwo; - } - } + out_cellData.m_topology.m_evInds[i] -= evIndexSubtract[ out_cellData.m_topology.m_evInds[i] ]; } - return dominantMaterial; + // Memory management + delete[] tValues; } //-------------------------------------------------------------------------------- diff --git a/src/axom/mir/InterfaceReconstructor.hpp b/src/axom/mir/InterfaceReconstructor.hpp index 646e1f284e..ed674af4a4 100644 --- a/src/axom/mir/InterfaceReconstructor.hpp +++ b/src/axom/mir/InterfaceReconstructor.hpp @@ -19,6 +19,9 @@ #include "MIRMesh.hpp" #include "CellData.hpp" #include "ZooClippingTables.hpp" +#include "MIRUtilities.hpp" +#include "CellClipper.hpp" +#include "CellGenerator.hpp" #include @@ -62,10 +65,10 @@ namespace mir * \brief Performs material interface reconstruction using the zoo-based algorithm. * * \param inputMesh The mesh composed of mixed cells. - * - * \return The mesh composed of clean cells. + * \param outputMesh The mesh composed of clean cells. */ - mir::MIRMesh computeReconstructedInterface(mir::MIRMesh& inputMesh); + void computeReconstructedInterface(mir::MIRMesh& inputMesh, + mir::MIRMesh& outputMesh); /** @@ -74,198 +77,27 @@ namespace mir * \param inputMesh The mesh made up of mixed cells. * \param numIterations The number of iterations for which to run the algorithm. * \param percent The percent of the difference to use when modifying the original element volume fractions. - * - * \return The mesh made up of clean cells. - */ - mir::MIRMesh computeReconstructedInterfaceIterative(mir::MIRMesh& inputMesh, const int numIterations, const axom::float64 percent); - - // private: - - /** - * \brief A wrapper function that calls the appropriate splitting method based on the shape of the given element. - * - * \param eID The ID of the element to be split. - * \param matOneID The ID of the first material to use for splitting the element, - * \param matTwoID The ID of the second material to use for splitting the element. - * \param tempMesh A pointer to the intermediate mesh that is currently being processed. - * \param out_cellData Container to store the output of splitting the element. - */ - void computeClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh& tempMesh, CellData& out_cellData); - - - /** - * \brief Splits the specified quad element into clean cells of the two given material types - * based on the volume fractions of the two materials. - * - * \param eID The ID of the quad element. - * \param matOneID The ID of the first material to use for splitting the quad, - * \param matTwoID The ID of the second material to use for splitting the quad. - * \param tempMesh A pointer to the intermediate mesh that is currently being processed. - * \param out_cellData Container to store the output of splitting the quad. - */ - void computeQuadClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh& tempMesh, CellData& out_cellData); - - - /** - * \brief Splits the specified triangle element into clean cells of the two given material types - * based on the volume fractions of the two materials. - * - * \param eID The ID of the triangle element. - * \param matOneID The ID of the first material to use for splitting the triangle, - * \param matTwoID The ID of the second material to use for splitting the triangle. - * \param tempMesh A pointer to the intermediate mesh that is currently being processed. - * \param out_cellData Container to store the output of splitting the triangle. - */ - void computeTriangleClippingPoints(const int eID, const int matOneID, const int matTwoID, mir::MIRMesh& tempMesh, CellData& out_cellData); - - - /** - * \brief Performs linear interpolation between the two vertex positions. - * - * \param vertexOnePos The position of the first vertex. - * \param vertexTwoPos The position of the second vertex. - * \param t The percent of the distance from vertex one to vertex two to interpolate at. - * - * \return The interpolated position. - */ - mir::Point2 interpolateVertexPosition(const mir::Point2& vertexOnePos, const mir::Point2& vertexTwoPos, const float t); - - - /** - * \brief Performs linear interpolation between the two given float values. - * - * \param f0 The first float value. - * \param f1 The second float value. - * \param t The percent of the distance from the first float value to the second. - * - * \return The interpolated value. - */ - axom::float64 lerpFloat(const axom::float64 f0, const axom::float64 f1, const axom::float64 t); - - - /** - * \brief Computes the t value as a percent from vertex one to vertex two based on the materials given. - * - * \param vertexOneID The ID of the first vertex. - * \param vertexTwoID The ID of the second vertex. - * \param matOneID The ID of the first material to use for interpolating between. - * \param matTwoID The ID of the second material to use for interpolating between. - * \param tempMesh The intermediate mesh that is currently being processed. - * - * \return The t value, which is the percent distance from vertex one to vertex two where the two volume fractions are equal. - */ - axom::float64 computeClippingPointOnEdge(const int vertexOneID, const int vertexTwoID, const int matOneID, const int matTwoID, mir::MIRMesh& tempMesh); - - - /** - * \brief Generates the topology of the new elements resulting from a split. - * - * \param newElements An ordered map of the generated elements' IDs to a list of the vertex IDs in the local frame of the output element. - * \param newVertices An order map of the vertex IDs in the local frame of the output element to the generated elements' IDs it is associated with. - * \param out_cellData Container to store the topology data of the generated elements. - */ - void generateTopologyData(const std::map >& newElements, const std::map >& newVertices, CellData& out_cellData); - - - /** - * \brief Calculates the bit map representing the clipping case for a quad. - * - * \param tempMesh The intermediate mesh that is currently being processed. - * \param matOneID The ID of the first material being used for splitting. - * \param matTwoID The ID of the second material being used for splitting. - * \param upperLeftVertex The ID of the upper left vertex in the global frame of the original mesh. - * \param lowerLeftVertex The ID of the lower left vertex in the global frame of the original mesh. - * \param lowerRightVertex The ID of the lower right vertex in the global frame of the original mesh. - * \param upperRightVertex The ID of the upper right vertex in the global frame of the original mesh. - * - * \return The bitmap representing the clipping case. - */ - unsigned int determineQuadClippingCase(mir::MIRMesh& tempMesh, const int matOneID, const int matTwoID, const int upperLeftVertex, const int lowerLeftVertex, const int lowerRightVertex, const int upperRightVertex); - - /** - * \brief Generate the vertex position data for the new elements resulting from splitting a quad. - * - * \param newVertices An order map of the vertex IDs in the local frame of the output element to the generated elements' IDs it is associated with. - * \param tempMesh The intermediate mesh that is currently being processed. - * \param verticesClippingTValue The set of t values where the quad should be split on each edge. - * \param upperLeftVertex The ID of the upper left vertex in the global frame of the original mesh. - * \param lowerLeftVertex The ID of the lower left vertex in the global frame of the original mesh. - * \param lowerRightVertex The ID of the lower right vertex in the global frame of the original mesh. - * \param upperRightVertex The ID of the upper right vertex in the global frame of the original mesh. - * \param out_cellData Container to store the vertex position data of the generated elements. - */ - void generateVertexPositionsFromQuad(const std::map >& newVertices, mir::MIRMesh& tempMesh, axom::float64* verticesClippingTValue, const int upperLeftVertex, const int lowerLeftVertex, const int lowerRightVertex, const int upperRightVertex, CellData& out_cellData); - - /** - * \brief Generate the vertex volume fraction data for the new vertices resulting from splitting a quad. - * - * \param newVertices An order map of the vertex IDs in the local frame of the output element to the generated elements' IDs it is associated with. - * \param tempMesh The intermediate mesh that is currently being processed. - * \param verticesClippingTValue The set of t values where the quad should be split on each edge. - * \param upperLeftVertex The ID of the upper left vertex in the global frame of the original mesh. - * \param lowerLeftVertex The ID of the lower left vertex in the global frame of the original mesh. - * \param lowerRightVertex The ID of the lower right vertex in the global frame of the original mesh. - * \param upperRightVertex The ID of the upper right vertex in the global frame of the original mesh. - * \param out_cellData Container to store the vertex volume fraction data of the generated elements. + * \param outputMesh The mesh composed of clean cells. */ - void generateVertexVolumeFractionsFromQuad(std::map >& newVertices, mir::MIRMesh&tempMesh, axom::float64* verticesClippingTValue, const int upperLeftVertex, const int lowerLeftVertex, const int lowerRightVertex, const int upperRightVertex, CellData& out_cellData); + void computeReconstructedInterfaceIterative(mir::MIRMesh& inputMesh, + const int numIterations, + const axom::float64 percent, + mir::MIRMesh& outputMesh); /** - * \brief Calculates the bit map representing the clipping case for a triangle. - * - * \param tempMesh The intermediate mesh that is currently being processed. - * \param matOneID The ID of the first material being used for splitting. - * \param matTwoID The ID of the second material being used for splitting. - * \param upperVertex The ID of the upper vertex in the global frame of the original mesh. - * \param lowerLeftVertex The ID of the lower left vertex in the global frame of the original mesh. - * \param lowerRightVertex The ID of the lower right vertex in the global frame of the original mesh. - * - * \return The bitmap representing the clipping case. - */ - unsigned int determineTriangleClippingCase(mir::MIRMesh& tempMesh, const int matOneID, const int matTwoID, const int upperVertex, const int lowerLeftVertex, const int lowerRightVertex); - - /** - * \brief Generate the vertex position data for the new elements resulting from splitting a triangle. - * - * \param newVertices An order map of the vertex IDs in the local frame of the output element to the generated elements' IDs it is associated with. - * \param tempMesh The intermediate mesh that is currently being processed. - * \param verticesClippingTValue The set of t values where the quad should be split on each edge. - * \param upperVertex The ID of the upper vertex in the global frame of the original mesh. - * \param lowerLeftVertex The ID of the lower left vertex in the global frame of the original mesh. - * \param lowerRightVertex The ID of the lower right vertex in the global frame of the original mesh. - * \param out_cellData Container to store the vertex position data of the generated elements. - */ - void generateVertexPositionsFromTriangle(const std::map >& newVertices, mir::MIRMesh& tempMesh, axom::float64* verticesClippingTValue, const int upperVertex, const int lowerLeftVertex, const int lowerRightVertex, CellData& out_cellData); - - /** - * \brief Generate the vertex volume fraction data for the new vertices resulting from splitting a triangle. - * - * \param newVertices An order map of the vertex IDs in the local frame of the output element to the generated elements' IDs it is associated with. - * \param tempMesh The intermediate mesh that is currently being processed. - * \param verticesClippingTValue The set of t values where the quad should be split on each edge. - * \param upperVertex The ID of the upper vertex in the global frame of the original mesh. - * \param lowerLeftVertex The ID of the lower left vertex in the global frame of the original mesh. - * \param lowerRightVertex The ID of the lower right vertex in the global frame of the original mesh. - * \param out_cellData Container to store the vertex volume fraction data of the generated elements. - */ - void generateVertexVolumeFractionsFromTriangle(const std::map >& newVertices, mir::MIRMesh& tempMesh, axom::float64* verticesClippingTValue, const int upperVertex, const int lowerLeftVertex, const int lowerRightVertex, CellData& out_cellData); - - /** - * \brief Determines the mroe dominant material of the two given for the given element. - * - * \param elementShape An enumerator denoting the element's shape. - * \param vertexIDs A list of vertex IDs into the vertexVF param. - * \param matOne The ID of the first material. - * \param matTwo The ID of the second material. - * \param vertexVF The list of volume fractions associated with the given vertices in the vertexIDs param. - * - * \return The ID of the dominant material of the element. + * \brief Generates a set of clean cells by splitting the element with the two given materials. * - * \note The dominant element for the 2D cases will be the same as the material present at one of the - * original vertices that existed prior to the split. So, if you can find this vertex and its dominant - * material, then you know the dominant material of this new element. + * \param eID The ID of the element to be split. + * \param matOne The first material to split with. + * \param matTwo The second material to split with. + * \param tempMesh A reference to the mesh the element comes from. + * \param out_cellData Container to store the data of the generated elements. */ - int determineDominantMaterial(const Shape elementShape, const std::vector& vertexIDs, const int matOne, const int matTwo, const std::vector >& vertexVF); + void generateCleanCells(const int eID, + const int matOne, + const int matTwo, + mir::MIRMesh& tempMesh, + CellData& out_cellData); private: mir::MIRMesh m_originalMesh; diff --git a/src/axom/mir/MIRMesh.cpp b/src/axom/mir/MIRMesh.cpp index 6e7c687dcf..c47fecf3bf 100644 --- a/src/axom/mir/MIRMesh.cpp +++ b/src/axom/mir/MIRMesh.cpp @@ -33,6 +33,7 @@ MIRMesh::MIRMesh(MIRMesh* _mesh) m_materialVolumeFractionsElement = _mesh->m_materialVolumeFractionsElement; m_materialVolumeFractionsVertex = _mesh->m_materialVolumeFractionsVertex; m_elementParentIDs = _mesh->m_elementParentIDs; + m_shapeTypes = _mesh->m_shapeTypes; m_elementDominantMaterials = _mesh->m_elementDominantMaterials; m_numMaterials = _mesh->m_numMaterials; } @@ -46,7 +47,12 @@ MIRMesh::~MIRMesh() //-------------------------------------------------------------------------------- -void MIRMesh::initializeMesh(VertSet _verts, ElemSet _elems, int _numMaterials, CellTopologyData _topology, CellMapData _mapData, std::vector > _elementVF) +void MIRMesh::initializeMesh(const VertSet _verts, + const ElemSet _elems, + const int _numMaterials, + const CellTopologyData& _topology, + const CellMapData& _mapData, + const std::vector >& _elementVF) { // Initialize the vertex and element sets m_verts = _verts; @@ -64,9 +70,10 @@ void MIRMesh::initializeMesh(VertSet _verts, ElemSet _elems, int _numMaterials, constructMeshRelations(); // Initialize the mesh's data maps - constructVertexPositionMap(_mapData.m_vertexPositions.data()); - constructElementParentMap(_mapData.m_elementParents.data()); + constructVertexPositionMap(_mapData.m_vertexPositions); + constructElementParentMap(_mapData.m_elementParents); constructElementDominantMaterialMap(_mapData.m_elementDominantMaterials); + constructElementShapeTypesMap(_mapData.m_shapeTypes); // Initialize the element and vertex volume fraction maps if (_elementVF.size() > 0) @@ -121,7 +128,7 @@ void MIRMesh::constructMeshRelations() //-------------------------------------------------------------------------------- -void MIRMesh::constructMeshVolumeFractionsMaps(std::vector > elementVF) +void MIRMesh::constructMeshVolumeFractionsMaps(const std::vector >& elementVF) { // Clear the old maps m_materialVolumeFractionsElement.clear(); @@ -170,7 +177,7 @@ void MIRMesh::constructMeshVolumeFractionsMaps(std::vector > vertexVF) +void MIRMesh::constructMeshVolumeFractionsVertex(const std::vector >& vertexVF) { // Initialize the maps for all of the materials with the input volume fraction data for each vertex for (int matID = 0; matID < m_numMaterials; ++matID) @@ -190,7 +197,7 @@ void MIRMesh::constructMeshVolumeFractionsVertex(std::vector& data) { // construct the position map on the vertices m_vertexPositions = PointMap( &m_verts ); @@ -203,7 +210,7 @@ void MIRMesh::constructVertexPositionMap(Point2* data) //-------------------------------------------------------------------------------- -void MIRMesh::constructElementParentMap(int* elementParents) +void MIRMesh::constructElementParentMap(const std::vector& elementParents) { // Initialize the map for the elements' parent IDs m_elementParentIDs = IntMap( &m_elems ); @@ -217,7 +224,7 @@ void MIRMesh::constructElementParentMap(int* elementParents) //-------------------------------------------------------------------------------- -void MIRMesh::constructElementDominantMaterialMap(std::vector dominantMaterials) +void MIRMesh::constructElementDominantMaterialMap(const std::vector& dominantMaterials) { // Initialize the map for the elements' dominant colors m_elementDominantMaterials = IntMap( &m_elems ); @@ -231,6 +238,20 @@ void MIRMesh::constructElementDominantMaterialMap(std::vector dominantMater //-------------------------------------------------------------------------------- +void MIRMesh::constructElementShapeTypesMap(const std::vector& shapeTypes) +{ + // Initialize the map for the elements' dominant colors + m_shapeTypes = IntMap( &m_elems ); + + // Copy the dat for the elements + for (int eID = 0; eID < m_elems.size(); ++eID) + m_shapeTypes[eID] = shapeTypes[eID]; + + SLIC_ASSERT_MSG( m_shapeTypes.isValid(), "Element dominant materials map is not valid."); +} + +//-------------------------------------------------------------------------------- + void MIRMesh::print() { printf("\n------------------------Printing Mesh Information:------------------------\n"); @@ -287,6 +308,13 @@ void MIRMesh::print() } printf("}\n"); + printf("shapeTypes: { "); + for (int i = 0; i < m_elems.size(); ++i) + { + printf("%d ", m_shapeTypes[i]); + } + printf("}\n"); + printf("vertexVolumeFractions: { \n"); for (unsigned long i = 0; i < m_materialVolumeFractionsVertex.size(); ++i) { @@ -354,10 +382,9 @@ void MIRMesh::writeMeshToFile(std::string filename) meshfile << "\n\nCELL_TYPES " << m_elems.size() << "\n"; for (int i = 0; i < m_elems.size(); ++i) { - int nVerts = m_meshTopology.m_evBegins[i + 1] - m_meshTopology.m_evBegins[i]; - if (nVerts == 3) + if (m_shapeTypes[i] == mir::Shape::Triangle) meshfile << "5\n"; - else if (nVerts == 4) + else if (m_shapeTypes[i] == mir::Shape::Quad) meshfile << "9\n"; } @@ -423,7 +450,9 @@ std::vector > MIRMesh::computeOriginalElementVolumeFr //-------------------------------------------------------------------------------- -axom::float64 MIRMesh::computeTriangleArea(Point2 p0, Point2 p1, Point2 p2) +axom::float64 MIRMesh::computeTriangleArea(Point2 p0, + Point2 p1, + Point2 p2) { axom::float64 a = sqrt( ((p1.m_x - p0.m_x) * (p1.m_x - p0.m_x)) + ((p1.m_y - p0.m_y) * (p1.m_y - p0.m_y)) );// the distance from p0 to p1 axom::float64 b = sqrt( ((p2.m_x - p1.m_x) * (p2.m_x - p1.m_x)) + ((p2.m_y - p1.m_y) * (p2.m_y - p1.m_y)) );// the distance from p1 to p2 @@ -436,7 +465,10 @@ axom::float64 MIRMesh::computeTriangleArea(Point2 p0, Point2 p1, Point2 p2) //-------------------------------------------------------------------------------- -axom::float64 MIRMesh::computeQuadArea(Point2 p0, Point2 p1, Point2 p2, Point2 p3) +axom::float64 MIRMesh::computeQuadArea(Point2 p0, + Point2 p1, + Point2 p2, + Point2 p3) { return computeTriangleArea(p0, p1, p2) + computeTriangleArea(p2, p3, p0); } diff --git a/src/axom/mir/MIRMesh.hpp b/src/axom/mir/MIRMesh.hpp index 63b23d2ccc..984ada037e 100644 --- a/src/axom/mir/MIRMesh.hpp +++ b/src/axom/mir/MIRMesh.hpp @@ -76,7 +76,12 @@ namespace mir * \param _mapData The data used to initialized the maps associated with the vertex and element sets. * \param _elementVF The volume fractions of each element. Note that this is an optional parameter. */ - void initializeMesh(VertSet _verts, ElemSet _elems, int _numMaterials, CellTopologyData _topology, CellMapData _mapData, std::vector > _elementVF = {}); + void initializeMesh(const VertSet _verts, + const ElemSet _elems, + const int _numMaterials, + const CellTopologyData& _topology, + const CellMapData& _mapData, + const std::vector >& _elementVF = {}); /** * \brief Constructs the mesh boundary and coboundary relations. @@ -91,14 +96,14 @@ namespace mir * * \param elementVF The volume fractions of each element. */ - void constructMeshVolumeFractionsMaps(std::vector > elementVF); + void constructMeshVolumeFractionsMaps(const std::vector >& elementVF); /** * \brief Constructs the vertex volume fraction map. * * \param vertexVF The volume fractions of each vertex. */ - void constructMeshVolumeFractionsVertex(std::vector > vertexVF); + void constructMeshVolumeFractionsVertex(const std::vector >& vertexVF); /** * \brief Prints out the data contained within this mesh in a nice format. @@ -110,7 +115,7 @@ namespace mir * * \param filename The location where the mesh file will be read from. */ - void readMeshFromFile(std::string filename); + void readMeshFromFile(const std::string filename); /** * \brief Writes out the mesh to the given file. @@ -119,7 +124,7 @@ namespace mir * * \note Currently reads in an ASCII, UNSTRUCTURED_GRID .vtk file. */ - void writeMeshToFile(std::string filename); + void writeMeshToFile(const std::string filename); /** @@ -132,23 +137,30 @@ namespace mir /** * \brief Constucts the positions map on the vertices. * - * \param data The array of position data for each vertex. + * \param data The vector of position data for each vertex. */ - void constructVertexPositionMap(Point2* data); + void constructVertexPositionMap(const std::vector& data); /** * \brief Constructs the map of elements to their original element parent. * - * \param cellParents The array of parent IDs for each element of the mesh. + * \param cellParents The vector of parent IDs for each element of the mesh. */ - void constructElementParentMap(int* cellParents); + void constructElementParentMap(const std::vector& elementParents); /** * \brief Constructs the map of elements to their dominant materials. * * \param dominantMaterials A vector of material ids that are the dominant material of each element. */ - void constructElementDominantMaterialMap(std::vector dominantMaterials); + void constructElementDominantMaterialMap(const std::vector& dominantMaterials); + + /** + * \brief Constructs the map of elements to their shape types. + * + * \param shapeTypes A vector of shape enumerators that are the shape type of each element. + */ + void constructElementShapeTypesMap(const std::vector& shapeTypes); /** * \brief Computes the area of the triangle defined by the given three vertex positions using Heron's formula. @@ -157,7 +169,9 @@ namespace mir * \param p1 The position of the second vertex. * \param p2 The position of the third vertex. */ - axom::float64 computeTriangleArea(Point2 p0, Point2 p1, Point2 p2); + axom::float64 computeTriangleArea(Point2 p0, + Point2 p1, + Point2 p2); /** * \brief Computes the area of the quad defined by the given four vertex positions. @@ -169,7 +183,10 @@ namespace mir * * \note It is assumed that the points are given in consecutive, counter-clockwise order. */ - axom::float64 computeQuadArea(Point2 p0, Point2 p1, Point2 p2, Point2 p3); + axom::float64 computeQuadArea(Point2 p0, + Point2 p1, + Point2 p2, + Point2 p3); /**************************************************************** * VARIABLES @@ -189,6 +206,7 @@ namespace mir std::vector m_materialVolumeFractionsVertex; // the volume fractions of each material for each vertex IntMap m_elementParentIDs; // the ID of the parent element from the original mesh IntMap m_elementDominantMaterials; // the dominant material of the cell (a processed mesh should have only one material per cell) + IntMap m_shapeTypes; // the int enumerator of what type of shape each element is int m_numMaterials; // the number of materials present in the mesh CellTopologyData m_meshTopology; // the topology/connectivity of the mesh diff --git a/src/axom/mir/MIRMeshTypes.hpp b/src/axom/mir/MIRMeshTypes.hpp index b6fc18ec28..9daed29cbb 100644 --- a/src/axom/mir/MIRMeshTypes.hpp +++ b/src/axom/mir/MIRMeshTypes.hpp @@ -25,6 +25,17 @@ namespace axom { namespace mir { + + enum Shape + { + Triangle, + Quad, + Tetrahedron, + Triangular_Prism, + Pyramid, + Hexahedron + }; + /** * \brief Simple 2D Point class for example */ diff --git a/src/axom/mir/MIRUtilities.hpp b/src/axom/mir/MIRUtilities.hpp new file mode 100644 index 0000000000..db7c537a6e --- /dev/null +++ b/src/axom/mir/MIRUtilities.hpp @@ -0,0 +1,209 @@ +// Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +/** + * \file MIRUtilities.hpp + * + * \brief Contains a set of functions that provide general utility + * within Axom's MIR component. + * + */ + +#ifndef __MIR_UTILITIES_H__ +#define __MIR_UTILITIES_H__ + +#include "ZooClippingTables.hpp" + +//-------------------------------------------------------------------------------- + +namespace axom +{ +namespace mir +{ +namespace utilities +{ + +//-------------------------------------------------------------------------------- + + /** + * \brief Determines the number of vertices of the given shape in the finite element zoo. + * + * \param shape The shape type from the finite element zoo. + * + * \return THe number of vertices of the shape. + */ + inline int numVerts(mir::Shape shape) + { + int numVertices = -1; + switch (shape) + { + case mir::Shape::Triangle: + numVertices = 3; + break; + case mir::Shape::Quad: + numVertices = 4; + break; + case mir::Shape::Tetrahedron: + numVertices = 4; + break; + case mir::Shape::Pyramid: + numVertices = 5; + break; + case mir::Shape::Triangular_Prism: + numVertices = 6; + break; + case mir::Shape::Hexahedron: + numVertices = 8; + break; + default: + printf("Invalid shape. Cannot determine numVerts().\n"); + } + return numVertices; + } + +//-------------------------------------------------------------------------------- + + /** + * \brief Determines the maximum number of possible vertices of the given shape in the finite element zoo. + * This number includes the midpoint vertices between each of the original shape's vertices. + * + * \param shape The shape type from the finite element zoo. + * + * \return THe number of vertices of the shape. + */ + inline int maxPossibleNumVerts(mir::Shape shape) + { + int numVertices = -1; + switch (shape) + { + case mir::Shape::Triangle: + numVertices = 6; + break; + case mir::Shape::Quad: + numVertices = 8; + break; + case mir::Shape::Tetrahedron: + numVertices = 10; + break; + case mir::Shape::Pyramid: + numVertices = 13; + break; + case mir::Shape::Triangular_Prism: + numVertices = 15; + break; + case mir::Shape::Hexahedron: + numVertices = 20; + break; + default: + printf("Invalid shape. Cannot determine maxPossibleNumVerts().\n"); + } + return numVertices; + } + +//-------------------------------------------------------------------------------- + + /** + * \brief Performs linear interpolation between the two given float values. + * + * \param f0 The first float value. + * \param f1 The second float value. + * \param t The percent of the distance from the first float value to the second. + * + * \return The interpolated value. + */ + inline axom::float64 lerpFloat(const axom::float64 f0, + const axom::float64 f1, + const axom::float64 t) + { + return (1 - t) * f0 + t * f1; + } + +//-------------------------------------------------------------------------------- + + /** + * \brief Performs linear interpolation between the two vertex positions. + * + * \param vertexOnePos The position of the first vertex. + * \param vertexTwoPos The position of the second vertex. + * \param t The percent of the distance from vertex one to vertex two to interpolate at. + * + * \return The interpolated position. + */ + inline mir::Point2 interpolateVertexPosition(const mir::Point2& vertexOnePos, + const mir::Point2& vertexTwoPos, + const float t) + { + mir::Point2 interpolatedPoint; + interpolatedPoint.m_x = lerpFloat(vertexOnePos.m_x, vertexTwoPos.m_x, t); + interpolatedPoint.m_y = lerpFloat(vertexOnePos.m_y, vertexTwoPos.m_y, t); + return interpolatedPoint; + } + +//-------------------------------------------------------------------------------- + + /** + * \brief Returns the local vertex ID of the from/to vertex that is one of + * the two endpoints that the edge the given midpoint is on. + * + * \param shapeType The shape type from the finite element zoo. + * \param midpointVertexID The ID of the vertex between the two endpoints. + * \param isFromVertex A flag denoting which of the two edge endpoints to return. + * + * \return The vertex ID of one of the endpoints. + */ + inline int getEdgeEndpoint(const mir::Shape shapeType, + const int midpointVertexID, + const bool isFromVertex) + { + switch(shapeType) + { + case mir::Shape::Triangle: + if ( midpointVertexID == 3 && isFromVertex ) { return 0; } + if ( midpointVertexID == 3 && !isFromVertex ) { return 1; } + if ( midpointVertexID == 4 && isFromVertex ) { return 1; } + if ( midpointVertexID == 4 && !isFromVertex ) { return 2; } + if ( midpointVertexID == 5 && isFromVertex ) { return 2; } + if ( midpointVertexID == 5 && !isFromVertex ) { return 0; } + break; + case mir::Shape::Quad: + if ( midpointVertexID == 4 && isFromVertex ) { return 0; } + if ( midpointVertexID == 4 && !isFromVertex ) { return 1; } + if ( midpointVertexID == 5 && isFromVertex ) { return 1; } + if ( midpointVertexID == 5 && !isFromVertex ) { return 2; } + if ( midpointVertexID == 6 && isFromVertex ) { return 2; } + if ( midpointVertexID == 6 && !isFromVertex ) { return 3; } + if ( midpointVertexID == 7 && isFromVertex ) { return 3; } + if ( midpointVertexID == 7 && !isFromVertex ) { return 0; } + break; + default: + printf("Edge endpoint case not implemented.\n"); + return -1; + break; + } + return -1; + } + +//-------------------------------------------------------------------------------- + +/** + * \brief Calculate the distance between the two given points. + * + * \param p0 The first point. + * \param p1 The second point. + * + * \return The distance between the two points. + */ +inline axom::float64 distance(mir::Point2 p0, mir::Point2 p1) +{ + return sqrt( ((p1.m_x - p0.m_x) * (p1.m_x - p0.m_x)) + ((p1.m_y - p0.m_y) * (p1.m_y - p0.m_y)) ); +} + +//-------------------------------------------------------------------------------- + +} +} +} + +#endif \ No newline at end of file diff --git a/src/axom/mir/MeshTester.cpp b/src/axom/mir/MeshTester.cpp index 690c9ad35b..d4a7238aeb 100644 --- a/src/axom/mir/MeshTester.cpp +++ b/src/axom/mir/MeshTester.cpp @@ -116,6 +116,7 @@ MIRMesh MeshTester::initTestCaseOne() mapData.m_elementDominantMaterials = {NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT}; mapData.m_elementParents = { 0,1,2,3,4,5,6,7,8 }; // For the base mesh, the parents are always themselves mapData.m_vertexPositions = points; + mapData.m_shapeTypes = {mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad}; // Build the mesh mir::MIRMesh testMesh; @@ -217,6 +218,7 @@ mir::MIRMesh MeshTester::initTestCaseTwo() mapData.m_elementDominantMaterials = {NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT}; mapData.m_elementParents = { 0,1,2,3,4,5,6,7,8 }; // For the base mesh, the parents are always themselves mapData.m_vertexPositions = points; + mapData.m_shapeTypes = {mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad}; // Build the mesh mir::MIRMesh testMesh; @@ -289,6 +291,7 @@ mir::MIRMesh MeshTester::initTestCaseThree() mapData.m_elementDominantMaterials = {NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT}; mapData.m_elementParents = { 0,1,2,3 }; // For the base mesh, the parents are always themselves mapData.m_vertexPositions = points; + mapData.m_shapeTypes = {mir::Shape::Triangle, mir::Shape::Triangle, mir::Shape::Triangle, mir::Shape::Triangle}; // Build the mesh mir::MIRMesh testMesh; @@ -399,6 +402,7 @@ mir::MIRMesh MeshTester::initTestCaseFour() mapData.m_elementDominantMaterials = {NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT}; mapData.m_elementParents = { 0,1,2,3,4,5,6,7,8 }; // For the base mesh, the parents are always themselves mapData.m_vertexPositions = points; + mapData.m_shapeTypes = {mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad}; // Build the mesh mir::MIRMesh testMesh; @@ -442,10 +446,12 @@ mir::MIRMesh MeshTester::createUniformGridTestCaseMesh(int gridSize, mir::Point2 std::vector elementParents;// For the base mesh, the parents are always themselves std::vector elementDominantMaterials; + std::vector elementShapeTypes; for (int i = 0; i < cellData.m_numElems; ++i) { elementParents.push_back(i); elementDominantMaterials.push_back(NULL_MAT); + elementShapeTypes.push_back(mir::Shape::Quad); } CellTopologyData topology; @@ -471,10 +477,10 @@ mir::MIRMesh MeshTester::createUniformGridTestCaseMesh(int gridSize, mir::Point2 axom::float64 MeshTester::calculatePercentOverlapMonteCarlo(int gridSize, mir::Point2 circleCenter, axom::float64 circleRadius, mir::Point2 quadP0, mir::Point2 quadP1, mir::Point2 quadP2, mir::Point2 quadP3) { // Check if any of the quad's corners are within the circle - axom::float64 distP0 = distance(quadP0, circleCenter); - axom::float64 distP1 = distance(quadP1, circleCenter); - axom::float64 distP2 = distance(quadP2, circleCenter); - axom::float64 distP3 = distance(quadP3, circleCenter); + axom::float64 distP0 = mir::utilities::distance(quadP0, circleCenter); + axom::float64 distP1 = mir::utilities::distance(quadP1, circleCenter); + axom::float64 distP2 = mir::utilities::distance(quadP2, circleCenter); + axom::float64 distP3 = mir::utilities::distance(quadP3, circleCenter); if (distP0 < circleRadius && distP1 < circleRadius && distP2 < circleRadius && distP3 < circleRadius) { @@ -492,7 +498,7 @@ axom::float64 MeshTester::calculatePercentOverlapMonteCarlo(int gridSize, mir::P for (int x = 0; x < gridSize; ++x) { mir::Point2 samplePoint(delta_x * x + quadP1.m_x, delta_y * y + quadP1.m_y); - if (distance(samplePoint, circleCenter) < circleRadius) + if (mir::utilities::distance(samplePoint, circleCenter) < circleRadius) ++countOverlap; } } @@ -626,13 +632,6 @@ mir::CellData MeshTester::generateGrid(int gridSize) //-------------------------------------------------------------------------------- -axom::float64 MeshTester::distance(mir::Point2 p0, mir::Point2 p1) -{ - return sqrt( ((p1.m_x - p0.m_x) * (p1.m_x - p0.m_x)) + ((p1.m_y - p0.m_y) * (p1.m_y - p0.m_y)) ); -} - -//-------------------------------------------------------------------------------- - mir::MIRMesh MeshTester::initTestCaseFive(int gridSize, int numCircles) { @@ -700,7 +699,7 @@ mir::MIRMesh MeshTester::initTestCaseFive(int gridSize, int numCircles) bool isPointSampled = false; for (int cID = 0; cID < numCircles && !isPointSampled; ++cID) { - if (distance(samplePoint, circleCenter) < circleRadii[cID]) + if (mir::utilities::distance(samplePoint, circleCenter) < circleRadii[cID]) { materialCount[cID]++; isPointSampled = true; @@ -723,10 +722,12 @@ mir::MIRMesh MeshTester::initTestCaseFive(int gridSize, int numCircles) std::vector elementParents; // For the base mesh, the parents are always themselves std::vector elementDominantMaterials; + std::vector elementShapeTypes; for (int i = 0; i < cellData.m_numElems; ++i) { elementParents.push_back(i); elementDominantMaterials.push_back(NULL_MAT); + elementShapeTypes.push_back(mir::Shape::Quad); } CellTopologyData topology; @@ -739,6 +740,7 @@ mir::MIRMesh MeshTester::initTestCaseFive(int gridSize, int numCircles) mapData.m_elementDominantMaterials = elementDominantMaterials; mapData.m_elementParents = elementParents; mapData.m_vertexPositions = cellData.m_mapData.m_vertexPositions; + mapData.m_shapeTypes = elementShapeTypes; // Build the mesh mir::MIRMesh testMesh; @@ -752,10 +754,10 @@ mir::MIRMesh MeshTester::initTestCaseFive(int gridSize, int numCircles) int MeshTester::circleQuadCornersOverlaps(mir::Point2 circleCenter, axom::float64 circleRadius, mir::Point2 quadP0, mir::Point2 quadP1, mir::Point2 quadP2, mir::Point2 quadP3) { // Check if any of the quad's corners are within the circle - axom::float64 distP0 = distance(quadP0, circleCenter); - axom::float64 distP1 = distance(quadP1, circleCenter); - axom::float64 distP2 = distance(quadP2, circleCenter); - axom::float64 distP3 = distance(quadP3, circleCenter); + axom::float64 distP0 = mir::utilities::distance(quadP0, circleCenter); + axom::float64 distP1 = mir::utilities::distance(quadP1, circleCenter); + axom::float64 distP2 = mir::utilities::distance(quadP2, circleCenter); + axom::float64 distP3 = mir::utilities::distance(quadP3, circleCenter); int numCorners = 0; @@ -791,14 +793,17 @@ mir::MIRMesh MeshTester::initQuadClippingTestMesh() std::vector elementParents; std::vector elementDominantMaterials; + std::vector elementShapeTypes; for (int i = 0; i < cellData.m_numElems; ++i) { elementParents.push_back(i); elementDominantMaterials.push_back(NULL_MAT); + elementShapeTypes.push_back(mir::Shape::Quad); } cellData.m_mapData.m_elementDominantMaterials = elementDominantMaterials; cellData.m_mapData.m_elementParents = elementParents; + cellData.m_mapData.m_shapeTypes = elementShapeTypes; // Build the mesh mir::MIRMesh testMesh; diff --git a/src/axom/mir/MeshTester.hpp b/src/axom/mir/MeshTester.hpp index 9641fec6a1..37160ae894 100644 --- a/src/axom/mir/MeshTester.hpp +++ b/src/axom/mir/MeshTester.hpp @@ -17,6 +17,7 @@ #include "axom/slam.hpp" // unified header for slam classes and functions #include "MIRMesh.hpp" +#include "MIRUtilities.hpp" #include @@ -119,17 +120,6 @@ namespace mir mir::MIRMesh initQuadClippingTestMesh(); private: - - /** - * \brief Calculate the distance between the two given points. - * - * \param p0 The first point. - * \param p1 The second point. - * - * \return The distance between the two points. - */ - axom::float64 distance(mir::Point2 p0, mir::Point2 p1); - /** * \brief Generates a 2D uniform grid of n x n elements. * diff --git a/src/axom/mir/ZooClippingTables.cpp b/src/axom/mir/ZooClippingTables.cpp index 05e96ea2d0..9f87edbe5d 100644 --- a/src/axom/mir/ZooClippingTables.cpp +++ b/src/axom/mir/ZooClippingTables.cpp @@ -44,6 +44,27 @@ namespace mir {4,0,1,2,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1} }; + const std::vector > quadClipTableVec = + { + {4,0,1,2,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, + {3,3,7,6,4,7,0,2,6,3,0,1,2,-1,-1,-1,-1,-1,-1}, + {3,6,5,2,4,3,1,5,6,3,0,1,3,-1,-1,-1,-1,-1,-1}, + {4,0,1,5,7,4,7,5,2,3,-1,-1,-1,-1,-1,-1,-1,-1}, + {3,4,1,5,4,0,4,5,2,3,0,2,3,-1,-1,-1,-1,-1,-1}, + {3,4,1,5,4,0,4,5,2,4,0,2,6,7,3,7,6,3,-1}, + {4,0,4,6,3,4,4,1,2,6,-1,-1,-1,-1,-1,-1,-1,-1,-1}, + {3,0,4,7,4,4,1,3,7,3,1,2,3,-1,-1,-1,-1,-1,-1}, + {3,0,4,7,4,4,1,3,7,3,1,2,3,-1,-1,-1,-1,-1,-1}, + {4,0,4,6,3,4,4,1,2,6,-1,-1,-1,-1,-1,-1,-1,-1,-1}, + {3,4,1,5,4,0,4,5,2,4,0,2,6,7,3,7,6,3,-1}, + {3,4,1,5,4,0,4,5,2,3,0,2,3,-1,-1,-1,-1,-1,-1}, + {4,0,1,5,7,4,7,5,2,3,-1,-1,-1,-1,-1,-1,-1,-1}, + {3,6,5,2,4,3,1,5,6,3,0,1,3,-1,-1,-1,-1,-1,-1}, + {3,3,7,6,4,7,0,2,6,3,0,1,2,-1,-1,-1,-1,-1,-1}, + {4,0,1,2,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1} + }; + + // Triangle Vertex Local Indices // 0 @@ -66,5 +87,18 @@ namespace mir {4,0,1,4,5,3,5,4,2,-1}, {3,0,1,2,-1,-1,-1,-1,-1,-1} }; + + const std::vector > triangleClipTableVec = + { + {3,0,1,2,-1,-1,-1,-1,-1,-1}, + {4,0,1,4,5,3,5,4,2,-1}, + {4,0,3,4,2,3,3,1,4,-1}, + {4,3,1,2,5,3,0,3,5,-1}, + {4,3,1,2,5,3,0,3,5,-1}, + {4,0,3,4,2,3,3,1,4,-1}, + {4,0,1,4,5,3,5,4,2,-1}, + {3,0,1,2,-1,-1,-1,-1,-1,-1} + }; + } } \ No newline at end of file diff --git a/src/axom/mir/ZooClippingTables.hpp b/src/axom/mir/ZooClippingTables.hpp index 924b98792a..4802db44f7 100644 --- a/src/axom/mir/ZooClippingTables.hpp +++ b/src/axom/mir/ZooClippingTables.hpp @@ -13,23 +13,17 @@ * for the shape types in the zoo. */ +#include + namespace axom { namespace mir { - - enum Shape - { - Triangle, - Quad, - Tetrahedron, - Triangular_Prism, - Pyramid, - Hexahedron - }; - extern const int quadClipTable[16][19]; extern const int triangleClipTable[8][10]; + + extern const std::vector > triangleClipTableVec; + extern const std::vector > quadClipTableVec; } } #endif \ No newline at end of file diff --git a/src/axom/mir/examples/mir_concentric_circles.cpp b/src/axom/mir/examples/mir_concentric_circles.cpp index 48d361400f..6eb3493bf6 100644 --- a/src/axom/mir/examples/mir_concentric_circles.cpp +++ b/src/axom/mir/examples/mir_concentric_circles.cpp @@ -48,7 +48,8 @@ int main( int argc, char** argv ) // Begin material interface reconstruction startTime = Clock::now(); mir::InterfaceReconstructor reconstructor; - mir::MIRMesh processedMesh = reconstructor.computeReconstructedInterface(testMesh); + mir::MIRMesh processedMesh; + reconstructor.computeReconstructedInterface(testMesh, processedMesh); endTime = Clock::now(); std::cout << "Material interface reconstruction time: " << std::chrono::duration_cast(endTime - startTime).count() << " ms" << std::endl; diff --git a/src/axom/mir/examples/mir_tutorial_simple.cpp b/src/axom/mir/examples/mir_tutorial_simple.cpp index ad67317a6e..c1034b1937 100644 --- a/src/axom/mir/examples/mir_tutorial_simple.cpp +++ b/src/axom/mir/examples/mir_tutorial_simple.cpp @@ -27,10 +27,10 @@ int main( int AXOM_NOT_USED(argc), char** AXOM_NOT_USED(argv) ) auto startTime = Clock::now(); mir::MeshTester tester; // mir::MIRMesh testMesh = tester.initTestCaseOne(); - // mir::MIRMesh testMesh = tester.initTestCaseTwo(); + mir::MIRMesh testMesh = tester.initTestCaseTwo(); // mir::MIRMesh testMesh = tester.initTestCaseThree(); // mir::MIRMesh testMesh = tester.initTestCaseFour(); - mir::MIRMesh testMesh = tester.initTestCaseFive(50, 25); + // mir::MIRMesh testMesh = tester.initTestCaseFive(25, 12); auto endTime = Clock::now(); std::cout << "Mesh init time: " << std::chrono::duration_cast(endTime - startTime).count() << " ms" << std::endl; @@ -38,15 +38,33 @@ int main( int AXOM_NOT_USED(argc), char** AXOM_NOT_USED(argv) ) // Begin material interface reconstruction startTime = Clock::now(); + mir::MIRMesh processedMesh; + mir::InterfaceReconstructor reconstructor; - // mir::MIRMesh processedMesh = reconstructor.computeReconstructedInterface(testMesh); // Process once, with original Meredith algorithm - mir::MIRMesh processedMesh = reconstructor.computeReconstructedInterfaceIterative(testMesh, 5, 0.3); // 5 iterations, 30 percent with iterative Meredith algorithm + reconstructor.computeReconstructedInterface(testMesh, processedMesh); // Process once, with original Meredith algorithm + // reconstructor.computeReconstructedInterfaceIterative(testMesh, 100, 0.3, processedMesh); // 100 iterations, 20 percent with iterative Meredith algorithm endTime = Clock::now(); std::cout << "Material interface reconstruction time: " << std::chrono::duration_cast(endTime - startTime).count() << " ms" << std::endl; // Output results - processedMesh.writeMeshToFile("/Users/sterbentz3/Desktop/testOutputIterative6.vtk"); + processedMesh.writeMeshToFile("/Users/sterbentz3/Desktop/testOutputNewMARKONEW4.vtk"); + + std::vector > materialVolumeFractionsElement = processedMesh.computeOriginalElementVolumeFractions(); + + // // Print out the results + // printf("elementVolumeFractions: {\n"); + // for (int matID = 0; matID < processedMesh.m_numMaterials; ++matID) + // { + // printf("Material %d: {", matID); + // for (int eID = 0; eID < materialVolumeFractionsElement[matID].size(); ++eID) + // { + // printf(" %f,", materialVolumeFractionsElement[matID][eID]); + // } + // printf("}\n"); + // } + // printf("}\n"); + // // END TESTING return 0; } diff --git a/src/axom/mir/tests/CMakeLists.txt b/src/axom/mir/tests/CMakeLists.txt index 15a42d39ff..5e969fd329 100644 --- a/src/axom/mir/tests/CMakeLists.txt +++ b/src/axom/mir/tests/CMakeLists.txt @@ -14,6 +14,9 @@ set(gtest_mir_tests mir_smoke.cpp mir_interface_reconstructor.cpp + mir_cell_clipper.cpp + mir_utilities.cpp + mir_cell_generator.cpp ) diff --git a/src/axom/mir/tests/mir_cell_clipper.cpp b/src/axom/mir/tests/mir_cell_clipper.cpp new file mode 100644 index 0000000000..68f538e3f4 --- /dev/null +++ b/src/axom/mir/tests/mir_cell_clipper.cpp @@ -0,0 +1,451 @@ +// Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#ifndef MIR_CELL_CLIPPER_TEST_H_ +#define MIR_CELL_CLIPPER_TEST_H_ + +#include "gtest/gtest.h" + +#include "axom/slic.hpp" +#include "axom/mir.hpp" + +using namespace axom; + +//---------------------------------------------------------------------- + +TEST(mir_clipping_case, triangle_case_zero) +{ + mir::Shape shape = mir::Shape::Triangle; + std::vector matOneVF = {0.0, 0.0, 0.0}; + std::vector matTwoVF = {1.0, 1.0, 1.0}; + + mir::CellClipper clipper; + unsigned int clippingCase = clipper.determineClippingCase(shape, matOneVF, matTwoVF); + + EXPECT_EQ( 0 , clippingCase); +} + +//---------------------------------------------------------------------- + +TEST(mir_clipping_case, triangle_case_three) +{ + mir::Shape shape = mir::Shape::Triangle; + std::vector matOneVF = {0.0, 1.0, 1.0}; + std::vector matTwoVF = {1.0, 0.0, 0.0}; + + mir::CellClipper clipper; + unsigned int clippingCase = clipper.determineClippingCase(shape, matOneVF, matTwoVF); + EXPECT_EQ( 3 , clippingCase); +} + +//---------------------------------------------------------------------- + +TEST(mir_clipping_case, triangle_case_four) +{ + mir::Shape shape = mir::Shape::Triangle; + std::vector matOneVF = {1.0, 0.0, 0.0}; + std::vector matTwoVF = {0.0, 1.0, 1.0}; + + mir::CellClipper clipper; + unsigned int clippingCase = clipper.determineClippingCase(shape, matOneVF, matTwoVF); + + EXPECT_EQ( 4 , clippingCase); +} + +//---------------------------------------------------------------------- + +TEST(mir_clipping_case, triangle_case_seven) +{ + mir::Shape shape = mir::Shape::Triangle; + std::vector matOneVF = {1.0, 1.0, 1.0}; + std::vector matTwoVF = {0.0, 0.0, 0.0}; + + mir::CellClipper clipper; + unsigned int clippingCase = clipper.determineClippingCase(shape, matOneVF, matTwoVF); + + EXPECT_EQ( 7 , clippingCase); +} + +//---------------------------------------------------------------------- + +TEST(mir_clipping_case, quad_case_zero) +{ + mir::Shape shape = mir::Shape::Quad; + std::vector matOneVF = {0.0, 0.0, 0.0, 0.0}; + std::vector matTwoVF = {1.0, 1.0, 1.0, 1.0}; + + mir::CellClipper clipper; + unsigned int clippingCase = clipper.determineClippingCase(shape, matOneVF, matTwoVF); + EXPECT_EQ( 0 , clippingCase); +} + +//---------------------------------------------------------------------- + +TEST(mir_clipping_case, quad_case_one) +{ + mir::Shape shape = mir::Shape::Quad; + std::vector matOneVF = {0.0, 0.0, 0.0, 1.0}; + std::vector matTwoVF = {1.0, 1.0, 1.0, 0.0}; + + mir::CellClipper clipper; + unsigned int clippingCase = clipper.determineClippingCase(shape, matOneVF, matTwoVF); + EXPECT_EQ( 1 , clippingCase); +} + +//---------------------------------------------------------------------- + +TEST(mir_clipping_case, quad_case_two) +{ + mir::Shape shape = mir::Shape::Quad; + std::vector matOneVF = {0.0, 0.0, 1.0, 0.0}; + std::vector matTwoVF = {1.0, 1.0, 0.0, 1.0}; + + mir::CellClipper clipper; + unsigned int clippingCase = clipper.determineClippingCase(shape, matOneVF, matTwoVF); + EXPECT_EQ( 2 , clippingCase); +} + +//---------------------------------------------------------------------- + +TEST(mir_clipping_case, quad_case_three) +{ + mir::Shape shape = mir::Shape::Quad; + std::vector matOneVF = {0.0, 0.0, 1.0, 1.0}; + std::vector matTwoVF = {1.0, 1.0, 0.0, 0.0}; + + mir::CellClipper clipper; + unsigned int clippingCase = clipper.determineClippingCase(shape, matOneVF, matTwoVF); + EXPECT_EQ( 3 , clippingCase); +} + +//---------------------------------------------------------------------- + +TEST(mir_clipping_case, quad_case_five) +{ + mir::Shape shape = mir::Shape::Quad; + std::vector matOneVF = {0.0, 1.0, 0.0, 1.0}; + std::vector matTwoVF = {1.0, 0.0, 1.0, 0.0}; + + mir::CellClipper clipper; + unsigned int clippingCase = clipper.determineClippingCase(shape, matOneVF, matTwoVF); + EXPECT_EQ( 5 , clippingCase); +} + +//---------------------------------------------------------------------- + +TEST(mir_clipping_case, quad_case_ten) +{ + mir::Shape shape = mir::Shape::Quad; + std::vector matOneVF = {1.0, 0.0, 1.0, 0.0}; + std::vector matTwoVF = {0.0, 1.0, 0.0, 1.0}; + + mir::CellClipper clipper; + unsigned int clippingCase = clipper.determineClippingCase(shape, matOneVF, matTwoVF); + EXPECT_EQ( 10 , clippingCase); +} + +//---------------------------------------------------------------------- + +TEST(mir_clipping_case, quad_case_fifteen) +{ + mir::Shape shape = mir::Shape::Quad; + std::vector matOneVF = {1.0, 1.0, 1.0, 1.0}; + std::vector matTwoVF = {0.0, 0.0, 0.0, 0.0}; + + mir::CellClipper clipper; + unsigned int clippingCase = clipper.determineClippingCase(shape, matOneVF, matTwoVF); + EXPECT_EQ( 15 , clippingCase); +} + +//---------------------------------------------------------------------- + +TEST(mir_clipping_place, clip_edge_when_mat_one_dominates) +{ + axom::float64 vfMatOneVertexOne = 0.0; + axom::float64 vfMatTwoVertexOne = 1.0; + + axom::float64 vfMatOneVertexTwo = 0.0; + axom::float64 vfMatTwoVertexTwo = 1.0; + + mir::CellClipper clipper; + axom::float64 tValue = clipper.computeTValueOnEdge(vfMatOneVertexOne, vfMatTwoVertexOne, vfMatOneVertexTwo, vfMatTwoVertexTwo); + + EXPECT_DOUBLE_EQ( tValue, 0.0 ); +} + +//---------------------------------------------------------------------- + +TEST(mir_clipping_place, clip_edge_when_mat_two_dominates) +{ + axom::float64 vfMatOneVertexOne = 1.0; + axom::float64 vfMatTwoVertexOne = 0.0; + + axom::float64 vfMatOneVertexTwo = 1.0; + axom::float64 vfMatTwoVertexTwo = 0.0; + + mir::CellClipper clipper; + axom::float64 tValue = clipper.computeTValueOnEdge(vfMatOneVertexOne, vfMatTwoVertexOne, vfMatOneVertexTwo, vfMatTwoVertexTwo); + + EXPECT_DOUBLE_EQ( tValue, 0.0 ); +} + +// //---------------------------------------------------------------------- + + +TEST(mir_clipping_place, clip_edge_in_middle) +{ + axom::float64 vfMatOneVertexOne = 1.0; + axom::float64 vfMatTwoVertexOne = 0.0; + + axom::float64 vfMatOneVertexTwo = 0.0; + axom::float64 vfMatTwoVertexTwo = 1.0; + + mir::CellClipper clipper; + axom::float64 tValue = clipper.computeTValueOnEdge(vfMatOneVertexOne, vfMatTwoVertexOne, vfMatOneVertexTwo, vfMatTwoVertexTwo); + + EXPECT_DOUBLE_EQ( tValue, 0.5 ); +} + +// //---------------------------------------------------------------------- + +TEST(mir_clipping_place, clip_edge_with_one_null_material) +{ + axom::float64 vfMatOneVertexOne = -1.0; + axom::float64 vfMatTwoVertexOne = 0.0; + + axom::float64 vfMatOneVertexTwo = -1.0; + axom::float64 vfMatTwoVertexTwo = 0.0; + + mir::CellClipper clipper; + axom::float64 tValue = clipper.computeTValueOnEdge(vfMatOneVertexOne, vfMatTwoVertexOne, vfMatOneVertexTwo, vfMatTwoVertexTwo); + + EXPECT_DOUBLE_EQ( tValue, 0.0 ); +} + +// //---------------------------------------------------------------------- + +TEST(mir_clipping_place, clip_edge_with_two_null_material) +{ + axom::float64 vfMatOneVertexOne = -1.0; + axom::float64 vfMatTwoVertexOne = -1.0; + + axom::float64 vfMatOneVertexTwo = -1.0; + axom::float64 vfMatTwoVertexTwo = -1.0; + + mir::CellClipper clipper; + axom::float64 tValue = clipper.computeTValueOnEdge(vfMatOneVertexOne, vfMatTwoVertexOne, vfMatOneVertexTwo, vfMatTwoVertexTwo); + + EXPECT_DOUBLE_EQ( tValue, 0.0 ); +} + +//---------------------------------------------------------------------- + +TEST(mir_clipping_cell_and_vertex_output, clip_quad_case_zero) +{ + mir::Shape shape = mir::Shape::Quad; + std::vector matOneVF = {0.0, 0.0, 0.0, 0.0}; + std::vector matTwoVF = {1.0, 1.0, 1.0, 1.0}; + + std::vector > vertexVF; + vertexVF.push_back(matOneVF); + vertexVF.push_back(matTwoVF); + + std::map > newElements; + std::map > newVertices; + axom::float64 tValues[8] = { 0 }; + + mir::CellClipper clipper; + clipper.computeClippingPoints(shape, vertexVF, newElements, newVertices, tValues); + + EXPECT_EQ( 1, newElements.size() ); + EXPECT_EQ( 4, newVertices.size() ); + + EXPECT_EQ( 0, newElements[0][0] ); + EXPECT_EQ( 1, newElements[0][1] ); + EXPECT_EQ( 2, newElements[0][2] ); + EXPECT_EQ( 3, newElements[0][3] ); + + EXPECT_EQ( 0, newVertices[0][0] ); + EXPECT_EQ( 0, newVertices[1][0] ); + EXPECT_EQ( 0, newVertices[2][0] ); + EXPECT_EQ( 0, newVertices[3][0] ); + + for (int i = 0; i < 8; ++i) + { + EXPECT_DOUBLE_EQ( 0, tValues[i] ); + } +} + +//---------------------------------------------------------------------- + +TEST(mir_clipping_cell_and_vertex_output, clip_quad_case_one) +{ + mir::Shape shape = mir::Shape::Quad; + std::vector matOneVF = {0.0, 0.0, 0.0, 1.0}; + std::vector matTwoVF = {1.0, 1.0, 1.0, 0.0}; + + std::vector > vertexVF; + vertexVF.push_back(matOneVF); + vertexVF.push_back(matTwoVF); + + std::map > newElements; + std::map > newVertices; + axom::float64 tValues[8] = { 0 }; + + mir::CellClipper clipper; + clipper.computeClippingPoints(shape, vertexVF, newElements, newVertices, tValues); + + EXPECT_EQ( 3, newElements.size() ); + EXPECT_EQ( 6, newVertices.size() ); + + // Check the first element + EXPECT_EQ( 3, newElements[0][0] ); + EXPECT_EQ( 7, newElements[0][1] ); + EXPECT_EQ( 6, newElements[0][2] ); + + // Check the second element + EXPECT_EQ( 7, newElements[1][0] ); + EXPECT_EQ( 0, newElements[1][1] ); + EXPECT_EQ( 2, newElements[1][2] ); + EXPECT_EQ( 6, newElements[1][3] ); + + // Check the third element + EXPECT_EQ( 0, newElements[2][0] ); + EXPECT_EQ( 1, newElements[2][1] ); + EXPECT_EQ( 2, newElements[2][2] ); + + // Check each vertex's associated elements + EXPECT_EQ( 1, newVertices[0][0] ); + EXPECT_EQ( 2, newVertices[0][1] ); + + EXPECT_EQ( 2, newVertices[1][0] ); + + EXPECT_EQ( 1, newVertices[2][0] ); + EXPECT_EQ( 2, newVertices[2][1] ); + + EXPECT_EQ( 0, newVertices[3][0] ); + + EXPECT_EQ( 0, newVertices[6][0] ); + EXPECT_EQ( 1, newVertices[6][1] ); + + EXPECT_EQ( 0, newVertices[7][0] ); + EXPECT_EQ( 1, newVertices[7][1] ); +} + +//---------------------------------------------------------------------- + +TEST(mir_clipping_cell_and_vertex_output, clip_quad_case_three) +{ + mir::Shape shape = mir::Shape::Quad; + std::vector matOneVF = {0.0, 0.0, 1.0, 1.0}; + std::vector matTwoVF = {1.0, 1.0, 0.0, 0.0}; + + std::vector > vertexVF; + vertexVF.push_back(matOneVF); + vertexVF.push_back(matTwoVF); + + std::map > newElements; + std::map > newVertices; + axom::float64 tValues[8] = { 0 }; + + mir::CellClipper clipper; + clipper.computeClippingPoints(shape, vertexVF, newElements, newVertices, tValues); + + EXPECT_EQ( 2, newElements.size() ); + EXPECT_EQ( 6, newVertices.size() ); + + EXPECT_EQ( 0, newElements[0][0] ); + EXPECT_EQ( 1, newElements[0][1] ); + EXPECT_EQ( 5, newElements[0][2] ); + EXPECT_EQ( 7, newElements[0][3] ); + + EXPECT_EQ( 7, newElements[1][0] ); + EXPECT_EQ( 5, newElements[1][1] ); + EXPECT_EQ( 2, newElements[1][2] ); + EXPECT_EQ( 3, newElements[1][3] ); + + EXPECT_EQ( 0, newVertices[0][0] ); + EXPECT_EQ( 0, newVertices[1][0] ); + EXPECT_EQ( 1, newVertices[2][0] ); + EXPECT_EQ( 1, newVertices[3][0] ); + EXPECT_EQ( 0, newVertices[5][0] ); + EXPECT_EQ( 1, newVertices[5][1] ); + EXPECT_EQ( 0, newVertices[7][0] ); + EXPECT_EQ( 1, newVertices[7][1] ); + +} + +//---------------------------------------------------------------------- + +TEST(mir_clipping_cell_and_vertex_output, clip_quad_case_five) +{ + mir::Shape shape = mir::Shape::Quad; + std::vector matOneVF = {0.0, 1.0, 0.0, 1.0}; + std::vector matTwoVF = {1.0, 0.0, 1.0, 0.0}; + + std::vector > vertexVF; + vertexVF.push_back(matOneVF); + vertexVF.push_back(matTwoVF); + + std::map > newElements; + std::map > newVertices; + axom::float64 tValues[8] = { 0 }; + + mir::CellClipper clipper; + clipper.computeClippingPoints(shape, vertexVF, newElements, newVertices, tValues); + + EXPECT_EQ( 4, newElements.size() ); + EXPECT_EQ( 8, newVertices.size() ); + + EXPECT_EQ( 4, newElements[0][0] ); + EXPECT_EQ( 1, newElements[0][1] ); + EXPECT_EQ( 5, newElements[0][2] ); + + EXPECT_EQ( 0, newElements[1][0] ); + EXPECT_EQ( 4, newElements[1][1] ); + EXPECT_EQ( 5, newElements[1][2] ); + EXPECT_EQ( 2, newElements[1][3] ); + + EXPECT_EQ( 0, newElements[2][0] ); + EXPECT_EQ( 2, newElements[2][1] ); + EXPECT_EQ( 6, newElements[2][2] ); + EXPECT_EQ( 7, newElements[2][3] ); + + EXPECT_EQ( 7, newElements[3][0] ); + EXPECT_EQ( 6, newElements[3][1] ); + EXPECT_EQ( 3, newElements[3][2] ); + + EXPECT_EQ( 1, newVertices[0][0] ); + EXPECT_EQ( 2, newVertices[0][1] ); + EXPECT_EQ( 0, newVertices[1][0] ); + EXPECT_EQ( 1, newVertices[2][0] ); + EXPECT_EQ( 2, newVertices[2][1] ); + EXPECT_EQ( 3, newVertices[3][0] ); + EXPECT_EQ( 0, newVertices[4][0] ); + EXPECT_EQ( 1, newVertices[4][1] ); + EXPECT_EQ( 0, newVertices[5][0] ); + EXPECT_EQ( 1, newVertices[5][1] ); + EXPECT_EQ( 2, newVertices[6][0] ); + EXPECT_EQ( 3, newVertices[6][1] ); + EXPECT_EQ( 2, newVertices[7][0] ); + EXPECT_EQ( 3, newVertices[7][1] ); + +} + +//---------------------------------------------------------------------- + +int main(int argc, char* argv[]) +{ + int result = 0; + ::testing::InitGoogleTest(&argc, argv); + + axom::slic::UnitTestLogger logger; // create & initialize test logger, + + result = RUN_ALL_TESTS(); + return result; +} + + +#endif // MIR_CELL_CLIPPER_TEST_H_ diff --git a/src/axom/mir/tests/mir_cell_generator.cpp b/src/axom/mir/tests/mir_cell_generator.cpp new file mode 100644 index 0000000000..f03b480bda --- /dev/null +++ b/src/axom/mir/tests/mir_cell_generator.cpp @@ -0,0 +1,231 @@ +// Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#ifndef MIR_CELL_GENERATOR_TEST_H_ +#define MIR_CELL_GENERATOR_TEST_H_ + +#include "gtest/gtest.h" + +#include "axom/slic.hpp" +#include "axom/mir.hpp" + +using namespace axom; + +//---------------------------------------------------------------------- + +TEST(mir_cell_generator, generate_quad_topology) +{ + EXPECT_EQ(true, 1); + // this function generates the evInds, evBegins, ... etc given the map of elements -> verts, and verts -> elements + std::map > elementMap; + elementMap[0] = { 4, 1, 5 }; + elementMap[1] = { 0, 4, 5, 2 }; + elementMap[2] = { 0, 2, 6, 7 }; + elementMap[3] = { 7, 6, 3 }; + + std::map > vertexMap; + vertexMap[0] = { 1, 2 }; + vertexMap[1] = { 0 }; + vertexMap[2] = { 1, 2 }; + vertexMap[3] = { 3 }; + vertexMap[4] = { 0, 1 }; + vertexMap[5] = { 0, 1 }; + vertexMap[6] = { 2, 3 }; + vertexMap[7] = { 2, 3 }; + + mir::CellData cellData; + mir::CellGenerator generator; + generator.generateTopologyData(elementMap, vertexMap, cellData); + + EXPECT_EQ( 4, cellData.m_topology.m_evInds[0] ); + EXPECT_EQ( 1, cellData.m_topology.m_evInds[1] ); + EXPECT_EQ( 5, cellData.m_topology.m_evInds[2] ); + EXPECT_EQ( 0, cellData.m_topology.m_evInds[3] ); + EXPECT_EQ( 4, cellData.m_topology.m_evInds[4] ); + EXPECT_EQ( 5, cellData.m_topology.m_evInds[5] ); + EXPECT_EQ( 2, cellData.m_topology.m_evInds[6] ); + EXPECT_EQ( 0, cellData.m_topology.m_evInds[7] ); + EXPECT_EQ( 2, cellData.m_topology.m_evInds[8] ); + EXPECT_EQ( 6, cellData.m_topology.m_evInds[9] ); + EXPECT_EQ( 7, cellData.m_topology.m_evInds[10] ); + EXPECT_EQ( 7, cellData.m_topology.m_evInds[11] ); + EXPECT_EQ( 6, cellData.m_topology.m_evInds[12] ); + EXPECT_EQ( 3, cellData.m_topology.m_evInds[13] ); + + EXPECT_EQ( 1, cellData.m_topology.m_veInds[0] ); + EXPECT_EQ( 2, cellData.m_topology.m_veInds[1] ); + EXPECT_EQ( 0, cellData.m_topology.m_veInds[2] ); + EXPECT_EQ( 1, cellData.m_topology.m_veInds[3] ); + EXPECT_EQ( 2, cellData.m_topology.m_veInds[4] ); + EXPECT_EQ( 3, cellData.m_topology.m_veInds[5] ); + EXPECT_EQ( 0, cellData.m_topology.m_veInds[6] ); + EXPECT_EQ( 1, cellData.m_topology.m_veInds[7] ); + EXPECT_EQ( 0, cellData.m_topology.m_veInds[8] ); + EXPECT_EQ( 1, cellData.m_topology.m_veInds[9] ); + EXPECT_EQ( 2, cellData.m_topology.m_veInds[10] ); + EXPECT_EQ( 3, cellData.m_topology.m_veInds[11] ); + EXPECT_EQ( 2, cellData.m_topology.m_veInds[12] ); + EXPECT_EQ( 3, cellData.m_topology.m_veInds[13] ); + + EXPECT_EQ( 0, cellData.m_topology.m_evBegins[0] ); + EXPECT_EQ( 3, cellData.m_topology.m_evBegins[1] ); + EXPECT_EQ( 7, cellData.m_topology.m_evBegins[2] ); + EXPECT_EQ( 11, cellData.m_topology.m_evBegins[3] ); + + EXPECT_EQ( 0, cellData.m_topology.m_veBegins[0] ); + EXPECT_EQ( 2, cellData.m_topology.m_veBegins[1] ); + EXPECT_EQ( 3, cellData.m_topology.m_veBegins[2] ); + EXPECT_EQ( 5, cellData.m_topology.m_veBegins[3] ); + EXPECT_EQ( 6, cellData.m_topology.m_veBegins[4] ); + EXPECT_EQ( 8, cellData.m_topology.m_veBegins[5] ); + EXPECT_EQ( 10, cellData.m_topology.m_veBegins[6] ); + EXPECT_EQ( 12, cellData.m_topology.m_veBegins[7] ); +} + +//---------------------------------------------------------------------- + +TEST(mir_cell_generator, generate_vertex_positions) +{ + mir::Shape shapeType = mir::Shape::Quad; + + std::map > vertexMap; + vertexMap[0] = { 1, 2 }; + vertexMap[1] = { 0 }; + vertexMap[2] = { 1, 2 }; + vertexMap[3] = { 3 }; + vertexMap[4] = { 0, 1 }; + vertexMap[5] = { 0, 1 }; + vertexMap[6] = { 2, 3 }; + vertexMap[7] = { 2, 3 }; + + std::vector originalVertexPositions; + originalVertexPositions.push_back( mir::Point2(0.0, 1.0) ); + originalVertexPositions.push_back( mir::Point2(0.0, 0.0) ); + originalVertexPositions.push_back( mir::Point2(1.0, 0.0) ); + originalVertexPositions.push_back( mir::Point2(1.0, 1.0) ); + + axom::float64 tValues[8] = { 0, 0, 0, 0, 0.5, 0.5, 0.5, 0.5 }; + + mir::CellData cellData; + mir::CellGenerator cellGenerator; + cellGenerator.generateVertexPositions(shapeType, vertexMap, originalVertexPositions, tValues, cellData); + + + EXPECT_NEAR( cellData.m_mapData.m_vertexPositions[0].m_x, 0.0, 0.00001 ); + EXPECT_NEAR( cellData.m_mapData.m_vertexPositions[0].m_y, 1.0, 0.00001 ); + + EXPECT_NEAR( cellData.m_mapData.m_vertexPositions[1].m_x, 0.0, 0.00001 ); + EXPECT_NEAR( cellData.m_mapData.m_vertexPositions[1].m_y, 0.0, 0.00001 ); + + EXPECT_NEAR( cellData.m_mapData.m_vertexPositions[2].m_x, 1.0, 0.00001 ); + EXPECT_NEAR( cellData.m_mapData.m_vertexPositions[2].m_y, 0.0, 0.00001 ); + + EXPECT_NEAR( cellData.m_mapData.m_vertexPositions[3].m_x, 1.0, 0.00001 ); + EXPECT_NEAR( cellData.m_mapData.m_vertexPositions[3].m_y, 1.0, 0.00001 ); + + EXPECT_NEAR( cellData.m_mapData.m_vertexPositions[4].m_x, 0.0, 0.00001 ); + EXPECT_NEAR( cellData.m_mapData.m_vertexPositions[4].m_y, 0.5, 0.00001 ); + + EXPECT_NEAR( cellData.m_mapData.m_vertexPositions[5].m_x, 0.5, 0.00001 ); + EXPECT_NEAR( cellData.m_mapData.m_vertexPositions[5].m_y, 0.0, 0.00001 ); + + EXPECT_NEAR( cellData.m_mapData.m_vertexPositions[6].m_x, 1.0, 0.00001 ); + EXPECT_NEAR( cellData.m_mapData.m_vertexPositions[6].m_y, 0.5, 0.00001 ); + + EXPECT_NEAR( cellData.m_mapData.m_vertexPositions[7].m_x, 0.5, 0.00001 ); + EXPECT_NEAR( cellData.m_mapData.m_vertexPositions[7].m_y, 1.0, 0.00001 ); +} + +//---------------------------------------------------------------------- + +TEST(mir_cell_generator, generate_vertex_volume_fractions) +{ + mir::Shape shapeType = mir::Shape::Quad; + + std::map > vertexMap; + vertexMap[0] = { 1, 2 }; + vertexMap[1] = { 0 }; + vertexMap[2] = { 1, 2 }; + vertexMap[3] = { 3 }; + vertexMap[4] = { 0, 1 }; + vertexMap[5] = { 0, 1 }; + vertexMap[6] = { 2, 3 }; + vertexMap[7] = { 2, 3 }; + + std::vector > originalVertexVF(2); + originalVertexVF[0] = { 0.0, 0.33, 0.67, 1.0 }; + originalVertexVF[1] = { 1.0, 0.67, 0.33, 0.0 }; + + axom::float64 tValues[8] = { 0, 0, 0, 0, 0.5, 0.5, 0.5, 0.5 }; + + mir::CellData cellData; + mir::CellGenerator cellGenerator; + cellGenerator.generateVertexVolumeFractions(shapeType, vertexMap, originalVertexVF, tValues, cellData); + + + EXPECT_NEAR( cellData.m_mapData.m_vertexVolumeFractions[0][0], 0.0, 0.00001 ); + EXPECT_NEAR( cellData.m_mapData.m_vertexVolumeFractions[1][0], 1.0, 0.00001 ); + + EXPECT_NEAR( cellData.m_mapData.m_vertexVolumeFractions[0][1], 0.33, 0.00001 ); + EXPECT_NEAR( cellData.m_mapData.m_vertexVolumeFractions[1][1], 0.67, 0.00001 ); + + EXPECT_NEAR( cellData.m_mapData.m_vertexVolumeFractions[0][2], 0.67, 0.00001 ); + EXPECT_NEAR( cellData.m_mapData.m_vertexVolumeFractions[1][2], 0.33, 0.00001 ); + + EXPECT_NEAR( cellData.m_mapData.m_vertexVolumeFractions[0][3], 1.0, 0.00001 ); + EXPECT_NEAR( cellData.m_mapData.m_vertexVolumeFractions[1][3], 0.0, 0.00001 ); + + + EXPECT_NEAR( cellData.m_mapData.m_vertexVolumeFractions[0][4], 0.165, 0.00001 ); + EXPECT_NEAR( cellData.m_mapData.m_vertexVolumeFractions[1][4], 0.835, 0.00001 ); + + EXPECT_NEAR( cellData.m_mapData.m_vertexVolumeFractions[0][5], 0.5, 0.00001 ); + EXPECT_NEAR( cellData.m_mapData.m_vertexVolumeFractions[1][5], 0.5, 0.00001 ); + + EXPECT_NEAR( cellData.m_mapData.m_vertexVolumeFractions[0][6], 0.835, 0.00001 ); + EXPECT_NEAR( cellData.m_mapData.m_vertexVolumeFractions[1][6], 0.165, 0.00001 ); + + EXPECT_NEAR( cellData.m_mapData.m_vertexVolumeFractions[0][7], 0.5, 0.00001 ); + EXPECT_NEAR( cellData.m_mapData.m_vertexVolumeFractions[1][7], 0.5, 0.00001 ); +} + +//---------------------------------------------------------------------- + +TEST(mir_cell_generator, determine_clean_cell_material) +{ + mir::Shape shapeType = mir::Shape::Quad; + + std::vector vertexIDs = { 0, 1, 5, 7 }; + + int matOne = 0; + int matTwo = 1; + + std::vector > originalVertexVF(2); + originalVertexVF[0] = { 0.0, 0.33, 0.67, 1.0 }; + originalVertexVF[1] = { 1.0, 0.67, 0.33, 0.0 }; + + mir::CellData cellData; + mir::CellGenerator cellGenerator; + + int dominantMaterial = cellGenerator.determineCleanCellMaterial(shapeType, vertexIDs, matOne, matTwo, originalVertexVF); + + EXPECT_EQ( dominantMaterial, 1 ); +} + +//---------------------------------------------------------------------- + +int main(int argc, char* argv[]) +{ + int result = 0; + ::testing::InitGoogleTest(&argc, argv); + + axom::slic::UnitTestLogger logger; // create & initialize test logger, + + result = RUN_ALL_TESTS(); + return result; +} + + +#endif // MIR_CELL_GENERATOR_TEST_H_ diff --git a/src/axom/mir/tests/mir_interface_reconstructor.cpp b/src/axom/mir/tests/mir_interface_reconstructor.cpp index 8a11c21e73..875c7365df 100644 --- a/src/axom/mir/tests/mir_interface_reconstructor.cpp +++ b/src/axom/mir/tests/mir_interface_reconstructor.cpp @@ -3,8 +3,8 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#ifndef MIR_SMOKE_H_ -#define MIR_SMOKE_H_ +#ifndef MIR_INTERFACE_RECONSTRUCTOR_TEST_H_ +#define MIR_INTERFACE_RECONSTRUCTOR_TEST_H_ #include "gtest/gtest.h" @@ -13,31 +13,6 @@ using namespace axom; -TEST(mir_quad_clipping, quad_clipping_case_zero) -{ - // Initialize a 3x3 mesh with 2 materials - mir::MeshTester meshGenerator; - mir::MIRMesh testMesh = meshGenerator.initQuadClippingTestMesh(); - - // Initialize its volume fractions to custom values to guarantee this clipping case - std::vector > vertexVF; - vertexVF.resize(2); - vertexVF[0] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; - vertexVF[1] = {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}; - testMesh.constructMeshVolumeFractionsVertex(vertexVF); - - int upperLeftVertexID = 5; - int lowerLeftVertexID = 9; - int lowerRightVertexID = 10; - int upperRightVertexID = 6; - - mir::InterfaceReconstructor reconstructor; - unsigned int clippingCase = reconstructor.determineQuadClippingCase( testMesh, 0, 1, upperLeftVertexID, lowerLeftVertexID, lowerRightVertexID, upperRightVertexID ); - - EXPECT_EQ( clippingCase, 0 ); -} - - //---------------------------------------------------------------------- int main(int argc, char* argv[]) @@ -52,4 +27,4 @@ int main(int argc, char* argv[]) } -#endif // MIR_SMOKE_H_ +#endif // MIR_INTERFACE_RECONSTRUCTOR_TEST_H_ diff --git a/src/axom/mir/tests/mir_utilities.cpp b/src/axom/mir/tests/mir_utilities.cpp new file mode 100644 index 0000000000..82e26b89a0 --- /dev/null +++ b/src/axom/mir/tests/mir_utilities.cpp @@ -0,0 +1,101 @@ +// Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#ifndef MIR_UTILITIES_TEST_H_ +#define MIR_UTILITIES_TEST_H_ + +#include "gtest/gtest.h" + +#include "axom/slic.hpp" +#include "axom/mir.hpp" + +using namespace axom; + +TEST(mir_interpolation, float_linear_interpolation) +{ + axom::float64 f0 = 50.0; + axom::float64 f1 = 100.0; + + axom::float64 t = 0.0; + axom::float64 interpolant = mir::utilities::lerpFloat(f0, f1, t); + EXPECT_DOUBLE_EQ( interpolant, 50.0 ); + + t = 1.0; + interpolant = mir::utilities::lerpFloat(f0, f1, t); + EXPECT_DOUBLE_EQ( interpolant, 100.0 ); + + t = 0.5; + interpolant = mir::utilities::lerpFloat(f0, f1, t); + EXPECT_DOUBLE_EQ( interpolant, 75.0 ); + + t = 0.66; + interpolant = mir::utilities::lerpFloat(f0, f1, t); + EXPECT_DOUBLE_EQ( interpolant, 83.0 ); +} + +//---------------------------------------------------------------------- + +TEST(mir_interpolation, point_linear_interpolation) +{ + mir::Point2 p0(0.25, 0.0); + mir::Point2 p1(1.25, 1.0); + + axom::float64 t = 0.0; + mir::Point2 interpolant = mir::utilities::interpolateVertexPosition(p0, p1, t); + EXPECT_DOUBLE_EQ( interpolant.m_x, 0.25); + EXPECT_DOUBLE_EQ( interpolant.m_y, 0.0); + + t = 1.0; + interpolant = mir::utilities::interpolateVertexPosition(p0, p1, t); + EXPECT_DOUBLE_EQ( interpolant.m_x, 1.25); + EXPECT_DOUBLE_EQ( interpolant.m_y, 1.0); + + t = 0.5; + interpolant = mir::utilities::interpolateVertexPosition(p0, p1, t); + EXPECT_DOUBLE_EQ( interpolant.m_x, 0.75); + EXPECT_DOUBLE_EQ( interpolant.m_y, 0.5); + + t = 0.66; + interpolant = mir::utilities::interpolateVertexPosition(p0, p1, t); + EXPECT_NEAR( interpolant.m_x, 0.91, 0.00001); + EXPECT_NEAR( interpolant.m_y, 0.66, 0.00001); +} + +//---------------------------------------------------------------------- + +TEST(mir_distance_utility, compute_distance) +{ + mir::Point2 p0( 0.25, 0.0 ); + mir::Point2 p1( 1.25, 0.0 ); + axom::float64 dist = mir::utilities::distance(p0, p1); + EXPECT_DOUBLE_EQ( dist, 1.0 ); + + mir::Point2 p2( 0.25, 0.0 ); + mir::Point2 p3( 1.25, 1.0 ); + dist = mir::utilities::distance(p2, p3); + EXPECT_NEAR( dist, 1.4142, 0.001 ); + + mir::Point2 p4( 1.0, 1.0 ); + mir::Point2 p5( 1.0, 1.0 ); + dist = mir::utilities::distance(p4, p5); + EXPECT_DOUBLE_EQ( dist, 0.0 ); + +} + +//---------------------------------------------------------------------- + +int main(int argc, char* argv[]) +{ + int result = 0; + ::testing::InitGoogleTest(&argc, argv); + + axom::slic::UnitTestLogger logger; // create & initialize test logger, + + result = RUN_ALL_TESTS(); + return result; +} + + +#endif // MIR_UTILITIES_TEST_H_ diff --git a/src/docs/dependencies.dot b/src/docs/dependencies.dot index 75ab74554e..cddf4cd4df 100644 --- a/src/docs/dependencies.dot +++ b/src/docs/dependencies.dot @@ -2,7 +2,7 @@ digraph dependencies { quest -> {slam primal mint spin}; {quest slam primal mint spin} -> {slic core}; mint -> sidre [style="dashed"]; - mir -> {slic core}; + mir -> {slic core slam}; spin -> {slam primal}; sidre -> {slic core}; slic -> core; diff --git a/src/index.rst b/src/index.rst index 9ac474a4b6..afcf6266ee 100644 --- a/src/index.rst +++ b/src/index.rst @@ -88,7 +88,7 @@ Dependencies between modules are as follows: - Slic optionally depends on Lumberjack - Slam, Spin, Primal, Mint, Quest, and Sidre depend on Slic - Mint optionally depends on Sidre -- Mir depends on Slic +- Mir depends on Slic and Slam - Quest depends on Slam, Spin, Primal, and Mint The figure below summarizes the dependencies between the modules. Solid links From d811ce037e46ed95a5c56609c4ac7053b53cdca4 Mon Sep 17 00:00:00 2001 From: Sterbentz Date: Thu, 27 Jun 2019 09:03:42 -0700 Subject: [PATCH 12/23] Implemented function for determining the two endpoints of a given midpoint for the 3D cases. --- src/axom/mir/CellGenerator.cpp | 2 +- src/axom/mir/MIRUtilities.hpp | 74 +++++++++++++++++++ src/axom/mir/examples/mir_tutorial_simple.cpp | 2 +- 3 files changed, 76 insertions(+), 2 deletions(-) diff --git a/src/axom/mir/CellGenerator.cpp b/src/axom/mir/CellGenerator.cpp index 05b61aa030..c81c535f59 100644 --- a/src/axom/mir/CellGenerator.cpp +++ b/src/axom/mir/CellGenerator.cpp @@ -110,7 +110,7 @@ void CellGenerator::generateVertexVolumeFractions(const mir::Shape shapeType, { int vID = itr->first; - for (int matID = 0; matID < vertexVF.size(); ++matID) + for (unsigned long matID = 0; matID < vertexVF.size(); ++matID) { if ( vID < mir::utilities::numVerts(shapeType) ) { diff --git a/src/axom/mir/MIRUtilities.hpp b/src/axom/mir/MIRUtilities.hpp index db7c537a6e..0f3c75c084 100644 --- a/src/axom/mir/MIRUtilities.hpp +++ b/src/axom/mir/MIRUtilities.hpp @@ -177,6 +177,80 @@ namespace utilities if ( midpointVertexID == 7 && isFromVertex ) { return 3; } if ( midpointVertexID == 7 && !isFromVertex ) { return 0; } break; + case mir::Shape::Tetrahedron: + if ( midpointVertexID == 4 && isFromVertex ) { return 0; } + if ( midpointVertexID == 4 && !isFromVertex ) { return 1; } + if ( midpointVertexID == 5 && isFromVertex ) { return 0; } + if ( midpointVertexID == 5 && !isFromVertex ) { return 2; } + if ( midpointVertexID == 6 && isFromVertex ) { return 0; } + if ( midpointVertexID == 6 && !isFromVertex ) { return 3; } + if ( midpointVertexID == 7 && isFromVertex ) { return 1; } + if ( midpointVertexID == 7 && !isFromVertex ) { return 2; } + if ( midpointVertexID == 8 && isFromVertex ) { return 2; } + if ( midpointVertexID == 8 && !isFromVertex ) { return 3; } + if ( midpointVertexID == 9 && isFromVertex ) { return 3; } + if ( midpointVertexID == 9 && !isFromVertex ) { return 1; } + case mir::Shape::Pyramid: + if ( midpointVertexID == 5 && isFromVertex ) { return 0; } + if ( midpointVertexID == 5 && !isFromVertex ) { return 1; } + if ( midpointVertexID == 6 && isFromVertex ) { return 0; } + if ( midpointVertexID == 6 && !isFromVertex ) { return 2; } + if ( midpointVertexID == 7 && isFromVertex ) { return 0; } + if ( midpointVertexID == 7 && !isFromVertex ) { return 3; } + if ( midpointVertexID == 8 && isFromVertex ) { return 0; } + if ( midpointVertexID == 8 && !isFromVertex ) { return 4; } + if ( midpointVertexID == 9 && isFromVertex ) { return 1; } + if ( midpointVertexID == 9 && !isFromVertex ) { return 2; } + if ( midpointVertexID == 10 && isFromVertex ) { return 2; } + if ( midpointVertexID == 10 && !isFromVertex ) { return 3; } + if ( midpointVertexID == 11 && isFromVertex ) { return 3; } + if ( midpointVertexID == 11 && !isFromVertex ) { return 4; } + if ( midpointVertexID == 12 && isFromVertex ) { return 4; } + if ( midpointVertexID == 12 && !isFromVertex ) { return 1; } + case mir::Shape::Triangular_Prism: + if ( midpointVertexID == 6 && isFromVertex ) { return 0; } + if ( midpointVertexID == 6 && !isFromVertex ) { return 1; } + if ( midpointVertexID == 7 && isFromVertex ) { return 1; } + if ( midpointVertexID == 7 && !isFromVertex ) { return 2; } + if ( midpointVertexID == 8 && isFromVertex ) { return 2; } + if ( midpointVertexID == 8 && !isFromVertex ) { return 0; } + if ( midpointVertexID == 9 && isFromVertex ) { return 0; } + if ( midpointVertexID == 9 && !isFromVertex ) { return 3; } + if ( midpointVertexID == 10 && isFromVertex ) { return 1; } + if ( midpointVertexID == 10 && !isFromVertex ) { return 4; } + if ( midpointVertexID == 11 && isFromVertex ) { return 2; } + if ( midpointVertexID == 11 && !isFromVertex ) { return 5; } + if ( midpointVertexID == 12 && isFromVertex ) { return 3; } + if ( midpointVertexID == 12 && !isFromVertex ) { return 4; } + if ( midpointVertexID == 13 && isFromVertex ) { return 4; } + if ( midpointVertexID == 13 && !isFromVertex ) { return 5; } + if ( midpointVertexID == 14 && isFromVertex ) { return 5; } + if ( midpointVertexID == 14 && !isFromVertex ) { return 3; } + case mir::Shape::Hexahedron: + if ( midpointVertexID == 8 && isFromVertex ) { return 0; } + if ( midpointVertexID == 8 && !isFromVertex ) { return 1; } + if ( midpointVertexID == 9 && isFromVertex ) { return 1; } + if ( midpointVertexID == 9 && !isFromVertex ) { return 2; } + if ( midpointVertexID == 10 && isFromVertex ) { return 2; } + if ( midpointVertexID == 10 && !isFromVertex ) { return 3; } + if ( midpointVertexID == 11 && isFromVertex ) { return 3; } + if ( midpointVertexID == 11 && !isFromVertex ) { return 0; } + if ( midpointVertexID == 12 && isFromVertex ) { return 0; } + if ( midpointVertexID == 12 && !isFromVertex ) { return 4; } + if ( midpointVertexID == 13 && isFromVertex ) { return 1; } + if ( midpointVertexID == 13 && !isFromVertex ) { return 5; } + if ( midpointVertexID == 14 && isFromVertex ) { return 2; } + if ( midpointVertexID == 14 && !isFromVertex ) { return 6; } + if ( midpointVertexID == 15 && isFromVertex ) { return 3; } + if ( midpointVertexID == 15 && !isFromVertex ) { return 7; } + if ( midpointVertexID == 16 && isFromVertex ) { return 4; } + if ( midpointVertexID == 16 && !isFromVertex ) { return 5; } + if ( midpointVertexID == 17 && isFromVertex ) { return 5; } + if ( midpointVertexID == 17 && !isFromVertex ) { return 6; } + if ( midpointVertexID == 18 && isFromVertex ) { return 6; } + if ( midpointVertexID == 18 && !isFromVertex ) { return 7; } + if ( midpointVertexID == 19 && isFromVertex ) { return 7; } + if ( midpointVertexID == 19 && !isFromVertex ) { return 4; } default: printf("Edge endpoint case not implemented.\n"); return -1; diff --git a/src/axom/mir/examples/mir_tutorial_simple.cpp b/src/axom/mir/examples/mir_tutorial_simple.cpp index c1034b1937..2217f64c4b 100644 --- a/src/axom/mir/examples/mir_tutorial_simple.cpp +++ b/src/axom/mir/examples/mir_tutorial_simple.cpp @@ -48,7 +48,7 @@ int main( int AXOM_NOT_USED(argc), char** AXOM_NOT_USED(argv) ) std::cout << "Material interface reconstruction time: " << std::chrono::duration_cast(endTime - startTime).count() << " ms" << std::endl; // Output results - processedMesh.writeMeshToFile("/Users/sterbentz3/Desktop/testOutputNewMARKONEW4.vtk"); + processedMesh.writeMeshToFile("/Users/sterbentz3/Desktop/processedMesh.vtk"); std::vector > materialVolumeFractionsElement = processedMesh.computeOriginalElementVolumeFractions(); From 062143149a50627c8b06de8b46b4783c4dda751b Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 1 Jul 2019 16:51:15 -0700 Subject: [PATCH 13/23] Updates to build system for mir component --- src/axom/mainpage.md | 2 +- src/axom/mir/CMakeLists.txt | 7 ++----- src/axom/mir/examples/CMakeLists.txt | 9 ++------- src/axom/mir/tests/CMakeLists.txt | 7 +++++-- 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/axom/mainpage.md b/src/axom/mainpage.md index 60708dae52..84f42826ad 100644 --- a/src/axom/mainpage.md +++ b/src/axom/mainpage.md @@ -21,7 +21,7 @@ Dependencies between components are as follows: - Slic optionally depends on Lumberjack - Slam, Primal, Mint, Quest, Spin, and Sidre depend on Slic - Mint optionally depends on Sidre -- Mir depends on Slic +- Mir depends on Slic and Slam - Spin depends on Primal and Slam - Quest depends on Slam, Primal, Spin, and Mint diff --git a/src/axom/mir/CMakeLists.txt b/src/axom/mir/CMakeLists.txt index db7d2fda20..65104b39b1 100644 --- a/src/axom/mir/CMakeLists.txt +++ b/src/axom/mir/CMakeLists.txt @@ -10,7 +10,7 @@ # Check necessary dependencies #------------------------------------------------------------------------------ axom_component_requires(NAME MIR - COMPONENTS SLIC) + COMPONENTS SLIC SLAM) #------------------------------------------------------------------------------ # Specify all headers/sources @@ -40,7 +40,7 @@ set(mir_sources #------------------------------------------------------------------------------ # Build and install the library #------------------------------------------------------------------------------ -set(mir_depends_on core slic) +set(mir_depends_on core slic slam) blt_add_library( NAME mir @@ -61,9 +61,6 @@ axom_install_component(NAME mir #------------------------------------------------------------------------------ if (AXOM_ENABLE_TESTS) add_subdirectory(tests) - if (ENABLE_BENCHMARKS) - # add_subdirectory(benchmarks) - endif() endif() if (AXOM_ENABLE_EXAMPLES) diff --git a/src/axom/mir/examples/CMakeLists.txt b/src/axom/mir/examples/CMakeLists.txt index 16e15481a7..24878a8dde 100644 --- a/src/axom/mir/examples/CMakeLists.txt +++ b/src/axom/mir/examples/CMakeLists.txt @@ -9,16 +9,11 @@ set( mir_examples ) set( mir_example_dependencies - #core + core + slic mir - #slic ) -blt_list_append( TO mir_example_dependencies ELEMENTS sidre conduit IF AXOM_MIR_USE_SIDRE ) -blt_list_append( TO mir_example_dependencies ELEMENTS raja IF RAJA_FOUND ) -blt_list_append( TO mir_example_dependencies ELEMENTS openmp IF ENABLE_OPENMP ) -blt_list_append( TO mir_example_dependencies ELEMENTS cuda IF ENABLE_CUDA ) - foreach( example ${mir_examples} ) get_filename_component( example_name ${example} NAME_WE ) diff --git a/src/axom/mir/tests/CMakeLists.txt b/src/axom/mir/tests/CMakeLists.txt index 5e969fd329..102b1234c7 100644 --- a/src/axom/mir/tests/CMakeLists.txt +++ b/src/axom/mir/tests/CMakeLists.txt @@ -19,8 +19,10 @@ set(gtest_mir_tests mir_cell_generator.cpp ) - -set(mir_tests_depends_on core slic mir gtest) +set(mir_tests_depends_on + slic + mir + gtest) #------------------------------------------------------------------------------ # Add gtest based tests @@ -32,6 +34,7 @@ foreach(test ${gtest_mir_tests}) OUTPUT_DIR ${TEST_OUTPUT_DIRECTORY} DEPENDS_ON ${mir_tests_depends_on} FOLDER axom/mir/tests ) + blt_add_test( NAME ${test_name} COMMAND ${test_name}_test ) endforeach() From 37e41385b76f0650f0f554e058348ac5b46fd357 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 3 Jul 2019 15:18:55 -0700 Subject: [PATCH 14/23] Adds some improvements to MIRMesh * Adds copy constructor and copy assignment operator * Adds validity check predicate -- isValid() * Adds unit tests. Initially focused on exercising constructors and assignment operators. --- src/axom/mir/InterfaceReconstructor.cpp | 4 +- src/axom/mir/MIRMesh.cpp | 254 +++++++++++++++++++++--- src/axom/mir/MIRMesh.hpp | 29 ++- src/axom/mir/tests/CMakeLists.txt | 1 + src/axom/mir/tests/mir_mesh.cpp | 229 +++++++++++++++++++++ 5 files changed, 483 insertions(+), 34 deletions(-) create mode 100644 src/axom/mir/tests/mir_mesh.cpp diff --git a/src/axom/mir/InterfaceReconstructor.cpp b/src/axom/mir/InterfaceReconstructor.cpp index e4fdab75d8..b18c17cfdb 100644 --- a/src/axom/mir/InterfaceReconstructor.cpp +++ b/src/axom/mir/InterfaceReconstructor.cpp @@ -39,7 +39,7 @@ void InterfaceReconstructor::computeReconstructedInterface(mir::MIRMesh& inputMe for (int matID = 0; matID < m_originalMesh.m_numMaterials; ++matID) { // Copy the mesh to be split - mir::MIRMesh intermediateMesh(&finalMesh); + mir::MIRMesh intermediateMesh(finalMesh); // Create an array to store the output of each element being split. CellData temp_cellData[intermediateMesh.m_elems.size()]; @@ -271,4 +271,4 @@ void InterfaceReconstructor::generateCleanCells(const int eID, //-------------------------------------------------------------------------------- } -} \ No newline at end of file +} diff --git a/src/axom/mir/MIRMesh.cpp b/src/axom/mir/MIRMesh.cpp index c47fecf3bf..96130fed54 100644 --- a/src/axom/mir/MIRMesh.cpp +++ b/src/axom/mir/MIRMesh.cpp @@ -5,44 +5,64 @@ #include "MIRMesh.hpp" +#include "axom/core.hpp" + namespace axom { namespace mir { -//-------------------------------------------------------------------------------- MIRMesh::MIRMesh() -{ - -} + : m_verts(0) + , m_elems(0) + , m_numMaterials(0) +{} //-------------------------------------------------------------------------------- -MIRMesh::MIRMesh(MIRMesh* _mesh) +MIRMesh::MIRMesh(const MIRMesh& other) + : m_verts(other.m_verts) + , m_elems(other.m_elems) + , m_materialVolumeFractionsElement(other.m_materialVolumeFractionsElement) + , m_materialVolumeFractionsVertex(other.m_materialVolumeFractionsVertex) + , m_numMaterials(other.m_numMaterials) + , m_meshTopology(other.m_meshTopology) { - m_meshTopology.m_evInds = _mesh->m_meshTopology.m_evInds; - m_meshTopology.m_evBegins = _mesh->m_meshTopology.m_evBegins; - m_meshTopology.m_veInds = _mesh->m_meshTopology.m_veInds; - m_meshTopology.m_veBegins = _mesh->m_meshTopology.m_veBegins; - m_verts = _mesh->m_verts; - m_elems = _mesh->m_elems; - m_bdry = _mesh->m_bdry; - m_cobdry = _mesh->m_cobdry; - m_vertexPositions = _mesh->m_vertexPositions; - m_materialVolumeFractionsElement = _mesh->m_materialVolumeFractionsElement; - m_materialVolumeFractionsVertex = _mesh->m_materialVolumeFractionsVertex; - m_elementParentIDs = _mesh->m_elementParentIDs; - m_shapeTypes = _mesh->m_shapeTypes; - m_elementDominantMaterials = _mesh->m_elementDominantMaterials; - m_numMaterials = _mesh->m_numMaterials; -} + constructMeshRelations(); + constructVertexPositionMap(other.m_vertexPositions.data()); + constructElementParentMap(other.m_elementParentIDs.data()); + constructElementDominantMaterialMap(other.m_elementDominantMaterials.data()); -//-------------------------------------------------------------------------------- + m_shapeTypes= IntMap( &m_elems ); + for(auto el: m_elems) { m_shapeTypes[el] = other.m_shapeTypes[el]; } -MIRMesh::~MIRMesh() +} + +MIRMesh& MIRMesh::operator=(const MIRMesh& other) { + if(this != &other) + { + m_verts = other.m_verts; + m_elems = other.m_elems; + + m_meshTopology = other.m_meshTopology; + constructMeshRelations(); + constructVertexPositionMap(other.m_vertexPositions.data()); + constructElementParentMap(other.m_elementParentIDs.data()); + constructElementDominantMaterialMap(other.m_elementDominantMaterials.data()); + + + m_shapeTypes = IntMap( &m_elems ); + for(auto el: m_elems) { m_shapeTypes[el] = other.m_shapeTypes[el]; } + + m_materialVolumeFractionsElement = other.m_materialVolumeFractionsElement; + m_materialVolumeFractionsVertex = other.m_materialVolumeFractionsVertex; + + m_numMaterials = other.m_numMaterials; + } + return *this; } //-------------------------------------------------------------------------------- @@ -122,8 +142,8 @@ void MIRMesh::constructMeshRelations() SLIC_ASSERT_MSG( m_bdry.isValid(), "Boundary relation is not valid."); SLIC_ASSERT_MSG( m_cobdry.isValid(), "Coboundary relation is not valid."); - SLIC_INFO("Elem-Vert relation has size " << m_bdry.totalSize()); - SLIC_INFO("Vert-Elem relation has size " << m_cobdry.totalSize()); + SLIC_DEBUG("Elem-Vert relation has size " << m_bdry.totalSize()); + SLIC_DEBUG("Vert-Elem relation has size " << m_cobdry.totalSize()); } //-------------------------------------------------------------------------------- @@ -243,7 +263,7 @@ void MIRMesh::constructElementShapeTypesMap(const std::vector& shape // Initialize the map for the elements' dominant colors m_shapeTypes = IntMap( &m_elems ); - // Copy the dat for the elements + // Copy the data for the elements for (int eID = 0; eID < m_elems.size(); ++eID) m_shapeTypes[eID] = shapeTypes[eID]; @@ -315,6 +335,18 @@ void MIRMesh::print() } printf("}\n"); + printf("elementVolumeFractions: { \n"); + for (unsigned long i = 0; i < m_materialVolumeFractionsElement.size(); ++i) + { + printf(" { "); + for (int j = 0; j < m_elems.size(); ++j) + { + printf("%.3f, ", m_materialVolumeFractionsElement[i][j]); + } + printf("}\n"); + } + printf("}\n"); + printf("vertexVolumeFractions: { \n"); for (unsigned long i = 0; i < m_materialVolumeFractionsVertex.size(); ++i) { @@ -475,5 +507,173 @@ axom::float64 MIRMesh::computeQuadArea(Point2 p0, //-------------------------------------------------------------------------------- +bool MIRMesh::isValid(bool verbose) const +{ + bool bValid = true; + + // Check the topology data + bValid = bValid && isTopologyValid(verbose); + + // Check the volume fraction data + bValid = bValid && areVolumeFractionsValid(verbose); + + // Check the map data + bValid = bValid && areMapsValid(verbose); + + return bValid; +} + +bool MIRMesh::isTopologyValid(bool verbose) const +{ + bool bValid = true; + + // check the vertex and element sets + bValid = bValid && m_verts.isValid(verbose); + bValid = bValid && m_elems.isValid(verbose); + + // check the relations + if(!m_verts.empty() && !m_elems.empty() ) + { + bValid = bValid && m_bdry.isValid(verbose); + bValid = bValid && m_cobdry.isValid(verbose); + } + + if(!bValid && verbose) + { + SLIC_WARNING("MIRMesh invalid: Invalid topology"); + } + + return bValid; +} + +bool MIRMesh::areVolumeFractionsValid(bool verbose) const +{ + bool bValid = true; + + int vfElemSize = m_materialVolumeFractionsElement.size(); + if( vfElemSize != m_numMaterials) + { + bValid = false; + if(verbose) + { + SLIC_WARNING("MIRMesh invalid: Invalid number of materials in volume fraction array."); + } + return bValid; + } + + // Check the sizes of the volume fraction arrays + for(int i=0; i < m_numMaterials; ++i) + { + const auto& mats = m_materialVolumeFractionsElement[i]; + if( mats.size() != m_elems.size()) + { + bValid = false; + if(verbose) + { + SLIC_WARNING("MIRMesh invalid: Material " << i <<" has wrong " + << " number of materials in volume fraction array." + << " Expected " << m_elems.size() + << ", got " << mats.size() << "."); + } + } + } + + if(!bValid) + { + return false; + } + + // Check that the volume fractions sum to 1.0 + for(auto el : m_elems.positions()) + { + double sum = 0; + for(int m=0; m < m_numMaterials; ++m) + { + sum += m_materialVolumeFractionsElement[m][el]; + } + + if(! axom::utilities::isNearlyEqual(sum, 1.)) + { + bValid = false; + if(verbose) + { + std::stringstream sstr; + + for(int m=0; m < m_numMaterials; ++m) + { + sstr << m_materialVolumeFractionsElement[m][el] << " "; + } + + SLIC_WARNING("MIRMesh invalid: Sum of materials in element" + << el << " was " << sum << ". Expected 1." + << " Values were: " << sstr.str() ); + } + } + } + + return bValid; +} + +bool MIRMesh::areMapsValid(bool verbose) const +{ + bool bValid = true; + + // Check the positions map + if( m_vertexPositions.size() != m_verts.size()) + { + bValid = false; + if(verbose) + { + SLIC_WARNING("MIRMesh invalid: Incorrect number of vertex positions." + << " Expected " << m_verts.size() + << ". Got " << m_vertexPositions.size()); + } + } + + bValid = bValid && m_vertexPositions.isValid(verbose); + + // Check the element maps + bValid = bValid && m_elementParentIDs.isValid(verbose); + bValid = bValid && m_elementDominantMaterials.isValid(verbose); + bValid = bValid && m_shapeTypes.isValid(verbose); + + if( m_elementParentIDs.size() != m_elems.size() ) + { + bValid = false; + if(verbose) + { + SLIC_WARNING("MIRMesh invalid: Incorrect number of elem parent IDs." + << " Expected " << m_elems.size() + << ". Got " << m_elementParentIDs.size()); + } + } + + if( m_elementDominantMaterials.size() != m_elems.size() ) + { + bValid = false; + if(verbose) + { + SLIC_WARNING("MIRMesh invalid: Incorrect number of elem dominant mats." + << " Expected " << m_elems.size() + << ". Got " << m_elementDominantMaterials.size()); + } + } + + if( m_shapeTypes.size() != m_elems.size() ) + { + bValid = false; + if(verbose) + { + SLIC_WARNING("MIRMesh invalid: Incorrect number of elem shape types." + << " Expected " << m_elems.size() + << ". Got " << m_shapeTypes.size()); + } + } + + return bValid; + +} + + +} } -} \ No newline at end of file diff --git a/src/axom/mir/MIRMesh.hpp b/src/axom/mir/MIRMesh.hpp index 984ada037e..57b1e7446d 100644 --- a/src/axom/mir/MIRMesh.hpp +++ b/src/axom/mir/MIRMesh.hpp @@ -57,14 +57,19 @@ namespace mir MIRMesh(); /** - * \brief Copy constrcutor. + * \brief Copy constructor. */ - MIRMesh(MIRMesh* _mesh); + MIRMesh(const MIRMesh& other); /** * \brief Default destructor. */ - ~MIRMesh(); + ~MIRMesh() = default; + + /** + * \brief Copy assignment. + */ + MIRMesh& operator=(const MIRMesh& other); /** * \brief Initializes a mesh with the provided data and topology. @@ -132,10 +137,24 @@ namespace mir */ std::vector > computeOriginalElementVolumeFractions(); + /** + * \brief Checks that this instance of MIRMesh is valid + * + * \return True if this instance is valid, false otherwise + */ + bool isValid(bool verbose) const; + private: + /// Utility predicate to check if the mesh's topology is valid + bool isTopologyValid(bool verbose) const; + /// Utility predicate to check if the mesh's volume fractions are valid + bool areVolumeFractionsValid(bool verbose) const; + /// Utility predicate to check if the mesh's maps are valid + bool areMapsValid(bool verbose) const; + /** - * \brief Constucts the positions map on the vertices. + * \brief Constructs the positions map on the vertices. * * \param data The vector of position data for each vertex. */ @@ -215,4 +234,4 @@ namespace mir //-------------------------------------------------------------------------------- } } -#endif \ No newline at end of file +#endif diff --git a/src/axom/mir/tests/CMakeLists.txt b/src/axom/mir/tests/CMakeLists.txt index 102b1234c7..d4b750967b 100644 --- a/src/axom/mir/tests/CMakeLists.txt +++ b/src/axom/mir/tests/CMakeLists.txt @@ -13,6 +13,7 @@ set(gtest_mir_tests mir_smoke.cpp + mir_mesh.cpp mir_interface_reconstructor.cpp mir_cell_clipper.cpp mir_utilities.cpp diff --git a/src/axom/mir/tests/mir_mesh.cpp b/src/axom/mir/tests/mir_mesh.cpp new file mode 100644 index 0000000000..0d9797e16d --- /dev/null +++ b/src/axom/mir/tests/mir_mesh.cpp @@ -0,0 +1,229 @@ +// Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#ifndef MIR_MESH_TEST_H_ +#define MIR_MESH_TEST_H_ + +#include "gtest/gtest.h" + +#include "axom/slic.hpp" +#include "axom/mir.hpp" + + +namespace mir = axom::mir; + +class MirMeshTest : public ::testing::Test +{ +public: + + template + using Vec = std::vector; + + using IndexVec = Vec; + using VolFracVec = Vec; + using VolumeFractions = Vec; + + enum { GREEN = 0, BLUE = 1 }; + +public: + + mir::MIRMesh& getMesh() { return m_mesh; } + const mir::MIRMesh& getMesh() const { return m_mesh; } + +protected: + void SetUp() override + { + m_verts= mir::VertSet(16); + m_elems= mir::ElemSet(9); + m_nMats = 2; + + // Create the mesh connectivity information + setupTopoData(); + setupMapData(); + setupVolumeFractions(); + + m_mesh.initializeMesh(this->m_verts, this->m_elems, this->m_nMats, + this->m_topoData, this->m_mapData, this->m_volFracs); + } + + // Set up the mesh's topological connectivity + void setupTopoData() + { + m_topoData.m_evInds = { + 0,4,5,1, // elem 0, card 4, start 0 + 1,5,6,2, // elem 1, card 4, start 4 + 2,6,7,3, // elem 2, card 4, start 8 + 4,8,9,5, // elem 3, card 4, start 12 + 5,9,10,6, // elem 4, card 4, start 16 + 6,10,11,7, // elem 5, card 4, start 20 + 8,12,13,9, // elem 6, card 4, start 24 + 9,13,14,10, // elem 7, card 4, start 28 + 10,14,15,11 // elem 8, card 4, start 32, end 36 + }; + + m_topoData.m_evBegins = { + 0,4,8,12,16,20,24,28,32,36 + }; + + m_topoData.m_veInds = { + 0, // vert 0, card 1, start 0 + 0,1, // vert 1, card 2, start 1 + 1,2, // vert 2, card 2, start 3 + 2, // vert 3, card 1, start 5 + 0,3, // vert 4, card 2, start 6 + 0,1,3,4, // vert 5, card 4, start 8 + 1,2,4,5, // vert 6, card 4, start 12 + 2,5, // vert 7, card 2, start 16 + 3,6, // vert 8, card 2, start 18 + 3,4,6,7, // vert 9, card 4, start 20 + 4,5,7,8, // vert 10, card 4, start 24 + 5,8, // vert 11, card 2, start 28 + 6, // vert 12, card 1, start 30 + 6,7, // vert 13, card 2, start 31 + 7,8, // vert 14, card 2, start 33 + 8, // vert 15, card 1, start 35, end 36 + }; + + m_topoData.m_veBegins = { + 0,1,3,5,6,8,12,16,18,20,24,28,30,31,33,35,36 + }; + } + + // Set up the mesh's map data + void setupMapData() + { + m_mapData.m_vertexPositions = { + mir::Point2( 0.0, 3.0 ), + mir::Point2( 1.0, 3.0 ), + mir::Point2( 2.0, 3.0 ), + mir::Point2( 3.0, 3.0 ), + + mir::Point2( 0.0, 2.0 ), + mir::Point2( 1.0, 2.0 ), + mir::Point2( 2.0, 2.0 ), + mir::Point2( 3.0, 2.0 ), + + mir::Point2( 0.0, 1.0 ), + mir::Point2( 1.0, 1.0 ), + mir::Point2( 2.0, 1.0 ), + mir::Point2( 3.0, 1.0 ), + + mir::Point2( 0.0, 0.0 ), + mir::Point2( 1.0, 0.0 ), + mir::Point2( 2.0, 0.0 ), + mir::Point2( 3.0, 0.0 ) + }; + + m_mapData.m_elementDominantMaterials = Vec(m_elems.size(), mir::NULL_MAT); + m_mapData.m_elementParents = { 0, 1, 2, 3, 4, 5, 6, 7, 8 }; + m_mapData.m_shapeTypes = Vec(m_elems.size(), mir::Shape::Quad); + } + + // Set up the mesh's volume fraction data + void setupVolumeFractions() + { + m_volFracs.resize(m_nMats); + m_volFracs[GREEN] = {1.0, 1.0, 1.0, 1.0, 0.5, 0.2, 0.2, 0.0, 0.0}; + m_volFracs[BLUE] = {0.0, 0.0, 0.0, 0.0, 0.5, 0.8, 0.8, 1.0, 1.0}; + } + +protected: + mir::VertSet m_verts; + mir::ElemSet m_elems; + int m_nMats; + + mir::CellTopologyData m_topoData; + mir::CellMapData m_mapData; + mir::CellData m_cellData; + VolumeFractions m_volFracs; + + mir::MIRMesh m_mesh; +}; + + +TEST_F(MirMeshTest,default_ctor) +{ + mir::MIRMesh mesh; + EXPECT_TRUE(mesh.isValid(true)); +} + + +TEST_F(MirMeshTest,initialize) +{ + mir::MIRMesh mesh; + mesh.initializeMesh( + this->m_verts, + this->m_elems, + this->m_nMats, + this->m_topoData, + this->m_mapData, + this->m_volFracs); + + EXPECT_TRUE(mesh.isValid(true)); + + mesh.print(); +} + +TEST_F(MirMeshTest,copy_ctor) +{ + // test copy constructor + { + mir::MIRMesh& mesh = this->getMesh(); + EXPECT_TRUE(mesh.isValid(true)); + + mir::MIRMesh mirCopy(mesh); + EXPECT_TRUE( mirCopy.isValid(true) ); + } + + // test const copy constructor + { + const mir::MIRMesh& cmesh = this->getMesh(); + EXPECT_TRUE(cmesh.isValid(true)); + + // test copy constructor + const mir::MIRMesh mirCopy(cmesh); + EXPECT_TRUE( mirCopy.isValid(true) ); + } +} + +TEST_F(MirMeshTest,copy_assign) +{ + // test copy assignment from a non-const MIRMesh + { + mir::MIRMesh& mesh = this->getMesh(); + EXPECT_TRUE(mesh.isValid(true)); + + mir::MIRMesh mirCopy; + mirCopy = mesh; + EXPECT_TRUE( mirCopy.isValid(true) ); + } + + // test copy assignment from a const MIRMesh + { + const mir::MIRMesh& cmesh = this->getMesh(); + EXPECT_TRUE(cmesh.isValid(true)); + + mir::MIRMesh mirCopy; + mirCopy = cmesh; + EXPECT_TRUE( mirCopy.isValid(true) ); + } +} + + +//------------------------------------------------------------------------------ + +int main(int argc, char* argv[]) +{ + int result = 0; + ::testing::InitGoogleTest(&argc, argv); + + axom::slic::UnitTestLogger logger; // create & initialize test logger, + + result = RUN_ALL_TESTS(); + return result; +} + + +#endif // MIR_MESH_TEST_H_ From ad822e8c09e7fb541cf71a5917a3f8c6d4659bfb Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 3 Jul 2019 15:21:47 -0700 Subject: [PATCH 15/23] Improves output of slam::Map's validity check and print functions --- src/axom/slam/Map.hpp | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/axom/slam/Map.hpp b/src/axom/slam/Map.hpp index 35d9c55090..60da43c560 100644 --- a/src/axom/slam/Map.hpp +++ b/src/axom/slam/Map.hpp @@ -15,7 +15,6 @@ #include #include -#include #include "axom/core.hpp" #include "axom/slic.hpp" @@ -483,23 +482,16 @@ bool Map::isValid(bool verboseOutput) const } - if(verboseOutput) + if(verboseOutput && !bValid) { std::stringstream sstr; sstr << "\n*** Detailed results of isValid on the map.\n"; - if(bValid) - { - sstr << "Map was valid." << std::endl; - } - else - { - sstr << "Map was NOT valid.\n" + sstr << "Map was NOT valid.\n" << sstr.str() << std::endl; - } - std::cout << sstr.str() << std::endl; + SLIC_DEBUG( sstr.str() ); } return bValid; @@ -510,10 +502,11 @@ template void Map::print() const { bool valid = isValid(true); - std::stringstream sstr; if (valid) { + std::stringstream sstr; + if (!m_set) { sstr << "** map is empty."; @@ -534,9 +527,14 @@ void Map::print() const } } } + + SLIC_INFO( sstr.str() ); + } + else + { + SLIC_INFO("Map was not valid."); } - std::cout << sstr.str() << std::endl; } From 1bfbecc5c1c362c0da049e863c35c8a2ddacf022 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 3 Jul 2019 15:22:31 -0700 Subject: [PATCH 16/23] Refactors how test meshes are set up in mir's MeshTester class --- src/axom/mir/MeshTester.cpp | 292 ++++++++++++++++-------------------- src/axom/mir/MeshTester.hpp | 22 ++- 2 files changed, 141 insertions(+), 173 deletions(-) diff --git a/src/axom/mir/MeshTester.cpp b/src/axom/mir/MeshTester.cpp index d4a7238aeb..6a8eb612e5 100644 --- a/src/axom/mir/MeshTester.cpp +++ b/src/axom/mir/MeshTester.cpp @@ -5,34 +5,30 @@ #include "MeshTester.hpp" +namespace numerics = axom::numerics; +namespace slam = axom::slam; + namespace axom { namespace mir { - -//-------------------------------------------------------------------------------- - -MeshTester::MeshTester() -{ - -} - -//-------------------------------------------------------------------------------- - -MeshTester::~MeshTester() -{ - -} //-------------------------------------------------------------------------------- MIRMesh MeshTester::initTestCaseOne() { + mir::CellTopologyData topoData; + mir::CellMapData mapData; + mir::CellData cellData; + VolumeFractions volFracs; + int numElements = 9; int numVertices = 16; + mir::VertSet verts(numVertices); // Construct a vertex set with 16 vertices + mir::ElemSet elems(numElements); // Construct an element set with 9 elements // Create the mesh connectivity information - std::vector evInds = { + topoData.m_evInds = { 0,4,5,1, // elem 0, card 4, start 0 1,5,6,2, // elem 1, card 4, start 4 2,6,7,3, // elem 2, card 4, start 8 @@ -44,10 +40,10 @@ MIRMesh MeshTester::initTestCaseOne() 10,14,15,11 // elem 8, card 4, start 32, end 36 }; - std::vector evBegins = { + topoData.m_evBegins = { 0,4,8,12,16,20,24,28,32,36 }; - std::vector veInds = { + topoData.m_veInds = { 0, // vert 0, card 1, start 0 0,1, // vert 1, card 2, start 1 1,2, // vert 2, card 2, start 3 @@ -65,25 +61,20 @@ MIRMesh MeshTester::initTestCaseOne() 7,8, // vert 14, card 2, start 33 8, // vert 15, card 1, start 35, end 36 }; - std::vector veBegins = { + topoData.m_veBegins = { 0,1,3,5,6,8,12,16,18,20,24,28,30,31,33,35,36 }; - mir::VertSet verts = mir::VertSet(numVertices); // Construct a vertex set with 16 vertices - mir::ElemSet elems = mir::ElemSet(numElements); // Construct an element set with 9 elements - - int numMaterials = 2; enum { GREEN = 0, BLUE = 1 }; - std::vector > elementVF; elementVF.resize(numMaterials); + volFracs.resize(numMaterials); + + volFracs[GREEN] = {1.0, 1.0, 1.0, 1.0, 0.5, 0.2, 0.2, 0.0, 0.0}; + volFracs[BLUE] = {0.0, 0.0, 0.0, 0.0, 0.5, 0.8, 0.8, 1.0, 1.0}; - std::vector greenVolumeFractions = {1.0, 1.0, 1.0, 1.0, 0.5, 0.2, 0.2, 0.0, 0.0}; - elementVF[GREEN] = greenVolumeFractions; - std::vector blueVolumeFractions = {0.0, 0.0, 0.0, 0.0, 0.5, 0.8, 0.8, 1.0, 1.0}; - elementVF[BLUE] = blueVolumeFractions; - std::vector points = + mapData.m_vertexPositions = { mir::Point2( 0.0, 3.0 ), mir::Point2( 1.0, 3.0 ), @@ -106,21 +97,13 @@ MIRMesh MeshTester::initTestCaseOne() mir::Point2( 3.0, 0.0 ) }; - CellTopologyData topology; - topology.m_evInds = evInds; - topology.m_evBegins = evBegins; - topology.m_veInds = veInds; - topology.m_veBegins = veBegins; - - CellMapData mapData; - mapData.m_elementDominantMaterials = {NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT}; + mapData.m_elementDominantMaterials = Vec(numElements, NULL_MAT); mapData.m_elementParents = { 0,1,2,3,4,5,6,7,8 }; // For the base mesh, the parents are always themselves - mapData.m_vertexPositions = points; - mapData.m_shapeTypes = {mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad}; + mapData.m_shapeTypes = Vec(numElements, mir::Shape::Quad); // Build the mesh mir::MIRMesh testMesh; - testMesh.initializeMesh(verts, elems, numMaterials, topology, mapData, elementVF); + testMesh.initializeMesh(verts, elems, numMaterials, topoData, mapData, volFracs); return testMesh; } @@ -129,11 +112,18 @@ MIRMesh MeshTester::initTestCaseOne() mir::MIRMesh MeshTester::initTestCaseTwo() { + mir::CellTopologyData topoData; + mir::CellMapData mapData; + mir::CellData cellData; + VolumeFractions volFracs; + int numElements = 9; int numVertices = 16; + mir::VertSet verts(numVertices); // Construct a vertex set with 16 vertices + mir::ElemSet elems(numElements); // Construct an element set with 9 elements // Create the mesh connectivity information - std::vector evInds = { + topoData.m_evInds = { 0,4,5,1, // elem 0, card 4, start 0 1,5,6,2, // elem 1, card 4, start 4 2,6,7,3, // elem 2, card 4, start 8 @@ -145,10 +135,10 @@ mir::MIRMesh MeshTester::initTestCaseTwo() 10,14,15,11 // elem 8, card 4, start 32, end 36 }; - std::vector evBegins = { + topoData.m_evBegins = { 0,4,8,12,16,20,24,28,32,36 }; - std::vector veInds = { + topoData.m_veInds = { 0, // vert 0, card 1, start 0 0,1, // vert 1, card 2, start 1 1,2, // vert 2, card 2, start 3 @@ -166,26 +156,19 @@ mir::MIRMesh MeshTester::initTestCaseTwo() 7,8, // vert 14, card 2, start 33 8, // vert 15, card 1, start 35, end 36 }; - std::vector veBegins = { + topoData.m_veBegins = { 0,1,3,5,6,8,12,16,18,20,24,28,30,31,33,35,36 }; - mir::VertSet verts = mir::VertSet(numVertices); // Construct a vertex set with 16 vertices - mir::ElemSet elems = mir::ElemSet(numElements); // Construct an element set with 9 elements - int numMaterials = 3; enum { BLUE = 0, RED = 1, ORANGE = 2 }; - std::vector > elementVF; elementVF.resize(numMaterials); + volFracs.resize(numMaterials); + volFracs[BLUE] = {1.0, 1.0, 1.0, 1.0, 0.5, 0.2, 0.2, 0.0, 0.0}; + volFracs[RED] = {0.0, 0.0, 0.0, 0.0, 0.3, 0.8, 0.0, 0.3, 1.0}; + volFracs[ORANGE] = {0.0, 0.0, 0.0, 0.0, 0.2, 0.0, 0.8, 0.7, 0.0}; - std::vector blueVolumeFractions = {1.0, 1.0, 1.0, 1.0, 0.5, 0.2, 0.2, 0.0, 0.0}; - elementVF[BLUE] = blueVolumeFractions; - std::vector redVolumeFractions = {0.0, 0.0, 0.0, 0.0, 0.3, 0.8, 0.0, 0.3, 1.0}; - elementVF[RED] = redVolumeFractions; - std::vector orangeVolumeFractions = {0.0, 0.0, 0.0, 0.0, 0.2, 0.0, 0.8, 0.7, 0.0}; - elementVF[ORANGE] = orangeVolumeFractions; - - std::vector points = + mapData.m_vertexPositions = { mir::Point2( 0.0, 3.0 ), mir::Point2( 1.0, 3.0 ), @@ -208,21 +191,13 @@ mir::MIRMesh MeshTester::initTestCaseTwo() mir::Point2( 3.0, 0.0 ) }; - CellTopologyData topology; - topology.m_evInds = evInds; - topology.m_evBegins = evBegins; - topology.m_veInds = veInds; - topology.m_veBegins = veBegins; - - CellMapData mapData; - mapData.m_elementDominantMaterials = {NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT}; + mapData.m_elementDominantMaterials = Vec(numElements, NULL_MAT); mapData.m_elementParents = { 0,1,2,3,4,5,6,7,8 }; // For the base mesh, the parents are always themselves - mapData.m_vertexPositions = points; - mapData.m_shapeTypes = {mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad}; + mapData.m_shapeTypes = Vec(numElements, mir::Shape::Quad); // Build the mesh mir::MIRMesh testMesh; - testMesh.initializeMesh(verts, elems, numMaterials, topology, mapData, elementVF); + testMesh.initializeMesh(verts, elems, numMaterials, topoData, mapData, volFracs); return testMesh; } @@ -231,21 +206,29 @@ mir::MIRMesh MeshTester::initTestCaseTwo() mir::MIRMesh MeshTester::initTestCaseThree() { - int numElements = 4; - int numVertices = 6; // OR create a middle triangle with all of one material, and then a ring of triangles around it that are full of the other material + mir::CellTopologyData topoData; + mir::CellMapData mapData; + mir::CellData cellData; + VolumeFractions volFracs; + + int numElements = 4; + int numVertices = 6; // OR create a middle triangle with all of one material, and then a ring of triangles around it that are full of the other material + + mir::VertSet verts = mir::VertSet(numVertices); + mir::ElemSet elems = mir::ElemSet(numElements); // Create the mesh connectivity information - std::vector evInds = { + topoData.m_evInds = { 0,1,2, // elem 0, card 3, start 0 1,3,4, // elem 1, card 3, start 3 1,4,2, // elem 2, card 3, start 6 2,4,5 // elem 3, card 3, start 9, end 12 }; - std::vector evBegins = { + topoData.m_evBegins = { 0,3,6,9,12 }; - std::vector veInds = { + topoData.m_veInds = { 0, // vert 0, card 1, start 0 0,1,2, // vert 1, card 3, start 1 0,2,3, // vert 2, card 3, start 4 @@ -253,24 +236,19 @@ mir::MIRMesh MeshTester::initTestCaseThree() 1,2,3, // vert 4, card 3, start 8 3 // vert 5, card 1, start 11, end 12 }; - std::vector veBegins = { + topoData.m_veBegins = { 0,1,4,7,8,11,12 }; - mir::VertSet verts = mir::VertSet(numVertices); // Construct a vertex set with 24 vertices - mir::ElemSet elems = mir::ElemSet(numElements); // Construct an element set with 8 elements int numMaterials = 2; enum { BLUE = 0, RED = 1, }; - std::vector > elementVF; elementVF.resize(numMaterials); - - std::vector blueVolumeFractions = {0.0, 0.5, 0.8, 0.5}; - elementVF[BLUE] = blueVolumeFractions; - std::vector redVolumeFractions = {1.0, 0.5, 0.2, 0.5}; - elementVF[RED] = redVolumeFractions; + volFracs.resize(numMaterials); + volFracs[BLUE] = {0.0, 0.5, 0.8, 0.5}; + volFracs[RED] = {1.0, 0.5, 0.2, 0.5}; - std::vector points = + mapData.m_vertexPositions = { mir::Point2( 1.0, 2.0 ), mir::Point2( 0.5, 1.0 ), @@ -280,22 +258,13 @@ mir::MIRMesh MeshTester::initTestCaseThree() mir::Point2( 2.0, 0.0 ) }; - - CellTopologyData topology; - topology.m_evInds = evInds; - topology.m_evBegins = evBegins; - topology.m_veInds = veInds; - topology.m_veBegins = veBegins; - - CellMapData mapData; - mapData.m_elementDominantMaterials = {NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT}; + mapData.m_elementDominantMaterials = Vec(numElements, NULL_MAT); mapData.m_elementParents = { 0,1,2,3 }; // For the base mesh, the parents are always themselves - mapData.m_vertexPositions = points; - mapData.m_shapeTypes = {mir::Shape::Triangle, mir::Shape::Triangle, mir::Shape::Triangle, mir::Shape::Triangle}; + mapData.m_shapeTypes = Vec(numElements, mir::Shape::Triangle); // Build the mesh mir::MIRMesh testMesh; - testMesh.initializeMesh(verts, elems, numMaterials, topology, mapData, elementVF); + testMesh.initializeMesh(verts, elems, numMaterials, topoData, mapData, volFracs); return testMesh; } @@ -304,11 +273,18 @@ mir::MIRMesh MeshTester::initTestCaseThree() mir::MIRMesh MeshTester::initTestCaseFour() { + mir::CellTopologyData topoData; + mir::CellMapData mapData; + mir::CellData cellData; + VolumeFractions volFracs; + int numElements = 9; int numVertices = 16; + mir::VertSet verts = mir::VertSet(numVertices); // Construct a vertex set with 16 vertices + mir::ElemSet elems = mir::ElemSet(numElements); // Construct an element set with 9 elements // Create the mesh connectivity information - std::vector evInds = { + topoData.m_evInds = { 0,4,5,1, // elem 0, card 4, start 0 1,5,6,2, // elem 1, card 4, start 4 2,6,7,3, // elem 2, card 4, start 8 @@ -320,10 +296,10 @@ mir::MIRMesh MeshTester::initTestCaseFour() 10,14,15,11 // elem 8, card 4, start 32, end 36 }; - std::vector evBegins = { + topoData.m_evBegins = { 0,4,8,12,16,20,24,28,32,36 }; - std::vector veInds = { + topoData.m_veInds = { 0, // vert 0, card 1, start 0 0,1, // vert 1, card 2, start 1 1,2, // vert 2, card 2, start 3 @@ -341,14 +317,12 @@ mir::MIRMesh MeshTester::initTestCaseFour() 7,8, // vert 14, card 2, start 33 8, // vert 15, card 1, start 35, end 36 }; - std::vector veBegins = { + topoData.m_veBegins = { 0,1,3,5,6,8,12,16,18,20,24,28,30,31,33,35,36 }; - mir::VertSet verts = mir::VertSet(numVertices); // Construct a vertex set with 16 vertices - mir::ElemSet elems = mir::ElemSet(numElements); // Construct an element set with 9 elements - std::vector points = + mapData.m_vertexPositions = { mir::Point2( 0.0, 3.0 ), mir::Point2( 1.0, 3.0 ), @@ -374,10 +348,15 @@ mir::MIRMesh MeshTester::initTestCaseFour() int numMaterials = 2; enum { GREEN = 0, BLUE = 1 }; - std::vector > elementVF; elementVF.resize(numMaterials); + volFracs.resize(numMaterials); - std::vector greenVolumeFractions; greenVolumeFractions.resize(numElements); - std::vector blueVolumeFractions; blueVolumeFractions.resize(numElements); + auto& greenVolumeFractions = volFracs[GREEN]; + auto& blueVolumeFractions = volFracs[BLUE]; + const auto& points = mapData.m_vertexPositions; + const auto& evInds = topoData.m_evInds; + + greenVolumeFractions.resize(numElements); + blueVolumeFractions.resize(numElements); // Generate the element volume fractions for the circle mir::Point2 circleCenter(1.5, 1.5); @@ -385,28 +364,24 @@ mir::MIRMesh MeshTester::initTestCaseFour() int gridSize = 1000; for (int i = 0; i < numElements; ++i) { - greenVolumeFractions[i] = calculatePercentOverlapMonteCarlo(gridSize, circleCenter, circleRadius, points[evInds[i * 4 + 0]], points[evInds[i * 4 + 1]], points[evInds[i * 4 + 2]], points[evInds[i * 4 + 3]]); - blueVolumeFractions[i] = 1.0 - greenVolumeFractions[i]; + auto vf = calculatePercentOverlapMonteCarlo(gridSize, + circleCenter, + circleRadius, + points[evInds[i * 4 + 0]], + points[evInds[i * 4 + 1]], + points[evInds[i * 4 + 2]], + points[evInds[i * 4 + 3]]); + greenVolumeFractions[i] = vf; + blueVolumeFractions[i] = 1.0 - vf; } - elementVF[GREEN] = greenVolumeFractions; - elementVF[BLUE] = blueVolumeFractions; - - CellTopologyData topology; - topology.m_evInds = evInds; - topology.m_evBegins = evBegins; - topology.m_veInds = veInds; - topology.m_veBegins = veBegins; - - CellMapData mapData; - mapData.m_elementDominantMaterials = {NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT, NULL_MAT}; + mapData.m_elementDominantMaterials = Vec(numElements, NULL_MAT); mapData.m_elementParents = { 0,1,2,3,4,5,6,7,8 }; // For the base mesh, the parents are always themselves - mapData.m_vertexPositions = points; - mapData.m_shapeTypes = {mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad, mir::Shape::Quad}; + mapData.m_shapeTypes = Vec(numElements, mir::Shape::Quad); // Build the mesh mir::MIRMesh testMesh; - testMesh.initializeMesh(verts, elems, numMaterials, topology, mapData, elementVF); + testMesh.initializeMesh(verts, elems, numMaterials, topoData, mapData, volFracs); return testMesh; } @@ -424,50 +399,39 @@ mir::MIRMesh MeshTester::createUniformGridTestCaseMesh(int gridSize, mir::Point2 int numMaterials = 2; enum { GREEN = 0, BLUE = 1 }; - std::vector > elementVF; elementVF.resize(numMaterials); - - std::vector greenVolumeFractions; greenVolumeFractions.resize(cellData.m_numElems); - std::vector blueVolumeFractions; blueVolumeFractions.resize(cellData.m_numElems); + VolumeFractions volFracs; + volFracs.resize(numMaterials); + volFracs[GREEN].resize(cellData.m_numElems); + volFracs[BLUE].resize(cellData.m_numElems); // Generate the element volume fractions for the circle - int numMonteCarloSamples = 100; + const int numMonteCarloSamples = 100; + auto& pos = cellData.m_mapData.m_vertexPositions; + const auto& evInds = cellData.m_topology.m_evInds; for (int i = 0; i < cellData.m_numElems; ++i) { - greenVolumeFractions[i] = calculatePercentOverlapMonteCarlo(numMonteCarloSamples, circleCenter, circleRadius, - cellData.m_mapData.m_vertexPositions[cellData.m_topology.m_evInds[i * 4 + 0]], - cellData.m_mapData.m_vertexPositions[cellData.m_topology.m_evInds[i * 4 + 1]], - cellData.m_mapData.m_vertexPositions[cellData.m_topology.m_evInds[i * 4 + 2]], - cellData.m_mapData.m_vertexPositions[cellData.m_topology.m_evInds[i * 4 + 3]]); - blueVolumeFractions[i] = 1.0 - greenVolumeFractions[i]; + auto vf = calculatePercentOverlapMonteCarlo(numMonteCarloSamples, + circleCenter, + circleRadius, + pos[evInds[i * 4 + 0]], + pos[evInds[i * 4 + 1]], + pos[evInds[i * 4 + 2]], + pos[evInds[i * 4 + 3]]); + volFracs[GREEN][i] = vf; + volFracs[BLUE][i] = 1.0 - vf; } - elementVF[GREEN] = greenVolumeFractions; - elementVF[BLUE] = blueVolumeFractions; - - std::vector elementParents;// For the base mesh, the parents are always themselves - std::vector elementDominantMaterials; - std::vector elementShapeTypes; - for (int i = 0; i < cellData.m_numElems; ++i) + cellData.m_mapData.m_elementDominantMaterials = Vec(cellData.m_numVerts, NULL_MAT); + cellData.m_mapData.m_shapeTypes = Vec(cellData.m_numVerts, mir::Shape::Quad); + cellData.m_mapData.m_elementParents.resize(cellData.m_numVerts); + for (auto i : elems.positions() ) { - elementParents.push_back(i); - elementDominantMaterials.push_back(NULL_MAT); - elementShapeTypes.push_back(mir::Shape::Quad); + cellData.m_mapData.m_elementParents[i] = i; } - CellTopologyData topology; - topology.m_evInds = cellData.m_topology.m_evInds; - topology.m_evBegins = cellData.m_topology.m_evBegins; - topology.m_veInds = cellData.m_topology.m_veInds; - topology.m_veBegins = cellData.m_topology.m_veBegins; - - CellMapData mapData; - mapData.m_elementDominantMaterials = elementDominantMaterials; - mapData.m_elementParents = elementParents; - mapData.m_vertexPositions = cellData.m_mapData.m_vertexPositions; - // Build the mesh mir::MIRMesh testMesh; - testMesh.initializeMesh(verts, elems, numMaterials, topology, mapData, elementVF); + testMesh.initializeMesh(verts, elems, numMaterials, cellData.m_topology, cellData.m_mapData, volFracs); return testMesh; } @@ -519,8 +483,13 @@ mir::CellData MeshTester::generateGrid(int gridSize) int numElements = gridSize * gridSize; int numVertices = (gridSize + 1) * (gridSize + 1); + mir::CellData data; + + data.m_numVerts = numVertices; + data.m_numElems = numElements; + // Generate the evInds - std::vector evInds; + auto& evInds = data.m_topology.m_evInds; for (int eID = 0; eID < numElements; ++eID) { int row = eID / gridSize; // note the integer division @@ -534,7 +503,7 @@ mir::CellData MeshTester::generateGrid(int gridSize) } // Generate the evBegins - std::vector evBegins; + auto& evBegins = data.m_topology.m_evBegins; evBegins.push_back(0); for (int i = 0; i < numElements; ++i) { @@ -542,8 +511,9 @@ mir::CellData MeshTester::generateGrid(int gridSize) } // Generate the veInds + auto& veInds = data.m_topology.m_veInds; + auto& veBegins = data.m_topology.m_veBegins; std::map > veInds_data; - std::vector veInds; for (int evInd_itr = 0; evInd_itr < numElements * 4; ++evInd_itr) { int currentElementID = evInd_itr / 4; // note the integer division @@ -561,7 +531,6 @@ mir::CellData MeshTester::generateGrid(int gridSize) } // Generate the veBegins - std::vector veBegins; veBegins.push_back(0); int currentIndexCount = 0; for (auto itr = veInds_data.begin(); itr != veInds_data.end(); itr++) @@ -571,7 +540,7 @@ mir::CellData MeshTester::generateGrid(int gridSize) } // Generate the vertex positions - std::vector points; + auto& points = data.m_mapData.m_vertexPositions; for (int y = gridSize; y > -1; --y) { for (int x = 0; x < gridSize + 1; ++x) @@ -580,15 +549,6 @@ mir::CellData MeshTester::generateGrid(int gridSize) } } - mir::CellData data; - data.m_numVerts = numVertices; - data.m_numElems = numElements; - data.m_topology.m_evInds = evInds; - data.m_topology.m_evBegins = evBegins; - data.m_topology.m_veInds = veInds; - data.m_topology.m_veBegins = veBegins; - data.m_mapData.m_vertexPositions = points; - // // Print out the results // printf("evInds: { "); // for (int i = 0; i < evInds.size(); i++) diff --git a/src/axom/mir/MeshTester.hpp b/src/axom/mir/MeshTester.hpp index 37160ae894..7d4ea33c40 100644 --- a/src/axom/mir/MeshTester.hpp +++ b/src/axom/mir/MeshTester.hpp @@ -20,9 +20,7 @@ #include "MIRUtilities.hpp" #include - -namespace numerics = axom::numerics; -namespace slam = axom::slam; +#include namespace axom { @@ -37,16 +35,25 @@ namespace mir */ class MeshTester { +public: + template + using Vec = std::vector; + + using IndexVec = Vec; + using VolFracVec = Vec; + using VolumeFractions = Vec; + + public: /** * \brief Default constructor. */ - MeshTester(); + MeshTester() = default; /** * \brief Default destructor. */ - ~MeshTester(); + ~MeshTester() = default; public: /** @@ -56,7 +63,7 @@ namespace mir * * \return The generated mesh. */ - MIRMesh initTestCaseOne(); + mir::MIRMesh initTestCaseOne(); /** * \brief Initializes an MIRMesh based on the example from Meredith and Childs 2010 paper. @@ -110,7 +117,7 @@ namespace mir mir::MIRMesh createUniformGridTestCaseMesh(int gridSize, mir::Point2 circleCenter, axom::float64 circleRadius); /** - * \brief Intializes a mesh to be used for validating the results of quad clipping. + * \brief Initializes a mesh to be used for validating the results of quad clipping. * * \note The mesh is a 3x3 uniform grid with 2 materials and element volume fraction such * that the mesh would be split horizontally through the middle. @@ -155,6 +162,7 @@ namespace mir * \return The number of corners of the quad that are within the circle. */ int circleQuadCornersOverlaps(mir::Point2 circleCenter, axom::float64 circleRadius, mir::Point2 quadP0, mir::Point2 quadP1, mir::Point2 quadP2, mir::Point2 quadP3); + }; } } From de268cf21594f1fd7e5cf292369d42083bb5e8c3 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 3 Jul 2019 15:45:08 -0700 Subject: [PATCH 17/23] Adds command line arguments to mir's tutorial example --- src/axom/mir/ZooClippingTables.hpp | 4 +- src/axom/mir/examples/mir_tutorial_simple.cpp | 252 +++++++++++++++--- 2 files changed, 217 insertions(+), 39 deletions(-) diff --git a/src/axom/mir/ZooClippingTables.hpp b/src/axom/mir/ZooClippingTables.hpp index 4802db44f7..de2b8be83c 100644 --- a/src/axom/mir/ZooClippingTables.hpp +++ b/src/axom/mir/ZooClippingTables.hpp @@ -9,7 +9,7 @@ /** * \file ZooClippingTables.hpp * - * \brief Contains the defintions for the clipping cases and enumerator + * \brief Contains the definitions for the clipping cases and enumerator * for the shape types in the zoo. */ @@ -26,4 +26,4 @@ namespace mir extern const std::vector > quadClipTableVec; } } -#endif \ No newline at end of file +#endif diff --git a/src/axom/mir/examples/mir_tutorial_simple.cpp b/src/axom/mir/examples/mir_tutorial_simple.cpp index 2217f64c4b..460a076b80 100644 --- a/src/axom/mir/examples/mir_tutorial_simple.cpp +++ b/src/axom/mir/examples/mir_tutorial_simple.cpp @@ -3,70 +3,248 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#include "axom/core.hpp" // for axom macros -#include "axom/mir.hpp" // for Mir classes & functions +#include "axom/core.hpp" +#include "axom/slic.hpp" #include "axom/slam.hpp" +#include "axom/mir.hpp" -#include -using Clock = std::chrono::high_resolution_clock; +#include // namespace aliases namespace numerics = axom::numerics; namespace slam = axom::slam; namespace mir = axom::mir; +namespace fs = axom::utilities::filesystem; //-------------------------------------------------------------------------------- +enum InputStatus +{ + SUCCESS, + INVALID, + SHOWHELP +}; + +struct Input +{ + int m_test_case; // valid values 1,2,3,4,5 + bool m_should_iterate; + int m_iter_count; + double m_iter_percent; + bool m_verbose; + std::string m_output_dir; + InputStatus m_status; + + Input() + : m_test_case(2) + , m_should_iterate(false) + , m_iter_count(100) + , m_iter_percent(.3) + , m_verbose(false) + , m_status(SUCCESS) + { + m_output_dir = fs::joinPath(AXOM_BIN_DIR, "mir_examples"); + } + + Input(int argc, char** argv) : Input() + { + for (int i = 1 ; i < argc ; /* increment i in loop */) + { + std::string arg = argv[i]; + if (arg == "--test-case") + { + m_test_case = std::stoi(argv[++i]); + } + else if (arg == "--output-dir") + { + m_output_dir = argv[++i]; + } + else if (arg == "--iter-count") + { + m_iter_count = std::stoi(argv[++i]); + m_should_iterate = true; + } + else if (arg == "--iter-percent") + { + m_iter_percent = std::stod(argv[++i]); + m_should_iterate = true; + } + else if (arg == "--verbose") + { + m_verbose = true; + } + else // help or unknown parameter + { + if(arg != "--help" && arg != "-h") + { + SLIC_WARNING("Unrecognized parameter: " << arg); + m_status = INVALID; + } + else + { + m_status = SHOWHELP; + } + return; + } + ++i; + } + + checkTestCase(); + checkOutputDir(); + checkIterationParams(); + } + + + bool shouldIterate() const { return m_should_iterate; } + int numIterations() const { return m_iter_count; } + int iterPercentage() const { return m_iter_percent; } + + + void showhelp() + { + std::cout + << "Argument usage:" + "\n --help Show this help message." + "\n --test-case N Mesh test case. Default N = 2." + "\n Valid values {1,2,3,4,5}" + "\n --output-dir dir Directory for output mesh" + "\n Default is: '${AXOM_BIN_DIR}/mir_examples'" + "\n --iter-count N Number of iterations for iterative algorithm" + "\n Defaults to 100." + "\n Setting a value triggers iterative algorithm" + "\n --iter-percent D Volume diff percentage for iterative algorithm" + "\n Must be between 0 and 1. Defaults to 0.3" + "\n Setting a value triggers iterative algorithm" + "\n --verbose Increases verbosity of output" + << std::endl << std::endl; + }; + +private: + void checkTestCase() + { + if(m_test_case < 1 || m_test_case > 5) + { + m_status = INVALID; + SLIC_WARNING("Invalid test case " << m_test_case); + } + } + + void checkOutputDir() + { + if(! fs::pathExists(m_output_dir)) + { + fs::makeDirsForPath(m_output_dir); + } + } + + void checkIterationParams() + { + if(m_should_iterate) + { + if(m_iter_count < 1) + { + m_status = INVALID; + SLIC_WARNING("Invalid iteration count " << m_iter_count); + } + + if(m_iter_percent <= 0. || m_iter_percent > 1.) + { + m_status = INVALID; + SLIC_WARNING("Invalid iteration percentage " << m_iter_percent); + } + } + } + +}; + /*! * \brief Tutorial main showing how to initialize test cases and perform mir. */ -int main( int AXOM_NOT_USED(argc), char** AXOM_NOT_USED(argv) ) +int main(int argc, char** argv) { + axom::slic::UnitTestLogger logger; // create & initialize test logger + axom::slic::setLoggingMsgLevel( axom::slic::message::Info ); - // Intialize a mesh for testing MIR - auto startTime = Clock::now(); + // Parse arguments + Input params(argc, argv); + + if (params.m_status != SUCCESS) + { + if (params.m_status == SHOWHELP) + { + params.showhelp(); + return 0; + } + else if (params.m_status == INVALID) + { + params.showhelp(); + return 1; + } + } + + mir::MIRMesh testMesh; mir::MeshTester tester; - // mir::MIRMesh testMesh = tester.initTestCaseOne(); - mir::MIRMesh testMesh = tester.initTestCaseTwo(); - // mir::MIRMesh testMesh = tester.initTestCaseThree(); - // mir::MIRMesh testMesh = tester.initTestCaseFour(); - // mir::MIRMesh testMesh = tester.initTestCaseFive(25, 12); - auto endTime = Clock::now(); - std::cout << "Mesh init time: " << std::chrono::duration_cast(endTime - startTime).count() << " ms" << std::endl; + auto timer = axom::utilities::Timer(true); + + switch(params.m_test_case) + { + case 1: testMesh = tester.initTestCaseOne(); break; + case 2: testMesh = tester.initTestCaseTwo(); break; + case 3: testMesh = tester.initTestCaseThree(); break; + case 4: testMesh = tester.initTestCaseFour(); break; + case 5: testMesh = tester.initTestCaseFive(25, 12); break; + } + + timer.stop(); + SLIC_INFO( "Mesh init time: " << timer.elapsedTimeInMilliSec() << " ms."); + + SLIC_INFO("Test mesh is " << (testMesh.isValid(true) ? "" : " NOT") << " valid."); + + if(params.m_verbose) + { + SLIC_INFO("Initial mesh:"); + testMesh.print(); + } // Begin material interface reconstruction - startTime = Clock::now(); + timer.start(); mir::MIRMesh processedMesh; - mir::InterfaceReconstructor reconstructor; - reconstructor.computeReconstructedInterface(testMesh, processedMesh); // Process once, with original Meredith algorithm - // reconstructor.computeReconstructedInterfaceIterative(testMesh, 100, 0.3, processedMesh); // 100 iterations, 20 percent with iterative Meredith algorithm + + if(! params.shouldIterate()) // Process once, with original Meredith algorithm + { + reconstructor.computeReconstructedInterface(testMesh, processedMesh); + } + else // use iterative algorithm + { + int n = params.numIterations(); + double p = params.iterPercentage(); + + reconstructor.computeReconstructedInterfaceIterative(testMesh, n, p, processedMesh); + } - endTime = Clock::now(); - std::cout << "Material interface reconstruction time: " << std::chrono::duration_cast(endTime - startTime).count() << " ms" << std::endl; + timer.stop(); + SLIC_INFO( "Reconstruction time: " << timer.elapsedTimeInMilliSec() << " ms."); // Output results - processedMesh.writeMeshToFile("/Users/sterbentz3/Desktop/processedMesh.vtk"); + std::string dir = params.m_output_dir; + processedMesh.writeMeshToFile( fs::joinPath(dir, "processedMesh.vtk") ); - std::vector > materialVolumeFractionsElement = processedMesh.computeOriginalElementVolumeFractions(); - - // // Print out the results - // printf("elementVolumeFractions: {\n"); - // for (int matID = 0; matID < processedMesh.m_numMaterials; ++matID) - // { - // printf("Material %d: {", matID); - // for (int eID = 0; eID < materialVolumeFractionsElement[matID].size(); ++eID) - // { - // printf(" %f,", materialVolumeFractionsElement[matID][eID]); - // } - // printf("}\n"); - // } - // printf("}\n"); - // // END TESTING + using VolFracs = std::vector >; + timer.start(); + VolFracs materialVolumeFractionsElement = processedMesh.computeOriginalElementVolumeFractions(); + timer.stop(); + SLIC_INFO( "Computing volumes took: " << timer.elapsedTimeInMilliSec() << " ms."); + + if(params.m_verbose) + { + SLIC_INFO("Final mesh:"); + processedMesh.print(); + } return 0; } -//-------------------------------------------------------------------------------- \ No newline at end of file +//-------------------------------------------------------------------------------- From 654c51bc43b82d40e39371f36df9555217e0734d Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 3 Jul 2019 16:04:21 -0700 Subject: [PATCH 18/23] Minor update to concentric circles example --- .../mir/examples/mir_concentric_circles.cpp | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/axom/mir/examples/mir_concentric_circles.cpp b/src/axom/mir/examples/mir_concentric_circles.cpp index 6eb3493bf6..1455d16f81 100644 --- a/src/axom/mir/examples/mir_concentric_circles.cpp +++ b/src/axom/mir/examples/mir_concentric_circles.cpp @@ -4,31 +4,30 @@ // SPDX-License-Identifier: (BSD-3-Clause) #include "axom/core.hpp" // for axom macros +#include "axom/slic.hpp" #include "axom/mir.hpp" // for Mir classes & functions -#include "axom/slam.hpp" -#include #include -using Clock = std::chrono::high_resolution_clock; - // namespace aliases -namespace numerics = axom::numerics; -namespace slam = axom::slam; namespace mir = axom::mir; //-------------------------------------------------------------------------------- -/*! - * \brief Tutorial main - */ +std::string usageString() +{ + return "Args are "; +} + int main( int argc, char** argv ) { + axom::slic::UnitTestLogger logger; // create & initialize test logger + axom::slic::setLoggingMsgLevel( axom::slic::message::Info ); if (argc != 4) { - printf("Incorrect number of args. Args are \n"); - return 0; + SLIC_WARNING("Incorrect number of args. " << usageString() ); + return 1; } try @@ -38,20 +37,21 @@ int main( int argc, char** argv ) int numCircles = std::stoi(argv[2]); std::string outputFilePath = std::string(argv[3]); - // Intialize a mesh for testing MIR - auto startTime = Clock::now(); + // Initialize a mesh for testing MIR + auto timer = axom::utilities::Timer(true); mir::MeshTester tester; mir::MIRMesh testMesh = tester.initTestCaseFive(gridSize, numCircles); - auto endTime = Clock::now(); - std::cout << "Mesh init time: " << std::chrono::duration_cast(endTime - startTime).count() << " ms" << std::endl; + timer.stop(); + SLIC_INFO("Mesh init time: " << timer.elapsedTimeInMilliSec() << " ms."); // Begin material interface reconstruction - startTime = Clock::now(); + timer.start(); mir::InterfaceReconstructor reconstructor; mir::MIRMesh processedMesh; reconstructor.computeReconstructedInterface(testMesh, processedMesh); - endTime = Clock::now(); - std::cout << "Material interface reconstruction time: " << std::chrono::duration_cast(endTime - startTime).count() << " ms" << std::endl; + timer.stop(); + SLIC_INFO("Material interface reconstruction time: " + << timer.elapsedTimeInMilliSec() << " ms."); // Output results processedMesh.writeMeshToFile(outputFilePath + "outputConcentricCircles.vtk"); @@ -60,12 +60,12 @@ int main( int argc, char** argv ) } catch (std::invalid_argument const &e) { - printf("Bad input. Arguments are \n"); - return 0; + SLIC_WARNING("Bad input. " << usageString() ); + return 1; } catch (std::out_of_range const &e) { - printf("Integer overflow. Arguments are \n"); - return 0; + SLIC_WARNING("Integer overflow. " << usageString() ); + return 1; } -} \ No newline at end of file +} From 5979ad6ff3b53efefef755d492f7b372c559d030 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 3 Jul 2019 21:22:27 -0700 Subject: [PATCH 19/23] Adds a linear interpolation function to axom::core::utilities Implementation and tests implemented by M. Sterbentz and moved from mir component. --- src/axom/core/tests/utils_utilities.cpp | 31 +++++++++++++++++++++++++ src/axom/core/utilities/Utilities.hpp | 17 +++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/axom/core/tests/utils_utilities.cpp b/src/axom/core/tests/utils_utilities.cpp index 85c092b7e8..1b11d57a25 100644 --- a/src/axom/core/tests/utils_utilities.cpp +++ b/src/axom/core/tests/utils_utilities.cpp @@ -116,3 +116,34 @@ TEST(core_Utilities,minmax) EXPECT_EQ(a, axom::utilities::max(a,b)); } } + +TEST(core_Utilities, lerp) +{ + std::cout<<"Testing linear interpolation (lerp) function."<< std::endl; + + double f0 = 50.0; + double f1 = 100.0; + + // Test end points + { + EXPECT_DOUBLE_EQ( f0, axom::utilities::lerp(f0, f1, 0.) ); + EXPECT_DOUBLE_EQ( f1, axom::utilities::lerp(f1, f0, 0.) ); + + EXPECT_DOUBLE_EQ( f1, axom::utilities::lerp(f0, f1, 1.) ); + EXPECT_DOUBLE_EQ( f0, axom::utilities::lerp(f1, f0, 1.) ); + } + + // Test midpoint + { + double t = 0.5; + double exp = 75.; + EXPECT_DOUBLE_EQ( exp, axom::utilities::lerp(f0, f1, t)); + } + + // Another test + { + double t = 0.66; + double exp = 83.; + EXPECT_DOUBLE_EQ( exp, axom::utilities::lerp(f0, f1, t)); + } +} diff --git a/src/axom/core/utilities/Utilities.hpp b/src/axom/core/utilities/Utilities.hpp index d891db13e2..522cea8509 100644 --- a/src/axom/core/utilities/Utilities.hpp +++ b/src/axom/core/utilities/Utilities.hpp @@ -96,6 +96,22 @@ inline T log2( T& val) return std::log2(val); } + +/*! + * \brief Linearly interpolates between two values + * \param [in] val0 The first value + * \param [in] val2 The second value + * \param [in] t The interpolation parameter. + * \return The interpolated value + */ +template < typename T > +inline AXOM_HOST_DEVICE +T lerp( T v0, T v1, T t) +{ + constexpr T one = T(1); + return (one-t)*v0 + t*v1; +} + /*! * \brief Clamps an input value to a given range. * \param [in] val The value to clamp. @@ -113,7 +129,6 @@ T clampVal( T val, T lower, T upper ) : (val > upper) ? upper : val; } - /*! * \brief Clamps the upper range on an input value * From ef0b27e40ed930f0ef927a3354665af8e95fb551 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 3 Jul 2019 21:25:48 -0700 Subject: [PATCH 20/23] Mir component now depends on primal --- src/axom/mainpage.md | 2 +- src/axom/mir/CMakeLists.txt | 2 +- src/docs/dependencies.dot | 2 +- src/index.rst | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/axom/mainpage.md b/src/axom/mainpage.md index 84f42826ad..366e41642b 100644 --- a/src/axom/mainpage.md +++ b/src/axom/mainpage.md @@ -21,7 +21,7 @@ Dependencies between components are as follows: - Slic optionally depends on Lumberjack - Slam, Primal, Mint, Quest, Spin, and Sidre depend on Slic - Mint optionally depends on Sidre -- Mir depends on Slic and Slam +- Mir depends on Slic, Slam and Primal - Spin depends on Primal and Slam - Quest depends on Slam, Primal, Spin, and Mint diff --git a/src/axom/mir/CMakeLists.txt b/src/axom/mir/CMakeLists.txt index 65104b39b1..ef829028f0 100644 --- a/src/axom/mir/CMakeLists.txt +++ b/src/axom/mir/CMakeLists.txt @@ -10,7 +10,7 @@ # Check necessary dependencies #------------------------------------------------------------------------------ axom_component_requires(NAME MIR - COMPONENTS SLIC SLAM) + COMPONENTS SLIC SLAM PRIMAL) #------------------------------------------------------------------------------ # Specify all headers/sources diff --git a/src/docs/dependencies.dot b/src/docs/dependencies.dot index cddf4cd4df..5a5b7fef92 100644 --- a/src/docs/dependencies.dot +++ b/src/docs/dependencies.dot @@ -2,7 +2,7 @@ digraph dependencies { quest -> {slam primal mint spin}; {quest slam primal mint spin} -> {slic core}; mint -> sidre [style="dashed"]; - mir -> {slic core slam}; + mir -> {slic core slam primal}; spin -> {slam primal}; sidre -> {slic core}; slic -> core; diff --git a/src/index.rst b/src/index.rst index afcf6266ee..393cba7c82 100644 --- a/src/index.rst +++ b/src/index.rst @@ -88,7 +88,7 @@ Dependencies between modules are as follows: - Slic optionally depends on Lumberjack - Slam, Spin, Primal, Mint, Quest, and Sidre depend on Slic - Mint optionally depends on Sidre -- Mir depends on Slic and Slam +- Mir depends on Slic, Slam and Primal - Quest depends on Slam, Spin, Primal, and Mint The figure below summarizes the dependencies between the modules. Solid links From 557e9109506cda4c22d027a6a853ba2f717398a4 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 3 Jul 2019 21:29:54 -0700 Subject: [PATCH 21/23] Mir implementation now uses primal for its Point class and for cell areas This replaces the custom Point2 class and lerp functionality. --- src/axom/mir/CellGenerator.cpp | 8 +- src/axom/mir/MIRMesh.cpp | 13 +- src/axom/mir/MIRMeshTypes.hpp | 33 +--- src/axom/mir/MIRUtilities.hpp | 54 +---- src/axom/mir/MeshTester.cpp | 231 ++++++++++++---------- src/axom/mir/MeshTester.hpp | 20 +- src/axom/mir/tests/mir_cell_generator.cpp | 42 ++-- src/axom/mir/tests/mir_utilities.cpp | 71 ------- 8 files changed, 178 insertions(+), 294 deletions(-) diff --git a/src/axom/mir/CellGenerator.cpp b/src/axom/mir/CellGenerator.cpp index c81c535f59..3e5537325f 100644 --- a/src/axom/mir/CellGenerator.cpp +++ b/src/axom/mir/CellGenerator.cpp @@ -91,7 +91,8 @@ void CellGenerator::generateVertexPositions(const mir::Shape shapeType, int vIDFrom = mir::utilities::getEdgeEndpoint(shapeType, vID, true); int vIDTo = mir::utilities::getEdgeEndpoint(shapeType, vID, false); - out_cellData.m_mapData.m_vertexPositions.push_back( mir::utilities::interpolateVertexPosition( vertexPositions[vIDFrom], vertexPositions[vIDTo], tValues[vID] ) ); + out_cellData.m_mapData.m_vertexPositions.push_back( + mir::Point2::lerp( vertexPositions[vIDFrom], vertexPositions[vIDTo], tValues[vID] ) ); } } } @@ -123,7 +124,8 @@ void CellGenerator::generateVertexVolumeFractions(const mir::Shape shapeType, int vIDFrom = mir::utilities::getEdgeEndpoint(shapeType, vID, true); int vIDTo = mir::utilities::getEdgeEndpoint(shapeType, vID, false); - out_cellData.m_mapData.m_vertexVolumeFractions[matID].push_back( mir::utilities::lerpFloat( vertexVF[matID][vIDFrom], vertexVF[matID][vIDTo], tValues[vID] ) ); + out_cellData.m_mapData.m_vertexVolumeFractions[matID].push_back( + axom::utilities::lerp( vertexVF[matID][vIDFrom], vertexVF[matID][vIDTo], tValues[vID] ) ); } } } @@ -215,4 +217,4 @@ mir::Shape CellGenerator::determineElementShapeType(const Shape parentShapeType } -} \ No newline at end of file +} diff --git a/src/axom/mir/MIRMesh.cpp b/src/axom/mir/MIRMesh.cpp index 96130fed54..60ad504887 100644 --- a/src/axom/mir/MIRMesh.cpp +++ b/src/axom/mir/MIRMesh.cpp @@ -6,6 +6,7 @@ #include "MIRMesh.hpp" #include "axom/core.hpp" +#include "axom/primal.hpp" namespace axom { @@ -310,7 +311,7 @@ void MIRMesh::print() printf("vertexPositions: { "); for (int i = 0; i < m_verts.size(); ++i) { - printf("{%.2f, %.2f} ", m_vertexPositions[i].m_x, m_vertexPositions[i].m_y); + printf("{%.2f, %.2f} ", m_vertexPositions[i][0], m_vertexPositions[i][1]); } printf("}\n"); @@ -396,7 +397,7 @@ void MIRMesh::writeMeshToFile(std::string filename) // write positions for (int vID = 0; vID < m_verts.size(); ++vID) { - meshfile << m_vertexPositions[vID].m_x << " " << m_vertexPositions[vID].m_y << " 0\n"; // must always set all 3 coords; set Z=0 for 2D + meshfile << m_vertexPositions[vID][0] << " " << m_vertexPositions[vID][1] << " 0\n"; // must always set all 3 coords; set Z=0 for 2D } meshfile << "\nCELLS " << m_elems.size() << " " << m_meshTopology.m_evInds.size() + m_elems.size(); @@ -486,13 +487,7 @@ axom::float64 MIRMesh::computeTriangleArea(Point2 p0, Point2 p1, Point2 p2) { - axom::float64 a = sqrt( ((p1.m_x - p0.m_x) * (p1.m_x - p0.m_x)) + ((p1.m_y - p0.m_y) * (p1.m_y - p0.m_y)) );// the distance from p0 to p1 - axom::float64 b = sqrt( ((p2.m_x - p1.m_x) * (p2.m_x - p1.m_x)) + ((p2.m_y - p1.m_y) * (p2.m_y - p1.m_y)) );// the distance from p1 to p2 - axom::float64 c = sqrt( ((p0.m_x - p2.m_x) * (p0.m_x - p2.m_x)) + ((p0.m_y - p2.m_y) * (p0.m_y - p2.m_y)) );// the distance from p2 to p0 - - axom::float64 s = (a + b + c) / 2; // the semi-perimeter of the triangle - - return sqrt(s * (s - a) * (s - b) * (s - c)); + return primal::Triangle(p0,p1,p2).area(); } //-------------------------------------------------------------------------------- diff --git a/src/axom/mir/MIRMeshTypes.hpp b/src/axom/mir/MIRMeshTypes.hpp index 9daed29cbb..7ec2c79ba2 100644 --- a/src/axom/mir/MIRMeshTypes.hpp +++ b/src/axom/mir/MIRMeshTypes.hpp @@ -14,13 +14,10 @@ #include "axom/core.hpp" // for axom macros -// #include "axom/mir.hpp" // for Mir classes & functions #include "axom/slam.hpp" +#include "axom/primal.hpp" -namespace numerics = axom::numerics; -namespace slam = axom::slam; - namespace axom { namespace mir @@ -36,32 +33,8 @@ namespace mir Hexahedron }; - /** - * \brief Simple 2D Point class for example - */ - struct Point2 - { - Point2(double x = 0., double y = 0.) : m_x(x), m_y(y) {} - - Point2(const Point2& other) : m_x(other.m_x), m_y(other.m_y) {} - - Point2& operator=(const Point2& other) - { m_x = other.m_x; m_y = other.m_y; return *this; } - - Point2& operator+=(const Point2& other) - { m_x += other.m_x; m_y += other.m_y; return *this; } - Point2& operator/=(double val) - { m_x /= val; m_y += val; return *this; } - - double& operator[] (int i) { return (i==0) ? m_x : m_y; } - const double& operator[] (int i) const { return (i==0) ? m_x : m_y; } - - friend std::ostream& operator<<(std::ostream& os, const Point2& pt) - { return os << "{x:" << pt.m_x << ", y:" << pt.m_y <<"}"; } - - double m_x, m_y; - }; + using Point2 = primal::Point; // SET TYPE ALIASES using PosType = slam::DefaultPositionType; @@ -88,4 +61,4 @@ namespace mir using IntMap = slam::Map< BaseSet, int >; } } -#endif \ No newline at end of file +#endif diff --git a/src/axom/mir/MIRUtilities.hpp b/src/axom/mir/MIRUtilities.hpp index 0f3c75c084..69c5eb3c17 100644 --- a/src/axom/mir/MIRUtilities.hpp +++ b/src/axom/mir/MIRUtilities.hpp @@ -102,44 +102,6 @@ namespace utilities return numVertices; } -//-------------------------------------------------------------------------------- - - /** - * \brief Performs linear interpolation between the two given float values. - * - * \param f0 The first float value. - * \param f1 The second float value. - * \param t The percent of the distance from the first float value to the second. - * - * \return The interpolated value. - */ - inline axom::float64 lerpFloat(const axom::float64 f0, - const axom::float64 f1, - const axom::float64 t) - { - return (1 - t) * f0 + t * f1; - } - -//-------------------------------------------------------------------------------- - - /** - * \brief Performs linear interpolation between the two vertex positions. - * - * \param vertexOnePos The position of the first vertex. - * \param vertexTwoPos The position of the second vertex. - * \param t The percent of the distance from vertex one to vertex two to interpolate at. - * - * \return The interpolated position. - */ - inline mir::Point2 interpolateVertexPosition(const mir::Point2& vertexOnePos, - const mir::Point2& vertexTwoPos, - const float t) - { - mir::Point2 interpolatedPoint; - interpolatedPoint.m_x = lerpFloat(vertexOnePos.m_x, vertexTwoPos.m_x, t); - interpolatedPoint.m_y = lerpFloat(vertexOnePos.m_y, vertexTwoPos.m_y, t); - return interpolatedPoint; - } //-------------------------------------------------------------------------------- @@ -259,20 +221,6 @@ namespace utilities return -1; } -//-------------------------------------------------------------------------------- - -/** - * \brief Calculate the distance between the two given points. - * - * \param p0 The first point. - * \param p1 The second point. - * - * \return The distance between the two points. - */ -inline axom::float64 distance(mir::Point2 p0, mir::Point2 p1) -{ - return sqrt( ((p1.m_x - p0.m_x) * (p1.m_x - p0.m_x)) + ((p1.m_y - p0.m_y) * (p1.m_y - p0.m_y)) ); -} //-------------------------------------------------------------------------------- @@ -280,4 +228,4 @@ inline axom::float64 distance(mir::Point2 p0, mir::Point2 p1) } } -#endif \ No newline at end of file +#endif diff --git a/src/axom/mir/MeshTester.cpp b/src/axom/mir/MeshTester.cpp index 6a8eb612e5..f64e310c8c 100644 --- a/src/axom/mir/MeshTester.cpp +++ b/src/axom/mir/MeshTester.cpp @@ -76,25 +76,25 @@ MIRMesh MeshTester::initTestCaseOne() mapData.m_vertexPositions = { - mir::Point2( 0.0, 3.0 ), - mir::Point2( 1.0, 3.0 ), - mir::Point2( 2.0, 3.0 ), - mir::Point2( 3.0, 3.0 ), - - mir::Point2( 0.0, 2.0 ), - mir::Point2( 1.0, 2.0 ), - mir::Point2( 2.0, 2.0 ), - mir::Point2( 3.0, 2.0 ), - - mir::Point2( 0.0, 1.0 ), - mir::Point2( 1.0, 1.0 ), - mir::Point2( 2.0, 1.0 ), - mir::Point2( 3.0, 1.0 ), - - mir::Point2( 0.0, 0.0 ), - mir::Point2( 1.0, 0.0 ), - mir::Point2( 2.0, 0.0 ), - mir::Point2( 3.0, 0.0 ) + mir::Point2::make_point( 0.0, 3.0 ), + mir::Point2::make_point( 1.0, 3.0 ), + mir::Point2::make_point( 2.0, 3.0 ), + mir::Point2::make_point( 3.0, 3.0 ), + + mir::Point2::make_point( 0.0, 2.0 ), + mir::Point2::make_point( 1.0, 2.0 ), + mir::Point2::make_point( 2.0, 2.0 ), + mir::Point2::make_point( 3.0, 2.0 ), + + mir::Point2::make_point( 0.0, 1.0 ), + mir::Point2::make_point( 1.0, 1.0 ), + mir::Point2::make_point( 2.0, 1.0 ), + mir::Point2::make_point( 3.0, 1.0 ), + + mir::Point2::make_point( 0.0, 0.0 ), + mir::Point2::make_point( 1.0, 0.0 ), + mir::Point2::make_point( 2.0, 0.0 ), + mir::Point2::make_point( 3.0, 0.0 ) }; mapData.m_elementDominantMaterials = Vec(numElements, NULL_MAT); @@ -170,25 +170,25 @@ mir::MIRMesh MeshTester::initTestCaseTwo() mapData.m_vertexPositions = { - mir::Point2( 0.0, 3.0 ), - mir::Point2( 1.0, 3.0 ), - mir::Point2( 2.0, 3.0 ), - mir::Point2( 3.0, 3.0 ), - - mir::Point2( 0.0, 2.0 ), - mir::Point2( 1.0, 2.0 ), - mir::Point2( 2.0, 2.0 ), - mir::Point2( 3.0, 2.0 ), - - mir::Point2( 0.0, 1.0 ), - mir::Point2( 1.0, 1.0 ), - mir::Point2( 2.0, 1.0 ), - mir::Point2( 3.0, 1.0 ), - - mir::Point2( 0.0, 0.0 ), - mir::Point2( 1.0, 0.0 ), - mir::Point2( 2.0, 0.0 ), - mir::Point2( 3.0, 0.0 ) + mir::Point2::make_point( 0.0, 3.0 ), + mir::Point2::make_point( 1.0, 3.0 ), + mir::Point2::make_point( 2.0, 3.0 ), + mir::Point2::make_point( 3.0, 3.0 ), + + mir::Point2::make_point( 0.0, 2.0 ), + mir::Point2::make_point( 1.0, 2.0 ), + mir::Point2::make_point( 2.0, 2.0 ), + mir::Point2::make_point( 3.0, 2.0 ), + + mir::Point2::make_point( 0.0, 1.0 ), + mir::Point2::make_point( 1.0, 1.0 ), + mir::Point2::make_point( 2.0, 1.0 ), + mir::Point2::make_point( 3.0, 1.0 ), + + mir::Point2::make_point( 0.0, 0.0 ), + mir::Point2::make_point( 1.0, 0.0 ), + mir::Point2::make_point( 2.0, 0.0 ), + mir::Point2::make_point( 3.0, 0.0 ) }; mapData.m_elementDominantMaterials = Vec(numElements, NULL_MAT); @@ -250,12 +250,12 @@ mir::MIRMesh MeshTester::initTestCaseThree() mapData.m_vertexPositions = { - mir::Point2( 1.0, 2.0 ), - mir::Point2( 0.5, 1.0 ), - mir::Point2( 1.5, 1.0 ), - mir::Point2( 0.0, 0.0 ), - mir::Point2( 1.0, 0.0 ), - mir::Point2( 2.0, 0.0 ) + mir::Point2::make_point( 1.0, 2.0 ), + mir::Point2::make_point( 0.5, 1.0 ), + mir::Point2::make_point( 1.5, 1.0 ), + mir::Point2::make_point( 0.0, 0.0 ), + mir::Point2::make_point( 1.0, 0.0 ), + mir::Point2::make_point( 2.0, 0.0 ) }; mapData.m_elementDominantMaterials = Vec(numElements, NULL_MAT); @@ -324,25 +324,25 @@ mir::MIRMesh MeshTester::initTestCaseFour() mapData.m_vertexPositions = { - mir::Point2( 0.0, 3.0 ), - mir::Point2( 1.0, 3.0 ), - mir::Point2( 2.0, 3.0 ), - mir::Point2( 3.0, 3.0 ), - - mir::Point2( 0.0, 2.0 ), - mir::Point2( 1.0, 2.0 ), - mir::Point2( 2.0, 2.0 ), - mir::Point2( 3.0, 2.0 ), - - mir::Point2( 0.0, 1.0 ), - mir::Point2( 1.0, 1.0 ), - mir::Point2( 2.0, 1.0 ), - mir::Point2( 3.0, 1.0 ), - - mir::Point2( 0.0, 0.0 ), - mir::Point2( 1.0, 0.0 ), - mir::Point2( 2.0, 0.0 ), - mir::Point2( 3.0, 0.0 ) + mir::Point2::make_point( 0.0, 3.0 ), + mir::Point2::make_point( 1.0, 3.0 ), + mir::Point2::make_point( 2.0, 3.0 ), + mir::Point2::make_point( 3.0, 3.0 ), + + mir::Point2::make_point( 0.0, 2.0 ), + mir::Point2::make_point( 1.0, 2.0 ), + mir::Point2::make_point( 2.0, 2.0 ), + mir::Point2::make_point( 3.0, 2.0 ), + + mir::Point2::make_point( 0.0, 1.0 ), + mir::Point2::make_point( 1.0, 1.0 ), + mir::Point2::make_point( 2.0, 1.0 ), + mir::Point2::make_point( 3.0, 1.0 ), + + mir::Point2::make_point( 0.0, 0.0 ), + mir::Point2::make_point( 1.0, 0.0 ), + mir::Point2::make_point( 2.0, 0.0 ), + mir::Point2::make_point( 3.0, 0.0 ) }; int numMaterials = 2; @@ -359,7 +359,7 @@ mir::MIRMesh MeshTester::initTestCaseFour() blueVolumeFractions.resize(numElements); // Generate the element volume fractions for the circle - mir::Point2 circleCenter(1.5, 1.5); + auto circleCenter = mir::Point2::make_point(1.5, 1.5); axom::float64 circleRadius = 1.25; int gridSize = 1000; for (int i = 0; i < numElements; ++i) @@ -388,7 +388,7 @@ mir::MIRMesh MeshTester::initTestCaseFour() //-------------------------------------------------------------------------------- -mir::MIRMesh MeshTester::createUniformGridTestCaseMesh(int gridSize, mir::Point2 circleCenter, axom::float64 circleRadius) +mir::MIRMesh MeshTester::createUniformGridTestCaseMesh(int gridSize, const mir::Point2& circleCenter, axom::float64 circleRadius) { // Generate the mesh topology mir::CellData cellData = generateGrid(gridSize); @@ -438,40 +438,55 @@ mir::MIRMesh MeshTester::createUniformGridTestCaseMesh(int gridSize, mir::Point2 //-------------------------------------------------------------------------------- -axom::float64 MeshTester::calculatePercentOverlapMonteCarlo(int gridSize, mir::Point2 circleCenter, axom::float64 circleRadius, mir::Point2 quadP0, mir::Point2 quadP1, mir::Point2 quadP2, mir::Point2 quadP3) +axom::float64 MeshTester::calculatePercentOverlapMonteCarlo( + int gridSize, + const mir::Point2& circleCenter, + axom::float64 circleRadius, + const mir::Point2& quadP0, + const mir::Point2& quadP1, + const mir::Point2& quadP2, + const mir::Point2& quadP3) { // Check if any of the quad's corners are within the circle - axom::float64 distP0 = mir::utilities::distance(quadP0, circleCenter); - axom::float64 distP1 = mir::utilities::distance(quadP1, circleCenter); - axom::float64 distP2 = mir::utilities::distance(quadP2, circleCenter); - axom::float64 distP3 = mir::utilities::distance(quadP3, circleCenter); - - if (distP0 < circleRadius && distP1 < circleRadius && distP2 < circleRadius && distP3 < circleRadius) + auto d0Sq = primal::squared_distance(quadP0, circleCenter); + auto d1Sq = primal::squared_distance(quadP1, circleCenter); + auto d2Sq = primal::squared_distance(quadP2, circleCenter); + auto d3Sq = primal::squared_distance(quadP3, circleCenter); + auto dRSq = circleRadius * circleRadius; + + int inFlags = ((d0Sq < dRSq) ? 1 << 0 : 0) + + ((d1Sq < dRSq) ? 1 << 1 : 0) + + ((d2Sq < dRSq) ? 1 << 2 : 0) + + ((d3Sq < dRSq) ? 1 << 3 : 0); + const int allFlags = 15; + const int noFlags = 0; + + if (inFlags == allFlags ) { // The entire quad overlaps the circle - return 1.0; + return 1.; + } + else if( inFlags == noFlags) + { + return 0.; } - else if (distP0 < circleRadius || distP1 < circleRadius || distP2 < circleRadius || distP3 < circleRadius) + else { // Some of the quad overlaps the circle, so run the Monte Carlo sampling to determine how much - axom::float64 delta_x = abs(quadP2.m_x - quadP1.m_x) / (double) (gridSize - 1); - axom::float64 delta_y = abs(quadP0.m_y - quadP1.m_y) / (double) (gridSize - 1); + axom::float64 delta_x = axom::utilities::abs(quadP2[0] - quadP1[0]) / static_cast(gridSize - 1); + axom::float64 delta_y = axom::utilities::abs(quadP0[1] - quadP1[1]) / static_cast(gridSize - 1); int countOverlap = 0; for (int y = 0; y < gridSize; ++y) { for (int x = 0; x < gridSize; ++x) { - mir::Point2 samplePoint(delta_x * x + quadP1.m_x, delta_y * y + quadP1.m_y); - if (mir::utilities::distance(samplePoint, circleCenter) < circleRadius) + mir::Point2 samplePoint = mir::Point2::make_point(delta_x * x + quadP1[0], + delta_y * y + quadP1[1]); + if (primal::squared_distance(samplePoint, circleCenter) < dRSq) ++countOverlap; } } - return countOverlap / (double) (gridSize * gridSize); - } - else - { - // None of the quad overlaps the circle - return 0; + return countOverlap / static_cast(gridSize * gridSize); } } @@ -545,7 +560,7 @@ mir::CellData MeshTester::generateGrid(int gridSize) { for (int x = 0; x < gridSize + 1; ++x) { - points.push_back(mir::Point2(x, y)); + points.push_back(mir::Point2::make_point(x, y)); } } @@ -583,7 +598,7 @@ mir::CellData MeshTester::generateGrid(int gridSize) // printf("points: { "); // for (int i = 0; i < numVertices; ++i) // { - // printf("{%.2f, %.2f} ", points[i].m_x, points[i].m_y); + // printf("{%.2f, %.2f} ", points[i][0], points[i][1]); // } // printf("}\n"); @@ -605,7 +620,7 @@ mir::MIRMesh MeshTester::initTestCaseFive(int gridSize, int numCircles) int numMaterials = numCircles + 1; int defaultMaterialID = numMaterials - 1; // default material is always the last index - mir::Point2 circleCenter(gridSize / 2.0, gridSize / 2.0); // all circles are centered around the same point + mir::Point2 circleCenter = mir::Point2::make_point(gridSize / 2.0, gridSize / 2.0); // all circles are centered around the same point // Initialize the radii of the circles std::vector circleRadii; @@ -633,12 +648,13 @@ mir::MIRMesh MeshTester::initTestCaseFive(int gridSize, int numCircles) } // Use the uniform sampling method to generate volume fractions for each material + // Note: Assumes that the cell is a parallelogram. This could be modified via biliear interpolation for (int eID = 0; eID < cellData.m_numElems; ++eID) { - mir::Point2 v0 = cellData.m_mapData.m_vertexPositions[cellData.m_topology.m_evInds[eID * 4 + 0]]; - mir::Point2 v1 = cellData.m_mapData.m_vertexPositions[cellData.m_topology.m_evInds[eID * 4 + 1]]; - mir::Point2 v2 = cellData.m_mapData.m_vertexPositions[cellData.m_topology.m_evInds[eID * 4 + 2]]; - mir::Point2 v3 = cellData.m_mapData.m_vertexPositions[cellData.m_topology.m_evInds[eID * 4 + 3]]; + mir::Point2& v0 = cellData.m_mapData.m_vertexPositions[cellData.m_topology.m_evInds[eID * 4 + 0]]; + mir::Point2& v1 = cellData.m_mapData.m_vertexPositions[cellData.m_topology.m_evInds[eID * 4 + 1]]; + mir::Point2& v2 = cellData.m_mapData.m_vertexPositions[cellData.m_topology.m_evInds[eID * 4 + 2]]; + //mir::Point2& v3 = cellData.m_mapData.m_vertexPositions[cellData.m_topology.m_evInds[eID * 4 + 3]]; // Run the uniform sampling to determine how much of the current cell is composed of each material int materialCount[numMaterials]; for (int i = 0; i < numMaterials; ++i) materialCount[i] = 0; @@ -648,18 +664,19 @@ mir::MIRMesh MeshTester::initTestCaseFive(int gridSize, int numCircles) materialVolumeFractionsData[matID][eID] = materialCount[matID] / (double) (gridSize * gridSize); } - axom::float64 delta_x = abs(v2.m_x - v1.m_x) / (double) (gridSize - 1); - axom::float64 delta_y = abs(v0.m_y - v1.m_y) / (double) (gridSize - 1); + axom::float64 delta_x = axom::utilities::abs(v2[0] - v1[1]) / (double) (gridSize - 1); + axom::float64 delta_y = axom::utilities::abs(v0[1] - v1[1]) / (double) (gridSize - 1); for (int y = 0; y < gridSize; ++y) { for (int x = 0; x < gridSize; ++x) { - mir::Point2 samplePoint(delta_x * x + v1.m_x, delta_y * y + v1.m_y); + mir::Point2 samplePoint = mir::Point2::make_point(delta_x * x + v1[0], delta_y * y + v1[1]); bool isPointSampled = false; for (int cID = 0; cID < numCircles && !isPointSampled; ++cID) { - if (mir::utilities::distance(samplePoint, circleCenter) < circleRadii[cID]) + const auto r = circleRadii[cID]; + if (primal::squared_distance(samplePoint, circleCenter) < r*r) { materialCount[cID]++; isPointSampled = true; @@ -711,23 +728,29 @@ mir::MIRMesh MeshTester::initTestCaseFive(int gridSize, int numCircles) //-------------------------------------------------------------------------------- -int MeshTester::circleQuadCornersOverlaps(mir::Point2 circleCenter, axom::float64 circleRadius, mir::Point2 quadP0, mir::Point2 quadP1, mir::Point2 quadP2, mir::Point2 quadP3) +int MeshTester::circleQuadCornersOverlaps(const mir::Point2& circleCenter, + axom::float64 circleRadius, + const mir::Point2& quadP0, + const mir::Point2& quadP1, + const mir::Point2& quadP2, + const mir::Point2& quadP3) { // Check if any of the quad's corners are within the circle - axom::float64 distP0 = mir::utilities::distance(quadP0, circleCenter); - axom::float64 distP1 = mir::utilities::distance(quadP1, circleCenter); - axom::float64 distP2 = mir::utilities::distance(quadP2, circleCenter); - axom::float64 distP3 = mir::utilities::distance(quadP3, circleCenter); + auto d0Sq = primal::squared_distance(quadP0, circleCenter); + auto d1Sq = primal::squared_distance(quadP1, circleCenter); + auto d2Sq = primal::squared_distance(quadP2, circleCenter); + auto d3Sq = primal::squared_distance(quadP3, circleCenter); + auto dRSq = circleRadius * circleRadius; int numCorners = 0; - if (distP0 < circleRadius) + if (d0Sq < dRSq) numCorners++; - if (distP1 < circleRadius) + if (d1Sq < dRSq) numCorners++; - if (distP2 < circleRadius) + if (d2Sq < dRSq) numCorners++; - if (distP3 < circleRadius) + if (d3Sq < dRSq) numCorners++; return numCorners; diff --git a/src/axom/mir/MeshTester.hpp b/src/axom/mir/MeshTester.hpp index 7d4ea33c40..dcbc380460 100644 --- a/src/axom/mir/MeshTester.hpp +++ b/src/axom/mir/MeshTester.hpp @@ -114,7 +114,9 @@ namespace mir * * \return The generated mesh. */ - mir::MIRMesh createUniformGridTestCaseMesh(int gridSize, mir::Point2 circleCenter, axom::float64 circleRadius); + mir::MIRMesh createUniformGridTestCaseMesh(int gridSize, + const mir::Point2& circleCenter, + axom::float64 circleRadius); /** * \brief Initializes a mesh to be used for validating the results of quad clipping. @@ -147,7 +149,14 @@ namespace mir * * /return The percent value overlap of the circle and the quad between [0, 1]. */ - axom::float64 calculatePercentOverlapMonteCarlo(int gridSize, mir::Point2 circleCenter, axom::float64 circleRadius, mir::Point2 quadP0, mir::Point2 quadP1, mir::Point2 quadP2, mir::Point2 quadP3); + axom::float64 calculatePercentOverlapMonteCarlo( + int gridSize, + const mir::Point2& circleCenter, + axom::float64 circleRadius, + const mir::Point2& quadP0, + const mir::Point2& quadP1, + const mir::Point2& quadP2, + const mir::Point2& quadP3); /** * \brief Calculates the number of corners of the quad that are within the circle. @@ -161,7 +170,12 @@ namespace mir * * \return The number of corners of the quad that are within the circle. */ - int circleQuadCornersOverlaps(mir::Point2 circleCenter, axom::float64 circleRadius, mir::Point2 quadP0, mir::Point2 quadP1, mir::Point2 quadP2, mir::Point2 quadP3); + int circleQuadCornersOverlaps(const mir::Point2& circleCenter, + axom::float64 circleRadius, + const mir::Point2& quadP0, + const mir::Point2& quadP1, + const mir::Point2& quadP2, + const mir::Point2& quadP3); }; } diff --git a/src/axom/mir/tests/mir_cell_generator.cpp b/src/axom/mir/tests/mir_cell_generator.cpp index f03b480bda..9505eae633 100644 --- a/src/axom/mir/tests/mir_cell_generator.cpp +++ b/src/axom/mir/tests/mir_cell_generator.cpp @@ -101,10 +101,10 @@ TEST(mir_cell_generator, generate_vertex_positions) vertexMap[7] = { 2, 3 }; std::vector originalVertexPositions; - originalVertexPositions.push_back( mir::Point2(0.0, 1.0) ); - originalVertexPositions.push_back( mir::Point2(0.0, 0.0) ); - originalVertexPositions.push_back( mir::Point2(1.0, 0.0) ); - originalVertexPositions.push_back( mir::Point2(1.0, 1.0) ); + originalVertexPositions.push_back( mir::Point2::make_point(0.0, 1.0) ); + originalVertexPositions.push_back( mir::Point2::make_point(0.0, 0.0) ); + originalVertexPositions.push_back( mir::Point2::make_point(1.0, 0.0) ); + originalVertexPositions.push_back( mir::Point2::make_point(1.0, 1.0) ); axom::float64 tValues[8] = { 0, 0, 0, 0, 0.5, 0.5, 0.5, 0.5 }; @@ -112,30 +112,30 @@ TEST(mir_cell_generator, generate_vertex_positions) mir::CellGenerator cellGenerator; cellGenerator.generateVertexPositions(shapeType, vertexMap, originalVertexPositions, tValues, cellData); + const auto& positions = cellData.m_mapData.m_vertexPositions; + EXPECT_NEAR( positions[0][0], 0.0, 0.00001 ); + EXPECT_NEAR( positions[0][1], 1.0, 0.00001 ); - EXPECT_NEAR( cellData.m_mapData.m_vertexPositions[0].m_x, 0.0, 0.00001 ); - EXPECT_NEAR( cellData.m_mapData.m_vertexPositions[0].m_y, 1.0, 0.00001 ); + EXPECT_NEAR( positions[1][0], 0.0, 0.00001 ); + EXPECT_NEAR( positions[1][1], 0.0, 0.00001 ); - EXPECT_NEAR( cellData.m_mapData.m_vertexPositions[1].m_x, 0.0, 0.00001 ); - EXPECT_NEAR( cellData.m_mapData.m_vertexPositions[1].m_y, 0.0, 0.00001 ); + EXPECT_NEAR( positions[2][0], 1.0, 0.00001 ); + EXPECT_NEAR( positions[2][1], 0.0, 0.00001 ); - EXPECT_NEAR( cellData.m_mapData.m_vertexPositions[2].m_x, 1.0, 0.00001 ); - EXPECT_NEAR( cellData.m_mapData.m_vertexPositions[2].m_y, 0.0, 0.00001 ); + EXPECT_NEAR( positions[3][0], 1.0, 0.00001 ); + EXPECT_NEAR( positions[3][1], 1.0, 0.00001 ); - EXPECT_NEAR( cellData.m_mapData.m_vertexPositions[3].m_x, 1.0, 0.00001 ); - EXPECT_NEAR( cellData.m_mapData.m_vertexPositions[3].m_y, 1.0, 0.00001 ); + EXPECT_NEAR( positions[4][0], 0.0, 0.00001 ); + EXPECT_NEAR( positions[4][1], 0.5, 0.00001 ); - EXPECT_NEAR( cellData.m_mapData.m_vertexPositions[4].m_x, 0.0, 0.00001 ); - EXPECT_NEAR( cellData.m_mapData.m_vertexPositions[4].m_y, 0.5, 0.00001 ); + EXPECT_NEAR( positions[5][0], 0.5, 0.00001 ); + EXPECT_NEAR( positions[5][1], 0.0, 0.00001 ); - EXPECT_NEAR( cellData.m_mapData.m_vertexPositions[5].m_x, 0.5, 0.00001 ); - EXPECT_NEAR( cellData.m_mapData.m_vertexPositions[5].m_y, 0.0, 0.00001 ); + EXPECT_NEAR( positions[6][0], 1.0, 0.00001 ); + EXPECT_NEAR( positions[6][1], 0.5, 0.00001 ); - EXPECT_NEAR( cellData.m_mapData.m_vertexPositions[6].m_x, 1.0, 0.00001 ); - EXPECT_NEAR( cellData.m_mapData.m_vertexPositions[6].m_y, 0.5, 0.00001 ); - - EXPECT_NEAR( cellData.m_mapData.m_vertexPositions[7].m_x, 0.5, 0.00001 ); - EXPECT_NEAR( cellData.m_mapData.m_vertexPositions[7].m_y, 1.0, 0.00001 ); + EXPECT_NEAR( positions[7][0], 0.5, 0.00001 ); + EXPECT_NEAR( positions[7][1], 1.0, 0.00001 ); } //---------------------------------------------------------------------- diff --git a/src/axom/mir/tests/mir_utilities.cpp b/src/axom/mir/tests/mir_utilities.cpp index 82e26b89a0..6d5a6c2dde 100644 --- a/src/axom/mir/tests/mir_utilities.cpp +++ b/src/axom/mir/tests/mir_utilities.cpp @@ -11,78 +11,7 @@ #include "axom/slic.hpp" #include "axom/mir.hpp" -using namespace axom; -TEST(mir_interpolation, float_linear_interpolation) -{ - axom::float64 f0 = 50.0; - axom::float64 f1 = 100.0; - - axom::float64 t = 0.0; - axom::float64 interpolant = mir::utilities::lerpFloat(f0, f1, t); - EXPECT_DOUBLE_EQ( interpolant, 50.0 ); - - t = 1.0; - interpolant = mir::utilities::lerpFloat(f0, f1, t); - EXPECT_DOUBLE_EQ( interpolant, 100.0 ); - - t = 0.5; - interpolant = mir::utilities::lerpFloat(f0, f1, t); - EXPECT_DOUBLE_EQ( interpolant, 75.0 ); - - t = 0.66; - interpolant = mir::utilities::lerpFloat(f0, f1, t); - EXPECT_DOUBLE_EQ( interpolant, 83.0 ); -} - -//---------------------------------------------------------------------- - -TEST(mir_interpolation, point_linear_interpolation) -{ - mir::Point2 p0(0.25, 0.0); - mir::Point2 p1(1.25, 1.0); - - axom::float64 t = 0.0; - mir::Point2 interpolant = mir::utilities::interpolateVertexPosition(p0, p1, t); - EXPECT_DOUBLE_EQ( interpolant.m_x, 0.25); - EXPECT_DOUBLE_EQ( interpolant.m_y, 0.0); - - t = 1.0; - interpolant = mir::utilities::interpolateVertexPosition(p0, p1, t); - EXPECT_DOUBLE_EQ( interpolant.m_x, 1.25); - EXPECT_DOUBLE_EQ( interpolant.m_y, 1.0); - - t = 0.5; - interpolant = mir::utilities::interpolateVertexPosition(p0, p1, t); - EXPECT_DOUBLE_EQ( interpolant.m_x, 0.75); - EXPECT_DOUBLE_EQ( interpolant.m_y, 0.5); - - t = 0.66; - interpolant = mir::utilities::interpolateVertexPosition(p0, p1, t); - EXPECT_NEAR( interpolant.m_x, 0.91, 0.00001); - EXPECT_NEAR( interpolant.m_y, 0.66, 0.00001); -} - -//---------------------------------------------------------------------- - -TEST(mir_distance_utility, compute_distance) -{ - mir::Point2 p0( 0.25, 0.0 ); - mir::Point2 p1( 1.25, 0.0 ); - axom::float64 dist = mir::utilities::distance(p0, p1); - EXPECT_DOUBLE_EQ( dist, 1.0 ); - - mir::Point2 p2( 0.25, 0.0 ); - mir::Point2 p3( 1.25, 1.0 ); - dist = mir::utilities::distance(p2, p3); - EXPECT_NEAR( dist, 1.4142, 0.001 ); - - mir::Point2 p4( 1.0, 1.0 ); - mir::Point2 p5( 1.0, 1.0 ); - dist = mir::utilities::distance(p4, p5); - EXPECT_DOUBLE_EQ( dist, 0.0 ); - -} //---------------------------------------------------------------------- From 56b04b433c7b3819872d8da20efe2d3042098a58 Mon Sep 17 00:00:00 2001 From: Sterbentz Date: Wed, 24 Jul 2019 11:29:12 -0700 Subject: [PATCH 22/23] Fixed return value bug in input parsing. Fixed grid size calculations bug for concentric circles test case. --- src/axom/mir/MeshTester.cpp | 2 +- src/axom/mir/examples/mir_tutorial_simple.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/axom/mir/MeshTester.cpp b/src/axom/mir/MeshTester.cpp index f64e310c8c..2b576c66a5 100644 --- a/src/axom/mir/MeshTester.cpp +++ b/src/axom/mir/MeshTester.cpp @@ -664,7 +664,7 @@ mir::MIRMesh MeshTester::initTestCaseFive(int gridSize, int numCircles) materialVolumeFractionsData[matID][eID] = materialCount[matID] / (double) (gridSize * gridSize); } - axom::float64 delta_x = axom::utilities::abs(v2[0] - v1[1]) / (double) (gridSize - 1); + axom::float64 delta_x = axom::utilities::abs(v2[0] - v1[0]) / (double) (gridSize - 1); axom::float64 delta_y = axom::utilities::abs(v0[1] - v1[1]) / (double) (gridSize - 1); for (int y = 0; y < gridSize; ++y) diff --git a/src/axom/mir/examples/mir_tutorial_simple.cpp b/src/axom/mir/examples/mir_tutorial_simple.cpp index 460a076b80..2de48c4be2 100644 --- a/src/axom/mir/examples/mir_tutorial_simple.cpp +++ b/src/axom/mir/examples/mir_tutorial_simple.cpp @@ -97,7 +97,7 @@ struct Input bool shouldIterate() const { return m_should_iterate; } int numIterations() const { return m_iter_count; } - int iterPercentage() const { return m_iter_percent; } + double iterPercentage() const { return m_iter_percent; } void showhelp() From 491e4818e461f205db4b70fef117e358ce46a6c9 Mon Sep 17 00:00:00 2001 From: Sterbentz Date: Fri, 9 Aug 2019 08:46:04 -0700 Subject: [PATCH 23/23] Added documentation for the MIR component. Added minimal tutorial example for MIR component. Modified MIRMesh's .vtk file output property name. --- src/axom/mir/MIRMesh.cpp | 2 +- src/axom/mir/docs/sphinx/equi_z.rst | 61 ++++++++++++++++++ src/axom/mir/docs/sphinx/index.rst | 44 ++++++++++++- src/axom/mir/docs/sphinx/iterative_equi_z.rst | 24 +++++++ src/axom/mir/docs/sphinx/quad_cases.png | Bin 0 -> 36824 bytes src/axom/mir/docs/sphinx/triangle_cases.png | Bin 0 -> 25494 bytes src/axom/mir/docs/sphinx/zoo.png | Bin 0 -> 34037 bytes src/axom/mir/examples/CMakeLists.txt | 1 + .../mir/examples/mir_minimal_tutorial.cpp | 41 ++++++++++++ 9 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 src/axom/mir/docs/sphinx/equi_z.rst create mode 100644 src/axom/mir/docs/sphinx/iterative_equi_z.rst create mode 100644 src/axom/mir/docs/sphinx/quad_cases.png create mode 100644 src/axom/mir/docs/sphinx/triangle_cases.png create mode 100644 src/axom/mir/docs/sphinx/zoo.png create mode 100644 src/axom/mir/examples/mir_minimal_tutorial.cpp diff --git a/src/axom/mir/MIRMesh.cpp b/src/axom/mir/MIRMesh.cpp index 60ad504887..9181c10522 100644 --- a/src/axom/mir/MIRMesh.cpp +++ b/src/axom/mir/MIRMesh.cpp @@ -423,7 +423,7 @@ void MIRMesh::writeMeshToFile(std::string filename) // write element materials meshfile << "\n\nCELL_DATA " << m_elems.size() - << "\nSCALARS cellIds int 1" + << "\nSCALARS materialIDs int 1" << "\nLOOKUP_TABLE default \n"; for(int i=0 ; i< m_elems.size() ; ++i) { diff --git a/src/axom/mir/docs/sphinx/equi_z.rst b/src/axom/mir/docs/sphinx/equi_z.rst new file mode 100644 index 0000000000..3201fb6660 --- /dev/null +++ b/src/axom/mir/docs/sphinx/equi_z.rst @@ -0,0 +1,61 @@ +.. ## Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +.. ## other Axom Project Developers. See the top-level COPYRIGHT file for details. +.. ## +.. ## SPDX-License-Identifier: (BSD-3-Clause) + +======================= +Equi-Z Algorithm +======================= + +The Equi-Z algorithm provided here is an implementation of the algorithm described in +the 2005 paper "Material Interface Reconstruction in VisIt" by J. S. Meredith. + +Functionality for this algorithm is provided by ``computeReconstructedInterface()``. +It takes in references to two meshes: the mesh to be processed and the mesh that +will contain the results of the material interface reconstruction. + +.. raw:: html + +

Key Concepts

+ +Full details of the algorithm can be found in the original paper, but key concepts +are explained here. + +The input into this algorithm is a volumetric mesh composed of element based volume fractions. +The elements of this input mesh can contain multiple materials. The output of this algorithm +is a new volumetric mesh composed of clean elements (i.e. only one material is present in each element). +Note that both the input and output meshes are composed of elements that are part of the finite element zoo (described below). +Each element of the mesh is then processed independently. For each pair of materials of the current element, convert the element +centered volume fractions to vertex centered volume fractions, which are found by calculating the average value of the materials +of the adjacent elements. Then, determine the dominant material at each vertex of the element. The configuration of the dominant +materials is used as an index into a lookup table of clipping cases (described below). Once the clipping case is found, linear interpolation is used +to find the exact points along the edges at which the current element should be split. The element is then decomposed into clean, zoo +elements. + + +.. raw:: html + +

Finite Element Zoo

+ +For two-dimensional meshes, the finite element zoo consists of triangles and quadrilaterals, while for three-dimensional meshes, +the finite element zoo consists of tetrahedrons, pyramids, triangular prisms (wedges), and hexahedrons. This can be seen in the +figure below. + +.. figure:: zoo.png + +Splitting the mesh into elements contained in the zoo prevents a couple of issues: the first being +interpolation imprinting and the second being the need to split arbitrary polyhedra. + +.. raw:: html + +

Clipping Cases

+ +During the course of the algorithm, it is necessary to map each shape and the material that is dominant at each of its vertices +to a specific, pre-defined clipping case. For two dimensional shapes, the cases can be seen below. + +.. figure:: triangle_cases.png + +.. figure:: quad_cases.png + +Note that while this algorithm can handle any number of materials in the input mesh, it considers two materials at a time when splitting +an element. \ No newline at end of file diff --git a/src/axom/mir/docs/sphinx/index.rst b/src/axom/mir/docs/sphinx/index.rst index 22f5487fd6..b4b2b257aa 100644 --- a/src/axom/mir/docs/sphinx/index.rst +++ b/src/axom/mir/docs/sphinx/index.rst @@ -9,4 +9,46 @@ Mir User Documentation Axom's Material Interface Reconstruction (MIR) component provides algorithms for reconstructing the interface surfaces between different materials in multimaterial -meshes. +meshes. The goal of the component is to provide simulation code developers with a +set of algorithms for performing material interface reconstruction with efficient +implementations and an easy-to-use API. + +This component currently provides support for two MIR algorithms: + +.. toctree:: + :maxdepth: 2 + + equi_z + iterative_equi_z + +.. raw:: html + +

Current limitations

+ +.. note:: This MIR component is under active development with many features planned. + +* Improved efficiency for the Equi-Z algorithm implementation is under development. +* Support for GPUs in MIR is planned. +* Support for material interface reconstruction in high-order meshes is planned. + + +.. raw:: html + +

Example Code

+ +In order to access the MIR functionality in this component, import the MIR header file: + +.. literalinclude:: ../../examples/mir_minimal_tutorial.cpp + :start-after: _mir_header_start + :end-before: _mir_header_end + :language: C++ + +The following is all the code needed to generate a mesh with the ``MIRMesh`` class from one of +the default test cases, perform material interface reconstruction using the Equi-Z algorithm +with the ``computeReconstructedInterface()`` function, and then write out the resulting mesh +to a vtk file. + +.. literalinclude:: ../../examples/mir_minimal_tutorial.cpp + :start-after: _mir_main_loop_start + :end-before: _mir_main_loop_end + :language: C++ \ No newline at end of file diff --git a/src/axom/mir/docs/sphinx/iterative_equi_z.rst b/src/axom/mir/docs/sphinx/iterative_equi_z.rst new file mode 100644 index 0000000000..f067cd896b --- /dev/null +++ b/src/axom/mir/docs/sphinx/iterative_equi_z.rst @@ -0,0 +1,24 @@ +.. ## Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +.. ## other Axom Project Developers. See the top-level COPYRIGHT file for details. +.. ## +.. ## SPDX-License-Identifier: (BSD-3-Clause) + +========================== +Iterative Equi-Z Algorithm +========================== + +The iterative Equi-Z algorithm provided here is an implementation of the algorithm described +in the 2010 paper "Visualization and Analysis-Oriented Reconstruction of Material Interfaces" +by Meredith and Childs. + +Functionality for this algorithm is provided by ``computeReconstructedInterfaceIterative()``. +It takes in references to two meshes (the mesh to be processed and the mesh that +will contain the results of the material interface reconstruction), an integer denoting the +number of iterations to run for, and a double denoting the percentage difference to modify +the volume fractions by at each iteration. + +This algorithm performs reconstruction using the Equi-Z algorithm, but after finishing recontruction, +calculates a percent difference between the resulting element volume fractions and the desired element volume fractions. +It modifies the original element volume fractions by these values and does another reconstruction with +the Equi-Z algorithm. Since convergence to an optimal value is not guaranteed, it does this for a set number +of iterations that the user specifies. \ No newline at end of file diff --git a/src/axom/mir/docs/sphinx/quad_cases.png b/src/axom/mir/docs/sphinx/quad_cases.png new file mode 100644 index 0000000000000000000000000000000000000000..aa6de0cc948f022325927fd3e8a283ff63e41ed9 GIT binary patch literal 36824 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 literal 0 HcmV?d00001 diff --git a/src/axom/mir/docs/sphinx/triangle_cases.png b/src/axom/mir/docs/sphinx/triangle_cases.png new file mode 100644 index 0000000000000000000000000000000000000000..f32e57e941ea1237b01c087e06f015a68daa1883 GIT binary patch literal 25494 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 Y0M0QSN3}m;>i_@%07*qoM6N<$f+1L0s{jB1 literal 0 HcmV?d00001 diff --git a/src/axom/mir/docs/sphinx/zoo.png b/src/axom/mir/docs/sphinx/zoo.png new file mode 100644 index 0000000000000000000000000000000000000000..49734e028deed637c14560374561db212387d15c GIT binary patch literal 34037 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 bZ3+^{4LFLrkVyf9fImf9HJK_YvoHSxkG(KG literal 0 HcmV?d00001 diff --git a/src/axom/mir/examples/CMakeLists.txt b/src/axom/mir/examples/CMakeLists.txt index 24878a8dde..0ec24732d9 100644 --- a/src/axom/mir/examples/CMakeLists.txt +++ b/src/axom/mir/examples/CMakeLists.txt @@ -5,6 +5,7 @@ set( mir_examples mir_tutorial_simple.cpp + mir_minimal_tutorial.cpp mir_concentric_circles.cpp ) diff --git a/src/axom/mir/examples/mir_minimal_tutorial.cpp b/src/axom/mir/examples/mir_minimal_tutorial.cpp new file mode 100644 index 0000000000..c618928475 --- /dev/null +++ b/src/axom/mir/examples/mir_minimal_tutorial.cpp @@ -0,0 +1,41 @@ +// Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "axom/core.hpp" +#include "axom/slic.hpp" +#include "axom/slam.hpp" + +// _mir_header_start +#include "axom/mir.hpp" +// _mir_header_end + +#include + +// namespace aliases +namespace mir = axom::mir; +namespace fs = axom::utilities::filesystem; + +/*! + * \brief Tutorial main showing how to initialize test cases and perform mir. + */ +int main(int argc, char** argv) +{ + // _mir_main_loop_start + // Initialize the mesh + mir::MIRMesh testMesh; + mir::MeshTester tester; + testMesh = tester.initTestCaseOne(); + + // Perform material interface reconstruction + mir::MIRMesh processedMesh; + mir::InterfaceReconstructor reconstructor; + reconstructor.computeReconstructedInterface( testMesh, processedMesh ); + + // Output the results to a .vtk file + std::string outputDirectory = fs::joinPath(AXOM_BIN_DIR, "mir_examples/processedMesh.vtk"); + processedMesh.writeMeshToFile( outputDirectory ); + // _mir_main_loop_end + return 0; +} \ No newline at end of file