diff --git a/CHANGELOG.md b/CHANGELOG.md index c1ca5a8eb..62c23fdaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project aspires to adhere to [Semantic Versioning](https://semver.org/s - Added a `topologies` option to the relay extract. This allows you to select which topologies are saved. This option can be used with the existing `fields` option, the result is the union of the selected topologies and fields. - Added `near_plane` and `far_plane` to the camera details provided in Ascent::info() - Added `add_mpi_ranks` and `add_domain_ids` filters for adding rank and domain fields to a mesh +- Added `transform` filter, which allows you to rotate, scale, translate, mesh coordinates ### Changed - Changed the replay utility's binary names such that `replay_ser` is now `ascent_replay` and `raplay_mpi` is now `ascent_replay_mpi`. This will help prevent potential name collisions with other tools that also have replay utilities. diff --git a/src/docs/sphinx/Actions/Pipelines.rst b/src/docs/sphinx/Actions/Pipelines.rst index 3dbb2864f..92aaf5cdc 100644 --- a/src/docs/sphinx/Actions/Pipelines.rst +++ b/src/docs/sphinx/Actions/Pipelines.rst @@ -82,19 +82,8 @@ filter to extract the intersection of what remains from the threshold with a sph ascent.execute(actions); ascent.close(); -Ascent and VTK-h are under heavy development and features are being added rapidly. -As we stand up the infrastructure necessary to support a wide variety filter we created the following filters for the alpha release: - - - Contour - - Threshold - - Slice - - Three Slice - - Automatic Slice - - Clip - - Clip by field - - Isovolume - - Vector magnitude +Ascent is under active development and features are being added rapidly. In the following section we provide brief descriptions and code examples of the supported filters. For complete code examples, please consult the unit tests located in ``src/tests/ascent``.. @@ -103,16 +92,12 @@ Filters ------- Our filter API consists of the type of filter and the parameters associated with the filter in the general form: -.. code-block:: json +.. code-block:: yaml - { - "type" : "filter_name", - "params": - { - "string_param" : "string", - "double_param" : 2.0 - } - } + type: "filter_name" + params: + string_param: "string" + double_param: 2.0 In c++, the equivalent declarations would be as follows: @@ -123,9 +108,6 @@ In c++, the equivalent declarations would be as follows: filter["params/string_param"] = "string"; filter["params/double_param"] = 2.0; -Included Filters -^^^^^^^^^^^^^^^^ - Contour ~~~~~~~ The contour filter evaluates a node-centered scalar field for all points at a given iso-value. @@ -607,6 +589,73 @@ values are removed from the data set. An example of creating a iso-volume of values between 5.0 and 10.0. +Transform +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The transform filter allows you to scale, translate, and rotate your mesh. +It also accepts a general 4x4 transformation matrix. + +Translate Example: + +.. code-block:: yaml + + - + action: "add_pipelines" + pipelines: + pl1: + f1: + type: "transform" + params: + translate: + x: 23.0 + y: 15.0 + +Scale Example: + +.. code-block:: yaml + + - + action: "add_pipelines" + pipelines: + pl1: + f1: + type: "transform" + params: + scale: + x: 2.0 + y: 0.5 + z: 2.0 + +Rotate Example: + +.. code-block:: yaml + + - + action: "add_pipelines" + pipelines: + pl1: + f1: + type: "transform" + params: + rotate: + angle: 45.0 + axis: + y: 1.0 + + +Matrix Example: + +.. code-block:: yaml + + - + action: "add_pipelines" + pipelines: + pl1: + f1: + type: "transform" + params: + matrix: [2.0, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 50.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 1.0] + + Particle Advection ~~~~~~~~~~~~~~~~~~~~ The particle advection filter distributes some number of weightless particles over a user-specified vector field (``field``) and, given some advection distance (``step_size``), advects them for some number of advection steps (``num_steps``). diff --git a/src/libs/ascent/runtimes/flow_filters/ascent_runtime_filters.cpp b/src/libs/ascent/runtimes/flow_filters/ascent_runtime_filters.cpp index f83503059..f0d370fec 100644 --- a/src/libs/ascent/runtimes/flow_filters/ascent_runtime_filters.cpp +++ b/src/libs/ascent/runtimes/flow_filters/ascent_runtime_filters.cpp @@ -140,6 +140,7 @@ register_builtin() AscentRuntime::register_filter_type("transforms","gradient"); AscentRuntime::register_filter_type("transforms","divergence"); AscentRuntime::register_filter_type("transforms","vorticity"); + AscentRuntime::register_filter_type("transforms","transform"); AscentRuntime::register_filter_type("transforms","scale"); AscentRuntime::register_filter_type("transforms","project_2d"); AscentRuntime::register_filter_type("transforms","triangulate"); diff --git a/src/libs/ascent/runtimes/flow_filters/ascent_runtime_vtkh_filters.cpp b/src/libs/ascent/runtimes/flow_filters/ascent_runtime_vtkh_filters.cpp index 1390bcca5..ab804600e 100644 --- a/src/libs/ascent/runtimes/flow_filters/ascent_runtime_vtkh_filters.cpp +++ b/src/libs/ascent/runtimes/flow_filters/ascent_runtime_vtkh_filters.cpp @@ -4549,6 +4549,332 @@ VTKHScale::execute() set_output(res); } + +//----------------------------------------------------------------------------- + +VTKHTransform::VTKHTransform() +:Filter() +{ +// empty +} + +//----------------------------------------------------------------------------- +VTKHTransform::~VTKHTransform() +{ +// empty +} + +//----------------------------------------------------------------------------- +void +VTKHTransform::declare_interface(Node &i) +{ + i["type_name"] = "vtkh_transform"; + i["port_names"].append() = "in"; + i["output_port"] = "true"; +} + +//----------------------------------------------------------------------------- +bool +VTKHTransform::verify_params(const conduit::Node ¶ms, + conduit::Node &info) +{ + info.reset(); + +/* + scale/x,y,z + translate/x,y,z + rotate/x,y,z + transform_matrix: float64 x 16 +*/ + + bool res = true; + + std::vector modes = {"scale","translate","rotate","matrix"}; + + index_t mode_count = 0; + for( auto mode : modes) + { + if(params.has_child(mode)) + { + mode_count++; + } + } + + if(mode_count > 1) + { + info["errors"].append() = "transform only supports one of: scale, translate, rotate, or matrix"; + res = false; + } + + if(mode_count == 0) + { + info["errors"].append() = "transform requires parameters for: scale, translate, rotate, or matrix"; + res = false; + } + + if(params.has_child("scale")) + { + const Node &p_vals = params["scale"]; + if( ! p_vals.has_child("x") && + ! p_vals.has_child("y") && + ! p_vals.has_child("z") ) + { + res = false; + info["errors"].append()="scale transform requires: scale/x, scale/y, and/or scale/z"; + } + res &= check_numeric("x", p_vals, info, false, true); + res &= check_numeric("y", p_vals, info, false, true); + res &= check_numeric("z", p_vals, info, false, true); + } + + if(params.has_child("translate")) + { + const Node &p_vals = params["translate"]; + if( ! p_vals.has_child("x") && + ! p_vals.has_child("y") && + ! p_vals.has_child("z") ) + { + res = false; + info["errors"].append() = "translate transform requires: translate/x, translate/y, and/or translate/z"; + } + res &= check_numeric("x", p_vals, info, false, true); + res &= check_numeric("y", p_vals, info, false, true); + res &= check_numeric("z", p_vals, info, false, true); + } + + if(params.has_child("rotate")) + { + const Node &p_vals = params["rotate"]; + bool rotate_ok = check_numeric("angle", p_vals, info, true, true); + + if(p_vals.has_child("axis")) + { + const Node &p_axis = p_vals["axis"]; + if( ! p_axis.has_child("x") && + ! p_axis.has_child("y") && + ! p_axis.has_child("z") ) + { + rotate_ok = false; + } + + res &= check_numeric("x", p_axis, info, false, true); + res &= check_numeric("y", p_axis, info, false, true); + res &= check_numeric("z", p_axis, info, false, true); + + } + else + { + rotate_ok = false; + } + + if(!rotate_ok) + { + res = false; + info["errors"].append()="rotate transform requires: rotate/angle and rotate/axis/x, rotate/axis/y, and/or rotate/axis/z"; + } + } + + if(params.has_child("matrix")) + { + res &= check_numeric("matrix",params, info, true, true); + if(res) + { + // make sure it is 16 long + index_t matrix_len = params["matrix"].dtype().number_of_elements(); + if(matrix_len != 16) + { + res = false; + info["errors"].append()="matrix must an array with 16 entries (representing a 4x4 transform matrix)"; + } + } + } + + std::vector valid_paths = { "scale/x", + "scale/y", + "scale/z", + "translate/x", + "translate/y", + "translate/z", + "rotate/angle", + "rotate/axis/x", + "rotate/axis/y", + "rotate/axis/z", + "matrix"}; + + std::string surprises = surprise_check(valid_paths, params); + + if(surprises != "") + { + res = false; + info["errors"].append() = surprises; + } + + return res; +} + +//----------------------------------------------------------------------------- +void +VTKHTransform::execute() +{ + + if(!input(0).check_type()) + { + ASCENT_ERROR("vtkh_transform input must be a data object"); + } + + // grab the data collection and ask for a vtkh collection + // which is one vtkh data set per topology + DataObject *data_object = input(0); + if(!data_object->is_valid()) + { + set_output(data_object); + return; + } + + std::shared_ptr collection = data_object->as_vtkh_collection(); + + bool use_scale = false; + bool use_translate = false; + bool use_rotate = false; + bool use_matrix = false; + + double t_scale[3] = {1.0, 1.0, 1.0}; + double t_translate[3] = {0.0, 0.0, 0.0}; + double t_rotate_angle = 0.0; + double t_rotate_axis[3] = {0.0, 0.0, 0.0}; + double t_matrix[16] = {0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0}; + + if(params().has_child("scale")) + { + use_scale = true; + const Node &p_vals = params()["scale"]; + if(p_vals.has_child("x")) + { + t_scale[0] = get_float64(p_vals["x"],data_object); + } + + if(p_vals.has_child("y")) + { + t_scale[1] = get_float64(p_vals["y"],data_object); + } + + if(p_vals.has_child("z")) + { + t_scale[2] = get_float64(p_vals["z"],data_object); + } + } + + if(params().has_child("translate")) + { + use_translate = true; + const Node &p_vals = params()["translate"]; + if(p_vals.has_child("x")) + { + t_translate[0] = get_float64(p_vals["x"],data_object); + } + + if(p_vals.has_child("y")) + { + t_translate[1] = get_float64(p_vals["y"],data_object); + } + + if(p_vals.has_child("z")) + { + t_translate[2] = get_float64(p_vals["z"],data_object); + } + } + + if(params().has_child("rotate")) + { + use_rotate = true; + const Node &p_vals = params()["rotate"]; + + t_rotate_angle = get_float64(p_vals["angle"],data_object); + + const Node &p_axis = p_vals["axis"]; + if(p_axis.has_child("x")) + { + t_rotate_axis[0] = get_float64(p_axis["x"],data_object); + } + + if(p_axis.has_child("y")) + { + t_rotate_axis[1] = get_float64(p_axis["y"],data_object); + } + + if(p_axis.has_child("z")) + { + t_rotate_axis[2] = get_float64(p_axis["z"],data_object); + } + } + + if(params().has_child("matrix")) + { + use_matrix = true; + // matrix + float64_accessor matrix_vals = params()["matrix"].value(); + for(index_t i=0;i<16;i++) + { + t_matrix[i] = matrix_vals[i]; + } + } + + std::vector topo_names = collection->topology_names(); + int rank = 0; +#ifdef ASCENT_MPI_ENABLED + MPI_Comm mpi_comm = MPI_Comm_f2c(Workspace::default_mpi_comm()); + MPI_Comm_rank(mpi_comm, &rank); +#endif + + VTKHCollection *new_coll = new VTKHCollection(); + for(auto &topo : topo_names) + { + vtkh::DataSet &data = collection->dataset_by_topology(topo); + vtkh::PointTransform transform; + + if(use_scale) + { + transform.SetScale(t_scale[0], + t_scale[1], + t_scale[2]); + } + + if(use_translate) + { + transform.SetTranslation(t_translate[0], + t_translate[1], + t_translate[2]); + } + + if(use_rotate) + { + transform.SetRotation(t_rotate_angle, + t_rotate_axis[0], + t_rotate_axis[1], + t_rotate_axis[2]); + } + + if(use_matrix) + { + + transform.SetTransform(t_matrix); + } + + transform.SetInput(&data); + transform.Update(); + vtkh::DataSet *trans_output = transform.GetOutput(); + new_coll->add(*trans_output, topo); + delete trans_output; + } + + //// re wrap in data object + DataObject *res = new DataObject(new_coll); + set_output(res); +} + //----------------------------------------------------------------------------- VTKHParticleAdvection::VTKHParticleAdvection() diff --git a/src/libs/ascent/runtimes/flow_filters/ascent_runtime_vtkh_filters.hpp b/src/libs/ascent/runtimes/flow_filters/ascent_runtime_vtkh_filters.hpp index 42521eec1..fd600ccfc 100644 --- a/src/libs/ascent/runtimes/flow_filters/ascent_runtime_vtkh_filters.hpp +++ b/src/libs/ascent/runtimes/flow_filters/ascent_runtime_vtkh_filters.hpp @@ -450,6 +450,19 @@ class ASCENT_API VTKHScale : public ::flow::Filter virtual void execute(); }; +//----------------------------------------------------------------------------- +class ASCENT_API VTKHTransform : public ::flow::Filter +{ +public: + VTKHTransform(); + virtual ~VTKHTransform(); + + virtual void declare_interface(conduit::Node &i); + virtual bool verify_params(const conduit::Node ¶ms, + conduit::Node &info); + virtual void execute(); +}; + //----------------------------------------------------------------------------- class ASCENT_API VTKHTriangulate : public ::flow::Filter { diff --git a/src/libs/vtkh/filters/PointTransform.cpp b/src/libs/vtkh/filters/PointTransform.cpp index fa9668a1e..0b5597e2e 100644 --- a/src/libs/vtkh/filters/PointTransform.cpp +++ b/src/libs/vtkh/filters/PointTransform.cpp @@ -5,61 +5,114 @@ namespace vtkh { +//---------------------------------------------------------------------------// PointTransform::PointTransform() { ResetTransform(); } +//---------------------------------------------------------------------------// PointTransform::~PointTransform() { } -void PointTransform::ResetTransform() +//---------------------------------------------------------------------------// +void +PointTransform::ResetTransform() { vtkm::MatrixIdentity(m_transform); } +//---------------------------------------------------------------------------// void PointTransform::SetTranslation(const double& tx, const double& ty, const double& tz) { - vtkm::Matrix matrix = vtkm::Transform3DTranslate(tx, ty, tz); + vtkm::Matrix matrix = vtkm::Transform3DTranslate(tx, ty, tz); m_transform = vtkm::MatrixMultiply(m_transform, matrix); } -void PointTransform::SetRotation(const double& angleDegrees, - const vtkm::Vec& axis) +//---------------------------------------------------------------------------// +void +PointTransform::SetRotation(const double& angleDegrees, + const double& axisX, + const double& axisY, + const double& axisZ) { - vtkm::Matrix matrix = vtkm::Transform3DRotate(angleDegrees, axis); + vtkm::Matrix matrix = vtkm::Transform3DRotate(angleDegrees, + axisX, + axisY, + axisZ); m_transform = vtkm::MatrixMultiply(m_transform, matrix); } -void PointTransform::SetTransform(const vtkm::Matrix& mtx) +//---------------------------------------------------------------------------// +void +PointTransform::SetTransform(const double *matrix_values) +{ + // Note: row vs col vs matrix vs array, what is the best + // order for users to provide flat values to matrix? + // This order is decent, was able to throw values in + // from example matrices in the wild easily. + + m_transform[0][0] = matrix_values[0]; + m_transform[0][1] = matrix_values[1]; + m_transform[0][2] = matrix_values[2]; + m_transform[0][3] = matrix_values[3]; + + m_transform[1][0] = matrix_values[4]; + m_transform[1][1] = matrix_values[5]; + m_transform[1][2] = matrix_values[6]; + m_transform[1][3] = matrix_values[7]; + + m_transform[2][0] = matrix_values[8]; + m_transform[2][1] = matrix_values[9]; + m_transform[2][2] = matrix_values[10]; + m_transform[2][3] = matrix_values[11]; + + m_transform[3][0] = matrix_values[12]; + m_transform[3][1] = matrix_values[13]; + m_transform[3][2] = matrix_values[14]; + m_transform[3][3] = matrix_values[15]; +} + + +//---------------------------------------------------------------------------// +void +PointTransform::SetTransform(const vtkm::Matrix& mtx) { m_transform = mtx; } -void PointTransform::SetScale(const double& sx, - const double& sy, - const double& sz) +//---------------------------------------------------------------------------// +void +PointTransform::SetScale(const double& sx, + const double& sy, + const double& sz) { vtkm::Matrix matrix = vtkm::Transform3DScale(sx, sy, sz); m_transform = vtkm::MatrixMultiply(m_transform, matrix); } -void PointTransform::PreExecute() +//---------------------------------------------------------------------------// +void +PointTransform::PreExecute() { Filter::PreExecute(); } -void PointTransform::PostExecute() +//---------------------------------------------------------------------------// +void +PointTransform::PostExecute() { Filter::PostExecute(); } -void PointTransform::DoExecute() +//---------------------------------------------------------------------------// +void +PointTransform::DoExecute() { this->m_output = new DataSet(); const int num_domains = this->m_input->GetNumberOfDomains(); @@ -78,6 +131,7 @@ void PointTransform::DoExecute() } } +//---------------------------------------------------------------------------// std::string PointTransform::GetName() const { diff --git a/src/libs/vtkh/filters/PointTransform.hpp b/src/libs/vtkh/filters/PointTransform.hpp index e77353611..34c056a10 100644 --- a/src/libs/vtkh/filters/PointTransform.hpp +++ b/src/libs/vtkh/filters/PointTransform.hpp @@ -19,16 +19,23 @@ class VTKH_API PointTransform : public Filter void ResetTransform(); - void SetTranslation(const double& tx, const double& ty, const double& tz); - void SetRotation(const double& angleDegrees, const vtkm::Vec& axis); - void SetScale(const double& sx, const double& sy, const double& sz); + void SetTranslation(const double& tx, + const double& ty, + const double& tz); + void SetRotation(const double& angleDegrees, + const double& axisX, + const double& axisY, + const double& axisZ); + void SetScale(const double& sx, + const double& sy, + const double& sz); + void SetTransform(const double *matrix_values); void SetTransform(const vtkm::Matrix& mtx); protected: void PreExecute() override; void PostExecute() override; void DoExecute() override; - std::string m_field_name; vtkm::Matrix m_transform; }; diff --git a/src/tests/_baseline_images/tout_transform_matrix100.png b/src/tests/_baseline_images/tout_transform_matrix100.png new file mode 100644 index 000000000..4d5f44692 Binary files /dev/null and b/src/tests/_baseline_images/tout_transform_matrix100.png differ diff --git a/src/tests/_baseline_images/tout_transform_rotate_arb100.png b/src/tests/_baseline_images/tout_transform_rotate_arb100.png new file mode 100644 index 000000000..e4bc8fe4d Binary files /dev/null and b/src/tests/_baseline_images/tout_transform_rotate_arb100.png differ diff --git a/src/tests/_baseline_images/tout_transform_rotate_x100.png b/src/tests/_baseline_images/tout_transform_rotate_x100.png new file mode 100644 index 000000000..6759a79dd Binary files /dev/null and b/src/tests/_baseline_images/tout_transform_rotate_x100.png differ diff --git a/src/tests/_baseline_images/tout_transform_rotate_y100.png b/src/tests/_baseline_images/tout_transform_rotate_y100.png new file mode 100644 index 000000000..bf87a8cf7 Binary files /dev/null and b/src/tests/_baseline_images/tout_transform_rotate_y100.png differ diff --git a/src/tests/_baseline_images/tout_transform_rotate_z100.png b/src/tests/_baseline_images/tout_transform_rotate_z100.png new file mode 100644 index 000000000..57b30a289 Binary files /dev/null and b/src/tests/_baseline_images/tout_transform_rotate_z100.png differ diff --git a/src/tests/_baseline_images/tout_transform_scale100.png b/src/tests/_baseline_images/tout_transform_scale100.png new file mode 100644 index 000000000..4d5f44692 Binary files /dev/null and b/src/tests/_baseline_images/tout_transform_scale100.png differ diff --git a/src/tests/_baseline_images/tout_transform_translate100.png b/src/tests/_baseline_images/tout_transform_translate100.png new file mode 100644 index 000000000..e57a2cf66 Binary files /dev/null and b/src/tests/_baseline_images/tout_transform_translate100.png differ diff --git a/src/tests/ascent/CMakeLists.txt b/src/tests/ascent/CMakeLists.txt index d782826a8..7e2664116 100644 --- a/src/tests/ascent/CMakeLists.txt +++ b/src/tests/ascent/CMakeLists.txt @@ -52,6 +52,7 @@ set(BASIC_TESTS t_ascent_smoke t_ascent_image_compare t_ascent_pipelines_to_pipelines t_ascent_threshold + t_ascent_transform t_ascent_slice t_ascent_scale t_ascent_particle_advection diff --git a/src/tests/ascent/t_ascent_transform.cpp b/src/tests/ascent/t_ascent_transform.cpp new file mode 100644 index 000000000..ccd32035b --- /dev/null +++ b/src/tests/ascent/t_ascent_transform.cpp @@ -0,0 +1,566 @@ +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// +// Copyright (c) Lawrence Livermore National Security, LLC and other Ascent +// Project developers. See top-level LICENSE AND COPYRIGHT files for dates and +// other details. No copyright assignment is required to contribute to Ascent. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// + +//----------------------------------------------------------------------------- +/// +/// file: t_ascent_transform.cpp +/// +//----------------------------------------------------------------------------- + + +#include "gtest/gtest.h" + +#include + +#include +#include + +#include + +#include "t_config.hpp" +#include "t_utils.hpp" + + +using namespace std; +using namespace conduit; +using namespace ascent; + + +index_t EXAMPLE_MESH_SIDE_DIM = 20; + +//----------------------------------------------------------------------------- +bool +vtkm_avalible() +{ + // the vtkm runtime is currently our only rendering runtime + Node n; + ascent::about(n); + // only run this test if ascent was built with vtkm support + if(n["runtimes/ascent/vtkm/status"].as_string() == "disabled") + { + ASCENT_INFO("Ascent vtkm support disabled, skipping test"); + return false; + } + return true; +} + +//----------------------------------------------------------------------------- +void +setup(Node &data) +{ + // + // Create an example mesh. + // + Node verify_info; + conduit::blueprint::mesh::examples::braid("hexs", + EXAMPLE_MESH_SIDE_DIM, + EXAMPLE_MESH_SIDE_DIM, + EXAMPLE_MESH_SIDE_DIM, + data); + EXPECT_TRUE(conduit::blueprint::mesh::verify(data,verify_info)); +} + +//----------------------------------------------------------------------------- +void +setup(const std::string &tout_name, Node &data, std::string &output_file) +{ + setup(data); + string output_path = prepare_output_dir(); + output_file = conduit::utils::join_file_path(output_path,tout_name); + + // remove old images before rendering + remove_test_image(output_file); +} + + +//----------------------------------------------------------------------------- +TEST(ascent_translate, test_translate) +{ + if(!vtkm_avalible()) + { + return; + } + + std::string output_file; + conduit::Node data; + setup("tout_transform_translate",data,output_file); + + conduit::Node actions; + conduit::Node &add_pipelines = actions.append(); + add_pipelines["action"] = "add_pipelines"; + conduit::Node &pipelines = add_pipelines["pipelines"]; + pipelines["pl1/f1/type"] = "transform"; + // filter knobs + conduit::Node &p = pipelines["pl1/f1/params"]; + // translate by x and y + p["translate/x"] = 23.0; + p["translate/y"] = 15.0; + conduit::Node &add_scenes = actions.append(); + add_scenes["action"] = "add_scenes"; + conduit::Node &scenes = add_scenes["scenes"]; + + // render transformed + scenes["s1/plots/p1/type"] = "pseudocolor"; + scenes["s1/plots/p1/field"] = "braid"; + scenes["s1/plots/p1/pipeline"] = "pl1"; + // render orig (w/ different field so we can tell them apart easily) + scenes["s1/plots/p2/type"] = "pseudocolor"; + scenes["s1/plots/p2/field"] = "radial"; + + scenes["s1/image_prefix"] = output_file; + + std::cout << actions.to_yaml() << std::endl; + + Ascent ascent; + ascent.open(); + ascent.publish(data); + ascent.execute(actions); + ascent.close(); + + // check that we created an image + EXPECT_TRUE(check_test_image(output_file)); + std::string msg = "An example transform filter using translation."; + ASCENT_ACTIONS_DUMP(actions,output_file,msg); +} + +//----------------------------------------------------------------------------- +TEST(ascent_translate, test_scale) +{ + if(!vtkm_avalible()) + { + return; + } + + std::string output_file; + conduit::Node data; + setup("tout_transform_scale",data,output_file); + + conduit::Node actions; + conduit::Node &add_pipelines = actions.append(); + add_pipelines["action"] = "add_pipelines"; + conduit::Node &pipelines = add_pipelines["pipelines"]; + // filter knobs + // scale + pipelines["pl1/f1/type"] = "transform"; + pipelines["pl1/f1/params/scale/x"]= 2.0; + pipelines["pl1/f1/params/scale/y"]= 0.5; + pipelines["pl1/f1/params/scale/z"]= 2.0; + // then translate x + pipelines["pl1/f2/type"] = "transform"; + pipelines["pl1/f2/params/translate/y"]= 50.0; + + conduit::Node &add_scenes = actions.append(); + add_scenes["action"] = "add_scenes"; + conduit::Node &scenes = add_scenes["scenes"]; + + // render transformed + scenes["s1/plots/p1/type"] = "pseudocolor"; + scenes["s1/plots/p1/field"] = "braid"; + scenes["s1/plots/p1/pipeline"] = "pl1"; + // render orig (w/ different field so we can tell them apart easily) + scenes["s1/plots/p2/type"] = "pseudocolor"; + scenes["s1/plots/p2/field"] = "radial"; + + scenes["s1/image_prefix"] = output_file; + + std::cout << actions.to_yaml() << std::endl; + + Ascent ascent; + ascent.open(); + ascent.publish(data); + ascent.execute(actions); + ascent.close(); + + // check that we created an image + EXPECT_TRUE(check_test_image(output_file)); + std::string msg = "An example transform filter using scale."; + ASCENT_ACTIONS_DUMP(actions,output_file,msg); +} + +//----------------------------------------------------------------------------- +TEST(ascent_translate, test_rotate_x) +{ + if(!vtkm_avalible()) + { + return; + } + + std::string output_file; + conduit::Node data; + setup("tout_transform_rotate_x",data,output_file); + + conduit::Node actions; + conduit::Node &add_pipelines = actions.append(); + add_pipelines["action"] = "add_pipelines"; + conduit::Node &pipelines = add_pipelines["pipelines"]; + // filter knobs + // scale + pipelines["pl1/f1/type"] = "transform"; + pipelines["pl1/f1/params/rotate/angle"]= 45.0; + pipelines["pl1/f1/params/rotate/axis/x"]= 1.0; + // then translate x + pipelines["pl1/f2/type"] = "transform"; + pipelines["pl1/f2/params/translate/y"]= 50.0; + + conduit::Node &add_scenes = actions.append(); + add_scenes["action"] = "add_scenes"; + conduit::Node &scenes = add_scenes["scenes"]; + + // render transformed + scenes["s1/plots/p1/type"] = "pseudocolor"; + scenes["s1/plots/p1/field"] = "braid"; + scenes["s1/plots/p1/pipeline"] = "pl1"; + // render orig (w/ different field so we can tell them apart easily) + scenes["s1/plots/p2/type"] = "pseudocolor"; + scenes["s1/plots/p2/field"] = "radial"; + + scenes["s1/image_prefix"] = output_file; + + std::cout << actions.to_yaml() << std::endl; + + Ascent ascent; + ascent.open(); + ascent.publish(data); + ascent.execute(actions); + ascent.close(); + + // check that we created an image + EXPECT_TRUE(check_test_image(output_file)); + std::string msg = "An example transform filter rotating around the x-axis."; + ASCENT_ACTIONS_DUMP(actions,output_file,msg); +} + + +//----------------------------------------------------------------------------- +TEST(ascent_translate, test_rotate_y) +{ + if(!vtkm_avalible()) + { + return; + } + + std::string output_file; + conduit::Node data; + setup("tout_transform_rotate_y",data,output_file); + + conduit::Node actions; + conduit::Node &add_pipelines = actions.append(); + add_pipelines["action"] = "add_pipelines"; + conduit::Node &pipelines = add_pipelines["pipelines"]; + // filter knobs + // scale + pipelines["pl1/f1/type"] = "transform"; + pipelines["pl1/f1/params/rotate/angle"]= 45.0; + pipelines["pl1/f1/params/rotate/axis/y"]= 1.0; + // then translate x + pipelines["pl1/f2/type"] = "transform"; + pipelines["pl1/f2/params/translate/y"]= 50.0; + + conduit::Node &add_scenes = actions.append(); + add_scenes["action"] = "add_scenes"; + conduit::Node &scenes = add_scenes["scenes"]; + + // render transformed + scenes["s1/plots/p1/type"] = "pseudocolor"; + scenes["s1/plots/p1/field"] = "braid"; + scenes["s1/plots/p1/pipeline"] = "pl1"; + // render orig (w/ different field so we can tell them apart easily) + scenes["s1/plots/p2/type"] = "pseudocolor"; + scenes["s1/plots/p2/field"] = "radial"; + + scenes["s1/image_prefix"] = output_file; + + std::cout << actions.to_yaml() << std::endl; + + Ascent ascent; + ascent.open(); + ascent.publish(data); + ascent.execute(actions); + ascent.close(); + + // check that we created an image + EXPECT_TRUE(check_test_image(output_file)); + std::string msg = "An example transform filter rotating around the y-axis."; + ASCENT_ACTIONS_DUMP(actions,output_file,msg); +} + +//----------------------------------------------------------------------------- +TEST(ascent_translate, test_rotate_z) +{ + if(!vtkm_avalible()) + { + return; + } + + std::string output_file; + conduit::Node data; + setup("tout_transform_rotate_z",data,output_file); + + conduit::Node actions; + conduit::Node &add_pipelines = actions.append(); + add_pipelines["action"] = "add_pipelines"; + conduit::Node &pipelines = add_pipelines["pipelines"]; + // filter knobs + // scale + pipelines["pl1/f1/type"] = "transform"; + pipelines["pl1/f1/params/rotate/angle"]= 45.0; + pipelines["pl1/f1/params/rotate/axis/z"]= 1.0; + // then translate x + pipelines["pl1/f2/type"] = "transform"; + pipelines["pl1/f2/params/translate/y"]= 50.0; + + conduit::Node &add_scenes = actions.append(); + add_scenes["action"] = "add_scenes"; + conduit::Node &scenes = add_scenes["scenes"]; + + // render transformed + scenes["s1/plots/p1/type"] = "pseudocolor"; + scenes["s1/plots/p1/field"] = "braid"; + scenes["s1/plots/p1/pipeline"] = "pl1"; + // render orig (w/ different field so we can tell them apart easily) + scenes["s1/plots/p2/type"] = "pseudocolor"; + scenes["s1/plots/p2/field"] = "radial"; + + scenes["s1/image_prefix"] = output_file; + + std::cout << actions.to_yaml() << std::endl; + + Ascent ascent; + ascent.open(); + ascent.publish(data); + ascent.execute(actions); + ascent.close(); + + // check that we created an image + EXPECT_TRUE(check_test_image(output_file)); + std::string msg = "An example transform filter rotating around the z-axis."; + ASCENT_ACTIONS_DUMP(actions,output_file,msg); +} + +//----------------------------------------------------------------------------- +TEST(ascent_translate, test_rotate_arb) +{ + if(!vtkm_avalible()) + { + return; + } + + std::string output_file; + conduit::Node data; + setup("tout_transform_rotate_arb",data,output_file); + + conduit::Node actions; + conduit::Node &add_pipelines = actions.append(); + add_pipelines["action"] = "add_pipelines"; + conduit::Node &pipelines = add_pipelines["pipelines"]; + // filter knobs + // scale + pipelines["pl1/f1/type"] = "transform"; + pipelines["pl1/f1/params/rotate/angle"]= 45.0; + pipelines["pl1/f1/params/rotate/axis/x"]= .5; + pipelines["pl1/f1/params/rotate/axis/y"]= 1.0; + // then translate x + pipelines["pl1/f2/type"] = "transform"; + pipelines["pl1/f2/params/translate/y"]= 50.0; + + conduit::Node &add_scenes = actions.append(); + add_scenes["action"] = "add_scenes"; + conduit::Node &scenes = add_scenes["scenes"]; + + // render transformed + scenes["s1/plots/p1/type"] = "pseudocolor"; + scenes["s1/plots/p1/field"] = "braid"; + scenes["s1/plots/p1/pipeline"] = "pl1"; + // render orig (w/ different field so we can tell them apart easily) + scenes["s1/plots/p2/type"] = "pseudocolor"; + scenes["s1/plots/p2/field"] = "radial"; + + scenes["s1/image_prefix"] = output_file; + + std::cout << actions.to_yaml() << std::endl; + + Ascent ascent; + ascent.open(); + ascent.publish(data); + ascent.execute(actions); + ascent.close(); + + // check that we created an image + EXPECT_TRUE(check_test_image(output_file)); + std::string msg = "An example transform filter rotating around an arbitrary axis."; + ASCENT_ACTIONS_DUMP(actions,output_file,msg); +} + + +//----------------------------------------------------------------------------- +TEST(ascent_translate, test_matrix) +{ + if(!vtkm_avalible()) + { + return; + } + + std::string output_file; + conduit::Node data; + setup("tout_transform_matrix",data,output_file); + + conduit::Node actions; + conduit::Node &add_pipelines = actions.append(); + add_pipelines["action"] = "add_pipelines"; + conduit::Node &pipelines = add_pipelines["pipelines"]; + // filter knobs + // scale + pipelines["pl1/f1/type"] = "transform"; + // this matrix is equiv to + // scale/x = 2.0 + // scale/y = 0.5 + // scale/z = 2.0 + // and + // translate/y = 50.0 + pipelines["pl1/f1/params/matrix"] = { 2.0, 0.0, 0.0, 0.0, + 0.0, 0.5, 0.0, 50.0, + 0.0, 0.0, 2.0, 0.0, + 0.0, 0.0, 0.0, 1.0} ; + + conduit::Node &add_scenes = actions.append(); + add_scenes["action"] = "add_scenes"; + conduit::Node &scenes = add_scenes["scenes"]; + + // render translated + scenes["s1/plots/p1/type"] = "pseudocolor"; + scenes["s1/plots/p1/field"] = "braid"; + scenes["s1/plots/p1/pipeline"] = "pl1"; + // render orig (w/ different field so we can tell them apart easily) + scenes["s1/plots/p2/type"] = "pseudocolor"; + scenes["s1/plots/p2/field"] = "radial"; + + scenes["s1/image_prefix"] = output_file; + + std::cout << actions.to_yaml() << std::endl; + + Ascent ascent; + ascent.open(); + ascent.publish(data); + ascent.execute(actions); + ascent.close(); + + // check that we created an image + EXPECT_TRUE(check_test_image(output_file)); + std::string msg = "An example transform filter rotating around an arbitrary axis."; + ASCENT_ACTIONS_DUMP(actions,output_file,msg); +} + +//----------------------------------------------------------------------------- +TEST(ascent_translate, test_bad_params) +{ + if(!vtkm_avalible()) + { + return; + } + + conduit::Node data; + setup(data); + + conduit::Node actions; + conduit::Node &add_pipelines = actions.append(); + add_pipelines["action"] = "add_pipelines"; + conduit::Node &pipelines = add_pipelines["pipelines"]; + + { + Ascent ascent; + conduit::Node ascent_opts; + ascent_opts["exceptions"] = "forward"; + ascent.open(ascent_opts); + ascent.publish(data); + pipelines["pl1/f1/type"] = "transform"; + + // too many + pipelines["pl1/f1/params"].reset(); + pipelines["pl1/f1/params/rotate/x"]= 45.0; + pipelines["pl1/f1/params/translate/x"]= 45.0; + pipelines["pl1/f1/params/scale/x"]= 45.0; + EXPECT_THROW(ascent.execute(actions),conduit::Error); + + // translate missing x,y,z + pipelines["pl1/f1/params"].reset(); + pipelines["pl1/f1/params/translate/zz"]= 45.0; + EXPECT_THROW(ascent.execute(actions),conduit::Error); + + // scale missing x,y,z + pipelines["pl1/f1/params"].reset(); + pipelines["pl1/f1/params/scale/xx"]= 45.0; + EXPECT_THROW(ascent.execute(actions),conduit::Error); + + // rot missing axis + pipelines["pl1/f1/params"].reset(); + pipelines["pl1/f1/params/rotate/angle"]= 45.0; + EXPECT_THROW(ascent.execute(actions),conduit::Error); + + // matrix bad size + pipelines["pl1/f1/params"].reset(); + pipelines["pl1/f1/params/matrix"]= { 45.0 , 0.0} ; + EXPECT_THROW(ascent.execute(actions),conduit::Error); + + ascent.close(); + } + // now lets see the errors after checking they are thrown + { + Ascent ascent; + // no forward + ascent.open(); + ascent.publish(data); + pipelines.reset(); + pipelines["pl1/f1/type"] = "transform"; + + // too many + pipelines["pl1/f1/params"].reset(); + pipelines["pl1/f1/params/rotate/x"]= 45.0; + pipelines["pl1/f1/params/translate/x"]= 45.0; + pipelines["pl1/f1/params/scale/x"]= 45.0; + ascent.execute(actions); + + // translate missing x,y,z + pipelines["pl1/f1/params"].reset(); + pipelines["pl1/f1/params/translate/zz"]= 45.0; + ascent.execute(actions); + + // scale missing x,y,z + pipelines["pl1/f1/params"].reset(); + pipelines["pl1/f1/params/scale/xx"]= 45.0; + ascent.execute(actions); + + // rot missing axis + pipelines["pl1/f1/params"].reset(); + pipelines["pl1/f1/params/rotate/angle"]= 45.0; + ascent.execute(actions); + + // matrix bad size + pipelines["pl1/f1/params"].reset(); + pipelines["pl1/f1/params/matrix"]= { 45.0 , 0.0} ; + ascent.execute(actions); + + } +} + + +//----------------------------------------------------------------------------- +int main(int argc, char* argv[]) +{ + int result = 0; + + ::testing::InitGoogleTest(&argc, argv); + + // allow override of the data size via the command line + if(argc == 2) + { + EXAMPLE_MESH_SIDE_DIM = atoi(argv[1]); + } + + result = RUN_ALL_TESTS(); + return result; +} + +